import { Component, OnInit, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule, Router } from '@angular/router'; import { AuthService } from '../../../services/auth.service'; import { WorkHourService } from '../../../services/work-hour.service'; import { WorkHourDashboardData, MonthlyWorkHourStats, PerformanceLevel } from '../../../models/work-hour.model'; // 项目复杂度类型 interface ProjectComplexity { type: string; // '家装小户型' | '家装大户型' | '工装商铺' | '工装写字楼' standardHours: { requirementDeepening: number; modeling: number; rendering: number; postProduction: number; }; } // 逾期项目信息 interface OverdueProject { id: string; name: string; stage: string; overdueDays: number; reason: string; designerName: string; } // 设计师效率统计 interface DesignerEfficiency { id: string; name: string; avgCompletionTime: number; // 天 overdueProjectCount: number; stageUtilization: { requirementDeepening: number; // 百分比 modeling: number; rendering: number; postProduction: number; }; idleDays: number; // 闲置天数 suggestion: string; } @Component({ selector: 'app-dashboard', imports: [CommonModule, RouterModule], templateUrl: './dashboard.html', styleUrl: './dashboard.scss' }) export class Dashboard implements OnInit { // 添加today属性用于模板中的日期显示 today = new Date(); // 待办任务数据 todoTasks = signal([ { id: 1, title: '临时收款待核定', priority: 'high', source: 'reconciliation', dueTime: '10:30' }, { id: 2, title: '报价待审核', priority: 'medium', source: 'project-records', dueTime: '14:00' }, { id: 3, title: '素材成本待录入', priority: 'low', source: 'reconciliation', dueTime: '16:30' }, { id: 4, title: '临时收款待核定', priority: 'high', source: 'reconciliation', dueTime: '11:00' }, { id: 5, title: '销售监管提醒', priority: 'high', source: 'project-records', dueTime: '09:30' } ]); // 数据概览数据 dashboardStats = signal({ todayOrders: 12, pendingPayment: 156800, quotedProjects: 28, materialCostSaved: 8900 }); // 用户角色 userRole = signal('teamLead'); // teamLead 或 juniorMember // 工时统计数据 workHourDashboard = signal(null); monthlyStats = signal([]); showWorkHourModule = signal(false); // 新增:项目复杂度预设标准工时 projectComplexities: ProjectComplexity[] = [ { type: '家装小户型', standardHours: { requirementDeepening: 16, modeling: 40, rendering: 24, postProduction: 8 } }, { type: '家装大户型', standardHours: { requirementDeepening: 24, modeling: 64, rendering: 40, postProduction: 16 } }, { type: '工装商铺', standardHours: { requirementDeepening: 32, modeling: 80, rendering: 48, postProduction: 16 } }, { type: '工装写字楼', standardHours: { requirementDeepening: 40, modeling: 120, rendering: 72, postProduction: 24 } } ]; // 新增:逾期项目列表 overdueProjects = signal([ { id: 'P001', name: '现代简约三居室', stage: '渲染', overdueDays: 2, reason: '客户需求变更', designerName: '张设计' }, { id: 'P002', name: '商业办公空间', stage: '建模', overdueDays: 5, reason: '资源不足', designerName: '李设计' }, { id: 'P003', name: '北欧风格两居室', stage: '后期', overdueDays: 1, reason: '客户需求变更', designerName: '王设计' } ]); // 新增:设计师效率统计 designerEfficiencies = signal([ { id: 'D001', name: '张设计', avgCompletionTime: 18, overdueProjectCount: 1, stageUtilization: { requirementDeepening: 85, modeling: 92, rendering: 78, postProduction: 88 }, idleDays: 3, suggestion: '表现优秀,可适当增加项目难度' }, { id: 'D002', name: '李设计', avgCompletionTime: 25, overdueProjectCount: 2, stageUtilization: { requirementDeepening: 70, modeling: 65, rendering: 72, postProduction: 80 }, idleDays: 8, suggestion: '近30天闲置8天,建议分配更多项目' }, { id: 'D003', name: '王设计', avgCompletionTime: 15, overdueProjectCount: 0, stageUtilization: { requirementDeepening: 95, modeling: 90, rendering: 93, postProduction: 92 }, idleDays: 1, suggestion: '高效能设计师,建议承接重点项目' }, { id: 'D004', name: '赵设计', avgCompletionTime: 22, overdueProjectCount: 1, stageUtilization: { requirementDeepening: 80, modeling: 75, rendering: 70, postProduction: 85 }, idleDays: 10, suggestion: '需要提升渲染阶段效率,建议参加培训' } ]); // 时间筛选维度 timeDimension = signal<'week' | 'month' | 'quarter'>('month'); // 显示悬浮按钮 showFloatingButton = signal(true); // 当前激活的导航项 activeNavItem = signal(''); constructor(private authService: AuthService, private workHourService: WorkHourService, private router: Router) {} ngOnInit(): void { // 初始化用户角色 this.initializeUserRole(); // 初始化设计师效率数据(使用默认的month维度) this.updateDesignerEfficienciesByDimension('month'); // 加载工时统计数据 this.loadWorkHourData(); } // 初始化用户角色 initializeUserRole(): void { // 从AuthService获取用户角色 const roles = this.authService.getUserRoles(); // 默认使用teamLead角色 let userRole = 'teamLead'; // 如果用户有admin角色,也视为teamLead if (roles.includes('admin')) { userRole = 'teamLead'; } else if (roles.length > 0) { // 否则使用第一个角色 userRole = roles[0]; } this.userRole.set(userRole); // 根据用户角色过滤待办任务 this.filterTasksByRole(); } // 根据用户角色过滤待办任务 filterTasksByRole(): void { if (this.userRole() !== 'teamLead') { // 初级组员只显示非财务审批类任务 const filteredTasks = this.todoTasks().filter(task => task.title !== '临时收款待核定' && task.title !== '报价待审核' ); this.todoTasks.set(filteredTasks); } } // 检查用户角色 checkUserRole(requiredRole: string): boolean { return this.authService.hasRole(requiredRole); } // 处理待办任务点击 handleTaskClick(task: any) { // 检查财务相关任务的权限 if ((task.title === '临时收款待核定' || task.title === '报价待审核') && !this.checkUserRole('teamLead')) { alert('⚠️ 权限不足\n\n只有组长及以上权限可以处理此任务\n\n请联系管理员申请权限'); return; } // 显示任务详情 const taskDetails = `📋 待办任务详情\n\n任务:${task.title}\n优先级:${task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低'}\n截止时间:${task.time}\n\n是否立即处理此任务?`; if (confirm(taskDetails)) { // 根据任务来源跳转到对应页面 switch(task.source) { case 'reconciliation': alert('🔄 正在跳转到对账管理页面...'); // window.location.href = '/finance/reconciliation'; break; case 'project-records': alert('🔄 正在跳转到项目记录页面...'); // window.location.href = '/finance/project-records'; break; default: alert(`📌 任务已标记\n\n任务"${task.title}"已加入处理队列`); } } } // 处理快捷操作点击 handleQuickAction(action: string) { // 检查权限 if ((action === 'recordPayment' || action === 'generateReport') && !this.checkUserRole('teamLead')) { alert('⚠️ 权限不足\n\n只有组长及以上权限可以执行此操作\n\n请联系管理员申请权限'); return; } // 设置当前激活的导航项 this.activeNavItem.set(action); switch(action) { case 'newQuote': this.showNewQuoteDialog(); break; case 'recordPayment': this.showRecordPaymentDialog(); break; case 'generateReport': this.showGenerateReportDialog(); break; case 'quotationApproval': const confirmNavigation = confirm('💼 即将跳转到报价审核页面\n\n是否继续?'); if (confirmNavigation) { this.router.navigate(['/finance/quotation-approval']); } break; } } // 显示新建报价对话框 private showNewQuoteDialog(): void { const projectName = prompt('📝 新建报价\n\n请输入项目名称:', '示例项目'); if (projectName) { const projectType = prompt('请选择项目类型:\n\n1. 家装-小户型\n2. 家装-大户型\n3. 工装-商铺\n4. 工装-写字楼\n\n请输入数字 1-4:', '1'); if (projectType) { alert(`✅ 报价创建成功!\n\n项目名称:${projectName}\n项目类型:${this.getProjectTypeText(projectType)}\n\n系统正在为您准备报价模板...`); console.log('创建报价:', { projectName, projectType }); // window.location.href = '/finance/project-records'; } } } // 显示记录回款对话框 private showRecordPaymentDialog(): void { const amount = prompt('💰 记录回款\n\n请输入回款金额(元):', ''); if (amount && !isNaN(Number(amount))) { const paymentMethod = prompt('请选择支付方式:\n\n1. 微信支付\n2. 支付宝\n3. 银行转账\n4. 现金\n\n请输入数字 1-4:', '1'); if (paymentMethod) { alert(`✅ 回款记录成功!\n\n回款金额:¥${Number(amount).toLocaleString()}\n支付方式:${this.getPaymentMethodText(paymentMethod)}\n记录时间:${new Date().toLocaleString()}\n\n财务数据已更新`); console.log('记录回款:', { amount, paymentMethod }); // window.location.href = '/finance/reconciliation'; } } } // 显示生成报表对话框 private showGenerateReportDialog(): void { const reportType = prompt('📊 生成财务报表\n\n请选择报表类型:\n\n1. 月度收入报表\n2. 项目利润分析\n3. 回款统计报表\n4. 成本费用报表\n\n请输入数字 1-4:', '1'); if (reportType) { const period = prompt('请选择时间周期:\n\n1. 本月\n2. 上月\n3. 本季度\n4. 本年度\n\n请输入数字 1-4:', '1'); if (period) { alert(`✅ 报表生成成功!\n\n报表类型:${this.getReportTypeText(reportType)}\n时间周期:${this.getPeriodText(period)}\n生成时间:${new Date().toLocaleString()}\n\n报表正在下载,请稍候...`); console.log('生成报表:', { reportType, period }); // window.location.href = '/finance/reports'; } } } // 辅助方法:获取项目类型文本 private getProjectTypeText(type: string): string { const types: Record = { '1': '家装-小户型', '2': '家装-大户型', '3': '工装-商铺', '4': '工装-写字楼' }; return types[type] || '未知类型'; } // 辅助方法:获取支付方式文本 private getPaymentMethodText(method: string): string { const methods: Record = { '1': '微信支付', '2': '支付宝', '3': '银行转账', '4': '现金' }; return methods[method] || '未知方式'; } // 辅助方法:获取报表类型文本 private getReportTypeText(type: string): string { const types: Record = { '1': '月度收入报表', '2': '项目利润分析', '3': '回款统计报表', '4': '成本费用报表' }; return types[type] || '未知报表'; } // 辅助方法:获取周期文本 private getPeriodText(period: string): string { const periods: Record = { '1': '本月', '2': '上月', '3': '本季度', '4': '本年度' }; return periods[period] || '未知周期'; } // 格式化金额显示 formatAmount(amount: number): string { return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(amount); } // 加载工时统计数据 loadWorkHourData(): void { this.workHourService.getDashboardData().subscribe(data => { this.workHourDashboard.set(data); }); this.workHourService.getMonthlyStats().subscribe(stats => { this.monthlyStats.set(stats); }); } // 切换工时模块显示 toggleWorkHourModule(): void { this.showWorkHourModule.set(!this.showWorkHourModule()); } // 获取绩效等级颜色 getPerformanceLevelColor(level: string): string { const colors: Record = { 'S': '#ff6b6b', 'A': '#4ecdc4', 'B': '#45b7d1', 'C': '#96ceb4' }; return colors[level] || '#ccc'; } // 获取绩效等级人数 getPerformanceLevelCount(level: string): number { const dashboard = this.workHourDashboard(); if (!dashboard) return 0; const distribution = dashboard.performanceDistribution as Record; return distribution[level] || 0; } // 获取绩效等级描述 getPerformanceLevelDescription(level: string): string { const descriptions: Record = { 'S': '卓越表现', 'A': '优秀表现', 'B': '良好表现', 'C': '待提升' }; return descriptions[level] || '未知'; } // 格式化工时显示 formatWorkHours(hours: number): string { const days = Math.floor(hours / 8); const remainingHours = hours % 8; if (days > 0 && remainingHours > 0) { return `${days}天${remainingHours}小时`; } else if (days > 0) { return `${days}天`; } else { return `${remainingHours}小时`; } } // 新增:获取标准工时 getStandardHours(complexityType: string): ProjectComplexity | undefined { return this.projectComplexities.find(c => c.type === complexityType); } // 新增:获取逾期预警样式类 getOverdueClass(days: number): string { if (days >= 5) return 'severe'; if (days >= 3) return 'warning'; return 'mild'; } // 新增:切换时间维度 setTimeDimension(dimension: 'week' | 'month' | 'quarter'): void { this.timeDimension.set(dimension); // 根据不同维度更新设计师效率数据 this.updateDesignerEfficienciesByDimension(dimension); // 重新加载工时数据 this.loadWorkHourData(); } // 根据时间维度更新设计师效率数据 private updateDesignerEfficienciesByDimension(dimension: 'week' | 'month' | 'quarter'): void { const baseData: DesignerEfficiency[] = [ { id: 'D001', name: '张设计', avgCompletionTime: dimension === 'week' ? 15 : dimension === 'month' ? 18 : 20, overdueProjectCount: dimension === 'week' ? 0 : dimension === 'month' ? 1 : 2, stageUtilization: { requirementDeepening: dimension === 'week' ? 90 : dimension === 'month' ? 85 : 80, modeling: dimension === 'week' ? 95 : dimension === 'month' ? 92 : 88, rendering: dimension === 'week' ? 82 : dimension === 'month' ? 78 : 75, postProduction: dimension === 'week' ? 92 : dimension === 'month' ? 88 : 85 }, idleDays: dimension === 'week' ? 1 : dimension === 'month' ? 3 : 5, suggestion: dimension === 'week' ? '本周表现优秀,可适当增加项目难度' : dimension === 'month' ? '表现优秀,可适当增加项目难度' : '本季度整体表现良好,建议继续保持' }, { id: 'D002', name: '李设计', avgCompletionTime: dimension === 'week' ? 22 : dimension === 'month' ? 25 : 28, overdueProjectCount: dimension === 'week' ? 1 : dimension === 'month' ? 2 : 4, stageUtilization: { requirementDeepening: dimension === 'week' ? 75 : dimension === 'month' ? 70 : 65, modeling: dimension === 'week' ? 70 : dimension === 'month' ? 65 : 60, rendering: dimension === 'week' ? 76 : dimension === 'month' ? 72 : 68, postProduction: dimension === 'week' ? 82 : dimension === 'month' ? 80 : 75 }, idleDays: dimension === 'week' ? 2 : dimension === 'month' ? 8 : 15, suggestion: dimension === 'week' ? '本周闲置2天,建议分配更多项目' : dimension === 'month' ? '近30天闲置8天,建议分配更多项目' : '本季度闲置时间较长,需要加强项目分配' }, { id: 'D003', name: '王设计', avgCompletionTime: dimension === 'week' ? 12 : dimension === 'month' ? 15 : 17, overdueProjectCount: 0, stageUtilization: { requirementDeepening: dimension === 'week' ? 98 : dimension === 'month' ? 95 : 92, modeling: dimension === 'week' ? 95 : dimension === 'month' ? 90 : 88, rendering: dimension === 'week' ? 96 : dimension === 'month' ? 93 : 90, postProduction: dimension === 'week' ? 94 : dimension === 'month' ? 92 : 89 }, idleDays: dimension === 'week' ? 0 : dimension === 'month' ? 1 : 2, suggestion: dimension === 'week' ? '本周表现卓越,建议承接重点项目' : dimension === 'month' ? '高效能设计师,建议承接重点项目' : '本季度持续高效,可考虑带教新人' }, { id: 'D004', name: '赵设计', avgCompletionTime: dimension === 'week' ? 20 : dimension === 'month' ? 22 : 24, overdueProjectCount: dimension === 'week' ? 0 : dimension === 'month' ? 1 : 3, stageUtilization: { requirementDeepening: dimension === 'week' ? 85 : dimension === 'month' ? 80 : 75, modeling: dimension === 'week' ? 78 : dimension === 'month' ? 75 : 70, rendering: dimension === 'week' ? 74 : dimension === 'month' ? 70 : 65, postProduction: dimension === 'week' ? 88 : dimension === 'month' ? 85 : 80 }, idleDays: dimension === 'week' ? 3 : dimension === 'month' ? 10 : 20, suggestion: dimension === 'week' ? '本周渲染阶段需提升,建议参加培训' : dimension === 'month' ? '需要提升渲染阶段效率,建议参加培训' : '本季度渲染效率偏低,需要重点关注和培训' } ]; this.designerEfficiencies.set(baseData); } // 新增:获取时间维度文本 getTimeDimensionText(): string { const dimension = this.timeDimension(); const texts = { week: '本周', month: '本月', quarter: '本季度' }; return texts[dimension]; } // 新增:导出逾期分析报表 exportOverdueReport(): void { const dimension = this.getTimeDimensionText(); const projects = this.overdueProjects(); const overdueCount = projects.length; const totalOverdueDays = projects.reduce((sum: number, p: OverdueProject) => sum + p.overdueDays, 0); const avgOverdueDays = overdueCount > 0 ? (totalOverdueDays / overdueCount).toFixed(1) : '0'; // 生成CSV内容 let csvContent = '\uFEFF'; // UTF-8 BOM for Excel compatibility csvContent += '逾期项目分析报表\n\n'; csvContent += `时间维度,${dimension}\n`; csvContent += `统计时间,${new Date().toLocaleDateString()}\n`; csvContent += `逾期项目数,${overdueCount}\n`; csvContent += `总逾期天数,${totalOverdueDays}\n`; csvContent += `平均逾期天数,${avgOverdueDays}\n\n`; csvContent += '项目ID,项目名称,当前阶段,逾期天数,逾期原因,负责设计师\n'; projects.forEach((p: OverdueProject) => { csvContent += `${p.id},"${p.name}",${p.stage},${p.overdueDays},"${p.reason}",${p.designerName}\n`; }); // 创建Blob并下载 const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', `逾期项目分析报表_${dimension}_${new Date().toISOString().split('T')[0]}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); alert(`✅ 报表导出成功!\n\n时间维度:${dimension}\n逾期项目数:${overdueCount}个\n平均逾期天数:${avgOverdueDays}天\n\n报表已下载到您的下载文件夹。`); } // 新增:查看项目详情 viewProjectDetail(projectId: string): void { this.router.navigate(['/designer/project-detail', projectId], { queryParams: { role: 'customer-service', activeTab: 'progress', currentStage: '客户评价', section: 'aftercare', view: 'project-review-only' } }); } // 新增:查看设计师详情 viewDesignerDetail(designerId: string): void { const designers = this.designerEfficiencies(); const designer = designers.find((d: DesignerEfficiency) => d.id === designerId); if (!designer) return; // 将对象转换为可读的利用率信息 const utilizationDetails = [ ` 需求深化:${designer.stageUtilization.requirementDeepening}%`, ` 建模阶段:${designer.stageUtilization.modeling}%`, ` 渲染阶段:${designer.stageUtilization.rendering}%`, ` 后期处理:${designer.stageUtilization.postProduction}%` ].join('\n'); // 计算效率等级 const avgUtilization = ( designer.stageUtilization.requirementDeepening + designer.stageUtilization.modeling + designer.stageUtilization.rendering + designer.stageUtilization.postProduction ) / 4; const efficiencyLevel = avgUtilization >= 90 ? '优秀' : avgUtilization >= 75 ? '良好' : avgUtilization >= 60 ? '中等' : '待提升'; alert(`👨‍💼 设计师效率详情 姓名:${designer.name} 效率等级:${efficiencyLevel} (平均利用率: ${avgUtilization.toFixed(1)}%) 平均完成时长:${designer.avgCompletionTime}天 当前闲置天数:${designer.idleDays}天 逾期项目数:${designer.overdueProjectCount}个 各阶段工时利用率: ${utilizationDetails} ${designer.suggestion} 点击确定查看更多详情...`); console.log('查看设计师详情:', designer); // TODO: 跳转到设计师详情页 } // 新增:跳转到售后复盘(悬浮按钮) goToAftercare(): void { // 跳转到指定的项目复盘页面 const projectId = 'mock-1'; this.router.navigate(['/designer/project-detail', projectId], { queryParams: { role: 'customer-service', activeTab: 'progress', currentStage: '客户评价', section: 'aftercare', // 定位到售后板块 view: 'project-review-only' // 只显示项目复盘内容 } }); } // 新增:获取效率等级颜色 getEfficiencyLevelColor(avgTime: number): string { if (avgTime <= 15) return '#34c759'; // 优秀 - 绿色 if (avgTime <= 20) return '#30b0c7'; // 良好 - 青色 if (avgTime <= 25) return '#ff9500'; // 一般 - 橙色 return '#ff3b30'; // 需提升 - 红色 } // 新增:获取阶段利用率颜色 getUtilizationColor(percentage: number): string { if (percentage >= 90) return '#34c759'; if (percentage >= 75) return '#30b0c7'; if (percentage >= 60) return '#ff9500'; return '#ff3b30'; } }