Jelajahi Sumber

feat:交付执行阶段布局和功能重构以及订单分配阶段优化逻辑

徐福静0235668 2 hari lalu
induk
melakukan
90efa6148a

+ 303 - 0
DELIVERY_REFACTOR_V2_SUMMARY.md

@@ -0,0 +1,303 @@
+# 交付执行页面重构 V2 - 完整总结
+
+## 📋 需求概述
+
+根据用户提供的设计图和需求,完成了交付执行页面的全面重构:
+
+### 核心需求
+1. ✅ **展开后显示4个阶段** - 建模(白膜)、软装、渲染、后期横向排列
+2. ✅ **2/4 完成度显示** - 右上角显示已完成阶段数/总阶段数
+3. ✅ **柔化边缘设计** - 圆角、渐变、阴影效果,不是规规矩矩的矩形
+4. ✅ **图片数量标记** - 每个阶段卡片显示图片数量徽章
+5. ✅ **精美样式设计** - 参考原有设计,使用渐变色区分不同阶段
+6. ✅ **空间级别确认** - 整个空间共用一个确认按钮,不是每个阶段都要确认
+7. ✅ **变更检测** - 文件变更后需要重新确认
+8. ✅ **完整记录** - 记录确认人身份、时间等信息
+
+---
+
+## 🎨 界面设计
+
+### 1. 空间头部(折叠状态)
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 空间1    [建模] [软装] [渲染] [后期]                    ▼  │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 2. 空间内容(展开状态)
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 空间1                                              [2/4]    │
+├─────────────────────────────────────────────────────────────┤
+│                                                              │
+│  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐                   │
+│  │ 建模 │  │ 软装 │  │ 渲染 │  │ 后期 │                   │
+│  │  3   │  │  5   │  │      │  │      │                   │
+│  │ [图] │  │ [图] │  │ 渲染 │  │ 后期 │                   │
+│  │      │  │      │  │关联  │  │上传  │                   │
+│  └──────┘  └──────┘  │任务  │  │大图  │                   │
+│                      └──────┘  └──────┘                   │
+│                                                              │
+│  [点击阶段查看文件详情]                                      │
+│                                                              │
+│  ┌────────────────────────────────────────────────────┐    │
+│  │ 建模 - 文件列表                    [上传文件]      │    │
+│  │ [图1] [图2] [图3]                                  │    │
+│  └────────────────────────────────────────────────────┘    │
+│                                                              │
+│  ┌────────────────────────────────────────────────────┐    │
+│  │          ✓ 交付执行清单确认                         │    │
+│  └────────────────────────────────────────────────────┘    │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🔧 技术实现
+
+### 1. 数据结构变更
+
+#### 新增接口:SpaceConfirmation
+```typescript
+interface SpaceConfirmation {
+  confirmedBy: string;        // 确认人ID
+  confirmedByName: string;     // 确认人姓名
+  confirmedByRole: string;     // 确认人角色
+  confirmedAt: Date;           // 确认时间
+  spaceId: string;             // 空间ID
+  filesSnapshot: string[];     // 文件ID快照(用于变更检测)
+}
+```
+
+#### Project.data 存储结构
+```javascript
+{
+  spaceConfirmations: {
+    [spaceId]: {
+      confirmedBy: "userId",
+      confirmedByName: "张三",
+      confirmedByRole: "设计师",
+      confirmedAt: "2024-01-01T12:00:00Z",
+      spaceId: "spaceId",
+      filesSnapshot: ["fileId1", "fileId2", "fileId3", ...]
+    }
+  }
+}
+```
+
+### 2. 核心方法
+
+#### 空间管理
+- `getCompletedStagesCount(spaceId)` - 获取已完成阶段数(有文件的阶段)
+- `getSpaceTotalFileCount(spaceId)` - 获取空间所有文件总数
+- `getSpaceStageFileCount(spaceId, stageType)` - 获取某阶段文件数
+
+#### 确认功能
+- `confirmSpace(spaceId)` - 确认整个空间的交付执行清单
+- `isSpaceConfirmed(spaceId)` - 检查空间是否已确认
+- `hasSpaceFilesChanged(spaceId)` - 检查文件是否有变更
+- `getSpaceConfirmation(spaceId)` - 获取空间确认信息
+- `getSpaceConfirmationText(spaceId)` - 获取确认显示文本
+
+#### 变更检测逻辑
+```typescript
+// 确认时保存文件快照
+filesSnapshot: ["file1", "file2", "file3"]
+
+// 检测变更时对比
+currentFiles: ["file1", "file2", "file4"]  // file3被删除,file4新增
+→ 检测到变更,需要重新确认
+```
+
+---
+
+## 🎨 样式设计特点
+
+### 1. 柔化边缘效果
+- **圆角**: `border-radius: 12px-16px`
+- **阴影**: `box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04)`
+- **渐变背景**: `linear-gradient(135deg, ...)`
+- **平滑过渡**: `transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1)`
+
+### 2. 阶段卡片颜色方案
+```scss
+// 建模(白膜)- 蓝紫色
+background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%);
+border-color: #a5b4fc;
+
+// 软装 - 粉色
+background: linear-gradient(135deg, #fce7f3 0%, #fbcfe8 100%);
+border-color: #f9a8d4;
+
+// 渲染 - 蓝色
+background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
+border-color: #93c5fd;
+
+// 后期 - 绿色
+background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
+border-color: #6ee7b7;
+```
+
+### 3. 交互效果
+- **Hover 上移**: `transform: translateY(-4px)`
+- **阴影增强**: `box-shadow: 0 8px 24px rgba(...)`
+- **边框变色**: `border-color: #667eea`
+- **图片数量徽章**: 渐变紫色圆形徽章
+
+---
+
+## 📊 功能流程
+
+### 1. 文件上传流程
+```
+1. 点击阶段卡片 → 展开文件详情
+2. 点击"上传文件"按钮 → 选择文件
+3. 文件上传到对应空间的对应阶段
+4. 阶段卡片显示图片数量徽章
+5. 完成度更新(如 1/4 → 2/4)
+```
+
+### 2. 确认流程
+```
+1. 空间有文件 → 显示"交付执行清单确认"按钮
+2. 点击确认 → 弹出确认对话框
+   - 显示已完成阶段数
+   - 显示总文件数
+   - 提示确认后如有变更需重新确认
+3. 确认成功 → 保存确认信息和文件快照
+4. 显示已确认状态(绿色)
+```
+
+### 3. 变更检测流程
+```
+1. 空间已确认 → 显示"已确认"状态
+2. 上传新文件或删除文件 → 检测到变更
+3. 显示"文件已变更,需重新确认"按钮(橙色)
+4. 重新确认 → 更新确认信息和文件快照
+```
+
+---
+
+## 🗂️ 文件变更清单
+
+### 修改的文件
+1. **stage-delivery.component.html** - 完全重构布局
+   - 空间头部显示4个阶段标签
+   - 展开后显示4个阶段卡片(横向)
+   - 阶段卡片显示图片预览和数量
+   - 空间级别确认按钮
+   - 文件详情展示区域
+
+2. **stage-delivery.component.ts** - 新增空间确认功能
+   - 新增 `SpaceConfirmation` 接口
+   - 新增空间确认相关方法
+   - 新增变更检测逻辑
+   - 保留旧方法以向后兼容
+
+3. **stage-delivery.component.scss** - 精美样式设计
+   - 4个阶段卡片网格布局
+   - 不同阶段的渐变色方案
+   - 柔化边缘效果(圆角、阴影)
+   - 图片数量徽章样式
+   - 确认按钮样式(绿色/橙色)
+
+### 备份文件
+- `stage-delivery.component.html.backup`
+- `stage-delivery.component.scss.backup`
+
+---
+
+## ✨ 亮点特性
+
+### 1. 智能变更检测
+- 通过文件ID快照对比,精确检测文件变更
+- 支持文件新增、删除、替换的检测
+- 变更后自动提示需要重新确认
+
+### 2. 精美视觉设计
+- 柔化边缘,不是规规矩矩的矩形
+- 不同阶段使用不同渐变色
+- Hover 效果流畅自然
+- 图片数量徽章醒目美观
+
+### 3. 用户体验优化
+- 一键确认整个空间,不需要逐个阶段确认
+- 2/4 完成度一目了然
+- 点击阶段卡片查看详情
+- 文件预览和管理便捷
+
+### 4. 数据完整性
+- 记录确认人姓名、角色、ID
+- 记录确认时间
+- 保存文件快照用于变更检测
+- 所有数据持久化到 Project.data
+
+---
+
+## 🔄 向后兼容
+
+保留了旧的阶段确认方法,确保向后兼容:
+- `confirmStage()` - 重定向到 `confirmSpace()`
+- `getStageConfirmation()` - 保留接口
+- `isStageConfirmed()` - 保留接口
+- `getStageConfirmationText()` - 保留接口
+
+---
+
+## 📝 使用说明
+
+### 1. 查看空间
+- 点击空间头部展开/收起
+- 展开后可看到4个阶段卡片和完成度
+
+### 2. 上传文件
+- 点击阶段卡片选择要上传的阶段
+- 点击"上传文件"按钮选择文件
+- 文件上传后阶段卡片显示数量徽章
+
+### 3. 确认交付
+- 上传完文件后点击"交付执行清单确认"
+- 确认后显示确认人信息和时间
+- 如有文件变更,需要重新确认
+
+---
+
+## 🎯 实现的需求对照
+
+| 需求 | 状态 | 说明 |
+|------|------|------|
+| 展开显示4个阶段 | ✅ | 横向排列,大卡片设计 |
+| 2/4 完成度显示 | ✅ | 右上角紫色徽章 |
+| 柔化边缘 | ✅ | 圆角16px + 阴影 + 渐变 |
+| 图片数量标记 | ✅ | 每个阶段显示徽章 |
+| 精美样式 | ✅ | 参考原设计,4种渐变色 |
+| 空间级别确认 | ✅ | 一个空间一个确认按钮 |
+| 变更检测 | ✅ | 文件快照对比 |
+| 记录确认信息 | ✅ | 姓名、角色、时间、快照 |
+| 数据存储 | ✅ | Project.data.spaceConfirmations |
+
+---
+
+## 🚀 编译状态
+
+所有编译错误已修复:
+- ✅ 修复了 `===` 运算符问题(改为 `==`)
+- ✅ 修复了模板表达式问题
+- ✅ 代码编译通过
+
+---
+
+## 📞 技术支持
+
+如有问题,请检查:
+1. Project.data.spaceConfirmations 字段是否正确存储
+2. 文件快照是否正确记录
+3. 变更检测逻辑是否正常工作
+4. 样式是否正确加载
+
+---
+
+**重构完成时间**: 2024-11-13
+**版本**: V2.0
+**状态**: ✅ 已完成并通过编译

+ 36 - 0
DELIVERY_STYLE_OPTIMIZATION.md

@@ -0,0 +1,36 @@
+# 交付执行阶段样式优化总结
+
+## 优化内容
+
+### 1. 隐藏群聊信息汇总
+- 在交付执行阶段隐藏群聊信息组件
+- 条件: currentStage !== 'delivery'
+
+### 2. 样式全面美化
+
+#### 整体背景
+- 渐变背景效果
+- 更有层次感
+
+#### 空间头部卡片
+- 渐变背景和边框
+- 彩虹渐变边框效果
+- 悬停动画
+
+#### 阶段标签
+- 光泽扫过动画
+- 渐变背景
+- 脉冲徽章
+
+#### 阶段卡片
+- 4种颜色方案
+- 深度阴影
+- 悬停缩放效果
+
+#### 确认按钮
+- 光泽动画
+- 强化阴影
+- 缩放效果
+
+## 完成状态
+已完成所有优化

+ 159 - 0
docs/设计师分配数据存储文档.md

@@ -0,0 +1,159 @@
+# 设计师分配数据存储文档
+
+## 概述
+本文档详细说明了设计师团队分配功能中,项目负责人和设计师空间分配数据的存储位置和数据结构。
+
+## 数据存储位置
+
+### 1. ProjectTeam 表
+**用途**: 存储每个设计师的团队分配信息
+**字段**: `data`
+
+```typescript
+{
+  spaces: string[],              // 分配的空间名称列表
+  isCrossTeam: boolean,          // 是否为跨组合作
+  isProjectLeader: boolean,      // 是否为项目负责人 ⭐
+  assignedAt: string            // 分配时间 (ISO格式)
+}
+```
+
+### 2. Project 表 - data 字段
+**用途**: 存储项目级别的空间分配统计
+**字段**: `data.spaceAssignmentStats`
+
+```typescript
+{
+  assignmentDate: string,        // 分配日期
+  projectLeader: string | null,  // 项目负责人姓名 ⭐
+  spaceStats: {                 // 空间统计
+    [spaceId: string]: {
+      spaceName: string,         // 空间名称
+      assignedDesigners: string[], // 分配的设计师列表
+      projectLeader?: string,    // 该空间的项目负责人 ⭐
+      totalArea?: number        // 空间面积
+    }
+  },
+  totalDesigners: number,       // 总设计师数量
+  crossTeamCollaborators: string[] // 跨组合作者列表
+}
+```
+
+### 3. Project 表 - date 字段 ⭐ (主要统计字段)
+**用途**: 按用户要求,详细的设计师和空间分配统计数据
+**字段**: `date.designerAssignmentStats`
+
+```typescript
+{
+  assignmentDate: string,       // 分配日期
+  projectLeader: {              // 项目负责人详细信息 ⭐
+    id: string | null,
+    name: string | null,
+    assignedSpaces: Array<{     // 负责人分配的空间 ⭐
+      id: string,
+      name: string,
+      area: number
+    }>
+  },
+  teamMembers: Array<{          // 团队成员详细信息
+    id: string,
+    name: string,
+    isProjectLeader: boolean,   // 是否为项目负责人标记 ⭐
+    assignedSpaces: Array<{     // 成员分配的空间
+      id: string,
+      name: string,
+      area: number
+    }>
+  }>,
+  crossTeamCollaborators: Array<{ // 跨组合作者详细信息
+    id: string,
+    name: string,
+    assignedSpaces: Array<{     // 合作者分配的空间
+      id: string,
+      name: string,
+      area: number
+    }>
+  }>,
+  totalSpaceArea: number,       // 总空间面积
+  totalSpaces: number          // 总空间数量
+}
+```
+
+## 项目负责人标识
+
+### 标识规则
+- **项目负责人**: 第一个被选择的设计师自动成为项目负责人
+- **标识符号**: ⭐ 星号标记
+- **存储标记**: `isProjectLeader: true`
+
+### 负责人数据位置
+1. **ProjectTeam.data.isProjectLeader**: `boolean` - 团队记录中的负责人标记
+2. **Project.date.designerAssignmentStats.projectLeader**: `object` - 负责人详细信息和空间分配
+3. **Project.date.designerAssignmentStats.teamMembers[].isProjectLeader**: `boolean` - 团队成员中的负责人标记
+
+## 空间分配数据位置
+
+### 设计师空间分配
+1. **ProjectTeam.data.spaces**: `string[]` - 设计师分配的空间名称列表
+2. **Project.date.designerAssignmentStats.teamMembers[].assignedSpaces**: 详细的空间信息(包含ID、名称、面积)
+3. **Project.date.designerAssignmentStats.crossTeamCollaborators[].assignedSpaces**: 跨组合作者的空间分配
+
+### 项目负责人空间分配 ⭐
+**主要位置**: `Project.date.designerAssignmentStats.projectLeader.assignedSpaces`
+
+```typescript
+assignedSpaces: Array<{
+  id: string,      // 空间ID
+  name: string,    // 空间名称
+  area: number     // 空间面积
+}>
+```
+
+## 数据查询示例
+
+### 查询项目负责人
+```typescript
+// 从Project表查询
+const project = await projectQuery.first();
+const leaderInfo = project.get('date')?.designerAssignmentStats?.projectLeader;
+
+// 负责人姓名
+const leaderName = leaderInfo?.name;
+
+// 负责人分配的空间
+const leaderSpaces = leaderInfo?.assignedSpaces || [];
+```
+
+### 查询设计师空间分配
+```typescript
+// 从Project表查询所有团队成员的空间分配
+const teamMembers = project.get('date')?.designerAssignmentStats?.teamMembers || [];
+
+teamMembers.forEach(member => {
+  console.log(`${member.name}${member.isProjectLeader ? ' ⭐负责人' : ''}`);
+  console.log('分配空间:', member.assignedSpaces.map(s => s.name).join(', '));
+});
+```
+
+### 查询空间统计
+```typescript
+// 从Project表查询空间统计
+const stats = project.get('date')?.designerAssignmentStats;
+console.log('总空间数量:', stats?.totalSpaces);
+console.log('总空间面积:', stats?.totalSpaceArea);
+console.log('项目负责人:', stats?.projectLeader?.name);
+```
+
+## 注意事项
+
+1. **主要数据源**: `Project.date.designerAssignmentStats` 是最完整的统计数据源
+2. **负责人标识**: 通过 `isProjectLeader: true` 和 ⭐ 星号进行标识
+3. **数据同步**: 分配时会同时更新 ProjectTeam 表和 Project 表的相关字段
+4. **空间信息**: 包含空间ID、名称和面积的完整信息
+5. **时间戳**: 所有分配都记录了分配时间用于追踪
+
+## 更新日期
+2024年11月13日
+
+## 版本
+v1.0 - 初始版本,包含项目负责人标记和空间数据统计功能

+ 43 - 26
src/app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component.html

@@ -182,10 +182,16 @@
                   
                   <div class="designer-info">
                     <div class="designer-name">
+                      @if (isProjectLeader(designer)) {
+                        <span class="project-leader-star">⭐</span>
+                      }
                       {{ designer.name }}
                       @if (designer.isTeamLeader) {
                         <span class="leader-badge">组长</span>
                       }
+                      @if (isProjectLeader(designer)) {
+                        <span class="project-leader-badge">负责人</span>
+                      }
                     </div>
                     
                     <div class="designer-status">
@@ -334,35 +340,46 @@
             }
           </div>
         </div>
-      }
-    </div>
 
-    <div class="modal-footer">
-      <div class="selection-summary">
-        @if (selectedDesigners.length > 0) {
-          <div class="summary-item">
-            <span class="summary-label">主要团队:</span>
-            <span class="summary-value">{{ getSelectedDesignersNames() }}</span>
+        <!-- 分配摘要和确认按钮区域 - 移到内容区域内 -->
+        <div class="assignment-summary-section">
+          <div class="summary-header">
+            <h4>分配摘要</h4>
+          </div>
+          
+          <div class="selection-summary">
+            @if (getProjectLeader()) {
+              <div class="summary-item">
+                <span class="summary-label">项目负责人:</span>
+                <span class="summary-value project-leader">⭐ {{ getProjectLeader()?.name }}</span>
+              </div>
+            }
+            @if (selectedDesigners.length > 0) {
+              <div class="summary-item">
+                <span class="summary-label">主要团队:</span>
+                <span class="summary-value">{{ getSelectedDesignersNames() }}</span>
+              </div>
+            }
+            @if (internalCrossTeamCollaborators.length > 0) {
+              <div class="summary-item">
+                <span class="summary-label">跨组合作:</span>
+                <span class="summary-value">{{ getCrossTeamCollaboratorsNames() }}</span>
+              </div>
+            }
           </div>
-        }
-        @if (internalCrossTeamCollaborators.length > 0) {
-          <div class="summary-item">
-            <span class="summary-label">跨组合作:</span>
-            <span class="summary-value">{{ getCrossTeamCollaboratorsNames() }}</span>
+
+          <div class="modal-actions">
+            <button class="btn-secondary" (click)="closeModal()">取消</button>
+            <button 
+              class="btn-primary" 
+              [disabled]="!canConfirmAssignment()" 
+              (click)="confirmAssignment()"
+            >
+              确认分配
+            </button>
           </div>
-        }
-      </div>
-      
-      <div class="modal-actions">
-        <button class="btn-secondary" (click)="closeModal()">取消</button>
-        <button 
-          class="btn-primary" 
-          [disabled]="!canConfirmAssignment()"
-          (click)="confirmAssignment()"
-        >
-          确认分配
-        </button>
-      </div>
+        </div>
+      }
     </div>
   </div>
 </div>

+ 69 - 16
src/app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component.scss

@@ -415,6 +415,23 @@
         font-weight: 500;
       }
 
+      .project-leader-star {
+        font-size: 16px;
+        margin-right: 4px;
+        animation: sparkle 2s ease-in-out infinite;
+      }
+
+      .project-leader-badge {
+        background: linear-gradient(135deg, #ff6b6b, #ffa500);
+        color: white;
+        font-size: 11px;
+        padding: 2px 6px;
+        border-radius: 4px;
+        font-weight: 600;
+        margin-left: 4px;
+        box-shadow: 0 2px 4px rgba(255, 107, 107, 0.3);
+      }
+
       .team-tag {
         background: #722ed1;
         color: white;
@@ -623,20 +640,34 @@
   }
 }
 
-.modal-footer {
-  padding: 24px 32px;
-  border-top: 1px solid #f0f0f0;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 24px;
+// 移除原来的 modal-footer 样式,因为已经移到内容区域内
+.assignment-summary-section {
+  margin-top: 24px;
+  padding: 24px;
+  background: #f8f9fa;
+  border-radius: 12px;
+  border: 1px solid #e9ecef;
+
+  .summary-header {
+    margin-bottom: 16px;
+    
+    h4 {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #262626;
+    }
+  }
 
   .selection-summary {
-    flex: 1;
+    margin-bottom: 20px;
 
     .summary-item {
-      margin-bottom: 8px;
+      margin-bottom: 12px;
       font-size: 14px;
+      display: flex;
+      align-items: center;
+      gap: 8px;
 
       .summary-label {
         color: #8c8c8c;
@@ -646,6 +677,15 @@
       .summary-value {
         color: #262626;
         font-weight: 500;
+
+        &.project-leader {
+          color: #ff6b6b;
+          font-weight: 600;
+          background: linear-gradient(135deg, rgba(255, 107, 107, 0.1), rgba(255, 165, 0, 0.1));
+          padding: 2px 8px;
+          border-radius: 4px;
+          border: 1px solid rgba(255, 107, 107, 0.3);
+        }
       }
     }
   }
@@ -653,27 +693,23 @@
   .modal-actions {
     display: flex;
     gap: 12px;
+    justify-content: flex-end;
 
     .btn-secondary, .btn-primary {
-      padding: 8px 16px;
+      padding: 10px 20px;
       border-radius: 6px;
       font-size: 14px;
       font-weight: 500;
       cursor: pointer;
       transition: all 0.2s ease;
       border: none;
-
-      &:disabled {
-        opacity: 0.5;
-        cursor: not-allowed;
-      }
     }
 
     .btn-secondary {
       background: #f5f5f5;
       color: #595959;
 
-      &:hover:not(:disabled) {
+      &:hover {
         background: #e6e6e6;
       }
     }
@@ -685,6 +721,12 @@
       &:hover:not(:disabled) {
         background: #40a9ff;
       }
+
+      &:disabled {
+        background: #d9d9d9;
+        color: #bfbfbf;
+        cursor: not-allowed;
+      }
     }
   }
 }
@@ -1163,4 +1205,15 @@
     transform: translateY(0);
     opacity: 1;
   }
+}
+
+@keyframes sparkle {
+  0%, 100% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  50% {
+    transform: scale(1.2);
+    opacity: 0.8;
+  }
 }

+ 17 - 1
src/app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component.ts

