furniture-3d-system-Wu/docs/SKU详情页-装配编辑器区域-布局设计方案.md
WuYongJie 5821d73291 初始提交:3D家具安装展示系统
包含:
- furniture-3d-system:Next.js 16 全栈应用(资产中心、产品管理、3D装配动画)
- autocad-plugin:AutoCAD 家具导出插件源码
- 资产中心改造:编辑弹窗化、删除功能、Toast反馈、自定义Select组件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 14:00:19 +08:00

16 KiB
Raw Blame History

SKU详情页-装配编辑器区域-布局设计方案

本方案用于指导重写SKU详情页中的3D预览/装配编辑器区域UI

目标文件: src/app/products/[id]/skus/[skuId]/SKUDetailClient.workspace.tsx
重写范围: 仅JSX渲染部分(从return语句开始),保持所有业务逻辑不变
设计风格: 参考右侧边栏(SKUReadOnlySidebar)的白色卡片+青绿色品牌色风格


一、当前UI问题分析

截图中的问题

  1. 顶部工具栏杂乱: 复选框、滑块、文字混排,视觉层次不清
  2. "3D预览"标题区: 与下方内容区分不明显
  3. 播放控制条: 样式老旧,不够精致
  4. "安装顺序预览"卡片: 样式与其他卡片不统一
  5. 整体缺少品牌色强调: 没有突出青绿色主题

二、新布局设计

整体结构

┌─────────────────────────────────────────────────────────────────┐
│  顶部导航栏 (SKUDetailTopNav - 保持现有)                         │
├─────────────────────────────────────────────────────────────────┤
│  SKU标题区 (保持现有)                                            │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────────────────────────────────┐  ┌─────────────────┐ │
│  │                                      │  │   右侧边栏       │ │
│  │   左侧:3D预览区 (重写给这部分)        │  │   (保持现有)     │ │
│  │                                      │  │                 │ │
│  │  ┌────────────────────────────────┐  │  │                 │ │
│  │  │ 3D预览卡片                      │  │  │                 │ │
│  │  │ ┌──────────────────────────┐  │  │  │                 │ │
│  │  │ │  3D画布区域               │  │  │  │                 │ │
│  │  │ │                          │  │  │  │                 │ │
│  │  │ │  (ModelViewer组件)        │  │  │  │                 │ │
│  │  │ │                          │  │  │  │                 │ │
│  │  │ └──────────────────────────┘  │  │  │                 │ │
│  │  │                               │  │  │                 │ │
│  │  │ [悬浮播放控制条]               │  │  │                 │ │
│  │  └────────────────────────────────┘  │  │                 │ │
│  │                                      │  │                 │ │
│  │  ┌────────────────────────────────┐  │  │                 │ │
│  │  │ 装配步骤卡片 (重写给这部分)     │  │  │                 │ │
│  │  └────────────────────────────────┘  │  │                 │ │
│  │                                      │  │                 │ │
│  └──────────────────────────────────────┘  └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

三、组件详细设计

3.1 3D预览卡片

整体样式:

<div className="rounded-apple-lg bg-white border border-apple-separator shadow-xs overflow-hidden">

3.1.1 卡片头部

布局: 左侧标题 + 右侧工具按钮组

