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:
2026-09-08 10:02:55 +08:00
parent abcfa5bef7
commit e248b6e43a
125 changed files with 7482 additions and 4304 deletions
@@ -2,7 +2,7 @@
<div v-if="currentView === 'menu'" class="image-video-page">
<header class="page-header">
<span class="header-title">数富AI</span>
<a class="back-link" :href="goHome()">返回首页</a>
<router-link class="back-link" :to="goHome()">返回首页</router-link>
</header>
<main class="entrances" :class="{ 'is-loading': permissionsLoading }" aria-label="视频与图片入口">
@@ -21,12 +21,9 @@
<button v-if="hasMenuPermission('mix-video')" type="button" class="entrance-btn" @click="showSoon('混剪')">
混剪
</button>
<a v-if="hasMenuPermission('image') && isDesktop" class="entrance-btn" href="/new_web_source/image.html">
<router-link v-if="hasMenuPermission('image')" class="entrance-btn" to="/image">
图片
</a>
<button v-else-if="hasMenuPermission('image')" type="button" class="entrance-btn" @click="showDesktopOnly('图片工作台')">
图片
</button>
</router-link>
</main>
<div class="toast" :class="{ show: Boolean(statusText), error: statusType === 'error' }">
@@ -56,7 +53,7 @@
:show-actions="false"
>
<template #logoExtra>
<button type="button" class="delivery-page__top-return" @click="currentView = 'menu'">
<button type="button" class="delivery-page__top-return" @click="goBackToMenu">
返回二级菜单
</button>
</template>
@@ -71,30 +68,28 @@
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import PageShell from '@/components/layout/PageShell.vue'
import BrandTopBar from '@/pages/brand/components/BrandTopBar.vue'
import DeliveryVideoWorkspace from '@/pages/image-video/components/DeliveryVideoWorkspace.vue'
import { getCurrentUserAppColumnKeys } from '@/shared/api/permission'
import { getPywebviewApi } from '@/shared/bridges/pywebview'
import { isDesktopClientPage } from '@/shared/page-prefix'
import DownloadProgressPanel from '@/shared/components/DownloadProgressPanel.vue'
type ViewMode = 'menu' | 'delivery'
const isDesktop = isDesktopClientPage()
const route = useRoute()
const router = useRouter()
function goHome() {
// 桌面端首页是 Flask /home(重定向到 Vue home);dev 下是 MPA 的 /home.html
return isDesktop ? '/home' : '/home.html'
// 桌面端与 Web 统一为 SPA 首页路径
return '/home'
}
function showDesktopOnly(name: string) {
showStatus(`${name}为桌面客户端功能,请在本地客户端中打开`, 'error')
}
const currentView = ref<ViewMode>('menu')
/** 视图由路由 query 驱动:?view=delivery 为带货视频工作台,否则二级菜单页 */
const currentView = ref<ViewMode>(route.query.view === 'delivery' ? 'delivery' : 'menu')
const allowedColumnKeys = ref(new Set<string>())
const permissionsLoading = ref(true)
const launching = ref(false)
@@ -139,10 +134,22 @@ onMounted(async () => {
}
})
watch(
() => route.query.view,
(view) => {
currentView.value = view === 'delivery' ? 'delivery' : 'menu'
},
)
function openDeliveryWorkspace() {
currentView.value = 'delivery'
router.push({ path: '/image-video', query: { view: 'delivery' } })
}
function goBackToMenu() {
router.push({ path: '/image-video' })
}
function formatBytes(value: number) {
const size = Number(value || 0)
if (!Number.isFinite(size) || size <= 0) return '未知大小'