library-picturebook-activity/backend/scripts/init-dev-tenants.ts
aid 418aa57ea8 Day4: 超管端设计优化 + UGC绘本创作社区P0实现
一、超管端设计优化
- 文档管理SOP体系建立,docs目录重组
- 统一用户管理:跨租户全局视角,合并用户管理+公众用户
- 活动监管全模块重构:全部活动(统计卡片+阶段筛选+SuperDetail详情页)、报名数据/作品数据/评审进度(两层合一扁平列表)、成果发布(去Tab+统计+隐藏写操作)
- 菜单精简:移除评委管理/评审规则/通知管理
- Bug修复:租户编辑丢失隐藏菜单、pageSize限制、主色统一

二、UGC绘本创作社区P0
- 数据库:10张新表(user_works/user_work_pages/work_tags等)
- 子女账号独立化:Child升级为独立User,家长切换+独立登录
- 用户作品库:CRUD+发布审核,8个API
- AI创作流程:提交→生成→保存到作品库,4个API
- 作品广场:首页改造为推荐流,标签+搜索+排序
- 内容审核(超管端):作品审核+作品管理+标签管理
- 活动联动:WorkSelector作品选择器
- 布局改造:底部5Tab(发现/创作/活动/作品库/我的)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:20:25 +08:00

