// 灵犀 · 种子数据(A2) // 原则: // 1) 幂等:按业务唯一键 upsert,重复执行不重复插入;已存在的密码 / 人工调整值不被覆盖; // 2) 权限点目录、角色-权限点、角色-数据范围、用户-角色为种子可重建映射:先清后建 // (限定在种子角色 / 种子用户范围内;生产禁止直接跑 seed); // 3) 权限点编码唯一事实源是 packages/shared/src/permissions.ts,本文件不复制编码字符串; // 4) 超管、部门、示例角色 / 用户口径见 prisma/SEED.md。 import { randomBytes } from 'node:crypto'; import { existsSync } from 'node:fs'; import { resolve } from 'node:path'; import type { Prisma } from '@prisma/client'; import { PrismaClient, type RoleStatus, type UserStatus } from '@prisma/client'; import { config as loadDotenv } from 'dotenv'; // 直接引用 shared 源码目录(seed 由 tsx 执行;shared 是权限点唯一事实源,避免两处编码漂移) import { SYSTEM_CONFIG_DEFINITIONS, systemConfigValueType, } from '../packages/shared/src/config/system-configs.js'; import { findSodConflicts, Permission, PERMISSION_CATALOG, splitPermissions, } from '../packages/shared/src/permissions.js'; import dictSeed from './seed-data/dicts.json' with { type: 'json' }; for (const file of ['.env.local', '.env']) { const path = resolve(process.cwd(), file); if (existsSync(path)) { loadDotenv({ path }); } } const prisma = new PrismaClient(); const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; function ulid(): string { let time = Date.now(); const timeChars = new Array(10); for (let i = 9; i >= 0; i -= 1) { timeChars[i] = ENCODING[time % 32] ?? '0'; time = Math.floor(time / 32); } const random = randomBytes(16); const randomChars = Array.from(random, (byte) => ENCODING[byte & 31] ?? '0'); return `${timeChars.join('')}${randomChars.join('')}`; } /** 示例账号统一初始密码:lingxi2026(Argon2id,首登强制修改) */ const SEED_PASSWORD_HASH = '$argon2id$v=19$m=65536,p=4,t=3$Osdgg3Fz53AwcoC2kyGiGg$QVbxW7Ok/SlCNbML95go2J/6GMSfE1EG3jBTJgR4zO4'; interface SeedDepartment { code: string; name: string; parentCode: string | null; leaderAccount: string | null; sortOrder: number; } const DEPARTMENTS: SeedDepartment[] = [ { code: 'content', name: '内容生产中心', parentCode: null, leaderAccount: 'chen.mo', sortOrder: 1, }, { code: 'delivery', name: '交付中心', parentCode: null, leaderAccount: 'gao.cen', sortOrder: 2 }, { code: 'tech', name: '平台技术部', parentCode: null, leaderAccount: 'shen.zhiyuan', sortOrder: 3, }, { code: 'content-g1', name: '素材一组', parentCode: 'content', leaderAccount: null, sortOrder: 1, }, { code: 'content-g2', name: '素材二组', parentCode: 'content', leaderAccount: null, sortOrder: 2, }, { code: 'content-g3', name: '设计组', parentCode: 'content', leaderAccount: null, sortOrder: 3 }, { code: 'delivery-cs', name: '客户成功组', parentCode: 'delivery', leaderAccount: null, sortOrder: 1, }, { code: 'tech-ai', name: 'AI 工程组', parentCode: 'tech', leaderAccount: null, sortOrder: 1 }, { code: 'tech-ops', name: '平台运维组', parentCode: 'tech', leaderAccount: null, sortOrder: 2 }, ]; interface SeedRole { code: string; name: string; description: string; isSystem: boolean; status?: RoleStatus; /** 普通权限点(不得包含特殊权限;特殊权限必须放 specialPermissions 显式单独授予) */ permissions: string[]; /** 特殊权限点:默认关闭,仅在种子里显式授予(原型口径:超管 6 + 平台管理员 1 + AI 工程师 1) */ specialPermissions?: string[]; /** 数据范围声明(一个角色只允许一种类型;A3 前「指定客户组」targetIds 为空) */ dataScope: SeedDataScope; } interface SeedDataScope { type: string; targetIds?: string[]; } /** * 种子角色与权限映射:与 design/a2-ui-v1/brief.md §4 的共享角色数据对齐 * (各角色普通权限数 12 / 9 / 4 / 2 / 4 / 3 / 5 / 4 / 5,特殊权限授予 8 项)。 */ const ROLES: SeedRole[] = [ { code: 'super_admin', name: '超级管理员', description: '内置角色,拥有系统全部权限', isSystem: true, permissions: PERMISSION_CATALOG.filter((item) => !item.isSpecial).map((item) => item.code), specialPermissions: PERMISSION_CATALOG.filter((item) => item.isSpecial).map( (item) => item.code, ), dataScope: { type: 'all' }, }, { code: 'platform_admin', name: '平台管理员', description: '组织、角色与平台配置管理', isSystem: false, permissions: [ Permission.USER_VIEW, Permission.USER_MANAGE, Permission.ROLE_VIEW, Permission.ROLE_MANAGE, Permission.SYSTEM_CONFIGURE, Permission.INDUSTRY_MANAGE, Permission.DICT_MANAGE, Permission.FORM_DESIGN, Permission.CUSTOMER_READ, Permission.CUSTOMER_GROUP_MANAGE, Permission.DELIVERY_READ, Permission.ASSET_READ, ], specialPermissions: [Permission.AUDIT_READ], dataScope: { type: 'all' }, }, { code: 'material_operator', name: '素材运营', description: '素材生产、快筛、导出与归档', isSystem: false, permissions: [ Permission.SCRIPT_READ, Permission.SCRIPT_WRITE, Permission.MATERIAL_READ, Permission.MATERIAL_WRITE, Permission.MATERIAL_RENDER, Permission.ASSET_READ, Permission.ASSET_WRITE, Permission.DELIVERY_READ, Permission.CUSTOMER_READ, ], dataScope: { type: 'owned_customers' }, }, { code: 'designer', name: '设计师', description: '素材设计与资产维护', isSystem: false, permissions: [ Permission.MATERIAL_READ, Permission.MATERIAL_WRITE, Permission.ASSET_READ, Permission.ASSET_WRITE, ], dataScope: { type: 'owned_customers' }, }, { code: 'copywriter', name: '文案策划', description: '脚本撰写与要素卡维护', isSystem: false, permissions: [Permission.SCRIPT_READ, Permission.SCRIPT_WRITE], dataScope: { type: 'department' }, }, { code: 'reviewer', name: '审核员', description: '脚本与素材审核', isSystem: false, permissions: [ Permission.SCRIPT_READ, Permission.SCRIPT_REVIEW, Permission.MATERIAL_READ, Permission.MATERIAL_REVIEW, ], dataScope: { type: 'department_and_children' }, }, { code: 'ai_engineer', name: 'AI 工程师', description: '模型接入与生成链路维护', isSystem: false, permissions: [ Permission.MATERIAL_READ, Permission.MATERIAL_RENDER, Permission.SYSTEM_CONFIGURE, ], specialPermissions: [Permission.AI_CONFIGURE], dataScope: { type: 'all' }, }, { code: 'account_owner', name: '客户负责人', description: '客户资料、脚本审核与交付验收', isSystem: false, permissions: [ Permission.CUSTOMER_READ, Permission.CUSTOMER_MANAGE, Permission.CUSTOMER_TRANSFER, Permission.DELIVERY_READ, Permission.SCRIPT_REVIEW, ], dataScope: { type: 'customer_groups', targetIds: [] }, }, { code: 'industry_config', name: '行业配置', description: '行业包、表单与字典维护', isSystem: false, permissions: [ Permission.INDUSTRY_MANAGE, Permission.FORM_DESIGN, Permission.DICT_MANAGE, Permission.CUSTOMER_READ, ], dataScope: { type: 'all' }, }, { code: 'review_lead', name: '审核主管', description: '原审核组主管角色,已并入审核员', isSystem: false, status: 'DISABLED', permissions: [ Permission.SCRIPT_READ, Permission.SCRIPT_REVIEW, Permission.MATERIAL_READ, Permission.MATERIAL_REVIEW, Permission.CUSTOMER_READ, ], dataScope: { type: 'department_and_children' }, }, ]; interface SeedUser { name: string; account: string; phone: string; jobTitle: string; departmentCode: string; roleCodes: string[]; status: UserStatus; } const USERS: SeedUser[] = [ { name: '灵犀管理员', account: 'admin', phone: '13000000001', jobTitle: '平台管理员', departmentCode: 'tech', roleCodes: ['super_admin'], status: 'ACTIVE', }, { name: '陈默', account: 'chen.mo', phone: '13866202046', jobTitle: '内容生产中心主管', departmentCode: 'content-g1', roleCodes: ['material_operator', 'account_owner'], status: 'ACTIVE', }, { name: '林清', account: 'lin.qing', phone: '13912347712', jobTitle: '设计师', departmentCode: 'content-g3', roleCodes: ['designer'], status: 'ACTIVE', }, { name: '周岸', account: 'zhou.an', phone: '13755205520', jobTitle: '文案策划', departmentCode: 'content-g2', roleCodes: ['copywriter'], status: 'ACTIVE', }, { name: '赵一诺', account: 'zhao.yinuo', phone: '13633183188', jobTitle: '审核员', departmentCode: 'content-g2', roleCodes: ['reviewer'], status: 'ACTIVE', }, { name: '高岑', account: 'gao.cen', phone: '18644024402', jobTitle: '交付中心主管', departmentCode: 'delivery-cs', roleCodes: ['account_owner'], status: 'ACTIVE', }, { name: '苏禾', account: 'su.he', phone: '18877657765', jobTitle: 'AI 工程师', departmentCode: 'tech-ai', roleCodes: ['ai_engineer'], status: 'ACTIVE', }, { name: '沈知远', account: 'shen.zhiyuan', phone: '13300000071', jobTitle: '平台技术部主管', departmentCode: 'tech', roleCodes: ['super_admin'], status: 'ACTIVE', }, { name: '江岚', account: 'jiang.lan', phone: '15933203320', jobTitle: '平台管理员', departmentCode: 'tech-ops', roleCodes: ['platform_admin'], status: 'ACTIVE', }, { name: '唐嘉', account: 'tang.jia', phone: '13566416641', jobTitle: '素材运营', departmentCode: 'content-g1', roleCodes: ['material_operator'], status: 'DISABLED', }, { name: '何岸', account: 'he.an', phone: '18711931193', jobTitle: '客户负责人', departmentCode: 'delivery-cs', roleCodes: ['account_owner'], status: 'DISABLED', }, ]; async function seedConfigs(): Promise { for (const definition of SYSTEM_CONFIG_DEFINITIONS) { await prisma.systemConfig.upsert({ where: { configKey: definition.key }, // 只同步元数据,不覆盖人工调整过的值 / 版本 update: { description: definition.description, valueType: systemConfigValueType(definition), isEditable: definition.editable, }, create: { id: ulid(), configKey: definition.key, configValue: definition.defaultValue, valueType: systemConfigValueType(definition), description: definition.description, isEditable: definition.editable, }, }); } console.log(`[seed] system_configs 就绪:${String(SYSTEM_CONFIG_DEFINITIONS.length)} 项(幂等)`); } async function seedDepartments(): Promise { for (const item of DEPARTMENTS) { const parent = item.parentCode === null ? null : await prisma.department.findUnique({ where: { code: item.parentCode } }); await prisma.department.upsert({ where: { code: item.code }, update: {}, create: { id: ulid(), code: item.code, name: item.name, parentId: parent?.id ?? null, sortOrder: item.sortOrder, }, }); } console.log(`[seed] departments 就绪:${String(DEPARTMENTS.length)} 个(幂等)`); } async function seedPermissions(): Promise { for (const item of PERMISSION_CATALOG) { await prisma.permission.upsert({ where: { code: item.code }, update: { module: item.module, moduleLabel: item.moduleLabel, name: item.name, description: item.description, isSpecial: item.isSpecial, sortOrder: item.sortOrder, }, create: { code: item.code, module: item.module, moduleLabel: item.moduleLabel, name: item.name, description: item.description, isSpecial: item.isSpecial, sortOrder: item.sortOrder, }, }); } console.log(`[seed] permissions 就绪:${String(PERMISSION_CATALOG.length)} 个(幂等,只增不删)`); } async function seedRoleAccess(roleId: string, item: SeedRole): Promise { const normal = splitPermissions(item.permissions); if (normal.specialPermissions.length > 0) { throw new Error( `种子角色 ${item.code} 的 permissions 混入特殊权限(必须放 specialPermissions):${normal.specialPermissions.join(',')}`, ); } const special = splitPermissions(item.specialPermissions ?? []); if (special.permissions.length > 0) { throw new Error( `种子角色 ${item.code} 的 specialPermissions 混入普通权限:${special.permissions.join(',')}`, ); } const conflicts = item.isSystem ? [] : findSodConflicts([...normal.permissions, ...special.specialPermissions]); if (conflicts.length > 0) { throw new Error( `种子角色 ${item.code} 违反职责互斥:${conflicts.map((c) => c.label).join(';')}`, ); } await prisma.rolePermission.deleteMany({ where: { roleId } }); await prisma.rolePermission.createMany({ data: [...normal.permissions, ...special.specialPermissions].map((permissionCode) => ({ roleId, permissionCode, })), skipDuplicates: true, }); await prisma.dataScope.deleteMany({ where: { roleId } }); // 无授权对象(含 A3 前空客户组)也保留一行 target_id = NULL,确保范围类型不丢失(不降级为最小权限) const targetIds = item.dataScope.targetIds?.length ? item.dataScope.targetIds : [null]; await prisma.dataScope.createMany({ data: targetIds.map((targetId) => ({ id: ulid(), roleId, scopeType: item.dataScope.type, targetId, })), skipDuplicates: true, }); } async function seedRoles(): Promise { for (const item of ROLES) { const role = await prisma.role.upsert({ where: { code: item.code }, update: {}, create: { id: ulid(), code: item.code, name: item.name, description: item.description, isSystem: item.isSystem, status: item.status ?? 'ACTIVE', }, }); await seedRoleAccess(role.id, item); } console.log( `[seed] roles 就绪:${String(ROLES.length)} 个 + 权限点 / 数据范围映射(幂等,特殊权限按原型显式授予)`, ); } async function seedUsers(): Promise { for (const item of USERS) { const department = await prisma.department.findUnique({ where: { code: item.departmentCode } }); if (department === null) { throw new Error(`部门不存在:${item.departmentCode}`); } const user = await prisma.user.upsert({ where: { account: item.account }, update: {}, create: { id: ulid(), name: item.name, account: item.account, email: `${item.account}@lingxi.cn`, phone: item.phone, passwordHash: SEED_PASSWORD_HASH, departmentId: department.id, jobTitle: item.jobTitle, status: item.status, mustChangePassword: true, ...(item.status === 'DISABLED' ? { disabledAt: new Date() } : {}), }, }); await prisma.userRole.deleteMany({ where: { userId: user.id } }); for (const code of item.roleCodes) { const role = await prisma.role.findUnique({ where: { code } }); if (role === null) throw new Error(`角色不存在:${code}`); await prisma.userRole.create({ data: { userId: user.id, roleId: role.id } }); } } console.log( `[seed] users 就绪:${String(USERS.length)} 个(幂等,初始密码 lingxi2026,首登强制修改)`, ); } async function seedDepartmentLeaders(): Promise { for (const item of DEPARTMENTS) { if (item.leaderAccount === null) continue; const [department, leader] = await Promise.all([ prisma.department.findUnique({ where: { code: item.code } }), prisma.user.findUnique({ where: { account: item.leaderAccount } }), ]); if (department !== null && leader !== null) { await prisma.department.update({ where: { id: department.id }, data: { leaderUserId: leader.id }, }); } } console.log('[seed] 部门负责人就绪(幂等)'); } /** * A3 客户与行业种子(幂等): * - 两级行业:电商(服饰鞋包 / 美妆个护)、金融(信贷); * - 2 个客户(花间集、素研)与 1 个客户组(电商核心组); * - 花间集 3 条客户级字段:已生效 2(1 条进入生成 + 1 条仅检索)、待审批 1。 * 不覆盖人工调整值:按业务唯一键查找,存在则只补齐缺失关系。 */ /* eslint-disable max-lines-per-function -- A3 种子按行业 / 客户 / 客户组 / 字段顺序幂等写入 */ async function seedClient(): Promise { const admin = await prisma.user.findUnique({ where: { account: 'admin' }, select: { id: true } }); const chen = await prisma.user.findUnique({ where: { account: 'chen.mo' }, select: { id: true }, }); const lin = await prisma.user.findUnique({ where: { account: 'lin.qing' }, select: { id: true }, }); const jiang = await prisma.user.findUnique({ where: { account: 'jiang.lan' }, select: { id: true }, }); if (admin === null || chen === null || lin === null || jiang === null) { throw new Error('A3 种子依赖 admin / chen.mo / lin.qing / jiang.lan 账号,请先执行 A1 种子'); } const adminId = admin.id; async function upsertIndustry( name: string, parentId: string | null, level: number, sortOrder: number, remark: string, ): Promise { const existing = await prisma.industry.findFirst({ where: { name, parentId }, select: { id: true }, }); if (existing !== null) return existing.id; const id = ulid(); await prisma.industry.create({ data: { id, name, parentId, level, sortOrder, remark, status: 'ACTIVE' }, }); return id; } const ec = await upsertIndustry('电商', null, 1, 10, '电商行业客户,覆盖服饰、美妆等类目'); const apparel = await upsertIndustry('服饰鞋包', ec, 2, 10, '服饰、鞋履、箱包类目'); const beauty = await upsertIndustry('美妆个护', ec, 2, 20, '护肤、彩妆、个护类目'); const fin = await upsertIndustry('金融', null, 1, 20, '金融行业客户,合规要求高'); await upsertIndustry('信贷', fin, 2, 10, '消费贷、经营贷、信用卡'); async function upsertCustomer(input: { name: string; shortName: string; industryId: string; ownerUserId: string; contactName: string; contactPhone: string; remark: string; preferences: Prisma.InputJsonValue; customFields: Prisma.InputJsonValue; }): Promise { const existing = await prisma.customer.findFirst({ where: { industryId: input.industryId, name: input.name, deletedAt: null }, select: { id: true }, }); if (existing !== null) return existing.id; const id = ulid(); await prisma.customer.create({ data: { id, industryId: input.industryId, name: input.name, shortName: input.shortName, status: 'ACTIVE', ownerUserId: input.ownerUserId, contactName: input.contactName, contactPhone: input.contactPhone, preferences: input.preferences, customFields: input.customFields, remark: input.remark, createdBy: adminId, }, }); return id; } const huajianji = await upsertCustomer({ name: '花间集', shortName: 'HJ', industryId: apparel, ownerUserId: chen.id, contactName: '林晓', contactPhone: '138****2210', remark: '秋冬新品主推,素材需保留品牌金色描边。', preferences: { naming: '客户名_方向_日期_序号', directory: '花间集/2026/秋冬', specs: ['1080×1920', '1080×1080'], taboo: ['绝对化用语', '未授权肖像'], }, customFields: [ { key: '门店编号', value: '沪-012' }, { key: '内部产品代码', value: 'HJ-2026' }, ], }); const suyan = await upsertCustomer({ name: '素研', shortName: 'SY', industryId: beauty, ownerUserId: lin.id, contactName: '周航', contactPhone: '139****8832', remark: '以成分卖点为主,需规避医疗功效表述。', preferences: { naming: '客户名_产品线_日期_序号', directory: '素研/2026/Q4', specs: ['1080×1920'], taboo: ['医疗功效表述'], }, customFields: [{ key: '门店编号', value: '杭-008' }], }); async function upsertGroup(): Promise { const existing = await prisma.customerGroup.findUnique({ where: { name: '电商核心组' }, select: { id: true }, }); const groupId = existing?.id ?? ulid(); if (existing === null) { await prisma.customerGroup.create({ data: { id: groupId, name: '电商核心组', description: '电商行业核心客户,作为数据范围授权单位', createdBy: adminId, }, }); } await prisma.customerGroupMember.createMany({ data: [huajianji, suyan].map((customerId) => ({ groupId, customerId })), skipDuplicates: true, }); } await upsertGroup(); const fieldSeeds = [ { customerId: huajianji, name: '商品系列', code: 'series', type: 'select', options: ['秋冬', '春夏'], purpose: '卖点与脚本分组,按系列匹配脚本模板', entersGeneration: true, status: 'ACTIVE' as const, approverUserId: jiang.id, }, { customerId: huajianji, name: '门店编号', code: 'store_code', type: 'text', options: [], purpose: '门店素材检索与报表拆分', entersGeneration: false, status: 'ACTIVE' as const, approverUserId: jiang.id, }, { customerId: huajianji, name: '活动编码', code: 'campaign_code', type: 'text', options: [], purpose: '活动素材命名与检索,用于区分不同投放批次', entersGeneration: true, status: 'PENDING' as const, approverUserId: null, }, { customerId: suyan, name: '产品线', code: 'product_line', type: 'select', options: ['精华', '面霜'], purpose: '按产品线匹配脚本方向与卖点', entersGeneration: true, status: 'ACTIVE' as const, approverUserId: jiang.id, }, ]; for (const field of fieldSeeds) { const existing = await prisma.customerField.findUnique({ where: { customerId_code: { customerId: field.customerId, code: field.code } }, select: { id: true }, }); if (existing !== null) continue; await prisma.customerField.create({ data: { id: ulid(), customerId: field.customerId, name: field.name, code: field.code, type: field.type, options: field.options, purpose: field.purpose, entersGeneration: field.entersGeneration, status: field.status, applicantUserId: chen.id, approverUserId: field.approverUserId, approvedAt: field.status === 'ACTIVE' ? new Date() : null, }, }); } console.log('[seed] A3 客户与行业就绪(幂等)'); } /* eslint-enable max-lines-per-function */ interface DictSeedType { code: string; name: string; group: string; kind: 'tree' | 'list'; description: string; industryScoped: boolean; builtin: boolean; status: 'active' | 'disabled'; sortOrder: number; updatedBy: string; } interface DictSeedItem { typeCode: string; code: string; name: string; parentCode: string | null; industryId: string | null; sortOrder: number; status: 'active' | 'disabled'; remark: string; updatedBy: string; } const DICT_TYPES = dictSeed.types as DictSeedType[]; const DICT_ITEMS = dictSeed.items as DictSeedItem[]; /** 原型示例数据用行业 code 表示作用域;种子落库时映射为 A3 行业名称 */ const DICT_INDUSTRY_CODE_TO_NAME: Readonly> = { ec: '电商', ec_apparel: '服饰鞋包', ec_beauty: '美妆个护', fin: '金融', fin_credit: '信贷', local: '本地生活', local_food: '餐饮到店', }; /** A4 数据字典种子:13 类 / 96 条;行业作用域用 A3 的行业 ID 解析;幂等,不覆盖人工调整 */ // eslint-disable-next-line max-lines-per-function -- 类型 + 条目两段种子共用行业映射,集中便于阅读 async function seedDicts(): Promise { const industries = await prisma.industry.findMany({ select: { id: true, name: true, parentId: true }, }); const industryByName = new Map(industries.map((industry) => [industry.name, industry.id])); const typeIdByCode = new Map(); for (const item of DICT_TYPES) { const status = item.status === 'active' ? 'ACTIVE' : 'DISABLED'; const existing = await prisma.dictType.findUnique({ where: { code: item.code }, select: { id: true }, }); if (existing !== null) { typeIdByCode.set(item.code, existing.id); continue; } const id = ulid(); await prisma.dictType.create({ data: { id, code: item.code, name: item.name, groupKey: item.group, kind: item.kind === 'tree' ? 'TREE' : 'LIST', description: item.description, industryScoped: item.industryScoped, builtin: item.builtin, status, sortOrder: item.sortOrder, updatedBy: item.updatedBy, }, }); typeIdByCode.set(item.code, id); } for (const item of DICT_ITEMS) { const typeId = typeIdByCode.get(item.typeCode); if (typeId === undefined) continue; const industryName = item.industryId === null ? null : (DICT_INDUSTRY_CODE_TO_NAME[item.industryId] ?? item.industryId); const industryId = industryName === null ? null : (industryByName.get(industryName) ?? null); const existing = await prisma.dictItem.findFirst({ where: { typeId, code: item.code, industryId }, select: { id: true }, }); if (existing !== null) continue; await prisma.dictItem.create({ data: { id: ulid(), typeId, code: item.code, name: item.name, parentCode: item.parentCode, industryId, sortOrder: item.sortOrder, status: item.status === 'active' ? 'ACTIVE' : 'DISABLED', remark: item.remark, updatedBy: item.updatedBy, }, }); } console.log( `[seed] A4 数据字典就绪:${String(DICT_TYPES.length)} 类 / ${String(DICT_ITEMS.length)} 条(幂等)`, ); } async function main(): Promise { await seedConfigs(); await seedPermissions(); await seedDepartments(); await seedRoles(); await seedUsers(); await seedDepartmentLeaders(); await seedClient(); await seedDicts(); } void main() .catch((error: unknown) => { console.error('[seed] 失败:', error); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); });