项目-售后归档.md 87 KB

项目管理 - 售后归档阶段 PRD (Product表版本)

1. 功能概述

1.1 阶段定位

售后归档阶段是项目管理流程的收尾环节,包含尾款结算、全景图合成、客户评价、投诉处理、项目复盘五大核心模块。该阶段负责完成项目交付、收集反馈、总结经验,为后续项目优化提供数据支撑。

1.2 核心目标

  • 多产品设计售后管理:基于Product表实现单产品到多产品项目的差异化管理
  • 实现自动化尾款结算流程
  • 按产品生成全景图分享链接
  • 收集客户多维度评价(按产品维度)
  • 处理客户投诉反馈(产品定位)
  • 生成多产品项目复盘报告

1.3 涉及角色

  • 客服人员:跟进尾款支付、发送评价链接、处理投诉
  • 技术人员:验收交付物、启动自动结算、合成全景图
  • 组长:审核复盘报告、处理投诉、优化流程
  • 财务人员:确认款项到账、核对支付凭证

1.4 多产品设计售后特性

1.4.1 产品差异化售后策略

  • 单产品设计项目:标准售后流程,统一结算和评价
  • 双产品设计项目:按产品独立生成全景图,支持分别评价
  • 多产品设计项目
    • 分产品尾款结算(支持按产品或整体结算)
    • 产品全景图合集(支持单产品查看和全屋漫游)
    • 分产品客户评价(支持整体和细分产品评价)
    • 产品定位的投诉处理
    • 多维度项目复盘(产品间对比分析)

1.4.2 产品间协同售后

graph TD
    A[所有产品设计完成] --> B{是否多产品设计项目?}
    B -->|否| C[单产品设计售后流程]
    B -->|是| D[多产品协同售后]

    D --> E[产品设计质量检查]
    D --> F[跨产品一致性验证]
    D --> G[整体结算策略选择]
    D --> H[全景图合成方案确定]

    E --> I[产品间对比分析]
    F --> I
    G --> J[分产品/整体结算]
    H --> K[产品全景图合集]
    I --> L[多产品复盘报告]
    J --> M[客户多维度评价]
    K --> N[产品漫游体验]
    L --> O[项目归档完成]
    M --> O
    N --> O

    style C fill:#e8f5e9
    style D fill:#fff3e0
    style O fill:#e3f2fd

2. 多产品尾款结算模块

2.1 基于Product表的多产品结算策略

2.1.1 结算模式选择

interface MultiProductSettlementStrategy {
  mode: 'unified' | 'separated' | 'hybrid';     // 统一结算/分产品结算/混合模式
  products: string[];                            // 参与结算的产品ID列表
  settlementBreakdown: ProductSettlementBreakdown[]; // 产品结算明细
  discounts: SettlementDiscount[];               // 多产品优惠
  paymentSchedule: PaymentSchedule[];            // 付款计划
}

interface ProductSettlementBreakdown {
  productId: string;
  productName: string;
  productType: string;                           // "bedroom", "living_room" 等
  totalAmount: number;
  paidAmount: number;
  remainingAmount: number;
  completionPercentage: number;                  // 该产品完成度
  isFullyDelivered: boolean;                    // 是否完全交付
  specialNotes?: string;                        // 特殊说明
}

class MultiProductSettlementManager {
  // 智能推荐结算模式
  recommendSettlementMode(products: Product[]): SettlementModeRecommendation {
    const totalProducts = products.length;
    const completedProducts = products.filter(p => p.status === 'completed').length;
    const highPriorityProducts = products.filter(p => p.space?.priority >= 8).length;

    // 策略1:所有产品都完成且优先级相似,推荐统一结算
    if (completedProducts === totalProducts && this.hasSimilarPriority(products)) {
      return {
        recommendedMode: 'unified',
        confidence: 0.9,
        reason: '所有产品已完成且优先级相近,统一结算更便捷'
      };
    }

    // 策略2:高优先级产品已完成,推荐混合模式
    if (highPriorityProducts > 0 && highPriorityProducts === completedProducts) {
      return {
        recommendedMode: 'hybrid',
        confidence: 0.8,
        reason: '高优先级产品已完成,可以优先结算'
      };
    }

    // 策略3:产品完成情况差异大,推荐分产品结算
    if (this.hasVariableCompletion(products)) {
      return {
        recommendedMode: 'separated',
        confidence: 0.85,
        reason: '各产品完成情况差异较大,分产品结算更清晰'
      };
    }

    // 默认推荐统一结算
    return {
      recommendedMode: 'unified',
      confidence: 0.6,
      reason: '标准项目,统一结算'
    };
  }

  // 计算多产品优惠
  calculateMultiProductDiscount(
    products: Product[],
    baseTotal: number
  ): SettlementDiscount[] {
    const discounts: SettlementDiscount[] = [];

    // 1. 产品数量折扣
    if (products.length >= 5) {
      discounts.push({
        type: 'product_count',
        description: '5产品及以上项目享受10%折扣',
        value: baseTotal * 0.1,
        applicable: true
      });
    } else if (products.length >= 3) {
      discounts.push({
        type: 'product_count',
        description: '3-4产品项目享受5%折扣',
        value: baseTotal * 0.05,
        applicable: true
      });
    }

    // 2. 优先级折扣
    const highPriorityCount = products.filter(p => p.space?.priority >= 8).length;
    if (highPriorityCount === products.length) {
      discounts.push({
        type: 'high_priority',
        description: '全高优先级产品项目额外5%折扣',
        value: baseTotal * 0.05,
        applicable: true
      });
    }

    // 3. 同时完成折扣
    const completedWithinTimeframe = this.getProductsCompletedWithinTimeframe(products, 7); // 7天内
    if (completedWithinTimeframe.length === products.length) {
      discounts.push({
        type: 'simultaneous_completion',
        description: '所有产品同时完成享受3%折扣',
        value: baseTotal * 0.03,
        applicable: true
      });
    }

    return discounts;
  }

  // 生成结算报告
  generateSettlementReport(
    strategy: MultiProductSettlementStrategy,
    products: Product[]
  ): SettlementReport {
    const report: SettlementReport = {
      projectId: this.getProjectId(),
      settlementDate: new Date(),
      strategy: strategy.mode,
      totalProducts: products.length,
      completedProducts: products.filter(p => p.status === 'completed').length,

      // 财务明细
      financials: {
        totalBaseAmount: this.calculateBaseAmount(products),
        discounts: strategy.discounts,
        finalAmount: this.calculateFinalAmount(products, strategy.discounts),
        paidAmount: this.calculatePaidAmount(products),
        remainingAmount: 0
      },

      // 产品详情
      productDetails: strategy.settlementBreakdown.map(breakdown => ({
        productId: breakdown.productId,
        productName: breakdown.productName,
        productType: breakdown.productType,
        totalAmount: breakdown.totalAmount,
        discountApplied: this.getProductDiscount(breakdown.productId, strategy.discounts),
        finalAmount: breakdown.remainingAmount,
        completionStatus: breakdown.isFullyDelivered ? 'completed' : 'partial',
        deliveryQuality: this.assessDeliveryQuality(breakdown.productId)
      })),

      // 风险评估
      riskAssessment: this.assessSettlementRisks(products, strategy),

      // 建议
      recommendations: this.generateSettlementRecommendations(products, strategy)
    };

    return report;
  }
}

interface SettlementModeRecommendation {
  recommendedMode: 'unified' | 'separated' | 'hybrid';
  confidence: number;    // 推荐置信度 0-1
  reason: string;        // 推荐理由
}

interface SettlementDiscount {
  type: 'space_count' | 'high_priority' | 'simultaneous_completion' | 'early_payment';
  description: string;
  value: number;
  applicable: boolean;
}

interface SettlementReport {
  projectId: string;
  settlementDate: Date;
  strategy: string;
  totalSpaces: number;
  completedSpaces: number;
  financials: {
    totalBaseAmount: number;
    discounts: SettlementDiscount[];
    finalAmount: number;
    paidAmount: number;
    remainingAmount: number;
  };
  spaceDetails: any[];
  riskAssessment: any;
  recommendations: string[];
}

3. 多产品全景图合成模块

3.1 基于Product表的产品全景图管理

3.1.1 全景图合成策略

class MultiProductPanoramaManager {
  // 生成产品全景图合集
  async generateProductPanoramaCollection(
    products: Product[],
    synthesisOptions: PanoramaSynthesisOptions
  ): Promise<PanoramaCollection> {

    const collection: PanoramaCollection = {
      id: `collection_${Date.now()}`,
      projectId: this.getProjectId(),
      totalProducts: products.length,
      synthesisStrategy: synthesisOptions.strategy,

      // 单产品全景图
      productPanoramas: [],

      // 跨产品连接
      productConnections: [],

      // 全屋漫游
      fullHouseTour: null,

      // 分享链接
      shareLinks: {
        collection: '',
        individualProducts: {} as Record<string, string>
      },

      createdAt: new Date(),
      status: 'processing'
    };

    // 1. 生成各产品独立全景图
    for (const product of products) {
      const productPanorama = await this.generateProductPanorama(product, synthesisOptions);
      collection.productPanoramas.push(productPanorama);
    }

    // 2. 分析产品间连接关系
    collection.productConnections = await this.analyzeProductConnections(products);

    // 3. 生成全屋漫游(如果是多产品项目)
    if (products.length > 1) {
      collection.fullHouseTour = await this.generateFullHouseTour(
        collection.productPanoramas,
        collection.productConnections
      );
    }

    // 4. 生成分享链接
    collection.shareLinks = await this.generatePanoramaShareLinks(collection);

    // 5. 保存并返回结果
    await this.savePanoramaCollection(collection);

    return collection;
  }

  // 生成单产品全景图
  private async generateProductPanorama(
    product: Product,
    options: PanoramaSynthesisOptions
  ): Promise<ProductPanorama> {

    // 获取产品的最终交付图片
    const finalImages = await this.getProductFinalImages(product.id);

    // KR Panel 集成
    const krPanelConfig = {
      spaceType: product.productType,
      spaceName: product.productName,
      images: finalImages,
      synthesisQuality: options.quality,
      outputFormat: options.format,
      includeHotspots: options.includeHotspots,
      backgroundMusic: options.backgroundMusic
    };

    // 调用 KR Panel 合成
    const panoramaData = await this.krPanelService.synthesizePanorama(krPanelConfig);

    return {
      id: `panorama_${product.id}_${Date.now()}`,
      productId: product.id,
      productName: product.productName,
      productType: product.productType,

      // 全景图资源
      panoramaUrl: panoramaData.panoramaUrl,
      thumbnailUrl: panoramaData.thumbnailUrl,
      previewImages: panoramaData.previewImages,

      // 热点信息
      hotspots: panoramaData.hotspots || [],

      // 技术参数
      resolution: panoramaData.resolution,
      fileSize: panoramaData.fileSize,
      renderTime: panoramaData.renderTime,

      // 元数据
      metadata: {
        createdAt: new Date(),
        synthesisEngine: 'KR Panel',
        quality: options.quality,
        imageCount: finalImages.length
      }
    };
  }

