PROJECT-SPACE-DELIVERABLE-SERVICE-GUIDE.md 13 KB

项目空间与交付物统计服务使用指南

概述

ProjectSpaceDeliverableService 是一个封装的服务,用于计算项目中的空间数量和每个空间对应的交付物上传状态。这个服务可以在不同的地方使用,如组长端的项目时间轴、看板等。

功能特性

空间统计:根据 Product 表计算项目中有多少个空间 ✅ 交付物统计:根据 ProjectFile 表统计每个空间的交付物上传情况 ✅ 多类型支持:支持白模、软装、渲染、后期四种交付类型 ✅ 自动去重:自动去除重复的空间(按名称) ✅ 完成率计算:自动计算每个空间和整体的完成率 ✅ 状态标签:提供状态文本和颜色标识

服务位置

src/modules/project/services/project-space-deliverable.service.ts

数据结构

空间交付物信息 (SpaceDeliverableInfo)

interface SpaceDeliverableInfo {
  spaceId: string;              // 空间ID(Product ID)
  spaceName: string;            // 空间名称
  spaceType: string;            // 空间类型
  deliverableTypes: {
    whiteModel: number;         // 白模文件数量
    softDecor: number;          // 软装文件数量
    rendering: number;          // 渲染文件数量
    postProcess: number;        // 后期文件数量
  };
  totalFiles: number;           // 总文件数
  hasDeliverables: boolean;     // 是否已上传交付物
  completionRate: number;       // 完成度(0-100)
}

项目统计摘要 (ProjectSpaceDeliverableSummary)

interface ProjectSpaceDeliverableSummary {
  projectId: string;                    // 项目ID
  projectName: string;                  // 项目名称
  totalSpaces: number;                  // 空间总数
  spacesWithDeliverables: number;       // 已上传交付物的空间数
  spaces: SpaceDeliverableInfo[];       // 空间详细列表
  totalDeliverableFiles: number;        // 总交付文件数
  totalByType: {                        // 各类型总计
    whiteModel: number;
    softDecor: number;
    rendering: number;
    postProcess: number;
  };
  overallCompletionRate: number;        // 整体完成率(0-100)
}

主要方法

1. 获取项目空间与交付物统计摘要

async getProjectSpaceDeliverableSummary(projectId: string): Promise<ProjectSpaceDeliverableSummary>

用途:获取项目的完整统计信息

示例

import { ProjectSpaceDeliverableService } from '@modules/project/services/project-space-deliverable.service';

constructor(
  private projectSpaceDeliverableService: ProjectSpaceDeliverableService
) {}

async loadProjectStats() {
  const summary = await this.projectSpaceDeliverableService
    .getProjectSpaceDeliverableSummary('project123');
  
  console.log(`项目有 ${summary.totalSpaces} 个空间`);
  console.log(`已上传交付物的空间:${summary.spacesWithDeliverables} 个`);
  console.log(`总完成率:${summary.overallCompletionRate}%`);
  
  // 遍历每个空间的详细信息
  summary.spaces.forEach(space => {
    console.log(`空间:${space.spaceName}`);
    console.log(`  白模:${space.deliverableTypes.whiteModel} 个文件`);
    console.log(`  软装:${space.deliverableTypes.softDecor} 个文件`);
    console.log(`  渲染:${space.deliverableTypes.rendering} 个文件`);
    console.log(`  后期:${space.deliverableTypes.postProcess} 个文件`);
    console.log(`  完成率:${space.completionRate}%`);
  });
}

2. 检查项目是否所有空间都已交付

async isAllSpacesDelivered(projectId: string): Promise<boolean>

用途:快速检查项目是否全部完成

示例

const isCompleted = await this.projectSpaceDeliverableService
  .isAllSpacesDelivered('project123');

if (isCompleted) {
  console.log('✅ 项目所有空间都已完成交付');
} else {
  console.log('⚠️ 还有空间未完成交付');
}

3. 获取未完成空间列表

async getIncompleteSpaces(projectId: string): Promise<string[]>

用途:获取还未上传交付物的空间名称列表

示例

const incompleteSpaces = await this.projectSpaceDeliverableService
  .getIncompleteSpaces('project123');

