Vue3+Quasar+TS项目导航守卫实现遇TS2740错误求助
问题:Quasar + Vue3 + TypeScript 路由鉴权导航守卫报错 TS2740
ERROR in src/router/index.ts:26:5 TS2740: Type 'Router' is missing the following properties from type 'readonly RouteRecordRaw[]': length, concat, join, slice, and 16 more. 24 | const Router = createRouter({ 25 | scrollBehavior: () => ({ left: 0, top: 0 }), > 26 | routes, | ^^^^^^ 27 | 28 | // Leave this as is and make changes in quasar.conf.js instead! 29 | // quasar.conf.js -> build -> vueRouterMode
已尝试的操作:
- 最初按Vue3官方文档配置导航守卫,报错后将quasar.config.js中的
vueRouterMode从hash改为history; - 验证所有依赖已更新,检查代码拼写和配置错误;
- 上述操作后错误仍未解决。
相关代码
routes.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import { useAuthStore } from 'src/stores/auth-store'; import MainLayout from 'layouts/MainLayout.vue'; import AdminLayout from 'layouts/AdminLayout.vue'; import IndexPage from 'pages/IndexPage.vue'; import SignUp from 'pages/Authentication/SignUp.vue'; import SignIn from 'pages/Authentication/SignIn.vue'; import DashboardPage from 'pages/SuperAdmin/DashboardPage.vue'; const routes: Array<RouteRecordRaw> = [ { path: '/', component: MainLayout, children: [ { path: '', component: SignUp }, { path: '/home', component: IndexPage }, { path: '/login', component: SignIn } ], }, { path: '/admin', component: AdminLayout, children: [ { path: '', component: DashboardPage }, ], meta: { requiresAdmin: true } }, { path: '/:catchAll(.*)*', component: () => import('pages/ErrorNotFound.vue'), }, ]; const router = createRouter({ history: createWebHistory(), routes, }); router.beforeEach((to, from, next) => { const auth = useAuthStore(); if (to.matched.some(record => record.meta.requiresAdmin)) { if (auth.isAdminUser) { next(); } else { next('/'); } } else { next(); } }); export default router;
auth-store.ts
import { defineStore } from 'pinia'; import AuthRequests from 'boot/api/AuthRequests'; import jwt_decode from 'jwt-decode'; import { api } from 'boot/axios'; import { Roles } from 'src/utils/Constants' interface AuthState { jwt: string | null; expiration: number | null; } interface DecodedJWT { permissions?: string[]; exp: number; } export const useAuthStore = defineStore('auth', { state: (): AuthState => { return JSON.parse(localStorage.getItem('authStore') as string) || { jwt: null, expiration: null }; }, getters: { isAuthenticated: (state: AuthState): boolean => { return state.jwt !== null; }, hasPermission: (state: AuthState) => (permission: string): boolean => { if (!state.jwt) return false; const decoded_jwt: DecodedJWT = jwt_decode(state.jwt); return decoded_jwt.permissions ? decoded_jwt.permissions.includes(permission) : false; }, isAdminUser: (): boolean => useAuthStore().hasPermission(Roles.ADMIN), isVenueOwner: (): boolean => useAuthStore().hasPermission(Roles.VENUE_OWNER), isStaff: (): boolean => useAuthStore().hasPermission(Roles.STAFF), isEventOrganizer: (): boolean => useAuthStore().hasPermission(Roles.EVENT_ORGANIZER), isGroupAdmin: (): boolean => useAuthStore().hasPermission(Roles.GROUP_ADMIN), isStandardUser: (): boolean => useAuthStore().hasPermission(Roles.USER), }, actions: { async authenticate(email: string, password: string): Promise<void> { try { const { data } = await AuthRequests.authenticate(email, password); const decoded_jwt: DecodedJWT = jwt_decode(data); this.jwt = data; this.expiration = decoded_jwt.exp; api.defaults.headers.common['Authorization'] = 'Bearer ' + data; localStorage.setItem('authStore', JSON.stringify({ jwt: data, expiration: decoded_jwt.exp, })); } catch (e) { console.error(e); } }, logout(): void { this.jwt = null; delete api.defaults.headers.common['Authorization']; localStorage.removeItem('authStore'); } } });
src/router/index.ts
import { route } from 'quasar/wrappers'; import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory, } from 'vue-router'; import routes from './routes'; export default route(function (/* { store, ssrContext } */) { const createHistory = process.env.SERVER ? createMemoryHistory : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory); const Router = createRouter({ scrollBehavior: () => ({ left: 0, top: 0 }), routes, history: createHistory( process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE ), }); return Router; });
解决方案
错误根源是routes.ts导出的是Router实例,而非路由数组,但index.ts中import routes from './routes'期望导入的是RouteRecordRaw[]类型的路由数组,类型不匹配导致TS报错。
修改步骤:
- 调整routes.ts,仅导出路由数组,移除其中创建Router实例和导航守卫的代码:
import { RouteRecordRaw } from 'vue-router'; import MainLayout from 'layouts/MainLayout.vue'; import AdminLayout from 'layouts/AdminLayout.vue'; import IndexPage from 'pages/IndexPage.vue'; import SignUp from 'pages/Authentication/SignUp.vue'; import SignIn from 'pages/Authentication/SignIn.vue'; import DashboardPage from 'pages/SuperAdmin/DashboardPage.vue'; const routes: Array<RouteRecordRaw> = [ { path: '/', component: MainLayout, children: [ { path: '', component: SignUp }, { path: '/home', component: IndexPage }, { path: '/login', component: SignIn } ], }, { path: '/admin', component: AdminLayout, children: [ { path: '', component: DashboardPage }, ], meta: { requiresAdmin: true } }, { path: '/:catchAll(.*)*', component: () => import('pages/ErrorNotFound.vue'), }, ]; export default routes;
- 将导航守卫移到index.ts中,在创建Router实例后添加:
import { route } from 'quasar/wrappers'; import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory, } from 'vue-router'; import routes from './routes'; import { useAuthStore } from 'src/stores/auth-store'; export default route(function (/* { store, ssrContext } */) { const createHistory = process.env.SERVER ? createMemoryHistory : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory); const Router = createRouter({ scrollBehavior: () => ({ left: 0, top: 0 }), routes, history: createHistory( process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE ), }); // 添加导航守卫 Router.beforeEach((to, from, next) => { const auth = useAuthStore(); if (to.matched.some(record => record.meta.requiresAdmin)) { if (auth.isAdminUser) { next(); } else { next('/'); } } else { next(); } }); return Router; });
这样调整后,类型匹配问题解决,导航守卫也能正常工作。
内容的提问来源于stack exchange,提问作者Tolgahan Dayanikli
相关产品推荐
相关产品推荐