<div className="px-4 py-3 border-b border-apple-separator bg-apple-bg-primary">
  <div className="flex items-center justify-between">
    {/* 左侧:标题 */}
    <div className="flex items-center gap-2">
      <Box className="w-4 h-4 text-[#008D74]" strokeWidth={2} />
      <span className="text-apple-sm font-apple-system font-apple-semibold text-apple-label-primary">
        3D 预览
      </span>
      {/* 已选中板件数量标签 */}
      {highlightedParts.length > 0 && (
        <span 
          className="px-1.5 py-0.5 rounded-sm text-apple-xs font-apple-medium"
          style={{ backgroundColor: '#CFEAE2', color: '#005C4B' }}
        >
          已选中 {highlightedParts.length} 个板件
        </span>
      )}
    </div>
    
    {/* 右侧:工具按钮组 */}
    <div className="flex items-center gap-2">
      {/* 设置按钮 - 点击展开下拉菜单 */}
      <button className="flex items-center gap-1.5 px-3 py-1.5 rounded-apple-md text-apple-xs font-apple-system text-apple-label-secondary hover:text-apple-label-primary hover:bg-apple-bg-secondary/50 transition-colors">
        <Settings2 className="w-3.5 h-3.5" />
        <span>显示设置</span>
      </button>
    </div>
  </div>
  
  {/* 第二行:快捷工具栏 - 仅在展开时显示 */}
  <div className="flex items-center gap-4 mt-3 pt-3 border-t border-apple-separator/50">
    {/* 孔位高亮开关 */}
    <label className="flex items-center gap-2 cursor-pointer">
      <div className={cn(
        "w-8 h-4 rounded-full transition-colors relative",
        showHoleHighlights ? "bg-[#008D74]" : "bg-apple-fill-tertiary"
      )}>
        <div className={cn(
          "absolute top-0.5 w-3 h-3 rounded-full bg-white transition-transform",
          showHoleHighlights ? "translate-x-4" : "translate-x-0.5"
        )} />
      </div>
      <span className="text-apple-xs text-apple-label-secondary">孔位高亮</span>
    </label>
    
    {/* 折角线开关 */}
    <label className="flex items-center gap-2 cursor-pointer">
      <div className={cn(
        "w-8 h-4 rounded-full transition-colors relative",
        showFoldLines ? "bg-[#008D74]" : "bg-apple-fill-tertiary"
      )}>
        <div className={cn(
          "absolute top-0.5 w-3 h-3 rounded-full bg-white transition-transform",
          showFoldLines ? "translate-x-4" : "translate-x-0.5"
        )} />
      </div>
      <span className="text-apple-xs text-apple-label-secondary">折角线</span>
    </label>
    
    {/* 爆炸程度滑块 */}
    <div className="flex items-center gap-2 flex-1 max-w-[200px]">
      <span className="text-apple-xs text-apple-label-secondary whitespace-nowrap">爆炸</span>
      <Slider 
        min={0} 
        max={2} 
        step={0.01} 
        value={explosionProgress}
        onValueChange={setExplosionProgress}
        className="flex-1"
      />
      <span className="text-apple-xs text-apple-label-secondary w-10 text-right">
        {explosionProgress.toFixed(2)}x
      </span>
    </div>
  </div>
</div>

3.1.2 3D画布区域

保持现有ModelViewer组件不变,只调整容器样式:

<div 
  className="relative bg-apple-bg-secondary/30"
  style={{ height: 'calc(100vh - 360px)', minHeight: '400px', maxHeight: '600px' }}
>
  {/* ModelViewer组件保持现有 */}
  <ModelViewer 
    modelUrl={resolvedModelUrl}
    // ... 保持所有props不变
  />
  
  {/* ContextMenu保持现有 */}
  
  {/* 播放控制条 - 新版设计 */}
  {isViewerReady && !viewerLoadError && playbackSteps.length > 0 && (
    <PlaybackControlBar />
  )}
</div>

3.1.3 播放控制条组件 (PlaybackControlBar)

悬浮在画布底部中央:

