订单分配阶段是项目管理流程的第一个环节,主要负责将客户咨询转化为正式项目订单,并完成产品设计化管理。该阶段通过Project表统一管理各个空间设计产品,是连接客服端和设计师端的关键桥梁。
class ProductIdentificationService {
// 基于客户需求描述自动识别空间设计产品
async identifyProductsFromDescription(description: string): Promise<ProductIdentificationResult> {
const result: ProductIdentificationResult = {
identifiedProducts: [],
confidence: 0,
reasoning: '',
suggestedQuestions: [],
recommendedConfiguration: null
};
// 产品类型关键词映射
const productKeywords = {
[ProductType.LIVING_ROOM]: ['客厅', '起居室', '会客厅', '茶室', '待客区', '客厅背景墙'],
[ProductType.BEDROOM]: ['卧室', '主卧', '次卧', '儿童房', '老人房', '客房', '主人房', '主卧套间'],
[ProductType.KITCHEN]: ['厨房', '开放式厨房', '中西厨', '餐厨一体', '橱柜设计'],
[ProductType.BATHROOM]: ['卫生间', '浴室', '洗手间', '盥洗室', '主卫', '次卫', '干湿分离'],
[ProductType.DINING_ROOM]: ['餐厅', '餐厅区', '用餐区', '就餐空间', '客餐厅'],
[ProductType.STUDY]: ['书房', '工作室', '办公室', '学习区', '阅读区', '家庭办公'],
[ProductType.BALCONY]: ['阳台', '露台', '花园阳台', '休闲阳台', '生活阳台'],
[ProductType.CORRIDOR]: ['走廊', '过道', '玄关', '门厅', '入户', '玄关柜'],
[ProductType.STORAGE]: ['储物间', '衣帽间', '杂物间', '收纳空间', '衣柜设计'],
[ProductType.ENTRANCE]: ['门厅', '玄关', '入户花园', '门廊'],
[ProductType.WARDROBE]: ['衣柜', '衣帽间', '储物柜', '定制柜'],
[ProductType.TV_BACKGROUND]: ['电视背景墙', '影视墙', '电视柜'],
[ProductType.SOFA_BACKGROUND]: ['沙发背景墙', '背景墙'],
[ProductType.BED_BACKGROUND]: ['床头背景墙', '床背景'],
[ProductType.OTHER]: ['其他', '定制', '特殊空间']
};
// 分析描述中的产品关键词
const foundProducts: Array<{ type: ProductType; keywords: string[]; confidence: number; metadata: any }> = [];
for (const [productType, keywords] of Object.entries(productKeywords)) {
const matchedKeywords = keywords.filter(keyword =>
description.toLowerCase().includes(keyword.toLowerCase())
);
if (matchedKeywords.length > 0) {
// 计算产品复杂度和特征
const complexity = this.assessProductComplexity(description, productType);
const estimatedArea = this.estimateProductArea(description, productType);
const specialFeatures = this.extractSpecialFeatures(description, productType);
foundProducts.push({
type: productType as ProductType,
keywords: matchedKeywords,
confidence: matchedKeywords.length / keywords.length,
metadata: {
complexity,
estimatedArea,
specialFeatures,
priority: this.calculateProductPriority(productType, complexity)
}
});
}
}
// 按置信度排序
foundProducts.sort((a, b) => b.confidence - a.confidence);
// 构建识别结果
result.identifiedProducts = foundProducts.map(fp => ({
type: fp.type,
productName: this.getDefaultProductName(fp.type),
priority: fp.metadata.priority,
confidence: fp.confidence,
identifiedKeywords: fp.keywords,
estimatedArea: fp.metadata.estimatedArea,
complexity: fp.metadata.complexity,
specialFeatures: fp.metadata.specialFeatures
}));
// 计算整体置信度
result.confidence = foundProducts.length > 0
? foundProducts.reduce((sum, fp) => sum + fp.confidence, 0) / foundProducts.length
: 0;
// 生成推理说明
result.reasoning = this.generateIdentificationReasoning(foundProducts);
// 生成建议问题
result.suggestedQuestions = this.generateClarifyingQuestions(foundProducts);
// 生成推荐配置
result.recommendedConfiguration = this.generateRecommendedConfiguration(foundProducts);
return result;
}
// 基于面积和预算推断产品设计数量和类型
estimateProductConfiguration(totalArea: number, budget: number, description: string): ProductEstimationResult {
const result: ProductEstimationResult = {
estimatedProductCount: 1,
confidence: 0.5,
reasoning: '',
possibleProductTypes: [],
recommendedProducts: [],
budgetAllocation: {},
designComplexity: 'medium'
};
// 基于面积的产品数量估算
const areaBasedCount = Math.max(1, Math.floor(totalArea / 18)); // 每18平米一个主要产品
// 基于预算的产品数量估算
const budgetBasedCount = Math.max(1, Math.floor(budget / 25000)); // 每2.5万一个产品
// 综合判断
const finalCount = Math.min(areaBasedCount, budgetBasedCount);
result.estimatedProductCount = finalCount;
// 推断可能的产品类型
result.possibleProductTypes = this.inferPossibleProductTypes(totalArea, budget, description);
// 生成推荐产品配置
result.recommendedProducts = this.generateRecommendedProducts(
result.possibleProductTypes,
totalArea,
budget,
finalCount
);
// 计算预算分配
result.budgetAllocation = this.calculateBudgetAllocation(result.recommendedProducts, budget);
// 评估设计复杂度
result.designComplexity = this.assessOverallDesignComplexity(result.recommendedProducts, description);
// 生成推理
result.reasoning = `基于面积${totalArea}平米和预算${budget}元,估算需要${finalCount}个主要产品设计产品,设计复杂度为${result.designComplexity}`;
return result;
}
// 生成产品设计配置建议
generateProductConfiguration(
identifiedProducts: IdentifiedProduct[],
totalArea: number,
budget: number
): ProductConfiguration {
const configuration: ProductConfiguration = {
products: [],
totalEstimatedBudget: 0,
budgetAllocation: {},
recommendations: [],
designStyle: null,
complexityAnalysis: null,
timelineEstimate: null
};
// 为识别出的产品创建配置
for (const product of identifiedProducts) {
const productConfig = this.createProductConfiguration(product, totalArea, budget);
configuration.products.push(productConfig);
configuration.budgetAllocation[product.type] = productConfig.estimatedBudget;
}
// 如果没有识别出产品,创建默认配置
if (configuration.products.length === 0) {
const defaultProduct = this.createDefaultProductConfiguration(totalArea, budget);
configuration.products.push(defaultProduct);
configuration.budgetAllocation[defaultProduct.type] = defaultProduct.estimatedBudget;
}
// 计算总预算
configuration.totalEstimatedBudget = Object.values(configuration.budgetAllocation)
.reduce((sum, budget) => sum + budget, 0);
// 分析设计风格
configuration.designStyle = this.analyzeDesignStyle(identifiedProducts);
// 分析复杂度
configuration.complexityAnalysis = this.analyzeComplexity(configuration.products);
// 估算时间线
configuration.timelineEstimate = this.estimateTimeline(configuration.products);
// 生成建议
configuration.recommendations = this.generateConfigurationRecommendations(configuration);
return configuration;
}
private createProductConfiguration(
product: IdentifiedProduct,
totalArea: number,
budget: number
): ProductConfig {
const basePrice = this.getBasePriceForProductType(product.type, product.estimatedArea || 0);
const complexityMultiplier = this.getComplexityMultiplier(product.complexity);
const styleFactor = this.getStyleFactor(product.type);
return {
type: product.type,
productName: product.productName,
estimatedArea: product.estimatedArea || this.getDefaultAreaForType(product.type),
estimatedBudget: Math.round(basePrice * complexityMultiplier * styleFactor),
priority: product.priority,
complexity: product.complexity,
specialFeatures: product.specialFeatures || [],
estimatedDuration: this.estimateProductDuration(product.type, product.complexity),
designerSkills: this.getRequiredDesignerSkills(product.type, product.complexity),
deliverables: this.getStandardDeliverables(product.type)
};
}
}
interface ProductIdentificationResult {
identifiedProducts: IdentifiedProduct[];
confidence: number;
reasoning: string;
suggestedQuestions: string[];
recommendedConfiguration: ProductConfiguration | null;
}
interface IdentifiedProduct {
type: ProductType;
productName: string;
priority: number;
confidence: number;
identifiedKeywords: string[];
estimatedArea?: number;
complexity: 'simple' | 'medium' | 'complex';
specialFeatures?: string[];
}
interface ProductEstimationResult {
estimatedProductCount: number;
confidence: number;
reasoning: string;
possibleProductTypes: ProductType[];
recommendedProducts: ProductConfig[];
budgetAllocation: Record<ProductType, number>;
designComplexity: 'simple' | 'medium' | 'complex';
}
interface ProductConfiguration {
products: ProductConfig[];
totalEstimatedBudget: number;
budgetAllocation: Record<ProductType, number>;
recommendations: string[];
designStyle: DesignStyleAnalysis | null;
complexityAnalysis: ComplexityAnalysis | null;
timelineEstimate: TimelineEstimate | null;
}
interface ProductConfig {
type: ProductType;
productName: string;
estimatedArea: number;
estimatedBudget: number;
priority: number;
complexity: 'simple' | 'medium' | 'complex';
specialFeatures: string[];
estimatedDuration: number;
designerSkills: string[];
deliverables: string[];
}
<!-- 产品设计管理面板 -->
<div class="product-management-panel">
<div class="panel-header">
<h3>产品设计配置</h3>
<div class="project-type-indicator">
<span class="indicator-label">项目类型:</span>
<span class="indicator-value"
[class.single-product]="isSingleProductProject"
[class.multi-product]="!isSingleProductProject">
{{ isSingleProductProject ? '单产品项目' : '多产品项目' }}
</span>
<span class="product-count">{{ projectProducts.length }} 个产品</span>
</div>
</div>
<!-- 产品识别结果 -->
<div class="product-identification-result" *ngIf="productIdentificationResult">
<div class="identification-summary">
<h4>识别到 {{ productIdentificationResult.identifiedProducts.length }} 个产品设计</h4>
<div class="confidence-indicator">
<span class="confidence-label">识别置信度:</span>
<div class="confidence-bar">
<div class="confidence-fill"
[style.width.%]="productIdentificationResult.confidence * 100"
[class.high]="productIdentificationResult.confidence >= 0.8"
[class.medium]="productIdentificationResult.confidence >= 0.5 && productIdentificationResult.confidence < 0.8"
[class.low]="productIdentificationResult.confidence < 0.5">
</div>
</div>
<span class="confidence-value">{{ Math.round(productIdentificationResult.confidence * 100) }}%</span>
</div>
</div>
<div class="identification-reasoning">
<p>{{ productIdentificationResult.reasoning }}</p>
</div>
<!-- 推荐配置 -->
@if (productIdentificationResult.recommendedConfiguration) {
<div class="recommended-configuration">
<h5>推荐配置</h5>
<div class="configuration-summary">
<div class="summary-item">
<label>设计风格:</label>
<span>{{ productIdentificationResult.recommendedConfiguration.designStyle?.primaryStyle || '现代简约' }}</span>
</div>
<div class="summary-item">
<label>设计复杂度:</label>
<span class="complexity-badge complexity-{{ productIdentificationResult.recommendedConfiguration.complexityAnalysis?.overallComplexity }}">
{{ getComplexityName(productIdentificationResult.recommendedConfiguration.complexityAnalysis?.overallComplexity) }}
</span>
</div>
<div class="summary-item">
<label>预估工期:</label>
<span>{{ productIdentificationResult.recommendedConfiguration.timelineEstimate?.totalDays || 30 }} 天</span>
</div>
</div>
</div>
}
<!-- 建议问题 -->
<div class="suggested-questions" *ngIf="productIdentificationResult.suggestedQuestions.length > 0">
<h5>建议确认的问题:</h5>
<ul>
@for (question of productIdentificationResult.suggestedQuestions; track question) {
<li>{{ question }}</li>
}
</ul>
</div>
</div>
<!-- 产品列表 -->
<div class="products-list">
<div class="list-header">
<h4>产品设计列表</h4>
<div class="list-actions">
<button class="btn-add-product" (click)="showAddProductDialog()">
<i class="icon-plus"></i> 添加产品
</button>
<button class="btn-secondary" (click)="showBatchProductConfig = true">
<i class="icon-settings"></i> 批量配置
</button>
</div>
</div>
<div class="products-grid">
@for (product of projectProducts; track product.id) {
<div class="product-card"
[class.priority-high]="product.priority >= 8"
[class.priority-medium]="product.priority >= 5 && product.priority < 8"
[class.priority-low]="product.priority < 5"
[class.complexity-simple]="product.complexity === 'simple'"
[class.complexity-medium]="product.complexity === 'medium'"
[class.complexity-complex]="product.complexity === 'complex'">
<!-- 产品头部 -->
<div class="product-header">
<div class="product-icon">
<i class="icon-{{ getProductIcon(product.type) }}"></i>
</div>
<div class="product-info">
<input type="text"
[(ngModel)]="product.name"
class="product-name-input"
placeholder="产品名称">
<select [(ngModel)]="product.type" class="product-type-select">
<option value="{{ ProductType.LIVING_ROOM }}">客厅</option>
<option value="{{ ProductType.BEDROOM }}">卧室</option>
<option value="{{ ProductType.KITCHEN }}">厨房</option>
<option value="{{ ProductType.BATHROOM }}">卫生间</option>
<option value="{{ ProductType.DINING_ROOM }}">餐厅</option>
<option value="{{ ProductType.STUDY }}">书房</option>
<option value="{{ ProductType.BALCONY }}">阳台</option>
<option value="{{ ProductType.CORRIDOR }}">走廊/玄关</option>
<option value="{{ ProductType.STORAGE }}">储物间</option>
<option value="{{ ProductType.ENTRANCE }}">门厅</option>
<option value="{{ ProductType.TV_BACKGROUND }}">电视背景墙</option>
<option value="{{ ProductType.SOFA_BACKGROUND }}">沙发背景墙</option>
<option value="{{ ProductType.BED_BACKGROUND }}">床头背景墙</option>
<option value="{{ ProductType.WARDROBE }}">衣柜</option>
<option value="{{ ProductType.OTHER }}">其他</option>
</select>
</div>
<div class="product-actions">
<button class="btn-icon" (click)="editProduct(product.id)" title="编辑">
<i class="icon-edit"></i>
</button>
<button class="btn-icon" (click)="duplicateProduct(product.id)" title="复制">
<i class="icon-copy"></i>
</button>
<button class="btn-icon danger" (click)="removeProduct(product.id)" title="删除">
<i class="icon-delete"></i>
</button>
</div>
</div>
<!-- 产品详情 -->
<div class="product-details">
<div class="detail-row">
<label>面积:</label>
<input type="number"
[(ngModel)]="product.area"
min="1"
step="0.1"
class="detail-input">
<span class="unit">m²</span>
</div>
<div class="detail-row">
<label>优先级:</label>
<select [(ngModel)]="product.priority" class="priority-select">
<option [ngValue]="10">最高优先级</option>
<option [ngValue]="8">高优先级</option>
<option [ngValue]="5">中优先级</option>
<option [ngValue]="3">低优先级</option>
<option [ngValue]="1">最低优先级</option>
</select>
</div>
<div class="detail-row">
<label>复杂度:</label>
<select [(ngModel)]="product.complexity" class="complexity-select">
<option value="simple">简单</option>
<option value="medium">中等</option>
<option value="complex">复杂</option>
</select>
</div>
<div class="detail-row">
<label>预估工期:</label>
<input type="number"
[(ngModel)]="product.estimatedDuration"
min="1"
class="detail-input">
<span class="unit">天</span>
</div>
<!-- 特殊特征 -->
<div class="special-features">
<label>特殊特征:</label>
<div class="features-tags">
@for (feature of product.specialFeatures || []; track feature) {
<span class="feature-tag">
{{ feature }}
<button class="remove-feature" (click)="removeFeature(product.id, feature)">
<i class="icon-close"></i>
</button>
</span>
}
<button class="add-feature-btn" (click)="showAddFeatureDialog(product.id)">
<i class="icon-plus"></i>
</button>
</div>
</div>
</div>
<!-- 产品预算 -->
<div class="product-budget">
<label>预估预算:</label>
<div class="budget-input-group">
<input type="number"
[(ngModel)]="product.estimatedBudget"
min="0"
step="100"
class="budget-input">
<span class="currency">元</span>
</div>
<div class="budget-breakdown" *ngIf="product.estimatedBudget">
<div class="breakdown-item">
<span class="label">设计费:</span>
<span class="value">¥{{ Math.round(product.estimatedBudget * 0.3).toLocaleString() }}</span>
</div>
<div class="breakdown-item">
<span class="label">建模费:</span>
<span class="value">¥{{ Math.round(product.estimatedBudget * 0.3).toLocaleString() }}</span>
</div>
<div class="breakdown-item">
<span class="label">渲染费:</span>
<span class="value">¥{{ Math.round(product.estimatedBudget * 0.25).toLocaleString() }}</span>
</div>
<div class="breakdown-item">
<span class="label">软装费:</span>
<span class="value">¥{{ Math.round(product.estimatedBudget * 0.15).toLocaleString() }}</span>
</div>
</div>
</div>
</div>
}
</div>
</div>
<!-- 产品统计信息 -->
<div class="product-statistics">
<div class="stat-group">
<h4>统计信息</h4>
<div class="stat-items">
<div class="stat-item">
<span class="stat-label">产品总数:</span>
<span class="stat-value">{{ projectProducts.length }}</span>
</div>
<div class="stat-item">
<span class="stat-label">总面积:</span>
<span class="stat-value">{{ totalProductArea }}m²</span>
</div>
<div class="stat-item">
<span class="stat-label">总预算:</span>
<span class="stat-value">¥{{ totalProductBudget.toLocaleString() }}</span>
</div>
<div class="stat-item">
<span class="stat-label">平均单价:</span>
<span class="stat-value">¥{{ averagePricePerSqm.toLocaleString() }}/m²</span>
</div>
<div class="stat-item">
<span class="stat-label">预估总工期:</span>
<span class="stat-value">{{ totalEstimatedDuration }}天</span>
</div>
</div>
</div>
<!-- 产品类型分布 -->
<div class="product-distribution">
<h4>产品类型分布</h4>
<div class="distribution-chart">
@for (type of getProductTypeDistribution(); track type.type) {
<div class="distribution-item">
<div class="type-info">
<i class="icon-{{ getProductIcon(type.type) }}"></i>
<span class="type-name">{{ getProductTypeName(type.type) }}</span>
</div>
<div class="type-stats">
<span class="count">{{ type.count }}</span>
<span class="percentage">{{ type.percentage }}%</span>
</div>
</div>
}
</div>
</div>
</div>
</div>
class MultiProductQuotationService {
// 计算多产品项目报价
calculateMultiProductQuotation(
products: Project[],
globalOptions: QuotationOptions
): MultiProductQuotation {
const quotation: MultiProductQuotation = {
projectId: this.getProjectId(),
quotationDate: new Date(),
currency: 'CNY',
// 产品报价明细
productQuotations: [],
// 全局折扣和优惠
globalDiscounts: [],
// 汇总信息
summary: {
totalBaseAmount: 0,
totalDiscountAmount: 0,
finalAmount: 0,
averagePricePerSqm: 0,
totalArea: 0,
productCount: products.length
},
// 报价策略
pricingStrategy: this.determinePricingStrategy(products),
// 时间估算
timeline: this.calculateProjectTimeline(products)
};
// 计算各产品报价
for (const product of products) {
const productQuotation = this.calculateProductQuotation(product, globalOptions);
quotation.productQuotations.push(productQuotation);
quotation.summary.totalArea += product.area || 0;
}
// 计算基础总额
quotation.summary.totalBaseAmount = quotation.productQuotations
.reduce((sum, pq) => sum + pq.totalAmount, 0);
// 应用多产品折扣
quotation.globalDiscounts = this.calculateMultiProductDiscounts(
products,
quotation.summary.totalBaseAmount,
quotation.pricingStrategy
);
// 计算折扣总额
quotation.summary.totalDiscountAmount = quotation.globalDiscounts
.reduce((sum, discount) => sum + discount.value, 0);
// 计算最终金额
quotation.summary.finalAmount = quotation.summary.totalBaseAmount - quotation.summary.totalDiscountAmount;
// 计算平均单价
quotation.summary.averagePricePerSqm = quotation.summary.totalArea > 0
? quotation.summary.finalAmount / quotation.summary.totalArea
: 0;
return quotation;
}
// 计算单个产品报价
private calculateProductQuotation(
product: Project,
options: QuotationOptions
): ProductQuotation {
// 基础价格计算
const basePrice = this.calculateBasePrice(product, options);
// 复杂度调整
const complexityMultiplier = this.getComplexityMultiplier(product.complexity);
// 优先级调整
const priorityMultiplier = this.getPriorityMultiplier(product.priority);
// 面积系数
const areaCoefficient = this.getAreaCoefficient(product.area || 0);
// 设计风格系数
const styleCoefficient = this.getStyleCoefficient(options.designStyle);
// 产品类型系数
const typeCoefficient = this.getTypeCoefficient(product.type);
// 计算最终价格
const finalPrice = basePrice * complexityMultiplier * priorityMultiplier * areaCoefficient * styleCoefficient * typeCoefficient;
const productQuotation: ProductQuotation = {
productId: product.id,
productName: product.name,
productType: product.type as ProductType,
area: product.area || 0,
// 价格明细
priceBreakdown: {
basePrice: basePrice,
complexityAdjustment: basePrice * (complexityMultiplier - 1),
priorityAdjustment: basePrice * (priorityMultiplier - 1),
areaAdjustment: basePrice * (areaCoefficient - 1),
styleAdjustment: basePrice * (styleCoefficient - 1),
typeAdjustment: basePrice * (typeCoefficient - 1),
},
// 总价
totalAmount: finalPrice,
// 单价
unitPrice: product.area ? finalPrice / product.area : 0,
// 时间预估
estimatedDays: this.calculateEstimatedDays(product, finalPrice),
// 设计师配置
designerRequirements: this.getDesignerRequirements(product),
// 交付物清单
deliverables: this.getDeliverablesForProduct(product),
// 风险评估
riskAssessment: this.assessProductRisk(product),
// 质量标准
qualityStandards: this.getQualityStandardsForProduct(product)
};
return productQuotation;
}
// 计算多产品折扣
private calculateMultiProductDiscounts(
products: Project[],
baseAmount: number,
strategy: PricingStrategy
): QuotationDiscount[] {
const discounts: QuotationDiscount[] = [];
// 1. 产品数量折扣
const productCount = products.length;
if (productCount >= 8) {
discounts.push({
type: 'product_count',
name: '8产品及以上项目折扣',
description: '8个及以上产品项目享受15%折扣',
value: baseAmount * 0.15,
isApplicable: true,
tier: 'platinum'
});
} else if (productCount >= 5) {
discounts.push({
type: 'product_count',
name: '5-7产品项目折扣',
description: '5-7个产品项目享受12%折扣',
value: baseAmount * 0.12,
isApplicable: true,
tier: 'gold'
});
} else if (productCount >= 3) {
discounts.push({
type: 'product_count',
name: '3-4产品项目折扣',
description: '3-4个产品项目享受8%折扣',
value: baseAmount * 0.08,
isApplicable: true,
tier: 'silver'
});
} else if (productCount >= 2) {
discounts.push({
type: 'product_count',
name: '双产品项目折扣',
description: '双产品项目享受5%折扣',
value: baseAmount * 0.05,
isApplicable: true,
tier: 'bronze'
});
}
// 2. 总额折扣
if (baseAmount >= 500000) {
discounts.push({
type: 'total_amount',
name: '高额度项目折扣',
description: '项目总额超过50万享受额外5%折扣',
value: baseAmount * 0.05,
isApplicable: true,
tier: 'vip'
});
} else if (baseAmount >= 200000) {
discounts.push({
type: 'total_amount',
name: '中高额度项目折扣',
description: '项目总额超过20万享受额外3%折扣',
value: baseAmount * 0.03,
isApplicable: true,
tier: 'premium'
});
}
// 3. 复杂度折扣(针对高复杂度产品组合)
const allHighComplexity = products.every(product => product.complexity === 'complex');
if (allHighComplexity && productCount >= 3) {
discounts.push({
type: 'complexity_bonus',
name: '高复杂度项目折扣',
description: '全高复杂度多产品项目享受4%折扣',
value: baseAmount * 0.04,
isApplicable: true,
tier: 'complexity'
});
}
// 4. 策略折扣
if (strategy === 'premium' && productCount >= 3) {
discounts.push({
type: 'strategy_bonus',
name: '高端项目策略折扣',
description: '高端多产品项目额外3%折扣',
value: baseAmount * 0.03,
isApplicable: true,
tier: 'strategy'
});
}
// 5. 季节性折扣
const seasonalDiscount = this.getSeasonalDiscount();
if (seasonalDiscount) {
discounts.push(seasonalDiscount);
}
return discounts;
}
// 生成报价单
async generateQuotationDocument(quotation: MultiProductQuotation): Promise<QuotationDocument> {
const document: QuotationDocument = {
id: `quotation_${Date.now()}`,
projectId: quotation.projectId,
documentNumber: this.generateQuotationNumber(),
issueDate: new Date(),
validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30天有效期
currency: quotation.currency,
quotation: quotation,
// 客户信息
customerInfo: await this.getCustomerInfo(),
// 项目信息
projectInfo: await this.getProjectInfo(),
// 支付条款
paymentTerms: this.generatePaymentTerms(quotation),
// 服务内容
serviceScope: this.generateServiceScope(quotation),
// 注意事项
notes: this.generateQuotationNotes(quotation),
// 产品汇总
productSummary: this.generateProductSummary(quotation),
// 设计团队信息
designTeam: await this.getDesignTeamInfo()
};
return document;
}
// 确定报价策略
private determinePricingStrategy(products: Project[]): PricingStrategy {
const avgBudget = products.reduce((sum, p) => sum + (p.estimatedBudget || 0), 0) / products.length;
const avgComplexity = products.reduce((sum, p) => sum + this.getComplexityScore(p.complexity), 0) / products.length;
if (avgBudget > 100000 && avgComplexity > 2) {
return 'premium';
} else if (avgBudget > 50000) {
return 'standard';
} else {
return 'economy';
}
}
}
interface MultiProductQuotation {
projectId: string;
quotationDate: Date;
currency: string;
productQuotations: ProductQuotation[];
globalDiscounts: QuotationDiscount[];
summary: {
totalBaseAmount: number;
totalDiscountAmount: number;
finalAmount: number;
averagePricePerSqm: number;
totalArea: number;
productCount: number;
};
pricingStrategy: PricingStrategy;
timeline: ProjectTimeline;
}
interface ProductQuotation {
productId: string;
productName: string;
productType: ProductType;
area: number;
priceBreakdown: {
basePrice: number;
complexityAdjustment: number;
priorityAdjustment: number;
areaAdjustment: number;
styleAdjustment: number;
typeAdjustment: number;
};
totalAmount: number;
unitPrice: number;
estimatedDays: number;
designerRequirements: string[];
deliverables: string[];
riskAssessment: RiskAssessment;
qualityStandards: QualityStandard[];
}
interface QuotationDiscount {
type: 'product_count' | 'total_amount' | 'complexity_bonus' | 'strategy_bonus' | 'seasonal' | 'special_offer';
name: string;
description: string;
value: number;
isApplicable: boolean;
tier: 'bronze' | 'silver' | 'gold' | 'platinum' | 'vip' | 'premium' | 'complexity' | 'strategy';
}
type PricingStrategy = 'economy' | 'standard' | 'premium';
位置:订单分配阶段左侧面板
展示内容:
interface CustomerInfoDisplay {
// 基础信息
name: string; // 客户姓名
phone: string; // 联系电话
wechat?: string; // 微信号
customerType: string; // 客户类型:新客户/老客户/VIP客户
source: string; // 来源:小程序/官网咨询/推荐介绍
remark?: string; // 备注信息
// 状态信息
syncStatus: 'syncing' | 'synced' | 'error'; // 同步状态
lastSyncTime?: Date; // 最后同步时间
// 需求标签
demandType?: string; // 需求类型:价格敏感/质量敏感/综合要求
followUpStatus?: string; // 跟进状态:待报价/待确认需求/已失联
preferenceTags?: string[]; // 偏好标签数组
// 项目需求特征
projectType?: string; // 项目类型倾向:全屋设计/局部改造/软装设计
budgetRange?: { // 预算范围
min: number;
max: number;
currency: string;
};
timeline?: { // 时间要求
preferredStart?: Date;
deadline?: Date;
urgency: 'low' | 'medium' | 'high';
};
}
数据来源:
客服端同步:通过路由查询参数 syncData
传递客户信息
// 客服端跳转示例
router.navigate(['/designer/project-detail', projectId], {
queryParams: {
syncData: JSON.stringify({
customerInfo: {...},
requirementInfo: {...},
preferenceTags: [...],
projectAnalysis: {...}
})
}
});
实时同步机制:
交互特性:
isCustomerInfoExpanded
)适用场景:手动创建订单时快速选择已有客户
搜索逻辑:
searchCustomer(): void {
// 至少输入2个字符才触发搜索
if (this.customerSearchKeyword.trim().length >= 2) {
// 模糊搜索客户姓名、手机号、微信号
this.customerSearchResults = this.customerService.search({
keyword: this.customerSearchKeyword,
fields: ['name', 'phone', 'wechat'],
filters: {
customerType: this.selectedCustomerType,
isActive: true
},
limit: 20
});
}
}
搜索结果展示:
表单定义:
orderCreationForm = this.fb.group({
orderAmount: ['', [Validators.required, Validators.min(0)]],
smallImageDeliveryTime: ['', Validators.required],
decorationType: ['', Validators.required],
requirementReason: ['', Validators.required],
isMultiDesigner: [false],
projectComplexity: ['medium'], // 新增:项目复杂度
designStyle: [''], // 新增:设计风格
specialRequirements: [''] // 新增:特殊要求
});
字段详解:
字段名 | 类型 | 验证规则 | 说明 | UI组件 |
---|---|---|---|---|
orderAmount |
number | required, min(0) | 订单金额,单位:元 | 数字输入框,支持千分位格式化 |
smallImageDeliveryTime |
Date | required | 小图交付时间 | 日期选择器,限制最早日期为今天 |
decorationType |
string | required | 装修类型:全包/半包/清包/软装 | 下拉选择框 |
requirementReason |
string | required | 需求原因:新房装修/旧房改造/局部翻新 | 单选框组 |
isMultiDesigner |
boolean | - | 是否需要多设计师协作 | 复选框 |
projectComplexity |
string | - | 项目复杂度:简单/中等/复杂 | 下拉选择框 |
designStyle |
string | - | 设计风格偏好 | 下拉选择框或标签选择 |
specialRequirements |
string | - | 特殊要求说明 | 文本域 |
表单验证提示:
markAllAsTouched()
显示所有错误折叠面板设计:
<div class="optional-info-section">
<div class="section-header" (click)="isOptionalFormExpanded = !isOptionalFormExpanded">
<span>可选信息</span>
<span class="toggle-icon">{{ isOptionalFormExpanded ? '▼' : '▶' }}</span>
</div>
@if (isOptionalFormExpanded) {
<div class="section-content">
<!-- 可选字段表单 -->
</div>
}
</div>
可选字段:
optionalForm = this.fb.group({
largeImageDeliveryTime: [''], // 大图交付时间
spaceRequirements: [''], // 空间需求描述(已移至产品设计)
designAngles: [''], // 设计角度要求
specialAreaHandling: [''], // 特殊区域处理说明
materialRequirements: [''], // 材质要求
lightingRequirements: [''], // 光照需求
budgetRange: [''], // 预算范围
timelinePreference: [''], // 时间偏好
qualityStandards: [''], // 质量标准要求
});
组件标签:
<app-product-quotation-details
[initialData]="quotationData"
[products]="projectProducts"
[readonly]="isReadOnly()"
(dataChange)="onQuotationDataChange($event)"
(productChange)="onProductChange($event)">
</app-product-quotation-details>
数据结构:
interface ProductQuotationData {
products: Array<{
id: string;
name: string; // 产品名称:客厅设计、主卧设计
type: ProductType; // 产品类型
area?: number; // 面积
amount: number; // 金额
description?: string; // 描述
estimatedDays: number; // 预估工期
complexity: string; // 复杂度
priority: number; // 优先级
}>;
totalAmount: number; // 总金额
materialCost: number; // 材料费
laborCost: number; // 人工费
designFee: number; // 设计费
modelingFee: number; // 建模费
renderingFee: number; // 渲染费
softDecorFee: number; // 软装费
managementFee: number; // 管理费
discounts: DiscountItem[]; // 折扣项
paymentSchedule: PaymentSchedule[]; // 付款计划
}
产品报价管理:
AI辅助生成(增强功能):
generateProductQuotations(): void {
// 基于产品设计配置自动生成报价明细
const productQuotations = this.projectProducts.map((product, index) => ({
id: product.id,
name: product.name,
type: product.type,
area: product.area || this.getDefaultAreaForType(product.type),
amount: this.calculateProductPrice(product),
estimatedDays: product.estimatedDuration || this.getDefaultDaysForType(product.type),
description: `${product.name}设计费用`,
complexity: product.complexity,
priority: product.priority
}));
this.quotationData = {
products: productQuotations,
totalAmount: productQuotations.reduce((total, item) => total + item.amount, 0),
// ... 其他费用计算
};
// 自动应用折扣策略
this.applyDiscountStrategy();
}
报价策略管理:
付款计划配置:
generatePaymentSchedule(totalAmount: number): PaymentSchedule[] {
const schedules: PaymentSchedule[] = [
{
stage: '签约定金',
percentage: 30,
amount: totalAmount * 0.3,
dueDate: new Date(),
status: 'pending',
description: '签约时支付定金'
},
{
stage: '方案确认',
percentage: 40,
amount: totalAmount * 0.4,
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
status: 'pending',
description: '方案确认后支付'
},
{
stage: '设计完成',
percentage: 30,
amount: totalAmount * 0.3,
dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
status: 'pending',
description: '设计完成后支付尾款'
}
];
return schedules;
}
报价单导出:
组件标签:
<app-product-designer-assignment
[projectData]="projectData"
[products]="projectProducts"
[readonly]="isReadOnly()"
(assignmentChange)="onDesignerAssignmentChange($event)"
(designerClick)="onDesignerClick($event)"
(batchAssignmentChange)="onBatchAssignmentChange($event)">
</app-product-designer-assignment>
数据结构:
interface ProductDesignerAssignmentData {
assignments: ProductDesignerAssignment[];
teamId: string;
teamName: string;
leaderId: string;
assignmentDate: Date;
expectedStartDate: Date;
batchAssignmentMode: boolean;
assignmentStrategy: AssignmentStrategy;
}
interface ProductDesignerAssignment {
productId: string;
productName: string;
productType: ProductType;
assignedDesigners: Designer[];
primaryDesigner?: Designer;
requiredSkills: string[];
estimatedWorkload: number;
assignmentDate: Date;
status: 'pending' | 'confirmed' | 'in_progress' | 'completed';
}
interface Designer {
id: string;
name: string;
avatar: string;
teamId: string;
teamName: string;
isTeamLeader: boolean;
status: 'idle' | 'busy' | 'unavailable' | 'on_vacation';
currentProjects: number;
skillMatch: number; // 技能匹配度 0-100
recentOrders: number; // 近期订单数
idleDays: number; // 闲置天数
workload: number; // 工作负荷 0-100
reviewDates: string[]; // 对图评审日期列表
specialties: ProductType[]; // 专业领域
experience: {
years: number;
completedProjects: number;
clientSatisfaction: number;
};
rating: {
overall: number;
communication: number;
quality: number;
timeliness: number;
};
}
智能推荐算法:
calculateProductDesignerScore(designer: Designer, product: Project): number {
let score = 0;
// 1. 专业匹配度(权重40%)
const specialtyMatch = designer.specialties.includes(product.type as ProductType) ? 100 : 0;
score += specialtyMatch * 0.4;
// 2. 技能匹配度(权重25%)
const requiredSkills = this.getRequiredSkillsForProduct(product);
const skillMatch = this.calculateSkillMatch(designer, requiredSkills);
score += skillMatch * 0.25;
// 3. 工作负荷(权重15%,负荷越低分数越高)
score += (100 - designer.workload) * 0.15;
// 4. 经验和评价(权重10%)
const experienceScore = this.calculateExperienceScore(designer);
score += experienceScore * 0.1;
// 5. 近期活跃度(权重5%,活跃度适中分数越高)
const activityScore = this.calculateActivityScore(designer);
score += activityScore * 0.05;
// 6. 产品复杂度适配(权重5%)
const complexityScore = this.calculateComplexityMatch(designer, product.complexity);
score += complexityScore * 0.05;
return Math.min(100, Math.max(0, score));
}
// 产品批量分配算法
assignDesignersToProducts(products: Project[], availableDesigners: Designer[]): ProductDesignerAssignment[] {
const assignments: ProductDesignerAssignment[] = [];
const usedDesigners = new Set<string>();
for (const product of products) {
// 为每个产品筛选合适的设计师
const suitableDesigners = availableDesigners
.filter(designer => !usedDesigners.has(designer.id))
.filter(designer => designer.status === 'idle' || designer.workload < 80)
.map(designer => ({
designer,
score: this.calculateProductDesignerScore(designer, product)
}))
.sort((a, b) => b.score - a.score);
// 选择最佳匹配设计师
if (suitableDesigners.length > 0) {
const bestDesigner = suitableDesigners[0].designer;
// 如果产品复杂,考虑分配助理设计师
const assistantDesigners = product.complexity === 'complex'
? this.findAssistantDesigners(bestDesigner, availableDesigners, usedDesigners)
: [];
const assignment: ProductDesignerAssignment = {
productId: product.id,
productName: product.name,
productType: product.type as ProductType,
assignedDesigners: [bestDesigner, ...assistantDesigners],
primaryDesigner: bestDesigner,
requiredSkills: this.getRequiredSkillsForProduct(product),
estimatedWorkload: this.calculateProductWorkload(product),
assignmentDate: new Date(),
status: 'pending'
};
assignments.push(assignment);
// 标记已使用的设计师
usedDesigners.add(bestDesigner.id);
assistantDesigners.forEach(ad => usedDesigners.add(ad.id));
}
}
return assignments;
}
设计师列表展示:
idle
空闲 - 绿色busy
繁忙 - 橙色unavailable
不可用 - 灰色on_vacation
休假 - 蓝色class BatchProductAssignmentService {
async executeBatchAssignment(
assignments: ProductDesignerAssignment[],
options: BatchAssignmentOptions
): Promise<BatchAssignmentResult> {
const result: BatchAssignmentResult = {
successCount: 0,
failureCount: 0,
results: [],
conflicts: [],
warnings: []
};
// 检查设计师时间冲突
const conflicts = this.checkDesignerConflicts(assignments);
result.conflicts = conflicts;
if (conflicts.length > 0 && options.resolveConflicts) {
// 自动解决冲突
const resolvedAssignments = await this.resolveConflicts(assignments, conflicts);
assignments = resolvedAssignments;
}
// 执行批量分配
for (const assignment of assignments) {
try {
await this.executeAssignment(assignment, options);
result.successCount++;
result.results.push({
assignmentId: assignment.productId,
success: true,
message: `成功为${assignment.productName}分配设计师`
});
} catch (error) {
result.failureCount++;
result.results.push({
assignmentId: assignment.productId,
success: false,
message: `分配失败: ${error.message}`
});
}
}
return result;
}
private checkDesignerConflicts(assignments: ProductDesignerAssignment[]): DesignerConflict[] {
const conflicts: DesignerConflict[] = [];
const designerUsage = new Map<string, ProductDesignerAssignment[]>();
// 统计每个设计师的分配情况
for (const assignment of assignments) {
for (const designer of assignment.assignedDesigners) {
if (!designerUsage.has(designer.id)) {
designerUsage.set(designer.id, []);
}
designerUsage.get(designer.id)!.push(assignment);
}
}
// 检查冲突
for (const [designerId, designerAssignments] of designerUsage.entries()) {
if (designerAssignments.length > 3) {
conflicts.push({
designerId,
designerName: designerAssignments[0].assignedDesigners.find(d => d.id === designerId)?.name || '',
conflictType: 'over_assignment',
assignments: designerAssignments,
recommendation: '建议减少分配数量或分配助理设计师'
});
}
// 检查时间冲突
const timeConflicts = this.checkTimeConflicts(designerAssignments);
conflicts.push(...timeConflicts);
}
return conflicts;
}
}
实现逻辑:
ngOnInit(): void {
// 自动生成下单时间
this.orderTime = new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// 生成订单编号
this.orderNumber = this.generateOrderNumber();
}
generateOrderNumber(): string {
const date = new Date();
const dateStr = date.toISOString().slice(0, 10).replace(/-/g, '');
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
return `ORD${dateStr}${random}`;
}
显示格式:2025-10-20 14:30:25
,订单编号:ORD202510201234
sequenceDiagram
participant CS as 客服端
participant Route as 路由
participant PD as 项目详情页
participant Form as 订单表单
participant ProductService as 产品服务
CS->>Route: navigate with syncData
Route->>PD: queryParams.syncData
PD->>PD: parseJSON(syncData)
PD->>Form: patchValue(customerInfo)
PD->>ProductService: identifyProducts(requirementInfo)
ProductService-->>PD: productIdentificationResult
PD->>Form: patchValue(productData)
PD->>PD: syncRequirementKeyInfo(requirementInfo)
PD->>PD: 更新 projectData
PD->>PD: 触发 cdr.detectChanges()
PD-->>CS: 同步完成
关键代码实现:
// project-detail.ts lines 741-816
this.route.queryParamMap.subscribe({
next: (qp) => {
const syncDataParam = qp.get('syncData');
if (syncDataParam) {
try {
const syncData = JSON.parse(syncDataParam);
// 设置同步状态
this.isSyncingCustomerInfo = true;
// 存储订单分配数据用于显示
this.orderCreationData = syncData;
// 同步客户信息到表单
if (syncData.customerInfo) {
this.customerForm.patchValue({
name: syncData.customerInfo.name || '',
phone: syncData.customerInfo.phone || '',
wechat: syncData.customerInfo.wechat || '',
customerType: syncData.customerInfo.customerType || '新客户',
source: syncData.customerInfo.source || '小程序',
remark: syncData.customerInfo.remark || ''
});
}
// 产品识别和配置
if (syncData.requirementInfo) {
this.identifyAndConfigureProducts(syncData.requirementInfo);
}
// 同步偏好标签
if (syncData.preferenceTags) {
this.project.customerTags = syncData.preferenceTags;
}
// 模拟同步完成
setTimeout(() => {
this.isSyncingCustomerInfo = false;
this.lastSyncTime = new Date();
this.cdr.detectChanges();
}, 1500);
} catch (error) {
console.error('解析同步数据失败:', error);
this.isSyncingCustomerInfo = false;
}
}
}
});
flowchart TD
A[客服/设计师填写表单] --> B{表单验证}
B -->|验证失败| C[显示错误提示]
C --> A
B -->|验证成功| D[调用 createProductOrder]
D --> E[整合表单数据]
E --> F[整合产品数据]
F --> G[整合报价数据]
G --> H[整合设计师分配数据]
H --> I[调用 ProjectService.createProject]
I --> J{API响应}
J -->|成功| K[显示成功提示]
K --> L[推进到需求沟通阶段]
L --> M[展开需求沟通面板]
M --> N[滚动到需求沟通区域]
J -->|失败| O[显示错误信息]
关键方法实现:
// project-detail.ts lines 4783-4808
createProductOrder(): void {
if (!this.canCreateOrder()) {
// 标记所有字段为已触摸,以显示验证错误
this.orderCreationForm.markAllAsTouched();
return;
}
const orderData = {
...this.orderCreationForm.value,
...this.optionalForm.value,
customerInfo: this.orderCreationData?.customerInfo,
productData: {
products: this.projectProducts,
productConfiguration: this.productConfiguration,
productDependencies: this.productDependencies
},
quotationData: this.quotationData,
designerAssignment: this.designerAssignmentData,
orderInfo: {
orderNumber: this.orderNumber,
orderTime: this.orderTime,
pricingStrategy: this.pricingStrategy
}
};
console.log('创建产品订单:', orderData);
// 调用 ProjectService 创建项目
this.projectService.createProject(orderData).subscribe({
next: (result) => {
if (result.success) {
alert('订单分配成功!');
// 订单分配成功后自动切换到下一环节
this.advanceToNextStage('订单分配');
}
},
error: (error) => {
console.error('订单分配失败:', error);
alert('订单分配失败,请重试');
}
});
}
flowchart TD
A[客户需求] --> B[AI产品识别]
B --> C[生成产品配置]
C --> D[确认产品列表]
D --> E{是否多产品?}
E -->|是| F[产品依赖分析]
E -->|否| G[单产品配置]
F --> H[跨产品协调配置]
G --> I[报价计算]
H --> I
I --> J[设计师分配]
J --> K[创建项目]
K --> L[推进到需求阶段]
接口地址:POST /api/projects
请求参数:
interface CreateProductProjectRequest {
// 客户信息
customerId: string;
customerName: string;
customerPhone: string;
customerWechat?: string;
customerType: string;
customerSource: string;
customerRemark?: string;
// 订单信息
orderNumber: string;
orderTime: Date;
orderAmount: number;
smallImageDeliveryTime: Date;
largeImageDeliveryTime?: Date;
decorationType: string;
requirementReason: string;
isMultiDesigner: boolean;
projectComplexity?: string;
designStyle?: string;
specialRequirements?: string;
// 产品数据
productData: {
products: ProductConfig[];
productConfiguration: ProductConfiguration;
productDependencies: ProductDependency[];
productCount: number;
totalArea: number;
totalBudget: number;
designComplexity: string;
};
// 报价明细
quotation: {
products: ProductQuotationItem[];
totalAmount: number;
materialCost: number;
laborCost: number;
designFee: number;
modelingFee: number;
renderingFee: number;
softDecorFee: number;
managementFee: number;
discounts: DiscountItem[];
paymentSchedule: PaymentSchedule[];
pricingStrategy: PricingStrategy;
};
// 设计师分配
assignment: {
assignments: ProductDesignerAssignment[];
teamId: string;
leaderId: string;
assignmentDate: Date;
expectedStartDate: Date;
batchAssignmentMode: boolean;
assignmentStrategy: AssignmentStrategy;
};
// 偏好标签
preferenceTags?: string[];
}
响应数据:
interface CreateProductProjectResponse {
success: boolean;
message: string;
projectId: string;
project: {
id: string;
name: string;
currentStage: ProjectStage;
createdAt: Date;
assignedProducts: string[];
productCount: number;
};
}
接口地址:POST /api/products/identify
请求参数:
interface IdentifyProductsRequest {
description: string;
totalArea?: number;
budget?: number;
projectType?: string;
existingProducts?: ProductType[];
}
interface IdentifyProductsResponse {
success: boolean;
result: ProductIdentificationResult;
recommendedProducts: ProductConfig[];
suggestions: string[];
}
接口地址:POST /api/products/quotation
请求参数:
interface CalculateProductQuotationRequest {
products: ProductConfig[];
options: QuotationOptions;
strategy?: PricingStrategy;
}
interface CalculateProductQuotationResponse {
success: boolean;
quotation: MultiProductQuotation;
recommendations: string[];
}
文档版本:v2.0 (基于Project表产品设计管理) 创建日期:2025-10-20 最后更新:2025-10-20 维护人:产品团队