🌏 preface:

In the article “VUE3 Background Management System [Template Construction]”, I introduced the background management system constructed by VUe3.0 and Vite2.0 in detail. Although it is only a simple background management system, the technologies involved in it basically cover VUE3 based vuE-Router and VUex. And vuEX data persistence with the help of third-party open source plug-ins. Before just introduced the page layout of vUE background management system, as well as the use of some commonly used plug-ins, such as: rich text editor, video player, page scroll bar beautification (forgot to introduce before, this article will be added and supplemented). This article mainly introduces the vue-Router dynamic matching and dynamic verification, to achieve different accounts with different permissions, through the front-end to the user permissions corresponding restrictions; In some paths without access permission to give the corresponding prompt and subsequent corresponding jump recovery and other logical operations. User authentication can be restricted by the front end or by background interface data. Dynamic route rendering through background interface was encountered in the previous development process, and the following is the restriction of pure front-end route access.

🏀 Route configuration:

import Layout from ".. /layout/Index.vue";
import RouteView from ".. /components/RouteView.vue";

const layoutMap = [
    {
        path: "/".name: "Index".meta: { title: "Console".icon: "home" },
        component: () = > import(".. /views/Index.vue")}, {path: "/data".meta: { title: "Data Management".icon: "database" },
        component: RouteView,
        children: [{path: "/data/list".name: "DataList".meta: { title: "Data list".roles: ["admin"]},component: () = > import(".. /views/data/List.vue")}, {path: "/data/table".name: "DataTable".meta: { title: "Data table" },
                component: () = > import(".. /views/data/Table.vue"}]}, {path: "/admin".meta: { title: "User Management".icon: "user" },
        component: RouteView,
        children: [{path: "/admin/user".name: "AdminAuth".meta: { title: "User List".roles: ["admin"]},component: () = > import(".. /views/admin/AuthList.vue")}, {path: "/admin/role".name: "AdminRole".meta: { title: "List of roles" },
                component: () = > import(".. /views/admin/RoleList.vue"}]}, {path: "user".name: "User".hidden: true /* No side navigation display */.meta: { title: "Personal Center" },
        component: () = > import(".. /views/admin/User.vue")}, {path: "/error".name: "NotFound".hidden: true.meta: { title: "Not Found" },
        component: () = > import(".. /components/NotFound.vue")}];const routes = [
    {
        path: "/login".name: "Login".meta: { title: "User Login" },
        component: () = > import(".. /views/Login.vue")}, {path: "/".component: Layout,
        children: [...layoutMap]
    },
    { path: "/ *".redirect: { name: "NotFound"}}];export { routes, layoutMap };
Copy the code

Note:

  • The route list is divided into two parts. One is the default route (for example, the Login page) that does not require permission verification.
  • Routing elements in layoutMap are all configuration information related to routing paths, that is, routing information that wraps all user permissions.
  • Route authentication ultimately limits the data elements in the layoutMap array, and filters corresponding restrictions to limit route access.

🚗 Route interception:

// Vue - Router4.0
import { createRouter, createWebHistory } from "vue-router";
import { decode } from "js-base64";
import { routes } from "./router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

NProgress.configure({ showSpinner: false });

const router = createRouter({
    history: createWebHistory(),
    routes: [...routes],
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
            return savedPosition;
        } else {
            return { top: 0}; }}});// Route interception is written the same as vue-Router3. x below
Copy the code
// Vue -router3.x
import Vue from "vue";
import VueRouter from "vue-router";
import { decode } from "js-base64";
import { routes } from "./router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

NProgress.configure({ showSpinner: false });

Vue.use(VueRouter);

const router = new VueRouter({
    mode: "history".base: process.env.BASE_URL,
    routes: [...routes],
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
            return savedPosition;
        } else {
            return { top: 0}; }}}); router.beforeEach((to, from, next) = > {
    NProgress.start();
    const jwt = sessionStorage.getItem("jwt") | |"";

    document.title = jwt ? (to.meta.title ? to.meta.title + "- Management application" : "Management System") : "System Login";
    if (to.path === "/login") {!!!!! jwt ? next("/") : next();
    } else {
        if (from.path === "/login" && !jwt) {
            NProgress.done(true);
            next(false);
            return;
        }
        if(!!!!! jwt) {if (to.meta.hasOwnProperty("roles")) {
                let roles = to.meta.roles || [],
                    { role } = jwt && JSON.parse(decode(jwt));
                roles.includes(role) ? next() : next("/error");
                return;
            }
            next();
        } else {
            next("/login"); }}}); router.afterEach(() = > {
    NProgress.done();
});

