docs: 修复设计规格审查问题 — 迁移编号/通知端点/根因验证
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

CRITICAL 修复:
- 迁移编号 000098/000099 → 000100/000101(避免与已有迁移冲突)
- 通知端点改为对接 GET /messages(后端无独立通知端点)

IMPORTANT 修复:
- 危急值 500 增加强制根因验证步骤(先确认 RLS 状态再决定是否补齐)
- 品牌设置增加公开端点 + localStorage 缓存策略(解决登录页未认证问题)
- #15 统计仪表盘降级为验证任务(DoctorDashboard 已消费 personalStats)
This commit is contained in:
iven
2026-05-01 17:12:41 +08:00
parent ff073c83a5
commit 988b405c5d

View File

@@ -3,7 +3,7 @@ title: 三端联调审计问题修复设计规格
created: 2026-05-01
status: draft
scope: 全量修复15 项)
estimated_effort: 24h
estimated_effort: 23h
phases: 3
---
@@ -30,13 +30,13 @@ phases: 3
| P3 | 12 | AI 分析 SSE 无触发入口 | 孤岛 | 4h |
| P3 | 13 | 家属管理无 UI | 孤岛 | 3h |
| P3 | 14 | E2E 测试数据污染 | 基础设施 | 1h |
| P4 | 15 | 统计仪表盘消费不足 | 孤岛 | 2h |
| P4 | 15 | 统计仪表盘消费验证 | 孤岛 | 1h |
## 实施阶段
- **Phase 1快速修复~5h**: #1-#6 — 主题设置联动、菜单入口、测试修复、危急值 500、事件堆积
- **Phase 2体验补全~5.5h**: #7-#11 — 导航关联、小程序修复
- **Phase 3功能闭环~10h**: #12-#15 — AI SSE 入口、家属管理、E2E 清理、统计消费
- **Phase 3功能闭环~9h**: #12-#15 — AI SSE 入口、家属管理、E2E 清理、统计验证
---
@@ -119,6 +119,26 @@ fn default_theme() -> ThemeResp {
}
```
**新增公开端点**: `crates/erp-config/src/handler/theme_handler.rs`
```rust
/// GET /api/v1/public/theme — 公开主题端点(无需认证)
/// 只返回品牌信息,用于登录页等未认证场景。
pub async fn get_public_theme<S>(
State(state): State<ConfigState>,
Extension(ctx): Extension<TenantContext>,
) -> Result<JsonResponse<ApiResponse<PublicThemeResp>>, AppError>
where
ConfigState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
// 从 settings 读取,返回品牌字段即可
// 如果租户上下文不存在,返回默认品牌信息
}
```
此端点需要在 `erp-server` 的公开路由组(无需认证)中注册。
### 前端修改
**文件**: `apps/web/src/api/themes.ts` — 扩展 ThemeConfig
@@ -162,12 +182,40 @@ export interface ThemeConfig {
**文件**: `apps/web/src/pages/Login.tsx` — 从主题配置读取
```tsx
const { themeConfig } = useAppStore();
**关键问题**: 登录页在未认证状态下无法调用 `/api/v1/themes`(需要 token。解决方案
<h1 className="brand-title">{themeConfig?.brand_name || 'HMS 健康管理平台'}</h1>
<p className="brand-desc">{themeConfig?.brand_slogan || '新一代健康管理平台'}</p>
<p className="brand-sub-desc">{themeConfig?.brand_features || '患者管理 · 健康监测 · 随访管理 · AI 智能分析'}</p>
1. 登录页先尝试从 `localStorage('hms-theme-config')` 读取缓存的主题配置
2. 同时将后端 `GET /api/v1/themes` 端点增加一个**无需认证**的公开端点 `GET /api/v1/public/theme`(只返回品牌信息,不含 sidebar_style 等内部配置)
3. Login 组件 mount 时调用公开端点获取品牌信息,失败则用 localStorage 缓存,再失败用硬编码默认值
```tsx
// Login.tsx
const [brandConfig, setBrandConfig] = useState<{
brand_name?: string;
brand_slogan?: string;
brand_features?: string;
brand_copyright?: string;
} | null>(null);
useEffect(() => {
// 尝试从公开端点获取品牌信息
fetch('/api/v1/public/theme')
.then(res => res.json())
.then(data => {
const config = data?.data;
setBrandConfig(config);
localStorage.setItem('hms-theme-config', JSON.stringify(config));
})
.catch(() => {
// fallback: localStorage 缓存 → 默认值
const cached = localStorage.getItem('hms-theme-config');
if (cached) setBrandConfig(JSON.parse(cached));
});
}, []);
<h1 className="brand-title">{brandConfig?.brand_name || 'HMS 健康管理平台'}</h1>
<p className="brand-desc">{brandConfig?.brand_slogan || '新一代健康管理平台'}</p>
<p className="brand-sub-desc">{brandConfig?.brand_features || '患者管理 · 健康监测 · 随访管理 · AI 智能分析'}</p>
```
底部版权同理。
@@ -236,7 +284,7 @@ ExperimentOutlined: <ExperimentOutlined />,
### 修复
**新建迁移**: `crates/erp-server/migration/src/m20260501_000098_seed_action_inbox_menu.rs`
**新建迁移**: `crates/erp-server/migration/src/m20260501_000100_seed_action_inbox_menu.rs`
```sql
INSERT INTO menus (id, tenant_id, parent_id, title, path, icon, sort_order,
@@ -273,21 +321,38 @@ WHERE NOT EXISTS (
### 修复
**步骤 1**: 确认迁移状态和表存在
**步骤 1**: 确认迁移状态和根因(强制验证)
```sql
-- 确认迁移已执行
SELECT * FROM seaql_migrations WHERE name LIKE '%090%';
-- 确认表存在
SELECT table_name FROM information_schema.tables
WHERE table_name IN ('critical_alerts', 'critical_alert_responses');
-- 确认 RLS 状态(关键:判断是否为 RLS 导致 500
SELECT relname, relrowsecurity, relforcerowsecurity
FROM pg_class WHERE relname = 'critical_alerts';
-- 如果 relrowsecurity = false → 不是 RLS 问题,需查看 tracing 日志
-- 如果 relrowsecurity = true 且无 policy → 需要步骤 2
```
**步骤 2**: 补齐 RLS 策略
启动后端并调用端点,查看 tracing 日志中的具体错误信息:
```bash
cargo run
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/v1/health/critical-alerts
# 查看 tracing 输出确认根因
```
**新建迁移**: `crates/erp-server/migration/src/m20260501_000099_rls_for_post_migration_tables.rs`
**步骤 2**: 根据步骤 1 结果决定是否补齐 RLS 策略
如果 RLS 已启用但缺少策略,**新建迁移**: `crates/erp-server/migration/src/m20260501_000101_rls_for_post_migration_tables.rs`
使用动态 SQL 扫描所有含 `tenant_id` 列但缺少 `tenant_isolation` 策略的表,自动补齐 RLS。
> **注意**: 此迁移需在 `migration/src/lib.rs` 中注册。
**步骤 3**: 在 `critical_alert_service.rs` 添加 tracing 日志
**步骤 3**: 在 `critical_alert_service.rs` 添加 tracing 日志(无论根因是 RLS 还是其他,都应加日志)
```rust
.map_err(|e| {
@@ -484,14 +549,17 @@ onClick={() => {
1. 新建通知 API 服务:`apps/miniprogram/src/services/notification.ts`
后端 `erp-message` 模块目前只有 `GET /api/v1/messages``PUT /api/v1/messages/{id}/read`,没有独立的通知端点。方案:对接现有消息列表端点,按类型筛选通知类消息。
```typescript
import { http } from '@/utils/http';
export const notificationService = {
// 复用现有消息列表端点,按 category 筛选通知
list: (params?: { page?: number; page_size?: number }) =>
http.get('/api/v1/messages/notifications', { params }),
http.get('/api/v1/messages', { params: { ...params, category: 'notification' } }),
markRead: (id: string) =>
http.put(`/api/v1/messages/notifications/${id}/read`),
http.put(`/api/v1/messages/${id}/read`),
};
```
@@ -502,12 +570,14 @@ export const notificationService = {
setNotifications([]);
// 修改后
const res = await notificationService.list({ page: 1, page_size: 20 });
setNotifications(res.data?.data || []);
try {
const res = await notificationService.list({ page: 1, page_size: 20 });
setNotifications(res.data?.data || []);
} catch {
setNotifications([]);
}
```
**前提条件**: 后端 `erp-message` 模块需确认通知 API 端点路径。如果尚无独立通知端点,可先对接消息列表端点 `/api/v1/messages` 并按类型筛选。
### 验证
小程序消息页"通知"Tab 展示从后端获取的数据列表。
@@ -667,27 +737,23 @@ export async function cleanupE2EData(api: ApiClient): Promise<void> {
---
## #15: 统计仪表盘消费不足 [P4]
## #15: 统计仪表盘消费验证 [P4]
### 根因
9 个统计端点中仅 `get_personal_stats`个人维度统计)未被前端消费。其余端点已被聚合端点 `health-data` 覆盖。
审计报告称"统计仪表盘消费不足"。经代码验证,`DoctorDashboard.tsx``NurseDashboard.tsx` 已在消费 `pointsApi.getPersonalStats()`分别在第 43 行和第 20 行。9 个后端统计端点中 6 个已被前端消费,仅 `get_dashboard_stats`(聚合端点,被分别调用替代)和 3 个单独端点(被 `health-data` 聚合端点覆盖)未被直接调用,但功能上已覆盖。
### 修复
**文件**: `apps/web/src/pages/health/StatisticsDashboard/DoctorDashboard.tsx`
降级为验证任务:
消费 `pointsApi.getPersonalStats()`,展示:
- 今日预约数、逾期随访、今日待随访
- 待审化验报告、异常体征患者、我的患者数
**文件**: `apps/web/src/pages/health/StatisticsDashboard/NurseDashboard.tsx`
类似消费,侧重体征上报率、今日随访。
1. 确认 DoctorDashboard/NurseDashboard 展示的个人统计指标是否完整(对比后端 `personal_stats` DTO 返回的全部字段)
2. 如果有遗漏字段(如 `abnormal_vital_signs``pending_lab_reviews`),在对应仪表盘中补充展示
3. 确认 `useStatsData.ts``tryFetch` 调用链无报错
### 验证
医生/护士仪表盘新增个人统计指标卡片
医生角色登录 → 统计报表页 → 确认所有统计指标正常展示
---