docs: add design for CRM customer server-side column filters.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-09-06 08:13:48 +08:00
1 parent 55cbd1d3d2
commit 16d382e6aa
1 file changed
+222
@@ -0,0 +1,222 @@
# CRM 客户域:列头漏斗整库服务端筛选(首批)
**日期**:2026-09-06
**状态**:待审阅
**范围(首批)**:`web-admin` 客户管理 + 客户公海主列表;后端客户/公海 list 与 export 过滤扩展;`useServerColumnFilters` 通用化
**后续**:线索/商机/产品/报价/合同等主列表 → 弹窗内列表(本文件仅锁定首批与模式)
**对齐标杆**:`docs/superpowers/specs/2026-09-06-reimburse-apply-server-column-filters-design.md`(报销申请)
---
## 1. 背景与目标
报销申请已落地「列头漏斗 + 整库服务端筛」。CRM 菜单下列表/弹窗表需同一模式。范围大,**分批交付**;首批打通客户域并通用化 composable,再复制到其它 CRM 页。
### 目标
1. 客户管理、客户公海主列表:去掉顶部 `filter-block`,检索 = 列头漏斗 + 快捷搜索(客户页另保留 Tab)。
2. 四类列交互与报销一致:日期段、数字运算/区间、字典枚举多选、其余文本模糊。
3. 过滤打整库;`total`/换页/导出与列条件一致。
4. 藏列清该列条件并重查。
5. 将 `useServerColumnFilters.toQueryParams` 从「报销写死字段」升级为可配置 `queryKey`,报销页回归不破。
---
## 2. 已确认产品决策
| # | 决策 |
|---|------|
| D1 | 交付方式:分批;先主列表后弹窗内表 |
| D2 | 首批:客户管理 + 客户公海主列表 |
| D3 | 规则与报销申请完全一致:去顶部筛;保留 Tab/快捷搜索;四类漏斗整库;藏列清条件;默认值迁列漏斗 |
| D4 | 原顶部有检索字段但无列 → 先补列;本批顶部字段均已有列 |
| D5 | 架构:通用化 `ColumnFilterDef` + `toQueryParams`(方案 1),不复制 CRM 专用 composable |
**本页默认值**:客户/公海 `searchForm` 初始均为空,无默认值可迁;规则保留供他页使用。
---
## 3. 首批范围
### 3.1 改造
| 页面 | 路径 |
|------|------|
| 客户管理 | `web-admin/src/views/crm/customer/index.vue` |
| 客户公海 | `web-admin/src/views/crm/customer/pool/index.vue` |
| Composable | `web-admin/src/composables/useServerColumnFilters.ts` + `useClientColumnFilters.ts`(`ColumnFilterDef`) |
| 报销回归 | `web-admin/src/views/bx/reimburse/apply/index.vue`(仅补 queryKey 配置) |
| 后端 | `CustomerListBody` / `PoolCustomerListBody`、`list_customers` / 公海 list、对应 export |
### 3.2 明确不做(首批)
- 其它 CRM 主列表(线索、商机、产品、报价、合同等)
- 客户详情/联系人/发票等弹窗内嵌表
- 公海规则页、看板/漏斗/统计
- OpenSpec 基线归档(标杆稳定后另开)
---
## 4. 产品与交互
### 4.1 布局
- 删除顶部 `filter-block`。
- **客户管理**:保留 Tab(全部 / 我的 / 待跟进 / 重点)→ 请求 `viewMode`;与列条件 **AND**。
- 保留工具栏快捷搜索 → `keyword`(或现网等价字段);与列条件 **AND**。
- 「清空列过滤」只清列条件,不清快捷搜索、不改 Tab。
### 4.2 列头漏斗覆盖
业务数据列全挂漏斗(不含勾选、序号)。覆盖 `listColumnDefs` + 固定列「客户编号」「客户名称」。
| 类型 | 列(示意) |
|------|------------|
| enum | customerType、level、source、industry、parkArea、scale、status、dealStatus |
| datetime | createTime、updateTime、lastTime、nextTime、dealTime、intoPoolTime、claimTime、protectTime |
| number | dealAmount、followCount、contactCount、opportunityCount、contractCount |
| text | customerNo、customerName、shortName、mobile、telephone、email、website、province、city、district、address、ownerUserName、createByName、updateByName、createDeptName、remark |
公海列与客户高度同构;列表恒为公海范围(`status=pool` 或等价),列筛叠加 AND。
### 4.3 藏列与默认值
- 隐藏列 → 清空该列已生效条件 → 立即重查。
- 再显示列 → 不自动恢复条件。
- 「重置列显示」→ 恢复列可见性默认 + 列漏斗 `initialFilters`(本批为空)。
- 他页若有顶部默认:仅当对应列默认可见时写入 initial。
### 4.4 导出
导出请求体与当前列表同一套:`keyword` + `viewMode`(若有)+ 列筛参数 + 选中 ids(若有)。
---
## 5. 架构:Composable 通用化
### 5.1 扩展 `ColumnFilterDef`
```ts
interface ColumnFilterDef {
prop: string
label: string
type: 'text' | 'enum' | 'number' | 'datetime'
enumOptions?: ColumnFilterEnumOption[]
getValue?: (row: any) => unknown
/** 文本主查询键;默认 prop。枚举多选默认 `${queryKey}In` */
queryKey?: string
/** datetime 起止;默认 `${queryKey}From` / `${queryKey}To` */
queryKeyFrom?: string
queryKeyTo?: string
/** number 键前缀;默认 queryKey → `${prefix}Op` / Min / Max / 单值键=prefix */
queryKeyPrefix?: string
}
```
### 5.2 `toQueryParams` 规则
| type | 输出 |
|------|------|
| text | `q[queryKey] = text.trim()` |
| enum | `q[`${queryKey}In`] = selected.join(',')` |
| datetime | From/To 闭区间日期 |
| number | Op + Min/Max 或单值 |
报销申请页改为显式配置(如 `applyTime` → `applyDateFrom`/`applyDateTo`,`amount` → `amountOp`…,`status` → `statusIn`),行为与现网一致。
---
## 6. API 约定(客户 / 公海)
### 6.1 已有(客户 list)
`POST /web/crm/customer/list`(`CustomerListBody`)已支持:
`keyword`、`customerName`、`customerNo`、`customerType`、`level`、`source`、`industry`、`parkArea`、`status`、`dealStatus`、`ownerUserId`、`beginTime`/`endTime`(创建时间)、`viewMode`/`viewType` 等。
### 6.2 首批扩展
在 list / export 共用拼装中增加(命名 camelCase,与前端 `toQueryParams` 对齐):
1. **枚举多选 `*In`**:如 `customerTypeIn`、`levelIn`、`sourceIn`、`statusIn`、`dealStatusIn`、`parkAreaIn`、`industryIn`、`scaleIn`;逗号分隔 → SQL `IN`。保留原单值字段兼容。
2. **文本模糊**:对挂漏斗的 text 列补独立参数(如 `shortName`、`mobile`、`telephone`、`email`、`website`、`address`、`ownerUserName`、`remark`、`province`/`city`/`district`、`createByName` 等);`contains`/`like`。
3. **其它日期**:`lastTimeFrom`/`To`、`nextTimeFrom`/`To`、`dealTimeFrom`/`To`、`intoPoolTimeFrom`/`To`、`claimTimeFrom`/`To`、`protectTimeFrom`/`To`、`updateTimeFrom`/`To`;创建时间可继续用 `beginTime`/`endTime`,或统一为 `createTimeFrom`/`To`(前端 queryKey 显式映射到现网键,避免双栈)。
4. **数字**:`dealAmountOp` + `dealAmount`/`Min`/`Max`,以及 followCount、contactCount、opportunityCount、contractCount 同模式。
### 6.3 公海
`POST /web/crm/customer/pool/list`(及 export)扩展与公海表漏斗列对齐的参数;内部复用 `list_customers`(或同一过滤拼装),并固定公海范围,避免两套逻辑。
### 6.4 语义
- 多列 AND;`keyword` AND;`viewMode` AND。
- 租户隔离、数据权限、软删与现网一致。
- 非法数字/空 `*In`:忽略该维或 400(与报销一致,倾向忽略空维)。
---
## 7. 数据流
```text
进入客户页
→ Tab = viewMode;initialFilters(空)
→ POST list (page, pageSize, viewMode, keyword?, column params?)
列漏斗确定 → 合并 keyword + viewMode + toQueryParams → page=1 重查
藏列 → 清该列条件 → 重查
清空列过滤 → filters={} → 重查(保留 keyword、Tab)
导出 → 与当前列表相同过滤字段
```
公海同理(无 Tab / 无 viewMode,或固定 pool)。
---
## 8. 验收标准(首批)
1. 客户、公海无顶部筛选区;有列头漏斗 + 快捷搜索;客户 Tab 可用。
2. 四类漏斗行为正确,跨页、`total`、换 pageSize 条件保持。
3. 藏带条件列 → 条件清除并刷新;再显示后漏斗空。
4. 导出与当前筛选一致(权限范围内)。
5. 报销申请列筛回归通过(日期/金额/状态/文本仍服务端生效)。
6. 租户与数据权限不弱于改造前。
---
## 9. 风险
- 列多 → 参数多:过滤必须集中函数,禁止 list/export 各写一份。
- Tab「重点」与 level 列筛可叠加。
- Composable 通用化误伤报销:须显式 queryKey + 回归。
- 公海参数今日偏少:扩展时勿破坏 `status=pool` 边界。
---
## 10. 后续批次路线图
| 批次 | 内容 |
|------|------|
| 1(本文) | 客户 + 公海 + composable 通用化 + 报销回归 |
| 2 | 线索、商机主列表 |
| 3 | 产品、报价/审批、合同/审批等主列表 |
| 4 | 弹窗/详情内嵌表 |
---
## 11. 建议实现顺序
1. 扩展 `ColumnFilterDef` / `toQueryParams`;报销页补配置并回归。
2. 后端客户/公海 list+export 过滤扩展 + 必要单测。
3. 改造客户、公海页面 UI 与接线。
4. 手工验收 §8。
---
## 12. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-09-06 | 初稿:CRM 首批客户+公海;规则对齐报销;composable 通用化;分批路线图 |