chore(deps): 前端运行时升级到 vue-router 5 / pinia 4 / vueuse 15 / vue-i18n 11 / axios 1.20

This commit is contained in:
Estrella Pan
2026-10-20 11:06:50 +02:00
parent 2f1fa317d1
commit 13dc621ff7
13 changed files with 726 additions and 562 deletions

View File

@@ -24,14 +24,15 @@
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@vueuse/core": "^10.11.1",
"axios": "^1.7.9",
"naive-ui": "^2.44.1",
"pinia": "^2.2.2",
"vue": "^3.5.8",
"vue-i18n": "^9.14.2",
"@vue/devtools-api": "^8.2.1",
"@vueuse/core": "^15.0.0",
"axios": "^1.20.0",
"naive-ui": "^2.45.3",
"pinia": "^4.1.0",
"vue": "^3.5.44",
"vue-i18n": "^11.4.16",
"vue-inline-svg": "^3.1.4",
"vue-router": "^4.4.5",
"vue-router": "^5.4.0",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
@@ -61,7 +62,6 @@
"unocss": "^66.11.0",
"unplugin-auto-import": "^21.1.0",
"unplugin-vue-components": "^32.1.0",
"unplugin-vue-router": "^0.6.4",
"vite": "^8.3.4",
"vitest": "^5.0.5",
"vue-tsc": "^3.3.13"

