8.8 KiB
8.8 KiB
Spec: 第三步 - 模型生成和展示功能
ADDED Requirements
生成模型展示
- Requirement: 实现已生成模型的展示区域,支持点击放大预览功能
- Scenario: 用户可以看到通过第二步生成的3D模型,模型以缩略图形式展示,点击后可以使用ModelViewer组件进行全屏预览
重新生成功能
- Requirement: 添加重新生成模型按钮,支持更新模型内容
- Scenario: 用户对当前生成的模型不满意时,点击重新生成按钮,系统重新执行模型生成算法
导出功能
- Requirement: 实现导出功能,提供不同格式的模型文件导出选项
- Scenario: 用户可以选择不同的文件格式(如GLB、OBJ、FBX等)进行模型导出,下载到本地
发货功能
- Requirement: 添加发货按钮,点击后进入第四步物流发货环节
- Scenario: 用户满意模型结果后,点击发货按钮,系统进入物流信息填写流程并进入第四步
模型预览集成
- Requirement: 集成现有的ModelViewer组件进行全屏模型预览
- Scenario: 点击模型缩略图后弹出全屏预览对话框,用户可以旋转、缩放、查看模型详情
技术实现细节
布局结构
<div class="model-step-content">
<div class="model-display">
<div class="model-header">
<h3>{{ t('disassembly.steps.model') }}</h3>
<el-tag type="success">{{ t('disassembly.status.completed') }}</el-tag>
</div>
<div class="model-preview-container">
<div
class="model-thumbnail"
@click="openModelPreview"
>
<ModelViewer
:model-url="generatedModelUrl"
:show-controls="false"
:loading-text="t('modelViewer.loadingModel')"
style="height: 300px; width: 100%;"
/>
<div class="preview-overlay">
<el-icon><View /></el-icon>
<span>{{ t('disassembly.actions.previewModel') }}</span>
</div>
</div>
</div>
</div>
<div class="step-actions">
<el-dropdown @command="handleExport">
<el-button type="info">
{{ t('disassembly.actions.export') }}
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="glb">GLB格式</el-dropdown-item>
<el-dropdown-item command="obj">OBJ格式</el-dropdown-item>
<el-dropdown-item command="fbx">FBX格式</el-dropdown-item>
<el-dropdown-item command="gltf">GLTF格式</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button
type="warning"
@click="regenerateModel"
:loading="regenerateLoading"
>
{{ t('disassembly.actions.regenerate') }}
</el-button>
<el-button
type="primary"
@click="proceedToShipping"
:loading="shippingLoading"
>
{{ t('disassembly.actions.ship') }}
</el-button>
</div>
</div>
模型预览对话框
const modelPreviewVisible = ref(false)
const openModelPreview = () => {
modelPreviewVisible.value = true
}
<!-- 模型预览对话框 -->
<el-dialog
:title="t('disassembly.modelPreview')"
v-model="modelPreviewVisible"
width="90%"
top="5vh"
:z-index="3000"
:append-to-body="true"
class="model-preview-dialog"
>
<div class="model-preview-content">
<ModelViewer
ref="modelViewerRef"
:model-url="generatedModelUrl"
:show-controls="true"
:loading-text="t('modelViewer.loadingModel')"
style="height: 70vh; width: 100%;"
/>
</div>
</el-dialog>
重新生成逻辑
const regenerateModel = async () => {
// 检查是否有第四步需要覆盖
if (workflowState.currentStep >= 4) {
const confirmed = await ElMessageBox.confirm(
t('disassembly.confirm.regenerateModelOverride'),
t('common.confirm'),
{
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
type: 'warning'
}
)
if (!confirmed) return
}
try {
regenerateLoading.value = true
// 重新生成模型(用户后续实现)
// const newModel = await regenerateModelAPI(contentId)
// 模拟重新生成过程
await new Promise(resolve => setTimeout(resolve, 4000))
// 更新模型数据(使用不同的demo模型或随机生成)
generatedModelUrl.value = generateMockModel()
// 清空第四步
if (workflowState.currentStep >= 4) {
workflowState.contentData.shippingInfo = {}
workflowState.stepStatus[4] = 'pending'
workflowState.currentStep = 3
}
ElMessage.success(t('disassembly.success.modelRegenerateCompleted'))
} catch (error) {
ElMessage.error(t('disassembly.errors.modelRegenerateFailed'))
} finally {
regenerateLoading.value = false
}
}
导出功能实现
const handleExport = async (format) => {
try {
ElMessage.info(t('disassembly.info.exporting'))
// 调用导出API(用户后续实现)
// await exportModelAPI(contentId, format)
// 模拟导出过程
await new Promise(resolve => setTimeout(resolve, 2000))
// 创建下载链接
const downloadUrl = generateDownloadUrl(format)
const link = document.createElement('a')
link.href = downloadUrl
link.download = `model-${contentId}.${format}`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
ElMessage.success(t('disassembly.success.exportCompleted'))
} catch (error) {
ElMessage.error(t('disassembly.errors.exportFailed'))
}
}
const generateDownloadUrl = (format) => {
// 模拟不同格式的下载链接
const mockUrls = {
glb: '/src/assets/demo/model.glb',
obj: '/src/assets/demo/model.obj', // 假设存在
fbx: '/src/assets/demo/model.fbx', // 假设存在
gltf: '/src/assets/demo/model.gltf' // 假设存在
}
return mockUrls[format] || mockUrls.glb
}
发货流程
const proceedToShipping = async () => {
try {
shippingLoading.value = true
// 更新步骤状态
updateStepStatus(3, 'completed')
updateStepStatus(4, 'current')
workflowState.currentStep = 4
ElMessage.success(t('disassembly.success.proceedToShipping'))
} catch (error) {
ElMessage.error(t('disassembly.errors.proceedToShippingFailed'))
} finally {
shippingLoading.value = false
}
}
样式实现
.model-display {
background: #ffffff;
border-radius: 12px;
padding: 24px;
margin-bottom: 32px;
box-shadow: 0 2px 8px rgba(107, 70, 193, 0.1);
}
.model-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.model-header h3 {
margin: 0;
color: #1f2937;
font-size: 18px;
font-weight: 600;
}
.model-preview-container {
display: flex;
justify-content: center;
align-items: center;
background: #f8fafc;
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
}
.model-thumbnail {
position: relative;
cursor: pointer;
border-radius: 8px;
overflow: hidden;
transition: transform 200ms ease;
}
.model-thumbnail:hover {
transform: scale(1.02);
}
.preview-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(107, 70, 193, 0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 200ms ease;
color: white;
}
.preview-overlay .el-icon {
font-size: 32px;
margin-bottom: 8px;
}
.model-thumbnail:hover .preview-overlay {
opacity: 1;
}
.step-actions {
display: flex;
gap: 16px;
justify-content: flex-end;
padding-top: 24px;
border-top: 1px solid #e5e7eb;
}
.step-actions .el-dropdown {
margin-right: auto;
}
/* 响应式设计 */
@media (max-width: 768px) {
.model-display {
padding: 16px;
}
.model-preview-container {
padding: 12px;
}
.step-actions {
flex-direction: column;
gap: 12px;
}
.step-actions .el-dropdown {
margin-right: 0;
width: 100%;
}
.step-actions .el-button {
width: 100%;
}
}
数据模拟
// 模拟不同模型数据
const generateMockModel = () => {
const models = [
'/src/assets/demo/model.glb',
'/src/assets/demo/model2.glb', // 假设存在
'/src/assets/demo/model3.glb' // 假设存在
]
return models[Math.floor(Math.random() * models.length)]
}
验证标准
- 生成模型能够正确加载和显示
- 点击模型能够弹出全屏预览对话框
- ModelViewer组件在预览对话框中正常工作
- 重新生成按钮能够触发模型重新生成流程
- 导出功能能够正常下载不同格式的文件
- 发货按钮能够正常进入第四步
- 响应式布局在各种设备上表现良好