feat: 完成CRM商机和线索管理模块开发

## 新增功能

### 商机中心 (/opportunity)
- Stats统计卡片(商机总数、金额、赢单、转化率)
- Pipeline商机管道(阶段Tab:全部/线索/谈判中/方案/赢单)
- 商机列表真实数据渲染(来自crm_opportunity表)
- 商机卡片详情(经销商、负责人、金额、概率)
- Pipeline计数实时更新

### 线索中心 (/lead)
- 线索列表完整功能(CRUD)
- 线索详情Drawer(基础信息 + 跟进记录Timeline)
- 新建线索(含ERP客户关联、手机号验证)
- 添加跟进记录(跟进方式、内容、下次时间)
- 分配负责人(用户选择器,显示真实姓名)
- 线索转经销商(自动创建商机)
- 删除线索(逻辑删除)

## 后端开发

### 数据库表
- crm_lead(线索表)
- crm_lead_follow(线索跟进记录表)
- crm_dealer(经销商表)
- crm_opportunity(商机表)
- crm_opportunity_follow(商机跟进记录表)
- 数据字典初始化

### API接口
- 线索管理:CRUD、详情、跟进、分配、转化
- 商机管理:列表查询
- 用户选择器:员工门户专用API

### 核心功能
- 线索转化自动创建经销商和商机
- 负责人翻译显示真实姓名(修复)
- 手机号前后端双重格式验证(修复)

## 前端开发

### 页面架构改进
- 商机中心:保留原CRM设计风格(Stats + Pipeline)
- 线索中心:独立页面(完整线索管理)
- 左侧菜单:独立菜单项(商机中心、线索中心)

### API模块
- src/api/crm/:线索和商机API类型定义和调用方法
- src/api/user/:用户选择器API

### 样式设计
- 商机中心:100%保持原CRM Pipeline设计风格
- 使用CSS变量系统(var(--radius-lg), var(--shadow-sm)等)
- Pipeline Tab白色圆角设计
- 商机卡片阴影和hover效果
- 头像堆叠显示

## 配置修改

- Gateway路由:添加CRM模块路由配置
- Gateway路由:添加system模块路由配置
- Aside菜单:拆分商机中心和线索中心

## Bug修复

- 修复负责人显示手机号问题(UserNameTranslationImpl返回昵称)
- 修复手机号格式验证缺失(前后端双重验证)
- 修复商机管道设计风格不一致(完整复制原CRM样式)

## 文档

- CRM销售模块详细设计说明书V3.md
- CRM线索转化API契约
- CRM线索转化开发计划
- CRM线索转化测试指引
- CRM线索管理测试指引
- CRM商机管理测试指引
- CRM架构改进报告
- CRM Bug修复报告

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
大壮
2026-05-20 09:46:59 +00:00
parent 6ad14b07dc
commit 3f643ef31f
59 changed files with 11876 additions and 18 deletions

512
docs/crm-api-contract-v3.md Normal file
View File