function PlaybackControlBar() {
  return (
    <div className="absolute bottom-4 left-1/2 -translate-x-1/2">
      <div className="bg-white/95 backdrop-blur-md rounded-full pl-2 pr-4 py-2 shadow-lg border border-apple-separator flex items-center gap-2">
        {/* 重置按钮 */}
        <button 
          onClick={reset}
          className="w-8 h-8 rounded-full bg-apple-bg-secondary hover:bg-apple-fill-tertiary flex items-center justify-center transition-colors"
        >
          <RotateCcw className="w-4 h-4 text-apple-label-secondary" />
        </button>
        
        {/* 上一步 */}
        <button
          onClick={() => goToStep(currentStep - 1)}
          disabled={!canGoPrev}
          className={cn(
            "w-8 h-8 rounded-full flex items-center justify-center transition-colors",
            canGoPrev 
              ? "bg-apple-bg-secondary hover:bg-apple-fill-tertiary" 
              : "opacity-40 cursor-not-allowed"
          )}
        >
          <ChevronLeft className="w-4 h-4 text-apple-label-secondary" />
        </button>
        
        {/* 播放/暂停按钮 - 品牌色主按钮 */}
        <button
          onClick={togglePlay}
          className="w-10 h-10 rounded-full bg-[#008D74] hover:bg-[#007A63] flex items-center justify-center transition-colors shadow-md"
        >
          {isPlaying ? (
            <Pause className="w-5 h-5 text-white" fill="white" />
          ) : (
            <Play className="w-5 h-5 text-white ml-0.5" fill="white" />
          )}
        </button>
        
        {/* 下一步 */}
        <button
          onClick={() => goToStep(currentStep + 1)}
          disabled={!canGoNext}
          className={cn(
            "w-8 h-8 rounded-full flex items-center justify-center transition-colors",
            canGoNext 
              ? "bg-apple-bg-secondary hover:bg-apple-fill-tertiary" 
              : "opacity-40 cursor-not-allowed"
          )}
        >
          <ChevronRight className="w-4 h-4 text-apple-label-secondary" />
        </button>
        
        {/* 步骤进度 */}
        <div className="flex items-center gap-1.5 ml-1">
          <span className="text-apple-sm font-apple-system font-apple-semibold text-apple-label-primary">
            {hasPlaybackStarted ? currentStep + 1 : '-'}
          </span>
          <span className="text-apple-xs text-apple-label-tertiary">/</span>
          <span className="text-apple-xs text-apple-label-tertiary">
            {playbackSteps.length}
          </span>
        </div>
      </div>
    </div>
  )
}

3.1.4 无模型空状态

{!resolvedModelUrl && (
  <div className="h-full flex flex-col items-center justify-center">
    <div className="w-20 h-20 rounded-apple-xl bg-[#EAF6F2] flex items-center justify-center text-[#008D74] mb-4">
      <Package className="w-10 h-10" strokeWidth={1.5} />
    </div>
    <p className="text-apple-md font-apple-system font-apple-semibold text-apple-label-primary mb-1">
      暂无 3D 模型
    </p>
    <p className="text-apple-xs text-apple-label-tertiary">
      该产品尚未上传模型文件
    </p>
  </div>
)}

3.2 装配步骤卡片

用于显示安装顺序预览:

