紧急事件与待办任务组件复用指南.md 16 KB

紧急事件与待办任务组件复用指南

📋 概述

本文档说明如何在客服端和组长端复用紧急事件和待办任务面板组件,实现类似 employee-detail-panel 的组件化复用模式。


🎯 可复用组件

1. UrgentEventsPanelComponent(紧急事件面板)

路径src/app/shared/components/urgent-events-panel/urgent-events-panel.component.ts

功能

  • ✅ 显示紧急事件列表
  • ✅ 支持加载状态、空状态、错误状态
  • ✅ 支持查看项目详情
  • ✅ 自动计算逾期天数

2. TodoTasksPanelComponent(待办任务面板)

路径src/app/shared/components/todo-tasks-panel/todo-tasks-panel.component.ts

功能

  • ✅ 显示待办任务列表(来自项目问题板块)
  • ✅ 支持加载状态、空状态、错误状态
  • ✅ 支持查看详情、标记已读
  • ✅ 支持刷新功能

📖 使用方法

客服端 Dashboard 集成示例

步骤 1:导入组件

// src/app/pages/customer-service/dashboard/dashboard.ts
import { UrgentEventsPanelComponent, type UrgentEvent } from '../../../shared/components/urgent-events-panel';
import { TodoTasksPanelComponent, type TodoTaskFromIssue as TodoTask } from '../../../shared/components/todo-tasks-panel';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [
    CommonModule, 
    FormsModule, 
    RouterModule, 
    UrgentEventsPanelComponent,  // ⭐ 导入紧急事件组件
    TodoTasksPanelComponent       // ⭐ 导入待办任务组件
  ],
  templateUrl: './dashboard.html',
  styleUrls: ['./dashboard.scss']
})
export class Dashboard implements OnInit {
  // 数据状态
  urgentEventsList = signal<UrgentEvent[]>([]);
  loadingUrgentEvents = signal(false);
  
  todoTasksFromIssues = signal<TodoTask[]>([]);
  loadingTodoTasks = signal(false);
  todoTaskError = signal('');
  
  // ... 其他代码
}

步骤 2:实现事件处理方法

// src/app/pages/customer-service/dashboard/dashboard.ts

/**
 * 从紧急事件面板查看项目
 */
onUrgentEventViewProject(projectId: string): void {
  console.log('🔍 [紧急事件] 查看项目:', projectId);
  const cid = localStorage.getItem('company') || 'cDL6R1hgSi';
  this.router.navigate(['/wxwork', cid, 'project', projectId, 'order'], {
    queryParams: { roleName: 'customer-service' }
  });
}

/**
 * 从待办任务面板查看详情
 */
onTodoTaskViewDetails(task: TodoTask): void {
  console.log('🔍 [待办任务] 查看详情:', task.title);
  const cid = localStorage.getItem('company') || 'cDL6R1hgSi';
  this.router.navigate(['/wxwork', cid, 'project', task.projectId, 'order'], {
    queryParams: {
      openIssues: 'true',
      highlightIssue: task.id,
      roleName: 'customer-service'
    }
  });
}

/**
 * 从待办任务面板标记为已读
 */
async onTodoTaskMarkAsRead(task: TodoTask): void {
  try {
    console.log('✅ [待办任务] 标记为已读:', task.title);
    
    // 从列表中移除
    const currentTasks = this.todoTasksFromIssues();
    const updatedTasks = currentTasks.filter(t => t.id !== task.id);
    this.todoTasksFromIssues.set(updatedTasks);
    
    // 同步更新紧急事件列表
    this.syncUrgentEvents();
  } catch (error) {
    console.error('❌ 标记已读失败:', error);
  }
}

/**
 * 刷新待办任务和紧急事件
 */
async onRefreshTodoTasks(): Promise<void> {
  console.log('🔄 [待办任务] 刷新...');
  await this.loadTodoTasksFromIssues();
}

步骤 3:数据转换方法

// src/app/pages/customer-service/dashboard/dashboard.ts

/**
 * 将待办任务同步为紧急事件格式
 */