export default router;
Copy the code

Note:

  • According to the information of the accessed routing node, the dynamic route permission verification is carried out. The routes with access permission are let go, and the routes without access permission are intercepted accordingly.
  • Nprogress refers to the progress bar of route access, which is indicated by the corresponding progress bar when it is accessed. There is also a rotating little chrysanthemum (namely, route load indicator), which can be configured through relevant configurations.
  • If a user accesses /login with user information, the user is redirected to the system console page by default. Otherwise, the user is not intercepted and redirected to the login page.
  • When accessing a non-login page, verify the role administrator’s permission. If the role administrator has any permission, perform the following operations. If the role administrator has no permission, the role administrator is redirected to the/Error page, indicating that the role administrator has no right to access the current path.

🍎 Route filtering:

/* Processing permission */
export const hasPermission = (route, role) = > {
    if (route["meta"] && route.meta.hasOwnProperty("roles")) {
        return route.meta.roles.includes(role);
    }
    return true;
};

/* Filter the array */
export const filterAsyncRouter = (routers, role) = > {
    let tmp = [];
    tmp = routers.filter(el= > {
        if (hasPermission(el, role)) {
            if (el["children"] && el.children.length) {
                el.children = filterAsyncRouter(el.children, role);
            }
            return true;
        }
        return false;
    });
    return tmp;
};
Copy the code

Note: These two functions are encapsulated to filter the route data of the specified permission, and return the filtered data (that is, the page that the current account has access to);

Vuex stores and filters routing information

import Vue from "vue";
import Vuex from "vuex";
import { layoutMap } from ".. /router/router";
import { filterAsyncRouter } from ".. /utils/tool";
import createPersistedState from "vuex-persistedstate";
import SecureLS from "secure-ls";
import { CLEAR_USER, SET_USER, SET_ROUTES } from "./mutation-types";

Vue.use(Vuex);

const state = {
    users: null.routers: []};const getters = {};

const mutations = {
    [CLEAR_USER](state) {
        state.users = null;
        state.routers.length = 0; }, [SET_USER](state, payload) { state.users = payload; }, [SET_ROUTES](state, payload) { state.routers = payload; }};const ls = new SecureLS({
    encodingType: "aes" /* Encryption mode */.isCompression: false /* Compress data */.encryptionSecret: "vue" /* Encryption key */
});

const actions = {
    clearUser({ commit }) {
        commit(CLEAR_USER);
    },
    setUser({ commit }, payload) {
        let deepCopy = JSON.parse(JSON.stringify(layoutMap)), accessedRouters = filterAsyncRouter(deepCopy, payload.role); commit(SET_USER, payload); commit(SET_ROUTES, accessedRouters); }};const myPersistedState = createPersistedState({
    key: "store".storage: window.sessionStorage,
    // storage: {
    // getItem: state => ls.get(state),
    // setItem: (state, value) => ls.set(state, value),
    // removeItem: state => ls.remove(state)
    // * Permanent storage */
    reducer(state) {
        return{... state }; }});export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions
    // plugins: [myPersistedState]
});
Copy the code

Note:

  • Secure-ls is an encryption tool function with a high encryption level and is generally unbreakable. Secure-ls encrypts and decrypts data based on the key and private key. For details, see Github.
  • Vuex-persistedstate is used for persistent vuex processing. Storage methods include sessionStorage, localStorage and cookies. The first two methods are commonly used.
  • Filter routes with vuex, then render and iterate in menu. vue.

🍉 Route list rendering:

<template>
    <a-layout-sider class="sider" v-model="collapsed" collapsible :collapsedWidth="56">
        <div class="logo">
            <a-icon type="ant-design" />
        </div>
        <a-menu
            class="menu"
            theme="dark"
            mode="inline"
            :defaultOpenKeys="[defaultOpenKeys]"
            :selectedKeys="[$route.path]"
            :inlineIndent="16"
        >
            <template v-for="route in routers">
                <template v-if=! "" route['hidden']">
                    <a-sub-menu v-if="route.children && route.children.length" :key="route.path">
                        <span slot="title">
                            <a-icon :type="route.meta['icon']" />
                            <span>{{ route.meta.title }}</span>
                        </span>
                        <a-menu-item v-for="sub in route.children" :key="sub.path">
                            <router-link :to="{ path: sub.path }">
                                <a-icon v-if="sub.meta['icon']" :type="sub.meta['icon']" />
                                <span>{{ sub.meta.title }}</span>
                            </router-link>
                        </a-menu-item>
                    </a-sub-menu>
                    <a-menu-item v-else :key="route.path">
                        <router-link :to="{ path: route.path }">
                            <a-icon :type="route.meta['icon']" />
                            <span>{{ route.meta.title }}</span>
                        </router-link>
                    </a-menu-item>
                </template>
            </template>
        </a-menu>
    </a-layout-sider>
