| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 |
- import { Injectable } from '@angular/core';
- import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
- import { ProjectFileService } from './project-file.service';
- const Parse = FmodeParse.with('nova');
- /**
- * 空间交付物统计信息
- */
- export interface SpaceDeliverableInfo {
- /** 空间ID(Product ID) */
- spaceId: string;
- /** 空间名称 */
- spaceName: string;
- /** 空间类型 */
- spaceType: string;
- /** 交付物类型统计 */
- deliverableTypes: {
- /** 白模文件数量 */
- whiteModel: number;
- /** 软装文件数量 */
- softDecor: number;
- /** 渲染文件数量 */
- rendering: number;
- /** 后期文件数量 */
- postProcess: number;
- };
- /** 总文件数 */
- totalFiles: number;
- /** 是否已上传交付物 */
- hasDeliverables: boolean;
- /** 完成度(0-100) */
- completionRate: number;
- }
- /**
- * 🆕 阶段进度信息
- */
- export interface PhaseProgressInfo {
- /** 阶段名称 */
- phaseName: 'modeling' | 'softDecor' | 'rendering' | 'postProcessing';
- /** 阶段中文标签 */
- phaseLabel: string;
- /** 应完成空间数(有此类型交付物要求的空间数) */
- requiredSpaces: number;
- /** 已完成空间数(已上传此类型交付物的空间数) */
- completedSpaces: number;
- /** 完成率(0-100) */
- completionRate: number;
- /** 总文件数 */
- totalFiles: number;
- /** 未完成空间列表 */
- incompleteSpaces: Array<{
- spaceId: string;
- spaceName: string;
- assignee?: string; // 负责人
- }>;
- }
- /**
- * 项目空间与交付物统计信息
- */
- export interface ProjectSpaceDeliverableSummary {
- /** 项目ID */
- projectId: string;
- /** 项目名称 */
- projectName: string;
- /** 空间总数 */
- totalSpaces: number;
- /** 已上传交付物的空间数 */
- spacesWithDeliverables: number;
- /** 空间详细列表 */
- spaces: SpaceDeliverableInfo[];
- /** 总交付文件数 */
- totalDeliverableFiles: number;
- /** 各类型总计 */
- totalByType: {
- whiteModel: number;
- softDecor: number;
- rendering: number;
- postProcess: number;
- };
- /** 整体完成率(0-100) */
- overallCompletionRate: number;
- /** 🆕 各阶段进度详情 */
- phaseProgress: {
- modeling: PhaseProgressInfo;
- softDecor: PhaseProgressInfo;
- rendering: PhaseProgressInfo;
- postProcessing: PhaseProgressInfo;
- };
- }
- /**
- * 项目空间与交付物统计服务
- *
- * 功能:
- * 1. 计算项目中有多少个空间(基于Product表)
- * 2. 统计每个空间对应的交付物上传情况(基于ProjectFile表)
- * 3. 提供详细的统计数据,方便在不同地方使用(如时间轴、看板等)
- */
- @Injectable({
- providedIn: 'root'
- })
- export class ProjectSpaceDeliverableService {
- // ✅ 新增:内存缓存
- private summaryCache = new Map<string, ProjectSpaceDeliverableSummary>();
- constructor(
- private projectFileService: ProjectFileService
- ) {}
- /**
- * 🆕 注入统计数据(用于云函数预加载)
- */
- injectSummary(projectId: string, summary: ProjectSpaceDeliverableSummary): void {
- this.summaryCache.set(projectId, summary);
- }
- /**
- * 🆕 清除缓存
- */
- clearCache(projectId?: string): void {
- if (projectId) {
- this.summaryCache.delete(projectId);
- } else {
- this.summaryCache.clear();
- }
- }
- /**
- * 获取项目的空间与交付物统计信息
- *
- * @param projectId 项目ID
- * @param forceRefresh 是否强制刷新(忽略缓存)
- * @returns 项目空间与交付物统计摘要
- */
- async getProjectSpaceDeliverableSummary(
- projectId: string,
- forceRefresh: boolean = false
- ): Promise<ProjectSpaceDeliverableSummary> {
- // ✅ 检查缓存
- if (!forceRefresh && this.summaryCache.has(projectId)) {
- return this.summaryCache.get(projectId)!;
- }
- try {
- // 1. 获取项目信息
- const projectQuery = new Parse.Query('Project');
- const project = await projectQuery.get(projectId);
- const projectName = project.get('title') || project.get('name') || '未命名项目';
- // ✅ 应用方案:获取项目的所有空间(Product),包含负责人信息
- let products: FmodeObject[] = [];
- try {
- const productQuery = new Parse.Query('Product');
- productQuery.equalTo('project', project.toPointer());
- productQuery.ascending('createdAt');
- productQuery.include('profile'); // ✅ 包含负责人信息(如果存在)
- // ✅ 优化:如果 profile 是 Pointer,需要 include profile 的 name 字段
- // 注意:Parse 的 include 会自动包含关联对象的基本字段,但为了确保 name 字段可用,我们显式 include
- products = await productQuery.find();
-
- // ✅ 调试:检查 profile 字段是否正确加载
- // console.log(`📊 查询到 ${products.length} 个 Product,检查 profile 字段:`);
- // products.forEach((product, index) => {
- // const profile = product.get('profile');
- // const productName = product.get('productName') || '未命名';
- // if (profile) {
- // const profileName = profile.get?.('name') || profile.name || '未知';
- // console.log(` ${index + 1}. ${productName}: profile = ${profileName}`);
- // } else {
- // console.log(` ${index + 1}. ${productName}: 无 profile`);
- // }
- // });
- // console.log(`📊 项目 ${projectName} 共有 ${products.length} 个空间(Product)`);
- } catch (productError: any) {
- // ✅ 容错处理:Product 查询失败时,记录警告但继续处理
- console.warn(`⚠️ Product 查询失败,将使用空的空间列表:`, productError.message || productError.toString());
- // console.warn(`⚠️ Product 错误详情:`, productError);
- products = []; // 查询失败时使用空数组
- }
- // 3. 去重:按空间名称去重(忽略大小写和首尾空格)
- const uniqueProducts = this.deduplicateProducts(products);
- // console.log(`📊 去重后空间数:${uniqueProducts.length}`);
- // 4. 统计每个空间的交付物
- const spaceInfos: SpaceDeliverableInfo[] = [];
- let totalDeliverableFiles = 0;
- let spacesWithDeliverables = 0;
- const totalByType = {
- whiteModel: 0,
- softDecor: 0,
- rendering: 0,
- postProcess: 0
- };
- // ✅ 应用方案:优化性能,一次性查询所有交付文件
- const allDeliveryFiles = await this.projectFileService.getProjectFiles(projectId, {
- stage: 'delivery'
- });
- // console.log(`📊 项目 ${projectName} 共有 ${allDeliveryFiles.length} 个交付文件`);
- for (const product of uniqueProducts) {
- // ✅ 应用方案:传入所有文件列表,避免重复查询
- const spaceInfo = await this.getSpaceDeliverableInfo(projectId, product, allDeliveryFiles);
- spaceInfos.push(spaceInfo);
- // 累加统计
- totalDeliverableFiles += spaceInfo.totalFiles;
- if (spaceInfo.hasDeliverables) {
- spacesWithDeliverables++;
- }
- totalByType.whiteModel += spaceInfo.deliverableTypes.whiteModel;
- totalByType.softDecor += spaceInfo.deliverableTypes.softDecor;
- totalByType.rendering += spaceInfo.deliverableTypes.rendering;
- totalByType.postProcess += spaceInfo.deliverableTypes.postProcess;
- }
- // 5. 计算整体完成率
- const overallCompletionRate = this.calculateOverallCompletionRate(spaceInfos);
- // ✅ 应用方案:计算各阶段进度详情,传入 products 以获取负责人信息
- const phaseProgress = this.calculatePhaseProgress(spaceInfos, project, uniqueProducts);
- const result = {
- projectId,
- projectName,
- totalSpaces: uniqueProducts.length,
- spacesWithDeliverables,
- spaces: spaceInfos,
- totalDeliverableFiles,
- totalByType,
- overallCompletionRate,
- phaseProgress
- };
- // ✅ 存入缓存
- this.summaryCache.set(projectId, result);
- return result;
- } catch (error) {
- console.error('获取项目空间交付物统计失败:', error);
- throw error;
- }
- }
- /**
- * 获取单个空间的交付物信息
- *
- * @param projectId 项目ID
- * @param product Product对象
- * @param allDeliveryFiles 所有交付文件列表(可选,用于性能优化)
- * @returns 空间交付物信息
- */
- private async getSpaceDeliverableInfo(
- projectId: string,
- product: FmodeObject,
- allDeliveryFiles?: FmodeObject[]
- ): Promise<SpaceDeliverableInfo> {
- const spaceId = product.id!;
- const spaceName = product.get('productName') || '未命名空间';
- const spaceType = product.get('productType') || 'other';
- // 定义交付物类型映射
- const deliveryTypeMap = {
- whiteModel: 'delivery_white_model',
- softDecor: 'delivery_soft_decor',
- rendering: 'delivery_rendering',
- postProcess: 'delivery_post_process'
- };
- // 统计各类型文件数量
- const deliverableTypes = {
- whiteModel: 0,
- softDecor: 0,
- rendering: 0,
- postProcess: 0
- };
- // ✅ 应用方案:优化文件查询逻辑,支持 deliveryType 字段
- // 如果已传入文件列表,直接使用;否则查询
- let deliveryFiles: FmodeObject[];
- if (allDeliveryFiles) {
- deliveryFiles = allDeliveryFiles;
- } else {
- deliveryFiles = await this.projectFileService.getProjectFiles(projectId, {
- stage: 'delivery'
- });
- }
- // 定义交付类型映射(支持多种字段格式)
- const deliveryTypeMappings = {
- whiteModel: {
- fileType: 'delivery_white_model',
- deliveryType: ['white_model', 'delivery_white_model', 'whiteModel']
- },
- softDecor: {
- fileType: 'delivery_soft_decor',
- deliveryType: ['soft_decor', 'delivery_soft_decor', 'softDecor']
- },
- rendering: {
- fileType: 'delivery_rendering',
- deliveryType: ['rendering', 'delivery_rendering']
- },
- postProcess: {
- fileType: 'delivery_post_process',
- deliveryType: ['post_process', 'delivery_post_process', 'postProcess']
- }
- };
- // 统计各类型文件数量
- for (const [key, mapping] of Object.entries(deliveryTypeMappings)) {
- // 过滤当前空间的该类型文件
- const spaceFiles = deliveryFiles.filter(file => {
- const data = file.get('data') || {};
- const fileType = file.get('fileType') || '';
- const deliveryType = data.deliveryType || '';
- const uploadStage = data.uploadStage || '';
-
- // ✅ 应用方案:优先使用 data.spaceId,其次使用 data.productId
- const isSpaceMatch = data.spaceId === spaceId || data.productId === spaceId;
-
- // ✅ 应用方案:支持多种字段格式匹配
- const isTypeMatch =
- fileType === mapping.fileType ||
- mapping.deliveryType.includes(deliveryType) ||
- mapping.deliveryType.includes(fileType);
-
- // ✅ 应用方案:确认是交付阶段文件(推荐检查 uploadStage)
- const isDeliveryStage = uploadStage === 'delivery' || !uploadStage || fileType.includes('delivery');
-
- return isSpaceMatch && isTypeMatch && isDeliveryStage;
- });
- deliverableTypes[key as keyof typeof deliverableTypes] = spaceFiles.length;
- }
- // 计算总文件数
- const totalFiles = Object.values(deliverableTypes).reduce((sum, count) => sum + count, 0);
- // 判断是否已上传交付物
- const hasDeliverables = totalFiles > 0;
- // 计算完成度(假设每种类型至少需要1个文件才算完成)
- const completedTypes = Object.values(deliverableTypes).filter(count => count > 0).length;
- const completionRate = Math.round((completedTypes / 4) * 100);
- return {
- spaceId,
- spaceName,
- spaceType,
- deliverableTypes,
- totalFiles,
- hasDeliverables,
- completionRate
- };
- }
- /**
- * 去重Product列表(按名称去重)
- *
- * @param products Product对象数组
- * @returns 去重后的Product数组
- */
- private deduplicateProducts(products: FmodeObject[]): FmodeObject[] {
- const seen = new Set<string>();
- const unique: FmodeObject[] = [];
- for (const product of products) {
- const name = (product.get('productName') || '').trim().toLowerCase();
- if (!seen.has(name) && name) {
- seen.add(name);
- unique.push(product);
- }
- }
- return unique;
- }
- /**
- * 计算整体完成率
- *
- * @param spaceInfos 空间信息列表
- * @returns 完成率(0-100)
- */
- private calculateOverallCompletionRate(spaceInfos: SpaceDeliverableInfo[]): number {
- if (spaceInfos.length === 0) return 0;
- const totalCompletionRate = spaceInfos.reduce(
- (sum, space) => sum + space.completionRate,
- 0
- );
- return Math.round(totalCompletionRate / spaceInfos.length);
- }
- /**
- * ✅ 应用方案:计算各阶段进度详情
- *
- * @param spaceInfos 空间信息列表
- * @param project 项目对象
- * @param products Product对象数组(可选,用于获取空间负责人)
- * @returns 各阶段进度信息
- */
- private calculatePhaseProgress(
- spaceInfos: SpaceDeliverableInfo[],
- project: FmodeObject,
- products?: FmodeObject[]
- ): ProjectSpaceDeliverableSummary['phaseProgress'] {
- // 获取项目阶段截止信息中的负责人
- const projectData = project.get('data') || {};
- const phaseDeadlines = projectData.phaseDeadlines || {};
-
- // ✅ 🆕 获取 designerAssignmentStats 统计数据(主要数据源)
- const projectDate = project.get('date') || {};
- const designerAssignmentStats = projectDate.designerAssignmentStats || {};
- const projectLeader = designerAssignmentStats.projectLeader || null;
- const teamMembers = designerAssignmentStats.teamMembers || [];
- const crossTeamCollaborators = designerAssignmentStats.crossTeamCollaborators || [];
- // 阶段映射:交付物类型 -> 阶段名称
- const phaseMap = {
- modeling: {
- key: 'whiteModel' as const,
- label: '建模',
- phaseName: 'modeling' as const
- },
- softDecor: {
- key: 'softDecor' as const,
- label: '软装',
- phaseName: 'softDecor' as const
- },
- rendering: {
- key: 'rendering' as const,
- label: '渲染',
- phaseName: 'rendering' as const
- },
- postProcessing: {
- key: 'postProcess' as const,
- label: '后期',
- phaseName: 'postProcessing' as const
- }
- };
- const result: any = {};
- // ✅ 🆕 构建空间ID到负责人姓名的映射(优先级:designerAssignmentStats > Product.profile > phaseDeadlines.assignee)
- const spaceAssigneeMap = new Map<string, string>();
-
- // 优先级1:从 designerAssignmentStats 获取空间分配信息(最准确)
- // 1.1 项目负责人的空间分配
- if (projectLeader && projectLeader.assignedSpaces && Array.isArray(projectLeader.assignedSpaces)) {
- projectLeader.assignedSpaces.forEach((space: { id: string; name: string; area?: number }) => {
- if (space.id && projectLeader.name) {
- spaceAssigneeMap.set(space.id, projectLeader.name);
- // console.log(`📊 [designerAssignmentStats] 空间 ${space.name} (ID: ${space.id}) 的负责人: ${projectLeader.name} (项目负责人)`);
- }
- });
- }
-
- // 1.2 团队成员的空间分配
- teamMembers.forEach((member: { id: string; name: string; isProjectLeader?: boolean; assignedSpaces?: Array<{ id: string; name: string; area?: number }> }) => {
- if (member.assignedSpaces && Array.isArray(member.assignedSpaces) && member.name) {
- member.assignedSpaces.forEach((space: { id: string; name: string; area?: number }) => {
- if (space.id && !spaceAssigneeMap.has(space.id)) {
- // 如果该空间还没有分配负责人,则使用当前成员
- spaceAssigneeMap.set(space.id, member.name);
- // console.log(`📊 [designerAssignmentStats] 空间 ${space.name} (ID: ${space.id}) 的负责人: ${member.name}${member.isProjectLeader ? ' (项目负责人)' : ''}`);
- }
- });
- }
- });
-
- // 1.3 跨组合作者的空间分配
- crossTeamCollaborators.forEach((collaborator: { id: string; name: string; assignedSpaces?: Array<{ id: string; name: string; area?: number }> }) => {
- if (collaborator.assignedSpaces && Array.isArray(collaborator.assignedSpaces) && collaborator.name) {
- collaborator.assignedSpaces.forEach((space: { id: string; name: string; area?: number }) => {
- if (space.id && !spaceAssigneeMap.has(space.id)) {
- spaceAssigneeMap.set(space.id, collaborator.name);
- // console.log(`📊 [designerAssignmentStats] 空间 ${space.name} (ID: ${space.id}) 的负责人: ${collaborator.name} (跨组合作)`);
- }
- });
- }
- });
-
- // 优先级2:从 Product.profile 获取空间负责人(如果 designerAssignmentStats 中没有)
- if (products) {
- products.forEach(product => {
- const spaceId = product.id!;
- // 如果该空间还没有分配负责人,才尝试使用 Product.profile
- if (!spaceAssigneeMap.has(spaceId)) {
- const profile = product.get('profile');
- if (profile) {
- // ✅ 优化:支持多种方式获取设计师姓名
- let profileName: string | undefined;
-
- // 方式1:profile 是 Parse Object,使用 get('name')
- if (profile.get && typeof profile.get === 'function') {
- profileName = profile.get('name') || profile.get('username') || '';
- }
- // 方式2:profile 是普通对象,直接访问 name 属性
- else if (profile.name) {
- profileName = profile.name;
- }
- // 方式3:profile 是字符串(ID),需要查询(暂时跳过,性能考虑)
- else if (typeof profile === 'string') {
- // 可以后续实现查询逻辑
- profileName = undefined;
- }
-
- if (profileName) {
- spaceAssigneeMap.set(spaceId, profileName);
- // console.log(`📊 [Product.profile] 空间 ${product.get('productName')} (ID: ${spaceId}) 的负责人: ${profileName}`);
- } else {
- // console.warn(`⚠️ 空间 ${product.get('productName')} (ID: ${spaceId}) 的负责人信息无法获取`, profile);
- }
- }
- }
- });
- }
- // 计算每个阶段的进度
- Object.entries(phaseMap).forEach(([phaseKey, phaseConfig]) => {
- const requiredSpaces = spaceInfos.length; // 假设所有空间都需要各阶段交付物
- let completedSpaces = 0;
- let totalFiles = 0;
- const incompleteSpaces: Array<{
- spaceId: string;
- spaceName: string;
- assignee?: string;
- }> = [];
- // ✅ 优先级3:获取阶段负责人信息(作为最后备选)
- const phaseInfo = phaseDeadlines[phaseKey];
- const assignee = phaseInfo?.assignee;
- let phaseAssigneeName: string | undefined;
-
- if (assignee) {
- // 如果 assignee 是对象(Parse Pointer)
- if (assignee.objectId) {
- // 为了性能,暂时不实时查询,但保留接口
- phaseAssigneeName = undefined; // 可以后续实现查询逻辑
- }
- // 如果 assignee 是字符串(ID)
- else if (typeof assignee === 'string') {
- phaseAssigneeName = undefined; // 可以后续实现查询逻辑
- }
- // 如果 assignee 是对象且包含 name 字段
- else if (assignee.name) {
- phaseAssigneeName = assignee.name;
- }
- }
-
- spaceInfos.forEach(space => {
- const fileCount = space.deliverableTypes[phaseConfig.key];
- totalFiles += fileCount;
- if (fileCount > 0) {
- completedSpaces++;
- } else {
- // ✅ 应用方案:未完成空间列表,按优先级获取负责人
- // 优先级:designerAssignmentStats > Product.profile > phaseDeadlines.assignee
- let spaceAssignee = spaceAssigneeMap.get(space.spaceId);
-
- // 如果找不到,尝试使用阶段负责人
- if (!spaceAssignee) {
- spaceAssignee = phaseAssigneeName;
- }
-
- // 如果还是没有,设置为"未分配"
- if (!spaceAssignee) {
- spaceAssignee = '未分配';
- }
-
- // console.log(`📊 未完成空间: ${space.spaceName} (ID: ${space.spaceId}), 负责人: ${spaceAssignee}`);
-
- incompleteSpaces.push({
- spaceId: space.spaceId,
- spaceName: space.spaceName,
- assignee: spaceAssignee
- });
- }
- });
- const completionRate = requiredSpaces > 0
- ? Math.round((completedSpaces / requiredSpaces) * 100)
- : 0;
- result[phaseKey] = {
- phaseName: phaseConfig.phaseName,
- phaseLabel: phaseConfig.label,
- requiredSpaces,
- completedSpaces,
- completionRate,
- totalFiles,
- incompleteSpaces
- };
- });
- return result;
- }
- /**
- * 检查项目是否所有空间都已上传交付物
- *
- * @param projectId 项目ID
- * @returns 是否全部完成
- */
- async isAllSpacesDelivered(projectId: string): Promise<boolean> {
- try {
- const summary = await this.getProjectSpaceDeliverableSummary(projectId);
- return summary.spacesWithDeliverables === summary.totalSpaces && summary.totalSpaces > 0;
- } catch (error) {
- console.error('检查项目交付完成状态失败:', error);
- return false;
- }
- }
- /**
- * 获取项目未完成空间列表
- *
- * @param projectId 项目ID
- * @returns 未完成空间的名称列表
- */
- async getIncompleteSpaces(projectId: string): Promise<string[]> {
- try {
- const summary = await this.getProjectSpaceDeliverableSummary(projectId);
- return summary.spaces
- .filter(space => !space.hasDeliverables)
- .map(space => space.spaceName);
- } catch (error) {
- console.error('获取未完成空间列表失败:', error);
- return [];
- }
- }
- /**
- * 获取项目交付进度百分比
- *
- * @param projectId 项目ID
- * @returns 进度百分比(0-100)
- */
- async getProjectDeliveryProgress(projectId: string): Promise<number> {
- try {
- const summary = await this.getProjectSpaceDeliverableSummary(projectId);
- return summary.overallCompletionRate;
- } catch (error) {
- console.error('获取项目交付进度失败:', error);
- return 0;
- }
- }
- /**
- * 获取空间类型显示名称
- *
- * @param spaceType 空间类型
- * @returns 显示名称
- */
- getSpaceTypeName(spaceType: string): string {
- const nameMap: Record<string, string> = {
- 'living_room': '客厅',
- 'bedroom': '卧室',
- 'kitchen': '厨房',
- 'bathroom': '卫生间',
- 'dining_room': '餐厅',
- 'study': '书房',
- 'balcony': '阳台',
- 'corridor': '走廊',
- 'storage': '储物间',
- 'entrance': '玄关',
- 'other': '其他'
- };
- return nameMap[spaceType] || '其他';
- }
- /**
- * 格式化统计摘要为文本
- *
- * @param summary 统计摘要
- * @returns 格式化的文本
- */
- formatSummaryText(summary: ProjectSpaceDeliverableSummary): string {
- const lines = [
- `项目:${summary.projectName}`,
- `空间总数:${summary.totalSpaces}`,
- `已完成空间:${summary.spacesWithDeliverables}/${summary.totalSpaces}`,
- `总文件数:${summary.totalDeliverableFiles}`,
- ` - 白模:${summary.totalByType.whiteModel}`,
- ` - 软装:${summary.totalByType.softDecor}`,
- ` - 渲染:${summary.totalByType.rendering}`,
- ` - 后期:${summary.totalByType.postProcess}`,
- `完成率:${summary.overallCompletionRate}%`
- ];
- return lines.join('\n');
- }
- /**
- * 获取项目交付状态标签
- *
- * @param completionRate 完成率
- * @returns 状态标签
- */
- getDeliveryStatusLabel(completionRate: number): string {
- if (completionRate === 0) return '未开始';
- if (completionRate < 25) return '刚开始';
- if (completionRate < 50) return '进行中';
- if (completionRate < 75) return '接近完成';
- if (completionRate < 100) return '即将完成';
- return '已完成';
- }
- /**
- * 获取项目交付状态颜色
- *
- * @param completionRate 完成率
- * @returns 颜色类名或颜色值
- */
- getDeliveryStatusColor(completionRate: number): string {
- if (completionRate === 0) return '#94a3b8'; // 灰色
- if (completionRate < 25) return '#fbbf24'; // 黄色
- if (completionRate < 50) return '#fb923c'; // 橙色
- if (completionRate < 75) return '#60a5fa'; // 蓝色
- if (completionRate < 100) return '#818cf8'; // 紫色
- return '#34d399'; // 绿色
- }
- }
|