fix(frontend): 菜单点击偶发无响应三处根因——①路由懒加载chunk失败被静默吞掉(新增onError自动整页重载+保险丝防循环) ②首页入口被权限接口阻塞(改乐观显示+缓存TTL10分钟+超时30s→8s) ③菜单hover预取目标页chunk(工具卡片/工作流步骤/顶部导航)

This commit is contained in:
2026-09-09 18:53:34 +08:00
parent 83cb3ff84e
commit 0cf20527a4
6 changed files with 139 additions and 14 deletions
@@ -69,6 +69,7 @@
:class="{ 'workflow-step--disabled': !step.toolId || !stepHref(step.toolId) }"
:style="{ '--gc': currentGroup.color }"
@click="onWorkflowStepClick(step)"
@mouseenter="prefetchRouteChunk(router, stepHref(step.toolId))"
>
<span class="step-circle" :style="circleStyle(currentGroup)">{{ index + 1 }}</span>
<span class="step-name">{{ step.name }}</span>
@@ -88,7 +89,7 @@
<div class="card-grid">
<template v-for="tool in currentGroup.tools" :key="tool.id">
<router-link v-if="resolveHref(tool.href)" :to="resolveHref(tool.href)!" class="tool-card" :style="cardStyle(currentGroup)">
<router-link v-if="resolveHref(tool.href)" :to="resolveHref(tool.href)!" class="tool-card" :style="cardStyle(currentGroup)" @mouseenter="prefetchToolChunk(tool)">
<div class="tool-card-top">
<ToolIcon :name="tool.id" />
<span v-if="tool.tag" class="tool-tag" :style="tagStyle(currentGroup)">{{ tool.tag }}</span>
@@ -129,6 +130,7 @@ import { TUTORIAL_DOWNLOAD_URL } from '@/pages/amazon/download-btn'
import { filterGroupsByPermission, filterWorkflowByPermission, TOOL_GROUPS, TOOL_MAP } from '@/pages/amazon/tool-catalog'
import type { ToolGroup, ToolInfo, VisibleGroup, WorkflowStep } from '@/pages/amazon/tool-catalog'
import { resolvePageHref } from '@/shared/page-prefix'
import { prefetchRouteChunk } from '@/shared/route-prefetch'
import { getPywebviewApi } from '@/shared/bridges/pywebview'
import { getCurrentUserAppColumnKeys } from '@/shared/api/permission'
@@ -187,6 +189,11 @@ function onWorkflowStepClick(step: WorkflowStep) {
showSoon(target?.href ? `${step.name}仅桌面客户端可用` : `${step.name}暂未开通,敬请期待`)
}
/** 卡片 hover 预取目标页 chunk:弱网下点击跳转不再等 chunk 下载 */
function prefetchToolChunk(tool: ToolInfo) {
prefetchRouteChunk(router, resolveHref(tool.href))
}
function onPendingClick(tool: ToolInfo) {
if (!tool.href) {
showSoon(`${tool.name}暂未开通,敬请期待`)
@@ -13,7 +13,7 @@
<div class="nav-section-title">{{ group.label }}</div>
<div class="nav-section-items">
<template v-for="item in group.items" :key="item.key">
<router-link v-if="item.href" :to="item.href" class="nav-item" :class="{ active: active === item.key }">
<router-link v-if="item.href" :to="item.href" class="nav-item" :class="{ active: active === item.key }" @mouseenter="onNavItemHover(item.href)">
{{ item.label }}
</router-link>
<span v-else class="nav-item disabled" :class="{ active: active === item.key }">
@@ -36,7 +36,11 @@ import { computed, onMounted, ref } from 'vue'
import BrandApiSecretSettingsButton from '@/pages/brand/components/BrandApiSecretSettingsButton.vue'
import { getCurrentUserAppColumnKeys } from '@/shared/api/permission'
import { prefetchRouteChunk } from '@/shared/route-prefetch'
import DownloadProgressPanel from '@/shared/components/DownloadProgressPanel.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
type ActiveNavKey =
| 'brand'
@@ -160,6 +164,11 @@ onMounted(async () => {
allowedColumnKeys.value = []
}
})
/** 导航项 hover 预取目标页 chunk:弱网下点击跳转不再等 chunk 下载 */
function onNavItemHover(href?: string) {
prefetchRouteChunk(router, href)
}
</script>
<style scoped>
+13 -11
View File
@@ -1,5 +1,5 @@
<template>
<div class="home-root" :class="{ 'nav-permissions-loading': !permissionReady }">
<div class="home-root">
<header class="header">
<span class="header-title">数富AI</span>
<div class="header-right">
@@ -59,7 +59,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { restoreLoginUser } from '@/shared/auth/ensure-auth'
import { getCurrentUserAppColumnRaw, type PermissionMenuItem } from '@/shared/api/permission'
import { getCurrentUserAppColumnRaw, readCachedAppColumnPermissions, type PermissionMenuItem } from '@/shared/api/permission'
import { useVersionUpdate } from '@/shared/composables/useVersionUpdate'
import { resolvePageHref } from '@/shared/page-prefix'
@@ -73,10 +73,10 @@ const { checking, updating, currentVersion, hasUpdate, canDownload, hint, checke
const logoutHref = computed(() => `${resolvePageHref('/new_web_source/login.html')}?logout=1`)
// 桌面端首页仅保留一级入口:亚马逊 / 视频;图片已收进视频页(/image-video)二级菜单。
// 入口先乐观显示(接口返回后按权限隐藏),避免权限接口慢时首页入口空白"点不了"。
type PermissionState = { amazon: boolean; video: boolean }
const permissionState = ref<PermissionState>({ amazon: false, video: false })
const permissionState = ref<PermissionState>({ amazon: true, video: true })
const permissionUnavailable = ref(false)
const permissionReady = ref(false)
const amazonHref = computed(() => resolvePageHref('/new_web_source/amazon-console.html'))
const videoHref = computed(() => resolvePageHref('/new_web_source/image-video.html'))
@@ -125,18 +125,24 @@ async function loadPermissions() {
const currentUid = getUid()
if (!currentUid) {
permissionUnavailable.value = true
permissionReady.value = true
return
}
// 先用本地缓存立即渲染入口(有缓存时接口失败也不让入口消失)
const cached = readCachedAppColumnPermissions(Number(currentUid))
if (cached) {
permissionState.value = computePermissionState(cached)
}
try {
// 经 shared/api/permission 层拉取原始权限菜单项(含本地缓存写入;接口失败走 catch)
const items = await getCurrentUserAppColumnRaw()
permissionState.value = computePermissionState(items)
permissionUnavailable.value = false
} catch {
permissionUnavailable.value = true
// 无缓存才退回"显示全部入口";有缓存已按缓存显示,保持现状即可
if (!cached) {
permissionUnavailable.value = true
}
}
permissionReady.value = true
}
async function loadCurrentUser() {
@@ -339,10 +345,6 @@ body {
min-height: 18px;
}
.home-root.nav-permissions-loading .entrance-btn {
visibility: hidden;
}
.entrances {
display: flex;
gap: 32px;