import { cloneDeep } from 'lodash-es' import { Vue } from 'vue-property-decorator' import Router, { Route, RouteConfig, RouterOptions } from 'vue-router' import VueRouter from 'vue-router/src' import { RouteConfigSingleView } from 'vue-router/types/router' import { viewsMap } from './file-base-router' import { runMiddleware } from './middleware-handler' import { SysMenuOutput } from '@/api/swagger' import store from '@/store' import { getToken } from '@/util/auth' const asyncMainLayoutFn = () => import('@/layout/main/MainLayout.vue') const async404NotFoundLayoutFn = () => import('@/views/error/NotFound.vue') const publicLayout = () => import('@/layout/public/PublicLayout.vue') interface IMeta { title?: string // 标题 backgroundColor?: string dismiss?: boolean middleware?: string // 要运行的中间件名称,多个中间件使用 ',' 进行拼接,例如 '中间件A,中间件B,中间件C' keepAlive?: boolean // 路由页面是要需要 keepAlive 特性 redirect?: string // 页面是否要重定向至某一个页面 } // 静态路由 const staticRoutes = [ { path: '/login', component: () => import('@/views/public/Login.vue'), meta: { dismiss: true, title: '登录', component: () => import('@/views/public/Login.vue'), }, }, { path: '/settings/profile', component: window.__POWERED_BY_QIANKUN__ ? publicLayout : asyncMainLayoutFn, children: [ { path: '', name: '/settings/profile', component: () => import('@/views/public/Settings.vue'), meta: { title: '个人设置', component: () => import('@/views/public/Settings.vue'), }, }, ], }, { path: '/faq', redirect: '/faq', component: window.__POWERED_BY_QIANKUN__ ? publicLayout : asyncMainLayoutFn, children: [ { path: '/faq/menu', name: '/faq/menu', component: () => import('@/views/help/Menu.vue'), meta: { title: '帮助中心首页', bgColor: '#f8f9ff', routerGuard: true, keepAlive: true, component: () => import('@/views/help/Menu.vue'), }, }, { path: '/faq/form', name: '/faq/form', component: () => import('@/views/faq/Form.vue'), meta: { title: '帮助中心表单', routerGuard: true, keepAlive: false, component: () => import('@/views/faq/Form.vue'), }, }, { path: '/faq/help', name: '/faq/help', component: () => import('@/views/help/Index.vue'), meta: { title: '帮助中心', bgColor: '#f8f9ff', routerGuard: true, keepAlive: false, component: () => import('@/views/help/Index.vue'), }, }, ], }, { path: '/register', redirect: '/register/personal', component: () => import('@/layout/public/RegisterLayout.vue'), children: [ { path: '/register/personal', name: '/register/personal', component: () => import('@/views/public/SelfRegister.vue'), meta: { title: '个人注册', routerGuard: true, keepAlive: true, component: () => import('@/views/public/SelfRegister.vue'), }, }, ], }, { path: '*', component: async404NotFoundLayoutFn, meta: { title: '页面不存在', component: () => import('@/views/error/NotFound.vue'), }, }, ] class DynamicRouter extends Router { public routes: RouteConfig[] = [] private _options!: RouterOptions constructor(options: RouterOptions) { super(options) this._options = cloneDeep(options) const { addRoutes } = (this as VueRouter).matcher const { routes } = options this.routes = routes || [] ;(this as VueRouter).matcher.addRoutes = (newRoutes) => { this.routes.push(...newRoutes) addRoutes(newRoutes) } } /** * 重置路由到默认 */ public resetToDefault() { this.replaceRoutes(staticRoutes) } /** * 替换路由 * @param routes */ public replaceRoutes(routes: RouteConfig[]) { const newRouter = new DynamicRouter({ mode: this._options.mode, base: this._options.base, routes: routes, }) as VueRouter ;(this as VueRouter).matcher = newRouter.matcher // the relevant part } } let router: DynamicRouter Vue.use(DynamicRouter) /** * 从路由的 query 中生成 meta 参数 * @param params 例如 `keepAlive=true&retry=5&maxTimes=1000` */ function generateRouteMeta(params?: string): { [key: string]: unknown } { if (!params) { return {} } const query = new URLSearchParams(params) const ite = query.entries() const meta = {} for (const [key, value] of ite) { if (/^(\d+\.)?\d+$/.test(value)) { meta[key] = +value } else if (value === 'true') { meta[key] = true } else if (value === 'false') { meta[key] = false } else { meta[key] = value } } return meta } /** * 生成动态路由 * @param _menus * @param parent 父级菜单 */ function generateRoutesFromMenus(_menus = store.state.menu?.menus, parent?: SysMenuOutput): RouteConfig[] { const menus = cloneDeep(_menus || []) const routes: RouteConfig[] = [] for (const menu of menus) { const r: RouteConfigSingleView = { path: menu.link as string, name: menu.name, meta: { title: menu.name, isMenu: true, component: menu.component, ...generateRouteMeta(menu.routeParams), }, } const meta: IMeta = r.meta || {} if (meta.redirect) { r.redirect = meta.redirect } const asyncComponentFunction = viewsMap[menu.component || ''] if (menu.component) { r.component = asyncComponentFunction } else { // 如果这个路由有子路由 // 那么重定向到第一个路由 if (menu.children?.length && !r.redirect) { r.redirect = menu.children[0].link } else if (!menu.children?.length && !r.redirect) { // 没有子路由 // 并且没有重定向到某个页面,那么跳到 404 r.component = async404NotFoundLayoutFn } } if (menu.children?.length) { r.component = r.component || window.__POWERED_BY_QIANKUN__ ? publicLayout : asyncMainLayoutFn // 如果这个路由有子路由 // 那么重定向到第一个路由 r.children = generateRoutesFromMenus(menu.children, menu) if (!r.redirect) { r.redirect = menu.children[0].link } } else if (!parent) { // 并且没有重定向到某个页面,那么跳到 404 const childComponent = r.component ? r.component : async404NotFoundLayoutFn // 最顶层的菜单, 并且没有子菜单 r.component = window.__POWERED_BY_QIANKUN__ ? publicLayout : asyncMainLayoutFn delete r.name r.children = [ { path: '', name: menu.name, meta: { title: menu.name, component: menu.component, ...generateRouteMeta(menu.routeParams), }, redirect: r.redirect, component: childComponent, }, ] } routes.push(r) } return routes } export async function createRouter(): Promise { const accessToken = getToken() await store.dispatch('user/logged', accessToken).catch(() => Promise.resolve()) router = new DynamicRouter({ mode: 'history', // require service support base: window.__POWERED_BY_QIANKUN__ ? 'system' : process.env.BASE_URL, routes: [ // 动态生成路由, 如果未登录的情况下,因为没有菜单列表,所以可能生成空的路由 ...generateRoutesFromMenus(), ...staticRoutes, ], }) router.beforeEach(async (to: Route, from: Route, next: any) => { store.commit('loadingCtrl/start') next() }) router.beforeResolve(async (to: Route, from: Route, next: any) => { if (window.__POWERED_BY_QIANKUN__) { const matched = to.matched.map((v) => { return Object.assign(v, { path: !v.path.includes('/system') ? '/system' + v.path : v.path, }) }) Vue.prototype.$parentReuseTabStore.setMatched(matched) } const accessToken1 = getToken() const toMeta: IMeta = to.meta || {} // 运行中间间 if (toMeta.middleware) { await runMiddleware((toMeta.middleware as string).split(','), from, to, next) } if (toMeta.dismiss !== true) { if (!accessToken1 || accessToken1.length < 32) { next({ path: '/login', query: { redirect: to.path } }) return } else { return store .dispatch('user/logged', accessToken1) .then(() => { next() }) .catch((err: any) => { // eslint-disable-next-line no-console console.log(err) next({ path: '/login', query: { redirect: to.path } }) }) } } next() }) router.afterEach((to, from) => { const toMeta: IMeta = to.meta || {} if (toMeta.dismiss !== true) { const { matched } = to const current: any = matched[matched.length - 1] const tabInfo = { name: to.name, path: window.__POWERED_BY_QIANKUN__ ? '/system' + to.path : to.path, title: toMeta.title, query: to.query, params: to.params, closable: true, activeName: from.name, componentName: current?.components.default?.extendOptions?.name || current?.components.default?.name, } // 设置复用tab store.dispatch('reuseTab/add', tabInfo) store.commit('reuseTab/changeTo', tabInfo) } // 设置背景色 store.commit('app/setBgColor', toMeta.backgroundColor) // 设置标题 store.commit('app/changeTitle', toMeta.title) // 设置loading状态为已完成 store.commit('loadingCtrl/completed') }) return router } // eslint-disable-next-line import/no-unused-modules export function getRouter() { return router }