From 0afb73a45820f082f1fa971c7640eb9c18e46f94 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Thu, 20 Feb 2025 20:02:27 +0800 Subject: [PATCH 1/6] add --- .../server/src/models/post/post.controller.ts | 3 +- apps/server/src/models/post/utils.ts | 27 + .../main/courses/components/FilterSection.tsx | 92 +-- apps/web/src/app/main/courses/page.tsx | 161 +++-- .../course/detail/CourseDetailContext.tsx | 5 +- .../course/detail/CourseDetailLayout.tsx | 3 +- .../editor/context/CourseEditorContext.tsx | 23 +- .../course/editor/form/CourseBasicForm.tsx | 3 +- .../editor/form/CourseContentForm copy.tsx | 604 ------------------ .../form/CourseContentForm/LectureList.tsx | 18 +- .../CourseContentForm/SortableSection.tsx | 1 + .../components/models/term/term-editor.tsx | 2 +- .../video-player/ControlButtons/Speed.tsx | 2 +- packages/common/src/constants.ts | 9 +- packages/common/src/models/post.ts | 11 +- 15 files changed, 226 insertions(+), 738 deletions(-) delete mode 100644 apps/web/src/components/models/course/editor/form/CourseContentForm copy.tsx diff --git a/apps/server/src/models/post/post.controller.ts b/apps/server/src/models/post/post.controller.ts index e5731af..1e9eb77 100755 --- a/apps/server/src/models/post/post.controller.ts +++ b/apps/server/src/models/post/post.controller.ts @@ -6,6 +6,5 @@ import { db } from '@nice/common'; @Controller('post') export class PostController { - constructor(private readonly postService: PostService) { } - + constructor(private readonly postService: PostService) {} } diff --git a/apps/server/src/models/post/utils.ts b/apps/server/src/models/post/utils.ts index 91af13e..e2fb89b 100644 --- a/apps/server/src/models/post/utils.ts +++ b/apps/server/src/models/post/utils.ts @@ -123,3 +123,30 @@ export async function updateCourseEnrollmentStats(courseId: string) { }, }); } +export async function setCourseInfo(data: Post) { + if (data?.type === PostType.COURSE) { + const ancestries = await db.postAncestry.findMany({ + where: { + ancestorId: data.id, + }, + select: { + id: true, + descendant: true, + }, + }); + const descendants = ancestries.map((ancestry) => ancestry.descendant); + const sections = descendants.filter((descendant) => { + return ( + descendant.type === PostType.SECTION && descendant.parentId === data.id + ); + }); + const lectures = descendants.filter((descendant) => { + return ( + descendant.type === PostType.LECTURE && + sections.map((section) => section.id).includes(descendant.parentId) + ); + }); + } + + Object.assign(data, {}); +} diff --git a/apps/web/src/app/main/courses/components/FilterSection.tsx b/apps/web/src/app/main/courses/components/FilterSection.tsx index 93e0e6f..678c227 100644 --- a/apps/web/src/app/main/courses/components/FilterSection.tsx +++ b/apps/web/src/app/main/courses/components/FilterSection.tsx @@ -1,54 +1,54 @@ -import { Checkbox, Divider, Radio, Space } from 'antd'; -import { categories, levels } from '../mockData'; +import { Checkbox, Divider, Radio, Space } from "antd"; +import { categories, levels } from "../mockData"; +import { api } from "@nice/client"; interface FilterSectionProps { - selectedCategory: string; - selectedLevel: string; - onCategoryChange: (category: string) => void; - onLevelChange: (level: string) => void; + selectedCategory: string; + selectedLevel: string; + onCategoryChange: (category: string) => void; + onLevelChange: (level: string) => void; } export default function FilterSection({ - selectedCategory, - selectedLevel, - onCategoryChange, - onLevelChange, + selectedCategory, + selectedLevel, + onCategoryChange, + onLevelChange, }: FilterSectionProps) { - return ( -
-
-

课程分类

- onCategoryChange(e.target.value)} - className="flex flex-col space-y-3" - > - 全部课程 - {categories.map(category => ( - - {category} - - ))} - -
+ // const { data } = api.term; + return ( +
+
+

课程分类

+ onCategoryChange(e.target.value)} + className="flex flex-col space-y-3"> + 全部课程 + {categories.map((category) => ( + + {category} + + ))} + +
- + -
-

难度等级

- onLevelChange(e.target.value)} - className="flex flex-col space-y-3" - > - 全部难度 - {levels.map(level => ( - - {level} - - ))} - -
-
- ); -} \ No newline at end of file +
+

难度等级

