revision-task-dashboard-integration.md 20 KB

改图任务Dashboard集成文档

📋 功能概述

在设计师组长Dashboard的紧急事件板块增加改图任务审批功能,显示待审批的大修改工单,并提供审批操作。


🎯 实现位置

Dashboard位置src/app/pages/designer/dashboard/

显示区域:附加信息区域(additional-info-section),位于"待处理反馈"和"代班信息"之间


✨ 核心功能

1. 改图任务卡片显示

  • 显示位置:紧急事件板块的橙色区域
  • 显示条件pendingRevisionTasks.length > 0
  • 卡片内容
    • 工单编号(前8位)
    • 大修改标签
    • 项目名称(可点击跳转)
    • 涉及空间标签
    • 修改内容描述
    • 创建人信息
    • 创建时间(相对时间)
    • 审批按钮

2. 审批弹窗

  • 触发方式:点击卡片上的"审批"按钮
  • 弹窗内容
    • 工单完整信息
    • 审批选项(通过/驳回)
    • 通过:可选备注
    • 驳回:必填原因
    • 确认/取消按钮

📂 修改文件

1. TypeScript (dashboard.ts)

新增导入

import { RevisionTaskService, RevisionTask } from '../../../services/revision-task.service';

新增属性

// 改图工单相关
pendingRevisionTasks: RevisionTask[] = []; // 待审批的改图工单
showRevisionApprovalModal: boolean = false; // 审批弹窗
currentRevisionTask: RevisionTask | null = null; // 当前审批的工单
revisionApprovalForm = {
  action: 'approve' as 'approve' | 'reject',
  notes: '',
  rejectionReason: ''
};

构造函数注入

constructor(
  // ... 其他服务
  private revisionTaskService: RevisionTaskService
) {}

数据加载

await Promise.all([
  // ... 其他加载
  this.loadPendingRevisionTasks() // 🆕 加载待审批改图工单
]);

核心方法

1. 加载待审批工单

private async loadPendingRevisionTasks(): Promise<void> {
  // 查询所有项目的revisionTasks
  // 筛选status === 'pending_approval'
  // 按创建时间倒序排列
}

2. 打开审批弹窗

openRevisionApprovalModal(task: RevisionTask): void {
  this.currentRevisionTask = task;
  this.revisionApprovalForm = {
    action: 'approve',
    notes: '',
    rejectionReason: ''
  };
  this.showRevisionApprovalModal = true;
}

3. 提交审批

async submitRevisionApproval(): Promise<void> {
  if (action === 'approve') {
    await this.revisionTaskService.approveRevisionTask(...);
  } else {
    // 验证驳回原因
    await this.revisionTaskService.rejectRevisionTask(...);
  }
  // 重新加载待审批工单
  await this.loadPendingRevisionTasks();
}

4. 跳转到项目

goToRevisionProject(task: RevisionTask): void {
  this.router.navigate(['/wxwork', this.cid, 'project', task.projectId]);
}

5. 格式化时间

formatRevisionTime(date: Date): string {
  // 刚刚、X分钟前、X小时前、X天前
  // 超过7天显示日期
}

2. HTML模板 (dashboard.html)

改图任务卡片区域(Line 171-228)

<!-- 改图任务审批区域 -->
<div class="info-column revision-tasks-column" *ngIf="pendingRevisionTasks.length > 0">
  <div class="section-header">
    <h2>
      <svg>...</svg>
      改图任务 ({{ pendingRevisionTasks.length }})
    </h2>
  </div>
  
  <div class="revision-task-list">
    <div *ngFor="let task of pendingRevisionTasks" class="revision-task-card">
      <!-- 工单头部 -->
      <div class="task-header">
        <span class="task-id">#{{ task.id?.substring(0, 8) }}</span>
        <span class="task-badge major">大修改</span>
      </div>
      
      <!-- 项目名称(可点击) -->
      <div class="task-project" (click)="goToRevisionProject(task)">
        <svg>...</svg>
        {{ task.projectName }}
      </div>
      
      <!-- 涉及空间 -->
      <div class="task-spaces" *ngIf="task.spaceNames && task.spaceNames.length > 0">
        <label>涉及空间:</label>
        <div class="space-tags">
          <span *ngFor="let space of task.spaceNames" class="space-tag">{{ space }}</span>
        </div>
      </div>
      
      <!-- 修改内容 -->
      <div class="task-description">
        <label>修改内容:</label>
        <p>{{ task.description }}</p>
      </div>
      
      <!-- 元信息 -->
      <div class="task-meta">
        <span class="task-creator">
          <svg>...</svg>
          {{ task.createdByName }}
        </span>
        <span class="task-time">{{ formatRevisionTime(task.createdAt) }}</span>
      </div>
      
      <!-- 审批按钮 -->
      <div class="task-actions">
        <button class="btn-approve" (click)="openRevisionApprovalModal(task)">
          <svg>...</svg>
          审批
        </button>
      </div>
    </div>
  </div>
