2025102216-team-leader-database-integration.md 20 KB

组长端数据库接入完整指南

日期: 2025-10-22 16:00
任务: 为组长端接入真实数据库,实现增删查改功能
状态: ✅ 已完成


一、概述

1.1 背景

根据项目规则文档(.trae/rules/project_rules.md)和数据库文档(docs/Database/database-tables-overview.md),为组长端实现完整的数据库接入,使组长端能够:

  • 📊 查看真实的项目数据
  • 👥 管理设计师信息
  • ✏️ 分配项目给设计师
  • 📈 查看实时统计数据
  • 🔄 执行增删查改操作

1.2 技术栈

  • 数据库: Parse Server (MongoDB)
  • 数据服务: fmode-ng/parse (FmodeParse)
  • 多租户: 通过 company 字段隔离
  • 软删除: 使用 isDeleted 字段

二、数据服务架构

2.1 服务层设计

创建了3个核心数据服务:

src/app/pages/team-leader/services/
├── project-data.service.ts      # 项目数据服务(增删查改)
├── dashboard-data.service.ts    # 仪表盘数据服务(统计)
└── designer.service.ts          # 设计师服务(已完善)

2.2 服务职责

服务 职责 主要方法
ProjectDataService 项目增删查改 getProjects, createProject, updateProject, deleteProject, assignProject
DashboardDataService 统计数据 getKPIStats, getStageDistribution, getDesignerWorkloadDistribution, getTodoTasks
DesignerService 设计师管理 getDesigners, getDesignerWorkload, updateDesignerTags, getRecommendedDesigners

三、ProjectDataService(项目数据服务)

3.1 初始化

constructor() {
  this.cid = localStorage.getItem('company') || '';
  console.log('🏢 ProjectDataService初始化,当前公司ID:', this.cid);
  this.initParse();
}

private async initParse(): Promise<void> {
  const { FmodeParse } = await import('fmode-ng/parse');
  this.Parse = FmodeParse.with("nova");
}

关键点:

  • localStorage.getItem('company') 获取公司ID
  • 使用 FmodeParse.with("nova") 初始化Parse连接
  • 延迟加载,避免阻塞应用启动

3.2 查询操作

3.2.1 获取所有项目

async getProjects(filters?: {
  status?: string;
  assignee?: string;
  currentStage?: string;
  searchTerm?: string;
}): Promise<any[]>

功能:

  • 查询当前公司的所有项目
  • 支持按状态、设计师、阶段筛选
  • 支持关键词搜索(项目名称、客户名称)
  • 自动关联客户和设计师信息

示例:

// 获取所有进行中的项目
const projects = await projectDataService.getProjects({
  status: '进行中'
});

// 获取某个设计师的项目
const designerProjects = await projectDataService.getProjects({
  assignee: 'designerId123'
});

// 搜索项目
const searchResults = await projectDataService.getProjects({
  searchTerm: '现代简约'
});

3.2.2 根据ID获取项目

async getProjectById(projectId: string): Promise<any>

功能:

  • 查询单个项目详情
  • 包含客户、设计师、部门关联信息

3.2.3 获取设计师的项目列表

async getProjectsByDesigner(designerId: string): Promise<any[]>

功能:

  • 查询设计师负责的所有项目
  • 自动排除已删除项目

3.2.4 获取超期项目

async getOverdueProjects(): Promise<any[]>

功能:

  • 查询所有超期项目
  • 按超期时间倒序排列

3.2.5 获取临期项目

async getDueSoonProjects(): Promise<any[]>

功能:

  • 查询7天内到期的项目
  • 按截止时间正序排列

3.3 创建操作

3.3.1 创建项目

async createProject(projectData: {
  title: string;
  customer: string;     // ContactInfo ID
  assignee?: string;    // Profile ID
  status?: string;
  currentStage?: string;
  deadline?: Date;
  data?: any;
}): Promise<any>

功能:

  • 创建新项目
  • 自动设置公司ID
  • 初始化阶段历史记录
  • 返回创建后的项目

示例:

const newProject = await projectDataService.createProject({
  title: '李总现代简约全案',
  customer: 'contactId123',
  assignee: 'designerId456',
  status: '进行中',
  currentStage: '方案深化',
  deadline: new Date('2024-12-31'),
  data: {
    totalBudget: 120000,
    tags: ['全案设计', '现代简约']
  }
});

3.4 更新操作

3.4.1 更新项目

async updateProject(projectId: string, updates: {
  title?: string;
  status?: string;
  currentStage?: string;
  assignee?: string;
  deadline?: Date;
  data?: any;
}): Promise<boolean>

功能:

  • 更新项目字段
  • 更新阶段时自动记录阶段历史
  • 支持部分更新

示例:

// 更新项目状态
await projectDataService.updateProject('projectId123', {
  status: '已完成',
  currentStage: '售后归档'
});

// 延长截止时间
await projectDataService.updateProject('projectId123', {
  deadline: new Date('2025-01-15')
});

3.4.2 分配项目

async assignProject(projectId: string, designerId: string): Promise<boolean>

功能:

  • 分配项目给设计师
  • 自动更新状态为"进行中"
  • 自动更新阶段为"方案深化"

示例:

await projectDataService.assignProject('projectId123', 'designerId456');

3.4.3 更新项目阶段

async updateProjectStage(projectId: string, stage: string): Promise<boolean>

功能:

  • 单独更新项目阶段
  • 自动记录阶段历史

3.5 删除操作

3.5.1 软删除项目

async deleteProject(projectId: string): Promise<boolean>