private syncUrgentEvents(): void {
  const urgentIssues = this.todoTasksFromIssues().filter(issue => 
    issue.priority === 'urgent' || issue.priority === 'critical' || issue.priority === 'high'
  );
  
  const urgentEvents: UrgentEvent[] = urgentIssues.map(issue => {
    const now = new Date();
    const deadline = issue.dueDate || new Date();
    const diffTime = deadline.getTime() - now.getTime();
    const overdueDays = -Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    
    return {
      id: issue.id,
      title: issue.title,
      description: issue.description,
      eventType: this.mapIssueTypeToEventType(issue.issueType),
      deadline: deadline,
      projectId: issue.projectId,
      projectName: issue.projectName,
      designerName: issue.assigneeName,
      urgencyLevel: this.mapPriorityToUrgency(issue.priority),
      overdueDays: overdueDays > 0 ? overdueDays : undefined,
      phaseName: issue.relatedStage
    };
  });
  
  this.urgentEventsList.set(urgentEvents);
}

/**
 * 将 IssueType 映射到 EventType
 */
private mapIssueTypeToEventType(issueType: IssueType): 'review' | 'delivery' | 'phase_deadline' | 'custom' {
  const mapping: Record<IssueType, 'review' | 'delivery' | 'phase_deadline' | 'custom'> = {
    'bug': 'custom',
    'task': 'custom',
    'feedback': 'review',
    'risk': 'custom',
    'feature': 'custom'
  };
  return mapping[issueType] || 'custom';
}

/**
 * 将 IssuePriority 映射到紧急程度
 */
private mapPriorityToUrgency(priority: IssuePriority): 'critical' | 'high' | 'medium' {
  if (priority === 'urgent' || priority === 'critical') return 'critical';
  if (priority === 'high') return 'high';
  return 'medium';
}

步骤 4:在模板中使用组件

<!-- src/app/pages/customer-service/dashboard/dashboard.html -->

<div class="content-grid">
  <!-- ⭐ 使用紧急事件面板组件 -->
  <app-urgent-events-panel
    [events]="urgentEventsList()"
    [loading]="loadingUrgentEvents()"
    [title]="'紧急事件'"
    [subtitle]="'自动计算的截止事件'"
    [loadingText]="'计算紧急事件中...'"
    [emptyText]="'暂无紧急事件'"
    [emptyHint]="'所有项目时间节点正常 ✅'"
    (viewProject)="onUrgentEventViewProject($event)">
  </app-urgent-events-panel>
  
  <!-- ⭐ 使用待办任务面板组件 -->
  <app-todo-tasks-panel
    [tasks]="todoTasksFromIssues()"
    [loading]="loadingTodoTasks()"
    [error]="todoTaskError()"
    [title]="'待办任务'"
    [subtitle]="'来自项目问题板块'"
    [loadingText]="'加载待办任务中...'"
    [emptyText]="'暂无待办任务'"
    [emptyHint]="'所有项目问题都已处理完毕 🎉'"
    (viewDetails)="onTodoTaskViewDetails($event)"
    (markAsRead)="onTodoTaskMarkAsRead($event)"
    (refresh)="onRefreshTodoTasks()">
  </app-todo-tasks-panel>
</div>

组长端 Dashboard 集成示例

步骤 1:导入组件

// src/app/pages/team-leader/dashboard/dashboard.ts
import { UrgentEventsPanelComponent, type UrgentEvent } from '../../../shared/components/urgent-events-panel';
import { TodoTasksPanelComponent, type TodoTaskFromIssue } from '../../../shared/components/todo-tasks-panel';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    RouterModule,
    ProjectTimelineComponent,
    EmployeeDetailPanelComponent,
    UrgentEventsPanelComponent,   // ⭐ 导入紧急事件组件
    TodoTasksPanelComponent        // ⭐ 导入待办任务组件
  ],
  templateUrl: './dashboard.html',
  styleUrls: ['./dashboard.scss']
})
export class Dashboard implements OnInit {
  // 数据状态
  urgentEvents: UrgentEvent[] = [];
  loadingUrgentEvents: boolean = false;
  
  todoTasksFromIssues: TodoTaskFromIssue[] = [];
  loadingTodoTasks: boolean = false;
  todoTaskError: string = '';
  
  // ... 其他代码
}

步骤 2:实现事件处理方法

