358 lines
13 KiB
JavaScript
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.

/* eslint-disable @typescript-eslint/no-require-imports */
/**
* 更新演示数据脚本 - 添加真实的3D模型
* 使用更好的3D模型替换默认的演示模型
*/
const DEMO_CONFIRM_TOKEN = 'reset-local-demo'
let prisma = null
let pool = null
function assertLocalDestructiveDemoAllowed() {
if (!process.argv.includes(`--confirm=${DEMO_CONFIRM_TOKEN}`)) {
throw new Error(`拒绝执行 destructive demo seed:必须显式追加 --confirm=${DEMO_CONFIRM_TOKEN}。`)
}
if (process.env.NODE_ENV === 'production') {
throw new Error('拒绝执行 destructive demo seed:NODE_ENV=production。')
}
const databaseUrl = process.env.DATABASE_URL || ''
if (!databaseUrl) {
throw new Error('拒绝执行 destructive demo seed:DATABASE_URL 未配置。')
}
const parsed = new URL(databaseUrl)
if (!['localhost', '127.0.0.1', '::1'].includes(parsed.hostname)) {
throw new Error('拒绝执行 destructive demo seed:只允许本机数据库。')
}
}
function createPrismaClient() {
const { PrismaClient } = require('@prisma/client')
const { PrismaPg } = require('@prisma/adapter-pg')
const { Pool } = require('pg')
pool = new Pool({ connectionString: process.env.DATABASE_URL })
prisma = new PrismaClient({ adapter: new PrismaPg(pool) })
}
// 优质的免费3D模型资源
const QUALITY_MODELS = [
{
name: '现代办公椅',
sku: 'CHAIR-MOD-001',
category: '办公家具',
modelUrl: 'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf',
thumbnailUrl: 'https://threejs.org/examples/screenshots/misc_controls_orbit.jpg',
description: '现代简约设计的办公椅,符合人体工程学'
},
{
name: '实木餐桌',
sku: 'TABLE-WOOD-001',
category: '餐厅家具',
modelUrl: 'https://threejs.org/examples/models/gltf/Flamingo.glb',
thumbnailUrl: 'https://threejs.org/examples/screenshots/webgl_animation_skinning_blending.jpg',
description: '天然实木制作的餐桌,环保耐用'
},
{
name: '北欧书架',
sku: 'SHELF-NORDIC-001',
category: '书房家具',
modelUrl: 'https://threejs.org/examples/models/gltf/Parrot.glb',
thumbnailUrl: 'https://threejs.org/examples/screenshots/webgl_animation_skinning_morph.jpg',
description: '北欧风格设计的多层书架,简洁实用'
}
]
async function updateDemoModels() {
assertLocalDestructiveDemoAllowed()
createPrismaClient()
console.log('🔄 开始更新演示模型...')
try {
// 清理现有数据
console.log('🧹 清理现有数据...')
await prisma.shareLink.deleteMany()
await prisma.assemblyStep.deleteMany()
await prisma.panel.deleteMany()
await prisma.product.deleteMany()
// 创建新的产品和数据
for (let i = 0; i < QUALITY_MODELS.length; i++) {
const modelData = QUALITY_MODELS[i]
console.log(`📦 创建产品: ${modelData.name}`)
// 创建产品
const product = await prisma.product.create({
data: {
name: modelData.name,
sku: modelData.sku,
category: modelData.category,
modelUrl: modelData.modelUrl,
thumbnailUrl: modelData.thumbnailUrl
}
})
// 为每个产品创建装配步骤
const steps = generateAssemblySteps(modelData.name, i)
for (const stepData of steps) {
await prisma.assemblyStep.create({
data: {
...stepData,
productId: product.id
}
})
}
// 创建板件信息
const panels = generatePanels(modelData.name, modelData.category)
for (const panelData of panels) {
await prisma.panel.create({
data: {
...panelData,
productId: product.id
}
})
}
// 创建分享链接
const shareToken = `demo${i + 1}${Math.random().toString(36).substr(2, 3)}`
await prisma.shareLink.create({
data: {
productId: product.id,
token: shareToken,
expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), // 90天后过期
viewCount: Math.floor(Math.random() * 50) + 10 // 随机访问量
}
})
console.log(`✅ ${modelData.name} 创建完成 (分享链接: /share/${shareToken})`)
}
console.log('\n🎉 演示模型更新完成!')
console.log('\n📋 可用的分享链接:')
const allShareLinks = await prisma.shareLink.findMany({
include: {
product: true
}
})
allShareLinks.forEach(link => {
console.log(` ${link.product.name}: http://localhost:3000/share/${link.token}`)
})
console.log('\n📊 更新统计:')
const stats = await getStats()
console.log(` 产品: ${stats.products}个`)
console.log(` 装配步骤: ${stats.assemblySteps}个`)
console.log(` 板件: ${stats.panels}个`)
console.log(` 分享链接: ${stats.shareLinks}个`)
} catch (error) {
console.error('❌ 更新演示模型失败:', error)
throw error
} finally {
await prisma?.$disconnect()
await pool?.end()
}
}
// 生成装配步骤
function generateAssemblySteps(productName, index) {
const stepTemplates = [
// 办公椅步骤
[
{
order: 1,
title: '准备工作',
description: '检查包装内容,确认所有配件齐全',
partIds: ['base', 'wheels', 'screws'],
transform: { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 2000,
easing: 'power2.inOut'
},
{
order: 2,
title: '安装轮子',
description: '将5个轮子插入椅子底座的孔中',
partIds: ['base', 'wheel1', 'wheel2', 'wheel3', 'wheel4', 'wheel5'],
transform: { position: { x: 0, y: -0.3, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 3000,
easing: 'power2.inOut'
},
{
order: 3,
title: '安装气压杆',
description: '将气压杆插入底座中心孔',
partIds: ['base', 'cylinder'],
transform: { position: { x: 0, y: 0.2, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 2500,
easing: 'power2.inOut'
},
{
order: 4,
title: '安装座椅',
description: '将座椅放置在气压杆上,确保稳固',
partIds: ['seat', 'cylinder'],
transform: { position: { x: 0, y: 0.5, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 2500,
easing: 'power2.inOut'
},
{
order: 5,
title: '安装靠背',
description: '将靠背与座椅连接,调整到合适角度',
partIds: ['seat', 'backrest'],
transform: { position: { x: 0, y: 0.3, z: -0.2 }, rotation: { x: -0.1, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 3000,
easing: 'power2.inOut'
},
{
order: 6,
title: '最终检查',
description: '检查所有连接点,测试椅子功能',
partIds: ['complete'],
transform: { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: Math.PI * 2, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 4000,
easing: 'power2.inOut'
}
],
// 餐桌步骤
[
{
order: 1,
title: '准备桌腿',
description: '取出四条桌腿,检查表面是否完好',
partIds: ['leg1', 'leg2', 'leg3', 'leg4'],
transform: { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 2000,
easing: 'power2.inOut'
},
{
order: 2,
title: '安装桌腿',
description: '将桌腿固定到桌面底部的预留孔位',
partIds: ['tabletop', 'leg1', 'leg2', 'leg3', 'leg4'],
transform: { position: { x: 0, y: -0.4, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 4000,
easing: 'power2.inOut'
},
{
order: 3,
title: '加固连接',
description: '使用螺丝加固桌腿与桌面的连接',
partIds: ['tabletop', 'legs', 'screws'],
transform: { position: { x: 0, y: -0.2, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 3000,
easing: 'power2.inOut'
},
{
order: 4,
title: '翻转桌子',
description: '小心翻转桌子,使其正面朝上',
partIds: ['complete'],
transform: { position: { x: 0, y: 0, z: 0 }, rotation: { x: Math.PI, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 3500,
easing: 'power2.inOut'
}
],
// 书架步骤
[
{
order: 1,
title: '组装侧板',
description: '将左右两块侧板准备就位',
partIds: ['side_left', 'side_right'],
transform: { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 2000,
easing: 'power2.inOut'
},
{
order: 2,
title: '安装层板',
description: '依次安装各层隔板,从下往上',
partIds: ['side_left', 'side_right', 'shelf1', 'shelf2', 'shelf3'],
transform: { position: { x: 0, y: 0.1, z: 0 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 4000,
easing: 'power2.inOut'
},
{
order: 3,
title: '安装背板',
description: '将背板固定到书架后方',
partIds: ['frame', 'backpanel'],
transform: { position: { x: 0, y: 0, z: -0.1 }, rotation: { x: 0, y: 0, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 3000,
easing: 'power2.inOut'
},
{
order: 4,
title: '最终调整',
description: '检查书架稳定性,调整水平',
partIds: ['complete'],
transform: { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: Math.PI / 4, z: 0 }, scale: { x: 1, y: 1, z: 1 } },
duration: 2500,
easing: 'power2.inOut'
}
]
]
return stepTemplates[index] || stepTemplates[0]
}
// 生成板件信息
function generatePanels(productName, category) {
const panelTemplates = {
'办公家具': [
{ code: 'A01', name: '座椅面板', gltfNodeName: 'seat', length: 45.0, width: 40.0, thickness: 3.0 },
{ code: 'A02', name: '靠背面板', gltfNodeName: 'backrest', length: 40.0, width: 50.0, thickness: 2.5 },
{ code: 'B01', name: '椅腿组件', gltfNodeName: 'legs', length: 60.0, width: 60.0, thickness: 45.0 },
{ code: 'C01', name: '气压杆', gltfNodeName: 'cylinder', length: 5.0, width: 5.0, thickness: 20.0 }
],
'餐厅家具': [
{ code: 'T01', name: '桌面板', gltfNodeName: 'tabletop', length: 120.0, width: 80.0, thickness: 3.0 },
{ code: 'T02', name: '桌腿(前左)', gltfNodeName: 'leg1', length: 5.0, width: 5.0, thickness: 72.0 },
{ code: 'T03', name: '桌腿(前右)', gltfNodeName: 'leg2', length: 5.0, width: 5.0, thickness: 72.0 },
{ code: 'T04', name: '桌腿(后左)', gltfNodeName: 'leg3', length: 5.0, width: 5.0, thickness: 72.0 },
{ code: 'T05', name: '桌腿(后右)', gltfNodeName: 'leg4', length: 5.0, width: 5.0, thickness: 72.0 }
],
'书房家具': [
{ code: 'S01', name: '左侧板', gltfNodeName: 'side_left', length: 30.0, width: 180.0, thickness: 2.0 },
{ code: 'S02', name: '右侧板', gltfNodeName: 'side_right', length: 30.0, width: 180.0, thickness: 2.0 },
{ code: 'S03', name: '层板1', gltfNodeName: 'shelf1', length: 80.0, width: 28.0, thickness: 2.0 },
{ code: 'S04', name: '层板2', gltfNodeName: 'shelf2', length: 80.0, width: 28.0, thickness: 2.0 },
{ code: 'S05', name: '层板3', gltfNodeName: 'shelf3', length: 80.0, width: 28.0, thickness: 2.0 },
{ code: 'S06', name: '背板', gltfNodeName: 'backpanel', length: 80.0, width: 180.0, thickness: 0.5 }
]
}
return panelTemplates[category] || panelTemplates['办公家具']
}
// 获取统计信息
async function getStats() {
const [products, assemblySteps, panels, shareLinks] = await Promise.all([
prisma.product.count(),
prisma.assemblyStep.count(),
prisma.panel.count(),
prisma.shareLink.count()
])
return { products, assemblySteps, panels, shareLinks }
}
// 运行脚本
if (require.main === module) {
updateDemoModels()
.then(() => {
console.log('\n✨ 演示模型更新完成!现在可以体验更丰富的3D展示效果了!')
process.exit(0)
})
.catch((error) => {
console.error('脚本执行失败:', error)
process.exit(1)
})
}
module.exports = { updateDemoModels }