if (incompleteSpaces.length > 0) {
  console.log('未完成的空间:', incompleteSpaces.join(', '));
  // 例如:未完成的空间:客厅, 主卧, 厨房
}

4. 获取项目交付进度百分比

async getProjectDeliveryProgress(projectId: string): Promise<number>

用途:获取项目的整体完成进度(0-100)

示例

const progress = await this.projectSpaceDeliverableService
  .getProjectDeliveryProgress('project123');

console.log(`项目完成进度:${progress}%`);

// 在进度条中使用
this.progressWidth = `${progress}%`;

5. 获取交付状态标签

getDeliveryStatusLabel(completionRate: number): string

用途:根据完成率获取状态文本

返回值

  • 0%: "未开始"
  • 1-24%: "刚开始"
  • 25-49%: "进行中"
  • 50-74%: "接近完成"
  • 75-99%: "即将完成"
  • 100%: "已完成"

示例

const rate = 65;
const status = this.projectSpaceDeliverableService
  .getDeliveryStatusLabel(rate);

console.log(status); // "接近完成"

6. 获取交付状态颜色

getDeliveryStatusColor(completionRate: number): string

用途:根据完成率获取对应的颜色值

返回值

  • 0%: #94a3b8 (灰色)
  • 1-24%: #fbbf24 (黄色)
  • 25-49%: #fb923c (橙色)
  • 50-74%: #60a5fa (蓝色)
  • 75-99%: #818cf8 (紫色)
  • 100%: #34d399 (绿色)

示例

const rate = 75;
const color = this.projectSpaceDeliverableService
  .getDeliveryStatusColor(rate);

// 在HTML中使用
<div [style.background-color]="color">
  {{ rate }}%
</div>

7. 格式化统计摘要为文本

formatSummaryText(summary: ProjectSpaceDeliverableSummary): string

用途:将统计摘要格式化为易读的文本

示例

const summary = await this.projectSpaceDeliverableService
  .getProjectSpaceDeliverableSummary('project123');

const text = this.projectSpaceDeliverableService
  .formatSummaryText(summary);

console.log(text);
// 输出:
// 项目:某某项目
// 空间总数:3
// 已完成空间:2/3
// 总文件数:15
//   - 白模:4
//   - 软装:5
//   - 渲染:4
//   - 后期:2
// 完成率:67%

在组件中使用

示例1:在组长端时间轴中使用

// project-timeline.ts
import { ProjectSpaceDeliverableService, ProjectSpaceDeliverableSummary } 
  from '@modules/project/services/project-space-deliverable.service';

@Component({
  selector: 'app-project-timeline',
  // ...
})
export class ProjectTimelineComponent implements OnInit {
  spaceDeliverableCache: Map<string, ProjectSpaceDeliverableSummary> = new Map();
  
  constructor(
    private projectSpaceDeliverableService: ProjectSpaceDeliverableService
  ) {}
  
  async ngOnInit() {
    await this.loadProjectStats();
  }
  
  private async loadProjectStats() {
    for (const project of this.projects) {
      try {
        const summary = await this.projectSpaceDeliverableService
          .getProjectSpaceDeliverableSummary(project.projectId);
        
        // 缓存统计数据
        this.spaceDeliverableCache.set(project.projectId, summary);
        
        console.log(`✅ 项目 ${project.projectName} 统计完成:`, {
          空间数: summary.totalSpaces,
          已完成空间: summary.spacesWithDeliverables,
          完成率: `${summary.overallCompletionRate}%`
        });
      } catch (error) {
        console.warn(`⚠️ 加载项目统计失败:`, error);
      }
    }
  }
  
  // 获取项目空间数量
  getProjectSpaceCount(projectId: string): number {
    const summary = this.spaceDeliverableCache.get(projectId);
    return summary?.totalSpaces || 0;
  }
  
  // 获取项目交付完成率
  getProjectDeliveryCompletionRate(projectId: string): number {
    const summary = this.spaceDeliverableCache.get(projectId);
    return summary?.overallCompletionRate || 0;
  }
  
  // 格式化工具提示
  formatTooltip(projectId: string): string {
    const summary = this.spaceDeliverableCache.get(projectId);
    if (!summary) return '加载中...';
    
    return `📦 空间与交付物统计\n\n` +
           `空间总数: ${summary.totalSpaces}\n` +
           `已完成空间: ${summary.spacesWithDeliverables}/${summary.totalSpaces}\n` +
           `完成率: ${summary.overallCompletionRate}%`;
  }
}

