本文档说明如何在客服端和组长端复用紧急事件和待办任务面板组件,实现类似 employee-detail-panel 的组件化复用模式。
路径:src/app/shared/components/urgent-events-panel/urgent-events-panel.component.ts
功能:
路径:src/app/shared/components/todo-tasks-panel/todo-tasks-panel.component.ts
功能:
// 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('');
// ... 其他代码
}
// 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();
}
// 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';
}
<!-- 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>
// 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 = '';
// ... 其他代码
}
// 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();
}
<!-- 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>
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;
}
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;
}
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
events |
UrgentEvent[] |
[] |
紧急事件列表 |
loading |
boolean |
false |
加载状态 |
title |
string |
'紧急事件' |
面板标题 |
subtitle |
string |
'自动计算的截止事件' |
副标题 |
loadingText |
string |
'计算紧急事件中...' |
加载提示文字 |
emptyText |
string |
'暂无紧急事件' |
空状态文字 |
emptyHint |
string |
'所有项目时间节点正常 ✅' |
空状态提示 |
| 事件 | 参数类型 | 说明 |
|---|---|---|
viewProject |
string |
查看项目详情(projectId) |
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tasks |
TodoTaskFromIssue[] |
[] |
待办任务列表 |
loading |
boolean |
false |
加载状态 |
error |
string |
'' |
错误信息 |
title |
string |
'待办任务' |
面板标题 |
subtitle |
string |
'来自项目问题板块' |
副标题 |
loadingText |
string |
'加载待办任务中...' |
加载提示文字 |
emptyText |
string |
'暂无待办任务' |
空状态文字 |
emptyHint |
string |
'所有项目问题都已处理完毕 🎉' |
空状态提示 |
| 事件 | 参数类型 | 说明 |
|---|---|---|
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 - 徽章样式UrgentEventsPanelComponentTodoTasksPanelComponenturgentEventsList、loadingUrgentEventsonUrgentEventViewProject() 方法onTodoTaskViewDetails() 方法onTodoTaskMarkAsRead() 方法onRefreshTodoTasks() 方法mapIssueTypeToEventType、mapPriorityToUrgency)UrgentEventsPanelComponentTodoTasksPanelComponent问题:
优势:
将待办任务和紧急事件的样式提取到独立的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']
})
@Input() showPriority: boolean = true; // 是否显示优先级
@Input() showAssignee: boolean = true; // 是否显示指派人
@Input() showActions: boolean = true; // 是否显示操作按钮
@Input() maxItems: number = 50; // 最大显示数量
@Input() filterPriority: IssuePriority[] = []; // 优先级过滤
@Input() sortBy: 'priority' | 'date' | 'status' = 'priority'; // 排序方式
更新日期: 2025-11-11
维护人员: 开发团队