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

6.8 KiB

Spec: 第二步 - 拆件结果展示功能

ADDED Requirements

拆件结果展示

  • Requirement: 实现已拆件图片内容的展示布局,支持多张图片的展示
  • Scenario: 用户可以看到拆件后的图片结果,图片以网格或列表形式展示,支持点击预览

重新生成功能

  • Requirement: 添加重新生成按钮,实现重新执行拆件逻辑并更新图片内容
  • Scenario: 用户对当前拆件结果不满意时,点击重新生成按钮,系统重新执行拆件算法并生成新的图片结果

生成模型功能

  • Requirement: 添加生成模型按钮,点击后进入第三步模型生成环节
  • Scenario: 用户满意拆件结果后,点击生成模型按钮,系统进入模型生成流程并进入第三步

步骤覆盖逻辑

  • Requirement: 实现上游操作覆盖下游内容的逻辑处理
  • Scenario: 用户在第二步点击重新生成时,如果已有第三步或第四步内容,系统会弹出确认对话框并清空下游步骤

技术实现细节

布局结构

<div class="disassembly-step-content">
  <div class="disassembly-results">
    <div class="results-header">
      <h3>{{ t('disassembly.steps.disassembly') }}</h3>
      <el-tag type="success">{{ t('disassembly.status.completed') }}</el-tag>
    </div>
    
    <div class="results-grid">
      <div 
        v-for="(image, index) in disassemblyImages" 
        :key="index"
        class="result-image-container"
        @click="openResultImagePreview(image)"
      >
        <img :src="image.url" :alt="`拆件结果 ${index + 1}`" />
        <div class="image-overlay">
          <el-icon><ZoomIn /></el-icon>
        </div>
      </div>
    </div>
  </div>
  
  <div class="step-actions">
    <el-button 
      type="warning" 
      @click="regenerateDisassembly"
      :loading="regenerateLoading"
    >
      {{ t('disassembly.actions.regenerate') }}
    </el-button>
    <el-button 
      type="primary" 
      @click="generateModel"
      :loading="modelGenerationLoading"
    >
      {{ t('disassembly.actions.generateModel') }}
    </el-button>
  </div>
</div>

重新生成逻辑

const regenerateDisassembly = async () => {
  // 检查是否有下游步骤需要覆盖
  if (hasDownstreamContent()) {
    const confirmed = await ElMessageBox.confirm(
      t('disassembly.confirm.regenerateOverride'),
      t('common.confirm'),
      {
        confirmButtonText: t('common.confirm'),
        cancelButtonText: t('common.cancel'),
        type: 'warning'
      }
    )
    
    if (!confirmed) return
  }
  
  try {
    regenerateLoading.value = true
    
    // 重新执行拆件逻辑(用户后续实现)
    // const newResults = await callDisassemblyAPI(contentId, true)
    
    // 模拟重新生成过程
    await new Promise(resolve => setTimeout(resolve, 3000))
    
    // 更新拆件结果
    disassemblyImages.value = generateMockDisassemblyResults()
    
    // 清空下游步骤
    clearDownstreamSteps(2)
    
    ElMessage.success(t('disassembly.success.regenerateCompleted'))
    
  } catch (error) {
    ElMessage.error(t('disassembly.errors.regenerateFailed'))
  } finally {
    regenerateLoading.value = false
  }
}

const generateModel = async () => {
  try {
    modelGenerationLoading.value = true
    
    // 生成模型(用户后续实现)
    // const modelData = await generateModelAPI(contentId)
    
    // 模拟模型生成过程
    await new Promise(resolve => setTimeout(resolve, 2000))
    
    // 更新步骤状态
    updateStepStatus(2, 'completed')
    updateStepStatus(3, 'current')
    workflowState.currentStep = 3
    workflowState.contentData.generatedModel = '/src/assets/demo/model.glb'
    
    ElMessage.success(t('disassembly.success.modelGenerationStarted'))
    
  } catch (error) {
    ElMessage.error(t('disassembly.errors.modelGenerationFailed'))
  } finally {
    modelGenerationLoading.value = false
  }
}

下游步骤覆盖逻辑

const hasDownstreamContent = () => {
  return workflowState.currentStep > 2
}

const clearDownstreamSteps = (fromStep) => {
  Object.keys(workflowState.stepStatus).forEach(step => {
    const stepNum = parseInt(step)
    if (stepNum > fromStep) {
      workflowState.stepStatus[step] = 'pending'
      if (stepNum === 3) {
        workflowState.contentData.generatedModel = null
      }
      if (stepNum === 4) {
        workflowState.contentData.shippingInfo = {}
      }
    }
  })
  
  // 调整当前步骤
  workflowState.currentStep = fromStep
}

样式实现

.disassembly-results {
  background: #ffffff;
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 32px;
  box-shadow: 0 2px 8px rgba(107, 70, 193, 0.1);
}

.results-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
}

.results-header h3 {
  margin: 0;
  color: #1f2937;
  font-size: 18px;
  font-weight: 600;
}

.results-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
  margin-bottom: 24px;
}

.result-image-container {
  position: relative;
  aspect-ratio: 1;
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  transition: transform 200ms ease;
}

.result-image-container:hover {
  transform: scale(1.02);
}

.result-image-container img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.image-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(107, 70, 193, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 200ms ease;
  color: white;
  font-size: 24px;
}

.result-image-container:hover .image-overlay {
  opacity: 1;
}

.step-actions {
  display: flex;
  gap: 16px;
  justify-content: flex-end;
  padding-top: 24px;
  border-top: 1px solid #e5e7eb;
}

/* 响应式设计 */
@media (max-width: 768px) {
  .disassembly-results {
    padding: 16px;
  }
  
  .results-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
  }
  
  .step-actions {
    flex-direction: column;
  }
}

数据模拟

// 模拟拆件结果数据
const generateMockDisassemblyResults = () => {
  return [
    {
      url: '/src/assets/demo/suoluetu.png',
      title: '拆件结果 1',
      description: '主体部分'
    },
    {
      url: '/src/assets/demo/suoluetu.png',
      title: '拆件结果 2',
      description: '细节部分'
    },
    {
      url: '/src/assets/demo/suoluetu.png',
      title: '拆件结果 3',
      description: '组件部分'
    }
  ]
}

验证标准

  • 拆件结果图片能够正确加载和显示
  • 点击图片能够弹出预览对话框
  • 重新生成按钮能够触发重新拆件流程
  • 生成模型按钮能够正常进入第三步
  • 步骤覆盖逻辑在各种情况下正常工作
  • 响应式布局在各种设备上表现良好