@@ -62,6 +62,7 @@ export interface DesignerAssignmentResult {
   crossTeamCollaborators: Designer[];
   quotationAssignments: any[];
   spaceAssignments: DesignerSpaceAssignment[]; // 空间分配结果
+  projectLeader?: Designer; // 项目负责人(第一个选择的设计师)
 }
 
 @Component({
@@ -1060,6 +1061,17 @@ export class DesignerTeamAssignmentModalComponent implements OnInit, OnChanges {
     }
   }
 
+  // 获取项目负责人(第一个选择的设计师)
+  getProjectLeader(): Designer | null {
+    return this.internalSelectedDesigners.length > 0 ? this.internalSelectedDesigners[0] : null;
+  }
+
+  // 检查设计师是否为项目负责人
+  isProjectLeader(designer: Designer): boolean {
+    const leader = this.getProjectLeader();
+    return leader ? leader.id === designer.id : false;
+  }
+
   // 切换跨组合作设计师
   toggleCrossTeamCollaborator(designer: Designer) {
     const index = this.internalCrossTeamCollaborators.findIndex(d => d.id === designer.id);
@@ -1195,12 +1207,16 @@ export class DesignerTeamAssignmentModalComponent implements OnInit, OnChanges {
       }
     });
 
+    // 获取项目负责人(第一个选择的设计师)
+    const projectLeader = this.getProjectLeader();
+
     const result: DesignerAssignmentResult = {
       selectedDesigners: [...this.internalSelectedDesigners],
       primaryTeamId: this.internalSelectedTeamId,
       crossTeamCollaborators: [...this.internalCrossTeamCollaborators],
       quotationAssignments: [], // 这里可以根据需要生成报价分配
-      spaceAssignments
+      spaceAssignments,
+      projectLeader // 添加项目负责人信息
     };
 
     this.confirm.emit(result);

+ 107 - 3
src/modules/project/components/team-assign/team-assign.component.ts

@@ -516,7 +516,10 @@ export class TeamAssignComponent implements OnInit {
           sa => sa.designerId === designer.id
         );
         
-        await this.saveDesignerToTeam(designer, spaceAssignment?.spaceIds || []);
+        // 检查是否为项目负责人
+        const isProjectLeader = result.projectLeader?.id === designer.id;
+        
+        await this.saveDesignerToTeam(designer, spaceAssignment?.spaceIds || [], false, isProjectLeader);
       }
 
       // 保存跨组合作者
@@ -525,9 +528,12 @@ export class TeamAssignComponent implements OnInit {
           sa => sa.designerId === collaborator.id
         );
         
-        await this.saveDesignerToTeam(collaborator, spaceAssignment?.spaceIds || [], true);
+        await this.saveDesignerToTeam(collaborator, spaceAssignment?.spaceIds || [], true, false);
       }
 
+      // 保存空间数据统计到projectfile表
+      await this.saveSpaceDataToProjectFile(result);
+
       // 重新加载项目团队数据
       await this.loadProjectTeams();
       
@@ -550,7 +556,8 @@ export class TeamAssignComponent implements OnInit {
   private async saveDesignerToTeam(
     designer: Designer, 
     spaceIds: string[], 
-    isCrossTeam: boolean = false
+    isCrossTeam: boolean = false,
+    isProjectLeader: boolean = false
   ): Promise<void> {
     if (!this.project) return;
 
@@ -590,9 +597,106 @@ export class TeamAssignComponent implements OnInit {
       ...teamObj.get('data'),
       spaces: spaceNames,
       isCrossTeam: isCrossTeam,
+      isProjectLeader: isProjectLeader,
       assignedAt: new Date().toISOString()
     });
 
     await teamObj.save();
   }
+
+  /**
+   * 保存空间数据统计到projectfile表
+   */
+  private async saveSpaceDataToProjectFile(result: DesignerAssignmentResult): Promise<void> {
+    if (!this.project) return;
+
+    try {
+      // 统计空间分配数据
+      const spaceStats: { [spaceId: string]: { 
+        spaceName: string; 
+        assignedDesigners: string[]; 
+        projectLeader?: string;
+        totalArea?: number;
+      }} = {};
+
+      // 处理所有空间分配
+      for (const assignment of result.spaceAssignments) {
+        for (const spaceId of assignment.spaceIds) {
+          if (!spaceStats[spaceId]) {
+            const space = this.projectSpaces.find(s => s.id === spaceId);
+            spaceStats[spaceId] = {
+              spaceName: space?.name || '未知空间',
+              assignedDesigners: [],
+              totalArea: space?.area || 0
+            };
+          }
+          
+          spaceStats[spaceId].assignedDesigners.push(assignment.designerName);
+          
+          // 如果是项目负责人,标记
+          if (result.projectLeader?.id === assignment.designerId) {
+            spaceStats[spaceId].projectLeader = assignment.designerName;
+          }
+        }
+      }
+
+      // 保存到项目的data字段
+      const projectData = this.project.get('data') || {};
+      projectData.spaceAssignmentStats = {
+        assignmentDate: new Date().toISOString(),
+        projectLeader: result.projectLeader?.name || null,
+        spaceStats: spaceStats,
+        totalDesigners: result.selectedDesigners.length + result.crossTeamCollaborators.length,
+        crossTeamCollaborators: result.crossTeamCollaborators.map(c => c.name)
+      };
+
+      // 🔥 同时保存到date字段进行统计(按用户要求)
+      const currentDate = this.project.get('date') || {};
+      currentDate.designerAssignmentStats = {
+        assignmentDate: new Date().toISOString(),
+        projectLeader: {
+          id: result.projectLeader?.id || null,
+          name: result.projectLeader?.name || null,
+          assignedSpaces: result.spaceAssignments
+            .filter(sa => sa.designerId === result.projectLeader?.id)
+            .flatMap(sa => sa.spaceIds.map(id => {
+              const space = this.projectSpaces.find(s => s.id === id);
+              return { id, name: space?.name || '未知空间', area: space?.area || 0 };
+            }))
+        },
+        teamMembers: result.selectedDesigners.map(designer => ({
+          id: designer.id,
+          name: designer.name,
+          isProjectLeader: designer.id === result.projectLeader?.id,
+          assignedSpaces: result.spaceAssignments
+            .filter(sa => sa.designerId === designer.id)
+            .flatMap(sa => sa.spaceIds.map(id => {
+              const space = this.projectSpaces.find(s => s.id === id);
+              return { id, name: space?.name || '未知空间', area: space?.area || 0 };
+            }))
+        })),
+        crossTeamCollaborators: result.crossTeamCollaborators.map(collaborator => ({
+          id: collaborator.id,
+          name: collaborator.name,
+          assignedSpaces: result.spaceAssignments
+            .filter(sa => sa.designerId === collaborator.id)
+            .flatMap(sa => sa.spaceIds.map(id => {
+              const space = this.projectSpaces.find(s => s.id === id);
+              return { id, name: space?.name || '未知空间', area: space?.area || 0 };
+            }))
+        })),
+        totalSpaceArea: Object.values(spaceStats).reduce((sum, stat) => sum + (stat.totalArea || 0), 0),
+        totalSpaces: Object.keys(spaceStats).length
+      };
+
+      this.project.set('data', projectData);
+      this.project.set('date', currentDate);
+      await this.project.save();
+
+      console.log('空间数据统计已保存到项目文件:', projectData.spaceAssignmentStats);
+    } catch (err) {
+      console.error('保存空间数据统计失败:', err);
+      throw err;
+    }
+  }
 }

+ 2 - 2
src/modules/project/pages/project-detail/project-detail.component.html

@@ -93,8 +93,8 @@
       (contactSelected)="onContactSelected($event)">
     </app-contact-selector>
 
