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

View File

@@ -0,0 +1,141 @@
/**
* CRM 线索管理模块 API 调用
* 服务归属hzhub-system (端口 8083)
* API前缀/crm/lead通过 Gateway 路由)
*/
import type {
CrmLeadBo,
CrmLeadFollowBo,
CrmLeadFollowVo,
CrmLeadVo,
CrmOpportunityBo,
CrmOpportunityVo,
LeadAssignRequest,
LeadConvertRequest,
LeadQueryParams,
OpportunityQueryParams,
R,
TableDataInfo,
} from './types';
// 导出类型供外部使用
export type {
CrmLeadBo,
CrmLeadFollowBo,
CrmLeadFollowVo,
CrmLeadVo,
CrmOpportunityBo,
CrmOpportunityVo,
LeadAssignRequest,
LeadConvertRequest,
LeadQueryParams,
OpportunityQueryParams,
R,
TableDataInfo,
} from './types';
import request from '@/utils/request';
/**
* 获取线索列表(分页)
*/
export function getLeadList(params: LeadQueryParams): Promise<TableDataInfo<CrmLeadVo>> {
return request.get('/crm/lead/list', params).json();
}
/**
* 获取线索详情
*/
export function getLeadDetail(leadId: number): Promise<R<CrmLeadVo>> {
return request.get(`/crm/lead/${leadId}`).json();
}
/**
* 新增线索
*/
export function createLead(data: CrmLeadBo): Promise<R<void>> {
return request.post('/crm/lead', data).json();
}
/**
* 编辑线索
*/
export function updateLead(data: CrmLeadBo): Promise<R<void>> {
return request.put('/crm/lead', data).json();
}
/**
* 删除线索(支持批量)
*/
export function deleteLead(leadIds: string): Promise<R<void>> {
return request.delete(`/crm/lead/${leadIds}`).json();
}
/**
* 分配线索
*/
export function assignLead(data: LeadAssignRequest): Promise<R<void>> {
return request.put('/crm/lead/assign', data).json();
}
/**
* 获取线索跟进记录列表
*/
export function getLeadFollowRecords(leadId: number): Promise<R<CrmLeadFollowVo[]>> {
return request.get(`/crm/lead/follow/${leadId}`).json();
}
/**
* 添加线索跟进记录
*/
export function addLeadFollow(data: CrmLeadFollowBo): Promise<R<void>> {
return request.post('/crm/lead/follow', data).json();
}
/**
* 线索转经销商(第二阶段实现)
*/
export function convertLeadToDealer(data: LeadConvertRequest): Promise<R<void>> {
return request.post('/crm/lead/convert', data).json();
}
/**
* ========================================
* CRM 商机管理模块 API 调用
* ========================================
*/
/**
* 获取商机列表(分页)
*/
export function getOpportunityList(params: OpportunityQueryParams): Promise<TableDataInfo<CrmOpportunityVo>> {
return request.get('/crm/opportunity/list', params).json();
}
/**
* 获取商机详情
*/
export function getOpportunityDetail(opportunityId: number): Promise<R<CrmOpportunityVo>> {
return request.get(`/crm/opportunity/${opportunityId}`).json();
}
/**
* 新增商机
*/
export function createOpportunity(data: CrmOpportunityBo): Promise<R<void>> {
return request.post('/crm/opportunity', data).json();
}
/**
* 编辑商机
*/
export function updateOpportunity(data: CrmOpportunityBo): Promise<R<void>> {
return request.put('/crm/opportunity', data).json();
}
/**
* 删除商机(支持批量)
*/
export function deleteOpportunity(opportunityIds: string): Promise<R<void>> {
return request.delete(`/crm/opportunity/${opportunityIds}`).json();
}