docs: 修复设计规格审查问题 — 迁移编号/通知端点/根因验证
CRITICAL 修复: - 迁移编号 000098/000099 → 000100/000101(避免与已有迁移冲突) - 通知端点改为对接 GET /messages(后端无独立通知端点) IMPORTANT 修复: - 危急值 500 增加强制根因验证步骤(先确认 RLS 状态再决定是否补齐) - 品牌设置增加公开端点 + localStorage 缓存策略(解决登录页未认证问题) - #15 统计仪表盘降级为验证任务(DoctorDashboard 已消费 personalStats)
This commit is contained in:
@@ -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` 调用链无报错
|
||||
|
||||
### 验证
|
||||
|
||||
医生/护士仪表盘新增个人统计指标卡片。
|
||||
以医生角色登录 → 统计报表页 → 确认所有统计指标正常展示。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user