You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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报错。

修改步骤:

  1. 调整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;
  1. 将导航守卫移到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 05:07:38