# 设计师标签体系与智能匹配功能实施方案

**实施日期**: 2025-10-22  
**关联文档**: `2025102210-team-leader-dashboard-optimization.md`

## 一、需求概述

### 1.1 业务背景

组长在分配项目时需要考虑多个维度：
- ❌ **现状问题**：手动查看设计师工作量、擅长领域，效率低
- ✅ **优化目标**：一键智能推荐最合适的设计师，减少50%分配时间

### 1.2 核心功能

| 功能模块 | 说明 | 优先级 |
|---------|------|-------|
| **技术人员标签体系** | 完善Profile.data字段，存储擅长领域、过往案例等 | P0 |
| **智能匹配推荐** | 基于标签、负载、历史表现推荐设计师 | P0 |
| **真实数据对接** | 将dashboard模拟数据改为fmode-ng查询 | P0 |

---

## 二、Profile.data 标签数据结构

### 2.1 完整结构定义

```typescript
interface ProfileData {
  // 基础信息
  avatar?: string;               // 头像URL
  department?: string;           // 部门名称（冗余字段）
  
  // 🎯 **新增：技术人员标签体系**
  tags: {
    // 擅长领域（可多选）
    expertise: {
      styles: string[];          // 擅长风格：['现代', '北欧', '新中式', ...]
      skills: string[];          // 专业技能：['建模', '渲染', '后期', '软装', ...]
      spaceTypes: string[];      // 擅长空间：['卧室', '客厅', '厨房', ...]
    };
    
    // 工作能力
    capacity: {
      weeklyProjects: number;    // 单周可处理项目量（建议值）
      maxConcurrent: number;     // 最大并发项目数
      avgDaysPerProject: number; // 平均完成天数
    };
    
    // 紧急订单意愿
    emergency: {
      willing: boolean;          // 是否接受紧急订单
      premium: number;           // 加急溢价（%，如20表示20%加价）
      maxPerWeek: number;        // 每周最多接受紧急单数
    };
    
    // 过往表现
    history: {
      totalProjects: number;     // 历史总项目数
      completionRate: number;    // 完成率（%）
      avgRating: number;         // 平均评分（1-5）
      onTimeRate: number;        // 按时交付率（%）
      excellentCount: number;    // 优秀作品数
    };
    
    // 案例作品（objectId数组）
    portfolio: string[];         // ProjectFile的objectId数组，展示代表作
  };
  
  // 其他扩展字段
  [key: string]: any;
}
```

### 2.2 数据示例

```json
{
  "avatar": "https://cdn.yss.com/avatars/zhang.jpg",
  "department": "设计一组",
  "tags": {
    "expertise": {
      "styles": ["现代简约", "北欧风格", "极简主义"],
      "skills": ["建模", "渲染", "软装搭配"],
      "spaceTypes": ["卧室", "客厅", "书房"]
    },
    "capacity": {
      "weeklyProjects": 3,
      "maxConcurrent": 5,
      "avgDaysPerProject": 7
    },
    "emergency": {
      "willing": true,
      "premium": 20,
      "maxPerWeek": 2
    },
    "history": {
      "totalProjects": 128,
      "completionRate": 95,
      "avgRating": 4.7,
      "onTimeRate": 92,
      "excellentCount": 45
    },
    "portfolio": ["file001", "file002", "file003"]
  }
}
```

---

## 三、智能匹配算法

### 3.1 匹配评分公式

```typescript
总分 = 风格匹配分(30%) + 负载适配分(30%) + 历史表现分(25%) + 紧急适配分(15%)
```

### 3.2 评分细则

#### 1️⃣ **风格匹配分**（最高30分）
```typescript
if (设计师擅长风格包含项目风格) {
  基础分 = 20分
  if (该风格是设计师最擅长的前3个) {
    加分 = 10分
  } else {
    加分 = 5分
  }
}
```

#### 2️⃣ **负载适配分**（最高30分）
```typescript
当前负载率 = (加权工作量 / 单周处理量) * 100
if (负载率 < 60%) {
  负载分 = 30分  // 空闲，优先推荐
} else if (负载率 < 80%) {
  负载分 = 20分  // 繁忙但可接
} else if (负载率 < 100%) {
  负载分 = 10分  // 接近满载
} else {
  负载分 = 0分   // 超负荷，不推荐
}
```

#### 3️⃣ **历史表现分**（最高25分）
```typescript
表现分 = (完成率 * 0.4 + 按时率 * 0.3 + 平均评分/5 * 100 * 0.3) * 0.25
```

#### 4️⃣ **紧急适配分**（最高15分，仅紧急项目适用）
```typescript
if (项目是紧急) {
  if (设计师愿意接紧急单 && 本周紧急单未达上限) {
    紧急分 = 15分
  } else {
    紧急分 = 0分
  }
} else {
  紧急分 = 0分  // 普通项目不考虑此项
}
```

### 3.3 推荐逻辑

