Files
zyt/artifacts/video-column-layout-review.md
T
2026-10-17 16:08:24 +08:00

88 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 桌面问诊列表:视频固定列布局审查
核对日期: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`(以下行号为审查时快照;并行修复可能改变行号)。
## 根因
这是单元格内部可用宽度和标签高度协商不一致,不能单纯归因为行高不足或固定表滚动错位。
1. 问诊列表采用 tech-blue 固定列宽 `92 + 158 = 250px`,视频列声明宽度为 92px;见 `_BLUE_COLUMN_WIDTHS` 和 `DiagnosisTableHost.__init__()`。
2. `app/src/doctor_workstation/ui/theme.py:701-703` 的全局 `QTableWidget::item, QTableView::item { padding: 6px 8px; ... }` 仍作用于固定表。Qt 为 index widget 分配的是 item 内容区,所以声明 92px 的视频列实际只给 `_FixedCell` 76px 宽。
3. `_video_widget()` 内又有左右各 3px、上下各 2px 的布局边距,标签实际宽度进一步降至 70px。
4. 视频布局整体设置 `QHBoxLayout.setAlignment(AlignCenter)`,状态标签同时 `setWordWrap(True)`。布局把标签约束到其 `sizeHint()` 高度,但受限宽度下实际 `heightForWidth()` 更高,产生标签内部上下裁切;`QLabel.AlignCenter` 使文字在不足的高度中居中,更容易出现首尾被切掉的观感。
5. `_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,已有文案能正常放下。
## 最小修复建议
1. 在固定表自己的局部 QSS 中清除 `::item` padding,让固定列的 index widget 使用真实列宽。范围应限定 `#DiagnosisFixedTable`,无需改变全局主题或临床数据主表的绘制规则。固定表两列均为 index widget,可同时避免操作按钮被额外压缩。
2. 去掉视频 `QHBoxLayout` 的整体 `AlignCenter`,让只读 `QLabel` 填充布局可用宽高。继续保留标签自己的 `AlignCenter`、`wordWrap=True`;交互按钮使用单个布局项对齐来保持居中。
3. 对服务端 `状态 · 时间` 文案,将这一明确分隔符变成展示换行,形成状态与时间层次;不要按任意字符截断,也不要丢弃时间。标签 tooltip 保存完整原文,便于查看完整时间或未来较长说明。
4. 保留现有状态、角色/权限、点击流程、列宽和双表行高同步规则。无须为本次已知文案扩大固定区域、改全局字号或统一增高所有行。
### 是否需要动态行高
服务端 `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,也未修改现有测试;以上数值来自离屏几何探针,不是生产客户端验收结果。