售后归档阶段是项目管理流程的收尾环节,包含尾款结算、全景图合成、客户评价、投诉处理、项目复盘五大核心模块。该阶段负责完成项目交付、收集反馈、总结经验,为后续项目优化提供数据支撑。
graph TD
    A[后期完成] --> B[尾款结算]
    B --> C[全景图合成]
    C --> D[客户评价]
    D --> E[投诉处理]
    E --> F[项目复盘]
    style B fill:#e8f5e9
    style C fill:#fff3e0
    style D fill:#e3f2fd
    style E fill:#fce4ec
    style F fill:#f3e5f5
// 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- 支付到账后将自动解锁大图');
}
权限验证:
// 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;
}
// 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()}`);
}
// 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: 通知客服发图
// 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('🎉 尾款结算流程完成');
}
// 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();
}
// 上传支付凭证
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;
  });
}
// 调用支付凭证识别服务
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;                         // 备注信息
}
// 客服一键发送渲染大图
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('发送失败,请重试');
    }
  });
}
// 开始全景图合成
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();
}
// 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集成:
// 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';
}
// 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);
}
分享链接特点:
// 生成客户评价链接
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;
}
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}`;
}
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;       // 是否允许公开
}
// 处理客户提交的评价
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评价数据已保存并通知相关人员。');
}
// 计算平均评分
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);
}
// 人工创建投诉记录
createComplaintManually(): void {
  console.log('📝 人工创建投诉记录');
  // 验证权限
  if (!this.isTeamLeaderView() && !this.isCustomerServiceView()) {
    alert('⚠️ 仅组长和客服可以创建投诉记录');
    return;
  }
  // 打开投诉创建表单
  this.showComplaintForm = true;
  this.complaintFormData = {
    source: 'manual',
    stage: '',
    reason: '',
    description: '',
    severity: 'medium',
    tags: []
  };
}
// 提交投诉记录
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;
  }>;
}
// 启动关键词监测
setupKeywordMonitoring(): void {
  console.log('🔍 设置关键词监测');
  // 打开监控设置面板
  this.showKeywordMonitoringSettings = true;
  // 初始化默认关键词
  if (this.monitoringKeywords.length === 0) {
    this.monitoringKeywords = [
      '不满意',
      '投诉',
      '退款',
      '差评',
      '质量问题',
      '延期',
      '态度差'
    ];
  }
  console.log('📋 当前监控关键词:', this.monitoringKeywords);
}
// 检测消息中的关键词
private detectKeywords(message: string): string[] {
  const detectedKeywords: string[] = [];
  this.monitoringKeywords.forEach(keyword => {
    if (message.includes(keyword)) {
      detectedKeywords.push(keyword);
    }
  });
  return detectedKeywords;
}
// 检测到关键词后自动创建投诉
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)]; // 去重
}
// 处理投诉
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已通知客户和相关人员。');
}
// 收集SOP执行数据
collectSOPExecutionData(): any {
  return {
    requirementCommunications: this.countRequirementCommunications(),
    revisionCount: this.countRevisions(),
    deliveryCycleCompliance: this.checkDeliveryCycleCompliance(),
    customerSatisfaction: this.getCustomerSatisfactionScore(),
    stageDetails: this.getStageExecutionDetails()
  };
}
// 获取各阶段执行详情
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
    }
  ];
}
// 提取经验复盘数据
extractExperienceSummary(): any {
  return {
    customerNeeds: this.extractCustomerNeeds(),
    customerConcerns: this.extractCustomerConcerns(),
    complaintPoints: this.extractComplaintPoints(),
    projectHighlights: this.extractProjectHighlights(),
    keyConversations: this.extractKeyConversations()
  };
}
private extractCustomerNeeds(): string[] {
  // 从需求沟通记录中提取
  return [
    '客户希望整体风格偏现代简约',
    '客户重视收纳空间的设计',
    '客户要求使用环保材料',
    '客户希望采光效果良好'
  ];
}
// 生成优化建议
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;
}
// 生成完整复盘报告
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您可以查看详情或导出报告。');
}
// 导出复盘报告
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文件已下载到您的下载文件夹。');
}
| 操作 | 客服 | 设计师 | 组长 | 技术 | 财务 | 
|---|---|---|---|---|---|
| 查看售后板块 | ✅ | ✅ | ✅ | ✅ | ✅ | 
| 启动自动结算 | ❌ | ❌ | ❌ | ✅ | ❌ | 
| 上传支付凭证 | ✅ | ❌ | ✅ | ❌ | ✅ | 
| 发送图片给客户 | ✅ | ❌ | ✅ | ❌ | ❌ | 
| 合成全景图 | ❌ | ❌ | ❌ | ✅ | ❌ | 
| 生成评价链接 | ✅ | ❌ | ✅ | ❌ | ❌ | 
| 创建投诉记录 | ✅ | ❌ | ✅ | ❌ | ❌ | 
| 处理投诉 | ✅ | ❌ | ✅ | ❌ | ❌ | 
| 生成复盘报告 | ❌ | ❌ | ✅ | ✅ | ❌ | 
| 导出复盘报告 | ❌ | ❌ | ✅ | ✅ | ✅ | 
// 检查尾款结算权限
canInitiateSettlement(): boolean {
  return this.isTechnicalView();
}
// 检查全景图合成权限
canSynthesizePanoramic(): boolean {
  return this.isTechnicalView();
}
// 检查投诉处理权限
canHandleComplaints(): boolean {
  return this.isTeamLeaderView() || this.isCustomerServiceView();
}
// 检查复盘报告权限
canGenerateReviewReport(): boolean {
  return this.isTeamLeaderView() || this.isTechnicalView();
}
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: 生成复盘报告
// 售后数据同步到项目
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);
    }
  });
}
// 支付监听连接失败处理
private handlePaymentMonitoringError(error: any): void {
  console.error('支付监听连接失败:', error);
  // 降级为手动模式
  this.miniprogramPaymentStatus = 'error';
  alert(`⚠️ 支付自动监听失败\n\n请使用"上传支付凭证"功能手动确认支付。`);
  // 显示手动上传入口
  this.showManualPaymentVoucherUpload = true;
}
// 全景图合成失败处理
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请检查后重试。`);
}
// 检查评价链接是否过期
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旧链接已失效,请使用新链接。');
}
// 使用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);
  }
}
// 压缩全景图用于预览
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);
  });
}
// 缓存复盘报告数据
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;
}
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);
  });
});
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');
  });
});
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 维护人:产品团队
相关文档: