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

428 lines
16 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.

# 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预览卡片
**整体样式**:
```tsx
<div className="rounded-apple-lg bg-white border border-apple-separator shadow-xs overflow-hidden">
```
#### 3.1.1 卡片头部
**布局**: 左侧标题 + 右侧工具按钮组
```tsx
<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组件不变,只调整容器样式**:
```tsx
<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)
**悬浮在画布底部中央**:
```tsx
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 无模型空状态
```tsx
{!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 装配步骤卡片
**用于显示安装顺序预览**:
```tsx
<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传递不变
---
**设计完成**