  // 分析产品连接关系
  private async analyzeProductConnections(products: Product[]): Promise<ProductConnection[]> {
    const connections: ProductConnection[] = [];

    // 基于产品类型和位置推断连接关系
    for (let i = 0; i < products.length; i++) {
      for (let j = i + 1; j < products.length; j++) {
        const product1 = products[i];
        const product2 = products[j];

        const connection = await this.determineProductConnection(product1, product2);
        if (connection) {
          connections.push(connection);
        }
      }
    }

    return connections;
  }

  private async determineProductConnection(
    product1: Product,
    product2: Product
  ): Promise<ProductConnection | null> {
    // 定义常见的产品连接关系
    const connectionRules = [
      {
        from: 'living_room',
        to: 'dining_room',
        type: 'direct',
        transitionStyle: 'open_passage',
        likelihood: 0.9
      },
      {
        from: 'living_room',
        to: 'corridor',
        type: 'direct',
        transitionStyle: 'doorway',
        likelihood: 0.8
      },
      {
        from: 'bedroom',
        to: 'corridor',
        type: 'direct',
        transitionStyle: 'doorway',
        likelihood: 0.9
      },
      {
        from: 'kitchen',
        to: 'dining_room',
        type: 'direct',
        transitionStyle: 'open_passage',
        likelihood: 0.7
      }
    ];

    // 查找匹配的连接规则
    const rule = connectionRules.find(r =>
      (r.from === product1.productType && r.to === product2.productType) ||
      (r.from === product2.productType && r.to === product1.productType)
    );

    if (rule && rule.likelihood > 0.6) {
      return {
        fromProductId: product1.id,
        toProductId: product2.id,
        connectionType: rule.type,
        transitionStyle: rule.transitionStyle,
        confidence: rule.likelihood,
        navigationLabel: `${product1.productName} → ${product2.productName}`,
        estimatedDistance: this.estimateProductDistance(product1, product2)
      };
    }

    return null;
  }

  // 生成全屋漫游
  private async generateFullHouseTour(
    panoramas: ProductPanorama[],
    connections: ProductConnection[]
  ): Promise<FullHouseTour> {

    // 构建漫游路径
    const tourPath = this.optimizeTourPath(panoramas, connections);

    // 生成导航数据
    const navigationData = {
      panoramas: panoramas.map(p => ({
        id: p.id,
        name: p.productName,
        type: p.productType,
        url: p.panoramaUrl,
        hotspots: p.hotspots
      })),
      connections: connections.map(c => ({
        from: c.fromProductId,
        to: c.toProductId,
        type: c.connectionType,
        style: c.transitionStyle,
        label: c.navigationLabel
      })),
      path: tourPath
    };

    // 生成漫游配置
    const tourConfig = {
      autoPlay: true,
      transitionDuration: 2000,
      pauseDuration: 5000,
      showNavigation: true,
      backgroundMusic: 'soft_ambient',
      quality: 'high'
    };

    return {
      id: `tour_${Date.now()}`,
      navigationData,
      tourConfig,
      totalDuration: this.calculateTourDuration(tourPath, tourConfig),
      estimatedSize: this.estimateTourSize(panoramas),
      generatedAt: new Date()
    };
  }
}

interface PanoramaSynthesisOptions {
  strategy: 'individual' | 'connected' | 'full_house';
  quality: 'standard' | 'high' | 'ultra';
  format: 'jpg' | 'png' | 'webp';
  includeHotspots: boolean;
  backgroundMusic?: string;
  maxFileSize?: number;
}

interface PanoramaCollection {
  id: string;
  projectId: string;
  totalProducts: number;
  synthesisStrategy: string;
  productPanoramas: ProductPanorama[];
  productConnections: ProductConnection[];
  fullHouseTour?: FullHouseTour;
  shareLinks: {
    collection: string;
    individualProducts: Record<string, string>;
  };
  createdAt: Date;
  status: 'processing' | 'completed' | 'failed';
}

interface ProductPanorama {
  id: string;
  productId: string;
  productName: string;
  productType: string;
  panoramaUrl: string;
  thumbnailUrl: string;
  previewImages: string[];
  hotspots: PanoramaHotspot[];
  resolution: { width: number; height: number };
  fileSize: number;
  renderTime: number;
  metadata: any;
}

interface ProductConnection {
  fromProductId: string;
  toProductId: string;
  connectionType: 'direct' | 'indirect' | 'external';
  transitionStyle: 'doorway' | 'open_passage' | 'stair' | 'corridor';
  confidence: number;
  navigationLabel: string;
  estimatedDistance: number;
}

interface FullHouseTour {
  id: string;
  navigationData: any;
  tourConfig: any;
  totalDuration: number;
  estimatedSize: number;
  generatedAt: Date;
}

4. 多产品客户评价模块

4.1 基于Product表的分产品评价系统

4.1.1 多维度评价结构

interface MultiProductCustomerReview {
  id: string;
  projectId: string;
  submittedAt: Date;

  // 整体评价
  overallReview: OverallReview;

  // 产品评价
  productReviews: ProductReview[];

  // 跨产品评价
  crossProductReview: CrossProductReview;

  // 推荐意愿
  recommendations: RecommendationData;
}

interface OverallReview {
  // 整体满意度评分 (1-5星)
  overallSatisfaction: number;

  // 多维度评分
  dimensionRatings: {
    designQuality: number;         // 设计质量
    productPlanning: number;       // 产品规划
    colorCoordination: number;     // 色彩协调
    functionality: number;         // 功能性
    timeliness: number;            // 及时性
    communication: number;         // 沟通效率
    professionalism: number;       // 专业程度
    valueForMoney: number;         // 性价比
  };

  // 文字评价
  comments: {
    strengths: string;            // 优点
    improvements: string;         // 改进建议
    overallImpression: string;    // 整体印象
    additionalComments: string;   // 其他意见
  };

  // 最满意和最不满意的产品
  mostSatisfiedProduct?: string;
  leastSatisfiedProduct?: string;
}

interface ProductReview {
  productId: string;
  productName: string;
  productType: string;             // "bedroom", "living_room" 等

  // 产品满意度评分
  satisfactionScore: number;

  // 产品特定评分
  productSpecificRatings: {
    layoutDesign: number;         // 布局设计
    functionality: number;         // 功能实现
    aestheticAppeal: number;       // 美观度
    practicality: number;         // 实用性
    storageSolutions: number;      // 收纳方案
    lighting: number;             // 灯光效果
  };

  // 产品使用反馈
  usageFeedback: {
    actualUsage: string;           // 实际使用情况
    favoriteFeatures: string[];    // 最喜欢的特点
    issuesEncountered: string[];   // 遇到的问题
    modifications: string[];       // 后续改动
    unexpectedBenefits: string[];  // 意外收获
  };

  // 产品文字评价
  comments: {
    whatWorkedWell: string;       // 做得好的地方
    whatCouldBeBetter: string;     // 可以改进的地方
    personalNotes: string;         // 个人备注
  };

  // 照片上传(实际使用后的照片)
  afterPhotos?: string[];
}

interface CrossProductReview {
  // 产品间一致性
  consistencyRatings: {
    styleConsistency: number;      // 风格一致性
    colorFlow: number;             // 色彩流线
    materialHarmony: number;       // 材质和谐
    scaleProportion: number;       // 比例协调
  };

  // 动线体验
  circulationExperience: {
    flowLogic: number;             // 流线逻辑性
    transitionSmoothness: number;  // 过渡流畅度
    accessibility: number;         // 便利性
  };

  // 跨产品评价
  crossProductComments: {
    productRelationships: string;  // 产品关系
    overallCohesion: string;       // 整体协调性
    suggestedImprovements: string; // 改进建议
  };
}

interface RecommendationData {
  wouldRecommend: boolean;         // 是否推荐
  likelihoodScore: number;         // 推荐意愿 0-10

  // 推荐理由
  recommendationReasons: string[];

  // 不推荐原因(如果不推荐)
  nonRecommendationReasons?: string[];

  // 推荐给的人群
  recommendedFor: string[];

  // 联系信息(允许联系)
  contactPermission: boolean;
  contactInfo?: {
    wechat?: string;
    phone?: string;
    email?: string;
  };
}

class MultiProductReviewManager {
  // 生成分产品评价链接
  async generateMultiProductReviewLinks(
    projectId: string,
    products: Product[]
  ): Promise<MultiProductReviewLinks> {

    const links: MultiProductReviewLinks = {
      projectId,
      collectionLink: '',
      productLinks: {} as Record<string, string>,
      expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30天后过期
      createdAt: new Date()
    };

    // 1. 生成合集评价链接
    links.collectionLink = await this.generateCollectionReviewLink(projectId, products);

    // 2. 生成各产品独立评价链接
    for (const product of products) {
      const productLink = await this.generateProductReviewLink(projectId, product);
      links.productLinks[product.id] = productLink;
    }

    // 3. 保存链接记录
    await this.saveReviewLinks(links);

    return links;
  }

  // 处理多产品评价提交
  async processMultiProductReview(
    reviewData: MultiProductCustomerReview
  ): Promise<ReviewProcessingResult> {

    const result: ReviewProcessingResult = {
      success: false,
      reviewId: '',
      processingSteps: []
    };

    try {
      // 1. 验证评价数据
      await this.validateReviewData(reviewData);
      result.processingSteps.push({ step: 'validation', status: 'completed' });

      // 2. 保存评价数据
      const savedReview = await this.saveMultiProductReview(reviewData);
      result.reviewId = savedReview.id;
      result.processingSteps.push({ step: 'saving', status: 'completed' });

      // 3. 计算评价统计
      const statistics = await this.calculateReviewStatistics(reviewData);
      result.processingSteps.push({ step: 'statistics', status: 'completed' });

      // 4. 更新项目评分
      await this.updateProjectRating(reviewData.projectId, statistics);
      result.processingSteps.push({ step: 'rating_update', status: 'completed' });

      // 5. 发送通知
      await this.sendReviewNotifications(reviewData);
      result.processingSteps.push({ step: 'notifications', status: 'completed' });

      result.success = true;

    } catch (error) {
      console.error('处理多产品评价失败:', error);
      result.error = error.message;
    }

    return result;
  }