+ onLevelChange(e.target.value)} + className="flex flex-col space-y-3"> + 全部难度 + {levels.map((level) => ( + + {level} + + ))} + +
+
+ ); +} diff --git a/apps/web/src/app/main/courses/page.tsx b/apps/web/src/app/main/courses/page.tsx index f852e00..cf2162e 100644 --- a/apps/web/src/app/main/courses/page.tsx +++ b/apps/web/src/app/main/courses/page.tsx @@ -1,73 +1,100 @@ -import { useState, useMemo } from 'react'; -import { mockCourses } from './mockData'; -import FilterSection from './components/FilterSection'; -import CourseList from './components/CourseList'; +import { useState, useMemo } from "react"; +import { mockCourses } from "./mockData"; +import FilterSection from "./components/FilterSection"; +import CourseList from "./components/CourseList"; +import { api } from "@nice/client"; +import { LectureType, PostType } from "@nice/common"; export default function CoursesPage() { - const [currentPage, setCurrentPage] = useState(1); - const [selectedCategory, setSelectedCategory] = useState(''); - const [selectedLevel, setSelectedLevel] = useState(''); - const pageSize = 12; + const [currentPage, setCurrentPage] = useState(1); + const [selectedCategory, setSelectedCategory] = useState(""); + const [selectedLevel, setSelectedLevel] = useState(""); + const pageSize = 12; + const { data, isLoading } = api.post.findManyWithPagination.useQuery({ + where: { + type: PostType.COURSE, + terms: { + some: { + AND: [ + ...(selectedCategory + ? [ + { + name: selectedCategory, + }, + ] + : []), + ...(selectedLevel + ? [ + { + name: selectedLevel, + }, + ] + : []), + ], + }, + }, + }, + }); + const filteredCourses = useMemo(() => { + return mockCourses.filter((course) => { + const matchCategory = + !selectedCategory || course.category === selectedCategory; + const matchLevel = !selectedLevel || course.level === selectedLevel; + return matchCategory && matchLevel; + }); + }, [selectedCategory, selectedLevel]); - const filteredCourses = useMemo(() => { - return mockCourses.filter(course => { - const matchCategory = !selectedCategory || course.category === selectedCategory; - const matchLevel = !selectedLevel || course.level === selectedLevel; - return matchCategory && matchLevel; - }); - }, [selectedCategory, selectedLevel]); + const paginatedCourses = useMemo(() => { + const startIndex = (currentPage - 1) * pageSize; + return filteredCourses.slice(startIndex, startIndex + pageSize); + }, [filteredCourses, currentPage]); - const paginatedCourses = useMemo(() => { - const startIndex = (currentPage - 1) * pageSize; - return filteredCourses.slice(startIndex, startIndex + pageSize); - }, [filteredCourses, currentPage]); + const handlePageChange = (page: number) => { + setCurrentPage(page); + window.scrollTo({ top: 0, behavior: "smooth" }); + }; - const handlePageChange = (page: number) => { - setCurrentPage(page); - window.scrollTo({ top: 0, behavior: 'smooth' }); - }; + return ( +
+
+
+ {/* 左侧筛选区域 */} +
+
+ { + setSelectedCategory(category); + setCurrentPage(1); + }} + onLevelChange={(level) => { + setSelectedLevel(level); + setCurrentPage(1); + }} + /> +
+
- return ( -
-
-
- {/* 左侧筛选区域 */} -
-
- { - setSelectedCategory(category); - setCurrentPage(1); - }} - onLevelChange={level => { - setSelectedLevel(level); - setCurrentPage(1); - }} - /> -
-
- - {/* 右侧课程列表区域 */} -
-
-
- - 共找到 {filteredCourses.length} 门课程 - -
- -
-
-
-
-
- ); -} \ No newline at end of file + {/* 右侧课程列表区域 */} +
+
+
+ + 共找到 {filteredCourses.length} 门课程 + +
+ +
+
+
+
+
+ ); +} diff --git a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx index 3458a1f..dbb7b87 100644 --- a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx @@ -1,8 +1,7 @@ -import { api,} from "@nice/client"; +import { api } from "@nice/client"; import { courseDetailSelect, CourseDto } from "@nice/common"; import React, { createContext, ReactNode, useState } from "react"; - interface CourseDetailContextType { editId?: string; // 添加 editId course?: CourseDto; @@ -23,7 +22,7 @@ export function CourseDetailProvider({ editId, }: CourseFormProviderProps) { const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } = - api.course.findFirst.useQuery( + (api.post as any).findFirst.useQuery( { where: { id: editId }, include: { diff --git a/apps/web/src/components/models/course/detail/CourseDetailLayout.tsx b/apps/web/src/components/models/course/detail/CourseDetailLayout.tsx index 4f4ecd5..d1fd37c 100644 --- a/apps/web/src/components/models/course/detail/CourseDetailLayout.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailLayout.tsx @@ -16,7 +16,8 @@ export default function CourseDetailLayout() { const [isSyllabusOpen, setIsSyllabusOpen] = useState(false); return (
- {/* 添加 Header 组件 */} + + {/* 添加 Header 组件 */} {/* 主内容区域 */} {/* 为了防止 Header 覆盖内容,添加上边距 */}
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 26c9e09..fc3dc51 100644 --- a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx +++ b/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx @@ -43,7 +43,12 @@ export function CourseFormProvider({ const [form] = Form.useForm(); const { create, update, createCourse } = usePost(); const { data: course }: { data: CourseDto } = api.post.findFirst.useQuery( - { where: { id: editId } }, + { + where: { id: editId }, + include: { + terms: true, + }, + }, { enabled: Boolean(editId) } ); const { @@ -51,7 +56,7 @@ export function CourseFormProvider({ }: { data: Taxonomy[]; } = api.taxonomy.getAll.useQuery({ - // type: ObjectType.COURSE, + type: ObjectType.COURSE, }); const navigate = useNavigate(); @@ -65,6 +70,9 @@ export function CourseFormProvider({ requirements: course?.meta?.requirements, objectives: course?.meta?.objectives, }; + course.terms?.forEach((term) => { + formData[term.taxonomyId] = term.id; // 假设 taxonomyName 是您在 Form.Item 中使用的 name + }); form.setFieldsValue(formData); } }, [course, form]); @@ -72,13 +80,24 @@ export function CourseFormProvider({ const onSubmit = async (values: CourseFormData) => { console.log(values); const sections = values?.sections || []; + const termIds = taxonomies + .map((tax) => values[tax.id]) // 获取每个 taxonomy 对应的选中值 + .filter((id) => id); // 过滤掉空值 + const formattedValues = { ...values, meta: { requirements: values.requirements, objectives: values.objectives, }, + terms: { + connect: termIds.map((id) => ({ id })), // 转换成 connect 格式 + }, }; + // 删除原始的 taxonomy 字段 + taxonomies.forEach((tax) => { + delete formattedValues[tax.name]; + }); delete formattedValues.requirements; delete formattedValues.objectives; delete formattedValues.sections; 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 754004d..14eae28 100644 --- a/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx +++ b/apps/web/src/components/models/course/editor/form/CourseBasicForm.tsx @@ -14,6 +14,7 @@ export function CourseBasicForm() { value: key as CourseLevel, }) ); + const { form, taxonomies } = useCourseEditor(); return (
@@ -50,7 +51,7 @@ export function CourseBasicForm() { }, ]} label={tax.name} - name={tax.name} + name={tax.id} key={index}> diff --git a/apps/web/src/components/models/course/editor/form/CourseContentForm copy.tsx b/apps/web/src/components/models/course/editor/form/CourseContentForm copy.tsx deleted file mode 100644 index 8ab2ad2..0000000 --- a/apps/web/src/components/models/course/editor/form/CourseContentForm copy.tsx +++ /dev/null @@ -1,604 +0,0 @@ -import { - PlusOutlined, - DragOutlined, - DeleteOutlined, - CaretRightOutlined, - SaveOutlined, -} from "@ant-design/icons"; -import { - Form, - Alert, - Button, - Input, - Select, - Space, - Collapse, - message, -} from "antd"; -import React, { useCallback, useEffect, useState } from "react"; -import { - DndContext, - closestCenter, - KeyboardSensor, - PointerSensor, - useSensor, - useSensors, - DragEndEvent, -} from "@dnd-kit/core"; -import { api, emitDataChange } from "@nice/client"; -import { - arrayMove, - SortableContext, - sortableKeyboardCoordinates, - useSortable, - verticalListSortingStrategy, -} from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; -import QuillEditor from "../../../../common/editor/quill/QuillEditor"; -import { TusUploader } from "../../../../common/uploader/TusUploader"; -import { Lecture, LectureType, PostType } from "@nice/common"; -import { useCourseEditor } from "../context/CourseEditorContext"; -import { usePost } from "@nice/client"; -import toast from "react-hot-toast"; -interface SectionData { - id: string; - title: string; - content?: string; - courseId?: string; -} - -interface LectureData { - id: string; - title: string; - meta?: { - type?: LectureType; - fieldIds?: []; - }; - content?: string; - sectionId?: string; -} -const CourseContentFormHeader = () => ( - -

通过组织清晰的章节和课时,帮助学员更好地学习。建议:

-
    -
  • 将相关内容组织到章节中
  • -
  • 每个章节建议包含 3-7 个课时
  • -
  • 课时可以是视频、文章或测验
  • -
- - } - className="mb-8" - /> -); - -const CourseSectionEmpty = () => ( -
-
- -

开始创建您的课程内容

-

点击下方按钮添加第一个章节

-
-
-); - -interface SortableSectionProps { - courseId?: string; - field: SectionData; - remove: () => void; - children: React.ReactNode; -} - -const SortableSection: React.FC = ({ - field, - remove, - courseId, - children, -}) => { - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ id: field?.id }); - - const [form] = Form.useForm(); - const [editing, setEditing] = useState(field.id ? false : true); - const [loading, setLoading] = useState(false); - const { create, update } = usePost(); - - const handleSave = async () => { - if (!courseId) { - toast.error("课程未创建,请先填写课程基本信息完成创建"); - return; - } - try { - setLoading(true); - const values = await form.validateFields(); - let result; - try { - if (!field?.id) { - result = await create.mutateAsync({ - data: { - title: values?.title, - type: PostType.SECTION, - parentId: courseId, - }, - }); - } else { - result = await update.mutateAsync({ - data: { - title: values?.title, - }, - }); - } - } catch (err) { - console.log(err); - } - - field.id = result.id; - setEditing(false); - message.success("保存成功"); - } catch (error) { - console.log(error); - message.error("保存失败"); - } finally { - setLoading(false); - } - }; - - const style = { - transform: CSS.Transform.toString(transform), - transition, - backgroundColor: isDragging ? "#f5f5f5" : undefined, - }; - - return ( -
- - - - - - - - - - - ) : ( -
- - - {field.title || "未命名章节"} - - - - - -
- ) - } - key={field.id || "new"}> - {children} -
-
-
- ); -}; - -interface SortableLectureProps { - field: LectureData; - remove: () => void; - sectionFieldKey: string; -} - -const SortableLecture: React.FC = ({ - field, - remove, - sectionFieldKey, -}) => { - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ id: field?.id }); - const { create, update } = usePost(); - const [form] = Form.useForm(); - const [editing, setEditing] = useState(field?.id ? false : true); - const [loading, setLoading] = useState(false); - const lectureType = - Form.useWatch(["meta", "type"], form) || LectureType.ARTICLE; - const handleSave = async () => { - try { - setLoading(true); - const values = await form.validateFields(); - let result; - try { - if (!field.id) { - result = await create.mutateAsync({ - data: { - parentId: sectionFieldKey, - type: PostType.LECTURE, - title: values?.title, - meta: { - type: values?.meta?.type, - fileIds: values?.meta?.fileIds, - }, - resources: { - connect: (values?.meta?.fileIds || []).map( - (fileId) => ({ - fileId, - }) - ), - }, - content: values?.content, - }, - }); - } else { - result = await update.mutateAsync({ - where: { - id: field?.id, - }, - data: { - title: values?.title, - meta: { - type: values?.meta?.type, - fieldIds: values?.meta?.fileIds, - }, - resources: { - connect: (values?.meta?.fileIds || []).map( - (fileId) => ({ - fileId, - }) - ), - }, - content: values?.content, - }, - }); - } - } catch (err) { - console.log(err); - } - - field.id = result.id; - setEditing(false); - message.success("保存成功"); - } catch (error) { - message.error("保存失败"); - } finally { - setLoading(false); - } - }; - - const style = { - transform: CSS.Transform.toString(transform), - transition, - borderBottom: "1px solid #f0f0f0", - backgroundColor: isDragging ? "#f5f5f5" : undefined, - }; - - return ( -
- {editing ? ( -
-
- - - - - } - 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", From fb026e1fde3f08a9a216ab9a8a56e1344bd12210 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Fri, 21 Feb 2025 13:20:13 +0800 Subject: [PATCH 6/6] add --- .../models/course/detail/CourseDetailContext.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx index 60d1bd3..8540e54 100644 --- a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx @@ -1,6 +1,7 @@ import { api } from "@nice/client"; import { courseDetailSelect, CourseDto } from "@nice/common"; -import React, { createContext, ReactNode, useState } from "react"; +import React, { createContext, ReactNode, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; interface CourseDetailContextType { editId?: string; // 添加 editId @@ -21,7 +22,7 @@ export function CourseDetailProvider({ children, editId, }: CourseFormProviderProps) { - + const navigate = useNavigate(); const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } = (api.post as any).findFirst.useQuery( { @@ -36,6 +37,10 @@ export function CourseDetailProvider({ const [selectedLectureId, setSelectedLectureId] = useState< string | undefined >(undefined); + useEffect(() => { + navigate(``) + + }, [selectedLectureId, editId]); const [isHeaderVisible, setIsHeaderVisible] = useState(true); // 新增 return (