Make writing a habit together! This is my first day to participate in the “Gold Digging Day New Plan · April More text challenge”, click to see the details of the activity.

1. Technical stack description

Vue2.6 + VUE-router + vuex + element-ui

2. Start: Create a project

Prerequisites: After installing NodeJS on a PC (mine is 14.15.1), install Vue using the NPM package manager that comes with NodeJS (mine is @vue/ CLI 4.5.12).

  1. From the command line, install the scaffolding under the specified directory with the following commandvue-cli
npm install -g @vue/cli
Copy the code
  1. Use vue’s create project command, vue create XXX (XXX refers to the project name)
  • Select the plug-ins needed for the project
? Check the features needed for your project: ◉ Choose Vue version // Select Vue version ◉ Babel // Support for The infection of Babel Infection TypeScript // Support for TypeScript source code writing infection of Progressive Web App (PWA) Support // PWA supported ◉ Router // VuE-Router ◉ // Vuex supported Vuex infection of CSS pre-processors // CSS preprocessors were supported. ◉ Linter/Formatter // supports code style checking and formatting. Infection Unit Testing // support for Unit Testing. Infection of E2E Testing // Support of E2E Testing. // Note: just select what you want to integrate (note: space is selected and cancelled, A is selected all)Copy the code
  • Select the VUE version. Since VUE3 has only been out for about 8 months, the audience is not wide, so choose vUE 2.x version

At this point, the project construction is complete, you can CD open the project, run.

3. Add element-UI as well as nprogress and normalize.css and configure vue.config.js

  • First installationelement-ui.nprogressandnormalize.css
npm install element-ui nprogress normalize.css
Copy the code

Since Element-UI uses sask-Loader, this is also required

npm install sass-loader
Copy the code

The current project plug-ins are as follows:

  • Configure the vue. Config. Js

Vue. Config. js is an optional configuration file that is automatically loaded by @vue/cli-service if it exists in the root directory of the project (the same as package.json). You can also use the vue field in package.json, but note that this requires you to follow the JSON format strictly.

Create vue.config.js in the root directory

Details of the official configuration vue.config.js

'use strict' const path = require('path') function resolve(dir) { return path.join(__dirname, dir) } // All configuration item explanations can be find in https://cli.vuejs.org/config/ module.exports = { PublicPath: outputDir: 'dist', // Build the output directory (package location) assetsDir: 'static', // directory where static resources (js, CSS, img, fonts) are generated (relative to outputDir) lintOnSave: false, // Check whether the syntax productionSourceMap: DevServer: {port: 8888, open: true,}, configureWebpack: {port: 8888, open: true,} {/ / absolute path to resolve: {alias: {' @ ': resolve (' SRC')}}}}Copy the code

3. Function realization

First talk about ideas, let everyone have a general impression, not to see the code in the fog.

Divided into the following steps:

  1. The front end writes the routing table locally and the roles of each route, that is, which roles can see the menu/route.
  2. When logging in, the backend requests the role of the logged-in user (administrator, normal user)
  3. Using the route guardian (router.beforeEach), compares the obtained user role with the local routing table, filters out the routes corresponding to the user, and uses the routes to perform menu rendering
  1. We will store it in thestorageIn thetokenAs a sign whether the user is logged in, if currentlystorageThere aretoken“Indicates that the current system has been logged in
  2. All pages of the system are divided into two categories: those that need login to view, and those that do not need loginlogin.vue.register.vueEtc.
  3. When the front end redirects routes, make the following judgments:

Here are a few additional points from the technology stack perspective:

  1. invue-routertheBeforeEach methodTo realize the above logic, judge the front-end jump direction;
  2. For tutorial purposes, do not introduce the back end, useUser information for simulated dataRespond as an intercepting service request from Axios;
  3. throughwindow.localStorage.setItemdouserInfoState management;

4. To achieve

According to the above steps, we carry out the implementation of each step

1. Write mock data to simulate the data source returned by the back-end

DynamicUser is the simulated back-end data. The general back-end database is divided into a user user table and a role permission routing table. The back-end is not involved here, so only the data source of the last back-end output is provided.

A complete example of back-end data is as follows:

