| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- import { Component, OnInit, OnDestroy } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { RouterModule } from '@angular/router';
- import { ProjectService } from '../../../services/project.service';
- import { Task } from '../../../models/project.model';
- import { SkillRadarComponent } from './skill-radar/skill-radar.component';
- import { PersonalBoard } from '../personal-board/personal-board';
- import { FmodeQuery, FmodeObject, FmodeUser } from 'fmode-ng/core';
- import { WxworkAuth } from 'fmode-ng/core';
- interface ShiftTask {
- id: string;
- projectId: string;
- projectName: string;
- taskDescription: string;
- priority: '高' | '中' | '低';
- shiftDate: string;
- status: '待处理' | '处理中' | '已完成';
- }
- interface ProjectTimelineItem {
- id: string;
- name: string;
- deadline: string;
- status: string;
- }
- @Component({
- selector: 'app-dashboard',
- standalone: true,
- imports: [CommonModule, RouterModule, SkillRadarComponent, PersonalBoard],
- templateUrl: './dashboard.html',
- styleUrl: './dashboard.scss'
- })
- export class Dashboard implements OnInit {
- // 视图管理
- activeDashboard: 'main' | 'skills' | 'personal' = 'main';
- // 新增:工作台视图模式(卡片/列表)
- viewMode: 'card' | 'list' = 'list';
-
- tasks: Task[] = [];
- overdueTasks: Task[] = [];
- urgentTasks: Task[] = [];
- pendingFeedbacks: {task: Task, feedback: any}[] = [];
- reminderMessage: string = '';
- feedbackProjectId: string = '';
- countdowns: Map<string, string> = new Map();
-
- // 代班信息相关属性
- shiftTasks: ShiftTask[] = [];
-
- // 个人项目饱和度相关属性
- workloadPercentage: number = 0;
- projectTimeline: ProjectTimelineItem[] = [];
- private wxAuth: WxworkAuth | null = null;
- private currentUser: FmodeUser | null = null;
- constructor(private projectService: ProjectService) {
- this.initAuth();
- }
- // 初始化企业微信认证
- private initAuth(): void {
- try {
- this.wxAuth = new WxworkAuth({
- cid: 'cDL6R1hgSi' // 公司帐套ID
- });
- console.log('✅ 设计师仪表板企业微信认证初始化成功');
- } catch (error) {
- console.error('❌ 设计师仪表板企业微信认证初始化失败:', error);
- }
- }
- async ngOnInit(): Promise<void> {
- await this.authenticateAndLoadData();
- }
- // 认证并加载数据
- private async authenticateAndLoadData(): Promise<void> {
- try {
- // 执行企业微信认证和登录
- const { user } = await this.wxAuth!.authenticateAndLogin();
- this.currentUser = user;
- if (user) {
- console.log('✅ 设计师登录成功:', user.get('username'));
- await this.loadDashboardData();
- } else {
- console.error('❌ 设计师登录失败');
- }
- } catch (error) {
- console.error('❌ 设计师认证过程出错:', error);
- // 降级到模拟数据
- this.loadMockData();
- }
- }
- // 加载仪表板数据
- private async loadDashboardData(): Promise<void> {
- try {
- await Promise.all([
- this.loadTasks(),
- this.loadShiftTasks(),
- this.calculateWorkloadPercentage(),
- this.loadProjectTimeline()
- ]);
- console.log('✅ 设计师仪表板数据加载完成');
- } catch (error) {
- console.error('❌ 设计师仪表板数据加载失败:', error);
- throw error;
- }
- }
- // 降级到模拟数据
- private loadMockData(): void {
- console.warn('⚠️ 使用模拟数据');
- this.loadTasks();
- this.loadShiftTasks();
- this.calculateWorkloadPercentage();
- this.loadProjectTimeline();
- }
-
- // 切换视图方法
- switchDashboard(view: 'main' | 'skills' | 'personal'): void {
- this.activeDashboard = view;
- }
-
- // 新增:切换卡片/列表视图
- toggleView(): void {
- this.viewMode = this.viewMode === 'card' ? 'list' : 'card';
- }
-
- // 获取前N个任务的方法
- getTopTasks(count: number): Task[] {
- // 过滤掉紧急任务和超期任务
- const regularTasks = this.tasks.filter(task =>
- !this.urgentTasks.some(urgent => urgent.id === task.id) &&
- !this.overdueTasks.some(overdue => overdue.id === task.id)
- );
-
- // 返回指定数量的任务
- return regularTasks.slice(0, count);
- }
-
- // 获取工作量颜色的方法
- getWorkloadColor(): string {
- if (this.workloadPercentage >= 80) {
- return '#ff4560'; // 红色
- } else if (this.workloadPercentage >= 50) {
- return '#ffa726'; // 橙色
- } else {
- return '#66bb6a'; // 绿色
- }
- }
-
- // 获取工作量状态的方法
- getWorkloadStatus(): string {
- if (this.workloadPercentage >= 80) {
- return '工作量饱和';
- } else if (this.workloadPercentage >= 50) {
- return '工作量适中';
- } else if (this.workloadPercentage > 0) {
- return '工作量轻松';
- } else {
- return '暂无工作任务';
- }
- }
- loadTasks(): void {
- this.projectService.getTasks().subscribe(tasks => {
- // 按阶段优先级排序:建模 > 渲染 > 对图 > 反馈处理 > 后期 > 其他
- this.tasks = tasks.sort((a, b) => {
- const stagePriority: Record<string, number> = {
- '建模': 5,
- '渲染': 4,
- '对图': 3,
- '反馈处理': 2,
- '后期': 1,
- '投诉处理': 0
- };
-
- const priorityA = stagePriority[a.stage] || 0;
- const priorityB = stagePriority[b.stage] || 0;
-
- if (priorityA !== priorityB) {
- return priorityB - priorityA;
- }
-
- // 优先级相同时,按截止日期排序
- return a.deadline.getTime() - b.deadline.getTime();
- });
-
- // 筛选超期任务
- this.overdueTasks = this.tasks.filter(task => task.isOverdue);
-
- // 筛选紧急任务(渲染超时预警,交付前3小时/1小时)
- this.urgentTasks = this.tasks.filter(task => {
- const now = new Date();
- const diffHours = (task.deadline.getTime() - now.getTime()) / (1000 * 60 * 60);
- return diffHours <= 3 && diffHours > 0 && task.stage === '渲染';
- });
-
- // 设置反馈项目ID
- if (this.overdueTasks.length > 0) {
- this.feedbackProjectId = this.overdueTasks[0].projectId;
- }
-
- // 加载待处理反馈
- this.loadPendingFeedbacks();
-
- // 启动倒计时
- this.startCountdowns();
- });
- }
-
- loadPendingFeedbacks(): void {
- this.pendingFeedbacks = [];
-
- // 模拟加载待处理反馈数据
- this.tasks.forEach(task => {
- // 使用模拟数据代替API调用
- const mockFeedbacks = [
- {
- id: 'fb-' + task.id,
- projectId: task.projectId,
- content: '客户对色彩不满意,需要调整',
- isSatisfied: false,
- problemLocation: '色彩',
- expectedEffect: '更明亮的色调',
- referenceCase: '无',
- status: '待处理' as const,
- createdAt: new Date(Date.now() - 30 * 60 * 1000) // 30分钟前
- },
- {
- id: 'fb-' + task.id + '-2',
- projectId: task.projectId,
- content: '家具款式需要调整',
- isSatisfied: false,
- problemLocation: '家具',
- expectedEffect: '更现代的款式',
- referenceCase: '无',
- status: '待处理' as const,
- createdAt: new Date(Date.now() - 45 * 60 * 1000) // 45分钟前
- }
- ];
-
- const pending = mockFeedbacks.filter(feedback =>
- feedback.status === '待处理' &&
- !feedback.isSatisfied
- );
-
- if (pending.length > 0) {
- this.pendingFeedbacks.push({task, feedback: pending[0]});
- }
- });
- }
-
- startCountdowns(): void {
- // 清除之前的定时器
- this.countdowns.clear();
-
- // 为所有任务启动倒计时,确保列表视图也有剩余时间显示
- this.tasks.forEach(task => {
- this.updateCountdown(task.id, task.deadline);
- });
-
- // 定期更新倒计时
- setInterval(() => {
- this.tasks.forEach(task => {
- this.updateCountdown(task.id, task.deadline);
- });
- }, 60000); // 每分钟更新一次
- }
-
- updateCountdown(taskId: string, deadline: Date): void {
- const now = new Date();
- const diffMs = deadline.getTime() - now.getTime();
-
- if (diffMs <= 0) {
- this.countdowns.set(taskId, '已超期');
- return;
- }
-
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
-
- if (diffHours > 0) {
- this.countdowns.set(taskId, `${diffHours}小时${diffMinutes}分钟`);
- } else {
- this.countdowns.set(taskId, `${diffMinutes}分钟`);
- }
- }
-
- getTaskCountdown(taskId: string): string {
- return this.countdowns.get(taskId) || '';
- }
-
- // 新增:列表视图专用剩余时间格式化(若未在countdowns中,直接计算)
- getListTimeLeft(task: Task): string {
- const cached = this.getTaskCountdown(task.id);
- if (cached) return cached;
- const now = new Date();
- const diffMs = task.deadline.getTime() - now.getTime();
- if (diffMs <= 0) return '已超期';
- const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
- const diffHours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
- if (diffDays > 0) return `${diffDays}天${diffHours}小时`;
- const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
- if (diffHours > 0) return `${diffHours}小时${diffMinutes}分钟`;
- return `${diffMinutes}分钟`;
- }
- // 新增:按紧急度排序
- getTasksSortedByUrgency(): Task[] {
- return this.tasks
- .filter(t => !t.isCompleted)
- .slice()
- .sort((a, b) => this.getUrgencyScore(b) - this.getUrgencyScore(a));
- }
- // 新增:紧急度评分,数值越大越紧急
- private getUrgencyScore(task: Task): number {
- if (task.isOverdue) return 10000;
- const now = new Date().getTime();
- const hoursLeft = (task.deadline.getTime() - now) / (1000 * 60 * 60);
- let base = 0;
- if (hoursLeft <= 3) base = 9000;
- else if (hoursLeft <= 24) base = 7000;
- else if (hoursLeft <= 72) base = 4000;
- else base = 1000;
- // 渲染阶段适度提高权重
- const stageBoost = task.stage === '渲染' ? 300 : 0;
- return base + stageBoost;
- }
- // 新增:紧急度标签
- getUrgencyLevel(task: Task): '超期' | '高' | '中' | '低' {
- if (task.isOverdue) return '超期';
- const now = new Date().getTime();
- const hoursLeft = (task.deadline.getTime() - now) / (1000 * 60 * 60);
- if (hoursLeft <= 24) return '高';
- if (hoursLeft <= 72) return '中';
- return '低';
- }
- // 新增:紧急度样式类
- getUrgencyClass(task: Task): string {
- const level = this.getUrgencyLevel(task);
- switch (level) {
- case '超期':
- return 'urgency-overdue';
- case '高':
- return 'urgency-high';
- case '中':
- return 'urgency-medium';
- default:
- return 'urgency-low';
- }
- }
-
- getTaskStageProgress(taskId: string): number {
- const task = this.tasks.find(t => t.id === taskId);
- if (!task) return 0;
- // 为不同阶段设置固定的模拟进度值
- const stageProgressMap: Record<string, number> = {
- '建模': 22,
- '渲染': 23,
- '对图': 50,
- '反馈处理': 80,
- '后期': 75,
- '投诉处理': 100
- };
- // 对于渲染任务,如果有实际的渲染进度数据,使用它
- if (task.stage === '渲染') {
- // 在实际应用中,这里会从服务中获取真实的进度
- // this.projectService.getRenderProgress(task.projectId).subscribe(progress => {
- // if (progress) {
- // return progress.completionRate;
- // }
- // });
- }
- return stageProgressMap[task.stage] || 0;
- }
- markTaskAsCompleted(taskId: string): void {
- this.projectService.markTaskAsCompleted(taskId).subscribe(() => {
- this.loadTasks(); // 重新加载任务列表
- });
- }
-
- handleFeedback(taskId: string): void {
- const task = this.tasks.find(t => t.id === taskId);
- if (task) {
- // 跳转到项目详情的反馈处理页面
- window.location.href = `/designer/project-detail/${task.projectId}#feedback`;
- }
- }
- generateReminderMessage(): void {
- this.projectService.generateReminderMessage('overdue').subscribe(message => {
- this.reminderMessage = message;
- });
- }
- clearReminder(): void {
- this.reminderMessage = '';
- }
- // 代班任务相关方法
- loadShiftTasks(): void {
- // 在实际应用中,这里应该从服务中获取代班任务
- // 这里使用模拟数据
- this.shiftTasks = [
- {
- id: 'shift1',
- projectId: 'project1',
- projectName: '现代风格客厅设计',
- taskDescription: '小图修改反馈和渲染进度跟踪',
- priority: '高',
- shiftDate: '2025-09-15',
- status: '待处理'
- },
- {
- id: 'shift2',
- projectId: 'project2',
- projectName: '北欧风卧室装修',
- taskDescription: '查看客户反馈并提供初步修改建议',
- priority: '中',
- shiftDate: '2025-09-16',
- status: '待处理'
- },
- {
- id: 'shift3',
- projectId: 'project3',
- projectName: '新中式书房改造',
- taskDescription: '完成剩余渲染任务',
- priority: '低',
- shiftDate: '2025-09-17',
- status: '处理中'
- }
- ];
- }
- // 打开添加代班任务的模态框
- openShiftModal(): void {
- // 在实际应用中,这里应该打开一个模态框让用户添加代班任务
- // 这里使用alert模拟
- window.fmode?.alert?.({
- header: '添加代班任务',
- message: '将打开添加代班任务的表单',
- buttons: [{ text: '确定', role: 'confirm' }]
- });
- // 实际实现可能是:this.modalService.openShiftModal();
- }
- // 查看代班任务详情
- viewShiftDetail(shiftId: string): void {
- const shift = this.shiftTasks.find(s => s.id === shiftId);
- if (shift) {
- // 实际应用中,这里应该打开详情页面或模态框
- console.log('查看代班任务详情:', shift);
- window?.fmode?.alert(`代班任务详情:\n项目:${shift.projectName}\n任务:${shift.taskDescription}\n优先级:${shift.priority}\n代班日期:${shift.shiftDate}`);
- }
- }
- // 标记代班任务完成
- markShiftComplete(shiftId: string): void {
- const shiftIndex = this.shiftTasks.findIndex(s => s.id === shiftId);
- if (shiftIndex !== -1) {
- // 在实际应用中,这里应该调用API更新状态
- this.shiftTasks[shiftIndex].status = '已完成';
- window?.fmode?.alert('代班任务已标记为完成');
- }
- }
- // 计算项目饱和度
- calculateWorkloadPercentage(): void {
- // 在实际应用中,这里应该从服务中获取真实的项目饱和度数据
- // 这里使用模拟数据,根据当前任务数量计算饱和度
- const totalCapacity = 5; // 假设设计师最大同时处理5个项目
- const currentProjects = this.tasks.length;
-
- // 计算饱和度百分比
- this.workloadPercentage = Math.round((currentProjects / totalCapacity) * 100);
-
- // 确保百分比在0-100之间
- this.workloadPercentage = Math.min(Math.max(this.workloadPercentage, 0), 100);
- }
- // 加载项目排期表
- loadProjectTimeline(): void {
- // 在实际应用中,这里应该从服务中获取项目排期数据
- // 这里使用模拟数据
- this.projectTimeline = [
- {
- id: 'timeline1',
- name: '现代风格客厅设计',
- deadline: '2025-09-20',
- status: '进行中'
- },
- {
- id: 'timeline2',
- name: '北欧风卧室装修',
- deadline: '2025-09-25',
- status: '进行中'
- },
- {
- id: 'timeline3',
- name: '新中式书房改造',
- deadline: '2025-09-30',
- status: '进行中'
- }
- ].sort((a, b) => new Date(a.deadline).getTime() - new Date(b.deadline).getTime());
- }
- }
|