This commit is contained in:
ditiqi 2025-02-26 21:08:38 +08:00
parent 358d2ea9d5
commit 4a6957f181
11 changed files with 147 additions and 64 deletions

View File

@ -306,37 +306,37 @@ export class PostService extends BaseTreeService<Prisma.PostDelegate> {
staff?.id && { staff?.id && {
authorId: staff.id, authorId: staff.id,
}, },
staff?.id && { // staff?.id && {
watchableStaffs: { // watchableStaffs: {
some: { // some: {
id: staff.id, // id: staff.id,
}, // },
}, // },
}, // },
deptId && { // deptId && {
watchableDepts: { // watchableDepts: {
some: { // some: {
id: { // id: {
in: parentDeptIds, // in: parentDeptIds,
}, // },
}, // },
}, // },
}, // },
{ // {
AND: [ // AND: [
{ // {
watchableStaffs: { // watchableStaffs: {
none: {}, // 匹配 watchableStaffs 为空 // none: {}, // 匹配 watchableStaffs 为空
}, // },
}, // },
{ // {
watchableDepts: { // watchableDepts: {
none: {}, // 匹配 watchableDepts 为空 // none: {}, // 匹配 watchableDepts 为空
}, // },
}, // },
], // ],
}, // },
].filter(Boolean); ].filter(Boolean);
if (orCondition?.length > 0) return orCondition; if (orCondition?.length > 0) return orCondition;

View File

@ -23,7 +23,7 @@ export async function updateTotalCourseViewCount(type: VisitType) {
views: true, views: true,
}, },
where: { where: {
postId: { in: lectures.map((lecture) => lecture.id) }, postId: { in: posts.map((post) => post.id) },
type: type, type: type,
}, },
}); });

View File