804
webui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,8 @@ const { slots, loaded, loadFailed } = usePluginUi();
const pageTitle = computed(() => {
const name = String(route.name ?? '');
if (name === 'Market') return t('market.title');
if (name !== 'Plugin') return name;
const id = (route.params as Record<string, string>).id;
if (route.name !== 'Plugin') return name;
const id = route.params.id;
const ui = slots.value.find((s) => s.slot === 'page' && s.plugin_id === id);
if (ui) return slotTitle(ui, lang.value === 'zh-CN' ? 'zh-CN' : 'en-US');
return loaded.value || loadFailed.value ? id : '';

View File

@@ -6,13 +6,13 @@ definePage({
name: 'Bangumi Torrents',
});
const route = useRoute();
const route = useRoute('Bangumi Torrents');
const router = useRouter();
const { t } = useI18n();
// KeepAlive 会复用同一实例,路由参数必须保持响应式;非法 id 归一为 null
const bangumiId = computed(() => {
const id = Number((route.params as Record<string, string>).id);
const id = Number(route.params.id);
return Number.isInteger(id) && id > 0 ? id : null;
});

View File

@@ -1,5 +1,4 @@
<script lang="ts" setup>
import { watchOnce } from '@vueuse/core';
import { useConfirm } from '@/hooks/useConfirm';
import { countLogLevels, parseLogLines } from '@/utils/log-parse';
import type { LogLevel } from '@/utils/log-parse';
@@ -113,13 +112,14 @@ onActivated(() => {
} else {
// SSE 只在日志变化时推送:新会话首次进入时主动拉一次全量日志
getLog(true);
watchOnce(
watch(
() => log.value,
() => {
nextTick(() => {
backToBottom();
});
}
},
{ once: true }
);
}
});

View File

@@ -7,13 +7,13 @@ definePage({
});
// 插件经 page 挂载点提供的独立页面;同一插件声明多个时取第一个
const route = useRoute();
const route = useRoute('Plugin');
const { slots, loaded, loadFailed } = usePluginUi();
const ui = computed(() =>
slots.value.find(
(s) =>
s.slot === 'page' &&
s.plugin_id === (route.params as Record<string, string>).id
s.plugin_id === route.params.id
)
);
</script>

View File

@@ -0,0 +1,10 @@
import { router } from './index';
// setup.ts 全局 mock 了 vue-router,这里换回真实模块
vi.unmock('vue-router');
describe('router', () => {
it('should register file-based routes when the module loads', () => {
expect(router.getRoutes().length).toBeGreaterThan(0);
});
});

View File

@@ -1,7 +1,9 @@
import { createRouter, createWebHashHistory } from 'vue-router/auto';
import { createRouter, createWebHashHistory } from 'vue-router';
import { routes } from 'vue-router/auto-routes';
const router = createRouter({
history: createWebHashHistory(),
routes,
});
let setupChecked = false;

View File

@@ -88,7 +88,8 @@ export function onResponseError(err: AxiosError<ApiErrorResponse>) {
// Handle network errors (no response from server)
if (!err.response) {
if (!silent) {
// 页面跳转中断的请求(ECONNABORTED)不是网络故障,不弹提示
if (!silent && err.code !== 'ECONNABORTED') {
showErrorThrottled(
returnUserLangText({
en: 'Network error. Please check your connection.',

View File

@@ -44,7 +44,7 @@ declare global {
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const defineLoader: typeof import('vue-router/auto').defineLoader
const definePage: typeof import('unplugin-vue-router/runtime')._definePage
const definePage: typeof import('vue-router/experimental').definePage
const defineStore: typeof import('pinia').defineStore
const dirtyConfigGroups: typeof import('../../src/utils/config-diff').dirtyConfigGroups
const effectScope: typeof import('vue').effectScope
@@ -77,8 +77,8 @@ declare global {
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router/auto').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router/auto').onBeforeRouteUpdate
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onDeactivated: typeof import('vue').onDeactivated
@@ -165,8 +165,8 @@ declare global {
const usePreferredDark: typeof import('@vueuse/core').usePreferredDark
const useProgramStore: typeof import('../../src/store/program').useProgramStore
const useRSSStore: typeof import('../../src/store/rss').useRSSStore
const useRoute: typeof import('vue-router/auto').useRoute
const useRouter: typeof import('vue-router/auto').useRouter
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
const useSafeArea: typeof import('../../src/hooks/useSafeArea').useSafeArea
const useSearchStore: typeof import('../../src/store/search').useSearchStore
const useSetupStore: typeof import('../../src/store/setup').useSetupStore

View File

@@ -1,154 +1,292 @@
/* eslint-disable */
/* prettier-ignore */
// oxfmt-ignore
// @ts-nocheck
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
// noinspection ES6UnusedImports
// Generated by vue-router. !! DO NOT MODIFY THIS FILE !!
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
/// <reference types="unplugin-vue-router/client" />
import type {
// type safe route locations
RouteLocationTypedList,
RouteLocationResolvedTypedList,
RouteLocationNormalizedTypedList,
RouteLocationNormalizedLoadedTypedList,
RouteLocationAsString,
RouteLocationAsRelativeTypedList,
RouteLocationAsPathTypedList,
// helper types
// route definitions
RouteRecordInfo,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
// vue-router extensions
_RouterTyped,
RouterLinkTyped,
RouterLinkPropsTyped,
NavigationGuard,
UseLinkFnTyped,
// data fetching
_DataLoader,
_DefineLoaderOptions,
} from 'unplugin-vue-router/types'
declare module 'vue-router/auto/routes' {
export interface RouteNamedMap {
'Index': RouteRecordInfo<'Index', '/', Record<never, never>, Record<never, never>>,
'Bangumi List': RouteRecordInfo<'Bangumi List', '/bangumi', Record<never, never>, Record<never, never>>,
'Bangumi Torrents': RouteRecordInfo<'Bangumi Torrents', '/bangumi-torrents/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'Orphan Torrents': RouteRecordInfo<'Orphan Torrents', '/bangumi-torrents/orphans', Record<never, never>, Record<never, never>>,
'Calendar': RouteRecordInfo<'Calendar', '/calendar', Record<never, never>, Record<never, never>>,
'Config': RouteRecordInfo<'Config', '/config', Record<never, never>, Record<never, never>>,
'Downloader': RouteRecordInfo<'Downloader', '/downloader', Record<never, never>, Record<never, never>>,
'Log': RouteRecordInfo<'Log', '/log', Record<never, never>, Record<never, never>>,
'Market': RouteRecordInfo<'Market', '/market/:id?', { id?: ParamValueZeroOrOne<true> }, { id?: ParamValueZeroOrOne<false> }>,
'Player': RouteRecordInfo<'Player', '/player', Record<never, never>, Record<never, never>>,
'Plugin': RouteRecordInfo<'Plugin', '/plugins/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'RSS': RouteRecordInfo<'RSS', '/rss', Record<never, never>, Record<never, never>>,
'Login': RouteRecordInfo<'Login', '/login', Record<never, never>, Record<never, never>>,
'Setup': RouteRecordInfo<'Setup', '/setup', Record<never, never>, Record<never, never>>,
}
}
declare module 'vue-router/auto' {
import type { RouteNamedMap } from 'vue-router/auto/routes'
export type RouterTyped = _RouterTyped<RouteNamedMap>
/**
* Type safe version of `RouteLocationNormalized` (the type of `to` and `from` in navigation guards).
* Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocationNormalized<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationNormalizedTypedList<RouteNamedMap>[Name]
/**
* Type safe version of `RouteLocationNormalizedLoaded` (the return type of `useRoute()`).
* Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocationNormalizedLoaded<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[Name]
/**
* Type safe version of `RouteLocationResolved` (the returned route of `router.resolve()`).
* Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocationResolved<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationResolvedTypedList<RouteNamedMap>[Name]
/**
* Type safe version of `RouteLocation` . Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocation<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationTypedList<RouteNamedMap>[Name]
/**
* Type safe version of `RouteLocationRaw` . Allows passing the name of the route to be passed as a generic.
*/
export type RouteLocationRaw<Name extends keyof RouteNamedMap = keyof RouteNamedMap> =
| RouteLocationAsString<RouteNamedMap>
| RouteLocationAsRelativeTypedList<RouteNamedMap>[Name]
| RouteLocationAsPathTypedList<RouteNamedMap>[Name]
/**
* Generate a type safe params for a route location. Requires the name of the route to be passed as a generic.
*/
export type RouteParams<Name extends keyof RouteNamedMap> = RouteNamedMap[Name]['params']
/**
* Generate a type safe raw params for a route location. Requires the name of the route to be passed as a generic.
*/
export type RouteParamsRaw<Name extends keyof RouteNamedMap> = RouteNamedMap[Name]['paramsRaw']
export function useRouter(): RouterTyped
export function useRoute<Name extends keyof RouteNamedMap = keyof RouteNamedMap>(name?: Name): RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[Name]
export const useLink: UseLinkFnTyped<RouteNamedMap>
export function onBeforeRouteLeave(guard: NavigationGuard<RouteNamedMap>): void
export function onBeforeRouteUpdate(guard: NavigationGuard<RouteNamedMap>): void
export const RouterLink: RouterLinkTyped<RouteNamedMap>
export const RouterLinkProps: RouterLinkPropsTyped<RouteNamedMap>
// Experimental Data Fetching
export function defineLoader<
P extends Promise<any>,
Name extends keyof RouteNamedMap = keyof RouteNamedMap,
isLazy extends boolean = false,
>(
name: Name,
loader: (route: RouteLocationNormalizedLoaded<Name>) => P,
options?: _DefineLoaderOptions<isLazy>,
): _DataLoader<Awaited<P>, isLazy>
export function defineLoader<
P extends Promise<any>,
isLazy extends boolean = false,
>(
loader: (route: RouteLocationNormalizedLoaded) => P,
options?: _DefineLoaderOptions<isLazy>,
): _DataLoader<Awaited<P>, isLazy>
export {
_definePage as definePage,
_HasDataLoaderMeta as HasDataLoaderMeta,
_setupDataFetchingGuard as setupDataFetchingGuard,
_stopDataFetchingScope as stopDataFetchingScope,
} from 'unplugin-vue-router/runtime'
}
} from 'vue-router'
import type {
_ExtractParamParserType,
} from 'vue-router/experimental'
declare module 'vue-router' {
import type { RouteNamedMap } from 'vue-router/auto/routes'
export interface TypesConfig {
beforeRouteUpdate: NavigationGuard<RouteNamedMap>
beforeRouteLeave: NavigationGuard<RouteNamedMap>
$route: RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[keyof RouteNamedMap]
$router: _RouterTyped<RouteNamedMap>
RouterLink: RouterLinkTyped<RouteNamedMap>
interface TypesConfig {
_ParamParsers: {}
RouteNamedMap: import('vue-router/auto-routes').RouteNamedMap
_RouteFileInfoMap: import('vue-router/auto-routes')._RouteFileInfoMap
}
}
declare module 'vue-router/auto-routes' {
/**
* Route name map generated by vue-router
*/
export interface RouteNamedMap {
'Index': RouteRecordInfo<
'Index',
'/',
Record<never, never>,
Record<never, never>,
| 'Bangumi List'
| 'Bangumi Torrents'
| 'Calendar'
| 'Config'
| 'Downloader'
| 'Log'
| 'Market'
| 'Orphan Torrents'
| 'Player'
| 'Plugin'
| 'RSS'
>,
'Bangumi List': RouteRecordInfo<
'Bangumi List',
'/bangumi',
Record<never, never>,
Record<never, never>,
| never
>,
'Bangumi Torrents': RouteRecordInfo<
'Bangumi Torrents',
'/bangumi-torrents/:id',
{ id: ParamValue<true> },
{ id: ParamValue<false> },
| never
>,
'Orphan Torrents': RouteRecordInfo<
'Orphan Torrents',
'/bangumi-torrents/orphans',
Record<never, never>,
Record<never, never>,
| never
>,
'Calendar': RouteRecordInfo<
'Calendar',
'/calendar',
Record<never, never>,
Record<never, never>,
| never
>,
'Config': RouteRecordInfo<
'Config',
'/config',
Record<never, never>,
Record<never, never>,
| never
>,
'Downloader': RouteRecordInfo<
'Downloader',
'/downloader',
Record<never, never>,
Record<never, never>,
| never
>,
'Log': RouteRecordInfo<
'Log',
'/log',
Record<never, never>,
Record<never, never>,
| never
>,
'Market': RouteRecordInfo<
'Market',
'/market/:id?',
{ id?: ParamValueZeroOrOne<true> },
{ id?: ParamValueZeroOrOne<false> },
| never
>,
'Player': RouteRecordInfo<
'Player',
'/player',
Record<never, never>,
Record<never, never>,
| never
>,
'Plugin': RouteRecordInfo<
'Plugin',
'/plugins/:id',
{ id: ParamValue<true> },
{ id: ParamValue<false> },
| never
>,
'RSS': RouteRecordInfo<
'RSS',
'/rss',
Record<never, never>,
Record<never, never>,
| never
>,
'Login': RouteRecordInfo<
'Login',
'/login',
Record<never, never>,
Record<never, never>,
| never
>,
'Setup': RouteRecordInfo<
'Setup',
'/setup',
Record<never, never>,
Record<never, never>,
| never
>,
}
/**
* Route file to route info map by vue-router.
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
*
* Each key is a file path relative to the project root with 2 properties:
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
* - views: names of nested views (can be passed to <RouterView name="...">)
*
* @internal
*/
export interface _RouteFileInfoMap {
'src/pages/index.vue': {
routes:
| 'Bangumi List'
| 'Bangumi Torrents'
| 'Calendar'
| 'Config'
| 'Downloader'
| 'Index'
| 'Log'
| 'Market'
| 'Orphan Torrents'
| 'Player'
| 'Plugin'
| 'RSS'
views:
| 'default'
pathParamNames:
| never
}
'src/pages/index/bangumi.vue': {
routes:
| 'Bangumi List'
views:
| never
pathParamNames:
| never
}
'src/pages/index/bangumi-torrents/[id]/index.vue': {
routes:
| 'Bangumi Torrents'
views:
| never
pathParamNames:
| never
}
'src/pages/index/bangumi-torrents/orphans/index.vue': {
routes:
| 'Orphan Torrents'
views:
| never
pathParamNames:
| never
}
'src/pages/index/calendar.vue': {
routes:
| 'Calendar'
views:
| never
pathParamNames:
| never
}
'src/pages/index/config.vue': {
routes:
| 'Config'
views:
| never
pathParamNames:
| never
}
'src/pages/index/downloader.vue': {
routes:
| 'Downloader'
views:
| never
pathParamNames:
| never
}
'src/pages/index/log.vue': {
routes:
| 'Log'
views:
| never
pathParamNames:
| never
}
'src/pages/index/market/[[id]].vue': {
routes:
| 'Market'
views:
| never
pathParamNames:
| 'id'
}
'src/pages/index/player.vue': {
routes:
| 'Player'
views:
| never
pathParamNames:
| never
}
'src/pages/index/plugins/[id].vue': {
routes:
| 'Plugin'
views:
| never
pathParamNames:
| 'id'
}
'src/pages/index/rss.vue': {
routes:
| 'RSS'
views:
| never
pathParamNames:
| never
}
'src/pages/login.vue': {
routes:
| 'Login'
views:
| never
pathParamNames:
| never
}
'src/pages/setup.vue': {
routes:
| 'Setup'
views:
| never
pathParamNames:
| never
}
}
/**
* Get a union of possible route names in a certain route component file.
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
*
* @internal
*/
export type _RouteNamesForFilePath<FilePath extends string> =
_RouteFileInfoMap extends Record<FilePath, infer Info>
? Info['routes']
: keyof RouteNamedMap
}
export {}

View File

@@ -4,8 +4,8 @@ import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import VueRouter from 'unplugin-vue-router/vite';
import { VueRouterAutoImports } from 'unplugin-vue-router';
import VueRouter from 'vue-router/vite';
import { VueRouterAutoImports } from 'vue-router/unplugin';
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
// 与后端给 SPA 文档加的响应头一致(backend/src/main.py)。只加在 preview:
@@ -48,9 +48,6 @@ export default defineConfig({
include: resolve(import.meta.dirname, './src/i18n/**'),
}),
],
// unplugin-vue-i18n 11 只产出 AST 形式的文案,vue-i18n 9 要打开 JIT 才认 AST;
// vue-i18n 升到 10+ 后默认开启,届时删掉这一项
define: { __INTLIFY_JIT_COMPILATION__: true },
css: {
preprocessorOptions: {
scss: {

View File

@@ -1,9 +1,11 @@
import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';
import VueRouter from 'vue-router/vite';
import AutoImport from 'unplugin-auto-import/vite';
export default defineConfig({
plugins: [
VueRouter({ dts: false }),
vue(),
AutoImport({
imports: ['vue', 'vitest', 'pinia'],