Files
erp/plans/merry-knitting-newt.md
iven 841766b168
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
fix(用户管理): 修复用户列表页面加载失败问题
修复用户列表页面加载失败导致测试超时的问题,确保页面元素正确渲染
2026-04-19 08:46:28 +08:00

170 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CRM 插件审计修复计划
## Context
CRM 插件开发审计发现 4 个 CRITICAL、6 个 HIGH、7 个 MEDIUM 问题。本计划按优先级分 3 批修复,覆盖后端 Rust 和前端 React 两端。
## Batch 1 — CRITICAL阻断性不修功能不可用
### Fix 1: 后端权限 SQL 参数化
**文件**: `crates/erp-plugin/src/service.rs` (第 578-714 行)
- `register_plugin_permissions()`: 将 `format!()` + `Statement::from_string()` 改为 `Statement::from_sql_and_values()` + 参数化占位符 `$1, $2, ...`
- `unregister_plugin_permissions()`: 同上,两段 UPDATE 都参数化
- 需要新增一个辅助函数 `resolve_manifest_id(plugin_id: Uuid, db) -> AppResult<String>` 从 plugins 表查 manifest_json 获取 metadata.id供 Fix 2 使用)
### Fix 2: 后端权限码使用 manifest_id 而非 UUID
**文件**: `crates/erp-plugin/src/data_handler.rs` (第 19-25 行, 第 49/107/145/177/211/256/301 行)
- 每个 handler 先从 DB 查 manifest_id`let manifest_id = resolve_manifest_id(plugin_id, &state.db).await?;`
- `compute_permission_code` 改为接受 manifest_id
- 添加 `resolve_manifest_id` 辅助函数:查 plugins 表 → 解析 manifest_json → 提取 metadata.id
- 考虑在 PluginState 中缓存 manifest_id 映射(或直接在 data_service 层缓存)
**实现**: 将 `resolve_manifest_id` 放在 `data_service.rs` 中(与 `resolve_table_name` 同级handler 调用它。
### Fix 3: 4 个路由页面组件自行加载 schema
**文件**: 4 个页面组件 + `api/plugins.ts` + `api/pluginData.ts`
统一模式:每个组件通过 `useParams()` 获取路由参数,内部调用 `getPluginSchema()` 加载 schema从 schema 中提取所需数据。
**3a. PluginTabsPage.tsx**:
- 移除所有 propspluginId/label/tabs/entities改用 `useParams<{ pluginId: string; pageLabel: string }>()`
- 内部调用 `getPluginSchema(pluginId)` 获取 schema
-`schema.ui.pages` 中找到 type='tabs' 且 label 匹配 pageLabel 的页面
- 替换 `require()` 为顶层 ES `import`
- 移除不存在的 `enableSearch` prop 传递
**3b. PluginTreePage.tsx**:
- 移除 propspluginId/entity/idField/parentField/labelField/fields改用 `useParams<{ pluginId: string; entityName: string }>()`
- 从 schema 加载 entity 字段和页面配置tree 页面的 id_field/parent_field/label_field
**3c. PluginGraphPage.tsx**:
- 移除所有 props改用 `useParams<{ pluginId: string; entityName: string }>()`
- 从 schema 中找到 type='graph' 的页面配置
**3d. PluginDashboardPage.tsx**:
- 移除 propspluginId/entities改用 `useParams<{ pluginId: string }>()`
- 内部调用 `getPluginSchema(pluginId)` 获取所有 entities
### Fix 4: PluginPageSchema 补充 graph/dashboard 类型
**文件**: `apps/web/src/api/plugins.ts` (第 154-158 行)
扩展 union type
```typescript
| { type: 'graph'; entity: string; label: string; relationship_entity: string; source_field: string; target_field: string; edge_label_field: string; node_label_field: string }
| { type: 'dashboard'; label: string }
```
### Fix 5: api/pluginData.ts 补充 count/aggregate API
**文件**: `apps/web/src/api/pluginData.ts`
新增两个函数:
- `countPluginData(pluginId, entity, options?)``GET /plugins/{id}/{entity}/count`
- `aggregatePluginData(pluginId, entity, groupBy, filter?)``GET /plugins/{id}/{entity}/aggregate`
## Batch 2 — HIGH稳定性和正确性
### Fix 6: AbortController 防竞态
**文件**: 所有 5 个页面组件的 useEffect
为数据加载 useEffect 添加 AbortController
```typescript
useEffect(() => {
const abortController = new AbortController();
// ... async loadData 中检查 abortController.signal.aborted
return () => abortController.abort();
}, [deps]);
```
注意:当前 api client (axios) 不支持 AbortSignal 透传,简单方案是在 setState 前检查 `abortController.signal.aborted` 或使用一个 `mounted` flag。
### Fix 7: Dashboard 改用后端 aggregate API
**文件**: `apps/web/src/pages/PluginDashboardPage.tsx`
- 使用 `countPluginData()` 获取总数
- 使用 `aggregatePluginData()` 获取分组统计
- 移除全量循环加载逻辑
- 保留 fallback如果 aggregate API 失败,显示总数 + 提示
### Fix 8: fetchData 双重请求修复
**文件**: `apps/web/src/pages/PluginCRUDPage.tsx` (第 501-511 行)
搜索操作改为直接传参模式:
```typescript
onSearch={(value) => {
setSearchText(value);
setPage(1);
fetchData(1, { search: value }); // 直接传参
}}
```
修改 `fetchData` 签名允许覆盖搜索参数。
### Fix 9: Canvas 高 DPI 支持
**文件**: `apps/web/src/pages/PluginGraphPage.tsx` (第 114-118 行)
```typescript
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.scale(dpr, dpr);
```
## Batch 3 — MEDIUM建议修复
### Fix 10: 服务端排序替代前端排序
**文件**: `apps/web/src/pages/PluginCRUDPage.tsx`
- Table 的 `onChange` 回调捕获 sortField/sortOrder
- 传给 `fetchData` 作为 `sort_by`/`sort_order` 参数
- 移除列定义中的 `sorter: true`
### Fix 11: Canvas 暗色主题支持
**文件**: `apps/web/src/pages/PluginGraphPage.tsx`
从 CSS 变量读取主题色:
```typescript
const style = getComputedStyle(canvas);
const textColor = style.getPropertyValue('--antd-color-text') || '#333';
const lineColor = style.getPropertyValue('--antd-color-border') || '#999';
```
### Fix 12: schema 加载失败提示用户
**文件**: 所有页面组件的 `.catch(() => {})`
替换为 `message.warning('Schema 加载失败,部分功能不可用')`
### Fix 13: 后端 data_service 缓存优化
**文件**: `crates/erp-plugin/src/data_service.rs`
合并 `resolve_table_name``resolve_entity_fields` 为一个函数 `resolve_entity_info()`,减少数据库查询次数。
## 关键文件清单
| 文件 | 修改类型 |
|------|---------|
| `crates/erp-plugin/src/service.rs` | SQL 参数化 |
| `crates/erp-plugin/src/data_handler.rs` | manifest_id 查找 |
| `crates/erp-plugin/src/data_service.rs` | resolve_manifest_id + resolve_entity_info 缓存 |
| `apps/web/src/api/plugins.ts` | PluginPageSchema 扩展 |
| `apps/web/src/api/pluginData.ts` | count/aggregate API |
| `apps/web/src/pages/PluginTabsPage.tsx` | 自加载 schema |
| `apps/web/src/pages/PluginTreePage.tsx` | 自加载 schema |
| `apps/web/src/pages/PluginGraphPage.tsx` | 自加载 schema + DPI + 暗色 |
| `apps/web/src/pages/PluginDashboardPage.tsx` | 自加载 schema + 后端 aggregate |
| `apps/web/src/pages/PluginCRUDPage.tsx` | 搜索/排序修复 |
## 验证计划
1. `cargo check --workspace` 通过
2. `cargo test --workspace` 通过
3. `cd apps/web && pnpm build` 通过(验证 require → import 修复)
4. 手动验证:
- 侧边栏点击 CRM tabs 菜单 → 页面正常渲染
- CRUD 页面搜索/筛选/排序正常
- Tree 页面展示树形结构
- Graph 页面渲染图谱(高 DPI 清晰)
- Dashboard 页面显示统计