| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292 | 
							- import { CommonModule } from '@angular/common';
 
- import { FormsModule } from '@angular/forms';
 
- import { Router, RouterModule } from '@angular/router';
 
- import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
 
- import { ProjectService } from '../../../services/project.service';
 
- // 项目阶段定义
 
- interface ProjectStage {
 
-   id: string;
 
-   name: string;
 
-   order: number;
 
- }
 
- interface ProjectPhase {
 
-   name: string;
 
-   percentage: number;
 
-   startPercentage: number;
 
-   isCompleted: boolean;
 
-   isCurrent: boolean;
 
- }
 
- interface Project {
 
-   id: string;
 
-   name: string;
 
-   type: 'soft' | 'hard';
 
-   memberType: 'vip' | 'normal';
 
-   designerName: string;
 
-   status: string;
 
-   expectedEndDate: Date; // TODO: 兼容旧字段,后续可移除
 
-   deadline: Date; // 真实截止时间字段
 
-   createdAt?: Date; // 真实开始时间字段(可选)
 
-   isOverdue: boolean;
 
-   overdueDays: number;
 
-   dueSoon: boolean;
 
-   urgency: 'high' | 'medium' | 'low';
 
-   phases: ProjectPhase[];
 
-   currentStage: string; // 新增:当前项目阶段
 
-   // 新增:质量评级
 
-   qualityRating?: 'excellent' | 'qualified' | 'unqualified' | 'pending';
 
-   lastCustomerFeedback?: string;
 
- // 预构建的搜索索引,减少重复 toLowerCase 与拼接
 
-   searchIndex?: string;
 
- }
 
- interface TodoTask {
 
-   id: string;
 
-   title: string;
 
-   description: string;
 
-   deadline: Date;
 
-   priority: 'high' | 'medium' | 'low';
 
-   type: 'review' | 'assign' | 'performance';
 
-   targetId: string;
 
- }
 
- declare const echarts: any;
 
- @Component({
 
-   selector: 'app-dashboard',
 
-   imports: [CommonModule, FormsModule, RouterModule],
 
-   templateUrl: './dashboard.html',
 
-   styleUrl: './dashboard.scss'
 
- })
 
- export class Dashboard implements OnInit, OnDestroy {
 
-   projects: Project[] = [];
 
-   filteredProjects: Project[] = [];
 
-   todoTasks: TodoTask[] = [];
 
-   overdueProjects: Project[] = [];
 
-   urgentPinnedProjects: Project[] = [];
 
-   showAlert: boolean = false;
 
-   selectedProjectId: string = '';
 
-   // 新增:关键词搜索
 
-   searchTerm: string = '';
 
-   searchSuggestions: Project[] = [];
 
-   showSuggestions: boolean = false;
 
-   private hideSuggestionsTimer: any;
 
-   
 
-   // 搜索性能与交互控制
 
-   private searchDebounceTimer: any;
 
-   private readonly SEARCH_DEBOUNCE_MS = 200; // 防抖时长(毫秒)
 
-   private readonly MIN_SEARCH_LEN = 2; // 最小触发建议长度
 
-   private readonly MAX_SUGGESTIONS = 8; // 建议最大条数
 
-   private isSearchFocused: boolean = false; // 是否处于输入聚焦态
 
-   // 新增:临期项目与筛选状态
 
-   dueSoonProjects: Project[] = [];
 
-   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: string[] = [];
 
-   // 新增:四大板块筛选
 
-   selectedCorePhase: 'all' | 'order' | 'requirements' | 'delivery' | 'aftercare' = 'all';
 
-   
 
-   // 设计师画像(用于智能推荐)
 
-   designerProfiles: any[] = [
 
-     { id: 'zhang', name: '张三', skills: ['现代风格', '北欧风格'], workload: 70, avgRating: 4.5, experience: 3 },
 
-     { id: 'li', name: '李四', skills: ['新中式', '宋式风格'], workload: 45, avgRating: 4.8, experience: 5 },
 
-     { id: 'wang', name: '王五', skills: ['北欧风格', '日式风格'], workload: 85, avgRating: 4.2, experience: 2 },
 
-     { id: 'zhao', name: '赵六', skills: ['现代风格', '轻奢风格'], workload: 30, avgRating: 4.6, experience: 4 }
 
-   ];
 
-   // 10个项目阶段
 
-   projectStages: ProjectStage[] = [
 
-     { id: 'pendingApproval', name: '待确认', order: 1 },
 
-     { id: 'pendingAssignment', name: '待分配', order: 2 },
 
-     { id: 'requirement', name: '需求沟通', order: 3 },
 
-     { id: 'planning', name: '方案规划', order: 4 },
 
-     { id: 'modeling', name: '建模阶段', order: 5 },
 
-     { id: 'rendering', name: '渲染阶段', order: 6 },
 
-     { id: 'postProduction', name: '后期处理', order: 7 },
 
-     { id: 'review', name: '方案评审', order: 8 },
 
-     { id: 'revision', name: '方案修改', order: 9 },
 
-     { id: 'delivery', name: '交付完成', order: 10 }
 
-   ];
 
-   // 5大核心阶段(聚合展示)
 
-   corePhases: ProjectStage[] = [
 
-     { id: 'order', name: '订单创建', order: 1 },        // 待确认、待分配
 
-     { id: 'requirements', name: '确认需求', order: 2 },  // 需求沟通、方案规划
 
-     { id: 'delivery', name: '交付执行', order: 3 },      // 建模、渲染、后期/评审/修改
 
-     { id: 'aftercare', name: '售后', order: 4 }          // 交付完成 → 售后
 
-   ];
 
-   // 甘特视图开关与实例引用
 
-   showGanttView: boolean = false;
 
-   private ganttChart: any | null = null;
 
-   @ViewChild('ganttChartRef', { static: false }) ganttChartRef!: ElementRef<HTMLDivElement>;
 
-   // 新增:工作量概览图表引用与实例
 
-   @ViewChild('workloadChartRef', { static: false }) workloadChartRef!: ElementRef<HTMLDivElement>;
 
-   private workloadChart: any | null = null;
 
-   workloadDimension: 'designer' | 'member' = 'designer';
 
-   // 甘特时间尺度:仅周/月
 
-   ganttScale: 'week' | 'month' = 'week';
 
-   // 新增:甘特模式(项目 / 设计师排班)
 
-   ganttMode: 'project' | 'designer' = 'project';
 
-   constructor(private projectService: ProjectService, private router: Router) {}
 
-   ngOnInit(): void {
 
-     this.loadProjects();
 
-     this.loadTodoTasks();
 
-     // 首次微任务后尝试初始化一次,确保容器已渲染
 
-     setTimeout(() => this.updateWorkloadChart(), 0);
 
-   }
 
-   loadProjects(): void {
 
-     // 模拟数据加载 - 增强数据结构,添加currentStage
 
-     this.projects = [
 
-       {
 
-         id: 'proj-001',
 
-         name: '现代风格客厅设计',
 
-         type: 'soft',
 
-         memberType: 'vip',
 
-         designerName: '张三',
 
-         status: '进行中',
 
-         expectedEndDate: new Date(2023, 9, 15),
 
-         deadline: new Date(2023, 9, 15),
 
-         isOverdue: true,
 
-         overdueDays: 2,
 
-         dueSoon: false,
 
-         urgency: 'high',
 
-         currentStage: 'rendering',
 
-         phases: [
 
-           { name: '建模', percentage: 15, startPercentage: 0, isCompleted: true, isCurrent: false },
 
-           { name: '渲染', percentage: 20, startPercentage: 15, isCompleted: false, isCurrent: true },
 
-           { name: '后期', percentage: 15, startPercentage: 35, isCompleted: false, isCurrent: false },
 
-           { name: '交付', percentage: 50, startPercentage: 50, isCompleted: false, isCurrent: false }
 
-         ]
 
-       },
 
-       {
 
-         id: 'proj-002',
 
-         name: '北欧风格卧室设计',
 
-         type: 'soft',
 
-         memberType: 'normal',
 
-         designerName: '李四',
 
-         status: '进行中',
 
-         expectedEndDate: new Date(2023, 9, 20),
 
-         deadline: new Date(2023, 9, 20),
 
-         isOverdue: false,
 
-         overdueDays: 0,
 
-         dueSoon: false,
 
-         urgency: 'medium',
 
-         currentStage: 'postProduction',
 
-         phases: [
 
-           { name: '建模', percentage: 15, startPercentage: 0, isCompleted: true, isCurrent: false },
 
-           { name: '渲染', percentage: 20, startPercentage: 15, isCompleted: true, isCurrent: false },
 
-           { name: '后期', percentage: 15, startPercentage: 35, isCompleted: false, isCurrent: true },
 
-           { name: '交付', percentage: 50, startPercentage: 50, isCompleted: false, isCurrent: false }
 
-         ]
 
-       },
 
-       {
 
-         id: 'proj-003',
 
-         name: '新中式餐厅设计',
 
-         type: 'hard',
 
-         memberType: 'normal',
 
-         designerName: '王五',
 
-         status: '进行中',
 
-         expectedEndDate: new Date(2023, 9, 25),
 
-         deadline: new Date(2023, 9, 25),
 
-         isOverdue: false,
 
-         overdueDays: 0,
 
-         dueSoon: false,
 
-         urgency: 'low',
 
-         currentStage: 'modeling',
 
-         phases: [
 
-           { name: '建模', percentage: 15, startPercentage: 0, isCompleted: false, isCurrent: true },
 
-           { name: '渲染', percentage: 20, startPercentage: 15, isCompleted: false, isCurrent: false },
 
-           { name: '后期', percentage: 15, startPercentage: 35, isCompleted: false, isCurrent: false },
 
-           { name: '交付', percentage: 50, startPercentage: 50, isCompleted: false, isCurrent: false }
 
-         ]
 
-       },
 
-       {
 
-         id: 'proj-004',
 
-         name: '工业风办公室设计',
 
-         type: 'hard',
 
-         memberType: 'normal',
 
-         designerName: '赵六',
 
-         status: '进行中',
 
-         expectedEndDate: new Date(2023, 9, 10),
 
-         deadline: new Date(2023, 9, 10),
 
-         isOverdue: true,
 
-         overdueDays: 7,
 
-         dueSoon: false,
 
-         urgency: 'high',
 
-         currentStage: 'review',
 
-         phases: [
 
-           { name: '建模', percentage: 15, startPercentage: 0, isCompleted: true, isCurrent: false },
 
-           { name: '渲染', percentage: 20, startPercentage: 15, isCompleted: true, isCurrent: false },
 
-           { name: '后期', percentage: 15, startPercentage: 35, isCompleted: false, isCurrent: false },
 
-           { name: '交付', percentage: 50, startPercentage: 50, isCompleted: false, isCurrent: false }
 
-         ]
 
-       },
 
-       // 添加更多不同阶段的项目
 
-       {
 
-         id: 'proj-005',
 
-         name: '现代简约厨房设计',
 
-         type: 'soft',
 
-         memberType: 'normal',
 
-         designerName: '',
 
-         status: '待分配',
 
-         expectedEndDate: new Date(2023, 10, 5),
 
-         deadline: new Date(2023, 10, 5),
 
-         isOverdue: false,
 
-         overdueDays: 0,
 
-         dueSoon: false,
 
-         urgency: 'medium',
 
-         currentStage: 'pendingAssignment',
 
-         phases: []
 
-       },
 
-       {
 
-         id: 'proj-006',
 
-         name: '日式风格书房设计',
 
-         type: 'hard',
 
-         memberType: 'normal',
 
-         designerName: '',
 
-         status: '待确认',
 
-         expectedEndDate: new Date(2023, 10, 10),
 
-         deadline: new Date(2023, 10, 10),
 
-         isOverdue: false,
 
-         overdueDays: 0,
 
-         dueSoon: false,
 
-         urgency: 'low',
 
-         currentStage: 'pendingApproval',
 
-         phases: []
 
-       },
 
-       {
 
-         id: 'proj-007',
 
-         name: '轻奢风格浴室设计',
 
-         type: 'soft',
 
-         memberType: 'normal',
 
-         designerName: '钱七',
 
-         status: '已完成',
 
-         expectedEndDate: new Date(2023, 9, 5),
 
-         deadline: new Date(2023, 9, 5),
 
-         isOverdue: false,
 
-         overdueDays: 0,
 
-         dueSoon: false,
 
-         urgency: 'medium',
 
-         currentStage: 'delivery',
 
-         phases: []
 
-       }
 
-     ];
 
-     // ===== 追加生成示例数据:保证总量达到100条 =====
 
-     const stageIds = this.projectStages.map(s => s.id);
 
-     const designers = ['张三','李四','王五','赵六','钱七','孙八','周九','吴十','郑一','冯二','陈三','褚四'];
 
-     const statusMap: Record<string, string> = {
 
-       pendingApproval: '待确认',
 
-       pendingAssignment: '待分配',
 
-       requirement: '进行中',
 
-       planning: '进行中',
 
-       modeling: '进行中',
 
-       rendering: '进行中',
 
-       postProduction: '进行中',
 
-       review: '进行中',
 
-       revision: '进行中',
 
-       delivery: '已完成'
 
-     };
 
-     for (let i = 8; i <= 100; i++) {
 
-       const stageIndex = (i - 1) % stageIds.length;
 
-       const currentStage = stageIds[stageIndex];
 
-       const type: 'soft' | 'hard' = i % 2 === 0 ? 'soft' : 'hard';
 
-       const urgency: 'high' | 'medium' | 'low' = i % 5 === 0 ? 'high' : (i % 3 === 0 ? 'medium' : 'low');
 
-       const isOverdue = ['planning','modeling','rendering','postProduction','review','revision','delivery'].includes(currentStage) ? i % 7 === 0 : false;
 
-       const overdueDays = isOverdue ? (i % 10) + 1 : 0;
 
-       const hasDesigner = !['pendingApproval', 'pendingAssignment'].includes(currentStage);
 
-       const designerName = hasDesigner ? designers[i % designers.length] : '';
 
-       const status = statusMap[currentStage] || '进行中';
 
-       const expectedEndDate = new Date();
 
-       const daysOffset = isOverdue ? - (overdueDays + (i % 5)) : ((i % 20) + 3);
 
-       expectedEndDate.setDate(expectedEndDate.getDate() + daysOffset);
 
-       const daysToDeadline = Math.ceil((expectedEndDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
 
-       const dueSoon = !isOverdue && daysToDeadline >= 0 && daysToDeadline <= 3;
 
-       const memberType: 'vip' | 'normal' = i % 4 === 0 ? 'vip' : 'normal';
 
-       this.projects.push({
 
-         id: `proj-${String(i).padStart(3, '0')}`,
 
-         name: `${type === 'soft' ? '软装' : '硬装'}示例项目 ${i}`,
 
-         type,
 
-         memberType,
 
-         designerName,
 
-         status,
 
-         expectedEndDate,
 
-         deadline: expectedEndDate,
 
-         isOverdue,
 
-         overdueDays,
 
-         dueSoon,
 
-         urgency,
 
-         currentStage,
 
-         phases: []
 
-       });
 
-     }
 
-     // ===== 示例数据生成结束 =====
 
-     // 统一补齐真实时间字段(deadline/createdAt),以真实字段贯通筛选与甘特
 
-     const DAY = 24 * 60 * 60 * 1000;
 
-     this.projects = this.projects.map(p => {
 
-       const deadline = p.deadline || p.expectedEndDate;
 
-       const baseDays = p.type === 'hard' ? 30 : 14;
 
-       const createdAt = p.createdAt || new Date(new Date(deadline).getTime() - baseDays * DAY);
 
-       return { ...p, deadline, createdAt } as Project;
 
-     });
 
-     // 筛选超期与临期项目
 
-     this.overdueProjects = this.projects.filter(project => project.isOverdue);
 
-     this.dueSoonProjects = this.projects.filter(project => project.dueSoon && !project.isOverdue);
 
-     this.filteredProjects = [...this.projects];
 
-     // 供筛选用的设计师列表
 
-     this.designers = Array.from(new Set(this.projects.map(p => p.designerName).filter(n => !!n)));
 
-     // 显示超期提醒
 
-     if (this.overdueProjects.length > 0) {
 
-       this.showAlert = true;
 
-     }
 
-   }
 
-   loadTodoTasks(): void {
 
-     // 模拟待办任务数据
 
-     this.todoTasks = [
 
-       {
 
-         id: 'todo-001',
 
-         title: '待评审效果图',
 
-         description: '现代风格客厅设计项目需要进行效果图评审',
 
-         deadline: new Date(2023, 9, 18, 15, 0),
 
-         priority: 'high',
 
-         type: 'review',
 
-         targetId: 'proj-001'
 
-       },
 
-       {
 
-         id: 'todo-002',
 
-         title: '待分配项目',
 
-         description: '新中式厨房设计项目需要分配给合适的设计师',
 
-         deadline: new Date(2023, 9, 19, 10, 0),
 
-         priority: 'high',
 
-         type: 'assign',
 
-         targetId: 'proj-new'
 
-       },
 
-       {
 
-         id: 'todo-003',
 
-         title: '待确认绩效',
 
-         description: '9月份团队绩效需要进行审核确认',
 
-         deadline: new Date(2023, 9, 22, 18, 0),
 
-         priority: 'medium',
 
-         type: 'performance',
 
-         targetId: 'sep-2023'
 
-       },
 
-       {
 
-         id: 'todo-004',
 
-         title: '待处理客户反馈',
 
-         description: '北欧风格卧室设计项目有客户反馈需要处理',
 
-         deadline: new Date(2023, 9, 20, 14, 0),
 
-         priority: 'medium',
 
-         type: 'review',
 
-         targetId: 'proj-002'
 
-       },
 
-       {
 
-         id: 'todo-005',
 
-         title: '团队会议',
 
-         description: '每周团队进度沟通会议',
 
-         deadline: new Date(2023, 9, 18, 10, 0),
 
-         priority: 'low',
 
-         type: 'performance',
 
-         targetId: 'weekly-meeting'
 
-       }
 
-     ];
 
-     // 按优先级排序:紧急且重要 > 重要不紧急 > 紧急不重要
 
-     this.todoTasks.sort((a, b) => {
 
-       const priorityOrder = {
 
-         'high': 3,
 
-         'medium': 2,
 
-         'low': 1
 
-       };
 
-       return priorityOrder[b.priority] - priorityOrder[a.priority];
 
-     });
 
-   }
 
-   // 筛选项目类型
 
-   filterProjects(event: Event): void {
 
-     const target = event.target as HTMLSelectElement;
 
-     this.selectedType = (target && target.value ? target.value : 'all') as any;
 
-     this.applyFilters();
 
-   }
 
-   // 筛选紧急程度
 
-   filterByUrgency(event: Event): void {
 
-     const target = event.target as HTMLSelectElement;
 
-     this.selectedUrgency = (target && target.value ? target.value : 'all') as any;
 
-     this.applyFilters();
 
-   }
 
-   // 筛选项目状态
 
-   filterByStatus(status: string): void {
 
-     // 点击同一状态时,切换回“全部”,以便恢复全量项目列表
 
-     const next = (this.selectedStatus === status) ? 'all' : (status && status.length ? status : 'all');
 
-     this.selectedStatus = next as any;
 
-     this.applyFilters();
 
-   }
 
-   
 
-   // 处理状态筛选下拉框变化
 
-   onStatusChange(event: Event): void {
 
-     const target = event.target as HTMLSelectElement;
 
-     this.selectedStatus = (target && target.value ? target.value : 'all') as any;
 
-     this.applyFilters();
 
-   }
 
-   // 新增:设计师筛选下拉事件处理
 
-   onDesignerChange(event: Event): void {
 
-     const target = event.target as HTMLSelectElement;
 
-     this.selectedDesigner = (target && target.value ? target.value : 'all');
 
-     this.applyFilters();
 
-   }
 
-   // 新增:会员类型筛选下拉事件处理
 
-   onMemberTypeChange(event: Event): void {
 
-     const select = event.target as HTMLSelectElement;
 
-     this.selectedMemberType = select.value as any;
 
-     this.applyFilters();
 
-   }
 
-   // 新增:四大板块改变
 
-   onCorePhaseChange(event: Event): void {
 
-     const select = event.target as HTMLSelectElement;
 
-     this.selectedCorePhase = select.value as any;
 
-     this.applyFilters();
 
-   }
 
-   // 时间窗快捷筛选(供UI按钮触发)
 
-   filterByTimeWindow(timeWindow: 'all' | 'today' | 'threeDays' | 'sevenDays'): void {
 
-     this.selectedTimeWindow = timeWindow;
 
-     this.applyFilters();
 
-   }
 
-   // 新增:搜索输入变化
 
-   onSearchChange(): void {
 
-     if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer);
 
-     this.searchDebounceTimer = setTimeout(() => {
 
-       this.updateSearchSuggestions();
 
-       this.applyFilters();
 
-     }, this.SEARCH_DEBOUNCE_MS);
 
-   }
 
-   // 新增:搜索框聚焦/失焦控制建议显隐
 
-   onSearchFocus(): void {
 
-     if (this.hideSuggestionsTimer) clearTimeout(this.hideSuggestionsTimer);
 
-     this.isSearchFocused = true;
 
-     this.updateSearchSuggestions();
 
-   }
 
-   onSearchBlur(): void {
 
-     // 延迟隐藏以允许选择项的 mousedown 触发
 
-     this.isSearchFocused = false;
 
-     this.hideSuggestionsTimer = setTimeout(() => {
 
-       this.showSuggestions = false;
 
-     }, 150);
 
-   }
 
-   // 新增:更新搜索建议(不叠加其它筛选,仅基于关键字)
 
-   private updateSearchSuggestions(): void {
 
-     const q = (this.searchTerm || '').trim().toLowerCase();
 
-     if (q.length < this.MIN_SEARCH_LEN) {
 
-       this.searchSuggestions = [];
 
-       this.showSuggestions = false;
 
-       return;
 
-     }
 
-     const scored = this.projects
 
-       .filter(p => (p.searchIndex || `${(p.name || '')}|${(p.designerName || '')}`.toLowerCase()).includes(q))
 
-       .map(p => {
 
-         const dl = p.deadline || p.expectedEndDate;
 
-         const dlTime = dl ? new Date(dl).getTime() : NaN;
 
-         const daysToDl = Math.ceil(((isNaN(dlTime) ? 0 : dlTime) - Date.now()) / (1000 * 60 * 60 * 24));
 
-         const urgencyScore = p.urgency === 'high' ? 3 : p.urgency === 'medium' ? 2 : 1;
 
-         const overdueScore = p.isOverdue ? 10 : 0;
 
-         const score = overdueScore + (4 - urgencyScore) * 2 - (isNaN(daysToDl) ? 0 : daysToDl);
 
-         return { p, score };
 
-       })
 
-       .sort((a, b) => b.score - a.score)
 
-       .slice(0, this.MAX_SUGGESTIONS)
 
-       .map(x => x.p);
 
-     this.searchSuggestions = scored;
 
-     this.showSuggestions = this.isSearchFocused && this.searchSuggestions.length > 0;
 
-   }
 
-   // 新增:选择建议项
 
-   selectSuggestion(project: Project): void {
 
-     this.searchTerm = project.name;
 
-     this.showSuggestions = false;
 
-     this.viewProjectDetails(project.id);
 
-   }
 
-   // 统一筛选
 
-   private applyFilters(): void {
 
-     let result = [...this.projects];
 
-     // 新增:关键词搜索(项目名 / 设计师名 / 含风格关键词的项目名)
 
-     const q = (this.searchTerm || '').trim().toLowerCase();
 
-     if (q) {
 
-       result = result.filter(p => (p.searchIndex || `${(p.name || '')}|${(p.designerName || '')}`.toLowerCase()).includes(q));
 
-     }
 
-     // 类型筛选
 
-     if (this.selectedType !== 'all') {
 
-       result = result.filter(p => p.type === this.selectedType);
 
-     }
 
-     // 紧急程度筛选
 
-     if (this.selectedUrgency !== 'all') {
 
-       result = result.filter(p => p.urgency === this.selectedUrgency);
 
-     }
 
-     // 项目状态筛选
 
-     if (this.selectedStatus !== 'all') {
 
-       if (this.selectedStatus === 'overdue') {
 
-         result = result.filter(p => p.isOverdue);
 
-       } else if (this.selectedStatus === 'dueSoon') {
 
-         result = result.filter(p => p.dueSoon && !p.isOverdue);
 
-       } else if (this.selectedStatus === 'pendingApproval') {
 
-         result = result.filter(p => p.currentStage === 'pendingApproval');
 
-       } else if (this.selectedStatus === 'pendingAssignment') {
 
-         result = result.filter(p => p.currentStage === 'pendingAssignment');
 
-       } else if (this.selectedStatus === 'progress') {
 
-         const progressStages = ['requirement','planning','modeling','rendering','postProduction','review','revision'];
 
-         result = result.filter(p => progressStages.includes(p.currentStage));
 
-       } else if (this.selectedStatus === 'completed') {
 
-         result = result.filter(p => p.currentStage === 'delivery');
 
-       }
 
-     }
 
-     // 新增:四大板块筛选
 
-     if (this.selectedCorePhase !== 'all') {
 
-       result = result.filter(p => this.mapStageToCorePhase(p.currentStage) === this.selectedCorePhase);
 
-     }
 
-     // 设计师筛选
 
-     if (this.selectedDesigner !== 'all') {
 
-       result = result.filter(p => p.designerName === this.selectedDesigner);
 
-     }
 
-     // 会员类型筛选
 
-     if (this.selectedMemberType !== 'all') {
 
-       result = result.filter(p => p.memberType === this.selectedMemberType);
 
-     }
 
-     // 新增:时间窗筛选
 
-     if (this.selectedTimeWindow !== 'all') {
 
-       const now = new Date();
 
-       const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
 
-       
 
-       result = result.filter(p => {
 
-         const projectDeadline = new Date(p.deadline);
 
-         const timeDiff = projectDeadline.getTime() - today.getTime();
 
-         const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
 
-         
 
-         switch (this.selectedTimeWindow) {
 
-           case 'today':
 
-             return daysDiff <= 1 && daysDiff >= 0;
 
-           case 'threeDays':
 
-             return daysDiff <= 3 && daysDiff >= 0;
 
-           case 'sevenDays':
 
-             return daysDiff <= 7 && daysDiff >= 0;
 
-           default:
 
-             return true;
 
-         }
 
-       });
 
-     }
 
-     this.filteredProjects = result;
 
-     // 新增:计算紧急任务固定区(超期 + 高紧急),与筛选联动
 
-     this.urgentPinnedProjects = this.filteredProjects
 
-       .filter(p => p.isOverdue && p.urgency === 'high')
 
-       .sort((a, b) => (b.overdueDays - a.overdueDays) || a.name.localeCompare(b.name, 'zh-Hans-CN'));
 
-     // 当显示甘特卡片时,同步刷新甘特图
 
-     if (this.showGanttView) {
 
-       this.updateGantt();
 
-     }
 
-     // 同步刷新工作量概览图
 
-     this.updateWorkloadChart();
 
-   }
 
-   // 切换项目看板/负载日历(甘特)视图
 
-   toggleView(): void {
 
-     this.showGanttView = !this.showGanttView;
 
-     if (this.showGanttView) {
 
-       setTimeout(() => this.initOrUpdateGantt(), 0);
 
-     } else {
 
-       if (this.ganttChart) {
 
-         this.ganttChart.dispose();
 
-         this.ganttChart = null;
 
-       }
 
-       if (this.workloadChart) {
 
-         this.workloadChart.dispose();
 
-         this.workloadChart = null;
 
-       }
 
-     }
 
-   }
 
-   // 设置甘特时间尺度
 
-   setGanttScale(scale: 'week' | 'month'): void {
 
-     if (this.ganttScale !== scale) {
 
-       this.ganttScale = scale;
 
-       this.updateGantt();
 
-     }
 
-   }
 
-   // 新增:切换甘特模式
 
-   setGanttMode(mode: 'project' | 'designer'): void {
 
-     if (this.ganttMode !== mode) {
 
-       this.ganttMode = mode;
 
-       this.updateGantt();
 
-     }
 
-   }
 
-   private initOrUpdateGantt(): void {
 
-     if (!this.ganttChartRef) return;
 
-     const el = this.ganttChartRef.nativeElement;
 
-     if (!this.ganttChart) {
 
-       this.ganttChart = echarts.init(el);
 
-       window.addEventListener('resize', () => {
 
-         this.ganttChart && this.ganttChart.resize();
 
-       });
 
-     }
 
-     this.updateGantt();
 
-   }
 
-   private updateGantt(): void {
 
-     if (!this.ganttChart) return;
 
-     if (this.ganttMode === 'designer') {
 
-       this.updateGanttDesigner();
 
-       return;
 
-     }
 
-     // 按紧急程度从上到下排序(高->中->低),同级按到期时间升序
 
-     const urgencyRank: Record<'high'|'medium'|'low', number> = { high: 0, medium: 1, low: 2 };
 
-     const projects = [...this.filteredProjects]
 
-       .sort((a, b) => {
 
-         const u = urgencyRank[a.urgency] - urgencyRank[b.urgency];
 
-         if (u !== 0) return u;
 
-         // 二级排序:临期优先(到期更近)> 已分配人员 > VIP客户 > 其他
 
-         const endDiff = new Date(a.deadline).getTime() - new Date(b.deadline).getTime();
 
-         if (endDiff !== 0) return endDiff;
 
-         const assignedA = !!a.designerName;
 
-         const assignedB = !!b.designerName;
 
-         if (assignedA !== assignedB) return assignedA ? -1 : 1; // 已分配在前
 
-         const vipA = a.memberType === 'vip';
 
-         const vipB = b.memberType === 'vip';
 
-         if (vipA !== vipB) return vipA ? -1 : 1; // VIP在前
 
-         return a.name.localeCompare(b.name, 'zh-CN');
 
-       });
 
-     const categories = projects.map(p => p.name);
 
-     const urgencyMap: Record<string, 'high'|'medium'|'low'> = Object.fromEntries(projects.map(p => [p.name, p.urgency])) as any;
 
-     const colorByUrgency: Record<'high'|'medium'|'low', string> = {
 
-       high: '#ef4444',
 
-       medium: '#f59e0b',
 
-       low: '#22c55e'
 
-     } as const;
 
-     const DAY = 24 * 60 * 60 * 1000;
 
-     const data = projects.map((p, idx) => {
 
-       const end = new Date(p.deadline).getTime();
 
-       const baseDays = p.type === 'hard' ? 30 : 14;
 
-       const start = p.createdAt ? new Date(p.createdAt).getTime() : end - baseDays * DAY;
 
-       const color = colorByUrgency[p.urgency] || '#60a5fa';
 
-       return {
 
-         name: p.name,
 
-         value: [idx, start, end, p.designerName, p.urgency, p.memberType, p.currentStage],
 
-         itemStyle: { color }
 
-       };
 
-     });
 
-     // 计算时间范围(仅周/月)
 
-     const now = new Date();
 
-     const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
 
-     const todayTs = today.getTime();
 
-     let xMin: number;
 
-     let xMax: number;
 
-     let xSplitNumber: number;
 
-     let xLabelFormatter: (value: number) => string;
 
-     if (this.ganttScale === 'week') {
 
-       const day = today.getDay(); // 0=周日
 
-       const diffToMonday = (day === 0 ? 6 : day - 1);
 
-       const startOfWeek = new Date(today.getTime() - diffToMonday * DAY);
 
-       const endOfWeek = new Date(startOfWeek.getTime() + 7 * DAY - 1);
 
-       xMin = startOfWeek.getTime();
 
-       xMax = endOfWeek.getTime();
 
-       xSplitNumber = 7;
 
-       const WEEK_LABELS = ['周日','周一','周二','周三','周四','周五','周六'];
 
-       xLabelFormatter = (val) => {
 
-         const d = new Date(val);
 
-         return WEEK_LABELS[d.getDay()];
 
-       };
 
-     } else { // month
 
-       const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
 
-       const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0, 23, 59, 59, 999);
 
-       xMin = startOfMonth.getTime();
 
-       xMax = endOfMonth.getTime();
 
-       xSplitNumber = 4;
 
-       xLabelFormatter = (val) => {
 
-         const d = new Date(val);
 
-         const weekOfMonth = Math.ceil(d.getDate() / 7);
 
-         return `第${weekOfMonth}周`;
 
-       };
 
-     }
 
-     // 计算默认可视区,并尝试保留上一次的滚动/缩放位置
 
-     const total = categories.length;
 
-     const visible = Math.min(total, 15); // 默认首屏展开15条
 
-     const defaultEndPercent = total > 0 ? Math.min(100, (visible / total) * 100) : 100;
 
-     const prevOpt: any = (this.ganttChart as any).getOption ? (this.ganttChart as any).getOption() : null;
 
-     const preservedStart = typeof prevOpt?.dataZoom?.[0]?.start === 'number' ? prevOpt.dataZoom[0].start : 0;
 
-     const preservedEnd = typeof prevOpt?.dataZoom?.[0]?.end === 'number' ? prevOpt.dataZoom[0].end : defaultEndPercent;
 
-     const option = {
 
-       backgroundColor: 'transparent',
 
-       tooltip: {
 
-         trigger: 'item',
 
-         formatter: (params: any) => {
 
-           const v = params.value;
 
-           const start = new Date(v[1]);
 
-           const end = new Date(v[2]);
 
-           return `项目:${params.name}<br/>负责人:${v[3] || '未分配'}<br/>阶段:${v[6]}<br/>起止:${start.toLocaleDateString()} ~ ${end.toLocaleDateString()}`;
 
-         }
 
-       },
 
-       grid: { left: 100, right: 64, top: 30, bottom: 30 },
 
-       xAxis: {
 
-         type: 'time',
 
-         min: xMin,
 
-         max: xMax,
 
-         splitNumber: xSplitNumber,
 
-         axisLine: { lineStyle: { color: '#e5e7eb' } },
 
-         axisLabel: { color: '#6b7280', formatter: (value: number) => xLabelFormatter(value) },
 
-         splitLine: { lineStyle: { color: '#f1f5f9' } }
 
-       },
 
-       yAxis: {
 
-         type: 'category',
 
-         data: categories,
 
-         inverse: true,
 
-         axisLabel: {
 
-           color: '#374151',
 
-           margin: 8,
 
-           formatter: (val: string) => {
 
-             const u = urgencyMap[val] || 'low';
 
-             const text = val.length > 16 ? val.slice(0, 16) + '…' : val;
 
-             return `{${u}Dot|●} ${text}`;
 
-           },
 
-           rich: {
 
-             highDot: { color: '#ef4444' },
 
-             mediumDot: { color: '#f59e0b' },
 
-             lowDot: { color: '#22c55e' }
 
-           }
 
-         },
 
-         axisTick: { show: false },
 
-         axisLine: { lineStyle: { color: '#e5e7eb' } }
 
-       },
 
-       dataZoom: [
 
-         { type: 'slider', yAxisIndex: 0, orient: 'vertical', right: 6, width: 14, start: preservedStart, end: preservedEnd, zoomLock: false },
 
-         { type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true, moveOnMouseWheel: true }
 
-       ],
 
-       series: [
 
-         {
 
-           type: 'custom',
 
-           renderItem: (params: any, api: any) => {
 
-             const categoryIndex = api.value(0);
 
-             const start = api.coord([api.value(1), categoryIndex]);
 
-             const end = api.coord([api.value(2), categoryIndex]);
 
-             const height = Math.max(api.size([0, 1])[1] * 0.5, 8);
 
-             const rectShape = echarts.graphic.clipRectByRect({
 
-               x: start[0],
 
-               y: start[1] - height / 2,
 
-               width: Math.max(end[0] - start[0], 2),
 
-               height
 
-             }, {
 
-               x: params.coordSys.x,
 
-               y: params.coordSys.y,
 
-               width: params.coordSys.width,
 
-               height: params.coordSys.height
 
-             });
 
-             return rectShape ? { type: 'rect', shape: rectShape, style: api.style() } : undefined;
 
-           },
 
-           encode: { x: [1, 2], y: 0 },
 
-           data,
 
-           itemStyle: { borderRadius: 4 },
 
-           emphasis: { focus: 'self' },
 
-           markLine: {
 
-             silent: true,
 
-             symbol: 'none',
 
-             lineStyle: { color: '#ef4444', type: 'dashed', width: 1 },
 
-             label: { formatter: '今日', color: '#ef4444', fontSize: 10, position: 'end' },
 
-             data: [ { xAxis: todayTs } ]
 
-           }
 
-         }
 
-       ]
 
-     };
 
-     // 强制刷新,避免缓存导致坐标轴不更新
 
-     this.ganttChart.clear();
 
-     this.ganttChart.setOption(option, true);
 
-     this.ganttChart.resize();
 
-   }
 
-   // 新增:设计师排班甘特
 
-   private updateGanttDesigner(): void {
 
-     if (!this.ganttChart) return;
 
-     const DAY = 24 * 60 * 60 * 1000;
 
-     const now = new Date();
 
-     const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
 
-     const todayTs = today.getTime();
 
-     // 时间轴按当前周/月
 
-     let xMin: number;
 
-     let xMax: number;
 
-     let xSplitNumber: number;
 
-     let xLabelFormatter: (value: number) => string;
 
-     if (this.ganttScale === 'week') {
 
-       const day = today.getDay();
 
-       const diffToMonday = (day === 0 ? 6 : day - 1);
 
-       const startOfWeek = new Date(today.getTime() - diffToMonday * DAY);
 
-       const endOfWeek = new Date(startOfWeek.getTime() + 7 * DAY - 1);
 
-       xMin = startOfWeek.getTime();
 
-       xMax = endOfWeek.getTime();
 
-       xSplitNumber = 7;
 
-       const WEEK_LABELS = ['周日','周一','周二','周三','周四','周五','周六'];
 
-       xLabelFormatter = (val) => WEEK_LABELS[new Date(val).getDay()];
 
-     } else {
 
-       const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
 
-       const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0, 23, 59, 59, 999);
 
-       xMin = startOfMonth.getTime();
 
-       xMax = endOfMonth.getTime();
 
-       xSplitNumber = 4;
 
-       xLabelFormatter = (val) => `第${Math.ceil(new Date(val).getDate() / 7)}周`;
 
-     }
 
-     // 仅统计已分配项目
 
-     const assigned = this.filteredProjects.filter(p => !!p.designerName);
 
-     const designers = Array.from(new Set(assigned.map(p => p.designerName)));
 
-     const byDesigner: Record<string, typeof assigned> = {} as any;
 
-     designers.forEach(n => byDesigner[n] = [] as any);
 
-     assigned.forEach(p => byDesigner[p.designerName].push(p));
 
-     const busyCountMap: Record<string, number> = Object.fromEntries(designers.map(n => [n, byDesigner[n].length]));
 
-     const sortedDesigners = designers.sort((a, b) => {
 
-       const diff = (busyCountMap[b] || 0) - (busyCountMap[a] || 0);
 
-       return diff !== 0 ? diff : a.localeCompare(b, 'zh-CN');
 
-     });
 
-     const categories = sortedDesigners;
 
-     // 工作量等级(用于左侧小圆点颜色)
 
-     const workloadLevelMap: Record<string, 'high'|'medium'|'low'> = {} as any;
 
-     categories.forEach(name => {
 
-       const cnt = busyCountMap[name] || 0;
 
-       workloadLevelMap[name] = cnt >= 5 ? 'high' : (cnt >= 3 ? 'medium' : 'low');
 
-     });
 
-     // 条形颜色仍按项目紧急度
 
-     const colorByUrgency: Record<'high'|'medium'|'low', string> = {
 
-       high: '#ef4444',
 
-       medium: '#f59e0b',
 
-       low: '#22c55e'
 
-     } as const;
 
-     const data = assigned.flatMap(p => {
 
-       const end = new Date(p.deadline).getTime();
 
-       const baseDays = p.type === 'hard' ? 30 : 14;
 
-       const start = p.createdAt ? new Date(p.createdAt).getTime() : end - baseDays * DAY;
 
-       const yIndex = categories.indexOf(p.designerName);
 
-       if (yIndex === -1) return [] as any[];
 
-       const color = colorByUrgency[p.urgency] || '#60a5fa';
 
-       return [{
 
-         name: p.name,
 
-         value: [yIndex, start, end, p.designerName, p.urgency, p.memberType, p.currentStage],
 
-         itemStyle: { color }
 
-       }];
 
-     });
 
-     const prevOpt: any = (this.ganttChart as any).getOption ? (this.ganttChart as any).getOption() : null;
 
-     const total = categories.length || 1;
 
-     const visible = Math.min(total, 30);
 
-     const defaultEndPercent = Math.min(100, (visible / total) * 100);
 
-     const preservedStart = typeof prevOpt?.dataZoom?.[0]?.start === 'number' ? prevOpt.dataZoom[0].start : 0;
 
-     const preservedEnd = typeof prevOpt?.dataZoom?.[0]?.end === 'number' ? prevOpt.dataZoom[0].end : defaultEndPercent;
 
-     const option = {
 
-       backgroundColor: 'transparent',
 
-       tooltip: {
 
-         trigger: 'item',
 
-         formatter: (params: any) => {
 
-           const v = params.value;
 
-           const start = new Date(v[1]);
 
-           const end = new Date(v[2]);
 
-           return `项目:${params.name}<br/>设计师:${v[3]}<br/>阶段:${v[6]}<br/>起止:${start.toLocaleDateString()} ~ ${end.toLocaleDateString()}`;
 
-         }
 
-       },
 
-       grid: { left: 110, right: 64, top: 30, bottom: 30 },
 
-       xAxis: {
 
-         type: 'time',
 
-         min: xMin,
 
-         max: xMax,
 
-         splitNumber: xSplitNumber,
 
-         axisLine: { lineStyle: { color: '#e5e7eb' } },
 
-         axisLabel: { color: '#6b7280', formatter: (value: number) => xLabelFormatter(value) },
 
-         splitLine: { lineStyle: { color: '#f1f5f9' } }
 
-       },
 
-       yAxis: {
 
-         type: 'category',
 
-         data: categories,
 
-         inverse: true,
 
-         axisLabel: {
 
-           color: '#374151',
 
-           margin: 8,
 
-           formatter: (val: string) => {
 
-             const lvl = workloadLevelMap[val] || 'low';
 
-             const count = busyCountMap[val] || 0;
 
-             const text = val.length > 8 ? val.slice(0, 8) + '…' : val;
 
-             return `{${lvl}Dot|●} ${text}(${count}项)`;
 
-           },
 
-           rich: {
 
-             highDot: { color: '#ef4444' },
 
-             mediumDot: { color: '#f59e0b' },
 
-             lowDot: { color: '#22c55e' }
 
-           }
 
-         },
 
-         axisTick: { show: false },
 
-         axisLine: { lineStyle: { color: '#e5e7eb' } }
 
-       },
 
-       dataZoom: [
 
-         { type: 'slider', yAxisIndex: 0, orient: 'vertical', right: 6, start: preservedStart, end: preservedEnd, zoomLock: false },
 
-         { type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true, moveOnMouseWheel: true }
 
-       ],
 
-       series: [
 
-         {
 
-           type: 'custom',
 
-           renderItem: (params: any, api: any) => {
 
-             const categoryIndex = api.value(0);
 
-             const start = api.coord([api.value(1), categoryIndex]);
 
-             const end = api.coord([api.value(2), categoryIndex]);
 
-             const height = Math.max(api.size([0, 1])[1] * 0.5, 8);
 
-             const rectShape = echarts.graphic.clipRectByRect({
 
-               x: start[0],
 
-               y: start[1] - height / 2,
 
-               width: Math.max(end[0] - start[0], 2),
 
-               height
 
-             }, {
 
-               x: params.coordSys.x,
 
-               y: params.coordSys.y,
 
-               width: params.coordSys.width,
 
-               height: params.coordSys.height
 
-             });
 
-             return rectShape ? { type: 'rect', shape: rectShape, style: api.style() } : undefined;
 
-           },
 
-           encode: { x: [1, 2], y: 0 },
 
-           data,
 
-           itemStyle: { borderRadius: 4 },
 
-           emphasis: { focus: 'self' },
 
-           markLine: {
 
-             silent: true,
 
-             symbol: 'none',
 
-             lineStyle: { color: '#ef4444', type: 'dashed', width: 1 },
 
-             label: { formatter: '今日', color: '#ef4444', fontSize: 10, position: 'end' },
 
-             data: [ { xAxis: todayTs } ]
 
-           }
 
-         }
 
-       ]
 
-     } as any;
 
-     this.ganttChart.clear();
 
-     this.ganttChart.setOption(option, true);
 
-     this.ganttChart.resize();
 
-   }
 
-   ngOnDestroy(): void {
 
-     if (this.ganttChart) {
 
-       this.ganttChart.dispose();
 
-       this.ganttChart = null;
 
-     }
 
-     if (this.workloadChart) {
 
-       this.workloadChart.dispose();
 
-       this.workloadChart = null;
 
-     }
 
-   }
 
-   // 选择单个项目
 
-   selectProject(): void {
 
-     if (this.selectedProjectId) {
 
-       this.router.navigate(['/team-leader/project-detail', this.selectedProjectId]);
 
-     }
 
-   }
 
-   // 获取特定阶段的项目
 
-   getProjectsByStage(stageId: string): Project[] {
 
-     return this.filteredProjects.filter(project => project.currentStage === stageId);
 
-   }
 
-   // 新增:阶段到核心阶段的映射
 
-   private mapStageToCorePhase(stageId: string): 'order' | 'requirements' | 'delivery' | 'aftercare' {
 
-     // 订单创建:立项初期
 
-     if (stageId === 'pendingApproval' || stageId === 'pendingAssignment') return 'order';
 
-     // 确认需求:需求沟通 + 方案规划
 
-     if (stageId === 'requirement' || stageId === 'planning') return 'requirements';
 
-     // 交付执行:制作与评审修订过程
 
-     if (stageId === 'modeling' || stageId === 'rendering' || stageId === 'postProduction' || stageId === 'review' || stageId === 'revision') return 'delivery';
 
-     // 售后:交付完成后的跟进(当前数据以交付完成代表进入售后)
 
-     return 'aftercare';
 
-   }
 
-   // 新增:获取核心阶段的项目
 
-   getProjectsByCorePhase(coreId: string): Project[] {
 
-     return this.filteredProjects.filter(p => this.mapStageToCorePhase(p.currentStage) === coreId);
 
-   }
 
-   // 新增:获取核心阶段的项目数量
 
-   getProjectCountByCorePhase(coreId: string): number {
 
-     return this.getProjectsByCorePhase(coreId).length;
 
-   }
 
-   // 获取特定阶段的项目数量
 
-   getProjectCountByStage(stageId: string): number {
 
-     return this.getProjectsByStage(stageId).length;
 
-   }
 
-   // 待审批项目:currentStage === 'pendingApproval'
 
-   get pendingApprovalProjects(): Project[] {
 
-     const src = (this.filteredProjects && this.filteredProjects.length) ? this.filteredProjects : this.projects;
 
-     return src.filter(p => p.currentStage === 'pendingApproval');
 
-   }
 
-   // 待指派项目:currentStage === 'pendingAssignment'
 
-   get pendingAssignmentProjects(): Project[] {
 
-     const src = (this.filteredProjects && this.filteredProjects.length) ? this.filteredProjects : this.projects;
 
-     return src.filter(p => p.currentStage === 'pendingAssignment');
 
-   }
 
