dashboard.html 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135
  1. <!-- 顶部导航栏 -->
  2. <nav class="top-navbar">
  3. <div class="navbar-left">
  4. <h2 class="navbar-title">设计组长工作台</h2>
  5. </div>
  6. <div class="navbar-right">
  7. <div class="date-display">
  8. {{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }}
  9. </div>
  10. <div class="user-profile">
  11. <img [src]="currentUser.avatar" [alt]="currentUser.name + '头像'" class="user-avatar">
  12. <span class="user-name">{{ currentUser.name }}</span>
  13. <span class="user-role">{{ currentUser.roleName }}</span>
  14. </div>
  15. </div>
  16. </nav>
  17. <header class="dashboard-header">
  18. <!-- 核心数据指标卡片(扩展为6个) -->
  19. <div class="dashboard-metrics">
  20. <div class="metric-card" (click)="filterByStatus('overdue')">
  21. <div class="metric-icon warning">⚠️</div>
  22. <div class="metric-content">
  23. <div class="metric-count">{{ overdueProjects.length }}</div>
  24. <div class="metric-label">已延期项目</div>
  25. </div>
  26. </div>
  27. <div class="metric-card" (click)="filterByStatus('dueSoon')">
  28. <div class="metric-icon info">⏳</div>
  29. <div class="metric-content">
  30. <div class="metric-count">{{ dueSoonProjects.length }}</div>
  31. <div class="metric-label">临期项目(3天内)</div>
  32. </div>
  33. </div>
  34. <div class="metric-card" (click)="filterByStatus('pendingApproval')">
  35. <div class="metric-icon info">📋</div>
  36. <div class="metric-content">
  37. <div class="metric-count">{{ pendingApprovalProjects.length }}</div>
  38. <div class="metric-label">待组长确认项目</div>
  39. </div>
  40. </div>
  41. <div class="metric-card" (click)="filterByStatus('pendingAssignment')">
  42. <div class="metric-icon primary">🎯</div>
  43. <div class="metric-content">
  44. <div class="metric-count">{{ pendingAssignmentProjects.length }}</div>
  45. <div class="metric-label">待分配方案项目</div>
  46. </div>
  47. </div>
  48. <!-- 新增:超负荷设计师数量 -->
  49. <div class="metric-card">
  50. <div class="metric-icon danger">🔥</div>
  51. <div class="metric-content">
  52. <div class="metric-count">{{ overloadedDesignersCount }}</div>
  53. <div class="metric-label">超负荷设计师</div>
  54. </div>
  55. </div>
  56. <!-- 新增:平均负载率 -->
  57. <div class="metric-card">
  58. <div class="metric-icon success">📊</div>
  59. <div class="metric-content">
  60. <div class="metric-count">{{ averageWorkloadRate.toFixed(0) }}%</div>
  61. <div class="metric-label">平均负载率</div>
  62. </div>
  63. </div>
  64. </div>
  65. </header>
  66. <main class="dashboard-main">
  67. <!-- 项目监控大盘 -->
  68. <section class="monitoring-section">
  69. <div class="section-header">
  70. <h2>项目监控大盘</h2>
  71. <div class="section-actions">
  72. @if (selectedStatus !== 'all') {
  73. <button class="btn-link" (click)="resetStatusFilter()">返回全部项目</button>
  74. }
  75. </div>
  76. </div>
  77. <!-- 工作量负载概览 -->
  78. <div class="workload-gantt-card">
  79. <div class="gantt-header">
  80. <h3>工作量负载概览</h3>
  81. <p class="gantt-subtitle">设计师每日工作状态一目了然</p>
  82. <div class="gantt-controls">
  83. <div class="scale-switch">
  84. <button [class.active]="workloadGanttScale === 'week'" (click)="setWorkloadGanttScale('week')">周视图</button>
  85. <button [class.active]="workloadGanttScale === 'month'" (click)="setWorkloadGanttScale('month')">月视图</button>
  86. </div>
  87. <div class="legend">
  88. <span class="legend-item"><span class="dot idle"></span>空闲</span>
  89. <span class="legend-item"><span class="dot busy"></span>忙碌</span>
  90. <span class="legend-item"><span class="dot overload"></span>超负荷</span>
  91. <span class="legend-item"><span class="dot leave"></span>请假</span>
  92. </div>
  93. </div>
  94. </div>
  95. <div class="gantt-container" #workloadGanttContainer></div>
  96. </div>
  97. <!-- 🆕 视图切换按钮(固定在此位置便于切换) -->
  98. <div class="view-toggle-bar">
  99. <button class="btn-toggle-view" (click)="toggleView()">
  100. <span class="toggle-icon">{{ showGanttView ? '📋' : '📊' }}</span>
  101. <span class="toggle-text">{{ showGanttView ? '切换到项目看板' : '切换到时间轴视图' }}</span>
  102. </button>
  103. </div>
  104. <!-- 项目负载时间轴(切换视图时显示) -->
  105. @if (showGanttView) {
  106. <app-project-timeline
  107. [projects]="projectTimelineData"
  108. [companyId]="currentUser.name"
  109. (projectClick)="onProjectTimelineClick($event)">
  110. </app-project-timeline>
  111. }
  112. @if (!showGanttView) {
  113. <div class="section-filters">
  114. <div class="search-box">
  115. <input type="search" class="input-search" placeholder="搜索项目/设计师/风格关键词" [(ngModel)]="searchTerm" (input)="onSearchChange()" (focus)="onSearchFocus()" (blur)="onSearchBlur()" />
  116. @if (showSuggestions) {
  117. <div class="suggestion-panel">
  118. @if (searchSuggestions.length > 0) {
  119. <ul>
  120. @for (suggest of searchSuggestions; track suggest.id) {
  121. <li (mousedown)="selectSuggestion(suggest)">
  122. <div class="line-1">
  123. <span class="name">{{ suggest.name }}</span>
  124. <span class="badge" [class.vip]="suggest.memberType==='vip'">{{ suggest.memberType==='vip' ? 'VIP' : '普通' }}</span>
  125. <span class="urgency" [class]="'u-' + suggest.urgency">{{ getUrgencyLabel(suggest.urgency) }}</span>
  126. </div>
  127. <div class="line-2">
  128. <span class="designer">{{ suggest.designerName || '未分配' }}</span>
  129. <span class="deadline">{{ suggest.deadline | date:'MM-dd' }}</span>
  130. </div>
  131. </li>
  132. }
  133. </ul>
  134. } @else {
  135. <div class="empty">抱歉,没有检索到哦</div>
  136. }
  137. </div>
  138. }
  139. </div>
  140. <select (change)="filterProjects($event)" class="custom-select">
  141. <option value="all">全部项目</option>
  142. <option value="soft">软装项目</option>
  143. <option value="hard">硬装项目</option>
  144. </select>
  145. <select (change)="filterByUrgency($event)" class="custom-select">
  146. <option value="all">全部紧急程度</option>
  147. <option value="high">高</option>
  148. <option value="medium">中</option>
  149. <option value="low">低</option>
  150. </select>
  151. <select (change)="onStatusChange($event)" class="custom-select">
  152. <option value="all">全部状态</option>
  153. <option value="progress">进行中</option>
  154. <option value="completed">已完成</option>
  155. <option value="overdue">已延期</option>
  156. <option value="dueSoon">临期(3天内)</option>
  157. <option value="pendingApproval">待确认</option>
  158. <option value="pendingAssignment">待分配</option>
  159. </select>
  160. <select (change)="onDesignerChange($event)" class="custom-select">
  161. <option value="all">全部设计师</option>
  162. @for (d of designers; track d) {
  163. <option [value]="d">{{ d }}</option>
  164. }
  165. </select>
  166. <select (change)="onMemberTypeChange($event)" class="custom-select">
  167. <option value="all">全部会员</option>
  168. <option value="vip">VIP会员</option>
  169. <option value="normal">普通会员</option>
  170. </select>
  171. <!-- 新增:四大板块筛选 -->
  172. <select [(ngModel)]="selectedCorePhase" (change)="onCorePhaseChange($event)" class="custom-select">
  173. <option value="all">全部板块</option>
  174. @for (core of corePhases; track core.id) {
  175. <option [value]="core.id">{{ core.name }}</option>
  176. }
  177. </select>
  178. <!-- 支持数百项目的下拉筛选 -->
  179. <select [(ngModel)]="selectedProjectId" (change)="selectProject()" class="custom-select project-selector">
  180. <option value="">选择项目</option>
  181. @for (project of projects; track project.id) {
  182. <option [value]="project.id">{{ project.name }}</option>
  183. }
  184. </select>
  185. <!-- 新增:时间窗快捷筛选按钮组 -->
  186. <div class="time-window-buttons">
  187. <button [class.active]="selectedTimeWindow === 'all'" (click)="filterByTimeWindow('all')">全部</button>
  188. <button [class.active]="selectedTimeWindow === 'today'" (click)="filterByTimeWindow('today')">今天到期</button>
  189. <button [class.active]="selectedTimeWindow === 'threeDays'" (click)="filterByTimeWindow('threeDays')">3天内</button>
  190. <button [class.active]="selectedTimeWindow === 'sevenDays'" (click)="filterByTimeWindow('sevenDays')">7天内</button>
  191. </div>
  192. </div>
  193. <!-- 项目看板 - 横向展开10个项目阶段 -->
  194. <div class="project-kanban">
  195. <!-- 新增:公共横向滚动容器,保证表头与表体同步滚动 -->
  196. <div class="kanban-scroll">
  197. <!-- 阶段标题 -->
  198. <div class="kanban-header">
  199. @for (core of corePhases; track core.id) {
  200. <div class="kanban-column-header">
  201. <h3>{{ core.name }}</h3>
  202. <span class="stage-count">{{ getProjectCountByCorePhase(core.id) }}</span>
  203. </div>
  204. }
  205. </div>
  206. <!-- 项目卡片 -->
  207. <div class="kanban-body">
  208. @for (core of corePhases; track core.id) {
  209. <div class="kanban-column">
  210. @for (project of getProjectsByCorePhase(core.id); track project.id) {
  211. <div class="project-card"
  212. (click)="viewProjectDetailsByPhase(project.id, core.id)"
  213. [class.overdue]="project.isOverdue"
  214. [class.high-urgency]="project.urgency === 'high'"
  215. [class.due-soon]="project.dueSoon && !project.isOverdue"
  216. [class.pending-approval]="isPendingApproval(project)">
  217. <!-- 待审批徽章 -->
  218. @if (isPendingApproval(project)) {
  219. <div class="approval-badge">
  220. <span class="badge-icon">📋</span>
  221. <span class="badge-text">待审批</span>
  222. </div>
  223. }
  224. <div class="project-card-header">
  225. <h4 (click)="viewProjectDetailsByPhase(project.id, core.id); $event.stopPropagation()" style="cursor: pointer;">{{ project.name }}</h4>
  226. <div class="right-badges">
  227. <span class="member-badge" [class.vip]="project.memberType === 'vip'">{{ project.memberType === 'vip' ? 'VIP' : '普通' }}</span>
  228. <span class="project-urgency" [class]="'urgency-' + project.urgency">{{ getUrgencyLabel(project.urgency) }}</span>
  229. </div>
  230. </div>
  231. <div class="project-card-content">
  232. <p>负责人: {{ project.designerName || '未分配' }}</p>
  233. <p class="deadline">{{ project.isOverdue ? '超期' + project.overdueDays + '天' : (project.dueSoon ? '临期: ' + (project.deadline | date:'MM-dd') : '截止: ' + (project.deadline | date:'MM-dd')) }}</p>
  234. </div>
  235. <div class="project-card-footer">
  236. <button (click)="viewProjectDetailsByPhase(project.id, core.id); $event.stopPropagation()" class="btn-view">查看详情</button>
  237. @if (project.currentStage === 'pendingAssignment') {
  238. <button (click)="openSmartMatch(project); $event.stopPropagation()" class="btn-smart">🤖 智能推荐</button>
  239. <button (click)="quickAssignProject(project.id); $event.stopPropagation()" class="btn-assign">手动分配</button>
  240. }
  241. <!-- 新增:质量评审快捷操作(保留,不影响四大板块分类) -->
  242. @if (project.currentStage === 'review' || project.currentStage === 'delivery') {
  243. <div class="inline-actions">
  244. <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'excellent')">评为优秀</button>
  245. <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'qualified')">评为合格</button>
  246. <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'unqualified')">评为不合格</button>
  247. </div>
  248. }
  249. </div>
  250. </div>
  251. }
  252. @if (getProjectsByCorePhase(core.id).length === 0) {
  253. <div class="empty-column">
  254. <span class="empty-icon">📦</span>
  255. <p>暂无项目</p>
  256. </div>
  257. }
  258. </div>
  259. }
  260. </div>
  261. </div>
  262. </div>
  263. }
  264. </section>
  265. <!-- 🆕 待办任务双栏布局(待办问题 + 紧急事件) -->
  266. <section class="todo-section todo-section-dual">
  267. <div class="section-header">
  268. <h2>待办事项</h2>
  269. <button
  270. class="btn-refresh"
  271. (click)="refreshTodoTasks()"
  272. [disabled]="loadingTodoTasks || loadingUrgentEvents"
  273. title="刷新待办事项">
  274. <svg viewBox="0 0 24 24" width="16" height="16" [class.rotating]="loadingTodoTasks || loadingUrgentEvents">
  275. <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"/>
  276. </svg>
  277. </button>
  278. </div>
  279. <!-- 🆕 双栏容器 -->
  280. <div class="todo-dual-columns">
  281. <!-- ========== 左栏:待办问题 ========== -->
  282. <div class="todo-column todo-column-issues">
  283. <div class="column-header">
  284. <h3>
  285. <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
  286. <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
  287. </svg>
  288. 待办问题
  289. @if (todoTasksFromIssues.length > 0) {
  290. <span class="task-count">({{ todoTasksFromIssues.length }})</span>
  291. }
  292. </h3>
  293. <span class="column-subtitle">来自项目问题板块</span>
  294. </div>
  295. <!-- 加载状态 -->
  296. @if (loadingTodoTasks) {
  297. <div class="loading-state">
  298. <svg class="spinner" viewBox="0 0 50 50">
  299. <circle cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle>
  300. </svg>
  301. <p>加载待办任务中...</p>
  302. </div>
  303. }
  304. <!-- 错误状态 -->
  305. @if (!loadingTodoTasks && todoTaskError) {
  306. <div class="error-state">
  307. <svg viewBox="0 0 24 24" width="48" height="48" fill="#ef4444">
  308. <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
  309. </svg>
  310. <p>{{ todoTaskError }}</p>
  311. <button class="btn-retry" (click)="refreshTodoTasks()">重试</button>
  312. </div>
  313. }
  314. <!-- 空状态 -->
  315. @if (!loadingTodoTasks && !todoTaskError && todoTasksFromIssues.length === 0) {
  316. <div class="empty-state">
  317. <svg viewBox="0 0 24 24" width="64" height="64" fill="#d1d5db">
  318. <path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
  319. </svg>
  320. <p>暂无待办任务</p>
  321. <p class="hint">所有项目问题都已处理完毕 🎉</p>
  322. </div>
  323. }
  324. <!-- 待办任务列表 -->
  325. @if (!loadingTodoTasks && !todoTaskError && todoTasksFromIssues.length > 0) {
  326. <div class="todo-list-compact">
  327. @for (task of todoTasksFromIssues; track task.id) {
  328. <div class="todo-item-compact" [attr.data-priority]="task.priority">
  329. <!-- 左侧优先级色条 -->
  330. <div class="priority-indicator" [attr.data-priority]="task.priority"></div>
  331. <!-- 任务内容 -->
  332. <div class="task-content">
  333. <!-- 标题行 -->
  334. <div class="task-header">
  335. <span class="task-title">{{ task.title }}</span>
  336. <div class="task-badges">
  337. <span class="badge badge-priority" [attr.data-priority]="task.priority">
  338. {{ getPriorityConfig(task.priority).label }}
  339. </span>
  340. <span class="badge badge-type">{{ getIssueTypeLabel(task.type) }}</span>
  341. </div>
  342. </div>
  343. <!-- 项目信息行 -->
  344. <div class="task-meta">
  345. <span class="project-info">
  346. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  347. <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
  348. </svg>
  349. 项目: {{ task.projectName }}
  350. @if (task.relatedSpace) {
  351. | {{ task.relatedSpace }}
  352. }
  353. @if (task.relatedStage) {
  354. | {{ task.relatedStage }}
  355. }
  356. </span>
  357. </div>
  358. <!-- 底部信息行 -->
  359. <div class="task-footer">
  360. <span class="time-info" [title]="formatExactTime(task.createdAt)">
  361. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  362. <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"/>
  363. </svg>
  364. 创建于 {{ formatRelativeTime(task.createdAt) }}
  365. </span>
  366. <span class="assignee-info">
  367. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  368. <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"/>
  369. </svg>
  370. 指派给: {{ task.assigneeName }}
  371. </span>
  372. </div>
  373. </div>
  374. <!-- 右侧操作按钮 -->
  375. <div class="task-actions">
  376. <button
  377. class="btn-action btn-view"
  378. (click)="navigateToIssue(task)"
  379. title="查看详情">
  380. <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
  381. <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
  382. </svg>
  383. 查看详情
  384. </button>
  385. <button
  386. class="btn-action btn-mark-read"
  387. (click)="markAsRead(task)"
  388. title="标记已读">
  389. <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
  390. <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
  391. </svg>
  392. 标记已读
  393. </button>
  394. </div>
  395. </div>
  396. }
  397. </div>
  398. }
  399. </div>
  400. <!-- ========== 左栏结束 ========== -->
  401. <!-- ========== 右栏:紧急事件 ========== -->
  402. <div class="todo-column todo-column-urgent">
  403. <div class="column-header">
  404. <h3>
  405. <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
  406. <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
  407. </svg>
  408. 紧急事件
  409. @if (urgentEvents.length > 0) {
  410. <span class="task-count urgent">({{ urgentEvents.length }})</span>
  411. }
  412. </h3>
  413. <span class="column-subtitle">自动计算的截止事件</span>
  414. </div>
  415. @if (!loadingUrgentEvents && urgentEvents.length > 0) {
  416. <div class="tag-filter-bar">
  417. <button
  418. class="tag-button"
  419. [class.active]="urgentEventTagFilter === 'all'"
  420. (click)="filterUrgentEventsByTag('all')"
  421. title="显示所有紧急事件"
  422. >
  423. <span class="tag-icon">📋</span>
  424. <span class="tag-label">全部</span>
  425. <span class="tag-count">{{ urgentEvents.length }}</span>
  426. </button>
  427. <button
  428. class="tag-button"
  429. [class.active]="urgentEventTagFilter === 'phase'"
  430. (click)="filterUrgentEventsByTag('phase')"
  431. title="工作阶段"
  432. >
  433. <span class="tag-icon">🔧</span>
  434. <span class="tag-label">工作阶段</span>
  435. <span class="tag-count">{{ getTagCount('phase') }}</span>
  436. </button>
  437. <button
  438. class="tag-button"
  439. [class.active]="urgentEventTagFilter === 'review'"
  440. (click)="filterUrgentEventsByTag('review')"
  441. title="小图截止"
  442. >
  443. <span class="tag-icon">📐</span>
  444. <span class="tag-label">小图截止</span>
  445. <span class="tag-count">{{ getTagCount('review') }}</span>
  446. </button>
  447. <button
  448. class="tag-button"
  449. [class.active]="urgentEventTagFilter === 'delivery'"
  450. (click)="filterUrgentEventsByTag('delivery')"
  451. title="交付延期"
  452. >
  453. <span class="tag-icon">📦</span>
  454. <span class="tag-label">交付延期</span>
  455. <span class="tag-count">{{ getTagCount('delivery') }}</span>
  456. </button>
  457. </div>
  458. }
  459. <!-- 加载状态 -->
  460. @if (loadingUrgentEvents) {
  461. <div class="loading-state">
  462. <svg class="spinner" viewBox="0 0 50 50">
  463. <circle cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle>
  464. </svg>
  465. <p>计算紧急事件中...</p>
  466. </div>
  467. }
  468. <!-- 空状态 -->
  469. @if (!loadingUrgentEvents && urgentEvents.length === 0) {
  470. <div class="empty-state">
  471. <svg viewBox="0 0 24 24" width="64" height="64" fill="#d1d5db">
  472. <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
  473. </svg>
  474. <p>暂无紧急事件</p>
  475. <p class="hint">所有项目时间节点正常 ✅</p>
  476. </div>
  477. }
  478. @if (!loadingUrgentEvents && urgentEvents.length > 0 && filteredUrgentEvents.length === 0) {
  479. <div class="empty-state filtered">
  480. <svg viewBox="0 0 24 24" width="48" height="48" fill="#d1d5db">
  481. <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
  482. </svg>
  483. <p>该筛选条件下暂无事件</p>
  484. <p class="hint">尝试切换其他标签</p>
  485. </div>
  486. }
  487. <!-- 紧急事件列表 -->
  488. @if (!loadingUrgentEvents && filteredUrgentEvents.length > 0) {
  489. <div class="todo-list-compact urgent-list">
  490. @for (event of filteredUrgentEvents; track event.id) {
  491. <div class="todo-item-compact urgent-item" [attr.data-urgency]="event.urgencyLevel">
  492. <!-- 左侧紧急程度色条 -->
  493. <div class="urgency-indicator" [attr.data-urgency]="event.urgencyLevel"></div>
  494. <!-- 事件内容 -->
  495. <div class="task-content">
  496. <!-- 标题行 -->
  497. <div class="task-header">
  498. <span class="task-title">{{ event.title }}</span>
  499. <div class="task-badges">
  500. <span class="badge badge-urgency" [attr.data-urgency]="event.urgencyLevel">
  501. @if (event.urgencyLevel === 'critical') { 🔴 紧急 }
  502. @else if (event.urgencyLevel === 'high') { 🟠 重要 }
  503. @else { 🟡 注意 }
  504. </span>
  505. <span class="badge badge-event-type">
  506. @if (event.eventType === 'review') { 对图 }
  507. @else if (event.eventType === 'delivery') { 交付 }
  508. @else if (event.eventType === 'phase_deadline') { {{ event.phaseName }} }
  509. </span>
  510. </div>
  511. </div>
  512. <!-- 描述 -->
  513. <div class="task-description">
  514. {{ event.description }}
  515. </div>
  516. <!-- 项目信息行 -->
  517. <div class="task-meta">
  518. <span class="project-info">
  519. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  520. <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
  521. </svg>
  522. 项目: {{ event.projectName }}
  523. </span>
  524. @if (event.designerName) {
  525. <span class="designer-info">
  526. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  527. <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"/>
  528. </svg>
  529. 设计师: {{ event.designerName }}
  530. </span>
  531. }
  532. </div>
  533. <!-- 底部信息行 -->
  534. <div class="task-footer">
  535. <span class="deadline-info" [class.overdue]="event.overdueDays && event.overdueDays > 0">
  536. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  537. <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"/>
  538. </svg>
  539. 截止: {{ event.deadline | date:'MM-dd HH:mm' }}
  540. @if (event.overdueDays && event.overdueDays > 0) {
  541. <span class="overdue-label">(逾期{{ event.overdueDays }}天)</span>
  542. }
  543. @else if (event.overdueDays && event.overdueDays < 0) {
  544. <span class="upcoming-label">(还剩{{ -event.overdueDays }}天)</span>
  545. }
  546. @else {
  547. <span class="today-label">(今天)</span>
  548. }
  549. </span>
  550. @if (event.completionRate !== undefined) {
  551. <span class="completion-info">
  552. <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
  553. <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
  554. </svg>
  555. 完成率: {{ event.completionRate }}%
  556. </span>
  557. }
  558. </div>
  559. </div>
  560. <!-- 右侧操作按钮 -->
  561. <div class="task-actions">
  562. <button
  563. class="btn-action btn-view"
  564. (click)="onProjectClick(event.projectId)"
  565. title="查看项目">
  566. <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
  567. <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
  568. </svg>
  569. 查看项目
  570. </button>
  571. </div>
  572. </div>
  573. }
  574. </div>
  575. }
  576. </div>
  577. <!-- ========== 右栏结束 ========== -->
  578. </div>
  579. <!-- ========== 双栏容器结束 ========== -->
  580. </section>
  581. <!-- 超期项目提醒 -->
  582. @if (showAlert && overdueProjects.length > 0) {
  583. <div class="overdue-alert">
  584. <div class="alert-content">
  585. <h3>⚠️ 超期项目提醒</h3>
  586. <ul>
  587. @for (project of overdueProjects.slice(0, 3); track $index) {
  588. <li>
  589. {{ project.name }} ({{ project.designerName }} 负责) - 超期{{ project.overdueDays }}天
  590. </li>
  591. }
  592. </ul>
  593. <div class="alert-actions">
  594. <button (click)="viewAllOverdueProjects()" class="btn-view-all">查看全部</button>
  595. <button (click)="closeAlert()" class="btn-close">关闭</button>
  596. </div>
  597. </div>
  598. </div>
  599. }
  600. @if (urgentPinnedProjects && urgentPinnedProjects.length > 0) {
  601. <div class="urgent-pinned">
  602. <div class="pinned-title">紧急任务固定区(超期 + 高紧急)</div>
  603. <div class="pinned-list">
  604. @for (p of urgentPinnedProjects.slice(0, 3); track $index) {
  605. <div class="pinned-item" (click)="filterByStatus('overdue')">
  606. <span class="dot dot-high"></span>
  607. <span class="name">{{ p.name }}</span>
  608. <span class="meta">{{ p.designerName || '未分配' }} · 超期{{ p.overdueDays }}天</span>
  609. </div>
  610. }
  611. @if (urgentPinnedProjects.length > 3) {
  612. <button class="btn-view-all" (click)="viewAllOverdueProjects()">更多…</button>
  613. }
  614. </div>
  615. </div>
  616. }
  617. </main>
  618. <!-- 员工详情面板组件 -->
  619. <app-employee-detail-panel
  620. [visible]="showEmployeeDetailPanel"
  621. [employeeDetail]="selectedEmployeeDetail"
  622. (close)="closeEmployeeDetailPanel()"
  623. (calendarMonthChange)="changeEmployeeCalendarMonth($event)"
  624. (calendarDayClick)="onCalendarDayClick($event)"
  625. (projectClick)="navigateToProjectFromPanel($event)"
  626. (refreshSurvey)="refreshEmployeeSurvey()">
  627. </app-employee-detail-panel>
  628. <!-- 以下代码已由 EmployeeDetailPanelComponent 组件替代,日历项目列表弹窗也已集成到组件内部 -->
  629. <!--
  630. <!-- 员工详情面板(旧代码已废弃) -->
  631. @if (showEmployeeDetailPanel && selectedEmployeeDetail) {
  632. <div class="employee-detail-overlay" (click)="closeEmployeeDetailPanel()">
  633. <div class="employee-detail-panel" (click)="$event.stopPropagation()">
  634. <!-- 面板头部 -->
  635. <div class="panel-header">
  636. <h3 class="panel-title">
  637. <svg class="icon-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  638. <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
  639. <circle cx="12" cy="7" r="4"></circle>
  640. </svg>
  641. {{ selectedEmployeeDetail.name }} 详情
  642. </h3>
  643. <button class="btn-close" (click)="closeEmployeeDetailPanel()">
  644. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  645. <line x1="18" y1="6" x2="6" y2="18"></line>
  646. <line x1="6" y1="6" x2="18" y2="18"></line>
  647. </svg>
  648. </button>
  649. </div>
  650. <!-- 面板内容 -->
  651. <div class="panel-content">
  652. <!-- 负载概况栏 -->
  653. <div class="section workload-section">
  654. <div class="section-header">
  655. <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  656. <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
  657. <line x1="9" y1="9" x2="15" y2="9"></line>
  658. <line x1="9" y1="15" x2="15" y2="15"></line>
  659. </svg>
  660. <h4>负载概况</h4>
  661. </div>
  662. <div class="workload-info">
  663. <div class="workload-stat">
  664. <span class="stat-label">当前负责项目数:</span>
  665. <span class="stat-value" [class]="selectedEmployeeDetail.currentProjects >= 3 ? 'high-workload' : 'normal-workload'">
  666. {{ selectedEmployeeDetail.currentProjects }} 个
  667. </span>
  668. </div>
  669. @if (selectedEmployeeDetail.projectData.length > 0) {
  670. <div class="project-list">
  671. <span class="project-label">核心项目:</span>
  672. <div class="project-tags">
  673. @for (project of selectedEmployeeDetail.projectData; track project.id) {
  674. <span class="project-tag clickable"
  675. (click)="navigateToProjectFromPanel(project.id)"
  676. title="点击查看项目详情">
  677. {{ project.name }}
  678. <svg class="icon-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  679. <path d="M7 17L17 7M17 7H7M17 7V17"/>
  680. </svg>
  681. </span>
  682. }
  683. @if (selectedEmployeeDetail.currentProjects > selectedEmployeeDetail.projectData.length) {
  684. <span class="project-tag more">+{{ selectedEmployeeDetail.currentProjects - selectedEmployeeDetail.projectData.length }}</span>
  685. }
  686. </div>
  687. </div>
  688. }
  689. </div>
  690. </div>
  691. <!-- 负载详细日历 -->
  692. <div class="section calendar-section">
  693. <div class="section-header">
  694. <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  695. <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
  696. <line x1="16" y1="2" x2="16" y2="6"></line>
  697. <line x1="8" y1="2" x2="8" y2="6"></line>
  698. <line x1="3" y1="10" x2="21" y2="10"></line>
  699. </svg>
  700. <h4>负载详细日历</h4>
  701. </div>
  702. @if (selectedEmployeeDetail.calendarData) {
  703. <div class="employee-calendar">
  704. <!-- 月份标题 -->
  705. <div class="calendar-month-header">
  706. <button class="btn-prev-month"
  707. (click)="changeEmployeeCalendarMonth(-1)"
  708. title="上月">
  709. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  710. <polyline points="15 18 9 12 15 6"></polyline>
  711. </svg>
  712. </button>
  713. <span class="month-title">
  714. {{ selectedEmployeeDetail.calendarData.currentMonth | date:'yyyy年M月' }}
  715. </span>
  716. <button class="btn-next-month"
  717. (click)="changeEmployeeCalendarMonth(1)"
  718. title="下月">
  719. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  720. <polyline points="9 18 15 12 9 6"></polyline>
  721. </svg>
  722. </button>
  723. </div>
  724. <!-- 星期标题 -->
  725. <div class="calendar-weekdays">
  726. <div class="weekday">日</div>
  727. <div class="weekday">一</div>
  728. <div class="weekday">二</div>
  729. <div class="weekday">三</div>
  730. <div class="weekday">四</div>
  731. <div class="weekday">五</div>
  732. <div class="weekday">六</div>
  733. </div>
  734. <!-- 日历网格 -->
  735. <div class="calendar-grid">
  736. @for (day of selectedEmployeeDetail.calendarData.days; track day.date.getTime()) {
  737. <div class="calendar-day"
  738. [class.today]="day.isToday"
  739. [class.other-month]="!day.isCurrentMonth"
  740. [class.has-projects]="day.projectCount > 0"
  741. [class.clickable]="day.projectCount > 0 && day.isCurrentMonth"
  742. (click)="onCalendarDayClick(day)">
  743. <div class="day-number">{{ day.date.getDate() }}</div>
  744. @if (day.projectCount > 0) {
  745. <div class="day-badge" [class.high-load]="day.projectCount >= 2">
  746. {{ day.projectCount }}个项目
  747. </div>
  748. }
  749. </div>
  750. }
  751. </div>
  752. <!-- 图例 -->
  753. <div class="calendar-legend">
  754. <div class="legend-item">
  755. <span class="legend-dot today-dot"></span>
  756. <span class="legend-text">今天</span>
  757. </div>
  758. <div class="legend-item">
  759. <span class="legend-dot project-dot"></span>
  760. <span class="legend-text">有项目</span>
  761. </div>
  762. <div class="legend-item">
  763. <span class="legend-dot high-dot"></span>
  764. <span class="legend-text">高负载</span>
  765. </div>
  766. </div>
  767. </div>
  768. }
  769. </div>
  770. <!-- 请假明细栏 -->
  771. <div class="section leave-section">
  772. <div class="section-header">
  773. <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  774. <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
  775. <line x1="16" y1="2" x2="16" y2="6"></line>
  776. <line x1="8" y1="2" x2="8" y2="6"></line>
  777. <line x1="3" y1="10" x2="21" y2="10"></line>
  778. </svg>
  779. <h4>请假明细(未来7天)</h4>
  780. </div>
  781. <div class="leave-table">
  782. @if (selectedEmployeeDetail.leaveRecords.length > 0) {
  783. <table>
  784. <thead>
  785. <tr>
  786. <th>日期</th>
  787. <th>状态</th>
  788. <th>备注</th>
  789. </tr>
  790. </thead>
  791. <tbody>
  792. @for (record of selectedEmployeeDetail.leaveRecords; track record.id) {
  793. <tr [class]="record.isLeave ? 'leave-day' : 'work-day'">
  794. <td>{{ record.date | date:'M月d日' }}</td>
  795. <td>
  796. <span class="status-badge" [class]="record.isLeave ? 'leave' : 'work'">
  797. {{ record.isLeave ? '请假' : '正常' }}
  798. </span>
  799. </td>
  800. <td>{{ record.isLeave ? getLeaveTypeText(record.leaveType) : '-' }}</td>
  801. </tr>
  802. }
  803. </tbody>
  804. </table>
  805. } @else {
  806. <div class="no-leave">
  807. <svg class="no-data-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  808. <circle cx="12" cy="12" r="10"></circle>
  809. <path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
  810. <line x1="9" y1="9" x2="9.01" y2="9"></line>
  811. <line x1="15" y1="9" x2="15.01" y2="9"></line>
  812. </svg>
  813. <p>未来7天无请假安排</p>
  814. </div>
  815. }
  816. </div>
  817. </div>
  818. <!-- 红色标记说明 -->
  819. <div class="section explanation-section">
  820. <div class="section-header">
  821. <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  822. <circle cx="12" cy="12" r="10"></circle>
  823. <line x1="12" y1="8" x2="12" y2="12"></line>
  824. <line x1="12" y1="16" x2="12.01" y2="16"></line>
  825. </svg>
  826. <h4>红色标记说明</h4>
  827. </div>
  828. <div class="explanation-content">
  829. <p class="explanation-text">{{ selectedEmployeeDetail.redMarkExplanation }}</p>
  830. </div>
  831. </div>
  832. <!-- 新增:能力问卷 -->
  833. <div class="section survey-section">
  834. <div class="section-header">
  835. <svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  836. <path d="M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3"/>
  837. </svg>
  838. <h4>能力问卷</h4>
  839. <button
  840. class="btn-refresh-survey"
  841. (click)="refreshEmployeeSurvey()"
  842. [disabled]="refreshingSurvey"
  843. title="刷新问卷状态">
  844. <svg viewBox="0 0 24 24" width="16" height="16" [class.rotating]="refreshingSurvey">
  845. <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"/>
  846. </svg>
  847. </button>
  848. </div>
  849. @if (selectedEmployeeDetail.surveyCompleted && selectedEmployeeDetail.surveyData) {
  850. <div class="survey-content">
  851. <div class="survey-status completed">
  852. <svg viewBox="0 0 24 24" width="20" height="20" fill="#34c759">
  853. <path d="M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z"/>
  854. </svg>
  855. <span>已完成问卷</span>
  856. <span class="survey-time">
  857. {{ selectedEmployeeDetail.surveyData.createdAt | date:'yyyy-MM-dd HH:mm' }}
  858. </span>
  859. </div>
  860. <!-- 能力画像摘要 -->
  861. @if (!showFullSurvey) {
  862. <div class="capability-summary">
  863. <h5>您的能力画像</h5>
  864. @if (getCapabilitySummary(selectedEmployeeDetail.surveyData.answers); as summary) {
  865. <div class="summary-grid">
  866. <div class="summary-item">
  867. <span class="label">擅长风格:</span>
  868. <span class="value">{{ summary.styles }}</span>
  869. </div>
  870. <div class="summary-item">
  871. <span class="label">擅长空间:</span>
  872. <span class="value">{{ summary.spaces }}</span>
  873. </div>
  874. <div class="summary-item">
  875. <span class="label">技术优势:</span>
  876. <span class="value">{{ summary.advantages }}</span>
  877. </div>
  878. <div class="summary-item">
  879. <span class="label">项目难度:</span>
  880. <span class="value">{{ summary.difficulty }}</span>
  881. </div>
  882. <div class="summary-item">
  883. <span class="label">周承接量:</span>
  884. <span class="value">{{ summary.capacity }}</span>
  885. </div>
  886. <div class="summary-item">
  887. <span class="label">紧急订单:</span>
  888. <span class="value">
  889. {{ summary.urgent }}
  890. @if (summary.urgentLimit) {
  891. <span class="limit-hint">(每月不超过{{summary.urgentLimit}}次)</span>
  892. }
  893. </span>
  894. </div>
  895. <div class="summary-item">
  896. <span class="label">进度同步:</span>
  897. <span class="value">{{ summary.feedback }}</span>
  898. </div>
  899. <div class="summary-item">
  900. <span class="label">沟通方式:</span>
  901. <span class="value">{{ summary.communication }}</span>
  902. </div>
  903. </div>
  904. }
  905. <button class="btn-view-full" (click)="toggleSurveyDisplay()">
  906. <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
  907. <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
  908. </svg>
  909. 查看完整问卷(共 {{ selectedEmployeeDetail.surveyData.answers.length }} 道题)
  910. </button>
  911. </div>
  912. }
  913. <!-- 完整问卷答案 -->
  914. @if (showFullSurvey) {
  915. <div class="survey-answers">
  916. <h5>完整问卷答案(共 {{ selectedEmployeeDetail.surveyData.answers.length }} 道题):</h5>
  917. @for (answer of selectedEmployeeDetail.surveyData.answers; track $index) {
  918. <div class="answer-item">
  919. <div class="question-text">
  920. <strong>Q{{$index + 1}}:</strong> {{ answer.question }}
  921. </div>
  922. <div class="answer-text">
  923. @if (!answer.answer) {
  924. <span class="answer-tag empty">未填写(选填)</span>
  925. } @else if (answer.type === 'single' || answer.type === 'text' || answer.type === 'textarea' || answer.type === 'number') {
  926. <span class="answer-tag single">{{ answer.answer }}</span>
  927. } @else if (answer.type === 'multiple') {
  928. @if (Array.isArray(answer.answer)) {
  929. @for (opt of answer.answer; track opt) {
  930. <span class="answer-tag multiple">{{ opt }}</span>
  931. }
  932. } @else {
  933. <span class="answer-tag single">{{ answer.answer }}</span>
  934. }
  935. } @else if (answer.type === 'scale') {
  936. <div class="answer-scale">
  937. <div class="scale-bar">
  938. <div class="scale-fill" [style.width.%]="(answer.answer / 10) * 100">
  939. <span>{{ answer.answer }} / 10</span>
  940. </div>
  941. </div>
  942. </div>
  943. } @else {
  944. <span class="answer-tag single">{{ answer.answer }}</span>
  945. }
  946. </div>
  947. </div>
  948. }
  949. <button class="btn-collapse" (click)="toggleSurveyDisplay()">
  950. <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
  951. <path d="M19 13H5v-2h14v2z"/>
  952. </svg>
  953. 收起详情
  954. </button>
  955. </div>
  956. }
  957. </div>
  958. } @else {
  959. <div class="survey-empty">
  960. <svg class="no-data-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  961. <circle cx="12" cy="12" r="10"></circle>
  962. <path d="M8 12h8M12 8v8"/>
  963. </svg>
  964. <p>该员工尚未完成能力问卷</p>
  965. </div>
  966. }
  967. </div>
  968. </div>
  969. </div>
  970. </div>
  971. }
  972. <!-- 日历项目列表弹窗 -->
  973. @if (showCalendarProjectList) {
  974. <div class="calendar-project-modal-overlay" (click)="closeCalendarProjectList()">
  975. <div class="calendar-project-modal" (click)="$event.stopPropagation()">
  976. <div class="modal-header">
  977. <h3>
  978. <svg class="header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  979. <path d="M9 11l3 3L22 4"></path>
  980. <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
  981. </svg>
  982. {{ selectedDate | date:'M月d日' }} 的项目
  983. </h3>
  984. <button class="btn-close" (click)="closeCalendarProjectList()">
  985. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  986. <line x1="18" y1="6" x2="6" y2="18"></line>
  987. <line x1="6" y1="6" x2="18" y2="18"></line>
  988. </svg>
  989. </button>
  990. </div>
  991. <div class="modal-body">
  992. <div class="project-count-info">
  993. 共 <strong>{{ selectedDayProjects.length }}</strong> 个项目
  994. </div>
  995. <div class="project-list">
  996. @for (project of selectedDayProjects; track project.id) {
  997. <div class="project-item" (click)="navigateToProjectFromPanel(project.id); closeCalendarProjectList()">
  998. <div class="project-info">
  999. <svg class="project-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  1000. <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
  1001. <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
  1002. </svg>
  1003. <div class="project-details">
  1004. <h4 class="project-name">{{ project.name }}</h4>
  1005. @if (project.deadline) {
  1006. <p class="project-deadline">
  1007. 截止日期: {{ project.deadline | date:'yyyy-MM-dd' }}
  1008. </p>
  1009. }
  1010. </div>
  1011. </div>
  1012. <svg class="arrow-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  1013. <path d="M5 12h14M12 5l7 7-7 7"/>
  1014. </svg>
  1015. </div>
  1016. }
  1017. </div>
  1018. </div>
  1019. </div>
  1020. </div>
  1021. }
  1022. -->
  1023. <!-- 智能推荐弹窗 -->
  1024. @if (showSmartMatch) {
  1025. <div class="smart-match-modal">
  1026. <div class="modal-backdrop" (click)="closeSmartMatch()"></div>
  1027. <div class="modal-content">
  1028. <div class="modal-header">
  1029. <h3>🤖 智能推荐设计师</h3>
  1030. <button class="btn-close" (click)="closeSmartMatch()">×</button>
  1031. </div>
  1032. @if (selectedProject) {
  1033. <div class="project-info">
  1034. <h4>{{ selectedProject.name }}</h4>
  1035. <div class="tags">
  1036. <span class="tag">{{ selectedProject.type === 'hard' ? '硬装' : '软装' }}</span>
  1037. <span class="tag">{{ selectedProject.memberType === 'vip' ? 'VIP' : '普通' }}</span>
  1038. <span class="tag urgency u-{{ selectedProject.urgency }}">
  1039. {{ getUrgencyLabel(selectedProject.urgency) }}
  1040. </span>
  1041. </div>
  1042. </div>
  1043. }
  1044. <div class="recommendations-list">
  1045. @for (rec of recommendations; track rec.designer.id; let i = $index) {
  1046. <div class="rec-card">
  1047. <div class="rank" [class.gold]="i===0" [class.silver]="i===1" [class.bronze]="i===2">
  1048. {{ i + 1 }}
  1049. </div>
  1050. <div class="designer-info">
  1051. <h4>{{ rec.designer.name }}</h4>
  1052. <div class="match-score-bar">
  1053. <div class="score-fill" [style.width.%]="rec.matchScore">
  1054. <span>{{ rec.matchScore }}分</span>
  1055. </div>
  1056. </div>
  1057. </div>
  1058. <div class="details">
  1059. <p><strong>擅长:</strong>{{ rec.designer.tags.expertise.styles.join('、') || '暂无标签' }}</p>
  1060. <p><strong>负载:</strong>{{ rec.loadRate.toFixed(0) }}% ({{ rec.currentProjects }}个项目)</p>
  1061. <p><strong>评分:</strong>⭐ {{ rec.designer.tags.history.avgRating || '暂无' }}</p>
  1062. <p class="reason"><strong>推荐理由:</strong>{{ rec.reason }}</p>
  1063. </div>
  1064. <button class="btn-assign" (click)="assignToDesigner(rec.designer.id)">
  1065. 分配给TA
  1066. </button>
  1067. </div>
  1068. }
  1069. @if (recommendations.length === 0) {
  1070. <div class="empty">
  1071. <p>未找到合适的设计师</p>
  1072. <p>您可以手动分配或调整项目参数</p>
  1073. </div>
  1074. }
  1075. </div>
  1076. </div>
  1077. </div>
  1078. }