82a782550e
- 新增 usersecret 模块:外观专利/货源查询密钥从本地 localStorage 迁移至 biz_user_api_secret(AES 加密、按 uid 绑定) - 后台「密钥管理」页:脱敏展示、立即检测、清空;每日 04:30 分布式锁定时巡检 - 桌面端:密钥设置面板走服务端、未配齐引导 /setup-secrets、代理余量展示 - 删除专利汇令牌全链路与密钥保留时长选择器
177 lines
5.3 KiB
TypeScript
177 lines
5.3 KiB
TypeScript
import { createRouter, createWebHistory } from 'vue-router'
|
||
|
||
/**
|
||
* 数富AI 桌面前端 SPA 路由(无 .html 后缀)
|
||
*
|
||
* 原 MPA(22 个 html 入口 + 22 个 *-main.ts)合并为单入口 index.html;
|
||
* 页面组件懒加载(每个工具页独立 chunk),URL 统一为 /xxx 路径形式。
|
||
*/
|
||
|
||
const routes = [
|
||
{ path: '/', redirect: '/home' },
|
||
{
|
||
path: '/login',
|
||
name: 'login',
|
||
component: () => import('@/pages/login/DesktopLoginPage.vue'),
|
||
},
|
||
{
|
||
path: '/home',
|
||
name: 'home',
|
||
component: () => import('@/pages/home/DesktopHomePage.vue'),
|
||
},
|
||
{
|
||
// 密钥未配置完整时的强制引导页(全站拦截落点)
|
||
path: '/setup-secrets',
|
||
name: 'setup-secrets',
|
||
component: () => import('@/pages/setup/DesktopSecretSetupPage.vue'),
|
||
},
|
||
{
|
||
path: '/amazon-console',
|
||
name: 'amazon-console',
|
||
component: () => import('@/pages/amazon/AmazonConsolePage.vue'),
|
||
},
|
||
{
|
||
path: '/image-video',
|
||
name: 'image-video',
|
||
// 视图切换走 ?view=delivery(菜单页 / 带货视频工作台)
|
||
component: () => import('@/pages/image-video/ImageVideoPage.vue'),
|
||
},
|
||
// ---- 亚马逊运营工具(品牌工具,右侧统一任务面板)----
|
||
{
|
||
path: '/collect-data',
|
||
name: 'collect-data',
|
||
component: () => import('@/pages/brand/components/BrandCollectDataTab.vue'),
|
||
},
|
||
{
|
||
path: '/variant-collection',
|
||
name: 'variant-collection',
|
||
component: () => import('@/pages/brand/components/BrandVariantTab.vue'),
|
||
},
|
||
{
|
||
path: '/dedupe',
|
||
name: 'dedupe',
|
||
component: () => import('@/pages/brand/components/BrandDedupeTab.vue'),
|
||
},
|
||
{
|
||
path: '/similar-asin',
|
||
name: 'similar-asin',
|
||
component: () => import('@/pages/brand/components/BrandSimilarAsinTab.vue'),
|
||
},
|
||
{
|
||
path: '/brand',
|
||
name: 'brand',
|
||
component: () => import('@/pages/brand/components/BrandBrandTab.vue'),
|
||
},
|
||
{
|
||
path: '/appearance-patent',
|
||
name: 'appearance-patent',
|
||
component: () => import('@/pages/brand/components/BrandAppearancePatentTab.vue'),
|
||
},
|
||
{
|
||
path: '/split',
|
||
name: 'split',
|
||
component: () => import('@/pages/brand/components/BrandSplitTab.vue'),
|
||
},
|
||
{
|
||
path: '/convert',
|
||
name: 'convert',
|
||
component: () => import('@/pages/brand/components/BrandConvertTab.vue'),
|
||
},
|
||
{
|
||
path: '/publish',
|
||
name: 'publish',
|
||
component: () => import('@/pages/brand/components/BrandPublishTab.vue'),
|
||
},
|
||
{
|
||
path: '/delete-brand',
|
||
name: 'delete-brand',
|
||
component: () => import('@/pages/brand/components/BrandDeleteBrandTab.vue'),
|
||
},
|
||
{
|
||
path: '/product-risk',
|
||
name: 'product-risk',
|
||
component: () => import('@/pages/brand/components/BrandProductRiskTab.vue'),
|
||
},
|
||
{
|
||
path: '/shop-match',
|
||
name: 'shop-match',
|
||
component: () => import('@/pages/brand/components/BrandShopMatchTab.vue'),
|
||
},
|
||
{
|
||
path: '/price-track',
|
||
name: 'price-track',
|
||
component: () => import('@/pages/brand/components/BrandPriceTrackTab.vue'),
|
||
},
|
||
{
|
||
path: '/patrol-delete',
|
||
name: 'patrol-delete',
|
||
component: () => import('@/pages/brand/components/BrandPatrolDeleteTab.vue'),
|
||
},
|
||
{
|
||
path: '/query-asin',
|
||
name: 'query-asin',
|
||
component: () => import('@/pages/brand/components/BrandQueryAsinTab.vue'),
|
||
},
|
||
{
|
||
path: '/shop-data-crawl',
|
||
name: 'shop-data-crawl',
|
||
component: () => import('@/pages/brand/components/BrandShopDataCrawlTab.vue'),
|
||
},
|
||
{
|
||
path: '/withdraw',
|
||
name: 'withdraw',
|
||
component: () => import('@/pages/brand/components/BrandWithdrawTab.vue'),
|
||
},
|
||
// ---- 兜底:未匹配路径回首页(保持与 MPA 时代"未知页 404 后回首页"一致体验)----
|
||
{ path: '/:pathMatch(.*)*', redirect: '/home' },
|
||
]
|
||
|
||
const router = createRouter({
|
||
history: createWebHistory(),
|
||
routes,
|
||
scrollBehavior(_to, _from, savedPosition) {
|
||
// 返回工具台的 #group-xxx 锚点由组件 onMounted 处理;普通滚动回顶部
|
||
if (savedPosition) return savedPosition
|
||
return { top: 0 }
|
||
},
|
||
})
|
||
|
||
// 懒加载 chunk 加载失败(网络抖动/瞬间断流)会被 vue-router 静默吞掉,
|
||
// 菜单点击表现为"点了没反应";此处统一重载页面(重新拉最新 index.html 与新 chunk)。
|
||
// 用会话内计数做保险丝,网络持续异常时最多重载 2 次,不再死循环。
|
||
router.onError((error) => {
|
||
const message = String((error as Error)?.message || '')
|
||
const isChunkLoadError =
|
||
message.includes('Failed to fetch dynamically imported module') ||
|
||
message.includes('Importing a module script failed')
|
||
if (isChunkLoadError) {
|
||
const RELOAD_KEY = 'spa-chunk-reload-count'
|
||
let count = 0
|
||
try {
|
||
count = Number(sessionStorage.getItem(RELOAD_KEY) || '0') || 0
|
||
} catch {
|
||
/* 存储不可用时按首次处理 */
|
||
}
|
||
if (count < 2) {
|
||
try {
|
||
sessionStorage.setItem(RELOAD_KEY, String(count + 1))
|
||
} catch {
|
||
/* 忽略存储异常 */
|
||
}
|
||
console.error(`[router] 页面资源加载失败,重载页面(第 ${count + 1} 次):`, error)
|
||
window.location.reload()
|
||
return
|
||
}
|
||
try {
|
||
sessionStorage.removeItem(RELOAD_KEY)
|
||
} catch {
|
||
/* 忽略存储异常 */
|
||
}
|
||
console.error('[router] 页面资源多次加载失败,停止重载:', error)
|
||
} else {
|
||
console.error('[router] 路由导航失败:', error)
|
||
}
|
||
})
|
||
|
||
export default router
|