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:
@@ -26,49 +26,34 @@
|
||||
</div>
|
||||
</div>
|
||||
<span class="username-text">{{ username || '未登录' }}</span>
|
||||
<a :href="logoutHref" class="logout-link">退出</a>
|
||||
<router-link :to="logoutHref" class="logout-link">退出</router-link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="entrances">
|
||||
<a
|
||||
<router-link
|
||||
v-if="canShow.amazon"
|
||||
:href="amazonHref || undefined"
|
||||
:to="amazonHref"
|
||||
class="entrance-btn"
|
||||
:class="{ disabled: !amazonHref }"
|
||||
data-column-key="brand"
|
||||
>亚马逊</a>
|
||||
<a
|
||||
v-else-if="showAmazonDisabled"
|
||||
class="entrance-btn disabled"
|
||||
data-column-key="brand"
|
||||
>亚马逊</a>
|
||||
>亚马逊</router-link>
|
||||
|
||||
<a
|
||||
<router-link
|
||||
v-if="canShow.video"
|
||||
:href="videoHref || undefined"
|
||||
:to="videoHref"
|
||||
class="entrance-btn wb"
|
||||
:class="{ disabled: !videoHref }"
|
||||
data-column-key="wb"
|
||||
>视频</a>
|
||||
<a
|
||||
v-else-if="showVideoDisabled"
|
||||
class="entrance-btn wb disabled"
|
||||
data-column-key="wb"
|
||||
>视频</a>
|
||||
>视频</router-link>
|
||||
|
||||
<a
|
||||
<router-link
|
||||
v-if="canShow.image"
|
||||
:href="imageHref || undefined"
|
||||
:to="imageHref"
|
||||
class="entrance-btn image"
|
||||
:class="{ disabled: !imageHref }"
|
||||
data-column-key="image"
|
||||
>图片</a>
|
||||
<a
|
||||
v-else-if="showImageDisabled"
|
||||
class="entrance-btn image disabled"
|
||||
data-column-key="image"
|
||||
>图片</a>
|
||||
>图片</router-link>
|
||||
</div>
|
||||
|
||||
<div class="toast" :class="{ show: Boolean(toastText) }">{{ toastText }}</div>
|
||||
@@ -77,8 +62,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { requestGetJson } from '@/shared/api/http'
|
||||
import { buildJavaUrl } from '@/shared/api/url'
|
||||
import { restoreLoginUser } from '@/shared/auth/ensure-auth'
|
||||
import { getCurrentUserAppColumnRaw, type PermissionMenuItem } from '@/shared/api/permission'
|
||||
import { getPywebviewApi, isDesktopRuntime } from '@/shared/bridges/pywebview'
|
||||
import { resolvePageHref } from '@/shared/page-prefix'
|
||||
|
||||
@@ -90,7 +75,7 @@ const updateReady = ref(false)
|
||||
const updateFileUrl = ref('')
|
||||
const toastText = ref('')
|
||||
|
||||
// 桌面端原 Flask /logout 已随瘦身下线:统一跳登录页并清本地 token(login.html?logout=1)
|
||||
// 桌面端原 Flask /logout 已随瘦身下线:统一跳登录页并清本地 token(/login?logout=1)
|
||||
const logoutHref = computed(() => `${resolvePageHref('/new_web_source/login.html')}?logout=1`)
|
||||
|
||||
type PermissionState = { amazon: boolean; video: boolean; image: boolean }
|
||||
@@ -105,10 +90,6 @@ const imageHref = computed(() => resolvePageHref('/new_web_source/image.html'))
|
||||
const canShow = computed(() =>
|
||||
permissionUnavailable.value ? { amazon: true, video: true, image: true } : permissionState.value,
|
||||
)
|
||||
// dev 环境没有 /image 等旧路径资源时,给出“仅桌面”灰卡而不是消失
|
||||
const showAmazonDisabled = computed(() => !permissionUnavailable.value && permissionState.value.amazon && !amazonHref.value)
|
||||
const showVideoDisabled = computed(() => !permissionUnavailable.value && permissionState.value.video && !videoHref.value)
|
||||
const showImageDisabled = computed(() => !permissionUnavailable.value && permissionState.value.image && !imageHref.value)
|
||||
|
||||
function getUid() {
|
||||
const raw = typeof window === 'undefined' ? '' : window.localStorage.getItem('uid') || ''
|
||||
@@ -116,7 +97,7 @@ function getUid() {
|
||||
return Number.isFinite(numeric) && numeric > 0 ? String(numeric) : ''
|
||||
}
|
||||
|
||||
function computePermissionState(items: Array<Record<string, unknown>>): PermissionState {
|
||||
function computePermissionState(items: PermissionMenuItem[]): PermissionState {
|
||||
const allowedKeys: string[] = []
|
||||
const allowedColumnKeys: string[] = []
|
||||
const allowedRootKeys: string[] = []
|
||||
@@ -126,7 +107,8 @@ function computePermissionState(items: Array<Record<string, unknown>>): Permissi
|
||||
if (key && list.indexOf(key) < 0) list.push(key)
|
||||
}
|
||||
;(items || []).forEach((item) => {
|
||||
push(allowedColumnKeys, item.columnKey || item.column_key)
|
||||
push(allowedColumnKeys, item.columnKey)
|
||||
push(allowedColumnKeys, item.column_key)
|
||||
const root = item.rootColumnKey || item.root_column_key
|
||||
if (root) {
|
||||
hasRootMetadata = true
|
||||
@@ -154,19 +136,10 @@ async function loadPermissions() {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const resp = await requestGetJson<{ success: boolean; data?: Array<Record<string, unknown>> }>(
|
||||
buildJavaUrl(`/api/admin/permission-users/${currentUid}/column-permissions?menuType=app`),
|
||||
)
|
||||
if (resp?.success && Array.isArray(resp.data)) {
|
||||
permissionState.value = computePermissionState(resp.data)
|
||||
try {
|
||||
window.localStorage.setItem(`app_column_permissions:${currentUid}`, JSON.stringify(resp.data))
|
||||
} catch {
|
||||
/* 忽略 */
|
||||
}
|
||||
} else {
|
||||
permissionUnavailable.value = true
|
||||
}
|
||||
// 经 shared/api/permission 层拉取原始权限菜单项(含本地缓存写入;接口失败走 catch)
|
||||
const items = await getCurrentUserAppColumnRaw()
|
||||
permissionState.value = computePermissionState(items)
|
||||
permissionUnavailable.value = false
|
||||
} catch {
|
||||
permissionUnavailable.value = true
|
||||
}
|
||||
@@ -177,21 +150,10 @@ async function loadCurrentUser() {
|
||||
try {
|
||||
const localName = typeof window === 'undefined' ? '' : window.localStorage.getItem('username') || ''
|
||||
if (localName) username.value = localName
|
||||
const token = typeof window === 'undefined' ? '' : window.localStorage.getItem('aiimage_auth_token') || ''
|
||||
const resp = await window.fetch('/newApi/check_login', {
|
||||
credentials: 'include',
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
})
|
||||
const body = await resp.json().catch(() => ({}))
|
||||
if (body?.success && body.data) {
|
||||
if (body.data.username) username.value = body.data.username
|
||||
if (body.data.userId) {
|
||||
try {
|
||||
window.localStorage.setItem('uid', String(body.data.userId))
|
||||
} catch {
|
||||
/* 忽略 */
|
||||
}
|
||||
}
|
||||
// 经 shared/auth 层恢复登录快照(页面不得直连 /newApi,见 shared-api-allowlist 测试约定)
|
||||
const restored = await restoreLoginUser()
|
||||
if (restored?.username) {
|
||||
username.value = restored.username
|
||||
}
|
||||
} catch {
|
||||
/* 保留本地用户名 */
|
||||
|
||||
Reference in New Issue
Block a user