149 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const CONFIRM_TOKEN = 'update-local-model-urls'
function assertLocalModelUrlUpdateAllowed() {
if (!process.argv.includes(`--confirm=${CONFIRM_TOKEN}`)) {
throw new Error(`update-model-urls 只允许显式本地维护:请追加 --confirm=${CONFIRM_TOKEN}。`)
}
if (process.env.NODE_ENV === 'production') {
throw new Error('update-model-urls 禁止在 NODE_ENV=production 下执行。')
}
const databaseUrl = process.env.DATABASE_URL || ''
if (!databaseUrl) {
throw new Error('update-model-urls 必须先配置 DATABASE_URL。')
}
const host = new URL(databaseUrl).hostname
if (!['localhost', '127.0.0.1', '::1'].includes(host)) {
throw new Error('update-model-urls 只允许连接本机数据库。')
}
}
// Three.js 官方示例模型 URL 列表
const THREE_JS_MODELS = {
// 高质量 PBR 模型
DamagedHelmet: 'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf',
FlightHelmet: 'https://threejs.org/examples/models/gltf/FlightHelmet/glTF/FlightHelmet.gltf',
// 动物模型
Horse: 'https://threejs.org/examples/models/gltf/Horse.glb',
Flamingo: 'https://threejs.org/examples/models/gltf/Flamingo.glb',
Parrot: 'https://threejs.org/examples/models/gltf/Parrot.glb',
Stork: 'https://threejs.org/examples/models/gltf/Stork.glb',
// 其他模型
Duck: 'https://threejs.org/examples/models/gltf/Duck/glTF/Duck.gltf',
BoomBox: 'https://threejs.org/examples/models/gltf/BoomBox/glTF/BoomBox.gltf',
Avocado: 'https://threejs.org/examples/models/gltf/Avocado/glTF/Avocado.gltf',
Fox: 'https://threejs.org/examples/models/gltf/Fox/glTF/Fox.gltf',
// 角色模型(带动画)
RobotExpressive: 'https://threejs.org/examples/models/gltf/RobotExpressive/RobotExpressive.glb',
Soldier: 'https://threejs.org/examples/models/gltf/Soldier.glb',
// 建筑/家具相关
SheenChair: 'https://threejs.org/examples/models/gltf/SheenChair.glb',
MaterialsVariantsShoe: 'https://threejs.org/examples/models/gltf/MaterialsVariantsShoe/glTF/MaterialsVariantsShoe.gltf',
// 车辆
Ferrari: 'https://threejs.org/examples/models/gltf/ferrari.glb',
}
// 模型预览图 URL(来自 Three.js 官网截图)
const THUMBNAILS = {
DamagedHelmet: 'https://threejs.org/examples/screenshots/webgl_loader_gltf.jpg',
FlightHelmet: 'https://threejs.org/examples/screenshots/webgl_loader_gltf_compressed.jpg',
Horse: 'https://threejs.org/examples/screenshots/webgl_morphtargets_horse.jpg',
Flamingo: 'https://threejs.org/examples/screenshots/webgl_loader_gltf.jpg',
Duck: 'https://threejs.org/examples/screenshots/webgl_loader_gltf.jpg',
Fox: 'https://threejs.org/examples/screenshots/webgl_animation_skinning_blending.jpg',
RobotExpressive: 'https://threejs.org/examples/screenshots/webgl_animation_skinning_morph.jpg',
Ferrari: 'https://threejs.org/examples/screenshots/webgl_materials_car.jpg',
SheenChair: 'https://threejs.org/examples/screenshots/webgl_loader_gltf_sheen.jpg',
}
async function main() {
assertLocalModelUrlUpdateAllowed()
const { prisma } = await import('../src/lib/prisma')
console.log('开始更新产品模型 URL...\n')
try {
// 获取所有产品
const products = await prisma.product.findMany({
orderBy: { modelNumber: 'asc' }
})
console.log(`找到 ${products.length} 个产品\n`)
// 模型分配方案(模拟不同产品使用不同模型)
const modelAssignments: Record<string, { model: keyof typeof THREE_JS_MODELS; thumbnail?: keyof typeof THUMBNAILS }> = {
'SF-001': { model: 'SheenChair', thumbnail: 'SheenChair' }, // 沙发 -> 椅子模型
'TB-001': { model: 'Duck', thumbnail: 'Duck' }, // 餐桌 -> 鸭子(测试)
'CH-001': { model: 'SheenChair', thumbnail: 'SheenChair' }, // 椅子 -> 椅子模型
'BD-001': { model: 'Fox', thumbnail: 'Fox' }, // 床 -> 狐狸(测试)
'CB-001': { model: 'BoomBox' }, // 柜子 -> 音箱(测试)
'WD-001': { model: 'Avocado' }, // 衣柜 -> 牛油果(测试)
'DK-001': { model: 'RobotExpressive', thumbnail: 'RobotExpressive' }, // 书桌 -> 机器人
'SH-001': { model: 'MaterialsVariantsShoe' }, // 鞋柜 -> 鞋子模型
'TV-001': { model: 'FlightHelmet', thumbnail: 'FlightHelmet' }, // 电视柜 -> 头盔
'CF-001': { model: 'Horse', thumbnail: 'Horse' }, // 茶几 -> 马(测试)
}
// 默认模型(对于没有特定分配的产品)
const defaultModels = [
{ model: 'DamagedHelmet' as const, thumbnail: 'DamagedHelmet' as const },
{ model: 'Flamingo' as const },
{ model: 'Parrot' as const },
{ model: 'Stork' as const },
{ model: 'Soldier' as const },
{ model: 'Ferrari' as const, thumbnail: 'Ferrari' as const },
]
let updatedCount = 0
for (let i = 0; i < products.length; i++) {
const product = products[i]
// 查找分配的模型
let assignment = modelAssignments[product.modelNumber]
// 如果没有特定分配,使用默认模型轮换
if (!assignment) {
assignment = defaultModels[i % defaultModels.length]
}
const modelUrl = THREE_JS_MODELS[assignment.model]
const thumbnailUrl = assignment.thumbnail ? THUMBNAILS[assignment.thumbnail] : null
// 更新产品
await prisma.product.update({
where: { id: product.id },
data: {
modelUrl,
...(thumbnailUrl && { thumbnailUrl })
}
})
console.log(`✓ ${product.modelNumber} (${product.name})`)
console.log(` 模型: ${assignment.model}`)
if (thumbnailUrl) {
console.log(` 预览图: ${assignment.thumbnail}`)
}
console.log()
updatedCount++
}
console.log(`\n完成!已更新 ${updatedCount} 个产品的模型 URL`)
// 显示模型 URL 汇总
console.log('\n=== 可用的 Three.js 示例模型 ===')
for (const [name, url] of Object.entries(THREE_JS_MODELS)) {
console.log(`${name}: ${url}`)
}
} finally {
await prisma.$disconnect()
}
}
main()
.catch(console.error)