import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Router, RouterModule } from '@angular/router'; import { firstValueFrom } from 'rxjs'; import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; import { ProjectService } from '../../../services/project.service'; import { DesignerService } from '../services/designer.service'; import { WxworkAuth } from 'fmode-ng/core'; import { ProjectTimelineComponent } from '../project-timeline'; import { EmployeeDetailPanelComponent } from '../employee-detail-panel'; import { DashboardMetricsComponent } from './components/dashboard-metrics/dashboard-metrics.component'; import { DashboardNavbarComponent } from './components/dashboard-navbar/dashboard-navbar.component'; import { WorkloadGanttComponent } from './components/workload-gantt/workload-gantt.component'; import { TodoSectionComponent } from './components/todo-section/todo-section.component'; import { SmartMatchModalComponent } from './components/smart-match-modal/smart-match-modal.component'; import { StagnationReasonModalComponent } from './components/stagnation-reason-modal/stagnation-reason-modal.component'; import { DashboardFilterBarComponent } from './components/dashboard-filter-bar/dashboard-filter-bar.component'; import { ProjectKanbanComponent } from './components/project-kanban/project-kanban.component'; import { DashboardAlertsComponent } from './components/dashboard-alerts/dashboard-alerts.component'; import type { ProjectTimeline } from '../project-timeline/project-timeline'; import { UrgentEventService } from '../services/urgent-event.service'; import { DesignerWorkloadService } from '../services/designer-workload.service'; import { DashboardNavigationHelper } from '../services/dashboard-navigation.helper'; import { TodoTaskService } from '../services/todo-task.service'; import { DashboardFilterService } from '../services/dashboard-filter.service'; import { PhaseDeadlines, PhaseName } from '../../../models/project-phase.model'; import { PROJECT_STAGES, CORE_PHASES } from './dashboard.constants'; import { ProjectStage, ProjectPhase, Project, TodoTaskFromIssue, UrgentEvent, LeaveRecord, EmployeeDetail, EmployeeCalendarData, EmployeeCalendarDay } from './dashboard.model'; @Component({ selector: 'app-dashboard', standalone: true, imports: [ CommonModule, FormsModule, RouterModule, ProjectTimelineComponent, EmployeeDetailPanelComponent, DashboardMetricsComponent, DashboardNavbarComponent, WorkloadGanttComponent, TodoSectionComponent, SmartMatchModalComponent, StagnationReasonModalComponent, DashboardFilterBarComponent, ProjectKanbanComponent, DashboardAlertsComponent ], templateUrl: './dashboard.html', styleUrl: './dashboard.scss', changeDetection: ChangeDetectionStrategy.OnPush }) export class Dashboard implements OnInit, OnDestroy { // 暴露 Array 给模板使用 Array = Array; projects: Project[] = []; filteredProjects: Project[] = []; urgentPinnedProjects: Project[] = []; showAlert: boolean = false; selectedProjectId: string = ''; // 待办任务数据(交给子组件处理显示) todoTasksFromIssues: TodoTaskFromIssue[] = []; loadingTodoTasks: boolean = false; todoTaskError: string = ''; private todoTaskRefreshTimer: any; // 紧急事件数据(交给子组件处理显示) urgentEvents: UrgentEvent[] = []; loadingUrgentEvents: boolean = false; handledUrgentEventIds: Set = new Set(); mutedUrgentEventIds: Set = new Set(); // 新增:当前用户信息 currentUser = { name: '组长', avatar: "data:image/svg+xml,%3Csvg width='40' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23CCFFCC'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3E组长%3C/text%3E%3C/svg%3E", roleName: '组长' }; currentDate = new Date(); // 真实设计师数据(从fmode-ng获取) realDesigners: any[] = []; // 设计师工作量映射(从 ProjectTeam 表) designerWorkloadMap: Map = new Map(); // designerId/name -> projects[] // 智能推荐相关 showSmartMatch: boolean = false; selectedProject: any = null; recommendations: any[] = []; // 🆕 停滞/改图原因弹窗 showStagnationModal: boolean = false; stagnationModalType: 'stagnation' | 'modification' = 'stagnation'; stagnationModalProject: Project | null = null; // 新增:关键词搜索 searchTerm: string = ''; // 临期项目与筛选状态 selectedType: 'all' | 'soft' | 'hard' = 'all'; selectedUrgency: 'all' | 'high' | 'medium' | 'low' = 'all'; selectedStatus: 'all' | 'progress' | 'completed' | 'overdue' | 'pendingApproval' | 'pendingAssignment' | 'dueSoon' = 'all'; selectedDesigner: string = 'all'; selectedMemberType: 'all' | 'vip' | 'normal' = 'all'; // 新增:时间窗筛选 selectedTimeWindow: 'all' | 'today' | 'threeDays' | 'sevenDays' = 'all'; designers: any[] = []; // 新增:四大板块筛选 selectedCorePhase: 'all' | 'order' | 'requirements' | 'delivery' | 'aftercare' | 'stalled' | 'modification' = 'all'; // 设计师画像(从fmode-ng动态获取,保留此字段作为兼容) designerProfiles: any[] = []; // 10个项目阶段 projectStages = PROJECT_STAGES; // 5大核心阶段(聚合展示) allCorePhases = CORE_PHASES; // 是否显示前期阶段(订单/需求) showPreProductionPhases: boolean = false; get visibleCorePhases(): any[] { if (this.showPreProductionPhases) { return this.allCorePhases; } // 默认隐藏订单和需求阶段 return this.allCorePhases.filter(p => p.id !== 'order' && p.id !== 'requirements'); } // 视图开关 showGanttView: boolean = true; // 个人详情面板相关属性 showEmployeeDetailPanel: boolean = false; selectedEmployeeName: string = ''; selectedEmployeeDetail: any | null = null; selectedEmployeeProjects: any[] = []; // 项目时间轴数据 projectTimelineData: ProjectTimeline[] = []; private timelineDataCache: ProjectTimeline[] = []; private lastDesignerWorkloadMapSize: number = 0; // 员工请假数据 // private leaveRecords: LeaveRecord[] = []; constructor( private projectService: ProjectService, private router: Router, private designerService: DesignerService, private cdr: ChangeDetectorRef, private urgentEventService: UrgentEventService, private designerWorkloadService: DesignerWorkloadService, private navigationHelper: DashboardNavigationHelper, private todoTaskService: TodoTaskService, private dashboardFilterService: DashboardFilterService ) {} async ngOnInit(): Promise { // 新增:加载用户Profile信息 await this.loadUserProfile(); await this.loadProjects(); await this.loadDesigners(); // 加载待办任务(从问题板块) await this.loadTodoTasksFromIssues(); // 🆕 计算紧急事件 this.calculateUrgentEvents(); // 启动自动刷新 this.startAutoRefresh(); } /** * 加载项目列表 */ async loadProjects(): Promise { try { const sourceProjects = await firstValueFrom(this.projectService.getProjects()) as any[]; // 获取项目分配信息(用于修正设计师信息,确保跟甘特图一致) const companyId = localStorage.getItem('company') || 'cDL6R1hgSi'; const assignments = await this.designerWorkloadService.getProjectAssignments(companyId); // 数据转换与增强,确保符合Dashboard模型要求 this.projects = sourceProjects.map(p => { const deadline = p.deadline ? new Date(p.deadline) : new Date(); const now = new Date(); const overdueDays = Math.ceil((now.getTime() - deadline.getTime()) / (1000 * 60 * 60 * 24)); // 🔧 增强时间字段获取逻辑 // 尝试从多种路径获取 createdAt let rawCreatedAt = p.createdAt; if (!rawCreatedAt && typeof p.get === 'function') { rawCreatedAt = p.get('createdAt'); } // 如果还是没有,尝试从 data 字段 if (!rawCreatedAt && p.data && p.data.createdAt) { rawCreatedAt = p.data.createdAt; } const createdAtDate = rawCreatedAt ? new Date(rawCreatedAt) : undefined; // 尝试获取 updatedAt let rawUpdatedAt = p.updatedAt; if (!rawUpdatedAt && typeof p.get === 'function') { rawUpdatedAt = p.get('updatedAt'); } const updatedAtDate = rawUpdatedAt ? new Date(rawUpdatedAt) : undefined; // 简单的类型推断 let type: 'soft' | 'hard' = 'soft'; if (p.customerTags && Array.isArray(p.customerTags)) { if (p.customerTags.some((t: any) => t.needType === '硬装')) { type = 'hard'; } } // 获取设计师分配信息 const projectAssignments = assignments.get(p.id) || []; const designerIds = projectAssignments.length > 0 ? projectAssignments.map(d => d.id) : (p.assigneeId ? [p.assigneeId] : []); const displayDesignerName = projectAssignments.length > 0 ? projectAssignments.map(d => d.name).join(', ') : (p.assigneeName || p.designerName || '未分配').trim(); const primaryDesignerId = designerIds[0] || p.assigneeId || ''; return { // 基础字段映射 id: p.id, name: p.name, status: p.status, currentStage: p.currentStage || '订单分配', createdAt: createdAtDate, // ✅ 使用增强后的 createdAt updatedAt: updatedAtDate, // ✅ 传递 updatedAt deadline: deadline, // 字段名称转换 designerName: displayDesignerName, designerId: primaryDesignerId, designerIds: designerIds, // 补充 Dashboard 模型必需的缺省字段 type: type, memberType: 'normal', urgency: 'medium', phases: [], expectedEndDate: deadline, // 新增字段初始化 isStalled: (p as any).isStalled || false, isModification: (p as any).isModification || false, // 计算字段 isOverdue: p.status !== '已完成' && overdueDays > 0, overdueDays: overdueDays > 0 ? overdueDays : 0, dueSoon: p.status !== '已完成' && overdueDays <= 0 && overdueDays >= -3, searchIndex: `${p.name}|${p.assigneeName || p.designerName || ''}`.toLowerCase(), // 保留原始数据供其他用途 data: p.data, contact: p.contact, customer: p.customerName } as Project; }); this.buildSearchIndexes(); this.applyFilters(); console.log(`✅ 加载项目成功,共 ${this.projects.length} 个`); } catch (error) { console.error('加载项目失败:', error); this.projects = []; } } /** * 从fmode-ng加载真实设计师数据 */ async loadDesigners(): Promise { try { this.realDesigners = await this.designerService.getDesigners(); // 更新设计师列表(用于筛选下拉框) this.designers = this.realDesigners.map(d => ({ id: d.id, name: (d.name || '').trim() })).filter(d => !!d.name); // 同时更新designerProfiles以保持兼容性 this.designerProfiles = this.realDesigners.map(d => ({ id: d.id, name: d.name, skills: d.tags.expertise.styles || [], workload: 0, // 后续动态计算 avgRating: d.tags.history.avgRating || 0, experience: 0 // 暂无此字段 })); // 加载设计师的实际工作量 await this.loadDesignerWorkload(); } catch (error) { console.error('加载设计师数据失败:', error); } } /** * 🔧 从 ProjectTeam 表加载每个设计师的实际工作量 */ async loadDesignerWorkload(): Promise { try { const cid = localStorage.getItem('company') || 'cDL6R1hgSi'; // 使用服务加载工作量 this.designerWorkloadMap = await this.designerWorkloadService.loadWorkload(cid); // 转换为时间轴数据 this.projectTimelineData = this.designerWorkloadService.transformToTimeline(this.designerWorkloadMap); // 更新缓存 this.timelineDataCache = this.projectTimelineData; // 统计信息 logging let totalProjectsInMap = 0; this.designerWorkloadMap.forEach(projects => totalProjectsInMap += projects.length); this.lastDesignerWorkloadMapSize = totalProjectsInMap; console.log(`� 加载完成: ${totalProjectsInMap} 个项目分布在 ${this.designerWorkloadMap.size} 个设计师中`); } catch (error) { console.error('加载设计师工作量失败:', error); } } /** * 将筛选后的项目转换为时间轴数据 * ✅ 修复:正确处理多设计师项目,避免下拉列表出现合并的设计师名字 */ convertToProjectTimeline(): void { if (!this.designerWorkloadService) return; // 将筛选后的项目按设计师分组 const groupedMap = new Map(); this.filteredProjects.forEach(p => { // ✅ 修复:如果 designerName 包含逗号,说明是多个设计师,需要拆分 const designerNames = (p.designerName || '未分配') .split(',') .map(name => name.trim()) .filter(name => name.length > 0); // 为每个设计师单独添加项目 designerNames.forEach((designerName, index) => { if (!groupedMap.has(designerName)) { groupedMap.set(designerName, []); } // 创建项目副本,确保每个设计师都有独立的项目条目 const projectCopy = { ...p, designerName: designerName, // 如果有多个设计师,使用对应的 designerId designerId: p.designerIds && p.designerIds[index] ? p.designerIds[index] : p.designerId }; groupedMap.get(designerName)?.push(projectCopy); }); }); // 使用服务转换 this.projectTimelineData = this.designerWorkloadService.transformToTimeline(groupedMap); this.cdr.markForCheck(); } /** * 处理项目点击事件 */ onProjectTimelineClick(projectId: string): void { if (!projectId) { return; } const project = this.projects.find(p => p.id === projectId); const currentStage = project?.currentStage || '订单分配'; this.navigationHelper.navigateToProject(projectId, currentStage); } /** * 构建搜索索引(如果需要) */ private buildSearchIndexes(): void { this.projects.forEach(p => { if (!p.searchIndex) { p.searchIndex = `${p.name}|${p.designerName}`.toLowerCase(); } }); } // 筛选状态改变(由子组件触发) // 重置状态筛选 resetStatusFilter(): void { this.selectedStatus = 'all'; this.applyFilters(); } onFilterChange(filterState: any): void { // 更新本地状态 this.searchTerm = filterState.searchTerm; this.selectedType = filterState.type; this.selectedUrgency = filterState.urgency; this.selectedStatus = filterState.status; this.selectedDesigner = filterState.designer; this.selectedMemberType = filterState.memberType; this.selectedCorePhase = filterState.corePhase; this.selectedProjectId = filterState.projectId; this.selectedTimeWindow = filterState.timeWindow; // 应用筛选 this.applyFilters(); } // 状态筛选(由指标卡片点击触发) filterByStatus(status: string): void { this.selectedStatus = status as any; this.applyFilters(); } // 统一筛选 applyFilters(): void { const criteria = { searchTerm: this.searchTerm, type: this.selectedType, urgency: this.selectedUrgency, status: this.selectedStatus, designer: this.selectedDesigner, memberType: this.selectedMemberType, corePhase: this.selectedCorePhase, timeWindow: this.selectedTimeWindow }; const result = this.dashboardFilterService.filterProjects(this.projects, criteria); this.filteredProjects = result.filteredProjects; this.urgentPinnedProjects = result.urgentPinnedProjects; // 当显示甘特卡片时,同步刷新时间轴 if (this.showGanttView) { this.convertToProjectTimeline(); } } /** * 计算项目加权值 */ calculateWorkloadWeight(project: any): number { return this.designerService.calculateProjectWeight(project); } /** * 获取设计师加权工作量 */ getDesignerWeightedWorkload(designerName: string): { weightedTotal: number; projectCount: number; overdueCount: number; loadRate: number; } { const designerProjects = this.filteredProjects.filter(p => p.designerName === designerName); const weightedTotal = designerProjects.reduce((sum, p) => sum + this.calculateWorkloadWeight(p), 0); const overdueCount = designerProjects.filter(p => p.isOverdue).length; // 从realDesigners获取设计师的单周处理量 const designer = this.realDesigners.find(d => d.name === designerName); const weeklyCapacity = designer?.tags?.capacity?.weeklyProjects || 3; const loadRate = weeklyCapacity > 0 ? (weightedTotal / weeklyCapacity) * 100 : 0; return { weightedTotal, projectCount: designerProjects.length, overdueCount, loadRate }; } /** * 工作量卡片数据(替代ECharts) */ get designerWorkloadCards(): Array<{ name: string; loadRate: number; weightedValue: number; projectCount: number; overdueCount: number; status: 'overload' | 'busy' | 'idle'; }> { const designers = Array.from(new Set(this.filteredProjects.map(p => p.designerName).filter(n => n && n !== '未分配'))); return designers.map(name => { const workload = this.getDesignerWeightedWorkload(name); let status: 'overload' | 'busy' | 'idle' = 'idle'; if (workload.loadRate > 80) status = 'overload'; else if (workload.loadRate > 50) status = 'busy'; return { name, loadRate: workload.loadRate, weightedValue: workload.weightedTotal, projectCount: workload.projectCount, overdueCount: workload.overdueCount, status }; }).sort((a, b) => b.loadRate - a.loadRate); // 按负载率降序 } /** * 获取超负荷设计师数量 */ get overloadedDesignersCount(): number { return this.designerWorkloadCards.filter(d => d.status === 'overload').length; } /** * 获取平均负载率 */ get averageWorkloadRate(): number { const cards = this.designerWorkloadCards; if (cards.length === 0) return 0; const sum = cards.reduce((acc, card) => acc + card.loadRate, 0); return sum / cards.length; } /** * 获取预警汇总数据 */ getAlertSummary(): { totalAlerts: number; overdueHighRisk: Project[]; overloadedDesigners: any[]; dueSoonProjects: Project[]; } { // 1. 超期高危项目(超期>=5天 或 高紧急度超期) const overdueHighRisk = this.filteredProjects .filter(p => p.isOverdue && (p.overdueDays >= 5 || p.urgency === 'high')) .sort((a, b) => b.overdueDays - a.overdueDays) .slice(0, 5); // 2. 超负荷设计师 const overloadedDesigners = this.designerWorkloadCards .filter(d => d.loadRate > 80) .sort((a, b) => b.loadRate - a.loadRate) .slice(0, 5); // 3. 即将到期项目(1-2天内) const now = new Date(); const dueSoonProjects = this.filteredProjects .filter(p => { if (p.isOverdue) return false; const daysLeft = Math.ceil((p.deadline.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); return daysLeft >= 1 && daysLeft <= 2; }) .sort((a, b) => a.deadline.getTime() - b.deadline.getTime()) .slice(0, 5); const totalAlerts = overdueHighRisk.length + overloadedDesigners.length + dueSoonProjects.length; return { totalAlerts, overdueHighRisk, overloadedDesigners, dueSoonProjects }; } /** * 打开智能推荐弹窗 */ async openSmartMatch(project: any): Promise { this.selectedProject = project; this.showSmartMatch = true; try { this.recommendations = await this.designerService.getRecommendedDesigners(project, this.realDesigners); } catch (error) { console.error('智能推荐失败:', error); this.recommendations = []; } } /** * 关闭智能推荐弹窗 */ closeSmartMatch(): void { this.showSmartMatch = false; this.selectedProject = null; this.recommendations = []; } /** * 分配项目给设计师 */ async assignToDesigner(designerId: string): Promise { if (!this.selectedProject) return; try { const success = await this.designerService.assignProject(this.selectedProject.id, designerId); if (success) { this.closeSmartMatch(); await this.loadProjects(); // 重新加载项目数据 } } catch (error) { console.error('❌ 分配项目失败:', error); window?.fmode?.alert('分配失败,请重试'); } } // 切换视图 toggleView(): void { this.showGanttView = !this.showGanttView; if (this.showGanttView) { this.convertToProjectTimeline(); } } ngOnDestroy(): void { // 清理待办任务自动刷新定时器 if (this.todoTaskRefreshTimer) { clearInterval(this.todoTaskRefreshTimer); } } // 🔥 已延期项目 get overdueProjects(): Project[] { return this.projects.filter(p => p.isOverdue); } // ⏳ 临期项目(3天内) get dueSoonProjects(): Project[] { return this.projects.filter(p => p.dueSoon && !p.isOverdue); } // 📋 待审批项目(支持中文和英文阶段名称) get pendingApprovalProjects(): Project[] { const pending = this.projects.filter(p => { const stage = (p.currentStage || '').trim(); const data = (p as any).data || {}; const approvalStatus = data.approvalStatus; // 1. 阶段为"订单分配"且审批状态为 pending // 2. 或者阶段为"待确认"/"待审批"(兼容旧数据) return (stage === '订单分配' && approvalStatus === 'pending') || stage === '待审批' || stage === '待确认'; }); return pending; } // 检查项目是否待审批 isPendingApproval(project: Project): boolean { const stage = (project.currentStage || '').trim(); const stageEn = stage.toLowerCase(); const data: any = (project as any).data || {}; // 🔥 新增:检查顶层的 pendingApproval 字段(备用方案) const topLevelPending = (project as any).pendingApproval === true && (project as any).approvalStage === '订单分配'; return (stage === '订单分配' && (data.approvalStatus === 'pending' || topLevelPending)) || ((stage === '交付执行' || stageEn === 'delivery') && (data.deliveryApproval?.status === 'pending' || (Array.isArray(data.pendingDeliveryApprovals) && data.pendingDeliveryApprovals.some((x: any) => x?.status === 'pending')))); } // 🎯 待分配项目(支持中文和英文阶段名称) get pendingAssignmentProjects(): Project[] { return this.projects.filter(p => { const stage = (p.currentStage || '').trim().toLowerCase(); return stage === 'pendingassignment' || stage === '待分配' || stage === '订单分配'; }); } // 智能推荐设计师 private getRecommendedDesigner(projectType: 'soft' | 'hard') { if (!this.designerProfiles || !this.designerProfiles.length) return null; const scoreOf = (p: any) => { const workloadScore = 100 - (p.workload ?? 0); // 负载越低越好 const ratingScore = (p.avgRating ?? 0) * 10; // 评分越高越好 const expScore = (p.experience ?? 0) * 5; // 经验越高越好 return workloadScore * 0.5 + ratingScore * 0.3 + expScore * 0.2; }; const sorted = [...this.designerProfiles].sort((a, b) => scoreOf(b) - scoreOf(a)); return sorted[0] || null; } // 项目质量评审(由子组件触发) reviewProjectQuality(data: {projectId: string, rating: 'excellent' | 'qualified' | 'unqualified'}): void { const project = this.projects.find(p => p.id === data.projectId); if (!project) return; project.qualityRating = data.rating; if (data.rating === 'unqualified') { project.currentStage = 'revision'; } this.applyFilters(); window?.fmode?.alert('质量评审已提交'); } /** * 根据看板列跳转到项目详情(参考客服板块实现) * @param projectId 项目ID * @param corePhaseId 核心阶段ID (order/requirements/delivery/aftercare) */ viewProjectDetailsByPhase(projectId: string, corePhaseId: string): void { this.navigationHelper.navigateToProjectByPhase(projectId, corePhaseId); } // 查看项目详情 - 跳转到纯净的项目详情页(无管理端UI) viewProjectDetails(projectId: string): void { if (!projectId) { return; } // 🔥 根据项目当前阶段决定跳转到哪个阶段页面 const project = this.projects.find(p => p.id === projectId); const currentStage = project?.currentStage || '订单分配'; this.navigationHelper.navigateToProject(projectId, currentStage); } // 快速分配项目(增强:加入智能推荐) async quickAssignProject(projectId: string): Promise { const project = this.projects.find(p => p.id === projectId); if (!project) { window?.fmode?.alert('未找到对应项目'); return; } const recommended = this.getRecommendedDesigner(project.type); if (recommended) { const reassigning = !!project.designerName; const message = `推荐设计师:${recommended.name}(工作负载:${recommended.workload}%,评分:${recommended.avgRating}分)` + (reassigning ? `\n\n该项目当前已由「${project.designerName}」负责,是否改为分配给「${recommended.name}」?` : '\n\n是否确认分配?'); const confirmAssign = await window?.fmode?.confirm(message); if (confirmAssign) { project.designerName = recommended.name; if (project.currentStage === 'pendingAssignment' || project.currentStage === 'pendingApproval') { project.currentStage = 'requirement'; } project.status = '进行中'; this.applyFilters(); window?.fmode?.alert(`项目已${reassigning ? '重新' : ''}分配给 ${recommended.name}`); return; } } // 无推荐或用户取消,跳转到详细分配页面 // 跳转到项目详情页 const current = this.projects.find(p => p.id === projectId)?.currentStage || '订单分配'; this.navigationHelper.navigateToProject(projectId, current); } // 预警相关操作(由子组件触发) viewAllOverdueProjects(): void { this.filterByStatus('overdue'); this.showAlert = false; } closeAlert(): void { this.showAlert = false; } // 处理甘特图员工点击事件 async onEmployeeClick(employeeName: string): Promise { if (!employeeName || employeeName === '未分配') { return; } this.selectedEmployeeName = employeeName; this.selectedEmployeeProjects = this.designerWorkloadMap.get(employeeName) || []; this.showEmployeeDetailPanel = true; // 🔥 修复:手动触发变更检测,确保弹窗立即显示 this.cdr.markForCheck(); } // 关闭员工详情面板 closeEmployeeDetailPanel(): void { this.showEmployeeDetailPanel = false; this.selectedEmployeeName = ''; this.selectedEmployeeProjects = []; this.selectedEmployeeDetail = null; } // 员工详情面板:日历月份切换 changeEmployeeCalendarMonth(direction: number): void { // 由 EmployeeDetailPanelComponent 内部处理 console.log('日历月份切换:', direction); } // 员工详情面板:日历日期点击 onCalendarDayClick(day: any): void { // 由 EmployeeDetailPanelComponent 内部处理 console.log('日历日期点击:', day); } // 员工详情面板:刷新问卷 refreshEmployeeSurvey(): void { // 由 EmployeeDetailPanelComponent 内部处理 console.log('刷新员工问卷'); } // 从员工详情面板跳转到项目详情 navigateToProjectFromPanel(projectId: string): void { if (!projectId) { return; } // 关闭员工详情面板 this.closeEmployeeDetailPanel(); // 🔥 根据项目当前阶段决定跳转到哪个阶段页面 const project = this.projects.find(p => p.id === projectId); const currentStage = project?.currentStage || '订单分配'; this.navigationHelper.navigateToProject(projectId, currentStage); } /** * 加载用户Profile信息 */ async loadUserProfile(): Promise { try { const cid = localStorage.getItem("company"); if (!cid) { console.warn('未找到公司ID,使用默认用户信息'); return; } const wwAuth = new WxworkAuth({ cid }); const profile = await wwAuth.currentProfile(); if (profile) { const name = profile.get("name") || profile.get("mobile") || '组长'; const avatar = profile.get("avatar"); const roleName = profile.get("roleName") || '组长'; this.currentUser = { name, avatar: avatar || this.generateDefaultAvatar(name), roleName }; console.log('用户Profile加载成功:', this.currentUser); } } catch (error) { console.error('加载用户Profile失败:', error); // 保持默认值 } } /** * 生成默认头像(SVG格式) * @param name 用户名 * @returns Base64编码的SVG数据URL */ generateDefaultAvatar(name: string): string { const initial = name ? name.substring(0, 2).toUpperCase() : '组长'; const bgColor = '#CCFFCC'; const textColor = '#555555'; const svg = ` ${initial} `; return `data:image/svg+xml,${encodeURIComponent(svg)}`; } // ==================== 新增:待办任务相关方法 ==================== /** * 从问题板块加载待办任务 */ async loadTodoTasksFromIssues(): Promise { this.loadingTodoTasks = true; this.todoTaskError = ''; try { this.todoTasksFromIssues = await this.todoTaskService.getTodoTasks(); console.log(`✅ 加载待办任务成功,共 ${this.todoTasksFromIssues.length} 条`); } catch (error) { console.error('❌ 加载待办任务失败:', error); this.todoTaskError = '加载失败,请稍后重试'; } finally { this.loadingTodoTasks = false; } } /** * 启动自动刷新(每5分钟) */ startAutoRefresh(): void { this.todoTaskRefreshTimer = setInterval(() => { console.log('🔄 自动刷新待办任务...'); this.loadTodoTasksFromIssues(); }, 5 * 60 * 1000); // 5分钟 } /** * 手动刷新待办任务 */ refreshTodoTasks(): void { console.log('🔄 手动刷新待办任务...'); this.loadTodoTasksFromIssues(); this.calculateUrgentEvents(); // 🆕 同时刷新紧急事件 } /** * 🆕 从项目时间轴数据计算紧急事件 * 识别截止时间已到或即将到达但未完成的关键节点 */ calculateUrgentEvents(): void { this.loadingUrgentEvents = true; try { // 使用服务计算紧急事件 this.urgentEvents = this.urgentEventService.calculateUrgentEvents( this.projectTimelineData, this.handledUrgentEventIds, this.mutedUrgentEventIds ); } catch (error) { console.error('❌ 计算紧急事件失败:', error); // 发生错误时清空列表,避免渲染异常数据 this.urgentEvents = []; } finally { this.loadingUrgentEvents = false; // 确保触发变更检测 this.cdr.markForCheck(); } } // 紧急事件处理方法(由子组件触发) confirmEventOnTime(event: UrgentEvent): void { this.mutedUrgentEventIds.add(event.id); this.urgentEvents = this.urgentEvents.filter(e => e.id !== event.id); this.cdr.markForCheck(); } resolveUrgentEvent(event: UrgentEvent): void { this.handledUrgentEventIds.add(event.id); this.urgentEvents = this.urgentEvents.filter(e => e.id !== event.id); this.cdr.markForCheck(); } markEventAsStagnant(payload: {event: UrgentEvent, reason: any}): void { const { event, reason } = payload; // 更新紧急事件 this.urgentEvents = this.urgentEvents.map(item => { if (item.id !== event.id) return item; const labels = new Set(item.labels || []); labels.add('停滞期'); return { ...item, category: 'customer' as const, statusType: 'stagnant' as const, isMarkedAsStagnant: true, stagnationReasonType: reason.reasonType, stagnationCustomReason: reason.customReason, estimatedResumeDate: reason.estimatedResumeDate, reasonNotes: reason.notes, markedAt: new Date(), markedBy: this.currentUser.name, stagnationDays: item.stagnationDays || 7, labels: Array.from(labels), followUpNeeded: true }; }); // 🆕 同步更新对应的项目对象 this.updateProjectMarkStatus(event.projectId, 'stagnation', reason); this.cdr.markForCheck(); // TODO: 持久化到后端数据库 this.saveEventMarkToDatabase(event, 'stagnation', reason); } markEventAsModification(payload: {event: UrgentEvent, reason: any}): void { const { event, reason } = payload; // 更新紧急事件 this.urgentEvents = this.urgentEvents.map(item => { if (item.id !== event.id) return item; const labels = new Set(item.labels || []); labels.add('改图期'); return { ...item, statusType: 'modification' as const, isMarkedAsModification: true, modificationReasonType: reason.reasonType, modificationCustomReason: reason.customReason, reasonNotes: reason.notes, markedAt: new Date(), markedBy: this.currentUser.name, labels: Array.from(labels) }; }); // 🆕 同步更新对应的项目对象 this.updateProjectMarkStatus(event.projectId, 'modification', reason); this.cdr.markForCheck(); // TODO: 持久化到后端数据库 this.saveEventMarkToDatabase(event, 'modification', reason); } /** * 更新项目的停滞/改图标记及原因信息 */ private updateProjectMarkStatus(projectId: string, type: 'stagnation' | 'modification', reason: any): void { this.projects = this.projects.map(project => { if (project.id !== projectId) return project; if (type === 'stagnation') { return { ...project, isStalled: true, isModification: false, stagnationReasonType: reason.reasonType, stagnationCustomReason: reason.customReason, estimatedResumeDate: reason.estimatedResumeDate, reasonNotes: reason.notes, markedAt: new Date(), markedBy: this.currentUser.name }; } else { return { ...project, isModification: true, isStalled: false, modificationReasonType: reason.reasonType, modificationCustomReason: reason.customReason, reasonNotes: reason.notes, markedAt: new Date(), markedBy: this.currentUser.name }; } }); // 重新应用筛选 this.applyFilters(); } private saveEventMarkToDatabase(event: UrgentEvent, type: 'stagnation' | 'modification', reason: any): void { // TODO: 实现数据持久化逻辑 // 可以保存到 Parse 数据库的 ProjectEvent 或 UrgentEventMark 表 console.log(`💾 保存事件标记到数据库:`, { eventId: event.id, projectId: event.projectId, type, reason, timestamp: new Date() }); } createTodoFromEvent(event: UrgentEvent): void { const now = new Date(); const newTask: TodoTaskFromIssue = { id: `urgent-todo-${event.id}-${now.getTime()}`, title: `【紧急】${event.title}`, description: event.description, priority: event.urgencyLevel === 'critical' ? 'urgent' : event.urgencyLevel === 'high' ? 'high' : 'medium', type: 'feedback', status: 'open', projectId: event.projectId, projectName: event.projectName, relatedStage: event.phaseName, assigneeName: event.designerName || '待分配', creatorName: this.currentUser.name, createdAt: now, updatedAt: now, dueDate: event.deadline, tags: [...(event.labels || []), '来自紧急事件'] }; this.todoTasksFromIssues = [newTask, ...this.todoTasksFromIssues]; this.resolveUrgentEvent(event); } // 待办任务操作(由子组件触发) navigateToIssue(task: TodoTaskFromIssue): void { this.navigationHelper.navigateToIssue(task.projectId, task.id); } markAsRead(task: TodoTaskFromIssue): void { this.todoTasksFromIssues = this.todoTasksFromIssues.filter(t => t.id !== task.id); console.log(`✅ 标记问题为已读: ${task.title}`); } // 标记项目为停滞(从看板触发) markProjectAsStalled(project: Project): void { // 🆕 弹出原因输入弹窗 this.stagnationModalType = 'stagnation'; this.stagnationModalProject = project; this.showStagnationModal = true; } // 标记项目为改图(从看板触发) markProjectAsModification(project: Project): void { // 🆕 弹出原因输入弹窗 this.stagnationModalType = 'modification'; this.stagnationModalProject = project; this.showStagnationModal = true; } // 🆕 确认标记停滞/改图原因 onStagnationReasonConfirm(reason: any): void { if (!this.stagnationModalProject) return; // 直接调用 updateProjectMarkStatus 更新项目 this.updateProjectMarkStatus( this.stagnationModalProject.id, this.stagnationModalType, reason ); // 关闭弹窗 this.closeStagnationModal(); // 显示确认消息 const message = this.stagnationModalType === 'stagnation' ? '已标记为停滞项目' : '已标记为改图项目'; window?.fmode?.alert(message); } // 🆕 关闭停滞/改图原因弹窗 closeStagnationModal(): void { this.showStagnationModal = false; this.stagnationModalProject = null; } // 切换前期阶段显示 togglePreProductionPhases(): void { this.showPreProductionPhases = !this.showPreProductionPhases; } }