PROJECT-SPACE-DELIVERABLE-QUICK-START.md 12 KB

项目空间与交付物统计服务 - 快速开始

🚀 5分钟快速上手

第一步:导入服务

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

@Component({
  // ...
})
export class YourComponent {
  constructor(
    private projectSpaceDeliverableService: ProjectSpaceDeliverableService
  ) {}
}

第二步:获取项目统计

async loadProjectStats(projectId: string) {
  const summary = await this.projectSpaceDeliverableService
    .getProjectSpaceDeliverableSummary(projectId);
  
  console.log('项目统计:', summary);
}

第三步:在模板中使用

<div class="project-card">
  <h3>{{ projectName }}</h3>
  
  <!-- 显示空间统计 -->
  <div class="stats">
    <span>空间: {{ summary.spacesWithDeliverables }}/{{ summary.totalSpaces }}</span>
    <span>文件: {{ summary.totalDeliverableFiles }}</span>
    <span>完成率: {{ summary.overallCompletionRate }}%</span>
  </div>
</div>

📊 常用场景

场景1:显示项目完成徽章

// TypeScript
getCompletionBadge(projectId: string) {
  const summary = this.cache.get(projectId);
  if (!summary) return null;
  
  return {
    text: `${summary.spacesWithDeliverables}/${summary.totalSpaces}`,
    color: this.projectSpaceDeliverableService.getDeliveryStatusColor(
      summary.overallCompletionRate
    ),
    tooltip: `已完成 ${summary.spacesWithDeliverables} 个空间,共 ${summary.totalSpaces} 个`
  };
}
<!-- HTML -->
@if (getCompletionBadge(project.id); as badge) {
  <span class="badge"
        [style.background-color]="badge.color"
        [title]="badge.tooltip">
    📦 {{ badge.text }}
  </span>
}

场景2:检查项目是否可以交付

async canDeliver(projectId: string): Promise<boolean> {
  const isCompleted = await this.projectSpaceDeliverableService
    .isAllSpacesDelivered(projectId);
  
  if (!isCompleted) {
    const incompleteSpaces = await this.projectSpaceDeliverableService
      .getIncompleteSpaces(projectId);
    
    alert(`以下空间还未完成:${incompleteSpaces.join(', ')}`);
    return false;
  }
  
  return true;
}

场景3:显示进度条

// TypeScript
async loadProgress(projectId: string) {
  this.progress = await this.projectSpaceDeliverableService
    .getProjectDeliveryProgress(projectId);
  
  this.progressColor = this.projectSpaceDeliverableService
    .getDeliveryStatusColor(this.progress);
}
<!-- HTML -->
<div class="progress-bar">
  <div class="progress-fill"
       [style.width.%]="progress"
       [style.background-color]="progressColor">
    {{ progress }}%
  </div>
</div>
// SCSS
.progress-bar {
  width: 100%;
  height: 24px;
  background: #e5e7eb;
  border-radius: 12px;
  overflow: hidden;
  
  .progress-fill {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-size: 12px;
    font-weight: 600;
    transition: width 0.3s, background-color 0.3s;
  }
}

💡 最佳实践

1. 使用缓存避免重复查询

export class YourComponent implements OnInit {
  private statsCache = new Map<string, ProjectSpaceDeliverableSummary>();
  
  async getStats(projectId: string) {
    if (!this.statsCache.has(projectId)) {
      const summary = await this.projectSpaceDeliverableService
        .getProjectSpaceDeliverableSummary(projectId);
      this.statsCache.set(projectId, summary);
    }
    
    return this.statsCache.get(projectId)!;
  }
}

2. 批量加载提高性能

async loadAllStats(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.statsCache.set(projectIds[index], result.value);
    }
  });
}

3. 添加错误处理

async loadStats(projectId: string) {
  try {
    const summary = await this.projectSpaceDeliverableService
      .getProjectSpaceDeliverableSummary(projectId);
    
    this.statsCache.set(projectId, summary);
    
  } catch (error) {
    console.error('加载统计失败:', error);
    
    // 使用默认值
    this.statsCache.set(projectId, {
      projectId,
      projectName: '未知项目',
      totalSpaces: 0,
      spacesWithDeliverables: 0,
      spaces: [],
      totalDeliverableFiles: 0,
      totalByType: {
        whiteModel: 0,
        softDecor: 0,
        rendering: 0,
        postProcess: 0
      },
      overallCompletionRate: 0
    });
  }
}

🎨 实际应用示例

组长端看板卡片

<div class="project-card">
  <!-- 项目标题 -->
  <div class="card-header">
    <h3>{{ project.name }}</h3>
    @if (getStats(project.id); as stats) {
      <span class="completion-badge"
            [style.background-color]="getStatusColor(stats.overallCompletionRate)">
        {{ stats.overallCompletionRate }}%
      </span>
    }
  </div>
  
  <!-- 空间列表 -->
  @if (getStats(project.id); as stats) {
    <div class="spaces-list">
      @for (space of stats.spaces; track space.spaceId) {
        <div class="space-item">
          <span class="space-name">{{ space.spaceName }}</span>
          <span class="space-files">
            📁 {{ space.totalFiles }}
          </span>
          @if (space.hasDeliverables) {
            <span class="check-icon">✅</span>
          } @else {
            <span class="pending-icon">⏳</span>
          }
        </div>
      }
    </div>
  }
  
  <!-- 文件统计 -->
  @if (getStats(project.id); as stats) {
    <div class="file-stats">
      <div class="stat-item">
        <span class="icon">🏗️</span>
        <span class="count">{{ stats.totalByType.whiteModel }}</span>
      </div>
      <div class="stat-item">
        <span class="icon">🎨</span>
        <span class="count">{{ stats.totalByType.softDecor }}</span>
      </div>
      <div class="stat-item">
        <span class="icon">🖼️</span>
        <span class="count">{{ stats.totalByType.rendering }}</span>
      </div>
      <div class="stat-item">
        <span class="icon">✨</span>
        <span class="count">{{ stats.totalByType.postProcess }}</span>
      </div>
    </div>
  }
