售后归档阶段是项目管理流程的收尾环节,包含尾款结算、全景图合成、客户评价、投诉处理、项目复盘五大核心模块。该阶段负责完成项目交付、收集反馈、总结经验,为后续项目优化提供数据支撑。
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 维护人:产品团队
相关文档: