|
|
@@ -10,6 +10,8 @@ import { WxworkAuth } from 'fmode-ng/core';
|
|
|
import { DesignerTaskService } from '../../../services/designer-task.service';
|
|
|
import { LeaveService, LeaveApplication } from '../../../services/leave.service';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
+// 🆕 导入项目负载时间轴组件
|
|
|
+import { ProjectTimelineComponent } from '../../team-leader/project-timeline';
|
|
|
|
|
|
interface ShiftTask {
|
|
|
id: string;
|
|
|
@@ -40,7 +42,7 @@ interface CurrentUser {
|
|
|
@Component({
|
|
|
selector: 'app-dashboard',
|
|
|
standalone: true,
|
|
|
- imports: [CommonModule, RouterModule, FormsModule, SkillRadarComponent, PersonalBoard],
|
|
|
+ imports: [CommonModule, RouterModule, FormsModule, SkillRadarComponent, PersonalBoard, ProjectTimelineComponent],
|
|
|
templateUrl: './dashboard.html',
|
|
|
styleUrl: './dashboard.scss'
|
|
|
})
|
|
|
@@ -89,6 +91,10 @@ export class Dashboard implements OnInit {
|
|
|
// 新增:顶部导航栏用户信息
|
|
|
displayUser: CurrentUser | null = null;
|
|
|
currentDate: Date = new Date();
|
|
|
+
|
|
|
+ // 🆕 项目负载时间轴数据
|
|
|
+ projectTimelineData: any[] = [];
|
|
|
+ designerWorkloadMap: Map<string, any[]> = new Map(); // 设计师工作量映射
|
|
|
|
|
|
constructor(
|
|
|
private projectService: ProjectService,
|
|
|
@@ -198,7 +204,8 @@ export class Dashboard implements OnInit {
|
|
|
this.loadRealTasks(), // 使用真实数据
|
|
|
this.loadShiftTasks(),
|
|
|
this.calculateWorkloadPercentage(),
|
|
|
- this.loadProjectTimeline()
|
|
|
+ this.loadProjectTimeline(),
|
|
|
+ this.loadDesignerProjects() // 🆕 加载项目负载数据
|
|
|
]);
|
|
|
console.log('✅ 设计师仪表板数据加载完成');
|
|
|
} catch (error) {
|
|
|
@@ -217,9 +224,6 @@ export class Dashboard implements OnInit {
|
|
|
throw new Error('未找到当前Profile');
|
|
|
}
|
|
|
|
|
|
- console.log('🔍 开始加载设计师任务');
|
|
|
- console.log('📋 当前 Profile ID:', this.currentProfile.id);
|
|
|
- console.log('📋 当前 Profile 对象:', this.currentProfile);
|
|
|
|
|
|
const designerTasks = await this.taskService.getMyTasks(this.currentProfile.id);
|
|
|
|
|
|
@@ -259,7 +263,6 @@ export class Dashboard implements OnInit {
|
|
|
// 启动倒计时
|
|
|
this.startCountdowns();
|
|
|
|
|
|
- console.log(`✅ 成功加载 ${this.tasks.length} 个真实任务`);
|
|
|
} catch (error) {
|
|
|
console.error('❌ 加载真实任务失败:', error);
|
|
|
throw error;
|
|
|
@@ -326,7 +329,6 @@ export class Dashboard implements OnInit {
|
|
|
};
|
|
|
});
|
|
|
|
|
|
- console.log(`✅ 成功加载 ${this.pendingFeedbacks.length} 个待处理反馈`);
|
|
|
} catch (error) {
|
|
|
console.error('❌ 加载待处理反馈失败:', error);
|
|
|
this.pendingFeedbacks = [];
|
|
|
@@ -824,7 +826,6 @@ export class Dashboard implements OnInit {
|
|
|
this.leaveApplications = await this.leaveService.getMyLeaveRecords(
|
|
|
this.currentProfile.id
|
|
|
);
|
|
|
- console.log(`✅ 成功加载 ${this.leaveApplications.length} 条请假记录`);
|
|
|
} catch (error) {
|
|
|
console.error('❌ 加载请假记录失败:', error);
|
|
|
}
|
|
|
@@ -880,7 +881,6 @@ export class Dashboard implements OnInit {
|
|
|
// 检查Profile中的surveyCompleted字段
|
|
|
this.surveyCompleted = this.currentProfile.get('surveyCompleted') || false;
|
|
|
|
|
|
- console.log(`📋 问卷完成状态: ${this.surveyCompleted}`);
|
|
|
|
|
|
// 如果问卷未完成,显示引导弹窗
|
|
|
if (!this.surveyCompleted) {
|
|
|
@@ -1188,5 +1188,274 @@ export class Dashboard implements OnInit {
|
|
|
console.warn('⚠️ 头像加载失败,使用默认头像');
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 🆕 加载设计师项目数据(仅当前登录设计师)
|
|
|
+ */
|
|
|
+ async loadDesignerProjects(): Promise<void> {
|
|
|
+ try {
|
|
|
+ if (!this.currentProfile) {
|
|
|
+ console.error('❌ currentProfile 为空,无法加载项目数据');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
|
|
|
+ const cid = this.cid || localStorage.getItem('company') || 'cDL6R1hgSi';
|
|
|
+
|
|
|
+
|
|
|
+ // 先查询当前公司的所有项目
|
|
|
+ const projectQuery = new Parse.Query('Project');
|
|
|
+ projectQuery.equalTo('company', cid);
|
|
|
+ projectQuery.notEqualTo('isDeleted', true);
|
|
|
+
|
|
|
+ // 查询当前设计师参与的 ProjectTeam 记录
|
|
|
+ const teamQuery = new Parse.Query('ProjectTeam');
|
|
|
+ teamQuery.matchesQuery('project', projectQuery);
|
|
|
+ teamQuery.equalTo('profile', this.currentProfile.toPointer());
|
|
|
+ teamQuery.notEqualTo('isDeleted', true);
|
|
|
+ teamQuery.include('project');
|
|
|
+ teamQuery.include('profile');
|
|
|
+ teamQuery.limit(1000);
|
|
|
+
|
|
|
+ const teamRecords = await teamQuery.find();
|
|
|
+
|
|
|
+
|
|
|
+ // 构建项目数据
|
|
|
+ this.designerWorkloadMap.clear();
|
|
|
+ const designerName = this.displayUser?.name || this.currentProfile.get('name') || '当前设计师';
|
|
|
+
|
|
|
+ const projectsData: any[] = [];
|
|
|
+
|
|
|
+ teamRecords.forEach((record: any) => {
|
|
|
+ const project = record.get('project');
|
|
|
+
|
|
|
+ if (!project) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取项目信息
|
|
|
+ const createdAtValue = project.get('createdAt');
|
|
|
+ const updatedAtValue = project.get('updatedAt');
|
|
|
+ const deadlineValue = project.get('deadline');
|
|
|
+ const deliveryDateValue = project.get('deliveryDate');
|
|
|
+ const expectedDeliveryDateValue = project.get('expectedDeliveryDate');
|
|
|
+ const demodayValue = project.get('demoday');
|
|
|
+
|
|
|
+ let finalCreatedAt = createdAtValue || updatedAtValue;
|
|
|
+ if (!finalCreatedAt && project.createdAt) {
|
|
|
+ finalCreatedAt = project.createdAt;
|
|
|
+ }
|
|
|
+ if (!finalCreatedAt && project.updatedAt) {
|
|
|
+ finalCreatedAt = project.updatedAt;
|
|
|
+ }
|
|
|
+
|
|
|
+ const projectData = {
|
|
|
+ id: project.id,
|
|
|
+ name: project.get('title') || '未命名项目',
|
|
|
+ status: project.get('status') || '进行中',
|
|
|
+ currentStage: project.get('currentStage') || '建模阶段',
|
|
|
+ deadline: deadlineValue || deliveryDateValue || expectedDeliveryDateValue,
|
|
|
+ demoday: demodayValue,
|
|
|
+ createdAt: finalCreatedAt,
|
|
|
+ designerName: designerName,
|
|
|
+ data: project.get('data') || {}
|
|
|
+ };
|
|
|
+
|
|
|
+ projectsData.push(projectData);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.designerWorkloadMap.set(designerName, projectsData);
|
|
|
+
|
|
|
+
|
|
|
+ // 转换为时间轴数据
|
|
|
+ this.convertToProjectTimeline();
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 加载设计师项目数据失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 🆕 将项目数据转换为时间轴格式
|
|
|
+ */
|
|
|
+ private convertToProjectTimeline(): void {
|
|
|
+ const designerName = this.displayUser?.name || this.currentProfile?.get('name') || '当前设计师';
|
|
|
+ const projects = this.designerWorkloadMap.get(designerName) || [];
|
|
|
+
|
|
|
+
|
|
|
+ this.projectTimelineData = projects.map((project, index) => {
|
|
|
+ const now = new Date();
|
|
|
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
|
+
|
|
|
+ // 🎨 测试专用:精心设计项目分布,展示不同负载状态(与组长端保持一致)
|
|
|
+ // 目标效果:
|
|
|
+ // 第1天(今天)=1项目(忙碌🔵), 第2天(明天)=0项目(空闲🟢), 第3天=3项目(超负荷🔴),
|
|
|
+ // 第4天=2项目(忙碌🔵), 第5天=1项目(忙碌🔵), 第6天=4项目(超负荷🔴), 第7天=2项目(忙碌🔵)
|
|
|
+
|
|
|
+ // 使用项目索引映射到具体天数,跳过第2天以实现0项目效果
|
|
|
+ const dayMapping = [
|
|
|
+ 1, // 项目0 → 第1天
|
|
|
+ 3, 3, 3, // 项目1,2,3 → 第3天(3个项目,超负荷)
|
|
|
+ 4, 4, // 项目4,5 → 第4天(2个项目)
|
|
|
+ 5, // 项目6 → 第5天(1个项目)
|
|
|
+ 6, 6, 6, 6, // 项目7,8,9,10 → 第6天(4个项目,超负荷)
|
|
|
+ 7, 7 // 项目11,12 → 第7天(2个项目)
|
|
|
+ ];
|
|
|
+
|
|
|
+ let dayOffset: number;
|
|
|
+
|
|
|
+ if (index < dayMapping.length) {
|
|
|
+ dayOffset = dayMapping[index];
|
|
|
+ } else {
|
|
|
+ // 超出13个项目后,分配到后续天数
|
|
|
+ dayOffset = 7 + ((index - dayMapping.length) % 7) + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ const adjustedEndDate = new Date(today.getTime() + dayOffset * 24 * 60 * 60 * 1000);
|
|
|
+
|
|
|
+
|
|
|
+ // 项目开始时间:交付前3-7天
|
|
|
+ const projectDuration = 3 + (index % 5); // 3-7天的项目周期
|
|
|
+ const adjustedStartDate = new Date(adjustedEndDate.getTime() - projectDuration * 24 * 60 * 60 * 1000);
|
|
|
+
|
|
|
+ // 🆕 小图对图时间:优先使用真实的 demoday,否则默认为交付前1-2天
|
|
|
+ let adjustedReviewDate: Date;
|
|
|
+ if (project.demoday && project.demoday instanceof Date) {
|
|
|
+ // 使用真实的小图对图日期
|
|
|
+ adjustedReviewDate = project.demoday;
|
|
|
+ } else {
|
|
|
+ // 默认计算:交付前1-2天
|
|
|
+ const reviewDaysBefore = 1 + (index % 2);
|
|
|
+ adjustedReviewDate = new Date(adjustedEndDate.getTime() - reviewDaysBefore * 24 * 60 * 60 * 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算项目状态
|
|
|
+ const daysUntilDeadline = Math.ceil((adjustedEndDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
|
+
|
|
|
+ let status: 'normal' | 'warning' | 'urgent' | 'overdue' = 'normal';
|
|
|
+ if (daysUntilDeadline < 0) {
|
|
|
+ status = 'overdue';
|
|
|
+ } else if (daysUntilDeadline <= 1) {
|
|
|
+ status = 'urgent';
|
|
|
+ } else if (daysUntilDeadline <= 3) {
|
|
|
+ status = 'warning';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 🆕 生成阶段截止时间数据(从交付日期往前推,每个阶段1天)
|
|
|
+ let phaseDeadlines = project.data?.phaseDeadlines;
|
|
|
+
|
|
|
+ // 如果项目没有阶段数据,动态生成(用于演示效果)
|
|
|
+ if (!phaseDeadlines) {
|
|
|
+ // ✅ 关键修复:从交付日期往前推算各阶段截止时间
|
|
|
+ const deliveryTime = adjustedEndDate.getTime();
|
|
|
+
|
|
|
+ // 后期截止 = 交付日期
|
|
|
+ const postProcessingDeadline = new Date(deliveryTime);
|
|
|
+ // 渲染截止 = 交付日期 - 1天
|
|
|
+ const renderingDeadline = new Date(deliveryTime - 1 * 24 * 60 * 60 * 1000);
|
|
|
+ // 软装截止 = 交付日期 - 2天
|
|
|
+ const softDecorDeadline = new Date(deliveryTime - 2 * 24 * 60 * 60 * 1000);
|
|
|
+ // 建模截止 = 交付日期 - 3天
|
|
|
+ const modelingDeadline = new Date(deliveryTime - 3 * 24 * 60 * 60 * 1000);
|
|
|
+
|
|
|
+ phaseDeadlines = {
|
|
|
+ modeling: {
|
|
|
+ startDate: adjustedStartDate,
|
|
|
+ deadline: modelingDeadline,
|
|
|
+ estimatedDays: 1,
|
|
|
+ status: now.getTime() >= modelingDeadline.getTime() && now.getTime() < softDecorDeadline.getTime() ? 'in_progress' :
|
|
|
+ now.getTime() >= softDecorDeadline.getTime() ? 'completed' : 'not_started',
|
|
|
+ priority: 'high' as 'high' | 'medium' | 'low'
|
|
|
+ },
|
|
|
+ softDecor: {
|
|
|
+ startDate: modelingDeadline,
|
|
|
+ deadline: softDecorDeadline,
|
|
|
+ estimatedDays: 1,
|
|
|
+ status: now.getTime() >= softDecorDeadline.getTime() && now.getTime() < renderingDeadline.getTime() ? 'in_progress' :
|
|
|
+ now.getTime() >= renderingDeadline.getTime() ? 'completed' : 'not_started',
|
|
|
+ priority: 'medium' as 'high' | 'medium' | 'low'
|
|
|
+ },
|
|
|
+ rendering: {
|
|
|
+ startDate: softDecorDeadline,
|
|
|
+ deadline: renderingDeadline,
|
|
|
+ estimatedDays: 1,
|
|
|
+ status: now.getTime() >= renderingDeadline.getTime() && now.getTime() < postProcessingDeadline.getTime() ? 'in_progress' :
|
|
|
+ now.getTime() >= postProcessingDeadline.getTime() ? 'completed' : 'not_started',
|
|
|
+ priority: 'medium' as 'high' | 'medium' | 'low'
|
|
|
+ },
|
|
|
+ postProcessing: {
|
|
|
+ startDate: renderingDeadline,
|
|
|
+ deadline: postProcessingDeadline,
|
|
|
+ estimatedDays: 1,
|
|
|
+ status: now.getTime() >= postProcessingDeadline.getTime() ? 'completed' :
|
|
|
+ now.getTime() >= renderingDeadline.getTime() ? 'in_progress' : 'not_started',
|
|
|
+ priority: 'low' as 'high' | 'medium' | 'low'
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 映射阶段
|
|
|
+ const stageMap: Record<string, 'plan' | 'model' | 'decoration' | 'render' | 'delivery'> = {
|
|
|
+ '方案设计': 'plan',
|
|
|
+ '方案规划': 'plan',
|
|
|
+ '建模': 'model',
|
|
|
+ '建模阶段': 'model',
|
|
|
+ '软装': 'decoration',
|
|
|
+ '软装设计': 'decoration',
|
|
|
+ '渲染': 'render',
|
|
|
+ '渲染阶段': 'render',
|
|
|
+ '后期': 'render',
|
|
|
+ '交付': 'delivery',
|
|
|
+ '已完成': 'delivery'
|
|
|
+ };
|
|
|
+ const currentStageEnum = stageMap[project.currentStage || '建模阶段'] || 'model';
|
|
|
+ const stageName = project.currentStage || '建模阶段';
|
|
|
+
|
|
|
+ // 计算阶段进度
|
|
|
+ const totalDuration = adjustedEndDate.getTime() - adjustedStartDate.getTime();
|
|
|
+ const elapsed = now.getTime() - adjustedStartDate.getTime();
|
|
|
+ const stageProgress = totalDuration > 0 ? Math.min(100, Math.max(0, (elapsed / totalDuration) * 100)) : 50;
|
|
|
+
|
|
|
+ // 检查是否停滞
|
|
|
+ const isStalled = false; // 调整后的项目都是进行中
|
|
|
+ const stalledDays = 0;
|
|
|
+
|
|
|
+ // 催办次数
|
|
|
+ const urgentCount = status === 'overdue' ? 2 : status === 'urgent' ? 1 : 0;
|
|
|
+
|
|
|
+ // 优先级
|
|
|
+ let priority: 'low' | 'medium' | 'high' | 'critical' = 'medium';
|
|
|
+ if (status === 'overdue') {
|
|
|
+ priority = 'critical';
|
|
|
+ } else if (status === 'urgent') {
|
|
|
+ priority = 'high';
|
|
|
+ } else if (status === 'warning') {
|
|
|
+ priority = 'medium';
|
|
|
+ } else {
|
|
|
+ priority = 'low';
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ projectId: project.id,
|
|
|
+ projectName: project.name,
|
|
|
+ designerId: this.currentProfile?.id || '',
|
|
|
+ designerName: designerName,
|
|
|
+ startDate: adjustedStartDate,
|
|
|
+ endDate: adjustedEndDate,
|
|
|
+ deliveryDate: adjustedEndDate,
|
|
|
+ reviewDate: adjustedReviewDate,
|
|
|
+ currentStage: currentStageEnum,
|
|
|
+ stageName: stageName,
|
|
|
+ stageProgress: stageProgress,
|
|
|
+ status: status,
|
|
|
+ isStalled: isStalled,
|
|
|
+ stalledDays: stalledDays,
|
|
|
+ urgentCount: urgentCount,
|
|
|
+ priority: priority,
|
|
|
+ phaseDeadlines: phaseDeadlines
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|