映三色设计师项目管理系统采用Project表统一管理空间设计产品。每个Project代表一个独立的空间设计产品,支持从单空间到复杂多空间项目的灵活管理。通过Product表的统一架构,实现设计产品化的项目管理模式。
当前系统采用Project表的统一管理架构:
interface Project {
// 产品基础信息
id: string;
name: string; // 产品名称:"李总主卧设计"
type: string; // 产品类型:"bedroom", "living_room" 等
status: 'not_started' | 'in_progress' | 'awaiting_review' | 'completed';
priority: number; // 优先级 1-10
complexity: 'simple' | 'medium' | 'complex';
// 空间属性(产品设计维度)
area?: number; // 空间面积
metadata?: {
dimensions?: { // 空间尺寸
length: number;
width: number;
height: number;
};
features?: string[]; // 空间特征:["朝南", "飘窗", "独立卫浴"]
constraints?: string[]; // 约束条件:["承重墙不可动"]
style?: string; // 设计风格
specialRequirements?: string[]; // 特殊要求
};
// 产品需求字段
requirements?: {
colorRequirement: Object; // 色彩需求
spaceStructureRequirement: Object; // 空间结构需求
materialRequirement: Object; // 材质需求
lightingRequirement: Object; // 灯光需求
specificRequirements?: string[]; // 特定需求
};
// 产品报价字段
quotation?: {
price: number; // 产品价格
currency: string; // "CNY"
breakdown: {
design: number; // 设计费
modeling: number; // 建模费
rendering: number; // 渲染费
softDecor: number; // 软装费
};
status: string; // "pending" | "approved"
validUntil: Date;
};
// 产品评价字段
reviews?: Array<Object>;
// 关联信息
projectId: string; // 所属主项目ID
designerId?: string; // 负责设计师ID
estimatedBudget?: number; // 预估预算
estimatedDuration?: number; // 预估工期
order: number; // 排序顺序
// 时间信息
createdAt: Date;
updatedAt: Date;
}
enum ProductType {
LIVING_ROOM = 'living_room', // 客厅
BEDROOM = 'bedroom', // 卧室
KITCHEN = 'kitchen', // 厨房
BATHROOM = 'bathroom', // 卫生间
DINING_ROOM = 'dining_room', // 餐厅
STUDY = 'study', // 书房
BALCONY = 'balcony', // 阳台
CORRIDOR = 'corridor', // 走廊
STORAGE = 'storage', // 储物间
ENTRANCE = 'entrance', // 玄关
OTHER = 'other' // 其他
}
interface ProductProgress {
productId: string; // 关联产品ID
stage: ProjectStage; // 当前阶段
progress: number; // 进度百分比 0-100
status: ProgressStatus;
timeline: StageTimeline[]; // 各阶段时间线
blockers?: string[]; // 阻碍因素
estimatedCompletion?: Date; // 预计完成时间
actualCompletion?: Date; // 实际完成时间
}
interface StageTimeline {
stage: ProjectStage;
startTime?: Date;
endTime?: Date;
duration?: number; // 持续时间(小时)
status: 'not_started' | 'in_progress' | 'completed' | 'blocked';
assignee?: string; // 负责人ID
deliverables?: string[]; // 交付物
quality?: {
score: number; // 质量评分
issues: string[]; // 问题点
};
}
enum ProgressStatus {
NOT_STARTED = 'not_started',
IN_PROGRESS = 'in_progress',
AWAITING_REVIEW = 'awaiting_review',
COMPLETED = 'completed',
BLOCKED = 'blocked',
DELAYED = 'delayed'
}
interface ProductAssignment {
productId: string; // 产品ID
stage: ProjectStage; // 阶段
assigneeId: string; // 负责人ID
assigneeName: string; // 负责人姓名
role: AssignmentRole; // 分配角色
assignedAt: Date; // 分配时间
assignedBy: string; // 分配人ID
status: 'active' | 'completed' | 'reassigned';
workload: number; // 工作量占比 0-1
notes?: string; // 分配备注
performance?: {
efficiency: number; // 工作效率
quality: number; // 工作质量
collaboration: number; // 协作能力
};
}
enum AssignmentRole {
PRIMARY_DESIGNER = 'primary_designer', // 主设计师
MODELING_DESIGNER = 'modeling_designer', // 建模师
RENDERING_DESIGNER = 'rendering_designer',// 渲染师
SOFT_DECOR_DESIGNER = 'soft_decor_designer', // 软装师
QUALITY_REVIEWER = 'quality_reviewer' // 质量审核员
}
// 扩展 主项目Project 数据结构
interface MultiProductProject {
id: string;
title: string;
status: string;
// 产品管理
products: Project[]; // 产品列表
// 全局报价信息
quotation: {
totalAmount: number; // 总金额
currency: string; // 货币单位
// 按产品分项报价
productQuotations: ProductQuotation[];
// 按费用类型汇总
breakdown: {
design: number; // 设计费
modeling: number; // 建模费
rendering: number; // 渲染费
softDecor: number; // 软装费
postProcess: number; // 后期费
};
// 折扣信息
discount?: {
type: 'percentage' | 'fixed';
value: number;
reason: string;
};
};
// 项目协调信息
coordination: {
dependencies: ProductDependency[]; // 产品依赖关系
batchOperations: BatchOperation[]; // 批量操作
qualityStandards: QualityStandard[]; // 质量标准
};
}
interface ProductQuotation {
productId: string; // 产品ID
productName: string; // 产品名称
productType: ProductType; // 产品类型
amount: number; // 该产品金额
items: QuotationItem[]; // 报价项明细
priority: number; // 优先级
estimatedDays: number; // 预估工期
notes?: string; // 备注
}
interface ProductDependency {
fromProduct: string; // 源产品ID
toProduct: string; // 目标产品ID
type: 'style_reference' | 'color_flow' | 'material_matching' | 'size_reference' | 'functional_connection';
description: string; // 依赖描述
status: 'pending' | 'satisfied' | 'blocked';
priority: number; // 依赖优先级
}
// 扩展 ProjectRequirement 数据结构
interface MultiProductRequirement {
products: ProductRequirement[]; // 产品需求列表
globalRequirements: GlobalRequirements; // 全局需求
crossProductRequirements: CrossProductRequirement[]; // 跨产品需求
}
interface ProductRequirement {
productId: string; // 产品ID
productName: string; // 产品名称
productType: ProductType; // 产品类型
// 四大核心需求数据
colorRequirement: ColorAtmosphereRequirement;
spaceStructureRequirement: SpaceStructureRequirement;
materialRequirement: MaterialRequirement;
lightingRequirement: LightingRequirement;
// 产品特定需求
specificRequirements: {
functional?: string[]; // 功能需求:收纳、展示等
style?: string[]; // 风格偏好
constraints?: string[]; // 限制条件:承重、管道等
specialFeatures?: string[]; // 特殊功能:智能家居、无障碍设计等
};
// 参考资料管理
referenceImages?: string[]; // 参考图片
referenceFiles?: any[]; // 参考文件
priority: number; // 优先级
complexity: 'simple' | 'medium' | 'complex'; // 复杂度
}
interface GlobalRequirements {
overallStyle: string; // 整体风格
budget: {
total: number;
currency: string;
breakdown?: Record<string, number>;
};
timeline: {
preferredStartDate?: Date;
deadline: Date;
milestones?: Array<{
date: Date;
description: string;
}>;
};
familyComposition: string; // 家庭构成
lifestyle: string[]; // 生活习惯
qualityStandards: string[]; // 质量标准
}
interface CrossProductRequirement {
type: 'style_consistency' | 'color_flow' | 'material_matching' | 'traffic_flow' | 'functional_connection';
description: string; // 跨产品需求描述
involvedProducts: string[]; // 涉及的产品ID列表
priority: number; // 优先级
impact: 'high' | 'medium' | 'low'; // 影响程度
}
// 扩展现有的 deliveryProcesses 数据结构
interface MultiProductDeliveryProcess {
processId: string; // 流程ID:modeling、softDecor、rendering、postProcess
processName: string; // 流程名称
// 产品管理(增强版)
products: DeliveryProduct[];
// 按产品组织的内容
content: Record<string, ProductContent>;
// 跨产品协调
crossProductCoordination: {
dependencies: ProductDependency[]; // 产品依赖关系
batchOperations: BatchOperation[]; // 批量操作
qualityStandards: QualityStandard[]; // 质量标准
designReviews: DesignReview[]; // 设计评审
};
// 整体进度管理
overallProgress: {
total: number; // 总体进度
byProduct: Record<string, number>; // 各产品进度
byStage: Record<string, number>; // 各阶段进度
estimatedCompletion: Date;
riskIndicators: RiskIndicator[]; // 风险指标
};
}
interface DeliveryProduct {
productId: string; // 产品ID
productName: string; // 产品名称
productType: ProductType; // 产品类型
priority: number; // 优先级
status: string; // 状态
// 交付内容
deliverables: {
concepts: ConceptDesign[]; // 概念设计
models: ThreeDModel[]; // 三维模型
renderings: Rendering[]; // 渲染图
documents: TechnicalDocument[]; // 技术文档
};
// 质量控制
quality: {
designReview: DesignReviewResult; // 设计评审结果
technicalValidation: ValidationResult; // 技术验证结果
clientFeedback: FeedbackRecord[]; // 客户反馈
};
}
interface DesignReview {
id: string;
type: 'internal' | 'client' | 'technical';
participants: string[]; // 参与者ID
scheduledDate: Date; // 计划日期
duration: number; // 评审时长(分钟)
status: 'scheduled' | 'in_progress' | 'completed' | 'cancelled';
// 评审内容
criteria: ReviewCriteria[]; // 评审标准
findings: ReviewFinding[]; // 评审发现
decisions: ReviewDecision[]; // 评审决策
actionItems: ActionItem[]; // 行动项
// 评审结果
overallScore: number; // 整体评分
approved: boolean; // 是否通过
nextReviewDate?: Date; // 下次评审日期
}
interface BatchOperation {
id: string;
type: 'style_sync' | 'color_adjustment' | 'material_update' | 'quality_standard_apply';
targetProducts: string[]; // 目标产品列表
operation: any; // 具体操作内容
status: 'pending' | 'in_progress' | 'completed' | 'failed';
createdBy: string;
createdAt: Date;
completedAt?: Date;
results?: OperationResult[]; // 操作结果
}
// 扩展售后数据结构
interface MultiProductAfterCare {
productReviews: ProductReview[]; // 各产品评价
crossProductAnalysis: CrossProductAnalysis; // 跨产品分析
productComparison: ProductComparison[]; // 产品对比
projectSummary: ProjectSummary; // 项目总结
}
interface ProductReview {
productId: string; // 产品ID
productName: string; // 产品名称
productType: ProductType; // 产品类型
// 客户评价
customerRating: {
overall: number; // 整体评分 1-5
aspects: {
design: number; // 设计评分
functionality: number; // 功能性评分
aesthetics: number; // 美观度评分
practicality: number; // 实用性评分
value: number; // 性价比评分
};
feedback: string; // 具体反馈
suggestions: string[]; // 改进建议
};
// 使用情况跟踪
usageMetrics: {
satisfaction: number; // 满意度 0-100
usageFrequency: string; // 使用频率
adaptations: string[]; // 后续改动
issues: string[]; // 问题记录
maintenance: MaintenanceRecord[]; // 维护记录
};
// 经济价值分析
economicValue: {
costPerProduct: number; // 单产品成本
perceivedValue: number; // 感知价值
roi: number; // 投资回报率
marketComparison: MarketComparison; // 市场对比
};
// 专业评价
professionalReview: {
technicalQuality: number; // 技术质量
innovation: number; // 创新性
sustainability: number; // 可持续性
scalability: number; // 可扩展性
};
}
interface CrossProductAnalysis {
styleConsistency: {
score: number; // 风格一致性评分 0-100
issues: string[]; // 不一致问题
improvements: string[]; // 改进建议
};
functionalFlow: {
score: number; // 功能流线评分
bottlenecks: string[]; // 瓶颈问题
optimizations: string[]; // 优化建议
};
spaceUtilization: {
efficiency: number; // 空间利用率
recommendations: string[]; // 优化建议
};
designCohesion: {
overallScore: number; // 设计凝聚力评分
thematicUnity: number; // 主题统一性
transitionQuality: number; // 过渡质量
};
}
interface ProjectSummary {
overallSuccess: {
score: number; // 项目成功度评分
achievements: string[]; // 主要成就
challenges: string[]; // 面临挑战
lessons: string[]; // 经验教训
};
businessMetrics: {
totalRevenue: number; // 总收入
totalCost: number; // 总成本
profitMargin: number; // 利润率
clientSatisfaction: number; // 客户满意度
referralPotential: number; // 推荐潜力
};
teamPerformance: {
collaborationScore: number; // 协作评分
efficiencyScore: number; // 效率评分
qualityScore: number; // 质量评分
innovationScore: number; // 创新评分
};
futureOpportunities: {
followUpProjects: string[]; // 后续项目机会
serviceExtensions: string[]; // 服务扩展机会
portfolioAdditions: string[]; // 作品集补充
};
}
graph TD
A[客服接收需求] --> B{是否多产品项目?}
B -->|否| C[创建单产品项目]
B -->|是| D[分析产品需求]
D --> E[识别潜在产品]
E --> F[创建产品列表]
F --> G[设置产品优先级]
G --> H[分配产品ID]
H --> I[进入订单分配阶段]
style C fill:#e8f5e9
style I fill:#e3f2fd
class ProductIdentifier {
// 基于关键词识别产品
identifyProductsFromDescription(description: string): ProductIdentificationResult {
const result: ProductIdentificationResult = {
identifiedProducts: [],
confidence: 0,
reasoning: '',
suggestedQuestions: [],
recommendedProductTypes: []
};
// 产品类型关键词映射
const productKeywords = {
[ProductType.LIVING_ROOM]: ['客厅', '起居室', '会客厅', '茶室', '待客区'],
[ProductType.BEDROOM]: ['卧室', '主卧', '次卧', '儿童房', '老人房', '客房', '主人房'],
[ProductType.KITCHEN]: ['厨房', '开放式厨房', '中西厨', '餐厨一体'],
[ProductType.BATHROOM]: ['卫生间', '浴室', '洗手间', '盥洗室', '主卫', '次卫'],
[ProductType.DINING_ROOM]: ['餐厅', '餐厅区', '用餐区', '就餐空间'],
[ProductType.STUDY]: ['书房', '工作室', '办公室', '学习区', '阅读区'],
[ProductType.BALCONY]: ['阳台', '露台', '花园阳台', '休闲阳台'],
[ProductType.CORRIDOR]: ['走廊', '过道', '玄关', '门厅', '入户'],
[ProductType.STORAGE]: ['储物间', '衣帽间', '杂物间', '收纳空间']
};
// 分析描述中的产品关键词
const foundProducts: Array<{ type: ProductType; keywords: string[]; confidence: number }> = [];
for (const [productType, keywords] of Object.entries(productKeywords)) {
const matchedKeywords = keywords.filter(keyword =>
description.toLowerCase().includes(keyword.toLowerCase())
);
if (matchedKeywords.length > 0) {
foundProducts.push({
type: productType as ProductType,
keywords: matchedKeywords,
confidence: matchedKeywords.length / keywords.length
});
}
}
// 按置信度排序
foundProducts.sort((a, b) => b.confidence - a.confidence);
// 构建识别结果
result.identifiedProducts = foundProducts.map(fp => ({
type: fp.type,
productName: this.getDefaultProductName(fp.type),
priority: this.calculateProductPriority(fp.type, fp.confidence),
confidence: fp.confidence,
identifiedKeywords: fp.keywords,
estimatedArea: this.estimateProductArea(fp.type),
suggestedComplexity: this.suggestProductComplexity(fp.type, description)
}));
// 计算整体置信度
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.recommendedProductTypes = this.recommendRelatedProductTypes(foundProducts);
return result;
}
// 基于面积和预算推断产品数量
estimateProductCount(totalArea: number, budget: number): ProductEstimationResult {
const result: ProductEstimationResult = {
estimatedProductCount: 1,
confidence: 0.5,
reasoning: '',
possibleProductTypes: [],
recommendedAllocation: {}
};
// 基于面积的产品数量估算
const areaBasedCount = Math.max(1, Math.floor(totalArea / 20)); // 每20平米一个主要产品
// 基于预算的产品数量估算
const budgetBasedCount = Math.max(1, Math.floor(budget / 30000)); // 每3万一个产品
// 综合判断
const finalCount = Math.min(areaBasedCount, budgetBasedCount);
result.estimatedProductCount = finalCount;
// 推断可能的产品类型
result.possibleProductTypes = this.inferPossibleProductTypes(totalArea, budget);
// 推荐预算分配
result.recommendedAllocation = this.calculateBudgetAllocation(
result.possibleProductTypes,
budget
);
// 生成推理
result.reasoning = `基于面积${totalArea}平米和预算${budget}元,估算需要${finalCount}个主要产品设计产品`;
return result;
}
}
class MultiProductPricingCalculator {
calculateProductPricing(
products: Project[],
globalRequirements: GlobalRequirements,
pricingRules: PricingRule[]
): MultiProductQuotation {
const productQuotations: ProductQuotation[] = [];
let totalAmount = 0;
for (const product of products) {
const productQuotation = this.calculateSingleProductPricing(product, globalRequirements, pricingRules);
productQuotations.push(productQuotation);
totalAmount += productQuotation.amount;
}
// 应用多产品折扣
const discount = this.calculateMultiProductDiscount(products.length, totalAmount);
const finalAmount = totalAmount - discount.value;
return {
projectId: this.getProjectId(),
quotationDate: new Date(),
currency: 'CNY',
productQuotations,
totalBaseAmount: totalAmount,
discountAmount: discount.value,
finalAmount,
breakdown: this.calculateBreakdown(productQuotations),
discount: discount.value > 0 ? discount : undefined
};
}
private calculateSingleProductPricing(
product: Project,
globalRequirements: GlobalRequirements,
pricingRules: PricingRule[]
): ProductQuotation {
const basePrice = this.getBasePriceForProductType(product.type, product.area || 0);
const complexityMultiplier = this.getComplexityMultiplier(product.complexity);
const priorityAdjustment = this.getPriorityAdjustment(product.priority);
const styleFactor = this.getStyleFactor(globalRequirements.overallStyle);
const items: QuotationItem[] = [
{
id: `${product.id}_design`,
category: 'design',
description: '概念设计费',
quantity: 1,
unitPrice: basePrice * 0.25 * complexityMultiplier * styleFactor,
totalPrice: basePrice * 0.25 * complexityMultiplier * styleFactor
},
{
id: `${product.id}_modeling`,
category: 'modeling',
description: '三维建模费',
quantity: 1,
unitPrice: basePrice * 0.30 * complexityMultiplier,
totalPrice: basePrice * 0.30 * complexityMultiplier
},
{
id: `${product.id}_rendering`,
category: 'rendering',
description: '效果图渲染费',
quantity: 1,
unitPrice: basePrice * 0.25 * complexityMultiplier,
totalPrice: basePrice * 0.25 * complexityMultiplier
},
{
id: `${product.id}_soft_decor`,
category: 'soft_decor',
description: '软装设计费',
quantity: 1,
unitPrice: basePrice * 0.15 * complexityMultiplier,
totalPrice: basePrice * 0.15 * complexityMultiplier
},
{
id: `${product.id}_technical`,
category: 'technical',
description: '技术文档费',
quantity: 1,
unitPrice: basePrice * 0.05 * complexityMultiplier,
totalPrice: basePrice * 0.05 * complexityMultiplier
}
];
const totalAmount = items.reduce((sum, item) => sum + item.totalPrice, 0);
return {
productId: product.id,
productName: product.name,
productType: product.type as ProductType,
amount: totalAmount * priorityAdjustment,
items,
priority: product.priority,
estimatedDays: this.calculateEstimatedDays(product, totalAmount),
notes: `复杂度系数: ${complexityMultiplier}, 优先级调整: ${priorityAdjustment}, 风格系数: ${styleFactor}`
};
}
private calculateMultiProductDiscount(productCount: number, totalAmount: number): { type: string; value: number; reason: string } {
if (productCount >= 5) {
return {
type: 'percentage',
value: totalAmount * 0.12, // 12% 折扣
reason: '5产品以上项目享受12%折扣'
};
} else if (productCount >= 3) {
return {
type: 'percentage',
value: totalAmount * 0.08, // 8% 折扣
reason: '3-4产品项目享受8%折扣'
};
} else if (productCount >= 2) {
return {
type: 'percentage',
value: totalAmount * 0.05, // 5% 折扣
reason: '双产品项目享受5%折扣'
};
} else if (totalAmount > 200000) {
return {
type: 'fixed',
value: 5000,
reason: '高额度项目固定优惠5000元'
};
}
return { type: 'percentage', value: 0, reason: '无折扣' };
}
}
class MultiProductRequirementCollector {
async collectRequirements(
products: Project[],
globalRequirements: GlobalRequirements
): Promise<MultiProductRequirement> {
const productRequirements: ProductRequirement[] = [];
// 1. 并行采集各产品需求
const requirementPromises = products.map(product =>
this.collectProductRequirements(product, globalRequirements)
);
const collectedRequirements = await Promise.all(requirementPromises);
productRequirements.push(...collectedRequirements);
// 2. 分析跨产品需求
const crossProductRequirements = await this.analyzeCrossProductRequirements(productRequirements);
// 3. 验证需求一致性
await this.validateRequirementConsistency(productRequirements, crossProductRequirements);
return {
products: productRequirements,
globalRequirements,
crossProductRequirements
};
}
private async collectProductRequirements(
product: Project,
globalRequirements: GlobalRequirements
): Promise<ProductRequirement> {
// 基于产品类型预填充需求模板
const template = this.getProductRequirementTemplate(product.type);
// 采集四大核心需求
const colorRequirement = await this.collectColorRequirement(product, template.colorTemplate);
const spaceStructureRequirement = await this.collectSpaceStructureRequirement(product, template.structureTemplate);
const materialRequirement = await this.collectMaterialRequirement(product, template.materialTemplate);
const lightingRequirement = await this.collectLightingRequirement(product, template.lightingTemplate);
// 采集产品特定需求
const specificRequirements = await this.collectSpecificRequirements(product, template.specificTemplate);
// 管理参考资料
const referenceImages = await this.collectReferenceImages(product);
const referenceFiles = await this.collectReferenceFiles(product);
return {
productId: product.id,
productName: product.name,
productType: product.type as ProductType,
colorRequirement,
spaceStructureRequirement,
materialRequirement,
lightingRequirement,
specificRequirements,
referenceImages,
referenceFiles,
priority: product.priority,
complexity: this.assessProductComplexity(product, specificRequirements)
};
}
private async analyzeCrossProductRequirements(
productRequirements: ProductRequirement[]
): Promise<CrossProductRequirement[]> {
const crossProductRequirements: CrossProductRequirement[] = [];
// 分析风格一致性需求
const styleRequirement = this.analyzeStyleConsistency(productRequirements);
if (styleRequirement) crossProductRequirements.push(styleRequirement);
// 分析色彩流线需求
const colorFlowRequirement = this.analyzeColorFlow(productRequirements);
if (colorFlowRequirement) crossProductRequirements.push(colorFlowRequirement);
// 分析材质匹配需求
const materialMatchingRequirement = this.analyzeMaterialMatching(productRequirements);
if (materialMatchingRequirement) crossProductRequirements.push(materialMatchingRequirement);
// 分析功能连接需求
const functionalConnectionRequirement = this.analyzeFunctionalConnections(productRequirements);
if (functionalConnectionRequirement) crossProductRequirements.push(functionalConnectionRequirement);
return crossProductRequirements;
}
}
class ProductDependencyManager {
analyzeProductDependencies(products: Project[]): ProductDependency[] {
const dependencies: ProductDependency[] = [];
// 分析风格参考依赖
const styleDependencies = this.analyzeStyleDependencies(products);
dependencies.push(...styleDependencies);
// 分析色彩流线依赖
const colorDependencies = this.analyzeColorDependencies(products);
dependencies.push(...colorDependencies);
// 分析尺寸参考依赖
const sizeDependencies = this.analyzeSizeDependencies(products);
dependencies.push(...sizeDependencies);
// 分析功能连接依赖
const functionalDependencies = this.analyzeFunctionalDependencies(products);
dependencies.push(...functionalDependencies);
return dependencies;
}
private analyzeStyleDependencies(products: Project[]): ProductDependency[] {
const dependencies: ProductDependency[] = [];
const livingRoom = products.find(p => p.type === ProductType.LIVING_ROOM);
if (livingRoom) {
// 客厅通常是风格参考基准
const otherProducts = products.filter(p => p.id !== livingRoom.id);
for (const product of otherProducts) {
dependencies.push({
fromProduct: livingRoom.id,
toProduct: product.id,
type: 'style_reference',
description: `${product.name}需要与客厅风格保持一致`,
status: 'pending',
priority: this.calculateDependencyPriority(livingRoom, product)
});
}
}
return dependencies;
}
async resolveDependency(dependency: ProductDependency): Promise<boolean> {
switch (dependency.type) {
case 'style_reference':
return await this.resolveStyleDependency(dependency);
case 'color_flow':
return await this.resolveColorDependency(dependency);
case 'material_matching':
return await this.resolveMaterialDependency(dependency);
case 'size_reference':
return await this.resolveSizeDependency(dependency);
case 'functional_connection':
return await this.resolveFunctionalDependency(dependency);
default:
return false;
}
}
private async resolveStyleDependency(dependency: ProductDependency): Promise<boolean> {
// 实现风格依赖解决逻辑
// 1. 获取源产品的设计方案
// 2. 提取关键风格元素
// 3. 应用到目标产品
// 4. 验证一致性
console.log(`解决风格依赖: ${dependency.fromProduct} -> ${dependency.toProduct}`);
dependency.status = 'satisfied';
return true;
}
}
class ProductBatchOperationManager {
async executeBatchOperation(operation: BatchOperation): Promise<boolean> {
try {
operation.status = 'in_progress';
switch (operation.type) {
case 'style_sync':
return await this.executeStyleSync(operation);
case 'color_adjustment':
return await this.executeColorAdjustment(operation);
case 'material_update':
return await this.executeMaterialUpdate(operation);
case 'quality_standard_apply':
return await this.executeQualityStandardApply(operation);
default:
throw new Error(`未知的批量操作类型: ${operation.type}`);
}
} catch (error) {
console.error(`批量操作失败:`, error);
operation.status = 'failed';
return false;
}
}
private async executeStyleSync(operation: BatchOperation): Promise<boolean> {
const { targetProducts, operation: syncData } = operation;
// 获取风格同步数据
const sourceStyle = syncData.sourceStyle;
const styleElements = syncData.elements;
const results: OperationResult[] = [];
// 批量应用到目标产品
for (const productId of targetProducts) {
try {
await this.applyStyleToProduct(productId, sourceStyle, styleElements);
results.push({
productId,
success: true,
message: '风格同步成功'
});
} catch (error) {
results.push({
productId,
success: false,
message: `风格同步失败: ${error.message}`
});
}
}
operation.results = results;
operation.status = 'completed';
operation.completedAt = new Date();
return results.every(r => r.success);
}
private async executeQualityStandardApply(operation: BatchOperation): Promise<boolean> {
const { targetProducts, operation: standardData } = operation;
// 获取质量标准数据
const qualityStandard = standardData.qualityStandard;
const applicableProductTypes = standardData.applicableProductTypes;
const results: OperationResult[] = [];
// 批量应用质量标准
for (const productId of targetProducts) {
try {
// 验证产品类型是否适用
const product = await this.getProductById(productId);
if (applicableProductTypes.includes(product.type)) {
await this.applyQualityStandard(productId, qualityStandard);
results.push({
productId,
success: true,
message: '质量标准应用成功'
});
} else {
results.push({
productId,
success: false,
message: '产品类型不适用该质量标准'
});
}
} catch (error) {
results.push({
productId,
success: false,
message: `质量标准应用失败: ${error.message}`
});
}
}
operation.results = results;
operation.status = 'completed';
operation.completedAt = new Date();
return results.every(r => r.success);
}
}
<!-- 产品概览界面 -->
<div class="product-overview-container">
<!-- 全局信息栏 -->
<div class="global-info-bar">
<div class="project-info">
<h3>{{ projectTitle }}</h3>
<span class="product-count">{{ products.length }}个产品</span>
<span class="total-budget">总预算: ¥{{ totalBudget.toLocaleString() }}</span>
<span class="project-type">{{ isMultiProductProject ? '多产品项目' : '单产品项目' }}</span>
</div>
<div class="overall-progress">
<div class="progress-circle">
<svg width="120" height="120">
<circle cx="60" cy="60" r="50" fill="none" stroke="#e0e0e0" stroke-width="8"/>
<circle cx="60" cy="60" r="50" fill="none" stroke="#4CAF50" stroke-width="8"
[attr.stroke-dasharray]="circumference"
[attr.stroke-dashoffset]="progressOffset"/>
</svg>
<div class="progress-text">
<span class="percentage">{{ overallProgress }}%</span>
<span class="label">总体进度</span>
</div>
</div>
</div>
</div>
<!-- 产品卡片网格 -->
<div class="products-grid">
@for (product of products; track product.id) {
<div class="product-card"
[class.priority-high]="product.priority >= 8"
[class.priority-medium]="product.priority >= 5 && product.priority < 8"
[class.status-completed]="product.status === 'completed'"
[class.status-in-progress]="product.status === 'in_progress'">
<!-- 产品头部 -->
<div class="product-header">
<div class="product-icon">
<i class="icon-{{ getProductIcon(product.type) }}"></i>
</div>
<div class="product-info">
<h4>{{ product.name }}</h4>
<span class="product-type">{{ getProductTypeName(product.type) }}</span>
@if (product.area) {
<span class="product-area">{{ product.area }}m²</span>
}
</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)="viewProductDetails(product.id)" title="查看详情">
<i class="icon-view"></i>
</button>
</div>
</div>
<!-- 产品进度 -->
<div class="product-progress">
<div class="progress-bar">
<div class="progress-fill"
[style.width.%]="getProductProgress(product.id)"
[class.color-warning]="getProductProgress(product.id) < 50"
[class.color-success]="getProductProgress(product.id) >= 80">
</div>
</div>
<span class="progress-text">{{ getProductProgress(product.id) }}%</span>
</div>
<!-- 当前阶段 -->
<div class="current-stage">
<span class="stage-label">当前阶段:</span>
<span class="stage-value">{{ getCurrentStage(product.id) }}</span>
</div>
<!-- 负责人 -->
<div class="assignee-info">
@if (getProductAssignee(product.id)) {
<div class="assignee-avatar">
<img [src]="getProductAssignee(product.id).avatar" [alt]="getProductAssignee(product.id).name">
</div>
<span class="assignee-name">{{ getProductAssignee(product.id).name }}</span>
} @else {
<span class="no-assignee">未分配</span>
}
</div>
<!-- 产品状态标签 -->
<div class="product-tags">
@if (product.priority >= 8) {
<span class="tag tag-high">高优先级</span>
}
@if (product.complexity === 'complex') {
<span class="tag tag-complex">复杂</span>
}
@if (hasCrossProductDependencies(product.id)) {
<span class="tag tag-dependency">依赖</span>
}
@if (product.estimatedBudget) {
<span class="tag tag-budget">¥{{ product.estimatedBudget.toLocaleString() }}</span>
}
</div>
<!-- 快速操作 -->
<div class="quick-actions">
<button class="btn-small"
[disabled]="!canAdvanceStage(product.id)"
(click)="advanceProductStage(product.id)">
推进阶段
</button>
<button class="btn-small btn-secondary"
(click)="viewProductFiles(product.id)">
查看文件
</button>
<button class="btn-small btn-secondary"
(click)="viewProductRequirements(product.id)">
查看需求
</button>
</div>
</div>
}
<!-- 添加新产品卡片 -->
<div class="product-card add-product-card" (click)="showAddProductDialog = true">
<div class="add-product-content">
<i class="icon-plus"></i>
<span>添加产品</span>
</div>
</div>
</div>
<!-- 产品协调面板 -->
@if (isMultiProductProject) {
<div class="product-coordination-panel">
<h3>产品协调管理</h3>
<!-- 依赖关系管理 -->
<div class="dependencies-section">
<h4>依赖关系</h4>
@for (dependency of productDependencies; track dependency.id) {
<div class="dependency-item"
[class.status-pending]="dependency.status === 'pending'"
[class.status-satisfied]="dependency.status === 'satisfied'"
[class.status-blocked]="dependency.status === 'blocked'">
<div class="dependency-info">
<span class="dependency-from">{{ getProductName(dependency.fromProduct) }}</span>
<i class="icon-arrow-right"></i>
<span class="dependency-to">{{ getProductName(dependency.toProduct) }}</span>
<span class="dependency-type">{{ getDependencyTypeName(dependency.type) }}</span>
</div>
<div class="dependency-actions">
@if (dependency.status === 'pending') {
<button class="btn-small" (click)="resolveDependency(dependency)">
解决依赖
</button>
}
</div>
</div>
}
</div>
<!-- 批量操作 -->
<div class="batch-operations-section">
<h4>批量操作</h4>
<div class="batch-actions">
<button class="btn-secondary" (click)="showBatchStyleSync = true">
<i class="icon-style"></i> 批量风格同步
</button>
<button class="btn-secondary" (click)="showBatchColorAdjustment = true">
<i class="icon-color"></i> 批量色彩调整
</button>
<button class="btn-secondary" (click)="showBatchQualityApply = true">
<i class="icon-quality"></i> 批量质量标准
</button>
</div>
</div>
</div>
}
</div>
<!-- 产品详情弹窗 -->
<div class="product-detail-modal" *ngIf="selectedProductId">
<div class="modal-overlay" (click)="closeProductDetails()"></div>
<div class="modal-content large">
<div class="modal-header">
<h3>{{ getProductName(selectedProductId) }} - 详细信息</h3>
<div class="header-actions">
<button class="btn-secondary" (click)="editProduct(selectedProductId)">
<i class="icon-edit"></i> 编辑产品
</button>
<button class="btn-secondary" (click)="exportProductReport(selectedProductId)">
<i class="icon-export"></i> 导出报告
</button>
<button class="btn-icon" (click)="closeProductDetails()">
<i class="icon-close"></i>
</button>
</div>
</div>
<div class="modal-body">
<!-- 标签页导航 -->
<div class="tab-navigation">
<button class="tab-btn"
[class.active]="activeTab === 'overview'"
(click)="activeTab = 'overview'">
概览
</button>
<button class="tab-btn"
[class.active]="activeTab === 'requirements'"
(click)="activeTab = 'requirements'">
需求
</button>
<button class="tab-btn"
[class.active]="activeTab === 'delivery'"
(click)="activeTab = 'delivery'">
交付
</button>
<button class="tab-btn"
[class.active]="activeTab === 'timeline'"
(click)="activeTab = 'timeline'">
时间线
</button>
<button class="tab-btn"
[class.active]="activeTab === 'dependencies'"
(click)="activeTab = 'dependencies'">
依赖关系
</button>
<button class="tab-btn"
[class.active]="activeTab === 'reviews'"
(click)="activeTab = 'reviews'">
评价
</button>
</div>
<!-- 标签页内容 -->
<div class="tab-content">
<!-- 概览标签页 -->
<div *ngIf="activeTab === 'overview'" class="overview-tab">
<div class="product-metadata">
<h4>产品信息</h4>
<div class="metadata-grid">
<div class="metadata-item">
<label>产品类型:</label>
<span>{{ getProductTypeName(getProduct(selectedProductId).type) }}</span>
</div>
<div class="metadata-item">
<label>面积:</label>
<span>{{ getProduct(selectedProductId).area }}m²</span>
</div>
<div class="metadata-item">
<label>优先级:</label>
<span class="priority-badge priority-{{ getProduct(selectedProductId).priority }}">
{{ getProduct(selectedProductId).priority }}
</span>
</div>
<div class="metadata-item">
<label>复杂度:</label>
<span>{{ getProduct(selectedProductId).complexity }}</span>
</div>
<div class="metadata-item">
<label>预估预算:</label>
<span>¥{{ (getProduct(selectedProductId).estimatedBudget || 0).toLocaleString() }}</span>
</div>
<div class="metadata-item">
<label>预估工期:</label>
<span>{{ getProduct(selectedProductId).estimatedDuration || 0 }}天</span>
</div>
</div>
</div>
<div class="product-progress-detail">
<h4>进度详情</h4>
<div class="progress-stages">
@for (stage of getAllStages(); track stage) {
<div class="stage-progress-item"
[class.completed]="isStageCompleted(selectedProductId, stage)"
[class.current]="isCurrentStage(selectedProductId, stage)">
<div class="stage-icon">
<i class="icon-{{ getStageIcon(stage) }}"></i>
</div>
<div class="stage-info">
<span class="stage-name">{{ stage }}</span>
<span class="stage-time">{{ getStageTime(selectedProductId, stage) }}</span>
</div>
<div class="stage-progress">
<div class="progress-bar small">
<div class="progress-fill"
[style.width.%]="getStageProgress(selectedProductId, stage)">
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
<!-- 需求标签页 -->
<div *ngIf="activeTab === 'requirements'" class="requirements-tab">
<app-product-requirements-view
[productId]="selectedProductId"
[readonly]="isReadOnly()">
</app-product-requirements-view>
</div>
<!-- 交付标签页 -->
<div *ngIf="activeTab === 'delivery'" class="delivery-tab">
<app-product-delivery-view
[productId]="selectedProductId"
[readonly]="isReadOnly()">
</app-product-delivery-view>
</div>
<!-- 时间线标签页 -->
<div *ngIf="activeTab === 'timeline'" class="timeline-tab">
<app-product-timeline-view
[productId]="selectedProductId">
</app-product-timeline-view>
</div>
<!-- 依赖关系标签页 -->
<div *ngIf="activeTab === 'dependencies'" class="dependencies-tab">
<app-product-dependencies-view
[productId]="selectedProductId">
</app-product-dependencies-view>
</div>
<!-- 评价标签页 -->
<div *ngIf="activeTab === 'reviews'" class="reviews-tab">
<app-product-reviews-view
[productId]="selectedProductId"
[readonly]="isReadOnly()">
</app-product-reviews-view>
</div>
</div>
</div>
</div>
</div>
<!-- 多产品文件浏览器 -->
<div class="multi-product-file-browser">
<!-- 产品选择器 -->
<div class="product-selector">
<div class="product-tabs">
@for (product of products; track product.id) {
<button class="product-tab"
[class.active]="selectedProductId === product.id"
[class.has-files]="getProductFileCount(product.id) > 0"
(click)="selectProduct(product.id)">
<div class="tab-content">
<i class="icon-{{ getProductIcon(product.type) }}"></i>
<span class="product-name">{{ product.name }}</span>
<span class="file-count" *ngIf="getProductFileCount(product.id) > 0">
{{ getProductFileCount(product.id) }}
</span>
</div>
</button>
}
</div>
<!-- 全选/批量操作 -->
<div class="batch-actions">
<label class="checkbox-label">
<input type="checkbox"
[(ngModel)]="selectAllProducts"
(change)="toggleSelectAllProducts()">
<span>全选产品</span>
</label>
@if (selectedProducts.length > 0) {
<div class="selected-actions">
<span class="selected-count">已选择 {{ selectedProducts.length }} 个产品</span>
<button class="btn-small" (click)="batchUploadFiles()">
批量上传
</button>
<button class="btn-small btn-secondary" (click)="batchDownloadFiles()">
批量下载
</button>
<button class="btn-small btn-secondary" (click)="batchSyncFiles()">
批量同步
</button>
</div>
}
</div>
</div>
<!-- 文件列表 -->
<div class="file-content-area">
@if (selectedProductId) {
<div class="product-file-view">
<!-- 当前产品信息 -->
<div class="current-product-header">
<div class="product-info">
<i class="icon-{{ getProductIcon(getProduct(selectedProductId).type) }}"></i>
<h4>{{ getProduct(selectedProductId).name }}</h4>
<span class="file-total">{{ getProductFileCount(selectedProductId) }} 个文件</span>
<span class="product-type">{{ getProductTypeName(getProduct(selectedProductId).type) }}</span>
</div>
<div class="view-options">
<div class="file-type-filter">
<select [(ngModel)]="fileTypeFilter" class="filter-select">
<option value="all">所有文件</option>
<option value="concept">概念设计</option>
<option value="model">三维模型</option>
<option value="rendering">渲染图</option>
<option value="document">技术文档</option>
<option value="reference">参考资料</option>
</select>
</div>
<div class="view-toggle">
<button class="btn-icon"
[class.active]="viewMode === 'grid'"
(click)="viewMode = 'grid'"
title="网格视图">
<i class="icon-grid"></i>
</button>
<button class="btn-icon"
[class.active]="viewMode === 'list'"
(click)="viewMode = 'list'"
title="列表视图">
<i class="icon-list"></i>
</button>
</div>
<button class="btn-primary" (click)="triggerFileUpload(selectedProductId)">
<i class="icon-upload"></i> 上传文件
</button>
</div>
</div>
<!-- 文件上传区域 -->
<div class="upload-zone"
[class.drag-over]="isDragOver"
(dragover)="isDragOver = true"
(dragleave)="isDragOver = false"
(drop)="handleFileDrop($event, selectedProductId)">
<div class="upload-prompt">
<i class="icon-upload"></i>
<p>拖拽文件到此处上传</p>
<p class="hint">或点击上传按钮选择文件</p>
<div class="supported-formats">
<span>支持格式:</span>
<span>JPG, PNG, PDF, DWG, SKP, 3DM, PSD</span>
</div>
</div>
</div>
<!-- 文件分类标签 -->
<div class="file-categories">
<button class="category-tab"
[class.active]="selectedCategory === 'all'"
(click)="selectedCategory = 'all'">
全部 ({{ getProductFileCount(selectedProductId) }})
</button>
<button class="category-tab"
[class.active]="selectedCategory === 'concept'"
(click)="selectedCategory = 'concept'">
概念设计 ({{ getFileCountByCategory(selectedProductId, 'concept') }})
</button>
<button class="category-tab"
[class.active]="selectedCategory === 'model'"
(click)="selectedCategory = 'model'">
三维模型 ({{ getFileCountByCategory(selectedProductId, 'model') }})
</button>
<button class="category-tab"
[class.active]="selectedCategory === 'rendering'"
(click)="selectedCategory = 'rendering'">
渲染图 ({{ getFileCountByCategory(selectedProductId, 'rendering') }})
</button>
<button class="category-tab"
[class.active]="selectedCategory === 'document'"
(click)="selectedCategory = 'document'">
技术文档 ({{ getFileCountByCategory(selectedProductId, 'document') }})
</button>
</div>
<!-- 文件网格视图 -->
@if (viewMode === 'grid') {
<div class="files-grid">
@for (file of getFilteredProductFiles(selectedProductId); track file.id) {
<div class="file-card"
[class.selected]="selectedFiles.has(file.id)"
[class.category-{{ file.category }}]="true"
(click)="toggleFileSelection(file.id)">
<div class="file-preview">
@if (isImageFile(file)) {
<img [src]="file.url" [alt]="file.name">
} @else {
<div class="file-icon-placeholder">
<i class="icon-{{ getFileIcon(file.type) }}"></i>
</div>
}
@if (file.category) {
<div class="file-category-badge">
{{ getCategoryName(file.category) }}
</div>
}
</div>
<div class="file-info">
<span class="file-name" [title]="file.name">{{ file.name }}</span>
<span class="file-size">{{ formatFileSize(file.size) }}</span>
<span class="file-date">{{ formatDate(file.uploadTime) }}</span>
@if (file.version) {
<span class="file-version">v{{ file.version }}</span>
}
</div>
<div class="file-actions">
<button class="btn-icon" (click)="previewFile(file)" title="预览">
<i class="icon-eye"></i>
</button>
<button class="btn-icon" (click)="downloadFile(file)" title="下载">
<i class="icon-download"></i>
</button>
<button class="btn-icon" (click)="shareFile(file)" title="分享">
<i class="icon-share"></i>
</button>
<button class="btn-icon" (click)="deleteFile(file)" title="删除">
<i class="icon-delete"></i>
</button>
</div>
</div>
}
</div>
}
<!-- 文件列表视图 -->
@if (viewMode === 'list') {
<div class="files-list">
<div class="list-header">
<div class="header-cell">
<input type="checkbox"
[(ngModel)]="selectAllFiles"
(change)="toggleSelectAllFiles()">
</div>
<div class="header-cell">文件名</div>
<div class="header-cell">类别</div>
<div class="header-cell">大小</div>
<div class="header-cell">类型</div>
<div class="header-cell">上传时间</div>
<div class="header-cell">版本</div>
<div class="header-cell">操作</div>
</div>
@for (file of getFilteredProductFiles(selectedProductId); track file.id) {
<div class="list-row"
[class.selected]="selectedFiles.has(file.id)"
[class.category-{{ file.category }}]="true">
<div class="list-cell">
<input type="checkbox"
[(ngModel)]="selectedFiles.has(file.id)"
(change)="toggleFileSelection(file.id)">
</div>
<div class="list-cell file-name-cell">
<i class="icon-{{ getFileIcon(file.type) }}"></i>
<span>{{ file.name }}</span>
</div>
<div class="list-cell">
@if (file.category) {
<span class="category-badge category-{{ file.category }}">
{{ getCategoryName(file.category) }}
</span>
}
</div>
<div class="list-cell">{{ formatFileSize(file.size) }}</div>
<div class="list-cell">{{ getFileTypeLabel(file.type) }}</div>
<div class="list-cell">{{ formatDate(file.uploadTime) }}</div>
<div class="list-cell">
@if (file.version) {
<span class="version-badge">v{{ file.version }}</span>
}
</div>
<div class="list-cell actions-cell">
<button class="btn-icon small" (click)="previewFile(file)" title="预览">
<i class="icon-eye"></i>
</button>
<button class="btn-icon small" (click)="downloadFile(file)" title="下载">
<i class="icon-download"></i>
</button>
<button class="btn-icon small" (click)="shareFile(file)" title="分享">
<i class="icon-share"></i>
</button>
<button class="btn-icon small" (click)="deleteFile(file)" title="删除">
<i class="icon-delete"></i>
</button>
</div>
</div>
}
</div>
}
</div>
} @else {
<div class="no-product-selected">
<i class="icon-folder"></i>
<p>请选择一个产品查看文件</p>
</div>
}
</div>
</div>
-- 为主项目Project表添加多产品支持字段
ALTER TABLE Project ADD COLUMN projectType VARCHAR(20) DEFAULT 'single';
ALTER TABLE Project ADD COLUMN productIds JSON;
ALTER TABLE Project ADD COLUMN productProgress JSON;
ALTER TABLE Project ADD COLUMN productAssignment JSON;
ALTER TABLE Project ADD COLUMN productDependencies JSON;
ALTER TABLE Project ADD COLUMN coordinationInfo JSON;
-- 创建产品索引
CREATE INDEX idx_project_projectType ON Project(projectType);
CREATE INDEX idx_project_productIds ON Project USING GIN(productIds);
-- 创建产品设计表(基于现有Product表)
-- Product表已存在,添加新的字段支持
ALTER TABLE Product ADD COLUMN area DECIMAL(8,2);
ALTER TABLE Product ADD COLUMN priority INTEGER DEFAULT 5;
ALTER TABLE Product ADD COLUMN complexity VARCHAR(20) DEFAULT 'medium';
ALTER TABLE Product ADD COLUMN orderNumber INTEGER DEFAULT 0;
ALTER TABLE Product ADD COLUMN estimatedBudget DECIMAL(12,2);
ALTER TABLE Product ADD COLUMN estimatedDuration INTEGER;
-- 创建产品进度表
CREATE TABLE ProductProgress (
id VARCHAR(50) PRIMARY KEY,
productId VARCHAR(50) NOT NULL,
stage VARCHAR(50) NOT NULL,
progress INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'not_started',
timeline JSON,
blockers JSON,
estimatedCompletion DATETIME,
actualCompletion DATETIME,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (productId) REFERENCES Product(objectId)
);
-- 创建产品分配表
CREATE TABLE ProductAssignment (
id VARCHAR(50) PRIMARY KEY,
productId VARCHAR(50) NOT NULL,
stage VARCHAR(50) NOT NULL,
assigneeId VARCHAR(50) NOT NULL,
assigneeName VARCHAR(100) NOT NULL,
role VARCHAR(50) NOT NULL,
assignedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
assignedBy VARCHAR(50),
status VARCHAR(20) DEFAULT 'active',
workload DECIMAL(3,2) DEFAULT 0.0,
notes TEXT,
FOREIGN KEY (productId) REFERENCES Product(objectId)
);
-- 创建产品依赖表
CREATE TABLE ProductDependency (
id VARCHAR(50) PRIMARY KEY,
fromProductId VARCHAR(50) NOT NULL,
toProductId VARCHAR(50) NOT NULL,
type VARCHAR(50) NOT NULL,
description TEXT,
status VARCHAR(20) DEFAULT 'pending',
priority INTEGER DEFAULT 5,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (fromProductId) REFERENCES Product(objectId),
FOREIGN KEY (toProductId) REFERENCES Product(objectId)
);
class ProductMigrationService {
async migrateExistingProjects(): Promise<void> {
console.log('开始迁移现有项目数据到产品管理模式...');
// 1. 获取所有现有项目
const projects = await this.getAllProjects();
for (const project of projects) {
await this.migrateProjectToProducts(project);
}
console.log('项目数据迁移完成');
}
private async migrateProjectToProducts(project: any): Promise<void> {
// 2. 分析项目是否为多产品
const isMultiProduct = await this.analyzeProjectProductType(project);
if (isMultiProduct) {
// 3. 创建产品记录
const products = await this.createProductsForProject(project);
// 4. 更新项目记录
await this.updateProjectWithProducts(project.objectId, products);
// 5. 迁移交付数据到产品维度
await this.migrateDeliveryDataToProducts(project, products);
// 6. 迁移需求数据到产品维度
await this.migrateRequirementDataToProducts(project, products);
} else {
// 单产品项目,创建默认产品
const defaultProduct = await this.createDefaultProduct(project);
await this.updateProjectWithProducts(project.objectId, [defaultProduct]);
}
}
private async analyzeProjectProductType(project: any): Promise<boolean> {
// 基于项目标题、描述、文件等信息判断是否为多产品
const indicators = [
project.title?.includes('全屋') || project.title?.includes('整套'),
project.data?.description?.includes('多空间'),
(project.data?.quotation?.items?.length || 0) > 3,
await this.hasMultipleRoomTypes(project)
];
return indicators.some(indicator => indicator === true);
}
private async createProductsForProject(project: any): Promise<Project[]> {
const products: Project[] = [];
// 基于报价项创建产品
if (project.data?.quotation?.items) {
for (const item of project.data.quotation.items) {
const productType = this.inferProductTypeFromDescription(item.description);
if (productType) {
const product: Project = {
id: `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: item.room || this.getDefaultProductName(productType),
type: productType,
status: 'not_started',
priority: this.calculateProductPriority(item.amount),
complexity: 'medium',
area: this.estimateProductArea(productType, item.amount),
estimatedBudget: item.amount,
estimatedDuration: this.estimateProductDuration(productType, item.amount),
order: products.length + 1,
projectId: project.objectId,
createdAt: new Date(),
updatedAt: new Date()
};
products.push(product);
}
}
}
// 如果没有从报价识别出产品,创建默认产品
if (products.length === 0) {
const defaultProduct = await this.createDefaultProduct(project);
products.push(defaultProduct);
}
return products;
}
private async createDefaultProduct(project: any): Promise<Project> {
return {
id: `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: project.title || '主空间设计',
type: 'living_room',
status: 'not_started',
priority: 5,
complexity: 'medium',
estimatedBudget: project.data?.quotation?.totalAmount || 0,
estimatedDuration: 30,
order: 1,
projectId: project.objectId,
createdAt: new Date(),
updatedAt: new Date()
};
}
private async migrateDeliveryDataToProducts(project: any, products: Project[]): Promise<void> {
// 迁移交付执行数据到对应产品
if (project.data?.deliveryProcesses) {
for (const process of project.data.deliveryProcesses) {
for (const product of products) {
// 将交付数据关联到对应产品
await this.createProductDeliveryData(product.id, process);
}
}
}
}
private async migrateRequirementDataToProducts(project: any, products: Project[]): Promise<void> {
// 迁移需求数据到对应产品
if (project.data?.requirements) {
for (const product of products) {
await this.createProductRequirementData(product.id, project.data.requirements);
}
}
}
}
class ProjectDataAdapter {
// 适配旧的单产品项目数据格式
adaptLegacyProject(legacyProject: any): any {
const adaptedProject: any = {
...legacyProject,
projectType: 'single',
products: this.createDefaultProductsFromLegacy(legacyProject),
productProgress: this.createDefaultProgressFromLegacy(legacyProject),
productAssignment: this.createDefaultAssignmentFromLegacy(legacyProject)
};
return adaptedProject;
}
private createDefaultProductsFromLegacy(legacyProject: any): Project[] {
return [{
id: `default_product_${legacyProject.objectId}`,
name: legacyProject.title || '主空间设计',
type: this.inferProductTypeFromLegacy(legacyProject),
status: this.legacyStatusToProductStatus(legacyProject.status),
priority: 5,
complexity: 'medium',
estimatedBudget: legacyProject.data?.quotation?.totalAmount || 0,
estimatedDuration: 30,
order: 1,
projectId: legacyProject.objectId,
createdAt: legacyProject.createdAt,
updatedAt: legacyProject.updatedAt,
requirements: legacyProject.data?.requirements,
quotation: legacyProject.data?.quotation,
reviews: legacyProject.data?.reviews
}];
}
// 适配新的多产品数据格式到旧格式(用于兼容性接口)
adaptToLegacyFormat(multiProductProject: any): any {
if (multiProductProject.projectType === 'single') {
return {
...multiProductProject,
// 将单产品数据扁平化到原有格式
data: {
...multiProductProject.data,
deliveryProcesses: this.extractDeliveryProcessesFromProducts(multiProductProject),
requirements: this.extractRequirementsFromProducts(multiProductProject),
quotation: this.extractQuotationFromProducts(multiProductProject)
}
};
}
// 多产品项目返回增强格式的数据
return multiProductProject;
}
}
文档版本:v4.0 (基于Project表统一产品设计管理) 更新日期:2025-10-20 维护者:YSS Development Team