/** * 修复缺失的模板记录 * * 问题:数据库中缺少内置模板记录(builtin-xxx),导致素材上传失败 * 解决:在数据库中创建所有内置模板记录 * * 使用方法: * npx ts-node scripts/fix-missing-templates.ts */ import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() // 内置模板配置(直接定义,避免导入问题) const BUILTIN_TEMPLATE_KEYS = [ '3dmax-material', 'psd-material', 'audio-material', 'video-material', 'model-material', 'other-material', ] as const const BUILTIN_TEMPLATE_NAMES: Record = { '3dmax-material': '3DMAX 素材模板', 'psd-material': 'PSD 素材模板', 'audio-material': '音频素材模板', 'video-material': '视频素材模板', 'model-material': '模特素材模板', 'other-material': '其他素材模板', } const BUILTIN_TEMPLATE_DESCRIPTIONS: Record = { '3dmax-material': '适用于 3D 模型类素材', 'psd-material': '针对平面设计 PSD 素材', 'audio-material': '适用于音频文件素材,支持 MP3、WAV、FLAC、AAC 等格式', 'video-material': '适用于视频文件素材', 'model-material': '用于模特/人像素材', 'other-material': '适用于其他类型的素材', } const BUILTIN_TEMPLATE_TYPES: Record = { '3dmax-material': '3DMAX', 'psd-material': 'PSD', 'audio-material': '音频', 'video-material': '视频', 'model-material': '模特', 'other-material': '其他', } const BUILTIN_TEMPLATE_KINDS: Record = { '3dmax-material': 'IMAGE', 'psd-material': 'IMAGE', 'audio-material': 'AUDIO', 'video-material': 'VIDEO', 'model-material': 'IMAGE', 'other-material': 'OTHER', } const BUILTIN_TEMPLATE_FIELDS: Record = { '3dmax-material': ['productModel', 'categoryName', 'sceneName', 'renderer'], 'psd-material': ['productModel', 'categoryName', 'sceneName'], 'audio-material': ['productModel', 'audioCategory', 'audioScene', 'authorName'], 'video-material': ['productModel', 'categoryName', 'sceneName'], 'model-material': ['productModel', 'categoryName', 'sceneName'], 'other-material': ['productModel', 'categoryName', 'sceneName'], } const STANDARD_FIELDS: Record = { productModel: { key: 'productModel', label: 'SPU型号', type: 'text', required: true, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true }, categoryName: { key: 'categoryName', label: '使用店铺', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true }, sceneName: { key: 'sceneName', label: '场景类别', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true }, renderer: { key: 'renderer', label: '渲染器', type: 'select', required: false, layoutWidth: 'full', visibleOnHomeFilter: false, isSearchable: true, addToCategory: true }, audioCategory: { key: 'audioCategory', label: '音频分类', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true }, audioScene: { key: 'audioScene', label: '使用场景', type: 'select', required: true, layoutWidth: 'full', visibleOnHomeFilter: true, isSearchable: true, addToCategory: true }, authorName: { key: 'authorName', label: '作者', type: 'text', required: false, layoutWidth: 'half', visibleOnHomeFilter: false, isSearchable: true }, } async function fixMissingTemplates() { console.log('开始修复缺失的模板记录...\n') try { // 1. 检查哪些内置模板缺失 console.log('步骤 1: 检查内置模板...') const existingTemplates = await prisma.uploadTemplate.findMany({ where: { id: { in: BUILTIN_TEMPLATE_KEYS.map(key => `builtin-${key}`) } }, select: { id: true, name: true } }) const existingIds = new Set(existingTemplates.map(t => t.id)) console.log(`已存在的内置模板: ${existingTemplates.length}/${BUILTIN_TEMPLATE_KEYS.length}`) existingTemplates.forEach(t => console.log(` ✓ ${t.id} - ${t.name}`)) const missingKeys = BUILTIN_TEMPLATE_KEYS.filter( key => !existingIds.has(`builtin-${key}`) ) if (missingKeys.length === 0) { console.log('\n✓ 所有内置模板都已存在,无需修复') return true } console.log(`\n缺失的内置模板: ${missingKeys.length}`) missingKeys.forEach(key => console.log(` ✗ builtin-${key} - ${BUILTIN_TEMPLATE_NAMES[key]}`)) // 2. 创建缺失的内置模板 console.log('\n步骤 2: 创建缺失的内置模板...') for (const key of missingKeys) { const templateId = `builtin-${key}` const name = BUILTIN_TEMPLATE_NAMES[key] const description = BUILTIN_TEMPLATE_DESCRIPTIONS[key] const materialType = BUILTIN_TEMPLATE_TYPES[key] const materialKind = BUILTIN_TEMPLATE_KINDS[key] const fieldKeys = BUILTIN_TEMPLATE_FIELDS[key] || [] console.log(`\n创建模板: ${name} (${templateId})`) // 创建模板记录 const template = await prisma.uploadTemplate.create({ data: { id: templateId, name, description, status: 'active', materialType, materialKind, version: 1, }, }) console.log(` ✓ 模板记录已创建`) // 创建字段记录 const requireRenderer = key === '3dmax-material' const fieldOverrides: Record> = { productModel: { required: true }, categoryName: { required: true }, sceneName: { required: true }, } if (requireRenderer) { fieldOverrides.renderer = { required: true } } for (let i = 0; i < fieldKeys.length; i++) { const fieldKey = fieldKeys[i] const fieldConfig = STANDARD_FIELDS[fieldKey] if (!fieldConfig) { console.warn(` ⚠ 字段配置不存在: ${fieldKey}`) continue } const override = fieldOverrides[fieldKey] || {} await prisma.uploadField.create({ data: { templateId: template.id, key: fieldConfig.key, label: fieldConfig.label, type: fieldConfig.type, required: override.required ?? fieldConfig.required, orderIndex: i, layoutWidth: fieldConfig.layoutWidth, validations: null, optionsSource: null, optionsStatic: fieldConfig.optionsStatic || null, allowedExtensions: null, visibleOnHomeFilter: fieldConfig.visibleOnHomeFilter, isSearchable: fieldConfig.isSearchable, addToCategory: fieldConfig.addToCategory || false, conditions: null, }, }) } console.log(` ✓ ${fieldKeys.length} 个字段已创建`) } // 3. 验证修复结果 console.log('\n步骤 3: 验证修复结果...') const allTemplates = await prisma.uploadTemplate.findMany({ where: { id: { in: BUILTIN_TEMPLATE_KEYS.map(key => `builtin-${key}`) } }, select: { id: true, name: true, status: true } }) console.log(`\n内置模板总数: ${allTemplates.length}/${BUILTIN_TEMPLATE_KEYS.length}`) allTemplates.forEach(t => console.log(` ✓ ${t.id} - ${t.name} (${t.status})`)) if (allTemplates.length === BUILTIN_TEMPLATE_KEYS.length) { console.log('\n✓ 所有内置模板修复完成!') return true } else { console.warn('\n⚠ 部分模板修复失败,请检查日志') return false } } catch (error) { console.error('\n✗ 修复失败:', error) throw error } finally { await prisma.$disconnect() } } // 执行脚本 fixMissingTemplates() .then((success) => { process.exit(success ? 0 : 1) }) .catch((error) => { console.error('脚本执行失败:', error) process.exit(1) })