  // 分析评价数据
  async analyzeMultiProductReviews(
    projectId: string
  ): Promise<MultiProductReviewAnalysis> {

    // 获取项目的所有评价
    const reviews = await this.getProjectReviews(projectId);

    const analysis: MultiProductReviewAnalysis = {
      projectId,
      totalReviews: reviews.length,

      // 整体分析
      overallAnalysis: this.analyzeOverallReviews(reviews),

      // 产品分析
      productAnalysis: this.analyzeProductReviews(reviews),

      // 跨产品分析
      crossProductAnalysis: this.analyzeCrossProductReviews(reviews),

      // 趋势分析
      trendAnalysis: this.analyzeReviewTrends(reviews),

      // 改进建议
      improvementSuggestions: this.generateImprovementSuggestions(reviews),

      // 对比分析
      benchmarkComparison: await this.benchmarkAgainstIndustry(reviews)
    };

    return analysis;
  }

  private analyzeProductReviews(reviews: MultiProductCustomerReview[]): Record<string, ProductAnalysis> {
    const productAnalysis: Record<string, ProductAnalysis> = {};

    // 按产品分组统计
    const reviewsByProduct: Record<string, ProductReview[]> = {};

    for (const review of reviews) {
      for (const productReview of review.productReviews) {
        if (!reviewsByProduct[productReview.productId]) {
          reviewsByProduct[productReview.productId] = [];
        }
        reviewsByProduct[productReview.productId].push(productReview);
      }
    }

    // 分析每个产品
    for (const [productId, productReviews] of Object.entries(reviewsByProduct)) {
      const satisfactionScores = productReviews.map(r => r.satisfactionScore);
      const averageSatisfaction = satisfactionScores.reduce((a, b) => a + b, 0) / satisfactionScores.length;

      productAnalysis[productId] = {
        productId,
        productName: productReviews[0].productName,
        productType: productReviews[0].productType,
        totalReviews: productReviews.length,
        averageSatisfaction,
        satisfactionDistribution: this.calculateSatisfactionDistribution(satisfactionScores),

        // 详细评分分析
        dimensionAverages: this.calculateDimensionAverages(productReviews),

        // 常见反馈
        commonStrengths: this.extractCommonStrengths(productReviews),
        commonIssues: this.extractCommonIssues(productReviews),

        // 改进建议
        improvementSuggestions: this.generateProductImprovementSuggestions(productReviews)
      };
    }

    return productAnalysis;
  }
}

interface MultiProductReviewLinks {
  projectId: string;
  collectionLink: string;
  productLinks: Record<string, string>;
  expiresAt: Date;
  createdAt: Date;
}

interface ReviewProcessingResult {
  success: boolean;
  reviewId: string;
  processingSteps: Array<{
    step: string;
    status: 'completed' | 'failed' | 'skipped';
    error?: string;
  }>;
  error?: string;
}

interface MultiProductReviewAnalysis {
  projectId: string;
  totalReviews: number;
  overallAnalysis: any;
  productAnalysis: Record<string, ProductAnalysis>;
  crossProductAnalysis: any;
  trendAnalysis: any;
  improvementSuggestions: string[];
  benchmarkComparison: any;
}

interface ProductAnalysis {
  productId: string;
  productName: string;
  productType: string;
  totalReviews: number;
  averageSatisfaction: number;
  satisfactionDistribution: Record<string, number>;
  dimensionAverages: Record<string, number>;
  commonStrengths: string[];
  commonIssues: string[];
  improvementSuggestions: string[];
}

5. 多产品投诉处理模块

5.1 基于Product表的产品定位投诉系统

5.1.1 产品投诉分类

class MultiProductComplaintManager {
  // 创建产品相关投诉
  async createProductComplaint(
    complaintData: ProductComplaintData
  ): Promise<ProductComplaint> {

    const complaint: ProductComplaint = {
      id: `complaint_${Date.now()}`,
      projectId: complaintData.projectId,

      // 投诉分类
      category: complaintData.category,
      subcategory: complaintData.subcategory,

      // 产品信息
      productId: complaintData.productId,
      productName: complaintData.productName,
      productType: complaintData.productType,
      affectedProducts: complaintData.affectedProducts || [],

      // 投诉内容
      title: complaintData.title,
      description: complaintData.description,
      severity: complaintData.severity,

      // 客户信息
      customerInfo: complaintData.customerInfo,

      // 处理信息
      status: 'pending',
      priority: this.calculateComplaintPriority(complaintData),
      assignedTo: null,
      assignedAt: null,

      // 时间信息
      createdAt: new Date(),
      expectedResolutionTime: this.calculateExpectedResolutionTime(complaintData),

      // 附件
      attachments: complaintData.attachments || []
    };

    // 保存投诉
    const savedComplaint = await this.saveProductComplaint(complaint);

    // 自动分析并分配
    await this.autoAnalyzeAndAssign(complaint);

    // 发送通知
    await this.sendComplaintNotifications(complaint);

    return savedComplaint;
  }

  // 产品投诉智能分类
  private classifyProductComplaint(description: string, productType: string): ComplaintClassification {
    const classification: ComplaintClassification = {
      category: 'other',
      subcategory: 'general',
      confidence: 0,
      keywords: []
    };

    // 产品特定关键词库
    const productSpecificKeywords = {
      'living_room': {
        'sofa': { category: 'furniture', subcategory: 'seating' },
        'tv': { category: 'electronics', subcategory: 'entertainment' },
        'lighting': { category: 'lighting', subcategory: 'ambient' },
        'storage': { category: 'storage', subcategory: 'display' }
      },
      'bedroom': {
        'bed': { category: 'furniture', subcategory: 'sleeping' },
        'wardrobe': { category: 'storage', subcategory: 'clothing' },
        'lighting': { category: 'lighting', subcategory: 'task' },
        'noise': { category: 'environmental', subcategory: 'acoustic' }
      },
      'kitchen': {
        'cabinet': { category: 'furniture', subcategory: 'storage' },
        'countertop': { category: 'materials', subcategory: 'surface' },
        'appliances': { category: 'equipment', subcategory: 'kitchen' },
        'plumbing': { category: 'systems', subcategory: 'water' }
      }
    };

    // 通用关键词
    const generalKeywords = {
      'color': { category: 'aesthetics', subcategory: 'color' },
      'size': { category: 'layout', subcategory: 'dimensions' },
      'quality': { category: 'quality', subcategory: 'materials' },
      'function': { category: 'functionality', subcategory: 'usage' },
      'delivery': { category: 'service', subcategory: 'timeline' }
    };

    // 分析描述中的关键词
    const allKeywords = {
      ...generalKeywords,
      ...(productSpecificKeywords[productType] || {})
    };

    const foundKeywords: Array<{ keyword: string; classification: any; confidence: number }> = [];

    for (const [keyword, classification] of Object.entries(allKeywords)) {
      if (description.toLowerCase().includes(keyword.toLowerCase())) {
        foundKeywords.push({
          keyword,
          classification,
          confidence: 0.8
        });
      }
    }

    if (foundKeywords.length > 0) {
      // 选择置信度最高的分类
      const bestMatch = foundKeywords.reduce((best, current) =>
        current.confidence > best.confidence ? current : best
      );

      classification.category = bestMatch.classification.category;
      classification.subcategory = bestMatch.classification.subcategory;
      classification.confidence = bestMatch.confidence;
      classification.keywords = foundKeywords.map(f => f.keyword);
    }

    return classification;
  }

  // 跨产品投诉处理
  async handleCrossProductComplaint(
    complaintData: CrossProductComplaintData
  ): Promise<CrossProductComplaint> {

    const complaint: CrossProductComplaint = {
      id: `cross_product_complaint_${Date.now()}`,
      projectId: complaintData.projectId,

      // 跨产品特有字段
      primaryProductId: complaintData.primaryProductId,
      affectedProducts: complaintData.affectedProducts,
      relationshipType: complaintData.relationshipType, // 'style_inconsistency', 'functional_conflict', 'transition_issue'

      // 投诉内容
      title: complaintData.title,
      description: complaintData.description,
      category: 'cross_product',
      severity: complaintData.severity,

      // 处理信息
      status: 'pending',
      requiresMultiProductCoordination: true,
      assignedTeam: this.assignCrossProductTeam(complaintData),

      // 时间信息
      createdAt: new Date(),
      expectedResolutionTime: this.calculateCrossProductResolutionTime(complaintData)
    };

    // 分析产品间关系
    complaint.productRelationshipAnalysis = await this.analyzeProductRelationship(
      complaint.primaryProductId,
      complaint.affectedProducts
    );

    // 保存并处理
    const savedComplaint = await this.saveCrossProductComplaint(complaint);
    await this.initiateCrossProductResolution(complaint);

    return savedComplaint;
  }

  // 生成投诉处理报告
  async generateProductComplaintReport(
    projectId: string,
    timeRange?: { start: Date; end: Date }
  ): Promise<ProductComplaintReport> {

    const complaints = await this.getProjectProductComplaints(projectId, timeRange);

    const report: ProductComplaintReport = {
      projectId,
      reportPeriod: timeRange || { start: new Date(0), end: new Date() },

      // 统计概览
      overview: {
        totalComplaints: complaints.length,
        resolvedComplaints: complaints.filter(c => c.status === 'resolved').length,
        pendingComplaints: complaints.filter(c => c.status === 'pending').length,
        averageResolutionTime: this.calculateAverageResolutionTime(complaints),
        complaintRate: this.calculateComplaintRate(complaints)
      },

      // 产品分布
      productDistribution: this.analyzeComplaintProductDistribution(complaints),

      // 分类统计
      categoryBreakdown: this.analyzeComplaintCategories(complaints),

      // 严重程度分析
      severityAnalysis: this.analyzeComplaintSeverity(complaints),

      // 处理效率
      resolutionEfficiency: this.analyzeResolutionEfficiency(complaints),

      // 改进建议
      recommendations: this.generateComplaintResolutionRecommendations(complaints),

      // 趋势分析
      trends: this.analyzeComplaintTrends(complaints)
    };

    return report;
  }
}

interface ProductComplaintData {
  projectId: string;
  productId: string;
  productName: string;
  productType: string;
  affectedProducts?: string[];
  category: string;
  subcategory: string;
  title: string;
  description: string;
  severity: 'low' | 'medium' | 'high' | 'critical';
  customerInfo: any;
  attachments?: any[];
}

interface CrossProductComplaintData {
  projectId: string;
  primaryProductId: string;
  affectedProducts: string[];
  relationshipType: 'style_inconsistency' | 'functional_conflict' | 'transition_issue';
  title: string;
  description: string;
  severity: 'low' | 'medium' | 'high' | 'critical';
}