示例2:在HTML模板中显示

<!-- 显示空间统计徽章 -->
<div class="project-card">
  <h3>{{ project.name }}</h3>
  
  @if (getSpaceDeliverableSummary(project.id); as summary) {
    <span class="badge"
          [title]="formatTooltip(project.id)"
          [style.background-color]="getProjectDeliveryStatusColor(project.id)">
      📦 {{ summary.spacesWithDeliverables }}/{{ summary.totalSpaces }}
    </span>
  }
</div>

<!-- 显示进度条 -->
<div class="progress-bar">
  <div class="progress-fill"
       [style.width.%]="getProjectDeliveryCompletionRate(project.id)"
       [style.background-color]="getProjectDeliveryStatusColor(project.id)">
    {{ getProjectDeliveryCompletionRate(project.id) }}%
  </div>
</div>

<!-- 显示详细统计 -->
<div class="stats-panel">
  @if (getSpaceDeliverableSummary(project.id); as summary) {
    <div class="stat-item">
      <label>空间总数</label>
      <span>{{ summary.totalSpaces }}</span>
    </div>
    <div class="stat-item">
      <label>已完成空间</label>
      <span>{{ summary.spacesWithDeliverables }}</span>
    </div>
    <div class="stat-item">
      <label>总文件数</label>
      <span>{{ summary.totalDeliverableFiles }}</span>
    </div>
    <div class="stat-item">
      <label>白模</label>
      <span>{{ summary.totalByType.whiteModel }}</span>
    </div>
    <div class="stat-item">
      <label>软装</label>
      <span>{{ summary.totalByType.softDecor }}</span>
    </div>
    <div class="stat-item">
      <label>渲染</label>
      <span>{{ summary.totalByType.rendering }}</span>
    </div>
    <div class="stat-item">
      <label>后期</label>
      <span>{{ summary.totalByType.postProcess }}</span>
    </div>
  }
</div>

性能优化建议

1. 使用缓存

// 缓存统计数据,避免重复查询
private cache: Map<string, ProjectSpaceDeliverableSummary> = new Map();

async getStats(projectId: string) {
  // 检查缓存
  if (this.cache.has(projectId)) {
    return this.cache.get(projectId)!;
  }
  
  // 加载并缓存
  const summary = await this.projectSpaceDeliverableService
    .getProjectSpaceDeliverableSummary(projectId);
  this.cache.set(projectId, summary);
  
  return summary;
}

2. 异步加载

// 异步加载,不阻塞主流程
private async loadStatsInBackground() {
  for (const project of this.projects) {
    // 使用 setTimeout 避免阻塞UI
    setTimeout(async () => {
      const summary = await this.projectSpaceDeliverableService
        .getProjectSpaceDeliverableSummary(project.id);
      this.updateProjectStats(project.id, summary);
    }, 0);
  }
}

3. 批量加载

// 并行加载多个项目的统计数据
private async loadStatsInBatch(projectIds: string[]) {
  const promises = projectIds.map(id =>
    this.projectSpaceDeliverableService.getProjectSpaceDeliverableSummary(id)
  );
  
  const results = await Promise.allSettled(promises);
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      this.cache.set(projectIds[index], result.value);
    } else {
      console.warn(`加载项目 ${projectIds[index]} 统计失败:`, result.reason);
    }
  });
}

注意事项

  1. 空间去重:服务会自动去除重复的空间(按名称去重,忽略大小写和首尾空格)
  2. 文件类型:只统计 delivery_* 类型的文件(白模、软装、渲染、后期)
  3. 异步操作:所有方法都是异步的,需要使用 await.then()
  4. 错误处理:建议在调用时添加 try-catch 处理错误情况
  5. 缓存策略:建议在组件中实现缓存,避免频繁查询数据库

完整应用示例

请参考以下文件中的实际应用:

  • 时间轴组件src/app/pages/team-leader/project-timeline/project-timeline.ts
  • 交付阶段组件src/modules/project/pages/project-detail/stages/stage-delivery.component.ts

相关文档

技术支持

如有问题,请联系开发团队或查看源码注释。