</template>

<script>
import { mapState } from "vuex";

export default {
    name: "Sider".data() {
        return {
            collapsed: false.defaultOpenKeys: ""
        };
    },
    computed: {
        ...mapState(["routers"])},created() {
        this.defaultOpenKeys = "/" + this.$route.path.split("/") [1]; }};</script>

<style lang="less" scoped>
.sider {
    height: 100vh;
    overflow: hidden;
    overflow-y: scroll;
    &::-webkit-scrollbar {
        display: none;
    }

    .logo {
        height: 56px;
        line-height: 56px;
        font-size: 30px;
        color: #fff;
        text-align: center;
        background-color: # 002140;
    }

    .menu {
        width: auto; }}</style>

<style>
ul.ant-menu-inline-collapsed > li.ant-menu-item.ul.ant-menu-inline-collapsed > li.ant-menu-submenu > div.ant-menu-submenu-title {
    padding: 0 16px ! important;
    text-align: center;
}
</style>
Copy the code

Note: This menu rendering is implemented based on Vue2. X and Ant Design Vue.

<template>
    <el-aside :width="isCollapse ? `64px` : `200px`">
        <div class="logo">
            <img src="@/assets/img/avatar.png" alt="logo" draggable="false" />
            <p>Vite2 Admin</p>
        </div>
        <el-menu
            background-color="# 001529"
            text-color="#eee"
            active-text-color="#fff"
            router
            unique-opened
            :default-active="route.path"
            :collapse="isCollapse"
        >
            <template v-for="item in routers" :key="item.name">
                <template v-if=! "" item['hidden']">
                    <el-submenu v-if="item.children && item.children.length" :index="concatPath(item.path)">
                        <template #title>
                            <i :class="item.meta.icon"></i>
                            <span>{{ item.meta.title }}</span>
                        </template>
                        <template v-for="sub in item.children" :key="sub.name">
                            <el-menu-item :index="concatPath(item.path, sub.path)">
                                <i :class="sub.meta['icon']"></i>
                                <template #title>{{ sub.meta.title }}</template>
                            </el-menu-item>
                        </template>
                    </el-submenu>
                    <el-menu-item v-else :index="concatPath(item.path)">
                        <i :class="item.meta['icon']"></i>
                        <template #title>{{ item.meta.title }}</template>
                    </el-menu-item>
                </template>
            </template>
        </el-menu>
        <div class="fold" @click="changeCollapse">
            <i v-show=! "" isCollapse" class="el-icon-d-arrow-left"></i>
            <i v-show="isCollapse" class="el-icon-d-arrow-right"></i>
        </div>
    </el-aside>
</template>

<script>
import { computed, reactive, toRefs } from "vue";
import { useRoute } from "vue-router";
import { useStore } from "vuex";

export default {
    setup() {
        const route = useRoute();
        const store = useStore();
        const state = reactive({ isCollapse: false });
        const routers = computed(() = > store.state.routers);

        const changeCollapse = () = >{ state.isCollapse = ! state.isCollapse; };const concatPath = (p_path, c_path = "") = > {
            return `${p_path ! = ="" ? "/" + p_path : "/"}${c_path ! = ="" ? "/" + c_path : ""}`;
        };

        return {
            route,
            routers,
            concatPath,
            changeCollapse,
            ...toRefs(state)
        };
    }
};
</script>
Copy the code

Note:

  • The menu navigation is implemented based on VUe3 and Element-Plus that supports VUE3. For detailed parameter Settings, refer to the official website of Element-Plus.
  • The route array obtained here is the route array data filtered by authentication. This menu dynamically traverses the specified menu data based on login information.

🍌 summary:

Combined with the previous template code, we can completely build a VUE background management system with front-end authority verification, mainly sorting out the routing data and the routing data information after filtering the route authentication. The main code is the filtering and permission verification functions encapsulated above. In the future, the background template code will be released. The template code is being improved at…… 🍎 🍎 🍎