</div>

审批弹窗(Line 459-580)

<!-- 改图工单审批弹窗 -->
@if (showRevisionApprovalModal && currentRevisionTask) {
  <div class="modal-overlay" (click)="closeRevisionApprovalModal()">
    <div class="revision-approval-modal" (click)="$event.stopPropagation()">
      <div class="modal-header">
        <h3>审批改图工单</h3>
        <button class="close-btn" (click)="closeRevisionApprovalModal()">×</button>
      </div>
      
      <div class="modal-body">
        <!-- 工单信息 -->
        <div class="task-info-section">
          <div class="info-row">
            <label>工单编号:</label>
            <span>#{{ currentRevisionTask.id?.substring(0, 8) }}</span>
          </div>
          <!-- ... 更多信息 -->
        </div>
        
        <!-- 审批操作 -->
        <div class="approval-action-section">
          <div class="action-type-selector">
            <label class="radio-label" [class.active]="revisionApprovalForm.action === 'approve'">
              <input type="radio" name="action" value="approve" [(ngModel)]="revisionApprovalForm.action">
              <svg>...</svg>
              <span>通过审批</span>
            </label>
            
            <label class="radio-label" [class.active]="revisionApprovalForm.action === 'reject'">
              <input type="radio" name="action" value="reject" [(ngModel)]="revisionApprovalForm.action">
              <svg>...</svg>
              <span>驳回</span>
            </label>
          </div>
          
          <!-- 备注/驳回原因 -->
          <div class="form-group" *ngIf="revisionApprovalForm.action === 'approve'">
            <label>审批备注(可选)</label>
            <textarea [(ngModel)]="revisionApprovalForm.notes"></textarea>
          </div>
          
          <div class="form-group" *ngIf="revisionApprovalForm.action === 'reject'">
            <label>驳回原因 <span class="required">*</span></label>
            <textarea [(ngModel)]="revisionApprovalForm.rejectionReason"></textarea>
          </div>
        </div>
      </div>
      
      <div class="modal-footer">
        <button class="btn-cancel" (click)="closeRevisionApprovalModal()">取消</button>
        <button 
          class="btn-submit" 
          [class.btn-approve]="revisionApprovalForm.action === 'approve'"
          [class.btn-reject]="revisionApprovalForm.action === 'reject'"
          (click)="submitRevisionApproval()">
          {{ revisionApprovalForm.action === 'approve' ? '确认通过' : '确认驳回' }}
        </button>
      </div>
    </div>
  </div>
}

3. SCSS样式 (dashboard.scss)

需要添加的样式类:

改图任务卡片样式

// 改图任务列表容器
.revision-tasks-column {
  background: linear-gradient(135deg, #fff5f0 0%, #ffffff 100%);
  border-left: 4px solid #FF6B35;
}

// 改图任务卡片
.revision-task-card {
  background: #ffffff;
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 16px;
  border: 1px solid #ffe4d6;
  box-shadow: 0 2px 8px rgba(255, 107, 53, 0.1);
  transition: all 0.3s ease;
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 16px rgba(255, 107, 53, 0.15);
  }
}

// 工单头部
.task-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.task-id {
  font-family: 'Courier New', monospace;
  color: #666;
  font-size: 12px;
}

.task-badge.major {
  background: linear-gradient(135deg, #FF6B35 0%, #FF8F5C 100%);
  color: white;
  padding: 4px 12px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 600;
}

// 项目名称(可点击)
.task-project {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #0047AB;
  font-weight: 500;
  margin-bottom: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
  
  &:hover {
    color: #0052C9;
    text-decoration: underline;
  }
  
  svg {
    flex-shrink: 0;
  }
}

// 空间标签
.task-spaces {
  margin-bottom: 12px;
  
  label {
    font-size: 12px;
    color: #666;
    margin-bottom: 6px;
    display: block;
  }
}

.space-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.space-tag {
  background: #e8f4ff;
  color: #0047AB;
  padding: 4px 10px;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 500;
}

// 修改描述
.task-description {
  margin-bottom: 12px;
  
  label {
    font-size: 12px;
    color: #666;
    margin-bottom: 4px;
    display: block;
  }
  
  p {
    font-size: 14px;
    color: #333;
    margin: 0;
    line-height: 1.6;
  }
}

// 元信息
.task-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  color: #999;
  margin-bottom: 12px;
}

