chore: initial import to Nexus_Edu

This commit is contained in:
SpecialX
2025-11-28 19:23:19 +08:00
commit 38244630a7
153 changed files with 22541 additions and 0 deletions

View File

@@ -0,0 +1,526 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
previewFeatures = ["fullTextIndex"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
// =============================================
// 基础审计字段模型
// =============================================
model ApplicationUser {
id String @id @default(uuid()) @db.VarChar(36)
realName String @map("real_name") @db.VarChar(50)
studentId String? @map("student_id") @db.VarChar(20)
avatarUrl String? @map("avatar_url") @db.VarChar(500)
gender Gender @default(Male)
currentSchoolId String? @map("current_school_id") @db.VarChar(36)
accountStatus AccountStatus @map("account_status") @default(Active)
email String? @db.VarChar(100)
phone String? @db.VarChar(20)
bio String? @db.Text
passwordHash String @map("password_hash") @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
// Relations
classMemberships ClassMember[]
createdQuestions Question[] @relation("CreatedQuestions")
createdExams Exam[] @relation("CreatedExams")
submissions StudentSubmission[]
messages Message[]
@@index([studentId])
@@index([email])
@@index([phone])
@@index([currentSchoolId])
@@map("application_users")
}
enum Gender {
Male
Female
}
enum AccountStatus {
Active
Suspended
Graduated
}
// =============================================
// 组织架构模块
// =============================================
model School {
id String @id @default(uuid()) @db.VarChar(36)
name String @db.VarChar(100)
regionCode String @map("region_code") @db.VarChar(20)
address String? @db.VarChar(200)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
grades Grade[]
@@index([regionCode])
@@map("schools")
}
model Grade {
id String @id @default(uuid()) @db.VarChar(36)
schoolId String @map("school_id") @db.VarChar(36)
name String @db.VarChar(50)
sortOrder Int @map("sort_order")
enrollmentYear Int @map("enrollment_year")
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
school School @relation(fields: [schoolId], references: [id])
classes Class[]
@@index([schoolId])
@@index([enrollmentYear])
@@map("grades")
}
model Class {
id String @id @default(uuid()) @db.VarChar(36)
gradeId String @map("grade_id") @db.VarChar(36)
name String @db.VarChar(50)
inviteCode String @unique @map("invite_code") @db.VarChar(10)
headTeacherId String? @map("head_teacher_id") @db.VarChar(36)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
grade Grade @relation(fields: [gradeId], references: [id])
members ClassMember[]
assignments Assignment[]
schedules Schedule[]
@@index([gradeId])
@@map("classes")
}
model ClassMember {
id String @id @default(uuid()) @db.VarChar(36)
classId String @map("class_id") @db.VarChar(36)
userId String @map("user_id") @db.VarChar(36)
roleInClass ClassRole @map("role_in_class") @default(Student)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
user ApplicationUser @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([classId, userId])
@@index([userId])
@@map("class_members")
}
enum ClassRole {
Student
Monitor
Committee
Teacher
}
// =============================================
// 教材与知识图谱模块
// =============================================
model Subject {
id String @id @default(uuid()) @db.VarChar(36)
name String @db.VarChar(50)
code String @unique @db.VarChar(20)
icon String? @db.VarChar(50)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
textbooks Textbook[]
questions Question[]
exams Exam[]
@@map("subjects")
}
model Textbook {
id String @id @default(uuid()) @db.VarChar(36)
subjectId String @map("subject_id") @db.VarChar(36)
name String @db.VarChar(100)
publisher String @db.VarChar(100)
versionYear String @map("version_year") @db.VarChar(20)
coverUrl String? @map("cover_url") @db.VarChar(500)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
subject Subject @relation(fields: [subjectId], references: [id])
units TextbookUnit[]
@@index([subjectId])
@@map("textbooks")
}
model TextbookUnit {
id String @id @default(uuid()) @db.VarChar(36)
textbookId String @map("textbook_id") @db.VarChar(36)
name String @db.VarChar(100)
sortOrder Int @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
textbook Textbook @relation(fields: [textbookId], references: [id], onDelete: Cascade)
lessons TextbookLesson[]
@@index([textbookId])
@@index([sortOrder])
@@map("textbook_units")
}
model TextbookLesson {
id String @id @default(uuid()) @db.VarChar(36)
unitId String @map("unit_id") @db.VarChar(36)
name String @db.VarChar(100)
sortOrder Int @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
unit TextbookUnit @relation(fields: [unitId], references: [id], onDelete: Cascade)
knowledgePoints KnowledgePoint[]
@@index([unitId])
@@index([sortOrder])
@@map("textbook_lessons")
}
model KnowledgePoint {
id String @id @default(uuid()) @db.VarChar(36)
lessonId String @map("lesson_id") @db.VarChar(36)
parentKnowledgePointId String? @map("parent_knowledge_point_id") @db.VarChar(36)
name String @db.VarChar(200)
difficulty Int
description String? @db.Text
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
lesson TextbookLesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
parentKnowledgePoint KnowledgePoint? @relation("KnowledgePointHierarchy", fields: [parentKnowledgePointId], references: [id], onDelete: Cascade)
childKnowledgePoints KnowledgePoint[] @relation("KnowledgePointHierarchy")
questionAssociations QuestionKnowledge[]
@@index([lessonId])
@@index([parentKnowledgePointId])
@@index([difficulty])
@@map("knowledge_points")
}
// =============================================
// 题库资源模块
// =============================================
model Question {
id String @id @default(uuid()) @db.VarChar(36)
subjectId String @map("subject_id") @db.VarChar(36)
content String @db.Text
optionsConfig Json? @map("options_config")
questionType QuestionType @map("question_type")
answer String @db.Text
explanation String? @db.Text
difficulty Int @default(3)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
subject Subject @relation(fields: [subjectId], references: [id])
creator ApplicationUser @relation("CreatedQuestions", fields: [createdBy], references: [id])
knowledgePoints QuestionKnowledge[]
examNodes ExamNode[]
@@index([subjectId])
@@index([questionType])
@@index([difficulty])
@@fulltext([content])
@@map("questions")
}
model QuestionKnowledge {
id String @id @default(uuid()) @db.VarChar(36)
questionId String @map("question_id") @db.VarChar(36)
knowledgePointId String @map("knowledge_point_id") @db.VarChar(36)
weight Int @default(100)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)
knowledgePoint KnowledgePoint @relation(fields: [knowledgePointId], references: [id], onDelete: Cascade)
@@unique([questionId, knowledgePointId])
@@index([knowledgePointId])
@@map("question_knowledge")
}
enum QuestionType {
SingleChoice
MultipleChoice
TrueFalse
FillBlank
Subjective
}
// =============================================
// 试卷工程模块
// =============================================
model Exam {
id String @id @default(uuid()) @db.VarChar(36)
subjectId String @map("subject_id") @db.VarChar(36)
title String @db.VarChar(200)
totalScore Decimal @map("total_score") @default(0) @db.Decimal(5, 1)
suggestedDuration Int @map("suggested_duration")
status ExamStatus @default(Draft)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
subject Subject @relation(fields: [subjectId], references: [id])
creator ApplicationUser @relation("CreatedExams", fields: [createdBy], references: [id])
nodes ExamNode[]
assignments Assignment[]
@@index([subjectId])
@@index([status])
@@index([createdBy])
@@map("exams")
}
model ExamNode {
id String @id @default(uuid()) @db.VarChar(36)
examId String @map("exam_id") @db.VarChar(36)
parentNodeId String? @map("parent_node_id") @db.VarChar(36)
nodeType NodeType @map("node_type")
questionId String? @map("question_id") @db.VarChar(36)
title String? @db.VarChar(200)
description String? @db.Text
score Decimal @db.Decimal(5, 1)
sortOrder Int @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
exam Exam @relation(fields: [examId], references: [id], onDelete: Cascade)
parentNode ExamNode? @relation("ExamNodeHierarchy", fields: [parentNodeId], references: [id], onDelete: Cascade)
childNodes ExamNode[] @relation("ExamNodeHierarchy")
question Question? @relation(fields: [questionId], references: [id])
submissionDetails SubmissionDetail[]
@@index([examId])
@@index([parentNodeId])
@@index([sortOrder])
@@index([questionId])
@@map("exam_nodes")
}
enum ExamStatus {
Draft
Published
}
enum NodeType {
Group
Question
}
// =============================================
// 教学执行模块
// =============================================
model Assignment {
id String @id @default(uuid()) @db.VarChar(36)
examId String @map("exam_id") @db.VarChar(36)
classId String @map("class_id") @db.VarChar(36)
title String @db.VarChar(200)
startTime DateTime @map("start_time")
endTime DateTime @map("end_time")
allowLateSubmission Boolean @map("allow_late_submission") @default(false)
autoScoreEnabled Boolean @map("auto_score_enabled") @default(true)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
exam Exam @relation(fields: [examId], references: [id])
class Class @relation(fields: [classId], references: [id])
submissions StudentSubmission[]
@@index([examId])
@@index([classId])
@@index([startTime])
@@index([endTime])
@@map("assignments")
}
model StudentSubmission {
id String @id @default(uuid()) @db.VarChar(36)
assignmentId String @map("assignment_id") @db.VarChar(36)
studentId String @map("student_id") @db.VarChar(36)
submissionStatus SubmissionStatus @map("submission_status") @default(Pending)
submitTime DateTime? @map("submit_time")
timeSpentSeconds Int? @map("time_spent_seconds")
totalScore Decimal? @map("total_score") @db.Decimal(5, 1)
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
assignment Assignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
student ApplicationUser @relation(fields: [studentId], references: [id], onDelete: Cascade)
details SubmissionDetail[]
@@unique([assignmentId, studentId])
@@index([studentId])
@@index([submitTime])
@@index([submissionStatus])
@@map("student_submissions")
}
model SubmissionDetail {
id String @id @default(uuid()) @db.VarChar(36)
submissionId String @map("submission_id") @db.VarChar(36)
examNodeId String @map("exam_node_id") @db.VarChar(36)
studentAnswer String? @map("student_answer") @db.Text
gradingData Json? @map("grading_data")
score Decimal? @db.Decimal(5, 1)
judgement JudgementResult?
teacherComment String? @map("teacher_comment") @db.Text
createdAt DateTime @default(now()) @map("created_at")
createdBy String @map("created_by") @db.VarChar(36)
updatedAt DateTime @updatedAt @map("updated_at")
updatedBy String @map("updated_by") @db.VarChar(36)
isDeleted Boolean @default(false) @map("is_deleted")
submission StudentSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
examNode ExamNode @relation(fields: [examNodeId], references: [id])
@@unique([submissionId, examNodeId])
@@index([examNodeId])
@@index([judgement])
@@map("submission_details")
}
enum SubmissionStatus {
Pending
Submitted
Grading
Graded
}
enum JudgementResult {
Correct
Incorrect
Partial
}
// =============================================
// 辅助功能模块
// =============================================
model Message {
id String @id @default(uuid()) @db.VarChar(36)
userId String @map("user_id") @db.VarChar(36)
title String @db.VarChar(200)
content String @db.Text
type String @db.VarChar(20) // Announcement, Notification, Alert
senderName String @map("sender_name") @db.VarChar(50)
isRead Boolean @default(false) @map("is_read")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user ApplicationUser @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("messages")
}
model Schedule {
id String @id @default(uuid()) @db.VarChar(36)
classId String @map("class_id") @db.VarChar(36)
subject String @db.VarChar(50)
room String? @db.VarChar(50)
dayOfWeek Int @map("day_of_week") // 1-7
period Int // 1-8
startTime String @map("start_time") @db.VarChar(10) // HH:mm
endTime String @map("end_time") @db.VarChar(10) // HH:mm
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
@@index([classId])
@@map("schedules")
}