-    <!-- 群聊信息汇总(新增) -->
-    @if (groupChat) {
+    <!-- 群聊信息汇总(新增,交付执行阶段隐藏) -->
+    @if (groupChat && currentStage !== 'delivery') {
       <app-group-chat-summary
         [groupChat]="groupChat"
         [contact]="contact"

+ 164 - 0
src/modules/project/pages/project-detail/stages/stage-delivery-new.component.html

@@ -0,0 +1,164 @@
+<div class="stage-delivery-container">
+  <!-- 加载状态 -->
+  @if (loading) {
+    <div class="loading-state">
+      <div class="spinner"></div>
+      <p>加载中...</p>
+    </div>
+  }
+
+  @if (!loading) {
+    <!-- 🆕 空间列表(可折叠展开,显示4个阶段) -->
+    @if (projectProducts.length > 0) {
+      <div class="spaces-list-section">
+        @for (space of projectProducts; track space.id) {
+          <!-- 空间头部(显示4个阶段标签) -->
+          <div class="space-header" (click)="toggleSpaceExpansion(space.id)">
+            <div class="space-name">{{ getSpaceDisplayName(space) }}</div>
+            
+            <!-- 4个阶段标签 -->
+            <div class="stage-tabs">
+              @for (type of deliveryTypes; track type.id) {
+                <div class="stage-tab" 
+                     [class.has-files]="getSpaceStageFileCount(space.id, type.id) > 0"
+                     [class.confirmed]="isStageConfirmed(space.id, type.id)"
+                     (click)="selectSpaceAndStage(space.id, type.id); $event.stopPropagation()">
+                  <span class="stage-name">{{ type.name }}</span>
+                  @if (getSpaceStageFileCount(space.id, type.id) > 0) {
+                    <span class="file-count">{{ getSpaceStageFileCount(space.id, type.id) }}</span>
+                  }
+                  @if (isStageConfirmed(space.id, type.id)) {
+                    <span class="confirmed-icon">✓</span>
+                  }
+                </div>
+              }
+            </div>
+            
+            <!-- 展开/收起图标 -->
+            <div class="expand-icon" [class.expanded]="isSpaceExpanded(space.id)">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+                <path d="M7 10l5 5 5-5z"/>
+              </svg>
+            </div>
+          </div>
+
+          <!-- 空间内容(展开时显示) -->
+          @if (isSpaceExpanded(space.id)) {
+            <div class="space-content">
+              <!-- 显示当前选中阶段的内容 -->
+              @if (selectedSpaceId === space.id && selectedStageType) {
+                <div class="stage-content-area">
+                  <!-- 阶段标题和文件数量 -->
+                  <div class="stage-header-bar">
+                    <h3>{{ getSpaceDisplayName(space) }}</h3>
+                    <div class="file-count-display">
+                      {{ getSpaceStageFileCount(space.id, selectedStageType) }}/4
+                    </div>
+                  </div>
+
+                  <!-- 文件上传区域 -->
+                  @if (canEdit) {
+                    <div class="upload-zone">
+                      <input
+                        type="file"
+                        multiple
+                        (change)="uploadDeliveryFile($event, space.id, selectedStageType)"
+                        [accept]="'image/*,.pdf,.dwg,.dxf,.skp,.max'"
+                        [disabled]="uploadingDeliveryFiles"
+                        hidden
+                        #fileInput />
+                      
+                      <button class="upload-btn" (click)="fileInput.click()" [disabled]="uploadingDeliveryFiles">
+                        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
+                          <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+                        </svg>
+                        <span>上传文件</span>
+                      </button>
+                    </div>
+                  }
+
+                  <!-- 文件网格显示 -->
+                  <div class="files-grid-display">
+                    @for (file of getProductDeliveryFiles(space.id, selectedStageType); track file.id) {
+                      <div class="file-item">
+                        <div class="file-preview-box" (click)="previewFile(file)">
+                          @if (isImageFile(file.name)) {
+                            <img [src]="file.url" [alt]="file.name" class="file-img" (error)="onImageError($event)" />
+                          } @else {
+                            <div class="file-placeholder">
+                              <svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
+                                <path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
+                              </svg>
+                            </div>
+                          }
+                          
+                          @if (canEdit) {
+                            <button class="delete-file-btn" (click)="deleteDeliveryFile(space.id, selectedStageType, file.id); $event.stopPropagation()">
+                              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
+                                <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
+                              </svg>
+                            </button>
+                          }
+                        </div>
+                        <div class="file-name-label" [title]="file.name">{{ file.name }}</div>
+                      </div>
+                    }
+                    
+                    <!-- 空状态提示 -->
+                    @if (getProductDeliveryFiles(space.id, selectedStageType).length === 0) {
+                      <div class="empty-files-hint">
+                        <p>暂无{{ deliveryTypes.find(t => t.id === selectedStageType)?.name }}文件</p>
+                        @if (selectedStageType === 'rendering') {
+                          <p class="hint-text">渲染<br/>关联任务</p>
+                        }
+                        @if (selectedStageType === 'post_process') {
+                          <p class="hint-text">后期<br/>上传大图</p>
+                        }
+                      </div>
+                    }
+                  </div>
+
+                  <!-- 阶段确认按钮 -->
+                  <div class="stage-confirm-section">
+                    @if (!isStageConfirmed(space.id, selectedStageType)) {
+                      <button 
+                        class="confirm-stage-btn" 
+                        (click)="confirmStage(space.id, selectedStageType)"
+                        [disabled]="saving || getSpaceStageFileCount(space.id, selectedStageType) === 0">
+                        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
+                          <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+                        </svg>
+                        <span>交付执行清单确认</span>
+                      </button>
+                    } @else {
+                      <div class="confirmed-info">
+                        <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
+                          <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+                        </svg>
+                        <span>已确认</span>
+                        <div class="confirmed-details">{{ getStageConfirmationText(space.id, selectedStageType) }}</div>
+                      </div>
+                    }
+                  </div>
+                </div>
+              }
+            </div>
+          }
+        }
+      </div>
+    }
+
+    <!-- 没有场景时的提示 -->
+    @if (projectProducts.length === 0 && !loading) {
+      <div class="no-products-state">
+        <div class="state-icon">
+          <svg class="icon" width="64" height="64" viewBox="0 0 24 24">
+            <path fill="currentColor" d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"/>
+          </svg>
+        </div>
+        <h3>暂无项目空间</h3>
+        <p>请先在方案深化阶段添加项目空间</p>
+      </div>
+    }
+  }
+</div>

+ 485 - 0
src/modules/project/pages/project-detail/stages/stage-delivery-new.component.scss

@@ -0,0 +1,485 @@
+.stage-delivery-container {
+  padding: 20px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+
+  // 加载状态
+  .loading-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60px 20px;
+    
+    .spinner {
+      width: 48px;
+      height: 48px;
+      border: 4px solid #e0e0e0;
+      border-top-color: #667eea;
+      border-radius: 50%;
+      animation: spin 0.8s linear infinite;
+    }
+    
+    p {
+      margin-top: 16px;
+      color: #64748b;
+      font-size: 14px;
+    }
+  }
+
+  @keyframes spin {
+    to { transform: rotate(360deg); }
+  }
+
+  // ==================== 🆕 空间列表样式 ====================
+  .spaces-list-section {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    
+    // 空间头部(显示空间名和4个阶段标签)
+    .space-header {
+      background: white;
+      border: 2px solid #e2e8f0;
+      border-radius: 12px;
+      padding: 16px 20px;
+      display: flex;
+      align-items: center;
+      gap: 16px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        border-color: #667eea;
+        box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
+        transform: translateY(-2px);
+      }
+      
+      .space-name {
+        font-size: 18px;
+        font-weight: 600;
+        color: #1e293b;
+        min-width: 120px;
+        flex-shrink: 0;
+      }
+      
+      // 4个阶段标签(横向排列)
+      .stage-tabs {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+        flex: 1;
+        
+        .stage-tab {
+          flex: 1;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          gap: 6px;
+          padding: 10px 16px;
+          background: #f8fafc;
+          border: 2px solid #e2e8f0;
+          border-radius: 8px;
+          font-size: 15px;
+          font-weight: 500;
+          color: #64748b;
+          transition: all 0.25s ease;
+          cursor: pointer;
+          position: relative;
+          
+          &:hover {
+            background: #f1f5f9;
+            border-color: #cbd5e1;
+          }
+          
+          .stage-name {
+            font-size: 15px;
+          }
+          
+          .file-count {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            min-width: 22px;
+            height: 22px;
+            padding: 0 6px;
+            background: #667eea;
+            color: white;
+            border-radius: 11px;
+            font-size: 12px;
+            font-weight: 600;
+          }
+          
+          .confirmed-icon {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            width: 20px;
+            height: 20px;
+            background: #10b981;
+            color: white;
+            border-radius: 50%;
+            font-size: 12px;
+            font-weight: bold;
+          }
+          
+          // 有文件时的样式
+          &.has-files {
+            background: #eef2ff;
+            border-color: #c7d2fe;
+            color: #4f46e5;
+          }
+          
+          // 已确认时的样式
+          &.confirmed {
+            background: #d1fae5;
+            border-color: #6ee7b7;
+            color: #059669;
+          }
+        }
+      }
+      
+      // 展开/收起图标
+      .expand-icon {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 32px;
+        height: 32px;
+        flex-shrink: 0;
+        color: #94a3b8;
+        transition: transform 0.3s ease;
+        
+        &.expanded {
+          transform: rotate(180deg);
+        }
+        
+        svg {
+          width: 24px;
+          height: 24px;
+        }
+      }
+    }
+    
+    // 空间内容(展开时显示)
+    .space-content {
+      background: white;
+      border: 2px solid #e2e8f0;
+      border-top: none;
+      border-radius: 0 0 12px 12px;
+      padding: 20px;
+      margin-top: -12px;
+      animation: slideDown 0.3s ease-out;
+      
+      .stage-content-area {
+        display: flex;
+        flex-direction: column;
+        gap: 16px;
+        
+        // 阶段标题栏
+        .stage-header-bar {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding-bottom: 12px;
+          border-bottom: 2px solid #e2e8f0;
+          
+          h3 {
+            margin: 0;
+            font-size: 18px;
+            font-weight: 600;
+            color: #1e293b;
+          }
+          
+          .file-count-display {
+            font-size: 16px;
+            font-weight: 600;
+            color: #667eea;
+            padding: 6px 12px;
+            background: #eef2ff;
+            border-radius: 6px;
+          }
+        }
+        
+        // 文件上传区域
+        .upload-zone {
+          display: flex;
+          justify-content: center;
+          padding: 12px 0;
+          
+          .upload-btn {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            padding: 12px 24px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            border-radius: 8px;
+            font-size: 15px;
+            font-weight: 600;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+            
+            &:hover:not(:disabled) {
+              transform: translateY(-2px);
+              box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
+            }
+            
+            &:active:not(:disabled) {
+              transform: translateY(0);
+            }
+            
+            &:disabled {
+              opacity: 0.6;
+              cursor: not-allowed;
+            }
+            
+            svg {
+              width: 20px;
+              height: 20px;
+            }
+          }
+        }
+        
+        // 文件网格显示
+        .files-grid-display {
+          display: grid;
+          grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+          gap: 16px;
+          padding: 16px 0;
+          
+          .file-item {
+            display: flex;
+            flex-direction: column;
+            gap: 8px;
+            
+            .file-preview-box {
+              position: relative;
+              width: 100%;
+              aspect-ratio: 1;
+              background: #f8fafc;
+              border: 2px solid #e2e8f0;
+              border-radius: 8px;
+              overflow: hidden;
+              cursor: pointer;
+              transition: all 0.3s ease;
+              
+              &:hover {
+                border-color: #667eea;
+                box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
+                transform: translateY(-2px);
+                
+                .delete-file-btn {
+                  opacity: 1;
+                }
+              }
+              
+              .file-img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+              }
+              
+              .file-placeholder {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                width: 100%;
+                height: 100%;
+                color: #cbd5e1;
+                
+                svg {
+                  width: 48px;
+                  height: 48px;
+                }
+              }
+              
+              .delete-file-btn {
+                position: absolute;
+                top: 8px;
+                right: 8px;
+                width: 32px;
+                height: 32px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                background: rgba(239, 68, 68, 0.9);
+                color: white;
+                border: none;
+                border-radius: 6px;
+                cursor: pointer;
+                opacity: 0;
+                transition: all 0.3s ease;
+                
+                &:hover {
+                  background: rgba(220, 38, 38, 1);
+                  transform: scale(1.1);
+                }
+                
+                svg {
+                  width: 16px;
+                  height: 16px;
+                }
+              }
+            }
+            
+            .file-name-label {
+              font-size: 13px;
+              color: #64748b;
+              text-align: center;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+              padding: 0 4px;
+            }
+          }
+          
+          // 空状态提示
+          .empty-files-hint {
+            grid-column: 1 / -1;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            padding: 40px 20px;
+            color: #94a3b8;
+            
+            p {
+              margin: 0;
+              font-size: 15px;
+              text-align: center;
+              
+              &.hint-text {
+                margin-top: 8px;
+                font-size: 13px;
+                line-height: 1.6;
+                color: #cbd5e1;
+              }
+            }
+          }
+        }
+        
+        // 阶段确认按钮区域
+        .stage-confirm-section {
+          display: flex;
+          justify-content: center;
+          padding: 16px 0;
+          border-top: 2px solid #e2e8f0;
+          
+          .confirm-stage-btn {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            padding: 14px 32px;
+            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+            color: white;
+            border: none;
+            border-radius: 10px;
+            font-size: 16px;
+            font-weight: 600;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
+            
+            &:hover:not(:disabled) {
+              transform: translateY(-2px);
+              box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
+            }
+            
+            &:active:not(:disabled) {
+              transform: translateY(0);
+            }
+            
+            &:disabled {
+              opacity: 0.5;
+              cursor: not-allowed;
+            }
+            
+            svg {
+              width: 20px;
+              height: 20px;
+            }
+          }
+          
+          .confirmed-info {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            gap: 8px;
+            padding: 12px 24px;
+            background: #d1fae5;
+            border: 2px solid #6ee7b7;
+            border-radius: 10px;
+            color: #059669;
+            
+            > span {
+              display: flex;
+              align-items: center;
+              gap: 8px;
+              font-size: 16px;
+              font-weight: 600;
+              
+              svg {
+                width: 20px;
+                height: 20px;
+              }
+            }
+            
+            .confirmed-details {
+              font-size: 13px;
+              color: #047857;
+              text-align: center;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 没有场景时的提示
+  .no-products-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 80px 20px;
+    
+    .state-icon {
+      color: #cbd5e1;
+      margin-bottom: 20px;
+      
+      svg {
+        width: 64px;
+        height: 64px;
+      }
+    }
+    
+    h3 {
+      margin: 0 0 12px;
+      font-size: 20px;
+      font-weight: 600;
+      color: #475569;
+    }
+    
+    p {
+      margin: 0;
+      font-size: 15px;
+      color: #94a3b8;
+      text-align: center;
+    }
+  }
+
+  // 动画
+  @keyframes slideDown {
+    from {
+      opacity: 0;
+      transform: translateY(-10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0);
+    }
+  }
+}

+ 159 - 326
src/modules/project/pages/project-detail/stages/stage-delivery.component.html

@@ -8,337 +8,196 @@
   }
 
   @if (!loading) {
-    <!-- 当前阶段审批状态提示 -->
-    @if (project && activeDeliveryType) {
-      @if (getDeliveryApprovalStatus(activeDeliveryType) === 'pending') {
-        <div class="approval-status-banner pending">
-          <div class="status-icon">⏳</div>
-          <div class="status-content">
-            <h4>【{{ getDeliveryTypeName(activeDeliveryType) }}】等待组长审批</h4>
-            <p>当前阶段已提交,正在等待组长审核批准</p>
-          </div>
-        </div>
-      }
-      @if (getDeliveryApprovalStatus(activeDeliveryType) === 'approved') {
-        <div class="approval-status-banner approved">
-          <div class="status-icon">✅</div>
-          <div class="status-content">
-            <h4>【{{ getDeliveryTypeName(activeDeliveryType) }}】审批已通过</h4>
-            <p>当前阶段已获组长批准</p>
-          </div>
-        </div>
-      }
-      @if (getDeliveryApprovalStatus(activeDeliveryType) === 'rejected') {
-        <div class="approval-status-banner rejected">
-          <div class="status-icon">❌</div>
-          <div class="status-content">
-            <h4>【{{ getDeliveryTypeName(activeDeliveryType) }}】已驳回</h4>
-            <p><strong>驳回原因:</strong>{{ getDeliveryRejectionReason() }}</p>
-          </div>
-        </div>
-      }
-    }
-    
-    <!-- 🔥 组长审批操作条:组长从组长看板进入时始终显示(只要有文件) -->
-    @if (isTeamLeader && !isFromCustomerService && shouldShowApprovalButtons()) {
-      <div class="leader-approval-bar">
-        <div class="approval-buttons-container">
-          <button class="btn-approve" (click)="approveDelivery()" [disabled]="saving || !hasDeliveryFiles()">
-            <span class="btn-icon">✅</span>
-            <span class="btn-text">通过审批</span>
-          </button>
-          <button class="btn-reject" (click)="rejectDelivery()" [disabled]="saving || !hasDeliveryFiles()">
-            <span class="btn-icon">❌</span>
-            <span class="btn-text">驳回交付</span>
-          </button>
-        </div>
-        @if (!hasDeliveryFiles()) {
-          <p class="approval-hint">💡 项目暂无交付文件,请等待设计师上传后再审批</p>
-        }
-      </div>
-    }
-
-
-    <!-- 场景Product选择标签 (第一层) -->
-    @if (isMultiProductProject) {
-      <div class="product-tabs-section">
-        <div class="section-label">选择空间场景</div>
-        <div class="product-tabs">
-          @for (product of projectProducts; track product.id) {
-            <div
-              class="product-tab"
-              [class.active]="activeProductId === product.id"
-              (click)="selectProduct(product.id)">
-              <div class="product-icon">
-                <svg class="icon" width="20" height="20" viewBox="0 0 24 24">
-                  <path fill="currentColor" d="M12,3L2,12H5V20H19V12H22M12,8.75A2.25,2.25 0 0,1 14.25,11A2.25,2.25 0 0,1 12,13.25A2.25,2.25 0 0,1 9.75,11A2.25,2.25 0 0,1 12,8.75Z"/>
-                </svg>
-              </div>
-              <span class="product-name">{{ getSpaceDisplayName(product) }}</span>
-              @if (getTotalDeliveryFileCount(product.id) > 0) {
-                <span class="file-count-badge">{{ getTotalDeliveryFileCount(product.id) }}</span>
-              }
-            </div>
-          }
-        </div>
-      </div>
-    }
-
-    <!-- 交付类型选择标签 (第二层) -->
-    @if (activeProductId) {
-      <div class="delivery-types-section">
-        <div class="section-label">选择交付类型</div>
-        <div class="delivery-types-tabs">
-          @for (type of deliveryTypes; track type.id) {
-            <div
-              class="delivery-type-tab"
-              [class.active]="activeDeliveryType === type.id"
-              [class]="getStageStatusClass(type.id)"
-              [attr.data-color]="type.color"
-              (click)="selectDeliveryType(type.id)">
-              <div class="type-icon">
-                <svg class="icon" width="24" height="24" viewBox="0 0 512 512">
-                  @switch (type.id) {
-                    @case ('white_model') {
-                      <path fill="currentColor" d="M234.5 5.7c13.9-5 29.1-5 43.1 0l192 68.6C495 83.4 512 107.5 512 134.6V377.4c0 27-17 51.2-42.5 60.3l-192 68.6c-13.9 5-29.1 5-43.1 0l-192-68.6C17 428.6 0 404.5 0 377.4V134.6c0-27 17-51.2 42.5-60.3l192-68.6zM256 66L82.3 128 256 190l173.7-62L256 66zm32 368.6l160-57.1v-188L288 246.6v188z"/>
-                    }
-                    @case ('soft_decor') {
-                      <path fill="currentColor" d="M512 256c0 .9 0 1.8 0 2.7c-.4 36.5-33.6 61.3-70.1 61.3H344c-26.5 0-48 21.5-48 48c0 3.4 .4 6.7 1 9.9c2.1 10.2 6.5 20 10.8 29.9c6.1 13.8 12.1 27.5 12.1 42c0 31.8-21.6 60.7-53.4 62c-3.5 .1-7 .2-10.6 .2C114.6 512 0 397.4 0 256S114.6 0 256 0S512 114.6 512 256zM128 288a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm0-96a32 32 0 1 0 0-64 32 32 0 1 0 0 64zM288 96a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm96 96a32 32 0 1 0 0-64 32 32 0 1 0 0 64z"/>
-                    }
-                    @case ('rendering') {
-                      <path fill="currentColor" d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/>
-                    }
-                    @case ('post_process') {
-                      <path fill="currentColor" d="M234.7 42.7L197 56.8c-3 1.1-5 4-5 7.2s2 6.1 5 7.2l37.7 14.1L248.8 123c1.1 3 4 5 7.2 5s6.1-2 7.2-5l14.1-37.7L315 71.2c3-1.1 5-4 5-7.2s-2-6.1-5-7.2L277.3 42.7 263.2 5c-1.1-3-4-5-7.2-5s-6.1 2-7.2 5L234.7 42.7zM46.1 395.4c-18.7 18.7-18.7 49.1 0 67.9l34.6 34.6c18.7 18.7 49.1 18.7 67.9 0L529.9 116.5c18.7-18.7 18.7-49.1 0-67.9L495.3 14.1c-18.7-18.7-49.1-18.7-67.9 0L46.1 395.4zM484.6 82.6l-105 105-23.3-23.3 105-105 23.3 23.3zM7.5 117.2C3 118.9 0 123.2 0 128s3 9.1 7.5 10.8L64 160l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L128 160l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L128 96 106.8 39.5C105.1 35 100.8 32 96 32s-9.1 3-10.8 7.5L64 96 7.5 117.2zm352 256c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L416 416l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L480 416l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L480 352l-21.2-56.5c-1.7-4.5-6-7.5-10.8-7.5s-9.1 3-10.8 7.5L416 352l-56.5 21.2z"/>
-                    }
+    <!-- 🆕 空间列表(可折叠展开,显示4个阶段) -->
+    @if (projectProducts.length > 0) {
+      <div class="spaces-list-section">
+        @for (space of projectProducts; track space.id) {
+          <!-- 空间头部(显示4个阶段标签) -->
+          <div class="space-header" (click)="toggleSpaceExpansion(space.id)">
+            <div class="space-name">{{ getSpaceDisplayName(space) }}</div>
+            
+            <!-- 4个阶段标签 -->
+            <div class="stage-tabs">
+              @for (type of deliveryTypes; track type.id) {
+                <div class="stage-tab" 
+                     [class.has-files]="getSpaceStageFileCount(space.id, type.id) > 0"
+                     [class.confirmed]="isStageConfirmed(space.id, type.id)"
+                     (click)="selectSpaceAndStage(space.id, type.id); $event.stopPropagation()">
+                  <span class="stage-name">{{ type.name }}</span>
+                  @if (getSpaceStageFileCount(space.id, type.id) > 0) {
+                    <span class="file-count">{{ getSpaceStageFileCount(space.id, type.id) }}</span>
                   }
-                </svg>
-              </div>
-              <div class="type-content">
-                <span class="type-name">{{ type.name }}</span>
-                <span class="type-description">{{ type.description }}</span>
-              </div>
-              <div class="type-badges">
-                @if (getCurrentTypeFileCount(activeProductId, type.id) > 0) {
-                  <span class="file-count-badge">{{ getCurrentTypeFileCount(activeProductId, type.id) }}</span>
-                }
-                @if (getTypeUnverifiedFileCount(activeProductId, type.id) > 0) {
-                  <span class="unverified-badge">{{ getTypeUnverifiedFileCount(activeProductId, type.id) }} 未验证</span>
-                }
-                <!-- 阶段状态徽章 -->
-                @if (isApproved(type.id)) {
-                  <span class="status-badge approved">✓ 已通过</span>
-                }
-                @if (needsApproval(type.id)) {
-                  <span class="status-badge pending">⏳ 待审批</span>
-                }
-                @if (isRejected(type.id)) {
-                  <span class="status-badge rejected">✗ 已驳回</span>
-                }
-              </div>
+                  @if (isStageConfirmed(space.id, type.id)) {
+                    <span class="confirmed-icon">✓</span>
+                  }
+                </div>
+              }
             </div>
-          }
-        </div>
-      </div>
-
-      <!-- 文件上传和展示区域 -->
-      <div class="delivery-content-section">
-        <!-- 阶段锁定提示 -->
-        @if (!canUploadCurrentStage()) {
-          <div class="stage-locked-notice">
-            <div class="lock-icon">🔒</div>
-            <div class="lock-content">
-              <h4>当前阶段尚未解锁</h4>
-              <p>请先完成<strong>{{ getPreviousStageName() }}</strong>阶段并等待组长审批通过后,才能上传当前阶段文件</p>
+            
+            <!-- 展开/收起图标 -->
+            <div class="expand-icon" [class.expanded]="isSpaceExpanded(space.id)">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+                <path d="M7 10l5 5 5-5z"/>
+              </svg>
             </div>
           </div>
-        }
-        
-        <!-- 上传区域 -->
-        <div class="upload-section" [class.disabled]="!canUploadCurrentStage()">
-          <div class="upload-area" [class.uploading]="uploadingDeliveryFiles" [class.locked]="!canUploadCurrentStage()">
-            <div class="upload-content">
-              <div class="upload-icon">
-                <svg class="icon" width="48" height="48" viewBox="0 0 24 24">
-                  <path fill="currentColor" d="M11 15h2V9h3l-4-5l-4 5h3Zm-7 7c-.55 0-1.02-.196-1.413-.587A1.928 1.928 0 0 1 2 20V8c0-.55.196-1.02.587-1.412A1.93 1.93 0 0 1 4 6h4l2-2h4l-2 2H8.83L7.5 7.5H4V20h16V8h-6V6h6c.55 0 1.02.196 1.413.588C21.803 6.98 22 7.45 22 8v12c0 .55-.196 1.02-.587 1.413A1.928 1.928 0 0 1 20 22Z"/>
-                </svg>
-              </div>
-              <div class="upload-text">
-                <h4>上传{{ getDeliveryTypeName(activeDeliveryType) }}文件</h4>
-                <p>{{ getDeliveryTypeDescription(activeDeliveryType) }}</p>
-              </div>
-              @if (canEdit && canUploadCurrentStage()) {
-                <input
-                  type="file"
-                  multiple
-                  (change)="uploadDeliveryFile($event, activeProductId, activeDeliveryType)"
-                  [accept]="'image/*,.pdf,.dwg,.dxf,.skp,.max'"
-                  [disabled]="uploadingDeliveryFiles"
-                  hidden
-                  #deliveryFileInput />
-                
-                <button
-                  class="upload-button"
-                  [disabled]="uploadingDeliveryFiles || !canUploadCurrentStage()"
-                  (click)="deliveryFileInput.click()">
-                  <svg class="icon" width="20" height="20" viewBox="0 0 24 24">
-                    <path fill="currentColor" d="M11 15h2V9h3l-4-5l-4 5h3Zm-7 7c-.55 0-1.02-.196-1.413-.587A1.928 1.928 0 0 1 2 20V8c0-.55.196-1.02.587-1.412A1.93 1.93 0 0 1 4 6h4l2-2h4l-2 2H8.83L7.5 7.5H4V20h16V8h-6V6h6c.55 0 1.02.196 1.413.588C21.803 6.98 22 7.45 22 8v12c0 .55-.196 1.02-.587 1.413A1.928 1.928 0 0 1 20 22Z"/>
-                  </svg>
-                  <span>选择文件上传</span>
-                </button>
-              }
-            </div>
 
-            <!-- 上传进度条 -->
-            @if (uploadingDeliveryFiles) {
-              <div class="upload-progress">
-                <div class="progress-bar">
-                  <div class="progress-fill" [style.width.%]="uploadProgress"></div>
+          <!-- 空间内容(展开时显示) -->
+          @if (isSpaceExpanded(space.id)) {
+            <div class="space-content">
+              <!-- 空间标题栏 -->
+              <div class="space-title-bar">
+                <h3>{{ getSpaceDisplayName(space) }}</h3>
+                <div class="completion-badge">
+                  {{ getCompletedStagesCount(space.id) }}/4
                 </div>
-                <span class="progress-text">上传中... {{ uploadProgress }}%</span>
               </div>
-            }
-          </div>
-        </div>
 
-        <!-- 文件列表展示 -->
-        <div class="files-display-section">
-          @if (getProductDeliveryFiles(activeProductId, activeDeliveryType).length > 0) {
-            <div class="files-header">
-              <h4>已上传文件 ({{ getProductDeliveryFiles(activeProductId, activeDeliveryType).length }})</h4>
-            </div>
-            <div class="files-grid">
-              @for (file of getProductDeliveryFiles(activeProductId, activeDeliveryType); track file.id) {
-                <div class="file-card" [class.has-approval-issue]="file.approvalStatus === 'rejected'">
-                  <!-- 文件预览 -->
-                  <div class="file-preview" (click)="previewFile(file)">
-                    @if (isImageFile(file.name)) {
-                      <img [src]="file.url" [alt]="file.name" class="preview-image" (error)="onImageError($event)" />
-                    } @else {
-                      <div class="file-type-icon">
-                        <svg class="icon" width="48" height="48" viewBox="0 0 24 24">
-                          <path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
-                        </svg>
-                      </div>
-                    }
-                    
-                    <!-- 审批状态角标 -->
-                    <div class="approval-corner-badge" [ngClass]="'badge-' + file.approvalStatus">
-                      @if (file.approvalStatus === 'unverified') {
-                        <span>⏳</span>
-                      } @else if (file.approvalStatus === 'pending') {
-                        <span>🔍</span>
-                      } @else if (file.approvalStatus === 'approved') {
-                        <span>✅</span>
-                      } @else if (file.approvalStatus === 'rejected') {
-                        <span>❌</span>
+              <!-- 4个阶段卡片(横向排列) -->
+              <div class="stages-grid">
+                @for (type of deliveryTypes; track type.id) {
+                  <div class="stage-card" 
+                       [class.has-files]="getSpaceStageFileCount(space.id, type.id) > 0"
+                       [attr.data-stage]="type.id"
+                       (click)="selectSpaceAndStage(space.id, type.id)">
+                    <div class="stage-card-header">
+                      <h4>{{ type.name }}</h4>
+                      @if (getSpaceStageFileCount(space.id, type.id) > 0) {
+                        <span class="image-count-badge">{{ getSpaceStageFileCount(space.id, type.id) }}</span>
                       }
                     </div>
                     
-                    <div class="file-overlay">
-                      <svg class="icon" width="24" height="24" viewBox="0 0 24 24">
-                        <path fill="white" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
-                      </svg>
+                    <div class="stage-card-body">
+                      @if (getSpaceStageFileCount(space.id, type.id) > 0) {
+                        <!-- 显示第一张图片作为预览 -->
+                        @for (firstFile of getProductDeliveryFiles(space.id, type.id).slice(0, 1); track firstFile.id) {
+                          @if (isImageFile(firstFile.name)) {
+                            <img [src]="firstFile.url" [alt]="firstFile.name" class="stage-preview-img" />
+                          } @else {
+                            <div class="stage-placeholder">
+                              <svg width="64" height="64" viewBox="0 0 24 24" fill="currentColor">
+                                <path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
+                              </svg>
+                            </div>
+                          }
+                        }
+                      } @else {
+                        <!-- 空状态 -->
+                        <div class="stage-empty-state">
+                          @if (type.id == 'rendering') {
+                            <p>渲染<br/>关联任务</p>
+                          } @else if (type.id == 'post_process') {
+                            <p>后期<br/>上传大图</p>
+                          } @else {
+                            <p>暂无文件</p>
+                          }
+                        </div>
+                      }
                     </div>
-                    
-                    <!-- 删除按钮(参考售后归档样式) -->
+                  </div>
+                }
+              </div>
+
+              <!-- 选中阶段的文件详情 -->
+              @if (selectedSpaceId == space.id && selectedStageType) {
+                <div class="stage-files-detail">
+                  <div class="detail-header">
+                    <h4>{{ getDeliveryTypeName(selectedStageType) }} - 文件列表</h4>
                     @if (canEdit) {
-                      <button class="delete-btn" (click)="deleteDeliveryFile(activeProductId, activeDeliveryType, file.id); $event.stopPropagation()">
-                        <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
-                          <path d="M112 112l20 320c.95 18.49 14.4 32 32 32h184c17.67 0 30.87-13.51 32-32l20-320" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
-                          <path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M80 112h352"/>
-                          <path d="M192 112V72h0a23.93 23.93 0 0124-24h80a23.93 23.93 0 0124 24h0v40M256 176v224M184 176l8 224M328 176l-8 224" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                      <input
+                        type="file"
+                        multiple
+                        (change)="uploadDeliveryFile($event, space.id, selectedStageType)"
+                        [accept]="'image/*,.pdf,.dwg,.dxf,.skp,.max'"
+                        [disabled]="uploadingDeliveryFiles"
+                        hidden
+                        #fileInput />
+                      
+                      <button class="upload-detail-btn" (click)="fileInput.click()" [disabled]="uploadingDeliveryFiles">
+                        <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
+                          <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
                         </svg>
+                        <span>上传文件</span>
                       </button>
                     }
                   </div>
 
-                  <!-- 文件信息(参考售后归档样式) -->
-                  <div class="file-info">
-                    <div class="file-name" [title]="file.name">{{ file.name }}</div>
-                    <div class="file-meta">
-                      <span class="file-size">{{ formatFileSize(file.size) }}</span>
-                      <span class="file-time">{{ file.uploadTime | date: 'yyyy-MM-dd HH:mm' }}</span>
-                    </div>
-                    @if (file.uploadedBy) {
-                      <div class="file-uploader">上传: {{ file.uploadedBy }}</div>
-                    }
-                    
-                    <!-- ✨ 审批状态显示(紧凑单行样式) -->
-                    @if (file.approvalStatus && file.approvalStatus !== 'unverified') {
-                      <div class="file-approval-status" [ngClass]="'status-' + file.approvalStatus">
-                        <span class="status-icon">
-                          @if (file.approvalStatus === 'approved') {
-                            ✅
-                          } @else if (file.approvalStatus === 'rejected') {
-                            ❌
-                          } @else if (file.approvalStatus === 'pending') {
-                            🔍
+                  <div class="files-detail-grid">
+                    @for (file of getProductDeliveryFiles(space.id, selectedStageType); track file.id) {
+                      <div class="file-detail-item">
+                        <div class="file-detail-preview" (click)="previewFile(file)">
+                          @if (isImageFile(file.name)) {
+                            <img [src]="file.url" [alt]="file.name" class="file-detail-img" (error)="onImageError($event)" />
+                          } @else {
+                            <div class="file-detail-placeholder">
+                              <svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
+                                <path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
+                              </svg>
+                            </div>
                           }
-                        </span>
-                        <span class="status-text">{{ getApprovalStatusText(file.approvalStatus) }}</span>
-                        @if (file.approvedBy) {
-                          <span class="status-by">· {{ file.approvedBy }}</span>
-                        }
-                        @if (file.rejectionReason) {
-                          <span class="status-reason">: {{ file.rejectionReason }}</span>
-                        }
+                          
+                          @if (canEdit) {
+                            <button class="delete-detail-btn" (click)="deleteDeliveryFile(space.id, selectedStageType, file.id); $event.stopPropagation()">
+                              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
+                                <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
+                              </svg>
+                            </button>
+                          }
+                        </div>
+                        <div class="file-detail-name" [title]="file.name">{{ file.name }}</div>
                       </div>
                     }
                   </div>
                 </div>
               }
-            </div>
-          } @else {
-            <div class="empty-state">
-              <div class="empty-icon">
-                <svg class="icon" width="64" height="64" viewBox="0 0 24 24">
-                  <path fill="currentColor" d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"/>
-                </svg>
+
+              <!-- 整体确认按钮(空间级别) -->
+              <div class="space-confirm-section">
+                @if (!isSpaceConfirmed(space.id)) {
+                  <button 
+                    class="space-confirm-btn" 
+                    (click)="confirmSpace(space.id)"
+                    [disabled]="saving || getSpaceTotalFileCount(space.id) == 0">
+                    <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor">
+                      <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+                    </svg>
+                    <span>交付执行清单确认</span>
+                  </button>
+                  @if (getSpaceTotalFileCount(space.id) == 0) {
+                    <p class="confirm-hint">请先上传文件后再确认</p>
+                  }
+                } @else {
+                  @if (hasSpaceFilesChanged(space.id)) {
+                    <button 
+                      class="space-reconfirm-btn" 
+                      (click)="confirmSpace(space.id)"
+                      [disabled]="saving">
+                      <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor">
+                        <path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/>
+                      </svg>
+                      <span>文件已变更,需重新确认</span>
+                    </button>
+                  } @else {
+                    <div class="space-confirmed-info">
+                      <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor">
+                        <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+                      </svg>
+                      <div class="confirmed-text">
+                        <span class="confirmed-label">已确认</span>
+                        <span class="confirmed-detail">{{ getSpaceConfirmationText(space.id) }}</span>
+                      </div>
+                    </div>
+                  }
+                }
               </div>
-              <h4>暂无{{ getDeliveryTypeName(activeDeliveryType) }}文件</h4>
-              <p>{{ getDeliveryTypeDescription(activeDeliveryType) }}</p>
-              @if (canEdit) {
-                <input
-                  type="file"
-                  multiple
-                  (change)="uploadDeliveryFile($event, activeProductId, activeDeliveryType)"
-                  [accept]="'image/*,.pdf,.dwg,.dxf,.skp,.max'"
-                  [disabled]="uploadingDeliveryFiles"
-                  hidden
-                  #deliveryFileInputEmpty />
-                
-                <button
-                  class="upload-button-primary"
-                  [disabled]="uploadingDeliveryFiles"
-                  (click)="deliveryFileInputEmpty.click()">
-                  <svg class="icon" width="20" height="20" viewBox="0 0 24 24">
-                    <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
-                  </svg>
-                  <span>立即上传</span>
-                </button>
-              }
             </div>
           }
-        </div>
-      </div>
-    }
-
-    <!-- 未选择场景时的提示 -->
-    @if (!activeProductId && projectProducts.length > 0) {
-      <div class="no-selection-state">
-        <div class="state-icon">
-          <svg class="icon" width="64" height="64" viewBox="0 0 24 24">
-            <path fill="currentColor" d="M12 3L2 12h3v8h14v-8h3L12 3m0 5.75A2.25 2.25 0 0 1 14.25 11A2.25 2.25 0 0 1 12 13.25A2.25 2.25 0 0 1 9.75 11A2.25 2.25 0 0 1 12 8.75Z"/>
-          </svg>
-        </div>
-        <h3>请选择一个空间场景</h3>
-        <p>选择场景后可以查看和管理该空间的交付文件</p>
+        }
       </div>
     }
 
     <!-- 没有场景时的提示 -->
-    @if (projectProducts.length === 0 && !loading) {
+    @if (projectProducts.length == 0 && !loading) {
       <div class="no-products-state">
         <div class="state-icon">
           <svg class="icon" width="64" height="64" viewBox="0 0 24 24">
@@ -349,31 +208,5 @@
         <p>请先在方案深化阶段添加项目空间</p>
       </div>
     }
-
-    <!-- 操作按钮(参考售后归档样式) -->
-    @if (!loading && !isAdminView) {
-      <div class="action-buttons">
-        <button
-          class="btn btn-primary btn-block btn-large"
-          (click)="submitDeliveryForApproval()"
-          [disabled]="!canEdit || saving || projectProducts.length === 0">
-          @if (saving) {
-            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
-              <path fill="currentColor" d="M304 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zm0 416a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM48 304a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm464-48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM142.9 437A48 48 0 1 0 75 369.1 48 48 0 1 0 142.9 437zm0-294.2A48 48 0 1 0 75 75a48 48 0 1 0 67.9 67.9zM369.1 437A48 48 0 1 0 437 369.1 48 48 0 1 0 369.1 437z"/>
-            </svg>
-            <span>提交中...</span>
-          } @else {
-            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
-              <path fill="currentColor" d="M476 3L36.8 230.2c-13 7-12 25.8 1.7 31.1L176 308l27 134.8c3 14.8 21 20.6 32.2 10.2l59.2-55.9 98.2 73.7c10.5 7.9 25.6 2.7 29.1-9.7L509 17.6C512.7 4.8 493.9-5.2 476 3zM214.4 453.1l-20.5-102.3 73.7 55.3-53.2 47z"/>
-            </svg>
-            <span>提交审批</span>
-          }
-        </button>
-      </div>
-      
-      @if (projectProducts.length === 0) {
-        <div class="button-tip">请先在"确认需求"阶段添加项目空间</div>
-      }
-    }
   }
 </div>

+ 340 - 0
src/modules/project/pages/project-detail/stages/stage-delivery.component.html.backup

@@ -0,0 +1,340 @@
+<div class="stage-delivery-container">
+  <!-- 加载状态 -->
+  @if (loading) {
+    <div class="loading-state">
+      <div class="spinner"></div>
+      <p>加载中...</p>
+    </div>
+  }
+
+  @if (!loading) {
+
+
+    <!-- 🆕 空间列表(可折叠展开,显示4个阶段) -->
+    @if (projectProducts.length > 0) {
+      <div class="spaces-list-section">
+        @for (space of projectProducts; track space.id) {
+          <!-- 空间头部(显示4个阶段标签) -->
+          <div class="space-header" (click)="toggleSpaceExpansion(space.id)">
+            <div class="space-name">{{ getSpaceDisplayName(space) }}</div>
+            
+            <!-- 4个阶段标签 -->
+            <div class="stage-tabs">
+              @for (type of deliveryTypes; track type.id) {
+                <div class="stage-tab" 
+                     [class.has-files]="getSpaceStageFileCount(space.id, type.id) > 0"
+                     [class.confirmed]="isStageConfirmed(space.id, type.id)"
+                     (click)="selectSpaceAndStage(space.id, type.id); $event.stopPropagation()">
+                  <span class="stage-name">{{ type.name }}</span>
+                  @if (getSpaceStageFileCount(space.id, type.id) > 0) {
+                    <span class="file-count">{{ getSpaceStageFileCount(space.id, type.id) }}</span>
+                  }
+                  @if (isStageConfirmed(space.id, type.id)) {
+                    <span class="confirmed-icon">✓</span>
+                  }
+                </div>
+              }
+            </div>
+            
+            <!-- 展开/收起图标 -->
+            <div class="expand-icon" [class.expanded]="isSpaceExpanded(space.id)">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+                <path d="M7 10l5 5 5-5z"/>
+              </svg>
+            </div>
+          </div>
+
+          <!-- 空间内容(展开时显示) -->
+          @if (isSpaceExpanded(space.id)) {
+            <div class="space-content">
+              <!-- 显示当前选中阶段的内容 -->
+              @if (selectedSpaceId === space.id && selectedStageType) {
+                <div class="stage-content-area">
+                  <!-- 阶段标题和文件数量 -->
+                  <div class="stage-header-bar">
+                    <h3>{{ getSpaceDisplayName(space) }}</h3>
+                    <div class="file-count-display">
+                      {{ getSpaceStageFileCount(space.id, selectedStageType) }}/4
+                    </div>
+                  </div>
+              <div class="type-icon">
+                <svg class="icon" width="24" height="24" viewBox="0 0 512 512">
+                  @switch (type.id) {
+                    @case ('white_model') {
+                      <path fill="currentColor" d="M234.5 5.7c13.9-5 29.1-5 43.1 0l192 68.6C495 83.4 512 107.5 512 134.6V377.4c0 27-17 51.2-42.5 60.3l-192 68.6c-13.9 5-29.1 5-43.1 0l-192-68.6C17 428.6 0 404.5 0 377.4V134.6c0-27 17-51.2 42.5-60.3l192-68.6zM256 66L82.3 128 256 190l173.7-62L256 66zm32 368.6l160-57.1v-188L288 246.6v188z"/>
+                    }
+                    @case ('soft_decor') {
+                      <path fill="currentColor" d="M512 256c0 .9 0 1.8 0 2.7c-.4 36.5-33.6 61.3-70.1 61.3H344c-26.5 0-48 21.5-48 48c0 3.4 .4 6.7 1 9.9c2.1 10.2 6.5 20 10.8 29.9c6.1 13.8 12.1 27.5 12.1 42c0 31.8-21.6 60.7-53.4 62c-3.5 .1-7 .2-10.6 .2C114.6 512 0 397.4 0 256S114.6 0 256 0S512 114.6 512 256zM128 288a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm0-96a32 32 0 1 0 0-64 32 32 0 1 0 0 64zM288 96a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm96 96a32 32 0 1 0 0-64 32 32 0 1 0 0 64z"/>
+                    }
+                    @case ('rendering') {
+                      <path fill="currentColor" d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/>
+                    }
+                    @case ('post_process') {
+                      <path fill="currentColor" d="M234.7 42.7L197 56.8c-3 1.1-5 4-5 7.2s2 6.1 5 7.2l37.7 14.1L248.8 123c1.1 3 4 5 7.2 5s6.1-2 7.2-5l14.1-37.7L315 71.2c3-1.1 5-4 5-7.2s-2-6.1-5-7.2L277.3 42.7 263.2 5c-1.1-3-4-5-7.2-5s-6.1 2-7.2 5L234.7 42.7zM46.1 395.4c-18.7 18.7-18.7 49.1 0 67.9l34.6 34.6c18.7 18.7 49.1 18.7 67.9 0L529.9 116.5c18.7-18.7 18.7-49.1 0-67.9L495.3 14.1c-18.7-18.7-49.1-18.7-67.9 0L46.1 395.4zM484.6 82.6l-105 105-23.3-23.3 105-105 23.3 23.3zM7.5 117.2C3 118.9 0 123.2 0 128s3 9.1 7.5 10.8L64 160l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L128 160l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L128 96 106.8 39.5C105.1 35 100.8 32 96 32s-9.1 3-10.8 7.5L64 96 7.5 117.2zm352 256c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L416 416l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L480 416l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L480 352l-21.2-56.5c-1.7-4.5-6-7.5-10.8-7.5s-9.1 3-10.8 7.5L416 352l-56.5 21.2z"/>
+                    }
+                  }
+                </svg>
+              </div>
+              <div class="type-content">
+                <span class="type-name">{{ type.name }}</span>
+                <span class="type-description">{{ type.description }}</span>
+              </div>
+              <div class="type-badges">
+                @if (getCurrentTypeFileCount(activeProductId, type.id) > 0) {
+                  <span class="file-count-badge">{{ getCurrentTypeFileCount(activeProductId, type.id) }}</span>
+                }
+                @if (getTypeUnverifiedFileCount(activeProductId, type.id) > 0) {
+                  <span class="unverified-badge">{{ getTypeUnverifiedFileCount(activeProductId, type.id) }} 未验证</span>
+                }
+                <!-- 阶段状态徽章 -->
+                @if (isApproved(type.id)) {
+                  <span class="status-badge approved">✓ 已通过</span>
+                }
+                @if (needsApproval(type.id)) {
+                  <span class="status-badge pending">⏳ 待审批</span>
+                }
+                @if (isRejected(type.id)) {
+                  <span class="status-badge rejected">✗ 已驳回</span>
+                }
+              </div>
+            </div>
+          }
+        </div>
+      </div>
+
+      <!-- 文件上传和展示区域 -->
+      <div class="delivery-content-section">
+        <!-- 阶段锁定提示 -->
+        @if (!canUploadCurrentStage()) {
+          <div class="stage-locked-notice">
+            <div class="lock-icon">🔒</div>
+            <div class="lock-content">
+              <h4>当前阶段尚未解锁</h4>
+              <p>请先完成<strong>{{ getPreviousStageName() }}</strong>阶段并等待组长审批通过后,才能上传当前阶段文件</p>
+            </div>
+          </div>
+        }
+        
+        <!-- 上传区域 -->
+        <div class="upload-section" [class.disabled]="!canUploadCurrentStage()">
+          <div class="upload-area" [class.uploading]="uploadingDeliveryFiles" [class.locked]="!canUploadCurrentStage()">
+            <div class="upload-content">
+              <div class="upload-icon">
+                <svg class="icon" width="48" height="48" viewBox="0 0 24 24">
+                  <path fill="currentColor" d="M11 15h2V9h3l-4-5l-4 5h3Zm-7 7c-.55 0-1.02-.196-1.413-.587A1.928 1.928 0 0 1 2 20V8c0-.55.196-1.02.587-1.412A1.93 1.93 0 0 1 4 6h4l2-2h4l-2 2H8.83L7.5 7.5H4V20h16V8h-6V6h6c.55 0 1.02.196 1.413.588C21.803 6.98 22 7.45 22 8v12c0 .55-.196 1.02-.587 1.413A1.928 1.928 0 0 1 20 22Z"/>
+                </svg>
+              </div>
+              <div class="upload-text">
+                <h4>上传{{ getDeliveryTypeName(activeDeliveryType) }}文件</h4>
+                <p>{{ getDeliveryTypeDescription(activeDeliveryType) }}</p>
+              </div>
+              @if (canEdit && canUploadCurrentStage()) {
+                <input
+                  type="file"
+                  multiple
+                  (change)="uploadDeliveryFile($event, activeProductId, activeDeliveryType)"
+                  [accept]="'image/*,.pdf,.dwg,.dxf,.skp,.max'"
+                  [disabled]="uploadingDeliveryFiles"
+                  hidden
+                  #deliveryFileInput />
+                
+                <button
+                  class="upload-button"
+                  [disabled]="uploadingDeliveryFiles || !canUploadCurrentStage()"
+                  (click)="deliveryFileInput.click()">
+                  <svg class="icon" width="20" height="20" viewBox="0 0 24 24">
+                    <path fill="currentColor" d="M11 15h2V9h3l-4-5l-4 5h3Zm-7 7c-.55 0-1.02-.196-1.413-.587A1.928 1.928 0 0 1 2 20V8c0-.55.196-1.02.587-1.412A1.93 1.93 0 0 1 4 6h4l2-2h4l-2 2H8.83L7.5 7.5H4V20h16V8h-6V6h6c.55 0 1.02.196 1.413.588C21.803 6.98 22 7.45 22 8v12c0 .55-.196 1.02-.587 1.413A1.928 1.928 0 0 1 20 22Z"/>
+                  </svg>
+                  <span>选择文件上传</span>
+                </button>
+              }
+            </div>
+
+            <!-- 上传进度条 -->
+            @if (uploadingDeliveryFiles) {
+              <div class="upload-progress">
+                <div class="progress-bar">
+                  <div class="progress-fill" [style.width.%]="uploadProgress"></div>
+                </div>
+                <span class="progress-text">上传中... {{ uploadProgress }}%</span>
+              </div>
+            }
+          </div>
+        </div>
+
+        <!-- 文件列表展示 -->
+        <div class="files-display-section">
+          @if (getProductDeliveryFiles(activeProductId, activeDeliveryType).length > 0) {
+            <div class="files-header">
+              <h4>已上传文件 ({{ getProductDeliveryFiles(activeProductId, activeDeliveryType).length }})</h4>
+            </div>
+            <div class="files-grid">
+              @for (file of getProductDeliveryFiles(activeProductId, activeDeliveryType); track file.id) {
+                <div class="file-card" [class.has-approval-issue]="file.approvalStatus === 'rejected'">
+                  <!-- 文件预览 -->
+                  <div class="file-preview" (click)="previewFile(file)">
+                    @if (isImageFile(file.name)) {
+                      <img [src]="file.url" [alt]="file.name" class="preview-image" (error)="onImageError($event)" />
+                    } @else {
+                      <div class="file-type-icon">
+                        <svg class="icon" width="48" height="48" viewBox="0 0 24 24">
+                          <path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
+                        </svg>
+                      </div>
+                    }
+                    
+                    <!-- 审批状态角标 -->
+                    <div class="approval-corner-badge" [ngClass]="'badge-' + file.approvalStatus">
+                      @if (file.approvalStatus === 'unverified') {
+                        <span>⏳</span>
+                      } @else if (file.approvalStatus === 'pending') {
+                        <span>🔍</span>
+                      } @else if (file.approvalStatus === 'approved') {
+                        <span>✅</span>
+                      } @else if (file.approvalStatus === 'rejected') {
+                        <span>❌</span>
+                      }
+                    </div>
+                    
+                    <div class="file-overlay">
+                      <svg class="icon" width="24" height="24" viewBox="0 0 24 24">
+                        <path fill="white" d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
+                      </svg>
+                    </div>
+                    
+                    <!-- 删除按钮(参考售后归档样式) -->
+                    @if (canEdit) {
+                      <button class="delete-btn" (click)="deleteDeliveryFile(activeProductId, activeDeliveryType, file.id); $event.stopPropagation()">
+                        <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                          <path d="M112 112l20 320c.95 18.49 14.4 32 32 32h184c17.67 0 30.87-13.51 32-32l20-320" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                          <path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M80 112h352"/>
+                          <path d="M192 112V72h0a23.93 23.93 0 0124-24h80a23.93 23.93 0 0124 24h0v40M256 176v224M184 176l8 224M328 176l-8 224" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/>
+                        </svg>
+                      </button>
+                    }
+                  </div>
+
+                  <!-- 文件信息(参考售后归档样式) -->
+                  <div class="file-info">
+                    <div class="file-name" [title]="file.name">{{ file.name }}</div>
+                    <div class="file-meta">
+                      <span class="file-size">{{ formatFileSize(file.size) }}</span>
+                      <span class="file-time">{{ file.uploadTime | date: 'yyyy-MM-dd HH:mm' }}</span>
+                    </div>
+                    @if (file.uploadedBy) {
+                      <div class="file-uploader">上传: {{ file.uploadedBy }}</div>
+                    }
+                    
+                    <!-- ✨ 审批状态显示(紧凑单行样式) -->
+                    @if (file.approvalStatus && file.approvalStatus !== 'unverified') {
+                      <div class="file-approval-status" [ngClass]="'status-' + file.approvalStatus">
+                        <span class="status-icon">
+                          @if (file.approvalStatus === 'approved') {
+                            ✅
+                          } @else if (file.approvalStatus === 'rejected') {
+                            ❌
+                          } @else if (file.approvalStatus === 'pending') {
+                            🔍
+                          }
+                        </span>
+                        <span class="status-text">{{ getApprovalStatusText(file.approvalStatus) }}</span>
+                        @if (file.approvedBy) {
+                          <span class="status-by">· {{ file.approvedBy }}</span>
+                        }
+                        @if (file.rejectionReason) {
+                          <span class="status-reason">: {{ file.rejectionReason }}</span>
+                        }
+                      </div>
+                    }
+                  </div>
+                </div>
+              }
+            </div>
+          } @else {
+            <div class="empty-state">
+              <div class="empty-icon">
+                <svg class="icon" width="64" height="64" viewBox="0 0 24 24">
+                  <path fill="currentColor" d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"/>
+                </svg>
+              </div>
+              <h4>暂无{{ getDeliveryTypeName(activeDeliveryType) }}文件</h4>
+              <p>{{ getDeliveryTypeDescription(activeDeliveryType) }}</p>
+              @if (canEdit) {
+                <input
+                  type="file"
+                  multiple
+                  (change)="uploadDeliveryFile($event, activeProductId, activeDeliveryType)"
+                  [accept]="'image/*,.pdf,.dwg,.dxf,.skp,.max'"
+                  [disabled]="uploadingDeliveryFiles"
+                  hidden
+                  #deliveryFileInputEmpty />
+                
+                <button
+                  class="upload-button-primary"
+                  [disabled]="uploadingDeliveryFiles"
+                  (click)="deliveryFileInputEmpty.click()">
+                  <svg class="icon" width="20" height="20" viewBox="0 0 24 24">
+                    <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+                  </svg>
+                  <span>立即上传</span>
+                </button>
+              }
+            </div>
+          }
+        </div>
+      </div>
+    }
+
+    <!-- 未选择场景时的提示 -->
+    @if (!activeProductId && projectProducts.length > 0) {
+      <div class="no-selection-state">
+        <div class="state-icon">
+          <svg class="icon" width="64" height="64" viewBox="0 0 24 24">
+            <path fill="currentColor" d="M12 3L2 12h3v8h14v-8h3L12 3m0 5.75A2.25 2.25 0 0 1 14.25 11A2.25 2.25 0 0 1 12 13.25A2.25 2.25 0 0 1 9.75 11A2.25 2.25 0 0 1 12 8.75Z"/>
+          </svg>
+        </div>
+        <h3>请选择一个空间场景</h3>
+        <p>选择场景后可以查看和管理该空间的交付文件</p>
+      </div>
+    }
+
+    <!-- 没有场景时的提示 -->
+    @if (projectProducts.length === 0 && !loading) {
+      <div class="no-products-state">
+        <div class="state-icon">
+          <svg class="icon" width="64" height="64" viewBox="0 0 24 24">
+            <path fill="currentColor" d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"/>
+          </svg>
+        </div>
+        <h3>暂无项目空间</h3>
+        <p>请先在方案深化阶段添加项目空间</p>
+      </div>
+    }
+
+    <!-- 操作按钮(参考售后归档样式) -->
+    @if (!loading && !isAdminView) {
+      <div class="action-buttons">
+        <button
+          class="btn btn-primary btn-block btn-large"
+          (click)="submitDeliveryForApproval()"
+          [disabled]="!canEdit || saving || projectProducts.length === 0">
+          @if (saving) {
+            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+              <path fill="currentColor" d="M304 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zm0 416a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM48 304a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm464-48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM142.9 437A48 48 0 1 0 75 369.1 48 48 0 1 0 142.9 437zm0-294.2A48 48 0 1 0 75 75a48 48 0 1 0 67.9 67.9zM369.1 437A48 48 0 1 0 437 369.1 48 48 0 1 0 369.1 437z"/>
+            </svg>
+            <span>提交中...</span>
+          } @else {
+            <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+              <path fill="currentColor" d="M476 3L36.8 230.2c-13 7-12 25.8 1.7 31.1L176 308l27 134.8c3 14.8 21 20.6 32.2 10.2l59.2-55.9 98.2 73.7c10.5 7.9 25.6 2.7 29.1-9.7L509 17.6C512.7 4.8 493.9-5.2 476 3zM214.4 453.1l-20.5-102.3 73.7 55.3-53.2 47z"/>
+            </svg>
+            <span>提交审批</span>
+          }
+        </button>
+      </div>
+      
+      @if (projectProducts.length === 0) {
+        <div class="button-tip">请先在"确认需求"阶段添加项目空间</div>
+      }
+    }
+  }
+</div>

+ 687 - 1750
src/modules/project/pages/project-detail/stages/stage-delivery.component.scss

@@ -1,508 +1,7 @@
 .stage-delivery-container {
-  padding: 16px;
-  background-color: #f8f9fa;
+  padding: 20px;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
   min-height: 100vh;
-  
-  // ============ 阶段锁定提示 ============
-  .stage-locked-notice {
-    background: linear-gradient(135deg, #fff3e0, #ffe0b2);
-    border: 2px solid #ff9800;
-    border-radius: 12px;
-    padding: 20px;
-    margin-bottom: 20px;
-    display: flex;
-    align-items: center;
-    gap: 16px;
-    animation: slideDown 0.3s ease-out;
-    
-    .lock-icon {
-      font-size: 48px;
-      flex-shrink: 0;
-    }
-    
-    .lock-content {
-      flex: 1;
-      
-      h4 {
-        margin: 0 0 8px;
-        font-size: 18px;
-        font-weight: 600;
-        color: #f57c00;
-      }
-      
-      p {
-        margin: 0;
-        font-size: 14px;
-        line-height: 1.6;
-        color: #e65100;
-        
-        strong {
-          font-weight: 600;
-          color: #d84315;
-        }
-      }
-    }
-  }
-
-  // ============ 上传区域锁定状态 ============
-  .upload-section.disabled {
-    opacity: 0.5;
-    pointer-events: none;
-    
-    .upload-area.locked {
-      background: #f5f5f5;
-      border-color: #e0e0e0;
-      cursor: not-allowed;
-    }
-  }
-  
-  // ============ 审批状态横幅样式(与订单分配阶段保持一致)============
-  .approval-status-banner {
-    padding: 16px 20px;
-    border-radius: 12px;  // ⭐ 增加圆角,更柔和
-    margin-bottom: 20px;
-    display: flex;
-    align-items: flex-start;
-    gap: 16px;
-    animation: slideDown 0.3s ease-out;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);  // ⭐ 添加阴影,更立体
-
-    .status-icon {
-      font-size: 32px;
-      flex-shrink: 0;
-    }
-
-    .status-content {
-      flex: 1;
-
-      h4 {
-        margin: 0 0 8px;
-        font-size: 18px;
-        font-weight: 600;
-      }
-
-      p {
-        margin: 0;
-        font-size: 14px;
-        line-height: 1.5;
-
-      strong {
-        font-weight: 600;
-        }
-      }
-
-      .btn-resubmit {
-        margin-top: 12px;
-        padding: 8px 20px;
-        background: white;
-        border: 2px solid currentColor;
-        border-radius: 6px;
-        font-size: 14px;
-        font-weight: 500;
-        cursor: pointer;
-        transition: all 0.3s;
-
-        &:hover {
-          transform: translateY(-2px);
-          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-        }
-      }
-    }
-
-    &.pending {
-      background: linear-gradient(135deg, #fff3e0, #ffe0b2);
-      border: 2px solid #ff9800;
-      color: #f57c00;
-
-      .btn-resubmit {
-        color: #f57c00;
-        border-color: #f57c00;
-
-        &:hover {
-          background: #f57c00;
-          color: white;
-        }
-      }
-    }
-
-    &.approved {
-      background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
-      border: 2px solid #4caf50;
-      color: #2e7d32;
-
-      .btn-resubmit {
-        color: #2e7d32;
-        border-color: #2e7d32;
-
-        &:hover {
-          background: #2e7d32;
-          color: white;
-        }
-      }
-    }
-
-    &.rejected {
-      background: linear-gradient(135deg, #ffebee, #ffcdd2);
-      border: 2px solid #f44336;
-      color: #c62828;
-
-      .btn-resubmit {
-        color: #c62828;
-        border-color: #c62828;
-
-        &:hover {
-          background: #c62828;
-          color: white;
-        }
-      }
-    }
-  }
-
-  // ============ 组长审批操作条样式(居中显示)============
-  .leader-approval-bar {
-    margin: 24px 0;
-    padding: 20px;
-    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
-    border-radius: 16px;
-    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
-    animation: slideDown 0.3s ease-out;
-
-    .approval-buttons-container {
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      gap: 20px;
-      flex-wrap: wrap;
-
-      button {
-        position: relative;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        gap: 10px;
-        padding: 14px 32px;
-        font-size: 16px;
-        font-weight: 600;
-        border: none;
-        border-radius: 12px;
-        cursor: pointer;
-        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-        min-width: 160px;
-        overflow: hidden;
-
-        &::before {
-          content: '';
-          position: absolute;
-          top: 50%;
-          left: 50%;
-          width: 0;
-          height: 0;
-          border-radius: 50%;
-          background: rgba(255, 255, 255, 0.3);
-          transform: translate(-50%, -50%);
-          transition: width 0.6s, height 0.6s;
-        }
-
-        &:hover::before {
-          width: 300px;
-          height: 300px;
-        }
-
-        .btn-icon {
-          font-size: 20px;
-          transition: transform 0.3s ease;
-        }
-
-        .btn-text {
-          position: relative;
-          z-index: 1;
-        }
-
-        &:hover .btn-icon {
-          transform: scale(1.2);
-        }
-
-        &:active {
-          transform: scale(0.95);
-        }
-
-        &:disabled {
-          opacity: 0.6;
-          cursor: not-allowed;
-          transform: none;
-
-          &:hover {
-            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-          }
-        }
-      }
-
-      .btn-approve {
-        background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
-        color: white;
-        box-shadow: 0 6px 20px rgba(76, 175, 80, 0.3);
-
-        &:hover:not(:disabled) {
-          background: linear-gradient(135deg, #66bb6a 0%, #43a047 100%);
-          box-shadow: 0 10px 30px rgba(76, 175, 80, 0.5);
-          transform: translateY(-3px) scale(1.02);
-        }
-
-        &:active:not(:disabled) {
-          background: linear-gradient(135deg, #388e3c 0%, #1b5e20 100%);
-        }
-      }
-
-      .btn-reject {
-        background: linear-gradient(135deg, #f44336 0%, #c62828 100%);
-        color: white;
-        box-shadow: 0 6px 20px rgba(244, 67, 54, 0.3);
-
-        &:hover:not(:disabled) {
-          background: linear-gradient(135deg, #ef5350 0%, #d32f2f 100%);
-          box-shadow: 0 10px 30px rgba(244, 67, 54, 0.5);
-          transform: translateY(-3px) scale(1.02);
-        }
-
-        &:active:not(:disabled) {
-          background: linear-gradient(135deg, #c62828 0%, #b71c1c 100%);
-        }
-      }
-    }
-
-    // 审批提示信息
-    .approval-hint {
-      text-align: center;
-      margin: 16px 0 0;
-      padding: 12px 20px;
-      background: rgba(255, 193, 7, 0.15);
-      border: 1px solid rgba(255, 193, 7, 0.3);
-      border-radius: 8px;
-      font-size: 14px;
-      color: #f57c00;
-      line-height: 1.6;
-      animation: pulse 2s ease-in-out infinite;
-    }
-  }
-
-  @keyframes slideDown {
-    from {
-      opacity: 0;
-      transform: translateY(-20px);
-    }
-    to {
-      opacity: 1;
-      transform: translateY(0);
-    }
-  }
-
-@keyframes pulse {
-  0%, 100% {
-    opacity: 1;
-  }
-  50% {
-    opacity: 0.7;
-  }
-}
-
-// ============ 🧪 测试标记区域样式 ============
-.test-mark-section {
-  margin: 20px 0;
-  padding: 16px 20px;
-  background: linear-gradient(135deg, #fff9e6, #fff3cc);
-  border: 2px dashed #ffa500;
-  border-radius: 12px;
-  animation: slideDown 0.3s ease-out;
-  
-  .btn-test-mark {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    gap: 10px;
-    padding: 12px 24px;
-    background: linear-gradient(135deg, #ffa500, #ff8c00);
-    color: white;
-    border: none;
-    border-radius: 10px;
-    font-size: 15px;
-    font-weight: 600;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    box-shadow: 0 4px 12px rgba(255, 165, 0, 0.3);
-    width: 100%;
-    max-width: 300px;
-    margin: 0 auto;
-    
-    .test-icon {
-      font-size: 18px;
-    }
-    
-    .test-text {
-      position: relative;
-      z-index: 1;
-    }
-    
-    &:hover:not(:disabled) {
-      background: linear-gradient(135deg, #ff8c00, #ff7700);
-      transform: translateY(-2px);
-      box-shadow: 0 6px 20px rgba(255, 165, 0, 0.4);
-    }
-    
-    &:active:not(:disabled) {
-      transform: translateY(0);
-    }
-    
-    &:disabled {
-      opacity: 0.6;
-      cursor: not-allowed;
-      transform: none;
-    }
-  }
-  
-  .test-hint {
-    text-align: center;
-    margin: 12px 0 0;
-    font-size: 13px;
-    color: #cc8400;
-    line-height: 1.5;
-    
-    &::before {
-      content: '💡 ';
-    }
-  }
-}
-
-  // 审批消息流(紧凑样式)
-  .approval-messages-container {
-    background: white;
-    border-radius: 12px;
-    padding: 12px 16px;
-    margin-bottom: 16px;
-    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
-
-    .messages-header {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-      font-size: 13px;
-      font-weight: 600;
-      color: #6c757d;
-      margin-bottom: 12px;
-      padding-bottom: 8px;
-      border-bottom: 1px solid #e9ecef;
-
-      .icon {
-        width: 16px;
-        height: 16px;
-        color: #6366f1;
-      }
-    }
-
-    .approval-messages {
-      display: flex;
-      flex-direction: column;
-      gap: 8px;
-
-      .approval-message {
-        display: flex;
-        gap: 10px;
-        padding: 8px 12px;
-        border-radius: 8px;
-        background: #f8f9fa;
-        transition: all 0.2s ease;
-
-        &:hover {
-          background: #e9ecef;
-        }
-
-        &[data-status="approved"] {
-          background: #d1fae5;
-          border-left: 3px solid #10b981;
-        }
-
-        &[data-status="rejected"] {
-          background: #fee2e2;
-          border-left: 3px solid #ef4444;
-        }
-
-        &[data-status="pending"] {
-          background: #fef3c7;
-          border-left: 3px solid #f59e0b;
-        }
-
-        .message-icon {
-          flex-shrink: 0;
-          width: 24px;
-          height: 24px;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-
-          .icon-emoji {
-            font-size: 18px;
-            line-height: 1;
-          }
-        }
-
-        .message-content {
-          flex: 1;
-          min-width: 0;
-
-          .message-header {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            margin-bottom: 4px;
-
-            .message-stage {
-              font-size: 12px;
-              font-weight: 600;
-              color: #374151;
-            }
-
-            .message-time {
-              font-size: 11px;
-              color: #9ca3af;
-            }
-          }
-
-          .message-body {
-            font-size: 13px;
-            color: #4b5563;
-            display: flex;
-            align-items: center;
-            flex-wrap: wrap;
-            gap: 4px;
-
-            .message-user {
-              font-weight: 600;
-              color: #1f2937;
-            }
-
-            .message-text {
-              color: #6b7280;
-            }
-
-            .message-approver {
-              color: #9ca3af;
-              font-size: 12px;
-            }
-          }
-
-          .message-comment {
-            margin-top: 6px;
-            padding: 6px 10px;
-            background: rgba(255, 255, 255, 0.6);
-            border-radius: 6px;
-            font-size: 12px;
-            color: #6b7280;
-            font-style: italic;
-          }
-        }
-      }
-    }
-  }
 
   // 加载状态
   .loading-state {
@@ -510,22 +9,24 @@
     flex-direction: column;
     align-items: center;
     justify-content: center;
-    padding: 60px 20px;
-    text-align: center;
-
+    padding: 80px 20px;
+    
     .spinner {
-      width: 40px;
-      height: 40px;
-      border: 4px solid #e9ecef;
-      border-top-color: var(--ion-color-primary, #3880ff);
+      width: 56px;
+      height: 56px;
+      border: 5px solid rgba(102, 126, 234, 0.1);
+      border-top-color: #667eea;
+      border-right-color: #764ba2;
       border-radius: 50%;
-      animation: spin 0.8s linear infinite;
+      animation: spin 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
+      box-shadow: 0 4px 16px rgba(102, 126, 234, 0.2);
     }
-
+    
     p {
-      margin-top: 16px;
-      color: #6c757d;
-      font-size: 14px;
+      margin-top: 20px;
+      color: #475569;
+      font-size: 15px;
+      font-weight: 500;
     }
   }
 
@@ -533,1340 +34,776 @@
     to { transform: rotate(360deg); }
   }
 
-  // 通用标签标题
-  .section-label {
-    font-size: 13px;
-    font-weight: 600;
-    color: #6c757d;
-    text-transform: uppercase;
-    letter-spacing: 0.5px;
-    margin-bottom: 12px;
-    padding-left: 4px;
-  }
-
-  // 场景Product选择标签 (第一层)
-  .product-tabs-section {
-    margin-bottom: 20px;
-
-    .product-tabs {
-      display: grid;
-      grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
-      gap: 10px;
-
-      .product-tab {
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        gap: 6px;
-        padding: 14px 12px;
-        background: white;
-        border-radius: 12px;
-        border: 2px solid #e9ecef;
-        cursor: pointer;
-        transition: all 0.3s ease;
-        position: relative;
-
-        .product-icon {
-          width: 36px;
-          height: 36px;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          border-radius: 10px;
-          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-          color: white;
-          transition: transform 0.3s ease;
-
-          .icon {
-            width: 20px;
-            height: 20px;
-          }
-        }
-
-        .product-name {
-          font-size: 13px;
-          font-weight: 600;
-          color: #495057;
-          text-align: center;
-          line-height: 1.3;
-        }
-
-        .file-count-badge {
-          position: absolute;
-          top: 8px;
-          right: 8px;
-          background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
-          color: white;
-          font-size: 11px;
-          font-weight: 700;
-          padding: 2px 6px;
-          border-radius: 10px;
-          min-width: 18px;
-          text-align: center;
-        }
-
-        &:hover {
-          border-color: var(--ion-color-primary, #3880ff);
-          transform: translateY(-2px);
-          box-shadow: 0 4px 12px rgba(56, 128, 255, 0.15);
-
-          .product-icon {
-            transform: scale(1.1);
-          }
-        }
-
-        &.active {
-          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-          border-color: transparent;
-          color: white;
-          box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
-
-          .product-icon {
-            background: rgba(255, 255, 255, 0.25);
-            transform: scale(1.15);
-          }
-
-          .product-name {
-            color: white;
-          }
-
-          .file-count-badge {
-            background: white;
-            color: #667eea;
-          }
+  // ==================== 🆕 空间列表样式 ====================
+  .spaces-list-section {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+    
+    // 空间头部(折叠状态)
+    .space-header {
+      display: flex;
+      align-items: center;
+      gap: 16px;
+      padding: 18px 24px;
+      background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
+      border: 2px solid transparent;
+      border-radius: 16px;
+      cursor: pointer;
+      transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+      box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
+      position: relative;
+      overflow: hidden;
+      
+      // 渐变边框效果
+      &::before {
+        content: '';
+        position: absolute;
+        inset: 0;
+        border-radius: 16px;
+        padding: 2px;
+        background: linear-gradient(135deg, #667eea, #764ba2, #f093fb, #4facfe);
+        -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+        mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
+        -webkit-mask-composite: xor;
+        mask-composite: exclude;
+        opacity: 0;
+        transition: opacity 0.4s ease;
+      }
+      
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 8px 24px rgba(102, 126, 234, 0.15);
+        
+        &::before {
+          opacity: 1;
         }
       }
-    }
-  }
-
-  // 交付类型选择标签 (第二层)
-  .delivery-types-section {
-    margin-bottom: 20px;
-
-    .delivery-types-tabs {
-      display: grid;
-      grid-template-columns: repeat(4, 1fr);
-      gap: 10px;
-
-      .delivery-type-tab {
+      
+      .space-name {
+        font-size: 18px;
+        font-weight: 600;
+        color: #1e293b;
+        min-width: 120px;
+        flex-shrink: 0;
+      }
+      
+      // 4个阶段标签(横向排列)
+      .stage-tabs {
         display: flex;
-        flex-direction: column;
-        align-items: center;
-        gap: 8px;
-        padding: 16px 10px;
-        background: white;
-        border-radius: 14px;
-        border: 2px solid #e9ecef;
-        cursor: pointer;
-        transition: all 0.3s ease;
-        position: relative;
-
-        .type-icon {
-          width: 44px;
-          height: 44px;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          border-radius: 12px;
-          background: #f8f9fa;
-          transition: all 0.3s ease;
-
-          .icon {
-            width: 24px;
-            height: 24px;
-            color: #6c757d;
-            transition: color 0.3s ease;
-          }
-        }
-
-        .type-content {
+        gap: 12px;
+        flex: 1;
+        justify-content: center;
+        
+        .stage-tab {
           display: flex;
-          flex-direction: column;
           align-items: center;
-          gap: 2px;
-
-          .type-name {
-            font-size: 14px;
-            font-weight: 700;
-            color: #212529;
-            text-align: center;
-          }
-
-          .type-description {
-            font-size: 11px;
-            color: #868e96;
-            text-align: center;
-            line-height: 1.3;
-            display: -webkit-box;
-            -webkit-line-clamp: 2;
-            line-clamp: 2;
-            -webkit-box-orient: vertical;
-            overflow: hidden;
-          }
-        }
-
-        .type-badges {
-          display: flex;
-          flex-wrap: wrap;
-          gap: 6px;
-          justify-content: center;
-          margin-top: 4px;
-        }
-        
-        .file-count-badge {
-          background: #6c757d;
-          color: white;
-          font-size: 11px;
-          font-weight: 700;
-          padding: 3px 8px;
-          border-radius: 12px;
-          min-width: 20px;
-          text-align: center;
-        }
-        
-        .unverified-badge {
-          background: #ffc107;
-          color: #000;
-          font-size: 10px;
-          font-weight: 600;
-          padding: 3px 8px;
-          border-radius: 12px;
-        }
-        
-        .status-badge {
-          font-size: 11px;
-          font-weight: 600;
-          padding: 3px 8px;
+          gap: 8px;
+          padding: 10px 18px;
+          background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+          border: 2px solid #e2e8f0;
           border-radius: 12px;
-          white-space: nowrap;
+          font-size: 14px;
+          color: #64748b;
+          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+          cursor: pointer;
+          position: relative;
+          overflow: hidden;
           
-          &.approved {
-            background: #4caf50;
-            color: white;
+          // 光泽效果
+          &::after {
+            content: '';
+            position: absolute;
+            top: -50%;
+            left: -50%;
+            width: 200%;
+            height: 200%;
+            background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+            transform: translateX(-100%);
+            transition: transform 0.6s ease;
           }
           
-          &.pending {
-            background: #ff9800;
-            color: white;
+          &:hover::after {
+            transform: translateX(100%);
           }
           
-          &.rejected {
-            background: #f44336;
-            color: white;
+          &.has-files {
+            background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 100%);
+            border-color: #c7d2fe;
+            color: #4f46e5;
+            box-shadow: 0 2px 8px rgba(79, 70, 229, 0.15);
           }
-        }
-
-        // 阶段状态样式
-        &.stage-approved {
-          border-color: #4caf50;
-          background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
           
-          .type-icon {
-            background: rgba(76, 175, 80, 0.1);
-            .icon { color: #4caf50; }
+          &.confirmed {
+            background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
+            border-color: #6ee7b7;
+            color: #059669;
+            box-shadow: 0 2px 8px rgba(5, 150, 105, 0.15);
           }
           
-          .type-name { color: #2e7d32; }
-        }
-        
-        &.stage-pending {
-          border-color: #ff9800;
-          background: linear-gradient(135deg, #fff3e0, #ffe0b2);
-          
-          .type-icon {
-            background: rgba(255, 152, 0, 0.1);
-            .icon { color: #ff9800; }
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
           }
           
-          .type-name { color: #f57c00; }
-        }
-        
-        &.stage-rejected {
-          border-color: #f44336;
-          background: linear-gradient(135deg, #ffebee, #ffcdd2);
-          
-          .type-icon {
-            background: rgba(244, 67, 54, 0.1);
-            .icon { color: #f44336; }
+          .stage-name {
+            font-weight: 600;
+            position: relative;
+            z-index: 1;
           }
           
-          .type-name { color: #c62828; }
-        }
-        
-        &.stage-active {
-          border-color: #f44336;
-          border-width: 3px;
-          box-shadow: 0 0 0 3px rgba(244, 67, 54, 0.1);
-          
-          .type-icon {
-            background: rgba(244, 67, 54, 0.1);
-            .icon { color: #f44336; }
+          .file-count {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            min-width: 24px;
+            height: 24px;
+            padding: 0 8px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border-radius: 12px;
+            font-size: 12px;
+            font-weight: 700;
+            box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
+            position: relative;
+            z-index: 1;
           }
           
-          .type-name { color: #c62828; font-weight: 800; }
-        }
-        
-        &.stage-default:hover {
-          border-color: #adb5bd;
-          transform: translateY(-2px);
-          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-
-          .type-icon {
-            transform: scale(1.08);
-          }
-        }
-
-        // 不同类型的配色
-        &[data-color="primary"].active {
-          background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
-          border-color: transparent;
-          box-shadow: 0 6px 20px rgba(79, 172, 254, 0.4);
-
-          .type-icon {
-            background: rgba(255, 255, 255, 0.25);
-            .icon { color: white; }
-          }
-
-          .type-content {
-            .type-name, .type-description { color: white; }
-          }
-
-          .file-count-badge {
-            background: white;
-            color: #4facfe;
+          .confirmed-icon {
+            font-size: 18px;
+            font-weight: 700;
+            position: relative;
+            z-index: 1;
           }
         }
-
-        &[data-color="secondary"].active {
-          background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
-          border-color: transparent;
-          box-shadow: 0 6px 20px rgba(250, 112, 154, 0.4);
-
-          .type-icon {
-            background: rgba(255, 255, 255, 0.25);
-            .icon { color: white; }
-          }
-
-          .type-content {
-            .type-name, .type-description { color: white; }
-          }
-
-          .file-count-badge {
-            background: white;
-            color: #fa709a;
-          }
+      }
+      
+      // 展开/收起图标
+      .expand-icon {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 36px;
+        height: 36px;
+        flex-shrink: 0;
+        background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
+        border-radius: 50%;
+        color: #64748b;
+        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+        
+        &.expanded {
+          transform: rotate(180deg);
+          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+          color: white;
         }
-
-        &[data-color="tertiary"].active {
-          background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
-          border-color: transparent;
-          box-shadow: 0 6px 20px rgba(168, 237, 234, 0.4);
-
-          .type-icon {
-            background: rgba(255, 255, 255, 0.25);
-            .icon { color: white; }
-          }
-
-          .type-content {
-            .type-name, .type-description { color: white; }
-          }
-
-          .file-count-badge {
-            background: white;
-            color: #a8edea;
-          }
+        
+        &:hover {
+          box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
         }
-
-        &[data-color="success"].active {
-          background: linear-gradient(135deg, #81fbb8 0%, #28c76f 100%);
-          border-color: transparent;
-          box-shadow: 0 6px 20px rgba(129, 251, 184, 0.4);
-
-          .type-icon {
-            background: rgba(255, 255, 255, 0.25);
-            .icon { color: white; }
-          }
-
-          .type-content {
-            .type-name, .type-description { color: white; }
-          }
-
-          .file-count-badge {
-            background: white;
-            color: #28c76f;
-          }
+        
+        svg {
+          width: 20px;
+          height: 20px;
         }
       }
     }
-  }
-
-  // 文件上传和展示区域
-  .delivery-content-section {
-    display: flex;
-    flex-direction: column;
-    gap: 20px;
-
-    // 上传区域
-    .upload-section {
-      .upload-area {
-        background: white;
-        border-radius: 16px;
-        padding: 24px;
-        border: 2px dashed #dee2e6;
-        transition: all 0.3s ease;
-
-        .upload-content {
-          display: flex;
-          flex-direction: column;
-          align-items: center;
-          gap: 16px;
-          text-align: center;
-
-          .upload-icon {
-            width: 56px;
-            height: 56px;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            border-radius: 50%;
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-
-            .icon {
-              width: 32px;
-              height: 32px;
-            }
-          }
-
-          .upload-text {
-            h4 {
-              font-size: 16px;
-              font-weight: 700;
-              color: #212529;
-              margin: 0 0 6px;
-            }
-
-            p {
-              font-size: 13px;
-              color: #6c757d;
-              margin: 0;
-              line-height: 1.4;
-            }
-          }
-
-          .upload-button {
-            display: flex;
-            align-items: center;
-            gap: 8px;
-            padding: 12px 24px;
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-            border: none;
-            border-radius: 10px;
-            font-size: 14px;
-            font-weight: 600;
-            cursor: pointer;
-            transition: all 0.3s ease;
-            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
-
-            .icon {
-              width: 20px;
-              height: 20px;
-            }
-
-            &:hover:not(:disabled) {
-              transform: translateY(-2px);
-              box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
-            }
-
-            &:active:not(:disabled) {
-              transform: translateY(0);
-            }
-
-            &:disabled {
-              opacity: 0.6;
-              cursor: not-allowed;
-            }
-          }
-        }
-
-        .upload-progress {
-          margin-top: 16px;
-          display: flex;
-          flex-direction: column;
-          gap: 8px;
-
-          .progress-bar {
-            height: 8px;
-            background: #e9ecef;
-            border-radius: 10px;
-            overflow: hidden;
-
-            .progress-fill {
-              height: 100%;
-              background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
-              border-radius: 10px;
-              transition: width 0.3s ease;
-            }
-          }
-
-          .progress-text {
-            font-size: 12px;
-            color: #667eea;
-            font-weight: 600;
-            text-align: center;
-          }
+    
+    // 空间内容(展开时显示)
+    .space-content {
+      background: white;
+      border: 2px solid #e2e8f0;
+      border-top: none;
+      border-radius: 0 0 12px 12px;
+      padding: 24px;
+      margin-top: -12px;
+      animation: slideDown 0.3s ease-out;
+      
+      // 空间标题栏
+      .space-title-bar {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 24px;
+        padding: 20px;
+        background: linear-gradient(135deg, #fafbfc 0%, #f8fafc 100%);
+        border-radius: 12px;
+        border: 2px solid #e2e8f0;
+        
+        h3 {
+          margin: 0;
+          font-size: 22px;
+          font-weight: 700;
+          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+          -webkit-background-clip: text;
+          -webkit-text-fill-color: transparent;
+          background-clip: text;
         }
-
-        &.uploading {
-          border-color: #667eea;
-          background: #f8f9ff;
+        
+        .completion-badge {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          min-width: 70px;
+          height: 40px;
+          padding: 0 20px;
+          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+          color: white;
+          border-radius: 20px;
+          font-size: 18px;
+          font-weight: 800;
+          box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
+          position: relative;
+          overflow: hidden;
+          
+          // 闪光效果
+          &::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: -100%;
+            width: 100%;
+            height: 100%;
+            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+            animation: shimmer 2s infinite;
+          }
         }
       }
-    }
-
-    // 文件展示区域
-    .files-display-section {
-      .files-header {
-        margin-bottom: 16px;
-
-        h4 {
-          font-size: 16px;
-          font-weight: 700;
-          color: #212529;
-          margin: 0;
-        }
+      
+      @keyframes shimmer {
+        0% { left: -100%; }
+        100% { left: 100%; }
       }
-
-      .files-grid {
+      
+      // 4个阶段卡片网格
+      .stages-grid {
         display: grid;
-        grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
-        gap: 14px;
-
-        .file-card {
-          background: white;
-          border-radius: 14px;
+        grid-template-columns: repeat(4, 1fr);
+        gap: 20px;
+        margin-bottom: 24px;
+        
+        .stage-card {
+          position: relative;
+          background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+          border: 3px solid #e2e8f0;
+          border-radius: 20px;
+          padding: 20px;
+          cursor: pointer;
+          transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
           overflow: hidden;
-          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
-          transition: all 0.3s ease;
-
+          
+          // 柔化边缘效果
+          box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+          
+          // 背景装饰
+          &::before {
+            content: '';
+            position: absolute;
+            top: -50%;
+            right: -50%;
+            width: 200%;
+            height: 200%;
+            background: radial-gradient(circle, rgba(102, 126, 234, 0.05) 0%, transparent 70%);
+            opacity: 0;
+            transition: opacity 0.4s ease;
+          }
+          
           &:hover {
-            transform: translateY(-4px);
-            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
+            transform: translateY(-6px) scale(1.02);
+            box-shadow: 0 12px 32px rgba(102, 126, 234, 0.2);
+            border-color: #667eea;
+            
+            &::before {
+              opacity: 1;
+            }
           }
-
-          .file-preview {
-            position: relative;
-            width: 100%;
-            aspect-ratio: 1;
-            overflow: hidden;
-            background: #f8f9fa;
-            cursor: pointer;
-
-            .preview-image {
-              width: 100%;
-              height: 100%;
-              object-fit: cover;
+          
+          // 不同阶段的渐变色(更鲜艳)
+          &[data-stage="white_model"] {
+            &.has-files {
+              background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 50%, #a5b4fc 100%);
+              border-color: #818cf8;
+              box-shadow: 0 4px 16px rgba(129, 140, 248, 0.3);
+              
+              &:hover {
+                box-shadow: 0 12px 32px rgba(129, 140, 248, 0.4);
+              }
             }
-
-            .file-type-icon {
-              width: 100%;
-              height: 100%;
-              display: flex;
-              align-items: center;
-              justify-content: center;
-
-              .icon {
-                width: 48px;
-                height: 48px;
-                color: #adb5bd;
+          }
+          
+          &[data-stage="soft_decor"] {
+            &.has-files {
+              background: linear-gradient(135deg, #fce7f3 0%, #fbcfe8 50%, #f9a8d4 100%);
+              border-color: #f472b6;
+              box-shadow: 0 4px 16px rgba(244, 114, 182, 0.3);
+              
+              &:hover {
+                box-shadow: 0 12px 32px rgba(244, 114, 182, 0.4);
               }
             }
-
-            .file-overlay {
-              position: absolute;
-              top: 0;
-              left: 0;
-              right: 0;
-              bottom: 0;
-              background: rgba(0, 0, 0, 0.6);
-              display: flex;
-              align-items: center;
-              justify-content: center;
-              opacity: 0;
-              transition: opacity 0.3s ease;
-
-              .icon {
-                width: 32px;
-                height: 32px;
+          }
+          
+          &[data-stage="rendering"] {
+            &.has-files {
+              background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 50%, #93c5fd 100%);
+              border-color: #60a5fa;
+              box-shadow: 0 4px 16px rgba(96, 165, 250, 0.3);
+              
+              &:hover {
+                box-shadow: 0 12px 32px rgba(96, 165, 250, 0.4);
               }
             }
-
-            &:hover .file-overlay {
-              opacity: 1;
+          }
+          
+          &[data-stage="post_process"] {
+            &.has-files {
+              background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 50%, #6ee7b7 100%);
+              border-color: #34d399;
+              box-shadow: 0 4px 16px rgba(52, 211, 153, 0.3);
+              
+              &:hover {
+                box-shadow: 0 12px 32px rgba(52, 211, 153, 0.4);
+              }
             }
           }
-
-          .file-info {
-            padding: 12px;
-
-            .file-name {
-              font-size: 13px;
+          
+          .stage-card-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            margin-bottom: 12px;
+            
+            h4 {
+              margin: 0;
+              font-size: 17px;
               font-weight: 600;
-              color: #212529;
-              margin-bottom: 6px;
-              overflow: hidden;
-              text-overflow: ellipsis;
-              white-space: nowrap;
+              color: #1e293b;
             }
-
-            .file-meta {
+            
+            .image-count-badge {
               display: flex;
               align-items: center;
-              gap: 8px;
-              font-size: 11px;
-              color: #868e96;
-              margin-bottom: 4px;
-
-              .file-size {
-                &::after {
-                  content: "•";
-                  margin-left: 8px;
-                }
+              justify-content: center;
+              min-width: 32px;
+              height: 32px;
+              padding: 0 10px;
+              background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+              color: white;
+              border-radius: 16px;
+              font-size: 14px;
+              font-weight: 800;
+              box-shadow: 0 3px 12px rgba(102, 126, 234, 0.4);
+              position: relative;
+              overflow: hidden;
+              
+              // 脉冲动画
+              &::after {
+                content: '';
+                position: absolute;
+                inset: -50%;
+                background: radial-gradient(circle, rgba(255, 255, 255, 0.4) 0%, transparent 70%);
+                animation: pulse 2s ease-in-out infinite;
               }
             }
-
-            .file-uploader {
-              font-size: 11px;
-              color: #adb5bd;
-            }
           }
-
-          .file-actions {
+          
+          @keyframes pulse {
+            0%, 100% { transform: scale(0.8); opacity: 0; }
+            50% { transform: scale(1.2); opacity: 1; }
+          }
+          
+          .stage-card-body {
+            width: 100%;
+            aspect-ratio: 1;
+            border-radius: 12px;
+            overflow: hidden;
+            background: white;
             display: flex;
-            gap: 6px;
-            padding: 0 12px 12px;
-
-            .action-button {
-              flex: 1;
+            align-items: center;
+            justify-content: center;
+            
+            .stage-preview-img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+            
+            .stage-placeholder {
               display: flex;
               align-items: center;
               justify-content: center;
-              padding: 8px;
-              background: #f8f9fa;
-              border: none;
-              border-radius: 8px;
-              cursor: pointer;
-              transition: all 0.2s ease;
-
-              .icon {
-                width: 18px;
-                height: 18px;
-                color: #6c757d;
-              }
-
-              &:hover {
-                background: #e9ecef;
-
-                .icon {
-                  color: #495057;
-                }
-              }
-
-              &.preview:hover {
-                background: #e3f2fd;
-                .icon { color: #2196f3; }
-              }
-
-              &.download:hover {
-                background: #e8f5e9;
-                .icon { color: #4caf50; }
+              width: 100%;
+              height: 100%;
+              color: #cbd5e1;
+              
+              svg {
+                width: 64px;
+                height: 64px;
               }
-
-              &.delete:hover {
-                background: #ffebee;
-                .icon { color: #f44336; }
+            }
+            
+            .stage-empty-state {
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              width: 100%;
+              height: 100%;
+              color: #94a3b8;
+              text-align: center;
+              
+              p {
+                margin: 0;
+                font-size: 14px;
+                line-height: 1.6;
               }
             }
           }
         }
       }
-
-      // 空状态
-      .empty-state {
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        justify-content: center;
-        padding: 60px 20px;
-        text-align: center;
-        background: white;
-        border-radius: 16px;
-
-        .empty-icon {
-          width: 80px;
-          height: 80px;
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          border-radius: 50%;
-          background: #f8f9fa;
-          margin-bottom: 20px;
-
-          .icon {
-            width: 48px;
-            height: 48px;
-            color: #adb5bd;
-          }
-        }
-
-        h4 {
-          font-size: 18px;
-          font-weight: 700;
-          color: #495057;
-          margin: 0 0 8px;
-        }
-
-        p {
-          font-size: 14px;
-          color: #868e96;
-          margin: 0 0 24px;
-          line-height: 1.5;
-        }
-
-        .upload-button-primary {
+      
+      // 选中阶段的文件详情
+      .stage-files-detail {
+        background: #f8fafc;
+        border: 2px solid #e2e8f0;
+        border-radius: 12px;
+        padding: 20px;
+        margin-bottom: 20px;
+        
+        .detail-header {
           display: flex;
           align-items: center;
-          gap: 8px;
-          padding: 14px 28px;
-          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-          color: white;
-          border: none;
-          border-radius: 12px;
-          font-size: 15px;
-          font-weight: 600;
-          cursor: pointer;
-          transition: all 0.3s ease;
-          box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
-
-          .icon {
-            width: 20px;
-            height: 20px;
-          }
-
-          &:hover {
-            transform: translateY(-2px);
-            box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
-          }
-
-          &:active {
-            transform: translateY(0);
-          }
-        }
-      }
-    }
-  }
-
-  // 未选择场景状态
-  .no-selection-state {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    padding: 80px 20px;
-    text-align: center;
-    background: white;
-    border-radius: 16px;
-    margin-top: 20px;
-
-    .state-icon {
-      width: 100px;
-      height: 100px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      border-radius: 50%;
-      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-      margin-bottom: 24px;
-
-      .icon {
-        width: 56px;
-        height: 56px;
-        color: white;
-      }
-    }
-
-    h3 {
-      font-size: 20px;
-      font-weight: 700;
-      color: #212529;
-      margin: 0 0 10px;
-    }
-
-    p {
-      font-size: 15px;
-      color: #6c757d;
-      margin: 0;
-      line-height: 1.5;
-    }
-  }
-
-  // 没有场景状态
-  .no-products-state {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    padding: 80px 20px;
-    text-align: center;
-    background: white;
-    border-radius: 16px;
-
-    .state-icon {
-      width: 100px;
-      height: 100px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      border-radius: 50%;
-      background: #f8f9fa;
-      margin-bottom: 24px;
-
-      .icon {
-        width: 56px;
-        height: 56px;
-        color: #adb5bd;
-      }
-    }
-
-    h3 {
-      font-size: 20px;
-      font-weight: 700;
-      color: #495057;
-      margin: 0 0 10px;
-    }
-
-    p {
-      font-size: 15px;
-      color: #868e96;
-      margin: 0;
-      line-height: 1.5;
-    }
-  }
-}
-
-// 移动端响应式
-@media (max-width: 768px) {
-  .stage-delivery-container {
-    padding: 12px;
-
-    .product-tabs-section {
-      .product-tabs {
-        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
-        gap: 8px;
-
-        .product-tab {
-          padding: 12px 10px;
-
-          .product-icon {
-            width: 32px;
-            height: 32px;
-
-            .icon {
-              width: 18px;
-              height: 18px;
-            }
-          }
-
-          .product-name {
-            font-size: 12px;
+          justify-content: space-between;
+          margin-bottom: 16px;
+          
+          h4 {
+            margin: 0;
+            font-size: 16px;
+            font-weight: 600;
+            color: #475569;
           }
-        }
-      }
-    }
-
-    .delivery-types-section {
-      .delivery-types-tabs {
-        grid-template-columns: repeat(2, 1fr);
-        gap: 8px;
-
-        .delivery-type-tab {
-          padding: 14px 8px;
-
-          .type-icon {
-            width: 40px;
-            height: 40px;
-
-            .icon {
-              width: 22px;
-              height: 22px;
+          
+          .upload-detail-btn {
+            display: flex;
+            align-items: center;
+            gap: 6px;
+            padding: 8px 16px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            border-radius: 8px;
+            font-size: 14px;
+            font-weight: 600;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+            
+            &:hover:not(:disabled) {
+              transform: translateY(-2px);
+              box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
             }
-          }
-
-          .type-content {
-            .type-name {
-              font-size: 13px;
+            
+            &:disabled {
+              opacity: 0.6;
+              cursor: not-allowed;
             }
-
-            .type-description {
-              font-size: 10px;
+            
+            svg {
+              width: 18px;
+              height: 18px;
             }
           }
         }
-      }
-    }
-
-    .delivery-content-section {
-      gap: 16px;
-
-      .upload-section {
-        .upload-area {
-          padding: 20px;
-
-          .upload-content {
-            gap: 12px;
-
-            .upload-icon {
-              width: 48px;
-              height: 48px;
-
-              .icon {
-                width: 28px;
-                height: 28px;
+        
+        .files-detail-grid {
+          display: grid;
+          grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+          gap: 12px;
+          
+          .file-detail-item {
+            display: flex;
+            flex-direction: column;
+            gap: 6px;
+            
+            .file-detail-preview {
+              position: relative;
+              width: 100%;
+              aspect-ratio: 1;
+              background: white;
+              border: 2px solid #e2e8f0;
+              border-radius: 10px;
+              overflow: hidden;
+              cursor: pointer;
+              transition: all 0.3s ease;
+              
+              &:hover {
+                border-color: #667eea;
+                box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
+                transform: translateY(-2px);
+                
+                .delete-detail-btn {
+                  opacity: 1;
+                }
               }
-            }
-
-            .upload-text {
-              h4 {
-                font-size: 15px;
+              
+              .file-detail-img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
               }
-
-              p {
-                font-size: 12px;
+              
+              .file-detail-placeholder {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                width: 100%;
+                height: 100%;
+                color: #cbd5e1;
+                
+                svg {
+                  width: 48px;
+                  height: 48px;
+                }
               }
-            }
-
-            .upload-button {
-              padding: 10px 20px;
-              font-size: 13px;
-
-              .icon {
-                width: 18px;
-                height: 18px;
+              
+              .delete-detail-btn {
+                position: absolute;
+                top: 6px;
+                right: 6px;
+                width: 28px;
+                height: 28px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                background: rgba(239, 68, 68, 0.9);
+                color: white;
+                border: none;
+                border-radius: 6px;
+                cursor: pointer;
+                opacity: 0;
+                transition: all 0.3s ease;
+                
+                &:hover {
+                  background: rgba(220, 38, 38, 1);
+                  transform: scale(1.1);
+                }
+                
+                svg {
+                  width: 16px;
+                  height: 16px;
+                }
               }
             }
+            
+            .file-detail-name {
+              font-size: 12px;
+              color: #64748b;
+              text-align: center;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+              padding: 0 4px;
+            }
           }
         }
       }
-
-      .files-display-section {
-        .files-grid {
-          grid-template-columns: repeat(2, 1fr);
+      
+      // 空间确认按钮区域
+      .space-confirm-section {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 12px;
+        padding: 20px;
+        background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+        border: 2px solid #e2e8f0;
+        border-radius: 12px;
+        
+        .space-confirm-btn,
+        .space-reconfirm-btn {
+          display: flex;
+          align-items: center;
           gap: 12px;
+          padding: 18px 48px;
+          background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+          color: white;
+          border: none;
+          border-radius: 16px;
+          font-size: 18px;
+          font-weight: 700;
+          cursor: pointer;
+          transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+          box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
+          position: relative;
+          overflow: hidden;
+          
+          // 光泽效果
+          &::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: -100%;
+            width: 100%;
+            height: 100%;
+            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+            transition: left 0.5s ease;
+          }
+          
+          &:hover:not(:disabled) {
+            transform: translateY(-4px) scale(1.05);
+            box-shadow: 0 12px 32px rgba(16, 185, 129, 0.5);
+            
+            &::before {
+              left: 100%;
+            }
+          }
+          
+          &:active:not(:disabled) {
+            transform: translateY(-2px) scale(1.02);
+          }
+          
+          &:disabled {
+            opacity: 0.5;
+            cursor: not-allowed;
+          }
+          
+          svg {
+            width: 24px;
+            height: 24px;
+            filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
+          }
         }
-      }
-    }
-
-    .no-selection-state,
-    .no-products-state {
-      padding: 60px 16px;
-
-      .state-icon {
-        width: 80px;
-        height: 80px;
-
-        .icon {
-          width: 48px;
-          height: 48px;
-        }
-      }
-
-      h3 {
-        font-size: 18px;
-      }
-
-      p {
-        font-size: 14px;
-      }
-    }
-  }
-
-  // 操作按钮(参考售后归档样式)
-  .action-buttons {
-    margin-top: 24px;
-    padding: 16px;
-
-    .btn {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      gap: 12px;
-      padding: 18px 48px;
-      border-radius: 14px;
-      font-size: 17px;
-      font-weight: 700;
-      cursor: pointer;
-      transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
-      border: none;
-      outline: none;
-      min-height: 58px;
-      width: 100%;
-      max-width: 400px;
-      position: relative;
-      overflow: hidden;
-      letter-spacing: 0.5px;
-      text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
-
-      .icon {
-        width: 22px;
-        height: 22px;
-        filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.15));
-        transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
-      }
-
-      // 多层按钮涟漪效果
-      &::before {
-        content: '';
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        width: 0;
-        height: 0;
-        border-radius: 50%;
-        background: radial-gradient(circle, rgba(255, 255, 255, 0.6) 0%, transparent 70%);
-        transform: translate(-50%, -50%);
-        transition: width 0.7s cubic-bezier(0.4, 0, 0.2, 1), height 0.7s cubic-bezier(0.4, 0, 0.2, 1);
-      }
-
-      // 光泽层
-      &::after {
-        content: '';
-        position: absolute;
-        top: -50%;
-        left: -50%;
-        width: 200%;
-        height: 200%;
-        background: linear-gradient(
-          120deg,
-          transparent 0%,
-          transparent 40%,
-          rgba(255, 255, 255, 0.15) 50%,
-          transparent 60%,
-          transparent 100%
-        );
-        transform: translateX(-100%);
-        transition: transform 0.6s ease;
-      }
-
-      &:active:not(:disabled)::before {
-        width: 400px;
-        height: 400px;
-        transition: width 0.3s, height 0.3s;
-      }
-
-      &:hover:not(:disabled)::after {
-        transform: translateX(100%);
-      }
-
-      // 提交审批按钮(参考售后归档样式)
-      &.btn-primary {
-        background: var(--primary-color, #3880ff);
-        color: white;
-
-        &:hover:not(:disabled) {
-          background: #2f6ce5;
-          transform: translateY(-2px);
-        }
-
-        &:active:not(:disabled) {
-          transform: translateY(0);
+        
+        .space-reconfirm-btn {
+          background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
+          box-shadow: 0 6px 20px rgba(245, 158, 11, 0.4);
+          
+          &:hover:not(:disabled) {
+            box-shadow: 0 12px 32px rgba(245, 158, 11, 0.5);
+          }
         }
-      }
-      
-      &.btn-block {
-        width: 100%;
-      }
-      
-      &.btn-large {
-        padding: 16px 24px;
-        font-size: 15px;
+        
+        .confirm-hint {
+          margin: 0;
+          font-size: 13px;
+          color: #94a3b8;
+          text-align: center;
         }
-
-        &:disabled {
-        opacity: 0.5;
-          cursor: not-allowed;
-        pointer-events: none;
-        box-shadow: none;
-        transform: none;
-      }
-
-      .icon-spin {
-        animation: spin 1s linear infinite;
-      }
-    }
-
-    // 移动端优化
-    @media (max-width: 768px) {
-      padding: 24px 16px;
-      margin: 32px auto 24px;
-      border-radius: 16px;
-      
-      .btn {
-        max-width: 100%;
-        width: 100%;
-        padding: 16px 36px;
-        font-size: 16px;
-        min-height: 54px;
-
-        .icon {
-          width: 20px;
-          height: 20px;
+        
+        .space-confirmed-info {
+          display: flex;
+          align-items: center;
+          gap: 16px;
+          padding: 20px 32px;
+          background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 50%, #6ee7b7 100%);
+          border: 3px solid #34d399;
+          border-radius: 16px;
+          color: #059669;
+          box-shadow: 0 6px 20px rgba(52, 211, 153, 0.3);
+          position: relative;
+          overflow: hidden;
+          
+          // 成功光环效果
+          &::before {
+            content: '';
+            position: absolute;
+            inset: -50%;
+            background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, transparent 70%);
+            animation: successGlow 3s ease-in-out infinite;
+          }
+          
+          svg {
+            width: 28px;
+            height: 28px;
+            flex-shrink: 0;
+            filter: drop-shadow(0 2px 6px rgba(5, 150, 105, 0.4));
+            position: relative;
+            z-index: 1;
+          }
+          
+          .confirmed-text {
+            display: flex;
+            flex-direction: column;
+            gap: 6px;
+            position: relative;
+            z-index: 1;
+            
+            .confirmed-label {
+              font-size: 18px;
+              font-weight: 700;
+              text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+            }
+            
+            .confirmed-detail {
+              font-size: 14px;
+              color: #047857;
+              font-weight: 500;
+            }
+          }
         }
-      }
-    }
-
-    @media (max-width: 480px) {
-      padding: 20px 12px;
-      margin: 24px auto 20px;
-      
-      .btn {
-        padding: 14px 28px;
-        font-size: 15px;
-        min-height: 50px;
-        gap: 10px;
-
-        .icon {
-          width: 18px;
-          height: 18px;
+        
+        @keyframes successGlow {
+          0%, 100% { transform: scale(1); opacity: 0.3; }
+          50% { transform: scale(1.5); opacity: 0.6; }
         }
       }
     }
   }
 
-  @keyframes spin {
-    from { transform: rotate(0deg); }
-    to { transform: rotate(360deg); }
-  }
-
-  .button-tip {
-    margin-top: 16px;
-    padding: 12px 20px;
-    text-align: center;
-    background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
-    border-left: 4px solid #ffc107;
-    border-radius: 8px;
-    color: #856404;
-    font-size: 14px;
-    box-shadow: 0 2px 8px rgba(255, 193, 7, 0.15);
-    
-    &::before {
-      content: '💡 ';
-    }
-  }
-}
-
-// ✨ 审批状态样式(紧凑单行样式)
-.file-approval-status {
-  margin-top: 8px;
-  padding: 6px 10px;
-  background: #f8f9fa;
-  border-radius: 6px;
-  font-size: 12px;
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  border-left: 3px solid #e0e0e0;
-
-  .status-icon {
-    font-size: 14px;
-    line-height: 1;
-  }
-
-  .status-text {
-    font-weight: 600;
-    color: #495057;
-  }
-
-  .status-by {
-    color: #6c757d;
-    font-size: 11px;
-  }
-
-  .status-reason {
-    color: #6c757d;
-    font-size: 11px;
-    font-style: italic;
-  }
-
-  &.status-pending {
-    background: rgba(56, 128, 255, 0.1);
-    border-left-color: #3880ff;
-  }
-  
-  &.status-approved {
-    background: rgba(45, 211, 111, 0.1);
-    border-left-color: #2dd36f;
-  }
-  
-  &.status-rejected {
-    background: rgba(235, 68, 90, 0.1);
-    border-left-color: #eb445a;
-  }
-}
-
-// 未验证文件徽章
-.unverified-badge {
-  display: inline-block;
-  padding: 2px 8px;
-  background: #ff9f43;
-  color: white;
-  border-radius: 10px;
-  font-size: 11px;
-  font-weight: 600;
-  margin-left: 8px;
-  animation: pulse 2s ease-in-out infinite;
-}
-
-@keyframes pulse {
-  0%, 100% {
-    opacity: 1;
-  }
-  50% {
-    opacity: 0.7;
-  }
-}
-
-// 类型徽章容器
-.type-badges {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-
-// ✨ 文件卡片增强样式(参考售后归档)
-.file-card {
-  transition: all 0.3s ease;
-  
-  &.has-approval-issue {
-    border: 2px solid #eb445a;
-    animation: shake 0.5s ease-in-out;
-  }
-  
-  .file-preview {
-    position: relative;
+  // 没有场景时的提示
+  .no-products-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 100px 20px;
+    background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+    border-radius: 20px;
+    border: 3px dashed #e2e8f0;
     
-    // 审批状态角标
-    .approval-corner-badge {
-      position: absolute;
-      top: 8px;
-      right: 8px;
-      width: 32px;
-      height: 32px;
-      border-radius: 50%;
+    .state-icon {
       display: flex;
       align-items: center;
       justify-content: center;
-      font-size: 16px;
-      background: rgba(255, 255, 255, 0.95);
-      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
-      z-index: 2;
-      
-      &.badge-unverified {
-        background: linear-gradient(135deg, #fff3cd, #ffc107);
-      }
-      
-      &.badge-pending {
-        background: linear-gradient(135deg, #d1ecf1, #17a2b8);
-      }
-      
-      &.badge-approved {
-        background: linear-gradient(135deg, #d4edda, #28a745);
-      }
+      width: 120px;
+      height: 120px;
+      background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
+      border-radius: 50%;
+      color: #94a3b8;
+      margin-bottom: 24px;
+      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
       
-      &.badge-rejected {
-        background: linear-gradient(135deg, #f8d7da, #dc3545);
+      svg {
+        width: 72px;
+        height: 72px;
       }
     }
     
-    // 删除按钮(参考售后归档样式)
-    .delete-btn {
-      position: absolute;
-      top: 8px;
-      left: 8px;
-      width: 32px;
-      height: 32px;
-      border: none;
-      background: rgba(235, 68, 90, 0.9);
-      border-radius: 50%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      cursor: pointer;
-      transition: all 0.3s;
-      padding: 0;
-      z-index: 3;
-      opacity: 0;
-      
-      &:hover {
-        background: #eb445a;
-        transform: scale(1.1);
-      }
-      
-      .icon {
-        width: 18px;
-        height: 18px;
-        color: white;
-      }
+    h3 {
+      margin: 0 0 12px 0;
+      font-size: 24px;
+      font-weight: 700;
+      background: linear-gradient(135deg, #475569 0%, #64748b 100%);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+      background-clip: text;
     }
-  }
-  
-  &:hover {
-    .delete-btn {
-      opacity: 1;
+    
+    p {
+      margin: 0;
+      font-size: 16px;
+      color: #94a3b8;
+      font-weight: 500;
     }
   }
-}
-
-@keyframes shake {
-  0%, 100% {
-    transform: translateX(0);
-  }
-  25% {
-    transform: translateX(-5px);
-  }
-  75% {
-    transform: translateX(5px);
-  }
-}
 
-@media (max-width: 480px) {
-  .stage-delivery-container {
-    .product-tabs-section {
-      .product-tabs {
-        grid-template-columns: repeat(2, 1fr);
-      }
+  // 动画
+  @keyframes slideDown {
+    from {
+      opacity: 0;
+      transform: translateY(-10px);
     }
-
-    .delivery-content-section {
-      .files-display-section {
-        .files-grid {
-          grid-template-columns: repeat(2, 1fr);
-        }
-      }
+    to {
+      opacity: 1;
+      transform: translateY(0);
     }
   }
 }
-
-// 🎨 按钮加载动画
-.icon-spin {
-  animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
-}

+ 1872 - 0
src/modules/project/pages/project-detail/stages/stage-delivery.component.scss.backup

@@ -0,0 +1,1872 @@
+.stage-delivery-container {
+  padding: 16px;
+  background-color: #f8f9fa;
+  min-height: 100vh;
+  
+  // ============ 阶段锁定提示 ============
+  .stage-locked-notice {
+    background: linear-gradient(135deg, #fff3e0, #ffe0b2);
+    border: 2px solid #ff9800;
+    border-radius: 12px;
+    padding: 20px;
+    margin-bottom: 20px;
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    animation: slideDown 0.3s ease-out;
+    
+    .lock-icon {
+      font-size: 48px;
+      flex-shrink: 0;
+    }
+    
+    .lock-content {
+      flex: 1;
+      
+      h4 {
+        margin: 0 0 8px;
+        font-size: 18px;
+        font-weight: 600;
+        color: #f57c00;
+      }
+      
+      p {
+        margin: 0;
+        font-size: 14px;
+        line-height: 1.6;
+        color: #e65100;
+        
+        strong {
+          font-weight: 600;
+          color: #d84315;
+        }
+      }
+    }
+  }
+
+  // ============ 上传区域锁定状态 ============
+  .upload-section.disabled {
+    opacity: 0.5;
+    pointer-events: none;
+    
+    .upload-area.locked {
+      background: #f5f5f5;
+      border-color: #e0e0e0;
+      cursor: not-allowed;
+    }
+  }
+  
+  // ============ 审批状态横幅样式(与订单分配阶段保持一致)============
+  .approval-status-banner {
+    padding: 16px 20px;
+    border-radius: 12px;  // ⭐ 增加圆角,更柔和
+    margin-bottom: 20px;
+    display: flex;
+    align-items: flex-start;
+    gap: 16px;
+    animation: slideDown 0.3s ease-out;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);  // ⭐ 添加阴影,更立体
+
+    .status-icon {
+      font-size: 32px;
+      flex-shrink: 0;
+    }
+
+    .status-content {
+      flex: 1;
+
+      h4 {
+        margin: 0 0 8px;
+        font-size: 18px;
+        font-weight: 600;
+      }
+
+      p {
+        margin: 0;
+        font-size: 14px;
+        line-height: 1.5;
+
+      strong {
+        font-weight: 600;
+        }
+      }
+
+      .btn-resubmit {
+        margin-top: 12px;
+        padding: 8px 20px;
+        background: white;
+        border: 2px solid currentColor;
+        border-radius: 6px;
+        font-size: 14px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: all 0.3s;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+        }
+      }
+    }
+
+    &.pending {
+      background: linear-gradient(135deg, #fff3e0, #ffe0b2);
+      border: 2px solid #ff9800;
+      color: #f57c00;
+
+      .btn-resubmit {
+        color: #f57c00;
+        border-color: #f57c00;
+
+        &:hover {
+          background: #f57c00;
+          color: white;
+        }
+      }
+    }
+
+    &.approved {
+      background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
+      border: 2px solid #4caf50;
+      color: #2e7d32;
+
+      .btn-resubmit {
+        color: #2e7d32;
+        border-color: #2e7d32;
+
+        &:hover {
+          background: #2e7d32;
+          color: white;
+        }
+      }
+    }
+
+    &.rejected {
+      background: linear-gradient(135deg, #ffebee, #ffcdd2);
+      border: 2px solid #f44336;
+      color: #c62828;
+
+      .btn-resubmit {
+        color: #c62828;
+        border-color: #c62828;
+
+        &:hover {
+          background: #c62828;
+          color: white;
+        }
+      }
+    }
+  }
+
+  // ============ 组长审批操作条样式(居中显示)============
+  .leader-approval-bar {
+    margin: 24px 0;
+    padding: 20px;
+    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+    border-radius: 16px;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+    animation: slideDown 0.3s ease-out;
+
+    .approval-buttons-container {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      gap: 20px;
+      flex-wrap: wrap;
+
+      button {
+        position: relative;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 10px;
+        padding: 14px 32px;
+        font-size: 16px;
+        font-weight: 600;
+        border: none;
+        border-radius: 12px;
+        cursor: pointer;
+        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+        min-width: 160px;
+        overflow: hidden;
+
+        &::before {
+          content: '';
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          width: 0;
+          height: 0;
+          border-radius: 50%;
+          background: rgba(255, 255, 255, 0.3);
+          transform: translate(-50%, -50%);
+          transition: width 0.6s, height 0.6s;
+        }
+
+        &:hover::before {
+          width: 300px;
+          height: 300px;
+        }
+
+        .btn-icon {
+          font-size: 20px;
+          transition: transform 0.3s ease;
+        }
+
+        .btn-text {
+          position: relative;
+          z-index: 1;
+        }
+
+        &:hover .btn-icon {
+          transform: scale(1.2);
+        }
+
+        &:active {
+          transform: scale(0.95);
+        }
+
+        &:disabled {
+          opacity: 0.6;
+          cursor: not-allowed;
+          transform: none;
+
+          &:hover {
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+          }
+        }
+      }
+
+      .btn-approve {
+        background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
+        color: white;
+        box-shadow: 0 6px 20px rgba(76, 175, 80, 0.3);
+
+        &:hover:not(:disabled) {
+          background: linear-gradient(135deg, #66bb6a 0%, #43a047 100%);
+          box-shadow: 0 10px 30px rgba(76, 175, 80, 0.5);
+          transform: translateY(-3px) scale(1.02);
+        }
+
+        &:active:not(:disabled) {
+          background: linear-gradient(135deg, #388e3c 0%, #1b5e20 100%);
+        }
+      }
+
+      .btn-reject {
+        background: linear-gradient(135deg, #f44336 0%, #c62828 100%);
+        color: white;
+        box-shadow: 0 6px 20px rgba(244, 67, 54, 0.3);
+
+        &:hover:not(:disabled) {
+          background: linear-gradient(135deg, #ef5350 0%, #d32f2f 100%);
+          box-shadow: 0 10px 30px rgba(244, 67, 54, 0.5);
+          transform: translateY(-3px) scale(1.02);
+        }
+
+        &:active:not(:disabled) {
+          background: linear-gradient(135deg, #c62828 0%, #b71c1c 100%);
+        }
+      }
+    }
+
+    // 审批提示信息
+    .approval-hint {
+      text-align: center;
+      margin: 16px 0 0;
+      padding: 12px 20px;
+      background: rgba(255, 193, 7, 0.15);
+      border: 1px solid rgba(255, 193, 7, 0.3);
+      border-radius: 8px;
+      font-size: 14px;
+      color: #f57c00;
+      line-height: 1.6;
+      animation: pulse 2s ease-in-out infinite;
+    }
+  }
+
+  @keyframes slideDown {
+    from {
+      opacity: 0;
+      transform: translateY(-20px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0);
+    }
+  }
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.7;
+  }
+}
+
+// ============ 🧪 测试标记区域样式 ============
+.test-mark-section {
+  margin: 20px 0;
+  padding: 16px 20px;
+  background: linear-gradient(135deg, #fff9e6, #fff3cc);
+  border: 2px dashed #ffa500;
+  border-radius: 12px;
+  animation: slideDown 0.3s ease-out;
+  
+  .btn-test-mark {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 10px;
+    padding: 12px 24px;
+    background: linear-gradient(135deg, #ffa500, #ff8c00);
+    color: white;
+    border: none;
+    border-radius: 10px;
+    font-size: 15px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    box-shadow: 0 4px 12px rgba(255, 165, 0, 0.3);
+    width: 100%;
+    max-width: 300px;
+    margin: 0 auto;
+    
+    .test-icon {
+      font-size: 18px;
+    }
+    
+    .test-text {
+      position: relative;
+      z-index: 1;
+    }
+    
+    &:hover:not(:disabled) {
+      background: linear-gradient(135deg, #ff8c00, #ff7700);
+      transform: translateY(-2px);
+      box-shadow: 0 6px 20px rgba(255, 165, 0, 0.4);
+    }
+    
+    &:active:not(:disabled) {
+      transform: translateY(0);
+    }
+    
+    &:disabled {
+      opacity: 0.6;
+      cursor: not-allowed;
+      transform: none;
+    }
+  }
+  
+  .test-hint {
+    text-align: center;
+    margin: 12px 0 0;
+    font-size: 13px;
+    color: #cc8400;
+    line-height: 1.5;
+    
+    &::before {
+      content: '💡 ';
+    }
+  }
+}
+
+  // 审批消息流(紧凑样式)
+  .approval-messages-container {
+    background: white;
+    border-radius: 12px;
+    padding: 12px 16px;
+    margin-bottom: 16px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+
+    .messages-header {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 13px;
+      font-weight: 600;
+      color: #6c757d;
+      margin-bottom: 12px;
+      padding-bottom: 8px;
+      border-bottom: 1px solid #e9ecef;
+
+      .icon {
+        width: 16px;
+        height: 16px;
+        color: #6366f1;
+      }
+    }
+
+    .approval-messages {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+
+      .approval-message {
+        display: flex;
+        gap: 10px;
+        padding: 8px 12px;
+        border-radius: 8px;
+        background: #f8f9fa;
+        transition: all 0.2s ease;
+
+        &:hover {
+          background: #e9ecef;
+        }
+
+        &[data-status="approved"] {
+          background: #d1fae5;
+          border-left: 3px solid #10b981;
+        }
+
+        &[data-status="rejected"] {
+          background: #fee2e2;
+          border-left: 3px solid #ef4444;
+        }
+
+        &[data-status="pending"] {
+          background: #fef3c7;
+          border-left: 3px solid #f59e0b;
+        }
+
+        .message-icon {
+          flex-shrink: 0;
+          width: 24px;
+          height: 24px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+
+          .icon-emoji {
+            font-size: 18px;
+            line-height: 1;
+          }
+        }
+
+        .message-content {
+          flex: 1;
+          min-width: 0;
+
+          .message-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            margin-bottom: 4px;
+
+            .message-stage {
+              font-size: 12px;
+              font-weight: 600;
+              color: #374151;
+            }
+
+            .message-time {
+              font-size: 11px;
+              color: #9ca3af;
+            }
+          }
+
+          .message-body {
+            font-size: 13px;
+            color: #4b5563;
+            display: flex;
+            align-items: center;
+            flex-wrap: wrap;
+            gap: 4px;
+
+            .message-user {
+              font-weight: 600;
+              color: #1f2937;
+            }
+
+            .message-text {
+              color: #6b7280;
+            }
+
+            .message-approver {
+              color: #9ca3af;
+              font-size: 12px;
+            }
+          }
+
+          .message-comment {
+            margin-top: 6px;
+            padding: 6px 10px;
+            background: rgba(255, 255, 255, 0.6);
+            border-radius: 6px;
+            font-size: 12px;
+            color: #6b7280;
+            font-style: italic;
+          }
+        }
+      }
+    }
+  }
+
+  // 加载状态
+  .loading-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60px 20px;
+    text-align: center;
+
+    .spinner {
+      width: 40px;
+      height: 40px;
+      border: 4px solid #e9ecef;
+      border-top-color: var(--ion-color-primary, #3880ff);
+      border-radius: 50%;
+      animation: spin 0.8s linear infinite;
+    }
+
+    p {
+      margin-top: 16px;
+      color: #6c757d;
+      font-size: 14px;
+    }
+  }
+
+  @keyframes spin {
+    to { transform: rotate(360deg); }
+  }
+
+  // 通用标签标题
+  .section-label {
+    font-size: 13px;
+    font-weight: 600;
+    color: #6c757d;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    margin-bottom: 12px;
+    padding-left: 4px;
+  }
+
+  // 场景Product选择标签 (第一层)
+  .product-tabs-section {
+    margin-bottom: 20px;
+
+    .product-tabs {
+      display: grid;
+      grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+      gap: 10px;
+
+      .product-tab {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 6px;
+        padding: 14px 12px;
+        background: white;
+        border-radius: 12px;
+        border: 2px solid #e9ecef;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        position: relative;
+
+        .product-icon {
+          width: 36px;
+          height: 36px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-radius: 10px;
+          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+          color: white;
+          transition: transform 0.3s ease;
+
+          .icon {
+            width: 20px;
+            height: 20px;
+          }
+        }
+
+        .product-name {
+          font-size: 13px;
+          font-weight: 600;
+          color: #495057;
+          text-align: center;
+          line-height: 1.3;
+        }
+
+        .file-count-badge {
+          position: absolute;
+          top: 8px;
+          right: 8px;
+          background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+          color: white;
+          font-size: 11px;
+          font-weight: 700;
+          padding: 2px 6px;
+          border-radius: 10px;
+          min-width: 18px;
+          text-align: center;
+        }
+
+        &:hover {
+          border-color: var(--ion-color-primary, #3880ff);
+          transform: translateY(-2px);
+          box-shadow: 0 4px 12px rgba(56, 128, 255, 0.15);
+
+          .product-icon {
+            transform: scale(1.1);
+          }
+        }
+
+        &.active {
+          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+          border-color: transparent;
+          color: white;
+          box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
+
+          .product-icon {
+            background: rgba(255, 255, 255, 0.25);
+            transform: scale(1.15);
+          }
+
+          .product-name {
+            color: white;
+          }
+
+          .file-count-badge {
+            background: white;
+            color: #667eea;
+          }
+        }
+      }
+    }
+  }
+
+  // 交付类型选择标签 (第二层)
+  .delivery-types-section {
+    margin-bottom: 20px;
+
+    .delivery-types-tabs {
+      display: grid;
+      grid-template-columns: repeat(4, 1fr);
+      gap: 10px;
+
+      .delivery-type-tab {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 8px;
+        padding: 16px 10px;
+        background: white;
+        border-radius: 14px;
+        border: 2px solid #e9ecef;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        position: relative;
+
+        .type-icon {
+          width: 44px;
+          height: 44px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-radius: 12px;
+          background: #f8f9fa;
+          transition: all 0.3s ease;
+
+          .icon {
+            width: 24px;
+            height: 24px;
+            color: #6c757d;
+            transition: color 0.3s ease;
+          }
+        }
+
+        .type-content {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          gap: 2px;
+
+          .type-name {
+            font-size: 14px;
+            font-weight: 700;
+            color: #212529;
+            text-align: center;
+          }
+
+          .type-description {
+            font-size: 11px;
+            color: #868e96;
+            text-align: center;
+            line-height: 1.3;
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            line-clamp: 2;
+            -webkit-box-orient: vertical;
+            overflow: hidden;
+          }
+        }
+
+        .type-badges {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 6px;
+          justify-content: center;
+          margin-top: 4px;
+        }
+        
+        .file-count-badge {
+          background: #6c757d;
+          color: white;
+          font-size: 11px;
+          font-weight: 700;
+          padding: 3px 8px;
+          border-radius: 12px;
+          min-width: 20px;
+          text-align: center;
+        }
+        
+        .unverified-badge {
+          background: #ffc107;
+          color: #000;
+          font-size: 10px;
+          font-weight: 600;
+          padding: 3px 8px;
+          border-radius: 12px;
+        }
+        
+        .status-badge {
+          font-size: 11px;
+          font-weight: 600;
+          padding: 3px 8px;
+          border-radius: 12px;
+          white-space: nowrap;
+          
+          &.approved {
+            background: #4caf50;
+            color: white;
+          }
+          
+          &.pending {
+            background: #ff9800;
+            color: white;
+          }
+          
+          &.rejected {
+            background: #f44336;
+            color: white;
+          }
+        }
+
+        // 阶段状态样式
+        &.stage-approved {
+          border-color: #4caf50;
+          background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
+          
+          .type-icon {
+            background: rgba(76, 175, 80, 0.1);
+            .icon { color: #4caf50; }
+          }
+          
+          .type-name { color: #2e7d32; }
+        }
+        
+        &.stage-pending {
+          border-color: #ff9800;
+          background: linear-gradient(135deg, #fff3e0, #ffe0b2);
+          
+          .type-icon {
+            background: rgba(255, 152, 0, 0.1);
+            .icon { color: #ff9800; }
+          }
+          
+          .type-name { color: #f57c00; }
+        }
+        
+        &.stage-rejected {
+          border-color: #f44336;
+          background: linear-gradient(135deg, #ffebee, #ffcdd2);
+          
+          .type-icon {
+            background: rgba(244, 67, 54, 0.1);
+            .icon { color: #f44336; }
+          }
+          
+          .type-name { color: #c62828; }
+        }
+        
+        &.stage-active {
+          border-color: #f44336;
+          border-width: 3px;
+          box-shadow: 0 0 0 3px rgba(244, 67, 54, 0.1);
+          
+          .type-icon {
+            background: rgba(244, 67, 54, 0.1);
+            .icon { color: #f44336; }
+          }
+          
+          .type-name { color: #c62828; font-weight: 800; }
+        }
+        
+        &.stage-default:hover {
+          border-color: #adb5bd;
+          transform: translateY(-2px);
+          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+
+          .type-icon {
+            transform: scale(1.08);
+          }
+        }
+
+        // 不同类型的配色
+        &[data-color="primary"].active {
+          background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+          border-color: transparent;
+          box-shadow: 0 6px 20px rgba(79, 172, 254, 0.4);
+
+          .type-icon {
+            background: rgba(255, 255, 255, 0.25);
+            .icon { color: white; }
+          }
+
+          .type-content {
+            .type-name, .type-description { color: white; }
+          }
+
+          .file-count-badge {
+            background: white;
+            color: #4facfe;
+          }
+        }
+
+        &[data-color="secondary"].active {
+          background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
+          border-color: transparent;
+          box-shadow: 0 6px 20px rgba(250, 112, 154, 0.4);
+
+          .type-icon {
+            background: rgba(255, 255, 255, 0.25);
+            .icon { color: white; }
+          }
+
+          .type-content {
+            .type-name, .type-description { color: white; }
+          }
+
+          .file-count-badge {
+            background: white;
+            color: #fa709a;
+          }
+        }
+
+        &[data-color="tertiary"].active {
+          background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
+          border-color: transparent;
+          box-shadow: 0 6px 20px rgba(168, 237, 234, 0.4);
+
+          .type-icon {
+            background: rgba(255, 255, 255, 0.25);
+            .icon { color: white; }
+          }
+
+          .type-content {
+            .type-name, .type-description { color: white; }
+          }
+
+          .file-count-badge {
+            background: white;
+            color: #a8edea;
+          }
+        }
+
+        &[data-color="success"].active {
+          background: linear-gradient(135deg, #81fbb8 0%, #28c76f 100%);
+          border-color: transparent;
+          box-shadow: 0 6px 20px rgba(129, 251, 184, 0.4);
+
+          .type-icon {
+            background: rgba(255, 255, 255, 0.25);
+            .icon { color: white; }
+          }
+
+          .type-content {
+            .type-name, .type-description { color: white; }
+          }
+
+          .file-count-badge {
+            background: white;
+            color: #28c76f;
+          }
+        }
+      }
+    }
+  }
+
+  // 文件上传和展示区域
+  .delivery-content-section {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+
+    // 上传区域
+    .upload-section {
+      .upload-area {
+        background: white;
+        border-radius: 16px;
+        padding: 24px;
+        border: 2px dashed #dee2e6;
+        transition: all 0.3s ease;
+
+        .upload-content {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          gap: 16px;
+          text-align: center;
+
+          .upload-icon {
+            width: 56px;
+            height: 56px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            border-radius: 50%;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+
+            .icon {
+              width: 32px;
+              height: 32px;
+            }
+          }
+
+          .upload-text {
+            h4 {
+              font-size: 16px;
+              font-weight: 700;
+              color: #212529;
+              margin: 0 0 6px;
+            }
+
+            p {
+              font-size: 13px;
+              color: #6c757d;
+              margin: 0;
+              line-height: 1.4;
+            }
+          }
+
+          .upload-button {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            padding: 12px 24px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            border-radius: 10px;
+            font-size: 14px;
+            font-weight: 600;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+
+            .icon {
+              width: 20px;
+              height: 20px;
+            }
+
+            &:hover:not(:disabled) {
+              transform: translateY(-2px);
+              box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
+            }
+
+            &:active:not(:disabled) {
+              transform: translateY(0);
+            }
+
+            &:disabled {
+              opacity: 0.6;
+              cursor: not-allowed;
+            }
+          }
+        }
+
+        .upload-progress {
+          margin-top: 16px;
+          display: flex;
+          flex-direction: column;
+          gap: 8px;
+
+          .progress-bar {
+            height: 8px;
+            background: #e9ecef;
+            border-radius: 10px;
+            overflow: hidden;
+
+            .progress-fill {
+              height: 100%;
+              background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+              border-radius: 10px;
+              transition: width 0.3s ease;
+            }
+          }
+
+          .progress-text {
+            font-size: 12px;
+            color: #667eea;
+            font-weight: 600;
+            text-align: center;
+          }
+        }
+
+        &.uploading {
+          border-color: #667eea;
+          background: #f8f9ff;
+        }
+      }
+    }
+
+    // 文件展示区域
+    .files-display-section {
+      .files-header {
+        margin-bottom: 16px;
+
+        h4 {
+          font-size: 16px;
+          font-weight: 700;
+          color: #212529;
+          margin: 0;
+        }
+      }
+
+      .files-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+        gap: 14px;
+
+        .file-card {
+          background: white;
+          border-radius: 14px;
+          overflow: hidden;
+          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+          transition: all 0.3s ease;
+
+          &:hover {
+            transform: translateY(-4px);
+            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
+          }
+
+          .file-preview {
+            position: relative;
+            width: 100%;
+            aspect-ratio: 1;
+            overflow: hidden;
+            background: #f8f9fa;
+            cursor: pointer;
+
+            .preview-image {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+
+            .file-type-icon {
+              width: 100%;
+              height: 100%;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+
+              .icon {
+                width: 48px;
+                height: 48px;
+                color: #adb5bd;
+              }
+            }
+
+            .file-overlay {
+              position: absolute;
+              top: 0;
+              left: 0;
+              right: 0;
+              bottom: 0;
+              background: rgba(0, 0, 0, 0.6);
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              opacity: 0;
+              transition: opacity 0.3s ease;
+
+              .icon {
+                width: 32px;
+                height: 32px;
+              }
+            }
+
+            &:hover .file-overlay {
+              opacity: 1;
+            }
+          }
+
+          .file-info {
+            padding: 12px;
+
+            .file-name {
+              font-size: 13px;
+              font-weight: 600;
+              color: #212529;
+              margin-bottom: 6px;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+            }
+
+            .file-meta {
+              display: flex;
+              align-items: center;
+              gap: 8px;
+              font-size: 11px;
+              color: #868e96;
+              margin-bottom: 4px;
+
+              .file-size {
+                &::after {
+                  content: "•";
+                  margin-left: 8px;
+                }
+              }
+            }
+
+            .file-uploader {
+              font-size: 11px;
+              color: #adb5bd;
+            }
+          }
+
+          .file-actions {
+            display: flex;
+            gap: 6px;
+            padding: 0 12px 12px;
+
+            .action-button {
+              flex: 1;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              padding: 8px;
+              background: #f8f9fa;
+              border: none;
+              border-radius: 8px;
+              cursor: pointer;
+              transition: all 0.2s ease;
+
+              .icon {
+                width: 18px;
+                height: 18px;
+                color: #6c757d;
+              }
+
+              &:hover {
+                background: #e9ecef;
+
+                .icon {
+                  color: #495057;
+                }
+              }
+
+              &.preview:hover {
+                background: #e3f2fd;
+                .icon { color: #2196f3; }
+              }
+
+              &.download:hover {
+                background: #e8f5e9;
+                .icon { color: #4caf50; }
+              }
+
+              &.delete:hover {
+                background: #ffebee;
+                .icon { color: #f44336; }
+              }
+            }
+          }
+        }
+      }
+
+      // 空状态
+      .empty-state {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: 60px 20px;
+        text-align: center;
+        background: white;
+        border-radius: 16px;
+
+        .empty-icon {
+          width: 80px;
+          height: 80px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-radius: 50%;
+          background: #f8f9fa;
+          margin-bottom: 20px;
+
+          .icon {
+            width: 48px;
+            height: 48px;
+            color: #adb5bd;
+          }
+        }
+
+        h4 {
+          font-size: 18px;
+          font-weight: 700;
+          color: #495057;
+          margin: 0 0 8px;
+        }
+
+        p {
+          font-size: 14px;
+          color: #868e96;
+          margin: 0 0 24px;
+          line-height: 1.5;
+        }
+
+        .upload-button-primary {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          padding: 14px 28px;
+          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+          color: white;
+          border: none;
+          border-radius: 12px;
+          font-size: 15px;
+          font-weight: 600;
+          cursor: pointer;
+          transition: all 0.3s ease;
+          box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+
+          .icon {
+            width: 20px;
+            height: 20px;
+          }
+
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
+          }
+
+          &:active {
+            transform: translateY(0);
+          }
+        }
+      }
+    }
+  }
+
+  // 未选择场景状态
+  .no-selection-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 80px 20px;
+    text-align: center;
+    background: white;
+    border-radius: 16px;
+    margin-top: 20px;
+
+    .state-icon {
+      width: 100px;
+      height: 100px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border-radius: 50%;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      margin-bottom: 24px;
+
+      .icon {
+        width: 56px;
+        height: 56px;
+        color: white;
+      }
+    }
+
+    h3 {
+      font-size: 20px;
+      font-weight: 700;
+      color: #212529;
+      margin: 0 0 10px;
+    }
+
+    p {
+      font-size: 15px;
+      color: #6c757d;
+      margin: 0;
+      line-height: 1.5;
+    }
+  }
+
+  // 没有场景状态
+  .no-products-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 80px 20px;
+    text-align: center;
+    background: white;
+    border-radius: 16px;
+
+    .state-icon {
+      width: 100px;
+      height: 100px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border-radius: 50%;
+      background: #f8f9fa;
+      margin-bottom: 24px;
+
+      .icon {
+        width: 56px;
+        height: 56px;
+        color: #adb5bd;
+      }
+    }
+
+    h3 {
+      font-size: 20px;
+      font-weight: 700;
+      color: #495057;
+      margin: 0 0 10px;
+    }
+
+    p {
+      font-size: 15px;
+      color: #868e96;
+      margin: 0;
+      line-height: 1.5;
+    }
+  }
+}
+
+// 移动端响应式
+@media (max-width: 768px) {
+  .stage-delivery-container {
+    padding: 12px;
+
+    .product-tabs-section {
+      .product-tabs {
+        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+        gap: 8px;
+
+        .product-tab {
+          padding: 12px 10px;
+
+          .product-icon {
+            width: 32px;
+            height: 32px;
+
+            .icon {
+              width: 18px;
+              height: 18px;
+            }
+          }
+
+          .product-name {
+            font-size: 12px;
+          }
+        }
+      }
+    }
+
+    .delivery-types-section {
+      .delivery-types-tabs {
+        grid-template-columns: repeat(2, 1fr);
+        gap: 8px;
+
+        .delivery-type-tab {
+          padding: 14px 8px;
+
+          .type-icon {
+            width: 40px;
+            height: 40px;
+
+            .icon {
+              width: 22px;
+              height: 22px;
+            }
+          }
+
+          .type-content {
+            .type-name {
+              font-size: 13px;
+            }
+
+            .type-description {
+              font-size: 10px;
+            }
+          }
+        }
+      }
+    }
+
+    .delivery-content-section {
+      gap: 16px;
+
+      .upload-section {
+        .upload-area {
+          padding: 20px;
+
+          .upload-content {
+            gap: 12px;
+
+            .upload-icon {
+              width: 48px;
+              height: 48px;
+
+              .icon {
+                width: 28px;
+                height: 28px;
+              }
+            }
+
+            .upload-text {
+              h4 {
+                font-size: 15px;
+              }
+
+              p {
+                font-size: 12px;
+              }
+            }
+
+            .upload-button {
+              padding: 10px 20px;
+              font-size: 13px;
+
+              .icon {
+                width: 18px;
+                height: 18px;
+              }
+            }
+          }
+        }
+      }
+
+      .files-display-section {
+        .files-grid {
+          grid-template-columns: repeat(2, 1fr);
+          gap: 12px;
+        }
+      }
+    }
+
+    .no-selection-state,
+    .no-products-state {
+      padding: 60px 16px;
+
+      .state-icon {
+        width: 80px;
+        height: 80px;
+
+        .icon {
+          width: 48px;
+          height: 48px;
+        }
+      }
+
+      h3 {
+        font-size: 18px;
+      }
+
+      p {
+        font-size: 14px;
+      }
+    }
+  }
+
+  // 操作按钮(参考售后归档样式)
+  .action-buttons {
+    margin-top: 24px;
+    padding: 16px;
+
+    .btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 12px;
+      padding: 18px 48px;
+      border-radius: 14px;
+      font-size: 17px;
+      font-weight: 700;
+      cursor: pointer;
+      transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
+      border: none;
+      outline: none;
+      min-height: 58px;
+      width: 100%;
+      max-width: 400px;
+      position: relative;
+      overflow: hidden;
+      letter-spacing: 0.5px;
+      text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+
+      .icon {
+        width: 22px;
+        height: 22px;
+        filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.15));
+        transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
+      }
+
+      // 多层按钮涟漪效果
+      &::before {
+        content: '';
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        width: 0;
+        height: 0;
+        border-radius: 50%;
+        background: radial-gradient(circle, rgba(255, 255, 255, 0.6) 0%, transparent 70%);
+        transform: translate(-50%, -50%);
+        transition: width 0.7s cubic-bezier(0.4, 0, 0.2, 1), height 0.7s cubic-bezier(0.4, 0, 0.2, 1);
+      }
+
+      // 光泽层
+      &::after {
+        content: '';
+        position: absolute;
+        top: -50%;
+        left: -50%;
+        width: 200%;
+        height: 200%;
+        background: linear-gradient(
+          120deg,
+          transparent 0%,
+          transparent 40%,
+          rgba(255, 255, 255, 0.15) 50%,
+          transparent 60%,
+          transparent 100%
+        );
+        transform: translateX(-100%);
+        transition: transform 0.6s ease;
+      }
+
+      &:active:not(:disabled)::before {
+        width: 400px;
+        height: 400px;
+        transition: width 0.3s, height 0.3s;
+      }
+
+      &:hover:not(:disabled)::after {
+        transform: translateX(100%);
+      }
+
+      // 提交审批按钮(参考售后归档样式)
+      &.btn-primary {
+        background: var(--primary-color, #3880ff);
+        color: white;
+
+        &:hover:not(:disabled) {
+          background: #2f6ce5;
+          transform: translateY(-2px);
+        }
+
+        &:active:not(:disabled) {
+          transform: translateY(0);
+        }
+      }
+      
+      &.btn-block {
+        width: 100%;
+      }
+      
+      &.btn-large {
+        padding: 16px 24px;
+        font-size: 15px;
+        }
+
+        &:disabled {
+        opacity: 0.5;
+          cursor: not-allowed;
+        pointer-events: none;
+        box-shadow: none;
+        transform: none;
+      }
+
+      .icon-spin {
+        animation: spin 1s linear infinite;
+      }
+    }
+
+    // 移动端优化
+    @media (max-width: 768px) {
+      padding: 24px 16px;
+      margin: 32px auto 24px;
+      border-radius: 16px;
+      
+      .btn {
+        max-width: 100%;
+        width: 100%;
+        padding: 16px 36px;
+        font-size: 16px;
+        min-height: 54px;
+
+        .icon {
+          width: 20px;
+          height: 20px;
+        }
+      }
+    }
+
+    @media (max-width: 480px) {
+      padding: 20px 12px;
+      margin: 24px auto 20px;
+      
+      .btn {
+        padding: 14px 28px;
+        font-size: 15px;
+        min-height: 50px;
+        gap: 10px;
+
+        .icon {
+          width: 18px;
+          height: 18px;
+        }
+      }
+    }
+  }
+
+  @keyframes spin {
+    from { transform: rotate(0deg); }
+    to { transform: rotate(360deg); }
+  }
+
+  .button-tip {
+    margin-top: 16px;
+    padding: 12px 20px;
+    text-align: center;
+    background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
+    border-left: 4px solid #ffc107;
+    border-radius: 8px;
+    color: #856404;
+    font-size: 14px;
+    box-shadow: 0 2px 8px rgba(255, 193, 7, 0.15);
+    
+    &::before {
+      content: '💡 ';
+    }
+  }
+}
+
+// ✨ 审批状态样式(紧凑单行样式)
+.file-approval-status {
+  margin-top: 8px;
+  padding: 6px 10px;
+  background: #f8f9fa;
+  border-radius: 6px;
+  font-size: 12px;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  border-left: 3px solid #e0e0e0;
+
+  .status-icon {
+    font-size: 14px;
+    line-height: 1;
+  }
+
+  .status-text {
+    font-weight: 600;
+    color: #495057;
+  }
+
+  .status-by {
+    color: #6c757d;
+    font-size: 11px;
+  }
+
+  .status-reason {
+    color: #6c757d;
+    font-size: 11px;
+    font-style: italic;
+  }
+
+  &.status-pending {
+    background: rgba(56, 128, 255, 0.1);
+    border-left-color: #3880ff;
+  }
+  
+  &.status-approved {
+    background: rgba(45, 211, 111, 0.1);
+    border-left-color: #2dd36f;
+  }
+  
+  &.status-rejected {
+    background: rgba(235, 68, 90, 0.1);
+    border-left-color: #eb445a;
+  }
+}
+
+// 未验证文件徽章
+.unverified-badge {
+  display: inline-block;
+  padding: 2px 8px;
+  background: #ff9f43;
+  color: white;
+  border-radius: 10px;
+  font-size: 11px;
+  font-weight: 600;
+  margin-left: 8px;
+  animation: pulse 2s ease-in-out infinite;
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.7;
+  }
+}
+
+// 类型徽章容器
+.type-badges {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+// ✨ 文件卡片增强样式(参考售后归档)
+.file-card {
+  transition: all 0.3s ease;
+  
+  &.has-approval-issue {
+    border: 2px solid #eb445a;
+    animation: shake 0.5s ease-in-out;
+  }
+  
+  .file-preview {
+    position: relative;
+    
+    // 审批状态角标
+    .approval-corner-badge {
+      position: absolute;
+      top: 8px;
+      right: 8px;
+      width: 32px;
+      height: 32px;
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 16px;
+      background: rgba(255, 255, 255, 0.95);
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+      z-index: 2;
+      
+      &.badge-unverified {
+        background: linear-gradient(135deg, #fff3cd, #ffc107);
+      }
+      
+      &.badge-pending {
+        background: linear-gradient(135deg, #d1ecf1, #17a2b8);
+      }
+      
+      &.badge-approved {
+        background: linear-gradient(135deg, #d4edda, #28a745);
+      }
+      
+      &.badge-rejected {
+        background: linear-gradient(135deg, #f8d7da, #dc3545);
+      }
+    }
+    
+    // 删除按钮(参考售后归档样式)
+    .delete-btn {
+      position: absolute;
+      top: 8px;
+      left: 8px;
+      width: 32px;
+      height: 32px;
+      border: none;
+      background: rgba(235, 68, 90, 0.9);
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      cursor: pointer;
+      transition: all 0.3s;
+      padding: 0;
+      z-index: 3;
+      opacity: 0;
+      
+      &:hover {
+        background: #eb445a;
+        transform: scale(1.1);
+      }
+      
+      .icon {
+        width: 18px;
+        height: 18px;
+        color: white;
+      }
+    }
+  }
+  
+  &:hover {
+    .delete-btn {
+      opacity: 1;
+    }
+  }
+}
+
+@keyframes shake {
+  0%, 100% {
+    transform: translateX(0);
+  }
+  25% {
+    transform: translateX(-5px);
+  }
+  75% {
+    transform: translateX(5px);
+  }
+}
+
+@media (max-width: 480px) {
+  .stage-delivery-container {
+    .product-tabs-section {
+      .product-tabs {
+        grid-template-columns: repeat(2, 1fr);
+      }
+    }
+
+    .delivery-content-section {
+      .files-display-section {
+        .files-grid {
+          grid-template-columns: repeat(2, 1fr);
+        }
+      }
+    }
+  }
+}
+
+// 🎨 按钮加载动画
+.icon-spin {
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}

+ 272 - 0
src/modules/project/pages/project-detail/stages/stage-delivery.component.ts

@@ -14,6 +14,30 @@ const Parse = FmodeParse.with('nova');
  */
 type ApprovalStatus = 'pending' | 'approved' | 'rejected' | 'unverified';
 
+/**
+ * 阶段确认记录接口(已废弃,改用空间级别确认)
+ */
+interface StageConfirmation {
+  confirmedBy: string; // 确认人ID
+  confirmedByName: string; // 确认人姓名
+  confirmedByRole: string; // 确认人角色
+  confirmedAt: Date; // 确认时间
+  stage: string; // 阶段类型 (white_model, soft_decor, rendering, post_process)
+  spaceId: string; // 空间ID
+}
+
+/**
+ * 空间确认记录接口(新)
+ */
+interface SpaceConfirmation {
+  confirmedBy: string; // 确认人ID
+  confirmedByName: string; // 确认人姓名
+  confirmedByRole: string; // 确认人角色
+  confirmedAt: Date; // 确认时间
+  spaceId: string; // 空间ID
+  filesSnapshot: string[]; // 确认时的文件ID快照,用于检测变更
+}
+
 /**
  * 交付文件接口
  */
@@ -73,6 +97,13 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
   projectProducts: Project[] = [];
   isMultiProductProject: boolean = false;
   activeProductId: string = '';
+  
+  // 空间展开/收起状态管理
+  expandedSpaces: Set<string> = new Set();
+  
+  // 当前选中的空间和阶段(用于查看图片)
+  selectedSpaceId: string = '';
+  selectedStageType: string = '';
 
   // 交付类型定义
   deliveryTypes = [
@@ -1927,4 +1958,245 @@ export class StageDeliveryComponent implements OnInit, OnDestroy {
       console.error('❌ 更新空间交付物汇总失败:', error);
     }
   }
+
+  // ==================== 新增:空间展开/收起管理 ====================
+  
+  /**
+   * 切换空间展开/收起状态
+   */
+  toggleSpaceExpansion(spaceId: string): void {
+    if (this.expandedSpaces.has(spaceId)) {
+      this.expandedSpaces.delete(spaceId);
+      console.log(`🔽 收起空间: ${spaceId}`);
+    } else {
+      this.expandedSpaces.add(spaceId);
+      console.log(`🔼 展开空间: ${spaceId}`);
+    }
+    this.cdr.markForCheck();
+  }
+
+  /**
+   * 检查空间是否已展开
+   */
+  isSpaceExpanded(spaceId: string): boolean {
+    return this.expandedSpaces.has(spaceId);
+  }
+
+  /**
+   * 选择空间和阶段(用于查看图片)
+   */
+  selectSpaceAndStage(spaceId: string, stageType: string): void {
+    this.selectedSpaceId = spaceId;
+    this.selectedStageType = stageType;
+    console.log(`📍 选中空间和阶段:`, { spaceId, stageType });
+    this.cdr.markForCheck();
+  }
+
+  /**
+   * 获取空间某个阶段的文件数量
+   */
+  getSpaceStageFileCount(spaceId: string, stageType: string): number {
+    return this.getProductDeliveryFiles(spaceId, stageType).length;
+  }
+
+  /**
+   * 获取空间所有阶段的总文件数
+   */
+  getSpaceTotalFileCount(spaceId: string): number {
+    return this.deliveryTypes.reduce((total, type) => {
+      return total + this.getSpaceStageFileCount(spaceId, type.id);
+    }, 0);
+  }
+
+  // ==================== 新增:空间级别确认功能 ====================
+
+  /**
+   * 获取空间已完成的阶段数量(有文件的阶段)
+   */
+  getCompletedStagesCount(spaceId: string): number {
+    return this.deliveryTypes.filter(type => 
+      this.getSpaceStageFileCount(spaceId, type.id) > 0
+    ).length;
+  }
+
+  /**
+   * 确认整个空间的交付执行清单
+   */
+  async confirmSpace(spaceId: string): Promise<void> {
+    if (!this.project || !this.currentUser) {
+      console.warn('❌ 无法确认:缺少项目或用户信息');
+      return;
+    }
+
+    const totalFiles = this.getSpaceTotalFileCount(spaceId);
+    if (totalFiles === 0) {
+      window?.fmode?.alert?.('该空间暂无文件,无法确认');
+      return;
+    }
+
+    const spaceName = this.projectProducts.find(p => p.id === spaceId)?.name || '空间';
+    const completedStages = this.getCompletedStagesCount(spaceId);
+
+    const confirmed = await window?.fmode?.confirm?.(
+      `确认【${spaceName}】的交付执行清单?\n\n` +
+      `已完成 ${completedStages}/4 个阶段,共 ${totalFiles} 个文件。\n\n` +
+      `确认后如有文件变更,需要重新确认。`
+    );
+
+    if (!confirmed) return;
+
+    try {
+      this.saving = true;
+      this.cdr.markForCheck();
+
+      console.log(`🔥 开始确认空间: ${spaceName}`);
+
+      const data = this.project.get('data') || {};
+      
+      // 初始化 spaceConfirmations 结构
+      if (!data.spaceConfirmations) {
+        data.spaceConfirmations = {};
+      }
+
+      // 获取当前空间所有文件的ID作为快照
+      const filesSnapshot: string[] = [];
+      this.deliveryTypes.forEach(type => {
+        const files = this.getProductDeliveryFiles(spaceId, type.id);
+        files.forEach(file => filesSnapshot.push(file.id));
+      });
+
+      // 记录确认信息
+      const confirmation: SpaceConfirmation = {
+        confirmedBy: this.currentUser.id || '',
+        confirmedByName: this.currentUser.get('name') || '',
+        confirmedByRole: this.currentUser.get('roleName') || '',
+        confirmedAt: new Date(),
+        spaceId: spaceId,
+        filesSnapshot: filesSnapshot
+      };
+
+      data.spaceConfirmations[spaceId] = confirmation;
+
+      // 保存到项目
+      this.project.set('data', data);
+      await this.project.save();
+
+      console.log('✅ 空间确认成功:', confirmation);
+      window?.fmode?.toast?.success?.(`✅ 已确认【${spaceName}】的交付执行清单`);
+
+      this.cdr.markForCheck();
+
+    } catch (error) {
+      console.error('❌ 确认空间失败:', error);
+      window?.fmode?.alert?.('确认失败,请重试');
+    } finally {
+      this.saving = false;
+      this.cdr.markForCheck();
+    }
+  }
+
+  /**
+   * 获取空间的确认信息
+   */
+  getSpaceConfirmation(spaceId: string): SpaceConfirmation | null {
+    if (!this.project) return null;
+    const data = this.project.get('data') || {};
+    return data.spaceConfirmations?.[spaceId] || null;
+  }
+
+  /**
+   * 检查空间是否已确认
+   */
+  isSpaceConfirmed(spaceId: string): boolean {
+    return this.getSpaceConfirmation(spaceId) !== null;
+  }
+
+  /**
+   * 检查空间文件是否有变更(与确认时的快照对比)
+   */
+  hasSpaceFilesChanged(spaceId: string): boolean {
+    const confirmation = this.getSpaceConfirmation(spaceId);
+    if (!confirmation) return false;
+
+    // 获取当前所有文件ID
+    const currentFileIds: string[] = [];
+    this.deliveryTypes.forEach(type => {
+      const files = this.getProductDeliveryFiles(spaceId, type.id);
+      files.forEach(file => currentFileIds.push(file.id));
+    });
+
+    // 对比快照
+    const snapshotIds = confirmation.filesSnapshot || [];
+    
+    // 检查数量是否变化
+    if (currentFileIds.length !== snapshotIds.length) {
+      return true;
+    }
+
+    // 检查文件ID是否完全一致
+    const currentSet = new Set(currentFileIds);
+    const snapshotSet = new Set(snapshotIds);
+    
+    for (const id of currentFileIds) {
+      if (!snapshotSet.has(id)) return true;
+    }
+    
+    for (const id of snapshotIds) {
+      if (!currentSet.has(id)) return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * 获取空间确认显示文本
+   */
+  getSpaceConfirmationText(spaceId: string): string {
+    const confirmation = this.getSpaceConfirmation(spaceId);
+    if (!confirmation) return '';
+    
+    const date = new Date(confirmation.confirmedAt);
+    const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
+    
+    return `${confirmation.confirmedByName} (${confirmation.confirmedByRole}) - ${dateStr}`;
+  }
+
+  // ==================== 保留:旧的阶段确认功能(向后兼容) ====================
+
+  /**
+   * 确认某个空间的某个阶段(已废弃)
+   */
+  async confirmStage(spaceId: string, stageType: string): Promise<void> {
+    // 重定向到空间级别确认
+    return this.confirmSpace(spaceId);
+  }
+
+  /**
+   * 获取某个空间某个阶段的确认信息(已废弃)
+   */
+  getStageConfirmation(spaceId: string, stageType: string): StageConfirmation | null {
+    if (!this.project) return null;
+    const data = this.project.get('data') || {};
+    return data.stageConfirmations?.[spaceId]?.[stageType] || null;
+  }
+
+  /**
+   * 检查某个空间某个阶段是否已确认(已废弃)
+   */
+  isStageConfirmed(spaceId: string, stageType: string): boolean {
+    return this.getStageConfirmation(spaceId, stageType) !== null;
+  }
+
+  /**
+   * 获取阶段确认显示文本(已废弃)
+   */
+  getStageConfirmationText(spaceId: string, stageType: string): string {
+    const confirmation = this.getStageConfirmation(spaceId, stageType);
+    if (!confirmation) return '';
+    
+    const date = new Date(confirmation.confirmedAt);
+    const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
+    
+    return `${confirmation.confirmedByName} (${confirmation.confirmedByRole}) - ${dateStr}`;
+  }
 }