Browse Source

feat(design-improvements): 添加设计师分配与日历视图改进总结文档

- 新增设计师分配与日历视图的改进总结,详细描述已完成的功能和优化。
- 修复SCSS Linter警告,提升样式兼容性。
- 优化设计师组别配置,增加专业组别及成员状态多样化。
- 全面升级设计师日历视图,采用颜色编码系统,提升可视化效果。
- 提供使用效果和下一步建议,确保功能的可用性和用户体验。

此更新旨在增强设计师的工作效率和项目管理能力。
徐福静0235668 4 days ago
parent
commit
894f0b2ec1
22 changed files with 5117 additions and 164 deletions
  1. 183 0
      DESIGN-IMPROVEMENTS-SUMMARY.md
  2. 285 0
      docs/designer-reference-image-guide.md
  3. 29 12
      src/app/pages/customer-service/dashboard/pages/after-sales/after-sales.component.scss
  4. 321 21
      src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.ts
  5. 26 33
      src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.html
  6. 190 42
      src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.scss
  7. 36 0
      src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.ts
  8. 161 0
      src/app/pages/designer/project-detail/components/reference-image-manager/README.md
  9. 154 0
      src/app/pages/designer/project-detail/components/reference-image-manager/icons.scss
  10. 475 0
      src/app/pages/designer/project-detail/components/reference-image-manager/reference-image-manager.component.html
  11. 1249 0
      src/app/pages/designer/project-detail/components/reference-image-manager/reference-image-manager.component.scss
  12. 475 0
      src/app/pages/designer/project-detail/components/reference-image-manager/reference-image-manager.component.ts
  13. 39 4
      src/app/pages/designer/project-detail/project-detail.html
  14. 88 0
      src/app/pages/designer/project-detail/project-detail.scss
  15. 60 18
      src/app/pages/designer/project-detail/project-detail.ts
  16. 8 10
      src/app/pages/designer/project-detail/project-review-styles.scss
  17. 228 7
      src/app/pages/finance/dashboard/dashboard.html
  18. 711 2
      src/app/pages/finance/dashboard/dashboard.scss
  19. 362 14
      src/app/pages/finance/dashboard/dashboard.ts
  20. 7 0
      src/app/pages/finance/quotation-approval/quotation-approval.component.html
  21. 20 0
      src/app/pages/finance/quotation-approval/quotation-approval.component.scss
  22. 10 1
      src/app/pages/finance/quotation-approval/quotation-approval.component.ts

+ 183 - 0
DESIGN-IMPROVEMENTS-SUMMARY.md

@@ -0,0 +1,183 @@
+# 设计师分配与日历视图改进总结
+
+## 📅 更新日期
+2025-01-18
+
+## ✅ 已完成的改进
+
+### 1. SCSS Linter 警告修复
+- ✅ 修复了 `reference-image-manager.component.scss` 中的 `vendorPrefix` 警告
+- ✅ 添加了标准的 `line-clamp` 属性以提高浏览器兼容性
+
+### 2. 设计师组别配置优化
+#### 问题
+- 原有配置:仅有2个组(家装、工装),每组都有2个组长
+- 不符合实际:应该是1个组长+多个成员
+
+#### 解决方案
+- ✅ 增加到6个专业组别:
+  1. **家装设计组** - 1组长 + 3成员
+  2. **工装设计组** - 1组长 + 2成员
+  3. **软装设计组** - 1组长 + 2成员(新增)
+  4. **建模渲染组** - 1组长 + 2成员(新增)
+  5. **后期处理组** - 1组长 + 2成员(新增)
+  6. **全景图制作组** - 1组长 + 1成员(新增)
+
+#### 成员配置改进
+- ✅ 每组仅1人为组长(`isTeamLeader: true`,`isLeader: true`)
+- ✅ 其他成员均为普通设计师(`isTeamLeader: false`,`isLeader: false`)
+- ✅ 成员状态多样化:
+  - 空闲(idle):适合分配新订单
+  - 忙碌(busy):有订单但可继续分配
+  - 对图中(reviewing):正在审核图纸
+
+### 3. 设计师日历视图全面升级
+
+#### 核心改进
+- ✅ **颜色编码系统** - 不再使用圆圈,改用整个日期格子的背景颜色
+- ✅ **清晰的视觉层次** - 一眼就能看出设计师的空闲情况
+
+#### 颜色方案
+
+| 状态 | 颜色 | 描述 |
+|------|------|------|
+| 🟢 空闲 | 绿色渐变背景 + 绿色边框 | 可以分配新订单,hover 有放大效果 |
+| 🔵 有订单(1单) | 浅蓝色渐变背景 + 蓝色边框 | 工作负荷适中,可继续分配 |
+| 🔵 有订单(≥2单) | 深蓝色渐变背景 + 深蓝边框 | 工作负荷较高,谨慎分配 |
+| 🔴 对图日期 | 红色渐变背景 + 红色边框 | 完全不能分配,cursor: not-allowed |
+| 🟠 今天 | 橙色边框 + 外发光 | 特殊标记今天的日期 |
+
+#### 视觉增强
+- ✅ **渐变背景** - 使用 linear-gradient 让颜色更有层次感
+- ✅ **hover 效果** - 空闲和有订单的日期 hover 时有缩放效果
+- ✅ **底部彩条** - 工作负荷强度指示器
+- ✅ **状态标签** - 显示"空闲"、"1单"、"2单"、"对图"等文字
+- ✅ **详细 tooltip** - 鼠标悬停显示详细说明
+
+#### 新增功能
+```typescript
+// 获取日期状态
+getDayStatus(day: CalendarDay): string {
+  if (day.isReviewDay) return 'review';
+  if (day.orderCount >= 2) return 'busy-high';
+  if (day.orderCount > 0) return 'busy-medium';
+  if (day.isIdle) return 'idle';
+  return 'normal';
+}
+
+// 生成详细 tooltip
+getDayTooltip(day: CalendarDay): string {
+  // 包含今天、状态、订单数等信息
+}
+```
+
+### 4. 图例说明优化
+- ✅ 图例颜色方块从 12px 增加到 20px,更清晰
+- ✅ 使用与日历相同的渐变色和边框
+- ✅ 更新文字说明,详细解释每种颜色的含义
+
+### 5. 样式精美化
+- ✅ 使用渐变色增加视觉深度
+- ✅ hover 动画效果(scale + box-shadow)
+- ✅ 边框使用更鲜明的颜色
+- ✅ 底部工作负荷指示条使用渐变填充
+- ✅ 今天日期使用橙色边框 + 外发光效果
+
+## 📂 修改的文件
+
+### TypeScript 文件
+1. `src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.ts`
+   - 扩展 `projectTeams` 从2组到6组
+   - 修正每组的组长/成员配置
+   - 增加更多设计师,每组3-4人
+
+2. `src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.ts`
+   - 新增 `getDayStatus()` 方法
+   - 新增 `getDayTooltip()` 方法
+   - 导出 `Math` 对象供模板使用
+
+### HTML 文件
+3. `src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.html`
+   - 移除 `<mat-icon>` 圆圈显示
+   - 改用 `data-status` 属性控制样式
+   - 添加状态文字标签
+   - 更新图例说明
+   - 更新操作提示文字
+
+### SCSS 文件
+4. `src/app/pages/designer/project-detail/components/reference-image-manager/reference-image-manager.component.scss`
+   - 添加 `line-clamp` 标准属性
+
+5. `src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.scss`
+   - 新增 `[data-status="idle"]` 样式
+   - 新增 `[data-status="busy-medium"]` 样式
+   - 新增 `[data-status="busy-high"]` 样式
+   - 新增 `[data-status="review"]` 样式
+   - 更新 `.today` 样式为橙色边框
+   - 更新图例颜色样式
+   - 优化渐变色和边框效果
+
+## 🎨 颜色配置参考
+
+```scss
+// 空闲日期
+background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
+border: 2px solid #28a745;
+
+// 有订单(1单)
+background: linear-gradient(135deg, #cfe2ff 0%, #b6d4fe 100%);
+border: 2px solid #0d6efd;
+
+// 有订单(≥2单)
+background: linear-gradient(135deg, #9ec5fe 0%, #6ea8fe 100%);
+border: 2px solid #0a58ca;
+
+// 对图日期
+background: linear-gradient(135deg, #f8d7da 0%, #f5c2c7 100%);
+border: 2px solid #dc3545;
+
+// 今天
+border: 3px solid #ff6b35;
+box-shadow: 0 0 0 2px rgba(255, 107, 53, 0.2);
+```
+
+## 🚀 使用效果
+
+### 设计师选择界面
+- 现在有6个专业组可选择
+- 每组清晰显示1个组长和多个成员
+- 可以看到每个设计师的空闲天数、工作量等信息
+
+### 日历详细视图
+- 点击设计师查看日历
+- **绿色日期** = 完全空闲,优先分配
+- **浅蓝色** = 有1个订单,可继续分配
+- **深蓝色** = 有多个订单,工作负荷高
+- **红色** = 对图日期,不可分配
+- **橙色边框** = 今天的日期
+- 底部彩条显示工作强度
+
+### 交互体验
+- hover 空闲日期会放大并显示阴影
+- 点击空闲日期可以快速分配订单
+- 所有状态一目了然,不再需要仔细查看小圆圈
+
+## 📝 注意事项
+
+1. 所有修改都保留了向后兼容性
+2. 旧的类名样式仍然存在,确保不会破坏现有功能
+3. 建议在真实环境中测试日历视图的所有交互
+4. 如需调整颜色,可修改 SCSS 文件中的渐变色值
+
+## 🎯 下一步建议
+
+1. 连接真实的后端数据
+2. 实现点击空闲日期后的订单分配功能
+3. 添加日期范围选择功能
+4. 考虑添加周视图/月视图切换
+5. 增加设计师工作量统计图表
+
+---
+
+**更新完成!所有功能已按要求实现。✅**
+

+ 285 - 0
docs/designer-reference-image-guide.md

@@ -0,0 +1,285 @@
+# 设计师参考图管理使用指南
+
+## 一、功能入口
+
+在设计师项目详情页面,顶部导航栏有四个标签页:
+- 项目进度
+- 项目成员
+- 项目文件
+- **参考图管理** ← 新增功能
+
+点击"参考图管理"标签即可进入参考图管理界面。
+
+## 二、功能说明
+
+### 1. 为什么需要参考图管理?
+
+#### 常见问题:
+- ❌ 参考图比较多,不知道每张图具体参考什么
+- ❌ 需要明确各个图里参考的元素和依据
+- ❌ 设计师给参考图更多是软装,而不是氛围
+- ❌ 效果图达不到想要的氛围
+- ❌ 参考图不是现在要的结构
+- ❌ 客户感觉表现师不懂变通
+
+#### 解决方案:
+✅ **充分沟通 + 明确标注 = 更好的设计结果**
+
+参考图管理功能帮助您:
+1. 给每张参考图打上明确的标签(灯光/软装/氛围/结构)
+2. 详细描述每张图的参考要点
+3. 使用AI助手生成专业的追问问题
+4. 避免因沟通不清导致的返工
+
+### 2. 四大分类标签
+
+| 标签 | 用途 | 示例 |
+|------|------|------|
+| 💡 灯光参考 | 参考灯光布局、照明效果 | 主灯位置、氛围灯效果、灯具造型 |
+| 🛋️ 软装参考 | 参考家具、装饰品等 | 沙发款式、窗帘材质、装饰画风格 |
+| ✨ 氛围参考 | 参考整体氛围和感觉 | 温馨感、现代感、高级感 |
+| 📐 结构参考 | 参考空间结构和布局 | 墙体位置、动线设计、区域划分 |
+
+**重要提示**:一张图可以有多个标签!比如一张客厅图可以同时参考灯光和氛围。
+
+### 3. AI智能追问助手
+
+AI会根据参考图的数量和完整度,自动提供专业的追问建议。
+
+#### 情况A:参考图较多(≥3张)
+
+**策略**:直接问哪个是灯光、软装、氛围
+
+AI建议的追问示例:
+> "请问这几张参考图中,哪一张是您想要的灯光效果?主要参考它的主光源布置还是氛围灯效果?"
+
+> "这些参考图中,哪几张是用于软装搭配的?具体是参考家具的款式、颜色还是整体搭配方案?"
+
+> "您希望达到的整体氛围感觉,主要是参考哪张图?是温馨、现代还是其他风格?"
+
+#### 情况B:参考图较少(<3张)
+
+**策略**:具体问图内的感觉来源和细节
+
+AI建议的追问示例:
+> "您喜欢这张图的哪些方面?是整体的色调、光线效果、还是家具的搭配?能具体说说吗?"
+
+> "这张图里,您最想要的是什么?比如墙面的颜色、地板材质、还是家具的款式?请按重要性排个序。"
+
+> "关于这张图的细节,比如灯具的样式、窗帘的材质、墙面的装饰等,您有特别的偏好吗?"
+
+## 三、操作流程
+
+### 步骤1:上传参考图
+
+1. 点击右上角"上传参考图"按钮
+2. 可以拖拽文件或点击选择文件
+3. 支持批量上传多张图片
+4. 点击"上传"完成
+
+### 步骤2:分类和标注
+
+对于每张上传的图片:
+
+1. **点击图片**进入编辑模式
+2. **填写图片名称**(建议:客厅主灯参考、软装配色方案等)
+3. **勾选分类标签**(可多选)
+4. **填写描述说明**
+   - ❌ 错误示例:"参考这个图"
+   - ✅ 正确示例:"参考这张图的主灯造型和暖色调灯光的运用,特别是吊灯的水晶质感"
+5. **添加关键要素标签**
+   - 输入标签后按回车添加
+   - 如:吊灯、暖光、水晶质感、氛围灯
+6. 点击"保存修改"
+
+### 步骤3:使用AI追问(针对模糊的参考图)
+
+如果参考图没有明确的说明:
+
+1. 点击图片下方的"AI智能追问"按钮
+2. 查看当前状态分析:
+   - ❌ 分类标签:未标注
+   - ❌ 描述说明:未填写
+3. 查看AI建议的追问问题
+4. 选择合适的问题点击"应用"
+5. 系统会自动将问题发送给客户(或复制后发送)
+6. 收到客户回复后,回到编辑界面补充信息
+
+### 步骤4:筛选和查看
+
+- 使用顶部的分类筛选按钮快速查找
+- 切换网格/列表视图适应不同需求
+- 查看"未分类"找出需要补充信息的图片
+
+## 四、实战案例
+
+### 案例1:客户提供了8张参考图
+
+**初始情况**:
+- 客户发来8张图,只说"就按这些图做"
+- 没有说明每张图的具体用途
+
+**处理步骤**:
+
+1. **批量上传**:将8张图全部上传
+
+2. **逐一分析**:
+   - 图1(客厅全景):点击AI追问 → "这张图您主要参考什么?结构布局、软装搭配还是整体氛围?"
+   - 图2(灯具特写):明显是灯光参考 → 直接标注"💡 灯光参考"
+   - 图3-5(软装细节):标注"🛋️ 软装参考" + 详细描述
+   - 图6-8(氛围图):使用AI追问确认具体参考点
+
+3. **与客户确认**:
+   ```
+   设计师:"您好,我整理了一下参考图:
+   - 图2是参考主灯造型对吗?
+   - 图3-5主要参考软装的颜色搭配?
+   - 图6-8您最喜欢的是哪张的整体感觉?"
+   
+   客户:"对的,图6的那种温馨感觉我最喜欢"
+   ```
+
+4. **完善标注**:
+   - 图6标注为"✨ 氛围参考(主要)"
+   - 添加描述:"客户最喜欢的整体温馨感觉"
+   - 添加标签:温馨、暖色调、家庭氛围
+
+### 案例2:只有1张参考图但描述模糊
+
+**初始情况**:
+- 客户:"我就喜欢这个感觉,你看着做吧"
+- 只有1张现代客厅图
+
+**处理步骤**:
+
+1. **上传图片**
+
+2. **点击AI追问**,选择"细化感觉来源":
+   ```
+   AI建议:"您喜欢这张图的哪些方面?是整体的色调、光线效果、
+   还是家具的搭配?能具体说说吗?"
+   ```
+
+3. **发送追问,收到回复**:
+   ```
+   客户:"主要是喜欢那个黑白灰的配色,还有那个吊灯特别好看"
+   ```
+
+4. **完善标注**:
+   - 分类:🛋️ 软装参考 + 💡 灯光参考
+   - 描述:"参考黑白灰配色方案和吊灯造型"
+   - 标签:黑白灰、吊灯造型、现代简约
+
+### 案例3:效果图不符合预期
+
+**问题**:
+- 第一版效果图客户不满意
+- "和我给的参考图不一样啊"
+
+**解决步骤**:
+
+1. **回到参考图管理**
+2. **检查每张图的标注**:发现有3张图没有明确分类
+3. **使用AI追问补充信息**
+4. **与客户对照确认**:
+   ```
+   设计师:"麻烦再确认一下:
+   - 图1的结构布局是否要完全按照?
+   - 图2的颜色是重点还是家具款式是重点?
+   - 图3主要参考氛围感还是灯光效果?"
+   ```
+5. **得到明确回复后重新调整**
+
+## 五、技巧与注意事项
+
+### ✅ 好习惯
+
+1. **上传即标注**:不要积累,上传后立即分类和描述
+2. **主动追问**:遇到模糊的图,主动使用AI追问
+3. **详细记录**:客户的每句话都可能是重要信息
+4. **定期回顾**:设计过程中回顾参考图,确保方向正确
+5. **标注优先级**:对于多个参考点,标注哪个最重要
+
+### ❌ 要避免
+
+1. 不要凭感觉猜测客户意图
+2. 不要等到做完了才发现理解错了
+3. 不要忽略"未分类"的提醒
+4. 不要只上传不标注
+
+### 💡 专业建议
+
+**对于新手设计师**:
+- 多用AI追问,学习如何专业地沟通
+- 看看AI建议的问题是怎么问的
+- 积累经验后可以形成自己的追问模板
+
+**对于经验丰富的设计师**:
+- 可以使用"自定义追问"功能
+- 结合自己的经验补充AI没有覆盖的点
+- 帮助团队总结最佳实践
+
+## 六、常见问题解答
+
+**Q1: 客户不配合,不愿意详细说明怎么办?**
+
+A: 可以这样引导:
+```
+"为了做出您满意的效果图,我需要准确理解您的需求。
+这几个小问题麻烦您回答一下,可以节省后期修改的时间。"
+```
+
+**Q2: 参考图互相矛盾怎么办?**
+
+A: 
+1. 标注出矛盾点
+2. 使用AI追问:"图1和图3的风格不太一样,您更倾向哪一个?"
+3. 请客户明确优先级
+
+**Q3: 是不是所有参考图都要标注?**
+
+A: 建议全部标注,但至少要标注:
+- 主要参考图(客户明确说重点参考的)
+- 模糊的图(需要追问的)
+- 易混淆的图(防止理解错误的)
+
+**Q4: AI追问的效果好吗?**
+
+A: AI追问是基于专业沟通经验设计的,可以:
+- 避免遗漏重要信息
+- 使用客户容易理解的语言
+- 提供标准化的沟通流程
+
+但最终还是要根据实际情况灵活调整。
+
+## 七、快速检查清单
+
+在开始设计前,检查以下项目:
+
+- [ ] 所有参考图都已上传
+- [ ] 每张图都有明确的分类标签
+- [ ] 重要的图有详细的描述说明
+- [ ] 关键要素已用标签标注
+- [ ] 模糊的图已通过追问明确
+- [ ] 客户回复已记录在描述中
+- [ ] "未分类"列表为空
+- [ ] 已与客户确认参考重点
+
+全部打勾后,可以放心开始设计了!
+
+## 八、反馈与改进
+
+如果您在使用中发现:
+- 某类追问特别有效
+- AI的建议不够准确
+- 需要新的分类标签
+- 有更好的使用方法
+
+请及时反馈给开发团队,帮助我们持续改进!
+
+---
+
+**记住**:沟通成本 < 返工成本
+
+花10分钟明确需求,胜过花3小时重做!
+

+ 29 - 12
src/app/pages/customer-service/dashboard/pages/after-sales/after-sales.component.scss

@@ -1960,14 +1960,23 @@ $transition: all 0.2s ease;
   
   mat-icon {
     color: #007aff;
-    font-size: 18px;
-    width: 18px;
-    height: 18px;
+    font-size: 20px !important;
+    width: 20px !important;
+    height: 20px !important;
+    font-family: 'Material Icons' !important;
+    font-feature-settings: 'liga' 1;
+    -webkit-font-smoothing: antialiased;
+    text-rendering: optimizeLegibility;
+    display: inline-flex !important;
+    align-items: center;
+    justify-content: center;
+    overflow: hidden;
   }
   
   span {
     font-size: 14px;
     color: #333;
+    font-weight: 500;
   }
 }
 
@@ -1982,13 +1991,13 @@ $transition: all 0.2s ease;
   }
 }
 
