docs(tasks): add T-582/T-583 fix dialogs rendering half (hidpi+helper, import worker)
T-582:弹窗偶发半截修复——启动设 HiDPI 舍入策略 + 统一弹窗 helper (通知类延迟到重绘冲刷后再 exec 治绘制竞态、几何钳制到可用屏幕治出屏), 先覆盖采集导入链路。T-583(治本):①Excel 导入改 worker 线程,消除 GUI 阻塞与重绘积压,复用 CollectWorker 范式。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
87fcda47e7
commit
5ac18ccc1c
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: T-582
|
||||
title: 修复弹窗偶发只显示一半:HiDPI 舍入策略 + 统一弹窗 helper(延迟冲刷 + 屏幕钳制)
|
||||
phase: 7
|
||||
deps: []
|
||||
status: TODO
|
||||
created: 2026-07-10
|
||||
---
|
||||
|
||||
## 问题 / 背景
|
||||
|
||||
用户反馈:部分弹窗偶发只显示一半(例:①采集导入 Excel 后的弹窗)。「偶尔」这一特征本身排除了纯 HiDPI 缩放(那会稳定半截),指向**绘制/几何时序竞态**。
|
||||
|
||||
已核代码事实:
|
||||
- ①导入是在 GUI 线程**同步**跑(`collect.py:242` `excel.import_tasks(...)`,非 worker),解析+入库期间事件循环阻塞;返回后紧接 `refresh_tasks()`(整表重绘)+ `_set_status()`,**再**弹框——模态框在积压重绘未冲刷时被 `exec()`,首帧可能几何/内容没算稳(偶发半截)。
|
||||
- 应用启动**未设任何 HiDPI 缩放策略**(`app/gui/__init__.py:125` 裸 `QApplication(sys.argv)`)——分数缩放(125/150%)下放大上述问题。
|
||||
- 弹窗**无屏幕边界钳制**:只有主窗口在 `main_window.py:66-71` 用 `move()` 钳到 `availableGeometry`,`QMessageBox` 靠近屏幕边缘/多显示器时下半截会被切。
|
||||
|
||||
根因排序:① 绘制竞态(最可能,吻合「偶尔」);② 弹窗跑出屏幕被切(次可能);③ 缺 HiDPI 舍入策略(放大器,非主因)。本任务修 ①②③ 中低风险的 HiDPI 策略 + 统一 helper(延迟冲刷治①、屏幕钳制治②、舍入策略治③);导入 worker 化(治本的另一半)见 T-583。
|
||||
|
||||
## 方案(改哪个文件、改成什么)
|
||||
|
||||
### 1. 启动加 HiDPI 舍入策略(`app/gui/__init__.py`)
|
||||
- 在 `QApplication` **创建之前**设 `QGuiApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)`。
|
||||
- 放在 `main()` 里 `app = QApplication.instance() or QApplication(sys.argv)` 之前;`QApplication.instance()` 已存在时(测试/复用)跳过设置避免告警。
|
||||
|
||||
### 2. 统一弹窗 helper:延迟一拍 + 屏幕钳制
|
||||
- 新增共享 helper(放 `app/gui/widgets.py` 或合适模块),如 `show_message(parent, icon, title, text, buttons=...) -> QMessageBox.StandardButton`:
|
||||
- 构造 `QMessageBox` 后,`box.show()` → 用 `QApplication.processEvents()` 或 `box.adjustSize()` 让尺寸算稳,再把窗口 `move()` **钳制到 `parent` 所在屏幕的 `availableGeometry`**(复用 `main_window.py:66-71` 同款:居中后夹到 max_x/max_y,防下半/右半出屏),最后 `exec()`。
|
||||
- 对"弹框紧跟重活"的场景,用 `QTimer.singleShot(0, ...)` 把 `exec()` 推迟到当前事件循环把积压重绘冲刷完之后(治绘制竞态)。实现时确保 helper 在需要返回用户选择的场景仍能同步拿到结果(question/确认类),或为「仅通知类」(information/warning) 与「需返回值类」(question) 分别提供入口。
|
||||
- 把 ①②③ 里高频、且出现在"重活之后"的通知类弹窗改走 helper:至少 `collect.py` 导入相关(`_show_error`、账号未就绪 warning、导入结果框)先接入;其余 `QMessageBox.information/warning` 逐步收敛(本任务先覆盖采集导入链路 + 提供 helper,不强制一次性全量替换)。
|
||||
|
||||
### 边界内的取舍
|
||||
- question/确认类弹窗需要同步返回值,`QTimer.singleShot(0)` 延迟不适用——这类只做「屏幕钳制 + adjustSize」,不做延迟 exec;延迟冲刷只用于不需返回值的通知类。实现时区分清楚。
|
||||
|
||||
## 验收要点
|
||||
- 启动设置了 `HighDpiScaleFactorRoundingPolicy.PassThrough`,且在 QApplication 创建前(单测可断言调用顺序,或至少不回归启动)。
|
||||
- helper 存在并被采集导入链路的通知类弹窗使用;helper 会把弹窗几何钳制进父窗口所在屏幕 `availableGeometry`(单测:给一个会越界的父窗口位置,断言 helper 计算出的目标坐标被夹在可用区域内)。
|
||||
- 通知类弹窗经 helper 延迟到重绘冲刷后再 exec(单测可 mock `QTimer.singleShot`/`processEvents` 断言被调用;不真正弹窗)。
|
||||
- question/确认类仍同步返回用户选择、行为不回归(如③开始更新确认、重置确认)。
|
||||
- 现有走 `QMessageBox` 的测试不回归。
|
||||
- 验证命令(unittest,不引入 pytest;GUI 测试用 offscreen):
|
||||
- `py -3.10 -m unittest tests.test_gui`
|
||||
- `python -m ruff check app tests main.py`
|
||||
- `py -3.10 -m compileall app main.py`
|
||||
- `py -3.10 -m unittest discover -s tests`
|
||||
- `git diff --check`
|
||||
|
||||
## 边界(不改什么)
|
||||
- 不把①导入改成 worker 线程(治本另一半在 T-583)。
|
||||
- 不改弹窗文案/业务语义、不改主窗口尺寸逻辑。
|
||||
- 不强制一次性替换全部 `QMessageBox` 调用点(先提供 helper + 覆盖采集导入链路)。
|
||||
- 不改 CDP/DB/AI/cmhub/Excel 解析逻辑。
|
||||
|
||||
## 执行记录
|
||||
|
||||
(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)
|
||||
Reference in New Issue
Block a user