interface ProductComplaint {
  id: string;
  projectId: string;
  category: string;
  subcategory: string;
  productId: string;
  productName: string;
  productType: string;
  affectedProducts: string[];
  title: string;
  description: string;
  severity: string;
  customerInfo: any;
  status: string;
  priority: number;
  assignedTo: string;
  assignedAt: Date;
  createdAt: Date;
  expectedResolutionTime: Date;
  attachments: any[];
}

interface CrossProductComplaint {
  id: string;
  projectId: string;
  primaryProductId: string;
  affectedProducts: string[];
  relationshipType: string;
  title: string;
  description: string;
  category: string;
  severity: string;
  status: string;
  requiresMultiProductCoordination: boolean;
  assignedTeam: string[];
  productRelationshipAnalysis: any;
  createdAt: Date;
  expectedResolutionTime: Date;
}

interface ProductComplaintReport {
  projectId: string;
  reportPeriod: { start: Date; end: Date };
  overview: any;
  productDistribution: any;
  categoryBreakdown: any;
  severityAnalysis: any;
  resolutionEfficiency: any;
  recommendations: string[];
  trends: any;
}

6. 多产品项目复盘模块

6.1 基于Product表的产品对比分析

6.1.1 产品绩效对比

class MultiProductReviewManager {
  // 生成多产品项目复盘报告
  async generateMultiProductReviewReport(
    projectId: string,
    options?: ReviewReportOptions
  ): Promise<MultiProductReviewReport> {

    const report: MultiProductReviewReport = {
      id: `review_report_${Date.now()}`,
      projectId,
      reportType: 'multi_product',
      generatedAt: new Date(),

      // 项目概览
      projectOverview: await this.generateProjectOverview(projectId),

      // 产品对比分析
      productComparison: await this.generateProductComparison(projectId),

      // 跨产品分析
      crossProductAnalysis: await this.generateCrossProductAnalysis(projectId),

      // 效率分析
      efficiencyAnalysis: await this.generateEfficiencyAnalysis(projectId),

      // 客户满意度分析
      satisfactionAnalysis: await this.generateSatisfactionAnalysis(projectId),

      // 改进建议
      improvementRecommendations: await this.generateImprovementRecommendations(projectId),

      // 经验总结
      lessonsLearned: await this.extractLessonsLearned(projectId)
    };

    return report;
  }

  // 生成产品对比分析
  private async generateProductComparison(projectId: string): Promise<ProductComparisonAnalysis> {
    const products = await this.getProjectProducts(projectId);

    const comparison: ProductComparisonAnalysis = {
      products: products.map(product => ({
        productId: product.id,
        productName: product.productName,
        productType: product.productType,
        metrics: {} as ProductMetrics
      })),

      // 对比维度
      comparisonMetrics: [
        'deliveryTime',
        'qualityScore',
        'customerSatisfaction',
        'budgetPerformance',
        'revisionCount',
        'teamEfficiency'
      ],

      // 产品排名
      productRankings: {} as Record<string, Record<string, number>>,

      // 最佳实践
      bestPractices: {},

      // 改进产品
      improvementAreas: {}
    };

    // 计算各产品指标
    for (const product of products) {
      comparison.products.find(p => p.productId === product.id)!.metrics =
        await this.calculateProductMetrics(product.id);
    }

    // 生成产品排名
    for (const metric of comparison.comparisonMetrics) {
      const ranked = comparison.products
        .sort((a, b) => (b.metrics as any)[metric] - (a.metrics as any)[metric])
        .map((product, index) => ({
          productId: product.productId,
          rank: index + 1,
          value: (product.metrics as any)[metric]
        }));

      comparison.productRankings[metric] = ranked;
    }

    // 识别最佳实践
    comparison.bestPractices = this.identifyBestPractices(comparison.products);

    // 识别改进区域
    comparison.improvementAreas = this.identifyImprovementAreas(comparison.products);

    return comparison;
  }

  // 计算产品指标
  private async calculateProductMetrics(productId: string): Promise<ProductMetrics> {
    const metrics: ProductMetrics = {
      // 时间指标
      deliveryTime: await this.calculateProductDeliveryTime(productId),
      onTimeDelivery: await this.calculateOnTimeDeliveryRate(productId),

      // 质量指标
      qualityScore: await this.calculateProductQualityScore(productId),
      revisionCount: await this.countProductRevisions(productId),
      reworkRate: await this.calculateProductReworkRate(productId),

      // 客户满意度
      customerSatisfaction: await this.calculateProductCustomerSatisfaction(productId),
      customerComplaints: await this.countProductComplaints(productId),

      // 财务指标
      budgetPerformance: await this.calculateProductBudgetPerformance(productId),
      profitability: await this.calculateProductProfitability(productId),

      // 团队效率
      teamEfficiency: await this.calculateProductTeamEfficiency(productId),
      resourceUtilization: await this.calculateProductResourceUtilization(productId)
    };

    return metrics;
  }

  // 识别最佳实践
  private identifyBestPractices(products: any[]): Record<string, BestPractice[]> {
    const bestPractices: Record<string, BestPractice[]> = {};

    // 找出各维度表现最好的产品
    const topPerformers = {
      deliveryTime: this.getTopPerformer(products, 'deliveryTime', 'asc'),      // 时间越短越好
      qualityScore: this.getTopPerformer(products, 'qualityScore', 'desc'),    // 质量越高越好
      customerSatisfaction: this.getTopPerformer(products, 'customerSatisfaction', 'desc'),
      budgetPerformance: this.getTopPerformer(products, 'budgetPerformance', 'desc'),
      teamEfficiency: this.getTopPerformer(products, 'teamEfficiency', 'desc')
    };

    // 提取最佳实践
    for (const [metric, performer] of Object.entries(topPerformers)) {
      const practices = await this.extractBestPractices(performer.productId, metric);
      bestPractices[metric] = practices;
    }

    return bestPractices;
  }

  // 生成跨产品分析
  private async generateCrossProductAnalysis(projectId: string): Promise<CrossProductAnalysis> {
    const analysis: CrossProductAnalysis = {
      // 风格一致性分析
      styleConsistency: await this.analyzeStyleConsistency(projectId),

      // 功能协调性分析
      functionalCoordination: await this.analyzeFunctionalCoordination(projectId),

      // 产品流线分析
      circulationFlow: await this.analyzeCirculationFlow(projectId),

      // 资源配置分析
      resourceAllocation: await this.analyzeResourceAllocation(projectId),

      // 时间协调分析
      timeCoordination: await this.analyzeTimeCoordination(projectId)
    };

    return analysis;
  }

  // 风格一致性分析
  private async analyzeStyleConsistency(projectId: string): Promise<StyleConsistencyAnalysis> {
    const products = await this.getProjectProducts(projectId);

    // 提取各产品的设计元素
    const designElements = await Promise.all(
      products.map(product => this.extractProductDesignElements(product.id))
    );

    // 分析一致性
    const consistencyAnalysis: StyleConsistencyAnalysis = {
      overallConsistencyScore: this.calculateOverallConsistency(designElements),

      // 具体维度分析
      colorConsistency: this.analyzeColorConsistency(designElements),
      materialConsistency: this.analyzeMaterialConsistency(designElements),
      styleConsistency: this.analyzeStyleConsistency(designElements),
      scaleConsistency: this.analyzeScaleConsistency(designElements),

      // 不一致点识别
      inconsistencies: this.identifyStyleInconsistencies(designElements),

      // 改进建议
      recommendations: this.generateStyleConsistencyRecommendations(designElements)
    };

    return consistencyAnalysis;
  }

  // 生成效率分析
  private async generateEfficiencyAnalysis(projectId: string): Promise<EfficiencyAnalysis> {
    const analysis: EfficiencyAnalysis = {
      // 时间效率
      timeEfficiency: await this.analyzeTimeEfficiency(projectId),

      // 资源效率
      resourceEfficiency: await this.analyzeResourceEfficiency(projectId),

      // 流程效率
      processEfficiency: await this.analyzeProcessEfficiency(projectId),

      // 协作效率
      collaborationEfficiency: await this.analyzeCollaborationEfficiency(projectId),

      // 效率瓶颈
      bottlenecks: await this.identifyEfficiencyBottlenecks(projectId),

      // 优化建议
      optimizationSuggestions: await this.generateEfficiencyOptimizationSuggestions(projectId)
    };

    return analysis;
  }
}

interface MultiProductReviewReport {
  id: string;
  projectId: string;
  reportType: string;
  generatedAt: Date;
  projectOverview: any;
  productComparison: ProductComparisonAnalysis;
  crossProductAnalysis: CrossProductAnalysis;
  efficiencyAnalysis: EfficiencyAnalysis;
  satisfactionAnalysis: any;
  improvementRecommendations: any[];
  lessonsLearned: string[];
}

interface ProductComparisonAnalysis {
  products: Array<{
    productId: string;
    productName: string;
    productType: string;
    metrics: ProductMetrics;
  }>;
  comparisonMetrics: string[];
  productRankings: Record<string, Array<{
    productId: string;
    rank: number;
    value: number;
  }>>;
  bestPractices: Record<string, BestPractice[]>;
  improvementAreas: Record<string, ImprovementArea[]>;
}

interface ProductMetrics {
  deliveryTime: number;
  onTimeDelivery: number;
  qualityScore: number;
  revisionCount: number;
  reworkRate: number;
  customerSatisfaction: number;
  customerComplaints: number;
  budgetPerformance: number;
  profitability: number;
  teamEfficiency: number;
  resourceUtilization: number;
}

interface BestPractice {
  title: string;
  description: string;
  applicableTo: SpaceType[];
  impactLevel: 'high' | 'medium' | 'low';
  implementationComplexity: 'simple' | 'moderate' | 'complex';
}

interface CrossSpaceAnalysis {
  styleConsistency: StyleConsistencyAnalysis;
  functionalCoordination: any;
  circulationFlow: any;
  resourceAllocation: any;
  timeCoordination: any;
}

interface StyleConsistencyAnalysis {
  overallConsistencyScore: number;
  colorConsistency: any;
  materialConsistency: any;
  styleConsistency: any;
  scaleConsistency: any;
  inconsistencies: any[];
  recommendations: string[];
}

interface EfficiencyAnalysis {
  timeEfficiency: any;
  resourceEfficiency: any;
  processEfficiency: any;
  collaborationEfficiency: any;
  bottlenecks: any[];
  optimizationSuggestions: string[];
}

文档版本:v3.0 (Product表统一空间管理) 更新日期:2025-10-20 维护者:YSS Development Team

