feat(web): 前端 SPA 化 + 工具页任务面板统一与历史批量删除
- SPA 化:22 个 MPA html 入口与 *-main.ts 合并为 index.html + vue-router(URL 无 .html 后缀), 页面跳转全部 router-link,/new_web_source/xxx.html 旧路径归一为 /xxx - 任务面板统一:共享 TaskCenterPanel/TaskItemCard/TaskStatCards/HistoryTaskLayer, 16 个工具页右侧统一为统计卡 + 当前任务 + 历史任务弹层(任务ID/开始/结束/状态必展示) - 历史记录支持单条删除 + 批量勾选删除(确认框/全选/失败提示) - Java 7 模块(dedupe/convert/split/productrisk/shopmatch/pricetrack/deletebrand) history 接口补齐任务时间字段(VO+Service,复用 biz_file_task 列) - 图片工作台/API 层(brand/permission/user)既有未提交改动一并提交
This commit is contained in:
@@ -86,45 +86,9 @@
|
||||
</aside>
|
||||
|
||||
<section class="right-panel">
|
||||
<div class="panel-header">
|
||||
<span>变体采集任务</span>
|
||||
<div class="panel-actions">
|
||||
<button type="button" class="btn-refresh" @click="refreshList">刷新任务状态</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-list-wrap">
|
||||
<div v-if="!currentTasks.length" class="empty-tasks">暂无变体采集任务</div>
|
||||
<ul v-else class="task-list">
|
||||
<li v-for="item in currentTasks" :key="`vc-${item.task_id}`" class="task-item">
|
||||
<div class="left">
|
||||
<span class="id">任务 {{ item.task_id }}</span>
|
||||
<div v-if="taskMeta(item).source" class="files">来源:{{ taskMeta(item).source }}</div>
|
||||
<div class="files">采集国家:{{ countryText(item) || '-' }}</div>
|
||||
<div class="files">更新时间:{{ item.update_time || formatDateTime(item.updated_at) }}</div>
|
||||
<div v-if="latestMessage(item)" class="files">{{ latestMessage(item) }}</div>
|
||||
</div>
|
||||
<div class="task-right">
|
||||
<span class="status" :class="statusClass(item.status)">{{ statusText(item.status) }}</span>
|
||||
<button type="button" class="act-btn" @click="refreshTask(item)">更新</button>
|
||||
<button type="button" class="act-btn" @click="showDetail(item)">详情</button>
|
||||
<button
|
||||
v-if="hasResultFiles(item)"
|
||||
type="button"
|
||||
class="act-btn ok"
|
||||
@click="downloadResult(item)"
|
||||
>下载结果</button>
|
||||
<button
|
||||
v-if="hasSourceFile(item)"
|
||||
type="button"
|
||||
class="act-btn"
|
||||
@click="downloadSource(item)"
|
||||
>下载源文件</button>
|
||||
<button type="button" class="act-btn warn" @click="reexportTask(item)">重新导出</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="currentTasks.length" class="pagination">
|
||||
<div class="panel-toolbar">
|
||||
<button type="button" class="btn-refresh" @click="refreshList">刷新任务状态</button>
|
||||
<div v-if="totalCount" class="toolbar-pagination">
|
||||
<span class="page-info">共 {{ totalCount }} 条 · 第 {{ currentPage }} / {{ totalPages }} 页</span>
|
||||
<div class="page-btns">
|
||||
<button type="button" class="page-btn" :disabled="currentPage <= 1" @click="goPage(currentPage - 1)">上一页</button>
|
||||
@@ -132,6 +96,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TaskCenterPanel
|
||||
title="变体采集任务"
|
||||
:cards="variantCards"
|
||||
:current-items="currentTaskViews"
|
||||
:history-items="historyTaskViews"
|
||||
current-title="当前任务"
|
||||
current-empty-text="暂无运行中的变体采集任务"
|
||||
history-empty-text="暂无历史记录"
|
||||
>
|
||||
<template #item-actions="{ item }">
|
||||
<template v-if="itemSource(item)">
|
||||
<button type="button" class="act-btn" @click="refreshTask(itemSource(item))">更新</button>
|
||||
<button type="button" class="act-btn" @click="showDetail(itemSource(item))">详情</button>
|
||||
<button v-if="hasResultFiles(itemSource(item))" type="button" class="act-btn ok"
|
||||
@click="downloadResult(itemSource(item))">下载结果</button>
|
||||
<button v-if="hasSourceFile(itemSource(item))" type="button" class="act-btn"
|
||||
@click="downloadSource(itemSource(item))">下载源文件</button>
|
||||
<button type="button" class="act-btn warn" @click="reexportTask(itemSource(item))">重新导出</button>
|
||||
</template>
|
||||
</template>
|
||||
<template #history-item-actions="{ item }">
|
||||
<template v-if="itemSource(item)">
|
||||
<button type="button" class="act-btn" @click="refreshTask(itemSource(item))">更新</button>
|
||||
<button type="button" class="act-btn" @click="showDetail(itemSource(item))">详情</button>
|
||||
<button v-if="hasResultFiles(itemSource(item))" type="button" class="act-btn ok"
|
||||
@click="downloadResult(itemSource(item))">下载结果</button>
|
||||
<button v-if="hasSourceFile(itemSource(item))" type="button" class="act-btn"
|
||||
@click="downloadSource(itemSource(item))">下载源文件</button>
|
||||
<button type="button" class="act-btn warn" @click="reexportTask(itemSource(item))">重新导出</button>
|
||||
</template>
|
||||
</template>
|
||||
</TaskCenterPanel>
|
||||
</section>
|
||||
</div>
|
||||
</AmazonToolPageShell>
|
||||
@@ -166,6 +162,8 @@
|
||||
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,
|
||||
@@ -214,6 +212,59 @@ const canSubmit = computed(() =>
|
||||
inputMode.value === 'asin' ? asinLines.value.length > 0 : selectedFiles.value.length > 0,
|
||||
)
|
||||
|
||||
// ---- 右侧任务面板统一视图(TaskCenterPanel)----
|
||||
/** 当前任务:排队中(0) / 执行中(1) 的任务(当前分页内) */
|
||||
const currentTaskViews = computed<TaskItemView[]>(() =>
|
||||
currentTasks.value.filter((item) => isBusy(item.status)).map(toVariantTaskView),
|
||||
)
|
||||
|
||||
/** 历史任务:已完成(2) / 失败(3) 的任务(当前分页内) */
|
||||
const historyTaskViews = computed<TaskItemView[]>(() =>
|
||||
currentTasks.value.filter((item) => !isBusy(item.status)).map(toVariantTaskView),
|
||||
)
|
||||
|
||||
const variantCards = computed<TaskStatCard[]>(() => [
|
||||
{ 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 variantStatusClass(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'
|
||||
if (numeric === 3 || /failed|error/i.test(String(status))) return 'failed'
|
||||
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
|
||||
|
||||
@@ -651,26 +702,12 @@ onBeforeUnmount(() => {
|
||||
.btn-run { padding: 10px 18px; color: #f5f8fc; background: #27ae60; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; }
|
||||
.btn-run:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.loading-msg { margin-top: 10px; }
|
||||
.panel-header { display: flex; justify-content: space-between; align-items: center; padding: 14px 20px; border-bottom: 1px solid #2e3a52; color: #f5f8fc; font-size: 15px; font-weight: 600; }
|
||||
.panel-actions { display: flex; gap: 8px; }
|
||||
.panel-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 20px 0; }
|
||||
.btn-refresh { padding: 6px 12px; color: #c8d2e2; background: #2e3a52; border: 1px solid #3e4a62; border-radius: 6px; cursor: pointer; font-size: 12px; }
|
||||
.task-list-wrap { flex: 1; padding: 16px 20px; overflow: auto; }
|
||||
.empty-tasks { color: #5e6878; font-size: 13px; padding: 28px; text-align: center; }
|
||||
.task-list { list-style: none; margin: 0; padding: 0; }
|
||||
.task-item { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding: 12px 14px; margin-bottom: 8px; border: 1px solid #2e3a52; border-radius: 8px; background: #1c2333; }
|
||||
.left { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
||||
.id { color: #f5f8fc; font-size: 13px; font-weight: 600; }
|
||||
.task-right { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
|
||||
.status { padding: 4px 10px; border-radius: 6px; font-size: 12px; white-space: nowrap; }
|
||||
.status.pending { background: rgba(149, 165, 166, 0.18); color: #a0acbe; }
|
||||
.status.running { background: rgba(52, 152, 219, 0.18); color: #3498db; }
|
||||
.status.success { background: rgba(46, 204, 113, 0.18); color: #2ecc71; }
|
||||
.status.failed { background: rgba(231, 76, 60, 0.18); color: #ff6b6b; }
|
||||
.status.cancelled { background: rgba(149, 165, 166, 0.18); color: #a0acbe; }
|
||||
.toolbar-pagination { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }
|
||||
.act-btn { padding: 4px 9px; border-radius: 6px; font-size: 12px; background: #2e3a52; color: #c8d2e2; border: 1px solid #3e4a62; cursor: pointer; }
|
||||
.act-btn.ok { background: rgba(52, 152, 219, 0.18); color: #69b6ff; border-color: transparent; }
|
||||
.act-btn.warn { background: rgba(230, 162, 60, 0.14); color: #e6a23c; border-color: transparent; }
|
||||
.pagination { display: flex; justify-content: space-between; align-items: center; padding: 10px 0 4px; }
|
||||
.page-info { color: #5e6878; font-size: 12px; }
|
||||
.page-btns { display: flex; gap: 8px; }
|
||||
.page-btn { padding: 5px 12px; border-radius: 6px; background: #2e3a52; color: #c8d2e2; border: 1px solid #3e4a62; cursor: pointer; font-size: 12px; }
|
||||
|
||||
Reference in New Issue
Block a user