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: '
已知集合 A = {1, 2, 3}, B = {2, 3, 4}, 则 A ∩ B = ( )
', 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: '若集合 A ⊆ B,则下列说法正确的是 ( )
', 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: '函数 f(x) = x² - 2x + 1 的最小值是 ______
', 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(); });