From ae597d8923fe1284fdc6e24feed1f4dde3828ec0 Mon Sep 17 00:00:00 2001 From: hsu06 Date: Thu, 2 Jul 2026 13:46:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(customer-management):=20=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?=E9=A3=8E=E9=99=A9=E6=A0=87=E7=AD=BE=E5=A4=8D=E6=A0=B8=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E4=B8=8E=E5=88=97=E8=A1=A8=20KPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 法务标签改用打标时间/复核周期/下次复核时间,移除 Drawer 内完成复核流程并推送法务工作台待办;列表 KPI 调整为五类卡片,新增页对齐地址解析与合作状态,并同步 PRD 与标注源。 Co-authored-by: Cursor --- .../.spec/prototype-review.md | 94 ++ .../.spec/requirements-prd-create.md | 38 +- .../.spec/requirements-prd-list.md | 420 ++++--- .../annotation-source.json | 103 +- .../components/BatchRiskTagModal.tsx | 63 +- .../components/CustomerTable.tsx | 13 +- .../components/RiskKpiCards.tsx | 59 + .../components/RiskLabelTags.tsx | 69 +- .../components/RiskTagDrawer.tsx | 432 +++++-- .../components/RiskTagForm.tsx | 1115 +++++++++++++++-- .../data/risk-tag-operation-logs.json | 92 +- src/prototypes/customer-management/index.tsx | 191 ++- .../customer-management/pages/CreatePage.tsx | 19 +- .../scripts/build-annotation-source.mjs | 20 +- .../customer-management/styles/index.css | 267 +++- src/prototypes/customer-management/types.ts | 8 + .../customer-management/types/riskTag.ts | 332 ++++- .../customer-management/utils/address.ts | 28 +- .../customer-management/utils/filters.ts | 13 +- .../customer-management/utils/permissions.ts | 106 +- .../customer-management/utils/riskKpi.ts | 43 + .../utils/riskTagConstants.ts | 328 ++++- .../customer-management/utils/riskTagStore.ts | 551 ++++++-- 23 files changed, 3706 insertions(+), 698 deletions(-) create mode 100644 src/prototypes/customer-management/.spec/prototype-review.md create mode 100644 src/prototypes/customer-management/components/RiskKpiCards.tsx create mode 100644 src/prototypes/customer-management/utils/riskKpi.ts diff --git a/src/prototypes/customer-management/.spec/prototype-review.md b/src/prototypes/customer-management/.spec/prototype-review.md new file mode 100644 index 0000000..d3bf9e5 --- /dev/null +++ b/src/prototypes/customer-management/.spec/prototype-review.md @@ -0,0 +1,94 @@ +# Prototype Review + +- 审查目标:`src/prototypes/customer-management`(列表页 + 新增页,重点为风险标签与 KPI 卡片筛选) +- 用户资料/参考资料: + - 本轮用户说明:风险标签「前台分区、权限分责、底层统一」改造诉求;11 项标签目录与分组权限(信用与回款→财务、合规与法律→法务、经营与履约→业管、开票异常→财务、多头签约→业管);KPI 卡片筛选不同类型风险客户 + - `.spec/requirements-prd-list.md`、`.spec/requirements-prd-create.md` + - `src/resources/oneos-web-legacy/需求说明/业务管理/客户管理` + - 源码:`index.tsx`、`components/*`、`utils/*`、`types/*`、`data/*` + - `.axhub/make/project.json`(入口与命名) + - `annotation-source.json`(标注与 PRD 摘要) +- 生成时间:2026-06-30 + +## 总体点评 + +客户管理原型在**风险标签列表维护**方向上已形成较完整的演示闭环:统一 `riskTagsByCustomer` 模型、按法务/财务/业管三分区 Drawer、角色切换、打标/审批/日志、批量打标,以及 8 张 KPI 卡片快速筛选,与近期用户口头决策高度一致,业务表达明显强于旧版 PRD 所描述的「单列表 + 运维/风控打标」方案。 + +当前最影响「需求评审可签字」的问题,不是单个控件缺失,而是**书面规格与实现、Legacy 需求三套口径并行**:`.spec/requirements-prd-list.md` 与 `annotation-source.json` 仍保留旧标签归属(如频繁争议→法务、车辆/资产→运维)、旧角色表(王风控/赵运维打标)、旧抽屉结构(单列表 Tab)和旧风险级别(极高),而源码已改为业管维护经营类标签、8 角色模拟、三分区卡片、致命级枚举与 KPI 卡片。评审若仍引用 PRD 旧章节,会产生「实现错了」的误判。 + +主流程方面,**列表 + 新增 + 风险标签维护**可演示;但 **详情/编辑/查看** 仍为 Toast,Legacy 与 PRD 均将其列为核心任务,主流程出口不完整。高风险标签的**解除申请**已能提交,但缺少与「新增待审批」对等的审批闭环,业务生命周期在「解除」分支上断档。 + +## P0-P3 优先级问题 + +### P1 - 书面 PRD / 标注与当前实现严重脱节,无法作为验收基线 + +- 证据:`requirements-prd-list.md` §5.1、§7.3–§7.4、§8–§9 仍描述运维/风控打标、标签类型「极高」、抽屉「当前标签」为扁平列表;`annotation-source.json` 中 `cm-risk-tag-drawer` 等节点摘要同步旧版。源码 `utils/riskTagConstants.ts` 已为 11 项目录 + 业管维护经营与履约;`permissions.ts` 为 8 角色且业管/财务/法务分责;`RiskTagDrawer.tsx` 为三分区卡片;`RiskKpiCards.tsx` + `utils/riskKpi.ts` 为 KPI 筛选,PRD §1 页面结构未收录。 +- 影响:产品、测试、开发若按 PRD 验收会系统性误判通过/不通过;标注阅读体验与真实原型行为不一致,需求评审无法形成单一真相源。 +- 修复方向:以当前源码与用户最新标签规则为准,回写 `requirements-prd-list.md` §1、§5、§7–§11 及 `annotation-source.json`;明确 KPI 卡片口径、三分区 Drawer、11 标签目录、8 角色权限表;标注「资料冲突/待确认」处需产品确认后再冻结。 + +### P1 - 详情 / 编辑 / 查看主流程未实现,Legacy 与 PRD 核心任务出口缺失 + +- 证据:PRD 列表 §1 核心任务含「进入详情/编辑」;Legacy `src/resources/.../客户管理` §2.16 要求查看页、编辑页与删除确认;源码 `index.tsx` 中 `onView` / `onEdit` 仅 `showToast('…原型演示')`,无页面路由或表单回显。 +- 影响:客户主数据「查—改—审标签—回看档案」链条在列表之后中断;无法验证编辑态是否仍遵守「不在编辑页维护风险标签」约束;与 OneOS Legacy 模块能力不对齐。 +- 修复方向:至少补充查看页/编辑页原型(可为只读详情 + 基础字段编辑),并在 PRD 标明与列表标签维护的边界;删除保留二次确认 Mock 即可。 + +### P1 - 高风险 / 致命标签「解除申请」缺少审批闭环 + +- 证据:用户改造要求「高风险和致命标签不能直接移除,只能提交解除申请或审批」;`index.tsx` `handleRemoveTag` 在 `isApplication` 时仅设 `removalRequested: true` 并写日志,标签仍为 `active`;`RiskTagDrawer.tsx` 审批面板仅处理 `status === 'pending'` 的新增待审批,无「解除待审批」审批入口与通过/驳回后状态迁移(`removed` / 保持生效)。 +- 影响:演示「解除高风险标签」时业务状态停在中间态,审计链路不完整,测试无法覆盖解除审批场景。 +- 修复方向:为 `removalRequested` 增加审批 UI(可与新增审批共用面板,区分动作类型);通过后 `status → removed` 并同步 `labels`;驳回则清除 `removalRequested` 并记录原因。 + +### P2 - 列表筛选与 KPI 卡片口径分裂,易产生「筛不出 / 数不对」误解 + +- 证据:筛选区「标签」多选(`FilterPanel.tsx` + `filters.ts`)匹配 `record.labels`,而 `syncRecordLabels` 仅同步**生效中**标签;KPI(`utils/riskKpi.ts`)基于 `riskTagsByCustomer`,「有风险客户」「高风险/待审批」含 `pending` 标签;KPI 切换即时生效,筛选区仍须点「查询」才生效(`pendingFilters` / `appliedFilters` 分离)。 +- 影响:同一客户可能在 KPI「高风险/待审批」命中,但筛选区「标签」选不到对应名称;KPI 与高级筛选组合时,用户难以理解最终列表口径。 +- 修复方向:统一筛选数据源(建议以 `riskTagsByCustomer` 为准并支持按 tagGroup / category / status 筛);或在 KPI 激活时联动写入 `appliedFilters`;在 PRD 补充 KPI 与筛选区优先级说明。 + +### P2 - Legacy 客户台账字段模型与原型列表差异大,合作状态等关键字段未贯通 + +- 证据:Legacy 要求列表含合作状态、城市、联系人、创建人等;原型 `CustomerRecord` / `CustomerTable` 为工商宽表 + 客户状态(正常/黑名单)+ 业务部门/负责人;新增页有「合作状态」(`CreatePage.tsx`),但列表无该列、筛选无合作状态,提交后也不落库(Toast 返回)。 +- 影响:与 OneOS 既有「业务管理-客户管理」认知不一致;无法演示「按合作状态 + 风险标签」联合运营场景;新增字段与列表台账脱节。 +- 修复方向:产品确认本原型是「风险增强版台账」还是「Legacy 对齐版」;若对齐 Legacy,补充合作状态/城市等列与筛选,并将新增 Mock 写入本地 state;若刻意简化,在 PRD 写清「不做范围」并与 Legacy 文档交叉引用。 + +## 完整性与项目对齐 + +| 维度 | 结论 | +|---|---| +| 核心用户 | 销售、业务负责人、法务、财务、业管、风控、管理层 — 角色切换已覆盖;运维角色可切换但无打标权限,与「经营与履约归业管」一致 | +| 主流程 | 列表筛选 → KPI 卡片 → 表格 → 风险标签 Drawer → 打标/审批/日志:**可演示**;新增页 → 校验 → Toast:**可演示**;详情/编辑/查看:**未实现** | +| 入口/出口 | 入口:侧边栏 `customer-management`(`project.json`);出口:新增返回列表、Drawer 关闭保持列表状态 — **符合**;缺详情/编辑出口 | +| 范围边界 | PRD 声明导入/导出/删除为 Toast 演示 — **一致**;风险标签不在新增/编辑页维护 — **实现一致**;KPI 卡片、三分区 Drawer、11 标签 — **实现有、PRD 无** | +| 项目 metadata | 原型 id `customer-management`,入口 `index.tsx` — **对齐** | +| 资料冲突/待确认 | ① PRD/annotation vs 当前实现(标签归属、角色、KPI)— **待产品确认并以新口径回写 PRD**;② Legacy vs 原型字段模型 — **待确认范围**;③ 用户会话要求「致命」级 vs PRD「极高」— **实现用 fatal/致命,PRD 未更新**;④ 用户要求高风险附件必填 vs PRD §8.3 附件选填 — **实现已必填,PRD 待确认** | + +## 业务逻辑连贯性 + +**流程顺序**:列表加载 → 角色切换 →(可选)KPI 卡片 / 筛选查询 → 行内打开 Drawer → Tab 切换打标/日志 — 顺序合理。批量打标依赖行选 + 权限,与单客户打标共用表单校验 — 一致。 + +**状态迁移**:新增低风险/中风险 → `active` 并同步 `labels`;高/致命 → `pending` → 审批 → `active`/`rejected` — **新增链路完整**。移除:低/中风险可直接 `removed`;高/致命仅 `removalRequested` — **解除链路不完整**(见 P1)。复核:`expireDate` 过期展示「待复核」Banner — **前端 Mock 可感知**。 + +**角色权限**:财务/法务/业管/ admin 可维护各自分区;销售/运维/风控/管理层查看或审批 — 与最新用户标签分组决策一致,但与 PRD §9 冲突。风控、管理层不可新增标签 — 符合用户会话;PRD 仍写风控可打全部标签。 + +**数据生命周期**:底层统一 `CustomerRiskTag[]`,列表 `labels` 为生效中投影 — 架构自洽。黑名单客户(`status === '黑名单'`)与「严重拖欠/制裁触发黑名单审批」仅 Toast 提示,未驱动 `status` 变更 — 业务联动待补。 + +**跨页面**:新增页合作状态、省市识别已实现;返回列表后数据不持久 — 原型可接受,但削弱与列表/KPI 联调。 + +## 状态、异常、边界与恢复 + +| 类别 | 检查结论 | +|---|---| +| 输入校验 | 打标:原因 ≥10 字、高/致命需附件、日期区间 — **有**;新增客户:必填 Toast — **有**但无字段级恢复(属 UI 范畴,业务上可感知错误) | +| 空/无数据 | 表格空态、分区「暂无该类风险标签」、无日志兜底 — **有** | +| 权限不足 | 销售新增 Tab 无权限文案、分区只读提示 — **有** | +| 重复打标 | 同名有效标签拦截 — **有** | +| 批量部分成功 | 已存在标签客户跳过 — **有**;缺批量结果汇总 Toast(条数/跳过数) | +| 并发/重复点击 | 无防抖;Mock 环境风险低 | +| 网络/集成失败 | 未模拟(原型范围外) | +| 恢复路径 | Drawer 内 error 区展示校验失败;审批驳回需填原因 — **有**;解除申请无后续恢复 — **缺** | +| KPI + 筛选重置 | 「重置」清空筛选并回到 KPI「全部客户」 — **有** | + +## 证据与评估说明 + +- **用户资料优先级**:本轮用户说明(11 标签、分组权限、KPI 卡片、三分区 Drawer)权重高于旧 PRD 章节;评审中已将 PRD/Legacy 与实现冲突标为「待确认」。 +- **读取范围**:`.spec/requirements-prd-list.md`、`requirements-prd-create.md`;`src/resources/oneos-web-legacy/需求说明/业务管理/客户管理`;`index.tsx`、`CreatePage.tsx`、`FilterPanel.tsx`、`CustomerTable.tsx`、`RiskTagDrawer.tsx`、`RiskTagForm.tsx`、`RiskLabelTags.tsx`、`RiskKpiCards.tsx`、`BatchRiskTagModal.tsx`;`utils/filters.ts`、`riskKpi.ts`、`riskTagStore.ts`、`riskTagConstants.ts`、`permissions.ts`;`types.ts`、`types/riskTag.ts`;`data/customers.json`、`risk-tag-operation-logs.json`;`.axhub/make/project.json`;`annotation-source.json`(摘要)。未读取 Impeccable 产物。 +- **独立评估**:`degraded` — 未拆分子代理;业务基线与源码证据在同一轮审查中完成,已尽量先建立基线再对照实现。 diff --git a/src/prototypes/customer-management/.spec/requirements-prd-create.md b/src/prototypes/customer-management/.spec/requirements-prd-create.md index 8204a8b..a34a2f6 100644 --- a/src/prototypes/customer-management/.spec/requirements-prd-create.md +++ b/src/prototypes/customer-management/.spec/requirements-prd-create.md @@ -1,6 +1,6 @@ # 客户管理 · 新增页产品需求说明(PRD) -> 从列表页「新增客户」进入。保存为原型 Toast,不对接真实接口。 +> 从列表页「新增客户」进入。保存为原型 Toast,不对接真实接口,**不写回列表台账**。 --- @@ -12,6 +12,8 @@ | 布局 | 三组卡片表单 + 底部 sticky 操作栏 | | 操作 | 重置(清空表单)、提交(校验后 Toast 成功并返回列表) | +**重要约束:** 本页**不包含**风险标签字段;标签仅在列表页维护。 + --- ## 2. 客户信息 @@ -19,15 +21,18 @@ | 字段 | 必填 | 控件 | 说明 | |---|---|---|---| | 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 | -| 客户分级 | 否 | 下拉 | A / B / C | -| 客户全称 | 是 | 文本 | 与客户证照一致;变更时同步发票抬头 | +| 客户全称 | 是 | 文本 | 与证照一致;变更时同步发票抬头 | | 客户简称 | 否 | 文本 | 选填 | -| 所属城市 | 是 | 省-市级联 | 示例省市区数据 | -| 通讯地址 | 否 | 文本 | | +| 省市 | 否 | 文本(置灰) | 自动从通讯地址/地址解析识别 | +| 所属城市 | 是 | 省-市(置灰) | **由地址解析自动识别**,不可手选 | +| 通讯地址 | 是 | 文本 | 填写完整地址后解析省、市(同供应商管理交互) | | 业务负责部门 | 是 | 下拉 | 变更时清空负责人 | | 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 | +| 合作状态 | 否 | 下拉 | 已合作 / 终止合作 / 洽谈中 / 合同到期 | | 备注 | 否 | 多行文本 | | +**已移除:** 客户分级(A/B/C)不在新增页展示。 + --- ## 3. 联系人信息 @@ -55,6 +60,25 @@ ## 5. 校验与提交 -- 提交前校验所有必填项 +| 校验项 | 规则 | +|---|---| +| 客户类型 | 必选 | +| 客户全称 | 非空 | +| 所属城市 | 通讯地址须能识别出省、市 | +| 业务负责部门 / 人员 | 必选 | +| 发票抬头 / 税号 / 开户银行 | 非空 | + - 通过后 Toast「客户已保存(原型演示)」并返回列表 -- 重置清空全部字段,客户类型默认「企业」 +- 重置清空全部字段;客户类型默认「企业」 +- 校验失败:Toast 首条错误(原型阶段) + +--- + +## 6. 原型实现索引 + +| 能力 | 代码位置 | +|---|---| +| 新增页 | `pages/CreatePage.tsx` | +| 地址解析 | `utils/address.ts` | +| 常量选项 | `utils/constants.ts` | +| 表单编排 | `index.tsx` | diff --git a/src/prototypes/customer-management/.spec/requirements-prd-list.md b/src/prototypes/customer-management/.spec/requirements-prd-list.md index 0340927..1e9c63f 100644 --- a/src/prototypes/customer-management/.spec/requirements-prd-list.md +++ b/src/prototypes/customer-management/.spec/requirements-prd-list.md @@ -1,6 +1,6 @@ # 客户管理 · 列表页产品需求说明(PRD) -> 本页为客户主数据台账列表。标注「原型演示」的跳转、导入、导出仅 Toast 提示。 +> 本页为客户主数据台账列表。标注「原型演示」的跳转、导入、导出、详情/编辑仅 Toast 提示。 --- @@ -9,11 +9,14 @@ | 项 | 说明 | |---|---| | 模块名称 | 客户管理 — 列表页 | -| 目标用户 | 销售、业务负责人、运营、**法务、财务、风控** | -| 核心任务 | 按多维度筛选客户台账,查看风险标签与状态,**在列表页维护客户风险标签**,新增客户或进入详情/编辑 | -| 页面结构 | 面包屑 + **操作角色切换** + 筛选区 + 工具栏 + 表格 + 分页 | +| 目标用户 | 销售、业务负责人、**法务、财务、业管、运维/资产、风控、管理层** | +| 核心任务 | 按多维度与 KPI 卡片筛选客户;查看并**在列表页维护风险标签**;新增客户(详情/编辑为原型演示) | +| 页面结构 | 面包屑 + **操作角色切换** + 筛选区 + **风险 KPI 卡片** + 工具栏 + 表格 + 分页 | -**重要约束:** 风险标签**不在**客户新增页、客户编辑表单中维护,仅在列表页作为操作入口。 +**重要约束:** + +- 风险标签**不在**客户新增页、客户编辑表单中维护,仅在列表页作为操作入口。 +- 底层使用统一 `riskTagsByCustomer` 数组(`CustomerRiskTag[]`),前台按**法务 / 财务 / 业管**三分区展示,不按 `legalTags` / `financeTags` 等字段拆散存储。 --- @@ -32,13 +35,13 @@ | 数据 | 来源 | 用途 | |---|---|---| | 客户台账 | `data/customers.json`(20 条样例) | 列表、筛选选项 | -| 客户标签(展示) | 客户 `labels` 字段 | 列表「风险标签」列、筛选区「标签」多选 | -| 风险标签明细 | `riskTagsByCustomer` 运行时状态 + 种子迁移 | 标签管理抽屉、审批、日志 | -| 标签目录 | `utils/riskTagConstants.ts` → `TAG_CATALOG` | 打标表单选项、级别、分组 | +| 客户标签(展示投影) | 客户 `labels` 字段 | 列表「风险标签」列、筛选区「标签」多选 | +| 风险标签明细 | `riskTagsByCustomer` 运行时状态 + 种子迁移 | 抽屉、KPI 卡片、审批、日志 | +| 标签目录 | `utils/riskTagConstants.ts` → `TAG_CATALOG`(11 项) | 打标表单、分区展示、KPI 口径 | | 操作日志 | `data/risk-tag-operation-logs.json` + 运行时追加 | 抽屉「操作日志」Tab | | 部门-负责人 | 由台账聚合 + `DEPT_MANAGER_MAP` | 筛选联动 | -列表 `labels` 与「生效中」风险标签自动同步,保证筛选器与列表展示一致。 +`labels` 由 `syncRecordLabels` 与**生效中**风险标签自动同步;**待审批**标签不参与 `labels` 投影。 --- @@ -54,7 +57,7 @@ | 区域 | 可搜索单选 | 精确匹配 `region` | | 客户状态 | 多选 | `status` 在选中集合内 | | 业务部门 | 多选 | `department` 在选中集合内 | -| 业务负责人 | 多选 | 随部门联动选项;`manager` 在选中集合内 | +| 业务负责人 | 多选 | 随部门联动;`manager` 在选中集合内 | | 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) | ### 4.1 操作逻辑 @@ -62,210 +65,333 @@ | 操作 | 行为 | |---|---| | **查询** | 将当前筛选项写入已应用条件,页码重置为 1,清空行选中 | -| **重置** | 清空待应用与已应用条件,页码重置为 1,清空行选中 | +| **重置** | 清空待应用与已应用条件;**KPI 卡片回到「全部客户」**;页码重置为 1;清空行选中 | | 修改筛选项未点查询 | 不影响当前列表 | +### 4.2 与 KPI 卡片的关系 + +- **KPI 卡片**:点击即生效,基于 `riskTagsByCustomer`(含待审批标签)。 +- **筛选区「标签」**:须点「查询」后生效,基于 `labels`(仅生效中标签)。 +- 二者叠加时取交集;口径差异见 §5 说明。 + --- -## 5. 列表工具栏 +## 5. 风险 KPI 卡片筛选 + +样式与供应商/车辆管理 KPI 卡片一致(`vm-kpi-*`),位于筛选区与工具栏之间。点击卡片切换列表数据范围并回到第 1 页;右上角「?」展示口径说明。 + +| KPI 卡片 | 统计/筛选口径 | +|---|---| +| 全部客户 | 台账全部记录 | +| 有风险客户 | 存在**生效中**或**待审批**风险标签 | +| 财务风险 | 含 `tagCategory = finance` 的有效标签(信用与回款、开票异常等) | +| 法务风险 | 含 `tagCategory = legal` 的有效标签(合规与法律类) | +| 业管风险 | 含 `tagCategory = business` 的有效标签(经营与履约、多头签约等) | + +代码:`components/RiskKpiCards.tsx`、`utils/riskKpi.ts`。 + +--- + +## 6. 列表工具栏 | 按钮 | 行为 | |---|---| -| 新增客户 | 进入新增客户页(分组表单) | +| 新增客户 | 进入新增客户页 | | 批量导入 | Toast:批量导入(原型演示) | -| **批量打标** | 勾选 ≥1 行且当前角色有打标权限时显示;打开批量打标弹窗 | +| **批量打标** | 勾选 ≥1 行且当前角色有打标权限时显示 | | 批量导出 | Toast:导出当前列表条数 | -### 5.1 操作角色切换(原型演示) +### 6.1 操作角色切换(原型演示) -列表顶部提供「当前操作角色」下拉,用于模拟不同部门用户: +列表顶部「当前操作角色(原型演示)」下拉,按**角色**切换(非按姓名),模拟 8 类用户: -| 角色 | 部门 | 打标权限 | -|---|---|---| -| 王风控 | 风控部 | 全部标签 | -| 李法务 | 法务部 | 法务类标签 | -| 张财务 | 财务部 | 财务类标签 | -| 赵运维 | 运维部 | 运维类标签 | -| 陈销售 | 销售一部 | 仅查看,无打标入口 | +| 角色 | 演示账号 | 部门 | 打标 | 审批 | 说明 | +|---|---|---|---|---|---| +| 销售 | 陈销售 | 销售一部 | 否 | 否 | 可查看标签/抽屉,操作列「查看标签」 | +| 法务 | 李法务 | 法务部 | 法务分区 | 是 | 维护合规与法律类标签 | +| 财务 | 张财务 | 财务部 | 财务分区 | 否 | 信用与回款 + 开票异常 | +| 业管 | 王业管 | 业管部 | 业管分区 | 否 | 经营与履约 + 多头签约 | +| 运维/资产 | 赵运维 | 运维资产部 | 否 | 否 | 仅查看 | +| 风控 | 刘风控 | 风控部 | 否 | 是 | 可审批待审批标签 | +| 管理层 | 周管理 | 管理层 | 否 | 是 | 可审批;可看财务/法务敏感信息 | +| 系统管理员 | 系统管理员 | 系统管理部 | 全部 | 是 | 全部维护与审批权限 | -切换角色后,风险标签管理抽屉内的**标签类型**、**可选标签**、**审批/移除按钮**随之联动更新。 +切换角色后,Drawer 内**分区维护按钮**、**新增标签可选范围**、**审批/解除按钮**、**敏感信息脱敏**随之联动。 --- -## 6. 列表字段说明 +## 7. 列表字段说明 | 列 | 说明 | |---|---| | 客户编号 | 左固定列 | | 客户名称 | 超长省略 | -| **风险标签** | 展示「生效中」标签;按风险级别着色(低/中/高/极高);首标签 + `+N` 悬浮全部;有待审批时显示「审」标识;可点击打开管理抽屉 | +| **风险标签** | 最多展示 **2** 个生效中标签;超出显示 `+N`;悬浮/点击 `+N` 展示全部;按级别着色(低/中/高/致命);Tooltip 含类型、责任部门、级别、状态;**所有角色**可点击打开 Drawer | | 客户区域 | 大区名称 | | 客户状态 | 绿点「正常」/ 红点「黑名单」 | | 业务部门 / 业务负责人 | 文本 | | 客户类型 / 所属行业 | 文本 | | 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 | | 详细地址 / 创建时间 | 文本;创建时间不换行 | -| 操作 | 详情、编辑、**管理标签**(有权限时)、删除(原型 Toast) | +| 操作 | 详情、编辑(Toast 演示)、**管理标签** / **查看标签**、删除(Toast 演示) | 支持行多选(checkbox,仅当前页);横向滚动;操作列右固定。 ---- +### 7.1 风险标签列展示样式 -## 7. 客户标签枚举与分级目录 +| 风险级别 | 样式 | +|---|---| +| 低 | 灰色边框 | +| 中 | 橙色浅底 | +| 高 | 红色浅底 | +| 致命 | 深红浅底 | -标签按业务分类维护,打标时下拉按 **optgroup 分组** 展示。风险级别由目录**自动带出且不可手改**。 - -### 7.1 信用与回款 - -| 标签名称 | 标签类型 | 风险级别 | 典型触发 | -|---|---|---|---| -| 逾期预警 | 财务 | 中 | 当前逾期或近 6 个月内曾逾期 | -| 严重拖欠 | 财务 | 高 | 逾期 > 30 天未结清或历史坏账 | -| 频繁改账期 | 财务 | 低 | 一年内申请延长账期 ≥ 3 次 | - -### 7.2 合规与法律 - -| 标签名称 | 标签类型 | 风险级别 | 典型触发 | -|---|---|---|---| -| 资质存疑 | 法务 | 中 | 证照过期、抬头与付款主体不一致等 | -| 涉诉/仲裁 | 法务 | 高 | 与我司或第三方重大未结诉讼 | -| 制裁/高风险地区 | 法务 | 高 | 名单筛查命中(依公司合规清单) | - -### 7.3 经营与履约 - -| 标签名称 | 标签类型 | 风险级别 | 典型触发 | -|---|---|---|---| -| 频繁争议 | 法务 | 中 | 合同或扣款争议次数超阈值 | -| 车辆/资产高风险 | 运维 | 高 | 违章、事故、疑似骗保等 | -| 信息不实 | 风控 | 中 | 联系人/主体核查不实、空壳公司等 | - -### 7.4 操作与流程 - -| 标签名称 | 标签类型 | 风险级别 | 典型触发 | -|---|---|---|---| -| 开票异常 | 财务 | 低 | 频繁退票、抬头异常变更 | -| 多头签约 | 风控 | 低 | 同一实控人多主体分别签约 | - -### 7.5 列表展示样式 - -- 低:灰色边框 -- 中:橙色浅底 -- 高:红色浅底 -- 极高:深红浅底 -- 单元格最大宽度受限,避免撑乱表格 +无生效中标签但有待审批时,列可展示待审批标识;全无标签显示「—」。 --- -## 8. 风险标签维护(列表页专属) +## 8. 风险标签目录(11 项) -### 8.1 入口 +标签按规范文档四组维护;打标下拉按 **optgroup**(信用与回款 / 合规与法律 / 经营与履约 / 操作与流程)分组。风险级别由目录**自动带出且不可手改**。 + +**维护归属(权限分责):** + +- **信用与回款** → 财务部 +- **合规与法律** → 法务部 +- **经营与履约** → 业管部 +- **操作与流程**:开票异常 → 财务部;多头签约 → 业管部 + +### 8.1 信用与回款(财务) + +| 代码 | 标签名称 | 展示分区 | 风险级别 | 典型触发 | 待审批 | 黑名单提示 | +|---|---|---|---|---|---|---| +| CR-01 | 逾期预警 | 财务 | 中 | 当前逾期或近 6 个月曾逾期 | 否 | 否 | +| CR-02 | 严重拖欠 | 财务 | 高 | 逾期 > 30 天或历史坏账 | 是 | 是 | +| CR-03 | 频繁改账期 | 财务 | 低 | 一年内延长账期 ≥ 3 次 | 否 | 否 | + +### 8.2 合规与法律(法务) + +| 代码 | 标签名称 | 展示分区 | 风险级别 | 典型触发 | 待审批 | 黑名单提示 | +|---|---|---|---|---|---|---| +| CP-01 | 资质存疑 | 法务 | 中 | 证照过期、抬头与付款主体不一致 | 否 | 否 | +| CP-02 | 涉诉/仲裁 | 法务 | 高 | 重大未结诉讼/仲裁 | 是 | 是 | +| CP-03 | 制裁/高风险地区 | 法务 | 高 | 制裁/合规名单命中 | 是 | 是 | + +### 8.3 经营与履约(业管) + +| 代码 | 标签名称 | 展示分区 | 风险级别 | 典型触发 | 待审批 | 黑名单提示 | +|---|---|---|---|---|---|---| +| OP-01 | 频繁争议 | 业管 | 中 | 合同或扣款争议超阈值 | 否 | 否 | +| OP-02 | 车辆/资产高风险 | 业管 | 高 | 违章、事故、疑似骗保等 | 是 | 否 | +| OP-03 | 信息不实 | 业管 | 中 | 联系人/主体核查不实 | 否 | 否 | + +### 8.4 操作与流程 + +| 代码 | 标签名称 | 展示分区 | 维护部门 | 风险级别 | 典型触发 | +|---|---|---|---|---|---| +| PR-01 | 开票异常 | 财务 | 财务部 | 低 | 频繁退票、抬头异常变更 | +| PR-02 | 多头签约 | 业管 | 业管部 | 低 | 同一实控人多主体分别签约 | + +--- + +## 9. 风险标签维护(列表页专属) + +### 9.1 入口 | 入口 | 说明 | |---|---| -| 行操作「管理标签」 | 打开右侧抽屉,管理单个客户 | -| 点击标签列 | 有权限时同「管理标签」 | -| 工具栏「批量打标」 | 对当前勾选客户批量提交同一标签 | +| 行操作「管理标签」/「查看标签」 | 打开右侧 Drawer | +| 点击风险标签列 | 同打开 Drawer | +| 工具栏「批量打标」 | 对勾选客户批量提交同一标签 | **不在**新增页、编辑页维护标签。 -### 8.2 管理抽屉结构 +### 9.2 管理抽屉结构 | Tab | 内容 | |---|---| -| 当前标签 | 已打标签列表:名称、类型、级别、状态、原因摘要;支持查看详情、移除、审批 | -| 新增标签 | 打标表单(见 8.3) | -| 操作日志 | 该客户全部标签相关审计记录 | +| 当前标签 | 按 **法务风险 / 财务风险 / 业管风险** 三分区 Card 展示;含分区说明、维护权限提示、标签卡片、空状态 | +| 新增标签 | 角色受限打标表单(见 9.3) | +| 操作日志 | 该客户审计记录,按时间倒序 | -### 8.3 打标表单字段 +Drawer 标题:**风险标签**;副标题为客户名称与编号。 + +### 9.3 当前标签页 — 分区与卡片 + +展示顺序:法务 → 财务 → 业管。 + +每个分区包含: + +1. 分区标题与说明(责任部门、关注范围) +2. 当前角色是否可维护(「可维护」/「当前角色无权维护该类标签」) +3. 「新增该类标签」按钮(有权限时) +4. 标签卡片:名称、级别、状态、时间字段、责任部门、操作人、原因摘要 + - **法务风险**:打标时间、复核周期、下次复核时间、最后复核时间(不展示「有效期至」) + - **财务/业管**:打标时间、有效期至 +5. 空状态:「暂无该类风险标签」 + +卡片操作:查看详情;审批(新增待审批或**解除待审批**且有权);**申请解除**(有权且生效中,须走审批流)。 + +**法务风险复核规则(原型 Mock):** + +- 法务标签自打标后持续生效,**不会因到期自动解除** +- 距 `nextReviewAt` ≤ 7 天:卡片显示「即将复核」 +- 当前日期超过 `nextReviewAt`:状态显示「待复核」,标签仍保留 +- **不在 Drawer 内完成复核**;系统自动向**法务工作台**推送待办,法务查验后回到本页「申请解除」 +- 打开 Drawer 且存在待复核法务标签时,页顶 Banner 提示待办已推送,并写入操作日志(工作台待办) +- 确认可解除时:填写解除原因 → 提交解除申请(走审批流);通过后记录 `removedAt` + +**解除审批(原型 Mock):** + +- 任意标签解除均提交申请,状态为「解除待审批」 +- 申请解除弹窗说明:**法务标签**提示「法务查验完成后…」;**财务/业管标签**仅提示「提交解除申请,经审批后生效」(不含法务查验文案) +- 法务/风控/管理层/系统管理员可审批:通过 → 已解除;驳回 → 恢复生效并清除申请标记 + +### 9.4 新增标签表单 | 字段 | 必填 | 规则 | |---|---|---| -| 标签名称 | 是 | 按 7.1–7.4 分组下拉;仅当前角色有权限的标签可选;已存在同名有效标签不可重复选 | -| 标签类型 | 是 | **根据当前操作角色自动带出**(财务→财务、法务→法务、运维→运维、风控→风控),**置灰不可编辑** | -| 风险级别 | 是 | **选择标签名称后按目录自动带出**,**置灰不可编辑** | -| 生效时间 | 是 | 日期,默认当天 | -| 有效期至 | 是 | 日期,须 ≥ 生效时间;默认一年后 | -| 原因说明 | 是 | 至少 10 字,供法务/财务/风控审批留痕 | -| 附件 | 否 | 支持 .pdf / .jpg / .png / .xlsx,可多个 | +| 标签类型 | 是 | 非 admin:按角色自动带出法务/财务/业管并置灰;admin 可选三分区 | +| 标签名称 | 是 | 按 8.1–8.4 optgroup;仅当前角色可维护的标签;不可重复有效同名标签 | +| 风险级别 | 是 | 选标签后自动带出,置灰 | +| 打标时间 | 是 | 日期(只读置灰) | **取操作当天**,不可编辑 | +| 复核周期 | 法务必填 | 下拉 | 1 / 2 / 3 / 6 个月;变更时自动推算下次复核时间 | +| 下次复核时间 | 法务必填 | 日期(只读置灰) | 根据打标时间 + 复核周期**自动推算**,不可编辑;下方提示「到期仅提醒责任人复核,不会自动解除标签。」 | +| 有效期至 | 财务/业管必填 | 日期 | ≥ 打标时间;业务有效期,与复核提醒无关 | +| 原因说明 | 是 | ≥ 10 字 | +| 附件 | 高/致命必填 | .pdf / .jpg / .png / .xlsx | -### 8.4 批量打标 +**法务 vs 财务/业管字段差异:** -- 展示已选客户 chips(超过 6 个显示 +N) -- 表单字段与单客户打标一致 +- 法务标签**不展示**「有效期至」,使用「下次复核时间」驱动定期复核提醒 +- 财务/业管保留「有效期至」,不在 UI 上与复核时间混用 + +提交规则: + +- 低/中:直接 **生效中** +- 高/致命:**待审批**;提交前二次确认 +- 涉诉/仲裁、严重拖欠、制裁/高风险地区:Toast 提示可能触发黑名单审批 + +销售/运维/风控/管理层(非 admin):新增 Tab 展示无权限说明。 + +### 9.5 批量打标 + +- 已选客户 chips(>6 显示 +N) +- 表单与单客户一致 - 已存在同名有效标签的客户自动跳过 -- 提交后写入批量操作日志 +- 写入批量操作日志 -### 8.5 标签生命周期 - -| 状态 | 说明 | -|---|---| -| 生效中 | 参与列表展示,同步至 `labels` | -| 待审批 | 高风险标签提交后进入;列表暂不展示为生效 | -| 待确认 | 预留 | -| 已驳回 | 审批未通过,不同步至 `labels` | -| 已移除 | 操作人主动移除,需填写原因 | -| 已过期 | 预留 | - -**高风险规则:** 风险级别为「高」或「极高」的标签提交后状态为「待审批」,须法务或风控审批通过后变为「生效中」。 - -### 8.6 移除标签 - -- 仅「生效中」且当前角色有移除权限的标签可移除 -- 须填写移除原因(至少 5 字) -- 写入操作日志,并从列表 `labels` 同步清除 - ---- - -## 9. 权限与审批 - -### 9.1 打标权限 - -| 角色 | 可选标签范围 | 标签类型(自动) | -|---|---|---| -| 财务 | 财务类(信用与回款、操作与流程中的财务标签) | 财务 | -| 法务 | 法务类(合规与法律、经营与履约中的法务标签) | 法务 | -| 运维 | 运维类(车辆/资产高风险) | 运维 | -| 风控 / 管理员 | 全部 | 风控 | -| 销售 | 无 | — | - -### 9.2 审批权限 - -- 法务、风控、管理员可审批「待审批」标签 -- 支持通过 / 驳回;驳回须填写原因 - -### 9.3 移除权限 - -与打标权限一致:各角色仅能移除本类型标签;风控 / 管理员可移除全部类型。 - ---- - -## 10. 操作日志 - -所有标签 **新增、移除、提交审批、审批通过、审批驳回、批量打标** 均写入日志。 +### 9.6 统一数据模型 `CustomerRiskTag` | 字段 | 说明 | |---|---| -| 操作类型 | 新增 / 移除 / 提交审批 / 审批通过 / 审批驳回 / 批量打标 | -| 客户 | 客户名称 + ID | -| 标签名称 | 涉及标签 | -| 详情 | 原因、审批结论等 | -| 操作人 | 姓名 + 角色 | -| 操作时间 | `YYYY-MM-DD HH:mm:ss` | +| id / customerId | 标识 | +| tagCode / tagName | 目录代码与名称 | +| tagCategory / tagCategoryName | legal / finance / business | +| responsibleDept / editableRoles | 责任部门与可编辑角色 | +| riskLevel / riskLevelName | low / medium / high / fatal | +| status / statusName | active / pending / expired / removed / rejected | +| source / sourceName | system / manual | +| reason / taggedAt / nextReviewAt / reviewCycle | 原因与复核时间(法务) | +| expireDate | 财务/业管业务有效期 | +| lastReviewedAt / reviewStatus / reviewLogs | 法务复核记录 | +| removedAt / removeReason | 解除时间与原因 | +| createdBy / createdAt / approvedBy / approvedAt | 操作与审批 | +| attachments / sensitiveLevel / financeDetail | 附件与脱敏 | +| removalRequested | 解除申请标记(原型) | -抽屉「操作日志」Tab 按客户过滤;原型预置样例日志见 `data/risk-tag-operation-logs.json`。 +### 9.7 标签生命周期 + +| 状态 | 说明 | 同步至 `labels` | +|---|---|---| +| 生效中 | 正常展示 | 是 | +| 待复核 | 法务标签超过下次复核时间,**仍生效** | 是 | +| 待审批 | 高/致命新增后 | 否 | +| 已驳回 | 审批拒绝 | 否 | +| 已解除 | 解除成功(含复核确认解除并审批通过) | 否 | +| 已失效 | 预留(法务标签**不会**自动进入此状态) | 否 | + +**法务复核与解除规则:** + +- 法务标签持续生效,系统仅按 `nextReviewAt` 提醒复核,**不自动解除** +- 复核维持:在工作台完成查验(原型阶段以 Toast + 日志表示);如需解除使用「申请解除」 +- 复核确认风险解除:填写解除原因 → 提交解除申请 → 审批通过后 `status → 已解除` 并记录 `removedAt` +- 任意路径的解除均需审批,不允许系统自动解除法务标签 + +### 9.8 敏感信息脱敏 + +| 场景 | 规则 | +|---|---| +| 财务敏感 | 非 finance / manager / admin 不可见逾期金额、账单编号等;提示「财务敏感信息已隐藏」 | +| 法务附件 | 非 legal / riskControl / manager / admin 不可见法务附件;提示「法务敏感附件仅授权角色可见」 | +| 操作日志 | 财务类日志 reason 对非授权角色脱敏 | --- -## 11. 原型实现索引 +## 10. 权限与审批 + +### 10.1 分区维护(打标 / 解除) + +| 角色 | 可维护分区 | 可选标签范围 | +|---|---|---| +| 财务 | 财务风险 | 信用与回款 + 开票异常 | +| 法务 | 法务风险 | 合规与法律 | +| 业管 | 业管风险 | 经营与履约 + 多头签约 | +| 系统管理员 | 全部 | 全部 11 项 | +| 销售 / 运维 / 风控 / 管理层 | — | 不可新增(风控/管理层可审批) | + +### 10.2 审批权限 + +法务、风控、管理层、系统管理员可审批「待审批」新增标签,以及「解除待审批」的解除申请;支持通过/驳回,驳回须填原因。 + +### 10.3 查看权限 + +所有角色可打开 Drawer 查看标签名称与级别;敏感字段按 §9.8 脱敏。 + +--- + +## 11. 操作日志 + +记录:新增标签、解除标签、提交审批、审批通过、审批驳回、**工作台待办**、批量打标。 + +| 字段 | 说明 | +|---|---| +| 操作时间 | `YYYY-MM-DD HH:mm:ss` | +| 操作人 / 操作角色 | 姓名 + 角色 | +| 操作类型 | 见上 | +| 标签名称 / 标签类型 | tagName + tagCategoryName | +| 操作原因 | reason | +| 变更前/后状态 | statusBefore → statusAfter | + +抽屉内按客户过滤、时间倒序;不同类型以 Tag 区分。 + +--- + +## 12. 原型边界(当前实现) + +| 能力 | 状态 | +|---|---| +| 列表 + KPI + 风险标签 Drawer | 已实现 | +| 详情 / 编辑 / 查看页 | Toast 演示 | +| 新增客户落库 | Toast 演示,不写回列表 | +| 高风险解除申请审批闭环 | 已实现:申请 → 审批通过/驳回 | +| 黑名单与标签联动 | 仅 Toast 提示,不自动改 `status` | +| 定时复核任务 | 仅前端 Mock 提示 | + +--- + +## 13. 原型实现索引 | 能力 | 代码位置 | |---|---| | 列表编排 | `index.tsx` | +| KPI 卡片 | `components/RiskKpiCards.tsx`、`utils/riskKpi.ts` | | 风险标签列 | `components/RiskLabelTags.tsx` | | 管理抽屉 | `components/RiskTagDrawer.tsx` | | 批量打标 | `components/BatchRiskTagModal.tsx` | -| 打标表单 | `components/RiskTagForm.tsx` | +| 打标表单 / 详情 | `components/RiskTagForm.tsx` | | 标签目录 | `utils/riskTagConstants.ts` | | 权限 | `utils/permissions.ts` | | 业务逻辑 | `utils/riskTagStore.ts` | -| 类型定义 | `types/riskTag.ts` | +| 列表筛选 | `utils/filters.ts` | +| 类型定义 | `types/riskTag.ts`、`types.ts` | diff --git a/src/prototypes/customer-management/annotation-source.json b/src/prototypes/customer-management/annotation-source.json index 2594d62..527130d 100644 --- a/src/prototypes/customer-management/annotation-source.json +++ b/src/prototypes/customer-management/annotation-source.json @@ -5,7 +5,7 @@ "version": 2, "prototypeName": "customer-management", "pageId": "list", - "updatedAt": 1782896901748, + "updatedAt": 1782970453061, "nodes": [ { "id": "cm-list-filter", @@ -39,8 +39,8 @@ "fingerprint": "cm-risk-role-switch", "path": [] }, - "aiPrompt": "模拟法务/财务/运维/风控/销售角色,驱动打标权限。", - "annotationText": "切换角色后抽屉内标签类型与可选标签联动更新。", + "aiPrompt": "模拟 8 类角色(销售/法务/财务/业管/运维/风控/管理层/管理员),驱动分区打标与审批权限。", + "annotationText": "切换角色后 Drawer 分区维护、可选标签、审批与脱敏规则联动更新。", "hasMarkdown": true, "color": "#64748b", "images": [], @@ -48,8 +48,28 @@ "updatedAt": 1740787200000 }, { - "id": "cm-list-toolbar", + "id": "cm-risk-kpi-cards", "index": 3, + "title": "风险 KPI 卡片", + "pageId": "list", + "locator": { + "selectors": [ + "[data-annotation-id=\"cm-risk-kpi-cards\"]" + ], + "fingerprint": "cm-risk-kpi-cards", + "path": [] + }, + "aiPrompt": "按风险类型快速筛选客户列表。", + "annotationText": "5 张卡片:全部客户、有风险客户、财务风险、法务风险、业管风险。", + "hasMarkdown": true, + "color": "#059669", + "images": [], + "createdAt": 1740787200000, + "updatedAt": 1740787200000 + }, + { + "id": "cm-list-toolbar", + "index": 4, "title": "列表工具栏", "pageId": "list", "locator": { @@ -69,7 +89,7 @@ }, { "id": "cm-list-table", - "index": 4, + "index": 5, "title": "客户台账列表", "pageId": "list", "locator": { @@ -89,8 +109,8 @@ }, { "id": "cm-risk-tag-drawer", - "index": 5, - "title": "风险标签管理抽屉", + "index": 6, + "title": "风险标签抽屉", "pageId": "list", "locator": { "selectors": [ @@ -99,8 +119,8 @@ "fingerprint": "cm-risk-tag-drawer", "path": [] }, - "aiPrompt": "单客户打标、审批、移除、日志。", - "annotationText": "仅列表页维护标签,不在新增/编辑表单维护。", + "aiPrompt": "单客户打标、法务工作台待办、审批、解除申请与审批、日志。", + "annotationText": "打标时间/下次复核时间只读;解除说明按标签类型区分(法务含查验提示,财务/业管不含)。", "hasMarkdown": true, "color": "#d97706", "images": [], @@ -109,7 +129,7 @@ }, { "id": "cm-batch-risk-tag-btn", - "index": 6, + "index": 7, "title": "批量打标入口", "pageId": "list", "locator": { @@ -129,7 +149,7 @@ }, { "id": "cm-batch-risk-tag-modal", - "index": 7, + "index": 8, "title": "批量打标弹窗", "pageId": "list", "locator": { @@ -149,7 +169,7 @@ }, { "id": "cm-create-base", - "index": 8, + "index": 9, "title": "客户信息", "pageId": "create", "locator": { @@ -160,7 +180,7 @@ "path": [] }, "aiPrompt": "客户类型、全称、城市、部门负责人、合作状态等。", - "annotationText": "通讯地址自动识别省市;不含风险标签字段。", + "annotationText": "通讯地址自动识别省市;合作状态四选一;不含风险标签字段。", "hasMarkdown": true, "color": "#2563eb", "images": [], @@ -169,7 +189,7 @@ }, { "id": "cm-create-contact", - "index": 9, + "index": 10, "title": "联系人信息", "pageId": "create", "locator": { @@ -189,7 +209,7 @@ }, { "id": "cm-create-invoice", - "index": 10, + "index": 11, "title": "付款及开票信息", "pageId": "create", "locator": { @@ -209,7 +229,7 @@ }, { "id": "cm-create-footer", - "index": 11, + "index": 12, "title": "底部操作栏", "pageId": "create", "locator": { @@ -230,17 +250,18 @@ ] }, "markdownMap": { - "cm-list-filter": "## 4. 筛选区\r\n\r\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\r\n\r\n| 字段 | 控件 | 匹配规则 |\r\n|---|---|---|\r\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\r\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\r\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签(OR) |\r\n| 区域 | 可搜索单选 | 精确匹配 `region` |\r\n| 客户状态 | 多选 | `status` 在选中集合内 |\r\n| 业务部门 | 多选 | `department` 在选中集合内 |\r\n| 业务负责人 | 多选 | 随部门联动选项;`manager` 在选中集合内 |\r\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\r\n\r\n### 4.1 操作逻辑\r\n\r\n| 操作 | 行为 |\r\n|---|---|\r\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1,清空行选中 |\r\n| **重置** | 清空待应用与已应用条件,页码重置为 1,清空行选中 |\r\n| 修改筛选项未点查询 | 不影响当前列表 |\r\n\r\n---", - "cm-risk-role-switch": "### 5.1 操作角色切换(原型演示)\r\n\r\n列表顶部提供「当前操作角色」下拉,用于模拟不同部门用户:\r\n\r\n| 角色 | 部门 | 打标权限 |\r\n|---|---|---|\r\n| 王风控 | 风控部 | 全部标签 |\r\n| 李法务 | 法务部 | 法务类标签 |\r\n| 张财务 | 财务部 | 财务类标签 |\r\n| 赵运维 | 运维部 | 运维类标签 |\r\n| 陈销售 | 销售一部 | 仅查看,无打标入口 |\r\n\r\n切换角色后,风险标签管理抽屉内的**标签类型**、**可选标签**、**审批/移除按钮**随之联动更新。\r\n\r\n---", - "cm-list-toolbar": "## 5. 列表工具栏\r\n\r\n| 按钮 | 行为 |\r\n|---|---|\r\n| 新增客户 | 进入新增客户页(分组表单) |\r\n| 批量导入 | Toast:批量导入(原型演示) |\r\n| **批量打标** | 勾选 ≥1 行且当前角色有打标权限时显示;打开批量打标弹窗 |\r\n| 批量导出 | Toast:导出当前列表条数 |\r\n\r\n### 5.1 操作角色切换(原型演示)\r\n\r\n列表顶部提供「当前操作角色」下拉,用于模拟不同部门用户:\r\n\r\n| 角色 | 部门 | 打标权限 |\r\n|---|---|---|\r\n| 王风控 | 风控部 | 全部标签 |\r\n| 李法务 | 法务部 | 法务类标签 |\r\n| 张财务 | 财务部 | 财务类标签 |\r\n| 赵运维 | 运维部 | 运维类标签 |\r\n| 陈销售 | 销售一部 | 仅查看,无打标入口 |\r\n\r\n切换角色后,风险标签管理抽屉内的**标签类型**、**可选标签**、**审批/移除按钮**随之联动更新。\r\n\r\n---", - "cm-list-table": "## 6. 列表字段说明\r\n\r\n| 列 | 说明 |\r\n|---|---|\r\n| 客户编号 | 左固定列 |\r\n| 客户名称 | 超长省略 |\r\n| **风险标签** | 展示「生效中」标签;按风险级别着色(低/中/高/极高);首标签 + `+N` 悬浮全部;有待审批时显示「审」标识;可点击打开管理抽屉 |\r\n| 客户区域 | 大区名称 |\r\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\r\n| 业务部门 / 业务负责人 | 文本 |\r\n| 客户类型 / 所属行业 | 文本 |\r\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\r\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\r\n| 操作 | 详情、编辑、**管理标签**(有权限时)、删除(原型 Toast) |\r\n\r\n支持行多选(checkbox,仅当前页);横向滚动;操作列右固定。\r\n\r\n---", - "cm-risk-tag-drawer": "## 8. 风险标签维护(列表页专属)\r\n\r\n### 8.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」 | 打开右侧抽屉,管理单个客户 |\r\n| 点击标签列 | 有权限时同「管理标签」 |\r\n| 工具栏「批量打标」 | 对当前勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。\r\n\r\n### 8.2 管理抽屉结构\r\n\r\n| Tab | 内容 |\r\n|---|---|\r\n| 当前标签 | 已打标签列表:名称、类型、级别、状态、原因摘要;支持查看详情、移除、审批 |\r\n| 新增标签 | 打标表单(见 8.3) |\r\n| 操作日志 | 该客户全部标签相关审计记录 |\r\n\r\n### 8.3 打标表单字段\r\n\r\n| 字段 | 必填 | 规则 |\r\n|---|---|---|\r\n| 标签名称 | 是 | 按 7.1–7.4 分组下拉;仅当前角色有权限的标签可选;已存在同名有效标签不可重复选 |\r\n| 标签类型 | 是 | **根据当前操作角色自动带出**(财务→财务、法务→法务、运维→运维、风控→风控),**置灰不可编辑** |\r\n| 风险级别 | 是 | **选择标签名称后按目录自动带出**,**置灰不可编辑** |\r\n| 生效时间 | 是 | 日期,默认当天 |\r\n| 有效期至 | 是 | 日期,须 ≥ 生效时间;默认一年后 |\r\n| 原因说明 | 是 | 至少 10 字,供法务/财务/风控审批留痕 |\r\n| 附件 | 否 | 支持 .pdf / .jpg / .png / .xlsx,可多个 |\r\n\r\n### 8.4 批量打标\r\n\r\n- 展示已选客户 chips(超过 6 个显示 +N)\r\n- 表单字段与单客户打标一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 提交后写入批量操作日志\r\n\r\n### 8.5 标签生命周期\r\n\r\n| 状态 | 说明 |\r\n|---|---|\r\n| 生效中 | 参与列表展示,同步至 `labels` |\r\n| 待审批 | 高风险标签提交后进入;列表暂不展示为生效 |\r\n| 待确认 | 预留 |\r\n| 已驳回 | 审批未通过,不同步至 `labels` |\r\n| 已移除 | 操作人主动移除,需填写原因 |\r\n| 已过期 | 预留 |\r\n\r\n**高风险规则:** 风险级别为「高」或「极高」的标签提交后状态为「待审批」,须法务或风控审批通过后变为「生效中」。\r\n\r\n### 8.6 移除标签\r\n\r\n- 仅「生效中」且当前角色有移除权限的标签可移除\r\n- 须填写移除原因(至少 5 字)\r\n- 写入操作日志,并从列表 `labels` 同步清除\r\n\r\n---", - "cm-batch-risk-tag-btn": "### 8.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」 | 打开右侧抽屉,管理单个客户 |\r\n| 点击标签列 | 有权限时同「管理标签」 |\r\n| 工具栏「批量打标」 | 对当前勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。", - "cm-batch-risk-tag-modal": "### 8.4 批量打标\r\n\r\n- 展示已选客户 chips(超过 6 个显示 +N)\r\n- 表单字段与单客户打标一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 提交后写入批量操作日志", - "cm-create-base": "## 2. 客户信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\r\n| 客户分级 | 否 | 下拉 | A / B / C |\r\n| 客户全称 | 是 | 文本 | 与客户证照一致;变更时同步发票抬头 |\r\n| 客户简称 | 否 | 文本 | 选填 |\r\n| 所属城市 | 是 | 省-市级联 | 示例省市区数据 |\r\n| 通讯地址 | 否 | 文本 | |\r\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\r\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\r\n| 备注 | 否 | 多行文本 | |\r\n\r\n---", + "cm-list-filter": "## 4. 筛选区\r\n\r\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\r\n\r\n| 字段 | 控件 | 匹配规则 |\r\n|---|---|---|\r\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\r\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\r\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签(OR) |\r\n| 区域 | 可搜索单选 | 精确匹配 `region` |\r\n| 客户状态 | 多选 | `status` 在选中集合内 |\r\n| 业务部门 | 多选 | `department` 在选中集合内 |\r\n| 业务负责人 | 多选 | 随部门联动;`manager` 在选中集合内 |\r\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\r\n\r\n### 4.1 操作逻辑\r\n\r\n| 操作 | 行为 |\r\n|---|---|\r\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1,清空行选中 |\r\n| **重置** | 清空待应用与已应用条件;**KPI 卡片回到「全部客户」**;页码重置为 1;清空行选中 |\r\n| 修改筛选项未点查询 | 不影响当前列表 |\r\n\r\n### 4.2 与 KPI 卡片的关系\r\n\r\n- **KPI 卡片**:点击即生效,基于 `riskTagsByCustomer`(含待审批标签)。\r\n- **筛选区「标签」**:须点「查询」后生效,基于 `labels`(仅生效中标签)。\r\n- 二者叠加时取交集;口径差异见 §5 说明。\r\n\r\n---", + "cm-risk-role-switch": "### 6.1 操作角色切换(原型演示)\r\n\r\n列表顶部「当前操作角色(原型演示)」下拉,按**角色**切换(非按姓名),模拟 8 类用户:\r\n\r\n| 角色 | 演示账号 | 部门 | 打标 | 审批 | 说明 |\r\n|---|---|---|---|---|---|\r\n| 销售 | 陈销售 | 销售一部 | 否 | 否 | 可查看标签/抽屉,操作列「查看标签」 |\r\n| 法务 | 李法务 | 法务部 | 法务分区 | 是 | 维护合规与法律类标签 |\r\n| 财务 | 张财务 | 财务部 | 财务分区 | 否 | 信用与回款 + 开票异常 |\r\n| 业管 | 王业管 | 业管部 | 业管分区 | 否 | 经营与履约 + 多头签约 |\r\n| 运维/资产 | 赵运维 | 运维资产部 | 否 | 否 | 仅查看 |\r\n| 风控 | 刘风控 | 风控部 | 否 | 是 | 可审批待审批标签 |\r\n| 管理层 | 周管理 | 管理层 | 否 | 是 | 可审批;可看财务/法务敏感信息 |\r\n| 系统管理员 | 系统管理员 | 系统管理部 | 全部 | 是 | 全部维护与审批权限 |\r\n\r\n切换角色后,Drawer 内**分区维护按钮**、**新增标签可选范围**、**审批/解除按钮**、**敏感信息脱敏**随之联动。\r\n\r\n---", + "cm-risk-kpi-cards": "## 5. 风险 KPI 卡片筛选\r\n\r\n样式与供应商/车辆管理 KPI 卡片一致(`vm-kpi-*`),位于筛选区与工具栏之间。点击卡片切换列表数据范围并回到第 1 页;右上角「?」展示口径说明。\r\n\r\n| KPI 卡片 | 统计/筛选口径 |\r\n|---|---|\r\n| 全部客户 | 台账全部记录 |\r\n| 有风险客户 | 存在**生效中**或**待审批**风险标签 |\r\n| 财务风险 | 含 `tagCategory = finance` 的有效标签(信用与回款、开票异常等) |\r\n| 法务风险 | 含 `tagCategory = legal` 的有效标签(合规与法律类) |\r\n| 业管风险 | 含 `tagCategory = business` 的有效标签(经营与履约、多头签约等) |\r\n\r\n代码:`components/RiskKpiCards.tsx`、`utils/riskKpi.ts`。\r\n\r\n---", + "cm-list-toolbar": "## 6. 列表工具栏\r\n\r\n| 按钮 | 行为 |\r\n|---|---|\r\n| 新增客户 | 进入新增客户页 |\r\n| 批量导入 | Toast:批量导入(原型演示) |\r\n| **批量打标** | 勾选 ≥1 行且当前角色有打标权限时显示 |\r\n| 批量导出 | Toast:导出当前列表条数 |\r\n\r\n### 6.1 操作角色切换(原型演示)\r\n\r\n列表顶部「当前操作角色(原型演示)」下拉,按**角色**切换(非按姓名),模拟 8 类用户:\r\n\r\n| 角色 | 演示账号 | 部门 | 打标 | 审批 | 说明 |\r\n|---|---|---|---|---|---|\r\n| 销售 | 陈销售 | 销售一部 | 否 | 否 | 可查看标签/抽屉,操作列「查看标签」 |\r\n| 法务 | 李法务 | 法务部 | 法务分区 | 是 | 维护合规与法律类标签 |\r\n| 财务 | 张财务 | 财务部 | 财务分区 | 否 | 信用与回款 + 开票异常 |\r\n| 业管 | 王业管 | 业管部 | 业管分区 | 否 | 经营与履约 + 多头签约 |\r\n| 运维/资产 | 赵运维 | 运维资产部 | 否 | 否 | 仅查看 |\r\n| 风控 | 刘风控 | 风控部 | 否 | 是 | 可审批待审批标签 |\r\n| 管理层 | 周管理 | 管理层 | 否 | 是 | 可审批;可看财务/法务敏感信息 |\r\n| 系统管理员 | 系统管理员 | 系统管理部 | 全部 | 是 | 全部维护与审批权限 |\r\n\r\n切换角色后,Drawer 内**分区维护按钮**、**新增标签可选范围**、**审批/解除按钮**、**敏感信息脱敏**随之联动。\r\n\r\n---", + "cm-list-table": "## 7. 列表字段说明\r\n\r\n| 列 | 说明 |\r\n|---|---|\r\n| 客户编号 | 左固定列 |\r\n| 客户名称 | 超长省略 |\r\n| **风险标签** | 最多展示 **2** 个生效中标签;超出显示 `+N`;悬浮/点击 `+N` 展示全部;按级别着色(低/中/高/致命);Tooltip 含类型、责任部门、级别、状态;**所有角色**可点击打开 Drawer |\r\n| 客户区域 | 大区名称 |\r\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\r\n| 业务部门 / 业务负责人 | 文本 |\r\n| 客户类型 / 所属行业 | 文本 |\r\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\r\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\r\n| 操作 | 详情、编辑(Toast 演示)、**管理标签** / **查看标签**、删除(Toast 演示) |\r\n\r\n支持行多选(checkbox,仅当前页);横向滚动;操作列右固定。\r\n\r\n### 7.1 风险标签列展示样式\r\n\r\n| 风险级别 | 样式 |\r\n|---|---|\r\n| 低 | 灰色边框 |\r\n| 中 | 橙色浅底 |\r\n| 高 | 红色浅底 |\r\n| 致命 | 深红浅底 |\r\n\r\n无生效中标签但有待审批时,列可展示待审批标识;全无标签显示「—」。\r\n\r\n---", + "cm-risk-tag-drawer": "## 9. 风险标签维护(列表页专属)\r\n\r\n### 9.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」/「查看标签」 | 打开右侧 Drawer |\r\n| 点击风险标签列 | 同打开 Drawer |\r\n| 工具栏「批量打标」 | 对勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。\r\n\r\n### 9.2 管理抽屉结构\r\n\r\n| Tab | 内容 |\r\n|---|---|\r\n| 当前标签 | 按 **法务风险 / 财务风险 / 业管风险** 三分区 Card 展示;含分区说明、维护权限提示、标签卡片、空状态 |\r\n| 新增标签 | 角色受限打标表单(见 9.3) |\r\n| 操作日志 | 该客户审计记录,按时间倒序 |\r\n\r\nDrawer 标题:**风险标签**;副标题为客户名称与编号。\r\n\r\n### 9.3 当前标签页 — 分区与卡片\r\n\r\n展示顺序:法务 → 财务 → 业管。\r\n\r\n每个分区包含:\r\n\r\n1. 分区标题与说明(责任部门、关注范围)\r\n2. 当前角色是否可维护(「可维护」/「当前角色无权维护该类标签」)\r\n3. 「新增该类标签」按钮(有权限时)\r\n4. 标签卡片:名称、级别、状态、时间字段、责任部门、操作人、原因摘要\r\n - **法务风险**:打标时间、复核周期、下次复核时间、最后复核时间(不展示「有效期至」)\r\n - **财务/业管**:打标时间、有效期至\r\n5. 空状态:「暂无该类风险标签」\r\n\r\n卡片操作:查看详情;审批(新增待审批或**解除待审批**且有权);**申请解除**(有权且生效中,须走审批流)。\r\n\r\n**法务风险复核规则(原型 Mock):**\r\n\r\n- 法务标签自打标后持续生效,**不会因到期自动解除**\r\n- 距 `nextReviewAt` ≤ 7 天:卡片显示「即将复核」\r\n- 当前日期超过 `nextReviewAt`:状态显示「待复核」,标签仍保留\r\n- **不在 Drawer 内完成复核**;系统自动向**法务工作台**推送待办,法务查验后回到本页「申请解除」\r\n- 打开 Drawer 且存在待复核法务标签时,页顶 Banner 提示待办已推送,并写入操作日志(工作台待办)\r\n- 确认可解除时:填写解除原因 → 提交解除申请(走审批流);通过后记录 `removedAt`\r\n\r\n**解除审批(原型 Mock):**\r\n\r\n- 任意标签解除均提交申请,状态为「解除待审批」\r\n- 申请解除弹窗说明:**法务标签**提示「法务查验完成后…」;**财务/业管标签**仅提示「提交解除申请,经审批后生效」(不含法务查验文案)\r\n- 法务/风控/管理层/系统管理员可审批:通过 → 已解除;驳回 → 恢复生效并清除申请标记\r\n\r\n### 9.4 新增标签表单\r\n\r\n| 字段 | 必填 | 规则 |\r\n|---|---|---|\r\n| 标签类型 | 是 | 非 admin:按角色自动带出法务/财务/业管并置灰;admin 可选三分区 |\r\n| 标签名称 | 是 | 按 8.1–8.4 optgroup;仅当前角色可维护的标签;不可重复有效同名标签 |\r\n| 风险级别 | 是 | 选标签后自动带出,置灰 |\r\n| 打标时间 | 是 | 日期(只读置灰) | **取操作当天**,不可编辑 |\r\n| 复核周期 | 法务必填 | 下拉 | 1 / 2 / 3 / 6 个月;变更时自动推算下次复核时间 |\r\n| 下次复核时间 | 法务必填 | 日期(只读置灰) | 根据打标时间 + 复核周期**自动推算**,不可编辑;下方提示「到期仅提醒责任人复核,不会自动解除标签。」 |\r\n| 有效期至 | 财务/业管必填 | 日期 | ≥ 打标时间;业务有效期,与复核提醒无关 |\r\n| 原因说明 | 是 | ≥ 10 字 |\r\n| 附件 | 高/致命必填 | .pdf / .jpg / .png / .xlsx |\r\n\r\n**法务 vs 财务/业管字段差异:**\r\n\r\n- 法务标签**不展示**「有效期至」,使用「下次复核时间」驱动定期复核提醒\r\n- 财务/业管保留「有效期至」,不在 UI 上与复核时间混用\r\n\r\n提交规则:\r\n\r\n- 低/中:直接 **生效中**\r\n- 高/致命:**待审批**;提交前二次确认\r\n- 涉诉/仲裁、严重拖欠、制裁/高风险地区:Toast 提示可能触发黑名单审批\r\n\r\n销售/运维/风控/管理层(非 admin):新增 Tab 展示无权限说明。\r\n\r\n### 9.5 批量打标\r\n\r\n- 已选客户 chips(>6 显示 +N)\r\n- 表单与单客户一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 写入批量操作日志\r\n\r\n### 9.6 统一数据模型 `CustomerRiskTag`\r\n\r\n| 字段 | 说明 |\r\n|---|---|\r\n| id / customerId | 标识 |\r\n| tagCode / tagName | 目录代码与名称 |\r\n| tagCategory / tagCategoryName | legal / finance / business |\r\n| responsibleDept / editableRoles | 责任部门与可编辑角色 |\r\n| riskLevel / riskLevelName | low / medium / high / fatal |\r\n| status / statusName | active / pending / expired / removed / rejected |\r\n| source / sourceName | system / manual |\r\n| reason / taggedAt / nextReviewAt / reviewCycle | 原因与复核时间(法务) |\r\n| expireDate | 财务/业管业务有效期 |\r\n| lastReviewedAt / reviewStatus / reviewLogs | 法务复核记录 |\r\n| removedAt / removeReason | 解除时间与原因 |\r\n| createdBy / createdAt / approvedBy / approvedAt | 操作与审批 |\r\n| attachments / sensitiveLevel / financeDetail | 附件与脱敏 |\r\n| removalRequested | 解除申请标记(原型) |\r\n\r\n### 9.7 标签生命周期\r\n\r\n| 状态 | 说明 | 同步至 `labels` |\r\n|---|---|---|\r\n| 生效中 | 正常展示 | 是 |\r\n| 待复核 | 法务标签超过下次复核时间,**仍生效** | 是 |\r\n| 待审批 | 高/致命新增后 | 否 |\r\n| 已驳回 | 审批拒绝 | 否 |\r\n| 已解除 | 解除成功(含复核确认解除并审批通过) | 否 |\r\n| 已失效 | 预留(法务标签**不会**自动进入此状态) | 否 |\r\n\r\n**法务复核与解除规则:**\r\n\r\n- 法务标签持续生效,系统仅按 `nextReviewAt` 提醒复核,**不自动解除**\r\n- 复核维持:在工作台完成查验(原型阶段以 Toast + 日志表示);如需解除使用「申请解除」\r\n- 复核确认风险解除:填写解除原因 → 提交解除申请 → 审批通过后 `status → 已解除` 并记录 `removedAt`\r\n- 任意路径的解除均需审批,不允许系统自动解除法务标签\r\n\r\n### 9.8 敏感信息脱敏\r\n\r\n| 场景 | 规则 |\r\n|---|---|\r\n| 财务敏感 | 非 finance / manager / admin 不可见逾期金额、账单编号等;提示「财务敏感信息已隐藏」 |\r\n| 法务附件 | 非 legal / riskControl / manager / admin 不可见法务附件;提示「法务敏感附件仅授权角色可见」 |\r\n| 操作日志 | 财务类日志 reason 对非授权角色脱敏 |\r\n\r\n---", + "cm-batch-risk-tag-btn": "### 9.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」/「查看标签」 | 打开右侧 Drawer |\r\n| 点击风险标签列 | 同打开 Drawer |\r\n| 工具栏「批量打标」 | 对勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。", + "cm-batch-risk-tag-modal": "### 9.5 批量打标\r\n\r\n- 已选客户 chips(>6 显示 +N)\r\n- 表单与单客户一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 写入批量操作日志", + "cm-create-base": "## 2. 客户信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\r\n| 客户全称 | 是 | 文本 | 与证照一致;变更时同步发票抬头 |\r\n| 客户简称 | 否 | 文本 | 选填 |\r\n| 省市 | 否 | 文本(置灰) | 自动从通讯地址/地址解析识别 |\r\n| 所属城市 | 是 | 省-市(置灰) | **由地址解析自动识别**,不可手选 |\r\n| 通讯地址 | 是 | 文本 | 填写完整地址后解析省、市(同供应商管理交互) |\r\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\r\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\r\n| 合作状态 | 否 | 下拉 | 已合作 / 终止合作 / 洽谈中 / 合同到期 |\r\n| 备注 | 否 | 多行文本 | |\r\n\r\n**已移除:** 客户分级(A/B/C)不在新增页展示。\r\n\r\n---", "cm-create-contact": "## 3. 联系人信息\r\n\r\n| 字段 | 必填 | 控件 |\r\n|---|---|---|\r\n| 姓名 | 否 | 文本 |\r\n| 手机号 | 否 | 文本 |\r\n| 职位 | 否 | 文本 |\r\n\r\n---", "cm-create-invoice": "## 4. 付款及开票信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 发票抬头 | 是 | 文本(禁用) | 自动带入客户全称,不可手改 |\r\n| 纳税人识别号 | 是 | 文本 | |\r\n| 注册地址 | 否 | 文本 | |\r\n| 注册电话 | 否 | 文本 | |\r\n| 开户银行 | 是 | 文本 | |\r\n| 账号 | 否 | 文本 | |\r\n\r\n---", - "cm-create-footer": "## 5. 校验与提交\r\n\r\n- 提交前校验所有必填项\r\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\r\n- 重置清空全部字段,客户类型默认「企业」" + "cm-create-footer": "## 5. 校验与提交\r\n\r\n| 校验项 | 规则 |\r\n|---|---|\r\n| 客户类型 | 必选 |\r\n| 客户全称 | 非空 |\r\n| 所属城市 | 通讯地址须能识别出省、市 |\r\n| 业务负责部门 / 人员 | 必选 |\r\n| 发票抬头 / 税号 / 开户银行 | 非空 |\r\n\r\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\r\n- 重置清空全部字段;客户类型默认「企业」\r\n- 校验失败:Toast 首条错误(原型阶段)\r\n\r\n---" }, "assetMap": {}, "directory": { @@ -255,7 +276,7 @@ "type": "markdown", "id": "cm-doc-overview", "title": "模块总览", - "markdown": "## 1. 模块定位\r\n\r\n| 项 | 说明 |\r\n|---|---|\r\n| 模块名称 | 客户管理 — 列表页 |\r\n| 目标用户 | 销售、业务负责人、运营、**法务、财务、风控** |\r\n| 核心任务 | 按多维度筛选客户台账,查看风险标签与状态,**在列表页维护客户风险标签**,新增客户或进入详情/编辑 |\r\n| 页面结构 | 面包屑 + **操作角色切换** + 筛选区 + 工具栏 + 表格 + 分页 |\r\n\r\n**重要约束:** 风险标签**不在**客户新增页、客户编辑表单中维护,仅在列表页作为操作入口。\r\n\r\n---" + "markdown": "## 1. 模块定位\r\n\r\n| 项 | 说明 |\r\n|---|---|\r\n| 模块名称 | 客户管理 — 列表页 |\r\n| 目标用户 | 销售、业务负责人、**法务、财务、业管、运维/资产、风控、管理层** |\r\n| 核心任务 | 按多维度与 KPI 卡片筛选客户;查看并**在列表页维护风险标签**;新增客户(详情/编辑为原型演示) |\r\n| 页面结构 | 面包屑 + **操作角色切换** + 筛选区 + **风险 KPI 卡片** + 工具栏 + 表格 + 分页 |\r\n\r\n**重要约束:**\r\n\r\n- 风险标签**不在**客户新增页、客户编辑表单中维护,仅在列表页作为操作入口。\r\n- 底层使用统一 `riskTagsByCustomer` 数组(`CustomerRiskTag[]`),前台按**法务 / 财务 / 业管**三分区展示,不按 `legalTags` / `financeTags` 等字段拆散存储。\r\n\r\n---" }, { "type": "link", @@ -274,13 +295,13 @@ "type": "markdown", "id": "cm-doc-prd-list", "title": "列表页 PRD", - "markdown": "# 客户管理 · 列表页产品需求说明(PRD)\r\n\r\n> 本页为客户主数据台账列表。标注「原型演示」的跳转、导入、导出仅 Toast 提示。\r\n\r\n---\r\n\r\n## 1. 模块定位\r\n\r\n| 项 | 说明 |\r\n|---|---|\r\n| 模块名称 | 客户管理 — 列表页 |\r\n| 目标用户 | 销售、业务负责人、运营、**法务、财务、风控** |\r\n| 核心任务 | 按多维度筛选客户台账,查看风险标签与状态,**在列表页维护客户风险标签**,新增客户或进入详情/编辑 |\r\n| 页面结构 | 面包屑 + **操作角色切换** + 筛选区 + 工具栏 + 表格 + 分页 |\r\n\r\n**重要约束:** 风险标签**不在**客户新增页、客户编辑表单中维护,仅在列表页作为操作入口。\r\n\r\n---\r\n\r\n## 2. 客户编号规则(集团多子公司)\r\n\r\n- 总长度 7,仅含大写字母 A–Z 与数字 0–9\r\n- 结构:第 1 位 = 签约/归属子公司代码;第 2–7 位 = 6 位数字流水号(000001–999999)\r\n- 流水号在「同一子公司代码」下递增\r\n- 校验正则:`/^[A-Z][0-9]{6}$/`\r\n- 示例:A000001(华东)、B000001(华南)、C000001(华北)等\r\n\r\n---\r\n\r\n## 3. 数据来源\r\n\r\n| 数据 | 来源 | 用途 |\r\n|---|---|---|\r\n| 客户台账 | `data/customers.json`(20 条样例) | 列表、筛选选项 |\r\n| 客户标签(展示) | 客户 `labels` 字段 | 列表「风险标签」列、筛选区「标签」多选 |\r\n| 风险标签明细 | `riskTagsByCustomer` 运行时状态 + 种子迁移 | 标签管理抽屉、审批、日志 |\r\n| 标签目录 | `utils/riskTagConstants.ts` → `TAG_CATALOG` | 打标表单选项、级别、分组 |\r\n| 操作日志 | `data/risk-tag-operation-logs.json` + 运行时追加 | 抽屉「操作日志」Tab |\r\n| 部门-负责人 | 由台账聚合 + `DEPT_MANAGER_MAP` | 筛选联动 |\r\n\r\n列表 `labels` 与「生效中」风险标签自动同步,保证筛选器与列表展示一致。\r\n\r\n---\r\n\r\n## 4. 筛选区\r\n\r\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\r\n\r\n| 字段 | 控件 | 匹配规则 |\r\n|---|---|---|\r\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\r\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\r\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签(OR) |\r\n| 区域 | 可搜索单选 | 精确匹配 `region` |\r\n| 客户状态 | 多选 | `status` 在选中集合内 |\r\n| 业务部门 | 多选 | `department` 在选中集合内 |\r\n| 业务负责人 | 多选 | 随部门联动选项;`manager` 在选中集合内 |\r\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\r\n\r\n### 4.1 操作逻辑\r\n\r\n| 操作 | 行为 |\r\n|---|---|\r\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1,清空行选中 |\r\n| **重置** | 清空待应用与已应用条件,页码重置为 1,清空行选中 |\r\n| 修改筛选项未点查询 | 不影响当前列表 |\r\n\r\n---\r\n\r\n## 5. 列表工具栏\r\n\r\n| 按钮 | 行为 |\r\n|---|---|\r\n| 新增客户 | 进入新增客户页(分组表单) |\r\n| 批量导入 | Toast:批量导入(原型演示) |\r\n| **批量打标** | 勾选 ≥1 行且当前角色有打标权限时显示;打开批量打标弹窗 |\r\n| 批量导出 | Toast:导出当前列表条数 |\r\n\r\n### 5.1 操作角色切换(原型演示)\r\n\r\n列表顶部提供「当前操作角色」下拉,用于模拟不同部门用户:\r\n\r\n| 角色 | 部门 | 打标权限 |\r\n|---|---|---|\r\n| 王风控 | 风控部 | 全部标签 |\r\n| 李法务 | 法务部 | 法务类标签 |\r\n| 张财务 | 财务部 | 财务类标签 |\r\n| 赵运维 | 运维部 | 运维类标签 |\r\n| 陈销售 | 销售一部 | 仅查看,无打标入口 |\r\n\r\n切换角色后,风险标签管理抽屉内的**标签类型**、**可选标签**、**审批/移除按钮**随之联动更新。\r\n\r\n---\r\n\r\n## 6. 列表字段说明\r\n\r\n| 列 | 说明 |\r\n|---|---|\r\n| 客户编号 | 左固定列 |\r\n| 客户名称 | 超长省略 |\r\n| **风险标签** | 展示「生效中」标签;按风险级别着色(低/中/高/极高);首标签 + `+N` 悬浮全部;有待审批时显示「审」标识;可点击打开管理抽屉 |\r\n| 客户区域 | 大区名称 |\r\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\r\n| 业务部门 / 业务负责人 | 文本 |\r\n| 客户类型 / 所属行业 | 文本 |\r\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\r\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\r\n| 操作 | 详情、编辑、**管理标签**(有权限时)、删除(原型 Toast) |\r\n\r\n支持行多选(checkbox,仅当前页);横向滚动;操作列右固定。\r\n\r\n---\r\n\r\n## 7. 客户标签枚举与分级目录\r\n\r\n标签按业务分类维护,打标时下拉按 **optgroup 分组** 展示。风险级别由目录**自动带出且不可手改**。\r\n\r\n### 7.1 信用与回款\r\n\r\n| 标签名称 | 标签类型 | 风险级别 | 典型触发 |\r\n|---|---|---|---|\r\n| 逾期预警 | 财务 | 中 | 当前逾期或近 6 个月内曾逾期 |\r\n| 严重拖欠 | 财务 | 高 | 逾期 > 30 天未结清或历史坏账 |\r\n| 频繁改账期 | 财务 | 低 | 一年内申请延长账期 ≥ 3 次 |\r\n\r\n### 7.2 合规与法律\r\n\r\n| 标签名称 | 标签类型 | 风险级别 | 典型触发 |\r\n|---|---|---|---|\r\n| 资质存疑 | 法务 | 中 | 证照过期、抬头与付款主体不一致等 |\r\n| 涉诉/仲裁 | 法务 | 高 | 与我司或第三方重大未结诉讼 |\r\n| 制裁/高风险地区 | 法务 | 高 | 名单筛查命中(依公司合规清单) |\r\n\r\n### 7.3 经营与履约\r\n\r\n| 标签名称 | 标签类型 | 风险级别 | 典型触发 |\r\n|---|---|---|---|\r\n| 频繁争议 | 法务 | 中 | 合同或扣款争议次数超阈值 |\r\n| 车辆/资产高风险 | 运维 | 高 | 违章、事故、疑似骗保等 |\r\n| 信息不实 | 风控 | 中 | 联系人/主体核查不实、空壳公司等 |\r\n\r\n### 7.4 操作与流程\r\n\r\n| 标签名称 | 标签类型 | 风险级别 | 典型触发 |\r\n|---|---|---|---|\r\n| 开票异常 | 财务 | 低 | 频繁退票、抬头异常变更 |\r\n| 多头签约 | 风控 | 低 | 同一实控人多主体分别签约 |\r\n\r\n### 7.5 列表展示样式\r\n\r\n- 低:灰色边框\r\n- 中:橙色浅底\r\n- 高:红色浅底\r\n- 极高:深红浅底\r\n- 单元格最大宽度受限,避免撑乱表格\r\n\r\n---\r\n\r\n## 8. 风险标签维护(列表页专属)\r\n\r\n### 8.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」 | 打开右侧抽屉,管理单个客户 |\r\n| 点击标签列 | 有权限时同「管理标签」 |\r\n| 工具栏「批量打标」 | 对当前勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。\r\n\r\n### 8.2 管理抽屉结构\r\n\r\n| Tab | 内容 |\r\n|---|---|\r\n| 当前标签 | 已打标签列表:名称、类型、级别、状态、原因摘要;支持查看详情、移除、审批 |\r\n| 新增标签 | 打标表单(见 8.3) |\r\n| 操作日志 | 该客户全部标签相关审计记录 |\r\n\r\n### 8.3 打标表单字段\r\n\r\n| 字段 | 必填 | 规则 |\r\n|---|---|---|\r\n| 标签名称 | 是 | 按 7.1–7.4 分组下拉;仅当前角色有权限的标签可选;已存在同名有效标签不可重复选 |\r\n| 标签类型 | 是 | **根据当前操作角色自动带出**(财务→财务、法务→法务、运维→运维、风控→风控),**置灰不可编辑** |\r\n| 风险级别 | 是 | **选择标签名称后按目录自动带出**,**置灰不可编辑** |\r\n| 生效时间 | 是 | 日期,默认当天 |\r\n| 有效期至 | 是 | 日期,须 ≥ 生效时间;默认一年后 |\r\n| 原因说明 | 是 | 至少 10 字,供法务/财务/风控审批留痕 |\r\n| 附件 | 否 | 支持 .pdf / .jpg / .png / .xlsx,可多个 |\r\n\r\n### 8.4 批量打标\r\n\r\n- 展示已选客户 chips(超过 6 个显示 +N)\r\n- 表单字段与单客户打标一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 提交后写入批量操作日志\r\n\r\n### 8.5 标签生命周期\r\n\r\n| 状态 | 说明 |\r\n|---|---|\r\n| 生效中 | 参与列表展示,同步至 `labels` |\r\n| 待审批 | 高风险标签提交后进入;列表暂不展示为生效 |\r\n| 待确认 | 预留 |\r\n| 已驳回 | 审批未通过,不同步至 `labels` |\r\n| 已移除 | 操作人主动移除,需填写原因 |\r\n| 已过期 | 预留 |\r\n\r\n**高风险规则:** 风险级别为「高」或「极高」的标签提交后状态为「待审批」,须法务或风控审批通过后变为「生效中」。\r\n\r\n### 8.6 移除标签\r\n\r\n- 仅「生效中」且当前角色有移除权限的标签可移除\r\n- 须填写移除原因(至少 5 字)\r\n- 写入操作日志,并从列表 `labels` 同步清除\r\n\r\n---\r\n\r\n## 9. 权限与审批\r\n\r\n### 9.1 打标权限\r\n\r\n| 角色 | 可选标签范围 | 标签类型(自动) |\r\n|---|---|---|\r\n| 财务 | 财务类(信用与回款、操作与流程中的财务标签) | 财务 |\r\n| 法务 | 法务类(合规与法律、经营与履约中的法务标签) | 法务 |\r\n| 运维 | 运维类(车辆/资产高风险) | 运维 |\r\n| 风控 / 管理员 | 全部 | 风控 |\r\n| 销售 | 无 | — |\r\n\r\n### 9.2 审批权限\r\n\r\n- 法务、风控、管理员可审批「待审批」标签\r\n- 支持通过 / 驳回;驳回须填写原因\r\n\r\n### 9.3 移除权限\r\n\r\n与打标权限一致:各角色仅能移除本类型标签;风控 / 管理员可移除全部类型。\r\n\r\n---\r\n\r\n## 10. 操作日志\r\n\r\n所有标签 **新增、移除、提交审批、审批通过、审批驳回、批量打标** 均写入日志。\r\n\r\n| 字段 | 说明 |\r\n|---|---|\r\n| 操作类型 | 新增 / 移除 / 提交审批 / 审批通过 / 审批驳回 / 批量打标 |\r\n| 客户 | 客户名称 + ID |\r\n| 标签名称 | 涉及标签 |\r\n| 详情 | 原因、审批结论等 |\r\n| 操作人 | 姓名 + 角色 |\r\n| 操作时间 | `YYYY-MM-DD HH:mm:ss` |\r\n\r\n抽屉「操作日志」Tab 按客户过滤;原型预置样例日志见 `data/risk-tag-operation-logs.json`。\r\n\r\n---\r\n\r\n## 11. 原型实现索引\r\n\r\n| 能力 | 代码位置 |\r\n|---|---|\r\n| 列表编排 | `index.tsx` |\r\n| 风险标签列 | `components/RiskLabelTags.tsx` |\r\n| 管理抽屉 | `components/RiskTagDrawer.tsx` |\r\n| 批量打标 | `components/BatchRiskTagModal.tsx` |\r\n| 打标表单 | `components/RiskTagForm.tsx` |\r\n| 标签目录 | `utils/riskTagConstants.ts` |\r\n| 权限 | `utils/permissions.ts` |\r\n| 业务逻辑 | `utils/riskTagStore.ts` |\r\n| 类型定义 | `types/riskTag.ts` |\r\n" + "markdown": "# 客户管理 · 列表页产品需求说明(PRD)\r\n\r\n> 本页为客户主数据台账列表。标注「原型演示」的跳转、导入、导出、详情/编辑仅 Toast 提示。\r\n\r\n---\r\n\r\n## 1. 模块定位\r\n\r\n| 项 | 说明 |\r\n|---|---|\r\n| 模块名称 | 客户管理 — 列表页 |\r\n| 目标用户 | 销售、业务负责人、**法务、财务、业管、运维/资产、风控、管理层** |\r\n| 核心任务 | 按多维度与 KPI 卡片筛选客户;查看并**在列表页维护风险标签**;新增客户(详情/编辑为原型演示) |\r\n| 页面结构 | 面包屑 + **操作角色切换** + 筛选区 + **风险 KPI 卡片** + 工具栏 + 表格 + 分页 |\r\n\r\n**重要约束:**\r\n\r\n- 风险标签**不在**客户新增页、客户编辑表单中维护,仅在列表页作为操作入口。\r\n- 底层使用统一 `riskTagsByCustomer` 数组(`CustomerRiskTag[]`),前台按**法务 / 财务 / 业管**三分区展示,不按 `legalTags` / `financeTags` 等字段拆散存储。\r\n\r\n---\r\n\r\n## 2. 客户编号规则(集团多子公司)\r\n\r\n- 总长度 7,仅含大写字母 A–Z 与数字 0–9\r\n- 结构:第 1 位 = 签约/归属子公司代码;第 2–7 位 = 6 位数字流水号(000001–999999)\r\n- 流水号在「同一子公司代码」下递增\r\n- 校验正则:`/^[A-Z][0-9]{6}$/`\r\n- 示例:A000001(华东)、B000001(华南)、C000001(华北)等\r\n\r\n---\r\n\r\n## 3. 数据来源\r\n\r\n| 数据 | 来源 | 用途 |\r\n|---|---|---|\r\n| 客户台账 | `data/customers.json`(20 条样例) | 列表、筛选选项 |\r\n| 客户标签(展示投影) | 客户 `labels` 字段 | 列表「风险标签」列、筛选区「标签」多选 |\r\n| 风险标签明细 | `riskTagsByCustomer` 运行时状态 + 种子迁移 | 抽屉、KPI 卡片、审批、日志 |\r\n| 标签目录 | `utils/riskTagConstants.ts` → `TAG_CATALOG`(11 项) | 打标表单、分区展示、KPI 口径 |\r\n| 操作日志 | `data/risk-tag-operation-logs.json` + 运行时追加 | 抽屉「操作日志」Tab |\r\n| 部门-负责人 | 由台账聚合 + `DEPT_MANAGER_MAP` | 筛选联动 |\r\n\r\n`labels` 由 `syncRecordLabels` 与**生效中**风险标签自动同步;**待审批**标签不参与 `labels` 投影。\r\n\r\n---\r\n\r\n## 4. 筛选区\r\n\r\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\r\n\r\n| 字段 | 控件 | 匹配规则 |\r\n|---|---|---|\r\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\r\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\r\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签(OR) |\r\n| 区域 | 可搜索单选 | 精确匹配 `region` |\r\n| 客户状态 | 多选 | `status` 在选中集合内 |\r\n| 业务部门 | 多选 | `department` 在选中集合内 |\r\n| 业务负责人 | 多选 | 随部门联动;`manager` 在选中集合内 |\r\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\r\n\r\n### 4.1 操作逻辑\r\n\r\n| 操作 | 行为 |\r\n|---|---|\r\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1,清空行选中 |\r\n| **重置** | 清空待应用与已应用条件;**KPI 卡片回到「全部客户」**;页码重置为 1;清空行选中 |\r\n| 修改筛选项未点查询 | 不影响当前列表 |\r\n\r\n### 4.2 与 KPI 卡片的关系\r\n\r\n- **KPI 卡片**:点击即生效,基于 `riskTagsByCustomer`(含待审批标签)。\r\n- **筛选区「标签」**:须点「查询」后生效,基于 `labels`(仅生效中标签)。\r\n- 二者叠加时取交集;口径差异见 §5 说明。\r\n\r\n---\r\n\r\n## 5. 风险 KPI 卡片筛选\r\n\r\n样式与供应商/车辆管理 KPI 卡片一致(`vm-kpi-*`),位于筛选区与工具栏之间。点击卡片切换列表数据范围并回到第 1 页;右上角「?」展示口径说明。\r\n\r\n| KPI 卡片 | 统计/筛选口径 |\r\n|---|---|\r\n| 全部客户 | 台账全部记录 |\r\n| 有风险客户 | 存在**生效中**或**待审批**风险标签 |\r\n| 财务风险 | 含 `tagCategory = finance` 的有效标签(信用与回款、开票异常等) |\r\n| 法务风险 | 含 `tagCategory = legal` 的有效标签(合规与法律类) |\r\n| 业管风险 | 含 `tagCategory = business` 的有效标签(经营与履约、多头签约等) |\r\n\r\n代码:`components/RiskKpiCards.tsx`、`utils/riskKpi.ts`。\r\n\r\n---\r\n\r\n## 6. 列表工具栏\r\n\r\n| 按钮 | 行为 |\r\n|---|---|\r\n| 新增客户 | 进入新增客户页 |\r\n| 批量导入 | Toast:批量导入(原型演示) |\r\n| **批量打标** | 勾选 ≥1 行且当前角色有打标权限时显示 |\r\n| 批量导出 | Toast:导出当前列表条数 |\r\n\r\n### 6.1 操作角色切换(原型演示)\r\n\r\n列表顶部「当前操作角色(原型演示)」下拉,按**角色**切换(非按姓名),模拟 8 类用户:\r\n\r\n| 角色 | 演示账号 | 部门 | 打标 | 审批 | 说明 |\r\n|---|---|---|---|---|---|\r\n| 销售 | 陈销售 | 销售一部 | 否 | 否 | 可查看标签/抽屉,操作列「查看标签」 |\r\n| 法务 | 李法务 | 法务部 | 法务分区 | 是 | 维护合规与法律类标签 |\r\n| 财务 | 张财务 | 财务部 | 财务分区 | 否 | 信用与回款 + 开票异常 |\r\n| 业管 | 王业管 | 业管部 | 业管分区 | 否 | 经营与履约 + 多头签约 |\r\n| 运维/资产 | 赵运维 | 运维资产部 | 否 | 否 | 仅查看 |\r\n| 风控 | 刘风控 | 风控部 | 否 | 是 | 可审批待审批标签 |\r\n| 管理层 | 周管理 | 管理层 | 否 | 是 | 可审批;可看财务/法务敏感信息 |\r\n| 系统管理员 | 系统管理员 | 系统管理部 | 全部 | 是 | 全部维护与审批权限 |\r\n\r\n切换角色后,Drawer 内**分区维护按钮**、**新增标签可选范围**、**审批/解除按钮**、**敏感信息脱敏**随之联动。\r\n\r\n---\r\n\r\n## 7. 列表字段说明\r\n\r\n| 列 | 说明 |\r\n|---|---|\r\n| 客户编号 | 左固定列 |\r\n| 客户名称 | 超长省略 |\r\n| **风险标签** | 最多展示 **2** 个生效中标签;超出显示 `+N`;悬浮/点击 `+N` 展示全部;按级别着色(低/中/高/致命);Tooltip 含类型、责任部门、级别、状态;**所有角色**可点击打开 Drawer |\r\n| 客户区域 | 大区名称 |\r\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\r\n| 业务部门 / 业务负责人 | 文本 |\r\n| 客户类型 / 所属行业 | 文本 |\r\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\r\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\r\n| 操作 | 详情、编辑(Toast 演示)、**管理标签** / **查看标签**、删除(Toast 演示) |\r\n\r\n支持行多选(checkbox,仅当前页);横向滚动;操作列右固定。\r\n\r\n### 7.1 风险标签列展示样式\r\n\r\n| 风险级别 | 样式 |\r\n|---|---|\r\n| 低 | 灰色边框 |\r\n| 中 | 橙色浅底 |\r\n| 高 | 红色浅底 |\r\n| 致命 | 深红浅底 |\r\n\r\n无生效中标签但有待审批时,列可展示待审批标识;全无标签显示「—」。\r\n\r\n---\r\n\r\n## 8. 风险标签目录(11 项)\r\n\r\n标签按规范文档四组维护;打标下拉按 **optgroup**(信用与回款 / 合规与法律 / 经营与履约 / 操作与流程)分组。风险级别由目录**自动带出且不可手改**。\r\n\r\n**维护归属(权限分责):**\r\n\r\n- **信用与回款** → 财务部\r\n- **合规与法律** → 法务部\r\n- **经营与履约** → 业管部\r\n- **操作与流程**:开票异常 → 财务部;多头签约 → 业管部\r\n\r\n### 8.1 信用与回款(财务)\r\n\r\n| 代码 | 标签名称 | 展示分区 | 风险级别 | 典型触发 | 待审批 | 黑名单提示 |\r\n|---|---|---|---|---|---|---|\r\n| CR-01 | 逾期预警 | 财务 | 中 | 当前逾期或近 6 个月曾逾期 | 否 | 否 |\r\n| CR-02 | 严重拖欠 | 财务 | 高 | 逾期 > 30 天或历史坏账 | 是 | 是 |\r\n| CR-03 | 频繁改账期 | 财务 | 低 | 一年内延长账期 ≥ 3 次 | 否 | 否 |\r\n\r\n### 8.2 合规与法律(法务)\r\n\r\n| 代码 | 标签名称 | 展示分区 | 风险级别 | 典型触发 | 待审批 | 黑名单提示 |\r\n|---|---|---|---|---|---|---|\r\n| CP-01 | 资质存疑 | 法务 | 中 | 证照过期、抬头与付款主体不一致 | 否 | 否 |\r\n| CP-02 | 涉诉/仲裁 | 法务 | 高 | 重大未结诉讼/仲裁 | 是 | 是 |\r\n| CP-03 | 制裁/高风险地区 | 法务 | 高 | 制裁/合规名单命中 | 是 | 是 |\r\n\r\n### 8.3 经营与履约(业管)\r\n\r\n| 代码 | 标签名称 | 展示分区 | 风险级别 | 典型触发 | 待审批 | 黑名单提示 |\r\n|---|---|---|---|---|---|---|\r\n| OP-01 | 频繁争议 | 业管 | 中 | 合同或扣款争议超阈值 | 否 | 否 |\r\n| OP-02 | 车辆/资产高风险 | 业管 | 高 | 违章、事故、疑似骗保等 | 是 | 否 |\r\n| OP-03 | 信息不实 | 业管 | 中 | 联系人/主体核查不实 | 否 | 否 |\r\n\r\n### 8.4 操作与流程\r\n\r\n| 代码 | 标签名称 | 展示分区 | 维护部门 | 风险级别 | 典型触发 |\r\n|---|---|---|---|---|---|\r\n| PR-01 | 开票异常 | 财务 | 财务部 | 低 | 频繁退票、抬头异常变更 |\r\n| PR-02 | 多头签约 | 业管 | 业管部 | 低 | 同一实控人多主体分别签约 |\r\n\r\n---\r\n\r\n## 9. 风险标签维护(列表页专属)\r\n\r\n### 9.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」/「查看标签」 | 打开右侧 Drawer |\r\n| 点击风险标签列 | 同打开 Drawer |\r\n| 工具栏「批量打标」 | 对勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。\r\n\r\n### 9.2 管理抽屉结构\r\n\r\n| Tab | 内容 |\r\n|---|---|\r\n| 当前标签 | 按 **法务风险 / 财务风险 / 业管风险** 三分区 Card 展示;含分区说明、维护权限提示、标签卡片、空状态 |\r\n| 新增标签 | 角色受限打标表单(见 9.3) |\r\n| 操作日志 | 该客户审计记录,按时间倒序 |\r\n\r\nDrawer 标题:**风险标签**;副标题为客户名称与编号。\r\n\r\n### 9.3 当前标签页 — 分区与卡片\r\n\r\n展示顺序:法务 → 财务 → 业管。\r\n\r\n每个分区包含:\r\n\r\n1. 分区标题与说明(责任部门、关注范围)\r\n2. 当前角色是否可维护(「可维护」/「当前角色无权维护该类标签」)\r\n3. 「新增该类标签」按钮(有权限时)\r\n4. 标签卡片:名称、级别、状态、时间字段、责任部门、操作人、原因摘要\r\n - **法务风险**:打标时间、复核周期、下次复核时间、最后复核时间(不展示「有效期至」)\r\n - **财务/业管**:打标时间、有效期至\r\n5. 空状态:「暂无该类风险标签」\r\n\r\n卡片操作:查看详情;审批(新增待审批或**解除待审批**且有权);**申请解除**(有权且生效中,须走审批流)。\r\n\r\n**法务风险复核规则(原型 Mock):**\r\n\r\n- 法务标签自打标后持续生效,**不会因到期自动解除**\r\n- 距 `nextReviewAt` ≤ 7 天:卡片显示「即将复核」\r\n- 当前日期超过 `nextReviewAt`:状态显示「待复核」,标签仍保留\r\n- **不在 Drawer 内完成复核**;系统自动向**法务工作台**推送待办,法务查验后回到本页「申请解除」\r\n- 打开 Drawer 且存在待复核法务标签时,页顶 Banner 提示待办已推送,并写入操作日志(工作台待办)\r\n- 确认可解除时:填写解除原因 → 提交解除申请(走审批流);通过后记录 `removedAt`\r\n\r\n**解除审批(原型 Mock):**\r\n\r\n- 任意标签解除均提交申请,状态为「解除待审批」\r\n- 申请解除弹窗说明:**法务标签**提示「法务查验完成后…」;**财务/业管标签**仅提示「提交解除申请,经审批后生效」(不含法务查验文案)\r\n- 法务/风控/管理层/系统管理员可审批:通过 → 已解除;驳回 → 恢复生效并清除申请标记\r\n\r\n### 9.4 新增标签表单\r\n\r\n| 字段 | 必填 | 规则 |\r\n|---|---|---|\r\n| 标签类型 | 是 | 非 admin:按角色自动带出法务/财务/业管并置灰;admin 可选三分区 |\r\n| 标签名称 | 是 | 按 8.1–8.4 optgroup;仅当前角色可维护的标签;不可重复有效同名标签 |\r\n| 风险级别 | 是 | 选标签后自动带出,置灰 |\r\n| 打标时间 | 是 | 日期(只读置灰) | **取操作当天**,不可编辑 |\r\n| 复核周期 | 法务必填 | 下拉 | 1 / 2 / 3 / 6 个月;变更时自动推算下次复核时间 |\r\n| 下次复核时间 | 法务必填 | 日期(只读置灰) | 根据打标时间 + 复核周期**自动推算**,不可编辑;下方提示「到期仅提醒责任人复核,不会自动解除标签。」 |\r\n| 有效期至 | 财务/业管必填 | 日期 | ≥ 打标时间;业务有效期,与复核提醒无关 |\r\n| 原因说明 | 是 | ≥ 10 字 |\r\n| 附件 | 高/致命必填 | .pdf / .jpg / .png / .xlsx |\r\n\r\n**法务 vs 财务/业管字段差异:**\r\n\r\n- 法务标签**不展示**「有效期至」,使用「下次复核时间」驱动定期复核提醒\r\n- 财务/业管保留「有效期至」,不在 UI 上与复核时间混用\r\n\r\n提交规则:\r\n\r\n- 低/中:直接 **生效中**\r\n- 高/致命:**待审批**;提交前二次确认\r\n- 涉诉/仲裁、严重拖欠、制裁/高风险地区:Toast 提示可能触发黑名单审批\r\n\r\n销售/运维/风控/管理层(非 admin):新增 Tab 展示无权限说明。\r\n\r\n### 9.5 批量打标\r\n\r\n- 已选客户 chips(>6 显示 +N)\r\n- 表单与单客户一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 写入批量操作日志\r\n\r\n### 9.6 统一数据模型 `CustomerRiskTag`\r\n\r\n| 字段 | 说明 |\r\n|---|---|\r\n| id / customerId | 标识 |\r\n| tagCode / tagName | 目录代码与名称 |\r\n| tagCategory / tagCategoryName | legal / finance / business |\r\n| responsibleDept / editableRoles | 责任部门与可编辑角色 |\r\n| riskLevel / riskLevelName | low / medium / high / fatal |\r\n| status / statusName | active / pending / expired / removed / rejected |\r\n| source / sourceName | system / manual |\r\n| reason / taggedAt / nextReviewAt / reviewCycle | 原因与复核时间(法务) |\r\n| expireDate | 财务/业管业务有效期 |\r\n| lastReviewedAt / reviewStatus / reviewLogs | 法务复核记录 |\r\n| removedAt / removeReason | 解除时间与原因 |\r\n| createdBy / createdAt / approvedBy / approvedAt | 操作与审批 |\r\n| attachments / sensitiveLevel / financeDetail | 附件与脱敏 |\r\n| removalRequested | 解除申请标记(原型) |\r\n\r\n### 9.7 标签生命周期\r\n\r\n| 状态 | 说明 | 同步至 `labels` |\r\n|---|---|---|\r\n| 生效中 | 正常展示 | 是 |\r\n| 待复核 | 法务标签超过下次复核时间,**仍生效** | 是 |\r\n| 待审批 | 高/致命新增后 | 否 |\r\n| 已驳回 | 审批拒绝 | 否 |\r\n| 已解除 | 解除成功(含复核确认解除并审批通过) | 否 |\r\n| 已失效 | 预留(法务标签**不会**自动进入此状态) | 否 |\r\n\r\n**法务复核与解除规则:**\r\n\r\n- 法务标签持续生效,系统仅按 `nextReviewAt` 提醒复核,**不自动解除**\r\n- 复核维持:在工作台完成查验(原型阶段以 Toast + 日志表示);如需解除使用「申请解除」\r\n- 复核确认风险解除:填写解除原因 → 提交解除申请 → 审批通过后 `status → 已解除` 并记录 `removedAt`\r\n- 任意路径的解除均需审批,不允许系统自动解除法务标签\r\n\r\n### 9.8 敏感信息脱敏\r\n\r\n| 场景 | 规则 |\r\n|---|---|\r\n| 财务敏感 | 非 finance / manager / admin 不可见逾期金额、账单编号等;提示「财务敏感信息已隐藏」 |\r\n| 法务附件 | 非 legal / riskControl / manager / admin 不可见法务附件;提示「法务敏感附件仅授权角色可见」 |\r\n| 操作日志 | 财务类日志 reason 对非授权角色脱敏 |\r\n\r\n---\r\n\r\n## 10. 权限与审批\r\n\r\n### 10.1 分区维护(打标 / 解除)\r\n\r\n| 角色 | 可维护分区 | 可选标签范围 |\r\n|---|---|---|\r\n| 财务 | 财务风险 | 信用与回款 + 开票异常 |\r\n| 法务 | 法务风险 | 合规与法律 |\r\n| 业管 | 业管风险 | 经营与履约 + 多头签约 |\r\n| 系统管理员 | 全部 | 全部 11 项 |\r\n| 销售 / 运维 / 风控 / 管理层 | — | 不可新增(风控/管理层可审批) |\r\n\r\n### 10.2 审批权限\r\n\r\n法务、风控、管理层、系统管理员可审批「待审批」新增标签,以及「解除待审批」的解除申请;支持通过/驳回,驳回须填原因。\r\n\r\n### 10.3 查看权限\r\n\r\n所有角色可打开 Drawer 查看标签名称与级别;敏感字段按 §9.8 脱敏。\r\n\r\n---\r\n\r\n## 11. 操作日志\r\n\r\n记录:新增标签、解除标签、提交审批、审批通过、审批驳回、**工作台待办**、批量打标。\r\n\r\n| 字段 | 说明 |\r\n|---|---|\r\n| 操作时间 | `YYYY-MM-DD HH:mm:ss` |\r\n| 操作人 / 操作角色 | 姓名 + 角色 |\r\n| 操作类型 | 见上 |\r\n| 标签名称 / 标签类型 | tagName + tagCategoryName |\r\n| 操作原因 | reason |\r\n| 变更前/后状态 | statusBefore → statusAfter |\r\n\r\n抽屉内按客户过滤、时间倒序;不同类型以 Tag 区分。\r\n\r\n---\r\n\r\n## 12. 原型边界(当前实现)\r\n\r\n| 能力 | 状态 |\r\n|---|---|\r\n| 列表 + KPI + 风险标签 Drawer | 已实现 |\r\n| 详情 / 编辑 / 查看页 | Toast 演示 |\r\n| 新增客户落库 | Toast 演示,不写回列表 |\r\n| 高风险解除申请审批闭环 | 已实现:申请 → 审批通过/驳回 |\r\n| 黑名单与标签联动 | 仅 Toast 提示,不自动改 `status` |\r\n| 定时复核任务 | 仅前端 Mock 提示 |\r\n\r\n---\r\n\r\n## 13. 原型实现索引\r\n\r\n| 能力 | 代码位置 |\r\n|---|---|\r\n| 列表编排 | `index.tsx` |\r\n| KPI 卡片 | `components/RiskKpiCards.tsx`、`utils/riskKpi.ts` |\r\n| 风险标签列 | `components/RiskLabelTags.tsx` |\r\n| 管理抽屉 | `components/RiskTagDrawer.tsx` |\r\n| 批量打标 | `components/BatchRiskTagModal.tsx` |\r\n| 打标表单 / 详情 | `components/RiskTagForm.tsx` |\r\n| 标签目录 | `utils/riskTagConstants.ts` |\r\n| 权限 | `utils/permissions.ts` |\r\n| 业务逻辑 | `utils/riskTagStore.ts` |\r\n| 列表筛选 | `utils/filters.ts` |\r\n| 类型定义 | `types/riskTag.ts`、`types.ts` |\r\n" }, { "type": "markdown", "id": "cm-doc-prd-create", "title": "新增页 PRD", - "markdown": "# 客户管理 · 新增页产品需求说明(PRD)\r\n\r\n> 从列表页「新增客户」进入。保存为原型 Toast,不对接真实接口。\r\n\r\n---\r\n\r\n## 1. 页面框架\r\n\r\n| 项 | 说明 |\r\n|---|---|\r\n| 面包屑 | 业务管理 / 客户管理 / 新增 |\r\n| 布局 | 三组卡片表单 + 底部 sticky 操作栏 |\r\n| 操作 | 重置(清空表单)、提交(校验后 Toast 成功并返回列表) |\r\n\r\n---\r\n\r\n## 2. 客户信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\r\n| 客户分级 | 否 | 下拉 | A / B / C |\r\n| 客户全称 | 是 | 文本 | 与客户证照一致;变更时同步发票抬头 |\r\n| 客户简称 | 否 | 文本 | 选填 |\r\n| 所属城市 | 是 | 省-市级联 | 示例省市区数据 |\r\n| 通讯地址 | 否 | 文本 | |\r\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\r\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\r\n| 备注 | 否 | 多行文本 | |\r\n\r\n---\r\n\r\n## 3. 联系人信息\r\n\r\n| 字段 | 必填 | 控件 |\r\n|---|---|---|\r\n| 姓名 | 否 | 文本 |\r\n| 手机号 | 否 | 文本 |\r\n| 职位 | 否 | 文本 |\r\n\r\n---\r\n\r\n## 4. 付款及开票信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 发票抬头 | 是 | 文本(禁用) | 自动带入客户全称,不可手改 |\r\n| 纳税人识别号 | 是 | 文本 | |\r\n| 注册地址 | 否 | 文本 | |\r\n| 注册电话 | 否 | 文本 | |\r\n| 开户银行 | 是 | 文本 | |\r\n| 账号 | 否 | 文本 | |\r\n\r\n---\r\n\r\n## 5. 校验与提交\r\n\r\n- 提交前校验所有必填项\r\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\r\n- 重置清空全部字段,客户类型默认「企业」\r\n" + "markdown": "# 客户管理 · 新增页产品需求说明(PRD)\r\n\r\n> 从列表页「新增客户」进入。保存为原型 Toast,不对接真实接口,**不写回列表台账**。\r\n\r\n---\r\n\r\n## 1. 页面框架\r\n\r\n| 项 | 说明 |\r\n|---|---|\r\n| 面包屑 | 业务管理 / 客户管理 / 新增 |\r\n| 布局 | 三组卡片表单 + 底部 sticky 操作栏 |\r\n| 操作 | 重置(清空表单)、提交(校验后 Toast 成功并返回列表) |\r\n\r\n**重要约束:** 本页**不包含**风险标签字段;标签仅在列表页维护。\r\n\r\n---\r\n\r\n## 2. 客户信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\r\n| 客户全称 | 是 | 文本 | 与证照一致;变更时同步发票抬头 |\r\n| 客户简称 | 否 | 文本 | 选填 |\r\n| 省市 | 否 | 文本(置灰) | 自动从通讯地址/地址解析识别 |\r\n| 所属城市 | 是 | 省-市(置灰) | **由地址解析自动识别**,不可手选 |\r\n| 通讯地址 | 是 | 文本 | 填写完整地址后解析省、市(同供应商管理交互) |\r\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\r\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\r\n| 合作状态 | 否 | 下拉 | 已合作 / 终止合作 / 洽谈中 / 合同到期 |\r\n| 备注 | 否 | 多行文本 | |\r\n\r\n**已移除:** 客户分级(A/B/C)不在新增页展示。\r\n\r\n---\r\n\r\n## 3. 联系人信息\r\n\r\n| 字段 | 必填 | 控件 |\r\n|---|---|---|\r\n| 姓名 | 否 | 文本 |\r\n| 手机号 | 否 | 文本 |\r\n| 职位 | 否 | 文本 |\r\n\r\n---\r\n\r\n## 4. 付款及开票信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 发票抬头 | 是 | 文本(禁用) | 自动带入客户全称,不可手改 |\r\n| 纳税人识别号 | 是 | 文本 | |\r\n| 注册地址 | 否 | 文本 | |\r\n| 注册电话 | 否 | 文本 | |\r\n| 开户银行 | 是 | 文本 | |\r\n| 账号 | 否 | 文本 | |\r\n\r\n---\r\n\r\n## 5. 校验与提交\r\n\r\n| 校验项 | 规则 |\r\n|---|---|\r\n| 客户类型 | 必选 |\r\n| 客户全称 | 非空 |\r\n| 所属城市 | 通讯地址须能识别出省、市 |\r\n| 业务负责部门 / 人员 | 必选 |\r\n| 发票抬头 / 税号 / 开户银行 | 非空 |\r\n\r\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\r\n- 重置清空全部字段;客户类型默认「企业」\r\n- 校验失败:Toast 首条错误(原型阶段)\r\n\r\n---\r\n\r\n## 6. 原型实现索引\r\n\r\n| 能力 | 代码位置 |\r\n|---|---|\r\n| 新增页 | `pages/CreatePage.tsx` |\r\n| 地址解析 | `utils/address.ts` |\r\n| 常量选项 | `utils/constants.ts` |\r\n| 表单编排 | `index.tsx` |\r\n" } ] }, @@ -303,43 +324,49 @@ "type": "markdown", "id": "cm-doc-node-cm-list-filter", "title": "筛选条件", - "markdown": "## 4. 筛选区\r\n\r\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\r\n\r\n| 字段 | 控件 | 匹配规则 |\r\n|---|---|---|\r\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\r\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\r\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签(OR) |\r\n| 区域 | 可搜索单选 | 精确匹配 `region` |\r\n| 客户状态 | 多选 | `status` 在选中集合内 |\r\n| 业务部门 | 多选 | `department` 在选中集合内 |\r\n| 业务负责人 | 多选 | 随部门联动选项;`manager` 在选中集合内 |\r\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\r\n\r\n### 4.1 操作逻辑\r\n\r\n| 操作 | 行为 |\r\n|---|---|\r\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1,清空行选中 |\r\n| **重置** | 清空待应用与已应用条件,页码重置为 1,清空行选中 |\r\n| 修改筛选项未点查询 | 不影响当前列表 |\r\n\r\n---" + "markdown": "## 4. 筛选区\r\n\r\n默认展示前 4 项(一行),点击「更多筛选」显示全部。\r\n\r\n| 字段 | 控件 | 匹配规则 |\r\n|---|---|---|\r\n| 客户编号 | 可搜索单选 | 精确匹配 `code` |\r\n| 客户名称 | 可搜索单选 | 精确匹配 `name` |\r\n| 标签 | 多选 | 客户 `labels` 包含任一选中标签(OR) |\r\n| 区域 | 可搜索单选 | 精确匹配 `region` |\r\n| 客户状态 | 多选 | `status` 在选中集合内 |\r\n| 业务部门 | 多选 | `department` 在选中集合内 |\r\n| 业务负责人 | 多选 | 随部门联动;`manager` 在选中集合内 |\r\n| 创建时间 | 日期范围 | `createTime` 落在区间内(含边界日) |\r\n\r\n### 4.1 操作逻辑\r\n\r\n| 操作 | 行为 |\r\n|---|---|\r\n| **查询** | 将当前筛选项写入已应用条件,页码重置为 1,清空行选中 |\r\n| **重置** | 清空待应用与已应用条件;**KPI 卡片回到「全部客户」**;页码重置为 1;清空行选中 |\r\n| 修改筛选项未点查询 | 不影响当前列表 |\r\n\r\n### 4.2 与 KPI 卡片的关系\r\n\r\n- **KPI 卡片**:点击即生效,基于 `riskTagsByCustomer`(含待审批标签)。\r\n- **筛选区「标签」**:须点「查询」后生效,基于 `labels`(仅生效中标签)。\r\n- 二者叠加时取交集;口径差异见 §5 说明。\r\n\r\n---" }, { "type": "markdown", "id": "cm-doc-node-cm-risk-role-switch", "title": "操作角色切换", - "markdown": "### 5.1 操作角色切换(原型演示)\r\n\r\n列表顶部提供「当前操作角色」下拉,用于模拟不同部门用户:\r\n\r\n| 角色 | 部门 | 打标权限 |\r\n|---|---|---|\r\n| 王风控 | 风控部 | 全部标签 |\r\n| 李法务 | 法务部 | 法务类标签 |\r\n| 张财务 | 财务部 | 财务类标签 |\r\n| 赵运维 | 运维部 | 运维类标签 |\r\n| 陈销售 | 销售一部 | 仅查看,无打标入口 |\r\n\r\n切换角色后,风险标签管理抽屉内的**标签类型**、**可选标签**、**审批/移除按钮**随之联动更新。\r\n\r\n---" + "markdown": "### 6.1 操作角色切换(原型演示)\r\n\r\n列表顶部「当前操作角色(原型演示)」下拉,按**角色**切换(非按姓名),模拟 8 类用户:\r\n\r\n| 角色 | 演示账号 | 部门 | 打标 | 审批 | 说明 |\r\n|---|---|---|---|---|---|\r\n| 销售 | 陈销售 | 销售一部 | 否 | 否 | 可查看标签/抽屉,操作列「查看标签」 |\r\n| 法务 | 李法务 | 法务部 | 法务分区 | 是 | 维护合规与法律类标签 |\r\n| 财务 | 张财务 | 财务部 | 财务分区 | 否 | 信用与回款 + 开票异常 |\r\n| 业管 | 王业管 | 业管部 | 业管分区 | 否 | 经营与履约 + 多头签约 |\r\n| 运维/资产 | 赵运维 | 运维资产部 | 否 | 否 | 仅查看 |\r\n| 风控 | 刘风控 | 风控部 | 否 | 是 | 可审批待审批标签 |\r\n| 管理层 | 周管理 | 管理层 | 否 | 是 | 可审批;可看财务/法务敏感信息 |\r\n| 系统管理员 | 系统管理员 | 系统管理部 | 全部 | 是 | 全部维护与审批权限 |\r\n\r\n切换角色后,Drawer 内**分区维护按钮**、**新增标签可选范围**、**审批/解除按钮**、**敏感信息脱敏**随之联动。\r\n\r\n---" + }, + { + "type": "markdown", + "id": "cm-doc-node-cm-risk-kpi-cards", + "title": "风险 KPI 卡片", + "markdown": "## 5. 风险 KPI 卡片筛选\r\n\r\n样式与供应商/车辆管理 KPI 卡片一致(`vm-kpi-*`),位于筛选区与工具栏之间。点击卡片切换列表数据范围并回到第 1 页;右上角「?」展示口径说明。\r\n\r\n| KPI 卡片 | 统计/筛选口径 |\r\n|---|---|\r\n| 全部客户 | 台账全部记录 |\r\n| 有风险客户 | 存在**生效中**或**待审批**风险标签 |\r\n| 财务风险 | 含 `tagCategory = finance` 的有效标签(信用与回款、开票异常等) |\r\n| 法务风险 | 含 `tagCategory = legal` 的有效标签(合规与法律类) |\r\n| 业管风险 | 含 `tagCategory = business` 的有效标签(经营与履约、多头签约等) |\r\n\r\n代码:`components/RiskKpiCards.tsx`、`utils/riskKpi.ts`。\r\n\r\n---" }, { "type": "markdown", "id": "cm-doc-node-cm-list-toolbar", "title": "列表工具栏", - "markdown": "## 5. 列表工具栏\r\n\r\n| 按钮 | 行为 |\r\n|---|---|\r\n| 新增客户 | 进入新增客户页(分组表单) |\r\n| 批量导入 | Toast:批量导入(原型演示) |\r\n| **批量打标** | 勾选 ≥1 行且当前角色有打标权限时显示;打开批量打标弹窗 |\r\n| 批量导出 | Toast:导出当前列表条数 |\r\n\r\n### 5.1 操作角色切换(原型演示)\r\n\r\n列表顶部提供「当前操作角色」下拉,用于模拟不同部门用户:\r\n\r\n| 角色 | 部门 | 打标权限 |\r\n|---|---|---|\r\n| 王风控 | 风控部 | 全部标签 |\r\n| 李法务 | 法务部 | 法务类标签 |\r\n| 张财务 | 财务部 | 财务类标签 |\r\n| 赵运维 | 运维部 | 运维类标签 |\r\n| 陈销售 | 销售一部 | 仅查看,无打标入口 |\r\n\r\n切换角色后,风险标签管理抽屉内的**标签类型**、**可选标签**、**审批/移除按钮**随之联动更新。\r\n\r\n---" + "markdown": "## 6. 列表工具栏\r\n\r\n| 按钮 | 行为 |\r\n|---|---|\r\n| 新增客户 | 进入新增客户页 |\r\n| 批量导入 | Toast:批量导入(原型演示) |\r\n| **批量打标** | 勾选 ≥1 行且当前角色有打标权限时显示 |\r\n| 批量导出 | Toast:导出当前列表条数 |\r\n\r\n### 6.1 操作角色切换(原型演示)\r\n\r\n列表顶部「当前操作角色(原型演示)」下拉,按**角色**切换(非按姓名),模拟 8 类用户:\r\n\r\n| 角色 | 演示账号 | 部门 | 打标 | 审批 | 说明 |\r\n|---|---|---|---|---|---|\r\n| 销售 | 陈销售 | 销售一部 | 否 | 否 | 可查看标签/抽屉,操作列「查看标签」 |\r\n| 法务 | 李法务 | 法务部 | 法务分区 | 是 | 维护合规与法律类标签 |\r\n| 财务 | 张财务 | 财务部 | 财务分区 | 否 | 信用与回款 + 开票异常 |\r\n| 业管 | 王业管 | 业管部 | 业管分区 | 否 | 经营与履约 + 多头签约 |\r\n| 运维/资产 | 赵运维 | 运维资产部 | 否 | 否 | 仅查看 |\r\n| 风控 | 刘风控 | 风控部 | 否 | 是 | 可审批待审批标签 |\r\n| 管理层 | 周管理 | 管理层 | 否 | 是 | 可审批;可看财务/法务敏感信息 |\r\n| 系统管理员 | 系统管理员 | 系统管理部 | 全部 | 是 | 全部维护与审批权限 |\r\n\r\n切换角色后,Drawer 内**分区维护按钮**、**新增标签可选范围**、**审批/解除按钮**、**敏感信息脱敏**随之联动。\r\n\r\n---" }, { "type": "markdown", "id": "cm-doc-node-cm-list-table", "title": "客户台账列表", - "markdown": "## 6. 列表字段说明\r\n\r\n| 列 | 说明 |\r\n|---|---|\r\n| 客户编号 | 左固定列 |\r\n| 客户名称 | 超长省略 |\r\n| **风险标签** | 展示「生效中」标签;按风险级别着色(低/中/高/极高);首标签 + `+N` 悬浮全部;有待审批时显示「审」标识;可点击打开管理抽屉 |\r\n| 客户区域 | 大区名称 |\r\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\r\n| 业务部门 / 业务负责人 | 文本 |\r\n| 客户类型 / 所属行业 | 文本 |\r\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\r\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\r\n| 操作 | 详情、编辑、**管理标签**(有权限时)、删除(原型 Toast) |\r\n\r\n支持行多选(checkbox,仅当前页);横向滚动;操作列右固定。\r\n\r\n---" + "markdown": "## 7. 列表字段说明\r\n\r\n| 列 | 说明 |\r\n|---|---|\r\n| 客户编号 | 左固定列 |\r\n| 客户名称 | 超长省略 |\r\n| **风险标签** | 最多展示 **2** 个生效中标签;超出显示 `+N`;悬浮/点击 `+N` 展示全部;按级别着色(低/中/高/致命);Tooltip 含类型、责任部门、级别、状态;**所有角色**可点击打开 Drawer |\r\n| 客户区域 | 大区名称 |\r\n| 客户状态 | 绿点「正常」/ 红点「黑名单」 |\r\n| 业务部门 / 业务负责人 | 文本 |\r\n| 客户类型 / 所属行业 | 文本 |\r\n| 统一社会信用代码 / 法人 / 注册资本 / 经营状态 | 文本 |\r\n| 详细地址 / 创建时间 | 文本;创建时间不换行 |\r\n| 操作 | 详情、编辑(Toast 演示)、**管理标签** / **查看标签**、删除(Toast 演示) |\r\n\r\n支持行多选(checkbox,仅当前页);横向滚动;操作列右固定。\r\n\r\n### 7.1 风险标签列展示样式\r\n\r\n| 风险级别 | 样式 |\r\n|---|---|\r\n| 低 | 灰色边框 |\r\n| 中 | 橙色浅底 |\r\n| 高 | 红色浅底 |\r\n| 致命 | 深红浅底 |\r\n\r\n无生效中标签但有待审批时,列可展示待审批标识;全无标签显示「—」。\r\n\r\n---" }, { "type": "markdown", "id": "cm-doc-node-cm-risk-tag-drawer", - "title": "风险标签管理抽屉", - "markdown": "## 8. 风险标签维护(列表页专属)\r\n\r\n### 8.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」 | 打开右侧抽屉,管理单个客户 |\r\n| 点击标签列 | 有权限时同「管理标签」 |\r\n| 工具栏「批量打标」 | 对当前勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。\r\n\r\n### 8.2 管理抽屉结构\r\n\r\n| Tab | 内容 |\r\n|---|---|\r\n| 当前标签 | 已打标签列表:名称、类型、级别、状态、原因摘要;支持查看详情、移除、审批 |\r\n| 新增标签 | 打标表单(见 8.3) |\r\n| 操作日志 | 该客户全部标签相关审计记录 |\r\n\r\n### 8.3 打标表单字段\r\n\r\n| 字段 | 必填 | 规则 |\r\n|---|---|---|\r\n| 标签名称 | 是 | 按 7.1–7.4 分组下拉;仅当前角色有权限的标签可选;已存在同名有效标签不可重复选 |\r\n| 标签类型 | 是 | **根据当前操作角色自动带出**(财务→财务、法务→法务、运维→运维、风控→风控),**置灰不可编辑** |\r\n| 风险级别 | 是 | **选择标签名称后按目录自动带出**,**置灰不可编辑** |\r\n| 生效时间 | 是 | 日期,默认当天 |\r\n| 有效期至 | 是 | 日期,须 ≥ 生效时间;默认一年后 |\r\n| 原因说明 | 是 | 至少 10 字,供法务/财务/风控审批留痕 |\r\n| 附件 | 否 | 支持 .pdf / .jpg / .png / .xlsx,可多个 |\r\n\r\n### 8.4 批量打标\r\n\r\n- 展示已选客户 chips(超过 6 个显示 +N)\r\n- 表单字段与单客户打标一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 提交后写入批量操作日志\r\n\r\n### 8.5 标签生命周期\r\n\r\n| 状态 | 说明 |\r\n|---|---|\r\n| 生效中 | 参与列表展示,同步至 `labels` |\r\n| 待审批 | 高风险标签提交后进入;列表暂不展示为生效 |\r\n| 待确认 | 预留 |\r\n| 已驳回 | 审批未通过,不同步至 `labels` |\r\n| 已移除 | 操作人主动移除,需填写原因 |\r\n| 已过期 | 预留 |\r\n\r\n**高风险规则:** 风险级别为「高」或「极高」的标签提交后状态为「待审批」,须法务或风控审批通过后变为「生效中」。\r\n\r\n### 8.6 移除标签\r\n\r\n- 仅「生效中」且当前角色有移除权限的标签可移除\r\n- 须填写移除原因(至少 5 字)\r\n- 写入操作日志,并从列表 `labels` 同步清除\r\n\r\n---" + "title": "风险标签抽屉", + "markdown": "## 9. 风险标签维护(列表页专属)\r\n\r\n### 9.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」/「查看标签」 | 打开右侧 Drawer |\r\n| 点击风险标签列 | 同打开 Drawer |\r\n| 工具栏「批量打标」 | 对勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。\r\n\r\n### 9.2 管理抽屉结构\r\n\r\n| Tab | 内容 |\r\n|---|---|\r\n| 当前标签 | 按 **法务风险 / 财务风险 / 业管风险** 三分区 Card 展示;含分区说明、维护权限提示、标签卡片、空状态 |\r\n| 新增标签 | 角色受限打标表单(见 9.3) |\r\n| 操作日志 | 该客户审计记录,按时间倒序 |\r\n\r\nDrawer 标题:**风险标签**;副标题为客户名称与编号。\r\n\r\n### 9.3 当前标签页 — 分区与卡片\r\n\r\n展示顺序:法务 → 财务 → 业管。\r\n\r\n每个分区包含:\r\n\r\n1. 分区标题与说明(责任部门、关注范围)\r\n2. 当前角色是否可维护(「可维护」/「当前角色无权维护该类标签」)\r\n3. 「新增该类标签」按钮(有权限时)\r\n4. 标签卡片:名称、级别、状态、时间字段、责任部门、操作人、原因摘要\r\n - **法务风险**:打标时间、复核周期、下次复核时间、最后复核时间(不展示「有效期至」)\r\n - **财务/业管**:打标时间、有效期至\r\n5. 空状态:「暂无该类风险标签」\r\n\r\n卡片操作:查看详情;审批(新增待审批或**解除待审批**且有权);**申请解除**(有权且生效中,须走审批流)。\r\n\r\n**法务风险复核规则(原型 Mock):**\r\n\r\n- 法务标签自打标后持续生效,**不会因到期自动解除**\r\n- 距 `nextReviewAt` ≤ 7 天:卡片显示「即将复核」\r\n- 当前日期超过 `nextReviewAt`:状态显示「待复核」,标签仍保留\r\n- **不在 Drawer 内完成复核**;系统自动向**法务工作台**推送待办,法务查验后回到本页「申请解除」\r\n- 打开 Drawer 且存在待复核法务标签时,页顶 Banner 提示待办已推送,并写入操作日志(工作台待办)\r\n- 确认可解除时:填写解除原因 → 提交解除申请(走审批流);通过后记录 `removedAt`\r\n\r\n**解除审批(原型 Mock):**\r\n\r\n- 任意标签解除均提交申请,状态为「解除待审批」\r\n- 申请解除弹窗说明:**法务标签**提示「法务查验完成后…」;**财务/业管标签**仅提示「提交解除申请,经审批后生效」(不含法务查验文案)\r\n- 法务/风控/管理层/系统管理员可审批:通过 → 已解除;驳回 → 恢复生效并清除申请标记\r\n\r\n### 9.4 新增标签表单\r\n\r\n| 字段 | 必填 | 规则 |\r\n|---|---|---|\r\n| 标签类型 | 是 | 非 admin:按角色自动带出法务/财务/业管并置灰;admin 可选三分区 |\r\n| 标签名称 | 是 | 按 8.1–8.4 optgroup;仅当前角色可维护的标签;不可重复有效同名标签 |\r\n| 风险级别 | 是 | 选标签后自动带出,置灰 |\r\n| 打标时间 | 是 | 日期(只读置灰) | **取操作当天**,不可编辑 |\r\n| 复核周期 | 法务必填 | 下拉 | 1 / 2 / 3 / 6 个月;变更时自动推算下次复核时间 |\r\n| 下次复核时间 | 法务必填 | 日期(只读置灰) | 根据打标时间 + 复核周期**自动推算**,不可编辑;下方提示「到期仅提醒责任人复核,不会自动解除标签。」 |\r\n| 有效期至 | 财务/业管必填 | 日期 | ≥ 打标时间;业务有效期,与复核提醒无关 |\r\n| 原因说明 | 是 | ≥ 10 字 |\r\n| 附件 | 高/致命必填 | .pdf / .jpg / .png / .xlsx |\r\n\r\n**法务 vs 财务/业管字段差异:**\r\n\r\n- 法务标签**不展示**「有效期至」,使用「下次复核时间」驱动定期复核提醒\r\n- 财务/业管保留「有效期至」,不在 UI 上与复核时间混用\r\n\r\n提交规则:\r\n\r\n- 低/中:直接 **生效中**\r\n- 高/致命:**待审批**;提交前二次确认\r\n- 涉诉/仲裁、严重拖欠、制裁/高风险地区:Toast 提示可能触发黑名单审批\r\n\r\n销售/运维/风控/管理层(非 admin):新增 Tab 展示无权限说明。\r\n\r\n### 9.5 批量打标\r\n\r\n- 已选客户 chips(>6 显示 +N)\r\n- 表单与单客户一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 写入批量操作日志\r\n\r\n### 9.6 统一数据模型 `CustomerRiskTag`\r\n\r\n| 字段 | 说明 |\r\n|---|---|\r\n| id / customerId | 标识 |\r\n| tagCode / tagName | 目录代码与名称 |\r\n| tagCategory / tagCategoryName | legal / finance / business |\r\n| responsibleDept / editableRoles | 责任部门与可编辑角色 |\r\n| riskLevel / riskLevelName | low / medium / high / fatal |\r\n| status / statusName | active / pending / expired / removed / rejected |\r\n| source / sourceName | system / manual |\r\n| reason / taggedAt / nextReviewAt / reviewCycle | 原因与复核时间(法务) |\r\n| expireDate | 财务/业管业务有效期 |\r\n| lastReviewedAt / reviewStatus / reviewLogs | 法务复核记录 |\r\n| removedAt / removeReason | 解除时间与原因 |\r\n| createdBy / createdAt / approvedBy / approvedAt | 操作与审批 |\r\n| attachments / sensitiveLevel / financeDetail | 附件与脱敏 |\r\n| removalRequested | 解除申请标记(原型) |\r\n\r\n### 9.7 标签生命周期\r\n\r\n| 状态 | 说明 | 同步至 `labels` |\r\n|---|---|---|\r\n| 生效中 | 正常展示 | 是 |\r\n| 待复核 | 法务标签超过下次复核时间,**仍生效** | 是 |\r\n| 待审批 | 高/致命新增后 | 否 |\r\n| 已驳回 | 审批拒绝 | 否 |\r\n| 已解除 | 解除成功(含复核确认解除并审批通过) | 否 |\r\n| 已失效 | 预留(法务标签**不会**自动进入此状态) | 否 |\r\n\r\n**法务复核与解除规则:**\r\n\r\n- 法务标签持续生效,系统仅按 `nextReviewAt` 提醒复核,**不自动解除**\r\n- 复核维持:在工作台完成查验(原型阶段以 Toast + 日志表示);如需解除使用「申请解除」\r\n- 复核确认风险解除:填写解除原因 → 提交解除申请 → 审批通过后 `status → 已解除` 并记录 `removedAt`\r\n- 任意路径的解除均需审批,不允许系统自动解除法务标签\r\n\r\n### 9.8 敏感信息脱敏\r\n\r\n| 场景 | 规则 |\r\n|---|---|\r\n| 财务敏感 | 非 finance / manager / admin 不可见逾期金额、账单编号等;提示「财务敏感信息已隐藏」 |\r\n| 法务附件 | 非 legal / riskControl / manager / admin 不可见法务附件;提示「法务敏感附件仅授权角色可见」 |\r\n| 操作日志 | 财务类日志 reason 对非授权角色脱敏 |\r\n\r\n---" }, { "type": "markdown", "id": "cm-doc-node-cm-batch-risk-tag-btn", "title": "批量打标入口", - "markdown": "### 8.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」 | 打开右侧抽屉,管理单个客户 |\r\n| 点击标签列 | 有权限时同「管理标签」 |\r\n| 工具栏「批量打标」 | 对当前勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。" + "markdown": "### 9.1 入口\r\n\r\n| 入口 | 说明 |\r\n|---|---|\r\n| 行操作「管理标签」/「查看标签」 | 打开右侧 Drawer |\r\n| 点击风险标签列 | 同打开 Drawer |\r\n| 工具栏「批量打标」 | 对勾选客户批量提交同一标签 |\r\n\r\n**不在**新增页、编辑页维护标签。" }, { "type": "markdown", "id": "cm-doc-node-cm-batch-risk-tag-modal", "title": "批量打标弹窗", - "markdown": "### 8.4 批量打标\r\n\r\n- 展示已选客户 chips(超过 6 个显示 +N)\r\n- 表单字段与单客户打标一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 提交后写入批量操作日志" + "markdown": "### 9.5 批量打标\r\n\r\n- 已选客户 chips(>6 显示 +N)\r\n- 表单与单客户一致\r\n- 已存在同名有效标签的客户自动跳过\r\n- 写入批量操作日志" } ] }, @@ -362,7 +389,7 @@ "type": "markdown", "id": "cm-doc-node-cm-create-base", "title": "客户信息", - "markdown": "## 2. 客户信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\r\n| 客户分级 | 否 | 下拉 | A / B / C |\r\n| 客户全称 | 是 | 文本 | 与客户证照一致;变更时同步发票抬头 |\r\n| 客户简称 | 否 | 文本 | 选填 |\r\n| 所属城市 | 是 | 省-市级联 | 示例省市区数据 |\r\n| 通讯地址 | 否 | 文本 | |\r\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\r\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\r\n| 备注 | 否 | 多行文本 | |\r\n\r\n---" + "markdown": "## 2. 客户信息\r\n\r\n| 字段 | 必填 | 控件 | 说明 |\r\n|---|---|---|---|\r\n| 客户类型 | 是 | 下拉 | 企业 / 个人 / 事业单位 |\r\n| 客户全称 | 是 | 文本 | 与证照一致;变更时同步发票抬头 |\r\n| 客户简称 | 否 | 文本 | 选填 |\r\n| 省市 | 否 | 文本(置灰) | 自动从通讯地址/地址解析识别 |\r\n| 所属城市 | 是 | 省-市(置灰) | **由地址解析自动识别**,不可手选 |\r\n| 通讯地址 | 是 | 文本 | 填写完整地址后解析省、市(同供应商管理交互) |\r\n| 业务负责部门 | 是 | 下拉 | 变更时清空负责人 |\r\n| 业务负责人员 | 是 | 下拉 | 随部门联动,未选部门时禁用 |\r\n| 合作状态 | 否 | 下拉 | 已合作 / 终止合作 / 洽谈中 / 合同到期 |\r\n| 备注 | 否 | 多行文本 | |\r\n\r\n**已移除:** 客户分级(A/B/C)不在新增页展示。\r\n\r\n---" }, { "type": "markdown", @@ -380,7 +407,7 @@ "type": "markdown", "id": "cm-doc-node-cm-create-footer", "title": "底部操作栏", - "markdown": "## 5. 校验与提交\r\n\r\n- 提交前校验所有必填项\r\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\r\n- 重置清空全部字段,客户类型默认「企业」" + "markdown": "## 5. 校验与提交\r\n\r\n| 校验项 | 规则 |\r\n|---|---|\r\n| 客户类型 | 必选 |\r\n| 客户全称 | 非空 |\r\n| 所属城市 | 通讯地址须能识别出省、市 |\r\n| 业务负责部门 / 人员 | 必选 |\r\n| 发票抬头 / 税号 / 开户银行 | 非空 |\r\n\r\n- 通过后 Toast「客户已保存(原型演示)」并返回列表\r\n- 重置清空全部字段;客户类型默认「企业」\r\n- 校验失败:Toast 首条错误(原型阶段)\r\n\r\n---" } ] } diff --git a/src/prototypes/customer-management/components/BatchRiskTagModal.tsx b/src/prototypes/customer-management/components/BatchRiskTagModal.tsx index 231479c..021c4c8 100644 --- a/src/prototypes/customer-management/components/BatchRiskTagModal.tsx +++ b/src/prototypes/customer-management/components/BatchRiskTagModal.tsx @@ -2,9 +2,9 @@ import React, { useEffect, useMemo, useState } from 'react'; import { X } from 'lucide-react'; import type { CustomerRecord } from '../types'; import { EMPTY_RISK_TAG_FORM, type MockUser, type RiskTagApplyForm, type RiskTagsByCustomer } from '../types/riskTag'; -import { canApplyRiskTagByName, roleToTagCategory } from '../utils/permissions'; +import { canAddRiskTags, canApplyRiskTagByName, roleToTagCategory } from '../utils/permissions'; import { TAG_CATALOG_MAP } from '../utils/riskTagConstants'; -import { defaultEffectiveTo, hasDuplicateActiveTag, todayDate, validateApplyForm } from '../utils/riskTagStore'; +import { defaultExpireDate, hasDuplicateActiveTag, todayDate, validateApplyForm } from '../utils/riskTagStore'; import { RiskTagApplyFormSection } from './RiskTagForm'; interface BatchRiskTagModalProps { @@ -26,12 +26,20 @@ export function BatchRiskTagModal({ onClose, onSubmit, }: BatchRiskTagModalProps) { - const [form, setForm] = useState(() => ({ - ...EMPTY_RISK_TAG_FORM, - tagType: roleToTagCategory(currentUser), - effectiveFrom: todayDate(), - effectiveTo: defaultEffectiveTo(), - })); + const category = roleToTagCategory(currentUser); + const [form, setForm] = useState(() => { + const taggedAt = todayDate(); + const category = roleToTagCategory(currentUser); + return { + ...EMPTY_RISK_TAG_FORM, + tagCategory: category, + taggedAt, + effectiveDate: taggedAt, + expireDate: category === 'legal' ? '' : defaultExpireDate(category, taggedAt), + nextReviewAt: category === 'legal' ? defaultExpireDate('legal', taggedAt) : '', + reviewCycle: '3个月', + }; + }); const [error, setError] = useState(''); const selectedRecords = useMemo( @@ -41,21 +49,39 @@ export function BatchRiskTagModal({ useEffect(() => { if (!open) return; - const nextType = roleToTagCategory(currentUser); + const nextCategory = roleToTagCategory(currentUser); setForm((prev) => { const keepTag = prev.tagName && canApplyRiskTagByName(currentUser, prev.tagName); const catalog = keepTag ? TAG_CATALOG_MAP[prev.tagName] : undefined; return { ...prev, - tagType: nextType, + tagCategory: nextCategory, tagName: keepTag ? prev.tagName : '', - riskLevel: keepTag && catalog ? catalog.defaultLevel : '中', + riskLevel: keepTag && catalog ? catalog.defaultLevel : 'medium', }; }); }, [currentUser, open]); if (!open) return null; + if (!canAddRiskTags(currentUser)) { + return ( +
+
e.stopPropagation()}> +
+