</div>
.project-card {
  background: white;
  border-radius: 12px;
  padding: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  
  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    
    h3 {
      margin: 0;
      font-size: 16px;
      font-weight: 600;
    }
    
    .completion-badge {
      padding: 4px 12px;
      border-radius: 12px;
      color: white;
      font-size: 12px;
      font-weight: 600;
    }
  }
  
  .spaces-list {
    margin-bottom: 16px;
    
    .space-item {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 8px 0;
      border-bottom: 1px solid #f3f4f6;
      
      &:last-child {
        border-bottom: none;
      }
      
      .space-name {
        flex: 1;
        font-size: 14px;
      }
      
      .space-files {
        font-size: 12px;
        color: #6b7280;
      }
      
      .check-icon {
        font-size: 16px;
      }
      
      .pending-icon {
        font-size: 16px;
        opacity: 0.5;
      }
    }
  }
  
  .file-stats {
    display: flex;
    gap: 12px;
    padding-top: 12px;
    border-top: 1px solid #f3f4f6;
    
    .stat-item {
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 4px;
      
      .icon {
        font-size: 20px;
      }
      
      .count {
        font-size: 14px;
        font-weight: 600;
        color: #374151;
      }
    }
  }
}

📝 TypeScript完整示例

import { Component, OnInit } from '@angular/core';
import { ProjectSpaceDeliverableService, ProjectSpaceDeliverableSummary } 
  from '@modules/project/services/project-space-deliverable.service';

@Component({
  selector: 'app-project-dashboard',
  templateUrl: './project-dashboard.component.html',
  styleUrls: ['./project-dashboard.component.scss']
})
export class ProjectDashboardComponent implements OnInit {
  projects: any[] = [];
  statsCache = new Map<string, ProjectSpaceDeliverableSummary>();
  loading = false;
  
  constructor(
    private projectSpaceDeliverableService: ProjectSpaceDeliverableService
  ) {}
  
  async ngOnInit() {
    await this.loadProjects();
    await this.loadAllStats();
  }
  
  async loadProjects() {
    // 加载项目列表...
    this.projects = []; // 从API获取
  }
  
  async loadAllStats() {
    this.loading = true;
    
    try {
      const projectIds = this.projects.map(p => p.id);
      const promises = projectIds.map(id =>
        this.projectSpaceDeliverableService.getProjectSpaceDeliverableSummary(id)
      );
      
      const results = await Promise.allSettled(promises);
      
      results.forEach((result, index) => {
        if (result.status === 'fulfilled') {
          this.statsCache.set(projectIds[index], result.value);
        } else {
          console.warn(`加载项目 ${projectIds[index]} 统计失败:`, result.reason);
        }
      });
      
    } catch (error) {
      console.error('加载统计失败:', error);
    } finally {
      this.loading = false;
    }
  }
  
  getStats(projectId: string): ProjectSpaceDeliverableSummary | null {
    return this.statsCache.get(projectId) || null;
  }
  
  getStatusColor(completionRate: number): string {
    return this.projectSpaceDeliverableService.getDeliveryStatusColor(completionRate);
  }
  
  getStatusLabel(completionRate: number): string {
    return this.projectSpaceDeliverableService.getDeliveryStatusLabel(completionRate);
  }
  
  formatTooltip(projectId: string): string {
    const stats = this.getStats(projectId);
    if (!stats) return '加载中...';
    
    return `空间: ${stats.spacesWithDeliverables}/${stats.totalSpaces}\n` +
           `文件: ${stats.totalDeliverableFiles}\n` +
           `完成率: ${stats.overallCompletionRate}%`;
  }
}

🔗 相关链接

❓ 常见问题

Q: 如何刷新统计数据?

// 清除缓存并重新加载
this.statsCache.delete(projectId);
await this.loadStats(projectId);

Q: 如何处理加载失败?

async getStats(projectId: string) {
  try {
    if (!this.statsCache.has(projectId)) {
      const summary = await this.projectSpaceDeliverableService
        .getProjectSpaceDeliverableSummary(projectId);
      this.statsCache.set(projectId, summary);
    }
    return this.statsCache.get(projectId)!;
  } catch (error) {
    console.error('加载失败:', error);
    return null; // 返回null,让模板使用@if处理
  }
}

Q: 如何优化大量项目的加载性能?

// 使用分页加载
async loadStatsInPages(projectIds: string[], pageSize = 10) {
  for (let i = 0; i < projectIds.length; i += pageSize) {
    const page = projectIds.slice(i, i + pageSize);
    await this.loadBatch(page);
    
    // 触发UI更新
    this.cdr.markForCheck();
  }
}

private async loadBatch(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.statsCache.set(projectIds[index], result.value);
    }
  });
}

提示:更多高级用法和详细说明,请查看完整使用指南