<div className="mt-4 rounded-apple-lg bg-white border border-apple-separator shadow-xs overflow-hidden">
  {/* 头部 */}
  <div className="px-4 py-3 border-b border-apple-separator flex items-center justify-between">
    <div className="flex items-center gap-2">
      <Layers className="w-4 h-4 text-[#008D74]" strokeWidth={2} />
      <span className="text-apple-sm font-apple-system font-apple-semibold text-apple-label-primary">
        装配步骤
      </span>
      {orderedSteps.length > 0 && (
        <span 
          className="px-1.5 py-0.5 rounded-sm text-apple-xs font-apple-medium"
          style={{ backgroundColor: '#CFEAE2', color: '#005C4B' }}
        >
          {orderedSteps.length} 步
        </span>
      )}
    </div>
    
    {/* 去编辑按钮 */}
    <Link 
      href={`/products/${product.id}/assembly`}
      className="flex items-center gap-1 px-3 py-1.5 rounded-apple-md text-apple-xs font-apple-medium text-[#008D74] hover:bg-[#EAF6F2] transition-colors"
    >
      <Settings2 className="w-3.5 h-3.5" />
      <span>去编辑</span>
    </Link>
  </div>
  
  {/* 内容 */}
  <div className="p-4">
    {orderedSteps.length === 0 ? (
      <div className="text-center py-6">
        <div className="w-12 h-12 rounded-apple-lg bg-apple-bg-secondary flex items-center justify-center text-apple-label-quaternary mx-auto mb-3">
          <Layers className="w-6 h-6" />
        </div>
        <p className="text-apple-sm text-apple-label-secondary mb-1">
          暂无装配步骤
        </p>
        <p className="text-apple-xs text-apple-label-tertiary mb-3">
          当前 SKU 还没有保存装配步骤
        </p>
        <Link 
          href={`/products/${product.id}/assembly`}
          className="inline-flex items-center gap-1.5 px-4 py-2 rounded-apple-md bg-[#008D74] text-white text-apple-sm font-apple-medium hover:bg-[#007A63] transition-colors"
        >
          <Settings2 className="w-4 h-4" />
          <span>去装配编辑器</span>
        </Link>
      </div>
    ) : (
      <div className="space-y-2 max-h-48 overflow-y-auto pr-1">
        {orderedSteps.map((step, index) => (
          <div 
            key={step.id}
            className="flex items-center gap-3 p-2.5 rounded-apple-md bg-apple-bg-secondary/50 hover:bg-apple-bg-secondary transition-colors"
          >
            {/* 步骤序号 - 圆形徽章 */}
            <div 
              className="w-6 h-6 rounded-full flex items-center justify-center text-apple-xs font-apple-semibold flex-shrink-0"
              style={{ 
                backgroundColor: index === currentStep ? '#008D74' : '#E5E5E7',
                color: index === currentStep ? 'white' : '#666'
              }}
            >
              {step.order}
            </div>
            
            {/* 步骤信息 */}
            <div className="flex-1 min-w-0">
              <p className="text-apple-sm font-apple-system text-apple-label-primary truncate">
                {step.title}
              </p>
              {step.description && (
                <p className="text-apple-xs text-apple-label-tertiary truncate">
                  {step.description}
                </p>
              )}
            </div>
            
            {/* 步骤类型图标 */}
            {step.stepType === 'hardware' && (
              <Wrench className="w-3.5 h-3.5 text-apple-label-tertiary flex-shrink-0" />
            )}
          </div>
        ))}
      </div>
    )}
  </div>
</div>

四、样式规范汇总

4.1 颜色系统

用途 值 备注
品牌主色 #008D74 按钮、强调、选中状态
品牌深色 #007A63 hover状态
品牌浅色 #CFEAE2 标签背景、选中背景
品牌文字 #005C4B 标签文字
背景灰 #FAFAFA 页面背景
卡片白 white 卡片背景
边框灰 border-apple-separator 卡片边框

4.2 圆角规范

  • 卡片: rounded-apple-lg (12px)
  • 按钮: rounded-apple-md (8px) 或 rounded-full (圆形)
  • 标签: rounded-sm (4px)
  • 开关: rounded-full

4.3 阴影规范

  • 卡片: shadow-xs
  • 悬浮控制条: shadow-lg
  • 主按钮: shadow-md

4.4 间距规范

  • 卡片内边距: p-4 (16px) 或 px-4 py-3 (16px 12px)
  • 元素间距: gap-2 (8px) 或 gap-3 (12px)
  • 外边距: mt-4 (16px)

4.5 字体规范

  • 标题: text-apple-sm font-apple-system font-apple-semibold
  • 正文: text-apple-xs font-apple-system
  • 标签: text-apple-xs font-apple-medium

五、注意事项

  1. 保持业务逻辑不变: 只修改JSX渲染部分,所有hooks、函数、状态管理保持原样
  2. 保持组件引用: ModelViewer、ContextMenu等复杂组件保持现有使用方式
  3. 响应式: 当前只考虑桌面端,移动端可后续优化
  4. 图标: 统一使用lucide-react,不要混用其他图标库

六、实施建议

  1. 先备份原文件
  2. 分段替换,每替换一部分就测试
  3. 重点关注:
    • 3D预览卡片头部工具栏
    • 播放控制条样式
    • 装配步骤卡片空状态和列表
  4. 保持所有事件处理函数、props传递不变

设计完成