diff --git a/apps/server/src/models/post/utils.ts b/apps/server/src/models/post/utils.ts index 40ea595..9185a0f 100644 --- a/apps/server/src/models/post/utils.ts +++ b/apps/server/src/models/post/utils.ts @@ -124,7 +124,10 @@ export async function updateCourseEnrollmentStats(courseId: string) { }, }); } + export async function setCourseInfo({ data }: { data: Post }) { + // await db.term + if (data?.type === PostType.COURSE) { const ancestries = await db.postAncestry.findMany({ where: { diff --git a/apps/web/src/app/main/course/detail/page.tsx b/apps/web/src/app/main/course/detail/page.tsx index d1bf172..16f7173 100644 --- a/apps/web/src/app/main/course/detail/page.tsx +++ b/apps/web/src/app/main/course/detail/page.tsx @@ -2,7 +2,7 @@ import CourseDetail from "@web/src/components/models/course/detail/CourseDetail" import { useParams } from "react-router-dom"; export function CourseDetailPage() { - const { id } = useParams(); + const { id, lectureId } = useParams(); console.log("Course ID:", id); - return ; + return ; } diff --git a/apps/web/src/app/main/courses/components/FilterSection.tsx b/apps/web/src/app/main/courses/components/FilterSection.tsx index 678c227..ae4da2b 100644 --- a/apps/web/src/app/main/courses/components/FilterSection.tsx +++ b/apps/web/src/app/main/courses/components/FilterSection.tsx @@ -1,6 +1,7 @@ import { Checkbox, Divider, Radio, Space } from "antd"; import { categories, levels } from "../mockData"; import { api } from "@nice/client"; +import { TaxonomySlug } from "@nice/common"; interface FilterSectionProps { selectedCategory: string; @@ -15,6 +16,13 @@ export default function FilterSection({ onCategoryChange, onLevelChange, }: FilterSectionProps) { + const { data: levels, isLoading } = api.term.findMany.useQuery({ + where: { + taxonomy: { + slug: TaxonomySlug.LEVEL, + }, + }, + }); // const { data } = api.term; return (
@@ -42,9 +50,9 @@ export default function FilterSection({ onChange={(e) => onLevelChange(e.target.value)} className="flex flex-col space-y-3"> 全部难度 - {levels.map((level) => ( - - {level} + {levels?.map((level) => ( + + {level.name} ))} diff --git a/apps/web/src/app/main/layout/MainHeader.tsx b/apps/web/src/app/main/layout/MainHeader.tsx index d7d089b..80ea733 100644 --- a/apps/web/src/app/main/layout/MainHeader.tsx +++ b/apps/web/src/app/main/layout/MainHeader.tsx @@ -1,65 +1,75 @@ -import { useState } from 'react'; -import { Input, Layout, Avatar, Button, Dropdown } from 'antd'; -import { SearchOutlined, UserOutlined } from '@ant-design/icons'; -import { useAuth } from '@web/src/providers/auth-provider'; -import { useNavigate } from 'react-router-dom'; -import { UserMenu } from './UserMenu'; -import { NavigationMenu } from './NavigationMenu'; +import { useState } from "react"; +import { Input, Layout, Avatar, Button, Dropdown } from "antd"; +import { EditFilled, SearchOutlined, UserOutlined } from "@ant-design/icons"; +import { useAuth } from "@web/src/providers/auth-provider"; +import { useNavigate } from "react-router-dom"; +import { UserMenu } from "./UserMenu"; +import { NavigationMenu } from "./NavigationMenu"; const { Header } = Layout; export function MainHeader() { - const [searchValue, setSearchValue] = useState(''); - const { isAuthenticated, user } = useAuth(); - const navigate = useNavigate(); + const [searchValue, setSearchValue] = useState(""); + const { isAuthenticated, user } = useAuth(); + const navigate = useNavigate(); - return ( -
-
-
-
navigate('/')} - className="text-2xl font-bold bg-gradient-to-r from-primary-600 via-primary-500 to-primary-400 bg-clip-text text-transparent hover:scale-105 transition-transform cursor-pointer" - > - 烽火慕课 -
- -
-
-
- } - placeholder="搜索课程" - className="w-72 rounded-full" - value={searchValue} - onChange={(e) => setSearchValue(e.target.value)} - /> -
- {isAuthenticated ? ( - } - trigger={['click']} - placement="bottomRight" - > - - {(user?.showname || user?.username || '')[0]?.toUpperCase()} - - - ) : ( - - )} -
-
-
- ); -} \ No newline at end of file + return ( +
+
+
+
navigate("/")} + className="text-2xl font-bold bg-gradient-to-r from-primary-600 via-primary-500 to-primary-400 bg-clip-text text-transparent hover:scale-105 transition-transform cursor-pointer"> + 烽火慕课 +
+ +
+
+
+ + } + placeholder="搜索课程" + className="w-72 rounded-full" + value={searchValue} + onChange={(e) => setSearchValue(e.target.value)} + /> +
+ {isAuthenticated && ( + <> + + + )} + {isAuthenticated ? ( + } + trigger={["click"]} + placement="bottomRight"> + + {(user?.showname || + user?.username || + "")[0]?.toUpperCase()} + + + ) : ( + + )} +
+
+
+ ); +} diff --git a/apps/web/src/components/models/course/detail/CourseDetail.tsx b/apps/web/src/components/models/course/detail/CourseDetail.tsx index 402fbd7..353ec4f 100644 --- a/apps/web/src/components/models/course/detail/CourseDetail.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetail.tsx @@ -1,7 +1,13 @@ import { CourseDetailProvider } from "./CourseDetailContext"; import CourseDetailLayout from "./CourseDetailLayout"; -export default function CourseDetail({ id }: { id?: string }) { +export default function CourseDetail({ + id, + lectureId, +}: { + id?: string; + lectureId?: string; +}) { const iframeStyle = { width: "50%", height: "100vh", diff --git a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx index dbb7b87..60d1bd3 100644 --- a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx @@ -21,12 +21,13 @@ export function CourseDetailProvider({ children, editId, }: CourseFormProviderProps) { + const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } = (api.post as any).findFirst.useQuery( { where: { id: editId }, include: { - sections: { include: { lectures: true } }, + // sections: { include: { lectures: true } }, enrollments: true, }, }, diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/CourseSyllabus.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/CourseSyllabus.tsx index 58212a1..aef7064 100644 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/CourseSyllabus.tsx +++ b/apps/web/src/components/models/course/detail/CourseSyllabus/CourseSyllabus.tsx @@ -7,11 +7,12 @@ import { import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; import React, { useState, useRef, useContext } from "react"; import { motion, AnimatePresence } from "framer-motion"; -import { SectionDto } from "@nice/common"; +import { SectionDto, TaxonomySlug } from "@nice/common"; import { SyllabusHeader } from "./SyllabusHeader"; import { SectionItem } from "./SectionItem"; import { CollapsedButton } from "./CollapsedButton"; import { CourseDetailContext } from "../CourseDetailContext"; +import { api } from "@nice/client"; interface CourseSyllabusProps { sections: SectionDto[]; @@ -29,7 +30,11 @@ export const CourseSyllabus: React.FC = ({ const { isHeaderVisible } = useContext(CourseDetailContext); const [expandedSections, setExpandedSections] = useState([]); const sectionRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); - + // api.term.findMany.useQuery({ + // where: { + // taxonomy: { slug: TaxonomySlug.CATEGORY }, + // }, + // }); const toggleSection = (sectionId: string) => { setExpandedSections((prev) => prev.includes(sectionId) diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx index 9a87d75..0999f3c 100644 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx +++ b/apps/web/src/components/models/course/detail/CourseSyllabus/LectureItem.tsx @@ -1,9 +1,8 @@ // components/CourseSyllabus/LectureItem.tsx -import { Lecture } from "@nice/common"; +import { Lecture, LectureType } from "@nice/common"; import React from "react"; -import { motion } from "framer-motion"; -import { ClockIcon, PlayCircleIcon } from "@heroicons/react/24/outline"; +import { ClockCircleOutlined, FileTextOutlined, PlayCircleOutlined } from "@ant-design/icons"; // 使用 Ant Design 图标 interface LectureItemProps { lecture: Lecture; @@ -14,23 +13,24 @@ export const LectureItem: React.FC = ({ lecture, onClick, }) => ( - onClick(lecture.id)}> - + {lecture.type === LectureType.VIDEO && ( + + )} + {lecture.type === LectureType.ARTICLE && ( + // 为文章类型添加图标 + )}

{lecture.title}

- {lecture.description && ( -

- {lecture.description} -

+ {lecture.subTitle && ( +

{lecture.subTitle}

)}
- + {lecture.duration}分钟
-
+
); diff --git a/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx b/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx index 100fb6e..95c61db 100644 --- a/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx +++ b/apps/web/src/components/models/course/detail/CourseSyllabus/SectionItem.tsx @@ -33,8 +33,8 @@ export const SectionItem = React.forwardRef( {section.title}

- {section.totalLectures}节课 ·{" "} - {Math.floor(section.totalDuration / 60)}分钟 + {section?.lectures?.length}节课 ·{" "} + {/* {Math.floor(section?.totalDuration / 60)}分钟 */}

diff --git a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx b/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx index fc3dc51..efa4962 100644 --- a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx +++ b/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx @@ -96,7 +96,7 @@ export function CourseFormProvider({ }; // 删除原始的 taxonomy 字段 taxonomies.forEach((tax) => { - delete formattedValues[tax.name]; + delete formattedValues[tax.id]; }); delete formattedValues.requirements; delete formattedValues.objectives; diff --git a/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx b/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx index 14eae28..f038144 100644 --- a/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx +++ b/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx @@ -53,7 +53,9 @@ export function CourseBasicForm() { label={tax.name} name={tax.id} key={index}> - + ))} {/* diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index cd51fcf..cee94f9 100755 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -56,11 +56,10 @@ export const routes: CustomRouteObject[] = [ { index: true, element: , - }, { path: "paths", - element: + element: , }, { path: "courses", @@ -125,7 +124,7 @@ export const routes: CustomRouteObject[] = [ ], }, { - path: ":id?/detail", // 使用 ? 表示 id 参数是可选的 + path: ":id?/detail/:lectureId?", // 使用 ? 表示 id 参数是可选的 element: , }, ], diff --git a/packages/common/prisma/schema.prisma b/packages/common/prisma/schema.prisma index 8ad7da3..103ea10 100644 --- a/packages/common/prisma/schema.prisma +++ b/packages/common/prisma/schema.prisma @@ -198,7 +198,8 @@ model Post { terms Term[] @relation("post_term") order Float? @default(0) @map("order") duration Int? - + rating Int? @default(0) + // 索引 // 日期时间类型字段 createdAt DateTime @default(now()) @map("created_at") publishedAt DateTime? @map("published_at") // 发布时间 @@ -242,7 +243,6 @@ model PostAncestry { relDepth Int @map("rel_depth") ancestor Post? @relation("AncestorPosts", fields: [ancestorId], references: [id]) descendant Post @relation("DescendantPosts", fields: [descendantId], references: [id]) - // 复合索引优化 // 索引建议 @@index([ancestorId]) // 针对祖先的查询 diff --git a/packages/common/src/enum.ts b/packages/common/src/enum.ts index c642ca9..9598a55 100755 --- a/packages/common/src/enum.ts +++ b/packages/common/src/enum.ts @@ -6,8 +6,8 @@ export enum PostType { POST_COMMENT = "post_comment", COURSE_REVIEW = "course_review", COURSE = "couse", - LECTURE = "lecture", SECTION = "section", + LECTURE = "lecture", } export enum LectureType { VIDEO = "video",