1. 排行榜悬浮窗关闭后小按钮消失问题 - 修复面板居中模式关闭时按钮位置错误 - 保存并恢复按钮的居中状态 2. 首页tag按钮按素材数量排序 - 添加按素材数量降序排序逻辑 3. 上传弹窗输入框浅色模式文字不可见 - 添加 text-slate-900 确保浅色模式下文字可见 - 添加 placeholder 样式确保占位符正确显示
225 lines
7.9 KiB
TypeScript
225 lines
7.9 KiB
TypeScript
/**
|
||
* 修复缺失的模板记录
|
||
*
|
||
* 问题:数据库中缺少内置模板记录(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<string, string> = {
|
||
'3dmax-material': '3DMAX 素材模板',
|
||
'psd-material': 'PSD 素材模板',
|
||
'audio-material': '音频素材模板',
|
||
'video-material': '视频素材模板',
|
||
'model-material': '模特素材模板',
|
||
'other-material': '其他素材模板',
|
||
}
|
||
|
||
const BUILTIN_TEMPLATE_DESCRIPTIONS: Record<string, string> = {
|
||
'3dmax-material': '适用于 3D 模型类素材',
|
||
'psd-material': '针对平面设计 PSD 素材',
|
||
'audio-material': '适用于音频文件素材,支持 MP3、WAV、FLAC、AAC 等格式',
|
||
'video-material': '适用于视频文件素材',
|
||
'model-material': '用于模特/人像素材',
|
||
'other-material': '适用于其他类型的素材',
|
||
}
|
||
|
||
const BUILTIN_TEMPLATE_TYPES: Record<string, string> = {
|
||
'3dmax-material': '3DMAX',
|
||
'psd-material': 'PSD',
|
||
'audio-material': '音频',
|
||
'video-material': '视频',
|
||
'model-material': '模特',
|
||
'other-material': '其他',
|
||
}
|
||
|
||
const BUILTIN_TEMPLATE_KINDS: Record<string, string> = {
|
||
'3dmax-material': 'IMAGE',
|
||
'psd-material': 'IMAGE',
|
||
'audio-material': 'AUDIO',
|
||
'video-material': 'VIDEO',
|
||
'model-material': 'IMAGE',
|
||
'other-material': 'OTHER',
|
||
}
|
||
|
||
const BUILTIN_TEMPLATE_FIELDS: Record<string, string[]> = {
|
||
'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<string, any> = {
|
||
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<string, Partial<{ required: boolean }>> = {
|
||
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)
|
||
})
|