From d147e538d11ea8a89040382327b8e4685da35a22 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sat, 30 Jan 2021 04:19:30 +0100 Subject: [PATCH] [Vue3][Quasar2] Fixed Store and Router, breaking changes in both. --- src/boot/plugins.ts | 63 +++++++++++++++------------- src/layouts/MainLayout.vue | 27 +++++------- src/plugins.d.ts | 18 ++++---- src/plugins/balance/routes/index.ts | 40 +++++++++++------- src/plugins/schedule/routes/index.ts | 39 ++++++++++------- src/plugins/user/routes/index.ts | 26 ++++++------ src/router/index.ts | 48 ++++++++++++++------- src/router/routes.ts | 6 +-- src/store/index.ts | 24 +++-------- 9 files changed, 157 insertions(+), 134 deletions(-) diff --git a/src/boot/plugins.ts b/src/boot/plugins.ts index c2c3336..caf64f5 100644 --- a/src/boot/plugins.ts +++ b/src/boot/plugins.ts @@ -1,12 +1,11 @@ import { boot } from 'quasar/wrappers'; -import { RouteConfig } from 'vue-router'; -import { VueRouter } from 'vue-router/types/router'; import { Store } from 'vuex'; import { StateInterface } from 'src/store'; import { FG_Plugin } from 'src/plugins'; import routes from 'src/router/routes'; import { axios } from 'boot/axios'; import { AxiosResponse } from 'axios'; +import { Router, RouteRecordRaw } from 'vue-router'; const config = { // Do not change required Modules !! @@ -35,21 +34,27 @@ interface Backend { export { Backend }; +function setPermissions(object: FG_Plugin.PluginRouteConfig) { + if (object.route.meta === undefined) object.route.meta = {}; + object.route.meta['permissions'] = object.permissions; +} + function combineRoutes( - target: RouteConfig[], + target: RouteRecordRaw[], source: FG_Plugin.PluginRouteConfig[], mainPath: '/' | '/main' = '/' -): RouteConfig[] { +): RouteRecordRaw[] { target.forEach((target) => { if (target.path === mainPath) { source.forEach((sourceMainConfig: FG_Plugin.PluginRouteConfig) => { - const targetMainConfig = target.children?.find((targetMainConfig: RouteConfig) => { - return sourceMainConfig.path === targetMainConfig.path; + const targetMainConfig = target.children?.find((targetMainConfig: RouteRecordRaw) => { + return sourceMainConfig.route.path === targetMainConfig.path; }); if (targetMainConfig) { - const sourceChildren: RouteConfig[] = []; + const sourceChildren: RouteRecordRaw[] = []; sourceMainConfig.children?.forEach((child) => { - sourceChildren.push(child); + setPermissions(child); + sourceChildren.push(child.route); }); if (targetMainConfig.children) { targetMainConfig.children = Object.assign(targetMainConfig.children, sourceChildren); @@ -63,10 +68,14 @@ function combineRoutes( if ( sourceMainConfig.children && sourceMainConfig.children.length > 0 && - !sourceMainConfig.component + !sourceMainConfig.route.component ) - sourceMainConfig.component = () => import('src/components/navigation/EmptyParent.vue'); - target.children.push(sourceMainConfig); + target.children.push({ + component: () => import('src/components/navigation/EmptyParent.vue'), + name: sourceMainConfig.route.name, + path: sourceMainConfig.route.path, + }); + else target.children.push(sourceMainConfig.route); } }); } @@ -90,18 +99,18 @@ function combineMainLinks( targetPluginMainLink.children.push({ title: sourcePluginChildLink.title, icon: sourcePluginChildLink.icon, - link: sourcePluginChildLink.name, - name: sourcePluginChildLink.name, - permissions: sourcePluginChildLink.meta?.permissions, + link: sourcePluginChildLink.route.name, + name: sourcePluginChildLink.route.name, + permissions: sourcePluginChildLink.permissions, }); }); } else { const mainLink: FG_Plugin.PluginMainLink = { title: source.title, icon: source.icon, - link: source.name, - name: source.name, - permissions: source.meta?.permissions, + link: source.route.name, + name: source.route.name, + permissions: source.permissions, }; source.children?.forEach((child) => { if (mainLink.children === undefined) { @@ -110,9 +119,9 @@ function combineMainLinks( mainLink.children.push({ title: child.title, icon: child.icon, - link: child.name, - name: child.name, - permissions: child.meta?.permissions, + link: child.route.name, + name: child.route.name, + permissions: child.permissions, }); }); target.push(mainLink); @@ -127,9 +136,9 @@ function loadShortCuts( source.forEach((route) => { if (route.shortcut) { target.push({ - link: route.name, + link: route.route.name, icon: route.icon, - permissions: route.meta?.permissions, + permissions: route.permissions, }); } if (route.children) { @@ -146,7 +155,7 @@ function loadPlugin( backendpromise: Promise, plugins: FG_Plugin.Plugin[], store: Store, - router: VueRouter + router: Router ): FG_Plugin.LoadedPlugins { modules.forEach((requiredModule) => { const plugin = plugins.find((plugin) => { @@ -202,7 +211,7 @@ async function getBackend(): Promise { // "async" is optional; // more info on params: https://quasar.dev/quasar-cli/cli-documentation/boot-files#Anatomy-of-a-boot-file -export default boot>(({ Vue, router, store }) => { +export default boot>(({ router, store, app }) => { const plugins: FG_Plugin.Plugin[] = []; const backendPromise = getBackend(); @@ -279,10 +288,8 @@ export default boot>(({ Vue, router, store }) => { loadedPlugins.widgets.sort((a, b) => b.priority - a.priority); - // add new routes for plugins, flatten them to allow empty parent routes - router.addRoutes(loadedPlugins.routes); + loadedPlugins.routes.forEach((route) => router.addRoute(route)); // save plugins in VM-variable - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - Vue.prototype.$flaschengeistPlugins = loadedPlugins; + app.provide('flaschengeistPlugins', loadedPlugins); }); diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 64cdf33..4360c01 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -102,10 +102,10 @@ import EssentialLink from 'components/navigation/EssentialLink.vue'; import ShortCutLink from 'components/navigation/ShortCutLink.vue'; import { Screen, Loading } from 'quasar'; -import { defineComponent, ref, computed } from '@vue/composition-api'; -import { Store } from 'vuex'; -import { StateInterface } from 'src/store'; +import { defineComponent, ref, inject, computed } from 'vue'; import { FG_Plugin } from 'src/plugins'; +import { useRoute } from 'vue-router'; +import { useStore } from 'vuex'; const links = [ { @@ -131,16 +131,13 @@ const shortcuts = [ }, ]; -declare module 'vue/types/vue' { - interface Vue { - $flaschengeistPlugins: FG_Plugin.LoadedPlugins; - } -} - export default defineComponent({ name: 'MainLayout', components: { EssentialLink, ShortCutLink }, - setup(_, ctx) { + setup() { + const route = useRoute(); + const store = useStore(); + const plugins = inject('flaschengeistPlugins'); const leftDrawer = ref(false); const leftDrawerOpen = ref( @@ -158,12 +155,10 @@ export default defineComponent({ } const pluginChildLinks = computed(() => { - const link: - | FG_Plugin.PluginMainLink - | undefined = ctx.root.$flaschengeistPlugins.mainLinks.find( + const link: FG_Plugin.PluginMainLink | undefined = plugins?.mainLinks.find( (plugin: FG_Plugin.PluginMainLink) => { - if (ctx.root.$route.matched.length > 1) { - return plugin.name == ctx.root.$route.matched[1].name; + if (route.matched.length > 1) { + return plugin.name == route.matched[1].name; } } ); @@ -177,7 +172,7 @@ export default defineComponent({ function logout() { Loading.show({ message: 'Session wird abgemeldet' }); - (>ctx.root.$store).dispatch('session/logout').finally(() => { + store.dispatch('session/logout').finally(() => { Loading.hide(); }); } diff --git a/src/plugins.d.ts b/src/plugins.d.ts index 15b7087..75b15d7 100644 --- a/src/plugins.d.ts +++ b/src/plugins.d.ts @@ -1,7 +1,6 @@ -import { RouteConfig } from 'vue-router'; +import { RouteRecordRaw } from 'vue-router'; import { Module } from 'vuex'; -import { StateInterface } from 'src/store'; -import { AsyncComponentPromise } from 'vue/types/options'; +import { Component } from 'vue'; declare namespace FG_Plugin { interface ShortCutLink { @@ -10,19 +9,20 @@ declare namespace FG_Plugin { permissions?: string[]; } - interface PluginRouteConfig extends RouteConfig { - shortcut?: boolean; + interface PluginRouteConfig { title: string; icon: string; + route: RouteRecordRaw; + shortcut?: boolean; children?: PluginRouteConfig[]; - meta?: { permissions?: string[] }; + permissions?: string[]; } interface Widget { name: string; priority: number; permissions: FG.Permission[]; - widget: AsyncComponentPromise; + widget: Component; } interface Plugin { @@ -33,7 +33,7 @@ declare namespace FG_Plugin { requiredBackendModules: string[]; mainRoutes?: PluginRouteConfig[]; outRoutes?: PluginRouteConfig[]; - store?: Map>; + store?: Map>; } interface PluginMainLink extends PluginChildLink { @@ -55,7 +55,7 @@ declare namespace FG_Plugin { interface LoadedPlugins { plugins: LoadedPlugin[]; - routes: RouteConfig[]; + routes: RouteRecordRaw[]; mainLinks: PluginMainLink[]; shortcuts: ShortCutLink[]; shortcutsOut: ShortCutLink[]; diff --git a/src/plugins/balance/routes/index.ts b/src/plugins/balance/routes/index.ts index 2339062..ae70140 100644 --- a/src/plugins/balance/routes/index.ts +++ b/src/plugins/balance/routes/index.ts @@ -5,35 +5,43 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ { title: 'Gerücht', icon: 'mdi-cash-100', - path: 'balance', - name: 'balance', - redirect: { name: 'balance-view' }, - meta: { permissions: ['user'] }, + permissions: ['user'], + route: { + path: 'balance', + name: 'balance', + redirect: { name: 'balance-view' }, + }, children: [ { title: 'Übersicht', icon: 'mdi-cash-check', - path: 'overview', - name: 'balance-view', - meta: { permissions: [permissions.SHOW] }, - component: () => import('../pages/Overview.vue'), + permissions: [permissions.SHOW], + route: { + path: 'overview', + name: 'balance-view', + component: () => import('../pages/Overview.vue'), + }, }, { title: 'Buchen', icon: 'mdi-cash-plus', - path: 'change', - name: 'balance-change', shortcut: true, - meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] }, - component: () => import('../pages/MainPage.vue'), + permissions: [permissions.DEBIT_OWN, permissions.SHOW], + route: { + path: 'change', + name: 'balance-change', + component: () => import('../pages/MainPage.vue'), + }, }, { title: 'Verwaltung', icon: 'mdi-account-cash', - path: 'admin', - name: 'balance-admin', - meta: { permissions: [permissions.SET_LIMIT, permissions.SHOW_OTHER] }, - component: () => import('../pages/Admin.vue'), + permissions: [permissions.SET_LIMIT, permissions.SHOW_OTHER], + route: { + path: 'admin', + name: 'balance-admin', + component: () => import('../pages/Admin.vue'), + }, }, ], }, diff --git a/src/plugins/schedule/routes/index.ts b/src/plugins/schedule/routes/index.ts index a47b006..3d78ea9 100644 --- a/src/plugins/schedule/routes/index.ts +++ b/src/plugins/schedule/routes/index.ts @@ -4,37 +4,44 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ { title: 'Dienste', icon: 'mdi-briefcase', - path: 'schedule', - name: 'schedule', - component: () => import('../pages/MainPage.vue'), - meta: { permissions: ['user'] }, + permissions: ['user'], + route: { + path: 'schedule', + name: 'schedule', + component: () => import('../pages/MainPage.vue'), + }, children: [ { title: 'Dienstübersicht', icon: 'mdi-account-group', - path: 'schedule-overview', - name: 'schedule-overview', + shortcut: true, - meta: { permissions: [] }, - component: () => import('../pages/Overview.vue'), + permissions: [], + route: { + path: 'schedule-overview', + name: 'schedule-overview', + component: () => import('../pages/Overview.vue'), + }, }, { title: 'Dienstverwaltung', icon: 'mdi-account-details', - path: 'schedule-management', - name: 'schedule-management', shortcut: false, - meta: { permissions: [] }, - component: () => import('../pages/Management.vue'), + route: { + path: 'schedule-management', + name: 'schedule-management', + component: () => import('../pages/Management.vue'), + }, }, { title: 'Dienstanfragen', icon: 'mdi-account-switch', - path: 'schedule-requests', - name: 'schedule-requests', shortcut: false, - meta: { permissions: [] }, - component: () => import('../pages/Requests.vue'), + route: { + path: 'schedule-requests', + name: 'schedule-requests', + component: () => import('../pages/Requests.vue'), + }, }, ], }, diff --git a/src/plugins/user/routes/index.ts b/src/plugins/user/routes/index.ts index 17c0dbf..1aa27cf 100644 --- a/src/plugins/user/routes/index.ts +++ b/src/plugins/user/routes/index.ts @@ -3,28 +3,30 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ { title: 'loadFromStore("user/displayName")', icon: 'mdi-account', - path: 'user', - name: 'user', - component: () => import('../pages/MainPage.vue'), - meta: { permissions: ['user'] }, + permissions: ['user'], + route: { path: 'user', name: 'user', component: () => import('../pages/MainPage.vue') }, children: [ { title: 'Einstellungen', icon: 'mdi-account-edit', - path: 'settings', - name: 'user-settings', shortcut: true, - meta: { permissions: ['user'] }, - component: () => import('../pages/Settings.vue'), + permissions: ['user'], + route: { + path: 'settings', + name: 'user-settings', + component: () => import('../pages/Settings.vue'), + }, }, { title: 'Admin', icon: 'mdi-cog', - path: 'admin', - name: 'admin-settings', shortcut: false, - meta: { permissions: ['users_edit_other'] }, - component: () => import('../pages/AdminSettings.vue'), + permissions: ['users_edit_other'], + route: { + path: 'admin', + name: 'admin-settings', + component: () => import('../pages/AdminSettings.vue'), + }, }, ], }, diff --git a/src/router/index.ts b/src/router/index.ts index 5ddd904..8cb857b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,24 +1,42 @@ import { route } from 'quasar/wrappers'; -import VueRouter from 'vue-router'; -import { Store } from 'vuex'; -import { StateInterface } from 'src/store'; +import { + createRouter, + createMemoryHistory, + createWebHistory, + createWebHashHistory, +} from 'vue-router'; +import routes from './routes'; /* * If not building with SSR mode, you can - * directly export the Router instantiation + * directly export the Router instantiation; + * + * The function below can be async too; either use + * async/await or return a Promise which resolves + * with the Router instance. */ -export const Router: VueRouter = new VueRouter({ - scrollBehavior: () => ({ x: 0, y: 0 }), - // Leave these as is and change from quasar.conf.js instead! - // quasar.conf.js -> build -> vueRouterMode - // quasar.conf.js -> build -> publicPath - mode: process.env.VUE_ROUTER_MODE, - base: process.env.VUE_ROUTER_BASE, -}); +function router(/* { store, ssrContext } */) { + const createHistory = + process.env.MODE === 'ssr' + ? createMemoryHistory + : process.env.VUE_ROUTER_MODE === 'history' + ? createWebHistory + : createWebHashHistory; -export default route>(function ({ Vue }) { - Vue.use(VueRouter); + const Router = createRouter({ + scrollBehavior: () => ({ left: 0, top: 0 }), + routes, + + // Leave this as is and make changes in quasar.conf.js instead! + // quasar.conf.js -> build -> vueRouterMode + // quasar.conf.js -> build -> publicPath + history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE), + }); return Router; -}); +} + +export default route(router); + +export const Router = router(); diff --git a/src/router/routes.ts b/src/router/routes.ts index 8386709..b39533a 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -1,6 +1,6 @@ -import { RouteConfig } from 'vue-router'; +import { RouteRecordRaw } from 'vue-router'; -const routes: RouteConfig[] = [ +const routes: RouteRecordRaw[] = [ { path: '/', redirect: 'login', @@ -56,7 +56,7 @@ const routes: RouteConfig[] = [ // Always leave this as last one, // but you can also remove it { - path: '*', + path: '/:catchAll(.*)*', redirect: 'login', }, ]; diff --git a/src/store/index.ts b/src/store/index.ts index ab93b94..02e6a17 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,27 +1,13 @@ import { store } from 'quasar/wrappers'; -import { SessionInterface } from 'src/plugins/user/store/session'; -import Vuex from 'vuex'; -import { UserStateInterface } from 'src/plugins/user/store/user'; +import { createStore } from 'vuex'; -/* - * If not building with SSR mode, you can - * directly export the Store instantiation - */ -export interface StateInterface { - user: UserStateInterface; - session: SessionInterface; - [key: string]: any; -} - -export default store(function ({ Vue }) { - Vue.use(Vuex); - - const Store = new Vuex.Store({ +export default store(function (/* { ssrContext } */) { + const Store = createStore({ modules: {}, // enable strict mode (adds overhead!) - // for dev mode only - strict: !!process.env.DEV, + // for dev mode and --debug builds only + strict: !!process.env.DEBUGGING, }); return Store;