# 统一报告管理中心 — 设计规格
> 日期: 2026-07-28
> 状态: 待审核
## 1. 背景与目标
### 现状
当前系统有4类报告分散在不同模块:
| 报告类型 | 源表 | 当前前端入口 | 文件支持 |
|----------|------|-------------|---------|
| 菌群检测 / 体检 | `health_reports` | `pages/health/report-list.vue` | ✅ PDF/图片 |
| DAN测评 | `dan_report_uploads` | `pages/dan-assessment/report-upload.vue` | ⚠️ 部分有PDF |
| 保单 | `insurance_policies` | `pages/wealth-sub/insurance-list.vue` | ❌ 纯结构化数据 |
| 家庭周报 | `family_fortune_report` | 无统一入口 | ✅ PDF |
用户需在多个入口之间切换查看不同类型报告,且无法统一搜索、下载、分享。
### 目标
- 统一报告管理页面,一个入口查看全部报告
- 支持按类型筛选、关键词搜索
- 报告下载(PDF/图片)和转发分享
- 小程序端 + Web管理端双端覆盖
---
## 2. 数据层:统一报告汇总表
### 2.1 表结构 `report_summary`
```sql
CREATE TABLE IF NOT EXISTS report_summary (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
-- 分类标识
report_type VARCHAR(32) NOT NULL COMMENT '报告类型: gut_flora/physical_exam/dan/insurance/fortune',
source_table VARCHAR(32) NOT NULL COMMENT '来源表: health_reports/dan_report_uploads/insurance_policies/family_fortune_report',
source_id BIGINT NOT NULL COMMENT '来源表记录ID',
-- 归属信息
user_id BIGINT NOT NULL COMMENT '创建者/上传者用户ID',
family_id BIGINT COMMENT '家庭ID',
subject_id BIGINT COMMENT '报告主体ID(users/family_members)',
subject_name VARCHAR(50) COMMENT '报告主体名称',
-- 展示信息
title VARCHAR(200) COMMENT '报告标题',
summary TEXT COMMENT '摘要(列表页展示)',
-- 文件信息
file_url VARCHAR(500) COMMENT '报告文件URL(PDF/图片)',
file_size BIGINT COMMENT '文件大小(字节)',
file_name VARCHAR(200) COMMENT '原始文件名',
has_file TINYINT DEFAULT 0 COMMENT '是否有可下载文件: 1=有, 0=无',
-- 评分/日期
overall_score INT COMMENT '综合评分(0-100,如适用)',
report_date DATE COMMENT '报告日期(体检日期/测评日期等)',
-- 状态
status VARCHAR(20) COMMENT '状态: active/confirmed/completed/expired/archived/discarded',
-- 前端路由
detail_route VARCHAR(100) COMMENT '详情页路由(如 /pages/health/physical-exam-detail)',
-- 时间
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_family_id (family_id),
INDEX idx_subject_id (subject_id),
INDEX idx_report_type (report_type),
INDEX idx_report_date (report_date),
INDEX idx_status (status),
UNIQUE KEY uk_source (source_table, source_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统一报告汇总表';
```
### 2.2 各源表字段映射规则
#### 菌群 / 体检 (`health_reports`)
| `report_summary` 字段 | 映射来源 | 说明 |
|----------------------|---------|------|
| `report_type` | `report_type` (gut_flora/physical_exam) | 直接映射 |
| `source_table` | 常量 `health_reports` | — |
| `source_id` | `id` | — |
| `user_id` | `user_id` | 上传者 |
| `family_id` | `family_id` | — |
| `subject_id` | `subject_id` | 报告主体(可为null) |
| `subject_name` | 从 `users` 表关联查询 | 按 `subject_id` |
| `title` | 拼接: `{报告主体姓名} 的 {report_type} 报告` | 体检报告/菌群检测报告 |
| `file_url` | `file_url` | — |
| `overall_score` | `overall_score` | — |
| `report_date` | `report_date` | — |
| `status` | `status` | active/archived |
| `detail_route` | gut_flora → `/pages/health/gut-flora-detail`, physical_exam → `/pages/health/physical-exam-detail` | 路由映射 |
| `has_file` | `file_url` IS NOT NULL → 1, else 0 | — |
#### DAN测评 (`dan_report_uploads`)
| `report_summary` 字段 | 映射来源 | 说明 |
|----------------------|---------|------|
| `report_type` | 常量 `dan` | — |
| `source_table` | 常量 `dan_report_uploads` | — |
| `source_id` | `id` | — |
| `user_id` | `uploader_user_id` | — |
| `family_id` | `family_id` | — |
| `subject_id` | `family_member_id` | 测评对象 |
| `subject_name` | 从 `family_members` 表关联查询 | 按 `family_member_id` |
| `title` | 拼接: `{subject_name} 的 {dimension} 测评` | dimension=mind/wisdom |
| `file_url` | `file_url` | — |
| `overall_score` | 从 `dan_assessment_results` 关联查询 `overall_score` | 通过 `linked_result_id` |
| `report_date` | `uploaded_at` (DATE部分) | 上传日期 |
| `status` | `draft_status` → 映射为 active/confirmed/discarded | — |
| `detail_route` | `/pages/dan-assessment/report-upload` (带id参数) | — |
| `has_file` | `file_url` IS NOT NULL → 1, else 0 | — |
#### 保单 (`insurance_policies`)
| `report_summary` 字段 | 映射来源 | 说明 |
|----------------------|---------|------|
| `report_type` | 常量 `insurance` | — |
| `source_table` | 常量 `insurance_policies` | — |
| `source_id` | `id` | — |
| `user_id` | `user_id` | — |
| `subject_name` | `insured_person` | 被保人 |
| `title` | 拼接: `{policy_name}` | — |
| `summary` | 拼接: `{insurance_company} · {policy_type} · 保额 ¥{sum_insured}` | — |
| `file_url` | NULL | 保单无PDF |
| `has_file` | 0 | 纯结构化数据 |
| `report_date` | `start_date` | 保单生效日期 |
| `status` | `status` | active/expired/cancelled |
| `detail_route` | `/pages/wealth-sub/insurance-list` | 跳转到保单列表,标记选中 |
| `overall_score` | NULL | 不适用 |
#### 家庭周报 (`family_fortune_report`)
| `report_summary` 字段 | 映射来源 | 说明 |
|----------------------|---------|------|
| `report_type` | 常量 `fortune` | — |
| `source_table` | 常量 `family_fortune_report` | — |
| `source_id` | `id` | — |
| `user_id` | 从 `family_members` 关联查询(family主用户) | — |
| `family_id` | `family_id` | — |
| `title` | 拼接: `{element} 天盘周报` | — |
| `summary` | 拼接: `{luckyDirection} · {createdAt}` | — |
| `file_url` | `pdfPath` | — |
| `file_size` | `fileSize` | — |
| `has_file` | `pdfPath` IS NOT NULL → 1, else 0 | — |
| `report_date` | `createdAt` (DATE部分) | — |
| `status` | active | — |
| `detail_route` | `/pages/tianpan/index` | 跳转到天盘 |
| `overall_score` | NULL | 不适用 |
---
## 3. 数据同步策略:Service层同步 + 定时任务兜底
### 3.1 Service层即时同步
新建 `ReportSummaryService`,提供 `sync()` 方法。在各源表的CRUD操作末尾调用。
```java
// 各Service中的伪代码
// HealthReportService.save(report) 末尾:
reportSummaryService.syncFromHealthReport(report);
// DanReportUploadService.confirm(upload) 末尾:
reportSummaryService.syncFromDanReport(upload);
// InsurancePolicyService.create(policy) 末尾:
reportSummaryService.syncFromInsurancePolicy(policy);
// FamilyFortuneReport相关Service:
reportSummaryService.syncFromFortuneReport(report);
```
**关键设计**:
- `sync()` 使用 `INSERT ... ON DUPLICATE KEY UPDATE` 保证幂等
- 通过 `UNIQUE KEY uk_source (source_table, source_id)` 去重
- 源表删除时:更新 `report_summary.status = 'discarded'`,不物理删除
### 3.2 定时任务全量校准
新建 `ReportSummarySyncTask`,每天凌晨 2:00 执行:
```
POST /api/admin/report-summary/sync (管理员手动触发)
定时: cron 0 0 2 * * ? (每日凌晨2点自动)
```
**执行逻辑**:
1. 扫描 `health_reports` → 更新/插入 `report_summary`(已删除的标记为 discarded)
2. 扫描 `dan_report_uploads` → 同上
3. 扫描 `insurance_policies` → 同上
4. 扫描 `family_fortune_report` → 同上
5. 统计并记录:新增 N 条、更新 N 条、标记废弃 N 条
6. 日志输出同步结果
**异常处理**:
- 源表查询失败 → 记录错误日志,继续处理其他类型
- 汇总写入失败 → 重试3次,仍失败则告警
---
## 4. 后端API设计
### 4.1 统一报告列表
```
POST /api/report-summary/list
请求体:
{
"page": 1,
"size": 20,
"type": "all", // gut_flora|physical_exam|dan|insurance|fortune|all
"keyword": "", // 搜索标题/主体名称
"subjectId": null, // 按报告主体筛选
"startDate": null, // 报告日期范围起
"endDate": null
}
响应:
{
"code": 200,
"data": {
"total": 156,
"list": [
{
"id": 1,
"reportType": "gut_flora",
"reportTypeName": "菌群检测",
"title": "小李的菌群检测报告",
"subjectName": "小李",
"reportDate": "2025-03-15",
"overallScore": 78,
"hasFile": true,
"status": "active",
"createdAt": "2025-03-16 10:30:00"
}
]
}
}
```
### 4.2 报告详情
```
POST /api/report-summary/detail/{summaryId}
响应:
{
"code": 200,
"data": {
"id": 1,
"reportType": "gut_flora",
"reportTypeName": "菌群检测",
"title": "小李的菌群检测报告",
"subjectName": "小李",
"summary": "肠道菌群健康评分78分...",
"fileUrl": "https://cdn.xxx.com/reports/abc123.pdf",
"fileName": "gut_flora_report_20250315.pdf",
"fileSize": 2048576,
"hasFile": true,
"overallScore": 78,
"reportDate": "2025-03-15",
"status": "active",
"detailRoute": "/pages/health/gut-flora-detail",
"detailParams": { "reportId": 45 } // 跳转到详情页的额外参数
}
}
```
### 4.3 报告下载
```
POST /api/report-summary/download/{summaryId}
响应:
{
"code": 200,
"data": {
"fileUrl": "https://cdn.xxx.com/reports/abc123.pdf",
"fileName": "gut_flora_report_20250315.pdf",
"fileSize": 2048576
}
}
```
### 4.4 管理员手动同步
```
POST /api/admin/report-summary/sync
响应:
{
"code": 200,
"message": "同步完成",
"data": {
"newCount": 5,
"updateCount": 12,
"discardedCount": 2,
"errorCount": 0
}
}
```
---
## 5. 前端设计
### 5.1 小程序端
#### 入口
在 `pages/profile/components/ProfileMenu.vue` 的「个人」分组末尾添加:
```html
```
对应方法:
```js
goToReportManagement() {
uni.navigateTo({ url: '/pages/profile/report-management' })
}
```
#### 页面:`pages/profile/report-management.vue`
**页面结构**:
```
┌─────────────────────────────┐
│ ← 报告管理 📥 │ ← 搜索按钮
├─────────────────────────────┤
│ [全部] [菌群] [体检] [DAN] [保单] [周报] │ ← 类型Tab
├─────────────────────────────┤
│ 🔍 搜索报告标题或成员名称... │ ← 搜索栏
├─────────────────────────────┤
│ ┌─────────────────────────┐ │
│ │ 菌群检测 2025-03 │ │
│ │ 小李的菌群检测报告 │ │
│ │ 综合评分: 78分 │ │ → 点击查看详情
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ DAN测评 2025-02 │ │
│ │ 小王的心智测评 │ │
│ │ 综合评分: 82分 │ │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ 保单 2024-01 │ │
│ │ 重疾险 - 中国平安 │ │ → 点击查看详情
│ │ 保额: ¥500,000 │ │
│ └─────────────────────────┘ │
└─────────────────────────────┘
```
**点击卡片后的操作菜单**(右滑/长按):
- 📄 查看详情(跳转到对应详情页)
- 📥 下载报告(有文件时显示)
- 🔄 转发报告(小程序分享能力)
- 🗑 删除报告(仅结构化数据,标记废弃)
**底部Tab**(可选):
- 分页加载(上拉加载更多)
#### 报告详情统一视图
新建 `pages/profile/report-detail.vue`,根据 `hasFile` 和 `detailRoute` 决定展示:
- `hasFile=1` → 显示PDF预览/下载按钮 + 报告摘要
- `hasFile=0` → 显示结构化数据详情(如保单明细)
- 始终提供「转发」按钮(小程序 `onShareAppMessage`)
### 5.2 Web管理端
#### 入口
在 `cfc-web/src/views/Layout.vue` 左侧菜单新增「报告管理」分组:
- `/report-management` — "报告管理"(perm: `report:management`)
#### 页面:`cfc-web/src/views/admin/ReportManagement.vue`
**页面结构**:Element UI表格 + 筛选 + 分页
| 字段 | 列 |
|------|-----|
| 报告ID | id |
| 报告类型 | 菌群/体检/DAN/保单/周报 (tag) |
| 标题 | title |
| 报告主体 | subjectName |
| 报告日期 | reportDate |
| 综合评分 | overallScore (有条件色) |
| 状态 | status (tag) |
| 操作 | 查看/下载/同步 |
**顶部筛选**:
- 类型下拉:全部/菌群/体检/DAN/保单/周报
- 关键词搜索:标题/主体名称
- 日期范围选择器
- "同步数据"按钮(触发手动同步)
**API**:
- 列表:`POST /api/report-summary/admin-list`(分页查询,管理员可见所有用户)
- 同步:`POST /api/admin/report-summary/sync`
- 详情:`POST /api/report-summary/detail/{id}`
- 下载:`POST /api/report-summary/download/{id}`
---
## 6. 安全与权限
### 6.1 小程序端权限
- 只能查看自己(`user_id`)的报告
- 家长可查看家庭成员的报告(通过 `family_id` 关联)
- 报告转发:使用小程序 `onShareAppMessage`,生成分享卡片
### 6.2 Web管理端权限
- `admin` 角色可查看全平台所有报告
- `teacher` 角色可查看自己负责家庭的报告(通过 `family_id` 过滤)
---
## 7. 小程序转发/分享能力
利用微信小程序开放能力:
```js
// report-detail.vue 中
onShareAppMessage() {
return {
title: this.reportTitle,
path: '/pages/profile/report-detail?id=' + this.reportId,
imageUrl: '/static/share-report.png' // 分享封面图
}
}
```
对于有文件的报告,下载后使用 `wx.shareFileMessage` 转发文件。
---
## 8. 新增/修改文件清单
### 后端(cfc-backend)
| 文件 | 操作 | 说明 |
|------|------|------|
| `entity/ReportSummary.java` | 新建 | 汇总表实体 |
| `mapper/ReportSummaryMapper.java` | 新建 | MyBatis Mapper |
| `mapper/ReportSummaryMapper.xml` | 新建 | XML映射 |
| `service/ReportSummaryService.java` | 新建 | 同步+查询逻辑 |
| `controller/ReportSummaryController.java` | 新建 | 前端API |
| `controller/admin/ReportSummaryAdminController.java` | 新建 | 管理端API |
| `task/ReportSummarySyncTask.java` | 新建 | 定时同步任务 |
| `config/DatabaseInitializer.java` | 修改 | 添加建表迁移 |
| `resources/schema.sql` | 修改 | 追加建表语句 |
| `service/HealthReportService.java` | 修改 | 末尾加sync调用 |
| `service/DanReportUploadService.java` | 修改 | 末尾加sync调用 |
| `service/InsurancePolicyService.java` | 修改 | 末尾加sync调用 |
| `entity/FamilyFortuneReport.java` 相关Service | 修改 | 末尾加sync调用 |
### 前端小程序(cfc-frontend)
| 文件 | 操作 | 说明 |
|------|------|------|
| `pages/profile/components/ProfileMenu.vue` | 修改 | 添加「报告管理」菜单项 |
| `pages/profile/report-management.vue` | 新建 | 报告管理主页面 |
| `pages/profile/report-detail.vue` | 新建 | 统一报告详情视图 |
| `utils/api.js` | 修改 | 添加报告管理API函数 |
| `pages.json` | 修改 | 注册新页面 |
### Web管理端(cfc-web)
| 文件 | 操作 | 说明 |
|------|------|------|
| `src/views/admin/ReportManagement.vue` | 新建 | 管理端报告管理页面 |
| `src/views/Layout.vue` | 修改 | 添加菜单入口 |
| `src/router/index.js` | 修改 | 添加路由 |
---
## 9. 设计约束
- 遵循项目统一 `@PostMapping` 规范
- 响应统一 `Result` 包装
- 小程序禁用可选链 `?.`,禁用 CSS Grid
- Vue 2 Options API
- 汇总表与源表通过 `source_table + source_id` 唯一关联
- 定时任务使用 Spring `@Scheduled`
- 定时同步对源表只做 READ,不修改源表数据