import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, OnInit, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { FmodeParse } from 'fmode-ng/parse'; import { Subscription } from 'rxjs'; import { calculateAllocation, calculateProductAllocation, ALLOCATION_RULES, getBasePrice, STYLE_LEVELS, SPACE_TYPES, BUSINESS_TYPES, HOME_DEFAULT_ROOMS, ARCHITECTURE_TYPES, type Allocation } from '../config/quotation-rules'; const Parse = FmodeParse.with('nova'); /** * 基于Product表的报价编辑器组件(重构版) * * 核心改进: * 1. 严格按照docs/data/quotation.md规则计算基础价格 * 2. 每个空间都是独立的设计产品服务 * 3. 添加产品支持预设场景选择 * 4. 所有价格均为整数(无小数) * 5. 自动计算内部执行分配(10%-40%-50%) */ @Component({ selector: 'app-quotation-editor', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './quotation-editor.component.html', styleUrls: ['./quotation-editor.component.scss'] }) export class QuotationEditorComponent implements OnInit, OnChanges, OnDestroy { // 输入属性 @Input() projectId: string = ''; @Input() project: any = null; @Input() canEdit: boolean = false; @Input() viewMode: 'table' | 'card' = 'card'; @Input() currentUser: any = null; // 输出事件 @Output() quotationChange = new EventEmitter(); @Output() totalChange = new EventEmitter(); @Output() loadingChange = new EventEmitter(); @Output() productsChange = new EventEmitter(); // 数据状态 loading: boolean = false; products: any[] = []; projectInfo: any = { title: '', projectType: '家装', // 家装 | 工装 | 建筑类 renderType: '静态单张', // 静态单张 | 360全景 deadline: '', description: '', priceLevel: '一级', // 一级(老客户) | 二级(中端组) | 三级(高端组) }; // 报价数据结构 quotation: any = { spaces: [], total: 0, spaceBreakdown: [], allocation: null as Allocation | null, generatedAt: null, validUntil: null }; // 分配类型定义(3个分配:建模阶段10%、软装渲染40%、公司分配50%) allocationTypes = [ { key: 'modeling', name: '建模阶段', percentage: 10, color: 'primary', icon: 'cube-outline', description: '3D模型构建' }, { key: 'decoration', name: '软装渲染', percentage: 40, color: 'warning', icon: 'color-palette-outline', description: '软装搭配+效果图渲染' }, { key: 'company', name: '公司分配', percentage: 50, color: 'success', icon: 'business-outline', description: '公司运营与利润' } ]; // 折叠状态 expandedProducts: Set = new Set(); // UI状态 showBreakdown: boolean = false; showAllocation: boolean = false; // 分配规则配置 allocationRules = ALLOCATION_RULES; // 报价配置(从quotation-rules导入) styleLevels = STYLE_LEVELS; spaceTypes = SPACE_TYPES; businessTypes = BUSINESS_TYPES; architectureTypes = ARCHITECTURE_TYPES; homeDefaultRooms = HOME_DEFAULT_ROOMS; // 订阅管理 private subscriptions: Subscription[] = []; // 预设场景列表 presetScenes: { [key: string]: string[] } = { '家装': ['客厅', '餐厅', '主卧', '次卧', '儿童房', '书房', '厨房', '卫生间', '阳台'], '工装': ['大堂', '接待区', '会议室', '办公区', '休息区', '展示区', '洽谈区'], '建筑类': ['门头', '小型单体', '大型单体', '鸟瞰'] }; // 产品添加/编辑模态框状态 showAddProductModal: boolean = false; isEditMode: boolean = false; // 是否为编辑模式 editingProductId: string = ''; // 正在编辑的产品ID newProduct: any = { isCustom: false, sceneName: '', productName: '', spaceType: '平层', styleLevel: '基础风格组', businessType: '办公空间', architectureType: '门头', adjustments: { extraFunction: 0, complexity: 0, design: false, panoramic: false } }; ngOnInit() { if (this.project) { this.loadProjectDataFromProject(); } else if (this.projectId) { this.loadProjectData(); } } ngOnChanges(changes: SimpleChanges) { if (changes['project'] && changes['project'].currentValue) { this.loadProjectDataFromProject(); } else if (changes['projectId'] && changes['projectId'].currentValue) { this.loadProjectData(); } if (changes['quotation'] && this.quotation?.spaces?.length > 0) { if (this.expandedProducts.size === 0) { this.expandedProducts.add(this.quotation.spaces[0].name); } } } ngOnDestroy() { this.subscriptions.forEach(sub => sub.unsubscribe()); } /** * 加载项目数据 */ private async loadProjectData(): Promise { if (!this.projectId) return; try { this.loading = true; this.loadingChange.emit(true); const projectQuery = new Parse.Query('Project'); projectQuery.include('customer', 'assignee', 'department'); this.project = await projectQuery.get(this.projectId); if (this.project) { this.projectInfo.title = this.project.get('title') || ''; this.projectInfo.projectType = this.project.get('projectType') || '家装'; this.projectInfo.renderType = this.project.get('renderType') || '静态单张'; this.projectInfo.deadline = this.project.get('deadline') || ''; this.projectInfo.description = this.project.get('description') || ''; const data = this.project.get('data') || {}; if (data.priceLevel) { this.projectInfo.priceLevel = data.priceLevel; } await this.loadProjectProducts(); if (data.quotation) { this.quotation = data.quotation; this.updateProductsFromQuotation(); } } } catch (error) { console.error('加载项目数据失败:', error); } finally { this.loading = false; this.loadingChange.emit(false); } } /** * 从传入的项目对象初始化数据 */ private async loadProjectDataFromProject(): Promise { if (!this.project) return; try { this.loading = true; this.loadingChange.emit(true); this.projectInfo.title = this.project.get('title') || ''; this.projectInfo.projectType = this.project.get('projectType') || '家装'; this.projectInfo.renderType = this.project.get('renderType') || '静态单张'; this.projectInfo.deadline = this.project.get('deadline') || ''; this.projectInfo.description = this.project.get('description') || ''; const data = this.project.get('data') || {}; if (data.priceLevel) { this.projectInfo.priceLevel = data.priceLevel; } await this.loadProjectProducts(); if (data.quotation) { this.quotation = data.quotation; this.updateProductsFromQuotation(); } } catch (error) { console.error('从项目对象加载数据失败:', error); } finally { this.loading = false; this.loadingChange.emit(false); } } /** * 加载项目产品列表 */ private async loadProjectProducts(): Promise { if (!this.project) return; try { const productQuery = new Parse.Query('Product'); productQuery.equalTo('project', this.project.toPointer()); productQuery.include('profile'); productQuery.ascending('productName'); this.products = await productQuery.find(); this.productsChange.emit(this.products); if (this.products.length === 0) { await this.createDefaultProducts(); } } catch (error) { console.error('加载产品列表失败:', error); } } /** * 创建默认产品 */ private async createDefaultProducts(): Promise { if (!this.project || !this.projectInfo.projectType) return; try { const defaultRooms = this.getDefaultRoomsForProjectType(); for (const roomName of defaultRooms) { await this.createProduct(roomName); } await this.loadProjectProducts(); } catch (error) { console.error('创建默认产品失败:', error); } } /** * 根据项目类型获取默认房间 */ private getDefaultRoomsForProjectType(): string[] { return this.presetScenes[this.projectInfo.projectType] || ['空间1']; } /** * 创建产品(重构版) * 支持传入完整配置,根据报价规则自动计算基础价格 */ private async createProduct( productName: string, config?: { spaceType?: string; styleLevel?: string; businessType?: string; architectureType?: string; } ): Promise { if (!this.project) return null; try { const product = new Parse.Object('Product'); product.set('project', this.project.toPointer()); product.set('productName', productName); product.set('productType', this.inferProductType(productName)); // 确定配置 const spaceType = config?.spaceType || this.getDefaultSpaceType(); const styleLevel = config?.styleLevel || '基础风格组'; const businessType = config?.businessType || '办公空间'; const architectureType = config?.architectureType; // 设置空间信息 product.set('space', { spaceName: productName, area: 0, dimensions: { length: 0, width: 0, height: 0 }, spaceType: spaceType, styleLevel: styleLevel, businessType: businessType, architectureType: architectureType, features: [], constraints: [], priority: 5, complexity: 'medium' }); // 计算基础价格(使用重构后的方法) const basePrice = this.calculateBasePrice({ priceLevel: this.projectInfo.priceLevel, projectType: this.projectInfo.projectType, renderType: this.projectInfo.renderType, spaceType: spaceType, styleLevel: styleLevel, businessType: businessType, architectureType: architectureType }); // 设置报价信息 product.set('quotation', { priceLevel: this.projectInfo.priceLevel, basePrice: Math.round(basePrice), price: Math.round(basePrice), currency: 'CNY', breakdown: this.calculatePriceBreakdown(basePrice), status: 'draft', validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) }); // 设置需求信息 product.set('requirements', { colorRequirement: {}, materialRequirement: {}, lightingRequirement: {}, specificRequirements: [], constraints: {} }); product.set('status', 'not_started'); await product.save(); return product; } catch (error) { console.error('创建产品失败:', error); return null; } } /** * 计算基础价格(重构版) * 严格按照 docs/data/quotation.md 规则 */ private calculateBasePrice(config: { priceLevel: string; projectType: string; renderType: string; spaceType: string; styleLevel?: string; businessType?: string; architectureType?: string; }): number { const { priceLevel, projectType, renderType, spaceType, styleLevel, businessType, architectureType } = config; // 使用quotation-rules.ts中的getBasePrice函数 const basePrice = getBasePrice( priceLevel, projectType as '家装' | '工装' | '建筑类', renderType, spaceType, styleLevel, businessType, architectureType ); // 确保返回整数 return Math.round(basePrice); } /** * 获取默认空间类型 */ private getDefaultSpaceType(): string { if (this.projectInfo.projectType === '家装') { return '平层'; } else if (this.projectInfo.projectType === '工装') { return '门厅空间'; } else if (this.projectInfo.projectType === '建筑类') { return '门头'; } return '平层'; } /** * 推断产品类型 */ private inferProductType(roomName: string): string { const name = roomName.toLowerCase(); if (name.includes('客厅') || name.includes('起居')) return 'living_room'; if (name.includes('卧室') || name.includes('主卧') || name.includes('次卧')) return 'bedroom'; if (name.includes('厨房')) return 'kitchen'; if (name.includes('卫生间') || name.includes('浴室')) return 'bathroom'; if (name.includes('餐厅')) return 'dining_room'; if (name.includes('书房') || name.includes('工作室')) return 'study'; if (name.includes('阳台')) return 'balcony'; if (name.includes('玄关') || name.includes('走廊')) return 'corridor'; return 'other'; } /** * 计算价格明细 */ private calculatePriceBreakdown(basePrice: number): any { // 确保所有价格都是整数 return { design: Math.round(basePrice * 0.3), modeling: Math.round(basePrice * 0.25), rendering: Math.round(basePrice * 0.25), softDecor: Math.round(basePrice * 0.15), postProcess: Math.round(basePrice * 0.05) }; } /** * 从报价数据更新产品 */ private updateProductsFromQuotation(): void { if (!this.quotation.spaces || !this.products.length) return; this.quotation.spaces.forEach((space: any) => { const product = this.products.find(p => p.get('productName') === space.name || p.get('productName').includes(space.name) ); if (product) { const quotation = product.get('quotation') || {}; quotation.price = Math.round(space.subtotal || 0); quotation.processes = space.processes; product.set('quotation', quotation); } }); } // ============ 报价管理核心方法 ============ /** * 生成基于产品的报价 */ async generateQuotationFromProducts(): Promise { if (!this.products.length) return; this.quotation.spaces = []; this.quotation.generatedAt = new Date(); this.quotation.validUntil = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); for (const product of this.products) { const productName = product.get('productName'); const quotation = product.get('quotation') || {}; const basePrice = quotation.price || this.calculateBasePrice({ priceLevel: this.projectInfo.priceLevel, projectType: this.projectInfo.projectType, renderType: this.projectInfo.renderType, spaceType: product.get('space')?.spaceType || this.getDefaultSpaceType(), styleLevel: product.get('space')?.styleLevel, businessType: product.get('space')?.businessType, architectureType: product.get('space')?.architectureType }); // 生成工序明细(确保价格为整数) const processes = this.generateDefaultProcesses(Math.round(basePrice)); const spaceData = { name: productName, productId: product.id, processes: processes, subtotal: this.calculateProductSubtotal(processes) }; this.quotation.spaces.push(spaceData); } this.calculateTotal(); this.updateProductBreakdown(); await this.saveQuotationToProject(); } /** * 生成默认分配(所有价格为整数) * 基于设计图总价按比例分配:建模阶段10%、软装渲染40%、公司分配50% */ private generateDefaultProcesses(basePrice: number): any { return { modeling: { enabled: true, amount: Math.round(basePrice * 0.1), percentage: 10, description: '3D模型构建' }, decoration: { enabled: true, amount: Math.round(basePrice * 0.4), percentage: 40, description: '软装搭配+效果图渲染' }, company: { enabled: true, amount: Math.round(basePrice * 0.5), percentage: 50, description: '公司运营与利润' } }; } /** * 计算产品小计(确保整数) * 现在processes实际是allocations(分配) */ private calculateProductSubtotal(processes: any): number { let subtotal = 0; for (const allocation of Object.values(processes)) { const alloc = allocation as any; if (alloc.enabled) { subtotal += Math.round(alloc.amount); } } return Math.round(subtotal); } /** * 更新产品占比明细 */ private updateProductBreakdown(): void { this.quotation.spaceBreakdown = this.quotation.spaces.map((space: any) => ({ spaceName: space.name, spaceId: space.productId || '', amount: Math.round(space.subtotal), percentage: this.quotation.total > 0 ? Math.round((space.subtotal / this.quotation.total) * 100) : 0 })); } /** * 保存报价到项目 */ private async saveQuotationToProject(): Promise { if (!this.project) return; try { const data = this.project.get('data') || {}; data.quotation = this.quotation; this.project.set('data', data); await this.project.save(); this.quotationChange.emit(this.quotation); } catch (error) { console.error('保存报价失败:', error); } } // ============ UI交互方法 ============ /** * 展开所有产品 */ expandAll() { this.quotation.spaces.forEach((space: any) => { this.expandedProducts.add(space.name); }); } /** * 折叠所有产品 */ collapseAll() { this.expandedProducts.clear(); } /** * 切换分配启用状态 */ toggleProcess(space: any, processKey: string) { const allocation = space.processes[processKey]; allocation.enabled = !allocation.enabled; if (!allocation.enabled) { allocation.amount = 0; } this.calculateTotal(); } /** * 工序价格或数量变化 */ onProcessChange() { this.calculateTotal(); } /** * 计算报价总额(确保所有价格为整数) */ calculateTotal() { let total = 0; for (const space of this.quotation.spaces) { for (const processKey of Object.keys(space.processes)) { const allocation = space.processes[processKey]; if (allocation.enabled) { // 确保分配金额是整数 const amount = Math.round(allocation.amount); total += amount; } } } this.quotation.total = Math.round(total); // 自动计算项目级别的内部执行分配(确保分配金额为整数) this.quotation.allocation = calculateAllocation(this.quotation.total); // 更新产品级别的分配 this.updateProductsAllocation(); this.quotationChange.emit(this.quotation); this.totalChange.emit(this.quotation.total); } /** * 更新所有产品的内部分配(确保整数) */ private updateProductsAllocation() { for (const space of this.quotation.spaces) { const product = this.products.find(p => p.id === space.productId); if (product) { const productPrice = Math.round(this.calculateSpaceSubtotal(space)); const quotation = product.get('quotation') || {}; quotation.allocation = calculateProductAllocation(productPrice); product.set('quotation', quotation); } } } /** * 计算空间小计 */ calculateSpaceSubtotal(space: any): number { return Math.round(this.calculateProductSubtotal(space.processes)); } /** * 计算分配金额 */ calculateProcessSubtotal(space: any, processKey: string): number { const allocation = space.processes?.[processKey]; const amount = Math.round(allocation?.amount || 0); return amount; } // ============ 辅助方法 ============ isProcessEnabled(space: any, processKey: string): boolean { const allocation = space.processes?.[processKey]; return allocation?.enabled || false; } setAllocationAmount(space: any, allocationKey: string, value: any): void { const allocation = space.processes?.[allocationKey]; if (allocation) { allocation.amount = Math.round(Number(value) || 0); } } getAllocationAmount(space: any, allocationKey: string): number { const allocation = space.processes?.[allocationKey]; return Math.round(allocation?.amount || 0); } getAllocationPercentage(space: any, allocationKey: string): number { const allocation = space.processes?.[allocationKey]; return allocation?.percentage || 0; } getAllocationDescription(space: any, allocationKey: string): string { const allocation = space.processes?.[allocationKey]; return allocation?.description || ''; } // ============ 产品管理方法 ============ /** * 添加新产品(简化版,用于快速添加) */ async addProduct(productName?: string): Promise { if (!this.project) return; const name = productName ||await window?.fmode?.input('请输入产品名称:'); if (!name) return; try { await this.createProduct(name); await this.loadProjectProducts(); await this.generateQuotationFromProducts(); } catch (error) { console.error('添加产品失败:', error); window?.fmode?.alert('添加失败,请重试'); } } /** * 编辑产品名称 */ async editProduct(productId: string): Promise { const product = this.products.find(p => p.id === productId); if (!product) return; const newName =await window?.fmode?.input('修改产品名称:', product.get('productName')); if (!newName || newName === product.get('productName')) return; try { product.set('productName', newName); product.set('productType', this.inferProductType(newName)); await product.save(); const spaceData = this.quotation.spaces.find((s: any) => s.productId === productId); if (spaceData) { spaceData.name = newName; } await this.saveQuotationToProject(); await this.loadProjectProducts(); } catch (error) { console.error('更新产品失败:', error); window?.fmode?.alert('更新失败,请重试'); } } /** * 删除产品 */ async deleteProduct(productId: string): Promise { if (!await window?.fmode?.confirm('确定要删除这个产品吗?相关数据将被清除。')) return; try { const product = this.products.find(p => p.id === productId); if (product) { await product.destroy(); } // 更新本地产品列表(不重新加载,避免触发createDefaultProducts) this.products = this.products.filter(p => p.id !== productId); this.productsChange.emit(this.products); // 更新报价spaces this.quotation.spaces = this.quotation.spaces.filter((s: any) => s.productId !== productId); // 重新计算总价 this.calculateTotal(); this.updateProductBreakdown(); // 保存报价 await this.saveQuotationToProject(); } catch (error) { console.error('删除产品失败:', error); window?.fmode?.alert('删除失败,请重试'); } } /** * 保存报价 */ async saveQuotation(): Promise { if (!this.canEdit) return; try { await this.saveQuotationToProject(); window?.fmode?.alert('保存成功'); } catch (error) { console.error('保存失败:', error); window?.fmode?.alert('保存失败'); } } /** * 切换内部执行分配显示 */ toggleAllocation() { this.showAllocation = !this.showAllocation; } toggleProductExpand(spaceName: string): void { if (this.expandedProducts.has(spaceName)) { this.expandedProducts.delete(spaceName); } else { this.expandedProducts.add(spaceName); } } isProductExpanded(spaceName: string): boolean { return this.expandedProducts.has(spaceName); } // ============ 辅助显示方法 ============ getProductDesigner(product: any): string { const profile = product.get('profile'); return profile ? profile.get('name') : '未分配'; } getProductStatus(product: any): string { return product.get('status') || 'not_started'; } getProductStatusColor(status: string): string { const colorMap: Record = { 'not_started': 'medium', 'in_progress': 'warning', 'awaiting_review': 'info', 'completed': 'success', 'blocked': 'danger', 'delayed': 'danger' }; return colorMap[status] || 'medium'; } getProductStatusText(status: string): string { const textMap: Record = { 'not_started': '未开始', 'in_progress': '进行中', 'awaiting_review': '待审核', 'completed': '已完成', 'blocked': '已阻塞', 'delayed': '已延期' }; return textMap[status] || status; } getProductIcon(productType: string): string { const iconMap: Record = { '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': 'room' }; return iconMap[productType] || 'room'; } formatPrice(price: number): string { // 确保价格为整数 return `¥${Math.round(price)}`; } forSpacePrice(space,allocationType){ let price = Math.round((this.getProductForSpace(space.productId)?.get('quotation')?.basePrice || 0) * allocationType.percentage / 100) this.formatPercentage(price) } formatPercentage(value: number): string { return `${Math.round(value)}%`; } getProductTypeForSpace(spaceName: string): string { return this.inferProductType(spaceName); } getProductIconForSpace(spaceName: string): string { const productType = this.getProductTypeForSpace(spaceName); return this.getProductIcon(productType); } getStatusColorForSpace(spaceId: string): string { const product = this.products.find(p => p.id === spaceId); if (product) { return this.getProductStatusColor(product.get('status')); } return 'medium'; } getStatusTextForSpace(spaceId: string): string { const product = this.products.find(p => p.id === spaceId); if (product) { return this.getProductStatusText(product.get('status')); } return '未开始'; } getDesignerNameForSpace(spaceId: string): string { const product = this.products.find(p => p.id === spaceId); if (product) { return this.getProductDesigner(product); } return '未分配'; } getProductForSpace(productId: string): any { return this.products.find(p => p.id === productId) || null; } getSpacePercentage(spaceId: string): number { if (!this.quotation.spaceBreakdown) return 0; const breakdown = this.quotation.spaceBreakdown.find((b: any) => b.spaceId === spaceId); return Math.round(breakdown?.percentage || 0); } // ============ 产品添加模态框方法 ============ /** * 打开添加产品模态框 */ openAddProductModal(): void { // 重置表单 this.resetNewProductForm(); this.isEditMode = false; this.editingProductId = ''; this.showAddProductModal = true; } /** * 打开编辑产品模态框 */ openEditProductModal(productId: string): void { const product = this.products.find(p => p.id === productId); if (!product) return; // 预填充产品数据 const space = product.get('space') || {}; const quotation = product.get('quotation') || {}; const adjustments = quotation.adjustments || {}; // 判断是否为自定义名称 const presetScenes = this.getPresetScenes(); const isPreset = presetScenes.includes(product.get('productName')); this.newProduct = { isCustom: !isPreset, sceneName: isPreset ? product.get('productName') : '', productName: product.get('productName'), spaceType: space.spaceType || this.getDefaultSpaceType(), styleLevel: space.styleLevel || '基础风格组', businessType: space.businessType || '办公空间', architectureType: space.architectureType || '门头', adjustments: { extraFunction: adjustments.extraFunction || 0, complexity: adjustments.complexity || 0, design: adjustments.design || false, panoramic: adjustments.panoramic || false } }; this.isEditMode = true; this.editingProductId = productId; this.showAddProductModal = true; } /** * 关闭添加/编辑产品模态框 */ closeAddProductModal(): void { this.showAddProductModal = false; this.isEditMode = false; this.editingProductId = ''; this.resetNewProductForm(); } /** * 重置新产品表单 */ private resetNewProductForm(): void { this.newProduct = { isCustom: false, sceneName: '', productName: '', spaceType: this.getDefaultSpaceType(), styleLevel: '基础风格组', businessType: '办公空间', architectureType: '门头', adjustments: { extraFunction: 0, complexity: 0, design: false, panoramic: false } }; } /** * 获取预设场景列表 */ getPresetScenes(): string[] { return this.presetScenes[this.projectInfo.projectType] || []; } /** * 选择预设场景 */ selectScene(scene: string): void { this.newProduct.isCustom = false; this.newProduct.sceneName = scene; this.newProduct.productName = scene; } /** * 选择自定义场景 */ selectCustomScene(): void { this.newProduct.isCustom = true; this.newProduct.sceneName = ''; this.newProduct.productName = ''; } /** * 计算预览基础价格 */ calculatePreviewBasePrice(): number { return this.calculateBasePrice({ priceLevel: this.projectInfo.priceLevel, projectType: this.projectInfo.projectType, renderType: this.projectInfo.renderType, spaceType: this.newProduct.spaceType, styleLevel: this.newProduct.styleLevel, businessType: this.newProduct.businessType, architectureType: this.newProduct.architectureType }); } /** * 计算预览加价总额 */ calculatePreviewAdjustmentTotal(): number { const basePrice = this.calculatePreviewBasePrice(); const adjustments = this.newProduct.adjustments; let total = 0; // 功能区加价 if (adjustments.extraFunction > 0) { if (this.projectInfo.projectType === '家装') { total += adjustments.extraFunction * 100; } else if (this.projectInfo.projectType === '工装') { total += adjustments.extraFunction * 400; } } // 造型复杂度加价 if (adjustments.complexity > 0) { total += adjustments.complexity; } // 设计服务倍增 let multiplier = 1; if (adjustments.design) { multiplier *= 2; } // 全景渲染倍增(仅工装) if (this.projectInfo.projectType === '工装' && adjustments.panoramic) { multiplier *= 2; } // 如果有倍增,计算额外的加价 if (multiplier > 1) { total = (basePrice + total) * multiplier - basePrice; } return Math.round(total); } /** * 计算预览最终价格 */ calculatePreviewFinalPrice(): number { const basePrice = this.calculatePreviewBasePrice(); const adjustments = this.newProduct.adjustments; let price = basePrice; // 功能区加价 if (adjustments.extraFunction > 0) { if (this.projectInfo.projectType === '家装') { price += adjustments.extraFunction * 100; } else if (this.projectInfo.projectType === '工装') { price += adjustments.extraFunction * 400; } } // 造型复杂度加价 if (adjustments.complexity > 0) { price += adjustments.complexity; } // 设计服务倍增 if (adjustments.design) { price *= 2; } // 全景渲染倍增(仅工装) if (this.projectInfo.projectType === '工装' && adjustments.panoramic) { price *= 2; } return Math.round(price); } /** * 验证新产品表单是否有效 */ isNewProductValid(): boolean { if (this.newProduct.isCustom) { return this.newProduct.productName.trim().length > 0; } return this.newProduct.sceneName.length > 0; } /** * 确认添加/编辑产品 */ async confirmAddProduct(): Promise { if (!this.isNewProductValid()) { window?.fmode?.alert('请完整填写产品信息'); return; } try { if (this.isEditMode) { await this.updateExistingProduct(); } else { await this.createNewProduct(); } } catch (error) { console.error(this.isEditMode ? '编辑产品失败:' : '添加产品失败:', error); window?.fmode?.alert(this.isEditMode ? '编辑产品失败,请重试' : '添加产品失败,请重试'); } } /** * 创建新产品 */ private async createNewProduct(): Promise { const productName = this.newProduct.isCustom ? this.newProduct.productName : this.newProduct.sceneName; const config: any = { spaceType: this.newProduct.spaceType }; if (this.projectInfo.projectType === '家装') { config.styleLevel = this.newProduct.styleLevel; } else if (this.projectInfo.projectType === '工装') { config.businessType = this.newProduct.businessType; } else if (this.projectInfo.projectType === '建筑类') { config.architectureType = this.newProduct.architectureType; } // 创建产品 const product = await this.createProductWithAdjustments(productName, config); if (product) { // 重新加载产品列表 await this.loadProjectProducts(); // 重新生成报价 await this.generateQuotationFromProducts(); // 关闭模态框 this.closeAddProductModal(); window?.fmode?.alert(`成功添加产品: ${productName}`); } else { throw new Error('创建产品失败'); } } /** * 更新现有产品 */ private async updateExistingProduct(): Promise { const product = this.products.find(p => p.id === this.editingProductId); if (!product) { throw new Error('找不到要编辑的产品'); } const productName = this.newProduct.isCustom ? this.newProduct.productName : this.newProduct.sceneName; // 更新产品名称和类型 product.set('productName', productName); product.set('productType', this.inferProductType(productName)); // 确定配置 const spaceType = this.newProduct.spaceType; const styleLevel = this.newProduct.styleLevel; const businessType = this.newProduct.businessType; const architectureType = this.newProduct.architectureType; // 更新空间信息 const space = product.get('space') || {}; space.spaceName = productName; space.spaceType = spaceType; space.styleLevel = styleLevel; space.businessType = businessType; space.architectureType = architectureType; product.set('space', space); // 重新计算基础价格 let basePrice = this.calculateBasePrice({ priceLevel: this.projectInfo.priceLevel, projectType: this.projectInfo.projectType, renderType: this.projectInfo.renderType, spaceType: spaceType, styleLevel: styleLevel, businessType: businessType, architectureType: architectureType }); // 应用加价规则 let finalPrice = basePrice; const adjustments = this.newProduct.adjustments; // 功能区加价 if (adjustments.extraFunction > 0) { if (this.projectInfo.projectType === '家装') { finalPrice += adjustments.extraFunction * 100; } else if (this.projectInfo.projectType === '工装') { finalPrice += adjustments.extraFunction * 400; } } // 造型复杂度加价 if (adjustments.complexity > 0) { finalPrice += adjustments.complexity; } // 设计服务倍增 if (adjustments.design) { finalPrice *= 2; } // 全景渲染倍增(仅工装) if (this.projectInfo.projectType === '工装' && adjustments.panoramic) { finalPrice *= 2; } finalPrice = Math.round(finalPrice); // 更新报价信息 const quotation = product.get('quotation') || {}; quotation.basePrice = Math.round(basePrice); quotation.price = finalPrice; quotation.adjustments = { extraFunction: adjustments.extraFunction, complexity: adjustments.complexity, design: adjustments.design, panoramic: adjustments.panoramic }; quotation.breakdown = this.calculatePriceBreakdown(finalPrice); product.set('quotation', quotation); // 保存产品 await product.save(); // 更新报价中的空间数据 const spaceData = this.quotation.spaces.find((s: any) => s.productId === this.editingProductId); if (spaceData) { spaceData.name = productName; // 重新生成该空间的分配 spaceData.processes = this.generateDefaultProcesses(finalPrice); } // 重新加载产品列表 await this.loadProjectProducts(); // 重新生成报价 await this.generateQuotationFromProducts(); // 关闭模态框 this.closeAddProductModal(); window?.fmode?.alert(`成功更新产品: ${productName}`); } /** * 创建产品(带加价规则) */ private async createProductWithAdjustments( productName: string, config: { spaceType?: string; styleLevel?: string; businessType?: string; architectureType?: string; } ): Promise { if (!this.project) return null; try { const product = new Parse.Object('Product'); product.set('project', this.project.toPointer()); product.set('productName', productName); product.set('productType', this.inferProductType(productName)); // 确定配置 const spaceType = config?.spaceType || this.getDefaultSpaceType(); const styleLevel = config?.styleLevel || '基础风格组'; const businessType = config?.businessType || '办公空间'; const architectureType = config?.architectureType; // 设置空间信息 product.set('space', { spaceName: productName, area: 0, dimensions: { length: 0, width: 0, height: 0 }, spaceType: spaceType, styleLevel: styleLevel, businessType: businessType, architectureType: architectureType, features: [], constraints: [], priority: 5, complexity: 'medium' }); // 计算基础价格 let basePrice = this.calculateBasePrice({ priceLevel: this.projectInfo.priceLevel, projectType: this.projectInfo.projectType, renderType: this.projectInfo.renderType, spaceType: spaceType, styleLevel: styleLevel, businessType: businessType, architectureType: architectureType }); // 应用加价规则 let finalPrice = basePrice; const adjustments = this.newProduct.adjustments; // 功能区加价 if (adjustments.extraFunction > 0) { if (this.projectInfo.projectType === '家装') { finalPrice += adjustments.extraFunction * 100; } else if (this.projectInfo.projectType === '工装') { finalPrice += adjustments.extraFunction * 400; } } // 造型复杂度加价 if (adjustments.complexity > 0) { finalPrice += adjustments.complexity; } // 设计服务倍增 if (adjustments.design) { finalPrice *= 2; } // 全景渲染倍增(仅工装) if (this.projectInfo.projectType === '工装' && adjustments.panoramic) { finalPrice *= 2; } finalPrice = Math.round(finalPrice); // 设置报价信息 product.set('quotation', { priceLevel: this.projectInfo.priceLevel, basePrice: Math.round(basePrice), price: finalPrice, currency: 'CNY', adjustments: { extraFunction: adjustments.extraFunction, complexity: adjustments.complexity, design: adjustments.design, panoramic: adjustments.panoramic }, breakdown: this.calculatePriceBreakdown(finalPrice), status: 'draft', validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) }); // 设置需求信息 product.set('requirements', { colorRequirement: {}, materialRequirement: {}, lightingRequirement: {}, specificRequirements: [], constraints: {} }); product.set('status', 'not_started'); await product.save(); return product; } catch (error) { console.error('创建产品失败:', error); return null; } } }