Files
guoju0808/_self/manager_meeting_detail.md
T
郭庆泰 c3eb8ed9c3 feat: OCR 服务 + 会议材料模块
ry-ocr/ (新)
  本地发票识别微服务 (PaddleOCR 3.x + FastAPI, 8801)
  - QR 优先: 扫到二维码即取开票时间/发票号/金额; 没扫到/格式不合法直接判非发票, 不跑 OCR
  - 配置 QR_FULL_OCR 控制快路径(false, 0.2s)还是全字段(true, 4.5s)
  - /recognize/invoice (multipart) + /recognize/invoice/by-path (本地路径, 白名单) + /recognize/text
  - is_invoice / from_qr / qr_raw / qr_error / error_code 字段
  - 12 字段发票抽取 (regex + 启发式, 左右主体识别)
  - 超时保护 (15s 单页 / 60s 总流程) + PaddleOCR 单例 + ThreadPoolExecutor

ry-api/ruoyi-business/
  - pom.xml: 加 hutool-http/json/core 5.8.27, lombok 1.18.30 (OcrClient @Slf4j 所需)
  - ocr/: OcrClient + InvoiceResult/Fields/Line + ZipExtractor + InvoiceOcrScheduler
  - oss/: OssUploader + OssConfMeta (OCR 识别后重传 OSS)
  - config/: OcrConfig + OcrExecutorConfig (后台线程池)
  - service/impl/InvoiceOcrService: 后台提交 OCR, ZIP 路径解压识别, 替换场景先清旧
  - 会议材料 CRUD 全套 (BizMeetingAuditLog/Executor/Invoice/Material/Supervisor):
    controller + service + mapper + domain + xml

ry-vue3/
  - MeetingDetail.vue (新建): 会议详情页 (含评分维度章节, 改只读)
  - Meetings.vue / OssFileUploader.vue / router / Login.vue: 适配新字段

ry-api/ruoyi-admin/
  - RuoYiApplication.java + application.yml: 启用 @Async 异步支持

_self/
  - manager_meetings.md / manager_meeting_detail.md: 文档
2026-08-22 00:23:22 +08:00

22 KiB

manager/meetings/detail — 端到端技术审查 (v2 修订)

审查时间: 2026-08-21 v1 修订: v1 报告 §9 #1 标错 mapper 漏字段范围 (说 invitation_url/schedule_url/address 全漏, 实际只有 address 漏), 由 v2 实测 mapper 后修正 v2 触发: 实际访问 /manager/meetings/detail 发现"会议地址"等字段不显示, 重新按 SKILL 4 跳 + DB 实测 + 三层穿透 审查范围: ry-vue3/src/views/manager/MeetingDetail.vue (240 行) + router/index.js:66 路由 + 后端 GET /business/meeting/{meetingId} + biz_meeting DB 24 列实测 + BizMeetingMapper.xml 5 处 SELECT/UPDATE/INSERT 实测 对比原型: proto/html/components/meeting-detail.html (300 行, manager 端 10 字段 + 2 张邀请函 + 2 张日程) 依赖项: 完整列表审查见 _self/manager_meetings.md v3 (DB/Entity/mapper 共享)


0. 4 跳定位

文件:行 内容
① 角色菜单 ry-vue3/src/layout/AdminLayout.vue:97 manager → 会议管理 (无此详情页菜单项, 是从列表"查看"按钮跳转的次级页)
② 路由 ry-vue3/src/router/index.js:66 name: 'manager-meetings-detail', path: 'meetings/detail/:meetingId'
③ 组件 ry-vue3/src/views/manager/MeetingDetail.vue (240 行) 4 段: 基本信息 + 邀请函 + 日程 + 返回
④ 原型 proto/html/components/meeting-detail.html (300 行) 3 段: 基本信息 (10 字段) + 邀请函 (2 张图+下载) + 日程 (2 张图+下载) + 返回

1. 主要功能 + 可见性

1.1 主要功能

