import { Injectable } from '@angular/core'; import { FmodeParse } from 'fmode-ng/parse'; const Parse = FmodeParse.with('nova'); export interface Project { id: string; name: string; // productName type: string; // productType area?: number; // space.area priority: number; // space.priority status: string; // Product.status complexity: string; // space.complexity metadata?: any; // space metadata estimatedBudget?: number; // quotation.price estimatedDuration?: number; // space metadata order: number; projectId: string; designerId?: string; // profile pointer quotation?: any; // Product.quotation requirements?: any; // Product.requirements reviews?: any; // Product.reviews } export interface ProductProgress { productId: string; stage: string; progress: number; status: string; timeline?: any[]; blockers?: string[]; estimatedCompletion?: Date; actualCompletion?: Date; } export interface ProductRequirement { productId: string; productName: string; productType: string; colorRequirement: any; spaceStructureRequirement: any; materialRequirement: any; lightingRequirement: any; specificRequirements: any; referenceImages?: string[]; referenceFiles?: any[]; } @Injectable({ providedIn: 'root' }) export class ProductSpaceService { constructor() {} /** * 创建产品空间(即创建Product) */ async createProductSpace(projectId: string, spaceData: Partial): Promise { try { const Product = Parse.Object.extend('Product'); const product = new Product(); // 获取项目 const projectQuery = new Parse.Query('Project'); const project = await projectQuery.get(projectId); // 设置产品字段 product.set('project', project); product.set('productName', spaceData.name || ''); product.set('productType', spaceData.type || 'other'); product.set('status', spaceData.status || 'not_started'); // 设置空间信息 product.set('space', { spaceName: spaceData.name || '', area: spaceData.area || 0, dimensions: { length: 0, width: 0, height: 0 }, features: [], constraints: [], priority: spaceData.priority || 5, complexity: spaceData.complexity || 'medium' }); // 设置报价信息 if (spaceData.estimatedBudget) { product.set('quotation', { price: spaceData.estimatedBudget, currency: 'CNY', breakdown: {}, status: 'draft', validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) }); } // 设置需求信息 product.set('requirements', { colorRequirement: spaceData.requirements?.colorRequirement || {}, spaceStructureRequirement: spaceData.requirements?.spaceStructureRequirement || {}, materialRequirement: spaceData.requirements?.materialRequirement || {}, lightingRequirement: spaceData.requirements?.lightingRequirement || {}, specificRequirements: spaceData.requirements?.specificRequirements || [], constraints: {} }); // 设置设计师 if (spaceData.designerId) { const designerQuery = new Parse.Query('Profile'); const designer = await designerQuery.get(spaceData.designerId); product.set('profile', designer); } const savedProduct = await product.save(); return this.parseProductData(savedProduct); } catch (error) { console.error('创建产品空间失败:', error); throw error; } } /** * 获取项目产品空间列表 */ async getProjectProductSpaces(projectId: string): Promise { try { const query = new Parse.Query('Product'); query.equalTo('project', { __type: 'Pointer', className: 'Project', objectId: projectId }); query.include('profile'); query.ascending('createdAt'); const results = await query.find(); const mapped = results.map(product => this.parseProductData(product)); // 去重:按名称(忽略大小写与首尾空格)保留第一个 const seen = new Set(); const unique: Project[] = []; for (const p of mapped) { const key = (p.name || '').trim().toLowerCase(); if (!seen.has(key)) { seen.add(key); unique.push(p); } } return unique; } catch (error) { console.error('获取项目产品空间失败:', error); return []; } } /** * 更新产品空间信息 */ async updateProductSpace(productId: string, updateData: Partial): Promise { try { const query = new Parse.Query('Product'); const product = await query.get(productId); // 更新产品基本信息 if (updateData.name !== undefined) product.set('productName', updateData.name); if (updateData.type !== undefined) product.set('productType', updateData.type); if (updateData.status !== undefined) product.set('status', updateData.status); // 更新空间信息 if (updateData.area !== undefined || updateData.priority !== undefined || updateData.complexity !== undefined) { const space = product.get('space') || {}; if (updateData.area !== undefined) space.area = updateData.area; if (updateData.priority !== undefined) space.priority = updateData.priority; if (updateData.complexity !== undefined) space.complexity = updateData.complexity; product.set('space', space); } // 更新报价信息 if (updateData.estimatedBudget !== undefined) { const quotation = product.get('quotation') || {}; quotation.price = updateData.estimatedBudget; product.set('quotation', quotation); } // 更新需求信息 if (updateData.requirements !== undefined) { const requirements = product.get('requirements') || {}; Object.assign(requirements, updateData.requirements); product.set('requirements', requirements); } // 更新设计师 if (updateData.designerId !== undefined) { if (updateData.designerId) { const designerQuery = new Parse.Query('Profile'); const designer = await designerQuery.get(updateData.designerId); product.set('profile', designer); } else { product.unset('profile'); } } const savedProduct = await product.save(); return this.parseProductData(savedProduct); } catch (error) { console.error('更新产品空间失败:', error); throw error; } } /** * 删除产品空间 */ async deleteProductSpace(productId: string): Promise { try { const query = new Parse.Query('Product'); const product = await query.get(productId); await product.destroy(); } catch (error) { console.error('删除产品空间失败:', error); throw error; } } /** * 更新产品进度 */ async updateProductProgress(progressData: ProductProgress): Promise { try { // 在Product的data字段中存储进度信息 const query = new Parse.Query('Product'); const product = await query.get(progressData.productId); const data = product.get('data') || {}; if (!data.progress) data.progress = []; // 查找现有进度记录 let progressRecord = data.progress.find((p: any) => p.stage === progressData.stage); if (!progressRecord) { // 创建新进度记录 progressRecord = { stage: progressData.stage, progress: 0, status: 'not_started', timeline: [], blockers: [] }; data.progress.push(progressRecord); } // 更新进度数据 progressRecord.progress = progressData.progress; progressRecord.status = progressData.status; progressRecord.timeline = progressData.timeline || []; progressRecord.blockers = progressData.blockers || []; progressRecord.estimatedCompletion = progressData.estimatedCompletion; progressRecord.actualCompletion = progressData.actualCompletion; product.set('data', data); await product.save(); } catch (error) { console.error('更新产品进度失败:', error); throw error; } } /** * 获取产品进度 */ async getProductProgress(productId: string, stage?: string): Promise { try { const query = new Parse.Query('Product'); const product = await query.get(productId); const data = product.get('data') || {}; const progressData = data.progress || []; let result = progressData; if (stage) { result = progressData.filter((p: any) => p.stage === stage); } return result.map((p: any) => ({ productId: productId, stage: p.stage, progress: p.progress, status: p.status, timeline: p.timeline, blockers: p.blockers, estimatedCompletion: p.estimatedCompletion, actualCompletion: p.actualCompletion })); } catch (error) { console.error('获取产品进度失败:', error); return []; } } /** * 创建产品依赖关系 */ async createProductDependency( projectId: string, fromProductId: string, toProductId: string, dependencyType: string, description: string ): Promise { try { // 在项目的data字段中存储产品依赖关系 const projectQuery = new Parse.Query('Project'); const project = await projectQuery.get(projectId); const data = project.get('data') || {}; if (!data.productDependencies) data.productDependencies = []; const dependency = { id: `dep_${Date.now()}`, fromProductId: fromProductId, toProductId: toProductId, type: dependencyType, description: description, status: 'pending', confidence: 0.8, createdAt: new Date() }; data.productDependencies.push(dependency); project.set('data', data); await project.save(); } catch (error) { console.error('创建产品依赖失败:', error); throw error; } } /** * 获取产品依赖关系 */ async getProductDependencies(projectId: string): Promise { try { const projectQuery = new Parse.Query('Project'); const project = await projectQuery.get(projectId); const data = project.get('data') || {}; return data.productDependencies || []; } catch (error) { console.error('获取产品依赖失败:', error); return []; } } /** * 保存产品需求 */ async saveProductRequirements( _projectId: string, productId: string, requirements: ProductRequirement ): Promise { try { const query = new Parse.Query('Product'); const product = await query.get(productId); // 更新产品的需求信息 product.set('requirements', { colorRequirement: requirements.colorRequirement, spaceStructureRequirement: requirements.spaceStructureRequirement, materialRequirement: requirements.materialRequirement, lightingRequirement: requirements.lightingRequirement, specificRequirements: requirements.specificRequirements, constraints: {} }); await product.save(); } catch (error) { console.error('保存产品需求失败:', error); throw error; } } /** * 获取产品需求 */ async getProductRequirements(projectId: string, productId?: string): Promise { try { let query = new Parse.Query('Product'); query.equalTo('project', { __type: 'Pointer', className: 'Project', objectId: projectId }); if (productId) { query.equalTo('objectId', productId); } const results = await query.find(); return results.map(product => ({ productId: product.id || '', productName: product.get('productName'), productType: product.get('productType'), colorRequirement: product.get('requirements')?.colorRequirement || {}, spaceStructureRequirement: product.get('requirements')?.spaceStructureRequirement || {}, materialRequirement: product.get('requirements')?.materialRequirement || {}, lightingRequirement: product.get('requirements')?.lightingRequirement || {}, specificRequirements: product.get('requirements')?.specificRequirements || [], referenceImages: product.get('requirements')?.referenceImages || [], referenceFiles: product.get('requirements')?.referenceFiles || [] })); } catch (error) { console.error('获取产品需求失败:', error); return []; } } /** * 计算产品完成进度 */ calculateProductProgress(_productId: string, _allStages: string[]): number { // 这里需要从缓存或数据库中获取各阶段进度 // 暂时返回模拟数据 return Math.floor(Math.random() * 100); } /** * 获取产品类型图标 */ 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'; } /** * 获取产品类型名称 */ getProductTypeName(productType: string): string { const nameMap: Record = { 'living_room': '客厅', 'bedroom': '卧室', 'kitchen': '厨房', 'bathroom': '卫生间', 'dining_room': '餐厅', 'study': '书房', 'balcony': '阳台', 'corridor': '走廊', 'storage': '储物间', 'entrance': '玄关', 'other': '其他' }; return nameMap[productType] || '其他'; } /** * 解析产品数据 */ private parseProductData(product: any): Project { const space = product.get('space') || {}; const quotation = product.get('quotation') || {}; const profile = product.get('profile'); return { id: product.id, name: product.get('productName'), type: product.get('productType'), area: space.area, priority: space.priority || 5, status: product.get('status'), complexity: space.complexity || 'medium', metadata: space.metadata || {}, estimatedBudget: quotation.price, estimatedDuration: space.estimatedDuration, order: 0, // 使用创建时间排序 projectId: product.get('project')?.objectId, designerId: profile?.id, quotation: quotation, requirements: product.get('requirements'), reviews: product.get('reviews') }; } /** * 统一空间数据管理 - 核心方法 * 将所有空间数据统一存储到Project.data.unifiedSpaces中 */ async saveUnifiedSpaceData(projectId: string, spaces: any[]): Promise { try { console.log(`🔄 [统一空间管理] 开始保存统一空间数据,项目ID: ${projectId}, 空间数: ${spaces.length}`); const projectQuery = new Parse.Query('Project'); const project = await projectQuery.get(projectId); const data = project.get('data') || {}; // 统一空间数据结构 data.unifiedSpaces = spaces.map((space, index) => ({ id: space.id || `space_${Date.now()}_${index}`, name: space.name || `空间${index + 1}`, type: space.type || 'other', area: space.area || 0, priority: space.priority || 5, status: space.status || 'not_started', complexity: space.complexity || 'medium', estimatedBudget: space.estimatedBudget || 0, order: space.order !== undefined ? space.order : index, // 报价信息 quotation: { price: space.estimatedBudget || 0, processes: space.processes || {}, subtotal: space.subtotal || 0 }, // 需求信息 requirements: space.requirements || {}, // 设计师分配 designerId: space.designerId || null, // 进度信息 progress: space.progress || [], // 创建和更新时间 createdAt: space.createdAt || new Date().toISOString(), updatedAt: new Date().toISOString() })); // 同时保存到quotation.spaces以保持向后兼容 if (!data.quotation) data.quotation = {}; data.quotation.spaces = data.unifiedSpaces.map(space => ({ name: space.name, spaceId: space.id, processes: space.quotation.processes, subtotal: space.quotation.subtotal })); project.set('data', data); await project.save(); // 同步到Product表 await this.syncUnifiedSpacesToProducts(projectId, data.unifiedSpaces); console.log(`✅ [统一空间管理] 统一空间数据保存完成`); } catch (error) { console.error('❌ [统一空间管理] 保存统一空间数据失败:', error); throw error; } } /** * 获取统一空间数据 */ async getUnifiedSpaceData(projectId: string): Promise { try { const projectQuery = new Parse.Query('Project'); const project = await projectQuery.get(projectId); const data = project.get('data') || {}; // 优先返回统一空间数据 if (data.unifiedSpaces && data.unifiedSpaces.length > 0) { console.log(`✅ [统一空间管理] 从unifiedSpaces获取到 ${data.unifiedSpaces.length} 个空间`); return data.unifiedSpaces; } // 如果没有统一数据,尝试从Product表迁移 const productSpaces = await this.getProjectProductSpaces(projectId); if (productSpaces.length > 0) { console.log(`🔄 [统一空间管理] 从Product表迁移 ${productSpaces.length} 个空间到统一存储`); const unifiedSpaces = productSpaces.map((space, index) => ({ id: space.id, name: space.name, type: space.type, area: space.area, priority: space.priority, status: space.status, complexity: space.complexity, estimatedBudget: space.estimatedBudget, order: index, quotation: { price: space.estimatedBudget || 0, processes: {}, subtotal: space.estimatedBudget || 0 }, requirements: space.requirements || {}, designerId: space.designerId, progress: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() })); // 保存迁移后的数据 await this.saveUnifiedSpaceData(projectId, unifiedSpaces); return unifiedSpaces; } // 如果都没有,返回空数组 console.log(`⚠️ [统一空间管理] 项目 ${projectId} 没有找到任何空间数据`); return []; } catch (error) { console.error('❌ [统一空间管理] 获取统一空间数据失败:', error); return []; } } /** * 将统一空间数据同步到Product表 */ private async syncUnifiedSpacesToProducts(projectId: string, unifiedSpaces: any[]): Promise { try { console.log(`🔄 [统一空间管理] 开始同步统一空间到Product表`); // 获取现有的Product列表 const existingProducts = await this.getProjectProductSpaces(projectId); const existingById = new Map(existingProducts.map(p => [p.id, p])); for (const space of unifiedSpaces) { const existingProduct = existingById.get(space.id); if (existingProduct) { // 更新现有Product await this.updateProductSpace(space.id, { name: space.name, type: space.type, area: space.area, priority: space.priority, status: space.status, complexity: space.complexity, estimatedBudget: space.quotation.price, requirements: space.requirements, designerId: space.designerId }); } else { // 创建新Product await this.createProductSpace(projectId, { name: space.name, type: space.type, area: space.area, priority: space.priority, status: space.status, complexity: space.complexity, estimatedBudget: space.quotation.price, requirements: space.requirements, designerId: space.designerId, order: space.order }); } } console.log(`✅ [统一空间管理] 统一空间同步到Product表完成`); } catch (error) { console.error('❌ [统一空间管理] 同步统一空间到Product表失败:', error); } } /** * 创建初始空间(只创建1-2个默认空间) */ async createInitialSpaces(projectId: string, projectType: '家装' | '工装' = '家装'): Promise { try { console.log(`🏠 [初始空间] 为项目 ${projectId} 创建初始空间,类型: ${projectType}`); let initialSpaces: any[] = []; if (projectType === '家装') { // 家装项目:只创建客厅和主卧两个空间 initialSpaces = [ { id: `space_${Date.now()}_1`, name: '客厅', type: 'living_room', area: 0, priority: 5, status: 'not_started', complexity: 'medium', estimatedBudget: 300, order: 0, quotation: { price: 300, processes: { modeling: { enabled: true, price: 100, unit: '张', quantity: 1 }, softDecor: { enabled: true, price: 100, unit: '张', quantity: 1 }, rendering: { enabled: true, price: 100, unit: '张', quantity: 1 }, postProcess: { enabled: false, price: 0, unit: '张', quantity: 1 } }, subtotal: 300 }, requirements: {}, designerId: null, progress: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, { id: `space_${Date.now()}_2`, name: '主卧', type: 'bedroom', area: 0, priority: 5, status: 'not_started', complexity: 'medium', estimatedBudget: 300, order: 1, quotation: { price: 300, processes: { modeling: { enabled: true, price: 100, unit: '张', quantity: 1 }, softDecor: { enabled: true, price: 100, unit: '张', quantity: 1 }, rendering: { enabled: true, price: 100, unit: '张', quantity: 1 }, postProcess: { enabled: false, price: 0, unit: '张', quantity: 1 } }, subtotal: 300 }, requirements: {}, designerId: null, progress: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } ]; } else { // 工装项目:只创建一个主要空间 initialSpaces = [ { id: `space_${Date.now()}_1`, name: '主要空间', type: 'other', area: 0, priority: 5, status: 'not_started', complexity: 'medium', estimatedBudget: 500, order: 0, quotation: { price: 500, processes: { modeling: { enabled: true, price: 150, unit: '张', quantity: 1 }, softDecor: { enabled: true, price: 150, unit: '张', quantity: 1 }, rendering: { enabled: true, price: 150, unit: '张', quantity: 1 }, postProcess: { enabled: true, price: 50, unit: '张', quantity: 1 } }, subtotal: 500 }, requirements: {}, designerId: null, progress: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } ]; } // 保存到统一存储 await this.saveUnifiedSpaceData(projectId, initialSpaces); console.log(`✅ [初始空间] 已创建 ${initialSpaces.length} 个初始空间`); return initialSpaces; } catch (error) { console.error('❌ [初始空间] 创建初始空间失败:', error); throw error; } } /** * 数据修复:强制同步当前项目的空间数据到统一存储 */ async forceRepairSpaceData(projectId: string): Promise { try { console.log(`🔧 [数据修复] 开始修复项目 ${projectId} 的空间数据...`); // 1. 获取项目数据 const projectQuery = new Parse.Query('Project'); const project = await projectQuery.get(projectId); const data = project.get('data') || {}; // 2. 获取Product表中的空间数据 const productSpaces = await this.getProjectProductSpaces(projectId); console.log(`🔧 [数据修复] Product表中有 ${productSpaces.length} 个空间`); // 3. 获取报价数据中的空间 const quotationSpaces = Array.isArray(data.quotation?.spaces) ? data.quotation.spaces : []; console.log(`🔧 [数据修复] 报价数据中有 ${quotationSpaces.length} 个空间`); // 4. 以Product表为准,构建统一空间数据 const unifiedSpaces = productSpaces.map((space, index) => { // 尝试从报价数据中找到对应的空间信息 const quotationSpace = quotationSpaces.find((q: any) => q.spaceId === space.id || (q.name && space.name && q.name.trim().toLowerCase() === space.name.trim().toLowerCase()) ); return { id: space.id, name: space.name, type: space.type, area: space.area || 0, priority: space.priority || 5, status: space.status || 'not_started', complexity: space.complexity || 'medium', estimatedBudget: space.estimatedBudget || 0, order: index, quotation: { price: space.estimatedBudget || 0, processes: quotationSpace?.processes || {}, subtotal: quotationSpace?.subtotal || space.estimatedBudget || 0 }, requirements: space.requirements || {}, designerId: space.designerId || null, progress: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; }); // 5. 保存到统一存储 await this.saveUnifiedSpaceData(projectId, unifiedSpaces); console.log(`✅ [数据修复] 项目 ${projectId} 空间数据修复完成,共 ${unifiedSpaces.length} 个空间`); console.log(`🔧 [数据修复] 修复的空间:`, unifiedSpaces.map(s => s.name)); } catch (error) { console.error('❌ [数据修复] 修复空间数据失败:', error); throw error; } } /** * 调试方法:检查项目的空间数据存储情况 */ async debugProjectSpaceData(projectId: string): Promise { try { console.log(`🔍 [调试] 检查项目 ${projectId} 的空间数据存储情况...`); // 1. 检查Project.data.unifiedSpaces const projectQuery = new Parse.Query('Project'); const project = await projectQuery.get(projectId); const data = project.get('data') || {}; console.log(`📊 [调试] Project.data.unifiedSpaces:`, data.unifiedSpaces?.length || 0, '个空间'); if (data.unifiedSpaces && data.unifiedSpaces.length > 0) { console.log(` - 统一空间列表:`, data.unifiedSpaces.map((s: any) => s.name)); } // 2. 检查Project.data.quotation.spaces console.log(`📊 [调试] Project.data.quotation.spaces:`, data.quotation?.spaces?.length || 0, '个空间'); if (data.quotation?.spaces && data.quotation.spaces.length > 0) { console.log(` - 报价空间列表:`, data.quotation.spaces.map((s: any) => s.name)); } // 3. 检查Product表 const productSpaces = await this.getProjectProductSpaces(projectId); console.log(`📊 [调试] Product表:`, productSpaces.length, '个空间'); if (productSpaces.length > 0) { console.log(` - Product空间列表:`, productSpaces.map(s => s.name)); } // 4. 数据一致性检查 const unifiedCount = data.unifiedSpaces?.length || 0; const quotationCount = data.quotation?.spaces?.length || 0; const productCount = productSpaces.length; console.log(`🔍 [调试] 数据一致性检查:`); console.log(` - 统一存储: ${unifiedCount} 个`); console.log(` - 报价数据: ${quotationCount} 个`); console.log(` - Product表: ${productCount} 个`); if (unifiedCount === quotationCount && quotationCount === productCount) { console.log(`✅ [调试] 数据一致性检查通过`); } else { console.log(`❌ [调试] 数据不一致,需要修复`); } } catch (error) { console.error('❌ [调试] 检查项目空间数据失败:', error); } } }