addd
This commit is contained in:
parent
358d2ea9d5
commit
4a6957f181
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 && {
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
),
|
),
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
// 索引
|
// 索引
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
Loading…
Reference in New Issue