439 lines
20 KiB
TypeScript

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
// 批量创建开发测试租户脚本
import * as dotenv from 'dotenv';
import * as path from 'path';
// 根据 NODE_ENV 加载对应的环境配置文件
const nodeEnv = process.env.NODE_ENV || 'development';
const envFile = `.env.${nodeEnv}`;
const backendDir = path.resolve(__dirname, '..');
const envPath = path.resolve(backendDir, envFile);
dotenv.config({ path: envPath });
if (!process.env.DATABASE_URL) {
dotenv.config({ path: path.resolve(backendDir, '.env') });
}
if (!process.env.DATABASE_URL) {
console.error('❌ 错误: 未找到 DATABASE_URL 环境变量');
process.exit(1);
}
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
// ============================================
// 要创建的租户配置
// ============================================
const devTenants = [
{
name: '学校管理端',
code: 'school',
description: '学校管理员端,管理学校信息、教师、学生等',
roles: [
{
code: 'school_admin',
name: '学校管理员',
description: '学校管理员',
isDefault: true,
permissions: [
'workbench:read',
'user:create', 'user:read', 'user:update', 'user:delete',
'role:read',
'permission:read',
'school:create', 'school:read', 'school:update',
'department:create', 'department:read', 'department:update', 'department:delete',
'grade:create', 'grade:read', 'grade:update', 'grade:delete',
'class:create', 'class:read', 'class:update', 'class:delete',
'teacher:create', 'teacher:read', 'teacher:update', 'teacher:delete',
'student:create', 'student:read', 'student:update', 'student:delete',
'activity:read', 'notice:read',
'registration:read', 'work:read',
'homework:create', 'homework:read', 'homework:update', 'homework:delete', 'homework:publish',
'homework-submission:read',
'homework-review-rule:create', 'homework-review-rule:read', 'homework-review-rule:update', 'homework-review-rule:delete',
'homework-score:read',
],
},
],
menus: ['工作台', '学校管理', '我的评审', '作业管理'],
},
{
name: '教师端',
code: 'teacher',
description: '教师端,可以报名活动、指导学生、管理作业',
roles: [
{
code: 'teacher',
name: '教师',
description: '教师角色',
isDefault: true,
permissions: [
'workbench:read',
'grade:read', 'class:read', 'student:read',
'activity:read', 'activity:guidance', 'notice:read',
'registration:create', 'registration:read', 'registration:update', 'registration:delete',
'work:create', 'work:read', 'work:update', 'work:submit',
'homework:create', 'homework:read', 'homework:update', 'homework:delete', 'homework:publish',
'homework-submission:read',
'homework-review-rule:create', 'homework-review-rule:read', 'homework-review-rule:update', 'homework-review-rule:delete',
'homework-score:create', 'homework-score:read',
],
},
],
menus: ['工作台', '我的评审', '作业管理'],
},
{
name: '学生端',
code: 'student',
description: '学生端,可以查看活动、上传作品、提交作业',
roles: [
{
code: 'student',
name: '学生',
description: '学生角色',
isDefault: true,
permissions: [
'workbench:read',
'activity:read', 'notice:read',
'registration:read',
'work:create', 'work:read', 'work:update', 'work:submit',
'homework:read',
'homework-submission:create', 'homework-submission:read', 'homework-submission:update',
'homework-score:read',
],
},
],
menus: ['工作台', '我的评审', '作业管理'],
},
{
name: '评委端',
code: 'judge',
description: '评委端,可以评审作品、打分',
roles: [
{
code: 'judge',
name: '评委',
description: '评委角色',
isDefault: true,
permissions: [
'workbench:read',
'activity:read', 'notice:read',
'work:read',
'judge:read', 'judge:assign',
'review:read', 'review:create', 'review:update',
],
},
],
menus: ['工作台', '我的评审'],
},
];
// ============================================
// 权限定义(完整列表)
// ============================================
const allPermissions = [
// 工作台
{ code: 'workbench:read', resource: 'workbench', action: 'read', name: '查看工作台', description: '允许查看工作台' },
// 用户管理
{ code: 'user:create', resource: 'user', action: 'create', name: '创建用户', description: '允许创建新用户' },
{ code: 'user:read', resource: 'user', action: 'read', name: '查看用户', description: '允许查看用户列表和详情' },
{ code: 'user:update', resource: 'user', action: 'update', name: '更新用户', description: '允许更新用户信息' },
{ code: 'user:delete', resource: 'user', action: 'delete', name: '删除用户', description: '允许删除用户' },
// 角色管理
{ code: 'role:create', resource: 'role', action: 'create', name: '创建角色', description: '允许创建新角色' },
{ code: 'role:read', resource: 'role', action: 'read', name: '查看角色', description: '允许查看角色列表和详情' },
{ code: 'role:update', resource: 'role', action: 'update', name: '更新角色', description: '允许更新角色信息' },
{ code: 'role:delete', resource: 'role', action: 'delete', name: '删除角色', description: '允许删除角色' },
{ code: 'role:assign', resource: 'role', action: 'assign', name: '分配角色', description: '允许给用户分配角色' },
// 权限管理
{ code: 'permission:read', resource: 'permission', action: 'read', name: '查看权限', description: '允许查看权限列表' },
// 学校管理
{ code: 'school:create', resource: 'school', action: 'create', name: '创建学校', description: '允许创建学校信息' },
{ code: 'school:read', resource: 'school', action: 'read', name: '查看学校', description: '允许查看学校信息' },
{ code: 'school:update', resource: 'school', action: 'update', name: '更新学校', description: '允许更新学校信息' },
{ code: 'school:delete', resource: 'school', action: 'delete', name: '删除学校', description: '允许删除学校信息' },
// 部门管理
{ code: 'department:create', resource: 'department', action: 'create', name: '创建部门', description: '允许创建部门' },
{ code: 'department:read', resource: 'department', action: 'read', name: '查看部门', description: '允许查看部门列表' },
{ code: 'department:update', resource: 'department', action: 'update', name: '更新部门', description: '允许更新部门信息' },
{ code: 'department:delete', resource: 'department', action: 'delete', name: '删除部门', description: '允许删除部门' },
// 年级管理
{ code: 'grade:create', resource: 'grade', action: 'create', name: '创建年级', description: '允许创建年级' },
{ code: 'grade:read', resource: 'grade', action: 'read', name: '查看年级', description: '允许查看年级列表' },
{ code: 'grade:update', resource: 'grade', action: 'update', name: '更新年级', description: '允许更新年级信息' },
{ code: 'grade:delete', resource: 'grade', action: 'delete', name: '删除年级', description: '允许删除年级' },
// 班级管理
{ code: 'class:create', resource: 'class', action: 'create', name: '创建班级', description: '允许创建班级' },
{ code: 'class:read', resource: 'class', action: 'read', name: '查看班级', description: '允许查看班级列表' },
{ code: 'class:update', resource: 'class', action: 'update', name: '更新班级', description: '允许更新班级信息' },
{ code: 'class:delete', resource: 'class', action: 'delete', name: '删除班级', description: '允许删除班级' },
// 教师管理
{ code: 'teacher:create', resource: 'teacher', action: 'create', name: '创建教师', description: '允许创建教师' },
{ code: 'teacher:read', resource: 'teacher', action: 'read', name: '查看教师', description: '允许查看教师列表' },
{ code: 'teacher:update', resource: 'teacher', action: 'update', name: '更新教师', description: '允许更新教师信息' },
{ code: 'teacher:delete', resource: 'teacher', action: 'delete', name: '删除教师', description: '允许删除教师' },
// 学生管理
{ code: 'student:create', resource: 'student', action: 'create', name: '创建学生', description: '允许创建学生' },
{ code: 'student:read', resource: 'student', action: 'read', name: '查看学生', description: '允许查看学生列表' },
{ code: 'student:update', resource: 'student', action: 'update', name: '更新学生', description: '允许更新学生信息' },
{ code: 'student:delete', resource: 'student', action: 'delete', name: '删除学生', description: '允许删除学生' },
// 我的评审
{ code: 'activity:read', resource: 'activity', action: 'read', name: '查看我的评审', description: '允许查看已发布的我的评审' },
{ code: 'activity:guidance', resource: 'activity', action: 'guidance', name: '指导学生', description: '允许指导学生参赛' },
// 活动报名
{ code: 'registration:create', resource: 'registration', action: 'create', name: '创建报名', description: '允许报名活动' },
{ code: 'registration:read', resource: 'registration', action: 'read', name: '查看报名', description: '允许查看报名记录' },
{ code: 'registration:update', resource: 'registration', action: 'update', name: '更新报名', description: '允许更新报名信息' },
{ code: 'registration:delete', resource: 'registration', action: 'delete', name: '取消报名', description: '允许取消报名' },
{ code: 'registration:approve', resource: 'registration', action: 'approve', name: '审核报名', description: '允许审核报名' },
// 参赛作品
{ code: 'work:create', resource: 'work', action: 'create', name: '上传作品', description: '允许上传参赛作品' },
{ code: 'work:read', resource: 'work', action: 'read', name: '查看作品', description: '允许查看参赛作品' },
{ code: 'work:update', resource: 'work', action: 'update', name: '更新作品', description: '允许更新作品信息' },
{ code: 'work:delete', resource: 'work', action: 'delete', name: '删除作品', description: '允许删除作品' },
{ code: 'work:submit', resource: 'work', action: 'submit', name: '提交作品', description: '允许提交作品' },
// 活动公告
{ code: 'notice:read', resource: 'notice', action: 'read', name: '查看公告', description: '允许查看活动公告' },
// 评委管理
{ code: 'judge:create', resource: 'judge', action: 'create', name: '创建评委', description: '允许创建评委' },
{ code: 'judge:read', resource: 'judge', action: 'read', name: '查看评委', description: '允许查看评委' },
{ code: 'judge:update', resource: 'judge', action: 'update', name: '更新评委', description: '允许更新评委' },
{ code: 'judge:delete', resource: 'judge', action: 'delete', name: '删除评委', description: '允许删除评委' },
{ code: 'judge:assign', resource: 'judge', action: 'assign', name: '分配评委', description: '允许分配评委' },
// 评审
{ code: 'review:create', resource: 'review', action: 'create', name: '创建评审', description: '允许创建评审' },
{ code: 'review:read', resource: 'review', action: 'read', name: '查看评审', description: '允许查看评审' },
{ code: 'review:update', resource: 'review', action: 'update', name: '更新评审', description: '允许更新评审' },
{ code: 'review:delete', resource: 'review', action: 'delete', name: '删除评审', description: '允许删除评审' },
// 作业管理
{ code: 'homework:create', resource: 'homework', action: 'create', name: '创建作业', description: '允许创建作业' },
{ code: 'homework:read', resource: 'homework', action: 'read', name: '查看作业', description: '允许查看作业列表' },
{ code: 'homework:update', resource: 'homework', action: 'update', name: '更新作业', description: '允许更新作业信息' },
{ code: 'homework:delete', resource: 'homework', action: 'delete', name: '删除作业', description: '允许删除作业' },
{ code: 'homework:publish', resource: 'homework', action: 'publish', name: '发布作业', description: '允许发布作业' },
// 作业提交
{ code: 'homework-submission:create', resource: 'homework-submission', action: 'create', name: '提交作业', description: '允许提交作业' },
{ code: 'homework-submission:read', resource: 'homework-submission', action: 'read', name: '查看作业提交', description: '允许查看作业提交记录' },
{ code: 'homework-submission:update', resource: 'homework-submission', action: 'update', name: '更新作业提交', description: '允许更新提交的作业' },
// 作业评审规则
{ code: 'homework-review-rule:create', resource: 'homework-review-rule', action: 'create', name: '创建作业评审规则', description: '允许创建作业评审规则' },
{ code: 'homework-review-rule:read', resource: 'homework-review-rule', action: 'read', name: '查看作业评审规则', description: '允许查看作业评审规则' },
{ code: 'homework-review-rule:update', resource: 'homework-review-rule', action: 'update', name: '更新作业评审规则', description: '允许更新作业评审规则' },
{ code: 'homework-review-rule:delete', resource: 'homework-review-rule', action: 'delete', name: '删除作业评审规则', description: '允许删除作业评审规则' },
// 作业评分
{ code: 'homework-score:create', resource: 'homework-score', action: 'create', name: '作业评分', description: '允许对作业评分' },
{ code: 'homework-score:read', resource: 'homework-score', action: 'read', name: '查看作业评分', description: '允许查看作业评分' },
];
async function initDevTenants() {
console.log('🚀 开始批量创建开发测试租户...\n');
try {
// 获取所有菜单
const allMenus = await prisma.menu.findMany({
orderBy: [{ sort: 'asc' }, { id: 'asc' }],
});
const results: any[] = [];
for (const tenantConfig of devTenants) {
console.log(`\n${'='.repeat(50)}`);
console.log(`📦 创建租户: ${tenantConfig.name}`);
console.log('='.repeat(50));
// 检查是否已存在
const existingTenant = await prisma.tenant.findFirst({
where: { code: tenantConfig.code }
});
if (existingTenant) {
console.log(` ⚠️ 租户 "${tenantConfig.code}" 已存在,跳过创建`);
results.push({
name: tenantConfig.name,
code: tenantConfig.code,
status: 'skipped',
username: 'admin',
password: `admin@${tenantConfig.code}`,
});
continue;
}
// 1. 创建租户
console.log('\n 🏢 步骤 1: 创建租户...');
const tenant = await prisma.tenant.create({
data: {
name: tenantConfig.name,
code: tenantConfig.code,
description: tenantConfig.description,
isSuper: 0,
validState: 1,
}
});
console.log(` ✓ 租户创建成功: ${tenant.name} (${tenant.code})`);
const tenantId = tenant.id;
// 2. 创建权限
console.log('\n 📝 步骤 2: 创建权限...');
const createdPermissions: { [code: string]: number } = {};
for (const perm of allPermissions) {
const existingPerm = await prisma.permission.findFirst({
where: { code: perm.code, tenantId }
});
if (!existingPerm) {
const permission = await prisma.permission.create({
data: { ...perm, tenantId, validState: 1 }
});
createdPermissions[perm.code] = permission.id;
} else {
createdPermissions[perm.code] = existingPerm.id;
}
}
console.log(` ✓ 共 ${Object.keys(createdPermissions).length} 个权限`);
// 3. 创建角色并分配权限
console.log('\n 👥 步骤 3: 创建角色...');
let defaultRoleId: number | null = null;
for (const roleConfig of tenantConfig.roles) {
const role = await prisma.role.create({
data: {
tenantId,
name: roleConfig.name,
code: roleConfig.code,
description: roleConfig.description,
validState: 1,
}
});
// 分配权限
for (const permCode of roleConfig.permissions) {
const permissionId = createdPermissions[permCode];
if (permissionId) {
await prisma.rolePermission.create({
data: { roleId: role.id, permissionId }
});
}
}
if (roleConfig.isDefault) {
defaultRoleId = role.id;
}
console.log(` ✓ 角色: ${role.name} (${roleConfig.permissions.length} 个权限)`);
}
// 4. 创建管理员用户
console.log('\n 👤 步骤 4: 创建管理员用户...');
const password = `admin@${tenantConfig.code}`;
const hashedPassword = await bcrypt.hash(password, 10);
const adminUser = await prisma.user.create({
data: {
tenantId,
username: 'admin',
password: hashedPassword,
nickname: `${tenantConfig.name}管理员`,
validState: 1,
}
});
console.log(` ✓ 用户: admin`);
// 5. 分配角色
if (defaultRoleId) {
await prisma.userRole.create({
data: { userId: adminUser.id, roleId: defaultRoleId }
});
console.log(` ✓ 已分配默认角色`);
}
// 6. 分配菜单
console.log('\n 📋 步骤 5: 分配菜单...');
const menuIds = new Set<number>();
for (const menu of allMenus) {
// 顶级菜单
if (!menu.parentId && tenantConfig.menus.includes(menu.name)) {
menuIds.add(menu.id);
}
// 子菜单
if (menu.parentId) {
const parentMenu = allMenus.find(m => m.id === menu.parentId);
if (parentMenu && tenantConfig.menus.includes(parentMenu.name)) {
menuIds.add(menu.id);
}
}
}
for (const menuId of menuIds) {
await prisma.tenantMenu.create({
data: { tenantId: tenant.id, menuId }
});
}
console.log(` ✓ 分配 ${menuIds.size} 个菜单`);
results.push({
name: tenantConfig.name,
code: tenantConfig.code,
status: 'created',
username: 'admin',
password: password,
});
}
// 输出汇总
console.log('\n\n' + '='.repeat(60));
console.log('🎉 开发测试租户创建完成!');
console.log('='.repeat(60));
console.log('\n📝 登录账号汇总:\n');
console.log('| 端\t\t| 租户编码\t| 用户名\t| 密码\t\t\t|');
console.log('|---------------|---------------|---------------|-----------------------|');
for (const r of results) {
console.log(`| ${r.name}\t| ${r.code}\t\t| ${r.username}\t\t| ${r.password}\t\t|`);
}
console.log('\n');
} catch (error) {
console.error('❌ 创建失败:', error);
throw error;
} finally {
await prisma.$disconnect();
}
}
// 执行初始化
initDevTenants()
.then(() => {
console.log('✅ 脚本执行完成!');
process.exit(0);
})
.catch((error) => {
console.error('💥 脚本执行失败:', error);
process.exit(1);
});