This commit is contained in:
ditiqi 2025-02-25 20:40:34 +08:00
parent 5686a337f3
commit e769005b87
12 changed files with 94 additions and 103 deletions

View File

@ -61,7 +61,7 @@ export class TermRouter {
.input(TermMethodSchema.getSimpleTree) .input(TermMethodSchema.getSimpleTree)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
const { staff } = ctx; const { staff } = ctx;
return await this.termService.getChildSimpleTree(staff, input); return await this.termService.getChildSimpleTree(input, staff);
}), }),
getParentSimpleTree: this.trpc.procedure getParentSimpleTree: this.trpc.procedure
.input(TermMethodSchema.getSimpleTree) .input(TermMethodSchema.getSimpleTree)

View File

@ -269,10 +269,10 @@ export class TermService extends BaseTreeService<Prisma.TermDelegate> {
} }
async getChildSimpleTree( async getChildSimpleTree(
staff: UserProfile,
data: z.infer<typeof TermMethodSchema.getSimpleTree>, data: z.infer<typeof TermMethodSchema.getSimpleTree>,
staff?: UserProfile,
) { ) {
const { domainId = null, permissions } = staff; const domainId = staff?.domainId || null;
const hasAnyPerms = const hasAnyPerms =
staff?.permissions?.includes(RolePerms.MANAGE_ANY_TERM) || staff?.permissions?.includes(RolePerms.MANAGE_ANY_TERM) ||
staff?.permissions?.includes(RolePerms.READ_ANY_TERM); staff?.permissions?.includes(RolePerms.READ_ANY_TERM);

View File

@ -0,0 +1,52 @@
import CourseList from "@web/src/components/models/course/list/CourseList";
import { useMainContext } from "../../layout/MainProvider";
import { PostType, Prisma } from "@nice/common";
import { useMemo } from "react";
export function CoursesContainer() {
const { searchValue, selectedTerms } = useMainContext();
const termFilters = useMemo(() => {
return Object.entries(selectedTerms)
.filter(([, terms]) => terms.length > 0)
.map(([, terms]) => terms);
}, [selectedTerms]);
const searchCondition: Prisma.StringNullableFilter = {
contains: searchValue,
mode: "insensitive" as Prisma.QueryMode, // 使用类型断言
};
return (
<>
<CourseList
params={{
pageSize: 12,
where: {
type: PostType.COURSE,
AND: termFilters.map((termFilter) => ({
terms: {
some: {
id: {
in: termFilter, // 确保至少有一个 term.id 在当前 termFilter 中
},
},
},
})),
OR: [
{ title: searchCondition },
{ subTitle: searchCondition },
{ content: searchCondition },
{
terms: {
some: {
name: searchCondition,
},
},
},
],
},
}}
cols={4}></CourseList>
</>
);
}
export default CoursesContainer;

View File

@ -2,14 +2,21 @@ import { Checkbox, Divider, Radio, Space, Spin } from "antd";
import { TaxonomySlug, TermDto } from "@nice/common"; import { TaxonomySlug, TermDto } from "@nice/common";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo, useState } from "react";
import { api } from "@nice/client"; import { api } from "@nice/client";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import TermSelect from "@web/src/components/models/term/term-select"; import TermSelect from "@web/src/components/models/term/term-select";
import { useMainContext } from "../../layout/MainProvider";
export default function FilterSection() { export default function FilterSection() {
const { data: taxonomies } = api.taxonomy.getAll.useQuery({}); const { data: taxonomies } = api.taxonomy.getAll.useQuery({});
const { setSelectedTerms } = useMainContext();
const handleTermChange = (slug, selected) => {
setSelectedTerms((prev) => ({
...prev,
[slug]: selected, // 更新对应 slug 的选择
}));
};
return ( return (
<div className="bg-white p-6 rounded-lg shadow-sm space-y-6 h-full"> <div className="bg-white p-6 rounded-lg shadow-sm space-y-6 h-full">
{taxonomies?.map((tax, index) => { {taxonomies?.map((tax, index) => {
@ -18,7 +25,13 @@ export default function FilterSection() {
<h3 className="text-lg font-medium mb-4"> <h3 className="text-lg font-medium mb-4">
{tax?.name} {tax?.name}
</h3> </h3>
<TermSelect multiple taxonomyId={tax?.id}></TermSelect> <TermSelect
className="w-72"
multiple
taxonomyId={tax?.id}
onChange={(selected) =>
handleTermChange(tax?.slug, selected)
}></TermSelect>
{index < taxonomies.length - 1 && ( {index < taxonomies.length - 1 && (
<Divider className="my-6" /> <Divider className="my-6" />
)} )}

View File

@ -1,9 +1,9 @@
import { useMainContext } from "../../layout/MainProvider"; import { useMainContext } from "../../layout/MainProvider";
import CourseList from "../components/CourseList"; import CourseList from "../../../../components/models/course/list/CourseList";
import FilterSection from "../components/FilterSection"; import FilterSection from "../components/FilterSection";
import CoursesContainer from "../components/CoursesContainer";
export function AllCoursesLayout() { export function AllCoursesLayout() {
const { searchValue, setSearchValue } = useMainContext();
return ( return (
<> <>
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
@ -12,12 +12,7 @@ export function AllCoursesLayout() {
<FilterSection></FilterSection> <FilterSection></FilterSection>
</div> </div>
<div className="w-5/6 p-4"> <div className="w-5/6 p-4">
<CourseList <CoursesContainer></CoursesContainer>
cols={4}
params={{
page: 1,
pageSize: 12,
}}></CourseList>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { useState, useMemo, useEffect } from "react"; import { useState, useMemo, useEffect } from "react";
import FilterSection from "./components/FilterSection"; import FilterSection from "./components/FilterSection";
import CourseList from "./components/CourseList"; import CourseList from "../../../components/models/course/list/CourseList";
import { api } from "@nice/client"; import { api } from "@nice/client";
import { import {
courseDetailSelect, courseDetailSelect,

View File

@ -50,13 +50,7 @@ const CategorySection = () => {
} }
} }
}, [courseCategoriesData, showAll]); }, [courseCategoriesData, showAll]);
// const courseCategories: CourseCategory[] = useMemo(() => {
// return data?.map((term) => ({
// name: term.name,
// count: term.hasChildren ? term.children.length : 0,
// description: term.description
// })) || [];
// },[data])
const handleMouseEnter = useCallback((index: number) => { const handleMouseEnter = useCallback((index: number) => {
setHoveredIndex(index); setHoveredIndex(index);
}, []); }, []);
@ -102,6 +96,7 @@ const CategorySection = () => {
navigate( navigate(
`/courses?category=${category.name}` `/courses?category=${category.name}`
); );
window.scrollTo({ window.scrollTo({
top: 0, top: 0,
behavior: "smooth", behavior: "smooth",
@ -135,17 +130,7 @@ const CategorySection = () => {
className="text-xl font-medium tracking-wide"> className="text-xl font-medium tracking-wide">
{category.name} {category.name}
</Text> </Text>
{/* <span
className={`px-3 py-1 rounded-full text-sm w-fit font-medium transition-all duration-500 ease-out ${
isHovered ? 'shadow-md scale-105' : ''
}`}
style={{
backgroundColor: `${categoryColor}15`,
color: categoryColor
}}
>
{category.children.length}
</span> */}
</div> </div>
<Text <Text
type="secondary" type="secondary"

View File

@ -5,7 +5,7 @@ import { ArrowRightOutlined } from "@ant-design/icons";
import { TaxonomySlug, TermDto } from "@nice/common"; import { TaxonomySlug, TermDto } from "@nice/common";
import { api } from "@nice/client"; import { api } from "@nice/client";
import { CoursesSectionTag } from "./CoursesSectionTag"; import { CoursesSectionTag } from "./CoursesSectionTag";
import CourseList from "../../courses/components/CourseList"; import CourseList from "../../../../components/models/course/list/CourseList";
interface GetTaxonomyProps { interface GetTaxonomyProps {
categories: string[]; categories: string[];
isLoading: boolean; isLoading: boolean;

View File

@ -1,8 +1,12 @@
import React, { createContext, ReactNode, useContext, useState } from "react"; import React, { createContext, ReactNode, useContext, useState } from "react";
interface SelectedTerms {
[key: string]: string[]; // 每个 slug 对应一个 string 数组
}
interface MainContextType { interface MainContextType {
searchValue?: string; searchValue?: string;
selectedTerms?: SelectedTerms;
setSearchValue?: React.Dispatch<React.SetStateAction<string>>; setSearchValue?: React.Dispatch<React.SetStateAction<string>>;
setSelectedTerms?: React.Dispatch<React.SetStateAction<SelectedTerms>>;
} }
const MainContext = createContext<MainContextType | null>(null); const MainContext = createContext<MainContextType | null>(null);
@ -12,11 +16,14 @@ interface MainProviderProps {
export function MainProvider({ children }: MainProviderProps) { export function MainProvider({ children }: MainProviderProps) {
const [searchValue, setSearchValue] = useState(""); const [searchValue, setSearchValue] = useState("");
const [selectedTerms, setSelectedTerms] = useState<SelectedTerms>({}); // 初始化状态
return ( return (
<MainContext.Provider <MainContext.Provider
value={{ value={{
searchValue, searchValue,
setSearchValue, setSearchValue,
selectedTerms,
setSelectedTerms,
}}> }}>
{children} {children}
</MainContext.Provider> </MainContext.Provider>

View File

@ -1,5 +1,5 @@
import { Pagination, Empty, Skeleton } from "antd"; import { Pagination, Empty, Skeleton } from "antd";
import CourseCard from "./CourseCard"; import CourseCard from "../../../../app/main/courses/components/CourseCard";
import { courseDetailSelect, CourseDto, Prisma } from "@nice/common"; import { courseDetailSelect, CourseDto, Prisma } from "@nice/common";
import { api } from "@nice/client"; import { api } from "@nice/client";
import { DefaultArgs } from "@prisma/client/runtime/library"; import { DefaultArgs } from "@prisma/client/runtime/library";
@ -49,7 +49,7 @@ export default function CourseList({
useEffect(() => { useEffect(() => {
setCurrentPage(params?.page || 1); setCurrentPage(params?.page || 1);
}, [params?.page]); }, [params?.page, params]);
function onPageChange(page: number, pageSize: number) { function onPageChange(page: number, pageSize: number) {
setCurrentPage(page); setCurrentPage(page);
window.scrollTo({ top: 0, behavior: "smooth" }); window.scrollTo({ top: 0, behavior: "smooth" });
@ -71,7 +71,7 @@ export default function CourseList({
<div className="flex justify-center mt-8"> <div className="flex justify-center mt-8">
<Pagination <Pagination
current={currentPage} current={currentPage}
total={totalPages} total={totalPages * params.pageSize}
pageSize={params?.pageSize} pageSize={params?.pageSize}
onChange={onPageChange} onChange={onPageChange}
showSizeChanger={false} showSizeChanger={false}

View File

@ -1,61 +0,0 @@
// CourseList.tsx
import { motion } from "framer-motion";
import { Course, CourseDto } from "@nice/common";
import { EmptyState } from "@web/src/components/common/space/Empty";
import { Pagination } from "@web/src/components/common/element/Pagination";
import React from "react";
interface CourseListProps {
courses?: CourseDto[];
renderItem: (course: CourseDto) => React.ReactNode;
emptyComponent?: React.ReactNode;
// 新增分页相关属性
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
}
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.05,
duration: 0.3,
},
},
};
export const CourseList = ({
courses,
renderItem,
emptyComponent: EmptyComponent,
currentPage,
totalPages,
onPageChange,
}: CourseListProps) => {
if (!courses || courses.length === 0) {
return EmptyComponent || <EmptyState />;
}
return (
<div>
<motion.div
variants={container}
initial="hidden"
animate="show"
className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{courses.map((course) => (
<motion.div key={course.id}>
{renderItem(course)}
</motion.div>
))}
</motion.div>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={onPageChange}
/>
</div>
);
};

View File

@ -69,10 +69,10 @@ export const InitTaxonomies: {
// name: "研判单元", // name: "研判单元",
// slug: TaxonomySlug.UNIT, // slug: TaxonomySlug.UNIT,
// }, // },
{ // {
name: "标签", // name: "标签",
slug: TaxonomySlug.TAG, // slug: TaxonomySlug.TAG,
}, // },
]; ];
export const InitAppConfigs: Prisma.AppConfigCreateInput[] = [ export const InitAppConfigs: Prisma.AppConfigCreateInput[] = [
{ {