@ -9,13 +9,18 @@ import { useNavigate } from "react-router-dom";
interface CourseCardProps { interface CourseCardProps {
course: CourseDto; course: CourseDto;
edit?: boolean;
} }
const { Title, Text } = Typography; const { Title, Text } = Typography;
export default function CourseCard({ course }: CourseCardProps) { export default function CourseCard({ course, edit = false }: CourseCardProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const handleClick = (course: CourseDto) => { const handleClick = (course: CourseDto) => {
if (!edit) {
navigate(`/course/${course.id}/detail`); navigate(`/course/${course.id}/detail`);
window.scrollTo({ top: 0, behavior: "smooth", }) } else {
navigate(`/course/${course.id}/editor`);
}
window.scrollTo({ top: 0, behavior: "smooth" });
}; };
return ( return (
<Card <Card
@ -80,9 +85,8 @@ export default function CourseCard({ course }: CourseCardProps) {
</Text> </Text>
</div> </div>
<span className="text-xs font-medium text-gray-500 flex items-center"> <span className="text-xs font-medium text-gray-500 flex items-center">
<EyeOutlined className="mr-1" />{course?.meta?.views <EyeOutlined className="mr-1" />
? `观看次数 ${course?.meta?.views}` {`观看次数 ${course?.meta?.views || 0}`}
: null}
</span> </span>
</div> </div>
<div className="pt-4 border-t border-gray-100 text-center"> <div className="pt-4 border-t border-gray-100 text-center">
@ -91,7 +95,7 @@ export default function CourseCard({ course }: CourseCardProps) {
size="large" size="large"
className="w-full shadow-[0_8px_20px_-6px_rgba(59,130,246,0.5)] hover:shadow-[0_12px_24px_-6px_rgba(59,130,246,0.6)] className="w-full shadow-[0_8px_20px_-6px_rgba(59,130,246,0.5)] hover:shadow-[0_12px_24px_-6px_rgba(59,130,246,0.6)]
transform hover:translate-y-[-2px] transition-all duration-500 ease-out"> transform hover:translate-y-[-2px] transition-all duration-500 ease-out">
{edit ? "进行编辑" : "立即学习"}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,15 +1,30 @@
import { useAuth } from "@web/src/providers/auth-provider";
import { Menu } from "antd"; import { Menu } from "antd";
import { useMemo } from "react";
import { useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
const menuItems = [
{ key: "home", path: "/", label: "首页" },
{ key: "courses", path: "/courses", label: "全部课程" },
{ key: "paths", path: "/paths", label: "学习路径" },
];
export const NavigationMenu = () => { export const NavigationMenu = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { isAuthenticated } = useAuth();
const { pathname } = useLocation(); const { pathname } = useLocation();
const menuItems = useMemo(() => {
const baseItems = [
{ key: "home", path: "/", label: "首页" },
{ key: "courses", path: "/courses", label: "全部课程" },
{ key: "paths", path: "/paths", label: "学习路径" },
];
if (!isAuthenticated) {
return baseItems;
} else {
return [
...baseItems,
{ key: "my-duty", path: "/my-duty", label: "我创建的" },
{ key: "my-learning", path: "/my-learning", label: "我学习的" },
];
}
}, [isAuthenticated]);
const selectedKey = const selectedKey =
menuItems.find((item) => item.path === pathname)?.key || ""; menuItems.find((item) => item.path === pathname)?.key || "";
return ( return (

View File

@ -90,14 +90,14 @@ export function UserMenu() {
icon: <UserOutlined className="text-lg" />, icon: <UserOutlined className="text-lg" />,
label: "我创建的课程", label: "我创建的课程",
action: () => { action: () => {
setModalOpen(true); navigate("/my/duty");
}, },
}, },
{ {
icon: <UserOutlined className="text-lg" />, icon: <UserOutlined className="text-lg" />,
label: "我学习的课程", label: "我学习的课程",
action: () => { action: () => {
setModalOpen(true); navigate("/my/learning");
}, },
}, },
canManageAnyStaff && { canManageAnyStaff && {

View File

@ -1,7 +1,21 @@
import CourseList from "@web/src/components/models/course/list/CourseList";
import { useAuth } from "@web/src/providers/auth-provider";
export default function MyDutyPage() { export default function MyDutyPage() {
const { user } = useAuth();
return (
<>
return <> <div className="p-4">
<CourseList
edit
params={{
pageSize: 12,
where: {
authorId: user.id,
},
}}
cols={4}></CourseList>
</div>
</> </>
);
} }

View File

@ -1,3 +1,21 @@
import CourseList from "@web/src/components/models/course/list/CourseList";
import { useAuth } from "@web/src/providers/auth-provider";
export default function MyLearningPage() { export default function MyLearningPage() {
return <></>; const { user } = useAuth();
return (
<>
<div className="p-4">
<CourseList
edit
params={{
pageSize: 12,
where: {
authorId: user.id,
},
}}
cols={4}></CourseList>
</div>
</>
);
} }

View File

@ -13,6 +13,7 @@ interface CourseListProps {
}; };
cols?: number; cols?: number;
showPagination?: boolean; showPagination?: boolean;
edit?: boolean;
} }
interface CoursesPagnationProps { interface CoursesPagnationProps {
data: { data: {
@ -25,6 +26,7 @@ export default function CourseList({
params, params,
cols = 3, cols = 3,
showPagination = true, showPagination = true,
edit = false,
}: CourseListProps) { }: CourseListProps) {
const [currentPage, setCurrentPage] = useState<number>(params?.page || 1); const [currentPage, setCurrentPage] = useState<number>(params?.page || 1);
const { data, isLoading }: CoursesPagnationProps = const { data, isLoading }: CoursesPagnationProps =
@ -55,7 +57,11 @@ export default function CourseList({
window.scrollTo({ top: 0, behavior: "smooth" }); window.scrollTo({ top: 0, behavior: "smooth" });
} }
if (isLoading) { if (isLoading) {
return <Skeleton paragraph={{ rows: 10 }}></Skeleton>; return (
<div className="space-y-6">
<Skeleton paragraph={{ rows: 10 }}></Skeleton>
</div>
);
} }
return ( return (
<div className="space-y-6"> <div className="space-y-6">
@ -66,7 +72,11 @@ export default function CourseList({
<Skeleton paragraph={{ rows: 5 }}></Skeleton> <Skeleton paragraph={{ rows: 5 }}></Skeleton>
) : ( ) : (
courses.map((course) => ( courses.map((course) => (
<CourseCard key={course.id} course={course} /> <CourseCard
edit={edit}
key={course.id}
course={course}
/>
)) ))
)} )}
</div> </div>

View File

@ -18,6 +18,8 @@ import CoursesPage from "../app/main/courses/page";
import PathsPage from "../app/main/paths/page"; import PathsPage from "../app/main/paths/page";
import { adminRoute } from "./admin-route"; import { adminRoute } from "./admin-route";
import { CoursePreview } from "../app/main/course/preview/page"; import { CoursePreview } from "../app/main/course/preview/page";
import MyLearningPage from "../app/main/my-learning/page";
import MyDutyPage from "../app/main/my-duty/page";
interface CustomIndexRouteObject extends IndexRouteObject { interface CustomIndexRouteObject extends IndexRouteObject {
name?: string; name?: string;
breadcrumb?: string; breadcrumb?: string;
@ -63,15 +65,32 @@ export const routes: CustomRouteObject[] = [
path: "courses", path: "courses",
element: <CoursesPage></CoursesPage>, element: <CoursesPage></CoursesPage>,
}, },
{
path: "my-duty",
element: (
<WithAuth>
<MyDutyPage></MyDutyPage>
</WithAuth>
),
},
{
path: "my-learning",
element: (
<WithAuth>
<MyLearningPage></MyLearningPage>
</WithAuth>
),
},
], ],
}, },
{ {
path: "course", path: "course",
children: [ children: [
{ {
path: ":id?/editor", path: ":id?/editor",
element: ( element: (
<WithAuth > <WithAuth>
<CourseEditorLayout></CourseEditorLayout> <CourseEditorLayout></CourseEditorLayout>
</WithAuth> </WithAuth>
), ),

View File

@ -88,9 +88,12 @@ model Staff {
deletedAt DateTime? @map("deleted_at") deletedAt DateTime? @map("deleted_at")
officerId String? @map("officer_id") officerId String? @map("officer_id")
watchedPost Post[] @relation("post_watch_staff") // watchedPost Post[] @relation("post_watch_staff")
visits Visit[] visits Visit[]
posts Post[] posts Post[]
learningPost Post[] @relation("post_student")
sentMsgs Message[] @relation("message_sender") sentMsgs Message[] @relation("message_sender")
receivedMsgs Message[] @relation("message_receiver") receivedMsgs Message[] @relation("message_receiver")
registerToken String? registerToken String?
@ -124,7 +127,7 @@ model Department {
deptStaffs Staff[] @relation("DeptStaff") deptStaffs Staff[] @relation("DeptStaff")
terms Term[] @relation("department_term") terms Term[] @relation("department_term")
watchedPost Post[] @relation("post_watch_dept") // watchedPost Post[] @relation("post_watch_dept")
hasChildren Boolean? @default(false) @map("has_children") hasChildren Boolean? @default(false) @map("has_children")
@@index([parentId]) @@index([parentId])
@ -201,7 +204,7 @@ model Post {
order Float? @default(0) @map("order") order Float? @default(0) @map("order")
duration Int? duration Int?
rating Int? @default(0) rating Int? @default(0)
students Staff[] @relation("post_student")
depts Department[] @relation("post_dept") depts Department[] @relation("post_dept")
// 索引 // 索引
// 日期时间类型字段 // 日期时间类型字段
@ -223,8 +226,8 @@ model Post {
ancestors PostAncestry[] @relation("DescendantPosts") ancestors PostAncestry[] @relation("DescendantPosts")
descendants PostAncestry[] @relation("AncestorPosts") descendants PostAncestry[] @relation("AncestorPosts")
resources Resource[] // 附件列表 resources Resource[] // 附件列表
watchableStaffs Staff[] @relation("post_watch_staff") // 可观看的员工列表,关联 Staff 模型 // watchableStaffs Staff[] @relation("post_watch_staff")
watchableDepts Department[] @relation("post_watch_dept") // 可观看的部门列表,关联 Department 模型 // watchableDepts Department[] @relation("post_watch_dept") // 可观看的部门列表,关联 Department 模型
meta Json? // 封面url 视频url objectives具体的学习目标 rating评分Int meta Json? // 封面url 视频url objectives具体的学习目标 rating评分Int
// 索引 // 索引

View File

@ -6,8 +6,8 @@ export const postDetailSelect: Prisma.PostSelect = {
title: true, title: true,
content: true, content: true,
resources: true, resources: true,
watchableDepts: true, // watchableDepts: true,
watchableStaffs: true, // watchableStaffs: true,
updatedAt: true, updatedAt: true,
author: { author: {
select: { select: {