|
@@ -23,14 +23,19 @@ interface Project {
|
|
|
id: string;
|
|
|
name: string;
|
|
|
type: 'soft' | 'hard';
|
|
|
+ memberType: 'vip' | 'normal';
|
|
|
designerName: string;
|
|
|
status: string;
|
|
|
expectedEndDate: Date;
|
|
|
isOverdue: boolean;
|
|
|
overdueDays: number;
|
|
|
+ dueSoon: boolean;
|
|
|
urgency: 'high' | 'medium' | 'low';
|
|
|
phases: ProjectPhase[];
|
|
|
currentStage: string; // 新增:当前项目阶段
|
|
|
+ // 新增:质量评级
|
|
|
+ qualityRating?: 'excellent' | 'qualified' | 'unqualified' | 'pending';
|
|
|
+ lastCustomerFeedback?: string;
|
|
|
}
|
|
|
|
|
|
interface TodoTask {
|
|
@@ -58,6 +63,25 @@ export class Dashboard implements OnInit {
|
|
|
showAlert: boolean = false;
|
|
|
selectedProjectId: string = '';
|
|
|
|
|
|
+ // 新增:临期项目与筛选状态
|
|
|
+ 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[] = [];
|
|
|
+
|
|
|
+ // 新增:智能推荐设计师配置
|
|
|
+ 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 },
|
|
@@ -72,6 +96,14 @@ export class Dashboard implements OnInit {
|
|
|
{ id: 'delivery', name: '交付完成', order: 10 }
|
|
|
];
|
|
|
|
|
|
+ // 新增:5大核心阶段
|
|
|
+ corePhases: ProjectStage[] = [
|
|
|
+ { id: 'preparation', name: '前期准备', order: 1 }, // 待确认、待分配
|
|
|
+ { id: 'design', name: '方案设计', order: 2 }, // 需求沟通、方案规划
|
|
|
+ { id: 'production', name: '制作执行', order: 3 }, // 建模、渲染、后期
|
|
|
+ { id: 'review', name: '评审修订', order: 4 }, // 评审、修改
|
|
|
+ { id: 'delivery', name: '交付完成', order: 5 } // 交付
|
|
|
+ ];
|
|
|
constructor(private projectService: ProjectService, private router: Router) {}
|
|
|
|
|
|
ngOnInit(): void {
|
|
@@ -86,11 +118,13 @@ export class Dashboard implements OnInit {
|
|
|
id: 'proj-001',
|
|
|
name: '现代风格客厅设计',
|
|
|
type: 'soft',
|
|
|
+ memberType: 'vip',
|
|
|
designerName: '张三',
|
|
|
status: '进行中',
|
|
|
expectedEndDate: new Date(2023, 9, 15),
|
|
|
isOverdue: true,
|
|
|
overdueDays: 2,
|
|
|
+ dueSoon: false,
|
|
|
urgency: 'high',
|
|
|
currentStage: 'rendering',
|
|
|
phases: [
|
|
@@ -104,11 +138,13 @@ export class Dashboard implements OnInit {
|
|
|
id: 'proj-002',
|
|
|
name: '北欧风格卧室设计',
|
|
|
type: 'soft',
|
|
|
+ memberType: 'normal',
|
|
|
designerName: '李四',
|
|
|
status: '进行中',
|
|
|
expectedEndDate: new Date(2023, 9, 20),
|
|
|
isOverdue: false,
|
|
|
overdueDays: 0,
|
|
|
+ dueSoon: false,
|
|
|
urgency: 'medium',
|
|
|
currentStage: 'postProduction',
|
|
|
phases: [
|
|
@@ -122,11 +158,13 @@ export class Dashboard implements OnInit {
|
|
|
id: 'proj-003',
|
|
|
name: '新中式餐厅设计',
|
|
|
type: 'hard',
|
|
|
+ memberType: 'normal',
|
|
|
designerName: '王五',
|
|
|
status: '进行中',
|
|
|
expectedEndDate: new Date(2023, 9, 25),
|
|
|
isOverdue: false,
|
|
|
overdueDays: 0,
|
|
|
+ dueSoon: false,
|
|
|
urgency: 'low',
|
|
|
currentStage: 'modeling',
|
|
|
phases: [
|
|
@@ -140,11 +178,13 @@ export class Dashboard implements OnInit {
|
|
|
id: 'proj-004',
|
|
|
name: '工业风办公室设计',
|
|
|
type: 'hard',
|
|
|
+ memberType: 'normal',
|
|
|
designerName: '赵六',
|
|
|
status: '进行中',
|
|
|
expectedEndDate: new Date(2023, 9, 10),
|
|
|
isOverdue: true,
|
|
|
overdueDays: 7,
|
|
|
+ dueSoon: false,
|
|
|
urgency: 'high',
|
|
|
currentStage: 'review',
|
|
|
phases: [
|
|
@@ -159,11 +199,13 @@ export class Dashboard implements OnInit {
|
|
|
id: 'proj-005',
|
|
|
name: '现代简约厨房设计',
|
|
|
type: 'soft',
|
|
|
+ memberType: 'normal',
|
|
|
designerName: '',
|
|
|
status: '待分配',
|
|
|
expectedEndDate: new Date(2023, 10, 5),
|
|
|
isOverdue: false,
|
|
|
overdueDays: 0,
|
|
|
+ dueSoon: false,
|
|
|
urgency: 'medium',
|
|
|
currentStage: 'pendingAssignment',
|
|
|
phases: []
|
|
@@ -172,11 +214,13 @@ export class Dashboard implements OnInit {
|
|
|
id: 'proj-006',
|
|
|
name: '日式风格书房设计',
|
|
|
type: 'hard',
|
|
|
+ memberType: 'normal',
|
|
|
designerName: '',
|
|
|
status: '待确认',
|
|
|
expectedEndDate: new Date(2023, 10, 10),
|
|
|
isOverdue: false,
|
|
|
overdueDays: 0,
|
|
|
+ dueSoon: false,
|
|
|
urgency: 'low',
|
|
|
currentStage: 'pendingApproval',
|
|
|
phases: []
|
|
@@ -185,11 +229,13 @@ export class Dashboard implements OnInit {
|
|
|
id: 'proj-007',
|
|
|
name: '轻奢风格浴室设计',
|
|
|
type: 'soft',
|
|
|
+ memberType: 'normal',
|
|
|
designerName: '钱七',
|
|
|
status: '已完成',
|
|
|
expectedEndDate: new Date(2023, 9, 5),
|
|
|
isOverdue: false,
|
|
|
overdueDays: 0,
|
|
|
+ dueSoon: false,
|
|
|
urgency: 'medium',
|
|
|
currentStage: 'delivery',
|
|
|
phases: []
|
|
@@ -225,16 +271,21 @@ export class Dashboard implements OnInit {
|
|
|
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,
|
|
|
isOverdue,
|
|
|
overdueDays,
|
|
|
+ dueSoon,
|
|
|
urgency,
|
|
|
currentStage,
|
|
|
phases: []
|
|
@@ -242,10 +293,14 @@ export class Dashboard implements OnInit {
|
|
|
}
|
|
|
// ===== 示例数据生成结束 =====
|
|
|
|
|
|
- // 筛选超期项目
|
|
|
+ // 筛选超期与临期项目
|
|
|
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;
|
|
@@ -316,54 +371,118 @@ export class Dashboard implements OnInit {
|
|
|
// 筛选项目类型
|
|
|
filterProjects(event: Event): void {
|
|
|
const target = event.target as HTMLSelectElement;
|
|
|
- const filterValue = target.value;
|
|
|
-
|
|
|
- if (filterValue === 'all') {
|
|
|
- this.filteredProjects = [...this.projects];
|
|
|
- } else {
|
|
|
- this.filteredProjects = this.projects.filter(project => project.type === filterValue);
|
|
|
- }
|
|
|
+ this.selectedType = (target && target.value ? target.value : 'all') as any;
|
|
|
+ this.applyFilters();
|
|
|
}
|
|
|
|
|
|
// 筛选紧急程度
|
|
|
filterByUrgency(event: Event): void {
|
|
|
const target = event.target as HTMLSelectElement;
|
|
|
- const filterValue = target.value;
|
|
|
-
|
|
|
- if (filterValue === 'all') {
|
|
|
- this.filteredProjects = [...this.projects];
|
|
|
- } else {
|
|
|
- this.filteredProjects = this.projects.filter(project => project.urgency === filterValue);
|
|
|
- }
|
|
|
+ this.selectedUrgency = (target && target.value ? target.value : 'all') as any;
|
|
|
+ this.applyFilters();
|
|
|
}
|
|
|
|
|
|
// 筛选项目状态
|
|
|
filterByStatus(status: string): void {
|
|
|
- if (status === 'all') {
|
|
|
- this.filteredProjects = [...this.projects];
|
|
|
- } else if (status === 'overdue') {
|
|
|
- this.filteredProjects = this.overdueProjects;
|
|
|
- } else if (status === 'pendingApproval') {
|
|
|
- this.filteredProjects = this.projects.filter(project => project.currentStage === 'pendingApproval');
|
|
|
- } else if (status === 'pendingAssignment') {
|
|
|
- this.filteredProjects = this.projects.filter(project => project.currentStage === 'pendingAssignment');
|
|
|
- } else if (status === 'progress') {
|
|
|
- this.filteredProjects = this.projects.filter(project =>
|
|
|
- ['requirement', 'planning', 'modeling', 'rendering', 'postProduction', 'review', 'revision'].includes(project.currentStage)
|
|
|
- );
|
|
|
- } else if (status === 'completed') {
|
|
|
- this.filteredProjects = this.projects.filter(project => project.currentStage === 'delivery');
|
|
|
- }
|
|
|
+ this.selectedStatus = (status && status.length ? status : 'all') as any;
|
|
|
+ this.applyFilters();
|
|
|
}
|
|
|
|
|
|
// 处理状态筛选下拉框变化
|
|
|
onStatusChange(event: Event): void {
|
|
|
const target = event.target as HTMLSelectElement;
|
|
|
- if (target && target.value) {
|
|
|
- this.filterByStatus(target.value);
|
|
|
+ this.selectedStatus = (target && target.value ? target.value : 'all') as any;
|
|
|
+ this.applyFilters();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 时间窗快捷筛选(供UI按钮触发)
|
|
|
+ filterByTimeWindow(timeWindow: 'all' | 'today' | 'threeDays' | 'sevenDays'): void {
|
|
|
+ this.selectedTimeWindow = timeWindow;
|
|
|
+ this.applyFilters();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一筛选
|
|
|
+ private applyFilters(): void {
|
|
|
+ let result = [...this.projects];
|
|
|
+
|
|
|
+ // 类型筛选
|
|
|
+ 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.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.expectedEndDate);
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增:设计师筛选
|
|
|
+ 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 target = event.target as HTMLSelectElement;
|
|
|
+ this.selectedMemberType = (target && target.value ? target.value : 'all') as any;
|
|
|
+ this.applyFilters();
|
|
|
+ }
|
|
|
+
|
|
|
// 选择单个项目
|
|
|
selectProject(): void {
|
|
|
if (this.selectedProjectId) {
|
|
@@ -376,6 +495,25 @@ export class Dashboard implements OnInit {
|
|
|
return this.filteredProjects.filter(project => project.currentStage === stageId);
|
|
|
}
|
|
|
|
|
|
+ // 新增:阶段到核心阶段的映射
|
|
|
+ private mapStageToCorePhase(stageId: string): 'preparation' | 'design' | 'production' | 'review' | 'delivery' {
|
|
|
+ if (stageId === 'pendingApproval' || stageId === 'pendingAssignment') return 'preparation';
|
|
|
+ if (stageId === 'requirement' || stageId === 'planning') return 'design';
|
|
|
+ if (stageId === 'modeling' || stageId === 'rendering' || stageId === 'postProduction') return 'production';
|
|
|
+ if (stageId === 'review' || stageId === 'revision') return 'review';
|
|
|
+ return 'delivery';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增:获取核心阶段的项目
|
|
|
+ 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;
|
|
@@ -391,13 +529,75 @@ export class Dashboard implements OnInit {
|
|
|
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/team-management']);
|
|
|
+ }
|
|
|
+
|
|
|
// 查看项目详情
|
|
|
viewProjectDetails(projectId: string): void {
|
|
|
this.router.navigate(['/team-leader/project-review', 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 无推荐或用户取消,跳转到详细分配页面
|
|
|
this.router.navigate(['/team-leader/project-review', projectId, 'assign']);
|
|
|
}
|
|
|
|