570
backend/prisma/seed.ts Normal file
View File

@@ -0,0 +1,570 @@
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcryptjs';
import { v4 as uuidv4 } from 'uuid';
const prisma = new PrismaClient();
async function main() {
console.log('🌱 开始创建种子数据...\n');
// 0. 清空数据 (按依赖顺序反向删除)
console.log('🧹 清空现有数据...');
await prisma.submissionDetail.deleteMany();
await prisma.studentSubmission.deleteMany();
await prisma.assignment.deleteMany();
await prisma.examNode.deleteMany();
await prisma.exam.deleteMany();
await prisma.questionKnowledge.deleteMany();
await prisma.question.deleteMany();
await prisma.knowledgePoint.deleteMany();
await prisma.textbookLesson.deleteMany();
await prisma.textbookUnit.deleteMany();
await prisma.textbook.deleteMany();
await prisma.subject.deleteMany();
await prisma.classMember.deleteMany();
await prisma.class.deleteMany();
await prisma.grade.deleteMany();
await prisma.school.deleteMany();
await prisma.applicationUser.deleteMany();
console.log(' ✅ 数据已清空');
// 1. 创建学校
console.log('📚 创建学校...');
const school = await prisma.school.create({
data: {
id: 'school-demo-001',
name: '北京示范高中',
regionCode: '110101',
address: '北京市东城区示范路100号',
createdBy: 'system',
updatedBy: 'system'
}
});
console.log(` ✅ 创建学校: ${school.name}`);
// 2. 创建年级
console.log('\n🎓 创建年级...');
const grades = await Promise.all([
prisma.grade.create({
data: {
id: 'grade-1',
schoolId: school.id,
name: '高一年级',
sortOrder: 1,
enrollmentYear: 2024,
createdBy: 'system',
updatedBy: 'system'
}
}),
prisma.grade.create({
data: {
id: 'grade-2',
schoolId: school.id,
name: '高二年级',
sortOrder: 2,
enrollmentYear: 2023,
createdBy: 'system',
updatedBy: 'system'
}
})
]);
console.log(` ✅ 创建 ${grades.length} 个年级`);
// 3. 创建科目
console.log('\n📖 创建科目...');
const subjects = await Promise.all([
prisma.subject.create({
data: {
id: 'subject-math',
name: '数学',
code: 'MATH',
icon: '📐',
createdBy: 'system',
updatedBy: 'system'
}
}),
prisma.subject.create({
data: {
id: 'subject-physics',
name: '物理',
code: 'PHYS',
icon: '⚡',
createdBy: 'system',
updatedBy: 'system'
}
}),
prisma.subject.create({
data: {
id: 'subject-english',
name: '英语',
code: 'ENG',
icon: '🔤',
createdBy: 'system',
updatedBy: 'system'
}
})
]);
console.log(` ✅ 创建 ${subjects.length} 个科目`);
// 4. 创建教师账号
console.log('\n👨🏫 创建教师账号...');
const teacherPassword = await bcrypt.hash('123456', 10);
const teachers = await Promise.all([
prisma.applicationUser.create({
data: {
id: 'teacher-001',
realName: '李明',
studentId: 'T2024001',
email: 'liming@school.edu',
phone: '13800138001',
gender: 'Male',
currentSchoolId: school.id,
accountStatus: 'Active',
passwordHash: teacherPassword,
bio: '数学教师教龄10年',
avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=teacher1',
createdBy: 'system',
updatedBy: 'system'
}
}),
prisma.applicationUser.create({
data: {
id: 'teacher-002',
realName: '张伟',
studentId: 'T2024002',
email: 'zhangwei@school.edu',
phone: '13800138002',
gender: 'Male',
currentSchoolId: school.id,
accountStatus: 'Active',
passwordHash: teacherPassword,
bio: '物理教师教龄8年',
avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=teacher2',
createdBy: 'system',
updatedBy: 'system'
}
})
]);
console.log(` ✅ 创建 ${teachers.length} 个教师账号 (密码: 123456)`);
// 5. 创建学生账号
console.log('\n👨🎓 创建学生账号...');
const studentPassword = await bcrypt.hash('123456', 10);
const students = [];
for (let i = 1; i <= 10; i++) {
const student = await prisma.applicationUser.create({
data: {
id: `student-${String(i).padStart(3, '0')}`,
realName: `学生${i}`,
studentId: `S2024${String(i).padStart(3, '0')}`,
email: `student${i}@school.edu`,
phone: `1380013${String(8000 + i)}`,
gender: i % 2 === 0 ? 'Female' : 'Male',
currentSchoolId: school.id,
accountStatus: 'Active',
passwordHash: studentPassword,
avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=student${i}`,
createdBy: 'system',
updatedBy: 'system'
}
});
students.push(student);
}
console.log(` ✅ 创建 ${students.length} 个学生账号 (密码: 123456)`);
// 6. 创建班级
console.log('\n🏫 创建班级...');
const class1 = await prisma.class.create({
data: {
id: 'class-001',
gradeId: grades[0].id,
name: '高一(1)班',
inviteCode: 'ABC123',
headTeacherId: teachers[0].id,
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
});
console.log(` ✅ 创建班级: ${class1.name} (邀请码: ${class1.inviteCode})`);
// 7. 添加班级成员
console.log('\n👥 添加班级成员...');
// 添加教师
await prisma.classMember.create({
data: {
id: 'cm-teacher-001',
classId: class1.id,
userId: teachers[0].id,
roleInClass: 'Teacher',
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
});
// 添加学生
for (let i = 0; i < students.length; i++) {
await prisma.classMember.create({
data: {
id: `cm-student-${String(i + 1).padStart(3, '0')}`,
classId: class1.id,
userId: students[i].id,
roleInClass: i === 0 ? 'Monitor' : 'Student',
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
});
}
console.log(` ✅ 添加 1 个教师和 ${students.length} 个学生到班级`);
// 8. 创建教材
console.log('\n📚 创建教材...');
const textbook = await prisma.textbook.create({
data: {
id: 'textbook-math-1',
subjectId: subjects[0].id,
name: '普通高中教科书·数学A版(必修第一册)',
publisher: '人民教育出版社',
versionYear: '2024',
coverUrl: 'https://placehold.co/300x400/007AFF/ffffff?text=Math',
createdBy: 'system',
updatedBy: 'system'
}
});
console.log(` ✅ 创建教材: ${textbook.name}`);
// 9. 创建单元和课节
console.log('\n📑 创建单元和课节...');
const unit1 = await prisma.textbookUnit.create({
data: {
id: 'unit-001',
textbookId: textbook.id,
name: '第一章 集合与常用逻辑用语',
sortOrder: 1,
createdBy: 'system',
updatedBy: 'system'
}
});
const lessons = await Promise.all([
prisma.textbookLesson.create({
data: {
id: 'lesson-001',
unitId: unit1.id,
name: '1.1 集合的概念',
sortOrder: 1,
createdBy: 'system',
updatedBy: 'system'
}
}),
prisma.textbookLesson.create({
data: {
id: 'lesson-002',
unitId: unit1.id,
name: '1.2 集合间的基本关系',
sortOrder: 2,
createdBy: 'system',
updatedBy: 'system'
}
})
]);
console.log(` ✅ 创建 1 个单元和 ${lessons.length} 个课节`);
// 10. 创建知识点
console.log('\n🎯 创建知识点...');
const knowledgePoints = await Promise.all([
prisma.knowledgePoint.create({
data: {
id: 'kp-001',
lessonId: lessons[0].id,
name: '集合的含义',
difficulty: 1,
description: '理解集合的基本概念',
createdBy: 'system',
updatedBy: 'system'
}
}),
prisma.knowledgePoint.create({
data: {
id: 'kp-002',
lessonId: lessons[1].id,
name: '子集的概念',
difficulty: 2,
description: '掌握子集的定义和性质',
createdBy: 'system',
updatedBy: 'system'
}
})
]);
console.log(` ✅ 创建 ${knowledgePoints.length} 个知识点`);
// 11. 创建题目
console.log('\n📝 创建题目...');
const questions = await Promise.all([
prisma.question.create({
data: {
id: 'question-001',
subjectId: subjects[0].id,
content: '<p>已知集合 A = {1, 2, 3}, B = {2, 3, 4}, 则 A ∩ B = ( )</p>',
questionType: 'SingleChoice',
difficulty: 2,
answer: 'B',
explanation: '集合 A 与 B 的公共元素为 2 和 3',
optionsConfig: {
options: [
{ label: 'A', content: '{1}' },
{ label: 'B', content: '{2, 3}' },
{ label: 'C', content: '{1, 2, 3, 4}' },
{ label: 'D', content: '∅' }
]
},
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
}),
prisma.question.create({
data: {
id: 'question-002',
subjectId: subjects[0].id,
content: '<p>若集合 A ⊆ B则下列说法正确的是 ( )</p>',
questionType: 'SingleChoice',
difficulty: 2,
answer: 'C',
explanation: '子集定义A的所有元素都在B中',
optionsConfig: {
options: [
{ label: 'A', content: 'A B = A' },
{ label: 'B', content: 'A ∩ B = B' },
{ label: 'C', content: 'A ∩ B = A' },
{ label: 'D', content: 'A B = ∅' }
]
},
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
}),
prisma.question.create({
data: {
id: 'question-003',
subjectId: subjects[0].id,
content: '<p>函数 f(x) = x² - 2x + 1 的最小值是 ______</p>',
questionType: 'FillBlank',
difficulty: 3,
answer: '0',
explanation: '配方法f(x) = (x-1)², 最小值为 0',
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
})
]);
console.log(` ✅ 创建 ${questions.length} 个题目`);
// 12. 创建试卷
console.log('\n📋 创建试卷...');
const exam = await prisma.exam.create({
data: {
id: 'exam-001',
subjectId: subjects[0].id,
title: '高一数学第一单元测试',
totalScore: 100,
suggestedDuration: 90,
status: 'Published',
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
});
console.log(` ✅ 创建试卷: ${exam.title}`);
// 13. 创建试卷节点
console.log('\n🌳 创建试卷结构...');
const groupNode = await prisma.examNode.create({
data: {
id: 'node-group-001',
examId: exam.id,
nodeType: 'Group',
title: '一、选择题',
description: '本大题共 2 小题,每小题 5 分,共 10 分',
score: 10,
sortOrder: 1,
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
});
await Promise.all([
prisma.examNode.create({
data: {
id: 'node-q-001',
examId: exam.id,
parentNodeId: groupNode.id,
nodeType: 'Question',
questionId: questions[0].id,
score: 5,
sortOrder: 1,
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
}),
prisma.examNode.create({
data: {
id: 'node-q-002',
examId: exam.id,
parentNodeId: groupNode.id,
nodeType: 'Question',
questionId: questions[1].id,
score: 5,
sortOrder: 2,
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
}),
prisma.examNode.create({
data: {
id: 'node-q-003',
examId: exam.id,
nodeType: 'Question',
questionId: questions[2].id,
title: '二、填空题',
score: 10,
sortOrder: 2,
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
})
]);
console.log(` ✅ 创建试卷结构1个分组3道题目`);
// 14. 创建作业
console.log('\n📮 创建作业...');
const assignment = await prisma.assignment.create({
data: {
id: 'assignment-001',
examId: exam.id,
classId: class1.id,
title: '第一单元课后练习',
startTime: new Date('2025-11-26T00:00:00Z'),
endTime: new Date('2025-12-31T23:59:59Z'),
allowLateSubmission: false,
autoScoreEnabled: true,
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
});
console.log(` ✅ 创建作业: ${assignment.title}`);
// 15. 为所有学生创建提交记录并模拟答题/批改
console.log('\n📬 创建学生提交记录并模拟答题...');
for (let i = 0; i < students.length; i++) {
const status = i < 5 ? 'Graded' : (i < 8 ? 'Submitted' : 'Pending');
const score = status === 'Graded' ? Math.floor(Math.random() * 20) + 80 : null; // 80-100分
const submission = await prisma.studentSubmission.create({
data: {
id: `submission-${String(i + 1).padStart(3, '0')}`,
assignmentId: assignment.id,
studentId: students[i].id,
submissionStatus: status,
submitTime: status !== 'Pending' ? new Date() : null,
totalScore: score,
timeSpentSeconds: status !== 'Pending' ? 3600 : null,
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
});
// 如果已提交或已批改,创建答题详情
if (status !== 'Pending') {
// 题目1单选题 (正确答案 B)
await prisma.submissionDetail.create({
data: {
id: uuidv4(),
submissionId: submission.id,
examNodeId: 'node-q-001',
studentAnswer: i % 3 === 0 ? 'A' : 'B', // 部分答错
score: status === 'Graded' ? (i % 3 === 0 ? 0 : 5) : null,
judgement: status === 'Graded' ? (i % 3 === 0 ? 'Incorrect' : 'Correct') : null,
createdBy: students[i].id,
updatedBy: teachers[0].id
}
});
// 题目2单选题 (正确答案 C)
await prisma.submissionDetail.create({
data: {
id: uuidv4(),
submissionId: submission.id,
examNodeId: 'node-q-002',
studentAnswer: 'C', // 全部答对
score: status === 'Graded' ? 5 : null,
judgement: status === 'Graded' ? 'Correct' : null,
createdBy: students[i].id,
updatedBy: teachers[0].id
}
});
// 题目3填空题 (正确答案 0)
await prisma.submissionDetail.create({
data: {
id: uuidv4(),
submissionId: submission.id,
examNodeId: 'node-q-003',
studentAnswer: '0',
score: status === 'Graded' ? 10 : null,
judgement: status === 'Graded' ? 'Correct' : null,
teacherComment: status === 'Graded' ? '做得好!' : null,
createdBy: students[i].id,
updatedBy: teachers[0].id
}
});
}
}
console.log(` ✅ 为 ${students.length} 个学生创建提交记录 (5个已批改, 3个已提交, 2个未提交)`);
// 创建更多试卷以测试列表
console.log('\n📄 创建更多试卷数据...');
for (let i = 2; i <= 15; i++) {
await prisma.exam.create({
data: {
id: `exam-${String(i).padStart(3, '0')}`,
subjectId: subjects[i % 3].id,
title: `模拟试卷 ${i}`,
totalScore: 100,
suggestedDuration: 90,
status: i % 2 === 0 ? 'Published' : 'Draft',
createdAt: new Date(Date.now() - i * 86400000), // 过去的时间
createdBy: teachers[0].id,
updatedBy: teachers[0].id
}
});
}
console.log(` ✅ 创建额外 14 份试卷`);
console.log('\n✨ 种子数据创建完成!\n');
console.log('═══════════════════════════════════════');
console.log('📊 数据统计:');
console.log('═══════════════════════════════════════');
console.log(` 学校: 1 所`);
console.log(` 年级: ${grades.length}`);
console.log(` 科目: ${subjects.length}`);
console.log(` 教师: ${teachers.length}`);
console.log(` 学生: ${students.length}`);
console.log(` 班级: 1 个`);
console.log(` 教材: 1 本`);
console.log(` 题目: ${questions.length}`);
console.log(` 试卷: 1 份`);
console.log(` 作业: 1 个`);
console.log('═══════════════════════════════════════\n');
console.log('🔑 测试账号:');
console.log('═══════════════════════════════════════');
console.log(' 教师账号: liming@school.edu / 123456');
console.log(' 学生账号: student1@school.edu / 123456');
console.log(' 班级邀请码: ABC123');
console.log('═══════════════════════════════════════\n');
}
main()
.catch((e) => {
console.error('❌ 种子数据创建失败:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});