style: unify GUI button radius

This commit is contained in:
chengma
2026-07-07 09:21:19 +08:00
parent 6ed3933856
commit ad75afb8f4
7 changed files with 87 additions and 15 deletions
+1
View File
@@ -27,6 +27,7 @@ class MainWindow(QMainWindow):
)
self.setWindowTitle("cmshopee")
self.resize(1180, 760)
self.setStyleSheet(BUTTON_BASE_STYLE)
self._settings_tab_index = TAB_TITLES.index("⑤ 设置")
self._last_tab_index = 0
self._reverting_tab_change = False
+1 -5
View File
@@ -101,10 +101,7 @@ class ApplyTab(QWidget):
self.start_update_button.setObjectName("startUpdateButton")
self.start_update_button.setMinimumWidth(118)
self.start_update_button.setStyleSheet(
"QPushButton#startUpdateButton { "
"font-weight: 600; padding: 6px 16px; "
f"color: {COLOR_WARNING}; border: 1px solid {COLOR_WARNING}; "
"border-radius: 4px; }"
_warning_outline_button_style("startUpdateButton")
)
self.stop_update_button = QPushButton("停止")
self.reset_update_button = QPushButton("重置更新状态")
@@ -829,4 +826,3 @@ class ApplyTab(QWidget):
lines.append(str(error))
return "\n".join(lines)
+58 -4
View File
@@ -91,6 +91,34 @@ COLOR_PENDING = "#9a6700"
COLOR_MUTED = "#6e7781"
COLOR_WARNING = "#bc4c00"
COLOR_DANGER_BG = "#ffebe9"
BUTTON_RADIUS_PX = 4
BUTTON_BASE_STYLE = f"""
QPushButton {{
min-height: 24px;
min-width: 72px;
padding: 4px 10px;
border: 1px solid #d0d7de;
border-radius: {BUTTON_RADIUS_PX}px;
background-color: #f6f8fa;
color: #24292f;
}}
QPushButton:hover {{
background-color: #eef4ff;
border-color: #8c959f;
}}
QPushButton:pressed {{
background-color: #d8dee4;
border-color: #6e7781;
}}
QPushButton:focus {{
border-color: #0969da;
}}
QPushButton:disabled {{
background-color: #f6f8fa;
border-color: #d8dee4;
color: #8c959f;
}}
"""
@@ -171,12 +199,38 @@ def _danger_metric_text(text, active):
return f'<span style="color:{COLOR_DANGER}; font-weight:600;">{text}</span>'
def _danger_outline_button_style(object_name):
def _outline_button_style(object_name, color, hover_bg="#f6f8fa", pressed_bg="#f6f8fa"):
return (
f"QPushButton#{object_name} {{ "
f"color: {COLOR_DANGER}; border: 1px solid {COLOR_DANGER}; "
"font-weight: 600; padding: 3px 10px; border-radius: 4px; "
f"color: {color}; border-color: {color}; font-weight: 600; "
"}"
f"QPushButton#{object_name}:hover {{ "
f"background-color: {hover_bg}; border-color: {color}; "
"}"
f"QPushButton#{object_name}:pressed {{ "
f"background-color: {pressed_bg}; border-color: {color}; "
"}"
f"QPushButton#{object_name}:disabled {{ "
"color: #8c959f; border-color: #d8dee4; "
"}"
)
def _warning_outline_button_style(object_name):
return _outline_button_style(
object_name,
COLOR_WARNING,
hover_bg="#fff8f0",
pressed_bg="#ffedd5",
)
def _danger_outline_button_style(object_name):
return _outline_button_style(
object_name,
COLOR_DANGER,
hover_bg="#fff1f0",
pressed_bg=COLOR_DANGER_BG,
)
@@ -425,4 +479,4 @@ def _safe_write_diagnostic_log(
def _elapsed_ms(started):
return int((time.monotonic() - started) * 1000)
__all__ = [name for name in globals() if not name.startswith("__")]
__all__ = [name for name in globals() if not name.startswith("__")]
+1 -1
View File
@@ -135,7 +135,7 @@
| T-533 | ② 增量生成:按缺失组件补生成(先标题后封面分离) | T-303, T-520, T-509 | 现象:先关闭「生成封面图片」只生成了标题(`stage=generated/status=success`、`new_cover_path` 为空),之后勾选封面再点「开始生成」,提示「当前筛选结果没有待生成任务」,无法补生成封面;若改用「重置生成结果」会连标题一起清掉、重生成时白扣一次标题点并覆盖 T-509 手动改的标题。根因:`ai.is_generatable_task` 把 `generated+success` 一律判为不可生成,且 `generate_batch` 对每条 eligible 任务都先重生成标题再生成封面。方案:把「开始生成」改为**按缺失组件增量补齐**——① 判定放宽:`generated+success` 且(封面开关 ON 且 `new_cover_path` 为空)判为可生成(只补封面);② `generate_batch` 组件级跳过:`new_title` 已存在则**跳过标题生成**(复用现有标题,不再调 cmhub 生文、不扣标题点、**不覆盖用户手动标题**),封面开关 ON 且 `new_cover_path` 为空才生成封面;③ 进度计数分组件——`title_total`=缺标题任务数、`cover_total`=(封面开关 ON 时)缺封面任务数,不再用 `len(eligible)` 一刀切;④「没有待生成任务」提示只在**按当前设置真的无缺口**(标题全有 且(封面关 或 封面全有))时出现;⑤ 幂等/点数:全组件齐备的任务不纳入、不重复调用扣点。保持 `set_generated` 写库、run_logs、诊断日志、停止取消未开始项语义不变;不改 cmhub HTTP 协议、DB schema、Excel、Shopee/CDP 流程。单测覆盖:只补封面(跳过标题、标题点不增、手动标题不被覆盖)、混合批(部分缺标题+部分缺封面)、无缺口提示无任务、封面关时标题态任务不纳入 | DONE |
| T-534 | ② 重置增强:多选/筛选范围 + 按组件(标题/封面/全部)重置 | T-533, T-404a, T-509 | 现状:②「重置生成结果」只能对单条、且整条全清(`db.reset_generated` 同时清 `new_title` 和 `new_cover_path` 退回 collected)。真实诉求是精准重置——一批标题不满意但封面 OK(只重标题、保封面、不重扣封面点)、或封面差但标题 OK(只重封面、**保留 `new_title` 保护 T-509 手动标题**、不重扣标题点);且需要作用于多条。方案:与 T-533 的组件模型咬合——① 后端 `db.reset_generated` 加组件开关 `reset_title`/`reset_cover`(`delete_file` 仅在重置封面时可选),只清对应字段与 `last_error`,stage 保持 `generated`,由 T-533 泛化后的 `is_generatable_task`(缺任一必需组件即可生成)识别缺口后由「开始生成」只补被清组件;② UI 重置入口给三选项「重置标题 / 重置封面 / 重置全部」;③ 作用域从单条扩到**多选(表格改 `ExtendedSelection`)或当前筛选结果**,与③「作用于当前筛选结果」心智一致;④ 二次确认框显示将重置的标题/封面条数、其中 `committed=1` 条数,并提示「线上已提交、本地重置不回滚 Shopee、重生成后再更新会再次提交线上」+ 重生成会消耗 cmhub 点数;⑤ 运行中禁用;批量写 `run_type=reset` 运行日志,不触碰 Shopee、不回写 Excel。**只重置封面时绝不改 `new_title`**。不改 cmhub HTTP 协议、Excel、Shopee/CDP 流程。单测覆盖:只重标题保留封面、只重封面保留标题(含手动标题不被清)、重置全部、多选/筛选批量、committed 提示、运行中禁用 | DONE |
| T-535 | ② cmhub 生成标题读取等待固定 600 秒 | T-526, T-533 | 问题:当前 cmhub 生文请求的读取等待时间复用 `ai.resolution_timeouts`,会跟随当前分辨率变化;默认 `1k=240s`,若上游模型排队或响应较慢,标题生成容易先超时。方案:cmhub `gen_title()` 的 `title_request` 读取等待固定使用 600 秒(等同 4k 上限),不再跟随当前分辨率;连接超时仍使用 `ai.cmhub.connect_timeout`,重试次数仍使用 `ai.retry`。封面生成与图片下载继续按分辨率读取等待,direct 兼容路径保持现状。补 `tests/test_ai.py` 断言标题请求 timeout 为 `(connect_timeout, 600)`,不改配置 schema、cmhub HTTP 协议、DB、Excel、Shopee/CDP 或 GUI 交互 | DONE |
| T-536 | GUI 按钮圆角全局统一 | T-512, T-513, T-523 | 现象:只有③「开始更新」及少数上色按钮(删除批次/删除账号/未匹配(n))有 `border-radius: 4px`,其余按钮走原生渲染显直角——不一致。根因:圆角是 T-512/T-513 给按钮上色时顺带写进 QSS 的副产品,不是全局形状决策;一旦给 `QPushButton` 设 stylesheet 就放弃原生渲染,才补了 radius/border。方案(**方式 A:全局统一圆角**):① 在 `app/gui/widgets.py` 抽一个**共享按钮基础样式常量/helper**(统一 `border-radius`,如 4px,与卡片 6px 圆角语言协调),并**接管按钮的全部视觉状态**——normal/hover/pressed/disabled/focus 的背景、边框都定义好,避免全局设 QSS 后按钮变扁平方块、丢 hover 反馈;② 在主窗口/app 级用全局 `QPushButton` QSS 应用该基础样式,让**所有按钮共享同一圆角**;③ warning(`startUpdateButton`)与 danger(`_danger_outline_button_style`:删除批次/删除账号/未匹配)按钮改为**只叠加颜色**,复用共享基础样式的圆角/内边距/状态,不再各自重写 radius/border——杜绝“上色=顺带圆角”的隐性耦合;④ 以 Windows 为主目标做一次视觉自测(hover/按下/禁用不劣于原生)。只改 GUI 样式层(`widgets.py` + 主窗口全局 QSS + 各上色按钮引用),不改任何按钮的启用/禁用逻辑、行为、业务流程、DB、Excel、Shopee/CDP。GUI 单测至少断言上色按钮仍带各自语义色且不再各自硬写 radius(改为引用共享样式);圆角外观本身以人工视觉验收为准 | TODO |
| T-536 | GUI 按钮圆角全局统一 | T-512, T-513, T-523 | 现象:只有③「开始更新」及少数上色按钮(删除批次/删除账号/未匹配(n))有 `border-radius: 4px`,其余按钮走原生渲染显直角——不一致。根因:圆角是 T-512/T-513 给按钮上色时顺带写进 QSS 的副产品,不是全局形状决策;一旦给 `QPushButton` 设 stylesheet 就放弃原生渲染,才补了 radius/border。方案(**方式 A:全局统一圆角**):① 在 `app/gui/widgets.py` 抽一个**共享按钮基础样式常量/helper**(统一 `border-radius`,如 4px,与卡片 6px 圆角语言协调),并**接管按钮的全部视觉状态**——normal/hover/pressed/disabled/focus 的背景、边框都定义好,避免全局设 QSS 后按钮变扁平方块、丢 hover 反馈;② 在主窗口/app 级用全局 `QPushButton` QSS 应用该基础样式,让**所有按钮共享同一圆角**;③ warning(`startUpdateButton`)与 danger(`_danger_outline_button_style`:删除批次/删除账号/未匹配)按钮改为**只叠加颜色**,复用共享基础样式的圆角/内边距/状态,不再各自重写 radius/border——杜绝“上色=顺带圆角”的隐性耦合;④ 以 Windows 为主目标做一次视觉自测(hover/按下/禁用不劣于原生)。只改 GUI 样式层(`widgets.py` + 主窗口全局 QSS + 各上色按钮引用),不改任何按钮的启用/禁用逻辑、行为、业务流程、DB、Excel、Shopee/CDP。GUI 单测至少断言上色按钮仍带各自语义色且不再各自硬写 radius(改为引用共享样式);圆角外观本身以人工视觉验收为准 | DONE |
## Phase 8 · 工程基础设施后续(`docs/engineering-review.md`)
File diff suppressed because one or more lines are too long
+9
View File
@@ -1254,3 +1254,12 @@
- 边界:封面生成和图片下载继续按分辨率读取等待;direct 兼容路径不变;未改配置 schema、cmhub HTTP 协议、DB、Excel、Shopee/CDP 或 GUI 交互。
- 测试:`tests/test_ai.py` 更新 cmhub 标题请求 timeout 断言为 `(connect_timeout, 600)`。
- 验证:`python -m py_compile app\ai.py tests\test_ai.py` 通过;`python -m unittest discover -s tests -p test_ai.py` 通过(24 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(212 tests)。
## 【2026-07-07】T-536 完成 · GUI 按钮圆角全局统一
- 状态:DONE
- 文档:`docs/06-tasks.md` 将 T-536 标记为 DONE,`docs/current-state.md` 记录全局按钮基础样式和语义色按钮叠加规则。
- 代码:`app/gui/widgets.py` 新增 `BUTTON_BASE_STYLE`、`BUTTON_RADIUS_PX`、`_warning_outline_button_style()` 和共享 `_outline_button_style()`;`MainWindow` 全局应用按钮基础 QSS;③「开始更新」改为 warning 语义色叠加,①「删除批次」/④「删除账号」/①「未匹配」继续复用 danger 语义色叠加。
- 边界:只改 GUI 样式层;未改按钮行为、启用/禁用逻辑、DB、Excel、Shopee/CDP 或 AI 流程。
- 测试:`tests/test_gui.py` 补充全局 `QPushButton` 基础样式断言,并确认 warning/danger 按钮保留语义色但不再各自硬写 `border-radius`/`padding`。
- 验证:`python -m py_compile app\gui\widgets.py app\gui\main_window.py app\gui\tabs\apply.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_gui.py` 通过(95 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(212 tests)。
+11
View File
@@ -162,8 +162,11 @@ class GuiTests(TempDirMixin, unittest.TestCase):
[window.tabs.tabText(index) for index in range(window.tabs.count())],
)
self.assertEqual("就绪", window.statusBar().currentMessage())
self.assertEqual(gui.BUTTON_BASE_STYLE, window.styleSheet())
self.assertEqual(TAB_STYLE, window.tabs.styleSheet())
self.assertFalse(window.tabs.tabIcon(TAB_TITLES.index("③ 更新shopee")).isNull())
self.assertIn("QPushButton", window.styleSheet())
self.assertIn("border-radius: 4px", window.styleSheet())
self.assertIn("min-width: 128px", window.tabs.styleSheet())
self.assertIn("padding: 8px 18px", window.tabs.styleSheet())
self.assertIn("margin-right: 8px", window.tabs.styleSheet())
@@ -2452,6 +2455,8 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertEqual("startUpdateButton", tab.start_update_button.objectName())
self.assertIn(gui.COLOR_WARNING, tab.start_update_button.styleSheet())
self.assertIn("border", tab.start_update_button.styleSheet())
self.assertNotIn("border-radius", tab.start_update_button.styleSheet())
self.assertNotIn("padding", tab.start_update_button.styleSheet())
self.assertFalse(tab.stop_update_button.isEnabled())
self.assertEqual(2, tab.model.rowCount())
self.assertEqual("主店", tab.model.index(0, 0).data())
@@ -3482,6 +3487,8 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertEqual(gui.COLOR_MUTED, tab.table.item(0, 4).foreground().color().name())
self.assertEqual("deleteAccountButton", tab.delete_button.objectName())
self.assertIn(gui.COLOR_DANGER, tab.delete_button.styleSheet())
self.assertNotIn("border-radius", tab.delete_button.styleSheet())
self.assertNotIn("padding", tab.delete_button.styleSheet())
visible_values = [
tab.table.item(0, column).text()
for column in range(tab.table.columnCount())
@@ -3634,6 +3641,8 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_foreground(tab.model, 0, 3, gui.COLOR_PENDING)
self.assertEqual("deleteBatchButton", tab.delete_batch_button.objectName())
self.assertIn(gui.COLOR_DANGER, tab.delete_batch_button.styleSheet())
self.assertNotIn("border-radius", tab.delete_batch_button.styleSheet())
self.assertNotIn("padding", tab.delete_batch_button.styleSheet())
self.assertEqual("Excel副店", tab.model.index(1, 0).data())
self.assertEqual("missing", tab.model.index(1, 1).data())
self.assertEqual("略过", tab.model.index(1, 3).data())
@@ -3645,6 +3654,8 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertIn(gui.COLOR_DANGER, tab.summary_label.text())
self.assertIn(gui.COLOR_DANGER, tab.show_unmatched_button.styleSheet())
self.assertIn("border", tab.show_unmatched_button.styleSheet())
self.assertNotIn("border-radius", tab.show_unmatched_button.styleSheet())
self.assertNotIn("padding", tab.show_unmatched_button.styleSheet())
self.assertIn("主店1", tab.match_detail_label.text())
self.assertIn("未匹配", tab.empty_label.text())