# 简单方案:通过 department.leader 显示负责人 **日期**: 2025-10-24 **核心思路**: 不修改数据库,只通过 `department.leader` 动态获取组长信息 --- ## 🎯 方案对比 ### 方案1: 在 Project 表存储 assignee(之前的方案) ``` Project表 ├─ assignee: Pointer ← 需要迁移数据 ├─ department: Pointer └─ ... ``` **缺点**: - ❌ 需要迁移现有项目数据 - ❌ 数据冗余(assignee 和 department.leader 重复) - ❌ 数据一致性问题(组长变更时需要同步更新) --- ### 方案2: 动态从 department.leader 获取(推荐)✅ ``` Project表 ├─ department: Pointer ← 只需要这个 └─ ... Department表 ├─ leader: Pointer ← 从这里获取组长 └─ ... ``` **优点**: - ✅ **不需要迁移数据** - ✅ 数据一致性好(组长只存在一个地方) - ✅ 逻辑清晰(项目属于项目组,项目组有组长) --- ## ✅ 实现方案 ### 修改1: 确保查询时包含 department.leader **文件**: `src/app/pages/admin/services/project.service.ts` 已修改(第27行): ```typescript async findProjects(options?: { status?: string; keyword?: string; skip?: number; limit?: number; }): Promise { return await this.adminData.findAll('Project', { include: ['customer', 'assignee', 'department', 'department.leader'], // ✅ 包含leader skip: options?.skip || 0, limit: options?.limit || 20, descending: 'updatedAt', // ... }); } ``` --- ### 修改2: toJSON 方法优先使用 department.leader **文件**: `src/app/pages/admin/services/project.service.ts` 已修改(第243-256行): ```typescript toJSON(project: FmodeObject): any { const json = this.adminData.toJSON(project); // 处理客户 if (json.customer && typeof json.customer === 'object') { json.customerName = json.customer.name || ''; json.customerId = json.customer.objectId; } // ✅ 处理负责人:优先使用assignee,如果为空则使用department.leader if (json.assignee && typeof json.assignee === 'object') { // 如果有明确指定的assignee,使用它 json.assigneeName = json.assignee.name || ''; json.assigneeId = json.assignee.objectId; json.assigneeRole = json.assignee.roleName || ''; } else if (json.department && typeof json.department === 'object') { // 如果没有assignee,使用department的leader(组长) const leader = json.department.leader; if (leader && typeof leader === 'object') { json.assigneeName = leader.name || ''; json.assigneeId = leader.objectId; json.assigneeRole = '组长'; // ✅ 标记为组长 } } return json; } ``` --- ## 🔧 需要做的事情 ### 唯一需要做的:确保项目有 department 字段 **检查方法**(在浏览器控制台执行): ```javascript (async function() { const Parse = window.FmodeParse.with('nova'); const company = localStorage.getItem('company'); const query = new Parse.Query('Project'); query.equalTo('company', company); query.notEqualTo('isDeleted', true); query.include('department', 'department.leader'); query.limit(20); const projects = await query.find(); let hasDept = 0; let noDept = 0; projects.forEach(p => { if (p.get('department')) { hasDept++; } else { noDept++; } }); console.log(`✅ 有department: ${hasDept} 个`); console.log(`❌ 没有department: ${noDept} 个`); if (noDept > 0) { console.log('\n⚠️ 有项目没有department,需要分配项目组'); } else { console.log('\n🎉 所有项目都有department,可以直接显示组长!'); } })(); ``` --- ### 如果有项目没有 department,执行以下脚本分配默认项目组: ```javascript (async function() { const Parse = window.FmodeParse.with('nova'); const company = localStorage.getItem('company'); console.log('🚀 开始为项目分配默认项目组...'); // 1. 查找没有department的项目 const projectQuery = new Parse.Query('Project'); projectQuery.equalTo('company', company); projectQuery.notEqualTo('isDeleted', true); projectQuery.doesNotExist('department'); projectQuery.limit(1000); const projects = await projectQuery.find(); console.log(`📊 找到 ${projects.length} 个没有项目组的项目`); if (projects.length === 0) { console.log('✅ 所有项目都已有项目组!'); return; } // 2. 获取默认项目组(第一个项目组) const deptQuery = new Parse.Query('Department'); deptQuery.equalTo('company', company); deptQuery.equalTo('type', 'project'); deptQuery.notEqualTo('isDeleted', true); deptQuery.include('leader'); deptQuery.ascending('createdAt'); const dept = await deptQuery.first(); if (!dept) { console.error('❌ 没有找到项目组,请先创建项目组'); return; } const leader = dept.get('leader'); console.log(`✅ 使用默认项目组: ${dept.get('name')}, 组长: ${leader?.get('name') || '无'}`); // 3. 批量分配 let success = 0; let failed = 0; for (let i = 0; i < projects.length; i++) { const project = projects[i]; const title = project.get('title') || '未命名项目'; try { project.set('department', dept); await project.save(); success++; console.log(`✅ [${i+1}/${projects.length}] "${title}" 已分配到 ${dept.get('name')}`); } catch (error) { failed++; console.error(`❌ [${i+1}/${projects.length}] "${title}" 失败:`, error); } } console.log('\n' + '='.repeat(60)); console.log('🎉 分配完成!'); console.log('='.repeat(60)); console.log(`📊 总计: ${projects.length} | ✅ 成功: ${success} | ❌ 失败: ${failed}`); console.log('='.repeat(60)); console.log('\n💡 请刷新页面(Ctrl+Shift+R)查看结果'); })(); ``` --- ## 🎯 完整流程 ### 步骤1: 检查项目状态 打开 `http://localhost:4200/admin/project-management`,按 F12,执行: ```javascript (async function() { const Parse = window.FmodeParse.with('nova'); const company = localStorage.getItem('company'); const query = new Parse.Query('Project'); query.equalTo('company', company); query.include('department', 'department.leader'); query.limit(20); const projects = await query.find(); console.table(projects.map(p => ({ '项目名称': p.get('title'), '有项目组': p.get('department') ? '✅' : '❌', '组长': p.get('department')?.get('leader')?.get('name') || '无' }))); })(); ``` --- ### 步骤2: 如果有项目缺少 department,执行分配脚本 复制上面的"为项目分配默认项目组"脚本,粘贴到控制台执行。 --- ### 步骤3: 刷新页面验证 按 `Ctrl+Shift+R` 刷新页面,查看项目列表的"负责人"列。 **预期结果**: - 所有项目的"负责人"列应显示组长名字 - 不再显示"未分配" --- ## 📊 数据结构 ### Project 表(需要的字段) ```javascript { objectId: "APwk78jnrh", title: "张家界凤凰城三期项目", department: Pointer { objectId: "xxx" }, // ✅ 只需要这个 company: "cDL6R1hgSi", status: "进行中", // 不需要 assignee 字段 } ``` ### Department 表 ```javascript { objectId: "xxx", name: "汪奥组", type: "project", leader: Pointer { objectId: "yyy" }, // ✅ 组长信息存在这里 company: "cDL6R1hgSi" } ``` ### Profile 表 ```javascript { objectId: "yyy", name: "汪奥", roleName: "组长", company: "cDL6R1hgSi" } ``` --- ## 🎉 优势总结 ### 方案2(推荐)vs 方案1(之前) | 对比项 | 方案1(存储assignee) | 方案2(使用leader)✅ | |--------|---------------------|---------------------| | 需要迁移数据 | ❌ 是 | ✅ 否 | | 数据一致性 | ❌ 可能不一致 | ✅ 始终一致 | | 逻辑复杂度 | ❌ 较复杂 | ✅ 简单 | | 查询性能 | ✅ 稍快 | ✅ 同样快 | | 维护成本 | ❌ 高 | ✅ 低 | --- ## 💡 为什么这个方案更好? 1. **逻辑清晰** - 项目 → 属于项目组 - 项目组 → 有组长 - 项目的负责人 = 项目组的组长 2. **数据一致性** - 组长信息只存在一个地方(Department.leader) - 组长变更时,所有项目的负责人自动更新 3. **无需迁移** - 只需确保项目有 `department` 字段 - 不需要为每个项目设置 `assignee` 字段 4. **符合业务逻辑** - 项目负责人就是项目组的组长 - 不需要额外的 `assignee` 字段 --- **现在执行检查脚本,看看是否所有项目都有 department!** 🚀