# 统一报告管理中心 — 设计规格 > 日期: 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,不修改源表数据