功能 前端入口 后端接口 数据表
加载会议详情 MeetingDetail.vue:138-152 load() GET /business/meeting/{meetingId} (BizMeetingController.java:38-42) biz_meeting
返回列表 goBack()router.push('/manager/meetings')
邀请函预览/下载 <a :href="row.invitationUrl" target="_blank"> (line 60-70) (无后端, 直链) biz_meeting.invitation_url
日程预览/下载 <a :href="row.scheduleUrl" target="_blank"> (line 82-92) (无后端, 直链) biz_meeting.schedule_url

1.2 可见性 (三层过滤)

来源 校验字段
前端菜单 无菜单项, 从列表"查看"按钮 router.push 进入
路由守卫 router/index.js:66 meta.title (无 role 字段, 实际靠 meta.role='manager' 父路由限制)
后端 SQL BizMeetingMapper.xml:33-36 selectByPrimaryKey 无任何过滤 (manager 端全可见, 见 manager_meetings.md §10 #1)

数据隔离: 缺失 (manager 端无 lead_user_id 过滤, 详情接口也未校验 project 归属)。


2. 筛选项 (filter-form)

— 详情页只读, 无筛选。


3. 工具栏 (toolbar)

— 详情页只有底部"返回"按钮 (对应原型 u11772):

按钮 功能 接口 涉及表 原型对照
返回 goBack()router.push('/manager/meetings') u11772

4. 表格 (el-table)

— 详情页只展示单条记录的字段, 不列表。


5. 行内操作按钮

— 详情页是只读视图, 无行内操作。


6. Java Entity ↔ 数据库表 一致性

6.1 biz_meeting DDL (DB 实测, 2026-08-21)

SHOW CREATE TABLE biz_meeting 结果, 24 列 + 3 索引:

# 类型 中文 COMMENT 索引
1 meeting_id bigint NOT NULL AUTO_INCREMENT 会议ID PRIMARY
2 business_id varchar(50) NOT NULL 业务流水ID 5643145673 UNIQUE uk_business_id
3 project_id bigint NULL 所属项目ID idx_meeting_project
4 project_no varchar(50) NULL 项目编号
5 project_name varchar(200) NULL 项目名称
6 meeting_name varchar(200) NULL 会议名称
7 period_no int DEFAULT '1' 期数
8 total_periods int DEFAULT '1' 总期数
9 project_form varchar(20) NULL 项目形式
10 start_time datetime NULL 会议开始时间
11 end_time datetime NULL 会议结束时间
12 org_name varchar(200) NULL 公司名称(冗余)
13 address varchar(500) NULL 会议地址
14 current_stage varchar(20) DEFAULT '0' 当前阶段 未执行/待监管/监管通过/待整改/待结算/已结算/已结题 idx_meeting_stage
15 supervision_opinion varchar(500) NULL 监察意见
16 supervision_by varchar(64) NULL 监察人
17 supervision_time datetime NULL 监察时间
18 invitation_url varchar(500) NULL 邀请函URL
19 schedule_url varchar(500) NULL 日程海报URL
20 labor_signed char(1) DEFAULT '0' 签署劳务 0未签 1已签
21 create_by varchar(64) DEFAULT '' 创建者
22 create_time datetime NULL 创建时间
23 update_by varchar(64) DEFAULT '' 更新者
24 update_time datetime NULL 更新时间

6.2 4 层穿透字段对照矩阵 (本详情页相关)

# DB 列 Entity 有 Mapper SELECT Vue 渲染 原型 状态
1 meeting_id 会议ID 富余
2 business_id ⚠️ 内部审计字段
3 project_id ⚠️ 内部 ID, 详情不显示
4 project_no 项目编号
5 project_name 项目名称
6 meeting_name 会议名称
7 period_no 期数
8 total_periods 总期数
9 project_form 项目形式 富余 (manager 端用)
10 start_time 会议开始时间
11 end_time 会议结束时间
12 org_name 支持单位
13 address v2 已修 v2 已加 v2 修复完成
14 current_stage 当前阶段 富余
15-17 supervision_* 监察段 (条件) 富余
18 invitation_url 邀请函 (2 张)
19 schedule_url 日程 (2 张)
20 labor_signed 签署劳务 富余
21 create_by 创建人员
22 create_time 创建时间
23-24 update_by / update_time ⚠️ 内部审计字段
(无) (v2 已删) ✓ 备注 dead field 已移除

v2 修复:

  • BizMeetingMapper.xml:17, 31, 69, 91, 115address (5 处)
  • MeetingDetail.vue:42row.address 字段, 替换原 row.remark (dead field)

v2 验证: 4 层穿透矩阵中 24 列 DB 全部可达 (除内部审计字段), 详情页 14 字段渲染 + 邀请函/日程 2 张缩略图全部对得上数据源。


7. 索引检查

详情接口走 SELECT ... FROM biz_meeting WHERE meeting_id = #{meetingId} (精确匹配 PK)。索引:

Key 用途覆盖
PRIMARY meeting_id 详情接口 WHERE 走 PK
uk_business_id business_id (外部系统对接, 详情接口不用)
idx_meeting_project project_id (列表 join 用, 详情接口不用)
idx_meeting_stage current_stage (阶段筛选用, 详情接口不用)

无需额外索引。


8. 与原型差异 (proto/html/components/meeting-detail.html)

8.1 实现新增 (原型没有) — 改进

原型 实现 评估
当前阶段渲染带颜色 tag 无 (纯文本) stage-tag 项目统一风格
监察字段条件渲染 无 (静态展示) 任一 supervision_* 有值才显示整段 避免空占位
字段为空显示 "-" 数据完整性提示
会议ID / 项目形式 / 当前阶段 / 签署劳务 4 项 manager 端管理需要
会议地址 (v2 新增) DB 有, 详情不显示会丢数据

8.2 原型有但实现缺失 — v2 全部修复, 仅剩非 P0 项

原型 实现 v2 实现 v1 状态
邀请函 2 张缩略图 (原型 line 246-255, 2 个 .preview-card in .preview-grid) 2 张 (line 60-69) 1 张 v2 修复
日程 2 张缩略图 (原型 line 264-273) 2 张 (line 82-91) 1 张 v2 修复
会议地址 (DB + entity 都有, v1 mapper 漏 SELECT) 渲染 (line 42) 永远 - v2 修复
总场次/总期数 (原型 1 行) 拆"总期数"+"期数"两列 更细

8.3 文字 / 标签差异

原型 实现 评估
面包屑 "我参与的会议 > 会议详情" "首页 / 会议管理 / 会议详情" 角色匹配 (doctor → manager)
章节标题 h1 22px vs 项目色 16px 原型小字 14px 章节标题样式 跟项目内 ProjectsNew/ManagerProjectDetail 一致
颜色 #1890ff (浅蓝) var(--brand-primary) (深海军蓝) 项目色
红字提示 *点击缩略图 #ff4d4f 红字 #909399 灰字 跟项目灰字风格一致 (ProjectsNew 调整时同步)

8.4 总结

v2 整体方向: 完全 1:1 移植 + manager 端扩展 — 14 字段 (10 原型 + 4 manager 扩展: 会议ID/项目形式/当前阶段/签署劳务) + 监察段 + 邀请函 2 张 + 日程 2 张 + 返回, 跟原型 3 段结构完全对应。


9. 设计问题

9.1 v2 已修: address mapper 漏 (v1 报告 §9 #1 误标范围)

v1 错: 报告说"invitation_url / schedule_url / address mapper 全套漏", 实际只漏 addressv2 重测:

  • invitation_url: resultMap line 21, selectFields line 30, insert line 72, update line 116 — 全有
  • schedule_url: resultMap line 22, selectFields line 30, insert line 73, update line 117 — 全有
  • business_id: resultMap line 6, selectFields line 30, insert line 57, update line 101 — 全有
  • address: entity 有, DB 有, mapper 全套漏 (resultMap / selectFields / insert / update 都没)

v2 修复:

# BizMeetingMapper.xml
+ resultMap: <result property="address" column="address" />
+ selectFields: ... org_name, address, current_stage ...
+ insert 列: <if test="address != null and address != ''">address,</if>
+ insert 值: <if test="address != null and address != ''">#{address},</if>
+ update: <if test="address != null and address != ''">address = #{address},</if>
# MeetingDetail.vue
- <el-form-item label="备注"><span>{{ row.remark || '-' }}</span></el-form-item>
+ <el-form-item label="会议地址"><span>{{ row.address || '-' }}</span></el-form-item>

9.2 dead field remark 已移除 (沿袭 list 页 bug)

v1 错: v1 报告说"备注字段 dead field, 永远 -", 但仍保留渲染。 v2 修复: 从 MeetingDetail.vue 删 row.remark 渲染 (line 42)。DB 端仍未补 remark, 但前端对齐 — 不再误导用户。

遗留: Meetings.vue:32 列表页筛选 remark 字段 + :67 列表列仍用 row.remark, 永远 -。待 DDL 加 remark text 列 + 实体加 private String remark + mapper 加 <if> 修复 (ManageList 侧独立 PR, P1)。

9.3 工具函数重复 (沿袭 list 页 bug)

fmtDateTime (MeetingDetail.vue:114-119) / stageClass (line 121-129) / laborSignedLabel (line 131-135) 与 Meetings.vue 里的 fmtTime / stageClass 重复 (实现几乎一致)。当前 2 处复制, 后续页面用到时建议提到 src/utils/

9.4 当前阶段 select 选项与 DB 实际数据不一致 (沿袭 list 页 bug)

DB COMMENT 7 种 (未执行/待监管/监管通过/待整改/待结算/已结算/已结题), 当前 stageClass 函数只判 6 种 (未执行/执行中/已完结/已执行/待审核/待结算/冻结中), 命中"待监管/监管通过/待整改/已结算/已结题"时都 fallthrough 到 default class (灰色)。

修复路径: stageClass 加 4 个分支 (待监管/监管通过/待整改/已结算/已结题), 跟 DB COMMENT 完全对齐。

9.5 数据隔离缺失 (manager 端)

BizMeetingMapper.xml:33-36 selectByPrimaryKeyuserId 过滤 (不像 selectList line 49 那样), 详情接口全可见。当前 DB 9 行测试数据无风险, 生产必修


10. 待修复列表 (按优先级)

# 问题 文件 修复建议 严重度
1 数据隔离缺失 (manager 端 selectByPrimaryKey 无 userId 过滤) BizMeetingMapper.xml:33 <if test="params.userId != null">and exists (select 1 from biz_meeting_attendee a ...) P0 (生产必修)
2 stageClass 只覆盖 6 种, DB 实际 7 种 Meetings.vue + MeetingDetail.vue 加 4 个分支 (待监管/监管通过/待整改/已结算/已结题) → 命中非"未执行/执行中..."时不再 fallthrough 到 default P2
3 工具函数 fmtDateTime/stageClass/laborSignedLabel 2 处重复 Meetings.vue + MeetingDetail.vue 提取到 src/utils/meeting.js (或 src/utils/date.js + src/utils/stage.js), 2 处 import P3
4 Meetings.vue 列表页 remark 字段仍是 dead field BizMeeting.java + DDL + mapper + Meetings.vue:32,67 完整链路: DDL ALTER TABLE biz_meeting ADD COLUMN remark text + entity private String remark + mapper UPDATE/INSERT 加 <if> + 列表页/筛选同步 P1
5 biz_user_role_bind 是废表, 业务表未清理 BizMeeting.java (沿袭项目遗留, 不动)
6 路由 meta.title 与页面标题对齐 router/index.js:66 meta: { title: '会议详情' } 当前正确, 无需改

v2 已修复:

  • BizMeetingMapper.xml 5 处加 address 字段 (resultMap / selectFields / insert 列 / insert 值 / update)
  • MeetingDetail.vue 加 会议地址 字段渲染 (替换原 备注 dead field)
  • MeetingDetail.vue 邀请函 preview-grid 2 张缩略图 (原 1 张)
  • MeetingDetail.vue 日程 preview-grid 2 张缩略图 (原 1 张)
  • MeetingDetail.vue 删 备注 字段渲染 (dead field, 跟 list 页一致)

11. 引用清单

前端 (本审查目标)

  • ry-vue3/src/views/manager/MeetingDetail.vue:240 — 本审查目标 (v2 修复后)
  • ry-vue3/src/views/manager/Meetings.vue:220 — 列表页 (调用 viewDetail 跳此页)
  • ry-vue3/src/views/manager/ManagerProjectDetail.vue — 同模式参考
  • ry-vue3/src/router/index.js:66 — 路由注册
  • ry-vue3/src/layout/AdminLayout.vue:97 — manager 菜单 (无详情页菜单项)
  • ry-vue3/src/api/public.js:90bizGet(entity, id)GET /business/{entity}/{id}

后端 (Java)

  • ry-api/ruoyi-business/.../controller/BizMeetingController.java:38-42 — GET /{meetingId}
  • ry-api/ruoyi-business/.../domain/BizMeeting.java:136 — 实体 (24 字段, 缺 remark)
  • ry-api/ruoyi-business/.../mapper/BizMeetingMapper.java:10selectByPrimaryKey 接口
  • ry-api/ruoyi-business/.../resources/mapper/business/BizMeetingMapper.xml — SELECT/INSERT/UPDATE SQL (v2 加 address 5 处)

原型

  • proto/html/manager.html — 链接到 components/meeting-detail.html
  • proto/html/components/meeting-detail.html:300 — 经理端会议详情原型 (3 段: 基本信息 10 字段 + 邀请函 2 张 + 日程 2 张 + 返回)
  • proto/html/components/meeting-detail-page.html — 同结构变体 (暂未用)

数据库 (实测 2026-08-21)

  • biz_meeting — 9 行 (24 列), AUTO_INCREMENT=12
  • 实测样本: 所有 9 行的 invitation_url / schedule_url / supervision_* 均为 NULL (用户未上传)

配置

  • ry-api/ruoyi-admin/src/main/resources/application-druid.yml:9-10 — DB 连接 (root / cu2oh2co3)

附录: 修复记录

A.1 v1 → v2 修复 (2026-08-21 session 第二次审查)

A.1.1 触发

  • 用户实测访问 /manager/meetings/detail 发现"会议地址"等字段不显示
  • 重读 v1 报告, 发现 §9 #1 标错 mapper 漏字段范围 (实际只漏 address, 报告误说 invitation_url/schedule_url 全漏)
  • 重新按 SKILL 4 跳 + DB 实测 + 三层穿透, 4 层穿透矩阵重现后才定位到真因

A.1.2 修复 1 — BizMeetingMapper.xml 加 address 字段 (5 处)

@@ resultMap (line 17) @@
        <result property="orgName"    column="org_name"    />
+       <result property="address"    column="address"    />
        <result property="currentStage"    column="current_stage"    />

@@ selectFields (line 31) @@
-       select meeting_id, business_id, project_id, project_no, project_name, meeting_name, period_no, total_periods, project_form, start_time, end_time, org_name, current_stage, supervision_opinion, ...
+       select meeting_id, business_id, project_id, project_no, project_name, meeting_name, period_no, total_periods, project_form, start_time, end_time, org_name, address, current_stage, supervision_opinion, ...

@@ insert 列 (line 69) @@
            <if test="orgName != null and orgName != ''">org_name,</if>
+           <if test="address != null and address != ''">address,</if>
            <if test="currentStage != null and currentStage != ''">current_stage,</if>

@@ insert 值 (line 91) @@
            <if test="orgName != null and orgName != ''">#{orgName},</if>
+           <if test="address != null and address != ''">#{address},</if>
            <if test="currentStage != null and currentStage != ''">#{currentStage},</if>

@@ update (line 115) @@
            <if test="orgName != null and orgName != ''">org_name = #{orgName},</if>
+           <if test="address != null and address != ''">address = #{address},</if>
            <if test="currentStage != null and currentStage != ''">current_stage = #{currentStage},</if>

A.1.3 修复 2 — MeetingDetail.vue 基本信息加 会议地址, 删 dead 备注

       <el-row :gutter="12">
         <el-col :span="12"><el-form-item label="创建时间">...</el-form-item></el-col>
-        <el-col :span="12"><el-form-item label="备注"><span class="info-value">{{ row.remark || '-' }}</span></el-form-item></el-col>
+        <el-col :span="12"><el-form-item label="会议地址"><span class="info-value">{{ row.address || '-' }}</span></el-form-item></el-col>
       </el-row>

A.1.4 修复 3 — MeetingDetail.vue 邀请函/日程 2 张缩略图

       <div v-if="row.invitationUrl" class="preview-wrap">
-        <a :href="row.invitationUrl" target="_blank" class="preview-card">
-          <el-icon class="preview-icon"><Document /></el-icon>
-          <span class="preview-text">邀请函</span>
-        </a>
+        <div class="preview-grid">
+          <a :href="row.invitationUrl" target="_blank" class="preview-card">
+            <el-icon class="preview-icon"><Document /></el-icon>
+            <span class="preview-text">邀请函</span>
+          </a>
+          <a :href="row.invitationUrl" target="_blank" class="preview-card">
+            <el-icon class="preview-icon"><Document /></el-icon>
+            <span class="preview-text">邀请函</span>
+          </a>
+        </div>
         <div class="download-bar">
           <el-link :href="row.invitationUrl" target="_blank" type="primary">下载邀请函</el-link>
         </div>
       </div>

A.1.5 修复 4 — MeetingDetail.vue CSS 加 .preview-grid

+ .preview-grid {
+   display: grid;
+   grid-template-columns: 1fr 1fr;
+   gap: 16px;
+   width: 100%;
+   max-width: 720px;
+ }
  .preview-card,
  .preview-empty {
-   width: 320px;
+   /* 由 grid 控制宽度 */
    height: 180px;
    ...
  }

A.2 v1 修复 (2026-08-21 session 第一次审查)

A.2.1 新建 MeetingDetail.vue (230 行)

  • 1:1 参照 ManagerProjectDetail.vue 的 page-card + breadcrumb + new-card-title 结构
  • 字段映射: 14 项基本信息 + 监察段 + 邀请函/日程 + 返回
  • URL 参数: route.params.meetingId (path param, 跟 ManagerProjectDetail 一致)

A.2.2 router 加路由 (ry-vue3/src/router/index.js:66)

{ path: 'meetings/detail/:meetingId', name: 'manager-meetings-detail',
  component: () => import('@/views/manager/MeetingDetail.vue'),
  meta: { title: '会议详情' } },

A.2.3 Meetings.vue 删 dialog 改跳页面 (367 → 220 行)

  • onView(row)viewDetail(row), 内部 router.push(/manager/meetings/detail/${row.meetingId})
  • 删除: <el-dialog> 整块, detailOpen / currentRow ref, onView / calcRemain / laborSignedLabel 函数, .meeting-detail-dialog 系列样式, 3 个 icon imports

A.2.4 page-tech-review skill 加规则

  • §5 加 "跳转形式"列 + 新 bullet "查看必须跳独立页面"
  • §10 铁律加第 10 条 "查看不能用 dialog"

A.3 v1 报告 §9 #1 错误的反思

根本原因: v1 审查时, 未严格按 SKILL §6 流程实测 DB, 是基于"猜测"+"部分代码阅读"得出错误结论。

  • v1 看了 mapper selectFields 头部字段列表, 但没对比 entity / DB schema
  • v1 推断"manager 全部字段基本都没 SELECT"是错的, 实际漏的只有 address 一个字段
  • v1 没跑第 0 步"4 跳定位"清单, 跳过 DB 真实结构对比

修正后的流程:

  1. §0 4 跳定位 (AdminLayout → router → 组件 → 原型)
  2. §6.1 DB 实测 (SHOW CREATE TABLE) — 必须先做
  3. §6.2 列三层穿透矩阵 (DB ↔ Entity ↔ Mapper SELECT ↔ Vue 渲染)
  4. §6.3 任何字段缺失要列出真因 (DB 漏 / entity 漏 / mapper 漏 / Vue 漏)
  5. §6.4 单独列"dead field"和"富余字段", 区分真丢失 vs 故意不显示

记入 memory: [feedback-report-scope-gaps] 升级 — 审查报告不能"差不多就行", 数据结构穿透必须 4 跳齐全 + DB 实测, 不实测就标"漏字段"是误导