2025102221-simple-solution.md 8.6 KB

简单方案:通过 department.leader 显示负责人

日期: 2025-10-24
核心思路: 不修改数据库,只通过 department.leader 动态获取组长信息


🎯 方案对比

方案1: 在 Project 表存储 assignee(之前的方案)

Project表
├─ assignee: Pointer<Profile>  ← 需要迁移数据
├─ department: Pointer<Department>
└─ ...

缺点:

  • ❌ 需要迁移现有项目数据
  • ❌ 数据冗余(assignee 和 department.leader 重复)
  • ❌ 数据一致性问题(组长变更时需要同步更新)

方案2: 动态从 department.leader 获取(推荐)✅

Project表
├─ department: Pointer<Department>  ← 只需要这个
└─ ...

Department表
├─ leader: Pointer<Profile>  ← 从这里获取组长
└─ ...

优点:

  • 不需要迁移数据
  • ✅ 数据一致性好(组长只存在一个地方)
  • ✅ 逻辑清晰(项目属于项目组,项目组有组长)

✅ 实现方案

修改1: 确保查询时包含 department.leader

文件: src/app/pages/admin/services/project.service.ts

已修改(第27行):

async findProjects(options?: {
  status?: string;
  keyword?: string;
  skip?: number;
  limit?: number;
}): Promise<FmodeObject[]> {
  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行):

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 字段

检查方法(在浏览器控制台执行):

(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,执行以下脚本分配默认项目组:

(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,执行:

(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 表(需要的字段)

{
  objectId: "APwk78jnrh",
  title: "张家界凤凰城三期项目",
  department: Pointer<Department> { objectId: "xxx" },  // ✅ 只需要这个
  company: "cDL6R1hgSi",
  status: "进行中",
  // 不需要 assignee 字段
}

Department 表

{
  objectId: "xxx",
  name: "汪奥组",
  type: "project",
  leader: Pointer<Profile> { objectId: "yyy" },  // ✅ 组长信息存在这里
  company: "cDL6R1hgSi"
}

Profile 表

{
  objectId: "yyy",
  name: "汪奥",
  roleName: "组长",
  company: "cDL6R1hgSi"
}

🎉 优势总结

方案2(推荐)vs 方案1(之前)

对比项 方案1(存储assignee) 方案2(使用leader)✅
需要迁移数据 ❌ 是 ✅ 否
数据一致性 ❌ 可能不一致 ✅ 始终一致
逻辑复杂度 ❌ 较复杂 ✅ 简单
查询性能 ✅ 稍快 ✅ 同样快
维护成本 ❌ 高 ✅ 低

💡 为什么这个方案更好?

  1. 逻辑清晰

    • 项目 → 属于项目组
    • 项目组 → 有组长
    • 项目的负责人 = 项目组的组长
  2. 数据一致性

    • 组长信息只存在一个地方(Department.leader)
    • 组长变更时,所有项目的负责人自动更新
  3. 无需迁移

    • 只需确保项目有 department 字段
    • 不需要为每个项目设置 assignee 字段
  4. 符合业务逻辑

    • 项目负责人就是项目组的组长
    • 不需要额外的 assignee 字段

现在执行检查脚本,看看是否所有项目都有 department! 🚀