```typescript
function getRecommendedDesigners(project: Project, allDesigners: Designer[]): Designer[] {
  return allDesigners
    .map(designer => ({
      ...designer,
      matchScore: calculateMatchScore(project, designer)
    }))
    .filter(d => d.matchScore >= 40) // 至少40分才推荐
    .sort((a, b) => b.matchScore - a.matchScore) // 降序排列
    .slice(0, 5); // 最多推荐5个
}
```

---

## 四、数据库查询实现

### 4.1 查询设计师列表（fmode-ng）

```typescript
import { FmodeParse as Parse } from 'fmode-ng/parse';

async getDesignersWithTags(companyId: string): Promise<any[]> {
  const query = new Parse.Query('Profile');
  query.equalTo('company', companyId);
  query.equalTo('roleName', '组员'); // 只查询设计师
  query.notEqualTo('isDeleted', true);
  query.select('name', 'department', 'data'); // 只返回需要的字段
  
  const profiles = await query.find();
  
  return profiles.map(p => ({
    id: p.id,
    name: p.get('name'),
    department: p.get('department'),
    tags: p.get('data')?.tags || this.getDefaultTags(), // 如果没有tags，使用默认值
    ...p.toJSON()
  }));
}

// 默认标签（新员工或未设置标签时）
private getDefaultTags() {
  return {
    expertise: { styles: [], skills: [], spaceTypes: [] },
    capacity: { weeklyProjects: 2, maxConcurrent: 3, avgDaysPerProject: 10 },
    emergency: { willing: false, premium: 0, maxPerWeek: 0 },
    history: { totalProjects: 0, completionRate: 0, avgRating: 0, onTimeRate: 0, excellentCount: 0 },
    portfolio: []
  };
}
```

### 4.2 查询设计师当前负载（基于Project表）

```typescript
async getDesignerWorkload(designerId: string, companyId: string): Promise<{
  projects: any[],
  weightedTotal: number,
  overdueCount: number
}> {
  // 查询设计师负责的所有进行中项目
  const projectQuery = new Parse.Query('Project');
  projectQuery.equalTo('assignee', designerId);
  projectQuery.equalTo('company', companyId);
  projectQuery.containedIn('status', ['进行中', '待分配']);
  projectQuery.notEqualTo('isDeleted', true);
  projectQuery.include('contact'); // 关联客户信息
  
  const projects = await projectQuery.find();
  
  // 计算加权总量（复用dashboard的calculateWorkloadWeight逻辑）
  const weightedTotal = projects.reduce((sum, proj) => {
    return sum + this.calculateWorkloadWeight(proj);
  }, 0);
  
  // 统计超期项目数
  const now = new Date();
  const overdueCount = projects.filter(p => {
    const deadline = p.get('deadline');
    return deadline && deadline < now;
  }).length;
  
  return {
    projects: projects.map(p => p.toJSON()),
    weightedTotal,
    overdueCount
  };
}
```

### 4.3 查询设计师历史表现（基于ProjectFeedback表）

```typescript
async getDesignerHistory(designerId: string, companyId: string): Promise<{
  totalProjects: number,
  completionRate: number,
  avgRating: number,
  onTimeRate: number
}> {
  // 查询该设计师历史项目总数
  const totalQuery = new Parse.Query('Project');
  totalQuery.equalTo('assignee', designerId);
  totalQuery.equalTo('company', companyId);
  totalQuery.notEqualTo('isDeleted', true);
  const totalProjects = await totalQuery.count();
  
  // 查询已完成项目
  const completedQuery = new Parse.Query('Project');
  completedQuery.equalTo('assignee', designerId);
  completedQuery.equalTo('status', '已完成');
  const completedCount = await completedQuery.count();
  
  const completionRate = totalProjects > 0 ? (completedCount / totalProjects * 100) : 0;
  
  // 查询反馈评分（需要ProjectFeedback表）
  // const feedbackQuery = new Parse.Query('ProjectFeedback');
  // feedbackQuery.equalTo('designer', designerId);
  // const feedbacks = await feedbackQuery.find();
  // const avgRating = feedbacks.length > 0
  //   ? feedbacks.reduce((sum, f) => sum + f.get('rating'), 0) / feedbacks.length
  //   : 0;
  
  // 暂时使用模拟数据，后续可实现真实统计
  const avgRating = 4.5;
  const onTimeRate = 90;
  
  return {
    totalProjects,
    completionRate,
    avgRating,
    onTimeRate
  };
}
```

---

## 五、UI交互设计

### 5.1 项目分配流程优化

**原流程**（手动）：
```
1. 组长查看待分配项目
2. 逐个查看设计师工作量
3. 手动判断谁合适
4. 分配项目
```

**新流程**（智能推荐）：
```
1. 组长查看待分配项目
2. 点击"智能推荐"按钮
   ↓
3. 系统展示推荐列表（最多5人）：
   - 🥇 张三（匹配度 92分）
     - ✅ 擅长现代风格
     - 📊 当前负载 58%（繁忙）
     - ⭐ 历史评分 4.7
     - 🎯 推荐理由：风格匹配度高，负载适中
   
   - 🥈 李四（匹配度 85分）
     ...
   
4. 组长一键选择推荐设计师
   或
   查看完整列表手动选择
```

