flnxi-Wu/prisma/seed.ts
WuYongJie 63ec6b7dfb fix: 修复排行榜按钮消失、tag排序和输入框浅色模式问题
1. 排行榜悬浮窗关闭后小按钮消失问题
   - 修复面板居中模式关闭时按钮位置错误
   - 保存并恢复按钮的居中状态

2. 首页tag按钮按素材数量排序
   - 添加按素材数量降序排序逻辑

3. 上传弹窗输入框浅色模式文字不可见
   - 添加 text-slate-900 确保浅色模式下文字可见
   - 添加 placeholder 样式确保占位符正确显示
2025-12-14 21:41:29 +08:00

289 lines
10 KiB
TypeScript

/**
* Prisma 数据库种子脚本
*
* 用于初始化数据库的基础数据,包括:
* 1. 内置模板
* 2. 默认管理员账户(可选)
* 3. 默认部门、店铺、场景
*
* 使用方法:
* npx prisma db seed
*
* 或在 package.json 中配置后自动执行:
* "prisma": {
* "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
* }
*/
import { PrismaClient } from '@prisma/client'
import bcrypt from 'bcryptjs'
const prisma = new PrismaClient()
// ============================================================================
// 内置模板配置(与 lib/system-integrity.ts 保持一致)
// ============================================================================
const BUILTIN_TEMPLATE_KEYS = [
'3dmax-material',
'psd-material',
'audio-material',
'video-material',
'model-material',
'other-material',
] as const
type BuiltinTemplateKey = (typeof BUILTIN_TEMPLATE_KEYS)[number]
const BUILTIN_TEMPLATE_CONFIG: Record<
BuiltinTemplateKey,
{
name: string
description: string
materialType: string
materialKind: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'
fields: Array<{
key: string
label: string
type: string
required: boolean
layoutWidth: string
visibleOnHomeFilter: boolean
isSearchable: boolean
addToCategory?: boolean
}>
}
> = {
'3dmax-material': {
name: '3DMAX 素材模板',
description: '适用于 3D 模型类素材',
materialType: '3DMAX',
materialKind: 'IMAGE',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'renderer', label: '渲染器', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true, addToCategory: true },
],
},
'psd-material': {
name: 'PSD 素材模板',
description: '针对平面设计 PSD 素材',
materialType: 'PSD',
materialKind: 'IMAGE',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
],
},
'audio-material': {
name: '音频素材模板',
description: '适用于音频文件素材,支持 MP3、WAV、FLAC、AAC 等格式',
materialType: '音频',
materialKind: 'AUDIO',
fields: [
{ key: 'productModel', label: '音频名称', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'audioCategory', label: '音频分类', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'audioScene', label: '使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'authorName', label: '作者', type: 'text', required: false, layoutWidth: 'half', visibleOnHomeFilter: false, isSearchable: true },
],
},
'video-material': {
name: '视频素材模板',
description: '适用于视频文件素材',
materialType: '视频',
materialKind: 'VIDEO',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
],
},
'model-material': {
name: '模特素材模板',
description: '用于模特/人像素材',
materialType: '模特',
materialKind: 'IMAGE',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
],
},
'other-material': {
name: '其他素材模板',
description: '适用于其他类型的素材',
materialType: '其他',
materialKind: 'OTHER',
fields: [
{ key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true },
{ key: 'categoryName', label: '使用店铺', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
{ key: 'sceneName', label: '场景类别', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true },
],
},
}
// ============================================================================
// 种子函数
// ============================================================================
async function seedBuiltinTemplates() {
console.log('正在创建内置模板...')
let created = 0
let skipped = 0
for (const key of BUILTIN_TEMPLATE_KEYS) {
const templateId = `builtin-${key}`
const config = BUILTIN_TEMPLATE_CONFIG[key]
// 检查是否已存在
const existing = await prisma.uploadTemplate.findUnique({
where: { id: templateId },
})
if (existing) {
console.log(` 跳过: ${templateId} (已存在)`)
skipped++
continue
}
// 创建模板和字段
await prisma.$transaction(async (tx) => {
await tx.uploadTemplate.create({
data: {
id: templateId,
name: config.name,
description: config.description,
status: 'active',
materialType: config.materialType,
materialKind: config.materialKind,
version: 1,
},
})
for (let i = 0; i < config.fields.length; i++) {
const field = config.fields[i]
await tx.uploadField.create({
data: {
templateId,
key: field.key,
label: field.label,
type: field.type,
required: field.required,
orderIndex: i,
layoutWidth: field.layoutWidth,
visibleOnHomeFilter: field.visibleOnHomeFilter,
isSearchable: field.isSearchable,
addToCategory: field.addToCategory || false,
},
})
}
})
console.log(` 创建: ${templateId}`)
created++
}
console.log(`内置模板: 创建 ${created} 个, 跳过 ${skipped} 个`)
}
async function seedDefaultAdmin() {
console.log('正在检查默认管理员...')
const adminEmail = process.env.INIT_ADMIN_EMAIL || 'admin@flnxi.com'
const adminPassword = process.env.INIT_ADMIN_PASSWORD || '88888888'
// 检查是否已有管理员
const existingAdmin = await prisma.user.findFirst({
where: { role: 'ADMIN' },
})
if (existingAdmin) {
console.log(` 跳过: 已存在管理员 (${existingAdmin.email})`)
return
}
// 创建默认管理员
const hashedPassword = await bcrypt.hash(adminPassword, 10)
const admin = await prisma.user.create({
data: {
email: adminEmail,
username: adminEmail.split('@')[0],
password: hashedPassword,
role: 'ADMIN',
isSuperAdmin: true,
isActive: true,
canDownload: true,
department: '系统管理',
},
})
console.log(` 创建: 管理员 ${admin.email}`)
}
async function seedDefaultData() {
console.log('正在创建默认数据...')
// 默认部门
const dept = await prisma.department.findFirst({ where: { name: '默认部门' } })
if (!dept) {
await prisma.department.create({ data: { name: '默认部门' } })
console.log(' 创建: 默认部门')
} else {
console.log(' 跳过: 默认部门 (已存在)')
}
// 默认店铺
const category = await prisma.category.findFirst({ where: { name: '默认店铺' } })
if (!category) {
await prisma.category.create({ data: { name: '默认店铺' } })
console.log(' 创建: 默认店铺')
} else {
console.log(' 跳过: 默认店铺 (已存在)')
}
// 默认场景
const scene = await prisma.scene.findFirst({ where: { name: '默认场景' } })
if (!scene) {
await prisma.scene.create({ data: { name: '默认场景' } })
console.log(' 创建: 默认场景')
} else {
console.log(' 跳过: 默认场景 (已存在)')
}
}
// ============================================================================
// 主函数
// ============================================================================
async function main() {
console.log('========================================')
console.log('开始数据库种子初始化')
console.log('========================================\n')
try {
await seedBuiltinTemplates()
console.log('')
await seedDefaultAdmin()
console.log('')
await seedDefaultData()
console.log('')
console.log('========================================')
console.log('数据库种子初始化完成')
console.log('========================================')
} catch (error) {
console.error('种子初始化失败:', error)
throw error
}
}
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})