Files
Nexus_Edu/backend/prisma/seed.ts
2025-11-28 19:23:19 +08:00

571 lines
20 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
});