### 5.2 推荐卡片设计

```html
<div class="recommendation-card" *ngFor="let rec of recommendations; let i = index">
  <div class="rank-badge" [class.gold]="i===0" [class.silver]="i===1" [class.bronze]="i===2">
    {{ i + 1 }}
  </div>
  
  <div class="designer-info">
    <h4>{{ rec.name }}</h4>
    <span class="match-score">匹配度 {{ rec.matchScore }}分</span>
  </div>
  
  <div class="match-details">
    <div class="detail-row">
      <span class="label">擅长风格</span>
      <span class="value">{{ rec.tags.expertise.styles.join(', ') }}</span>
    </div>
    <div class="detail-row">
      <span class="label">当前负载</span>
      <span class="value" [class.warning]="rec.loadRate > 80">
        {{ rec.loadRate }}% ({{ rec.currentProjects }}个项目)
      </span>
    </div>
    <div class="detail-row">
      <span class="label">历史评分</span>
      <span class="value">⭐ {{ rec.tags.history.avgRating.toFixed(1) }}</span>
    </div>
  </div>
  
  <div class="recommendation-reason">
    <strong>推荐理由：</strong>
    {{ rec.reason }}
  </div>
  
  <button class="btn-assign" (click)="assignToDesigner(rec.id)">
    分配给TA
  </button>
</div>
```

---

## 六、实施步骤

### 6.1 Phase 1: 数据层（优先级P0）

1. ✅ 定义Profile.data.tags数据结构
2. 🔨 创建`DesignerService`服务（fmode-ng查询）
3. 🔨 实现数据获取方法（设计师列表、负载、历史）

### 6.2 Phase 2: 算法层（优先级P0）

1. 🔨 实现智能匹配算法（calculateMatchScore）
2. 🔨 实现推荐逻辑（getRecommendedDesigners）
3. 🔨 添加单元测试

### 6.3 Phase 3: UI层（优先级P1）

1. 🔨 修改dashboard.ts，移除模拟数据
2. 🔨 集成DesignerService
3. 🔨 添加"智能推荐"按钮和弹窗

### 6.4 Phase 4: 优化（优先级P2）

1. 添加推荐缓存（避免重复计算）
2. 添加手动调整推荐权重功能
3. 添加推荐结果反馈收集

---

## 七、兼容性保障

### 7.1 向后兼容

- ✅ Profile.data字段已存在，扩展tags子字段不影响现有数据
- ✅ 如果tags不存在，使用默认值，不影响现有员工
- ✅ 智能推荐是新增功能，不影响手动分配流程

### 7.2 数据迁移

对于现有员工，可通过后台脚本批量初始化tags：

```typescript
async initializeDesignerTags(companyId: string) {
  const query = new Parse.Query('Profile');
  query.equalTo('company', companyId);
  query.equalTo('roleName', '组员');
  query.notEqualTo('isDeleted', true);
  
  const profiles = await query.find();
  
  for (const profile of profiles) {
    const currentData = profile.get('data') || {};
    if (!currentData.tags) {
      currentData.tags = this.getDefaultTags();
      profile.set('data', currentData);
      await profile.save();
    }
  }
  
  console.log(`已初始化 ${profiles.length} 个设计师的标签数据`);
}
```

---

## 八、测试清单

### 8.1 功能测试

- [ ] 验证Profile.data.tags数据正确保存和读取
- [ ] 验证智能匹配算法评分准确性
- [ ] 验证推荐列表按分数降序排列
- [ ] 验证负载计算与dashboard一致
- [ ] 验证紧急项目优先推荐愿意接单的设计师

### 8.2 性能测试

- [ ] 100个设计师时推荐响应时间 < 500ms
- [ ] 查询缓存生效，重复推荐不重复计算
- [ ] 数据库查询优化（使用select限制字段）

### 8.3 UI测试

- [ ] 推荐卡片样式正常显示
- [ ] 匹配度分数颜色编码正确（高分绿色，低分灰色）
- [ ] 一键分配按钮功能正常

---

## 九、效率提升预估

| 维度 | 优化前 | 优化后 | 提升 |
|-----|-------|-------|-----|
| **项目分配时间** | 5-10分钟/项目 | 1-2分钟/项目 | **70%↓** |
| **分配准确性** | 凭经验判断 | 算法+数据驱动 | **准确度↑** |
| **新员工上手** | 需熟悉所有设计师 | 系统自动推荐 | **门槛↓** |
| **负载均衡** | 可能分配不均 | 算法考虑负载 | **公平性↑** |

---

## 十、未来扩展

1. **AI风格识别**：分析项目需求图，自动识别风格标签
2. **学习优化**：根据分配结果反馈，动态调整匹配权重
3. **团队协作**：支持多人协作项目的智能组队推荐
4. **移动端**：开发移动端快速分配功能

---

**文档版本**: v1.0  
**最后更新**: 2025-10-22  
**维护者**: YSS Project Team


