import { Injectable } from '@angular/core'; import { ProjectSpaceDeliverableService } from '../../../../modules/project/services/project-space-deliverable.service'; /** * 仪表盘数据服务 * 负责组长端仪表盘的统计数据获取 */ @Injectable({ providedIn: 'root' }) export class DashboardDataService { private cid: string = ''; private Parse: any = null; constructor( private projectSpaceDeliverableService: ProjectSpaceDeliverableService ) { this.cid = localStorage.getItem('company') || ''; console.log('🏢 DashboardDataService初始化,当前公司ID:', this.cid || '(未设置)'); this.initParse(); } /** * 延迟初始化Parse */ private async initParse(): Promise { try { // 🔥 尝试从 core 导入以获取完整功能的 FmodeParse const { FmodeParse } = await import('fmode-ng/core'); this.Parse = FmodeParse.with("nova"); console.log('✅ DashboardDataService: FmodeParse (core) 初始化成功'); } catch (error) { console.warn('⚠️ 从 fmode-ng/core 导入失败,尝试 fmode-ng/parse', error); try { const { FmodeParse } = await import('fmode-ng/parse'); this.Parse = FmodeParse.with("nova"); console.log('✅ DashboardDataService: FmodeParse (parse) 初始化成功'); } catch (err) { console.error('❌ DashboardDataService: FmodeParse 初始化失败:', err); } } } /** * 确保Parse已初始化 */ private async ensureParse(): Promise { if (!this.Parse) { await this.initParse(); } return this.Parse; } /** * 获取KPI统计数据 * @returns KPI数据 */ async getKPIStats(): Promise<{ totalProjects: number; inProgressProjects: number; completedProjects: number; overdueProjects: number; dueSoonProjects: number; totalDesigners: number; availableDesigners: number; busyDesigners: number; overloadedDesigners: number; }> { const Parse = await this.ensureParse(); if (!Parse) { return this.getDefaultKPIStats(); } try { // 项目统计 const projectQuery = new Parse.Query('Project'); projectQuery.equalTo('company', this.cid); projectQuery.notEqualTo('isDeleted', true); const totalProjects = await projectQuery.count(); const inProgressQuery = new Parse.Query('Project'); inProgressQuery.equalTo('company', this.cid); inProgressQuery.equalTo('status', '进行中'); inProgressQuery.notEqualTo('isDeleted', true); const inProgressProjects = await inProgressQuery.count(); const completedQuery = new Parse.Query('Project'); completedQuery.equalTo('company', this.cid); completedQuery.equalTo('status', '已完成'); completedQuery.notEqualTo('isDeleted', true); const completedProjects = await completedQuery.count(); // 超期项目 const now = new Date(); const overdueQuery = new Parse.Query('Project'); overdueQuery.equalTo('company', this.cid); overdueQuery.equalTo('status', '进行中'); overdueQuery.lessThan('deadline', now); overdueQuery.notEqualTo('isDeleted', true); const overdueProjects = await overdueQuery.count(); // 临期项目(7天内到期) const sevenDaysLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); const dueSoonQuery = new Parse.Query('Project'); dueSoonQuery.equalTo('company', this.cid); dueSoonQuery.equalTo('status', '进行中'); dueSoonQuery.greaterThanOrEqualTo('deadline', now); dueSoonQuery.lessThanOrEqualTo('deadline', sevenDaysLater); dueSoonQuery.notEqualTo('isDeleted', true); const dueSoonProjects = await dueSoonQuery.count(); // 设计师统计 const designerQuery = new Parse.Query('Profile'); designerQuery.equalTo('company', this.cid); designerQuery.equalTo('roleName', '组员'); designerQuery.notEqualTo('isDeleted', true); const designers = await designerQuery.find(); const totalDesigners = designers.length; // 统计设计师负载 let availableDesigners = 0; let busyDesigners = 0; let overloadedDesigners = 0; for (const designer of designers) { const assignedQuery = new Parse.Query('Project'); assignedQuery.equalTo('assignee', designer.id); assignedQuery.equalTo('status', '进行中'); assignedQuery.notEqualTo('isDeleted', true); const projectCount = await assignedQuery.count(); if (projectCount === 0) { availableDesigners++; } else if (projectCount >= 3) { overloadedDesigners++; } else { busyDesigners++; } } console.log('✅ KPI统计数据获取成功'); return { totalProjects, inProgressProjects, completedProjects, overdueProjects, dueSoonProjects, totalDesigners, availableDesigners, busyDesigners, overloadedDesigners }; } catch (error) { console.error('❌ 获取KPI统计失败:', error); return this.getDefaultKPIStats(); } } /** * 获取项目阶段分布 * @returns 阶段分布数据 */ async getStageDistribution(): Promise> { const Parse = await this.ensureParse(); if (!Parse) return {}; try { const stages = ['订单分配', '方案深化', '交付执行', '售后归档']; const distribution: Record = {}; for (const stage of stages) { const query = new Parse.Query('Project'); query.equalTo('company', this.cid); query.equalTo('currentStage', stage); query.equalTo('status', '进行中'); query.notEqualTo('isDeleted', true); const count = await query.count(); distribution[stage] = count; } console.log('✅ 项目阶段分布获取成功:', distribution); return distribution; } catch (error) { console.error('❌ 获取项目阶段分布失败:', error); return {}; } } /** * 获取设计师工作负载分布 * @returns 负载分布数据 */ async getDesignerWorkloadDistribution(): Promise<{ idle: number; busy: number; overload: number; }> { const Parse = await this.ensureParse(); if (!Parse) { return { idle: 0, busy: 0, overload: 0 }; } try { const designerQuery = new Parse.Query('Profile'); designerQuery.equalTo('company', this.cid); designerQuery.equalTo('roleName', '组员'); designerQuery.notEqualTo('isDeleted', true); const designers = await designerQuery.find(); let idle = 0; let busy = 0; let overload = 0; for (const designer of designers) { const projectQuery = new Parse.Query('Project'); projectQuery.equalTo('assignee', designer.id); projectQuery.equalTo('status', '进行中'); projectQuery.notEqualTo('isDeleted', true); const projectCount = await projectQuery.count(); if (projectCount === 0) { idle++; } else if (projectCount >= 3) { overload++; } else { busy++; } } console.log('✅ 设计师负载分布获取成功:', { idle, busy, overload }); return { idle, busy, overload }; } catch (error) { console.error('❌ 获取设计师负载分布失败:', error); return { idle: 0, busy: 0, overload: 0 }; } } /** * 获取待办任务列表 * @returns 待办任务 */ async getTodoTasks(): Promise { const Parse = await this.ensureParse(); if (!Parse) return []; try { const tasks: any[] = []; // 1. 待分配的项目 const unassignedQuery = new Parse.Query('Project'); unassignedQuery.equalTo('company', this.cid); unassignedQuery.equalTo('status', '待分配'); unassignedQuery.notEqualTo('isDeleted', true); unassignedQuery.include('customer'); unassignedQuery.descending('createdAt'); unassignedQuery.limit(10); const unassignedProjects = await unassignedQuery.find(); for (const project of unassignedProjects) { const customer = project.get('customer'); tasks.push({ id: `assign-${project.id}`, title: `分配项目: ${project.get('title')}`, description: `客户 ${customer?.get('name') || '未知'} 的项目等待分配设计师`, deadline: project.get('createdAt'), priority: 'high' as const, type: 'assign' as const, targetId: project.id }); } // 2. 超期项目(需要跟进) const now = new Date(); const overdueQuery = new Parse.Query('Project'); overdueQuery.equalTo('company', this.cid); overdueQuery.equalTo('status', '进行中'); overdueQuery.lessThan('deadline', now); overdueQuery.notEqualTo('isDeleted', true); overdueQuery.include('assignee', 'customer'); overdueQuery.limit(10); const overdueProjects = await overdueQuery.find(); for (const project of overdueProjects) { const assignee = project.get('assignee'); const customer = project.get('customer'); const deadline = project.get('deadline'); const overdueDays = Math.ceil((now.getTime() - deadline.getTime()) / (1000 * 60 * 60 * 24)); tasks.push({ id: `overdue-${project.id}`, title: `超期项目: ${project.get('title')}`, description: `设计师 ${assignee?.get('name') || '未分配'} 负责的项目已超期 ${overdueDays} 天,客户:${customer?.get('name') || '未知'}`, deadline: project.get('deadline'), priority: 'high' as const, type: 'review' as const, targetId: project.id }); } // 3. 临期项目(3天内到期) const threeDaysLater = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000); const dueSoonQuery = new Parse.Query('Project'); dueSoonQuery.equalTo('company', this.cid); dueSoonQuery.equalTo('status', '进行中'); dueSoonQuery.greaterThanOrEqualTo('deadline', now); dueSoonQuery.lessThanOrEqualTo('deadline', threeDaysLater); dueSoonQuery.notEqualTo('isDeleted', true); dueSoonQuery.include('assignee'); dueSoonQuery.limit(10); const dueSoonProjects = await dueSoonQuery.find(); for (const project of dueSoonProjects) { const assignee = project.get('assignee'); const deadline = project.get('deadline'); const remainingDays = Math.ceil((deadline.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); tasks.push({ id: `duesoon-${project.id}`, title: `临期项目: ${project.get('title')}`, description: `设计师 ${assignee?.get('name') || '未分配'} 负责的项目还有 ${remainingDays} 天到期`, deadline: project.get('deadline'), priority: remainingDays <= 1 ? 'high' as const : 'medium' as const, type: 'review' as const, targetId: project.id }); } console.log(`✅ 获取待办任务成功,共 ${tasks.length} 个任务`); return tasks; } catch (error) { console.error('❌ 获取待办任务失败:', error); return []; } } /** * 获取组长看板数据 */ async getTeamLeaderDataFromCloud(): Promise { try { const { FmodeParse } = await import('fmode-ng/core'); const Parse: any = FmodeParse.initialize({ appId: "ncloudmaster", serverURL: "https://server.fmode.cn/parse", appName: 'NovaCloud' } as any); const startTime = Date.now(); // 调用云函数 (ID: 8qJkylemKn) const result = await Parse.Cloud.function({ id: '8qJkylemKn', companyId: this.cid }); console.log(`✅ 云函数数据加载成功,耗时 ${Date.now() - startTime}ms`); // 🆕 处理空间统计数据注入(预加载缓存) if (result && result.data && result.data.spaceStats) { const spaceStats = result.data.spaceStats; const count = Object.keys(spaceStats).length; console.log(`📊 预加载 ${count} 个项目的空间统计数据`); Object.keys(spaceStats).forEach(projectId => { this.projectSpaceDeliverableService.injectSummary(projectId, spaceStats[projectId]); }); } else if (result && result.spaceStats) { // 兼容直接返回的情况 const spaceStats = result.spaceStats; Object.keys(spaceStats).forEach(projectId => { this.projectSpaceDeliverableService.injectSummary(projectId, spaceStats[projectId]); }); } // 兼容返回格式 if (result && (result.stats || result.workload)) return result; if (result && result.data) return result.data; if (result && result.result) return result.result; return result; } catch (error) { console.error('❌ 云函数调用失败:', error); return null; } } // 辅助方法:统一处理返回结果格式 private normalizeResult(result: any) { if (!result) return null; if (result.stats || result.workload) return result; if (result.data) return result.data; if (result.result) return result.result; return result; } /** * 获取默认KPI统计(无数据时) */ private getDefaultKPIStats() { return { totalProjects: 0, inProgressProjects: 0, completedProjects: 0, overdueProjects: 0, dueSoonProjects: 0, totalDesigners: 0, availableDesigners: 0, busyDesigners: 0, overloadedDesigners: 0 }; } }