# 空间分配功能真实数据集成完成 **日期**: 2025-10-25 **状态**: ✅ 已完成 ## 📋 任务概述 在设计师分配弹窗中集成真实的空间数据,从 Parse Server 的 **Product 表**自动加载项目的空间场景数据,实现真实的空间分配功能。 ## 🎯 核心需求 1. ✅ 保留原有的空间分配UI和交互逻辑 2. ✅ 从 Product 表自动加载项目的空间数据 3. ✅ 将空间分配结果保存到 ProjectTeam 表 4. ✅ 遵循 Parse Server 数据范式(`rules/schemas.md`) 5. ✅ 添加加载状态、错误提示、空状态显示 ## 🏗️ 数据架构 ### Product 表结构(空间管理核心) 根据 `rules/schemas.md`,**Product 表是空间管理的核心**: ```javascript Product { objectId: 'prod001', project: Pointer, // 所属项目 profile: Pointer, // 负责设计师 productName: '主卧设计', // 空间名称 productType: 'bedroom', // 空间类型 status: 'in_progress', // 产品状态 space: { // 空间信息 spaceName: '主卧', area: 18.5, dimensions: { length: 4.5, width: 4.1, height: 2.8 }, features: ['朝南', '飘窗', '独立卫浴'] }, quotation: { /* 报价信息 */ }, requirements: { /* 设计需求 */ }, order: 1, // 排序 isDeleted: false } ``` ### ProjectTeam 表结构(团队分配) ```javascript ProjectTeam { objectId: 'team001', project: Pointer, // 所属项目 profile: Pointer, // 设计师 role: '设计师', // 角色 workload: 70, // 工作负载 data: { assignedSpaces: ['prod001', 'prod002'], // 负责的空间Product ID列表 assignedDate: '2025-10-25' }, isDeleted: false } ``` ## 🔧 核心实现 ### 1. 新增服务依赖 ```typescript import { ProductSpaceService, Project as ProductSpace } from '../../../../../../modules/project/services/product-space.service'; constructor( private cdr: ChangeDetectorRef, private productSpaceService: ProductSpaceService ) {} ``` ### 2. 新增输入属性 ```typescript @Input() loadRealSpaces: boolean = true; // 是否自动加载真实空间数据 ``` ### 3. 新增状态属性 ```typescript private parseProducts: ProductSpace[] = []; // 项目的产品空间列表 loadingSpaces = false; // 空间加载状态 spaceLoadError = ''; // 空间加载错误 ``` ### 4. 加载真实空间数据 #### `loadRealProjectSpaces()` 方法 ```typescript async loadRealProjectSpaces() { if (!this.projectId) { console.warn('未提供projectId,无法加载空间数据'); return; } try { this.loadingSpaces = true; this.spaceLoadError = ''; // 使用ProductSpaceService查询项目的所有空间产品 this.parseProducts = await this.productSpaceService .getProjectProductSpaces(this.projectId); if (this.parseProducts.length === 0) { console.warn('未找到项目空间数据'); this.spaceLoadError = '未找到项目空间数据'; return; } // 转换为SpaceScene格式 this.spaceScenes = this.parseProducts.map(product => ({ id: product.id, // Product.objectId name: product.name, // Product.productName area: product.area, // Product.space.area description: this.getProductDescription(product) })); console.log('成功加载项目空间数据:', this.spaceScenes); } catch (err) { console.error('加载项目空间数据失败:', err); this.spaceLoadError = '加载项目空间数据失败'; } finally { this.loadingSpaces = false; this.cdr.markForCheck(); } } ``` #### `getProductDescription()` 方法 生成空间的描述信息: ```typescript private getProductDescription(product: ProductSpace): string { const parts: string[] = []; // 产品类型映射 if (product.type) { const typeMap: Record = { 'living_room': '客厅', 'bedroom': '卧室', 'kitchen': '厨房', 'bathroom': '卫生间', 'study': '书房', 'dining_room': '餐厅', 'balcony': '阳台', 'entrance': '玄关', 'other': '其他' }; parts.push(typeMap[product.type] || product.type); } // 面积 if (product.area) { parts.push(`${product.area}㎡`); } // 状态映射 if (product.status) { const statusMap: Record = { 'pending': '待开始', 'in_progress': '进行中', 'completed': '已完成', 'on_hold': '暂停中' }; parts.push(statusMap[product.status] || product.status); } // 自定义描述 if (product.metadata?.description) { parts.push(product.metadata.description); } return parts.join(' · ') || '暂无描述'; } ``` ### 5. UI 状态显示 #### 加载状态 ```html @if (loadingSpaces) {
正在加载空间数据...
} ``` #### 错误状态 ```html @if (spaceLoadError && !loadingSpaces) {
{{ spaceLoadError }}
} ``` #### 空状态 ```html @if (spaceScenes.length === 0) {

该项目暂无空间数据

请先在项目中创建空间产品(Product)
} ``` #### 空间列表 ```html @for (space of spaceScenes; track space.id) { } ``` ### 6. 样式增强 新增了以下样式类: - `.space-loading` - 加载状态样式 - `.space-error` - 错误提示样式 - `.space-empty` - 空状态样式 - `@keyframes spin` - 加载动画 ## 📊 数据流程 ### 完整数据流 ``` 1. 用户打开设计师分配弹窗 ↓ 2. 弹窗初始化 (ngOnInit) ├─ loadRealProjectTeams() - 加载项目组和成员 └─ loadRealProjectSpaces() - 加载项目空间 ↓ 3. ProductSpaceService.getProjectProductSpaces(projectId) ├─ 查询 Product 表 │ WHERE project = projectId │ ORDER BY createdAt ASC └─ 返回 Product[] 列表 ↓ 4. 转换为 SpaceScene[] 格式 { id: product.id, name: product.productName, area: product.space?.area, description: 'bedroom · 18.5㎡ · 进行中' } ↓ 5. 用户选择设计师并分配空间 ├─ 点击设计师卡片的 "🏠" 按钮 ├─ 勾选该设计师负责的空间 └─ 点击"确认" ↓ 6. 生成 DesignerAssignmentResult { selectedDesigners: [Designer[]], spaceAssignments: [ { designerId: 'profile001', designerName: '张设计师', spaceIds: ['prod001', 'prod002'] // Product IDs } ] } ↓ 7. 父组件保存到 ProjectTeam ProjectTeam.data.assignedSpaces = ['prod001', 'prod002'] ``` ### Product 查询示例 ```typescript // ProductSpaceService 中的查询 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(); ``` ### 数据转换示例 ```typescript // Product -> SpaceScene const spaceScene: SpaceScene = { id: 'prod001', // Product.objectId name: '主卧设计', // Product.productName area: 18.5, // Product.space.area description: 'bedroom · 18.5㎡ · 进行中' }; ``` ## 🔄 组件集成 ### team-assign 组件使用 ```html [enableSpaceAssignment]="true" [calendarViewMode]="'month'" [selectedTeamId]="modalSelectedTeamId" (close)="closeDesignerModal()" (confirm)="handleDesignerAssignment($event)" > ``` ### 处理分配结果 ```typescript async handleDesignerAssignment(result: DesignerAssignmentResult) { for (const designer of result.selectedDesigners) { // 查找该设计师负责的空间 const spaceAssignment = result.spaceAssignments.find( sa => sa.designerId === designer.id ); // 保存到ProjectTeam,空间ID列表保存在data.assignedSpaces await this.saveDesignerToTeam( designer, spaceAssignment?.spaceIds || [] ); } } async saveDesignerToTeam(designer: Designer, spaceIds: string[]) { const ProjectTeam = Parse.Object.extend('ProjectTeam'); const teamMember = new ProjectTeam(); teamMember.set('project', this.project.toPointer()); teamMember.set('profile', Parse.Object.extend('Profile') .createWithoutData(designer.id)); teamMember.set('role', '设计师'); teamMember.set('data', { assignedSpaces: spaceIds, // Product IDs assignedDate: new Date().toISOString() }); await teamMember.save(); } ``` ## 🎨 UI/UX 优化 ### 1. 加载体验 - ✅ 显示加载动画和提示文本 - ✅ 禁用交互直到数据加载完成 ### 2. 错误处理 - ✅ 显示友好的错误提示 - ✅ 提供重试机制(刷新弹窗) ### 3. 空状态 - ✅ 清晰的图标和说明文字 - ✅ 引导用户创建空间产品 ### 4. 空间信息展示 - ✅ 显示空间名称(必填) - ✅ 显示面积(可选) - ✅ 显示描述信息(类型、状态等) ## ✅ 功能验证 ### 测试场景 #### 场景 1: 正常加载空间数据 ``` 前提: 项目已创建多个 Product 操作: 打开设计师分配弹窗 预期: - 显示"正在加载空间数据..." - 成功加载后显示所有空间列表 - 每个空间显示名称、面积、描述 ``` #### 场景 2: 项目无空间数据 ``` 前提: 项目未创建任何 Product 操作: 打开设计师分配弹窗 预期: - 显示空状态图标和提示 - 提示"该项目暂无空间数据" - 引导用户创建空间产品 ``` #### 场景 3: 网络错误 ``` 前提: 网络异常或服务器错误 操作: 打开设计师分配弹窗 预期: - 显示错误图标和错误信息 - 错误信息清晰明了 - 可以关闭弹窗重试 ``` #### 场景 4: 空间分配 ``` 前提: 项目有空间数据 操作: 1. 选择设计师 2. 点击 "🏠" 按钮 3. 勾选空间 4. 确认分配 预期: - 空间列表正确显示 - 勾选状态正确更新 - 分配结果正确保存到 ProjectTeam.data.assignedSpaces ``` ## 📝 数据范式遵循 ### Parse Server 数据表 #### Product(空间设计产品表) ```javascript { objectId: String, project: Pointer, profile: Pointer, // 负责设计师 productName: String, // 空间名称 productType: String, // 空间类型 space: { // 空间信息 spaceName: String, area: Number, dimensions: Object, features: Array }, quotation: Object, order: Number, isDeleted: Boolean } ``` #### ProjectTeam(项目团队表) ```javascript { objectId: String, project: Pointer, profile: Pointer, role: String, workload: Number, data: { assignedSpaces: Array, // Product objectId 数组 assignedDate: String }, isDeleted: Boolean } ``` ### 查询规则 1. **查询项目空间** ```typescript query.equalTo('project', projectPointer) query.notEqualTo('isDeleted', true) query.ascending('order') ``` 2. **查询设计师负责的空间** ```typescript query.equalTo('profile', designerPointer) query.notEqualTo('isDeleted', true) ``` 3. **查询项目团队的空间分配** ```typescript const team = await teamQuery.find(); const assignedSpaces = team.get('data')?.assignedSpaces || []; ``` ## 🚀 性能优化 ### 1. 数据缓存 - ✅ 空间数据在弹窗打开时加载一次 - ✅ 缓存在组件生命周期内有效 ### 2. 懒加载 - ✅ 只在 `loadRealSpaces=true` 时加载 - ✅ 只在有 `projectId` 时加载 ### 3. 状态管理 - ✅ 使用 `loadingSpaces` 避免重复加载 - ✅ 使用 `ChangeDetectorRef` 优化检测 ## 📚 相关文件 ### 核心文件 - `src/app/pages/designer/project-detail/components/designer-team-assignment-modal/` - `designer-team-assignment-modal.component.ts` - 组件逻辑 - `designer-team-assignment-modal.component.html` - 模板 - `designer-team-assignment-modal.component.scss` - 样式 ### 服务文件 - `src/modules/project/services/product-space.service.ts` - 空间数据服务 ### 使用示例 - `src/modules/project/components/team-assign/` - 团队分配组件集成 ### 规则文档 - `rules/schemas.md` - Parse Server 数据范式 ## 🎉 完成总结 ### 已实现功能 1. ✅ 从 Product 表自动加载真实空间数据 2. ✅ 将 Product 数据转换为 SpaceScene 格式 3. ✅ 完善的加载、错误、空状态UI 4. ✅ 空间分配结果保存到 ProjectTeam.data.assignedSpaces 5. ✅ 与 team-assign 组件无缝集成 6. ✅ 遵循 Parse Server 数据范式 ### 技术亮点 - 🎯 使用 ProductSpaceService 服务层封装数据访问 - 🎨 完善的UI状态管理和用户体验 - 📊 清晰的数据转换和映射逻辑 - 🔧 灵活的配置选项(loadRealSpaces) - 📝 遵循 Parse Server 数据范式规则 ### 使用方式 #### 启用自动加载(推荐) ```html ``` #### 手动传入空间数据 ```html ``` ## 🔗 相关文档 - [设计师分配弹窗使用指南](../../DESIGNER-MODAL-USAGE-GUIDE.md) - [真实数据集成文档](./20251025-designer-modal-real-data-integration.md) - [Parse Server 数据范式](../../rules/schemas.md)