@@ -0,0 +1,512 @@
# CRM 线索中心模块 API 契约V3 - 员工门户)
## 基础信息
- **服务归属**: hzhub-system (端口 8083)
- **API前缀**: `/crm/lead` (通过 Gateway 路由 `/crm/**` → hzhub-system)
- **前端项目**: hzhub-portal-employee (员工门户)
- **响应格式**: `R<T>` (org.hzhub.common.core.domain.R)
- **分页格式**: `TableDataInfo<T>` (org.hzhub.common.mybatis.core.page.TableDataInfo)
---
## 接口列表
### 1. 线索列表查询
**接口**: `GET /crm/lead/list`
**请求参数** (Query):
```json
{
"companyName": "XX贸易", // 公司名称(模糊查询)
"mobile": "13800000000", // 手机号
"intentLevel": "high", // AI意向等级字典crm_intent_level
"riskLevel": "low", // 风险等级字典crm_risk_level
"ownerUserId": 12345, // 负责人ID
"leadStatus": "following", // 线索状态字典crm_lead_status
"sourceType": "activity", // 来源类型字典crm_lead_source
"customerCode": "C001", // ERP客户编码
"pageNum": 1, // 页码
"pageSize": 10 // 每页大小
}
```
**响应**: `TableDataInfo<CrmLeadVo>`
```json
{
"code": 200,
"msg": "查询成功",
"rows": [
{
"leadId": 1001,
"tenantId": "000000",
"customerCode": "C001", // ERP客户编码
"companyName": "XX贸易有限公司",
"contactName": "张三",
"mobile": "138****0000", // 脱敏
"wechat": "zhangsan",
"province": "广东省",
"city": "深圳市",
"regionId": 100,
"regionName": "华南区", // 翻译
"sourceType": "activity",
"sourceTypeName": "活动", // 翻译
"industry": "食品",
"industryName": "食品行业", // 翻译
"storeCount": 20,
"intentLevel": "high",
"intentLevelName": "高意向", // 翻译
"aiScore": 85.5,
"riskLevel": "low",
"riskLevelName": "低风险", // 翻译
"ownerUserId": 12345,
"ownerUserName": "李四", // 翻译
"leadStatus": "following",
"leadStatusName": "跟进中", // 翻译
"nextFollowTime": "2026-05-20 14:00:00",
"createBy": 1,
"createByName": "系统管理员", // 翻译
"createTime": "2026-05-15 10:00:00"
}
],
"total": 100
}
```
---
### 2. 线索详情查询
**接口**: `GET /crm/lead/{leadId}`
**路径参数**: `leadId` (Long)
**响应**: `R<CrmLeadVo>`
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"leadId": 1001,
"customerCode": "C001",
"companyName": "XX贸易有限公司",
"contactName": "张三",
"mobile": "13800000000", // 未脱敏
// ... 其他字段同列表
}
}
```
---
### 3. 新增线索
**接口**: `POST /crm/lead`
**请求体**: `CrmLeadBo`
```json
{
"customerCode": "C001", // ERP客户编码可选
"companyName": "XX贸易有限公司",
"contactName": "张三",
"mobile": "13800000000",
"wechat": "zhangsan",
"province": "广东省",
"city": "深圳市",
"regionId": 100, // 关联 sys_dept
"sourceType": "activity",
"activityName": "春季招商会",
"referrerName": "王五",
"industry": "食品",
"companyScale": "50-100人",
"storeCount": 20,
"remark": "意向强烈,希望尽快对接"
}
```
**业务逻辑**:
1. 如果提供 `customerCode`,调用 `hzhub-erp:8082/erp/dynamic/v1/customer/detail` 拉取ERP客户信息
2. 自动填充客户基础信息companyName, contactName, mobile等
3. 校验手机号是否重复(同租户内)
4. 调用 LangChain4j AI服务分析意向等级`hzhub-ai:6039/ai/analyze/intent` - **第二阶段实现**
5. 根据区域规则sys_dept分配销售owner_user_id - **可选**
6. 返回成功消息
**响应**: `R<Void>`
```json
{
"code": 200,
"msg": "新增成功"
}
```
---
### 4. 编辑线索
**接口**: `PUT /crm/lead`
**请求体**: `CrmLeadBo`
```json
{
"leadId": 1001,
"companyName": "XX贸易已改名",
"contactName": "张三",
"mobile": "13800000000",
// ... 其他可编辑字段
}
```
**响应**: `R<Void>`
```json
{
"code": 200,
"msg": "修改成功"
}
```
---
### 5. 删除线索
**接口**: `DELETE /crm/lead/{leadIds}`
**路径参数**: `leadIds` (String逗号分隔如 "1001,1002,1003")
**响应**: `R<Void>`
```json
{
"code": 200,
"msg": "删除成功"
}
```
---
### 6. 分配线索
**接口**: `PUT /crm/lead/assign`
**请求体**:
```json
{
"leadId": 1001,
"ownerUserId": 12345 // 新负责人ID关联 sys_user
}
```
**响应**: `R<Void>`
```json
{
"code": 200,
"msg": "分配成功"
}
```
---
### 7. 获取跟进记录列表
**接口**: `GET /crm/lead/follow/{leadId}`
**路径参数**: `leadId` (Long)
**响应**: `R<List<CrmLeadFollowVo>>`
```json
{
"code": 200,
"msg": "查询成功",
"data": [
{
"followId": 2001,
"leadId": 1001,
"followType": "phone",
"followTypeName": "电话", // 翻译
"content": "客户表达了合作意向,希望了解招商政策",
"aiSummary": "客户意向高,关注返点政策",
"nextFollowTime": "2026-05-20 14:00:00",
"followUserId": 12345,
"followUserName": "李四", // 翻译
"createTime": "2026-05-15 15:00:00"
}
]
}
```
---
### 8. 添加跟进记录
**接口**: `POST /crm/lead/follow`
**请求体**: `CrmLeadFollowBo`
```json
{
"leadId": 1001,
"followType": "phone",
"content": "与客户沟通了具体合作细节,客户对返点政策满意",
"nextFollowTime": "2026-05-20 14:00:00"
}
```
**业务逻辑**:
1. 保存跟进记录
2. 调用 LangChain4j AI生成摘要`hzhub-ai:6039/ai/summarize` - **第二阶段实现**
3. 更新线索的 `nextFollowTime`
**响应**: `R<Void>`
```json
{
"code": 200,
"msg": "跟进成功"
}
```
---
### 9. 线索转经销商(第二阶段实现)
**接口**: `POST /crm/lead/convert`
**请求体**:
```json
{
"leadId": 1001,
"dealerName": "XX贸易",
"dealerCode": "DL20260001",
"customerCode": "C001" // ERP客户编码可选
}
```
**响应**: `R<Void>`
```json
{
"code": 200,
"msg": "转化成功"
}
```
---
## 数据字典定义
### crm_lead_source线索来源
| 字典值 | 字典标签 | 备注 |
|---|---|---|
| activity | 活动 | 线下招商活动 |
| referral | 推荐 | 客户推荐 |
| website | 网站 | 官网咨询 |
| exhibition | 展会 | 行业展会 |
| wecom | 企业微信 | 企业微信咨询 |
| erp | ERP客户 | 从ERP客户转化 |
| other | 其他 | 其他来源 |
### crm_lead_status线索状态
| 字典值 | 字典标签 | 备注 |
|---|---|---|
| new | 新线索 | 刚录入,未分配 |
| following | 跟进中 | 已分配,正在跟进 |
| converted | 已转化 | 已转为经销商 |
| invalid | 已作废 | 线索无效 |
### crm_intent_levelAI意向等级
| 字典值 | 字典标签 | AI评分范围 |
|---|---|---|
| high | 高意向 | >= 80 |
| medium | 中意向 | 60-80 |
| low | 低意向 | < 60 |
### crm_risk_level风险等级
| 字典值 | 字典标签 | 备注 |
|---|---|---|
| high | 高风险 | 需重点关注 |
| medium | 中风险 | 需持续跟踪 |
| low | 低风险 | 正常跟进 |
### crm_follow_type跟进方式
| 字典值 | 字典标签 |
|---|---|
| phone | 电话 |
| wecom | 企业微信 |
| visit | 拜访 |
| email | 邮件 |
| other | 其他 |
---
## 前端类型定义TypeScript - 员工门户)
```typescript
// CrmLeadVo
export interface CrmLeadVo {
leadId: number;
tenantId: string;
customerCode?: string; // ERP客户编码
companyName: string;
contactName: string;
mobile: string;
wechat?: string;
province?: string;
city?: string;
regionId?: number;
regionName?: string;
sourceType?: string;
sourceTypeName?: string;
activityName?: string;
referrerName?: string;
industry?: string;
industryName?: string;
companyScale?: string;
storeCount?: number;
intentLevel?: string;
intentLevelName?: string;
aiScore?: number;
riskLevel?: string;
riskLevelName?: string;
ownerUserId?: number;
ownerUserName?: string;
leadStatus: string;
leadStatusName?: string;
convertedDealerId?: number;
nextFollowTime?: string;
remark?: string;
createBy: number;
createByName?: string;
createTime: string;
updateBy?: number;
updateByName?: string;
updateTime?: string;
}
// CrmLeadBo
export interface CrmLeadBo {
leadId?: number;
customerCode?: string; // ERP客户编码可选
companyName: string;
contactName: string;
mobile: string;
wechat?: string;
province?: string;
city?: string;
regionId?: number;
sourceType?: string;
activityName?: string;
referrerName?: string;
industry?: string;
companyScale?: string;
storeCount?: number;
ownerUserId?: number;
remark?: string;
}
// CrmLeadFollowVo
export interface CrmLeadFollowVo {
followId: number;
leadId: number;
followType: string;
followTypeName?: string;
content: string;
aiSummary?: string;
nextFollowTime?: string;
followUserId: number;
followUserName?: string;
createTime: string;
}
// CrmLeadFollowBo
export interface CrmLeadFollowBo {
followId?: number;
leadId: number;
followType: string;
content: string;
nextFollowTime?: string;
}
// TableDataInfo员工门户已定义
export interface TableDataInfo<T> {
code: number;
msg: string;
rows: T[];
total: number;
}
// R员工门户已定义
export interface R<T> {
code: number;
msg: string;
data?: T;
}
```
---
## 注意事项(员工门户适配)
1. **多租户支持**: 所有查询自动过滤租户IDTenantEntity
2. **数据权限**: 根据 sys_dept 层级进行数据隔离
3. **敏感字段脱敏**: mobile 字段在列表查询时脱敏
4. **字段翻译**: 字典字段、用户ID、部门ID 自动翻译为名称
5. **操作日志**: 新增、编辑、删除操作记录日志
6. **防重复提交**: 新增操作防重复
7. **逻辑删除**: 使用 @TableLogic,删除时不物理删除
8. **ERP关联**: 如果提供 customerCode自动拉取ERP客户信息
9. **无需Sa-Token注解**: 员工门户权限由Gateway统一控制
---
## ERP集成接口hzhub-erp
### 获取ERP客户详情
**接口**: `GET /erp/dynamic/v1/customer/detail`
**请求参数**: `customerCode`
**响应**: `R<CustomerVO>`
**CustomerVO**(员工门户已定义):
```typescript
interface CustomerVO {
customerCode: string;
customerName: string;
contactName: string;
salesAreaName: string;
brandName: string;
phone: string;
province: string;
city: string;
// ... 其他字段见 hzhub-portal-employee/src/api/erp/index.ts
}
```
---
## 开发优先级
- **P0必须实现**: 接口1-8基础CRUD + 跟进)
- **P1第二阶段**: 接口9线索转经销商+ AI意向识别 + AI摘要生成
- **P2第三阶段**: AI风险分析 + AI预测模型