2.1 功能特点

  • 技术验收触发自动化结算
  • 小程序支付自动监听
  • 支付凭证智能识别
  • 渲染大图自动解锁
  • 客服一键发图

2.2 自动化结算流程

2.2.1 启动自动化结算

// project-detail.ts lines 3892-3938
initiateAutoSettlement(): void {
  console.log('🚀 启动自动化尾款结算流程');

  // 1. 权限验证
  if (!this.isTechnicalView()) {
    alert('⚠️ 仅技术人员可以启动自动化结算流程');
    return;
  }

  // 2. 验收状态检查
  if (!this.isAllDeliveryCompleted()) {
    alert('⚠️ 请先完成所有交付阶段验收');
    return;
  }

  console.log('✅ 验收状态检查通过');

  // 3. 激活小程序支付监听
  this.miniprogramPaymentStatus = 'active';
  console.log('📱 小程序支付监听已激活');

  // 4. 创建尾款结算记录
  this.createFinalPaymentRecord();

  // 5. 通知客服跟进尾款
  this.notifyCustomerServiceForFinalPayment();

  // 6. 启动支付自动化
  this.setupPaymentAutomation();

  alert('✅ 自动化结算流程已启动!\n\n- 小程序支付监听已激活\n- 客服已收到尾款跟进通知\n- 支付到账后将自动解锁大图');
}

权限验证

  • 仅技术人员可以启动
  • 确保所有交付阶段已完成
  • 验证交付物质量合格

2.2.2 创建结算记录

// project-detail.ts lines 3940-3962
private createFinalPaymentRecord(): void {
  const totalAmount = this.orderAmount || 150000;
  const downPayment = totalAmount * 0.5; // 假设定金50%
  const remainingAmount = totalAmount - downPayment;

  this.settlementRecord = {
    id: `settlement-${Date.now()}`,
    projectId: this.projectId,
    totalAmount: totalAmount,
    downPayment: downPayment,
    remainingAmount: remainingAmount,
    paidAmount: 0,
    status: 'pending',
    createdAt: new Date(),
    dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7天后到期
    paymentMethod: undefined,
    paidAt: undefined,
    notes: '技术验收完成,等待客户支付尾款'
  };

  console.log('📝 尾款结算记录已创建:', this.settlementRecord);
}

结算记录结构

interface SettlementRecord {
  id: string;
  projectId: string;
  totalAmount: number;          // 订单总金额
  downPayment: number;          // 定金金额
  remainingAmount: number;      // 尾款金额
  paidAmount: number;           // 已支付金额
  status: 'pending' | 'partial' | 'completed' | 'overdue';
  createdAt: Date;
  dueDate: Date;                // 到期日期
  paymentMethod?: 'wechat' | 'alipay' | 'bank';
  paidAt?: Date;
  voucherUrl?: string;          // 支付凭证URL
  notes?: string;
}

2.2.3 通知客服跟进

// project-detail.ts lines 3964-3978
private notifyCustomerServiceForFinalPayment(): void {
  const notification = {
    type: 'final-payment-reminder',
    projectId: this.projectId,
    projectName: this.project?.name || '未命名项目',
    amount: this.settlementRecord?.remainingAmount || 0,
    dueDate: this.settlementRecord?.dueDate,
    message: `项目"${this.project?.name}"已完成技术验收,请跟进客户支付尾款 ¥${this.settlementRecord?.remainingAmount.toLocaleString()}`
  };

  // 实际应用中调用通知服务
  console.log('📧 已发送客服通知:', notification);

  // 模拟通知发送
  alert(`✉️ 已通知客服跟进尾款\n\n项目: ${notification.projectName}\n尾款金额: ¥${notification.amount.toLocaleString()}`);
}

2.3 支付监听系统

2.3.1 小程序支付监听

// project-detail.ts lines 3980-4012
private setupPaymentAutomation(): void {
  console.log('🔧 设置支付自动化监听');

  // 监听小程序支付状态变化
  // 实际应用中应使用WebSocket或轮询API
  this.miniprogramPaymentStatus = 'active';

  // 模拟支付监听(实际项目中应替换为真实的WebSocket连接)
  this.simulatePaymentMonitoring();
}

private simulatePaymentMonitoring(): void {
  console.log('🔄 开始模拟支付监听...');

  // 实际项目中应该是:
  // 1. 建立WebSocket连接到支付服务器
  // 2. 监听支付成功事件
  // 3. 接收支付信息(金额、方式、时间等)
  // 4. 自动触发解锁流程

  // 这里仅作演示,实际不会自动触发
  console.log('💡 提示: 客户通过小程序支付后,系统将自动接收通知');
  console.log('💡 提示: 支付凭证识别功能可手动上传截图触发');
}

监听流程

sequenceDiagram
    participant Customer as 客户
    participant MiniApp as 小程序
    participant PaymentGateway as 支付网关
    participant System as 系统
    participant Designer as 设计师

    Customer->>MiniApp: 发起尾款支付
    MiniApp->>PaymentGateway: 调用支付接口
    PaymentGateway-->>MiniApp: 支付成功回调
    MiniApp->>System: 推送支付通知
    System->>System: 更新结算状态
    System->>System: 解锁渲染大图
    System->>Designer: 通知客服发图

2.3.2 支付到账处理

// project-detail.ts lines 4014-4048
onPaymentReceived(paymentInfo?: any): void {
  console.log('💰 收到支付通知:', paymentInfo);

  if (!this.settlementRecord) {
    console.error('❌ 结算记录不存在');
    return;
  }

  // 更新结算状态
  this.settlementRecord.status = 'completed';
  this.settlementRecord.paidAmount = paymentInfo?.amount || this.settlementRecord.remainingAmount;
  this.settlementRecord.paymentMethod = paymentInfo?.method || 'wechat';
  this.settlementRecord.paidAt = new Date();

  console.log('✅ 结算状态已更新:', this.settlementRecord);

  // 自动解锁渲染大图
  this.autoUnlockAndSendImages();

  // 发送支付确认通知
  this.sendPaymentConfirmationNotifications();

  // 停止支付监听
  this.miniprogramPaymentStatus = 'completed';

  console.log('🎉 尾款结算流程完成');
}

2.3.3 自动解锁大图

// project-detail.ts lines 4050-4068
private autoUnlockAndSendImages(): void {
  console.log('🔓 开始自动解锁渲染大图');

  // 解锁所有渲染大图
  let unlockedCount = 0;
  this.renderLargeImages.forEach(img => {
    if (img.locked) {
      img.locked = false;
      unlockedCount++;
    }
  });

  console.log(`✅ 已解锁${unlockedCount}张渲染大图`);

  // 通知客服可以发送大图
  alert(`✅ 尾款已到账,${unlockedCount}张渲染大图已解锁!\n\n客服可一键发送给客户。`);

  // 触发界面更新
  this.cdr.detectChanges();
}

2.4 支付凭证识别

2.4.1 凭证上传

// 上传支付凭证
uploadPaymentVoucher(event: Event): void {
  const input = event.target as HTMLInputElement;
  if (!input.files || input.files.length === 0) return;

  const file = input.files[0];

  // 验证文件类型
  if (!file.type.startsWith('image/')) {
    alert('请上传图片格式的支付凭证');
    return;
  }

  this.isUploadingVoucher = true;

  // 上传文件到服务器
  this.uploadFile(file).then(url => {
    // 触发智能识别
    this.recognizePaymentVoucher(url);
  }).catch(error => {
    console.error('支付凭证上传失败:', error);
    alert('上传失败,请重试');
    this.isUploadingVoucher = false;
  });
}

2.4.2 智能识别

// 调用支付凭证识别服务
private recognizePaymentVoucher(imageUrl: string): void {
  this.paymentVoucherService.recognize(imageUrl).subscribe({
    next: (result) => {
      console.log('识别结果:', result);

      // 显示识别结果
      this.voucherRecognitionResult = {
        amount: result.amount,
        paymentMethod: result.method,
        transactionId: result.transactionId,
        transactionTime: result.time,
        confidence: result.confidence
      };

      // 如果识别置信度高,自动填充
      if (result.confidence > 0.8) {
        this.autoFillPaymentInfo(result);
      }

      this.isUploadingVoucher = false;
    },
    error: (error) => {
      console.error('支付凭证识别失败:', error);
      alert('识别失败,请手动填写支付信息');
      this.isUploadingVoucher = false;
    }
  });
}

识别结果结构

interface VoucherRecognitionResult {
  amount: number;                           // 支付金额
  paymentMethod: 'wechat' | 'alipay';      // 支付方式
  transactionId: string;                    // 交易单号
  transactionTime: Date;                    // 交易时间
  confidence: number;                       // 识别置信度 0-1
  merchantName?: string;                    // 商户名称
  remarks?: string;                         // 备注信息
}

2.5 一键发图功能

// 客服一键发送渲染大图
sendImagesToCustomer(): void {
  const unlockedImages = this.renderLargeImages.filter(img => !img.locked);

  if (unlockedImages.length === 0) {
    alert('没有可发送的图片(渲染大图未解锁)');
    return;
  }

  // 生成图片下载链接
  const imageLinks = unlockedImages.map(img => ({
    name: img.name,
    url: img.url,
    size: img.size
  }));

  // 调用发送服务
  this.projectService.sendImagesToCustomer(
    this.projectId,
    imageLinks
  ).subscribe({
    next: (result) => {
      if (result.success) {
        alert(`✅ 已成功发送${unlockedImages.length}张图片给客户!`);

        // 标记为已发送
        unlockedImages.forEach(img => {
          img.synced = true;
        });
      }
    },
    error: (error) => {
      console.error('发送图片失败:', error);
      alert('发送失败,请重试');
    }
  });
}

3. 全景图合成模块

3.1 功能特点

  • KR Panel集成
  • 智能空间标注
  • 自动生成分享链接
  • 漫游式预览体验

3.2 全景图合成流程

3.2.1 启动合成

// 开始全景图合成
startPanoramicSynthesis(): void {
  console.log('🖼️ 启动全景图合成');

  // 打开文件选择对话框
  const input = document.createElement('input');
  input.type = 'file';
  input.multiple = true;
  input.accept = 'image/*';

  input.onchange = (event: any) => {
    const files = Array.from(event.target.files) as File[];
    if (files.length === 0) return;

    this.uploadAndSynthesizePanoramic(files);
  };

  input.click();
}

3.2.2 文件上传与合成

// project-detail.ts lines 4217-4288
private uploadAndSynthesizePanoramic(files: File[]): void {
  console.log(`📤 开始上传${files.length}个文件...`);

  this.isUploadingPanoramicFiles = true;
  this.panoramicUploadProgress = 0;

  // 模拟文件上传进度
  const uploadInterval = setInterval(() => {
    this.panoramicUploadProgress += 10;
    if (this.panoramicUploadProgress >= 100) {
      this.panoramicUploadProgress = 100;
      clearInterval(uploadInterval);

      // 上传完成,开始合成
      this.synthesizePanoramicView(files);
    }
  }, 300);
}