功能:

  • 软删除项目(设置 isDeleted = true
  • 项目仍保留在数据库中
  • 查询时会自动过滤

示例:

await projectDataService.deleteProject('projectId123');

3.5.2 物理删除项目(谨慎使用)

async destroyProject(projectId: string): Promise<boolean>

功能:

  • 物理删除项目
  • 永久删除,无法恢复
  • 仅在必要时使用

3.6 统计查询

3.6.1 获取项目统计

async getProjectStats(): Promise<{
  total: number;
  inProgress: number;
  completed: number;
  overdue: number;
  dueSoon: number;
  unassigned: number;
}>

功能:

  • 统计项目数量
  • 包含总数、进行中、已完成、超期、临期、未分配

四、DashboardDataService(仪表盘数据服务)

4.1 获取KPI统计

async getKPIStats(): Promise<{
  totalProjects: number;
  inProgressProjects: number;
  completedProjects: number;
  overdueProjects: number;
  dueSoonProjects: number;
  totalDesigners: number;
  availableDesigners: number;
  busyDesigners: number;
  overloadedDesigners: number;
}>

功能:

  • 项目统计:总数、进行中、已完成、超期、临期
  • 设计师统计:总数、空闲、忙碌、超负荷

使用示例:

const kpi = await dashboardDataService.getKPIStats();
console.log(`总项目数: ${kpi.totalProjects}`);
console.log(`超期项目: ${kpi.overdueProjects}`);
console.log(`空闲设计师: ${kpi.availableDesigners}`);

4.2 获取项目阶段分布

async getStageDistribution(): Promise<Record<string, number>>

功能:

  • 统计各阶段的项目数量
  • 返回 { '订单分配': 5, '方案深化': 10, ... }

4.3 获取设计师负载分布

async getDesignerWorkloadDistribution(): Promise<{
  idle: number;
  busy: number;
  overload: number;
}>

功能:

  • 统计设计师负载情况
  • 空闲(0个项目)、忙碌(1-2个项目)、超负荷(≥3个项目)

4.4 获取待办任务

async getTodoTasks(): Promise<any[]>

功能:

  • 自动生成待办任务列表
  • 包含:待分配项目、超期项目、临期项目
  • 按优先级排序

返回数据结构:

{
  id: string;              // 任务ID
  title: string;           // 任务标题
  description: string;     // 任务描述
  deadline: Date;          // 截止时间
  priority: 'high' | 'medium' | 'low';  // 优先级
  type: 'assign' | 'review' | 'performance';  // 任务类型
  targetId: string;        // 关联项目ID
}

五、DesignerService(设计师服务)

5.1 获取设计师列表

async getDesigners(): Promise<any[]>

功能:

  • 查询所有设计师(roleName = '组员')
  • 包含部门信息
  • 包含设计师tags(技能、容量、历史等)

返回数据结构:

{
  id: string;
  name: string;
  mobile: string;
  department: string;
  departmentId: string;
  tags: DesignerTags;  // 设计师画像
  data: any;
  profile: ParseObject;
}

5.2 更新设计师Tags

async updateDesignerTags(designerId: string, tags: Partial<DesignerTags>): Promise<boolean>

功能:

  • 更新设计师的技能、容量、紧急单偏好等
  • 支持部分更新(深度合并)

示例:

await designerService.updateDesignerTags('designerId123', {
  expertise: {
    styles: ['现代简约', '北欧风'],
    skills: ['建模', '渲染', '后期'],
    spaceTypes: ['客厅', '卧室']
  },
  capacity: {
    weeklyProjects: 3,
    maxConcurrent: 5,
    avgDaysPerProject: 7
  }
});

5.3 获取设计师负载

async getDesignerWorkload(designerId: string): Promise<{
  projects: any[];
  weightedTotal: number;
  overdueCount: number;
  loadRate: number;
}>

功能:

  • 查询设计师当前负责的项目
  • 计算加权负载总量
  • 计算超期项目数
  • 计算负载率

加权算法:

项目权重 = 类型系数 × 时间系数 × 紧急度系数

类型系数: 硬装=2.0, 软装=1.0
时间系数: 超期=1.5, 临期(≤3天)=1.3, 正常(≤7天)=1.0, 充裕=0.8
紧急度系数: 高=1.2, 中=1.0, 低=0.8

5.4 智能推荐设计师

async getRecommendedDesigners(project: any): Promise<any[]>

功能:

  • 根据项目需求推荐最合适的设计师
  • 综合考虑:风格匹配、负载情况、历史表现、紧急单偏好
  • 返回排序后的推荐列表(带匹配度分数)

评分规则:

总分 = 风格匹配分 × 40% + 负载适配分 × 30% + 历史表现分 × 20% + 紧急加分 × 10%

风格匹配分: 0-100(基于技能、风格、空间类型匹配度)
负载适配分: 0-100(负载率越低分数越高)
历史表现分: 0-100(完成率、评分、按时率)
紧急加分: 0-20(是否接受紧急单)

六、数据转换与兼容

6.1 Parse对象转换

所有数据服务都提供了 transformProject 等方法,将Parse对象转换为前端友好的格式:

private transformProject(project: any): any {
  return {
    id: project.id,
    name: project.get('title'),
    title: project.get('title'),
    customerName: customer?.get('name') || '未知客户',
    designerName: assignee?.get('name') || '未分配',
    status: project.get('status'),
    currentStage: project.get('currentStage'),
    deadline: project.get('deadline'),
    isOverdue: boolean,
    overdueDays: number,
    dueSoon: boolean,
    urgency: 'high' | 'medium' | 'low',
    // ... 更多字段
  };
}

6.2 日期处理

Parse返回的日期可能是 Date 对象或字符串,统一转换:

const deadline = deadlineRaw instanceof Date 
  ? deadlineRaw 
  : (deadlineRaw ? new Date(deadlineRaw) : null);

6.3 Pointer处理

创建Pointer引用:

const Profile = Parse.Object.extend('Profile');
const assignee = new Profile();
assignee.id = designerId;
project.set('assignee', assignee);

七、在Dashboard中使用

7.1 注入服务

import { ProjectDataService } from '../services/project-data.service';
import { DashboardDataService } from '../services/dashboard-data.service';
import { DesignerService } from '../services/designer.service';

export class Dashboard {
  constructor(
    private projectDataService: ProjectDataService,
    private dashboardDataService: DashboardDataService,
    private designerService: DesignerService
  ) {}
}

7.2 加载数据

async ngOnInit() {
  // 加载KPI数据
  const kpi = await this.dashboardDataService.getKPIStats();
  this.totalProjects = kpi.totalProjects;
  this.overdueCount = kpi.overdueProjects;
  
  // 加载项目列表
  this.projects = await this.projectDataService.getProjects();
  
  // 加载设计师列表
  this.designers = await this.designerService.getDesigners();
  
  // 加载待办任务
  this.todoTasks = await this.dashboardDataService.getTodoTasks();
}

7.3 分配项目

async onAssignProject(projectId: string, designerId: string) {
  const success = await this.projectDataService.assignProject(
    projectId, 
    designerId
  );
  
  if (success) {
    // 刷新项目列表
    this.projects = await this.projectDataService.getProjects();
    console.log('✅ 项目分配成功');
  }
}

7.4 更新项目状态

async onUpdateStatus(projectId: string, newStatus: string) {
  const success = await this.projectDataService.updateProject(
    projectId,
    { status: newStatus }
  );
  
  if (success) {
    // 刷新列表
    this.loadProjects();
  }
}

7.5 智能推荐

async onSmartRecommend(project: any) {
  this.recommendations = await this.designerService.getRecommendedDesigners(
    project
  );
  this.showSmartMatch = true;
}

八、数据库查询最佳实践

8.1 总是过滤已删除数据

query.notEqualTo('isDeleted', true);

8.2 总是过滤公司数据

query.equalTo('company', this.cid);

8.3 使用include减少查询次数

query.include('customer', 'assignee', 'assignee.department');

8.4 限制查询数量

query.limit(1000);  // 避免一次查询过多数据

8.5 使用索引字段

优先使用已建立索引的字段进行查询:

  • company + isDeleted
  • assignee + status
  • customer + isDeleted
  • currentStage + status
  • deadline

九、错误处理

9.1 Parse未初始化

const Parse = await this.ensureParse();
if (!Parse) {
  console.error('❌ Parse未初始化');
  return defaultValue;
}

9.2 查询失败

try {
  const projects = await query.find();
  return projects;
} catch (error) {
  console.error('❌ 查询失败:', error);
  return [];
}

9.3 更新失败

try {
  await project.save();
  console.log('✅ 更新成功');
  return true;
} catch (error) {
  console.error('❌ 更新失败:', error);
  return false;
}

十、数据初始化

10.1 设置公司ID

确保 localStorage 中有 company 字段:

localStorage.setItem('company', 'your-company-id');

10.2 测试数据创建

如果数据库为空,可以通过以下方式创建测试数据:

  1. 创建公司:

    const Company = Parse.Object.extend('Company');
    const company = new Company();
    company.set('name', '测试公司');
    await company.save();
    
  2. 创建部门:

    const Department = Parse.Object.extend('Department');
    const dept = new Department();
    dept.set('name', '设计一组');
    dept.set('type', 'project');
    dept.set('company', company.toPointer());
    await dept.save();
    
  3. 创建设计师:

    const Profile = Parse.Object.extend('Profile');
    const designer = new Profile();
    designer.set('name', '张三');
    designer.set('roleName', '组员');
    designer.set('company', company.toPointer());
    designer.set('department', dept.toPointer());
    await designer.save();
    
  4. 创建客户:

    const ContactInfo = Parse.Object.extend('ContactInfo');
    const contact = new ContactInfo();
    contact.set('name', '李总');
    contact.set('mobile', '13800138000');
    contact.set('company', company.toPointer());
    await contact.save();
    
  5. 创建项目:

    const project = await projectDataService.createProject({
    title: '李总现代简约全案',
    customer: contact.id,
    assignee: designer.id,
    status: '进行中',
    currentStage: '方案深化',
    deadline: new Date('2024-12-31')
    });
    

十一、服务文件清单

11.1 新增文件

文件 行数 说明
src/app/pages/team-leader/services/project-data.service.ts 579 项目数据服务(增删查改)
src/app/pages/team-leader/services/dashboard-data.service.ts 256 仪表盘数据服务(统计)
docs/task/2025102216-team-leader-database-integration.md - 本文档

11.2 更新文件

文件 变更 说明
src/app/pages/team-leader/services/designer.service.ts +44行 新增 updateDesignerTags 方法,优化 getDesigners 方法

十二、测试清单

12.1 ProjectDataService测试

  • getProjects() - 获取项目列表
  • getProjects({ status: '进行中' }) - 按状态筛选
  • getProjects({ assignee: 'xxx' }) - 按设计师筛选
  • getProjects({ searchTerm: '现代' }) - 关键词搜索
  • getProjectById() - 获取单个项目
  • createProject() - 创建项目
  • updateProject() - 更新项目
  • assignProject() - 分配项目
  • deleteProject() - 删除项目
  • getProjectStats() - 项目统计

12.2 DashboardDataService测试

  • getKPIStats() - KPI统计
  • getStageDistribution() - 阶段分布
  • getDesignerWorkloadDistribution() - 负载分布
  • getTodoTasks() - 待办任务

12.3 DesignerService测试

  • getDesigners() - 设计师列表
  • updateDesignerTags() - 更新tags
  • getDesignerWorkload() - 设计师负载
  • getRecommendedDesigners() - 智能推荐

十三、常见问题

Q1: 为什么查询不到数据?

A: 检查以下几点:

  1. localStorage.getItem('company') 是否正确
  2. 数据库中是否有对应公司的数据
  3. 数据的 isDeleted 是否为 false
  4. Parse连接是否正常初始化

Q2: 如何调试查询?

A: 在服务中添加详细日志:

console.log('🔍 查询条件:', {
  company: this.cid,
  status: filters?.status,
  assignee: filters?.assignee
});
const projects = await query.find();
console.log('✅ 查询结果:', projects.length, '个项目');

Q3: Pointer字段如何处理?

A: 创建Pointer时使用:

const Profile = Parse.Object.extend('Profile');
const pointer = new Profile();
pointer.id = 'profileId';
project.set('assignee', pointer);

读取Pointer时使用 include:

query.include('assignee');
const assignee = project.get('assignee');
console.log(assignee.get('name'));

Q4: 如何处理日期?

A: 统一转换为Date对象:

const deadline = project.get('deadline');
const date = deadline instanceof Date 
  ? deadline 
  : new Date(deadline);

十四、下一步

14.1 待实现功能

  • 实时数据更新(Parse LiveQuery)
  • 批量操作(批量分配、批量删除)
  • 数据缓存(减少查询次数)
  • 离线支持(本地数据库)

14.2 性能优化

  • 分页查询(避免一次加载过多数据)
  • 虚拟滚动(长列表优化)
  • 查询去抖动(防止频繁查询)
  • 数据预加载(提前加载可能用到的数据)

14.3 功能扩展

  • 项目文件管理(ProjectFile)
  • 空间产品管理(Product)
  • 付款记录管理(ProjectPayment)
  • 反馈管理(ProjectFeedback)
  • 问题追踪(ProjectIssue)

十五、总结

15.1 核心成果

3个数据服务: ProjectDataService、DashboardDataService、DesignerService
完整的增删查改: 项目、设计师、统计数据
智能推荐算法: 基于多维度匹配的设计师推荐
数据转换层: Parse对象与前端格式的无缝转换
错误处理: 完善的异常捕获和日志记录

15.2 技术要点

  • 🔐 多租户隔离: 通过 company 字段
  • 🗑️ 软删除: 使用 isDeleted 字段
  • 🔗 关联查询: 使用 include 减少请求
  • 📊 数据统计: 实时计算KPI和分布
  • 🤖 智能算法: 加权负载计算和推荐评分

15.3 使用建议

  1. 确保正确设置公司ID
  2. 优先使用索引字段查询
  3. 合理使用 include 关联数据
  4. 总是处理异常情况
  5. 添加详细的日志记录

文档版本: v1.0
最后更新: 2025-10-22 16:00
作者: AI Assistant
状态: ✅ 完成