批量打标

+ +
+
+

当前角色无权批量打标

+
+
+
+ ); + } + const handleSubmit = () => { const validation = validateApplyForm(form); if (validation) { @@ -75,16 +101,23 @@ export function BatchRiskTagModal({ return; } - const result = onSubmit({ ...form, tagType: roleToTagCategory(currentUser) }, eligibleIds); + const result = onSubmit(form, eligibleIds); if (result) { setError(result); return; } setForm({ ...EMPTY_RISK_TAG_FORM, - tagType: roleToTagCategory(currentUser), - effectiveFrom: todayDate(), - effectiveTo: defaultEffectiveTo(), + tagCategory: roleToTagCategory(currentUser), + taggedAt: todayDate(), + effectiveDate: todayDate(), + expireDate: roleToTagCategory(currentUser) === 'legal' + ? '' + : defaultExpireDate(roleToTagCategory(currentUser), todayDate()), + nextReviewAt: roleToTagCategory(currentUser) === 'legal' + ? defaultExpireDate('legal', todayDate()) + : '', + reviewCycle: '3个月', }); setError(''); onClose(); diff --git a/src/prototypes/customer-management/components/CustomerTable.tsx b/src/prototypes/customer-management/components/CustomerTable.tsx index b304d19..ea3fa51 100644 --- a/src/prototypes/customer-management/components/CustomerTable.tsx +++ b/src/prototypes/customer-management/components/CustomerTable.tsx @@ -3,7 +3,7 @@ import type { CustomerRecord } from '../types'; import type { CustomerRiskTag, MockUser } from '../types/riskTag'; import { RiskLabelTags } from './RiskLabelTags'; import { StatusBadge } from './StatusBadge'; -import { canManageRiskTags } from '../utils/permissions'; +import { canAddRiskTags } from '../utils/permissions'; interface CustomerTableProps { records: CustomerRecord[]; @@ -32,7 +32,7 @@ export function CustomerTable({ }: CustomerTableProps) { const allSelected = records.length > 0 && records.every((record) => selectedIds.has(record.id)); const [hoverId, setHoverId] = useState(null); - const canManage = canManageRiskTags(currentUser); + const canManage = canAddRiskTags(currentUser); if (records.length === 0) { return ( @@ -94,7 +94,7 @@ export function CustomerTable({ onManageTags(record) : undefined} + onView={() => onManageTags(record)} /> {record.region} @@ -114,11 +114,16 @@ export function CustomerTable({ | - {canManage && ( + {canManage ? ( <> | + ) : ( + <> + | + + )} | diff --git a/src/prototypes/customer-management/components/RiskKpiCards.tsx b/src/prototypes/customer-management/components/RiskKpiCards.tsx new file mode 100644 index 0000000..7abf504 --- /dev/null +++ b/src/prototypes/customer-management/components/RiskKpiCards.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { + Building2, + CircleHelp, + Scale, + Tags, + TrendingDown, + Briefcase, +} from 'lucide-react'; +import type { CustomerRiskKpiKey } from '../types'; +import { RISK_KPI_CARDS } from '../utils/riskKpi'; + +const ICONS: Record = { + all: , + withRisk: , + finance: , + legal: , + business: , +}; + +interface RiskKpiCardsProps { + counts: Record; + active: CustomerRiskKpiKey; + onChange: (key: CustomerRiskKpiKey) => void; +} + +export function RiskKpiCards({ counts, active, onChange }: RiskKpiCardsProps) { + return ( +
+ {RISK_KPI_CARDS.map((card) => ( + + ))} +
+ ); +} diff --git a/src/prototypes/customer-management/components/RiskLabelTags.tsx b/src/prototypes/customer-management/components/RiskLabelTags.tsx index b10be46..b11d2eb 100644 --- a/src/prototypes/customer-management/components/RiskLabelTags.tsx +++ b/src/prototypes/customer-management/components/RiskLabelTags.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import type { CustomerRiskTag } from '../types/riskTag'; -import { getActiveTags, getPendingTags } from '../utils/riskTagStore'; +import { getActiveTags, getPendingTags, getVisibleListTags } from '../utils/riskTagStore'; interface RiskLabelTagsProps { tags: CustomerRiskTag[]; @@ -9,19 +9,29 @@ interface RiskLabelTagsProps { function levelClass(level: CustomerRiskTag['riskLevel']): string { switch (level) { - case '低': return 'cm-risk-tag--low'; - case '中': return 'cm-risk-tag--medium'; - case '高': return 'cm-risk-tag--high'; - case '极高': return 'cm-risk-tag--critical'; + case 'low': return 'cm-risk-tag--low'; + case 'medium': return 'cm-risk-tag--medium'; + case 'high': return 'cm-risk-tag--high'; + case 'fatal': return 'cm-risk-tag--critical'; default: return 'cm-risk-tag--medium'; } } +function buildTooltip(tag: CustomerRiskTag): string { + return [ + tag.tagName, + `类型:${tag.tagCategoryName}`, + `责任部门:${tag.responsibleDept}`, + `风险级别:${tag.riskLevelName}`, + `状态:${tag.statusName}`, + ].join('\n'); +} + +const MAX_VISIBLE = 2; + export function RiskLabelTags({ tags, onView }: RiskLabelTagsProps) { const [open, setOpen] = useState(false); - const visibleTags = tags.filter((tag) => - tag.status === '生效中' || tag.status === '待审批' || tag.status === '待确认', - ); + const visibleTags = getVisibleListTags(tags); const active = getActiveTags(visibleTags); const pending = getPendingTags(visibleTags); const displayTags = active.length > 0 ? active : pending; @@ -30,21 +40,24 @@ export function RiskLabelTags({ tags, onView }: RiskLabelTagsProps) { return ; } - const primary = displayTags[0]; - const overflow = displayTags.length - 1; + const shown = displayTags.slice(0, MAX_VISIBLE); + const overflow = displayTags.length - MAX_VISIBLE; return ( - + {shown.map((tag) => ( + + ))} {pending.length > 0 && active.length === 0 && ( - + )} {overflow > 0 && ( setOpen(false)} onFocus={() => setOpen(true)} onBlur={() => setOpen(false)} + onClick={onView} tabIndex={0} role="button" aria-label={`还有 ${overflow} 个标签`} @@ -61,22 +75,17 @@ export function RiskLabelTags({ tags, onView }: RiskLabelTagsProps) { {open && ( {displayTags.map((tag) => ( - - {tag.tagName} - {tag.status !== '生效中' && ( - {tag.status} - )} + + {tag.tagName} + + {tag.tagCategoryName} · {tag.riskLevelName} · {tag.statusName} + ))} )} )} - {pending.length > 0 && active.length > 0 && ( - - +{pending.length}审 - - )} ); -} +} \ No newline at end of file diff --git a/src/prototypes/customer-management/components/RiskTagDrawer.tsx b/src/prototypes/customer-management/components/RiskTagDrawer.tsx index 27de564..8dd7c66 100644 --- a/src/prototypes/customer-management/components/RiskTagDrawer.tsx +++ b/src/prototypes/customer-management/components/RiskTagDrawer.tsx @@ -1,19 +1,35 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { Clock, Plus, Tag, X } from 'lucide-react'; +import { AlertTriangle, Clock, Plus, ShieldAlert, Tag, X } from 'lucide-react'; import type { CustomerRecord } from '../types'; import type { CustomerRiskTag, MockUser, RiskTagApplyForm, RiskTagOperationLog, + TagCategoryCode, } from '../types/riskTag'; -import { canApproveRiskTag, canApplyRiskTagByName, canRemoveRiskTag, roleToTagCategory } from '../utils/permissions'; -import { TAG_CATALOG_MAP } from '../utils/riskTagConstants'; +import { + canAddRiskTags, + canApproveRiskTag, + canMaintainCategory, + canRemoveRiskTag, + canViewFinanceSensitive, + roleLabel, + roleToTagCategory, +} from '../utils/permissions'; +import { TAG_CATEGORY_META, TAG_CATEGORY_ORDER } from '../utils/riskTagConstants'; import { EMPTY_RISK_TAG_FORM } from '../types/riskTag'; import { - defaultEffectiveTo, - formatNow, + defaultExpireDate, + defaultNextReviewDate, + getDisplayStatusName, + getTaggedAt, + groupTagsByCategory, hasDuplicateActiveTag, + isHighRiskLevel, + isLegalRiskTag, + legalTagsNeedReview, + normalizeRiskTag, todayDate, validateApplyForm, } from '../utils/riskTagStore'; @@ -29,8 +45,158 @@ interface RiskTagDrawerProps { currentUser: MockUser; onClose: () => void; onApply: (form: RiskTagApplyForm) => string | null; - onRemove: (tagId: string, reason: string) => string | null; + onRemove: (tagId: string, reason: string, isApplication?: boolean) => string | null; onApprove: (tagId: string, approved: boolean, rejectReason?: string) => string | null; + onDispatchLegalTodos?: (customerId: string, customerName: string, overdueTags: CustomerRiskTag[]) => void; +} + +function actionTagClass(action: RiskTagOperationLog['action']): string { + switch (action) { + case '新增标签': return 'cm-log-tag--add'; + case '解除标签': return 'cm-log-tag--remove'; + case '提交审批': return 'cm-log-tag--pending'; + case '审批通过': return 'cm-log-tag--pass'; + case '审批驳回': return 'cm-log-tag--reject'; + case '到期复核': return 'cm-log-tag--review'; + case '工作台待办': return 'cm-log-tag--review'; + default: return ''; + } +} + +function levelClass(level: CustomerRiskTag['riskLevel']): string { + switch (level) { + case 'low': return 'cm-risk-tag--low'; + case 'medium': return 'cm-risk-tag--medium'; + case 'high': return 'cm-risk-tag--high'; + case 'fatal': return 'cm-risk-tag--critical'; + default: return 'cm-risk-tag--medium'; + } +} + +interface CategorySectionProps { + category: TagCategoryCode; + tags: CustomerRiskTag[]; + currentUser: MockUser; + onDetail: (id: string) => void; + onRemove: (id: string) => void; + onApprove: (id: string) => void; + onAddInCategory: () => void; +} + +function renderTagTimeFields(tag: CustomerRiskTag) { + const normalized = normalizeRiskTag(tag); + if (isLegalRiskTag(normalized)) { + return ( + <> +
打标时间
{getTaggedAt(normalized)}
+
复核周期
{normalized.reviewCycle}
+
下次复核时间
{normalized.nextReviewAt || '—'}
+
最后复核时间
{normalized.lastReviewedAt || '—'}
+ + ); + } + return ( + <> +
打标时间
{getTaggedAt(normalized)}
+
有效期至
{normalized.expireDate}
+ + ); +} + +function CategorySection({ + category, + tags, + currentUser, + onDetail, + onRemove, + onApprove, + onAddInCategory, +}: CategorySectionProps) { + const meta = TAG_CATEGORY_META[category]; + const canMaintain = canMaintainCategory(currentUser, category); + const visibleTags = tags.filter((t) => t.status !== 'removed' && t.status !== 'rejected'); + const zoneAnnotationId = category === 'legal' ? 'cm-risk-zone-legal' : undefined; + + return ( +
+
+
+

{meta.name}标签

+

{meta.description}

+
+ {canMaintain ? ( + 可维护 + ) : ( + 当前角色无权维护该类标签 + )} +
+ + {canMaintain && ( + + )} + + {visibleTags.length === 0 ? ( +

暂无该类风险标签

+ ) : ( +
+ {visibleTags.map((tag) => { + const normalized = normalizeRiskTag(tag); + const displayStatus = getDisplayStatusName(normalized); + const dueSoon = isLegalRiskTag(normalized) && normalized.reviewStatus === 'dueSoon'; + const overdue = isLegalRiskTag(normalized) && normalized.reviewStatus === 'overdue'; + const canRemove = canRemoveRiskTag(currentUser, normalized.tagCategory) + && normalized.status === 'active' + && !normalized.removalRequested; + const showApprove = canApproveRiskTag(currentUser) + && (normalized.status === 'pending' || (normalized.status === 'active' && normalized.removalRequested)); + + return ( +
+
+
+ {normalized.tagName} + + {normalized.riskLevelName} + + {dueSoon && 即将复核} + {overdue && 待复核} +
+ {displayStatus} +
+
+ {renderTagTimeFields(normalized)} +
责任部门
{normalized.responsibleDept}
+
操作人
{normalized.createdBy}
+
+

{normalized.reason}

+
+ + {showApprove && ( + + )} + {canRemove && ( + + )} +
+
+ ); + })} +
+ )} +
+ ); } export function RiskTagDrawer({ @@ -43,6 +209,7 @@ export function RiskTagDrawer({ onApply, onRemove, onApprove, + onDispatchLegalTodos, }: RiskTagDrawerProps) { const [tab, setTab] = useState('tags'); const [detailId, setDetailId] = useState(null); @@ -51,6 +218,7 @@ export function RiskTagDrawer({ const [removeTargetId, setRemoveTargetId] = useState(null); const [rejectReason, setRejectReason] = useState(''); const [approveTargetId, setApproveTargetId] = useState(null); + const [confirmHighRisk, setConfirmHighRisk] = useState(false); const [error, setError] = useState(''); const detailTag = useMemo( @@ -59,45 +227,76 @@ export function RiskTagDrawer({ ); const customerLogs = useMemo( - () => logs.filter((log) => log.customerId === record?.id), + () => logs + .filter((log) => log.customerId === record?.id) + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)), [logs, record?.id], ); + const groupedTags = useMemo(() => groupTagsByCategory(tags), [tags]); + const legalOverdueTags = useMemo(() => legalTagsNeedReview(tags), [tags]); + const disabledTagNames = useMemo( () => tags - .filter((tag) => tag.status === '生效中' || tag.status === '待审批' || tag.status === '待确认') + .filter((tag) => tag.status === 'active' || tag.status === 'pending') .map((tag) => tag.tagName), [tags], ); - /** 顶部切换操作角色时,同步打标表单中的标签类型与可选标签 */ + const removeTarget = useMemo( + () => tags.find((t) => t.id === removeTargetId) ?? null, + [tags, removeTargetId], + ); + + const approveTarget = useMemo( + () => tags.find((t) => t.id === approveTargetId) ?? null, + [tags, approveTargetId], + ); + useEffect(() => { if (!open) return; - const nextType = roleToTagCategory(currentUser); - setForm((prev) => { - const keepTag = prev.tagName && canApplyRiskTagByName(currentUser, prev.tagName); - const catalog = keepTag ? TAG_CATALOG_MAP[prev.tagName] : undefined; - return { - ...prev, - tagType: nextType, - tagName: keepTag ? prev.tagName : '', - riskLevel: keepTag && catalog ? catalog.defaultLevel : '中', - }; - }); - }, [currentUser, open]); - - if (!open || !record) return null; - - const resetAddForm = () => { + const category = roleToTagCategory(currentUser); + const taggedAt = todayDate(); setForm({ ...EMPTY_RISK_TAG_FORM, - tagType: roleToTagCategory(currentUser), - effectiveFrom: todayDate(), - effectiveTo: defaultEffectiveTo(), + tagCategory: category, + taggedAt, + effectiveDate: taggedAt, + expireDate: category === 'legal' ? '' : defaultExpireDate(category, taggedAt), + nextReviewAt: category === 'legal' + ? defaultNextReviewDate(category, '3个月', taggedAt) + : '', + reviewCycle: '3个月', + }); + setConfirmHighRisk(false); + setError(''); + }, [currentUser, open]); + + useEffect(() => { + if (!open || !record || legalOverdueTags.length === 0 || !onDispatchLegalTodos) return; + onDispatchLegalTodos(record.id, record.name, legalOverdueTags); + }, [open, record, legalOverdueTags, onDispatchLegalTodos]); + + const resetAddForm = (category?: TagCategoryCode) => { + const cat = category ?? roleToTagCategory(currentUser); + const taggedAt = todayDate(); + setForm({ + ...EMPTY_RISK_TAG_FORM, + tagCategory: cat, + taggedAt, + effectiveDate: taggedAt, + expireDate: cat === 'legal' ? '' : defaultExpireDate(cat, taggedAt), + nextReviewAt: cat === 'legal' + ? defaultNextReviewDate(cat, '3个月', taggedAt) + : '', + reviewCycle: '3个月', }); setError(''); + setConfirmHighRisk(false); }; + if (!open || !record) return null; + const handleSubmitApply = () => { const validation = validateApplyForm(form); if (validation) { @@ -108,7 +307,11 @@ export function RiskTagDrawer({ setError('该客户已存在同名有效标签'); return; } - const result = onApply({ ...form, tagType: roleToTagCategory(currentUser) }); + if (isHighRiskLevel(form.riskLevel) && !confirmHighRisk) { + setConfirmHighRisk(true); + return; + } + const result = onApply(form); if (result) { setError(result); return; @@ -118,12 +321,12 @@ export function RiskTagDrawer({ }; const handleConfirmRemove = () => { - if (!removeTargetId) return; + if (!removeTargetId || !removeTarget) return; if (removeReason.trim().length < 5) { - setError('移除原因至少 5 个字'); + setError('解除原因至少 5 个字'); return; } - const result = onRemove(removeTargetId, removeReason.trim()); + const result = onRemove(removeTargetId, removeReason.trim(), true); if (result) { setError(result); return; @@ -149,10 +352,15 @@ export function RiskTagDrawer({ setError(''); }; + const openAddForCategory = (category: TagCategoryCode) => { + resetAddForm(category); + setTab('add'); + }; + return (
- +
+ {legalOverdueTags.length > 0 && ( +
+ + 存在待复核法务风险标签,已向法务工作台发起待办,请前往查验;确认可解除后在本页「申请解除」。 +
+ )} + {TAG_CATEGORY_ORDER.map((category) => ( + { setRemoveTargetId(id); setRemoveReason(''); setError(''); }} + onApprove={(id) => { setApproveTargetId(id); setRejectReason(''); setError(''); }} + onAddInCategory={() => openAddForCategory(category)} + /> ))}
)} @@ -227,7 +428,10 @@ export function RiskTagDrawer({ setForm((prev) => ({ ...prev, ...patch }))} + onChange={(patch) => { + setConfirmHighRisk(false); + setForm((prev) => ({ ...prev, ...patch })); + }} disabledTagNames={disabledTagNames} /> )} @@ -235,25 +439,55 @@ export function RiskTagDrawer({ {tab === 'logs' && (
    {customerLogs.length === 0 &&
  1. 暂无操作日志
  2. } - {customerLogs.map((log) => ( -
  3. -
    - {log.action} - -
    -

    {log.detail}

    - {log.operator} · {log.tagName} -
  4. - ))} + {customerLogs.map((log) => { + const isFinanceSensitive = log.tagCategory === 'finance' + && !canViewFinanceSensitive(currentUser); + const reasonText = isFinanceSensitive + ? '财务敏感信息已隐藏' + : log.reason || log.detail || '—'; + + return ( +
  5. +
    + {log.action} + +
    +

    + {log.tagName} + · {log.tagCategoryName} +

    +

    {reasonText}

    +
    + {log.operator} + ({roleLabel(log.operatorRole)}) + {(log.statusBefore || log.statusAfter) && ( + + {log.statusBefore} → {log.statusAfter} + + )} +
    +
  6. + ); + })}
)}