8.8 KiB
桌面问诊列表:视频固定列布局审查
核对日期:2026-10-17。仅审查本地源码并运行离屏几何探针,未连接生产服务或使用 UI 账号;未修改业务代码和现有测试。已有 dirty 文件均保留。
规范与范围
- 已读取仓库根目录
AGENTS.md;本地.trellis/不存在,无法读取其 workflow/spec。app/下未发现额外AGENTS.md。 - 对照
app/research/diagnosis_index_visual_spec.md:固定双表共享行高及垂直滚动,文本换行受实际列宽、字体和 DPR 影响。该文件记录的是早期后台复刻参考,当前桌面问诊列表的尺寸以实现和测试为准。 - 任务提及的
_call_cell在当前仓库不存在。实际入口为DiagnosisTableHost._video_widget(),位于app/src/doctor_workstation/ui/diagnosis_index_widgets.py:1786(以下行号为审查时快照;并行修复可能改变行号)。
根因
这是单元格内部可用宽度和标签高度协商不一致,不能单纯归因为行高不足或固定表滚动错位。
- 问诊列表采用 tech-blue 固定列宽
92 + 158 = 250px,视频列声明宽度为 92px;见_BLUE_COLUMN_WIDTHS和DiagnosisTableHost.__init__()。 app/src/doctor_workstation/ui/theme.py:701-703的全局QTableWidget::item, QTableView::item { padding: 6px 8px; ... }仍作用于固定表。Qt 为 index widget 分配的是 item 内容区,所以声明 92px 的视频列实际只给_FixedCell76px 宽。_video_widget()内又有左右各 3px、上下各 2px 的布局边距,标签实际宽度进一步降至 70px。- 视频布局整体设置
QHBoxLayout.setAlignment(AlignCenter),状态标签同时setWordWrap(True)。布局把标签约束到其sizeHint()高度,但受限宽度下实际heightForWidth()更高,产生标签内部上下裁切;QLabel.AlignCenter使文字在不足的高度中居中,更容易出现首尾被切掉的观感。 _sync_row_heights()只根据前十列/挂号内容计算行高,不考虑视频列。它是未来异常长文案的潜在缺口,但在本次现有服务端文案中,即使整行已有足够空间,标签也仍会被裁切。单纯增高整行无法修复标签高度仍被限制的问题。
本地实测
环境:Windows、PySide6 6.11.1、QT_QPA_PLATFORM=offscreen,调用真实 apply_theme(),tech-blue DiagnosisTableHost,Microsoft YaHei UI 13px。使用已预约视频诊单、只读视频状态、挂号含最近渠道,使行高为 82px。
| 状态文案 | 表格 visualRect | index widget | 标签尺寸 | 标签实际所需高度 |
|---|---|---|---|---|
视频已结束 · 10-17 13:30 |
92 × 82 | 76 × 69,偏移 (8,6) | 70 × 33 | 50 |
上次通话已取消 |
92 × 82 | 76 × 69 | 70 × 16 | 33 |
上次通话未接听 |
92 × 82 | 76 × 69 | 70 × 16 | 33 |
因此 82px 行内仍有空间,但结束状态缺 17px 标签高度。未应用全局主题的独立 host 不会受到这层 item padding,结束状态可能恰好不裁切,容易给出错误的验证结论。
另一个无主题探针也确认:即便不受全局 padding 影响,整体 AlignCenter 仍能使长标签在宽度收缩后维持错误高度。例如含较长日期/标点的结束文案,标签 86 × 50,而 heightForWidth(86) 为 67;取消/未接听文案标签 86 × 16,实际需要 33。取消布局整体对齐后,82px 行中的标签可填满 86 × 78,已有文案能正常放下。
最小修复建议
- 在固定表自己的局部 QSS 中清除
::itempadding,让固定列的 index widget 使用真实列宽。范围应限定#DiagnosisFixedTable,无需改变全局主题或临床数据主表的绘制规则。固定表两列均为 index widget,可同时避免操作按钮被额外压缩。 - 去掉视频
QHBoxLayout的整体AlignCenter,让只读QLabel填充布局可用宽高。继续保留标签自己的AlignCenter、wordWrap=True;交互按钮使用单个布局项对齐来保持居中。 - 对服务端
状态 · 时间文案,将这一明确分隔符变成展示换行,形成状态与时间层次;不要按任意字符截断,也不要丢弃时间。标签 tooltip 保存完整原文,便于查看完整时间或未来较长说明。 - 保留现有状态、角色/权限、点击流程、列宽和双表行高同步规则。无须为本次已知文案扩大固定区域、改全局字号或统一增高所有行。
是否需要动态行高
服务端 server/app/adminapi/lists/tcm/DiagnosisLists.php:500-540 的完整现有状态契约为:
| state | 服务端 label / 桌面公开展示 |
|---|---|
| none | 空 label / — |
| live | 视频通话进行中 / 桌面简写 通话中 |
| pending_room | 通话发起中,待同步房间 / 桌面简写 接通中,可进入但等待的医助路径为 等待接通 |
| ended | 视频已结束 · m-d H:i,无 end_time 时仅 视频已结束 |
| missed | 上次通话未接听 |
| cancelled | 上次通话已取消 |
清除外层 item padding 后,92px 列减去内部 6px 边距,标签宽 86px;已知最长状态最多需要约 33px(两行),tech-blue 当前最小 72px 行可提供 68px 标签高度。因此当前契约无需增加动态行高。
截图叙述中的完整年份/秒并非当前服务端 m-d H:i 的固定格式;它可以作为兼容性测试数据,但不应据此修改后端时间格式。对于未来任意超长 label,如果产品要求完整正文始终可见,则需让状态展示与高度测量复用同一文本和实际宽度,并把其高度纳入双表 max(...) 行高;这属于额外防御,不是修复已知状态裁切的前提。
现有测试缺口
app/tests/test_diagnosis_index_visual.py:92-94的 QApplication fixture 没有调用apply_theme(),无法复现全局 item padding。test_dedicated_model_fixed_columns_selection_and_sort验证 92/158px 和 250px 的声明尺寸,未验证 index widget 真正得到的矩形。test_frozen_rows_track_main_pixel_scroll_and_host_height_is_append_stable已覆盖双表像素滚动和行顶对齐,但不检查标签内部几何。test_required_reference_artifacts_exist只验证参考图片存在和尺寸,没有检查状态字形或标签是否裁切。app/tests/test_consultations_parity_ui.py的角色/权限回归只断言按钮、标签文字和信号。医生只读状态矩阵目前覆盖 live/pending_room/none,未覆盖 ended/missed/cancelled 及实际显示尺寸。app/scripts/render_diagnosis_visual.py:72的常规样本使用字符串video_call_hint,并没有ended + label + end_time的真实复合数据;已有artifacts/doctor-video-column.png也只展示三个短状态。
应覆盖的几何场景
| 场景 | 必要断言 |
|---|---|
| 真实全局主题 + ConsultationsPage + 82px 行 | 视频列 index widget 的宽度等于对应 visualRect 的宽度;不能只断言 columnWidth=92 |
| 最小 72px 行、不含最近渠道 | 状态标签处于单元格内部;label.height() >= label.heightForWidth(label.width()),并核对 contentsRect/文本所需高度 |
| ended 当前完整 label、无时间 label | 状态与时间均可读;全文 tooltip 保留;不生成可点击视频入口 |
| missed/cancelled 七字 label | 不因单行 sizeHint 只有 16px 而裁切两行正文 |
| full-year/seconds、显式换行、未知较长 label | 不出现横向越界;已承诺完整显示的文本高度满足要求,其余由全文 tooltip 可访问 |
| live/pending/none、未挂号、已完成、医助等待状态 | 原有状态及按钮规则保持;短文案仍居中 |
| 合法医助 live 按钮、医生 text 按钮、操作列 | 按钮完整可见且居中,操作项不被新布局拉伸或挤出 |
| 多预约/长挂号产生的较高行 | 标签居中不裁切;主表和固定表 rowHeight 仍相等 |
| 1024×640、1280×800、1366×768、1920×1080 | 窗口改变不压缩固定列;横向滚动条显隐前后视频 index widget 与 visualRect 一致 |
| 双表垂直滚动到中段/末尾 | 两表 viewport 和行顶保持同步;行本身被 viewport 正常截断应与标签内部错误裁切区分 |
| 100% / 125% / 150% 缩放及实际加载字体 | 使用真实主题与字体测量,允许平台像素取整误差,不能依赖单一截图像素数 |
| 静默相同刷新与状态变化刷新 | 同样数据继续保留既有 fixed widget 的零重建预算;变化后的标签按新文本重新布局 |
如果修改共享 _video_widget(),可再补 legacy 96px 视频列与 54px 最小行的轻量几何检查,避免布局更改引入其他页面回归;当前问诊列表验收以 tech-blue 92px 固定列为主。
本轮未运行既有 pytest,也未修改现有测试;以上数值来自离屏几何探针,不是生产客户端验收结果。