urgent-events-panel.component.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import { Component, Input, Output, EventEmitter } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. /**
  4. * 紧急事件接口
  5. */
  6. export interface UrgentEvent {
  7. id: string;
  8. title: string;
  9. description: string;
  10. eventType: 'review' | 'delivery' | 'phase_deadline' | 'custom';
  11. deadline: Date;
  12. projectId: string;
  13. projectName: string;
  14. designerName?: string;
  15. urgencyLevel: 'critical' | 'high' | 'medium';
  16. overdueDays?: number;
  17. phaseName?: string;
  18. }
  19. /**
  20. * 紧急事件面板组件(可复用)
  21. *
  22. * 功能:
  23. * 1. 显示紧急事件列表
  24. * 2. 支持加载状态、空状态、错误状态
  25. * 3. 支持点击查看项目详情
  26. * 4. 自动计算逾期天数
  27. */
  28. @Component({
  29. selector: 'app-urgent-events-panel',
  30. standalone: true,
  31. imports: [CommonModule],
  32. template: `
  33. <div class="todo-column todo-column-urgent">
  34. <!-- 面板头部 -->
  35. <div class="column-header">
  36. <h3>
  37. <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
  38. <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
  39. </svg>
  40. {{ title }}
  41. @if (events.length > 0) {
  42. <span class="task-count urgent">({{ events.length }})</span>
  43. }
  44. </h3>
  45. <span class="column-subtitle">{{ subtitle }}</span>
  46. </div>
  47. <!-- 加载状态 -->
  48. @if (loading) {
  49. <div class="loading-state">
  50. <svg class="spinner" viewBox="0 0 50 50">
  51. <circle cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle>
  52. </svg>
  53. <p>{{ loadingText }}</p>
  54. </div>
  55. }
  56. <!-- 空状态 -->
  57. @if (!loading && events.length === 0) {
  58. <div class="empty-state">
  59. <svg viewBox="0 0 24 24" width="64" height="64" fill="#d1d5db">
  60. <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
  61. </svg>
  62. <p>{{ emptyText }}</p>
  63. <p class="hint">{{ emptyHint }}</p>
  64. </div>
  65. }
  66. <!-- 紧急事件列表 -->
  67. @if (!loading && events.length > 0) {
  68. <div class="todo-list-compact urgent-list">
  69. @for (event of events; track event.id) {
  70. <div class="todo-item-compact urgent-item" [attr.data-urgency]="event.urgencyLevel">
  71. <!-- 左侧紧急程度色条 -->
  72. <div class="urgency-indicator" [attr.data-urgency]="event.urgencyLevel"></div>
  73. <!-- 事件内容 -->
  74. <div class="task-content">
  75. <!-- 标题行 -->
  76. <div class="task-header">
  77. <span class="task-title">{{ event.title }}</span>
  78. <div class="task-badges">
  79. <span class="badge badge-urgency" [attr.data-urgency]="event.urgencyLevel">
  80. @if (event.urgencyLevel === 'critical') { 🔴 紧急 }
  81. @else if (event.urgencyLevel === 'high') { 🟠 重要 }
  82. @else { 🟡 注意 }
  83. </span>
  84. <span class="badge badge-event-type">
  85. @if (event.eventType === 'review') { 对图 }
  86. @else if (event.eventType === 'delivery') { 交付 }
  87. @else if (event.eventType === 'phase_deadline') { {{ event.phaseName }} }
  88. @else { {{ event.eventType }} }
  89. </span>
  90. </div>
  91. </div>
  92. <!-- 描述 -->
  93. <div class="task-description">
  94. {{ event.description }}
  95. </div>
  96. <!-- 项目信息行 -->
  97. <div class="task-meta">
  98. <span class="project-info">
  99. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  100. <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
  101. </svg>
  102. 项目: {{ event.projectName }}
  103. </span>
  104. @if (event.designerName) {
  105. <span class="designer-info">
  106. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  107. <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
  108. </svg>
  109. 设计师: {{ event.designerName }}
  110. </span>
  111. }
  112. </div>
  113. <!-- 底部信息行 -->
  114. <div class="task-footer">
  115. <span class="deadline-info" [class.overdue]="event.overdueDays && event.overdueDays > 0">
  116. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  117. <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>
  118. </svg>
  119. 截止: {{ event.deadline | date:'MM-dd HH:mm' }}
  120. @if (event.overdueDays && event.overdueDays > 0) {
  121. <span class="overdue-label">(逾期{{ event.overdueDays }}天)</span>
  122. }
  123. @else if (event.overdueDays && event.overdueDays < 0) {
  124. <span class="upcoming-label">(还剩{{ -event.overdueDays }}天)</span>
  125. }
  126. @else {
  127. <span class="today-label">(今天)</span>
  128. }
  129. </span>
  130. <button
  131. class="btn-action btn-view-project"
  132. (click)="onViewProject(event.projectId)">
  133. 查看项目
  134. </button>
  135. </div>
  136. </div>
  137. </div>
  138. }
  139. </div>
  140. }
  141. </div>
  142. `,
  143. styles: [`
  144. /* 组件样式继承父组件的样式类 */
  145. :host {
  146. display: block;
  147. width: 100%;
  148. height: 100%;
  149. }
  150. `]
  151. })
  152. export class UrgentEventsPanelComponent {
  153. // 输入属性
  154. @Input() events: UrgentEvent[] = [];
  155. @Input() loading: boolean = false;
  156. @Input() title: string = '紧急事件';
  157. @Input() subtitle: string = '自动计算的截止事件';
  158. @Input() loadingText: string = '计算紧急事件中...';
  159. @Input() emptyText: string = '暂无紧急事件';
  160. @Input() emptyHint: string = '所有项目时间节点正常 ✅';
  161. // 输出事件
  162. @Output() viewProject = new EventEmitter<string>();
  163. /**
  164. * 查看项目详情
  165. */
  166. onViewProject(projectId: string): void {
  167. this.viewProject.emit(projectId);
  168. }
  169. }