private synthesizePanoramicView(files: File[]): void {
  console.log('🔧 开始合成全景图...');

  this.isUploadingPanoramicFiles = false;
  this.isSynthesizingPanoramic = true;
  this.panoramicSynthesisProgress = 0;

  // 模拟合成进度
  const synthesisInterval = setInterval(() => {
    this.panoramicSynthesisProgress += 5;
    if (this.panoramicSynthesisProgress >= 100) {
      this.panoramicSynthesisProgress = 100;
      clearInterval(synthesisInterval);

      // 合成完成
      this.completePanoramicSynthesis(files);
    }
  }, 500);
}

KR Panel集成

  • 支持多角度图片合成
  • 自动识别空间名称
  • 生成3D漫游场景
  • 支持VR模式预览

3.2.3 完成合成

// project-detail.ts lines 4290-4328
private completePanoramicSynthesis(files: File[]): void {
  this.isSynthesizingPanoramic = false;

  // 创建全景图合成记录
  const synthesis: PanoramicSynthesis = {
    id: `panoramic-${Date.now()}`,
    name: `全景图_${new Date().toLocaleDateString()}`,
    createdAt: new Date(),
    spaces: files.map((file, index) => ({
      id: `space-${index}`,
      name: this.extractSpaceName(file.name),
      imageUrl: URL.createObjectURL(file),
      angle: index * 60 // 假设每60度一个角度
    })),
    previewUrl: 'https://example.com/panoramic/preview',
    downloadUrl: 'https://example.com/panoramic/download',
    shareLink: '',
    fileSize: files.reduce((sum, f) => sum + f.size, 0),
    status: 'completed'
  };

  // 添加到历史记录
  this.panoramicSynthesisHistory.push(synthesis);

  // 生成分享链接
  this.generatePanoramicShareLink(synthesis);

  console.log('✅ 全景图合成完成:', synthesis);

  alert(`✅ 全景图合成完成!\n\n已生成${synthesis.spaces.length}个空间的全景图\n文件大小: ${this.formatFileSize(synthesis.fileSize)}`);
}

全景图数据结构

interface PanoramicSynthesis {
  id: string;
  name: string;
  createdAt: Date;
  spaces: Array<{
    id: string;
    name: string;              // 空间名称:客厅-角度1
    imageUrl: string;
    angle: number;             // 拍摄角度
  }>;
  previewUrl: string;          // 预览链接
  downloadUrl: string;         // 下载链接
  shareLink: string;           // 分享链接
  fileSize: number;
  status: 'processing' | 'completed' | 'failed';
}

3.3 自动生成分享链接

// project-detail.ts lines 4330-4360
private generatePanoramicShareLink(synthesis: PanoramicSynthesis): void {
  // 生成唯一分享链接
  const linkId = btoa(`panoramic-${synthesis.id}-${Date.now()}`);
  const shareLink = `https://vr.example.com/view/${linkId}`;

  synthesis.shareLink = shareLink;

  console.log('🔗 已生成分享链接:', shareLink);

  // 自动复制到剪贴板
  this.copyToClipboard(shareLink);

  // 通知客服发送给客户
  this.notifyCustomerServiceForPanoramicShare(synthesis);

  alert(`✅ 分享链接已生成并复制到剪贴板!\n\n${shareLink}\n\n客服已收到通知,可发送给客户。`);
}

private notifyCustomerServiceForPanoramicShare(synthesis: PanoramicSynthesis): void {
  const notification = {
    type: 'panoramic-ready',
    projectId: this.projectId,
    projectName: this.project?.name || '未命名项目',
    shareLink: synthesis.shareLink,
    spaceCount: synthesis.spaces.length,
    message: `项目"${this.project?.name}"的全景图已合成完成,请发送给客户查看`
  };

  console.log('📧 已通知客服发送全景图:', notification);
}

分享链接特点

  • 唯一性标识
  • 有效期控制(可选)
  • 访问统计
  • VR模式支持

4. 客户评价模块

4.1 功能特点

  • 多维度评分系统
  • 评价链接自动生成
  • 30天有效期
  • 数据统计分析

4.2 评价链接生成

4.2.1 生成评价令牌

// 生成客户评价链接
generateReviewLink(): void {
  console.log('📋 生成客户评价链接');

  // 生成唯一评价令牌
  const token = this.generateUniqueToken();

  // 创建评价链接记录
  const reviewLink: CustomerReviewLink = {
    id: `review-link-${Date.now()}`,
    projectId: this.projectId,
    token: token,
    link: `https://review.example.com/${token}`,
    createdAt: new Date(),
    expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30天后过期
    status: 'active',
    submittedAt: undefined
  };

  this.customerReviewLink = reviewLink;

  // 复制到剪贴板
  this.copyToClipboard(reviewLink.link);

  // 通知客服
  this.notifyCustomerServiceForReview(reviewLink);

  console.log('✅ 评价链接已生成:', reviewLink);

  alert(`✅ 评价链接已生成并复制到剪贴板!\n\n${reviewLink.link}\n\n有效期: 30天\n客服已收到通知,可发送给客户。`);
}

评价链接结构

interface CustomerReviewLink {
  id: string;
  projectId: string;
  token: string;                // 唯一令牌
  link: string;                 // 完整链接
  createdAt: Date;
  expiresAt: Date;              // 过期时间
  status: 'active' | 'submitted' | 'expired';
  submittedAt?: Date;
}

4.2.2 生成唯一令牌

private generateUniqueToken(): string {
  const timestamp = Date.now().toString(36);
  const randomStr = Math.random().toString(36).substring(2, 15);
  const projectIdHash = btoa(this.projectId).substring(0, 8);

  return `${timestamp}-${randomStr}-${projectIdHash}`;
}

4.3 评价数据结构

interface CustomerReview {
  id: string;
  projectId: string;
  submittedAt: Date;

  // 多维度评分 (1-5星)
  ratings: {
    overall: number;            // 整体满意度
    timeliness: number;         // 及时性
    quality: number;            // 设计质量
    communication: number;      // 沟通效率
    professionalism: number;    // 专业程度
  };

  // 文字评价
  comments: {
    strengths: string;          // 优点
    improvements: string;       // 改进建议
    additional: string;         // 其他意见
  };

  // 推荐意愿
  wouldRecommend: boolean;

  // 附加信息
  contact?: string;
  permitPublish: boolean;       // 是否允许公开
}

4.4 评价提交处理

// 处理客户提交的评价
onReviewSubmitted(reviewData: CustomerReview): void {
  console.log('📝 收到客户评价:', reviewData);

  // 保存评价数据
  this.customerReviews.push(reviewData);

  // 更新评价链接状态
  if (this.customerReviewLink) {
    this.customerReviewLink.status = 'submitted';
    this.customerReviewLink.submittedAt = new Date();
  }

  // 计算平均分
  this.calculateAverageRatings();

  // 通知相关人员
  this.notifyReviewReceived(reviewData);

  console.log('✅ 客户评价已保存');

  alert('✅ 感谢客户的宝贵评价!\n\n评价数据已保存并通知相关人员。');
}

4.5 评价数据分析

// 计算平均评分
private calculateAverageRatings(): void {
  if (this.customerReviews.length === 0) {
    this.averageRatings = {
      overall: 0,
      timeliness: 0,
      quality: 0,
      communication: 0,
      professionalism: 0
    };
    return;
  }

  const sum = this.customerReviews.reduce((acc, review) => ({
    overall: acc.overall + review.ratings.overall,
    timeliness: acc.timeliness + review.ratings.timeliness,
    quality: acc.quality + review.ratings.quality,
    communication: acc.communication + review.ratings.communication,
    professionalism: acc.professionalism + review.ratings.professionalism
  }), {
    overall: 0,
    timeliness: 0,
    quality: 0,
    communication: 0,
    professionalism: 0
  });

  const count = this.customerReviews.length;

  this.averageRatings = {
    overall: Math.round((sum.overall / count) * 10) / 10,
    timeliness: Math.round((sum.timeliness / count) * 10) / 10,
    quality: Math.round((sum.quality / count) * 10) / 10,
    communication: Math.round((sum.communication / count) * 10) / 10,
    professionalism: Math.round((sum.professionalism / count) * 10) / 10
  };

  console.log('📊 平均评分已更新:', this.averageRatings);
}

5. 投诉处理模块

5.1 功能特点

  • 人工创建投诉
  • 关键词自动抓取
  • 智能标注问题类型
  • 处理进度跟踪

5.2 人工创建投诉

5.2.1 创建投诉记录

// 人工创建投诉记录
createComplaintManually(): void {
  console.log('📝 人工创建投诉记录');

  // 验证权限
  if (!this.isTeamLeaderView() && !this.isCustomerServiceView()) {
    alert('⚠️ 仅组长和客服可以创建投诉记录');
    return;
  }

  // 打开投诉创建表单
  this.showComplaintForm = true;
  this.complaintFormData = {
    source: 'manual',
    stage: '',
    reason: '',
    description: '',
    severity: 'medium',
    tags: []
  };
}

5.2.2 提交投诉

// 提交投诉记录
submitComplaint(): void {
  if (!this.complaintFormData.reason || !this.complaintFormData.description) {
    alert('请填写投诉原因和详细描述');
    return;
  }

  const complaint: ComplaintRecord = {
    id: `complaint-${Date.now()}`,
    projectId: this.projectId,
    source: this.complaintFormData.source,
    stage: this.complaintFormData.stage || '未指定',
    reason: this.complaintFormData.reason,
    description: this.complaintFormData.description,
    severity: this.complaintFormData.severity,
    tags: this.complaintFormData.tags,
    status: '待处理',
    createdAt: new Date(),
    createdBy: this.getCurrentUserName(),
    assignedTo: undefined,
    resolvedAt: undefined,
    resolution: undefined
  };

  // 添加到投诉列表
  this.complaints.push(complaint);

  // 通知相关处理人员
  this.notifyComplaintHandler(complaint);

  // 关闭表单
  this.showComplaintForm = false;

  console.log('✅ 投诉记录已创建:', complaint);

  alert('✅ 投诉记录已创建!\n\n相关人员已收到通知。');
}

投诉数据结构

