diff --git a/apps/server/src/models/post/post.service.ts b/apps/server/src/models/post/post.service.ts index e260698..5048fff 100755 --- a/apps/server/src/models/post/post.service.ts +++ b/apps/server/src/models/post/post.service.ts @@ -98,11 +98,10 @@ export class PostService extends BaseTreeService { async createCourse( args: { courseDetail: Prisma.PostCreateArgs; - sections?: z.infer[]; }, params: { staff?: UserProfile; tx?: Prisma.TransactionClient }, ) { - const { courseDetail, sections } = args; + const { courseDetail } = args; // If no transaction is provided, create a new one if (!params.tx) { return await db.$transaction(async (tx) => { @@ -112,20 +111,6 @@ export class PostService extends BaseTreeService { console.log('courseDetail', courseDetail); const createdCourse = await this.create(courseDetail, courseParams); // If sections are provided, create them - if (sections && sections.length > 0) { - const sectionPromises = sections.map((section) => - this.createSection( - { - courseId: createdCourse.id, - title: section.title, - lectures: section.lectures, - }, - courseParams, - ), - ); - // Create all sections (and their lectures) in parallel - await Promise.all(sectionPromises); - } return createdCourse; }); } @@ -133,21 +118,6 @@ export class PostService extends BaseTreeService { console.log('courseDetail', courseDetail); const createdCourse = await this.create(courseDetail, params); // If sections are provided, create them - if (sections && sections.length > 0) { - const sectionPromises = sections.map((section) => - this.createSection( - { - courseId: createdCourse.id, - title: section.title, - lectures: section.lectures, - }, - params, - ), - ); - // Create all sections (and their lectures) in parallel - await Promise.all(sectionPromises); - } - return createdCourse; } async create( diff --git a/apps/server/src/models/post/utils.ts b/apps/server/src/models/post/utils.ts index c257663..6ca6139 100755 --- a/apps/server/src/models/post/utils.ts +++ b/apps/server/src/models/post/utils.ts @@ -161,12 +161,12 @@ export async function setCourseInfo({ data }: { data: Post }) { sections.map((section) => section.id).includes(descendant.parentId) ); }); - + const lectureCount = lectures?.length || 0; sections.forEach((section) => { section.lectures = lectures.filter( (lecture) => lecture.parentId === section.id, ); }); - Object.assign(data, { sections }); + Object.assign(data, { sections, lectureCount }); } } diff --git a/apps/server/src/tasks/init/gendev.service.ts b/apps/server/src/tasks/init/gendev.service.ts index ab8be57..e8c3dfa 100755 --- a/apps/server/src/tasks/init/gendev.service.ts +++ b/apps/server/src/tasks/init/gendev.service.ts @@ -45,6 +45,7 @@ export class GenDevService { await this.generateDepartments(3, 6); await this.generateTerms(2, 6); await this.generateStaffs(4); + await this.generateCourses(); } catch (err) { this.logger.error(err); } @@ -142,6 +143,64 @@ export class GenDevService { collectChildren(domainId); return children; } + private async generateCourses(countPerCate: number = 3) { + const titleList = [ + '计算机科学导论', + '数据结构与算法', + '网络安全', + '机器学习', + '数据库管理系统', + 'Web开发', + '移动应用开发', + '人工智能', + '计算机网络', + '操作系统', + '数字信号处理', + '无线通信', + '信息论', + '密码学', + '计算机图形学', + ]; + + if (!this.counts.courseCount) { + this.logger.log('Generating courses...'); + const depts = await db.department.findMany({ + select: { id: true, name: true }, + }); + const cates = await db.term.findMany({ + where: { + taxonomy: { slug: TaxonomySlug.CATEGORY }, + }, + select: { id: true, name: true }, + }); + const total = cates.length * countPerCate; + const levels = await db.term.findMany({ + where: { + taxonomy: { slug: TaxonomySlug.LEVEL }, + }, + select: { id: true, name: true }, + }); + for (const cate of cates) { + for (let i = 0; i < countPerCate; i++) { + const randomTitle = `${titleList[Math.floor(Math.random() * titleList.length)]} ${Math.random().toString(36).substring(7)}`; + const randomLevelId = + levels[Math.floor(Math.random() * levels.length)].id; + const randomDeptId = + depts[Math.floor(Math.random() * depts.length)].id; + + await this.createCourse( + randomTitle, + randomDeptId, + cate.id, + randomLevelId, + ); + this.logger.log( + `Generated ${this.deptGeneratedCount}/${total} departments`, + ); + } + } + } + } private async generateStaffs(countPerDept: number = 3) { if (this.counts.staffCount === 1) { this.logger.log('Generating staffs...'); @@ -214,16 +273,19 @@ export class GenDevService { type: PostType.COURSE, title: title, updatedAt: dayjs().toDate(), - // depts: { - // connect: { - // id: deptId, - // }, - // }, - // terms:{ - // connect:[cateId,levelId].map - // } + depts: { + connect: { + id: deptId, + }, + }, + terms: { + connect: [cateId, levelId].map((id) => ({ + id: id, + })), + }, }, }); + return course; } private async createDepartment( name: string, diff --git a/apps/server/src/tasks/init/utils.ts b/apps/server/src/tasks/init/utils.ts index 292eb9e..d55dcec 100755 --- a/apps/server/src/tasks/init/utils.ts +++ b/apps/server/src/tasks/init/utils.ts @@ -1,34 +1,44 @@ -import { db, getRandomElement, getRandomIntInRange, getRandomTimeInterval, } from '@nice/common'; +import { + db, + getRandomElement, + getRandomIntInRange, + getRandomTimeInterval, + PostType, +} from '@nice/common'; import dayjs from 'dayjs'; export interface DevDataCounts { - deptCount: number; + deptCount: number; - staffCount: number - termCount: number + staffCount: number; + termCount: number; + courseCount: number; } export async function getCounts(): Promise { - const counts = { - deptCount: await db.department.count(), + const counts = { + deptCount: await db.department.count(), - staffCount: await db.staff.count(), - termCount: await db.term.count(), - }; - return counts; + staffCount: await db.staff.count(), + termCount: await db.term.count(), + courseCount: await db.post.count({ + where: { + type: PostType.COURSE, + }, + }), + }; + return counts; } export function capitalizeFirstLetter(string: string) { - return string.charAt(0).toUpperCase() + string.slice(1); + return string.charAt(0).toUpperCase() + string.slice(1); } export function getRandomImageLinks(count: number = 5): string[] { - const baseUrl = 'https://picsum.photos/200/300?random='; - const imageLinks: string[] = []; + const baseUrl = 'https://picsum.photos/200/300?random='; + const imageLinks: string[] = []; - for (let i = 0; i < count; i++) { - // 生成随机数以确保每个链接都是唯一的 - const randomId = Math.floor(Math.random() * 1000); - imageLinks.push(`${baseUrl}${randomId}`); - } + for (let i = 0; i < count; i++) { + // 生成随机数以确保每个链接都是唯一的 + const randomId = Math.floor(Math.random() * 1000); + imageLinks.push(`${baseUrl}${randomId}`); + } - return imageLinks; + return imageLinks; } - - 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 4e4d641..dcf9a32 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 @@ -24,6 +24,7 @@ import { CourseContentFormHeader } from "./CourseContentFormHeader"; import { CourseSectionEmpty } from "./CourseSectionEmpty"; import { SortableSection } from "./SortableSection"; import { LectureList } from "./LectureList"; +import toast from "react-hot-toast"; const CourseContentForm: React.FC = () => { const { editId } = useCourseEditor(); @@ -110,10 +111,11 @@ const CourseContentForm: React.FC = () => { icon={} className="mt-4" onClick={() => { - setItems([ - ...items.filter((item) => !!item.id), - { id: null, title: "" }, - ]); + if (items.some((item) => item.id === null)) { + 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 7bad0ce..a9cf666 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 @@ -38,6 +38,7 @@ import { useCourseEditor } from "../../context/CourseEditorContext"; import { usePost } from "@nice/client"; import { LectureData, SectionData } from "./interface"; import { SortableLecture } from "./SortableLecture"; +import toast from "react-hot-toast"; interface LectureListProps { field: SectionData; @@ -135,16 +136,20 @@ export const LectureList: React.FC = ({ icon={} className="mt-4" onClick={() => { - setItems((prevItems) => [ - ...prevItems.filter((item) => !!item.id), - { - id: null, - title: "", - meta: { - type: LectureType.ARTICLE, - }, - } as Lecture, - ]); + if (items.some((item) => item.id === null)) { + toast.error("请先保存当前编辑章节"); + } else { + setItems((prevItems) => [ + ...prevItems.filter((item) => !!item.id), + { + id: null, + title: "", + meta: { + type: LectureType.ARTICLE, + }, + } as Lecture, + ]); + } }}> 添加课时 diff --git a/packages/common/src/models/post.ts b/packages/common/src/models/post.ts index fa030b6..de740d5 100755 --- a/packages/common/src/models/post.ts +++ b/packages/common/src/models/post.ts @@ -78,4 +78,5 @@ export type CourseDto = Course & { enrollments?: Enrollment[]; sections?: SectionDto[]; terms: Term[]; + lectureCount?: number; };