Const dynamicUser = [{name: "admin ", avatar: "https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/ccb565eca95535ab2caac9f6129b8b7a~300x300.image", desc: "The administrator - admin", the username: "admin", the password: "654321", token: "rtVrM4PhiFK8PNopqWuSjsc1n02oKc3f," routes: [{id: 1, name: "/", path: "/", component: "Layout", redirect: "/index", hidden: false, children: [ { name: "index", path: "/index", meta: { title: "index" }, component: "index/index" }, ]}, { id: 2, name: "/form", path: "/form", component: "Layout", redirect: "/form/index", hidden: false, children: [ { name: "/form/index", path: "/form/index", meta: { title: "form" }, component: "form/index" } ]}, { id: 3, name: "/example", path: "/example", component: "Layout", redirect: "/example/tree", meta: { title: "example" }, hidden: false, children: [ { name: "/tree", path: "/example/tree", meta: { title: "tree" }, component: "tree/index" }, { name: "/copy", path: "/example/copy", meta: { title: "copy" }, component: "tree/copy" } ] }, { id: 4, name: "/table", path: "/table", component: "Layout", redirect: "/table/index", hidden: false, children: [ { name: "/table/index", path: "/table/index", meta: { title: "table" }, component: "table/index" } ] }, { id: 5, name: "/admin", path: "/admin", component: "Layout", redirect: "/admin/index", hidden: false, children: [ { name: "/admin/index", path: "/admin/index", meta: { title: "admin" }, component: "admin/index" } ] }, { id: 6, name: "/people", path: "/people", component: "Layout", redirect: "/people/index", hidden: false, children: [ { name: "/people/index", path: "/people/index", meta: {title: "people"}, Component: "people/index"}]}]}, {title: "people"}, Component: "people/index"}]}, {name: "average user ", {avatar: "https://sf1-ttcdn-tos.pstatp.com/img/user-avatar/6364348965908f03e6a2dd188816e927~300x300.image", desc: "Username: ordinary users - people", "people", the password: "123456", token: "4 es8eydwznxrcx3b3439emtfnikrbywh routes: [{id: 1, name: "/", path: "/", component: "Layout", redirect: "/index", hidden: false, children: [ { name: "index", path: "/index", meta: { title: "index" }, component: "index/index" }, ]}, { id: 2, name: "/form", path: "/form", component: "Layout", redirect: "/form/index", hidden: false, children: [ { name: "/form/index", path: "/form/index", meta: { title: "form" }, component: "form/index" } ]}, { id: 3, name: "/example", path: "/example", component: "Layout", redirect: "/example/tree", meta: { title: "example" }, hidden: false, children: [ { name: "/tree", path: "/example/tree", meta: { title: "tree" }, component: "tree/index" }, { name: "/copy", path: "/example/copy", meta: { title: "copy" }, component: "tree/copy" } ] }, { id: 4, name: "/table", path: "/table", component: "Layout", redirect: "/table/index", hidden: false, children: [ { name: "/table/index", path: "/table/index", meta: { title: "table" }, component: "table/index" } ] }, { id: 6, name: "/people", path: "/people", component: "Layout", redirect: "/people/index", hidden: false, children: [ { name: "/people/index", path: "/people/index", meta: { title: "people" }, component: "people/index" } ] } ] } ] export default dynamicUserCopy the code

It can be seen that generally, after login, the returned data contains a user’s name, profile picture, brief description and token (username and password are only used to simulate the login data, in normal business flows, the backend can not be taken out). , routes is the differential dynamic route between admin and people. Admin has an admin page, while people does not.

Some developers prefer complete static routes in the front end, and then write the role of the user according to the meta attribute of the router. When logging in, they filter and compare the permissions according to the permissions returned by the back end, and process the routes corresponding to the user role well. This is also a mainstream way of dealing with it. This is tantamount to putting all the routing and permission business processing in the front end, once the online release, want to modify the need to repackage processing, and can not be dynamically added and deleted through the background

Such as:

Router /index.js {path: '', Component: layout, // Children: [{path: 'main', component: layout, // Children: [{path: 'main', component: Roles: ['user', 'admin']}}Copy the code

Another solution is to hand all the route permissions to the back-end end. The back-end end obtains the role permissions according to the front-end account and password, processes the routes, and throws out the routes matching the role. This writing method front-end calculation is not too large, and easy to modify and later maintenance and dynamic increase, deletion, change and check, this paper is to achieve in this form.

2. Simulate user login to obtain user permissions and routes

  1. inmain.jsInside, import the page, used as a route guard
import Vue from "vue" import App from "./App.vue" import router from "./router" import store from "./store" import ElementUI from "element-ui" import 'ElementUI /lib/theme-chalk/index.css' import "./router/router-config" // ProductionTip = false Vue. Use (ElementUI) new Vue({router, store, render: (h) => h(App), }).$mount("#app")Copy the code
  1. The login

Mock * mock * mock * mock * mock * mock * mock

Take the user role, store it in localStorage, and go to the home page

  • In this case, because of the use of the Element-UI form submission, so directthis.$refs.userForm.validate

The element-UI form submits the document!

  • Here,dynamicUserThis is the data stream of the mock, and the back end usually returns the corresponding result directly, but since FastMock is easy to fail, it is written by hand.

Define flag for login verification. If the corresponding username and password cannot be found in the loop, the user is told that the account password is incorrect and login failed.. But if one is successful, then flag is for! 0, and return the corresponding user information, user route, etc. In the end, the route hop initialization page (the home page) is displayed, and dynamic route loading and route hop are performed.

import dynamicUser from ".. /.. /mock" import { Message } from "element-ui" login() { this.$refs.userForm.validate(( valid ) => { if(valid) { let flag = ! 1 window.localStorage.removeItem("userInfo") dynamicUser.forEach(item => { if(item["username"] == this.user['username'] && item["password"] == this.user['password']) { flag = ! 0 Message({type: 'success', Message: "login successful ", showClose: true, duration: 3000}) window. LocalStorage. SetItem (" the userInfo ", JSON. Stringify (item)) / / here catch catch mistakes, and don't print, To explain this below. $router. Replace ({path: "/"}). The catch (() = > {})}}) if (! Flag) Message({type: 'warning', Message: "Account password error, please try again!" , showClose: true, duration: 3000 }) } else return false }) }Copy the code

Explanation: If a catch error is not caught and is not printed, the error shown in the figure appears. Cause: Vue-router routing version update is a problem, causing route jump failure. This error is thrown, but does not affect program functions

  • Solution 1:

$router.push(“/ XXX “).catch(() => {})

  • Solution 2:

Global solution, replace route Push and replace methods, placed in SRC /router/index.js:

const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
    if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
    return originalPush.call(this, location).catch(err => err)
}
Copy the code
  • Solution 3:

Reduce vue-router to 3.0.7, change it manually, then delete node_modules, and then NPM install

Is amended as:

3. (emphasis) The route guardian intercepts beforeach and dynamically renders outlets from the table

1. InrouterFolder, createrouter.config.jsFile, used to do the intercept page of the route guardian

2. The introduction ofrouter.Layout.NProgressThree plug-in
  • The router description

Js router is a new vue-router, which is equivalent to the vue-router object

  • Layout instructions

This is the outline of the page, and the page details are as follows

  • NProgress is a progress bar plug-in
import router from "./index" import Layout from ".. /layout/index" import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar styleCopy the code
  • There are three parameters to, form, and next in the beforeEach of the router that correspond to where to go, where to come from, and next

  • Next, according to where (to), we need to determine whether the route points to the routing address array to be filtered. If so, we can directly enter the page without judgment, such as the login page, registration page, password retrieval, etc. (depending on the specific business needs).

const filterRoutes = ["/login"] if (filterRoutes.indexOf(to.path) ! == -1) {next() return false}Copy the code
  • If the number of static routes in router/index.js is equal to the number of static routes in router/index.js, it indicates that the number of dynamic routes is not loaded yet. If the number of static routes in router/index.js is equal to the number of static routes in router/index.js, it indicates that the number of dynamic routes is not loaded yet
// Since I only make a login page in my current tutorial, So that is only a static page if the router. The options. Routes. Length = = 1) {/ / dynamic loading route here} else next () / / show that routing loaded, can be directly into the pageCopy the code
  • When the route is not loaded, you need to get the one cached at logintokenandRouting stackBecause ofThe refreshThe time,Vuex data cannot be persistedSo advice is bestroutesandtokenIn theThe cache storageInside, of course,cookiesI could do it on the inside, but in that case,Once the browser is closed, you need to log in again the next time you open it.
// Get the token and the original route array // Here we need to do the null value merge operation, in case the route exists, but the token is invalid, Then JSON. Parse failed to escape the situation lead to the error of const the userInfo = JSON. Parse (window. LocalStorage. The getItem (" the userInfo "))?? "" // When both token and original route exist // enter the route to perform route filtering and jump encapsulation functions // otherwise, If (userinfo.token && Userinfo.routes) onFilterRoutes(to, next, userinfo.routes) else next({path: "/login", replace: true })Copy the code

Null value merge operator (??) The right-hand null-value merge operator (??) is returned only if the left side is null and undefined. Is a logical operator that returns the right-hand operand if the left-hand operand is null or undefined, otherwise returns the left-hand operand.

  • When entering route filtering and forward encapsulation
  1. Perform an asynchronous request first to ensure that route filtering and path completion are complete. First pass routes to recursive function (filterASyncRoutes), which is used for path completion and Layout judgment and assignment. In addition, when routes have children, the recursive function (filterASyncRoutes) needs to be called again. Finally, the processed routing stack is returned to the route filtering function

  2. Sort the routes according to the routes returned by the asynchronous request. After all, when the user processes the routes dynamically, the order displayed is not the same as that in the process, which is not good.

  3. After routing is completed, add routes to router.options.routes dynamically. Use addRoute(item) to insert routes into the routing table.

  4. Finally, you can jump back to the current page

Function loadView(view) {return () => import(' @/views/${view} ')} // Async function OnFilterRoutes (to, next, e) {const routes = await filterASyncRoutes(e) // b) => a['id'] - b['id']) routes.forEach(item => { router.options.routes.push(item) router.addRoute(item) }) next({ ... to, replace: Function filterASyncRoutes(data) {const routes = data.filter(item => {const routes = data.filter(item => { If (item[" Component "] === "Layout") item.component = Layout else item[" Component "] = loadView(item[" Component "]) If (item["children"] && item["children"].length > 0) item["children"] = filterASyncRoutes(item.children) return  true }) return routes }Copy the code
tips:
  1. Why userouter.addroute, instead of usingrouter.addRoutes

Router.addroutes is obsolete: Router.addroutes () is used instead. Router. addRoute accepts either a routing rule, an object, or a string and an object.

  1. Why use () => import(@/views/${ view }) to do route stitching

Lazy loading: also called lazy loading, that is, when you need to load, along with the load or issues related to the import 】 () webpack4 lazy loading using variable error address: www.cnblogs.com/chenxi188/p…

Difference between import and require

The most important idea in Node programming is modularity. Both import and require are used by modularity.

Follow the specifications
  • requireIs the AMD specification introduction method
  • importIs an ES6 syntax standard that must be converted to ES5 syntax for browser compatibility
Call time
  • Require is a run-time call, so require can theoretically be used anywhere in the code
  • Import is called at compile time, so it must be placed at the top of the file
nature
  • Require is an assignment process. The result of require is an object, a number, a string, a function, etc., and the result of require is assigned to a variable
  • Import is a deconstruction process, but none of the engines currently implement import. We use Babel in Node to support ES6, which is simply transcoding ES6 to ES5 and then executing it. The import syntax is transcoding to require

Project & source code

Source address (gitee) :gitee.com/lemonote/vu…

Project address: dynamic.lemonotes.cn/#/login

The whole process is finished, and then it’s easy to make people confused

1. Display menu \ according to route

Code position: / SRC/Layout/sideBar/sidebaritem vue, first look at the elementUI menu component, the first to learn about some basic parameters, here I render the menu as a component: The recursive attribute is used to ensure that multi-level menus can be generated. I suggest that if you are not familiar with it, you use components to simulate writing a menu containing jump functions and icon display, and then look at the components I write

2. User exit system code position: / SRC/layout/headerTemp/index. The vue on exit, remember to remove existing localStorage user roles, and then use this. $router. Replace ({path: “/login”}) jumps to the login page,

Why use location.reload(), which clears the route from the previous addRoute and ensures that the correct menu is redrawn when the next user logs in

{/ / log out handleLogout (key) if (key = = "logout") {window. LocalStorage. RemoveItem (" the userInfo ") Message ({type: 'success', message: "Exit login ", showClose: true, duration: 3000}) this.$router. Replace ({path: "/login" }) location.reload() } }Copy the code

3. Why not use VUex? It was originally intended to use VUex for routing processing, but later it was found that vuex could not persist data when the browser manually refreshed or passively refreshed. In short, the value of state in VUex would be cleared. I chose cache storage to handle routing problems.

If have incorrect place, still hope small partner corrects ha

The last

Public number: small he growth, the Buddha department more text, are their own once stepped on the pit or is learned things

Interested little partners welcome to pay attention to me oh, I was: he Small Life. Everybody progress duck together