| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- 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<void> {
- 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<any> {
- 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<Record<string, number>> {
- const Parse = await this.ensureParse();
- if (!Parse) return {};
-
- try {
- const stages = ['订单分配', '方案深化', '交付执行', '售后归档'];
- const distribution: Record<string, number> = {};
-
- 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<any[]> {
- 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<any> {
- 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
- };
- }
- }
|