fix: update frontend

This commit is contained in:
koko
2026-04-28 21:42:30 +08:00
parent cd2b437be5
commit 1d9d138423
58 changed files with 462 additions and 37 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>外观专利检测</title>
<script type="module" crossorigin src="/assets/appearance-patent.js"></script>
<link rel="modulepreload" crossorigin href="/assets/pywebview-C66x_2Dh.js">
<link rel="modulepreload" crossorigin href="/assets/brand-BZije8D7.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-Dp5dN8OO.css">
<link rel="stylesheet" crossorigin href="/assets/appearance-patent-BPCrG75P.css">
<link rel="stylesheet" crossorigin href="/assets/el-table-column-Cy4YJvw0.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@@ -0,0 +1 @@
.module-page[data-v-d71413c4]{min-height:100vh;background:#1a1a1a}.main-content[data-v-d71413c4]{display:flex;height:calc(100vh - 56px);min-height:calc(100vh - 56px)}.left-panel[data-v-d71413c4]{width:400px;background:#1e1e1e;padding:20px;overflow-y:auto;border-right:1px solid #2a2a2a}.right-panel[data-v-d71413c4]{flex:1;min-width:0;background:#1a1a1a;display:flex;flex-direction:column}.section-title[data-v-d71413c4],.subsection-title[data-v-d71413c4]{font-size:13px;color:#bbb;margin-bottom:10px}.upload-zone[data-v-d71413c4]{border:1px dashed #3a3a3a;border-radius:10px;padding:18px;background:#252525;margin-bottom:18px}.hint[data-v-d71413c4],.loading-msg[data-v-d71413c4],.files[data-v-d71413c4],.muted[data-v-d71413c4]{color:#888;font-size:12px;line-height:1.5}.link[data-v-d71413c4]{color:#6ea8fe;text-decoration:none}.link[data-v-d71413c4]:hover{color:#9fc5ff}.btns[data-v-d71413c4],.run-row[data-v-d71413c4]{display:flex;gap:10px;flex-wrap:wrap}.opt-btn[data-v-d71413c4],.btn-run[data-v-d71413c4],.btn-delete[data-v-d71413c4],.download[data-v-d71413c4]{border:none;cursor:pointer;border-radius:7px}.opt-btn[data-v-d71413c4]{padding:8px 14px;color:#ccc;background:#2a2a2a;border:1px solid #3a3a3a}.btn-run[data-v-d71413c4]{padding:10px 18px;color:#fff;background:#3498db;font-weight:600}.btn-queue[data-v-d71413c4]{background:#27ae60}.btn-run[data-v-d71413c4]:disabled{opacity:.55;cursor:not-allowed}.selected-files[data-v-d71413c4]{margin-top:14px;color:#999;font-size:12px;word-break:break-all}.selected-files span[data-v-d71413c4]{display:block;margin:4px 0}.prompt-card[data-v-d71413c4]{margin-bottom:18px}.prompt-input[data-v-d71413c4]{width:100%;box-sizing:border-box;resize:vertical;min-height:180px;padding:10px 12px;border:1px solid #333;border-radius:8px;background:#202020;color:#d8d8d8;font-size:12px;line-height:1.6;outline:none}.prompt-input[data-v-d71413c4]:focus{border-color:#3498db}.prompt-default-label[data-v-d71413c4]{margin-top:10px;color:#8d8d8d;font-size:12px}.prompt-preview[data-v-d71413c4]{margin-top:10px;padding:12px;border:1px solid #2a2a2a;border-radius:8px;background:#202020;color:#9ea7b3;font-size:12px;line-height:1.6;white-space:pre-wrap}.parse-card[data-v-d71413c4],.queue-payload[data-v-d71413c4]{margin-top:14px;padding:12px;border:1px solid #2a2a2a;border-radius:8px;background:#202020;color:#b8c1cc;font-size:12px}.queue-payload[data-v-d71413c4]{max-height:220px;overflow:auto;color:#8fd3ff;white-space:pre-wrap}.panel-header[data-v-d71413c4]{padding:16px 20px;border-bottom:1px solid #2a2a2a;font-size:15px;font-weight:600;color:#ddd}.task-list-wrap[data-v-d71413c4]{flex:1;padding:16px 20px;overflow:auto}.clean-result-summary[data-v-d71413c4]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:16px}.summary-card[data-v-d71413c4]{padding:14px 16px;border:1px solid #2a2a2a;border-radius:8px;background:#1e1e1e}.summary-card strong[data-v-d71413c4]{display:block;margin-top:8px;color:#eaf4ff;font-size:22px}.summary-label[data-v-d71413c4]{color:#8d8d8d;font-size:12px}.result-list-wrap[data-v-d71413c4]{border:1px solid #2a2a2a;border-radius:8px;background:#1e1e1e;min-height:180px;margin:0 0 16px}.result-list-header[data-v-d71413c4]{display:flex;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #2a2a2a;color:#ddd;font-size:14px}.empty-tasks[data-v-d71413c4]{color:#666;font-size:13px;padding:18px;text-align:center}.result-table[data-v-d71413c4]{--el-table-bg-color: #222;--el-table-tr-bg-color: #222;--el-table-header-bg-color: #2a2a2a;--el-table-text-color: #ccc;--el-table-border-color: #333}.task-list[data-v-d71413c4]{list-style:none;margin:0;padding:12px}.task-item[data-v-d71413c4]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid #2a2a2a;border-radius:8px;margin-bottom:8px;background:#222}.left[data-v-d71413c4]{flex:1;min-width:0}.id[data-v-d71413c4]{color:#e0e0e0;font-size:13px;font-weight:600}.task-right[data-v-d71413c4]{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.status[data-v-d71413c4]{padding:4px 10px;border-radius:6px;font-size:12px}.status.success[data-v-d71413c4]{background:#2ecc712e;color:#2ecc71}.status.failed[data-v-d71413c4]{background:#e74c3c2e;color:#ff6b6b}.status.running[data-v-d71413c4]{background:#3498db2e;color:#3498db}.result-hint[data-v-d71413c4]{margin-top:6px;color:#e0b96d}.download[data-v-d71413c4]{padding:6px 10px;color:#d6ecff;background:#3498db2e}.btn-delete[data-v-d71413c4]{padding:6px 10px;color:#ff8f8f;background:#e74c3c1f}@media(max-width:1100px){.main-content[data-v-d71413c4]{flex-direction:column;height:auto}.left-panel[data-v-d71413c4]{width:100%;border-right:none;border-bottom:1px solid #2a2a2a}.clean-result-summary[data-v-d71413c4]{grid-template-columns:repeat(2,minmax(0,1fr))}}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{aZ as r}from"./pywebview-C66x_2Dh.js";const n="";function a(e){return r(`${n}/api/brand/expand-folder-recursive`,{folder:e})}export{a as e};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
const e=[{value:"SearchSuppressed",label:"在搜索结果中禁止显示"},{value:"ApprovalRequired",label:"需要批准"},{value:"Active",label:"在售"},{value:"DetailPageRemoved",label:"详情页面已删除"}];export{e as L};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,7 @@
<link rel="modulepreload" crossorigin href="/assets/listingFilters-BpGOU_pJ.js">
<link rel="stylesheet" crossorigin href="/assets/pywebview-Dp5dN8OO.css">
<link rel="stylesheet" crossorigin href="/assets/listingFilters-CK58rX4v.css">
<link rel="stylesheet" crossorigin href="/assets/shop-match-BmHsgsuC.css">
<link rel="stylesheet" crossorigin href="/assets/shop-match-BiZP8IGM.css">
<link rel="stylesheet" crossorigin href="/assets/el-table-column-Cy4YJvw0.css">
<link rel="stylesheet" crossorigin href="/assets/el-input-xaztPnzw.css">
</head>

321
ts.py Normal file
View File

@@ -0,0 +1,321 @@
import json
import os
import sys
import threading
import time
from pathlib import Path
from types import SimpleNamespace
import requests
ROOT_DIR = Path(__file__).resolve().parent
APP_DIR = ROOT_DIR / "app"
sys.path.insert(0, str(APP_DIR))
from amazon.main import TaskMonitor # noqa: E402
from amazon.patrol_delete import InventoryManage, PatrolDeleteTask # noqa: E402
from config import ( # noqa: E402
DELETE_BRAND_API_BASE,
JAVA_API_BASE,
JSON_TASK_QUEUE,
ZN_COMPANY,
ZN_PASSWORD,
ZN_USERNAME,
runing_task,
)
from main import WindowAPI # noqa: E402
SHOP_NAME = "郭亚芳"
USER_ID = 1
COUNTRIES = [item.strip() for item in os.environ.get("PATROL_COUNTRIES", "德国,西班牙,意大利").split(",") if item.strip()]
TS_MODE = os.environ.get("TS_MODE", "full").strip().lower()
class EventSlot:
def __iadd__(self, handler):
return self
class DummyWindow:
def __init__(self):
self.events = SimpleNamespace(maximized=EventSlot(), restored=EventSlot())
def log(message, payload=None):
print(f"[ts] {message}", flush=True)
if payload is not None:
print(json.dumps(payload, ensure_ascii=False, indent=2), flush=True)
def unwrap(response):
log(f"HTTP {response.request.method} {response.url} -> {response.status_code}")
response.raise_for_status()
data = response.json()
if not data.get("success"):
raise RuntimeError(data.get("message") or data.get("error") or response.text)
return data.get("data")
def get_match_item():
data = unwrap(
requests.post(
f"{JAVA_API_BASE}/api/patrol-delete/match-shops",
json={"user_id": USER_ID, "shop_names": [SHOP_NAME]},
timeout=40,
)
)
items = data.get("items") or []
if not items:
raise RuntimeError("match-shops did not return any items")
item = items[0]
log("match result", item)
if not item.get("matched"):
raise RuntimeError(f"shop is not matched: {item}")
return item
def create_java_task(match_item):
country_sections = [
{
"country": country,
"rows": [
{
"status": "全部",
"quantity": "",
"deleteQuantity": "",
"processStatus": "",
}
],
}
for country in COUNTRIES
]
cart_ratios = [{"country": country, "ratio": ""} for country in COUNTRIES]
task_item = {
"shopName": match_item.get("shopName") or SHOP_NAME,
"matched": bool(match_item.get("matched")),
"shopId": match_item.get("shopId"),
"platform": match_item.get("platform"),
"companyName": match_item.get("companyName"),
"matchStatus": match_item.get("matchStatus"),
"matchMessage": match_item.get("matchMessage"),
"countrySections": country_sections,
"cartRatios": cart_ratios,
}
data = unwrap(
requests.post(
f"{JAVA_API_BASE}/api/patrol-delete/tasks",
json={"user_id": USER_ID, "items": [task_item]},
timeout=40,
)
)
log("created task", data)
return data
def build_queue_payload(created_task):
task_id = created_task["taskId"]
created_item = (created_task.get("items") or [])[0]
sections = created_item.get("countrySections") or []
ratios = created_item.get("cartRatios") or []
payload = {
"type": "patrol-delete-run",
"ts": int(time.time() * 1000),
"data": {
"taskId": task_id,
"user_id": USER_ID,
"source": "ts-real-patrol-delete",
"items": [
{
"shopName": created_item.get("shopName") or SHOP_NAME,
"shopId": created_item.get("shopId"),
"platform": created_item.get("platform"),
"companyName": created_item.get("companyName"),
"matched": created_item.get("matched"),
"matchStatus": created_item.get("matchStatus"),
"matchMessage": created_item.get("matchMessage"),
}
],
"template_rows": [
{
"shopName": created_item.get("shopName") or SHOP_NAME,
"countries": [
{
"country": section.get("country"),
"status": (section.get("rows") or [{}])[0].get("status", ""),
"quantity": (section.get("rows") or [{}])[0].get("quantity", ""),
"deleteQuantity": (section.get("rows") or [{}])[0].get("deleteQuantity", ""),
"processStatus": (section.get("rows") or [{}])[0].get("processStatus", ""),
}
for section in sections
],
"cartRatios": ratios,
}
],
"country_sections": sections,
"cart_ratios": ratios,
},
}
log("queue payload", payload)
return payload
def make_monitor_without_startup_kill():
monitor = object.__new__(TaskMonitor)
monitor.running = True
monitor.user_info = {
"company": ZN_COMPANY,
"username": ZN_USERNAME,
"password": ZN_PASSWORD,
}
monitor.chunk_index = 1
monitor.max_workers = 1
monitor.executor = None
return monitor
def poll_java_task(task_id):
for _ in range(60):
try:
data = unwrap(
requests.post(
f"{JAVA_API_BASE}/api/patrol-delete/tasks/progress/batch",
json={"taskIds": [task_id]},
timeout=40,
)
)
log("java progress", data)
items = data.get("items") or []
if items and items[0].get("taskStatus") in {"SUCCESS", "FAILED", "COMPLETED"}:
return items[0]
except Exception as exc:
log(f"java progress poll failed: {exc}")
time.sleep(5)
return None
def drain_queue():
while True:
try:
JSON_TASK_QUEUE.get_nowait()
except Exception:
return
def make_user_info(company_name=None):
return {
"company": company_name or ZN_COMPANY,
"username": ZN_USERNAME,
"password": ZN_PASSWORD,
}
def complete_draft_payload_rows(tags):
rows = []
for item in tags:
rows.append(
{
"status": "补全草稿",
"tag": item.get("tag", ""),
"type": "completeDraft",
"quantity": str(item.get("quantity") if item.get("quantity") is not None else ""),
"deleteQuantity": str(item.get("quantity") if item.get("quantity") is not None else ""),
"processStatus": "已完成",
"raw_text": item.get("raw_text", ""),
}
)
return rows
def run_complete_draft_real_browser():
log("starting real complete-draft read test")
log("scope", {"shop": SHOP_NAME, "countries": COUNTRIES, "user_id": USER_ID})
match_item = get_match_item()
driver = InventoryManage(make_user_info(match_item.get("companyName")))
try:
browser = driver.open_shop(match_item.get("shopName") or SHOP_NAME)
if not browser or browser == "店铺不存在":
raise RuntimeError(f"open shop failed: {browser}")
if driver.need_login():
raise RuntimeError("shop requires login; complete-draft read test cannot continue safely")
normalized_sections = []
for country in COUNTRIES:
log(f"switching country for complete-draft read: {country}")
if not driver.switch_to_country(country):
raise RuntimeError(f"switch country failed: {country}")
tags = driver.get_complete_draft_quick_view_tags(match_item.get("shopName") or SHOP_NAME, country)
payload_rows = complete_draft_payload_rows(tags)
normalized = PatrolDeleteTask._normalize_country_sections_for_result(
[{"country": country, "rows": payload_rows}]
)[0]
normalized_sections.append(normalized)
log(
"complete draft country result",
{
"country": country,
"rawTags": tags,
"payloadRowsBeforeNormalize": payload_rows,
"normalizedForSubmit": normalized,
},
)
log("complete draft normalized sections", normalized_sections)
finally:
try:
driver.close_store()
except Exception as exc:
log(f"close store failed: {exc}")
def main():
if TS_MODE == "complete-draft":
run_complete_draft_real_browser()
return
log("starting real patrol-delete automation")
log("scope", {"shop": SHOP_NAME, "countries": COUNTRIES, "user_id": USER_ID})
log("java api base", {"JAVA_API_BASE": JAVA_API_BASE, "DELETE_BRAND_API_BASE": DELETE_BRAND_API_BASE})
log("ziniao env", {"company": bool(ZN_COMPANY), "username": bool(ZN_USERNAME), "password": bool(ZN_PASSWORD)})
drain_queue()
match_item = get_match_item()
created_task = create_java_task(match_item)
task_id = created_task["taskId"]
payload = build_queue_payload(created_task)
monitor = make_monitor_without_startup_kill()
monitor_thread = threading.Thread(target=monitor.start, daemon=True)
monitor_thread.start()
time.sleep(0.5)
enqueue_result = WindowAPI(DummyWindow()).enqueue_json(payload)
log("enqueue result", enqueue_result)
if not enqueue_result.get("success"):
raise RuntimeError(f"enqueue failed: {enqueue_result}")
deadline = time.time() + 60 * 45
last_status = None
while time.time() < deadline:
state = runing_task.get(task_id)
if state and state != last_status:
log("python task state", state)
last_status = dict(state)
if state and state.get("status") in {"completed", "failed", "stopped"}:
break
time.sleep(5)
monitor.running = False
JSON_TASK_QUEUE.put({"type": "__stop__"})
monitor_thread.join(timeout=10)
log("python final state", runing_task.get(task_id))
final_java = poll_java_task(task_id)
log("java final", final_java)
log("finished", {"taskId": task_id})
if __name__ == "__main__":
main()