dashboard.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. import { Component, OnInit, signal } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { RouterModule, Router } from '@angular/router';
  4. import { AuthService } from '../../../services/auth.service';
  5. import { WorkHourService } from '../../../services/work-hour.service';
  6. import { WorkHourDashboardData, MonthlyWorkHourStats, PerformanceLevel } from '../../../models/work-hour.model';
  7. // 项目复杂度类型
  8. interface ProjectComplexity {
  9. type: string; // '家装小户型' | '家装大户型' | '工装商铺' | '工装写字楼'
  10. standardHours: {
  11. requirementDeepening: number;
  12. modeling: number;
  13. rendering: number;
  14. postProduction: number;
  15. };
  16. }
  17. // 逾期项目信息
  18. interface OverdueProject {
  19. id: string;
  20. name: string;
  21. stage: string;
  22. overdueDays: number;
  23. reason: string;
  24. designerName: string;
  25. }
  26. // 设计师效率统计
  27. interface DesignerEfficiency {
  28. id: string;
  29. name: string;
  30. avgCompletionTime: number; // 天
  31. overdueProjectCount: number;
  32. stageUtilization: {
  33. requirementDeepening: number; // 百分比
  34. modeling: number;
  35. rendering: number;
  36. postProduction: number;
  37. };
  38. idleDays: number; // 闲置天数
  39. suggestion: string;
  40. }
  41. @Component({
  42. selector: 'app-dashboard',
  43. imports: [CommonModule, RouterModule],
  44. templateUrl: './dashboard.html',
  45. styleUrl: './dashboard.scss'
  46. })
  47. export class Dashboard implements OnInit {
  48. // 添加today属性用于模板中的日期显示
  49. today = new Date();
  50. // 待办任务数据
  51. todoTasks = signal([
  52. { id: 1, title: '临时收款待核定', priority: 'high', source: 'reconciliation', dueTime: '10:30' },
  53. { id: 2, title: '报价待审核', priority: 'medium', source: 'project-records', dueTime: '14:00' },
  54. { id: 3, title: '素材成本待录入', priority: 'low', source: 'reconciliation', dueTime: '16:30' },
  55. { id: 4, title: '临时收款待核定', priority: 'high', source: 'reconciliation', dueTime: '11:00' },
  56. { id: 5, title: '销售监管提醒', priority: 'high', source: 'project-records', dueTime: '09:30' }
  57. ]);
  58. // 数据概览数据
  59. dashboardStats = signal({
  60. todayOrders: 12,
  61. pendingPayment: 156800,
  62. quotedProjects: 28,
  63. materialCostSaved: 8900
  64. });
  65. // 用户角色
  66. userRole = signal('teamLead'); // teamLead 或 juniorMember
  67. // 工时统计数据
  68. workHourDashboard = signal<WorkHourDashboardData | null>(null);
  69. monthlyStats = signal<MonthlyWorkHourStats[]>([]);
  70. showWorkHourModule = signal(false);
  71. // 新增:项目复杂度预设标准工时
  72. projectComplexities: ProjectComplexity[] = [
  73. {
  74. type: '家装小户型',
  75. standardHours: { requirementDeepening: 16, modeling: 40, rendering: 24, postProduction: 8 }
  76. },
  77. {
  78. type: '家装大户型',
  79. standardHours: { requirementDeepening: 24, modeling: 64, rendering: 40, postProduction: 16 }
  80. },
  81. {
  82. type: '工装商铺',
  83. standardHours: { requirementDeepening: 32, modeling: 80, rendering: 48, postProduction: 16 }
  84. },
  85. {
  86. type: '工装写字楼',
  87. standardHours: { requirementDeepening: 40, modeling: 120, rendering: 72, postProduction: 24 }
  88. }
  89. ];
  90. // 新增:逾期项目列表
  91. overdueProjects = signal<OverdueProject[]>([
  92. { id: 'P001', name: '现代简约三居室', stage: '渲染', overdueDays: 2, reason: '客户需求变更', designerName: '张设计' },
  93. { id: 'P002', name: '商业办公空间', stage: '建模', overdueDays: 5, reason: '资源不足', designerName: '李设计' },
  94. { id: 'P003', name: '北欧风格两居室', stage: '后期', overdueDays: 1, reason: '客户需求变更', designerName: '王设计' }
  95. ]);
  96. // 新增:设计师效率统计
  97. designerEfficiencies = signal<DesignerEfficiency[]>([
  98. {
  99. id: 'D001',
  100. name: '张设计',
  101. avgCompletionTime: 18,
  102. overdueProjectCount: 1,
  103. stageUtilization: { requirementDeepening: 85, modeling: 92, rendering: 78, postProduction: 88 },
  104. idleDays: 3,
  105. suggestion: '表现优秀,可适当增加项目难度'
  106. },
  107. {
  108. id: 'D002',
  109. name: '李设计',
  110. avgCompletionTime: 25,
  111. overdueProjectCount: 2,
  112. stageUtilization: { requirementDeepening: 70, modeling: 65, rendering: 72, postProduction: 80 },
  113. idleDays: 8,
  114. suggestion: '近30天闲置8天,建议分配更多项目'
  115. },
  116. {
  117. id: 'D003',
  118. name: '王设计',
  119. avgCompletionTime: 15,
  120. overdueProjectCount: 0,
  121. stageUtilization: { requirementDeepening: 95, modeling: 90, rendering: 93, postProduction: 92 },
  122. idleDays: 1,
  123. suggestion: '高效能设计师,建议承接重点项目'
  124. },
  125. {
  126. id: 'D004',
  127. name: '赵设计',
  128. avgCompletionTime: 22,
  129. overdueProjectCount: 1,
  130. stageUtilization: { requirementDeepening: 80, modeling: 75, rendering: 70, postProduction: 85 },
  131. idleDays: 10,
  132. suggestion: '需要提升渲染阶段效率,建议参加培训'
  133. }
  134. ]);
  135. // 时间筛选维度
  136. timeDimension = signal<'week' | 'month' | 'quarter'>('month');
  137. // 显示悬浮按钮
  138. showFloatingButton = signal(true);
  139. // 当前激活的导航项
  140. activeNavItem = signal<string>('');
  141. constructor(private authService: AuthService, private workHourService: WorkHourService, private router: Router) {}
  142. ngOnInit(): void {
  143. // 初始化用户角色
  144. this.initializeUserRole();
  145. // 初始化设计师效率数据(使用默认的month维度)
  146. this.updateDesignerEfficienciesByDimension('month');
  147. // 加载工时统计数据
  148. this.loadWorkHourData();
  149. }
  150. // 初始化用户角色
  151. initializeUserRole(): void {
  152. // 从AuthService获取用户角色
  153. const roles = this.authService.getUserRoles();
  154. // 默认使用teamLead角色
  155. let userRole = 'teamLead';
  156. // 如果用户有admin角色,也视为teamLead
  157. if (roles.includes('admin')) {
  158. userRole = 'teamLead';
  159. } else if (roles.length > 0) {
  160. // 否则使用第一个角色
  161. userRole = roles[0];
  162. }
  163. this.userRole.set(userRole);
  164. // 根据用户角色过滤待办任务
  165. this.filterTasksByRole();
  166. }
  167. // 根据用户角色过滤待办任务
  168. filterTasksByRole(): void {
  169. if (this.userRole() !== 'teamLead') {
  170. // 初级组员只显示非财务审批类任务
  171. const filteredTasks = this.todoTasks().filter(task =>
  172. task.title !== '临时收款待核定' && task.title !== '报价待审核'
  173. );
  174. this.todoTasks.set(filteredTasks);
  175. }
  176. }
  177. // 检查用户角色
  178. checkUserRole(requiredRole: string): boolean {
  179. return this.authService.hasRole(requiredRole);
  180. }
  181. // 处理待办任务点击
  182. handleTaskClick(task: any) {
  183. // 检查财务相关任务的权限
  184. if ((task.title === '临时收款待核定' || task.title === '报价待审核') && !this.checkUserRole('teamLead')) {
  185. alert('⚠️ 权限不足\n\n只有组长及以上权限可以处理此任务\n\n请联系管理员申请权限');
  186. return;
  187. }
  188. // 显示任务详情
  189. const taskDetails = `📋 待办任务详情\n\n任务:${task.title}\n优先级:${task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低'}\n截止时间:${task.time}\n\n是否立即处理此任务?`;
  190. if (confirm(taskDetails)) {
  191. // 根据任务来源跳转到对应页面
  192. switch(task.source) {
  193. case 'reconciliation':
  194. alert('🔄 正在跳转到对账管理页面...');
  195. // window.location.href = '/finance/reconciliation';
  196. break;
  197. case 'project-records':
  198. alert('🔄 正在跳转到项目记录页面...');
  199. // window.location.href = '/finance/project-records';
  200. break;
  201. default:
  202. alert(`📌 任务已标记\n\n任务"${task.title}"已加入处理队列`);
  203. }
  204. }
  205. }
  206. // 处理快捷操作点击
  207. handleQuickAction(action: string) {
  208. // 检查权限
  209. if ((action === 'recordPayment' || action === 'generateReport') && !this.checkUserRole('teamLead')) {
  210. alert('⚠️ 权限不足\n\n只有组长及以上权限可以执行此操作\n\n请联系管理员申请权限');
  211. return;
  212. }
  213. // 设置当前激活的导航项
  214. this.activeNavItem.set(action);
  215. switch(action) {
  216. case 'newQuote':
  217. this.showNewQuoteDialog();
  218. break;
  219. case 'recordPayment':
  220. this.showRecordPaymentDialog();
  221. break;
  222. case 'generateReport':
  223. this.showGenerateReportDialog();
  224. break;
  225. case 'quotationApproval':
  226. const confirmNavigation = confirm('💼 即将跳转到报价审核页面\n\n是否继续?');
  227. if (confirmNavigation) {
  228. this.router.navigate(['/finance/quotation-approval']);
  229. }
  230. break;
  231. }
  232. }
  233. // 显示新建报价对话框
  234. private showNewQuoteDialog(): void {
  235. const projectName = prompt('📝 新建报价\n\n请输入项目名称:', '示例项目');
  236. if (projectName) {
  237. const projectType = prompt('请选择项目类型:\n\n1. 家装-小户型\n2. 家装-大户型\n3. 工装-商铺\n4. 工装-写字楼\n\n请输入数字 1-4:', '1');
  238. if (projectType) {
  239. alert(`✅ 报价创建成功!\n\n项目名称:${projectName}\n项目类型:${this.getProjectTypeText(projectType)}\n\n系统正在为您准备报价模板...`);
  240. console.log('创建报价:', { projectName, projectType });
  241. // window.location.href = '/finance/project-records';
  242. }
  243. }
  244. }
  245. // 显示记录回款对话框
  246. private showRecordPaymentDialog(): void {
  247. const amount = prompt('💰 记录回款\n\n请输入回款金额(元):', '');
  248. if (amount && !isNaN(Number(amount))) {
  249. const paymentMethod = prompt('请选择支付方式:\n\n1. 微信支付\n2. 支付宝\n3. 银行转账\n4. 现金\n\n请输入数字 1-4:', '1');
  250. if (paymentMethod) {
  251. alert(`✅ 回款记录成功!\n\n回款金额:¥${Number(amount).toLocaleString()}\n支付方式:${this.getPaymentMethodText(paymentMethod)}\n记录时间:${new Date().toLocaleString()}\n\n财务数据已更新`);
  252. console.log('记录回款:', { amount, paymentMethod });
  253. // window.location.href = '/finance/reconciliation';
  254. }
  255. }
  256. }
  257. // 显示生成报表对话框
  258. private showGenerateReportDialog(): void {
  259. const reportType = prompt('📊 生成财务报表\n\n请选择报表类型:\n\n1. 月度收入报表\n2. 项目利润分析\n3. 回款统计报表\n4. 成本费用报表\n\n请输入数字 1-4:', '1');
  260. if (reportType) {
  261. const period = prompt('请选择时间周期:\n\n1. 本月\n2. 上月\n3. 本季度\n4. 本年度\n\n请输入数字 1-4:', '1');
  262. if (period) {
  263. alert(`✅ 报表生成成功!\n\n报表类型:${this.getReportTypeText(reportType)}\n时间周期:${this.getPeriodText(period)}\n生成时间:${new Date().toLocaleString()}\n\n报表正在下载,请稍候...`);
  264. console.log('生成报表:', { reportType, period });
  265. // window.location.href = '/finance/reports';
  266. }
  267. }
  268. }
  269. // 辅助方法:获取项目类型文本
  270. private getProjectTypeText(type: string): string {
  271. const types: Record<string, string> = {
  272. '1': '家装-小户型',
  273. '2': '家装-大户型',
  274. '3': '工装-商铺',
  275. '4': '工装-写字楼'
  276. };
  277. return types[type] || '未知类型';
  278. }
  279. // 辅助方法:获取支付方式文本
  280. private getPaymentMethodText(method: string): string {
  281. const methods: Record<string, string> = {
  282. '1': '微信支付',
  283. '2': '支付宝',
  284. '3': '银行转账',
  285. '4': '现金'
  286. };
  287. return methods[method] || '未知方式';
  288. }
  289. // 辅助方法:获取报表类型文本
  290. private getReportTypeText(type: string): string {
  291. const types: Record<string, string> = {
  292. '1': '月度收入报表',
  293. '2': '项目利润分析',
  294. '3': '回款统计报表',
  295. '4': '成本费用报表'
  296. };
  297. return types[type] || '未知报表';
  298. }
  299. // 辅助方法:获取周期文本
  300. private getPeriodText(period: string): string {
  301. const periods: Record<string, string> = {
  302. '1': '本月',
  303. '2': '上月',
  304. '3': '本季度',
  305. '4': '本年度'
  306. };
  307. return periods[period] || '未知周期';
  308. }
  309. // 格式化金额显示
  310. formatAmount(amount: number): string {
  311. return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount);
  312. }
  313. // 加载工时统计数据
  314. loadWorkHourData(): void {
  315. this.workHourService.getDashboardData().subscribe(data => {
  316. this.workHourDashboard.set(data);
  317. });
  318. this.workHourService.getMonthlyStats().subscribe(stats => {
  319. this.monthlyStats.set(stats);
  320. });
  321. }
  322. // 切换工时模块显示
  323. toggleWorkHourModule(): void {
  324. this.showWorkHourModule.set(!this.showWorkHourModule());
  325. }
  326. // 获取绩效等级颜色
  327. getPerformanceLevelColor(level: string): string {
  328. const colors: Record<string, string> = {
  329. 'S': '#ff6b6b',
  330. 'A': '#4ecdc4',
  331. 'B': '#45b7d1',
  332. 'C': '#96ceb4'
  333. };
  334. return colors[level] || '#ccc';
  335. }
  336. // 获取绩效等级人数
  337. getPerformanceLevelCount(level: string): number {
  338. const dashboard = this.workHourDashboard();
  339. if (!dashboard) return 0;
  340. const distribution = dashboard.performanceDistribution as Record<string, number>;
  341. return distribution[level] || 0;
  342. }
  343. // 获取绩效等级描述
  344. getPerformanceLevelDescription(level: string): string {
  345. const descriptions: Record<string, string> = {
  346. 'S': '卓越表现',
  347. 'A': '优秀表现',
  348. 'B': '良好表现',
  349. 'C': '待提升'
  350. };
  351. return descriptions[level] || '未知';
  352. }
  353. // 格式化工时显示
  354. formatWorkHours(hours: number): string {
  355. const days = Math.floor(hours / 8);
  356. const remainingHours = hours % 8;
  357. if (days > 0 && remainingHours > 0) {
  358. return `${days}天${remainingHours}小时`;
  359. } else if (days > 0) {
  360. return `${days}天`;
  361. } else {
  362. return `${remainingHours}小时`;
  363. }
  364. }
  365. // 新增:获取标准工时
  366. getStandardHours(complexityType: string): ProjectComplexity | undefined {
  367. return this.projectComplexities.find(c => c.type === complexityType);
  368. }
  369. // 新增:获取逾期预警样式类
  370. getOverdueClass(days: number): string {
  371. if (days >= 5) return 'severe';
  372. if (days >= 3) return 'warning';
  373. return 'mild';
  374. }
  375. // 新增:切换时间维度
  376. setTimeDimension(dimension: 'week' | 'month' | 'quarter'): void {
  377. this.timeDimension.set(dimension);
  378. // 根据不同维度更新设计师效率数据
  379. this.updateDesignerEfficienciesByDimension(dimension);
  380. // 重新加载工时数据
  381. this.loadWorkHourData();
  382. }
  383. // 根据时间维度更新设计师效率数据
  384. private updateDesignerEfficienciesByDimension(dimension: 'week' | 'month' | 'quarter'): void {
  385. const baseData: DesignerEfficiency[] = [
  386. {
  387. id: 'D001',
  388. name: '张设计',
  389. avgCompletionTime: dimension === 'week' ? 15 : dimension === 'month' ? 18 : 20,
  390. overdueProjectCount: dimension === 'week' ? 0 : dimension === 'month' ? 1 : 2,
  391. stageUtilization: {
  392. requirementDeepening: dimension === 'week' ? 90 : dimension === 'month' ? 85 : 80,
  393. modeling: dimension === 'week' ? 95 : dimension === 'month' ? 92 : 88,
  394. rendering: dimension === 'week' ? 82 : dimension === 'month' ? 78 : 75,
  395. postProduction: dimension === 'week' ? 92 : dimension === 'month' ? 88 : 85
  396. },
  397. idleDays: dimension === 'week' ? 1 : dimension === 'month' ? 3 : 5,
  398. suggestion: dimension === 'week' ? '本周表现优秀,可适当增加项目难度' : dimension === 'month' ? '表现优秀,可适当增加项目难度' : '本季度整体表现良好,建议继续保持'
  399. },
  400. {
  401. id: 'D002',
  402. name: '李设计',
  403. avgCompletionTime: dimension === 'week' ? 22 : dimension === 'month' ? 25 : 28,
  404. overdueProjectCount: dimension === 'week' ? 1 : dimension === 'month' ? 2 : 4,
  405. stageUtilization: {
  406. requirementDeepening: dimension === 'week' ? 75 : dimension === 'month' ? 70 : 65,
  407. modeling: dimension === 'week' ? 70 : dimension === 'month' ? 65 : 60,
  408. rendering: dimension === 'week' ? 76 : dimension === 'month' ? 72 : 68,
  409. postProduction: dimension === 'week' ? 82 : dimension === 'month' ? 80 : 75
  410. },
  411. idleDays: dimension === 'week' ? 2 : dimension === 'month' ? 8 : 15,
  412. suggestion: dimension === 'week' ? '本周闲置2天,建议分配更多项目' : dimension === 'month' ? '近30天闲置8天,建议分配更多项目' : '本季度闲置时间较长,需要加强项目分配'
  413. },
  414. {
  415. id: 'D003',
  416. name: '王设计',
  417. avgCompletionTime: dimension === 'week' ? 12 : dimension === 'month' ? 15 : 17,
  418. overdueProjectCount: 0,
  419. stageUtilization: {
  420. requirementDeepening: dimension === 'week' ? 98 : dimension === 'month' ? 95 : 92,
  421. modeling: dimension === 'week' ? 95 : dimension === 'month' ? 90 : 88,
  422. rendering: dimension === 'week' ? 96 : dimension === 'month' ? 93 : 90,
  423. postProduction: dimension === 'week' ? 94 : dimension === 'month' ? 92 : 89
  424. },
  425. idleDays: dimension === 'week' ? 0 : dimension === 'month' ? 1 : 2,
  426. suggestion: dimension === 'week' ? '本周表现卓越,建议承接重点项目' : dimension === 'month' ? '高效能设计师,建议承接重点项目' : '本季度持续高效,可考虑带教新人'
  427. },
  428. {
  429. id: 'D004',
  430. name: '赵设计',
  431. avgCompletionTime: dimension === 'week' ? 20 : dimension === 'month' ? 22 : 24,
  432. overdueProjectCount: dimension === 'week' ? 0 : dimension === 'month' ? 1 : 3,
  433. stageUtilization: {
  434. requirementDeepening: dimension === 'week' ? 85 : dimension === 'month' ? 80 : 75,
  435. modeling: dimension === 'week' ? 78 : dimension === 'month' ? 75 : 70,
  436. rendering: dimension === 'week' ? 74 : dimension === 'month' ? 70 : 65,
  437. postProduction: dimension === 'week' ? 88 : dimension === 'month' ? 85 : 80
  438. },
  439. idleDays: dimension === 'week' ? 3 : dimension === 'month' ? 10 : 20,
  440. suggestion: dimension === 'week' ? '本周渲染阶段需提升,建议参加培训' : dimension === 'month' ? '需要提升渲染阶段效率,建议参加培训' : '本季度渲染效率偏低,需要重点关注和培训'
  441. }
  442. ];
  443. this.designerEfficiencies.set(baseData);
  444. }
  445. // 新增:获取时间维度文本
  446. getTimeDimensionText(): string {
  447. const dimension = this.timeDimension();
  448. const texts = {
  449. week: '本周',
  450. month: '本月',
  451. quarter: '本季度'
  452. };
  453. return texts[dimension];
  454. }
  455. // 新增:导出逾期分析报表
  456. exportOverdueReport(): void {
  457. const dimension = this.getTimeDimensionText();
  458. const projects = this.overdueProjects();
  459. const overdueCount = projects.length;
  460. const totalOverdueDays = projects.reduce((sum: number, p: OverdueProject) => sum + p.overdueDays, 0);
  461. const avgOverdueDays = overdueCount > 0 ? (totalOverdueDays / overdueCount).toFixed(1) : '0';
  462. // 生成CSV内容
  463. let csvContent = '\uFEFF'; // UTF-8 BOM for Excel compatibility
  464. csvContent += '逾期项目分析报表\n\n';
  465. csvContent += `时间维度,${dimension}\n`;
  466. csvContent += `统计时间,${new Date().toLocaleDateString()}\n`;
  467. csvContent += `逾期项目数,${overdueCount}\n`;
  468. csvContent += `总逾期天数,${totalOverdueDays}\n`;
  469. csvContent += `平均逾期天数,${avgOverdueDays}\n\n`;
  470. csvContent += '项目ID,项目名称,当前阶段,逾期天数,逾期原因,负责设计师\n';
  471. projects.forEach((p: OverdueProject) => {
  472. csvContent += `${p.id},"${p.name}",${p.stage},${p.overdueDays},"${p.reason}",${p.designerName}\n`;
  473. });
  474. // 创建Blob并下载
  475. const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  476. const link = document.createElement('a');
  477. const url = URL.createObjectURL(blob);
  478. link.setAttribute('href', url);
  479. link.setAttribute('download', `逾期项目分析报表_${dimension}_${new Date().toISOString().split('T')[0]}.csv`);
  480. link.style.visibility = 'hidden';
  481. document.body.appendChild(link);
  482. link.click();
  483. document.body.removeChild(link);
  484. URL.revokeObjectURL(url);
  485. alert(`✅ 报表导出成功!\n\n时间维度:${dimension}\n逾期项目数:${overdueCount}个\n平均逾期天数:${avgOverdueDays}天\n\n报表已下载到您的下载文件夹。`);
  486. }
  487. // 新增:查看项目详情
  488. viewProjectDetail(projectId: string): void {
  489. this.router.navigate(['/designer/project-detail', projectId], {
  490. queryParams: {
  491. role: 'customer-service',
  492. activeTab: 'progress',
  493. currentStage: '客户评价',
  494. section: 'aftercare',
  495. view: 'project-review-only'
  496. }
  497. });
  498. }
  499. // 新增:查看设计师详情
  500. viewDesignerDetail(designerId: string): void {
  501. const designers = this.designerEfficiencies();
  502. const designer = designers.find((d: DesignerEfficiency) => d.id === designerId);
  503. if (!designer) return;
  504. // 将对象转换为可读的利用率信息
  505. const utilizationDetails = [
  506. ` 需求深化:${designer.stageUtilization.requirementDeepening}%`,
  507. ` 建模阶段:${designer.stageUtilization.modeling}%`,
  508. ` 渲染阶段:${designer.stageUtilization.rendering}%`,
  509. ` 后期处理:${designer.stageUtilization.postProduction}%`
  510. ].join('\n');
  511. // 计算效率等级
  512. const avgUtilization = (
  513. designer.stageUtilization.requirementDeepening +
  514. designer.stageUtilization.modeling +
  515. designer.stageUtilization.rendering +
  516. designer.stageUtilization.postProduction
  517. ) / 4;
  518. const efficiencyLevel = avgUtilization >= 90 ? '优秀' :
  519. avgUtilization >= 75 ? '良好' :
  520. avgUtilization >= 60 ? '中等' : '待提升';
  521. alert(`👨‍💼 设计师效率详情
  522. 姓名:${designer.name}
  523. 效率等级:${efficiencyLevel} (平均利用率: ${avgUtilization.toFixed(1)}%)
  524. 平均完成时长:${designer.avgCompletionTime}天
  525. 当前闲置天数:${designer.idleDays}天
  526. 逾期项目数:${designer.overdueProjectCount}个
  527. 各阶段工时利用率:
  528. ${utilizationDetails}
  529. ${designer.suggestion}
  530. 点击确定查看更多详情...`);
  531. console.log('查看设计师详情:', designer);
  532. // TODO: 跳转到设计师详情页
  533. }
  534. // 新增:跳转到售后复盘(悬浮按钮)
  535. goToAftercare(): void {
  536. // 跳转到指定的项目复盘页面
  537. const projectId = 'mock-1';
  538. this.router.navigate(['/designer/project-detail', projectId], {
  539. queryParams: {
  540. role: 'customer-service',
  541. activeTab: 'progress',
  542. currentStage: '客户评价',
  543. section: 'aftercare', // 定位到售后板块
  544. view: 'project-review-only' // 只显示项目复盘内容
  545. }
  546. });
  547. }
  548. // 新增:获取效率等级颜色
  549. getEfficiencyLevelColor(avgTime: number): string {
  550. if (avgTime <= 15) return '#34c759'; // 优秀 - 绿色
  551. if (avgTime <= 20) return '#30b0c7'; // 良好 - 青色
  552. if (avgTime <= 25) return '#ff9500'; // 一般 - 橙色
  553. return '#ff3b30'; // 需提升 - 红色
  554. }
  555. // 新增:获取阶段利用率颜色
  556. getUtilizationColor(percentage: number): string {
  557. if (percentage >= 90) return '#34c759';
  558. if (percentage >= 75) return '#30b0c7';
  559. if (percentage >= 60) return '#ff9500';
  560. return '#ff3b30';
  561. }
  562. }