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