-   // 获取紧急程度标签
 
-   getUrgencyLabel(urgency: string): string {
 
-     const labels = {
 
-       high: '紧急',
 
-       medium: '一般',
 
-       low: '普通'
 
-     };
 
-     return labels[urgency as keyof typeof labels] || urgency;
 
-   }
 
-   // 智能推荐设计师
 
-   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(projectId: string, rating: 'excellent' | 'qualified' | 'unqualified'): void {
 
-     const project = this.projects.find(p => p.id === projectId);
 
-     if (!project) return;
 
-     project.qualityRating = rating;
 
-     if (rating === 'unqualified') {
 
-       // 不合格:回退到修改阶段
 
-       project.currentStage = 'revision';
 
-     }
 
-     this.applyFilters();
 
-     alert('质量评审已提交');
 
-   }
 
-   // 查看绩效预警(占位:跳转到团队管理)
 
-   viewPerformanceDetails(): void {
 
-     this.router.navigate(['/team-leader/team-management']);
 
-   }
 
-   // 打开负载日历(占位:跳转到团队管理)
 
-   navigateToWorkloadCalendar(): void {
 
-     this.router.navigate(['/team-leader/workload-calendar']);
 
-   }
 
-   // 查看项目详情
 
-   viewProjectDetails(projectId: string): void {
 
-     // 改为跳转到复用的项目详情(组长上下文,具备审核权限)
 
-     this.router.navigate(['/team-leader/project-detail', projectId]);
 
-   }
 
-   // 快速分配项目(增强:加入智能推荐)
 
-   quickAssignProject(projectId: string): void {
 
-     const project = this.projects.find(p => p.id === projectId);
 
-     if (!project) {
 
-       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 = confirm(message);
 
-       if (confirmAssign) {
 
-         project.designerName = recommended.name;
 
-         if (project.currentStage === 'pendingAssignment' || project.currentStage === 'pendingApproval') {
 
-           project.currentStage = 'requirement';
 
-         }
 
-         project.status = '进行中';
 
-         // 更新设计师筛选列表
 
-         this.designers = Array.from(new Set(this.projects.map(p => p.designerName).filter(n => !!n)));
 
-         this.applyFilters();
 
-         alert(`项目已${reassigning ? '重新' : ''}分配给 ${recommended.name}`);
 
-         return;
 
-       }
 
-     }
 
-     // 无推荐或用户取消,跳转到详细分配页面
 
-     // 改为跳转到复用详情(组长视图),通过 query 参数标记 assign 模式
 
-     this.router.navigate(['/team-leader/project-detail', projectId], { queryParams: { mode: 'assign' } });
 
-     }
 
-   // 导航到待办任务
 
-   navigateToTask(task: TodoTask): void {
 
-     switch (task.type) {
 
-       case 'review':
 
-         this.router.navigate(['team-leader/quality-management', task.targetId]);
 
-         break;
 
-       case 'assign':
 
-         this.router.navigate(['/team-leader/dashboard']);
 
-         break;
 
-       case 'performance':
 
-         this.router.navigate(['team-leader/team-management']);
 
-         break;
 
-     }
 
-   }
 
