feat(product-suite): support temporary item drafts
This commit is contained in:
+307
-23
@@ -43,7 +43,7 @@ from PySide6.QtWidgets import (
|
||||
from ... import accounts, appconfig, diagnostics, image_studio, image_studio_images, product_suite
|
||||
from .. import file_manager
|
||||
from ..image_preview import ImagePreviewDialog
|
||||
from ..widgets import COLOR_DANGER, _emit_status, run_worker
|
||||
from ..widgets import _emit_status, run_worker
|
||||
from ..workers import (
|
||||
ImageStudioDownloadOriginalWorker,
|
||||
ImageStudioPullImagesWorker,
|
||||
@@ -614,6 +614,7 @@ class SuiteTaskState:
|
||||
account_alias: str = ""
|
||||
item_id: str = ""
|
||||
project_id: int = None
|
||||
project_binding_state: str = ""
|
||||
prompt: str = ""
|
||||
settings: dict = field(default_factory=product_suite.default_suite_settings)
|
||||
current_job_ids: list = field(default_factory=list)
|
||||
@@ -624,6 +625,7 @@ class SuiteTaskState:
|
||||
pull_thread: object = None
|
||||
import_worker: object = None
|
||||
import_thread: object = None
|
||||
import_created_draft: bool = False
|
||||
ai_worker: object = None
|
||||
ai_thread: object = None
|
||||
download_queue: list = field(default_factory=list)
|
||||
@@ -675,7 +677,9 @@ class ProductSuiteTab(QWidget):
|
||||
self._build_ui()
|
||||
self._connect_signals()
|
||||
self.refresh_accounts()
|
||||
self.add_task(inherit=False)
|
||||
self._restore_draft_tasks()
|
||||
if not self._states:
|
||||
self.add_task(inherit=False)
|
||||
self.elapsed_timer = QTimer(self)
|
||||
self.elapsed_timer.setInterval(1000)
|
||||
self.elapsed_timer.timeout.connect(self._refresh_elapsed)
|
||||
@@ -771,6 +775,13 @@ class ProductSuiteTab(QWidget):
|
||||
"}"
|
||||
)
|
||||
layout.addWidget(self.add_images_button)
|
||||
self.item_id_hint_label = QLabel("请输入正确的商品ID")
|
||||
self.item_id_hint_label.setObjectName("suiteItemIdHintLabel")
|
||||
self.item_id_hint_label.setStyleSheet("color: #9a6700; font-weight: 600;")
|
||||
self.item_id_hint_label.setToolTip("未绑定正式商品ID时,可添加本地图片和生成套图,但不能拉取蝦皮主图")
|
||||
self.item_id_hint_label.setAccessibleName("商品ID提示")
|
||||
self.item_id_hint_label.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
|
||||
layout.addWidget(self.item_id_hint_label)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(QLabel("账号"))
|
||||
self.account_combo = QComboBox()
|
||||
@@ -1145,6 +1156,40 @@ class ProductSuiteTab(QWidget):
|
||||
self._loading = False
|
||||
self._update_account_tooltip()
|
||||
|
||||
def _append_task_state(self, state, title=None):
|
||||
self._states[state.key] = state
|
||||
index = self.task_tabs.addTab(title or "套图任务 %d" % state.serial)
|
||||
self.task_tabs.setTabData(index, state.key)
|
||||
if self.task_tabs.currentIndex() == index:
|
||||
self._on_task_changed(index)
|
||||
else:
|
||||
self.task_tabs.setCurrentIndex(index)
|
||||
return state
|
||||
|
||||
def _restore_draft_tasks(self):
|
||||
try:
|
||||
projects = image_studio.list_recoverable_draft_projects(path=self.db_path)
|
||||
except Exception as exc:
|
||||
self._status("临时草稿恢复失败:%s" % _user_error(exc), "danger")
|
||||
return
|
||||
for project in projects:
|
||||
state = SuiteTaskState(
|
||||
key=self._next_key,
|
||||
serial=self._next_serial,
|
||||
account_alias=project.account_alias,
|
||||
project_id=int(project.id),
|
||||
project_binding_state=project.binding_state,
|
||||
prompt=str(project.draft_prompt or ""),
|
||||
settings=product_suite.normalize_suite_settings(
|
||||
image_studio.project_suite_settings(project)
|
||||
),
|
||||
)
|
||||
self._next_key += 1
|
||||
self._next_serial += 1
|
||||
self._append_task_state(state, "临时草稿 %d" % state.serial)
|
||||
if projects:
|
||||
self._status("已恢复%d个临时草稿" % len(projects), "info")
|
||||
|
||||
def add_task(self, checked=False, inherit=True):
|
||||
source = self._displayed_state if inherit else None
|
||||
initial_settings = (
|
||||
@@ -1163,20 +1208,29 @@ class ProductSuiteTab(QWidget):
|
||||
state.account_alias = self.accounts[0].alias
|
||||
self._next_key += 1
|
||||
self._next_serial += 1
|
||||
self._states[state.key] = state
|
||||
index = self.task_tabs.addTab("套图任务 %d" % state.serial)
|
||||
self.task_tabs.setTabData(index, state.key)
|
||||
if self.task_tabs.currentIndex() == index:
|
||||
self._on_task_changed(index)
|
||||
else:
|
||||
self.task_tabs.setCurrentIndex(index)
|
||||
return state
|
||||
return self._append_task_state(state)
|
||||
|
||||
def _set_task_title(self, state):
|
||||
for index in range(self.task_tabs.count()):
|
||||
if self.task_tabs.tabData(index) == state.key:
|
||||
title = "临时草稿 %d" % state.serial if self._is_draft_state(state) else "套图任务 %d" % state.serial
|
||||
self.task_tabs.setTabText(index, title)
|
||||
return
|
||||
|
||||
def close_task(self, index):
|
||||
key = self.task_tabs.tabData(index)
|
||||
state = self._states.get(key)
|
||||
if state is None:
|
||||
return
|
||||
project = self._state_project(state)
|
||||
draft_action = None
|
||||
if image_studio.is_draft_project(project) and image_studio.project_has_content(
|
||||
project.id,
|
||||
path=self.db_path,
|
||||
):
|
||||
draft_action = self._draft_close_action()
|
||||
if draft_action == "cancel":
|
||||
return
|
||||
if state.generation_running():
|
||||
if not self._confirm(
|
||||
"关闭套图任务",
|
||||
@@ -1189,12 +1243,48 @@ class ProductSuiteTab(QWidget):
|
||||
state.ai_worker.cancel()
|
||||
if state.pull_worker is not None:
|
||||
state.pull_worker.cancel()
|
||||
if image_studio.is_draft_project(project):
|
||||
if draft_action == "delete":
|
||||
try:
|
||||
image_studio.soft_delete_project(
|
||||
project.id,
|
||||
reason="用户关闭临时草稿",
|
||||
path=self.db_path,
|
||||
)
|
||||
except Exception as exc:
|
||||
self._message("删除临时草稿失败", _user_error(exc))
|
||||
return
|
||||
self._status("临时草稿已删除", "success")
|
||||
else:
|
||||
try:
|
||||
image_studio.discard_empty_draft_project(project.id, path=self.db_path)
|
||||
except Exception as exc:
|
||||
self._status("清理空临时草稿失败:%s" % _user_error(exc), "danger")
|
||||
self._retired_states.append(state)
|
||||
self._states.pop(state.key, None)
|
||||
self.task_tabs.removeTab(index)
|
||||
if self.task_tabs.count() == 0:
|
||||
self.add_task(inherit=False)
|
||||
|
||||
def _draft_close_action(self):
|
||||
box = QMessageBox(self)
|
||||
box.setIcon(QMessageBox.Question)
|
||||
box.setWindowTitle("关闭临时草稿")
|
||||
box.setText("当前临时草稿包含图片或生成记录。请选择关闭后的处理方式。")
|
||||
keep_button = box.addButton("保留草稿", QMessageBox.AcceptRole)
|
||||
delete_button = box.addButton("删除草稿", QMessageBox.DestructiveRole)
|
||||
cancel_button = box.addButton("取消", QMessageBox.RejectRole)
|
||||
box.setDefaultButton(keep_button)
|
||||
box.exec()
|
||||
clicked = box.clickedButton()
|
||||
if clicked is keep_button:
|
||||
return "keep"
|
||||
if clicked is delete_button:
|
||||
return "delete"
|
||||
if clicked is cancel_button:
|
||||
return "cancel"
|
||||
return "cancel"
|
||||
|
||||
def _state_for_index(self, index):
|
||||
if index < 0:
|
||||
return None
|
||||
@@ -1220,6 +1310,7 @@ class ProductSuiteTab(QWidget):
|
||||
state.settings = self._settings_from_controls()
|
||||
|
||||
def _load_state(self, state):
|
||||
self._sync_state_project_binding(state)
|
||||
self._loading = True
|
||||
try:
|
||||
self.custom_category_edit.hide()
|
||||
@@ -1269,6 +1360,17 @@ class ProductSuiteTab(QWidget):
|
||||
state = self._displayed_state
|
||||
alias = str(self.account_combo.currentData() or "")
|
||||
if state.project_id is not None and alias != state.account_alias:
|
||||
if self._is_draft_state(state):
|
||||
self._message(
|
||||
"不能切换账号",
|
||||
"临时草稿已关联当前账号。请先绑定正式商品ID,或关闭草稿后再切换账号。",
|
||||
)
|
||||
self._loading = True
|
||||
try:
|
||||
self._set_combo_value(self.account_combo, state.account_alias)
|
||||
finally:
|
||||
self._loading = False
|
||||
return
|
||||
if not self._confirm(
|
||||
"切换账号",
|
||||
"切换账号后,当前任务会改为新的账号与商品上下文。确认继续吗?",
|
||||
@@ -1292,10 +1394,57 @@ class ProductSuiteTab(QWidget):
|
||||
state = self._displayed_state
|
||||
item_id = self.item_id_edit.text().strip()
|
||||
if item_id and not item_id.isdigit():
|
||||
self.item_id_edit.setStyleSheet("border: 1px solid %s;" % COLOR_DANGER)
|
||||
self.item_id_edit.setStyleSheet("border: 1px solid #9a6700;")
|
||||
self._status("商品ID只能输入数字", "warning")
|
||||
self._update_context_actions(state)
|
||||
return
|
||||
self.item_id_edit.setStyleSheet("")
|
||||
if self._is_draft_state(state):
|
||||
if not item_id:
|
||||
state.item_id = ""
|
||||
self._update_context_actions(state)
|
||||
return
|
||||
if not self._confirm(
|
||||
"绑定正式商品",
|
||||
"将当前临时草稿绑定到商品%s吗?\n已添加图片和生成记录会继续保留。" % item_id,
|
||||
):
|
||||
self._loading = True
|
||||
try:
|
||||
self.item_id_edit.setText(state.item_id)
|
||||
finally:
|
||||
self._loading = False
|
||||
self._update_context_actions(state)
|
||||
return
|
||||
try:
|
||||
project = image_studio.bind_draft_project(
|
||||
state.project_id,
|
||||
item_id,
|
||||
path=self.db_path,
|
||||
)
|
||||
except image_studio.ImageStudioProjectConflictError as exc:
|
||||
self._message("商品项目已存在", _user_error(exc))
|
||||
self._loading = True
|
||||
try:
|
||||
self.item_id_edit.setText(state.item_id)
|
||||
finally:
|
||||
self._loading = False
|
||||
self._update_context_actions(state)
|
||||
return
|
||||
except Exception as exc:
|
||||
self._message("绑定正式商品失败", _user_error(exc))
|
||||
self._loading = True
|
||||
try:
|
||||
self.item_id_edit.setText(state.item_id)
|
||||
finally:
|
||||
self._loading = False
|
||||
self._update_context_actions(state)
|
||||
return
|
||||
state.item_id = project.item_id
|
||||
state.project_binding_state = project.binding_state
|
||||
self._set_task_title(state)
|
||||
self._status("临时草稿已绑定商品%s" % project.item_id, "success")
|
||||
self._update_context_actions(state)
|
||||
return
|
||||
if state.project_id is not None and item_id != state.item_id:
|
||||
if not self._confirm(
|
||||
"切换商品",
|
||||
@@ -1310,8 +1459,6 @@ class ProductSuiteTab(QWidget):
|
||||
self._clear_project_binding(state)
|
||||
state.item_id = item_id
|
||||
if not item_id:
|
||||
self.item_id_edit.setStyleSheet("border: 1px solid %s;" % COLOR_DANGER)
|
||||
self._status("商品ID不能为空", "warning")
|
||||
self._update_context_actions(state)
|
||||
return
|
||||
if item_id and state.account_alias:
|
||||
@@ -1320,6 +1467,7 @@ class ProductSuiteTab(QWidget):
|
||||
|
||||
def _clear_project_binding(self, state):
|
||||
state.project_id = None
|
||||
state.project_binding_state = ""
|
||||
state.current_job_ids = []
|
||||
state.done = state.failed = state.total = 0
|
||||
state.started_at = None
|
||||
@@ -1330,17 +1478,62 @@ class ProductSuiteTab(QWidget):
|
||||
def _account_for_alias(self, alias):
|
||||
return next((account for account in self.accounts if account.alias == alias), None)
|
||||
|
||||
def _valid_context(self, state, *, show_message=True):
|
||||
if not state.account_alias:
|
||||
def _state_project(self, state, *, include_deleted=False):
|
||||
if state is None or state.project_id is None:
|
||||
return None
|
||||
try:
|
||||
project = image_studio.get_project(
|
||||
state.project_id,
|
||||
path=self.db_path,
|
||||
include_deleted=include_deleted,
|
||||
)
|
||||
except Exception as exc:
|
||||
self._status("商品套图项目读取失败:%s" % _user_error(exc), "danger")
|
||||
return None
|
||||
if project is not None:
|
||||
state.project_binding_state = project.binding_state
|
||||
return project
|
||||
|
||||
def _sync_state_project_binding(self, state):
|
||||
return self._state_project(state)
|
||||
|
||||
def _is_draft_state(self, state):
|
||||
if state is None:
|
||||
return False
|
||||
if state.project_binding_state in image_studio.PROJECT_BINDING_STATES:
|
||||
return state.project_binding_state == image_studio.PROJECT_BINDING_DRAFT
|
||||
project = self._state_project(state)
|
||||
return image_studio.is_draft_project(project)
|
||||
|
||||
def _has_account_context(self, state, *, show_message=True):
|
||||
if state is None or not state.account_alias:
|
||||
if show_message:
|
||||
self._message("未选择账号", "请先在顶部选择账号。")
|
||||
return False
|
||||
if self._account_for_alias(state.account_alias) is None:
|
||||
if show_message:
|
||||
self._message("账号不可用", "所选账号不存在,请到④账号管理刷新账号。")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _valid_context(self, state, *, show_message=True):
|
||||
if not self._has_account_context(state, show_message=show_message):
|
||||
return False
|
||||
if not state.item_id or not state.item_id.isdigit():
|
||||
if show_message:
|
||||
self._message("商品ID无效", "请输入正确的数字商品ID。")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _ensure_project_for_local_work(self, state):
|
||||
if state is None:
|
||||
return None
|
||||
if state.project_id is not None:
|
||||
return self._state_project(state)
|
||||
if state.item_id:
|
||||
return self._bind_project(state)
|
||||
return self._create_draft_project(state)
|
||||
|
||||
def _bind_project(self, state, *, load_existing=False):
|
||||
if not self._valid_context(state):
|
||||
return None
|
||||
@@ -1359,6 +1552,7 @@ class ProductSuiteTab(QWidget):
|
||||
self._message("创建套图任务失败", _user_error(exc))
|
||||
return None
|
||||
state.project_id = int(project.id)
|
||||
state.project_binding_state = project.binding_state
|
||||
if load_existing and previous_id != state.project_id:
|
||||
state.prompt = str(project.draft_prompt or "")
|
||||
state.settings = product_suite.normalize_suite_settings(
|
||||
@@ -1368,6 +1562,29 @@ class ProductSuiteTab(QWidget):
|
||||
self._load_state(state)
|
||||
return project
|
||||
|
||||
def _create_draft_project(self, state):
|
||||
if not self._has_account_context(state):
|
||||
return None
|
||||
account = self._account_for_alias(state.account_alias)
|
||||
try:
|
||||
project = image_studio.create_draft_project(
|
||||
account,
|
||||
draft_prompt=state.prompt,
|
||||
path=self.db_path,
|
||||
)
|
||||
except Exception as exc:
|
||||
self._message("创建临时草稿失败", _user_error(exc))
|
||||
return None
|
||||
state.project_id = int(project.id)
|
||||
state.project_binding_state = project.binding_state
|
||||
state.item_id = ""
|
||||
self._persist_state(state)
|
||||
self._set_task_title(state)
|
||||
if state is self._displayed_state:
|
||||
self._update_context_actions(state)
|
||||
self._status("已创建临时草稿,可继续添加本地图片", "info")
|
||||
return project
|
||||
|
||||
def _persist_state(self, state):
|
||||
if state.project_id is None:
|
||||
return
|
||||
@@ -1422,20 +1639,55 @@ class ProductSuiteTab(QWidget):
|
||||
state.prompt = self.prompt_edit.toPlainText()
|
||||
|
||||
def _update_context_actions(self, state):
|
||||
is_draft = self._is_draft_state(state)
|
||||
self.pull_button.setEnabled(
|
||||
self._valid_context(state, show_message=False) and state.pull_worker is None
|
||||
state is not None
|
||||
and state.pull_worker is None
|
||||
and (is_draft or self._valid_context(state, show_message=False))
|
||||
)
|
||||
self.pull_button.setToolTip(
|
||||
"需要先绑定正式商品ID" if is_draft else "拉取蝦皮主图"
|
||||
)
|
||||
self._refresh_item_id_hint(state)
|
||||
self._refresh_add_images_action(state)
|
||||
|
||||
def _refresh_item_id_hint(self, state):
|
||||
displayed_item_id = (
|
||||
self.item_id_edit.text().strip()
|
||||
if state is self._displayed_state
|
||||
else str(state.item_id or "").strip()
|
||||
)
|
||||
item_id_invalid = bool(displayed_item_id and not image_studio.is_formal_item_id(displayed_item_id))
|
||||
is_bound = bool(
|
||||
state
|
||||
and state.project_binding_state == image_studio.PROJECT_BINDING_BOUND
|
||||
and image_studio.is_formal_item_id(state.item_id)
|
||||
and not item_id_invalid
|
||||
)
|
||||
self.item_id_hint_label.setVisible(not is_bound)
|
||||
if self._is_draft_state(state):
|
||||
tooltip = "当前为临时草稿,可添加本地图片和生成套图;绑定正式商品ID后才能拉取蝦皮主图"
|
||||
elif item_id_invalid:
|
||||
tooltip = "商品ID只能输入数字;修正后可绑定或拉取蝦皮主图"
|
||||
else:
|
||||
tooltip = "未绑定正式商品ID时,可添加本地图片和生成套图,但不能拉取蝦皮主图"
|
||||
self.item_id_hint_label.setToolTip(tooltip)
|
||||
|
||||
def _refresh_add_images_action(self, state):
|
||||
asset_count = len(self._original_assets(state, include_missing=True)) if state else 0
|
||||
full = asset_count >= image_studio_images.MAX_ORIGINAL_ASSETS
|
||||
generation_running = bool(state and state.generation_running())
|
||||
importing = bool(state and state.import_worker is not None)
|
||||
self.add_images_button.setEnabled(
|
||||
state is not None and not full and not generation_running and not importing
|
||||
state is not None
|
||||
and self._has_account_context(state, show_message=False)
|
||||
and not full
|
||||
and not generation_running
|
||||
and not importing
|
||||
)
|
||||
if full:
|
||||
if not self._has_account_context(state, show_message=False):
|
||||
tooltip = "请先选择账号"
|
||||
elif full:
|
||||
tooltip = "已达到16张商品原图上限"
|
||||
elif generation_running:
|
||||
tooltip = "生成中不能添加商品原图"
|
||||
@@ -1536,8 +1788,12 @@ class ProductSuiteTab(QWidget):
|
||||
if state is None:
|
||||
return
|
||||
self._save_controls_to_state(state)
|
||||
if state.project_id is None and self._bind_project(state) is None:
|
||||
state.import_created_draft = False
|
||||
created_project = state.project_id is None
|
||||
project = self._ensure_project_for_local_work(state)
|
||||
if project is None:
|
||||
return
|
||||
state.import_created_draft = created_project and image_studio.is_draft_project(project)
|
||||
if state.import_worker is not None:
|
||||
self._status("当前任务正在添加图片,请稍候", "warning")
|
||||
return
|
||||
@@ -1561,9 +1817,29 @@ class ProductSuiteTab(QWidget):
|
||||
state.import_thread = self._start_thread(worker, "商品套图添加原图")
|
||||
self._status("正在后台添加商品原图", "info")
|
||||
|
||||
def _discard_empty_import_draft(self, state):
|
||||
if not state.import_created_draft:
|
||||
return
|
||||
state.import_created_draft = False
|
||||
project_id = state.project_id
|
||||
if project_id is None:
|
||||
return
|
||||
try:
|
||||
project = image_studio.discard_empty_draft_project(project_id, path=self.db_path)
|
||||
except Exception as exc:
|
||||
self._status("清理空临时草稿失败:%s" % _user_error(exc), "danger")
|
||||
return
|
||||
if project is not None and project.deleted_at is not None:
|
||||
state.project_id = None
|
||||
state.project_binding_state = ""
|
||||
state.item_id = ""
|
||||
self._set_task_title(state)
|
||||
self._status("未添加有效图片,已丢弃空临时草稿", "warning")
|
||||
|
||||
def _on_import_failed(self, state, error):
|
||||
state.import_worker = None
|
||||
state.import_thread = None
|
||||
self._discard_empty_import_draft(state)
|
||||
self._status("添加商品原图失败:%s" % _user_error(error), "danger")
|
||||
if state is self._displayed_state:
|
||||
self._refresh_add_images_action(state)
|
||||
@@ -1571,6 +1847,7 @@ class ProductSuiteTab(QWidget):
|
||||
def _on_import_finished(self, state, result):
|
||||
state.import_worker = None
|
||||
state.import_thread = None
|
||||
self._discard_empty_import_draft(state)
|
||||
if result.get("ok") is False:
|
||||
self._message("添加商品原图失败", _user_error(result.get("error")))
|
||||
else:
|
||||
@@ -1687,6 +1964,12 @@ class ProductSuiteTab(QWidget):
|
||||
if state is None:
|
||||
return
|
||||
self._save_controls_to_state(state)
|
||||
if self._is_draft_state(state):
|
||||
self._message(
|
||||
"无法拉取蝦皮主图",
|
||||
"当前为临时项目,无法拉取蝦皮主图。\n请先输入正式商品ID后再试。",
|
||||
)
|
||||
return
|
||||
if not self._valid_context(state):
|
||||
return
|
||||
if state.pull_worker is not None:
|
||||
@@ -1728,6 +2011,7 @@ class ProductSuiteTab(QWidget):
|
||||
project = result.get("project")
|
||||
if project is not None:
|
||||
state.project_id = int(project.id)
|
||||
state.project_binding_state = project.binding_state
|
||||
assets = [
|
||||
asset
|
||||
for asset in (result.get("assets") or [])
|
||||
@@ -1938,7 +2222,7 @@ class ProductSuiteTab(QWidget):
|
||||
context = (
|
||||
"商品ID:%s;平台:%s;国家地区:%s;输出语言:%s。当前已有要求:%s"
|
||||
% (
|
||||
state.item_id or "未填写",
|
||||
state.item_id or "未绑定商品",
|
||||
state.settings["platform"],
|
||||
state.settings["country"],
|
||||
state.settings["language"],
|
||||
@@ -2043,7 +2327,7 @@ class ProductSuiteTab(QWidget):
|
||||
return False
|
||||
if state is self._displayed_state:
|
||||
self._save_controls_to_state(state)
|
||||
if state.project_id is None and self._bind_project(state) is None:
|
||||
if self._ensure_project_for_local_work(state) is None:
|
||||
return False
|
||||
local_assets = [asset for asset in self._original_assets(state) if _asset_usable(asset)]
|
||||
if not local_assets:
|
||||
@@ -2056,7 +2340,7 @@ class ProductSuiteTab(QWidget):
|
||||
local_assets,
|
||||
state.prompt,
|
||||
state.settings,
|
||||
state.item_id,
|
||||
state.item_id or "未绑定商品",
|
||||
))
|
||||
if not specs:
|
||||
self._message("生成数量为0", "请至少把一个套图分类的数量设为1。")
|
||||
@@ -2393,7 +2677,7 @@ class ProductSuiteTab(QWidget):
|
||||
def open_project_folder(self, checked=False):
|
||||
state = self._displayed_state
|
||||
if state is None or state.project_id is None:
|
||||
self._message("未绑定商品", "请先选择账号并输入商品ID。")
|
||||
self._message("未创建套图任务", "请先选择账号并添加本地图片,或输入商品ID后拉取蝦皮主图。")
|
||||
return
|
||||
project = image_studio.get_project(state.project_id, path=self.db_path)
|
||||
if project is None:
|
||||
|
||||
Reference in New Issue
Block a user