// src/app/pages/team-leader/dashboard/dashboard.ts

/**
 * 从紧急事件面板查看项目
 */
onUrgentEventViewProject(projectId: string): void {
  console.log('🔍 [紧急事件] 查看项目:', projectId);
  this.viewProjectDetails(projectId);
}

/**
 * 从待办任务面板查看详情
 */
onTodoTaskViewDetails(task: TodoTaskFromIssue): void {
  console.log('🔍 [待办任务] 查看详情:', task.title);
  this.navigateToIssue(task);
}

/**
 * 从待办任务面板标记为已读
 */
async onTodoTaskMarkAsRead(task: TodoTaskFromIssue): void {
  try {
    console.log('✅ [待办任务] 标记为已读:', task.title);
    
    // 从列表中移除
    this.todoTasksFromIssues = this.todoTasksFromIssues.filter(t => t.id !== task.id);
  } catch (error) {
    console.error('❌ 标记已读失败:', error);
  }
}

/**
 * 刷新待办任务
 */
async onRefreshTodoTasks(): Promise<void> {
  console.log('🔄 [待办任务] 刷新...');
  await this.loadTodoTasksFromIssues();
}

步骤 3:在模板中使用组件

<!-- src/app/pages/team-leader/dashboard/dashboard.html -->

<!-- 🆕 待办任务双栏布局(待办问题 + 紧急事件) -->
<section class="todo-section todo-section-dual">
  <div class="section-header">
    <h2>待办事项</h2>
    <button class="btn-refresh" (click)="onRefreshTodoTasks()">
      <svg viewBox="0 0 24 24" width="16" height="16">
        <path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
      </svg>
    </button>
  </div>
  
  <div class="todo-columns">
    <!-- ⭐ 左栏:待办任务(使用组件) -->
    <app-todo-tasks-panel
      [tasks]="todoTasksFromIssues"
      [loading]="loadingTodoTasks"
      [error]="todoTaskError"
      [title]="'待办任务'"
      [subtitle]="'来自项目问题板块'"
      (viewDetails)="onTodoTaskViewDetails($event)"
      (markAsRead)="onTodoTaskMarkAsRead($event)"
      (refresh)="onRefreshTodoTasks()">
    </app-todo-tasks-panel>
    
    <!-- ⭐ 右栏:紧急事件(使用组件) -->
    <app-urgent-events-panel
      [events]="urgentEvents"
      [loading]="loadingUrgentEvents"
      [title]="'紧急事件'"
      [subtitle]="'自动计算的截止事件'"
      (viewProject)="onUrgentEventViewProject($event)">
    </app-urgent-events-panel>
  </div>
</section>

🔧 数据接口定义

UrgentEvent 接口

export interface UrgentEvent {
  id: string;
  title: string;
  description: string;
  eventType: 'review' | 'delivery' | 'phase_deadline' | 'custom';
  deadline: Date;
  projectId: string;
  projectName: string;
  designerName?: string;
  urgencyLevel: 'critical' | 'high' | 'medium';
  overdueDays?: number;
  phaseName?: string;
}

TodoTaskFromIssue 接口

export interface TodoTaskFromIssue {
  id: string;
  projectId: string;
  projectName: string;
  title: string;
  description: string;
  status: string; // 待处理 | 处理中 | 已解决 | 已关闭
  priority: IssuePriority; // urgent | critical | high | medium | low
  issueType: IssueType; // bug | task | feedback | risk | feature
  relatedStage?: string;
  assigneeId?: string;
  assigneeName?: string;
  creatorId?: string;
  creatorName?: string;
  createdAt: Date;
  updatedAt: Date;
  dueDate?: Date;
}

📊 输入输出属性

UrgentEventsPanelComponent

输入属性 (@Input)

属性 类型 默认值 说明
events UrgentEvent[] [] 紧急事件列表
loading boolean false 加载状态
title string '紧急事件' 面板标题
subtitle string '自动计算的截止事件' 副标题
loadingText string '计算紧急事件中...' 加载提示文字
emptyText string '暂无紧急事件' 空状态文字
emptyHint string '所有项目时间节点正常 ✅' 空状态提示

输出事件 (@Output)

事件 参数类型 说明
viewProject string 查看项目详情(projectId)