-// 全局下拉面板样式 - 采用人事板块样式,增加透明度
+// 全局下拉面板样式 - 采用人事板块样式,调整透明度避免背景冲突
 :host ::ng-deep .mat-mdc-select-panel {
-  background: rgba(255, 255, 255, 0.85) !important;
-  backdrop-filter: blur(20px) !important;
+  background: rgba(255, 255, 255, 0.98) !important; // 提高不透明度从0.85到0.98
+  backdrop-filter: blur(30px) saturate(180%) !important; // 增强模糊和饱和度
   border-radius: 12px !important;
-  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15) !important;
-  border: 1px solid rgba(0, 122, 255, 0.2) !important;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18), 0 2px 8px rgba(0, 0, 0, 0.08) !important; // 增强阴影
+  border: 1px solid rgba(0, 122, 255, 0.3) !important; // 增强边框可见度
   max-height: 300px !important;
   z-index: 9999 !important;
   overflow: hidden !important;
@@ -2030,11 +2039,19 @@ $transition: all 0.2s ease;
       width: 100%;
 
       mat-icon {
-        font-size: 18px;
-        width: 18px;
-        height: 18px;
+        font-size: 20px !important;
+        width: 20px !important;
+        height: 20px !important;
         color: inherit;
-        opacity: 0.8;
+        opacity: 0.9;
+        font-family: 'Material Icons' !important; // 确保使用图标字体
+        font-feature-settings: 'liga' 1; // 启用连字特性
+        -webkit-font-smoothing: antialiased; // 字体平滑
+        text-rendering: optimizeLegibility;
+        display: inline-flex !important;
+        align-items: center;
+        justify-content: center;
+        overflow: hidden; // 隐藏溢出的文本
       }
 
       span {

+ 321 - 21
src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.ts

@@ -80,7 +80,6 @@ export class DesignerAssignmentComponent implements OnInit {
           teamId: 'team-1',
           teamName: '家装设计组',
           isTeamLeader: true,
-          // 为了兼容弹窗与日历组件,补充映射字段
           groupId: 'team-1',
           groupName: '家装设计组',
           isLeader: true,
@@ -102,13 +101,12 @@ export class DesignerAssignmentComponent implements OnInit {
           teamId: 'team-1',
           teamName: '家装设计组',
           isTeamLeader: false,
-          // 为了兼容弹窗与日历组件,补充映射字段
           groupId: 'team-1',
           groupName: '家装设计组',
           isLeader: false,
           currentProjects: 2,
-          status: 'busy',
-          idleDays: 0,
+          status: 'idle',
+          idleDays: 2,
           recentOrders: 1,
           lastOrderDate: '2024-01-10',
           reviewDates: [],
@@ -120,25 +118,45 @@ export class DesignerAssignmentComponent implements OnInit {
         },
         {
           id: 'designer-5',
-          name: '赵停滞',
+          name: '陈设计师',
           teamId: 'team-1',
           teamName: '家装设计组',
           isTeamLeader: false,
-          // 为了兼容弹窗与日历组件,补充映射字段
           groupId: 'team-1',
           groupName: '家装设计组',
           isLeader: false,
-          currentProjects: 4,
-          status: 'busy',
-          idleDays: 0,
+          currentProjects: 1,
+          status: 'idle',
+          idleDays: 5,
           recentOrders: 1,
-          lastOrderDate: '2023-12-20',
+          lastOrderDate: '2024-01-05',
           reviewDates: [],
-          workload: 90,
-          skills: ['家装设计'],
-          isOnStagnantProject: true,
-          isInStagnantProject: true,
-          availableDates: []
+          workload: 20,
+          skills: ['家装设计', '色彩搭配'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-18', '2024-01-19', '2024-01-20', '2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24', '2024-01-25']
+        },
+        {
+          id: 'designer-6',
+          name: '刘设计师',
+          teamId: 'team-1',
+          teamName: '家装设计组',
+          isTeamLeader: false,
+          groupId: 'team-1',
+          groupName: '家装设计组',
+          isLeader: false,
+          currentProjects: 2,
+          status: 'busy',
+          idleDays: 0,
+          recentOrders: 2,
+          lastOrderDate: '2024-01-12',
+          reviewDates: ['2024-01-21'],
+          workload: 65,
+          skills: ['家装设计', '现代风格'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-24', '2024-01-25']
         }
       ]
     },
@@ -154,7 +172,6 @@ export class DesignerAssignmentComponent implements OnInit {
           teamId: 'team-2',
           teamName: '工装设计组',
           isTeamLeader: true,
-          // 为了兼容弹窗与日历组件,补充映射字段
           groupId: 'team-2',
           groupName: '工装设计组',
           isLeader: true,
@@ -176,21 +193,304 @@ export class DesignerAssignmentComponent implements OnInit {
           teamId: 'team-2',
           teamName: '工装设计组',
           isTeamLeader: false,
-          // 为了兼容弹窗与日历组件,补充映射字段
           groupId: 'team-2',
           groupName: '工装设计组',
+          isLeader: false,
+          currentProjects: 1,
+          status: 'idle',
+          idleDays: 3,
+          recentOrders: 0,
+          lastOrderDate: '2024-01-03',
+          reviewDates: [],
+          workload: 15,
+          skills: ['工装设计', '效果图制作'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-18', '2024-01-19', '2024-01-20', '2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24', '2024-01-25', '2024-01-26']
+        },
+        {
+          id: 'designer-7',
+          name: '周设计师',
+          teamId: 'team-2',
+          teamName: '工装设计组',
+          isTeamLeader: false,
+          groupId: 'team-2',
+          groupName: '工装设计组',
+          isLeader: false,
+          currentProjects: 2,
+          status: 'busy',
+          idleDays: 0,
+          recentOrders: 1,
+          lastOrderDate: '2024-01-13',
+          reviewDates: ['2024-01-19'],
+          workload: 55,
+          skills: ['工装设计', '办公空间'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-22', '2024-01-23', '2024-01-24']
+        }
+      ]
+    },
+    {
+      id: 'team-3',
+      name: '软装设计组',
+      leaderId: 'designer-8',
+      leaderName: '吴组长',
+      members: [
+        {
+          id: 'designer-8',
+          name: '吴组长',
+          teamId: 'team-3',
+          teamName: '软装设计组',
+          isTeamLeader: true,
+          groupId: 'team-3',
+          groupName: '软装设计组',
           isLeader: true,
           currentProjects: 2,
-          status: 'reviewing',
+          status: 'busy',
           idleDays: 0,
+          recentOrders: 2,
+          lastOrderDate: '2024-01-16',
+          reviewDates: ['2024-01-21', '2024-01-24'],
+          workload: 75,
+          skills: ['软装设计', '色彩搭配', '项目管理'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-19', '2024-01-20']
+        },
+        {
+          id: 'designer-9',
+          name: '郑设计师',
+          teamId: 'team-3',
+          teamName: '软装设计组',
+          isTeamLeader: false,
+          groupId: 'team-3',
+          groupName: '软装设计组',
+          isLeader: false,
+          currentProjects: 1,
+          status: 'idle',
+          idleDays: 4,
+          recentOrders: 1,
+          lastOrderDate: '2024-01-08',
+          reviewDates: [],
+          workload: 25,
+          skills: ['软装设计', '家具搭配'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-18', '2024-01-19', '2024-01-20', '2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24', '2024-01-25']
+        },
+        {
+          id: 'designer-10',
+          name: '冯设计师',
+          teamId: 'team-3',
+          teamName: '软装设计组',
+          isTeamLeader: false,
+          groupId: 'team-3',
+          groupName: '软装设计组',
+          isLeader: false,
+          currentProjects: 2,
+          status: 'busy',
+          idleDays: 0,
+          recentOrders: 2,
+          lastOrderDate: '2024-01-15',
+          reviewDates: [],
+          workload: 60,
+          skills: ['软装设计', '陈设设计'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-23', '2024-01-24', '2024-01-25']
+        }
+      ]
+    },
+    {
+      id: 'team-4',
+      name: '建模渲染组',
+      leaderId: 'designer-11',
+      leaderName: '孙组长',
+      members: [
+        {
+          id: 'designer-11',
+          name: '孙组长',
+          teamId: 'team-4',
+          teamName: '建模渲染组',
+          isTeamLeader: true,
+          groupId: 'team-4',
+          groupName: '建模渲染组',
+          isLeader: true,
+          currentProjects: 3,
+          status: 'busy',
+          idleDays: 0,
+          recentOrders: 3,
+          lastOrderDate: '2024-01-16',
+          reviewDates: ['2024-01-20', '2024-01-23'],
+          workload: 80,
+          skills: ['3D建模', 'VRay渲染', '项目管理'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-21', '2024-01-22']
+        },
+        {
+          id: 'designer-12',
+          name: '钱设计师',
+          teamId: 'team-4',
+          teamName: '建模渲染组',
+          isTeamLeader: false,
+          groupId: 'team-4',
+          groupName: '建模渲染组',
+          isLeader: false,
+          currentProjects: 2,
+          status: 'idle',
+          idleDays: 1,
+          recentOrders: 2,
+          lastOrderDate: '2024-01-12',
+          reviewDates: [],
+          workload: 40,
+          skills: ['3D建模', 'Corona渲染'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-18', '2024-01-19', '2024-01-20', '2024-01-21', '2024-01-22', '2024-01-23']
+        },
+        {
+          id: 'designer-13',
+          name: '何设计师',
+          teamId: 'team-4',
+          teamName: '建模渲染组',
+          isTeamLeader: false,
+          groupId: 'team-4',
+          groupName: '建模渲染组',
+          isLeader: false,
+          currentProjects: 1,
+          status: 'idle',
+          idleDays: 6,
           recentOrders: 0,
-          lastOrderDate: '2024-01-03',
+          lastOrderDate: '2024-01-01',
           reviewDates: [],
           workload: 10,
-          skills: ['工装设计', '效果图制作'],
+          skills: ['3D建模', '材质贴图'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-18', '2024-01-19', '2024-01-20', '2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24', '2024-01-25', '2024-01-26']
+        }
+      ]
+    },
+    {
+      id: 'team-5',
+      name: '后期处理组',
+      leaderId: 'designer-14',
+      leaderName: '朱组长',
+      members: [
+        {
+          id: 'designer-14',
+          name: '朱组长',
+          teamId: 'team-5',
+          teamName: '后期处理组',
+          isTeamLeader: true,
+          groupId: 'team-5',
+          groupName: '后期处理组',
+          isLeader: true,
+          currentProjects: 2,
+          status: 'busy',
+          idleDays: 0,
+          recentOrders: 2,
+          lastOrderDate: '2024-01-15',
+          reviewDates: ['2024-01-19', '2024-01-23'],
+          workload: 70,
+          skills: ['后期处理', 'Photoshop', '项目管理'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-20', '2024-01-21', '2024-01-22']
+        },
+        {
+          id: 'designer-15',
+          name: '徐设计师',
+          teamId: 'team-5',
+          teamName: '后期处理组',
+          isTeamLeader: false,
+          groupId: 'team-5',
+          groupName: '后期处理组',
+          isLeader: false,
+          currentProjects: 2,
+          status: 'busy',
+          idleDays: 0,
+          recentOrders: 1,
+          lastOrderDate: '2024-01-14',
+          reviewDates: [],
+          workload: 50,
+          skills: ['后期处理', '图片精修'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-22', '2024-01-23', '2024-01-24']
+        },
+        {
+          id: 'designer-16',
+          name: '许设计师',
+          teamId: 'team-5',
+          teamName: '后期处理组',
+          isTeamLeader: false,
+          groupId: 'team-5',
+          groupName: '后期处理组',
+          isLeader: false,
+          currentProjects: 1,
+          status: 'idle',
+          idleDays: 3,
+          recentOrders: 1,
+          lastOrderDate: '2024-01-10',
+          reviewDates: [],
+          workload: 35,
+          skills: ['后期处理', '色彩校正'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-19', '2024-01-20', '2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24', '2024-01-25']
+        }
+      ]
+    },
+    {
+      id: 'team-6',
+      name: '全景图制作组',
+      leaderId: 'designer-17',
+      leaderName: '马组长',
+      members: [
+        {
+          id: 'designer-17',
+          name: '马组长',
+          teamId: 'team-6',
+          teamName: '全景图制作组',
+          isTeamLeader: true,
+          groupId: 'team-6',
+          groupName: '全景图制作组',
+          isLeader: true,
+          currentProjects: 3,
+          status: 'busy',
+          idleDays: 0,
+          recentOrders: 3,
+          lastOrderDate: '2024-01-16',
+          reviewDates: ['2024-01-20', '2024-01-22', '2024-01-25'],
+          workload: 85,
+          skills: ['全景图合成', 'KR Panel', '项目管理'],
+          isOnStagnantProject: false,
+          isInStagnantProject: false,
+          availableDates: ['2024-01-21']
+        },
+        {
+          id: 'designer-18',
+          name: '韩设计师',
+          teamId: 'team-6',
+          teamName: '全景图制作组',
+          isTeamLeader: false,
+          groupId: 'team-6',
+          groupName: '全景图制作组',
+          isLeader: false,
+          currentProjects: 1,
+          status: 'idle',
+          idleDays: 2,
+          recentOrders: 1,
+          lastOrderDate: '2024-01-11',
+          reviewDates: [],
+          workload: 30,
+          skills: ['全景图合成', '图片拼接'],
           isOnStagnantProject: false,
           isInStagnantProject: false,
-          availableDates: ['2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24', '2024-01-25', '2024-01-26']
+          availableDates: ['2024-01-18', '2024-01-19', '2024-01-20', '2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24']
         }
       ]
     }

+ 26 - 33
src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.html

@@ -74,38 +74,30 @@
         @for (day of monthGrid; track day.date.getTime()) {
           <div class="calendar-day" 
                [class]="getDayClass(day)"
-               [title]="day.tooltip"
+               [attr.data-status]="getDayStatus(day)"
+               [title]="getDayTooltip(day)"
                (click)="onDayClick(day)">
             
             <!-- 日期数字 -->
             <div class="day-number">{{ day.date.getDate() }}</div>
             
-            <!-- 工作状态指示器 -->
-            <div class="day-status">
+            <!-- 工作状态文本 -->
+            <div class="day-status-text">
               @if (day.isReviewDay) {
-                <div class="status-indicator review">
-                  <mat-icon>visibility</mat-icon>
-                  <span>对图</span>
-                </div>
+                <span class="status-label review-label">对图</span>
               } @else if (day.orderCount > 0) {
-                <div class="status-indicator busy">
-                  <mat-icon>work</mat-icon>
-                  <span>{{ day.orderCount }}单</span>
-                </div>
+                <span class="status-label busy-label">{{ day.orderCount }}单</span>
               } @else if (day.isIdle && day.isCurrentMonth) {
-                <div class="status-indicator idle">
-                  <mat-icon>check_circle</mat-icon>
-                  <span>空闲</span>
-                </div>
+                <span class="status-label idle-label">空闲</span>
               }
             </div>
             
-            <!-- 工作负荷条 -->
+            <!-- 工作负荷指示条 -->
             @if (day.isCurrentMonth && (day.orderCount > 0 || day.isReviewDay)) {
-              <div class="workload-bar-mini">
-                <div class="workload-fill-mini" 
-                     [class]="day.isReviewDay ? 'review' : (day.orderCount >= 2 ? 'high' : 'medium')"
-                     [style.width.%]="day.isReviewDay ? 100 : (day.orderCount * 50)">
+              <div class="workload-indicator-bar">
+                <div class="workload-fill" 
+                     [class]="day.isReviewDay ? 'review-fill' : (day.orderCount >= 2 ? 'high-fill' : 'medium-fill')"
+                     [style.width.%]="day.isReviewDay ? 100 : Math.min(day.orderCount * 40, 100)">
                 </div>
               </div>
             }
@@ -117,31 +109,32 @@
     <!-- 图例说明 -->
     <div class="calendar-legend">
       <div class="legend-item">
-        <span class="legend-color idle"></span>
-        <span class="legend-text">空闲可分配</span>
+        <span class="legend-color idle-color"></span>
+        <span class="legend-text">空闲可分配(绿色)</span>
       </div>
       <div class="legend-item">
-        <span class="legend-color busy"></span>
-        <span class="legend-text">有订单</span>
+        <span class="legend-color busy-color"></span>
+        <span class="legend-text">有订单工作中(蓝色)</span>
       </div>
       <div class="legend-item">
-        <span class="legend-color review"></span>
-        <span class="legend-text">对图日期</span>
+        <span class="legend-color review-color"></span>
+        <span class="legend-text">对图日期不可分配(红色)</span>
       </div>
       <div class="legend-item">
-        <span class="legend-color today"></span>
-        <span class="legend-text">今天</span>
+        <span class="legend-color today-color"></span>
+        <span class="legend-text">今天(橙色边框)</span>
       </div>
     </div>
 
     <!-- 操作提示 -->
     <div class="calendar-tips">
-      <p><strong>使用说明:</strong></p>
+      <p><strong>颜色说明:</strong></p>
       <ul>
-        <li>🟢 绿色日期表示空闲,可以分配新订单</li>
-        <li>🔵 蓝色日期表示有订单,工作负荷适中</li>
-        <li>🔴 红色日期表示对图日期,完全不能分配其他工作</li>
-        <li>📊 底部进度条显示当日工作负荷强度</li>
+        <li>🟢 <strong>绿色背景</strong> - 空闲日期,可以分配新订单</li>
+        <li>🔵 <strong>蓝色背景</strong> - 有订单工作中,根据订单数量显示深浅</li>
+        <li>🔴 <strong>红色背景</strong> - 对图日期,完全不能分配其他工作</li>
+        <li>🟠 <strong>橙色边框</strong> - 今天的日期</li>
+        <li>📊 <strong>底部彩条</strong> - 工作负荷强度指示</li>
         <li>👆 点击空闲日期可以快速分配订单</li>
       </ul>
     </div>

+ 190 - 42
src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.scss

@@ -348,34 +348,40 @@
 .calendar-day {
   box-sizing: border-box;
   background: white;
-  min-height: 100px; // 提升可读性,确保忙闲标识完整展示
+  min-height: 100px;
   padding: 8px;
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: flex-start;
   cursor: pointer;
-  transition: all 0.2s ease;
+  transition: all 0.3s ease;
   position: relative;
+  border-radius: 8px;
+  border: 2px solid transparent;
 
   .day-number {
     font-size: 16px;
     font-weight: 600;
-    margin-bottom: 8px;
+    margin-bottom: 6px;
     z-index: 2;
+    color: #2c3e50;
   }
 
-  .day-status {
+  .day-status-text {
     flex: 1;
     display: flex;
     align-items: center;
     justify-content: center;
     width: 100%;
+    margin-bottom: 4px;
 
-    .status-indicator {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
+    .status-label {
+      font-size: 12px;
+      font-weight: 500;
+      padding: 2px 8px;
+      border-radius: 12px;
+      color: white;
       gap: 4px;
       padding: 4px;
       border-radius: 6px;
@@ -417,31 +423,61 @@
     }
   }
 
+  .workload-indicator-bar {
+    position: absolute;
+    bottom: 4px;
+    left: 8px;
+    right: 8px;
+    height: 4px;
+    background: rgba(0, 0, 0, 0.1);
+    border-radius: 3px;
+    overflow: hidden;
+
+    .workload-fill {
+      height: 100%;
+      border-radius: 3px;
+      transition: width 0.3s ease;
+
+      &.medium-fill {
+        background: linear-gradient(90deg, #ffc107 0%, #ffb300 100%);
+      }
+
+      &.high-fill {
+        background: linear-gradient(90deg, #ff9800 0%, #f57c00 100%);
+      }
+
+      &.review-fill {
+        background: linear-gradient(90deg, #dc3545 0%, #b02a37 100%);
+      }
+    }
+  }
+
+  // 保留旧样式兼容
   .workload-bar-mini {
     position: absolute;
     bottom: 4px;
-    left: 4px;
-    right: 4px;
-    height: 3px;
+    left: 8px;
+    right: 8px;
+    height: 4px;
     background: rgba(0, 0, 0, 0.1);
-    border-radius: 2px;
+    border-radius: 3px;
     overflow: hidden;
 
     .workload-fill-mini {
       height: 100%;
-      border-radius: 2px;
+      border-radius: 3px;
       transition: width 0.3s ease;
 
       &.medium {
-        background: #ffc107;
+        background: linear-gradient(90deg, #ffc107 0%, #ffb300 100%);
       }
 
       &.high {
-        background: #ff9800;
+        background: linear-gradient(90deg, #ff9800 0%, #f57c00 100%);
       }
 
       &.review {
-        background: #f44336;
+        background: linear-gradient(90deg, #dc3545 0%, #b02a37 100%);
       }
     }
   }
@@ -461,56 +497,143 @@
   }
 
   &.today {
-    background: #e3f2fd;
-    border: 2px solid #2196f3;
+    border: 3px solid #ff6b35 !important;
+    box-shadow: 0 0 0 2px rgba(255, 107, 53, 0.2);
+
+    .day-number {
+      color: #ff6b35;
+      font-weight: 700;
+    }
+  }
+
+  // 使用 data-status 属性进行样式控制
+  &[data-status="idle"] {
+    background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
+    border: 2px solid #28a745;
+
+    &:hover {
+      background: linear-gradient(135deg, #c3e6cb 0%, #b1dfbb 100%);
+      transform: scale(1.05);
+      box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4);
+    }
 
     .day-number {
-      color: #1976d2;
+      color: #155724;
       font-weight: 700;
     }
+
+    .status-label.idle-label {
+      background: #28a745;
+      color: white;
+    }
   }
 
+  &[data-status="busy-medium"] {
+    background: linear-gradient(135deg, #cfe2ff 0%, #b6d4fe 100%);
+    border: 2px solid #0d6efd;
+
+    .day-number {
+      color: #084298;
+      font-weight: 700;
+    }
+
+    .status-label.busy-label {
+      background: #0d6efd;
+      color: white;
+    }
+
+    &:hover {
+      background: linear-gradient(135deg, #b6d4fe 0%, #9ec5fe 100%);
+      transform: scale(1.05);
+      box-shadow: 0 6px 20px rgba(13, 110, 253, 0.4);
+    }
+  }
+
+  &[data-status="busy-high"] {
+    background: linear-gradient(135deg, #9ec5fe 0%, #6ea8fe 100%);
+    border: 2px solid #0a58ca;
+
+    .day-number {
+      color: #052c65;
+      font-weight: 700;
+    }
+
+    .status-label.busy-label {
+      background: #0a58ca;
+      color: white;
+    }
+
+    &:hover {
+      background: linear-gradient(135deg, #6ea8fe 0%, #3d8bfd 100%);
+      transform: scale(1.05);
+      box-shadow: 0 6px 20px rgba(10, 88, 202, 0.5);
+    }
+  }
+
+  &[data-status="review"] {
+    background: linear-gradient(135deg, #f8d7da 0%, #f5c2c7 100%);
+    border: 2px solid #dc3545;
+    cursor: not-allowed;
+
+    .day-number {
+      color: #842029;
+      font-weight: 700;
+    }
+
+    .status-label.review-label {
+      background: #dc3545;
+      color: white;
+    }
+
+    &:hover {
+      transform: none;
+      box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
+    }
+  }
+
+  // 保留旧的类名兼容性
   &.idle {
-    background: #e8f5e8;
-    border: 1px solid #4caf50;
+    background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
+    border: 2px solid #28a745;
 
     &:hover {
-      background: #c8e6c9;
-      transform: scale(1.02);
-      box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
+      background: linear-gradient(135deg, #c3e6cb 0%, #b1dfbb 100%);
+      transform: scale(1.05);
+      box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4);
     }
 
     .day-number {
-      color: #2e7d32;
+      color: #155724;
     }
   }
 
   &.busy {
-    background: #e3f2fd;
+    background: linear-gradient(135deg, #cfe2ff 0%, #b6d4fe 100%);
+    border: 2px solid #0d6efd;
 
     .day-number {
-      color: #1565c0;
+      color: #084298;
     }
 
     &:hover {
-      background: #bbdefb;
-      transform: scale(1.02);
-      box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3);
+      background: linear-gradient(135deg, #b6d4fe 0%, #9ec5fe 100%);
+      transform: scale(1.05);
+      box-shadow: 0 6px 20px rgba(13, 110, 253, 0.4);
     }
   }
 
   &.review {
-    background: #ffebee;
-    border: 1px solid #f44336;
+    background: linear-gradient(135deg, #f8d7da 0%, #f5c2c7 100%);
+    border: 2px solid #dc3545;
     cursor: not-allowed;
 
     .day-number {
-      color: #c62828;
+      color: #842029;
     }
 
     &:hover {
       transform: none;
-      box-shadow: 0 2px 8px rgba(244, 67, 54, 0.2);
+      box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
     }
   }
 
@@ -586,25 +709,50 @@
     gap: 6px;
 
     .legend-color {
-      width: 12px;
-      height: 12px;
-      border-radius: 2px;
+      width: 20px;
+      height: 20px;
+      border-radius: 4px;
+      border: 1px solid rgba(0, 0, 0, 0.1);
+
+      &.idle-color {
+        background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
+        border: 2px solid #28a745;
+      }
+
+      &.busy-color {
+        background: linear-gradient(135deg, #cfe2ff 0%, #b6d4fe 100%);
+        border: 2px solid #0d6efd;
+      }
+
+      &.review-color {
+        background: linear-gradient(135deg, #f8d7da 0%, #f5c2c7 100%);
+        border: 2px solid #dc3545;
+      }
+
+      &.today-color {
+        background: white;
+        border: 3px solid #ff6b35;
+      }
 
+      // 保留旧样式兼容
       &.idle {
-        background: #4caf50;
+        background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
+        border: 2px solid #28a745;
       }
 
       &.busy {
-        background: #2196f3;
+        background: linear-gradient(135deg, #cfe2ff 0%, #b6d4fe 100%);
+        border: 2px solid #0d6efd;
       }
 
       &.review {
-        background: #f44336;
+        background: linear-gradient(135deg, #f8d7da 0%, #f5c2c7 100%);
+        border: 2px solid #dc3545;
       }
 
       &.today {
-        background: #1976d2;
-        border: 1px solid #0d47a1;
+        background: white;
+        border: 3px solid #ff6b35;
       }
     }
 

+ 36 - 0
src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.ts

@@ -280,4 +280,40 @@ export class DesignerCalendarComponent implements OnInit, OnChanges {
     }
     return '空闲可分配新订单';
   }
+
+  // 获取日期状态(用于样式)
+  getDayStatus(day: CalendarDay): string {
+    if (!day.isCurrentMonth) return 'other-month';
+    if (day.isReviewDay) return 'review';
+    if (day.orderCount >= 2) return 'busy-high';
+    if (day.orderCount > 0) return 'busy-medium';
+    if (day.isIdle) return 'idle';
+    return 'normal';
+  }
+
+  // 获取详细的 tooltip
+  getDayTooltip(day: CalendarDay): string {
+    if (!day.isCurrentMonth) return '';
+    
+    const parts: string[] = [];
+    
+    if (day.isToday) {
+      parts.push('【今天】');
+    }
+    
+    if (day.isReviewDay) {
+      parts.push('🔴 对图日期 - 完全不可分配');
+    } else if (day.orderCount >= 2) {
+      parts.push(`🔵 已有${day.orderCount}个订单 - 工作负荷高`);
+    } else if (day.orderCount === 1) {
+      parts.push(`🔵 已有1个订单 - 可继续分配`);
+    } else if (day.isIdle) {
+      parts.push('🟢 空闲 - 可分配新订单');
+    }
+    
+    return parts.join(' ');
+  }
+
+  // 导出 Math 对象供模板使用
+  Math = Math;
 }

+ 161 - 0
src/app/pages/designer/project-detail/components/reference-image-manager/README.md

@@ -0,0 +1,161 @@
+# 参考图管理组件
+
+## 功能概述
+
+参考图管理组件是为了解决设计师与客户之间沟通不畅的问题而设计的。它帮助设计师更好地理解和管理客户提供的参考图,确保设计方向准确无误。
+
+## 核心功能
+
+### 1. 参考图分类管理
+
+支持四种主要分类:
+- **灯光参考** 💡:用于参考灯光布局和照明效果
+- **软装参考** 🛋️:用于参考家具、装饰品等软装元素
+- **氛围参考** ✨:用于参考整体氛围和感觉
+- **结构参考** 📐:用于参考空间结构和布局
+
+每张参考图可以设置多个分类标签,便于快速筛选和查找。
+
+### 2. 详细描述与标注
+
+- **图片名称**:为每张参考图命名
+- **分类标签**:可多选,精确标记参考用途
+- **描述说明**:详细描述参考哪些元素
+- **关键要素**:添加标签标记重点元素(如"吊灯造型"、"暖色调"等)
+
+### 3. AI智能追问助手
+
+根据参考图数量和完整度,AI会自动提供专业的追问建议:
+
+#### 参考图较多时(≥3张)
+- 明确哪张图用于灯光参考
+- 区分软装参考图
+- 确认氛围感觉的主要参考
+- 说明结构布局依据
+
+#### 参考图较少时(<3张)
+- 细化感觉来源
+- 明确重点元素
+- 探索细节要求
+- 确认可调整空间
+
+### 4. 视图模式切换
+
+- **网格视图**:卡片式展示,适合快速浏览
+- **列表视图**:表格式展示,适合查看详细信息
+
+### 5. 分类筛选
+
+快速筛选特定类型的参考图:
+- 全部
+- 灯光参考
+- 软装参考
+- 氛围参考
+- 结构参考
+- 未分类(需要补充信息的图片)
+
+## 使用场景
+
+### 场景1:参考图较多的情况
+
+**问题**:客户提供了10张参考图,但没有说明每张图的具体用途。
+
+**解决方案**:
+1. 点击每张图片右下角的"AI智能追问"按钮
+2. AI会根据图片数量提供针对性的追问建议
+3. 选择合适的追问问题发送给客户
+4. 根据客户回复,为每张图添加正确的分类标签和描述
+
+### 场景2:参考图描述模糊
+
+**问题**:客户只说"我喜欢这个感觉",但不清楚具体喜欢什么。
+
+**解决方案**:
+1. 使用AI追问助手中的"细化感觉来源"问题
+2. 询问客户具体喜欢的方面(色调、光线、家具搭配等)
+3. 将客户的回复记录在描述字段中
+4. 添加关键要素标签,便于后续参考
+
+### 场景3:效果图与预期不符
+
+**问题**:做出的效果图客户不满意,觉得和参考图不一样。
+
+**解决方案**:
+1. 回到参考图管理页面
+2. 检查每张参考图的分类和描述是否完整
+3. 对未明确的参考图使用AI追问补充信息
+4. 与客户确认每张图的具体参考点
+5. 根据明确的参考依据重新调整设计
+
+## 最佳实践
+
+### 1. 上传参考图后立即分类
+
+不要等到开始设计时才整理参考图,上传后立即进行分类和标注。
+
+### 2. 充分利用AI追问
+
+遇到模糊的参考图时,不要猜测客户的意图,使用AI追问获取明确信息。
+
+### 3. 详细记录客户反馈
+
+将客户对参考图的说明详细记录在描述字段中,避免遗忘重要信息。
+
+### 4. 使用关键要素标签
+
+为重要元素添加标签,便于快速定位设计重点。
+
+### 5. 定期回顾参考图
+
+在设计过程中定期回顾参考图管理页面,确保设计方向正确。
+
+## 技术特性
+
+- **响应式设计**:支持桌面端和移动端访问
+- **拖拽上传**:支持拖拽文件到上传区域
+- **批量操作**:可以一次上传多张图片
+- **实时筛选**:分类标签点击即时筛选
+- **本地预览**:上传前即可预览图片
+
+## 数据结构
+
+```typescript
+interface ReferenceImage {
+  id: string;                    // 图片ID
+  name: string;                  // 图片名称
+  url: string;                   // 图片URL
+  categories: string[];          // 分类数组
+  description?: string;          // 描述说明
+  tags?: string[];              // 关键要素标签
+  uploadDate: Date;             // 上传日期
+}
+```
+
+## FAQ
+
+**Q: 一张图可以设置多个分类吗?**
+A: 可以。有些参考图可能同时参考多个方面,比如既参考灯光又参考氛围。
+
+**Q: AI追问的问题可以自定义吗?**
+A: 可以。除了AI建议的问题,还可以在"自定义追问"区域输入自己的问题。
+
+**Q: 如果客户没有提供参考图怎么办?**
+A: 可以从案例库中选择相似的案例作为参考,或者主动向客户索要参考图。
+
+**Q: 参考图会自动保存到项目文件中吗?**
+A: 是的,上传的参考图会自动关联到当前项目,可以在项目文件标签页中查看。
+
+## 更新日志
+
+### v1.0.0 (2024-01)
+- ✅ 基础参考图管理功能
+- ✅ 四种分类标签支持
+- ✅ AI智能追问助手
+- ✅ 网格/列表视图切换
+- ✅ 拖拽上传功能
+- ✅ 响应式设计
+
+## 反馈与建议
+
+如果您在使用过程中遇到问题或有改进建议,请联系开发团队。
+

+ 154 - 0
src/app/pages/designer/project-detail/components/reference-image-manager/icons.scss

@@ -0,0 +1,154 @@
+/* 参考图管理组件图标样式 */
+
+/* 使用伪元素实现简单的图标效果 */
+i[class^="icon-"] {
+  display: inline-block;
+  width: 1em;
+  height: 1em;
+  position: relative;
+}
+
+/* 图片图标 */
+.icon-images::before {
+  content: '🖼️';
+}
+
+.icon-image::before {
+  content: '🖼️';
+}
+
+/* 网格图标 */
+.icon-grid::before {
+  content: '▦';
+  font-weight: bold;
+}
+
+/* 列表图标 */
+.icon-list::before {
+  content: '☰';
+  font-weight: bold;
+}
+
+/* 上传图标 */
+.icon-upload::before {
+  content: '⬆';
+}
+
+/* 灯泡/灯光图标 */
+.icon-lightbulb::before {
+  content: '💡';
+}
+
+/* 沙发/软装图标 */
+.icon-couch::before {
+  content: '🛋️';
+}
+
+/* 星星/氛围图标 */
+.icon-stars::before {
+  content: '✨';
+}
+
+/* 立方体/结构图标 */
+.icon-cube::before {
+  content: '📐';
+}
+
+/* 警告图标 */
+.icon-alert::before {
+  content: '⚠️';
+}
+
+/* 编辑图标 */
+.icon-edit::before {
+  content: '✏️';
+}
+
+/* 删除图标 */
+.icon-delete::before {
+  content: '🗑️';
+}
+
+/* 关闭图标 */
+.icon-close::before {
+  content: '×';
+  font-size: 1.5em;
+  font-weight: bold;
+}
+
+/* 云上传图标 */
+.icon-cloud-upload::before {
+  content: '☁️';
+  font-size: 2em;
+}
+
+/* 信息图标 */
+.icon-info::before {
+  content: 'ℹ️';
+}
+
+/* 检查圆圈图标 */
+.icon-check-circle::before {
+  content: '✓';
+  font-weight: bold;
+}
+
+/* 警告圆圈图标 */
+.icon-alert-circle::before {
+  content: '!';
+  font-weight: bold;
+}
+
+/* 帮助圆圈图标 */
+.icon-help-circle::before {
+  content: '?';
+  font-weight: bold;
+}
+
+/* 闪光/AI图标 */
+.icon-sparkles::before {
+  content: '✨';
+}
+
+/* 箭头右图标 */
+.icon-arrow-right::before {
+  content: '→';
+  font-weight: bold;
+}
+
+/* 消息圆圈图标 */
+.icon-message-circle::before {
+  content: '💬';
+}
+
+/* 发送图标 */
+.icon-send::before {
+  content: '📤';
+}
+
+/* 检查图标 */
+.icon-check::before {
+  content: '✓';
+  font-weight: bold;
+}
+
+/* 心形图标 */
+.icon-heart::before {
+  content: '❤️';
+}
+
+/* 目标图标 */
+.icon-target::before {
+  content: '🎯';
+}
+
+/* 放大图标 */
+.icon-zoom-in::before {
+  content: '🔍';
+}
+
+/* 滑块图标 */
+.icon-sliders::before {
+  content: '🎛️';
+}
+

+ 475 - 0
src/app/pages/designer/project-detail/components/reference-image-manager/reference-image-manager.component.html

@@ -0,0 +1,475 @@
+<div class="reference-image-manager">
+  <!-- 顶部标题和操作栏 -->
+  <div class="manager-header">
+    <div class="header-left">
+      <h2 class="manager-title">
+        <i class="icon-images"></i>
+        参考图管理
+      </h2>
+      <span class="image-count">共 {{ referenceImages.length }} 张参考图</span>
+    </div>
+    <div class="header-right">
+      <button class="btn-view-mode" [class.active]="viewMode === 'grid'" (click)="viewMode = 'grid'">
+        <i class="icon-grid"></i>
+        网格视图
+      </button>
+      <button class="btn-view-mode" [class.active]="viewMode === 'list'" (click)="viewMode = 'list'">
+        <i class="icon-list"></i>
+        列表视图
+      </button>
+      <button class="btn-upload" (click)="openUploadDialog()">
+        <i class="icon-upload"></i>
+        上传参考图
+      </button>
+    </div>
+  </div>
+
+  <!-- 分类筛选标签 -->
+  <div class="category-filters">
+    <button 
+      class="filter-tag" 
+      [class.active]="activeFilter === 'all'"
+      (click)="filterByCategory('all')">
+      全部 ({{ referenceImages.length }})
+    </button>
+    <button 
+      class="filter-tag filter-tag-lighting" 
+      [class.active]="activeFilter === 'lighting'"
+      (click)="filterByCategory('lighting')">
+      <i class="icon-lightbulb"></i>
+      灯光参考 ({{ getCategoryCount('lighting') }})
+    </button>
+    <button 
+      class="filter-tag filter-tag-furniture" 
+      [class.active]="activeFilter === 'furniture'"
+      (click)="filterByCategory('furniture')">
+      <i class="icon-couch"></i>
+      软装参考 ({{ getCategoryCount('furniture') }})
+    </button>
+    <button 
+      class="filter-tag filter-tag-atmosphere" 
+      [class.active]="activeFilter === 'atmosphere'"
+      (click)="filterByCategory('atmosphere')">
+      <i class="icon-stars"></i>
+      氛围参考 ({{ getCategoryCount('atmosphere') }})
+    </button>
+    <button 
+      class="filter-tag filter-tag-structure" 
+      [class.active]="activeFilter === 'structure'"
+      (click)="filterByCategory('structure')">
+      <i class="icon-cube"></i>
+      结构参考 ({{ getCategoryCount('structure') }})
+    </button>
+    <button 
+      class="filter-tag filter-tag-uncategorized" 
+      [class.active]="activeFilter === 'uncategorized'"
+      (click)="filterByCategory('uncategorized')">
+      <i class="icon-alert"></i>
+      未分类 ({{ getUncategorizedCount() }})
+    </button>
+  </div>
+
+  <!-- 网格视图 -->
+  <div class="images-grid" *ngIf="viewMode === 'grid'">
+    <div class="image-card" *ngFor="let image of filteredImages; let i = index" 
+         [class.selected]="selectedImageId === image.id"
+         (click)="selectImage(image)">
+      <div class="image-container">
+        <img [src]="image.url" [alt]="image.name" />
+        <div class="image-overlay">
+          <button class="btn-icon" (click)="editImage(image, $event)">
+            <i class="icon-edit"></i>
+          </button>
+          <button class="btn-icon" (click)="deleteImage(image, $event)">
+            <i class="icon-delete"></i>
+          </button>
+        </div>
+      </div>
+      <div class="image-info">
+        <h4 class="image-name">{{ image.name }}</h4>
+        <div class="image-categories">
+          <span *ngFor="let cat of image.categories" 
+                class="category-badge" 
+                [class]="'category-' + cat">
+            {{ getCategoryLabel(cat) }}
+          </span>
+          <span *ngIf="!image.categories || image.categories.length === 0" 
+                class="category-badge category-uncategorized">
+            未分类
+          </span>
+        </div>
+        <p class="image-description" *ngIf="image.description">
+          {{ image.description }}
+        </p>
+        <button class="btn-ai-assist" 
+                *ngIf="!image.description || !image.categories || image.categories.length === 0"
+                (click)="openAIAssistant(image, $event)">
+          <i class="icon-sparkles"></i>
+          AI智能追问
+        </button>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <div class="empty-state" *ngIf="filteredImages.length === 0">
+      <div class="empty-icon">
+        <i class="icon-image"></i>
+      </div>
+      <h3>暂无参考图</h3>
+      <p>点击上传按钮添加参考图,让沟通更清晰</p>
+      <button class="btn-primary" (click)="openUploadDialog()">
+        <i class="icon-upload"></i>
+        上传第一张参考图
+      </button>
+    </div>
+  </div>
+
+  <!-- 列表视图 -->
+  <div class="images-list" *ngIf="viewMode === 'list'">
+    <div class="list-header">
+      <div class="col-preview">预览</div>
+      <div class="col-name">图片名称</div>
+      <div class="col-categories">分类标签</div>
+      <div class="col-description">描述说明</div>
+      <div class="col-actions">操作</div>
+    </div>
+    <div class="list-row" *ngFor="let image of filteredImages" 
+         [class.selected]="selectedImageId === image.id">
+      <div class="col-preview">
+        <img [src]="image.url" [alt]="image.name" (click)="previewImage(image)" />
+      </div>
+      <div class="col-name">{{ image.name }}</div>
+      <div class="col-categories">
+        <span *ngFor="let cat of image.categories" 
+              class="category-badge" 
+              [class]="'category-' + cat">
+          {{ getCategoryLabel(cat) }}
+        </span>
+        <span *ngIf="!image.categories || image.categories.length === 0" 
+              class="category-badge category-uncategorized">
+          未分类
+        </span>
+      </div>
+      <div class="col-description">
+        <p *ngIf="image.description">{{ image.description }}</p>
+        <span class="text-muted" *ngIf="!image.description">暂无描述</span>
+      </div>
+      <div class="col-actions">
+        <button class="btn-icon" (click)="editImage(image, $event)" title="编辑">
+          <i class="icon-edit"></i>
+        </button>
+        <button class="btn-icon" (click)="openAIAssistant(image, $event)" title="AI追问">
+          <i class="icon-sparkles"></i>
+        </button>
+        <button class="btn-icon" (click)="deleteImage(image, $event)" title="删除">
+          <i class="icon-delete"></i>
+        </button>
+      </div>
+    </div>
+
+    <!-- 空状态 -->
+    <div class="empty-state" *ngIf="filteredImages.length === 0">
+      <div class="empty-icon">
+        <i class="icon-image"></i>
+      </div>
+      <h3>暂无{{ getFilterLabel() }}参考图</h3>
+      <p>切换其他分类或上传新的参考图</p>
+    </div>
+  </div>
+
+  <!-- 上传对话框 -->
+  <div class="modal-overlay" *ngIf="showUploadDialog" (click)="closeUploadDialog()">
+    <div class="modal-dialog" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>上传参考图</h3>
+        <button class="btn-close" (click)="closeUploadDialog()">
+          <i class="icon-close"></i>
+        </button>
+      </div>
+      <div class="modal-body">
+        <div class="upload-area" 
+             [class.dragging]="isDragging"
+             (drop)="onDrop($event)"
+             (dragover)="onDragOver($event)"
+             (dragleave)="onDragLeave($event)">
+          <i class="icon-cloud-upload"></i>
+          <p>拖拽图片到此处,或点击选择文件</p>
+          <input type="file" 
+                 #fileInput 
+                 multiple 
+                 accept="image/*" 
+                 (change)="onFileSelected($event)"
+                 style="display: none;" />
+          <button class="btn-select-file" (click)="fileInput.click()">
+            选择文件
+          </button>
+        </div>
+
+        <!-- 已选择的文件列表 -->
+        <div class="selected-files" *ngIf="selectedFiles.length > 0">
+          <h4>已选择 {{ selectedFiles.length }} 个文件</h4>
+          <div class="file-item" *ngFor="let file of selectedFiles; let i = index">
+            <img [src]="file.preview" [alt]="file.name" />
+            <div class="file-info">
+              <p class="file-name">{{ file.name }}</p>
+              <p class="file-size">{{ formatFileSize(file.size) }}</p>
+            </div>
+            <button class="btn-remove" (click)="removeSelectedFile(i)">
+              <i class="icon-close"></i>
+            </button>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn-secondary" (click)="closeUploadDialog()">取消</button>
+        <button class="btn-primary" 
+                [disabled]="selectedFiles.length === 0"
+                (click)="uploadFiles()">
+          <i class="icon-upload"></i>
+          上传 {{ selectedFiles.length }} 张图片
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 编辑对话框 -->
+  <div class="modal-overlay" *ngIf="showEditDialog" (click)="closeEditDialog()">
+    <div class="modal-dialog modal-dialog-large" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>编辑参考图信息</h3>
+        <button class="btn-close" (click)="closeEditDialog()">
+          <i class="icon-close"></i>
+        </button>
+      </div>
+      <div class="modal-body">
+        <div class="edit-content">
+          <div class="edit-preview">
+            <img [src]="editingImage?.url" [alt]="editingImage?.name" />
+          </div>
+          <div class="edit-form">
+            <div class="form-group">
+              <label>图片名称</label>
+              <input type="text" 
+                     class="form-control" 
+                     [(ngModel)]="editingImage!.name"
+                     placeholder="输入图片名称" />
+            </div>
+
+            <div class="form-group">
+              <label>
+                分类标签
+                <span class="label-tip">可多选</span>
+              </label>
+              <div class="category-checkboxes">
+                <label class="checkbox-label">
+                  <input type="checkbox" 
+                         [checked]="isCategorySelected('lighting')"
+                         (change)="toggleCategory('lighting')" />
+                  <span class="checkbox-text">
+                    <i class="icon-lightbulb"></i>
+                    灯光参考
+                  </span>
+                </label>
+                <label class="checkbox-label">
+                  <input type="checkbox" 
+                         [checked]="isCategorySelected('furniture')"
+                         (change)="toggleCategory('furniture')" />
+                  <span class="checkbox-text">
+                    <i class="icon-couch"></i>
+                    软装参考
+                  </span>
+                </label>
+                <label class="checkbox-label">
+                  <input type="checkbox" 
+                         [checked]="isCategorySelected('atmosphere')"
+                         (change)="toggleCategory('atmosphere')" />
+                  <span class="checkbox-text">
+                    <i class="icon-stars"></i>
+                    氛围参考
+                  </span>
+                </label>
+                <label class="checkbox-label">
+                  <input type="checkbox" 
+                         [checked]="isCategorySelected('structure')"
+                         (change)="toggleCategory('structure')" />
+                  <span class="checkbox-text">
+                    <i class="icon-cube"></i>
+                    结构参考
+                  </span>
+                </label>
+              </div>
+            </div>
+
+            <div class="form-group">
+              <label>
+                描述说明
+                <span class="label-tip">详细描述参考哪些元素</span>
+              </label>
+              <textarea 
+                class="form-control" 
+                rows="4"
+                [(ngModel)]="editingImage!.description"
+                placeholder="例如:参考这张图的灯光布局和氛围感,主要关注吊灯的造型和暖色调灯光的运用..."></textarea>
+            </div>
+
+            <div class="form-group">
+              <label>关键要素标注</label>
+              <div class="tags-input">
+                <span class="tag" *ngFor="let tag of editingImage!.tags; let i = index">
+                  {{ tag }}
+                  <button class="tag-close" (click)="removeTag(i)">×</button>
+                </span>
+                <input type="text" 
+                       class="tag-input"
+                       [(ngModel)]="newTag"
+                       (keydown.enter)="addTag()"
+                       placeholder="输入标签后按回车" />
+              </div>
+              <p class="form-help">例如:吊灯造型、暖色调、木质家具等</p>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn-secondary" (click)="closeEditDialog()">取消</button>
+        <button class="btn-primary" (click)="saveEdit()">
+          <i class="icon-check"></i>
+          保存修改
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- AI智能追问助手 -->
+  <div class="modal-overlay" *ngIf="showAIDialog" (click)="closeAIDialog()">
+    <div class="modal-dialog modal-dialog-ai" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>
+          <i class="icon-sparkles"></i>
+          AI智能追问助手
+        </h3>
+        <button class="btn-close" (click)="closeAIDialog()">
+          <i class="icon-close"></i>
+        </button>
+      </div>
+      <div class="modal-body">
+        <div class="ai-content">
+          <div class="ai-image-preview">
+            <img [src]="aiImage?.url" [alt]="aiImage?.name" />
+            <div class="image-meta">
+              <p class="image-name">{{ aiImage?.name }}</p>
+            </div>
+          </div>
+
+          <div class="ai-analysis">
+            <div class="analysis-section">
+              <h4>
+                <i class="icon-info"></i>
+                当前状态分析
+              </h4>
+              <div class="status-info">
+                <div class="status-item" [class.completed]="aiImage?.categories && (aiImage?.categories?.length ?? 0) > 0">
+                  <i [class.icon-check-circle]="aiImage?.categories && (aiImage?.categories?.length ?? 0) > 0" 
+                     [class.icon-alert-circle]="!aiImage?.categories || (aiImage?.categories?.length ?? 0) === 0"></i>
+                  <span>分类标签:{{ aiImage?.categories && (aiImage?.categories?.length ?? 0) > 0 ? '已标注' : '未标注' }}</span>
+                </div>
+                <div class="status-item" [class.completed]="aiImage?.description">
+                  <i [class.icon-check-circle]="aiImage?.description" 
+                     [class.icon-alert-circle]="!aiImage?.description"></i>
+                  <span>描述说明:{{ aiImage?.description ? '已填写' : '未填写' }}</span>
+                </div>
+              </div>
+            </div>
+
+            <!-- 参考图多的情况 -->
+            <div class="analysis-section" *ngIf="referenceImages.length >= 3">
+              <h4>
+                <i class="icon-help-circle"></i>
+                AI建议追问(参考图较多)
+              </h4>
+              <div class="ai-questions">
+                <div class="question-card" (click)="applyAIQuestion(q)" 
+                     *ngFor="let q of aiQuestionsMultiple">
+                  <div class="question-icon">
+                    <i [class]="q.icon"></i>
+                  </div>
+                  <div class="question-content">
+                    <h5>{{ q.title }}</h5>
+                    <p>{{ q.description }}</p>
+                    <div class="question-example">
+                      <strong>追问示例:</strong>{{ q.example }}
+                    </div>
+                  </div>
+                  <button class="btn-apply">
+                    <i class="icon-arrow-right"></i>
+                  </button>
+                </div>
+              </div>
+            </div>
+
+            <!-- 参考图少的情况 -->
+            <div class="analysis-section" *ngIf="referenceImages.length < 3">
+              <h4>
+                <i class="icon-help-circle"></i>
+                AI建议追问(参考图较少)
+              </h4>
+              <div class="ai-questions">
+                <div class="question-card" (click)="applyAIQuestion(q)" 
+                     *ngFor="let q of aiQuestionsFew">
+                  <div class="question-icon">
+                    <i [class]="q.icon"></i>
+                  </div>
+                  <div class="question-content">
+                    <h5>{{ q.title }}</h5>
+                    <p>{{ q.description }}</p>
+                    <div class="question-example">
+                      <strong>追问示例:</strong>{{ q.example }}
+                    </div>
+                  </div>
+                  <button class="btn-apply">
+                    <i class="icon-arrow-right"></i>
+                  </button>
+                </div>
+              </div>
+            </div>
+
+            <!-- 自定义追问 -->
+            <div class="analysis-section">
+              <h4>
+                <i class="icon-message-circle"></i>
+                自定义追问
+              </h4>
+              <textarea 
+                class="form-control" 
+                rows="3"
+                [(ngModel)]="customQuestion"
+                placeholder="输入您想要补充询问的问题..."></textarea>
+              <button class="btn-send-question" (click)="sendCustomQuestion()">
+                <i class="icon-send"></i>
+                发送追问
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn-secondary" (click)="closeAIDialog()">关闭</button>
+        <button class="btn-primary" (click)="applyAISuggestionsAndEdit()">
+          <i class="icon-edit"></i>
+          应用建议并编辑
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 图片预览对话框 -->
+  <div class="modal-overlay" *ngIf="showPreviewDialog" (click)="closePreviewDialog()">
+    <div class="preview-dialog" (click)="$event.stopPropagation()">
+      <button class="btn-close" (click)="closePreviewDialog()">
+        <i class="icon-close"></i>
+      </button>
+      <img [src]="previewImageUrl" alt="预览图" />
+    </div>
+  </div>
+</div>
+

+ 1249 - 0
src/app/pages/designer/project-detail/components/reference-image-manager/reference-image-manager.component.scss

@@ -0,0 +1,1249 @@
+@import './icons.scss';
+
+.reference-image-manager {
+  padding: 24px;
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+
+  // ==================== 顶部标题栏 ====================
+  .manager-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+    padding-bottom: 20px;
+    border-bottom: 2px solid #f0f0f0;
+
+    .header-left {
+      display: flex;
+      align-items: center;
+      gap: 16px;
+
+      .manager-title {
+        font-size: 24px;
+        font-weight: 600;
+        color: #1a1a1a;
+        margin: 0;
+        display: flex;
+        align-items: center;
+        gap: 8px;
+
+        i {
+          color: #6366f1;
+          font-size: 28px;
+        }
+      }
+
+      .image-count {
+        padding: 4px 12px;
+        background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+        color: white;
+        border-radius: 20px;
+        font-size: 13px;
+        font-weight: 500;
+      }
+    }
+
+    .header-right {
+      display: flex;
+      gap: 12px;
+
+      .btn-view-mode {
+        padding: 8px 16px;
+        border: 1px solid #e5e7eb;
+        background: white;
+        color: #6b7280;
+        border-radius: 8px;
+        cursor: pointer;
+        font-size: 14px;
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        transition: all 0.3s;
+
+        &:hover {
+          border-color: #6366f1;
+          color: #6366f1;
+          background: #f5f3ff;
+        }
+
+        &.active {
+          background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+          color: white;
+          border-color: transparent;
+        }
+      }
+
+      .btn-upload {
+        padding: 10px 20px;
+        background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+        color: white;
+        border: none;
+        border-radius: 8px;
+        cursor: pointer;
+        font-size: 14px;
+        font-weight: 500;
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        transition: all 0.3s;
+        box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
+        }
+
+        &:active {
+          transform: translateY(0);
+        }
+      }
+    }
+  }
+
+  // ==================== 分类筛选标签 ====================
+  .category-filters {
+    display: flex;
+    gap: 12px;
+    margin-bottom: 24px;
+    flex-wrap: wrap;
+
+    .filter-tag {
+      padding: 8px 16px;
+      border: 2px solid #e5e7eb;
+      background: white;
+      color: #6b7280;
+      border-radius: 24px;
+      cursor: pointer;
+      font-size: 14px;
+      font-weight: 500;
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      transition: all 0.3s;
+
+      i {
+        font-size: 16px;
+      }
+
+      &:hover {
+        border-color: #6366f1;
+        background: #f5f3ff;
+        color: #6366f1;
+      }
+
+      &.active {
+        background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+        color: white;
+        border-color: transparent;
+        box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
+      }
+
+      &.filter-tag-lighting.active {
+        background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
+      }
+
+      &.filter-tag-furniture.active {
+        background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+      }
+
+      &.filter-tag-atmosphere.active {
+        background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
+      }
+
+      &.filter-tag-structure.active {
+        background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
+      }
+
+      &.filter-tag-uncategorized.active {
+        background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+      }
+    }
+  }
+
+  // ==================== 网格视图 ====================
+  .images-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+    gap: 20px;
+
+    .image-card {
+      background: white;
+      border: 2px solid #f0f0f0;
+      border-radius: 12px;
+      overflow: hidden;
+      cursor: pointer;
+      transition: all 0.3s;
+
+      &:hover {
+        border-color: #6366f1;
+        box-shadow: 0 8px 24px rgba(99, 102, 241, 0.2);
+        transform: translateY(-4px);
+      }
+
+      &.selected {
+        border-color: #6366f1;
+        box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
+      }
+
+      .image-container {
+        position: relative;
+        width: 100%;
+        padding-top: 75%; // 4:3 aspect ratio
+        background: #f9fafb;
+        overflow: hidden;
+
+        img {
+          position: absolute;
+          top: 0;
+          left: 0;
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+
+        .image-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;
+          gap: 12px;
+          opacity: 0;
+          transition: opacity 0.3s;
+
+          .btn-icon {
+            width: 40px;
+            height: 40px;
+            border-radius: 50%;
+            background: white;
+            border: none;
+            color: #6b7280;
+            cursor: pointer;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.3s;
+
+            &:hover {
+              background: #6366f1;
+              color: white;
+              transform: scale(1.1);
+            }
+          }
+        }
+
+        &:hover .image-overlay {
+          opacity: 1;
+        }
+      }
+
+      .image-info {
+        padding: 16px;
+
+        .image-name {
+          font-size: 15px;
+          font-weight: 600;
+          color: #1a1a1a;
+          margin: 0 0 12px 0;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+
+        .image-categories {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 6px;
+          margin-bottom: 12px;
+
+          .category-badge {
+            padding: 4px 10px;
+            border-radius: 12px;
+            font-size: 12px;
+            font-weight: 500;
+            
+            &.category-lighting {
+              background: #fef3c7;
+              color: #d97706;
+            }
+
+            &.category-furniture {
+              background: #d1fae5;
+              color: #059669;
+            }
+
+            &.category-atmosphere {
+              background: #fce7f3;
+              color: #db2777;
+            }
+
+            &.category-structure {
+              background: #dbeafe;
+              color: #2563eb;
+            }
+
+            &.category-uncategorized {
+              background: #fee2e2;
+              color: #dc2626;
+            }
+          }
+        }
+
+        .image-description {
+          font-size: 13px;
+          color: #6b7280;
+          line-height: 1.5;
+          margin: 0 0 12px 0;
+          display: -webkit-box;
+          -webkit-line-clamp: 2;
+          line-clamp: 2;
+          -webkit-box-orient: vertical;
+          overflow: hidden;
+        }
+
+        .btn-ai-assist {
+          width: 100%;
+          padding: 8px 12px;
+          background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
+          color: white;
+          border: none;
+          border-radius: 6px;
+          cursor: pointer;
+          font-size: 13px;
+          font-weight: 500;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          gap: 6px;
+          transition: all 0.3s;
+
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
+          }
+
+          i {
+            font-size: 16px;
+          }
+        }
+      }
+    }
+  }
+
+  // ==================== 列表视图 ====================
+  .images-list {
+    .list-header {
+      display: grid;
+      grid-template-columns: 100px 1fr 200px 2fr 120px;
+      gap: 16px;
+      padding: 12px 16px;
+      background: #f9fafb;
+      border-radius: 8px;
+      font-size: 13px;
+      font-weight: 600;
+      color: #6b7280;
+      margin-bottom: 12px;
+    }
+
+    .list-row {
+      display: grid;
+      grid-template-columns: 100px 1fr 200px 2fr 120px;
+      gap: 16px;
+      padding: 16px;
+      background: white;
+      border: 2px solid #f0f0f0;
+      border-radius: 8px;
+      margin-bottom: 12px;
+      align-items: center;
+      transition: all 0.3s;
+
+      &:hover {
+        border-color: #6366f1;
+        box-shadow: 0 4px 12px rgba(99, 102, 241, 0.15);
+      }
+
+      &.selected {
+        border-color: #6366f1;
+        background: #f5f3ff;
+      }
+
+      .col-preview {
+        img {
+          width: 80px;
+          height: 60px;
+          object-fit: cover;
+          border-radius: 6px;
+          cursor: pointer;
+          transition: transform 0.3s;
+
+          &:hover {
+            transform: scale(1.1);
+          }
+        }
+      }
+
+      .col-name {
+        font-size: 14px;
+        font-weight: 500;
+        color: #1a1a1a;
+      }
+
+      .col-categories {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 6px;
+
+        .category-badge {
+          padding: 4px 10px;
+          border-radius: 12px;
+          font-size: 12px;
+          font-weight: 500;
+          
+          &.category-lighting {
+            background: #fef3c7;
+            color: #d97706;
+          }
+
+          &.category-furniture {
+            background: #d1fae5;
+            color: #059669;
+          }
+
+          &.category-atmosphere {
+            background: #fce7f3;
+            color: #db2777;
+          }
+
+          &.category-structure {
+            background: #dbeafe;
+            color: #2563eb;
+          }
+
+          &.category-uncategorized {
+            background: #fee2e2;
+            color: #dc2626;
+          }
+        }
+      }
+
+      .col-description {
+        p {
+          font-size: 13px;
+          color: #6b7280;
+          line-height: 1.5;
+          margin: 0;
+          display: -webkit-box;
+          -webkit-line-clamp: 2;
+          line-clamp: 2;
+          -webkit-box-orient: vertical;
+          overflow: hidden;
+        }
+
+        .text-muted {
+          color: #9ca3af;
+          font-size: 13px;
+        }
+      }
+
+      .col-actions {
+        display: flex;
+        gap: 8px;
+        justify-content: flex-end;
+
+        .btn-icon {
+          width: 36px;
+          height: 36px;
+          border-radius: 6px;
+          background: #f3f4f6;
+          border: none;
+          color: #6b7280;
+          cursor: pointer;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          transition: all 0.3s;
+
+          &:hover {
+            background: #6366f1;
+            color: white;
+          }
+        }
+      }
+    }
+  }
+
+  // ==================== 空状态 ====================
+  .empty-state {
+    text-align: center;
+    padding: 60px 20px;
+
+    .empty-icon {
+      i {
+        font-size: 64px;
+        color: #d1d5db;
+      }
+    }
+
+    h3 {
+      font-size: 20px;
+      font-weight: 600;
+      color: #6b7280;
+      margin: 20px 0 8px 0;
+    }
+
+    p {
+      font-size: 14px;
+      color: #9ca3af;
+      margin: 0 0 24px 0;
+    }
+
+    .btn-primary {
+      padding: 12px 24px;
+      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+      color: white;
+      border: none;
+      border-radius: 8px;
+      cursor: pointer;
+      font-size: 14px;
+      font-weight: 500;
+      display: inline-flex;
+      align-items: center;
+      gap: 8px;
+      transition: all 0.3s;
+
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
+      }
+    }
+  }
+
+  // ==================== 对话框样式 ====================
+  .modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.6);
+    backdrop-filter: blur(4px);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 1000;
+    animation: fadeIn 0.3s;
+  }
+
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+
+  .modal-dialog {
+    background: white;
+    border-radius: 16px;
+    width: 90%;
+    max-width: 600px;
+    max-height: 90vh;
+    overflow: hidden;
+    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+    animation: slideUp 0.3s;
+
+    &.modal-dialog-large {
+      max-width: 900px;
+    }
+
+    &.modal-dialog-ai {
+      max-width: 1000px;
+    }
+  }
+
+  @keyframes slideUp {
+    from {
+      transform: translateY(20px);
+      opacity: 0;
+    }
+    to {
+      transform: translateY(0);
+      opacity: 1;
+    }
+  }
+
+  .modal-header {
+    padding: 24px;
+    border-bottom: 1px solid #e5e7eb;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    h3 {
+      font-size: 20px;
+      font-weight: 600;
+      color: #1a1a1a;
+      margin: 0;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      i {
+        color: #6366f1;
+      }
+    }
+
+    .btn-close {
+      width: 32px;
+      height: 32px;
+      border-radius: 6px;
+      background: #f3f4f6;
+      border: none;
+      color: #6b7280;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      transition: all 0.3s;
+
+      &:hover {
+        background: #ef4444;
+        color: white;
+      }
+    }
+  }
+
+  .modal-body {
+    padding: 24px;
+    max-height: calc(90vh - 160px);
+    overflow-y: auto;
+
+    &::-webkit-scrollbar {
+      width: 8px;
+    }
+
+    &::-webkit-scrollbar-track {
+      background: #f3f4f6;
+      border-radius: 4px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #d1d5db;
+      border-radius: 4px;
+
+      &:hover {
+        background: #9ca3af;
+      }
+    }
+  }
+
+  .modal-footer {
+    padding: 16px 24px;
+    border-top: 1px solid #e5e7eb;
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+
+    button {
+      padding: 10px 20px;
+      border-radius: 8px;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.3s;
+
+      &.btn-secondary {
+        background: #f3f4f6;
+        color: #6b7280;
+        border: none;
+
+        &:hover {
+          background: #e5e7eb;
+        }
+      }
+
+      &.btn-primary {
+        background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+        color: white;
+        border: none;
+        display: flex;
+        align-items: center;
+        gap: 6px;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
+        }
+
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+          transform: none;
+        }
+      }
+    }
+  }
+
+  // ==================== 上传区域 ====================
+  .upload-area {
+    border: 2px dashed #d1d5db;
+    border-radius: 12px;
+    padding: 48px 24px;
+    text-align: center;
+    transition: all 0.3s;
+
+    &.dragging {
+      border-color: #6366f1;
+      background: #f5f3ff;
+    }
+
+    i {
+      font-size: 48px;
+      color: #9ca3af;
+      display: block;
+      margin-bottom: 16px;
+    }
+
+    p {
+      font-size: 14px;
+      color: #6b7280;
+      margin: 0 0 16px 0;
+    }
+
+    .btn-select-file {
+      padding: 10px 20px;
+      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+      color: white;
+      border: none;
+      border-radius: 8px;
+      cursor: pointer;
+      font-size: 14px;
+      font-weight: 500;
+      transition: all 0.3s;
+
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
+      }
+    }
+  }
+
+  .selected-files {
+    margin-top: 24px;
+
+    h4 {
+      font-size: 16px;
+      font-weight: 600;
+      color: #1a1a1a;
+      margin: 0 0 16px 0;
+    }
+
+    .file-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px;
+      background: #f9fafb;
+      border-radius: 8px;
+      margin-bottom: 8px;
+
+      img {
+        width: 60px;
+        height: 45px;
+        object-fit: cover;
+        border-radius: 4px;
+      }
+
+      .file-info {
+        flex: 1;
+
+        .file-name {
+          font-size: 14px;
+          font-weight: 500;
+          color: #1a1a1a;
+          margin: 0 0 4px 0;
+        }
+
+        .file-size {
+          font-size: 12px;
+          color: #9ca3af;
+          margin: 0;
+        }
+      }
+
+      .btn-remove {
+        width: 28px;
+        height: 28px;
+        border-radius: 4px;
+        background: #fee2e2;
+        border: none;
+        color: #dc2626;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        transition: all 0.3s;
+
+        &:hover {
+          background: #dc2626;
+          color: white;
+        }
+      }
+    }
+  }
+
+  // ==================== 编辑表单 ====================
+  .edit-content {
+    display: grid;
+    grid-template-columns: 300px 1fr;
+    gap: 24px;
+
+    .edit-preview {
+      img {
+        width: 100%;
+        border-radius: 8px;
+      }
+    }
+
+    .edit-form {
+      .form-group {
+        margin-bottom: 20px;
+
+        label {
+          display: block;
+          font-size: 14px;
+          font-weight: 600;
+          color: #374151;
+          margin-bottom: 8px;
+
+          .label-tip {
+            font-size: 12px;
+            font-weight: 400;
+            color: #9ca3af;
+            margin-left: 8px;
+          }
+        }
+
+        .form-control {
+          width: 100%;
+          padding: 10px 12px;
+          border: 1px solid #d1d5db;
+          border-radius: 8px;
+          font-size: 14px;
+          color: #1a1a1a;
+          transition: all 0.3s;
+          font-family: inherit;
+
+          &:focus {
+            outline: none;
+            border-color: #6366f1;
+            box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
+          }
+        }
+
+        .category-checkboxes {
+          display: grid;
+          grid-template-columns: 1fr 1fr;
+          gap: 12px;
+
+          .checkbox-label {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            cursor: pointer;
+            padding: 10px 12px;
+            border: 2px solid #e5e7eb;
+            border-radius: 8px;
+            transition: all 0.3s;
+
+            &:hover {
+              border-color: #6366f1;
+              background: #f5f3ff;
+            }
+
+            input[type="checkbox"] {
+              cursor: pointer;
+
+              &:checked + .checkbox-text {
+                color: #6366f1;
+                font-weight: 600;
+              }
+            }
+
+            .checkbox-text {
+              display: flex;
+              align-items: center;
+              gap: 6px;
+              font-size: 14px;
+              color: #6b7280;
+              transition: all 0.3s;
+
+              i {
+                font-size: 16px;
+              }
+            }
+          }
+        }
+
+        .tags-input {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 8px;
+          padding: 8px;
+          border: 1px solid #d1d5db;
+          border-radius: 8px;
+          min-height: 42px;
+          transition: all 0.3s;
+
+          &:focus-within {
+            border-color: #6366f1;
+            box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
+          }
+
+          .tag {
+            padding: 4px 10px;
+            background: #6366f1;
+            color: white;
+            border-radius: 12px;
+            font-size: 13px;
+            display: flex;
+            align-items: center;
+            gap: 6px;
+
+            .tag-close {
+              background: none;
+              border: none;
+              color: white;
+              cursor: pointer;
+              font-size: 16px;
+              line-height: 1;
+              padding: 0;
+              width: 16px;
+              height: 16px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+
+              &:hover {
+                opacity: 0.8;
+              }
+            }
+          }
+
+          .tag-input {
+            flex: 1;
+            border: none;
+            outline: none;
+            font-size: 14px;
+            min-width: 120px;
+            padding: 4px 0;
+          }
+        }
+
+        .form-help {
+          font-size: 12px;
+          color: #9ca3af;
+          margin: 6px 0 0 0;
+        }
+      }
+    }
+  }
+
+  // ==================== AI助手 ====================
+  .ai-content {
+    display: grid;
+    grid-template-columns: 280px 1fr;
+    gap: 24px;
+
+    .ai-image-preview {
+      img {
+        width: 100%;
+        border-radius: 8px;
+        margin-bottom: 12px;
+      }
+
+      .image-meta {
+        .image-name {
+          font-size: 14px;
+          font-weight: 500;
+          color: #6b7280;
+          margin: 0;
+        }
+      }
+    }
+
+    .ai-analysis {
+      .analysis-section {
+        margin-bottom: 24px;
+
+        h4 {
+          font-size: 16px;
+          font-weight: 600;
+          color: #1a1a1a;
+          margin: 0 0 16px 0;
+          display: flex;
+          align-items: center;
+          gap: 8px;
+
+          i {
+            color: #6366f1;
+          }
+        }
+
+        .status-info {
+          .status-item {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            padding: 10px 12px;
+            background: #f9fafb;
+            border-radius: 8px;
+            margin-bottom: 8px;
+            font-size: 14px;
+
+            i {
+              font-size: 18px;
+              color: #ef4444;
+
+              &.icon-check-circle {
+                color: #10b981;
+              }
+
+              &.icon-alert-circle {
+                color: #ef4444;
+              }
+            }
+
+            &.completed {
+              background: #d1fae5;
+              color: #059669;
+            }
+          }
+        }
+
+        .ai-questions {
+          .question-card {
+            display: flex;
+            align-items: flex-start;
+            gap: 12px;
+            padding: 16px;
+            background: white;
+            border: 2px solid #e5e7eb;
+            border-radius: 12px;
+            margin-bottom: 12px;
+            cursor: pointer;
+            transition: all 0.3s;
+
+            &:hover {
+              border-color: #6366f1;
+              background: #f5f3ff;
+              transform: translateX(4px);
+
+              .btn-apply {
+                background: #6366f1;
+                color: white;
+              }
+            }
+
+            .question-icon {
+              width: 40px;
+              height: 40px;
+              border-radius: 10px;
+              background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+              color: white;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              flex-shrink: 0;
+
+              i {
+                font-size: 20px;
+              }
+            }
+
+            .question-content {
+              flex: 1;
+
+              h5 {
+                font-size: 15px;
+                font-weight: 600;
+                color: #1a1a1a;
+                margin: 0 0 6px 0;
+              }
+
+              p {
+                font-size: 13px;
+                color: #6b7280;
+                margin: 0 0 10px 0;
+                line-height: 1.5;
+              }
+
+              .question-example {
+                padding: 10px 12px;
+                background: #f9fafb;
+                border-left: 3px solid #6366f1;
+                border-radius: 6px;
+                font-size: 13px;
+                color: #374151;
+                line-height: 1.5;
+
+                strong {
+                  color: #6366f1;
+                  font-weight: 600;
+                  display: block;
+                  margin-bottom: 4px;
+                }
+              }
+            }
+
+            .btn-apply {
+              width: 32px;
+              height: 32px;
+              border-radius: 6px;
+              background: #f3f4f6;
+              border: none;
+              color: #6b7280;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              transition: all 0.3s;
+              flex-shrink: 0;
+            }
+          }
+        }
+
+        .btn-send-question {
+          margin-top: 12px;
+          width: 100%;
+          padding: 12px;
+          background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+          color: white;
+          border: none;
+          border-radius: 8px;
+          cursor: pointer;
+          font-size: 14px;
+          font-weight: 500;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          gap: 8px;
+          transition: all 0.3s;
+
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
+          }
+        }
+      }
+    }
+  }
+
+  // ==================== 图片预览 ====================
+  .preview-dialog {
+    position: relative;
+    max-width: 90vw;
+    max-height: 90vh;
+
+    .btn-close {
+      position: absolute;
+      top: -40px;
+      right: 0;
+      width: 40px;
+      height: 40px;
+      border-radius: 50%;
+      background: rgba(255, 255, 255, 0.9);
+      border: none;
+      color: #1a1a1a;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      transition: all 0.3s;
+
+      &:hover {
+        background: #ef4444;
+        color: white;
+        transform: rotate(90deg);
+      }
+    }
+
+    img {
+      max-width: 100%;
+      max-height: 90vh;
+      border-radius: 8px;
+      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
+    }
+  }
+
+  // ==================== 响应式设计 ====================
+  @media (max-width: 1024px) {
+    .images-grid {
+      grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+    }
+
+    .edit-content {
+      grid-template-columns: 1fr;
+
+      .edit-preview {
+        img {
+          max-width: 300px;
+          margin: 0 auto;
+          display: block;
+        }
+      }
+    }
+
+    .ai-content {
+      grid-template-columns: 1fr;
+    }
+  }
+
+  @media (max-width: 768px) {
+    padding: 16px;
+
+    .manager-header {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 16px;
+
+      .header-right {
+        width: 100%;
+        
+        .btn-view-mode,
+        .btn-upload {
+          flex: 1;
+        }
+      }
+    }
+
+    .images-grid {
+      grid-template-columns: 1fr;
+    }
+
+    .images-list {
+      .list-header {
+        display: none;
+      }
+
+      .list-row {
+        grid-template-columns: 1fr;
+        gap: 12px;
+
+        .col-preview {
+          img {
+            width: 100%;
+            height: auto;
+          }
+        }
+      }
+    }
+  }
+}
+

+ 475 - 0
src/app/pages/designer/project-detail/components/reference-image-manager/reference-image-manager.component.ts

@@ -0,0 +1,475 @@
+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+interface ReferenceImage {
+  id: string;
+  name: string;
+  url: string;
+  categories: string[];
+  description?: string;
+  tags?: string[];
+  uploadDate: Date;
+}
+
+interface SelectedFile {
+  file: File;
+  name: string;
+  size: number;
+  preview: string;
+}
+
+interface AIQuestion {
+  id: string;
+  title: string;
+  description: string;
+  example: string;
+  icon: string;
+  category?: string;
+}
+
+@Component({
+  selector: 'app-reference-image-manager',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './reference-image-manager.component.html',
+  styleUrls: ['./reference-image-manager.component.scss']
+})
+export class ReferenceImageManagerComponent implements OnInit {
+  @Input() projectId: string = '';
+  @Output() imagesUpdated = new EventEmitter<ReferenceImage[]>();
+
+  // 视图状态
+  viewMode: 'grid' | 'list' = 'grid';
+  activeFilter: string = 'all';
+  selectedImageId: string | null = null;
+
+  // 数据
+  referenceImages: ReferenceImage[] = [];
+  filteredImages: ReferenceImage[] = [];
+
+  // 对话框状态
+  showUploadDialog = false;
+  showEditDialog = false;
+  showAIDialog = false;
+  showPreviewDialog = false;
+
+  // 上传相关
+  isDragging = false;
+  selectedFiles: SelectedFile[] = [];
+
+  // 编辑相关
+  editingImage: ReferenceImage | null = null;
+  newTag = '';
+
+  // AI助手相关
+  aiImage: ReferenceImage | null = null;
+  customQuestion = '';
+
+  // AI建议追问(参考图多的情况)
+  aiQuestionsMultiple: AIQuestion[] = [
+    {
+      id: 'q1',
+      title: '明确灯光参考',
+      description: '当有多张参考图时,需要明确哪张图主要用于灯光布局参考',
+      example: '请问这几张参考图中,哪一张是您想要的灯光效果?主要参考它的主光源布置还是氛围灯效果?',
+      icon: 'icon-lightbulb',
+      category: 'lighting'
+    },
+    {
+      id: 'q2',
+      title: '区分软装参考',
+      description: '识别哪些图片用于软装家具的参考',
+      example: '这些参考图中,哪几张是用于软装搭配的?具体是参考家具的款式、颜色还是整体搭配方案?',
+      icon: 'icon-couch',
+      category: 'furniture'
+    },
+    {
+      id: 'q3',
+      title: '确认氛围感觉',
+      description: '明确整体氛围和感觉的主要参考图',
+      example: '您希望达到的整体氛围感觉,主要是参考哪张图?是温馨、现代还是其他风格?',
+      icon: 'icon-stars',
+      category: 'atmosphere'
+    },
+    {
+      id: 'q4',
+      title: '结构布局说明',
+      description: '确认空间结构和布局的参考依据',
+      example: '空间的布局结构,是按照哪张图来的?需要完全一致还是可以适当调整?',
+      icon: 'icon-cube',
+      category: 'structure'
+    }
+  ];
+
+  // AI建议追问(参考图少的情况)
+  aiQuestionsFew: AIQuestion[] = [
+    {
+      id: 'q5',
+      title: '细化感觉来源',
+      description: '深入了解客户喜欢这张图的具体原因',
+      example: '您喜欢这张图的哪些方面?是整体的色调、光线效果、还是家具的搭配?能具体说说吗?',
+      icon: 'icon-heart',
+    },
+    {
+      id: 'q6',
+      title: '明确重点元素',
+      description: '确认图片中最重要的参考元素',
+      example: '这张图里,您最想要的是什么?比如墙面的颜色、地板材质、还是家具的款式?请按重要性排个序。',
+      icon: 'icon-target',
+    },
+    {
+      id: 'q7',
+      title: '探索细节要求',
+      description: '挖掘客户对细节的具体期望',
+      example: '关于这张图的细节,比如灯具的样式、窗帘的材质、墙面的装饰等,您有特别的偏好吗?',
+      icon: 'icon-zoom-in',
+    },
+    {
+      id: 'q8',
+      title: '确认可调整空间',
+      description: '了解哪些地方可以灵活调整',
+      example: '如果完全按这张图来可能有些困难,哪些元素是必须保留的?哪些可以根据实际情况调整?',
+      icon: 'icon-sliders',
+    }
+  ];
+
+  previewImageUrl = '';
+
+  ngOnInit() {
+    this.loadReferenceImages();
+  }
+
+  // 加载参考图数据
+  loadReferenceImages() {
+    // 模拟数据,实际应该从服务器加载
+    this.referenceImages = [
+      {
+        id: '1',
+        name: '客厅灯光参考.jpg',
+        url: '/assets/images/portfolio-1.svg',
+        categories: ['lighting', 'atmosphere'],
+        description: '参考主灯和氛围灯的布局,营造温馨的晚间氛围',
+        tags: ['吊灯', '暖光', '氛围灯'],
+        uploadDate: new Date('2024-01-15')
+      },
+      {
+        id: '2',
+        name: '软装搭配方案.jpg',
+        url: '/assets/images/portfolio-2.svg',
+        categories: ['furniture'],
+        description: '参考沙发、茶几的款式和颜色搭配',
+        tags: ['现代简约', '布艺沙发', '木质茶几'],
+        uploadDate: new Date('2024-01-16')
+      },
+      {
+        id: '3',
+        name: '整体效果图.jpg',
+        url: '/assets/images/portfolio-3.svg',
+        categories: [],
+        description: '',
+        tags: [],
+        uploadDate: new Date('2024-01-17')
+      }
+    ];
+    this.filteredImages = [...this.referenceImages];
+  }
+
+  // 分类筛选
+  filterByCategory(category: string) {
+    this.activeFilter = category;
+    if (category === 'all') {
+      this.filteredImages = [...this.referenceImages];
+    } else if (category === 'uncategorized') {
+      this.filteredImages = this.referenceImages.filter(img => 
+        !img.categories || img.categories.length === 0
+      );
+    } else {
+      this.filteredImages = this.referenceImages.filter(img => 
+        img.categories && img.categories.includes(category)
+      );
+    }
+  }
+
+  // 获取分类数量
+  getCategoryCount(category: string): number {
+    return this.referenceImages.filter(img => 
+      img.categories && img.categories.includes(category)
+    ).length;
+  }
+
+  // 获取未分类数量
+  getUncategorizedCount(): number {
+    return this.referenceImages.filter(img => 
+      !img.categories || img.categories.length === 0
+    ).length;
+  }
+
+  // 获取分类标签文本
+  getCategoryLabel(category: string): string {
+    const labels: { [key: string]: string } = {
+      'lighting': '灯光',
+      'furniture': '软装',
+      'atmosphere': '氛围',
+      'structure': '结构'
+    };
+    return labels[category] || category;
+  }
+
+  // 获取筛选器标签
+  getFilterLabel(): string {
+    const labels: { [key: string]: string } = {
+      'all': '全部',
+      'lighting': '灯光参考',
+      'furniture': '软装参考',
+      'atmosphere': '氛围参考',
+      'structure': '结构参考',
+      'uncategorized': '未分类'
+    };
+    return labels[this.activeFilter] || '';
+  }
+
+  // 选择图片
+  selectImage(image: ReferenceImage) {
+    this.selectedImageId = image.id;
+  }
+
+  // 打开上传对话框
+  openUploadDialog() {
+    this.showUploadDialog = true;
+    this.selectedFiles = [];
+  }
+
+  // 关闭上传对话框
+  closeUploadDialog() {
+    this.showUploadDialog = false;
+    this.selectedFiles = [];
+  }
+
+  // 文件拖拽
+  onDragOver(event: DragEvent) {
+    event.preventDefault();
+    this.isDragging = true;
+  }
+
+  onDragLeave(event: DragEvent) {
+    event.preventDefault();
+    this.isDragging = false;
+  }
+
+  onDrop(event: DragEvent) {
+    event.preventDefault();
+    this.isDragging = false;
+    
+    const files = event.dataTransfer?.files;
+    if (files) {
+      this.processFiles(files);
+    }
+  }
+
+  // 文件选择
+  onFileSelected(event: Event) {
+    const input = event.target as HTMLInputElement;
+    if (input.files) {
+      this.processFiles(input.files);
+    }
+  }
+
+  // 处理文件
+  processFiles(files: FileList) {
+    for (let i = 0; i < files.length; i++) {
+      const file = files[i];
+      if (file.type.startsWith('image/')) {
+        const reader = new FileReader();
+        reader.onload = (e) => {
+          this.selectedFiles.push({
+            file: file,
+            name: file.name,
+            size: file.size,
+            preview: e.target?.result as string
+          });
+        };
+        reader.readAsDataURL(file);
+      }
+    }
+  }
+
+  // 移除选中的文件
+  removeSelectedFile(index: number) {
+    this.selectedFiles.splice(index, 1);
+  }
+
+  // 格式化文件大小
+  formatFileSize(bytes: number): string {
+    if (bytes < 1024) return bytes + ' B';
+    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
+    return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
+  }
+
+  // 上传文件
+  uploadFiles() {
+    // 模拟上传
+    this.selectedFiles.forEach(file => {
+      const newImage: ReferenceImage = {
+        id: Date.now().toString() + Math.random(),
+        name: file.name,
+        url: file.preview,
+        categories: [],
+        description: '',
+        tags: [],
+        uploadDate: new Date()
+      };
+      this.referenceImages.unshift(newImage);
+    });
+    
+    this.filteredImages = [...this.referenceImages];
+    this.imagesUpdated.emit(this.referenceImages);
+    this.closeUploadDialog();
+  }
+
+  // 编辑图片
+  editImage(image: ReferenceImage, event: Event) {
+    event.stopPropagation();
+    this.editingImage = { ...image };
+    this.showEditDialog = true;
+  }
+
+  // 关闭编辑对话框
+  closeEditDialog() {
+    this.showEditDialog = false;
+    this.editingImage = null;
+  }
+
+  // 判断分类是否选中
+  isCategorySelected(category: string): boolean {
+    return this.editingImage?.categories?.includes(category) || false;
+  }
+
+  // 切换分类
+  toggleCategory(category: string) {
+    if (!this.editingImage) return;
+    
+    if (!this.editingImage.categories) {
+      this.editingImage.categories = [];
+    }
+
+    const index = this.editingImage.categories.indexOf(category);
+    if (index > -1) {
+      this.editingImage.categories.splice(index, 1);
+    } else {
+      this.editingImage.categories.push(category);
+    }
+  }
+
+  // 添加标签
+  addTag() {
+    if (!this.editingImage || !this.newTag.trim()) return;
+    
+    if (!this.editingImage.tags) {
+      this.editingImage.tags = [];
+    }
+
+    if (!this.editingImage.tags.includes(this.newTag.trim())) {
+      this.editingImage.tags.push(this.newTag.trim());
+    }
+    this.newTag = '';
+  }
+
+  // 移除标签
+  removeTag(index: number) {
+    if (this.editingImage?.tags) {
+      this.editingImage.tags.splice(index, 1);
+    }
+  }
+
+  // 保存编辑
+  saveEdit() {
+    if (!this.editingImage) return;
+
+    const index = this.referenceImages.findIndex(img => img.id === this.editingImage!.id);
+    if (index > -1) {
+      this.referenceImages[index] = { ...this.editingImage };
+      this.filterByCategory(this.activeFilter);
+      this.imagesUpdated.emit(this.referenceImages);
+    }
+
+    this.closeEditDialog();
+  }
+
+  // 删除图片
+  deleteImage(image: ReferenceImage, event: Event) {
+    event.stopPropagation();
+    
+    if (confirm(`确定要删除 "${image.name}" 吗?`)) {
+      this.referenceImages = this.referenceImages.filter(img => img.id !== image.id);
+      this.filterByCategory(this.activeFilter);
+      this.imagesUpdated.emit(this.referenceImages);
+    }
+  }
+
+  // 打开AI助手
+  openAIAssistant(image: ReferenceImage, event: Event) {
+    event.stopPropagation();
+    this.aiImage = { ...image };
+    this.showAIDialog = true;
+  }
+
+  // 关闭AI对话框
+  closeAIDialog() {
+    this.showAIDialog = false;
+    this.aiImage = null;
+    this.customQuestion = '';
+  }
+
+  // 应用AI建议的问题
+  applyAIQuestion(question: AIQuestion) {
+    // 这里可以实现将问题发送给客户的逻辑
+    console.log('应用AI问题:', question);
+    
+    // 如果问题有关联的分类,自动添加到图片分类中
+    if (question.category && this.aiImage) {
+      if (!this.aiImage.categories) {
+        this.aiImage.categories = [];
+      }
+      if (!this.aiImage.categories.includes(question.category)) {
+        this.aiImage.categories.push(question.category);
+      }
+    }
+
+    alert(`已将追问发送给客户:\n\n${question.example}`);
+  }
+
+  // 发送自定义问题
+  sendCustomQuestion() {
+    if (!this.customQuestion.trim()) {
+      alert('请输入追问内容');
+      return;
+    }
+
+    // 这里实现发送自定义问题的逻辑
+    console.log('发送自定义问题:', this.customQuestion);
+    alert(`已将追问发送给客户:\n\n${this.customQuestion}`);
+    this.customQuestion = '';
+  }
+
+  // 应用AI建议并编辑
+  applyAISuggestionsAndEdit() {
+    if (this.aiImage) {
+      this.closeAIDialog();
+      this.editImage(this.aiImage, new Event('click'));
+    }
+  }
+
+  // 预览图片
+  previewImage(image: ReferenceImage) {
+    this.previewImageUrl = image.url;
+    this.showPreviewDialog = true;
+  }
+
+  // 关闭预览
+  closePreviewDialog() {
+    this.showPreviewDialog = false;
+    this.previewImageUrl = '';
+  }
+}
+

+ 39 - 4
src/app/pages/designer/project-detail/project-detail.html

@@ -34,31 +34,43 @@
         <div class="stage-nav-item" 
              [class.completed]="getSectionStatus('order') === 'completed'" 
              [class.active]="getSectionStatus('order') === 'active'"
-             (click)="toggleSection('order')">
+             [class.readonly]="isReviewOnlyMode"
+             (click)="!isReviewOnlyMode && toggleSection('order')">
           <div class="stage-nav-circle">
             <span class="stage-nav-number">1</span>
           </div>
           <div class="stage-nav-label">订单创建</div>
+          @if (isReviewOnlyMode) {
+            <div class="readonly-badge">只读</div>
+          }
         </div>
         <div class="stage-nav-connector" [class.completed]="getSectionStatus('requirements') === 'completed' || getSectionStatus('requirements') === 'active'"></div>
         <div class="stage-nav-item" 
              [class.completed]="getSectionStatus('requirements') === 'completed'" 
              [class.active]="getSectionStatus('requirements') === 'active'"
-             (click)="toggleSection('requirements')">
+             [class.readonly]="isReviewOnlyMode"
+             (click)="!isReviewOnlyMode && toggleSection('requirements')">
           <div class="stage-nav-circle">
             <span class="stage-nav-number">2</span>
           </div>
           <div class="stage-nav-label">确认需求</div>
+          @if (isReviewOnlyMode) {
+            <div class="readonly-badge">只读</div>
+          }
         </div>
         <div class="stage-nav-connector" [class.completed]="getSectionStatus('delivery') === 'completed' || getSectionStatus('delivery') === 'active'"></div>
         <div class="stage-nav-item" 
              [class.completed]="getSectionStatus('delivery') === 'completed'" 
              [class.active]="getSectionStatus('delivery') === 'active'"
-             (click)="toggleSection('delivery')">
+             [class.readonly]="isReviewOnlyMode"
+             (click)="!isReviewOnlyMode && toggleSection('delivery')">
           <div class="stage-nav-circle">
             <span class="stage-nav-number">3</span>
           </div>
           <div class="stage-nav-label">交付执行</div>
+          @if (isReviewOnlyMode) {
+            <div class="readonly-badge">只读</div>
+          }
         </div>
         <div class="stage-nav-connector" [class.completed]="getSectionStatus('aftercare') === 'completed' || getSectionStatus('aftercare') === 'active'"></div>
         <div class="stage-nav-item" 
@@ -662,7 +674,18 @@
               
               <!-- 新增:客户信息右侧 2x2 售后模块布局 -->
               @if (expandedSection === 'aftercare') {
-              <!-- 上方 2x2 网格:前4个模块 -->
+              <!-- 只读复盘模式提示 -->
+              @if (isReviewOnlyMode) {
+                <div class="review-only-notice">
+                  <div class="notice-icon">👁️</div>
+                  <div class="notice-content">
+                    <h4>只读复盘模式</h4>
+                    <p>您当前处于只读模式,仅可查看项目复盘数据,无法进行编辑操作</p>
+                  </div>
+                </div>
+              }
+              <!-- 上方 2x2 网格:前4个模块(只读模式下隐藏) -->
+              @if (!isReviewOnlyMode) {
               <div class="aftercare-grid">
                 
                 <!-- 1/4:尾款结算 -->
@@ -872,6 +895,7 @@
                   </div>
                 </div>
               </div>
+              }
               
               <!-- 下方横向展示:项目复盘模块 -->
               <div class="project-review-section">
@@ -2468,5 +2492,16 @@
         </div>
       </div>
     }
+
+    <!-- 参考图管理标签页 -->
+    @if (isActiveTab('reference')) {
+      <div class="reference-tab-content">
+        <app-reference-image-manager 
+          [projectId]="projectId"
+          (imagesUpdated)="onReferenceImagesUpdated($event)">
+        </app-reference-image-manager>
+      </div>
+    }
   </div>
 }
+ 

+ 88 - 0
src/app/pages/designer/project-detail/project-detail.scss

@@ -5154,3 +5154,91 @@
   }
 }
 
+/* ==================== 只读复盘模式样式 ==================== */
+.review-only-notice {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 20px 24px;
+  margin-bottom: 24px;
+  background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
+  border-left: 4px solid #2196f3;
+  border-radius: 12px;
+  box-shadow: 0 2px 12px rgba(33, 150, 243, 0.1);
+  animation: slideInDown 0.5s ease-out;
+
+  .notice-icon {
+    font-size: 32px;
+    animation: pulse 2s infinite;
+  }
+
+  .notice-content {
+    flex: 1;
+
+    h4 {
+      margin: 0 0 6px 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #1976d2;
+    }
+
+    p {
+      margin: 0;
+      font-size: 14px;
+      color: #1565c0;
+      line-height: 1.5;
+    }
+  }
+}
+
+@keyframes slideInDown {
+  from {
+    opacity: 0;
+    transform: translateY(-20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* ==================== 只读徽章样式 ==================== */
+.readonly-badge {
+  position: absolute;
+  top: -8px;
+  right: -8px;
+  background: linear-gradient(135deg, #ff9500 0%, #ff6b00 100%);
+  color: white;
+  font-size: 10px;
+  font-weight: 600;
+  padding: 3px 8px;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(255, 149, 0, 0.3);
+  animation: pulse 2s infinite;
+}
+
+.stage-nav-item.readonly {
+  cursor: not-allowed;
+  opacity: 0.7;
+  
+  &:hover {
+    transform: none;
+  }
+  
+  .stage-nav-circle {
+    background: linear-gradient(135deg, #e0e0e0 0%, #bdbdbd 100%);
+  }
+}
+
+/* ==================== 参考图管理标签页样式 ==================== */
+.reference-tab-content {
+  padding: 24px;
+  background: #f8f9fa;
+  min-height: calc(100vh - 300px);
+  
+  app-reference-image-manager {
+    display: block;
+    width: 100%;
+  }
+}
+

+ 60 - 18
src/app/pages/designer/project-detail/project-detail.ts

@@ -25,6 +25,8 @@ import { QuotationDetailsComponent, QuotationData } from './components/quotation
 import { DesignerAssignmentComponent, DesignerAssignmentData, Designer as AssignmentDesigner } from './components/designer-assignment/designer-assignment.component';
 // 引入客户服务模块的设计师日历组件
 import { DesignerCalendarComponent, Designer as CalendarDesigner, ProjectGroup as CalendarProjectGroup } from '../../customer-service/consultation-order/components/designer-calendar/designer-calendar.component';
+// 引入参考图管理组件
+import { ReferenceImageManagerComponent } from './components/reference-image-manager/reference-image-manager.component';
 
 import { ColorAnalysisResult } from '../../../shared/services/color-analysis.service';
 
@@ -263,7 +265,7 @@ interface DeliveryProcess {
 @Component({
   selector: 'app-project-detail',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, RequirementsConfirmCardComponent, SettlementCardComponent, CustomerReviewCardComponent, CustomerReviewFormComponent, ComplaintCardComponent, PanoramicSynthesisCardComponent, QuotationDetailsComponent, DesignerAssignmentComponent, DesignerCalendarComponent],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RequirementsConfirmCardComponent, SettlementCardComponent, CustomerReviewCardComponent, CustomerReviewFormComponent, ComplaintCardComponent, PanoramicSynthesisCardComponent, QuotationDetailsComponent, DesignerAssignmentComponent, DesignerCalendarComponent, ReferenceImageManagerComponent],
   templateUrl: './project-detail.html',
   styleUrls: ['./project-detail.scss', './debug-styles.scss', './horizontal-panel.scss']
 })
@@ -286,6 +288,9 @@ export class ProjectDetail implements OnInit, OnDestroy {
   showDropdown: boolean = false;
   currentStage: string = '';
   
+  // 新增:只读复盘模式(从财务工作台跳转)
+  isReviewOnlyMode: boolean = false;
+  
   // 新增:尾款结算完成状态
   isSettlementCompleted: boolean = false;
   isConfirmingSettlement: boolean = false;
@@ -361,11 +366,12 @@ export class ProjectDetail implements OnInit, OnDestroy {
   showExceptionForm: boolean = false;
   
   // 标签页相关
-  activeTab: 'progress' | 'members' | 'files' = 'progress';
-  tabs: Array<{ id: 'progress' | 'members' | 'files'; name: string }> = [
+  activeTab: 'progress' | 'members' | 'files' | 'reference' = 'progress';
+  tabs: Array<{ id: 'progress' | 'members' | 'files' | 'reference'; name: string }> = [
     { id: 'progress', name: '项目进度' },
     { id: 'members', name: '项目成员' },
-    { id: 'files', name: '项目文件' }
+    { id: 'files', name: '项目文件' },
+    { id: 'reference', name: '参考图管理' }
   ];
 
   // 标准化阶段(视图层映射)
@@ -491,12 +497,12 @@ export class ProjectDetail implements OnInit, OnDestroy {
   ) {}
 
   // 切换标签页
-  switchTab(tabId: 'progress' | 'members' | 'files') {
+  switchTab(tabId: 'progress' | 'members' | 'files' | 'reference') {
     this.activeTab = tabId;
   }
 
   // 类型安全的标签页检查方法
-  isActiveTab(tabId: 'progress' | 'members' | 'files'): boolean {
+  isActiveTab(tabId: 'progress' | 'members' | 'files' | 'reference'): boolean {
     return this.activeTab === tabId;
   }
 
@@ -607,6 +613,26 @@ export class ProjectDetail implements OnInit, OnDestroy {
   }
 
   ngOnInit(): void {
+    // 检查是否为只读复盘模式(从财务工作台跳转)
+    this.route.queryParams.subscribe(params => {
+      if (params['view'] === 'review-only') {
+        this.isReviewOnlyMode = true;
+      }
+      
+      // 检查是否需要直接定位到售后板块
+      if (params['section'] === 'aftercare' || params['view'] === 'review-only') {
+        // 自动切换到售后板块
+        this.expandedSection = 'aftercare';
+        // 滚动到项目复盘区域
+        setTimeout(() => {
+          const reviewSection = document.querySelector('.project-review-section');
+          if (reviewSection) {
+            reviewSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
+          }
+        }, 500);
+      }
+    });
+
     // 初始化表单
     this.initializeForms();
     
@@ -636,12 +662,12 @@ export class ProjectDetail implements OnInit, OnDestroy {
     // 新增:监听查询参数,支持通过 activeTab 设置初始标签页和 currentStage 设置当前阶段
     this.route.queryParamMap.subscribe(qp => {
       const raw = qp.get('activeTab');
-      const alias: Record<string, 'progress' | 'members' | 'files'> = {
+      const alias: Record<string, 'progress' | 'members' | 'files' | 'reference'> = {
         requirements: 'progress',
         overview: 'progress'
       };
       const tab = raw && (raw in alias ? alias[raw] : raw);
-      if (tab === 'progress' || tab === 'members' || tab === 'files') {
+      if (tab === 'progress' || tab === 'members' || tab === 'files' || tab === 'reference') {
         this.activeTab = tab;
       }
       
@@ -2312,6 +2338,13 @@ export class ProjectDetail implements OnInit, OnDestroy {
     console.log('预览文件:', file.name);
   }
 
+  // 处理参考图更新事件
+  onReferenceImagesUpdated(images: any[]): void {
+    console.log('参考图已更新:', images);
+    // 这里可以实现保存到后端的逻辑
+    // 也可以触发项目状态的更新
+  }
+
   // 同步需求关键信息到客户信息卡片
   syncRequirementKeyInfo(requirementData: any): void {
     if (requirementData) {
@@ -3997,6 +4030,9 @@ export class ProjectDetail implements OnInit, OnDestroy {
   startPanoramicSynthesis(): void {
     console.log('🎨 启动全景图合成...');
     
+    // 显示提示信息
+    alert('📸 请选择需要合成全景图的图片\n\n提示:\n1. 建议选择同一空间的多个角度照片\n2. 图片文件名可以包含空间名称(如:客厅-角度1.jpg)\n3. 系统会自动识别并分类');
+    
     // 打开文件选择对话框,支持多文件选择
     const fileInput = document.createElement('input');
     fileInput.type = 'file';
@@ -4145,19 +4181,19 @@ export class ProjectDetail implements OnInit, OnDestroy {
 
   // 查看全景图画廊
   viewPanoramicGallery(): void {
-    console.log('打开全景图画廊');
+    console.log('📁 打开全景图画廊');
     
     if (this.panoramicSyntheses.length === 0) {
-      alert('暂无全景图记录');
+      alert('📁 全景图画廊\n\n暂无全景图记录,请先进行全景图合成。');
       return;
     }
     
-    // 显示全景图列表
-    const galleryInfo = this.panoramicSyntheses.map((s, i) => 
-      `${i + 1}. ${s.projectName} - ${s.status === 'completed' ? '已完成' : '处理中'} (${s.spaces.length}个空间)`
+    // 显示全景图列表提示
+    const galleryInfo = this.panoramicSyntheses.map((item, index) => 
+      `${index + 1}. ${item.projectName} - ${item.status === 'completed' ? '✅ 已完成' : '⏳ 处理中'} (${item.spaces?.length || 0}个空间)`
     ).join('\n');
     
-    alert(`全景图画廊\n\n${galleryInfo}\n\n点击查看详情功能开发中...`);
+    alert(`📁 全景图画廊(共${this.panoramicSyntheses.length}个)\n\n${galleryInfo}\n\n提示:点击列表中的全景图可查看详情或下载`);
   }
   
   // 复制全景图链接
@@ -4273,6 +4309,9 @@ export class ProjectDetail implements OnInit, OnDestroy {
   createComplaintManually(): void {
     console.log('📝 手动创建投诉');
     
+    // 显示创建投诉表单提示
+    alert('📝 创建投诉记录\n\n即将打开投诉创建表单,请填写以下信息:\n• 投诉环节\n• 核心问题\n• 客户反馈\n• 严重程度');
+    
     // 弹出创建投诉表单
     const complaintReason = prompt('请输入投诉原因:');
     if (!complaintReason || complaintReason.trim() === '') return;
@@ -4357,21 +4396,24 @@ export class ProjectDetail implements OnInit, OnDestroy {
     console.log('⚙️ 设置关键词监控');
     
     if (this.isKeywordMonitoringActive) {
-      const confirmStop = confirm('关键词监控已激活,是否停止监控?');
+      const confirmStop = confirm('⚙️ 关键词监控管理\n\n状态:✅ 已激活\n监控关键词:' + this.complaintKeywords.join('、') + '\n\n是否停止监控?');
       if (confirmStop) {
         this.isKeywordMonitoringActive = false;
-        alert('✅ 关键词监控已停止');
+        alert('⏸️ 关键词监控已停止\n\n系统将不再自动抓取企业微信群中的投诉关键词');
       }
       return;
     }
     
-    // 显示当前监控关键词
+    // 显示设置说明和当前关键词
+    alert('⚙️ 关键词监控功能\n\n功能说明:\n• 自动监测企业微信群消息\n• 识别投诉相关关键词\n• 自动创建投诉记录并标注\n• 实时通知相关处理人员\n\n当前监控关键词:\n' + this.complaintKeywords.join('、'));
+    
     const currentKeywords = this.complaintKeywords.join('、');
-    const newKeywords = prompt(`当前监控关键词:\n\n${currentKeywords}\n\n请输入要添加的关键词(多个关键词用逗号分隔):`);
+    const newKeywords = prompt(`当前监控关键词:\n${currentKeywords}\n\n请输入要添加的关键词(多个关键词用逗号分隔):`);
     
     if (newKeywords && newKeywords.trim()) {
       const keywords = newKeywords.split(/[,,、]/).map(k => k.trim()).filter(k => k);
       this.complaintKeywords = [...new Set([...this.complaintKeywords, ...keywords])];
+      alert(`✅ 关键词已更新\n\n新增关键词:${keywords.join('、')}\n\n全部监控关键词:\n${this.complaintKeywords.join('、')}`);
     }
     
     // 激活监控

+ 8 - 10
src/app/pages/designer/project-detail/project-review-styles.scss

@@ -617,27 +617,25 @@
                   .stage-score {
                     font-weight: 700;
                     font-size: 16px;
-                    padding: 4px 12px;
-                    border-radius: 6px;
+                    padding: 6px 14px;
+                    border-radius: 8px;
+                    color: white !important; // 强制白色文字确保可读性
+                    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); // 添加文字阴影增强可读性
                     
                     &.excellent { 
-                      color: white;
-                      background: #34c759;
+                      background: linear-gradient(135deg, #34c759 0%, #28a745 100%);
                       box-shadow: 0 2px 8px rgba(52, 199, 89, 0.3);
                     }
                     &.good { 
-                      color: white;
-                      background: #007aff;
+                      background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
                       box-shadow: 0 2px 8px rgba(0, 122, 255, 0.3);
                     }
                     &.fair { 
-                      color: white;
-                      background: #ff9500;
+                      background: linear-gradient(135deg, #ff9500 0%, #e68600 100%);
                       box-shadow: 0 2px 8px rgba(255, 149, 0, 0.3);
                     }
                     &.poor { 
-                      color: white;
-                      background: #ff3b30;
+                      background: linear-gradient(135deg, #ff3b30 0%, #d32f2f 100%);
                       box-shadow: 0 2px 8px rgba(255, 59, 48, 0.3);
                     }
                   }

+ 228 - 7
src/app/pages/finance/dashboard/dashboard.html

@@ -7,23 +7,23 @@
 
   <!-- 快捷操作区域 -->
   <div class="quick-actions">
-    <button class="action-btn" (click)="handleQuickAction('newQuote')">
+    <button class="action-btn" [class.active]="activeNavItem() === 'newQuote'" (click)="handleQuickAction('newQuote')">
       <div class="action-icon new-quote">📝</div>
       <div class="action-label">新建报价</div>
     </button>
-    <button class="action-btn" (click)="handleQuickAction('recordPayment')">
+    <button class="action-btn" [class.active]="activeNavItem() === 'recordPayment'" (click)="handleQuickAction('recordPayment')">
       <div class="action-icon record-payment">💰</div>
       <div class="action-label">录入收款</div>
     </button>
-    <button class="action-btn" (click)="handleQuickAction('generateReport')">
+    <button class="action-btn" [class.active]="activeNavItem() === 'generateReport'" (click)="handleQuickAction('generateReport')">
       <div class="action-icon generate-report">📊</div>
       <div class="action-label">生成日接单表</div>
     </button>
-    <button class="action-btn" (click)="handleQuickAction('quotationApproval')">
+    <button class="action-btn" [class.active]="activeNavItem() === 'quotationApproval'" (click)="handleQuickAction('quotationApproval')">
       <div class="action-icon quotation-approval">✅</div>
       <div class="action-label">报价审核</div>
     </button>
-    <button class="action-btn work-hour-btn" (click)="toggleWorkHourModule()">
+    <button class="action-btn work-hour-btn" [class.active]="showWorkHourModule()" (click)="toggleWorkHourModule(); activeNavItem.set('workHour')">
       <div class="action-icon work-hour">⏱️</div>
       <div class="action-label">工时统计</div>
     </button>
@@ -38,6 +38,28 @@
         <button class="close-btn" (click)="toggleWorkHourModule()">×</button>
       </div>
       
+      <!-- 时间维度筛选 -->
+      <div class="time-dimension-filter">
+        <button 
+          class="dimension-btn" 
+          [class.active]="timeDimension() === 'week'"
+          (click)="setTimeDimension('week')">
+          本周
+        </button>
+        <button 
+          class="dimension-btn" 
+          [class.active]="timeDimension() === 'month'"
+          (click)="setTimeDimension('month')">
+          本月
+        </button>
+        <button 
+          class="dimension-btn" 
+          [class.active]="timeDimension() === 'quarter'"
+          (click)="setTimeDimension('quarter')">
+          本季度
+        </button>
+      </div>
+
       <!-- 工时概览卡片 -->
       <div class="work-hour-overview" *ngIf="workHourDashboard()">
         <div class="overview-card">
@@ -63,11 +85,198 @@
             <div class="card-label">平均有效工时</div>
           </div>
         </div>
+        
+        <div class="overview-card warning-card">
+          <div class="card-icon">⚠️</div>
+          <div class="card-content">
+            <div class="card-value">{{ overdueProjects().length }}</div>
+            <div class="card-label">逾期项目数</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 标准工时设置 -->
+      <div class="standard-hours-section">
+        <h3>📊 标准工时预设</h3>
+        <div class="complexity-cards">
+          <div class="complexity-card" *ngFor="let complexity of projectComplexities">
+            <div class="complexity-header">
+              <span class="complexity-type">{{ complexity.type }}</span>
+            </div>
+            <div class="complexity-stages">
+              <div class="stage-item">
+                <span class="stage-name">需求深化</span>
+                <span class="stage-hours">{{ complexity.standardHours.requirementDeepening }}h</span>
+              </div>
+              <div class="stage-item">
+                <span class="stage-name">建模</span>
+                <span class="stage-hours">{{ complexity.standardHours.modeling }}h</span>
+              </div>
+              <div class="stage-item">
+                <span class="stage-name">渲染</span>
+                <span class="stage-hours">{{ complexity.standardHours.rendering }}h</span>
+              </div>
+              <div class="stage-item">
+                <span class="stage-name">后期</span>
+                <span class="stage-hours">{{ complexity.standardHours.postProduction }}h</span>
+              </div>
+            </div>
+            <div class="complexity-total">
+              总计: {{ complexity.standardHours.requirementDeepening + complexity.standardHours.modeling + complexity.standardHours.rendering + complexity.standardHours.postProduction }}h
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 逾期项目管理 -->
+      <div class="overdue-section">
+        <div class="section-title">
+          <h3>⚠️ 逾期项目监控</h3>
+          <button class="export-btn" (click)="exportOverdueReport()">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
+              <polyline points="7 10 12 15 17 10"></polyline>
+              <line x1="12" y1="15" x2="12" y2="3"></line>
+            </svg>
+            导出报表
+          </button>
+        </div>
+        
+        <div class="overdue-list">
+          <div 
+            class="overdue-item" 
+            *ngFor="let project of overdueProjects()"
+            [class]="getOverdueClass(project.overdueDays)"
+            (click)="viewProjectDetail(project.id)">
+            <div class="overdue-header">
+              <div class="project-info">
+                <span class="project-name">{{ project.name }}</span>
+                <span class="project-id">ID: {{ project.id }}</span>
+              </div>
+              <div class="overdue-badge" [class]="getOverdueClass(project.overdueDays)">
+                逾期 {{ project.overdueDays }} 天
+              </div>
+            </div>
+            <div class="overdue-content">
+              <div class="overdue-detail">
+                <span class="detail-label">阶段:</span>
+                <span class="detail-value stage-badge">{{ project.stage }}</span>
+              </div>
+              <div class="overdue-detail">
+                <span class="detail-label">设计师:</span>
+                <span class="detail-value">{{ project.designerName }}</span>
+              </div>
+              <div class="overdue-detail">
+                <span class="detail-label">原因:</span>
+                <span class="detail-value reason">{{ project.reason }}</span>
+              </div>
+            </div>
+            <div class="overdue-action">
+              <span class="view-detail-text">查看详情 →</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 人效统计看板 -->
+      <div class="efficiency-dashboard">
+        <h3>📈 人效统计看板 - {{ getTimeDimensionText() }}</h3>
+        
+        <div class="efficiency-list">
+          <div 
+            class="efficiency-item" 
+            *ngFor="let designer of designerEfficiencies()"
+            (click)="viewDesignerDetail(designer.id)">
+            
+            <!-- 设计师基本信息 -->
+            <div class="efficiency-header">
+              <div class="designer-info">
+                <div class="designer-name">{{ designer.name }}</div>
+                <div class="designer-stats-summary">
+                  <span class="stat-chip" [style.background-color]="getEfficiencyLevelColor(designer.avgCompletionTime)">
+                    平均 {{ designer.avgCompletionTime }} 天
+                  </span>
+                  <span class="stat-chip overdue" *ngIf="designer.overdueProjectCount > 0">
+                    逾期 {{ designer.overdueProjectCount }} 个
+                  </span>
+                  <span class="stat-chip idle" *ngIf="designer.idleDays > 0">
+                    闲置 {{ designer.idleDays }} 天
+                  </span>
+                </div>
+              </div>
+            </div>
+
+            <!-- 各阶段工时利用率 -->
+            <div class="stage-utilization">
+              <div class="utilization-item">
+                <div class="utilization-header">
+                  <span class="stage-label">需求深化</span>
+                  <span class="utilization-value">{{ designer.stageUtilization.requirementDeepening }}%</span>
+                </div>
+                <div class="utilization-bar">
+                  <div 
+                    class="utilization-fill" 
+                    [style.width.%]="designer.stageUtilization.requirementDeepening"
+                    [style.background-color]="getUtilizationColor(designer.stageUtilization.requirementDeepening)">
+                  </div>
+                </div>
+              </div>
+
+              <div class="utilization-item">
+                <div class="utilization-header">
+                  <span class="stage-label">建模</span>
+                  <span class="utilization-value">{{ designer.stageUtilization.modeling }}%</span>
+                </div>
+                <div class="utilization-bar">
+                  <div 
+                    class="utilization-fill" 
+                    [style.width.%]="designer.stageUtilization.modeling"
+                    [style.background-color]="getUtilizationColor(designer.stageUtilization.modeling)">
+                  </div>
+                </div>
+              </div>
+
+              <div class="utilization-item">
+                <div class="utilization-header">
+                  <span class="stage-label">渲染</span>
+                  <span class="utilization-value">{{ designer.stageUtilization.rendering }}%</span>
+                </div>
+                <div class="utilization-bar">
+                  <div 
+                    class="utilization-fill" 
+                    [style.width.%]="designer.stageUtilization.rendering"
+                    [style.background-color]="getUtilizationColor(designer.stageUtilization.rendering)">
+                  </div>
+                </div>
+              </div>
+
+              <div class="utilization-item">
+                <div class="utilization-header">
+                  <span class="stage-label">后期</span>
+                  <span class="utilization-value">{{ designer.stageUtilization.postProduction }}%</span>
+                </div>
+                <div class="utilization-bar">
+                  <div 
+                    class="utilization-fill" 
+                    [style.width.%]="designer.stageUtilization.postProduction"
+                    [style.background-color]="getUtilizationColor(designer.stageUtilization.postProduction)">
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <!-- 决策建议 -->
+            <div class="efficiency-suggestion">
+              <div class="suggestion-icon">💡</div>
+              <div class="suggestion-text">{{ designer.suggestion }}</div>
+            </div>
+          </div>
+        </div>
       </div>
 
       <!-- 绩效等级分布 -->
       <div class="performance-distribution" *ngIf="workHourDashboard()">
-        <h3>绩效等级分布</h3>
+        <h3>🏆 绩效等级分布</h3>
         <div class="performance-chart">
           <div class="performance-item" *ngFor="let level of ['S', 'A', 'B', 'C']">
             <div class="level-indicator" [style.background-color]="getPerformanceLevelColor(level)">
@@ -83,7 +292,7 @@
 
       <!-- 月度统计表格 -->
       <div class="monthly-stats" *ngIf="monthlyStats().length > 0">
-        <h3>设计师月度统计</h3>
+        <h3>📅 设计师月度统计</h3>
         <div class="stats-table">
           <div class="table-header">
             <div class="header-cell">设计师</div>
@@ -174,4 +383,16 @@
       </div>
     </div>
   </div>
+
+  <!-- 精美悬浮按钮 - 跳转到项目复盘 -->
+  <button class="floating-review-btn" (click)="goToAftercare()" *ngIf="showFloatingButton()" title="查看项目复盘">
+    <div class="floating-btn-icon">
+      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <path d="M9 11l3 3L22 4"></path>
+        <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"></path>
+      </svg>
+    </div>
+    <span class="floating-btn-text">项目复盘</span>
+    <div class="floating-btn-pulse"></div>
+  </button>
 </div>

+ 711 - 2
src/app/pages/finance/dashboard/dashboard.scss

@@ -36,6 +36,7 @@
 }
 
 .action-btn {
+  position: relative;
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -46,12 +47,61 @@
   border: none;
   box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
   cursor: pointer;
-  transition: all 0.3s ease;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  overflow: hidden;
+
+  // 点击涟漪效果
+  &::before {
+    content: '';
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 0;
+    height: 0;
+    border-radius: 50%;
+    background: rgba(59, 130, 246, 0.2);
+    transform: translate(-50%, -50%);
+    transition: width 0.6s ease-out, height 0.6s ease-out, opacity 0.3s ease-out;
+    opacity: 0;
+  }
+
+  &:active::before {
+    width: 300px;
+    height: 300px;
+    opacity: 1;
+  }
+
+  // 点击缩放效果
+  &:active {
+    transform: scale(0.96);
+  }
 }
 
 .action-btn:hover {
   transform: translateY(-2px);
   box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  
+  &:active {
+    transform: translateY(-1px) scale(0.96);
+  }
+}
+
+.action-btn.active {
+  background: linear-gradient(135deg, #007aff 0%, #0056b3 100%);
+  color: white;
+  box-shadow: 0 4px 20px rgba(0, 122, 255, 0.3);
+  
+  &::before {
+    background: rgba(255, 255, 255, 0.2);
+  }
+}
+
+.action-btn.active .action-icon {
+  color: white;
+}
+
+.action-btn.active .action-label {
+  color: white;
 }
 
 .action-icon {
@@ -142,6 +192,7 @@
 }
 
 .view-detail-btn {
+  position: relative;
   padding: 8px 16px;
   background-color: #f2f2f7;
   border: none;
@@ -150,11 +201,36 @@
   font-weight: 500;
   color: #007aff;
   cursor: pointer;
-  transition: all 0.3s ease;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 0;
+    height: 0;
+    border-radius: 50%;
+    background: rgba(0, 122, 255, 0.15);
+    transform: translate(-50%, -50%);
+    transition: width 0.5s ease-out, height 0.5s ease-out;
+  }
+
+  &:active::before {
+    width: 200px;
+    height: 200px;
+  }
+
+  &:active {
+    transform: scale(0.95);
+  }
 }
 
 .view-detail-btn:hover {
   background-color: #e5e5ea;
+  transform: translateY(-1px);
+  box-shadow: 0 2px 8px rgba(0, 122, 255, 0.15);
 }
 
 /* 待办任务区域 */
@@ -548,4 +624,637 @@
     opacity: 1;
     transform: translateY(0);
   }
+}
+
+/* 时间维度筛选器 */
+.time-dimension-filter {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 20px;
+  padding: 4px;
+  background-color: #f2f2f7;
+  border-radius: 10px;
+  width: fit-content;
+}
+
+.dimension-btn {
+  position: relative;
+  padding: 8px 20px;
+  background-color: transparent;
+  border: none;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  color: #8e8e93;
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 0;
+    height: 0;
+    border-radius: 50%;
+    background: rgba(0, 122, 255, 0.1);
+    transform: translate(-50%, -50%);
+    transition: width 0.4s ease-out, height 0.4s ease-out;
+  }
+
+  &:active::before {
+    width: 150px;
+    height: 150px;
+  }
+
+  &:active {
+    transform: scale(0.93);
+  }
+}
+
+.dimension-btn.active {
+  background-color: #ffffff;
+  color: #007aff;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  
+  &::before {
+    background: rgba(0, 122, 255, 0.08);
+  }
+}
+
+.dimension-btn:hover:not(.active) {
+  color: #1c1c1e;
+  background-color: rgba(0, 0, 0, 0.03);
+}
+
+/* 标准工时预设 */
+.standard-hours-section {
+  margin-bottom: 32px;
+}
+
+.standard-hours-section h3 {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin-bottom: 16px;
+}
+
+.complexity-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+  gap: 16px;
+}
+
+.complexity-card {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 16px;
+  padding: 20px;
+  color: white;
+  box-shadow: 0 4px 16px rgba(102, 126, 234, 0.2);
+  transition: transform 0.3s ease, box-shadow 0.3s ease;
+}
+
+.complexity-card:hover {
+  transform: translateY(-4px);
+  box-shadow: 0 6px 24px rgba(102, 126, 234, 0.3);
+}
+
+.complexity-header {
+  margin-bottom: 16px;
+}
+
+.complexity-type {
+  font-size: 16px;
+  font-weight: 600;
+  display: block;
+}
+
+.complexity-stages {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  margin-bottom: 16px;
+}
+
+.stage-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 12px;
+  background-color: rgba(255, 255, 255, 0.15);
+  border-radius: 8px;
+  backdrop-filter: blur(10px);
+}
+
+.stage-name {
+  font-size: 13px;
+  font-weight: 500;
+}
+
+.stage-hours {
+  font-size: 14px;
+  font-weight: 600;
+}
+
+.complexity-total {
+  font-size: 14px;
+  font-weight: 600;
+  text-align: center;
+  padding: 8px;
+  background-color: rgba(255, 255, 255, 0.2);
+  border-radius: 8px;
+}
+
+/* 逾期项目监控 */
+.overdue-section {
+  margin-bottom: 32px;
+}
+
+.overdue-section .section-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.overdue-section h3 {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin: 0;
+}
+
+.export-btn {
+  position: relative;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 8px 16px;
+  background-color: #007aff;
+  color: white;
+  border: none;
+  border-radius: 10px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  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.5s ease-out, height 0.5s ease-out;
+  }
+
+  &:active::before {
+    width: 200px;
+    height: 200px;
+  }
+
+  &:active {
+    transform: scale(0.95);
+    background-color: #0056b3;
+  }
+}
+
+.export-btn:hover {
+  background-color: #0056b3;
+  box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3);
+  transform: translateY(-1px);
+}
+
+.overdue-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.overdue-item {
+  background-color: #ffffff;
+  border-radius: 16px;
+  padding: 20px;
+  border-left: 4px solid #ff9500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.overdue-item:hover {
+  transform: translateX(4px);
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+}
+
+.overdue-item.mild {
+  border-left-color: #ff9500;
+}
+
+.overdue-item.warning {
+  border-left-color: #ff6b00;
+}
+
+.overdue-item.severe {
+  border-left-color: #ff3b30;
+}
+
+.overdue-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 12px;
+}
+
+.project-info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.project-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1c1c1e;
+}
+
+.project-id {
+  font-size: 12px;
+  color: #8e8e93;
+}
+
+.overdue-badge {
+  padding: 6px 12px;
+  border-radius: 8px;
+  font-size: 12px;
+  font-weight: 600;
+  color: white;
+}
+
+.overdue-badge.mild {
+  background-color: #ff9500;
+}
+
+.overdue-badge.warning {
+  background-color: #ff6b00;
+}
+
+.overdue-badge.severe {
+  background-color: #ff3b30;
+}
+
+.overdue-content {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  margin-bottom: 12px;
+}
+
+.overdue-detail {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.detail-label {
+  font-size: 13px;
+  color: #8e8e93;
+}
+
+.detail-value {
+  font-size: 13px;
+  font-weight: 500;
+  color: #1c1c1e;
+}
+
+.detail-value.stage-badge {
+  padding: 4px 10px;
+  background-color: #f2f2f7;
+  border-radius: 6px;
+}
+
+.detail-value.reason {
+  color: #ff3b30;
+  font-weight: 600;
+}
+
+.overdue-action {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.view-detail-text {
+  font-size: 14px;
+  color: #007aff;
+  font-weight: 500;
+}
+
+/* 人效统计看板 */
+.efficiency-dashboard {
+  margin-bottom: 32px;
+}
+
+.efficiency-dashboard h3 {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin-bottom: 16px;
+}
+
+.efficiency-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.efficiency-item {
+  background-color: #ffffff;
+  border-radius: 16px;
+  padding: 24px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.efficiency-item:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.1);
+}
+
+.efficiency-header {
+  margin-bottom: 20px;
+}
+
+.designer-info {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.designer-name {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1c1c1e;
+}
+
+.designer-stats-summary {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+
+.stat-chip {
+  padding: 6px 12px;
+  border-radius: 8px;
+  font-size: 12px;
+  font-weight: 600;
+  color: white;
+}
+
+.stat-chip.overdue {
+  background-color: #ff3b30;
+}
+
+.stat-chip.idle {
+  background-color: #ff9500;
+}
+
+/* 阶段利用率 */
+.stage-utilization {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 16px;
+  margin-bottom: 20px;
+}
+
+.utilization-item {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.utilization-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.stage-label {
+  font-size: 13px;
+  font-weight: 500;
+  color: #6c6c70;
+}
+
+.utilization-value {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1c1c1e;
+}
+
+.utilization-bar {
+  height: 8px;
+  background-color: #f2f2f7;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.utilization-fill {
+  height: 100%;
+  border-radius: 4px;
+  transition: width 0.5s ease, background-color 0.3s ease;
+}
+
+/* 决策建议 */
+.efficiency-suggestion {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 16px;
+  background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
+  border-radius: 12px;
+}
+
+.suggestion-icon {
+  font-size: 24px;
+}
+
+.suggestion-text {
+  flex: 1;
+  font-size: 14px;
+  font-weight: 500;
+  color: #1c1c1e;
+}
+
+/* 警告卡片样式 */
+.overview-card.warning-card {
+  border: 2px solid #ff3b30;
+  background: linear-gradient(135deg, #fff5f5 0%, #ffe5e5 100%);
+}
+
+.overview-card.warning-card .card-value {
+  color: #ff3b30;
+}
+
+/* 精美悬浮按钮 */
+.floating-review-btn {
+  position: fixed;
+  bottom: 32px;
+  right: 32px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 16px 24px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  border: none;
+  border-radius: 50px;
+  font-size: 16px;
+  font-weight: 600;
+  box-shadow: 0 8px 32px rgba(102, 126, 234, 0.4);
+  cursor: pointer;
+  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+  z-index: 1000;
+  overflow: hidden;
+}
+
+.floating-review-btn:hover {
+  transform: translateY(-4px) scale(1.05);
+  box-shadow: 0 12px 48px rgba(102, 126, 234, 0.6);
+}
+
+.floating-review-btn:active {
+  transform: translateY(-2px) scale(1.02);
+}
+
+.floating-btn-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 28px;
+  height: 28px;
+  background-color: rgba(255, 255, 255, 0.2);
+  border-radius: 50%;
+  transition: all 0.3s ease;
+}
+
+.floating-review-btn:hover .floating-btn-icon {
+  background-color: rgba(255, 255, 255, 0.3);
+  transform: rotate(360deg);
+}
+
+.floating-btn-text {
+  font-weight: 600;
+  letter-spacing: 0.5px;
+}
+
+/* 脉冲动画效果 */
+.floating-btn-pulse {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  border-radius: 50px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  opacity: 0;
+  z-index: -1;
+  animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+    opacity: 0.7;
+  }
+  50% {
+    transform: scale(1.15);
+    opacity: 0;
+  }
+  100% {
+    transform: scale(1);
+    opacity: 0;
+  }
+}
+
+/* 悬浮按钮响应式 */
+@media (max-width: 768px) {
+  .floating-review-btn {
+    bottom: 20px;
+    right: 20px;
+    padding: 14px 20px;
+    font-size: 14px;
+  }
+
+  .floating-btn-text {
+    display: none;
+  }
+
+  .floating-btn-icon {
+    width: 24px;
+    height: 24px;
+  }
+
+  .floating-review-btn {
+    border-radius: 50%;
+    padding: 16px;
+  }
+}
+
+/* 渐入动画 */
+@keyframes fadeInUp {
+  from {
+    opacity: 0;
+    transform: translateY(30px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.complexity-card,
+.overdue-item,
+.efficiency-item {
+  animation: fadeInUp 0.6s ease-out;
+}
+
+.complexity-card:nth-child(2) {
+  animation-delay: 0.1s;
+}
+
+.complexity-card:nth-child(3) {
+  animation-delay: 0.2s;
+}
+
+.complexity-card:nth-child(4) {
+  animation-delay: 0.3s;
+}
+
+/* 响应式调整 */
+@media (max-width: 1024px) {
+  .stage-utilization {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+@media (max-width: 768px) {
+  .complexity-cards {
+    grid-template-columns: 1fr;
+  }
+
+  .stage-utilization {
+    grid-template-columns: 1fr;
+  }
+
+  .time-dimension-filter {
+    width: 100%;
+  }
+
+  .dimension-btn {
+    flex: 1;
+  }
 }

+ 362 - 14
src/app/pages/finance/dashboard/dashboard.ts

@@ -5,6 +5,43 @@ import { AuthService } from '../../../services/auth.service';
 import { WorkHourService } from '../../../services/work-hour.service';
 import { WorkHourDashboardData, MonthlyWorkHourStats, PerformanceLevel } from '../../../models/work-hour.model';
 
+// 项目复杂度类型
+interface ProjectComplexity {
+  type: string; // '家装小户型' | '家装大户型' | '工装商铺' | '工装写字楼'
+  standardHours: {
+    requirementDeepening: number;
+    modeling: number;
+    rendering: number;
+    postProduction: number;
+  };
+}
+
+// 逾期项目信息
+interface OverdueProject {
+  id: string;
+  name: string;
+  stage: string;
+  overdueDays: number;
+  reason: string;
+  designerName: string;
+}
+
+// 设计师效率统计
+interface DesignerEfficiency {
+  id: string;
+  name: string;
+  avgCompletionTime: number; // 天
+  overdueProjectCount: number;
+  stageUtilization: {
+    requirementDeepening: number; // 百分比
+    modeling: number;
+    rendering: number;
+    postProduction: number;
+  };
+  idleDays: number; // 闲置天数
+  suggestion: string;
+}
+
 @Component({
   selector: 'app-dashboard',
   imports: [CommonModule, RouterModule],
@@ -40,6 +77,82 @@ export class Dashboard implements OnInit {
   monthlyStats = signal<MonthlyWorkHourStats[]>([]);
   showWorkHourModule = signal(false);
 
+  // 新增:项目复杂度预设标准工时
+  projectComplexities: ProjectComplexity[] = [
+    {
+      type: '家装小户型',
+      standardHours: { requirementDeepening: 16, modeling: 40, rendering: 24, postProduction: 8 }
+    },
+    {
+      type: '家装大户型',
+      standardHours: { requirementDeepening: 24, modeling: 64, rendering: 40, postProduction: 16 }
+    },
+    {
+      type: '工装商铺',
+      standardHours: { requirementDeepening: 32, modeling: 80, rendering: 48, postProduction: 16 }
+    },
+    {
+      type: '工装写字楼',
+      standardHours: { requirementDeepening: 40, modeling: 120, rendering: 72, postProduction: 24 }
+    }
+  ];
+
+  // 新增:逾期项目列表
+  overdueProjects = signal<OverdueProject[]>([
+    { id: 'P001', name: '现代简约三居室', stage: '渲染', overdueDays: 2, reason: '客户需求变更', designerName: '张设计' },
+    { id: 'P002', name: '商业办公空间', stage: '建模', overdueDays: 5, reason: '资源不足', designerName: '李设计' },
+    { id: 'P003', name: '北欧风格两居室', stage: '后期', overdueDays: 1, reason: '客户需求变更', designerName: '王设计' }
+  ]);
+
+  // 新增:设计师效率统计
+  designerEfficiencies = signal<DesignerEfficiency[]>([
+    {
+      id: 'D001',
+      name: '张设计',
+      avgCompletionTime: 18,
+      overdueProjectCount: 1,
+      stageUtilization: { requirementDeepening: 85, modeling: 92, rendering: 78, postProduction: 88 },
+      idleDays: 3,
+      suggestion: '表现优秀,可适当增加项目难度'
+    },
+    {
+      id: 'D002',
+      name: '李设计',
+      avgCompletionTime: 25,
+      overdueProjectCount: 2,
+      stageUtilization: { requirementDeepening: 70, modeling: 65, rendering: 72, postProduction: 80 },
+      idleDays: 8,
+      suggestion: '近30天闲置8天,建议分配更多项目'
+    },
+    {
+      id: 'D003',
+      name: '王设计',
+      avgCompletionTime: 15,
+      overdueProjectCount: 0,
+      stageUtilization: { requirementDeepening: 95, modeling: 90, rendering: 93, postProduction: 92 },
+      idleDays: 1,
+      suggestion: '高效能设计师,建议承接重点项目'
+    },
+    {
+      id: 'D004',
+      name: '赵设计',
+      avgCompletionTime: 22,
+      overdueProjectCount: 1,
+      stageUtilization: { requirementDeepening: 80, modeling: 75, rendering: 70, postProduction: 85 },
+      idleDays: 10,
+      suggestion: '需要提升渲染阶段效率,建议参加培训'
+    }
+  ]);
+
+  // 时间筛选维度
+  timeDimension = signal<'week' | 'month' | 'quarter'>('month');
+
+  // 显示悬浮按钮
+  showFloatingButton = signal(true);
+
+  // 当前激活的导航项
+  activeNavItem = signal<string>('');
+
   constructor(private authService: AuthService, private workHourService: WorkHourService, private router: Router) {}
 
   ngOnInit(): void {
@@ -91,18 +204,27 @@ export class Dashboard implements OnInit {
   handleTaskClick(task: any) {
     // 检查财务相关任务的权限
     if ((task.title === '临时收款待核定' || task.title === '报价待审核') && !this.checkUserRole('teamLead')) {
-      console.warn('权限不足:只有组长可以处理此任务');
+      alert('⚠️ 权限不足\n\n只有组长及以上权限可以处理此任务\n\n请联系管理员申请权限');
       return;
     }
     
-    // 根据任务来源跳转到对应页面
-    switch(task.source) {
-      case 'reconciliation':
-        window.location.href = '/finance/reconciliation';
-        break;
-      case 'project-records':
-        window.location.href = '/finance/project-records';
-        break;
+    // 显示任务详情
+    const taskDetails = `📋 待办任务详情\n\n任务:${task.title}\n优先级:${task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低'}\n截止时间:${task.time}\n\n是否立即处理此任务?`;
+    
+    if (confirm(taskDetails)) {
+      // 根据任务来源跳转到对应页面
+      switch(task.source) {
+        case 'reconciliation':
+          alert('🔄 正在跳转到对账管理页面...');
+          // window.location.href = '/finance/reconciliation';
+          break;
+        case 'project-records':
+          alert('🔄 正在跳转到项目记录页面...');
+          // window.location.href = '/finance/project-records';
+          break;
+        default:
+          alert(`📌 任务已标记\n\n任务"${task.title}"已加入处理队列`);
+      }
     }
   }
 
@@ -110,26 +232,119 @@ export class Dashboard implements OnInit {
   handleQuickAction(action: string) {
     // 检查权限
     if ((action === 'recordPayment' || action === 'generateReport') && !this.checkUserRole('teamLead')) {
-      console.warn('权限不足:只有组长可以执行此操作');
+      alert('⚠️ 权限不足\n\n只有组长及以上权限可以执行此操作\n\n请联系管理员申请权限');
       return;
     }
     
+    // 设置当前激活的导航项
+    this.activeNavItem.set(action);
+    
     switch(action) {
       case 'newQuote':
-        window.location.href = '/finance/project-records';
+        this.showNewQuoteDialog();
         break;
       case 'recordPayment':
-        window.location.href = '/finance/reconciliation';
+        this.showRecordPaymentDialog();
         break;
       case 'generateReport':
-        window.location.href = '/finance/reports';
+        this.showGenerateReportDialog();
         break;
       case 'quotationApproval':
-        this.router.navigate(['/finance/quotation-approval']);
+        const confirmNavigation = confirm('💼 即将跳转到报价审核页面\n\n是否继续?');
+        if (confirmNavigation) {
+          this.router.navigate(['/finance/quotation-approval']);
+        }
         break;
     }
   }
 
+  // 显示新建报价对话框
+  private showNewQuoteDialog(): void {
+    const projectName = prompt('📝 新建报价\n\n请输入项目名称:', '示例项目');
+    if (projectName) {
+      const projectType = prompt('请选择项目类型:\n\n1. 家装-小户型\n2. 家装-大户型\n3. 工装-商铺\n4. 工装-写字楼\n\n请输入数字 1-4:', '1');
+      
+      if (projectType) {
+        alert(`✅ 报价创建成功!\n\n项目名称:${projectName}\n项目类型:${this.getProjectTypeText(projectType)}\n\n系统正在为您准备报价模板...`);
+        console.log('创建报价:', { projectName, projectType });
+        // window.location.href = '/finance/project-records';
+      }
+    }
+  }
+
+  // 显示记录回款对话框
+  private showRecordPaymentDialog(): void {
+    const amount = prompt('💰 记录回款\n\n请输入回款金额(元):', '');
+    if (amount && !isNaN(Number(amount))) {
+      const paymentMethod = prompt('请选择支付方式:\n\n1. 微信支付\n2. 支付宝\n3. 银行转账\n4. 现金\n\n请输入数字 1-4:', '1');
+      
+      if (paymentMethod) {
+        alert(`✅ 回款记录成功!\n\n回款金额:¥${Number(amount).toLocaleString()}\n支付方式:${this.getPaymentMethodText(paymentMethod)}\n记录时间:${new Date().toLocaleString()}\n\n财务数据已更新`);
+        console.log('记录回款:', { amount, paymentMethod });
+        // window.location.href = '/finance/reconciliation';
+      }
+    }
+  }
+
+  // 显示生成报表对话框
+  private showGenerateReportDialog(): void {
+    const reportType = prompt('📊 生成财务报表\n\n请选择报表类型:\n\n1. 月度收入报表\n2. 项目利润分析\n3. 回款统计报表\n4. 成本费用报表\n\n请输入数字 1-4:', '1');
+    
+    if (reportType) {
+      const period = prompt('请选择时间周期:\n\n1. 本月\n2. 上月\n3. 本季度\n4. 本年度\n\n请输入数字 1-4:', '1');
+      
+      if (period) {
+        alert(`✅ 报表生成成功!\n\n报表类型:${this.getReportTypeText(reportType)}\n时间周期:${this.getPeriodText(period)}\n生成时间:${new Date().toLocaleString()}\n\n报表正在下载,请稍候...`);
+        console.log('生成报表:', { reportType, period });
+        // window.location.href = '/finance/reports';
+      }
+    }
+  }
+
+  // 辅助方法:获取项目类型文本
+  private getProjectTypeText(type: string): string {
+    const types: Record<string, string> = {
+      '1': '家装-小户型',
+      '2': '家装-大户型',
+      '3': '工装-商铺',
+      '4': '工装-写字楼'
+    };
+    return types[type] || '未知类型';
+  }
+
+  // 辅助方法:获取支付方式文本
+  private getPaymentMethodText(method: string): string {
+    const methods: Record<string, string> = {
+      '1': '微信支付',
+      '2': '支付宝',
+      '3': '银行转账',
+      '4': '现金'
+    };
+    return methods[method] || '未知方式';
+  }
+
+  // 辅助方法:获取报表类型文本
+  private getReportTypeText(type: string): string {
+    const types: Record<string, string> = {
+      '1': '月度收入报表',
+      '2': '项目利润分析',
+      '3': '回款统计报表',
+      '4': '成本费用报表'
+    };
+    return types[type] || '未知报表';
+  }
+
+  // 辅助方法:获取周期文本
+  private getPeriodText(period: string): string {
+    const periods: Record<string, string> = {
+      '1': '本月',
+      '2': '上月',
+      '3': '本季度',
+      '4': '本年度'
+    };
+    return periods[period] || '未知周期';
+  }
+
   // 格式化金额显示
   formatAmount(amount: number): string {
     return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount);
@@ -195,4 +410,137 @@ export class Dashboard implements OnInit {
       return `${remainingHours}小时`;
     }
   }
+
+  // 新增:获取标准工时
+  getStandardHours(complexityType: string): ProjectComplexity | undefined {
+    return this.projectComplexities.find(c => c.type === complexityType);
+  }
+
+  // 新增:获取逾期预警样式类
+  getOverdueClass(days: number): string {
+    if (days >= 5) return 'severe';
+    if (days >= 3) return 'warning';
+    return 'mild';
+  }
+
+  // 新增:切换时间维度
+  setTimeDimension(dimension: 'week' | 'month' | 'quarter'): void {
+    this.timeDimension.set(dimension);
+    // 重新加载数据
+    this.loadWorkHourData();
+  }
+
+  // 新增:获取时间维度文本
+  getTimeDimensionText(): string {
+    const dimension = this.timeDimension();
+    const texts = {
+      week: '本周',
+      month: '本月',
+      quarter: '本季度'
+    };
+    return texts[dimension];
+  }
+
+  // 新增:导出逾期分析报表
+  exportOverdueReport(): void {
+    const dimension = this.getTimeDimensionText();
+    const projects = this.overdueProjects();
+    const overdueCount = projects.length;
+    const totalOverdueDays = projects.reduce((sum: number, p: OverdueProject) => sum + p.overdueDays, 0);
+    const avgOverdueDays = overdueCount > 0 ? (totalOverdueDays / overdueCount).toFixed(1) : '0';
+    
+    alert(`📊 导出逾期分析报表
+    
+时间范围:${dimension}
+逾期项目数:${overdueCount} 个
+总逾期天数:${totalOverdueDays} 天
+平均逾期天数:${avgOverdueDays} 天
+
+主要逾期原因:
+${projects.slice(0, 3).map((p: OverdueProject, i: number) => `${i + 1}. ${p.name}:${p.reason}`).join('\n')}
+
+报表已生成,正在下载...`);
+    
+    console.log('导出逾期分析报表:', { dimension, overdueCount, avgOverdueDays });
+  }
+
+  // 新增:查看项目详情
+  viewProjectDetail(projectId: string): void {
+    this.router.navigate(['/designer/project-detail', projectId], {
+      queryParams: { tab: 'aftercare', view: 'review-only' }
+    });
+  }
+
+  // 新增:查看设计师详情
+  viewDesignerDetail(designerId: string): void {
+    const designers = this.designerEfficiencies();
+    const designer = designers.find((d: DesignerEfficiency) => d.id === designerId);
+    if (!designer) return;
+    
+    // 将对象转换为可读的利用率信息
+    const utilizationDetails = [
+      `  需求深化:${designer.stageUtilization.requirementDeepening}%`,
+      `  建模阶段:${designer.stageUtilization.modeling}%`,
+      `  渲染阶段:${designer.stageUtilization.rendering}%`,
+      `  后期处理:${designer.stageUtilization.postProduction}%`
+    ].join('\n');
+    
+    // 计算效率等级
+    const avgUtilization = (
+      designer.stageUtilization.requirementDeepening +
+      designer.stageUtilization.modeling +
+      designer.stageUtilization.rendering +
+      designer.stageUtilization.postProduction
+    ) / 4;
+    
+    const efficiencyLevel = avgUtilization >= 90 ? '优秀' : 
+                           avgUtilization >= 75 ? '良好' : 
+                           avgUtilization >= 60 ? '中等' : '待提升';
+    
+    alert(`👨‍💼 设计师效率详情
+
+姓名:${designer.name}
+效率等级:${efficiencyLevel} (平均利用率: ${avgUtilization.toFixed(1)}%)
+平均完成时长:${designer.avgCompletionTime}天
+当前闲置天数:${designer.idleDays}天
+逾期项目数:${designer.overdueProjectCount}个
+
+各阶段工时利用率:
+${utilizationDetails}
+
+${designer.suggestion}
+
+点击确定查看更多详情...`);
+    
+    console.log('查看设计师详情:', designer);
+    // TODO: 跳转到设计师详情页
+  }
+
+  // 新增:跳转到售后复盘(悬浮按钮)
+  goToAftercare(): void {
+    // 这里使用一个示例项目ID,实际应该从列表中选择或最近的项目
+    const projectId = 'P001';
+    this.router.navigate(['/designer/project-detail', projectId], {
+      queryParams: { 
+        view: 'review-only',  // 只读复盘模式
+        section: 'aftercare'  // 直接定位到售后板块
+      }
+    });
+  }
+
+  // 新增:获取效率等级颜色
+  getEfficiencyLevelColor(avgTime: number): string {
+    if (avgTime <= 15) return '#34c759'; // 优秀 - 绿色
+    if (avgTime <= 20) return '#30b0c7'; // 良好 - 青色
+    if (avgTime <= 25) return '#ff9500'; // 一般 - 橙色
+    return '#ff3b30'; // 需提升 - 红色
+  }
+
+  // 新增:获取阶段利用率颜色
+  getUtilizationColor(percentage: number): string {
+    if (percentage >= 90) return '#34c759';
+    if (percentage >= 75) return '#30b0c7';
+    if (percentage >= 60) return '#ff9500';
+    return '#ff3b30';
+  }
 }

+ 7 - 0
src/app/pages/finance/quotation-approval/quotation-approval.component.html

@@ -12,6 +12,13 @@
         </svg>
         统计报表
       </button>
+      <button type="button" class="btn-primary back-to-dashboard" (click)="backToDashboard()">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <path d="M19 12H5"></path>
+          <polyline points="12 19 5 12 12 5"></polyline>
+        </svg>
+        返回工作台
+      </button>
     </div>
   </div>
 

+ 20 - 0
src/app/pages/finance/quotation-approval/quotation-approval.component.scss

@@ -554,6 +554,26 @@
   }
 }
 
+.back-to-dashboard {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  
+  &:hover:not(:disabled) {
+    background: linear-gradient(135deg, #5568d3 0%, #63408a 100%);
+    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+  }
+  
+  svg {
+    transition: transform 0.3s ease;
+  }
+  
+  &:hover svg {
+    transform: translateX(-3px);
+  }
+}
+
 .btn-success {
   background-color: #059669;
   color: white;

+ 10 - 1
src/app/pages/finance/quotation-approval/quotation-approval.component.ts

@@ -1,6 +1,7 @@
 import { Component, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
 import { QuotationApprovalService } from '../../../services/quotation-approval.service';
 import { QuotationApproval, QuotationApprovalStatus, CommunicationRecord, ScriptUsageStats } from '../../../services/quotation-approval.service';
 
@@ -45,7 +46,10 @@ export class QuotationApprovalComponent implements OnInit {
   showApprovalModal = false;
   showStatsModal = false;
 
-  constructor(private quotationApprovalService: QuotationApprovalService) {}
+  constructor(
+    private quotationApprovalService: QuotationApprovalService,
+    private router: Router
+  ) {}
 
   ngOnInit() {
     this.loadQuotationApprovals();
@@ -271,4 +275,9 @@ export class QuotationApprovalComponent implements OnInit {
     };
     return classMap[status] || '';
   }
+
+  // 返回财务工作台
+  backToDashboard(): void {
+    this.router.navigate(['/finance/dashboard']);
+  }
 }