fix(用户管理): 修复用户列表页面加载失败问题
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

修复用户列表页面加载失败导致测试超时的问题,确保页面元素正确渲染
This commit is contained in:
iven
2026-04-19 08:46:28 +08:00
parent 0ee9d22634
commit 841766b168
174 changed files with 26366 additions and 675 deletions

View File

@@ -0,0 +1,242 @@
# CRM 插件平台标杆 — P0 基础能力设计
## Context
CRM 插件作为 ERP 平台的第一个行业插件,目前暴露了插件平台的多项基础能力缺口。本次设计的定位不是"CRM 功能最全",而是"插件平台能力最扎实"——通过 CRM 验证的每个能力都应被所有未来插件inventory、生产、财务等零改动复用。
对标一流 CRMSalesforce/HubSpot/Pipedrive的差距分析表明当前 CRM 更接近"客户通讯录+标签+图谱",距离可用 CRM 有显著差距。但这些差距中,最优先补的是**平台基础设施**,而非 CRM 业务功能。
## 设计决策记录
| 决策点 | 选择 | 理由 |
|--------|------|------|
| 目标定位 | 插件平台标杆 | 打磨通用能力,所有插件受益 |
| 推进节奏 | P0 基础先行 | 基础不扎实,上层的业务功能无法可靠运行 |
| 实体关系复杂度 | 全覆盖 (1:N/N:1/N:N/自引用) | CRM 和 inventory 都需要,一次到位 |
| 字段校验范围 | 完整套件 (6种) | 数据质量是所有插件的生命线 |
| 前端硬编码 | 全部通用化 | 第二个插件必须零改动可用 |
## P0-1: 实体关系声明 + ref_entity + 级联策略
### Manifest Schema 扩展
在 entity 下新增 `[[relations]]` 段:
```toml
[[schema.entities.relations]]
name = "contacts" # 关系名
target_entity = "contact" # 目标实体
type = "one_to_many" # one_to_many | many_to_one | many_to_many
foreign_key = "customer_id" # FK 字段
on_delete = "cascade_soft_delete" # cascade_soft_delete | set_null | restrict
display_field = "name" # 下拉框显示字段
# N:N 需要中间表
[[schema.entities.relations]]
name = "related_customers"
target_entity = "customer"
type = "many_to_many"
through_entity = "customer_relationship"
through_source_field = "from_customer_id"
through_target_field = "to_customer_id"
```
### 后端实现 (crates/erp-plugin/)
**关键文件:**
- `src/manifest.rs` — ManifestParser 新增 relations 解析
- `src/dynamic_table.rs` — 安装时存储关系到 entity metadata
- `src/data_service.rs` — 删除时执行级联策略,创建/更新时验证 FK
- `src/handler/data_handler.rs` — 错误响应格式
**级联策略执行流程:**
```
DELETE /plugins/{id}/{entity}/{rid}
→ 查询 entity 的所有 incoming relations (被引用的关系)
→ for each relation:
cascade_soft_delete → UPDATE child SET deleted_at=now() WHERE fk=rid
set_null → UPDATE child SET fk=NULL WHERE fk=rid
restrict → SELECT COUNT children, if >0 return 409 Conflict
→ 软删除目标记录
```
**FK 存在性校验:**
```
POST/PUT /plugins/{id}/{entity}
→ for each field with ref_entity:
SELECT EXISTS(SELECT 1 FROM plugin_xxx_{ref_entity} WHERE id=field_value AND deleted_at IS NULL)
→ 不存在则返回 400 + 具体字段错误
```
### 前端实现 (apps/web/)
**关键文件:**
- `src/pages/PluginCRUDPage.tsx` — 自动为 ref_entity 字段渲染 EntitySelect
- `src/pages/PluginDetailPage` — 自动渲染关联子实体内嵌列表
- `src/components/EntitySelect.tsx` — 增强支持 display_field 配置
- `src/api/plugins.ts` — schema 类型新增 relations
**详情页自动关联渲染:**
- 读取 entity 的 outgoing relations (one_to_many)
- 为每个 relation 渲染内嵌 CRUD 表格compact 模式,带 filter=fk:parent_id
- 级联删除前弹出确认("将同时删除 3 条联系人"
### CRM plugin.toml 改造
为 5 个实体补充 relations 声明:
- customer → contacts (1:N, cascade_soft_delete)
- customer → communications (1:N, cascade_soft_delete)
- customer → tags (1:N, cascade_soft_delete)
- customer → parent (N:1, set_null, 自引用)
- contact → communications (1:N, cascade_soft_delete)
---
## P0-2: 字段校验层
### Manifest Schema 扩展
在 field 下新增 `[validation]` 子结构:
```toml
[[schema.entities.fields]]
name = "phone"
field_type = "string"
[schema.entities.fields.validation]
pattern = "^1[3-9]\\d{9}$"
message = "请输入有效的手机号码"
min_length = 11
max_length = 11
[[schema.entities.fields]]
name = "email"
field_type = "string"
[schema.entities.fields.validation]
pattern = "^[\\w.-]+@[\\w.-]+\\.\\w+$"
message = "请输入有效的邮箱地址"
max_length = 254
```
### 后端校验器 (crates/erp-plugin/src/validation.rs — 新文件)
6 种校验器统一执行:
| 校验器 | 触发条件 | 错误格式 |
|--------|---------|---------|
| required | `field.required = true` | `{field}: 不能为空` |
| unique | `field.unique = true` | `{field}: 该值已存在` |
| pattern | `validation.pattern` regex match | `{field}: {validation.message}` |
| ref_exists | `field.ref_entity` FK 查询 | `{field}: 引用的{entity}不存在` |
| min_length / max_length | `validation.min_length / max_length` | `{field}: 长度必须在 {min}-{max} 之间` |
| min_value / max_value | `validation.min_value / max_value` | `{field}: 值必须在 {min}-{max} 之间` |
**执行位置:** `data_service.rs` 的 create/update 方法中,数据写入前统一调用。
**错误响应:**
```json
{
"success": false,
"error": "数据验证失败",
"details": [
{ "field": "phone", "message": "请输入有效的手机号码" },
{ "field": "customer_id", "message": "引用的客户不存在" }
]
}
```
### 前端校验生成
从 schema 自动生成 Ant Design Form rules
- `required``{ required: true, message: "..." }`
- `pattern``{ pattern: /regex/, message: "..." }`
- `min_length / max_length``{ min: n, max: n, message: "..." }`
### CRM plugin.toml 补充校验
- phone: pattern 手机号
- email: pattern 邮箱
- credit_code: pattern 统一社会信用代码 (18位)
- website: pattern URL
- customer_id: ref_entity = "customer" (FK 校验)
---
## P0-3: 前端去硬编码
### Dashboard 通用化
**文件:** `apps/web/src/pages/dashboard/dashboardConstants.tsx`, `PluginDashboardPage.tsx`
改造方案:
- 移除 `ENTITY_COLORS``ENTITY_ICONS` 硬编码映射
- 颜色自动分配: 8 色调色板按 entity 顺序循环
- 图标从 page schema 的 icon 字段读取
- 标题: `{manifest.name} 统计概览`,副标题: `{manifest.description}`
- Widget 定义从 page schema 的 widgets 数组读取
### Graph 通用化
**文件:** `apps/web/src/pages/plugins/graph/graphConstants.ts`
改造方案:
- 移除 `RELATIONSHIP_COLORS` 硬编码
- 关系类型标签从 field.options 读取 (已有 label 映射)
- 颜色用调色板按 option 顺序循环分配
- 未知类型 fallback 到灰色 + 原始 label
### CRUD 表格列可配置
**文件:** `PluginCRUDPage.tsx`
改造方案:
- manifest page 新增 `table_columns: ["name", "customer_type", "level", "status", "owner_id"]`
- 不声明则默认取前 8 个非 hidden 非 FK 字段
- 移除 `fields.slice(0, 5)` 硬编码
### 验证标准
> 换成 inventory 插件Dashboard/Graph/CRUD 应该零改动正确渲染。
---
## 关键文件清单
| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `crates/erp-plugin/src/manifest.rs` | 修改 | 新增 relations + validation 解析 |
| `crates/erp-plugin/src/validation.rs` | 新建 | 校验引擎 |
| `crates/erp-plugin/src/data_service.rs` | 修改 | 集成级联策略 + 校验 |
| `crates/erp-plugin/src/dynamic_table.rs` | 修改 | 安装时存储关系元数据 |
| `crates/erp-plugin/src/handler/data_handler.rs` | 修改 | FK 校验错误格式 |
| `crates/erp-plugin-crm/plugin.toml` | 修改 | 补充 relations + validation |
| `apps/web/src/pages/dashboard/dashboardConstants.tsx` | 修改 | 去硬编码,通用调色板 |
| `apps/web/src/pages/dashboard/DashboardWidgets.tsx` | 修改 | schema 驱动 |
| `apps/web/src/pages/PluginDashboardPage.tsx` | 修改 | 通用标题/副标题 |
| `apps/web/src/pages/plugins/graph/graphConstants.ts` | 修改 | 关系类型从 options 读取 |
| `apps/web/src/pages/PluginCRUDPage.tsx` | 修改 | 可配置列数 |
| `apps/web/src/api/plugins.ts` | 修改 | 类型定义更新 |
---
## 验证方案
1. **编译检查**: `cargo check` 全 workspace 通过
2. **单元测试**: validation.rs 每种校验器独立测试
3. **集成测试**: Testcontainers 验证级联删除/FK 校验/unique 冲突
4. **功能验证**:
- 重新安装 CRM 插件,确认 5 个 relation 正确注册
- 删除客户 → 联系人/沟通记录/标签级联软删除
- 创建联系人 → customer_id 不存在时返回 400
- 手机号/邮箱格式不正确时返回校验错误
- Dashboard 切换 inventory 插件时正确渲染
5. **前端验证**: `pnpm dev` 启动后手动测试所有页面
---
## 输出产物
1. 设计规格文档: `docs/superpowers/specs/2026-04-18-crm-plugin-platform-p0-design.md`
2. 实施计划: 通过 writing-plans skill 生成
3. 知识库文档: 记录讨论过程和决策理由