interface ComplaintRecord {
  id: string;
  projectId: string;
  source: 'manual' | 'keyword-detection';    // 来源
  stage: string;                              // 投诉环节
  reason: string;                             // 投诉原因
  description: string;                        // 详细描述
  severity: 'low' | 'medium' | 'high';       // 严重程度
  tags: string[];                             // 问题标签
  status: '待处理' | '处理中' | '已解决' | '已关闭';
  createdAt: Date;
  createdBy: string;
  assignedTo?: string;                        // 分配给
  resolvedAt?: Date;
  resolution?: string;                        // 解决方案
  attachments?: Array<{
    id: string;
    name: string;
    url: string;
  }>;
}

5.3 关键词自动监控

5.3.1 设置关键词监测

// 启动关键词监测
setupKeywordMonitoring(): void {
  console.log('🔍 设置关键词监测');

  // 打开监控设置面板
  this.showKeywordMonitoringSettings = true;

  // 初始化默认关键词
  if (this.monitoringKeywords.length === 0) {
    this.monitoringKeywords = [
      '不满意',
      '投诉',
      '退款',
      '差评',
      '质量问题',
      '延期',
      '态度差'
    ];
  }

  console.log('📋 当前监控关键词:', this.monitoringKeywords);
}

5.3.2 关键词检测

// 检测消息中的关键词
private detectKeywords(message: string): string[] {
  const detectedKeywords: string[] = [];

  this.monitoringKeywords.forEach(keyword => {
    if (message.includes(keyword)) {
      detectedKeywords.push(keyword);
    }
  });

  return detectedKeywords;
}

5.3.3 自动创建投诉

// 检测到关键词后自动创建投诉
onKeywordDetected(message: string, keyword: string): void {
  console.log(`🚨 检测到关键词: ${keyword}`);

  // 智能分析投诉严重程度
  const severity = this.assessComplaintSeverity(keyword);

  // 智能识别投诉环节
  const stage = this.identifyComplaintStage(message);

  // 智能标注问题类型
  const tags = this.generateComplaintTags(message, keyword);

  // 自动创建投诉记录
  const complaint: ComplaintRecord = {
    id: `complaint-auto-${Date.now()}`,
    projectId: this.projectId,
    source: 'keyword-detection',
    stage: stage,
    reason: `检测到关键词: ${keyword}`,
    description: message,
    severity: severity,
    tags: tags,
    status: '待处理',
    createdAt: new Date(),
    createdBy: '系统自动',
    assignedTo: undefined,
    resolvedAt: undefined,
    resolution: undefined
  };

  this.complaints.push(complaint);

  // 立即通知处理人员
  this.notifyUrgentComplaint(complaint);

  console.log('✅ 已自动创建投诉记录:', complaint);

  alert(`🚨 检测到客户投诉关键词: ${keyword}\n\n已自动创建投诉记录并通知相关人员。`);
}

智能分析方法

// 评估投诉严重程度
private assessComplaintSeverity(keyword: string): 'low' | 'medium' | 'high' {
  const highSeverityKeywords = ['退款', '投诉', '差评'];
  const mediumSeverityKeywords = ['不满意', '质量问题', '延期'];

  if (highSeverityKeywords.includes(keyword)) return 'high';
  if (mediumSeverityKeywords.includes(keyword)) return 'medium';
  return 'low';
}

// 识别投诉环节
private identifyComplaintStage(message: string): string {
  if (message.includes('需求') || message.includes('沟通')) return '需求沟通';
  if (message.includes('方案') || message.includes('设计')) return '方案确认';
  if (message.includes('建模') || message.includes('模型')) return '建模';
  if (message.includes('软装') || message.includes('家具')) return '软装';
  if (message.includes('渲染') || message.includes('效果图')) return '渲染';
  if (message.includes('交付') || message.includes('延期')) return '交付';
  return '未识别';
}

// 生成问题标签
private generateComplaintTags(message: string, keyword: string): string[] {
  const tags: string[] = [];

  // 根据消息内容添加标签
  if (message.includes('需求') || message.includes('理解')) tags.push('需求理解');
  if (message.includes('质量') || message.includes('效果')) tags.push('设计质量');
  if (message.includes('延期') || message.includes('时间')) tags.push('交付延期');
  if (message.includes('态度') || message.includes('服务')) tags.push('服务态度');
  if (message.includes('价格') || message.includes('费用')) tags.push('价格问题');

  // 添加关键词作为标签
  tags.push(keyword);

  return [...new Set(tags)]; // 去重
}

5.4 投诉处理流程

// 处理投诉
handleComplaint(complaintId: string, resolution: string): void {
  const complaint = this.complaints.find(c => c.id === complaintId);
  if (!complaint) return;

  complaint.status = '已解决';
  complaint.resolvedAt = new Date();
  complaint.resolution = resolution;

  // 通知客户和相关人员
  this.notifyComplaintResolved(complaint);

  console.log('✅ 投诉已处理:', complaint);

  alert('✅ 投诉处理完成!\n\n已通知客户和相关人员。');
}

6. 项目复盘模块

6.1 功能特点

  • 三大核心板块(SOP执行数据、经验复盘、优化建议)
  • 数据可视化展示
  • 自动生成复盘报告
  • 导出为PDF/Excel

6.2 SOP执行数据

6.2.1 数据收集

// 收集SOP执行数据
collectSOPExecutionData(): any {
  return {
    requirementCommunications: this.countRequirementCommunications(),
    revisionCount: this.countRevisions(),
    deliveryCycleCompliance: this.checkDeliveryCycleCompliance(),
    customerSatisfaction: this.getCustomerSatisfactionScore(),
    stageDetails: this.getStageExecutionDetails()
  };
}

6.2.2 阶段执行详情

// 获取各阶段执行详情
private getStageExecutionDetails(): Array<{
  stage: string;
  plannedDuration: number;
  actualDuration: number;
  status: 'on-time' | 'delayed' | 'ahead';
  score: number;
}> {
  return [
    {
      stage: '需求沟通',
      plannedDuration: 2,
      actualDuration: 2,
      status: 'on-time',
      score: 95
    },
    {
      stage: '方案确认',
      plannedDuration: 3,
      actualDuration: 4,
      status: 'delayed',
      score: 85
    },
    {
      stage: '建模',
      plannedDuration: 5,
      actualDuration: 4,
      status: 'ahead',
      score: 92
    },
    {
      stage: '软装',
      plannedDuration: 3,
      actualDuration: 3,
      status: 'on-time',
      score: 90
    },
    {
      stage: '渲染',
      plannedDuration: 4,
      actualDuration: 5,
      status: 'delayed',
      score: 88
    }
  ];
}

6.3 经验复盘

6.3.1 自动提取信息

// 提取经验复盘数据
extractExperienceSummary(): any {
  return {
    customerNeeds: this.extractCustomerNeeds(),
    customerConcerns: this.extractCustomerConcerns(),
    complaintPoints: this.extractComplaintPoints(),
    projectHighlights: this.extractProjectHighlights(),
    keyConversations: this.extractKeyConversations()
  };
}

6.3.2 提取客户需求

private extractCustomerNeeds(): string[] {
  // 从需求沟通记录中提取
  return [
    '客户希望整体风格偏现代简约',
    '客户重视收纳空间的设计',
    '客户要求使用环保材料',
    '客户希望采光效果良好'
  ];
}

6.4 优化建议

6.4.1 生成优化建议

// 生成优化建议
generateOptimizationSuggestions(): any[] {
  const suggestions = [];

  // 基于数据分析生成建议
  const sopData = this.collectSOPExecutionData();

  // 建议1:需求沟通优化
  if (sopData.requirementCommunications > 5) {
    suggestions.push({
      priority: 'high',
      priorityText: '高',
      category: '需求沟通',
      problem: '需求沟通次数过多(6次),影响项目效率',
      dataSupport: `需求沟通次数: ${sopData.requirementCommunications}次,标准为3-4次`,
      solution: '建议在首次沟通时使用标准化需求采集表,确保需求收集的完整性',
      actionPlan: [
        '制定标准需求采集表模板',
        '培训设计师使用标准表单',
        '要求首次沟通必须完成80%需求确认'
      ],
      expectedImprovement: '减少30%的需求沟通次数',
      referenceCase: '参考项目#1234在使用标准表后沟通次数从6次降至3次',
      accepted: false
    });
  }

  // 建议2:渲染阶段优化
  const renderingStage = sopData.stageDetails.find((s: any) => s.stage === '渲染');
  if (renderingStage && renderingStage.status === 'delayed') {
    suggestions.push({
      priority: 'medium',
      priorityText: '中',
      category: '渲染效率',
      problem: '渲染阶段超期1天,影响整体交付时间',
      dataSupport: `计划4天,实际5天,超期率25%`,
      solution: '建议提前进行渲染设备性能检查,并预留缓冲时间',
      actionPlan: [
        '每月检查渲染设备性能',
        '建模完成后立即启动预渲染',
        '渲染阶段预留20%缓冲时间'
      ],
      expectedImprovement: '降低渲染超期率至10%以下',
      referenceCase: '团队B采用预渲染机制后超期率从30%降至8%',
      accepted: false
    });
  }

  return suggestions;
}

优化建议结构

interface OptimizationSuggestion {
  priority: 'high' | 'medium' | 'low';
  priorityText: string;
  category: string;                     // 类别
  problem: string;                      // 问题描述
  dataSupport: string;                  // 数据支撑
  solution: string;                     // 解决方案
  actionPlan: string[];                 // 行动计划
  expectedImprovement: string;          // 预期提升
  referenceCase?: string;               // 参考案例
  accepted: boolean;                    // 是否已采纳
  acceptedAt?: Date;
}

6.5 复盘报告生成

6.5.1 生成报告

// 生成完整复盘报告
generateReviewReport(): void {
  console.log('📊 生成项目复盘报告');

  this.isGeneratingReview = true;

  // 模拟生成进度
  let progress = 0;
  const interval = setInterval(() => {
    progress += 20;
    if (progress >= 100) {
      clearInterval(interval);
      this.completeReviewReportGeneration();
    }
  }, 500);
}

private completeReviewReportGeneration(): void {
  // 收集所有数据
  const reportData = {
    projectInfo: {
      name: this.project?.name || '未命名项目',
      id: this.projectId,
      startDate: this.project?.createdAt,
      endDate: new Date()
    },
    sopData: this.collectSOPExecutionData(),
    experience: this.extractExperienceSummary(),
    suggestions: this.generateOptimizationSuggestions(),
    statistics: {
      overallScore: this.calculateOverallScore(),
      strengths: this.getProjectStrengths(),
      weaknesses: this.getProjectWeaknesses()
    }
  };

  // 保存报告
  this.reviewReport = reportData;
  this.isGeneratingReview = false;

  console.log('✅ 复盘报告生成完成:', reportData);

  alert('✅ 项目复盘报告已生成!\n\n您可以查看详情或导出报告。');
}

