diff --git a/apps/server/src/models/app-config/app-config.router.ts b/apps/server/src/models/app-config/app-config.router.ts index ece1b35..882fa16 100755 --- a/apps/server/src/models/app-config/app-config.router.ts +++ b/apps/server/src/models/app-config/app-config.router.ts @@ -4,44 +4,48 @@ import { AppConfigService } from './app-config.service'; import { z, ZodType } from 'zod'; import { Prisma } from '@nice/common'; import { RealtimeServer } from '@server/socket/realtime/realtime.server'; -const AppConfigUncheckedCreateInputSchema: ZodType = z.any() -const AppConfigUpdateArgsSchema: ZodType = z.any() -const AppConfigDeleteManyArgsSchema: ZodType = z.any() -const AppConfigFindFirstArgsSchema: ZodType = z.any() +const AppConfigUncheckedCreateInputSchema: ZodType = + z.any(); +const AppConfigUpdateArgsSchema: ZodType = z.any(); +const AppConfigDeleteManyArgsSchema: ZodType = + z.any(); +const AppConfigFindFirstArgsSchema: ZodType = + z.any(); @Injectable() export class AppConfigRouter { - constructor( - private readonly trpc: TrpcService, - private readonly appConfigService: AppConfigService, - private readonly realtimeServer: RealtimeServer - ) { } - router = this.trpc.router({ - create: this.trpc.protectProcedure - .input(AppConfigUncheckedCreateInputSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.appConfigService.create({ data: input }); - }), - update: this.trpc.protectProcedure - .input(AppConfigUpdateArgsSchema) - .mutation(async ({ ctx, input }) => { - - const { staff } = ctx; - return await this.appConfigService.update(input); - }), - deleteMany: this.trpc.protectProcedure.input(AppConfigDeleteManyArgsSchema).mutation(async ({ input }) => { - return await this.appConfigService.deleteMany(input) - }), - findFirst: this.trpc.protectProcedure.input(AppConfigFindFirstArgsSchema). - query(async ({ input }) => { - - return await this.appConfigService.findFirst(input) - }), - clearRowCache: this.trpc.protectProcedure.mutation(async () => { - return await this.appConfigService.clearRowCache() - }), - getClientCount: this.trpc.protectProcedure.query(() => { - return this.realtimeServer.getClientCount() - }) - }); + constructor( + private readonly trpc: TrpcService, + private readonly appConfigService: AppConfigService, + private readonly realtimeServer: RealtimeServer, + ) {} + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(AppConfigUncheckedCreateInputSchema) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.appConfigService.create({ data: input }); + }), + update: this.trpc.protectProcedure + .input(AppConfigUpdateArgsSchema) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.appConfigService.update(input); + }), + deleteMany: this.trpc.protectProcedure + .input(AppConfigDeleteManyArgsSchema) + .mutation(async ({ input }) => { + return await this.appConfigService.deleteMany(input); + }), + findFirst: this.trpc.protectProcedure + .input(AppConfigFindFirstArgsSchema) + .query(async ({ input }) => { + return await this.appConfigService.findFirst(input); + }), + clearRowCache: this.trpc.protectProcedure.mutation(async () => { + return await this.appConfigService.clearRowCache(); + }), + getClientCount: this.trpc.protectProcedure.query(() => { + return this.realtimeServer.getClientCount(); + }), + }); } diff --git a/apps/server/src/models/app-config/app-config.service.ts b/apps/server/src/models/app-config/app-config.service.ts index 733e620..bd003d7 100755 --- a/apps/server/src/models/app-config/app-config.service.ts +++ b/apps/server/src/models/app-config/app-config.service.ts @@ -1,10 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - db, - ObjectType, - Prisma, -} from '@nice/common'; - +import { db, ObjectType, Prisma } from '@nice/common'; import { BaseService } from '../base/base.service'; import { deleteByPattern } from '@server/utils/redis/utils'; @@ -12,10 +7,10 @@ import { deleteByPattern } from '@server/utils/redis/utils'; @Injectable() export class AppConfigService extends BaseService { constructor() { - super(db, "appConfig"); + super(db, 'appConfig'); } async clearRowCache() { - await deleteByPattern("row-*") - return true + await deleteByPattern('row-*'); + return true; } } diff --git a/apps/server/src/models/post/post.router.ts b/apps/server/src/models/post/post.router.ts index 2c7ae56..bcde094 100755 --- a/apps/server/src/models/post/post.router.ts +++ b/apps/server/src/models/post/post.router.ts @@ -58,13 +58,13 @@ export class PostRouter { const { staff } = ctx; return await this.postService.update(input, staff); }), - findById: this.trpc.protectProcedure + findById: this.trpc.procedure .input(z.object({ id: z.string(), args: PostFindFirstArgsSchema })) .query(async ({ ctx, input }) => { const { staff } = ctx; return await this.postService.findById(input.id, input.args); }), - findMany: this.trpc.protectProcedure + findMany: this.trpc.procedure .input(PostFindManyArgsSchema) .query(async ({ ctx, input }) => { const { staff } = ctx; @@ -84,7 +84,7 @@ export class PostRouter { .mutation(async ({ input }) => { return await this.postService.deleteMany(input); }), - findManyWithCursor: this.trpc.protectProcedure + findManyWithCursor: this.trpc.procedure .input( z.object({ cursor: z.any().nullish(), diff --git a/apps/server/src/models/post/post.service.ts b/apps/server/src/models/post/post.service.ts index 5048fff..675dda4 100755 --- a/apps/server/src/models/post/post.service.ts +++ b/apps/server/src/models/post/post.service.ts @@ -101,6 +101,11 @@ export class PostService extends BaseTreeService { }, params: { staff?: UserProfile; tx?: Prisma.TransactionClient }, ) { + // const await db.post.findMany({ + // where: { + // type: PostType.COURSE, + // }, + // }); const { courseDetail } = args; // If no transaction is provided, create a new one if (!params.tx) { diff --git a/apps/server/src/tasks/init/gendev.service.ts b/apps/server/src/tasks/init/gendev.service.ts index e8c3dfa..e2c31c9 100755 --- a/apps/server/src/tasks/init/gendev.service.ts +++ b/apps/server/src/tasks/init/gendev.service.ts @@ -45,7 +45,7 @@ export class GenDevService { await this.generateDepartments(3, 6); await this.generateTerms(2, 6); await this.generateStaffs(4); - await this.generateCourses(); + await this.generateCourses(8); } catch (err) { this.logger.error(err); } @@ -253,9 +253,11 @@ export class GenDevService { { name: '中级', taxonomyId: taxLevel.id }, { name: '高级', taxonomyId: taxLevel.id }, // 改为高级更合理 ]; - await this.termService.createMany({ - data: termsToCreate, - }); + for (const termData of termsToCreate) { + await this.termService.create({ + data: termData, + }); + } console.log('created level terms'); } catch (error) { console.error('Failed to create level terms:', error); diff --git a/apps/web/src/app/admin/base-setting/page.tsx b/apps/web/src/app/admin/base-setting/page.tsx index ae1060d..82601ee 100755 --- a/apps/web/src/app/admin/base-setting/page.tsx +++ b/apps/web/src/app/admin/base-setting/page.tsx @@ -1,33 +1,26 @@ -import { - AppConfigSlug, - BaseSetting, - RolePerms, -} from "@nice/common"; +import { AppConfigSlug, BaseSetting, RolePerms } from "@nice/common"; import { useContext, useEffect, useState } from "react"; -import { - Button, - Form, - Input, - message, - theme, -} from "antd"; +import { Button, Form, Input, message, theme } from "antd"; import { useAppConfig } from "@nice/client"; import { useAuth } from "@web/src/providers/auth-provider"; import FixedHeader from "@web/src/components/layout/fix-header"; import { useForm } from "antd/es/form/Form"; -import { api } from "@nice/client" +import { api } from "@nice/client"; import { MainLayoutContext } from "../layout"; export default function BaseSettingPage() { const { update, baseSetting } = useAppConfig(); - const utils = api.useUtils() - const [form] = useForm() + const utils = api.useUtils(); + const [form] = useForm(); const { token } = theme.useToken(); - const { data: clientCount } = api.app_config.getClientCount.useQuery(undefined, { - refetchInterval: 3000, - refetchIntervalInBackground: true - }) + const { data: clientCount } = api.app_config.getClientCount.useQuery( + undefined, + { + refetchInterval: 3000, + refetchIntervalInBackground: true, + } + ); const [isFormChanged, setIsFormChanged] = useState(false); const [loading, setLoading] = useState(false); const { user, hasSomePermissions } = useAuth(); @@ -36,31 +29,27 @@ export default function BaseSettingPage() { setIsFormChanged(true); } function onResetClick() { - if (!form) - return + if (!form) return; if (!baseSetting) { form.resetFields(); } else { form.resetFields(); form.setFieldsValue(baseSetting); - } setIsFormChanged(false); } function onSaveClick() { - if (form) - form.submit(); + if (form) form.submit(); } async function onSubmit(values: BaseSetting) { setLoading(true); try { - await update.mutateAsync({ where: { slug: AppConfigSlug.BASE_SETTING, }, - data: { meta: JSON.stringify(values) } + data: { meta: { ...baseSetting, ...values } }, }); setIsFormChanged(false); message.success("已保存"); @@ -72,7 +61,6 @@ export default function BaseSettingPage() { } useEffect(() => { if (baseSetting && form) { - form.setFieldsValue(baseSetting); } }, [baseSetting, form]); @@ -103,7 +91,6 @@ export default function BaseSettingPage() { !hasSomePermissions(RolePerms.MANAGE_BASE_SETTING) } onFinish={onSubmit} - onFieldsChange={handleFieldsChange} layout="vertical"> {/*
- {
- app在线人数 -
- {clientCount && clientCount > 0 ? `${clientCount}人在线` : '无人在线'} + { +
+ app在线人数 +
+ {clientCount && clientCount > 0 + ? `${clientCount}人在线` + : "无人在线"} +
-
} + }
); diff --git a/apps/web/src/app/main/courses/components/CourseCard.tsx b/apps/web/src/app/main/courses/components/CourseCard.tsx index d549a1e..5d7c3cd 100755 --- a/apps/web/src/app/main/courses/components/CourseCard.tsx +++ b/apps/web/src/app/main/courses/components/CourseCard.tsx @@ -1,9 +1,10 @@ import { Card, Rate, Tag } from 'antd'; import { Course } from '../mockData'; import { UserOutlined, ClockCircleOutlined } from '@ant-design/icons'; +import { CourseDto } from '@nice/common'; interface CourseCardProps { - course: Course; + course: CourseDto; } export default function CourseCard({ course }: CourseCardProps) { @@ -14,7 +15,7 @@ export default function CourseCard({ course }: CourseCardProps) { cover={ {course.title} } @@ -23,7 +24,7 @@ export default function CourseCard({ course }: CourseCardProps) {

{course.title}

-

{course.instructor}

+

{course.subTitle}

{course.rating} @@ -31,7 +32,7 @@ export default function CourseCard({ course }: CourseCardProps) {
- {course.enrollments} 人在学 + {course.enrollments?.length} 人在学
@@ -39,8 +40,8 @@ export default function CourseCard({ course }: CourseCardProps) {
- {course.category} - {course.level} + {course.terms[0].name} + {course.terms[1].name}
diff --git a/apps/web/src/app/main/courses/components/CourseList.tsx b/apps/web/src/app/main/courses/components/CourseList.tsx index 3d67b1c..f3e3177 100755 --- a/apps/web/src/app/main/courses/components/CourseList.tsx +++ b/apps/web/src/app/main/courses/components/CourseList.tsx @@ -1,9 +1,9 @@ import { Pagination, Empty } from 'antd'; import { Course } from '../mockData'; import CourseCard from './CourseCard'; - +import {CourseDto} from '@nice/common' interface CourseListProps { - courses: Course[]; + courses: CourseDto[]; total: number; pageSize: number; currentPage: number; diff --git a/apps/web/src/app/main/courses/components/FilterSection.tsx b/apps/web/src/app/main/courses/components/FilterSection.tsx index 6de4312..6fe06c4 100755 --- a/apps/web/src/app/main/courses/components/FilterSection.tsx +++ b/apps/web/src/app/main/courses/components/FilterSection.tsx @@ -75,7 +75,7 @@ export default function FilterSection({ : ( <> - 全部课程 + 全部课程 {gateGory.categories.map(category => ( {category} diff --git a/apps/web/src/app/main/courses/page.tsx b/apps/web/src/app/main/courses/page.tsx index cfdd484..a411d60 100755 --- a/apps/web/src/app/main/courses/page.tsx +++ b/apps/web/src/app/main/courses/page.tsx @@ -1,59 +1,82 @@ -import { useState, useMemo } from "react"; +import { useState, useMemo, useEffect } 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"; +import { courseDetailSelect, CourseDto, LectureType, PostType } from "@nice/common"; +import { useSearchParams } from "react-router-dom"; +import { set } from "idb-keyval"; export default function CoursesPage() { 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 pageSize = 9; + const [isAll,setIsAll] = useState(true) + const [searchParams, setSearchParams] = useSearchParams(); + let coursesData = [] + let isCourseLoading = false + if(!searchParams.get('searchValue')){ + console.log('no category') + const {data,isLoading} = api.post.findManyWithPagination.useQuery({ + where: { + type: PostType.COURSE, + terms:isAll?{}:{ + some: { + OR : [ + 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; + select:courseDetailSelect }); - }, [selectedCategory, selectedLevel]); + coursesData = data?.items + isCourseLoading = isLoading + }else{ + console.log('searchValue:'+searchParams.get('searchValue')) + const searchValue = searchParams.get('searchValue') + const {data,isLoading} = api.post.findManyWithPagination.useQuery({ + where: { + type: PostType.COURSE, + OR:[ + { title: { contains: searchValue, mode: 'insensitive' } }, + { subTitle: { contains: searchValue, mode: 'insensitive' } }, + { content: { contains: searchValue, mode: 'insensitive' } }, + { terms: { some: { name: { contains: searchValue, mode: 'insensitive' } } } } + ] + }, + select:courseDetailSelect + }) + coursesData = data?.items + isCourseLoading = isLoading + } + useEffect(() => { + if(searchParams.get('searchValue')==''){ + setSelectedCategory(''); + setSelectedLevel('') + } + }, [searchParams.get('searchValue')]); + const filteredCourses = useMemo(() => { + return isCourseLoading ? [] : coursesData; + }, [isCourseLoading, coursesData, selectedCategory, selectedLevel]); - const paginatedCourses = useMemo(() => { + const paginatedCourses :CourseDto[]= useMemo(() => { const startIndex = (currentPage - 1) * pageSize; - return filteredCourses.slice(startIndex, startIndex + pageSize); + return isCourseLoading ? [] : (filteredCourses.slice(startIndex, startIndex + pageSize) as any as CourseDto[]); }, [filteredCourses, currentPage]); const handlePageChange = (page: number) => { setCurrentPage(page); window.scrollTo({ top: 0, behavior: "smooth" }); }; + useEffect(()=>{ + setCurrentPage(1) + },[]) + return (
@@ -69,10 +92,14 @@ export default function CoursesPage() { console.log(category); setSelectedCategory(category); setCurrentPage(1); + setIsAll(!category) + setSearchParams({ searchValue: ''}); }} onLevelChange={(level) => { setSelectedLevel(level); setCurrentPage(1); + setIsAll(!level) + setSearchParams({ searchValue: ''}); }} />
diff --git a/apps/web/src/app/main/home/components/CategorySection.tsx b/apps/web/src/app/main/home/components/CategorySection.tsx index 967336f..c2eff20 100755 --- a/apps/web/src/app/main/home/components/CategorySection.tsx +++ b/apps/web/src/app/main/home/components/CategorySection.tsx @@ -63,7 +63,7 @@ const CategorySection = () => { orderBy: { createdAt: 'desc', // 按创建时间降序排列 }, - take:10 + take:8 }) // 分类展示 const [displayedCategories,setDisplayedCategories] = useState([]) @@ -194,7 +194,11 @@ const CategorySection = () => { type="default" size="large" className="px-8 h-12 text-base font-medium hover:shadow-md transition-all duration-300" - onClick={() => setShowAll(!showAll)} + onClick={() => { + //setShowAll(!showAll) + navigate("/courses") + window.scrollTo({ top: 0, behavior: 'smooth' }) + }} > {showAll ? '收起' : '查看更多分类'} diff --git a/apps/web/src/app/main/home/components/HeroSection.tsx b/apps/web/src/app/main/home/components/HeroSection.tsx index 3dac945..9dae829 100755 --- a/apps/web/src/app/main/home/components/HeroSection.tsx +++ b/apps/web/src/app/main/home/components/HeroSection.tsx @@ -1,160 +1,135 @@ -import React, { useRef, useCallback } from 'react'; -import { Button, Carousel, Typography } from 'antd'; +import React, { useRef, useCallback } from "react"; +import { Button, Carousel, Typography } from "antd"; import { - TeamOutlined, - BookOutlined, - StarOutlined, - ClockCircleOutlined, - LeftOutlined, - RightOutlined, - EyeOutlined -} from '@ant-design/icons'; -import type { CarouselRef } from 'antd/es/carousel'; + TeamOutlined, + BookOutlined, + StarOutlined, + ClockCircleOutlined, + LeftOutlined, + RightOutlined, + EyeOutlined, +} from "@ant-design/icons"; +import type { CarouselRef } from "antd/es/carousel"; const { Title, Text } = Typography; interface CarouselItem { - title: string; - desc: string; - image: string; - action: string; - color: string; + title: string; + desc: string; + image: string; + action: string; + color: string; } interface PlatformStat { - icon: React.ReactNode; - value: string; - label: string; + icon: React.ReactNode; + value: string; + label: string; } const carouselItems: CarouselItem[] = [ - { - title: '探索编程世界', - desc: '从零开始学习编程,开启你的技术之旅', - image: '/images/banner1.jpg', - action: '立即开始', - color: 'from-blue-600/90' - }, - { - title: '人工智能课程', - desc: '掌握AI技术,引领未来发展', - image: '/images/banner2.jpg', - action: '了解更多', - color: 'from-purple-600/90' - } + { + title: "探索编程世界", + desc: "从零开始学习编程,开启你的技术之旅", + image: "/images/banner1.jpg", + action: "立即开始", + color: "from-blue-600/90", + }, + { + title: "人工智能课程", + desc: "掌握AI技术,引领未来发展", + image: "/images/banner2.jpg", + action: "了解更多", + color: "from-purple-600/90", + }, ]; const platformStats: PlatformStat[] = [ - { icon: , value: '50,000+', label: '注册学员' }, - { icon: , value: '1,000+', label: '精品课程' }, - // { icon: , value: '98%', label: '好评度' }, - { icon: , value: '100万+', label: '观看次数' } + { icon: , value: "50,000+", label: "注册学员" }, + { icon: , value: "1,000+", label: "精品课程" }, + // { icon: , value: '98%', label: '好评度' }, + { icon: , value: "100万+", label: "观看次数" }, ]; const HeroSection = () => { - const carouselRef = useRef(null); + const carouselRef = useRef(null); - const handlePrev = useCallback(() => { - carouselRef.current?.prev(); - }, []); + const handlePrev = useCallback(() => { + carouselRef.current?.prev(); + }, []); - const handleNext = useCallback(() => { - carouselRef.current?.next(); - }, []); + const handleNext = useCallback(() => { + carouselRef.current?.next(); + }, []); - return ( -
-
- - {carouselItems.map((item, index) => ( -
-
-
-
+ return ( +
+
+ + {carouselItems.map((item, index) => ( +
+
+
+
- {/* Content Container */} -
-
- - {item.title} - - - {item.desc} - - -
-
-
- ))} - + {/* Content Container */} +
+
+ ))} + - {/* Navigation Buttons */} - - -
+ {/* Navigation Buttons */} + + +
- {/* Stats Container */} -
-
- {platformStats.map((stat, index) => ( -
-
- {stat.icon} -
-
- {stat.value} -
-
- {stat.label} -
-
- ))} -
-
-
- ); + {/* Stats Container */} +
+
+ {platformStats.map((stat, index) => ( +
+
+ {stat.icon} +
+
+ {stat.value} +
+
+ {stat.label} +
+
+ ))} +
+
+
+ ); }; -export default HeroSection; \ No newline at end of file +export default HeroSection; diff --git a/apps/web/src/app/main/layout/MainHeader.tsx b/apps/web/src/app/main/layout/MainHeader.tsx index 80ea733..c8e0d2a 100755 --- a/apps/web/src/app/main/layout/MainHeader.tsx +++ b/apps/web/src/app/main/layout/MainHeader.tsx @@ -35,6 +35,12 @@ export function MainHeader() { className="w-72 rounded-full" value={searchValue} onChange={(e) => setSearchValue(e.target.value)} + onPressEnter={(e)=>{ + //console.log(e) + setSearchValue('') + navigate(`/courses/?searchValue=${searchValue}`) + window.scrollTo({ top: 0, behavior: "smooth" }); + }} /> {isAuthenticated && ( diff --git a/apps/web/src/components/common/uploader/AvatarUploader.tsx b/apps/web/src/components/common/uploader/AvatarUploader.tsx index 1ca2e20..abbb355 100755 --- a/apps/web/src/components/common/uploader/AvatarUploader.tsx +++ b/apps/web/src/components/common/uploader/AvatarUploader.tsx @@ -36,7 +36,7 @@ const AvatarUploader: React.FC = ({ const [file, setFile] = useState(null); const avatarRef = useRef(null); const [previewUrl, setPreviewUrl] = useState(value || ""); - + const [imageSrc, setImageSrc] = useState(value); const [compressedUrl, setCompressedUrl] = useState(value || ""); const [url, setUrl] = useState(value || ""); const [uploading, setUploading] = useState(false); @@ -45,7 +45,9 @@ const AvatarUploader: React.FC = ({ const [avatarKey, setAvatarKey] = useState(0); const { token } = theme.useToken(); useEffect(() => { - setPreviewUrl(value || ""); + if (!previewUrl || previewUrl?.length < 1) { + setPreviewUrl(value || ""); + } }, [value]); const handleChange = async (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0]; @@ -128,6 +130,14 @@ const AvatarUploader: React.FC = ({ ref={avatarRef} src={previewUrl} shape="square" + onError={() => { + if (value && previewUrl && imageSrc === value) { + // 当原始图片(value)加载失败时,切换到 previewUrl + setImageSrc(previewUrl); + return true; // 阻止默认的 fallback 行为,让它尝试新设置的 src + } + return false; // 如果 previewUrl 也失败了,显示默认头像 + }} className="w-full h-full object-cover" /> ) : ( 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 913bf34..6a12e2c 100755 --- a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx +++ b/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx @@ -109,11 +109,12 @@ export function CourseFormProvider({ } try { if (editId) { - await update.mutateAsync({ + const result = await update.mutateAsync({ where: { id: editId }, data: formattedValues, }); message.success("课程更新成功!"); + navigate(`/course/${result.id}/editor/content`); } else { const result = await createCourse.mutateAsync({ courseDetail: { @@ -127,8 +128,8 @@ export function CourseFormProvider({ }, sections, }); - navigate(`/course/${result.id}/editor`, { replace: true }); message.success("课程创建成功!"); + navigate(`/course/${result.id}/editor/content`); } form.resetFields(); } catch (error) { diff --git a/apps/web/src/components/models/course/editor/form/CourseContentForm/CourseContentForm.tsx b/apps/web/src/components/models/course/editor/form/CourseContentForm/CourseContentForm.tsx index dcf9a32..6b0e103 100755 --- a/apps/web/src/components/models/course/editor/form/CourseContentForm/CourseContentForm.tsx +++ b/apps/web/src/components/models/course/editor/form/CourseContentForm/CourseContentForm.tsx @@ -34,7 +34,7 @@ const CourseContentForm: React.FC = () => { coordinateGetter: sortableKeyboardCoordinates, }) ); - const { softDeleteByIds } = usePost(); + const { softDeleteByIds, updateOrderByIds } = usePost(); const { data: sections = [], isLoading } = api.post.findMany.useQuery( { where: { @@ -60,11 +60,15 @@ const CourseContentForm: React.FC = () => { const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (!over || active.id === over.id) return; - + let newItems = []; setItems((items) => { const oldIndex = items.findIndex((item) => item.id === active.id); const newIndex = items.findIndex((item) => item.id === over.id); - return arrayMove(items, oldIndex, newIndex); + newItems = arrayMove(items, oldIndex, newIndex); + return newItems; + }); + updateOrderByIds.mutateAsync({ + ids: newItems.map((item) => item.id), }); }; @@ -112,7 +116,7 @@ const CourseContentForm: React.FC = () => { className="mt-4" onClick={() => { if (items.some((item) => item.id === null)) { - toast.error("请先保存当前编辑章节"); + toast.error("请先保存当前编辑的章节"); } else { setItems([...items, { id: null, title: "" }]); } diff --git a/apps/web/src/components/models/course/editor/form/CourseContentForm/LectureList.tsx b/apps/web/src/components/models/course/editor/form/CourseContentForm/LectureList.tsx index a9cf666..b495edd 100755 --- a/apps/web/src/components/models/course/editor/form/CourseContentForm/LectureList.tsx +++ b/apps/web/src/components/models/course/editor/form/CourseContentForm/LectureList.tsx @@ -137,7 +137,7 @@ export const LectureList: React.FC = ({ className="mt-4" onClick={() => { if (items.some((item) => item.id === null)) { - toast.error("请先保存当前编辑章节"); + toast.error("请先保存当前编辑中的课时!"); } else { setItems((prevItems) => [ ...prevItems.filter((item) => !!item.id), diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index fbc63cd..7e9174f 100755 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -56,7 +56,7 @@ export const InitTaxonomies: { objectType?: string[]; }[] = [ { - name: "分类", + name: "课程分类", slug: TaxonomySlug.CATEGORY, objectType: [ObjectType.COURSE], }, diff --git a/packages/common/src/enum.ts b/packages/common/src/enum.ts index 32903b2..6264c9b 100755 --- a/packages/common/src/enum.ts +++ b/packages/common/src/enum.ts @@ -100,6 +100,7 @@ export enum RolePerms { } export enum AppConfigSlug { BASE_SETTING = "base_setting", + } // 资源类型的枚举,定义了不同类型的资源,以字符串值表示 export enum ResourceType { diff --git a/packages/common/src/models/select.ts b/packages/common/src/models/select.ts index 5417c1c..36e072d 100755 --- a/packages/common/src/models/select.ts +++ b/packages/common/src/models/select.ts @@ -70,28 +70,27 @@ export const courseDetailSelect: Prisma.PostSelect = { title: true, subTitle: true, content: true, - - level: true, - // requirements: true, - // objectives: true, - // skills: true, - // audiences: true, - // totalDuration: true, - // totalLectures: true, - // averageRating: true, - // numberOfReviews: true, - // numberOfStudents: true, - // completionRate: true, - state: true, // isFeatured: true, createdAt: true, - publishedAt: true, + updatedAt: true, // 关联表选择 - children: { - include: { - children: true, + terms:{ + select:{ + id:true, + name:true, + taxonomy:{ + select:{ + id:true, + slug:true + } + } + } + }, + enrollments: { + select: { + id: true, }, }, - enrollments: true, meta: true, + rating: true, };