feat: add first-run empty state guidance

This commit is contained in:
chengma
2026-07-02 09:57:06 +08:00
parent 9bad70f425
commit d8c3c5d548
5 changed files with 214 additions and 9 deletions
+141 -1
View File
@@ -165,6 +165,35 @@ if QT_IMPORT_ERROR is None:
return QIcon(pixmap) return QIcon(pixmap)
def _build_empty_state_card(object_name, button_text="前往账号管理"):
card = QWidget()
card.setObjectName(object_name)
card.setStyleSheet(
f"QWidget#{object_name} {{ "
"background: #f6f8fa; border: 1px solid #d0d7de; "
"border-radius: 6px; padding: 10px; "
"}"
)
layout = QHBoxLayout(card)
layout.setContentsMargins(12, 10, 12, 10)
label = QLabel("")
label.setWordWrap(True)
button = QPushButton(button_text)
button.setObjectName(f"{object_name}Button")
button.setVisible(False)
layout.addWidget(label, 1)
layout.addWidget(button)
card.setVisible(False)
return card, label, button
def _set_empty_state(card, label, button, message=None, show_button=False):
active = bool(message)
card.setVisible(active)
label.setText(message or "")
button.setVisible(active and show_button)
def _database_path(db_path=None, config=None) -> str: def _database_path(db_path=None, config=None) -> str:
return db_path or appconfig.db_path(config) return db_path or appconfig.db_path(config)
@@ -696,11 +725,13 @@ if QT_IMPORT_ERROR is None:
status_callback=None, status_callback=None,
title_prompt_path=None, title_prompt_path=None,
cover_prompts_dir=None, cover_prompts_dir=None,
open_accounts_callback=None,
): ):
super().__init__(parent) super().__init__(parent)
self.config = appconfig.load_config() if config is None else config self.config = appconfig.load_config() if config is None else config
self.db_path = _database_path(db_path, self.config) self.db_path = _database_path(db_path, self.config)
self.status_callback = status_callback self.status_callback = status_callback
self.open_accounts_callback = open_accounts_callback
self.title_prompt_path = title_prompt_path or prompts.TITLE_PROMPT_PATH self.title_prompt_path = title_prompt_path or prompts.TITLE_PROMPT_PATH
self.cover_prompts_dir = cover_prompts_dir or prompts.COVER_PROMPTS_DIR self.cover_prompts_dir = cover_prompts_dir or prompts.COVER_PROMPTS_DIR
self.current_cover_template = None self.current_cover_template = None
@@ -779,6 +810,13 @@ if QT_IMPORT_ERROR is None:
filter_layout.addWidget(self.refresh_button) filter_layout.addWidget(self.refresh_button)
self.summary_label = QLabel("任务 0 条") self.summary_label = QLabel("任务 0 条")
(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
) = _build_empty_state_card("generateEmptyStateCard")
if self.open_accounts_callback is not None:
self.empty_state_button.clicked.connect(self.open_accounts_callback)
self.task_table = QTableView() self.task_table = QTableView()
self.model = GenerateTaskTableModel(self.task_table, db_path=self.db_path, status_callback=self._set_status) self.model = GenerateTaskTableModel(self.task_table, db_path=self.db_path, status_callback=self._set_status)
self.task_table.setModel(self.model) self.task_table.setModel(self.model)
@@ -799,6 +837,7 @@ if QT_IMPORT_ERROR is None:
right_layout.setContentsMargins(12, 0, 0, 0) right_layout.setContentsMargins(12, 0, 0, 0)
right_layout.addLayout(filter_layout) right_layout.addLayout(filter_layout)
right_layout.addWidget(self.summary_label) right_layout.addWidget(self.summary_label)
right_layout.addWidget(self.empty_state_card)
right_layout.addWidget(self.task_table, 1) right_layout.addWidget(self.task_table, 1)
right_layout.addWidget(QLabel("AI生成运行日志")) right_layout.addWidget(QLabel("AI生成运行日志"))
right_layout.addWidget(self.run_log_view) right_layout.addWidget(self.run_log_view)
@@ -1271,12 +1310,42 @@ if QT_IMPORT_ERROR is None:
except Exception as exc: except Exception as exc:
self.model.set_tasks([], []) self.model.set_tasks([], [])
self.summary_label.setText("任务读取失败") self.summary_label.setText("任务读取失败")
_set_empty_state(self.empty_state_card, self.empty_state_label, self.empty_state_button)
self._set_status(f"AI 生成任务读取失败:{exc}") self._set_status(f"AI 生成任务读取失败:{exc}")
return return
self.model.set_tasks(filtered_tasks, accounts_rows) self.model.set_tasks(filtered_tasks, accounts_rows)
self.summary_label.setText( self.summary_label.setText(
f"任务 {len(filtered_tasks)}/{len(batch_tasks)} 条" f"任务 {len(filtered_tasks)}/{len(batch_tasks)} 条"
) )
self._update_empty_state(batch_tasks, filtered_tasks, accounts_rows)
def _update_empty_state(self, batch_tasks, filtered_tasks, account_rows):
if not account_rows:
_set_empty_state(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
"第一步:前往『④账号管理』配置并登录账号,再回到②生成标题和封面。",
self.open_accounts_callback is not None,
)
return
if not batch_tasks:
_set_empty_state(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
"还没有可生成任务。请先在①导入采集完成旧标题和旧封面采集。",
)
return
if not filtered_tasks:
_set_empty_state(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
"当前筛选没有匹配的生成任务,请调整批次、店铺、商品ID或状态筛选。",
)
return
_set_empty_state(self.empty_state_card, self.empty_state_label, self.empty_state_button)
def _populate_batch_filter(self, batches, selected_batch): def _populate_batch_filter(self, batches, selected_batch):
previous = selected_batch if selected_batch in {batch.id for batch in batches} else None previous = selected_batch if selected_batch in {batch.id for batch in batches} else None
@@ -1403,6 +1472,13 @@ if QT_IMPORT_ERROR is None:
self.summary_label = QLabel("任务 0 条") self.summary_label = QLabel("任务 0 条")
self.risk_label = QLabel("可先点击「检查本轮更新」确认当前筛选范围;点击「开始更新」后会再次确认并按批提交线上。") self.risk_label = QLabel("可先点击「检查本轮更新」确认当前筛选范围;点击「开始更新」后会再次确认并按批提交线上。")
(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
) = _build_empty_state_card("applyEmptyStateCard")
if self.open_accounts_callback is not None:
self.empty_state_button.clicked.connect(self.open_accounts_callback)
self.task_table = QTableView() self.task_table = QTableView()
self.model = ApplyTaskTableModel(self.task_table) self.model = ApplyTaskTableModel(self.task_table)
self.task_table.setModel(self.model) self.task_table.setModel(self.model)
@@ -1449,6 +1525,7 @@ if QT_IMPORT_ERROR is None:
layout.addLayout(filter_layout) layout.addLayout(filter_layout)
layout.addWidget(self.risk_label) layout.addWidget(self.risk_label)
layout.addWidget(self.summary_label) layout.addWidget(self.summary_label)
layout.addWidget(self.empty_state_card)
layout.addWidget(self.task_table, 1) layout.addWidget(self.task_table, 1)
layout.addWidget(QLabel("运行日志")) layout.addWidget(QLabel("运行日志"))
layout.addWidget(self.run_log_view) layout.addWidget(self.run_log_view)
@@ -1499,14 +1576,44 @@ if QT_IMPORT_ERROR is None:
except Exception as exc: except Exception as exc:
self.model.set_tasks([], []) self.model.set_tasks([], [])
self.summary_label.setText("任务读取失败") self.summary_label.setText("任务读取失败")
_set_empty_state(self.empty_state_card, self.empty_state_label, self.empty_state_button)
self._set_status(f"更新任务读取失败:{exc}") self._set_status(f"更新任务读取失败:{exc}")
return return
self.model.set_tasks(filtered_tasks, account_rows) self.model.set_tasks(filtered_tasks, account_rows)
self.summary_label.setText( self.summary_label.setText(
f"任务 {len(filtered_tasks)}/{len(batch_tasks)} 条" f"任务 {len(filtered_tasks)}/{len(batch_tasks)} 条"
) )
self._update_empty_state(batch_tasks, filtered_tasks, account_rows)
self._update_write_back_button() self._update_write_back_button()
def _update_empty_state(self, batch_tasks, filtered_tasks, account_rows):
if not account_rows:
_set_empty_state(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
"第一步:前往『④账号管理』配置并登录账号,再回到③更新 Shopee。",
self.open_accounts_callback is not None,
)
return
if not batch_tasks:
_set_empty_state(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
"还没有可更新任务。请先在②AI生成完成新标题或新封面。",
)
return
if not filtered_tasks:
_set_empty_state(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
"当前筛选没有可更新任务,请调整批次、店铺、商品ID或状态筛选。",
)
return
_set_empty_state(self.empty_state_card, self.empty_state_label, self.empty_state_button)
def start_update(self, checked=False): def start_update(self, checked=False):
self._start_update(dry_run=False) self._start_update(dry_run=False)
@@ -2184,12 +2291,20 @@ if QT_IMPORT_ERROR is None:
self.run_log_view.setPlaceholderText("采集运行日志") self.run_log_view.setPlaceholderText("采集运行日志")
self.empty_label = QLabel("") self.empty_label = QLabel("")
(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
) = _build_empty_state_card("collectEmptyStateCard")
if self.open_accounts_callback is not None:
self.empty_state_button.clicked.connect(self.open_accounts_callback)
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(18, 18, 18, 18) layout.setContentsMargins(18, 18, 18, 18)
layout.addLayout(toolbar) layout.addLayout(toolbar)
layout.addLayout(summary_layout) layout.addLayout(summary_layout)
layout.addWidget(self.match_detail_label) layout.addWidget(self.match_detail_label)
layout.addWidget(self.empty_state_card)
layout.addWidget(self.table, 1) layout.addWidget(self.table, 1)
layout.addWidget(QLabel("采集运行日志")) layout.addWidget(QLabel("采集运行日志"))
layout.addWidget(self.run_log_view) layout.addWidget(self.run_log_view)
@@ -2401,11 +2516,12 @@ if QT_IMPORT_ERROR is None:
except Exception as exc: except Exception as exc:
self.model.set_tasks([], []) self.model.set_tasks([], [])
self.empty_label.setText("任务读取失败") self.empty_label.setText("任务读取失败")
_set_empty_state(self.empty_state_card, self.empty_state_label, self.empty_state_button)
self._set_status(f"任务读取失败:{exc}") self._set_status(f"任务读取失败:{exc}")
return return
self.model.set_tasks(task_rows, account_rows) self.model.set_tasks(task_rows, account_rows)
self._update_summary(task_rows, account_rows) self._update_summary(task_rows, account_rows)
self._update_empty_label(len(task_rows)) self._update_empty_state(task_rows, account_rows)
self._update_delete_batch_button() self._update_delete_batch_button()
def _populate_batch_filter(self, batches, selected_batch): def _populate_batch_filter(self, batches, selected_batch):
@@ -2774,6 +2890,29 @@ if QT_IMPORT_ERROR is None:
} }
return sum(1 for task in task_rows if str(task.alias).strip() not in aliases) return sum(1 for task in task_rows if str(task.alias).strip() not in aliases)
def _update_empty_state(self, task_rows, account_rows):
if not account_rows:
self.empty_label.setText("")
_set_empty_state(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
"第一步:前往『④账号管理』配置并登录账号,再回到①导入 Excel。",
self.open_accounts_callback is not None,
)
return
if not task_rows:
self.empty_label.setText("")
_set_empty_state(
self.empty_state_card,
self.empty_state_label,
self.empty_state_button,
"还没有导入任务。请点击「导入 Excel...」导入待处理商品。",
)
return
_set_empty_state(self.empty_state_card, self.empty_state_label, self.empty_state_button)
self._update_empty_label(len(task_rows))
def _update_empty_label(self, total_rows): def _update_empty_label(self, total_rows):
if total_rows == 0: if total_rows == 0:
self.empty_label.setText("暂无任务") self.empty_label.setText("暂无任务")
@@ -5668,6 +5807,7 @@ if QT_IMPORT_ERROR is None:
db_path=self.db_path, db_path=self.db_path,
config=self.config, config=self.config,
status_callback=self.statusBar().showMessage, status_callback=self.statusBar().showMessage,
open_accounts_callback=lambda: self.open_accounts_tab(),
) )
if title == "③ 更新shopee": if title == "③ 更新shopee":
return ApplyTab( return ApplyTab(
+1 -1
View File
@@ -97,7 +97,7 @@
| T-511 | 语义色板 + ①②③任务状态列上色 | T-202, T-303, T-401 | 依据 `docs/ui-color-design.md`。在 `app/gui.py` 顶部定义统一语义色板常量(success `#1a7f37`/danger `#cf222e`/info `#0969da`/pending `#9a6700`/muted `#6e7781`/warning `#bc4c00`),全局引用不散落硬编码。①②③ 三个 TableModel 在 `Qt.ForegroundRole` 按内部 `stage/status` 返回 `QColor`,不要按中文显示文案硬匹配:完成态=success、失败=danger、略过/取消=muted、待处理=pending、进行中=info;不给普通行刷底色(失败行浅底 `#ffebe9` 可选、仅此一种)。保持文字+颜色双编码,不只靠颜色。跑 `test_gui.py` 无回归 | DONE | | T-511 | 语义色板 + ①②③任务状态列上色 | T-202, T-303, T-401 | 依据 `docs/ui-color-design.md`。在 `app/gui.py` 顶部定义统一语义色板常量(success `#1a7f37`/danger `#cf222e`/info `#0969da`/pending `#9a6700`/muted `#6e7781`/warning `#bc4c00`),全局引用不散落硬编码。①②③ 三个 TableModel 在 `Qt.ForegroundRole` 按内部 `stage/status` 返回 `QColor`,不要按中文显示文案硬匹配:完成态=success、失败=danger、略过/取消=muted、待处理=pending、进行中=info;不给普通行刷底色(失败行浅底 `#ffebe9` 可选、仅此一种)。保持文字+颜色双编码,不只靠颜色。跑 `test_gui.py` 无回归 | DONE |
| T-512 | ③高风险按钮上色 + ①导入校验数字标红 | T-511, T-508, T-202b | ③「开始更新」(写线上)改用 warning `#bc4c00` 系强调(描边或填充;第一版不强制图标,避免 Windows/Qt 字体渲染不稳),与②安全流程 info 蓝主操作区分;「停止」保持中性。①导入汇总栏:未匹配数 >0 时用 danger 且保持现有按钮可点击筛出,=0 保持中性;无效行数 >0 时只标红提示,不承诺在任务表筛出,因为脏行未入库,后续若要点击查看需另做「导入错误明细」。颜色取自 T-511 色板常量 | DONE | | T-512 | ③高风险按钮上色 + ①导入校验数字标红 | T-511, T-508, T-202b | ③「开始更新」(写线上)改用 warning `#bc4c00` 系强调(描边或填充;第一版不强制图标,避免 Windows/Qt 字体渲染不稳),与②安全流程 info 蓝主操作区分;「停止」保持中性。①导入汇总栏:未匹配数 >0 时用 danger 且保持现有按钮可点击筛出,=0 保持中性;无效行数 >0 时只标红提示,不承诺在任务表筛出,因为脏行未入库,后续若要点击查看需另做「导入错误明细」。颜色取自 T-511 色板常量 | DONE |
| T-513 | 登录点 / ③Tab危险标识 / 破坏性按钮上色 | T-511, T-105, T-206 | ④登录状态 `●` 已登录=success、未登录/未检测=danger 或 muted(保留文字);③ 更新shopee Tab 若做危险标识,不依赖 QSS 硬选第 3 个 Tab,优先用 `QTabWidget.setTabIcon(2, ...)` 设置克制 warning 小圆点图标,或第一版先跳过 Tab 标识;不整条刷红。破坏性按钮「删除批次」「删除账号」文字/描边用 danger,与普通灰按钮区分(二次确认弹窗仍是主防线)。颜色取自 T-511 色板常量 | DONE | | T-513 | 登录点 / ③Tab危险标识 / 破坏性按钮上色 | T-511, T-105, T-206 | ④登录状态 `●` 已登录=success、未登录/未检测=danger 或 muted(保留文字);③ 更新shopee Tab 若做危险标识,不依赖 QSS 硬选第 3 个 Tab,优先用 `QTabWidget.setTabIcon(2, ...)` 设置克制 warning 小圆点图标,或第一版先跳过 Tab 标识;不整条刷红。破坏性按钮「删除批次」「删除账号」文字/描边用 danger,与普通灰按钮区分(二次确认弹窗仍是主防线)。颜色取自 T-511 色板常量 | DONE |
| T-514 | ①②③ 首次空状态引导卡片 | T-205 | 依据 `docs/ux-review.md` P1。①②③ 在「无账号/未登录」时用显式空状态引导卡片替代灰按钮,例如「第一步:前往『④账号管理』配置并登录账号 →」并带跳转按钮;账号就绪后卡片消失,老用户无感。仅 UI 引导,不改采集/生成/更新流程与既有预检拦截逻辑 | TODO | | T-514 | ①②③ 首次空状态引导卡片 | T-205 | 依据 `docs/ux-review.md` P1。①②③ 增加轻量空状态引导卡片,不改采集/生成/更新流程和既有预检拦截逻辑。无账号时三页均显示「第一步:前往『④账号管理』配置并登录账号」并提供「前往账号管理」按钮;有账号但无任务时,①提示导入 Excel,②提示先完成①采集,③提示先完成②生成;账号/任务就绪后卡片自动隐藏,老用户无感。卡片只做 UI 引导,不替代执行前账号 Chrome/CDP/登录态检查 | DONE |
| T-515 | 批次阶段进度总览 | T-206, T-401 | 依据 `docs/ux-review.md` P1。加轻量批次进度总览:按选中批次聚合各阶段计数(导入/已采集/已生成/已更新/失败/略过),落点可选①顶部进度条或独立概览区。数据由现有 `db.list_tasks(batch_id=...)` 按 `stage/status` 聚合,不新增表;已软删除批次不计入 | TODO | | T-515 | 批次阶段进度总览 | T-206, T-401 | 依据 `docs/ux-review.md` P1。加轻量批次进度总览:按选中批次聚合各阶段计数(导入/已采集/已生成/已更新/失败/略过),落点可选①顶部进度条或独立概览区。数据由现有 `db.list_tasks(batch_id=...)` 按 `stage/status` 聚合,不新增表;已软删除批次不计入 | TODO |
| T-516 | ①筛选对齐②③ | T-202, T-303b | 依据 `docs/ux-review.md` P2。① 导入采集补齐与②③一致的店铺/商品ID/状态筛选(至少店铺),保持三个列表页筛选心智一致;不改导入汇总栏与未匹配筛出逻辑,只扩展筛选维度 | TODO | | T-516 | ①筛选对齐②③ | T-202, T-303b | 依据 `docs/ux-review.md` P2。① 导入采集补齐与②③一致的店铺/商品ID/状态筛选(至少店铺),保持三个列表页筛选心智一致;不改导入汇总栏与未匹配筛出逻辑,只扩展筛选维度 | TODO |
| T-517 | ⑤设置分区 + 清理兼容字段 | T-506, T-507 | 依据 `docs/ux-review.md` P2。⑤ 视觉分区:高频「Shopee 更新安全/执行模式」与低频「基础设施(Chrome路径/端口/DB路径)」分块;彻底隐藏或清理残留的 `test_item_id`、`dry_run` 用户入口(保留内部兼容字段与语义),减少设置页过载与 UI 技术债 | TODO | | T-517 | ⑤设置分区 + 清理兼容字段 | T-506, T-507 | 依据 `docs/ux-review.md` P2。⑤ 视觉分区:高频「Shopee 更新安全/执行模式」与低频「基础设施(Chrome路径/端口/DB路径)」分块;彻底隐藏或清理残留的 `test_item_id`、`dry_run` 用户入口(保留内部兼容字段与语义),减少设置页过载与 UI 技术债 | TODO |
File diff suppressed because one or more lines are too long
+7
View File
@@ -953,3 +953,10 @@
- 测试:`tests/test_gui.py` 补充主窗口 ③Tab icon、账号登录状态文本与颜色、删除账号/删除批次按钮 danger 样式断言。 - 测试:`tests/test_gui.py` 补充主窗口 ③Tab icon、账号登录状态文本与颜色、删除账号/删除批次按钮 danger 样式断言。
- 验证:`python -m py_compile app\gui.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p "test_gui.py"` 通过(63 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(151 tests)。测试输出仍有 PySide6 字体目录提示,不影响结果。 - 验证:`python -m py_compile app\gui.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p "test_gui.py"` 通过(63 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(151 tests)。测试输出仍有 PySide6 字体目录提示,不影响结果。
- 下一步:T-514「①②③ 首次空状态引导卡片」。 - 下一步:T-514「①②③ 首次空状态引导卡片」。
## 【2026-07-02】T-514 完成 · ①②③首次空状态引导卡片
- 文档先行:扩展 `docs/06-tasks.md` 的 T-514 口径,明确无账号时①②③均显示「前往账号管理」引导,有账号但无任务时分别提示①导入 Excel、②先完成①采集、③先完成②生成;卡片只做 UI 引导,不替代执行前账号/Chrome/登录态预检。
- 代码:`app/gui.py` 新增共享 `_build_empty_state_card()` / `_set_empty_state()`;①导入采集、②AI生成、③更新shopee在刷新任务时按账号和任务状态显示/隐藏空状态卡片;②新增 `open_accounts_callback` 并由 `MainWindow` 传入跳④回调;原有导入、采集、生成、更新和安全拦截流程不变。
- 测试:`tests/test_gui.py` 新增①②③空状态断言,覆盖无账号时按钮跳④、有账号但无任务时显示对应下一步提示且隐藏跳④按钮。
- 验证:`python -m py_compile app\gui.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p "test_gui.py"` 通过(66 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(154 tests)。测试输出仍有 PySide6 字体目录提示,不影响结果。
- 下一步:T-515「批次阶段进度总览」。
+57
View File
@@ -109,6 +109,63 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir) self.assert_removed(temp_dir)
def test_collect_tab_shows_empty_state_without_accounts(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
opened = []
tab = CollectTab(config=cfg, open_accounts_callback=lambda: opened.append(True))
self.addCleanup(tab.close)
self.assertFalse(tab.empty_state_card.isHidden())
self.assertIn("④账号管理", tab.empty_state_label.text())
self.assertFalse(tab.empty_state_button.isHidden())
tab.empty_state_button.click()
self.assertEqual([True], opened)
self.assert_removed(temp_dir)
def test_generate_tab_shows_empty_state_for_first_steps(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
opened = []
tab = GenerateTab(config=cfg, open_accounts_callback=lambda: opened.append(True))
self.addCleanup(tab.close)
self.assertFalse(tab.empty_state_card.isHidden())
self.assertIn("④账号管理", tab.empty_state_label.text())
self.assertFalse(tab.empty_state_button.isHidden())
tab.empty_state_button.click()
self.assertEqual([True], opened)
accounts.create_account("主店", "alias", debug_port=9222, config=cfg)
tab.refresh_tasks()
self.assertFalse(tab.empty_state_card.isHidden())
self.assertIn("①导入采集", tab.empty_state_label.text())
self.assertTrue(tab.empty_state_button.isHidden())
self.assert_removed(temp_dir)
def test_apply_tab_shows_empty_state_for_first_steps(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
opened = []
tab = ApplyTab(config=cfg, open_accounts_callback=lambda: opened.append(True))
self.addCleanup(tab.close)
self.assertFalse(tab.empty_state_card.isHidden())
self.assertIn("④账号管理", tab.empty_state_label.text())
self.assertFalse(tab.empty_state_button.isHidden())
tab.empty_state_button.click()
self.assertEqual([True], opened)
accounts.create_account("主店", "alias", debug_port=9222, config=cfg)
tab.refresh_tasks()
self.assertFalse(tab.empty_state_card.isHidden())
self.assertIn("②AI生成", tab.empty_state_label.text())
self.assertTrue(tab.empty_state_button.isHidden())
self.assert_removed(temp_dir)
def test_settings_tab_loads_ai_models_and_masks_key_field(self): def test_settings_tab_loads_ai_models_and_masks_key_field(self):
with self.make_temp_dir() as temp_dir: with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir) cfg = self.make_config(temp_dir)