fix: 领取任务后立即刷新列表 (#107)
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
- 图标用 `FluentIcon`,**不得用表情符号**。
|
- 图标用 `FluentIcon`,**不得用表情符号**。
|
||||||
- 页面 `objectName` 固定为 `pddTaskPage`,不要改。
|
- 页面 `objectName` 固定为 `pddTaskPage`,不要改。
|
||||||
|
|
||||||
注意:“开始自动获取”和“重新执行”会真的操作手机。重新执行当前只允许
|
注意:“自动获取”和“重新执行”会真的操作手机。重新执行当前只允许
|
||||||
采集任务,采购任务必须在事件层拦截,不能从本入口下单。
|
采集任务,采购任务必须在事件层拦截,不能从本入口下单。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -387,10 +387,10 @@ class PDDTaskPage(QWidget):
|
|||||||
|
|
||||||
def _build_command_area(self) -> None:
|
def _build_command_area(self) -> None:
|
||||||
self.autoFetchButton = PrimaryPushButton(
|
self.autoFetchButton = PrimaryPushButton(
|
||||||
FIF.PLAY, "开始自动获取", self
|
FIF.PLAY, "自动获取", self
|
||||||
)
|
)
|
||||||
self.autoFetchButton.setMinimumWidth(132)
|
self.autoFetchButton.setMinimumWidth(132)
|
||||||
self.autoFetchButton.setAccessibleName("开始自动获取 PDD 任务")
|
self.autoFetchButton.setAccessibleName("自动获取 PDD 任务")
|
||||||
|
|
||||||
self.taskTypeLabel = CaptionLabel("任务类型", self)
|
self.taskTypeLabel = CaptionLabel("任务类型", self)
|
||||||
self.taskTypeCombo = ComboBox(self)
|
self.taskTypeCombo = ComboBox(self)
|
||||||
@@ -410,8 +410,8 @@ class PDDTaskPage(QWidget):
|
|||||||
self.keywordInput = SearchLineEdit(self)
|
self.keywordInput = SearchLineEdit(self)
|
||||||
self.keywordInput.setPlaceholderText("任务编号、商品编号或商品标题")
|
self.keywordInput.setPlaceholderText("任务编号、商品编号或商品标题")
|
||||||
self.keywordInput.setClearButtonEnabled(True)
|
self.keywordInput.setClearButtonEnabled(True)
|
||||||
self.keywordInput.setMinimumWidth(190)
|
self.keywordInput.setMinimumWidth(150)
|
||||||
self.keywordInput.setMaximumWidth(480)
|
self.keywordInput.setMaximumWidth(240)
|
||||||
self.keywordInput.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
self.keywordInput.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
self.keywordInput.setAccessibleName("任务关键词")
|
self.keywordInput.setAccessibleName("任务关键词")
|
||||||
|
|
||||||
@@ -626,7 +626,7 @@ class PDDTaskPage(QWidget):
|
|||||||
"""更新自动获取按钮;state 为 stopped/starting/running/stopping。"""
|
"""更新自动获取按钮;state 为 stopped/starting/running/stopping。"""
|
||||||
|
|
||||||
states = {
|
states = {
|
||||||
"stopped": ("开始自动获取", FIF.PLAY, True),
|
"stopped": ("自动获取", FIF.PLAY, True),
|
||||||
"starting": ("正在启动", FIF.SYNC, False),
|
"starting": ("正在启动", FIF.SYNC, False),
|
||||||
"running": ("停止自动获取", FIF.PAUSE, True),
|
"running": ("停止自动获取", FIF.PAUSE, True),
|
||||||
"stopping": ("正在停止", FIF.PAUSE, False),
|
"stopping": ("正在停止", FIF.PAUSE, False),
|
||||||
@@ -635,6 +635,7 @@ class PDDTaskPage(QWidget):
|
|||||||
self.autoFetchButton.setText(text)
|
self.autoFetchButton.setText(text)
|
||||||
self.autoFetchButton.setIcon(icon)
|
self.autoFetchButton.setIcon(icon)
|
||||||
self.autoFetchButton.setEnabled(enabled)
|
self.autoFetchButton.setEnabled(enabled)
|
||||||
|
self.autoFetchButton.setAccessibleName(f"{text} PDD 任务")
|
||||||
|
|
||||||
def set_engine_status(self, text: str) -> None:
|
def set_engine_status(self, text: str) -> None:
|
||||||
"""更新页面底部的任务引擎状态说明。"""
|
"""更新页面底部的任务引擎状态说明。"""
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ class ClaimTaskWorker(QObject):
|
|||||||
purchase_reconcile_factory=self._purchase_reconcile_factory,
|
purchase_reconcile_factory=self._purchase_reconcile_factory,
|
||||||
cancelled=lambda: self._cancelled,
|
cancelled=lambda: self._cancelled,
|
||||||
device_connection_checker=self._device_connection_checker,
|
device_connection_checker=self._device_connection_checker,
|
||||||
|
task_saved=self.taskSaved.emit,
|
||||||
)
|
)
|
||||||
result = dispatcher.execute_one()
|
result = dispatcher.execute_one()
|
||||||
if not self._cancelled or result.kind == "cancelled":
|
if not self._cancelled or result.kind == "cancelled":
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ class TaskDispatcher:
|
|||||||
purchase_reconcile_factory: Optional[PurchaseReconcileFactory] = None,
|
purchase_reconcile_factory: Optional[PurchaseReconcileFactory] = None,
|
||||||
cancelled: Callable[[], bool] = lambda: False,
|
cancelled: Callable[[], bool] = lambda: False,
|
||||||
device_connection_checker: Optional[Callable[[str], None]] = None,
|
device_connection_checker: Optional[Callable[[str], None]] = None,
|
||||||
|
task_saved: Callable[[str], None] = lambda _task_id: None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._gateway = gateway
|
self._gateway = gateway
|
||||||
self._repository = repository
|
self._repository = repository
|
||||||
@@ -151,6 +152,7 @@ class TaskDispatcher:
|
|||||||
self._purchase_mode = purchase_mode
|
self._purchase_mode = purchase_mode
|
||||||
self._reconcile_factory = purchase_reconcile_factory
|
self._reconcile_factory = purchase_reconcile_factory
|
||||||
self._cancelled = cancelled
|
self._cancelled = cancelled
|
||||||
|
self._task_saved = task_saved
|
||||||
self._device_connection_checker = (
|
self._device_connection_checker = (
|
||||||
device_connection_checker
|
device_connection_checker
|
||||||
or AndroidDeviceService().require_connected
|
or AndroidDeviceService().require_connected
|
||||||
@@ -251,10 +253,12 @@ class TaskDispatcher:
|
|||||||
)
|
)
|
||||||
if remote is None:
|
if remote is None:
|
||||||
return TaskDispatchOutcome("no_task", "暂无可领取的任务")
|
return TaskDispatchOutcome("no_task", "暂无可领取的任务")
|
||||||
|
saved_now = False
|
||||||
try:
|
try:
|
||||||
self._repository.add_claimed_task(
|
self._repository.add_claimed_task(
|
||||||
admin_task_to_new_claimed_task(remote)
|
admin_task_to_new_claimed_task(remote)
|
||||||
)
|
)
|
||||||
|
saved_now = True
|
||||||
except DuplicateTaskError:
|
except DuplicateTaskError:
|
||||||
pass
|
pass
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -266,6 +270,8 @@ class TaskDispatcher:
|
|||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"任务 {remote.task_id} 已领取,但未能保存到本地"
|
f"任务 {remote.task_id} 已领取,但未能保存到本地"
|
||||||
)
|
)
|
||||||
|
if saved_now:
|
||||||
|
self._task_saved(remote.task_id)
|
||||||
|
|
||||||
if task.task_type is TaskType.COLLECT:
|
if task.task_type is TaskType.COLLECT:
|
||||||
service = CollectTaskService(
|
service = CollectTaskService(
|
||||||
|
|||||||
@@ -80,6 +80,25 @@ class TaskTableModelCheckTests(unittest.TestCase):
|
|||||||
self.assertEqual(page.checkedCountLabel.text(), "已选 0 条")
|
self.assertEqual(page.checkedCountLabel.text(), "已选 0 条")
|
||||||
page.deleteLater()
|
page.deleteLater()
|
||||||
|
|
||||||
|
def test_auto_fetch_text_and_keyword_width(self):
|
||||||
|
page = PDDTaskPage()
|
||||||
|
|
||||||
|
self.assertEqual(page.autoFetchButton.text(), "自动获取")
|
||||||
|
self.assertEqual(
|
||||||
|
page.autoFetchButton.accessibleName(), "自动获取 PDD 任务"
|
||||||
|
)
|
||||||
|
self.assertEqual(page.keywordInput.minimumWidth(), 150)
|
||||||
|
self.assertEqual(page.keywordInput.maximumWidth(), 240)
|
||||||
|
|
||||||
|
page.set_auto_fetch_state("running")
|
||||||
|
self.assertEqual(page.autoFetchButton.text(), "停止自动获取")
|
||||||
|
self.assertEqual(
|
||||||
|
page.autoFetchButton.accessibleName(), "停止自动获取 PDD 任务"
|
||||||
|
)
|
||||||
|
page.set_auto_fetch_state("stopped")
|
||||||
|
self.assertEqual(page.autoFetchButton.text(), "自动获取")
|
||||||
|
page.deleteLater()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -182,6 +182,20 @@ class FakeCollector:
|
|||||||
return FakeCollectResult()
|
return FakeCollectResult()
|
||||||
|
|
||||||
|
|
||||||
|
class BlockingCollector:
|
||||||
|
"""让测试能在新任务落库后、采集结束前检查界面。"""
|
||||||
|
|
||||||
|
def __init__(self, started, release):
|
||||||
|
self.started = started
|
||||||
|
self.release = release
|
||||||
|
|
||||||
|
def collect(self, _task):
|
||||||
|
self.started.set()
|
||||||
|
if not self.release.wait(timeout=3):
|
||||||
|
raise RuntimeError("测试没有及时结束阻塞采集")
|
||||||
|
return FakeCollectResult()
|
||||||
|
|
||||||
|
|
||||||
def fake_collect_factory(*_args):
|
def fake_collect_factory(*_args):
|
||||||
return FakeCollector()
|
return FakeCollector()
|
||||||
|
|
||||||
@@ -1040,6 +1054,43 @@ class PDDTaskPageEventTest(unittest.TestCase):
|
|||||||
events.shutdown()
|
events.shutdown()
|
||||||
page.deleteLater()
|
page.deleteLater()
|
||||||
|
|
||||||
|
def test_new_claim_refreshes_table_before_collection_finishes(self):
|
||||||
|
gateway = RecordingClaimGateway(collect_admin_task("COL-EARLY"))
|
||||||
|
collect_started = threading.Event()
|
||||||
|
release_collect = threading.Event()
|
||||||
|
page = PDDTaskPage()
|
||||||
|
events = PDDTaskPageEvent(
|
||||||
|
page,
|
||||||
|
self.repository,
|
||||||
|
claim_gateway=gateway,
|
||||||
|
settings_repository=self._saved_settings(),
|
||||||
|
collect_service_factory=(
|
||||||
|
lambda *_args: BlockingCollector(
|
||||||
|
collect_started, release_collect
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
page.autoFetchRequested.emit()
|
||||||
|
try:
|
||||||
|
self.assertTrue(wait_until(self.app, collect_started.is_set))
|
||||||
|
self.assertTrue(events._claim_busy)
|
||||||
|
self.assertTrue(
|
||||||
|
wait_until(
|
||||||
|
self.app,
|
||||||
|
lambda: page.taskModel.data_row_count() == 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
page.taskModel.row_at(0).remote_task_id, "COL-EARLY"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
release_collect.set()
|
||||||
|
|
||||||
|
self.assertTrue(wait_until(self.app, lambda: not events._claim_busy))
|
||||||
|
events.shutdown()
|
||||||
|
page.deleteLater()
|
||||||
|
|
||||||
def test_ready_runtime_claims_and_dispatches_purchase_in_worker(self):
|
def test_ready_runtime_claims_and_dispatches_purchase_in_worker(self):
|
||||||
gateway = RecordingClaimGateway(purchase_admin_task())
|
gateway = RecordingClaimGateway(purchase_admin_task())
|
||||||
page = PDDTaskPage()
|
page = PDDTaskPage()
|
||||||
@@ -1091,7 +1142,7 @@ class PDDTaskPageEventTest(unittest.TestCase):
|
|||||||
page.autoFetchRequested.emit()
|
page.autoFetchRequested.emit()
|
||||||
|
|
||||||
self.assertFalse(events._next_cycle_timer.isActive())
|
self.assertFalse(events._next_cycle_timer.isActive())
|
||||||
self.assertEqual(page.autoFetchButton.text(), "开始自动获取")
|
self.assertEqual(page.autoFetchButton.text(), "自动获取")
|
||||||
events.shutdown()
|
events.shutdown()
|
||||||
page.deleteLater()
|
page.deleteLater()
|
||||||
|
|
||||||
@@ -1113,7 +1164,7 @@ class PDDTaskPageEventTest(unittest.TestCase):
|
|||||||
self.assertFalse(page.autoFetchButton.isEnabled())
|
self.assertFalse(page.autoFetchButton.isEnabled())
|
||||||
self.assertTrue(wait_until(self.app, lambda: not events._claim_busy))
|
self.assertTrue(wait_until(self.app, lambda: not events._claim_busy))
|
||||||
self.assertEqual(len(gateway.calls), 1)
|
self.assertEqual(len(gateway.calls), 1)
|
||||||
self.assertEqual(page.autoFetchButton.text(), "开始自动获取")
|
self.assertEqual(page.autoFetchButton.text(), "自动获取")
|
||||||
events.shutdown()
|
events.shutdown()
|
||||||
page.deleteLater()
|
page.deleteLater()
|
||||||
|
|
||||||
@@ -1142,7 +1193,7 @@ class PDDTaskPageEventTest(unittest.TestCase):
|
|||||||
|
|
||||||
page.autoFetchRequested.emit()
|
page.autoFetchRequested.emit()
|
||||||
self.assertTrue(wait_until(self.app, lambda: not events._claim_busy))
|
self.assertTrue(wait_until(self.app, lambda: not events._claim_busy))
|
||||||
self.assertEqual(page.autoFetchButton.text(), "开始自动获取")
|
self.assertEqual(page.autoFetchButton.text(), "自动获取")
|
||||||
events.shutdown()
|
events.shutdown()
|
||||||
page.deleteLater()
|
page.deleteLater()
|
||||||
|
|
||||||
@@ -1307,7 +1358,7 @@ class PDDTaskPageEventTest(unittest.TestCase):
|
|||||||
self.assertEqual(gateway.calls, [])
|
self.assertEqual(gateway.calls, [])
|
||||||
self.assertIn("设置页", page.statusLabel.text())
|
self.assertIn("设置页", page.statusLabel.text())
|
||||||
self.assertFalse(events._auto_fetch_running)
|
self.assertFalse(events._auto_fetch_running)
|
||||||
self.assertEqual(page.autoFetchButton.text(), "开始自动获取")
|
self.assertEqual(page.autoFetchButton.text(), "自动获取")
|
||||||
events.shutdown()
|
events.shutdown()
|
||||||
page.deleteLater()
|
page.deleteLater()
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ class TaskDispatcherTest(unittest.TestCase):
|
|||||||
live_ready=False,
|
live_ready=False,
|
||||||
purchase_mode="dry_run",
|
purchase_mode="dry_run",
|
||||||
device_checker=lambda _serial: None,
|
device_checker=lambda _serial: None,
|
||||||
|
task_saved=lambda _task_id: None,
|
||||||
):
|
):
|
||||||
purchase_factory = None
|
purchase_factory = None
|
||||||
if purchase_ready:
|
if purchase_ready:
|
||||||
@@ -159,6 +160,7 @@ class TaskDispatcherTest(unittest.TestCase):
|
|||||||
),
|
),
|
||||||
purchase_mode=purchase_mode,
|
purchase_mode=purchase_mode,
|
||||||
device_connection_checker=device_checker,
|
device_connection_checker=device_checker,
|
||||||
|
task_saved=task_saved,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_capability_only_includes_purchase_when_adapter_is_ready(self):
|
def test_capability_only_includes_purchase_when_adapter_is_ready(self):
|
||||||
@@ -249,6 +251,36 @@ class TaskDispatcherTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertIn(("purchase", "stopped"), self.calls)
|
self.assertIn(("purchase", "stopped"), self.calls)
|
||||||
|
|
||||||
|
def test_new_task_notifies_after_save_and_before_execution(self):
|
||||||
|
task = AdminTask(
|
||||||
|
"COL-SAVED",
|
||||||
|
TaskType.COLLECT,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
"goods_url": "https://example.test/COL-SAVED",
|
||||||
|
"goods_id": "COL-GOODS",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.gateway.enqueue_task(task, self.client.client_id)
|
||||||
|
notifications = []
|
||||||
|
|
||||||
|
def record_saved(task_id):
|
||||||
|
detail = self.repository.get_task(task_id)
|
||||||
|
notifications.append(
|
||||||
|
(task_id, detail.status if detail else None, list(self.calls))
|
||||||
|
)
|
||||||
|
|
||||||
|
outcome = self._dispatcher(
|
||||||
|
purchase_ready=False, task_saved=record_saved
|
||||||
|
).execute_one()
|
||||||
|
|
||||||
|
self.assertEqual(outcome.kind, "succeeded")
|
||||||
|
self.assertEqual(
|
||||||
|
notifications,
|
||||||
|
[("COL-SAVED", TaskStatus.CLAIMED, [])],
|
||||||
|
)
|
||||||
|
|
||||||
def test_purchase_is_not_claimed_when_runtime_is_not_ready(self):
|
def test_purchase_is_not_claimed_when_runtime_is_not_ready(self):
|
||||||
self.gateway.enqueue_task(purchase_task(), self.client.client_id)
|
self.gateway.enqueue_task(purchase_task(), self.client.client_id)
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ client/
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 主窗口文件 | `src/ui/main_window.py` | `src/ui_main.py`。**注意**:`client/AGENTS.md` 已按 `src/ui_main.py` 写规则,两处不一致,见下方说明 |
|
| 主窗口文件 | `src/ui/main_window.py` | `src/ui_main.py`。**注意**:`client/AGENTS.md` 已按 `src/ui_main.py` 写规则,两处不一致,见下方说明 |
|
||||||
| 目录分层 | domain / application / infrastructure / workers / ui | 平铺在 `src/` 下:`db.py`、`db_schema.py`、`task_repository.py`、`settings_repository.py`、`task_models.py` 等,另有 `src/util/`、`src/demo1/` |
|
| 目录分层 | domain / application / infrastructure / workers / ui | 平铺在 `src/` 下:`db.py`、`db_schema.py`、`task_repository.py`、`settings_repository.py`、`task_models.py` 等,另有 `src/util/`、`src/demo1/` |
|
||||||
| 主按钮文案 | 「开始自动获取」⇄「停止自动获取」 | 已按持续串行模式实现 |
|
| 主按钮文案 | 「自动获取」⇄「停止自动获取」 | 已按持续串行模式实现 |
|
||||||
| Admin 网关 | `AdminGateway` + Mock/HTTP 两实现 | 登记、领取、结果和失败提交已实现 |
|
| Admin 网关 | `AdminGateway` + Mock/HTTP 两实现 | 登记、领取、结果和失败提交已实现 |
|
||||||
| 任务应用服务 | `TaskDispatcher` / `CollectTaskService` / `PurchaseTaskService` | 自动获取先补交 Outbox,再按领取时间执行本地任务,最后按安全能力领取并分派新任务 |
|
| 任务应用服务 | `TaskDispatcher` / `CollectTaskService` / `PurchaseTaskService` | 自动获取先补交 Outbox,再按领取时间执行本地任务,最后按安全能力领取并分派新任务 |
|
||||||
| Outbox 提交 | 从 `outbox_events` 取件重试 | 采集结果与失败已实现,重试不重复采集 |
|
| Outbox 提交 | 从 `outbox_events` 取件重试 | 采集结果与失败已实现,重试不重复采集 |
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
```text
|
```text
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ PDD 任务 │
|
│ PDD 任务 │
|
||||||
│ [开始自动获取] [类型▼] [状态▼] [关键词...] [搜索] [刷新] [已选0条] [重新执行] [重新上报] │
|
│ [自动获取] [类型▼] [状态▼] [关键词...] [搜索] [刷新] [已选0条] [重新执行] [重新上报] │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────┤
|
||||||
│ 类型 │ 商品标题 │ 颜色 │ 尺码 │ 价格 │ 数量 │ 状态 │ 更新时间 │详情│
|
│ 类型 │ 商品标题 │ 颜色 │ 尺码 │ 价格 │ 数量 │ 状态 │ 更新时间 │详情│
|
||||||
│ │
|
│ │
|
||||||
@@ -50,10 +50,10 @@
|
|||||||
|
|
||||||
## 4. 顶部命令区
|
## 4. 顶部命令区
|
||||||
|
|
||||||
### 4.1 开始自动获取 / 停止自动获取
|
### 4.1 自动获取 / 停止自动获取
|
||||||
|
|
||||||
- 使用 `PrimaryPushButton`,是页面唯一主要强调操作。
|
- 使用 `PrimaryPushButton`,是页面唯一主要强调操作。
|
||||||
- 初始文本为“开始自动获取”,图标表达开始。
|
- 初始文本为“自动获取”,图标表达开始。
|
||||||
- 启动成功后文本变为“停止自动获取”,按钮位置和宽度尽量稳定。
|
- 启动成功后文本变为“停止自动获取”,按钮位置和宽度尽量稳定。
|
||||||
- `[必须]` 这两个文案是**按钮标签**。状态栏里的“自动获取:已开启/已停止”说的是**功能状态**,两者不是一回事,不要混改。
|
- `[必须]` 这两个文案是**按钮标签**。状态栏里的“自动获取:已开启/已停止”说的是**功能状态**,两者不是一回事,不要混改。
|
||||||
- 启动过程中禁用重复点击,显示“正在启动”。
|
- 启动过程中禁用重复点击,显示“正在启动”。
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
- 任务类型:`ComboBox`,选项为“全部、采集、采购”。
|
- 任务类型:`ComboBox`,选项为“全部、采集、采购”。
|
||||||
- 任务状态:`ComboBox`,选项为“全部”及标准任务状态。
|
- 任务状态:`ComboBox`,选项为“全部”及标准任务状态。
|
||||||
- 关键词:`SearchLineEdit`,提示“任务编号、商品编号或商品标题”。
|
- 关键词:`SearchLineEdit`,提示“任务编号、商品编号或商品标题”;建议宽度 150~240,随命令区布局自适应。
|
||||||
- 搜索:普通 `PushButton`。
|
- 搜索:普通 `PushButton`。
|
||||||
|
|
||||||
筛选条件之间采用 AND。点击搜索或在关键词输入框按 Enter 执行本地数据库查询,不请求领取任务。活动筛选必须可见,筛选无结果时保留条件并提供清除入口。
|
筛选条件之间采用 AND。点击搜索或在关键词输入框按 Enter 执行本地数据库查询,不请求领取任务。活动筛选必须可见,筛选无结果时保留条件并提供清除入口。
|
||||||
|
|||||||
Reference in New Issue
Block a user