-   // 获取优先级标签
 
-   getPriorityLabel(priority: 'high' | 'medium' | 'low'): string {
 
-     const labels: Record<'high' | 'medium' | 'low', string> = {
 
-       'high': '紧急且重要',
 
-       'medium': '重要不紧急',
 
-       'low': '紧急不重要'
 
-     };
 
-     return labels[priority];
 
-   }
 
-   // 导航到团队管理
 
-   navigateToTeamManagement(): void {
 
-     this.router.navigate(['/team-leader/team-management']);
 
-   }
 
-   // 导航到项目评审
 
-   navigateToProjectReview(): void {
 
-     // 统一入口:跳转到项目列表/看板,而非旧评审页
 
-     this.router.navigate(['/team-leader/dashboard']);
 
-   }
 
-   // 导航到质量管理
 
-   navigateToQualityManagement(): void {
 
-     this.router.navigate(['/team-leader/quality-management']);
 
-   }
 
-   // 打开工作量预估工具(已迁移)
 
-   openWorkloadEstimator(): void {
 
-     // 工具迁移至详情页:引导前往当前选中项目详情
 
-     if (this.selectedProjectId) {
 
-       this.router.navigate(['/team-leader/project-detail', this.selectedProjectId], { queryParams: { tool: 'estimator' } });
 
-     } else {
 
-       this.router.navigate(['/team-leader/dashboard']);
 
-     }
 
-     alert('工作量预估工具已迁移至项目详情页,您可以在建模阶段之前使用该工具进行工作量计算。');
 
-   }
 