.task-creator {
  display: flex;
  align-items: center;
  gap: 4px;
}

// 审批按钮
.task-actions {
  display: flex;
  justify-content: flex-end;
}

.btn-approve {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: linear-gradient(135deg, #34C759 0%, #4CD964 100%);
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(52, 199, 89, 0.25);
  
  &:hover {
    background: linear-gradient(135deg, #30B350 0%, #43C65A 100%);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(52, 199, 89, 0.35);
  }
  
  &:active {
    transform: translateY(0);
  }
}

审批弹窗样式

.revision-approval-modal {
  background: white;
  border-radius: 16px;
  width: 600px;
  max-width: 90vw;
  max-height: 85vh;
  overflow-y: auto;
  
  .modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 24px;
    border-bottom: 1px solid #e5e5ea;
    
    h3 {
      display: flex;
      align-items: center;
      font-size: 18px;
      font-weight: 600;
      margin: 0;
    }
    
    .close-btn {
      background: none;
      border: none;
      font-size: 28px;
      cursor: pointer;
      color: #999;
      
      &:hover {
        color: #333;
      }
    }
  }
  
  .modal-body {
    padding: 24px;
  }
  
  // 工单信息区
  .task-info-section {
    background: #f8f9fa;
    border-radius: 12px;
    padding: 16px;
    margin-bottom: 24px;
    
    .info-row {
      display: flex;
      margin-bottom: 12px;
      
      &:last-child {
        margin-bottom: 0;
      }
      
      label {
        font-weight: 600;
        color: #666;
        min-width: 90px;
        flex-shrink: 0;
      }
      
      span {
        color: #333;
      }
    }
    
    .description-row {
      flex-direction: column;
      
      .description-text {
        margin-top: 8px;
        line-height: 1.6;
        color: #333;
      }
    }
  }
  
  .space-tags-inline {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }
  
  .space-tag-small {
    background: #e8f4ff;
    color: #0047AB;
    padding: 2px 8px;
    border-radius: 6px;
    font-size: 12px;
  }
  
  // 审批操作区
  .approval-action-section {
    .action-type-selector {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 12px;
      margin-bottom: 20px;
    }
    
    .radio-label {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 12px 16px;
      border: 2px solid #e5e5ea;
      border-radius: 12px;
      cursor: pointer;
      transition: all 0.2s ease;
      
      input[type="radio"] {
        display: none;
      }
      
      svg {
        flex-shrink: 0;
      }
      
      &.active {
        border-color: #34C759;
        background: rgba(52, 199, 89, 0.05);
        
        &:nth-child(2) {
          border-color: #FF3B30;
          background: rgba(255, 59, 48, 0.05);
        }
      }
      
      &:hover {
        border-color: #cbd5e1;
      }
    }
    
    .form-group {
      label {
        display: block;
        font-weight: 600;
        margin-bottom: 8px;
        color: #333;
        
        .required {
          color: #FF3B30;
        }
      }
      
      textarea {
        width: 100%;
        padding: 12px;
        border: 1px solid #e5e5ea;
        border-radius: 8px;
        font-size: 14px;
        font-family: inherit;
        resize: vertical;
        
        &:focus {
          outline: none;
          border-color: #0047AB;
        }
      }
    }
  }
  
  .modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 12px;
    padding: 20px 24px;
    border-top: 1px solid #e5e5ea;
    
    button {
      padding: 10px 24px;
      border-radius: 8px;
      font-size: 14px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s ease;
      border: none;
      display: inline-flex;
      align-items: center;
      gap: 6px;
    }
    
    .btn-cancel {
      background: #f1f3f5;
      color: #666;
      
      &:hover {
        background: #e9ecef;
      }
    }
    
    .btn-submit {
      color: white;
      
      &.btn-approve {
        background: linear-gradient(135deg, #34C759 0%, #4CD964 100%);
        box-shadow: 0 2px 8px rgba(52, 199, 89, 0.25);
        
        &:hover {
          background: linear-gradient(135deg, #30B350 0%, #43C65A 100%);
          box-shadow: 0 4px 12px rgba(52, 199, 89, 0.35);
        }
      }
      
      &.btn-reject {
        background: linear-gradient(135deg, #FF3B30 0%, #FF5A4F 100%);
        box-shadow: 0 2px 8px rgba(255, 59, 48, 0.25);
        
        &:hover {
          background: linear-gradient(135deg, #E6352B 0%, #FF5046 100%);
          box-shadow: 0 4px 12px rgba(255, 59, 48, 0.35);
        }
      }
    }
  }
}

🔄 数据流

加载流程

1. Dashboard初始化
   ↓
2. authenticateAndLoadData()
   ↓
3. loadDashboardData()
   ↓
4. loadPendingRevisionTasks()
   ↓
5. 查询Project.data.revisionTasks
   ↓
6. 筛选status === 'pending_approval'
   ↓
7. 填充projectName和projectId(从Project对象)
   ↓
8. 按创建时间倒序排列
   ↓
9. 显示在Dashboard

审批流程

1. 点击"审批"按钮
   ↓
2. openRevisionApprovalModal(task)
   ↓
3. 显示审批弹窗
   ↓
4. 选择通过/驳回
   ↓
5. submitRevisionApproval()
   ↓
6. 调用RevisionTaskService
   ↓
7. 更新Project.data.revisionTasks
   ↓
8. 重新加载待审批工单
   ↓
9. 显示成功提示

📊 UI设计

卡片布局

┌────────────────────────────────────────┐
│ 改图任务 (2)                          │
├────────────────────────────────────────┤
│ ┌────────────────────────────────────┐ │
│ │ #RT_17... [大修改]                 │ │
│ │                                    │ │
│ │ 🏠 现代简约客厅设计项目             │ │
│ │                                    │ │
│ │ 涉及空间:[主卧] [次卧] [客厅]     │ │
│ │                                    │ │
│ │ 修改内容:主卧的整体氛围需要调整    │ │
│ │                                    │ │
│ │ 👤 刘雨熙   18分钟前               │ │
│ │                                    │ │
│ │                         [✓ 审批]   │ │
│ └────────────────────────────────────┘ │
└────────────────────────────────────────┘

审批弹窗

┌─────────────────────────────────────────┐
│ 📅 审批改图工单                    ×   │
├─────────────────────────────────────────┤
│ 工单编号:#RT_17652                     │
│ 项目名称:现代简约客厅设计项目           │
│ 创建人:刘雨熙 (designer)              │
│ 创建时间:18分钟前                      │
│ 涉及空间:[主卧] [次卧] [客厅]         │
│ 预计时间:2-3天                         │
│ 修改内容:主卧的整体氛围需要调整...      │
│                                         │
│ ┌───────────┐  ┌──────────┐           │
│ │ ✓ 通过审批 │  │ × 驳回   │           │
│ └───────────┘  └──────────┘           │
│                                         │
│ 审批备注(可选):                      │
│ ┌─────────────────────────────────┐   │
│ │ 同意修改,请尽快完成...         │   │
│ └─────────────────────────────────┘   │
│                                         │
│                  [取消] [✓ 确认通过]   │
└─────────────────────────────────────────┘

🎨 样式设计

配色方案

  • 主色:橙色系 (#FF6B35) - 警示色,突出紧急性
  • 成功色:绿色 (#34C759) - 通过审批
  • 危险色:红色 (#FF3B30) - 驳回
  • 背景色:白色 + 浅橙色渐变

iOS风格特点

  1. 圆角:8-16px
  2. 渐变:135度线性渐变
  3. 阴影:轻柔立体
  4. 动画:0.2-0.3s平滑过渡
  5. 交互反馈:悬停浮起、点击缩放

🧪 测试要点

功能测试

  • 待审批工单正确加载
  • 工单数量显示正确
  • 点击项目名称跳转正确
  • 审批弹窗正常打开/关闭
  • 通过审批成功
  • 驳回审批成功(含必填验证)
  • 审批后列表自动刷新

UI测试

  • 卡片布局正常
  • 标签显示正确
  • 时间格式化正确
  • 弹窗居中显示
  • 按钮样式正确
  • 响应式布局正常

边界测试

  • 无待审批工单时不显示
  • 驳回时未填原因提示
  • 网络错误处理
  • 空数据处理

📝 注意事项

  1. 权限控制:当前实现未做权限验证,所有登录用户都可以审批
  2. 通知功能:审批后的通知功能需要实现
  3. 批量操作:未实现批量审批功能
  4. 历史记录:未显示已审批的工单历史
  5. 性能优化:大量工单时需要分页

🚀 后续优化

短期优化

  • 添加角色权限验证(仅组长可审批)
  • 实现企业微信通知
  • 添加工单搜索和筛选
  • 支持批量审批

长期优化

  • 增加已审批工单历史
  • 添加审批统计图表
  • 支持工单评论功能
  • 集成到移动端

创建时间:2024-12-09

功能版本:v1.0

设计规范:iOS Design System