6.5.2 导出报告

// 导出复盘报告
exportReviewReport(format: 'pdf' | 'excel'): void {
  if (!this.reviewReport) {
    alert('请先生成复盘报告');
    return;
  }

  console.log(`📤 导出复盘报告 (${format})`);

  if (format === 'excel') {
    this.exportAsExcel(this.reviewReport);
  } else {
    this.exportAsPDF(this.reviewReport);
  }
}

private exportAsExcel(data: any): void {
  // 转换为CSV格式
  let csvContent = '\uFEFF'; // UTF-8 BOM

  // 项目概况
  csvContent += '=== 项目概况 ===\n';
  csvContent += `项目名称,${data.projectInfo.name}\n`;
  csvContent += `项目ID,${data.projectInfo.id}\n`;
  csvContent += `总耗时,${this.calculateProjectDuration()}天\n\n`;

  // SOP执行数据
  csvContent += '=== SOP执行数据 ===\n';
  csvContent += '阶段,计划时长,实际时长,状态,评分\n';
  data.sopData.stageDetails.forEach((stage: any) => {
    csvContent += `${stage.stage},${stage.plannedDuration},${stage.actualDuration},${stage.status},${stage.score}\n`;
  });

  // 优化建议
  csvContent += '\n=== 优化建议 ===\n';
  csvContent += '优先级,类别,问题,建议,预期提升\n';
  data.suggestions.forEach((s: any) => {
    csvContent += `${s.priorityText},${s.category},"${s.problem}","${s.solution}",${s.expectedImprovement}\n`;
  });

  // 创建下载
  const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  const link = document.createElement('a');
  const url = URL.createObjectURL(blob);

  link.href = url;
  link.download = `项目复盘报告_${data.projectInfo.name}_${this.formatDate(new Date())}.csv`;
  link.click();

  URL.revokeObjectURL(url);

  console.log('✅ 报告已导出为Excel');
  alert('✅ 报告已导出!\n\n文件已下载到您的下载文件夹。');
}

7. 权限控制

7.1 角色权限矩阵

操作 客服 设计师 组长 技术 财务
查看售后板块
启动自动结算
上传支付凭证
发送图片给客户
合成全景图
生成评价链接
创建投诉记录
处理投诉
生成复盘报告
导出复盘报告

7.2 权限检查实现

// 检查尾款结算权限
canInitiateSettlement(): boolean {
  return this.isTechnicalView();
}

// 检查全景图合成权限
canSynthesizePanoramic(): boolean {
  return this.isTechnicalView();
}

// 检查投诉处理权限
canHandleComplaints(): boolean {
  return this.isTeamLeaderView() || this.isCustomerServiceView();
}

// 检查复盘报告权限
canGenerateReviewReport(): boolean {
  return this.isTeamLeaderView() || this.isTechnicalView();
}

8. 数据流转

8.1 售后流程总览

sequenceDiagram
    participant Tech as 技术
    participant System as 系统
    participant Payment as 支付网关
    participant CS as 客服
    participant Customer as 客户

    Tech->>System: 启动自动结算
    System->>Payment: 激活支付监听
    System->>CS: 通知跟进尾款
    CS->>Customer: 发送支付请求
    Customer->>Payment: 完成支付
    Payment->>System: 支付通知
    System->>System: 解锁渲染大图
    System->>CS: 通知发送大图
    CS->>Customer: 发送渲染大图
    System->>CS: 生成评价链接
    CS->>Customer: 发送评价链接
    Customer->>System: 提交评价
    System->>Tech: 生成复盘报告

8.2 数据同步机制

// 售后数据同步到项目
private syncAfterCareDataToProject(): void {
  if (!this.project) return;

  this.project.afterCare = {
    settlement: this.settlementRecord,
    panoramic: this.panoramicSynthesisHistory,
    reviews: this.customerReviews,
    complaints: this.complaints,
    reviewReport: this.reviewReport
  };

  // 同步到服务器
  this.projectService.updateProject(this.project).subscribe({
    next: (result) => {
      console.log('✅ 售后数据已同步到项目');
    },
    error: (error) => {
      console.error('❌ 售后数据同步失败:', error);
    }
  });
}

9. 异常处理

9.1 支付监听失败

// 支付监听连接失败处理
private handlePaymentMonitoringError(error: any): void {
  console.error('支付监听连接失败:', error);

  // 降级为手动模式
  this.miniprogramPaymentStatus = 'error';

  alert(`⚠️ 支付自动监听失败\n\n请使用"上传支付凭证"功能手动确认支付。`);

  // 显示手动上传入口
  this.showManualPaymentVoucherUpload = true;
}

9.2 全景图合成失败

// 全景图合成失败处理
private handlePanoramicSynthesisError(error: any): void {
  console.error('全景图合成失败:', error);

  this.isSynthesizingPanoramic = false;

  let errorMessage = '全景图合成失败';

  if (error.code === 'INSUFFICIENT_IMAGES') {
    errorMessage = '图片数量不足,至少需要6张图片';
  } else if (error.code === 'INVALID_FORMAT') {
    errorMessage = '图片格式不支持,请使用JPG或PNG格式';
  }

  alert(`❌ ${errorMessage}\n\n请检查后重试。`);
}

9.3 评价链接过期

// 检查评价链接是否过期
checkReviewLinkExpiry(linkId: string): boolean {
  const link = this.customerReviewLinks.find(l => l.id === linkId);

  if (!link) return true;

  if (link.status === 'expired') return true;

  // 检查是否超过有效期
  if (new Date() > link.expiresAt) {
    link.status = 'expired';
    return true;
  }

  return false;
}

// 重新生成过期的评价链接
regenerateReviewLink(oldLinkId: string): void {
  const oldLink = this.customerReviewLinks.find(l => l.id === oldLinkId);

  if (!oldLink) return;

  // 将旧链接标记为过期
  oldLink.status = 'expired';

  // 生成新链接
  this.generateReviewLink();

  alert('✅ 已重新生成评价链接!\n\n旧链接已失效,请使用新链接。');
}

10. 性能优化

10.1 报告生成优化

// 使用Worker生成大型报告
private generateReportWithWorker(data: any): void {
  if (typeof Worker !== 'undefined') {
    const worker = new Worker(new URL('./report-generator.worker', import.meta.url));

    worker.onmessage = ({ data }) => {
      console.log('报告生成完成:', data);
      this.reviewReport = data.report;
      this.isGeneratingReview = false;
    };

    worker.postMessage({ type: 'generate', data });
  } else {
    // 降级为同步生成
    this.generateReportSync(data);
  }
}

10.2 图片压缩

// 压缩全景图用于预览
private compressImageForPreview(file: File): Promise<Blob> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = (e) => {
      const img = new Image();
      img.onload = () => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        // 压缩到最大1920px
        const maxDimension = 1920;
        const scale = Math.min(maxDimension / img.width, maxDimension / img.height, 1);

        canvas.width = img.width * scale;
        canvas.height = img.height * scale;

        ctx?.drawImage(img, 0, 0, canvas.width, canvas.height);

        canvas.toBlob((blob) => {
          if (blob) {
            resolve(blob);
          } else {
            reject(new Error('压缩失败'));
          }
        }, 'image/jpeg', 0.8);
      };

      img.src = e.target?.result as string;
    };

    reader.readAsDataURL(file);
  });
}

10.3 数据缓存

// 缓存复盘报告数据
private cacheReviewReport(report: any): void {
  try {
    localStorage.setItem(
      `review-report-${this.projectId}`,
      JSON.stringify(report)
    );
    console.log('✅ 复盘报告已缓存');
  } catch (error) {
    console.warn('缓存失败:', error);
  }
}

// 加载缓存的报告
private loadCachedReviewReport(): any | null {
  try {
    const cached = localStorage.getItem(`review-report-${this.projectId}`);
    if (cached) {
      return JSON.parse(cached);
    }
  } catch (error) {
    console.warn('加载缓存失败:', error);
  }
  return null;
}

11. 测试用例

11.1 尾款结算测试

describe('Final Payment Settlement', () => {
  it('should initiate auto settlement by technical user', () => {
    component.roleContext = 'technical';
    spyOn(component, 'isAllDeliveryCompleted').and.returnValue(true);

    component.initiateAutoSettlement();

    expect(component.miniprogramPaymentStatus).toBe('active');
    expect(component.settlementRecord).toBeDefined();
  });

  it('should reject non-technical users', () => {
    component.roleContext = 'designer';
    spyOn(window, 'alert');

    component.initiateAutoSettlement();

    expect(window.alert).toHaveBeenCalledWith(jasmine.stringContaining('仅技术人员'));
  });

  it('should unlock images after payment received', () => {
    component.renderLargeImages = [
      { id: '1', name: 'img1.jpg', url: 'blob:1', locked: true },
      { id: '2', name: 'img2.jpg', url: 'blob:2', locked: true }
    ];

    component.onPaymentReceived({ amount: 75000, method: 'wechat' });

    expect(component.renderLargeImages.every(img => !img.locked)).toBe(true);
  });
});

11.2 投诉处理测试

describe('Complaint Handling', () => {
  it('should create complaint manually', () => {
    component.roleContext = 'team-leader';
    component.complaintFormData = {
      source: 'manual',
      stage: '渲染',
      reason: '质量问题',
      description: '渲染效果不符合预期',
      severity: 'medium',
      tags: ['设计质量']
    };

    component.submitComplaint();

    expect(component.complaints.length).toBeGreaterThan(0);
  });

  it('should detect keywords and create complaint', () => {
    const message = '我对渲染效果很不满意,要求退款';

    component.monitoringKeywords = ['不满意', '退款'];

    component.onKeywordDetected(message, '不满意');

    expect(component.complaints.length).toBeGreaterThan(0);
    expect(component.complaints[0].severity).toBe('high');
  });
});

11.3 复盘报告测试

describe('Review Report Generation', () => {
  it('should generate complete review report', () => {
    component.generateReviewReport();

    // Wait for generation
    setTimeout(() => {
      expect(component.reviewReport).toBeDefined();
      expect(component.reviewReport.sopData).toBeDefined();
      expect(component.reviewReport.experience).toBeDefined();
      expect(component.reviewReport.suggestions.length).toBeGreaterThan(0);
    }, 3000);
  });

  it('should export report as Excel', () => {
    component.reviewReport = mockReviewReport;

    spyOn(document, 'createElement').and.callThrough();

    component.exportReviewReport('excel');

    expect(document.createElement).toHaveBeenCalledWith('a');
  });
});

文档版本:v1.0.0 创建日期:2025-10-16 最后更新:2025-10-16 维护人:产品团队

相关文档