TodoTasksPanelComponent

输入属性 (@Input)

属性 类型 默认值 说明
tasks TodoTaskFromIssue[] [] 待办任务列表
loading boolean false 加载状态
error string '' 错误信息
title string '待办任务' 面板标题
subtitle string '来自项目问题板块' 副标题
loadingText string '加载待办任务中...' 加载提示文字
emptyText string '暂无待办任务' 空状态文字
emptyHint string '所有项目问题都已处理完毕 🎉' 空状态提示

输出事件 (@Output)

事件 参数类型 说明
viewDetails TodoTaskFromIssue 查看任务详情
markAsRead TodoTaskFromIssue 标记为已读
refresh void 刷新任务列表

🎨 样式集成

客服端样式

组件会继承父组件的以下样式类(需要在 dashboard.scss 中定义):

// src/app/pages/customer-service/dashboard/dashboard.scss

.content-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin-top: 24px;
}

.todo-column {
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.column-header {
  margin-bottom: 20px;
  
  h3 {
    font-size: 18px;
    font-weight: 600;
    color: #1f2937;
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 4px 0;
  }
  
  .task-count {
    font-size: 14px;
    color: #6b7280;
    font-weight: normal;
  }
  
  .column-subtitle {
    font-size: 13px;
    color: #9ca3af;
  }
}

// ... 其他待办任务和紧急事件的样式

组件需要以下样式类支持:

  • .todo-list-compact - 任务列表容器
  • .todo-item-compact - 单个任务项
  • .priority-indicator - 优先级指示器
  • .urgency-indicator - 紧急程度指示器
  • .task-content - 任务内容区
  • .task-header - 任务头部
  • .task-badges - 徽章区域
  • .badge - 徽章样式
  • 等等...

📋 完整集成清单

客服端 Dashboard

  • 导入 UrgentEventsPanelComponent
  • 导入 TodoTasksPanelComponent
  • 添加数据状态 urgentEventsListloadingUrgentEvents
  • 实现 onUrgentEventViewProject() 方法
  • 实现 onTodoTaskViewDetails() 方法
  • 实现 onTodoTaskMarkAsRead() 方法
  • 实现 onRefreshTodoTasks() 方法
  • 实现数据转换方法(mapIssueTypeToEventTypemapPriorityToUrgency
  • 在 HTML 中使用组件(替换现有实现)
  • 确保样式正确继承

组长端 Dashboard

  • 导入 UrgentEventsPanelComponent
  • 导入 TodoTasksPanelComponent
  • 实现事件处理方法
  • 在 HTML 中使用组件(替换现有实现)
  • 确保样式正确继承

✅ 优势对比

使用组件前

问题

  • ❌ 代码重复(客服端和组长端都有相似的实现)
  • ❌ 维护成本高(修改一处需要同步修改多处)
  • ❌ 样式不统一
  • ❌ 逻辑耦合度高

使用组件后

优势

  • ✅ 代码复用(一次编写,多处使用)
  • ✅ 维护简单(修改组件,所有地方自动同步)
  • ✅ 样式统一
  • ✅ 逻辑解耦
  • ✅ 符合 Angular 最佳实践

🚀 后续优化建议

1. 提取共同样式

将待办任务和紧急事件的样式提取到独立的SCSS文件:

// src/app/shared/styles/todo-tasks-panel.scss
.todo-list-compact {
  // 共同样式
}

.todo-item-compact {
  // 共同样式
}

然后在组件中引入:

@Component({
  selector: 'app-todo-tasks-panel',
  styleUrls: ['../../../../shared/styles/todo-tasks-panel.scss']
})

2. 添加更多配置项

@Input() showPriority: boolean = true;  // 是否显示优先级
@Input() showAssignee: boolean = true;  // 是否显示指派人
@Input() showActions: boolean = true;   // 是否显示操作按钮
@Input() maxItems: number = 50;         // 最大显示数量

3. 添加过滤和排序功能

@Input() filterPriority: IssuePriority[] = []; // 优先级过滤
@Input() sortBy: 'priority' | 'date' | 'status' = 'priority'; // 排序方式

📚 相关文档

更新日期: 2025-11-11
维护人员: 开发团队