-   // 查看所有超期项目
 
-   viewAllOverdueProjects(): void {
 
-     this.filterByStatus('overdue');
 
-     this.closeAlert();
 
-   }
 
-   // 关闭提醒
 
-   closeAlert(): void {
 
-     this.showAlert = false;
 
-   }
 
-   // 维度切换(设计师/会员类型)
 
-   setWorkloadDimension(dim: 'designer' | 'member'): void {
 
-     if (this.workloadDimension !== dim) {
 
-       this.workloadDimension = dim;
 
-       this.updateWorkloadChart();
 
-     }
 
-   }
 
-   // 刷新“工作量概览”图表
 
-   private updateWorkloadChart(): void {
 
-     if (!this.workloadChartRef) { return; }
 
-     const el = this.workloadChartRef.nativeElement;
 
-     if (!el) { return; }
 
-     // 初始化实例(使用 SVG 渲染以获得更佳文本清晰度)
 
-     if (!this.workloadChart) {
 
-       this.workloadChart = echarts.init(el, null, { renderer: 'svg' });
 
-     }
 
-     const data = (this.filteredProjects && this.filteredProjects.length) ? this.filteredProjects : this.projects;
 
-     const byDesigner = this.workloadDimension === 'designer';
 
-     const groupKey = byDesigner ? 'designerName' : 'memberType';
 
-     const labelMap: Record<string, string> = { vip: 'VIP', normal: '普通' };
 
-     const groupSet = new Set<string>();
 
-     data.forEach(p => {
 
-       const val = (p as any)[groupKey] || (byDesigner ? '未分配' : '未知');
 
-       groupSet.add(val);
 
-     });
 
-     const categories = Array.from(groupSet).sort((a, b) => a.localeCompare(b, 'zh-Hans-CN'));
 
-     const count = (urg: 'high'|'medium'|'low', group: string) =>
 
-       data.filter(p => (((p as any)[groupKey] || (byDesigner ? '未分配' : '未知')) === group) && p.urgency === urg).length;
 
-     const high = categories.map(c => count('high', c));
 
-     const medium = categories.map(c => count('medium', c));
 
-     const low = categories.map(c => count('low', c));
 
-     const option = {
 
-       tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
 
-       legend: { data: ['高', '中', '低'] },
 
-       grid: { left: 12, right: 16, top: 28, bottom: 8, containLabel: true },
 
-       xAxis: { type: 'value', boundaryGap: [0, 0.01] },
 
-       yAxis: {
 
-         type: 'category',
 
-         data: categories.map(c => byDesigner ? c : (labelMap[c] || c))
 
-       },
 
-       series: [
 
-         { name: '高', type: 'bar', stack: 'workload', data: high, itemStyle: { color: '#ef4444' } },
 
-         { name: '中', type: 'bar', stack: 'workload', data: medium, itemStyle: { color: '#f59e0b' } },
 
-         { name: '低', type: 'bar', stack: 'workload', data: low, itemStyle: { color: '#10b981' } }
 
-       ]
 
-     } as any;
 
-     this.workloadChart.clear();
 
-     this.workloadChart.setOption(option, true);
 
-     this.workloadChart.resize();
 
-   }
 
-   resetStatusFilter(): void {
 
-     this.selectedStatus = 'all';
 
-     this.applyFilters();
 
-   }
 
- }
 
 
  |