deotalandAi/openspec/changes/create-disassembly-workflow/specs/model-step/spec.md

360 lines
8.8 KiB
Markdown
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.

# Spec: 第三步 - 模型生成和展示功能
## ADDED Requirements
### 生成模型展示
- **Requirement**: 实现已生成模型的展示区域,支持点击放大预览功能
- **Scenario**: 用户可以看到通过第二步生成的3D模型模型以缩略图形式展示点击后可以使用ModelViewer组件进行全屏预览
### 重新生成功能
- **Requirement**: 添加重新生成模型按钮,支持更新模型内容
- **Scenario**: 用户对当前生成的模型不满意时,点击重新生成按钮,系统重新执行模型生成算法
### 导出功能
- **Requirement**: 实现导出功能,提供不同格式的模型文件导出选项
- **Scenario**: 用户可以选择不同的文件格式如GLB、OBJ、FBX等进行模型导出下载到本地
### 发货功能
- **Requirement**: 添加发货按钮,点击后进入第四步物流发货环节
- **Scenario**: 用户满意模型结果后,点击发货按钮,系统进入物流信息填写流程并进入第四步
### 模型预览集成
- **Requirement**: 集成现有的ModelViewer组件进行全屏模型预览
- **Scenario**: 点击模型缩略图后弹出全屏预览对话框,用户可以旋转、缩放、查看模型详情
## 技术实现细节
### 布局结构
```html
<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>
```
### 模型预览对话框
```javascript
const modelPreviewVisible = ref(false)
const openModelPreview = () => {
modelPreviewVisible.value = true
}
```
```html
<!-- 模型预览对话框 -->
<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>
```
### 重新生成逻辑
```javascript
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
}
}
```
### 导出功能实现
```javascript
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
}
```
### 发货流程
```javascript
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
}
}
```
### 样式实现
```css
.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%;
}
}
```
## 数据模拟
```javascript
// 模拟不同模型数据
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组件在预览对话框中正常工作
- 重新生成按钮能够触发模型重新生成流程
- 导出功能能够正常下载不同格式的文件
- 发货按钮能够正常进入第四步
- 响应式布局在各种设备上表现良好