diff --git a/Dockerfile b/Dockerfile index 3d15a41..ff297ad 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # 基础镜像 -FROM node:18.17-alpine as base +FROM node:18-alpine as base RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ echo "https://mirrors.aliyun.com/alpine/v3.18/community" >> /etc/apk/repositories diff --git a/apps/server/src/models/post/post.router.ts b/apps/server/src/models/post/post.router.ts index bcde094..9ee4f9b 100755 --- a/apps/server/src/models/post/post.router.ts +++ b/apps/server/src/models/post/post.router.ts @@ -125,5 +125,14 @@ export class PostRouter { const { staff } = ctx; return await this.postService.updateOrderByIds(input.ids); }), + softDeletePostDescendant:this.trpc.protectProcedure + .input( + z.object({ + ancestorId:z.string() + }) + ) + .mutation(async ({ input })=>{ + return await this.postService.softDeletePostDescendant(input) + }) }); } diff --git a/apps/server/src/models/post/post.service.ts b/apps/server/src/models/post/post.service.ts index 0f3e8cd..5d4db4c 100755 --- a/apps/server/src/models/post/post.service.ts +++ b/apps/server/src/models/post/post.service.ts @@ -101,6 +101,7 @@ export class PostService extends BaseTreeService { }, params: { staff?: UserProfile; tx?: Prisma.TransactionClient }, ) { + const { courseDetail } = args; // If no transaction is provided, create a new one if (!params.tx) { @@ -295,40 +296,33 @@ export class PostService extends BaseTreeService { staff?.id && { authorId: staff.id, }, - // staff?.id && { - // watchableStaffs: { - // some: { - // id: staff.id, - // }, - // }, - // }, - // deptId && { - // watchableDepts: { - // some: { - // id: { - // in: parentDeptIds, - // }, - // }, - // }, - // }, - - // { - // AND: [ - // { - // watchableStaffs: { - // none: {}, // 匹配 watchableStaffs 为空 - // }, - // }, - // { - // watchableDepts: { - // none: {}, // 匹配 watchableDepts 为空 - // }, - // }, - // ], - // }, ].filter(Boolean); if (orCondition?.length > 0) return orCondition; return undefined; } + async softDeletePostDescendant(args:{ancestorId?:string}){ + const { ancestorId } = args + const descendantIds = [] + await db.postAncestry.findMany({ + where:{ + ancestorId, + }, + select:{ + descendantId:true + } + }).then(res=>{ + res.forEach(item=>{ + descendantIds.push(item.descendantId) + }) + }) + console.log(descendantIds) + const result = super.softDeleteByIds([...descendantIds,ancestorId]) + EventBus.emit('dataChanged', { + type: ObjectType.POST, + operation: CrudOperation.DELETED, + data: result, + }); + return result + } } diff --git a/apps/web/package.json b/apps/web/package.json index b484901..af89247 100755 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -64,6 +64,7 @@ "react-dom": "18.2.0", "react-hook-form": "^7.54.2", "react-hot-toast": "^2.4.1", + "react-player": "^2.16.0", "react-resizable": "^3.0.5", "react-router-dom": "^6.24.1", "superjson": "^2.2.1", diff --git a/apps/web/src/app/main/path/editor/page.tsx b/apps/web/src/app/main/path/editor/page.tsx index 2c0b8b0..16c106e 100755 --- a/apps/web/src/app/main/path/editor/page.tsx +++ b/apps/web/src/app/main/path/editor/page.tsx @@ -6,7 +6,7 @@ export default function PathEditorPage() { const { id } = useParams(); return ( - ; + ); } diff --git a/apps/web/src/components/common/editor/MindEditor.tsx b/apps/web/src/components/common/editor/MindEditor.tsx index 065917e..0aba7cf 100755 --- a/apps/web/src/components/common/editor/MindEditor.tsx +++ b/apps/web/src/components/common/editor/MindEditor.tsx @@ -25,6 +25,8 @@ import JoinButton from "../../models/course/detail/CourseOperationBtns/JoinButto import { CourseDetailContext } from "../../models/course/detail/PostDetailContext"; import ReactDOM from "react-dom"; import { createRoot } from "react-dom/client"; +import { useQueryClient } from "@tanstack/react-query"; +import { getQueryKey } from "@trpc/react-query"; export default function MindEditor({ id }: { id?: string }) { const containerRef = useRef(null); const { @@ -36,6 +38,7 @@ export default function MindEditor({ id }: { id?: string }) { const [instance, setInstance] = useState(null); const { isAuthenticated, user, hasSomePermissions } = useAuth(); const { read } = useVisitor(); + const queryClient = useQueryClient(); // const { data: post, isLoading }: { data: PathDto; isLoading: boolean } = // api.post.findFirst.useQuery( // { @@ -46,7 +49,11 @@ export default function MindEditor({ id }: { id?: string }) { // }, // { enabled: Boolean(id) } // ); - + const softDeletePostDescendant = api.post.softDeletePostDescendant.useMutation({ + onSuccess:()=>{ + queryClient.invalidateQueries({ queryKey: getQueryKey(api.post) }); + } + }) const canEdit: boolean = useMemo(() => { const isAuth = isAuthenticated && user?.id === post?.author?.id; return ( @@ -62,16 +69,16 @@ export default function MindEditor({ id }: { id?: string }) { }); const { handleFileUpload } = useTusUpload(); const [form] = Form.useForm(); - const CustomLinkIconPlugin = (mind) => { - mind.bus.addListener("operation", async () => { - const hyperLinkElement = - await document.querySelectorAll(".hyper-link"); - console.log("hyperLinkElement", hyperLinkElement); - hyperLinkElement.forEach((item) => { - const hyperLinkDom = createRoot(item); - hyperLinkDom.render(); - }); + const handleIcon = () => { + const hyperLinkElement = document.querySelectorAll(".hyper-link"); + console.log("hyperLinkElement", hyperLinkElement); + hyperLinkElement.forEach((item) => { + const hyperLinkDom = createRoot(item); + hyperLinkDom.render(); }); + } + const CustomLinkIconPlugin = (mind) => { + mind.bus.addListener("operation", handleIcon) }; useEffect(() => { if (post?.id && id) { @@ -133,7 +140,11 @@ export default function MindEditor({ id }: { id?: string }) { containerRef.current.hidden = true; //挂载实例 setInstance(mind); + }, [canEdit]); + useEffect(() => { + handleIcon() + }); useEffect(() => { if ((!id || post) && instance) { containerRef.current.hidden = false; @@ -204,10 +215,16 @@ export default function MindEditor({ id }: { id?: string }) { } console.log(result); }, - (error) => {}, + (error) => { }, `mind-thumb-${new Date().toString()}` ); }; + const handleDelete = async () => { + await softDeletePostDescendant.mutateAsync({ + ancestorId: id, + }); + navigate("/path"); + } useEffect(() => { containerRef.current.style.height = `${Math.floor(window.innerHeight - 271)}px`; }, []); @@ -248,14 +265,28 @@ export default function MindEditor({ id }: { id?: string }) {
{canEdit && ( - + <> + { + id && ( + + ) + } + + )}
diff --git a/apps/web/src/components/common/editor/NodeMenu.tsx b/apps/web/src/components/common/editor/NodeMenu.tsx index e833a79..2519c7e 100755 --- a/apps/web/src/components/common/editor/NodeMenu.tsx +++ b/apps/web/src/components/common/editor/NodeMenu.tsx @@ -12,7 +12,7 @@ import PostSelect from "../../models/post/PostSelect/PostSelect"; import { Lecture, PostType } from "@nice/common"; import { xmindColorPresets } from "./constant"; import { api } from "@nice/client"; -import { env } from "@web/src/env"; +import { useAuth } from "@web/src/providers/auth-provider"; interface NodeMenuProps { mind: MindElixirInstance; @@ -20,12 +20,13 @@ interface NodeMenuProps { //管理节点样式状态 const NodeMenu: React.FC = ({ mind }) => { + const [isOpen, setIsOpen] = useState(false); const [selectedFontColor, setSelectedFontColor] = useState(""); const [selectedBgColor, setSelectedBgColor] = useState(""); const [selectedSize, setSelectedSize] = useState(""); const [isBold, setIsBold] = useState(false); - + const { user} = useAuth(); const [urlMode, setUrlMode] = useState<"URL" | "POSTURL">("POSTURL"); const [url, setUrl] = useState(""); const [postId, setPostId] = useState(""); @@ -238,13 +239,15 @@ const NodeMenu: React.FC = ({ mind }) => { {urlMode === "POSTURL" ? ( { - if (typeof value === "string") { + if (typeof value === "string" ) { setPostId(value); } }} params={{ where: { type: PostType.LECTURE, + deletedAt: null, + authorId: user?.id, }, }} /> diff --git a/apps/web/src/components/common/uploader/ResourceShower.tsx b/apps/web/src/components/common/uploader/ResourceShower.tsx index 4401609..213a10b 100755 --- a/apps/web/src/components/common/uploader/ResourceShower.tsx +++ b/apps/web/src/components/common/uploader/ResourceShower.tsx @@ -7,8 +7,10 @@ import { formatFileSize, getCompressedImageUrl } from "@nice/utils"; export default function ResourcesShower({ resources = [], + isShowImage }: { resources: ResourceDto[]; + isShowImage?: boolean; }) { const { resources: dealedResources } = useMemo(() => { if (!resources) return { resources: [] }; @@ -36,7 +38,7 @@ export default function ResourcesShower({ const fileResources = dealedResources.filter((res) => !res.isImage); return (
- {imageResources.length > 0 && ( + {imageResources.length > 0 && isShowImage && ( {imageResources.map((resource) => ( diff --git a/apps/web/src/components/common/uploader/TusUploader.tsx b/apps/web/src/components/common/uploader/TusUploader.tsx index 17e2685..2ff3fad 100755 --- a/apps/web/src/components/common/uploader/TusUploader.tsx +++ b/apps/web/src/components/common/uploader/TusUploader.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState } from "react"; +import { ReactNode, useCallback, useState } from "react"; import { UploadOutlined, CheckCircleOutlined, @@ -12,6 +12,9 @@ export interface TusUploaderProps { onChange?: (value: string[]) => void; multiple?: boolean; allowTypes?: string[]; + style?:string + icon?:ReactNode, + description?:string } interface UploadingFile { @@ -27,6 +30,9 @@ export const TusUploader = ({ onChange, multiple = true, allowTypes = undefined, + style="", + icon = , + description = "点击或拖拽文件到此区域进行上传", }: TusUploaderProps) => { const { handleFileUpload, uploadProgress } = useTusUpload(); const [uploadingFiles, setUploadingFiles] = useState([]); @@ -137,7 +143,7 @@ export const TusUploader = ({ ); return ( -
+

- + {icon}

- 点击或拖拽文件到此区域进行上传 + {description}

{multiple ? "支持单个或批量上传文件" : "仅支持上传单个文件"} - {allowTypes && ( + {/* {allowTypes && ( 允许类型: {allowTypes.join(", ")} - )} + )} */}

diff --git a/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx b/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx index 6231436..cc6303a 100755 --- a/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx @@ -10,11 +10,11 @@ import { Skeleton } from "antd"; import ResourcesShower from "@web/src/components/common/uploader/ResourceShower"; import { useNavigate } from "react-router-dom"; import CourseDetailTitle from "./CourseDetailTitle"; - +import ReactPlayer from "react-player"; export const CourseDetailDisplayArea: React.FC = () => { // 创建滚动动画效果 const { - + isLoading, canEdit, lecture, @@ -41,6 +41,15 @@ export const CourseDetailDisplayArea: React.FC = () => { }} className="w-full bg-black rounded-lg ">
+ {/* { + console.log(error); + }} + /> */}
@@ -48,18 +57,22 @@ export const CourseDetailDisplayArea: React.FC = () => { )} {!lectureIsLoading && selectedLectureId && - lecture?.meta?.type === LectureType.ARTICLE && ( + (
- + {lecture?.meta?.type === LectureType.ARTICLE && ( + + )}
+ } + isShowImage = {lecture?.meta?.type === LectureType.ARTICLE} + >
diff --git a/apps/web/src/components/models/course/detail/CourseDetailTitle.tsx b/apps/web/src/components/models/course/detail/CourseDetailTitle.tsx index 3d23341..ff1a30e 100755 --- a/apps/web/src/components/models/course/detail/CourseDetailTitle.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailTitle.tsx @@ -23,15 +23,9 @@ export default function CourseDetailTitle() { {!selectedLectureId ? course?.title : lecture?.title}
- {course?.author?.showname && ( -
- 发布者: - {course?.author?.showname} -
- )} {course?.depts && course?.depts?.length > 0 && (
- 发布单位: + 发布单位: {course?.depts?.map((dept) => dept.name)}
)} diff --git a/apps/web/src/components/models/course/detail/CourseOperationBtns/CourseOperationBtns.tsx b/apps/web/src/components/models/course/detail/CourseOperationBtns/CourseOperationBtns.tsx index aa7124c..912386d 100644 --- a/apps/web/src/components/models/course/detail/CourseOperationBtns/CourseOperationBtns.tsx +++ b/apps/web/src/components/models/course/detail/CourseOperationBtns/CourseOperationBtns.tsx @@ -1,97 +1,40 @@ -import { useAuth } from "@web/src/providers/auth-provider"; -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { CourseDetailContext } from "../PostDetailContext"; -import { useStaff } from "@nice/client"; +import { api } from "@nice/client"; import { - CheckCircleOutlined, - CloseCircleOutlined, + DeleteTwoTone, EditTwoTone, - LoginOutlined, + ExclamationCircleFilled, } from "@ant-design/icons"; import toast from "react-hot-toast"; import JoinButton from "./JoinButton"; +import { Modal } from "antd"; +import { useQueryClient } from "@tanstack/react-query"; +import { getQueryKey } from "@trpc/react-query"; export default function CourseOperationBtns() { - // const { isAuthenticated, user } = useAuth(); const navigate = useNavigate(); - const { post, canEdit, userIsLearning, setUserIsLearning } = - useContext(CourseDetailContext); - // const { update } = useStaff(); - // const [isHovered, setIsHovered] = useState(false); - - // const toggleLearning = async () => { - // if (!userIsLearning) { - // await update.mutateAsync({ - // where: { id: user?.id }, - // data: { - // learningPosts: { - // connect: { id: course.id }, - // }, - // }, - // }); - // setUserIsLearning(true); - // toast.success("加入学习成功"); - // } else { - // await update.mutateAsync({ - // where: { id: user?.id }, - // data: { - // learningPosts: { - // disconnect: { - // id: course.id, - // }, - // }, - // }, - // }); - // toast.success("退出学习成功"); - // setUserIsLearning(false); - // } - // }; + const { post, canEdit } = useContext(CourseDetailContext); return ( <> - {/* {isAuthenticated && ( -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - className={`flex px-1 py-0.5 gap-1 hover:cursor-pointer transition-all ${ - userIsLearning - ? isHovered - ? "text-red-500 border-red-500 rounded-md " - : "text-green-500 " - : "text-primary " - }`}> - {userIsLearning ? ( - isHovered ? ( - - ) : ( - - ) - ) : ( - - )} - - {userIsLearning - ? isHovered - ? "退出学习" - : "正在学习" - : "加入学习"} - -
- )} */} {canEdit && ( -
{ - const url = post?.id - ? `/course/${post?.id}/editor` - : "/course/editor"; - navigate(url); - }}> - - {"编辑课程"} -
+ <> +
{ + const url = post?.id + ? `/course/${post?.id}/editor` + : "/course/editor"; + navigate(url); + }}> + + {"编辑课程"} +
+ + + )} ); 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 f623660..c5c30b9 100755 --- a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx +++ b/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx @@ -13,6 +13,8 @@ import { api, usePost } from "@nice/client"; import { useNavigate } from "react-router-dom"; import { z } from "zod"; import { useAuth } from "@web/src/providers/auth-provider"; +import { getQueryKey } from "@trpc/react-query"; +import { useQueryClient } from "@tanstack/react-query"; export type CourseFormData = { title: string; @@ -26,6 +28,7 @@ export type CourseFormData = { interface CourseEditorContextType { onSubmit: (values: CourseFormData) => Promise; + handleDeleteCourse: () => Promise; editId?: string; course?: CourseDto; taxonomies?: Taxonomy[]; // 根据实际类型调整 @@ -45,6 +48,7 @@ export function CourseFormProvider({ }: CourseFormProviderProps) { const [form] = Form.useForm(); const { create, update, createCourse } = usePost(); + const queryClient = useQueryClient(); const { user } = useAuth(); const { data: course }: { data: CourseDto } = api.post.findFirst.useQuery( { @@ -60,6 +64,11 @@ export function CourseFormProvider({ } = api.taxonomy.getAll.useQuery({ type: ObjectType.COURSE, }); + const softDeletePostDescendant = api.post.softDeletePostDescendant.useMutation({ + onSuccess:()=>{ + queryClient.invalidateQueries({ queryKey: getQueryKey(api.post) }); + } + }) const navigate = useNavigate(); useEffect(() => { @@ -92,7 +101,15 @@ export function CourseFormProvider({ form.setFieldsValue(formData); } }, [course, form]); - + const handleDeleteCourse = async () => { + if(editId){ + await softDeletePostDescendant.mutateAsync({ + ancestorId: editId, + }); + + navigate("/courses"); + } + } const onSubmit = async (values: any) => { const sections = values?.sections || []; const deptIds = values?.deptIds || []; @@ -172,6 +189,7 @@ export function CourseFormProvider({ course, taxonomies, form, + handleDeleteCourse }}>
{ + const queryClient = useQueryClient(); const { editId } = useCourseEditor(); const sensors = useSensors( useSensor(PointerSensor), @@ -71,7 +74,11 @@ const CourseContentForm: React.FC = () => { ids: newItems.map((item) => item.id), }); }; - + const softDeletePostDescendant = api.post.softDeletePostDescendant.useMutation({ + onSuccess:()=>{ + queryClient.invalidateQueries({ queryKey: getQueryKey(api.post) }); + } + }) return (
@@ -93,8 +100,8 @@ const CourseContentForm: React.FC = () => { field={section} remove={async () => { if (section?.id) { - await softDeleteByIds.mutateAsync({ - ids: [section.id], + await softDeletePostDescendant.mutateAsync({ + ancestorId: section.id, }); } setItems(sections); diff --git a/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx b/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx index b56f2cc..368f1e7 100755 --- a/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx +++ b/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx @@ -3,6 +3,8 @@ import { CaretRightOutlined, SaveOutlined, CaretDownOutlined, + PaperClipOutlined, + PlaySquareOutlined, } from "@ant-design/icons"; import { Form, Button, Input, Select, Space } from "antd"; import React, { useState } from "react"; @@ -86,12 +88,12 @@ export const SortableLecture: React.FC = ({ resources: [videoUrlId, ...fileIds].filter(Boolean)?.length > 0 ? { - connect: [videoUrlId, ...fileIds] - .filter(Boolean) - .map((fileId) => ({ - fileId, - })), - } + connect: [videoUrlId, ...fileIds] + .filter(Boolean) + .map((fileId) => ({ + fileId, + })), + } : undefined, content: values?.content, }, @@ -115,12 +117,12 @@ export const SortableLecture: React.FC = ({ resources: [videoUrlId, ...fileIds].filter(Boolean)?.length > 0 ? { - set: [videoUrlId, ...fileIds] - .filter(Boolean) - .map((fileId) => ({ - fileId, - })), - } + set: [videoUrlId, ...fileIds] + .filter(Boolean) + .map((fileId) => ({ + fileId, + })), + } : undefined, content: values?.content, }, @@ -175,22 +177,42 @@ export const SortableLecture: React.FC = ({ />
-
+
{lectureType === LectureType.VIDEO ? ( - - - + <> +
+ {/* 添加视频 */} + + } + description="点击或拖拽视频到此区域进行上传" + /> + +
+
+ {/* 添加附件 */} + + } + description="点击或拖拽附件到此区域进行上传" + /> + +
+ + ) : (
= ({ ]}> diff --git a/apps/web/src/components/models/course/editor/layout/CourseEditorHeader.tsx b/apps/web/src/components/models/course/editor/layout/CourseEditorHeader.tsx index a3197f8..c294d54 100755 --- a/apps/web/src/components/models/course/editor/layout/CourseEditorHeader.tsx +++ b/apps/web/src/components/models/course/editor/layout/CourseEditorHeader.tsx @@ -1,10 +1,11 @@ -import { ArrowLeftOutlined, ClockCircleOutlined } from "@ant-design/icons"; -import { Button, Tag, Typography } from "antd"; +import { ArrowLeftOutlined, ClockCircleOutlined, ExclamationCircleFilled } from "@ant-design/icons"; +import { Button, Modal, Tag, Typography } from "antd"; import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { CourseStatus, CourseStatusLabel } from "@nice/common"; import { useCourseEditor } from "../context/CourseEditorContext"; import { useAuth } from "@web/src/providers/auth-provider"; +import toast from "react-hot-toast"; const { Title } = Typography; @@ -18,8 +19,8 @@ const courseStatusVariant: Record = { export default function CourseEditorHeader() { const navigate = useNavigate(); const { user, hasSomePermissions } = useAuth(); - - const { onSubmit, course, form } = useCourseEditor(); + const { confirm } = Modal; + const { onSubmit, course, form, handleDeleteCourse, editId } = useCourseEditor(); const handleSave = () => { try { @@ -30,7 +31,26 @@ export default function CourseEditorHeader() { console.log(err); } }; - + const showDeleteConfirm = () => { + confirm({ + title: '确定删除该课程吗', + icon: , + content: '', + okText: '删除', + okType: 'danger', + cancelText: '取消', + async onOk() { + console.log('OK'); + console.log(editId) + await handleDeleteCourse() + toast.success('课程已删除') + navigate("/courses"); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; return (
@@ -70,16 +90,27 @@ export default function CourseEditorHeader() { )} */}
- + } + + > + 保存 + +
); diff --git a/apps/web/src/components/models/course/list/PostList.tsx b/apps/web/src/components/models/course/list/PostList.tsx index a276060..b0e69f5 100755 --- a/apps/web/src/components/models/course/list/PostList.tsx +++ b/apps/web/src/components/models/course/list/PostList.tsx @@ -44,7 +44,8 @@ export default function PostList({ const posts = useMemo(() => { if (data && !isLoading) { - return data?.items; + console.log(data?.items) + return data?.items.filter(item=>item.deletedAt === null); } return []; }, [data, isLoading]); diff --git a/apps/web/src/components/models/post/PostSelect/PostSelect.tsx b/apps/web/src/components/models/post/PostSelect/PostSelect.tsx index f97d148..63b01ee 100644 --- a/apps/web/src/components/models/post/PostSelect/PostSelect.tsx +++ b/apps/web/src/components/models/post/PostSelect/PostSelect.tsx @@ -13,6 +13,7 @@ export default function PostSelect({ placeholder = "请选择课时", params = { where: {}, select: {} }, className, + createdById, }: { value?: string | string[]; onChange?: (value: string | string[]) => void; @@ -22,6 +23,7 @@ export default function PostSelect({ select?: Prisma.PostSelect; }; className?: string; + createdById?: string; }) { const [searchValue, setSearch] = useState(""); const searchCondition: Prisma.PostWhereInput = useMemo(() => { diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index 97552d0..e48165d 100755 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -128,7 +128,7 @@ export const routes: CustomRouteObject[] = [ path: "course", children: [ { - path: ":id?/editor", + path: ":id?/editor", element: ( diff --git a/apps/web/web-dist.zip b/apps/web/web-dist.zip new file mode 100644 index 0000000..2e31d07 Binary files /dev/null and b/apps/web/web-dist.zip differ diff --git a/config/nginx/entrypoint.sh b/config/nginx/entrypoint.sh index 136c830..7a546eb 100755 --- a/config/nginx/entrypoint.sh +++ b/config/nginx/entrypoint.sh @@ -15,7 +15,7 @@ done if [ -f "/usr/share/nginx/html/index.html" ]; then # Use envsubst to replace environment variable placeholders echo "Processing /usr/share/nginx/html/index.html" - envsubst < /usr/share/nginx/html/index.temp > /usr/share/nginx/html/index.html.tmp + envsubst < /usr/share/nginx/html/index.template > /usr/share/nginx/html/index.html.tmp mv /usr/share/nginx/html/index.html.tmp /usr/share/nginx/html/index.html echo "Processed content:" cat /usr/share/nginx/html/index.html diff --git a/packages/common/src/models/select.ts b/packages/common/src/models/select.ts index f21f1a6..ba35ec2 100755 --- a/packages/common/src/models/select.ts +++ b/packages/common/src/models/select.ts @@ -100,6 +100,7 @@ export const courseDetailSelect: Prisma.PostSelect = { // isFeatured: true, createdAt: true, updatedAt: true, + deletedAt: true, // 关联表选择 terms: { select: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0efc82c..c92a770 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -386,6 +386,9 @@ importers: react-hot-toast: specifier: ^2.4.1 version: 2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-player: + specifier: ^2.16.0 + version: 2.16.0(react@18.2.0) react-resizable: specifier: ^3.0.5 version: 3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -5603,6 +5606,9 @@ packages: enquirer: optional: true + load-script@1.0.0: + resolution: {integrity: sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==} + load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5818,6 +5824,9 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + meow@8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} @@ -6645,6 +6654,9 @@ packages: react: '>= 16.3.0' react-dom: '>= 16.3.0' + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + react-hook-form@7.54.2: resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==} engines: {node: '>=18.0.0'} @@ -6664,6 +6676,11 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-player@2.16.0: + resolution: {integrity: sha512-mAIPHfioD7yxO0GNYVFD1303QFtI3lyyQZLY229UEAp/a10cSW+hPcakg0Keq8uWJxT2OiT/4Gt+Lc9bD6bJmQ==} + peerDependencies: + react: '>=16.6.0' + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -13723,6 +13740,8 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 8.1.0 + load-script@1.0.0: {} + load-tsconfig@0.2.5: {} loader-runner@4.3.0: {} @@ -13899,6 +13918,8 @@ snapshots: dependencies: fs-monkey: 1.0.6 + memoize-one@5.2.1: {} + meow@8.1.2: dependencies: '@types/minimist': 1.2.5 @@ -14774,6 +14795,8 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + react-fast-compare@3.2.2: {} + react-hook-form@7.54.2(react@18.2.0): dependencies: react: 18.2.0 @@ -14789,6 +14812,15 @@ snapshots: react-is@18.3.1: {} + react-player@2.16.0(react@18.2.0): + dependencies: + deepmerge: 4.3.1 + load-script: 1.0.0 + memoize-one: 5.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-fast-compare: 3.2.2 + react-refresh@0.14.2: {} react-resizable@3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):