diff --git a/frontend-vue/public/bg.jpg b/frontend-vue/public/bg.jpg index 2775db88..8b54ae52 100644 Binary files a/frontend-vue/public/bg.jpg and b/frontend-vue/public/bg.jpg differ diff --git a/frontend-vue/public/bg.webp b/frontend-vue/public/bg.webp new file mode 100644 index 00000000..8c499409 Binary files /dev/null and b/frontend-vue/public/bg.webp differ diff --git a/frontend-vue/src/pages/amazon/AmazonConsolePage.vue b/frontend-vue/src/pages/amazon/AmazonConsolePage.vue index aa1e1c1f..1457ce8b 100644 --- a/frontend-vue/src/pages/amazon/AmazonConsolePage.vue +++ b/frontend-vue/src/pages/amazon/AmazonConsolePage.vue @@ -363,14 +363,14 @@ onMounted(async () => { font-size: 11px; } -/* Hero 右侧工作台卡(350 宽 / 10px 圆角,对齐 gemini-code hero-right-card) */ +/* Hero 右侧工作台卡(370 宽 / 10px 圆角,对齐 gemini-code hero-right-card) */ .hero-card { - width: 350px; + width: 370px; flex-shrink: 0; display: flex; flex-direction: column; justify-content: space-between; - padding: 20px; + padding: 22px; border: 1px solid #3e4a62; border-radius: 10px; background: #2b3447; @@ -406,15 +406,26 @@ onMounted(async () => { background: #f59e0b; } +.hero-card-btn-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.hero-card-btn-row .hero-card-sub { + margin: 0; +} + .hero-card-caption { color: #a0acbe; - font-size: 11px; + font-size: 13px; } .hero-card-title { margin: 0 0 12px; color: #f5f8fc; - font-size: 17px; + font-size: 20px; font-weight: 700; line-height: 1.4; } @@ -428,7 +439,7 @@ onMounted(async () => { .hero-card-sub { margin: 0; color: #c8d2e2; - font-size: 11px; + font-size: 13px; line-height: 1.5; } @@ -448,12 +459,12 @@ onMounted(async () => { display: inline-flex; align-items: center; gap: 6px; - padding: 8px 18px; + padding: 9px 20px; border: none; border-radius: 6px; background: linear-gradient(135deg, #f59e0b, #d97706); color: #ffffff; - font-size: 12px; + font-size: 13px; font-weight: 700; font-family: inherit; cursor: pointer; @@ -486,16 +497,16 @@ onMounted(async () => { .workflow-title { color: #f5f8fc; - font-size: 14px; + font-size: 16px; font-weight: 700; } .workflow-badge { - padding: 2px 8px; + padding: 3px 10px; border-radius: 4px; background: #3a2a10; color: #f59e0b; - font-size: 10px; + font-size: 11px; font-weight: 700; white-space: nowrap; } @@ -503,7 +514,7 @@ onMounted(async () => { .workflow-hint { margin-left: auto; color: #a0acbe; - font-size: 11px; + font-size: 12px; white-space: nowrap; } @@ -548,25 +559,25 @@ onMounted(async () => { display: flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; + width: 34px; + height: 34px; border-radius: 50%; margin-bottom: 6px; - font-size: 12px; + font-size: 13px; font-weight: 700; } .step-name { margin-bottom: 2px; color: #c8d2e2; - font-size: 11px; + font-size: 13px; font-weight: 700; text-align: center; } .step-desc { color: #a0acbe; - font-size: 9px; + font-size: 11px; text-align: center; } @@ -600,19 +611,19 @@ onMounted(async () => { .group-name { color: #f5f8fc; - font-size: 16px; + font-size: 18px; font-weight: 700; } .group-desc { color: #a0acbe; - font-size: 11px; + font-size: 12px; } .group-count { margin-left: auto; color: #a0acbe; - font-size: 11px; + font-size: 12px; } /* 卡片墙(CARD 底 + 2px #2A3344 边框 + 10px 圆角 min-height 165,对齐 gemini-code tool-card) */ diff --git a/frontend-vue/src/pages/amazon/components/AmazonToolPageShell.vue b/frontend-vue/src/pages/amazon/components/AmazonToolPageShell.vue index 4ae26db1..b9b1877c 100644 --- a/frontend-vue/src/pages/amazon/components/AmazonToolPageShell.vue +++ b/frontend-vue/src/pages/amazon/components/AmazonToolPageShell.vue @@ -107,9 +107,9 @@ const backHref = computed(() => `${resolvePageHref('/new_web_source/amazon-conso .tool-head__desc { min-width: 0; - max-width: 650px; + max-width: 760px; color: #a0acbe; - font-size: 12px; + font-size: 14px; line-height: 1.5; overflow: hidden; text-overflow: ellipsis; diff --git a/frontend-vue/src/pages/amazon/components/AmazonTopBar.vue b/frontend-vue/src/pages/amazon/components/AmazonTopBar.vue index d0f35baa..2404bf3a 100644 --- a/frontend-vue/src/pages/amazon/components/AmazonTopBar.vue +++ b/frontend-vue/src/pages/amazon/components/AmazonTopBar.vue @@ -1,5 +1,7 @@ @@ -62,8 +62,8 @@ const username = ref('admin') /** 分类 tab 点击回到工具台对应分组 */ const toolbarHref = resolvePageHref('/new_web_source/amazon-console.html') -/** ⚙ 采集工作台:直达现有真实功能页(对齐 gemini-code 顶栏 ⚙ 入口) */ -const workHref = resolvePageHref('/new_web_source/collect-data.html') +/** 返回桌面入口首页(亚马逊/视频/图片入口) */ +const homeHref = resolvePageHref('/new_web_source/home.html') function onTabClick(key: 'front' | 'ops' | 'logi') { emit('change', key) @@ -114,6 +114,27 @@ onMounted(async () => { text-decoration: none; } +/* 返回首页按钮(顶栏最左,入口页见 DesktopHomePage) */ +.home-back { + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border: 1px solid #3e4a62; + border-radius: 6px; + color: #c8d2e2; + font-size: 13px; + text-decoration: none; + white-space: nowrap; + transition: all 0.2s; +} + +.home-back:hover { + background: #2b3447; + color: #f5f8fc; +} + .brand-logo { display: flex; align-items: center; @@ -161,11 +182,11 @@ onMounted(async () => { display: flex; flex-direction: column; align-items: center; - width: 140px; - height: 42px; + width: 150px; + height: 44px; background: transparent; color: #c8d2e2; - font-size: 13px; + font-size: 15px; font-weight: 700; text-decoration: none; transition: all 0.2s; @@ -175,7 +196,7 @@ onMounted(async () => { display: flex; align-items: center; gap: 8px; - padding: 6px 14px; + padding: 7px 14px; border-radius: 8px; transition: background 0.2s; } @@ -194,8 +215,8 @@ onMounted(async () => { } .cat-dot { - width: 8px; - height: 8px; + width: 9px; + height: 9px; border-radius: 50%; flex-shrink: 0; } @@ -204,11 +225,11 @@ onMounted(async () => { display: inline-flex; align-items: center; justify-content: center; - padding: 1px 7px; + padding: 1px 8px; border-radius: 12px; background: #343f55; color: #a0acbe; - font-size: 10px; + font-size: 11px; font-weight: 700; } @@ -230,47 +251,13 @@ onMounted(async () => { flex-shrink: 0; } -.help-btn { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 22px; - height: 22px; +.admin-name { color: #c8d2e2; - font-size: 16px; - font-weight: 700; - text-decoration: none; - cursor: pointer; - padding: 4px 8px; -} - -.help-btn:hover { - color: #f5f8fc; -} - -.admin-chip { - display: inline-flex; - align-items: center; - gap: 8px; - color: #c8d2e2; - font-size: 12px; + font-size: 13px; font-weight: 700; white-space: nowrap; } -.admin-badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 22px; - height: 20px; - padding: 0 4px; - background: #2a6b4f; - color: #ffffff; - font-size: 10px; - font-weight: 700; -} - @media (max-width: 1100px) { .amazon-top { flex-wrap: wrap; diff --git a/frontend-vue/src/pages/brand/components/BrandApiSecretSettingsButton.vue b/frontend-vue/src/pages/brand/components/BrandApiSecretSettingsButton.vue index 8926eab2..3b9ec8cd 100644 --- a/frontend-vue/src/pages/brand/components/BrandApiSecretSettingsButton.vue +++ b/frontend-vue/src/pages/brand/components/BrandApiSecretSettingsButton.vue @@ -391,12 +391,12 @@ loadStates() /* topbar 变体:纯文字「🔑 密钥设置」,hover 整块变 CARD2(对齐主程序顶栏) */ .secret-settings-trigger.variant-topbar { height: auto; - padding: 4px 10px; + padding: 6px 12px; border: 0; border-radius: 0; background: transparent; color: #c8d2e2; - font-size: 11px; + font-size: 14px; font-weight: 400; transition: background 0.12s ease, color 0.12s ease; } @@ -408,7 +408,7 @@ loadStates() .secret-settings-trigger.variant-topbar .secret-settings-icon { color: #c8d2e2; - font-size: 12px; + font-size: 15px; } .secret-settings-icon { diff --git a/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue b/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue index e9923113..8ecd638a 100644 --- a/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue @@ -6,7 +6,7 @@
-
+
+ 任务队列 -
+
+
+
暂无变体采集任务
+
+
+
+ 任务 {{ item.task_id }} + {{ statusText(item.status) }} +
+
+
国家:{{ countryText(item) || '-' }}
+
任务创建时间:{{ formatDateTime(item.create_time || item.updated_at) }}
+
最后更新时间:{{ formatDateTime(item.update_time || item.updated_at) }}
+
来源:{{ taskMeta(item).source }}
+
+
{{ latestMessage(item) }}
+
+ + + + + +
+
+
+
- - - -
@@ -162,8 +160,6 @@ import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import AmazonToolPageShell from '@/pages/amazon/components/AmazonToolPageShell.vue' -import TaskCenterPanel from '@/shared/components/tasks/TaskCenterPanel.vue' -import type { TaskItemView, TaskStatCard } from '@/shared/components/tasks/types' import { getPywebviewApi, type VariantTaskListItem, @@ -190,7 +186,7 @@ const PAGE_SIZE = 8 const inputMode = ref<'asin' | 'file'>('asin') const asinText = ref('') const selectedFiles = ref([]) -const selectedCountries = ref([]) +const selectedCountryCode = ref('DE') const submitting = ref(false) const tasks = reactive>({}) @@ -212,29 +208,15 @@ const canSubmit = computed(() => inputMode.value === 'asin' ? asinLines.value.length > 0 : selectedFiles.value.length > 0, ) -// ---- 右侧任务面板统一视图(TaskCenterPanel)---- -/** 当前任务:排队中(0) / 执行中(1) 的任务(当前分页内) */ -const currentTaskViews = computed(() => - currentTasks.value.filter((item) => isBusy(item.status)).map(toVariantTaskView), -) - -/** 历史任务:已完成(2) / 失败(3) 的任务(当前分页内) */ -const historyTaskViews = computed(() => - currentTasks.value.filter((item) => !isBusy(item.status)).map(toVariantTaskView), -) - -const variantCards = computed(() => [ - { label: '运行中任务', value: currentTaskViews.value.length }, - { label: '已结束任务', value: historyTaskViews.value.length }, - { label: '成功任务', value: historyTaskViews.value.filter((view) => view.statusClass === 'success').length }, - { label: '失败任务', value: historyTaskViews.value.filter((view) => view.statusClass === 'failed').length }, -]) - -function itemSource(item: TaskItemView): VariantEntry { - return item.source as VariantEntry +function countryLabel(code: string) { + return COUNTRY_OPTIONS.find((row) => row.code === code)?.label || code } -function variantStatusClass(status?: number | string) { +function taskMeta(item: VariantEntry) { + return item.meta || {} +} + +function statusClass(status?: number | string) { const numeric = Number(status) if (numeric === 1 || /running|processing/i.test(String(status))) return 'running' if (numeric === 2 || /success|completed|done/i.test(String(status))) return 'success' @@ -242,29 +224,6 @@ function variantStatusClass(status?: number | string) { return 'pending' } -function toVariantTaskView(item: VariantEntry): TaskItemView { - const extraLines: string[] = [] - const source = item.meta?.source || '' - if (source) extraLines.push(`来源:${source}`) - extraLines.push(`采集国家:${countryText(item) || '-'}`) - const updateText = item.update_time || formatDateTime(item.updated_at) - if (updateText) extraLines.push(`更新时间:${updateText}`) - extraLines.push(latestMessage(item)) - return { - key: `vc-${item.task_id}`, - title: `任务 ${item.task_id}`, - taskId: item.task_id, - // 桥接列表未提供任务开始时间,开始时间占位为 '-' - startedAt: '-', - // 桥接仅提供最近更新时间,作为结束时间的近似展示 - finishedAt: formatDateTime(item.update_time), - statusText: statusText(item.status), - statusClass: variantStatusClass(item.status), - extraLines, - source: item, - } -} - let autoTimer: number | null = null let refreshTimer: number | null = null @@ -369,8 +328,8 @@ async function submitTask() { ElMessage.warning('请在桌面客户端中打开本页面执行变体采集') return } - if (!selectedCountries.value.length) { - ElMessage.warning('请至少选择一个采集国家') + if (!selectedCountryCode.value) { + ElMessage.warning('请选择采集国家') return } if (!uid()) { @@ -386,7 +345,7 @@ async function submitTask() { files?: string[] } = { mode: inputMode.value, - countries: selectedCountries.value, + countries: selectedCountryCode.value ? [selectedCountryCode.value] : [], uid: uid(), } if (inputMode.value === 'asin') { @@ -473,6 +432,7 @@ async function loadTasks(page?: number) { const entry = tasks[item.task_id] entry.status = item.status if (item.update_time) entry.update_time = item.update_time + if (item.create_time) entry.create_time = item.create_time entry.res_type = item.res_type entry.res_mes = item.res_mes entry.file_url = item.file_url @@ -542,6 +502,26 @@ async function downloadSource(item: VariantTaskListItem) { } } +/** + * 删除任务:当前客户端/后端未提供删除接口(batch 仅 add/query/list/export), + * 先提示用户操作不可用,避免出现"已删除"假象 + */ +async function deleteTask(item: VariantTaskListItem) { + const api = getPywebviewApi() + const hasVcDelete = Boolean((api as Record | null)?.['vc_delete_task']) + if (!hasVcDelete) { + ElMessage.warning('当前客户端不支持删除任务,可在后台保存或稍后清理') + return + } + try { + await (api as { vc_delete_task: (taskId: string) => Promise<{ success?: boolean; error?: string }> }) + .vc_delete_task(item.task_id) + void loadTasks(currentPage.value || 1) + } catch (error) { + ElMessage.error(error instanceof Error ? error.message : '删除失败') + } +} + async function reexportTask(item: VariantTaskListItem) { const bridge = getPywebviewApi() if (!bridge?.vc_export_task) { @@ -657,48 +637,66 @@ onBeforeUnmount(() => {