Vscode plug-ins and configuration recommendations

This is a study summary of Vue-Elemen-Admin.

The official documentation

The directory structure

├ ─ ─ the build# Build relevant├ ─ ─ the mock# project mock data├ ─ ─ the plop - templates# Basic template├ ─ ─ the public# static resources│ │ ─ ─ the favicon. Ico# the favicon icon│ └ ─ ─ index. HTML# HTML templates├ ─ ─ the SRC# source code│ ├ ─ ─ API# all requests│ ├ ─ ─ assetsStatic resources such as theme fonts│ ├ ─ ─ the components# Global common component│ ├ ─ ─ directive# global directive│ ├ ─ ─ filters# global filter│ ├ ─ ─ the ICONS# Project all SVG ICONS│ ├ ─ ─ lang# Internationalization language│ ├ ─ ─ layout# the overall layout│ ├ ─ ─ the router# routing│ ├ ─ ─ store# Global Store management│ ├ ─ ─ styles# global style│ ├ ─ ─ utilsGlobal public method│ ├ ─ ─ vendor# public vendor│ ├ ─ ─ views# Views all pages│ ├ ─ ─ App. Vue# Entry page│ ├ ─ ─ the main js# entry file load component initialization etc│ └ ─ ─ permission. Js# Permission management├ ─ ─ tests# test├ ─ ─. Env. XXX# Environment variable configuration├ ─ ─. Eslintrc. Js# esLint configuration item├ ─ ─ babelrc# Babel - loader configuration├ ─ ─. Travis. YmlAutomate CI configuration├ ─ ─ vue. Config. Js# vue - cli configuration├ ─ ─ postcss. Config. Js# postcss configuration└ ─ ─ package. Json# package.json
Copy the code

To start developing

# Clone project
git clone https://github.com/PanJiaChen/vue-element-admin.git

Enter the project directory
cd vue-element-admin

# install dependencies
npm install

# It is recommended not to install dependencies directly using CNPM, there are all kinds of weird bugs. You can perform the following operations to solve the problem that the NPM download speed is slow
npm install --registry=https://registry.npm.taobao.org

# start service
npm run dev

# release

# Formal environment
npm run build:prod

# Integrated environment
npm run sit
Copy the code

If the node – sass installation error, you can retry NPM install node – sass, if still won’t do, can NPM install – registry=https://registry.npm.taobao.org/, and installation; CNPM install node-sass: CNPM install node-sass: CNPM install node-sass: CNPM install node-sass: CNPM

Node-sass error can be found at github.com/PanJiaChen/…

SRC directory

Views and API two modules one by one correspondence, so as to facilitate maintenance

API: Request interface folder

Views: Page component folder

api

A.js corresponds to a module in a views folder

Such as:

Login. js in the API corresponds to the login folder in the views. If there is a public module, it is good to place it separately

Usage:

1. Create a file in/SRC/APIxxx.js, e.g.bind.js

2. The introduction of

import axios from "@/utils/request";
import * as qs from "qs";

// Remove control - list page
export const deleteClassPlateCtrlByBatch = (params) = > {
  return axios.post(
    `ctrlSystem/deleteClassPlateCtrlByBatch`,
    qs.stringify(params)
  );
};
Copy the code

Use 3.

3.1 introduced
import { deleteStudentPlateCtrlByBatch } from "@/api/bindManage";
Copy the code
3.2 Used in functions
deleCtro(){
     let defaultBaseInfo = this.$store.state.user.defaultBaseInfo;
     let obj = {
        interUser: "runLfb".interPwd: hex_md5(1234578),
        operateAccountNo: defaultBaseInfo.operateAccountNo,
        belongSchoolId: defaultBaseInfo.belongSchoolId,
        schoolId: queryObj.schoolId,
        classId: queryObj.classId,
        surfacePlateBindRequestVoList: this.classIdList,
      };
      let params = {
        requestJson: JSON.stringify(obj)
      }
      console.log(obj);
      deleteStudentPlateCtrlByBatch(params).then((r) = > {
        console.log("deleteClassPlateCtrlByBatch", r);
        this.success(r);
      });
}
Copy the code

Encapsulation axios

1. /src/utils/request.js

import Vue from "vue";
import axios from "axios";
import { MessageBox, Message } from "element-ui";
import store from "@/store";
import { getToken } from "@/utils/auth";
import router from ".. /router";
import { Loading } from "element-ui";
import Cookies from "js-cookie";
// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 10000.// request timeout
});

// //========================================================token ======================================
var loading = ""; // Define the loading variable

function startLoading() {
  // Use Element loading-start
  loading = Loading.service({
    lock: true.// text: 'loading... ',
    background: "rgba(0, 0, 0, 0)"}); }function endLoading() {
  // Use the Element loading-close method
  loading.close();
}
// Refresh the token expiration time judgment
function isRefreshTokenExpired() {
  const oData = store.getters.getTokenTime; // This is the timestamp saved at login time
  const nDta = new Date().getTime();
  const stamp = nDta - oData; // The difference in microseconds
  // const seconds = parseInt((stamp % (1000 * 60 * 60)) / 1000)
  const seconds = parseInt(stamp / 1000);
  return (
    seconds >= (store.getters.getTokenUsable * 3) / 4 &&
    seconds < store.getters.getTokenUsable
  );
  // return false
}
/ / refresh token
function getRefreshToken() {
  // Refresh token note the service used here
  return service.post("/public/regenerationToken").then((res) = > {
    return Promise.resolve(res.data);
  });
}

// Indicates whether a refresh is being performed
window.isRefreshing = false;
// Store an array of requests
let refreshSubscribers = [];

/* Push all requests into an array */
function subscribeTokenRefresh(cb) {
  refreshSubscribers.push(cb);
}

// The request in the array gets the new token and executes itself, using the new token to request data
function onRrefreshed(token) {
  refreshSubscribers.map((cb) = > cb(token));
}
/ / delete the cookie
function removeCookie() {
  Cookies.remove("username", { path: "/" });
  Cookies.remove("password", { path: "/" });
}
// request interceptor
service.interceptors.request.use(
  (config) = > {
    startLoading();
    let url = config.url;
    // Fix the problem: Axios does not encode functional characters in urls, manually
    // Get parameter encoding
    if (config.method === "get" && config.params) {
      url += "?";
      const keys = Object.keys(config.params);
      for (const key of keys) {
        url += `${key}=The ${encodeURIComponent(config.params[key])}& `;
      }
      url = url.substring(0, url.length - 1);
      config.params = {};
    }
    config.url = url;

    const accessToken = store.getters.getAccessToken; // The token saved locally
    const refreshToken = store.getters.getRefreshToken; // The token saved locally
    /* Check whether the token exists */
    if(accessToken && accessToken ! ="undefined") {
      /* Add token type, token*/ to request header
      config.headers.access_token = accessToken;
      config.headers.client_type = store.state.user.client_type;
      // config.url = config.url + '? t=' + (new Date()).getTime().toString(); // Clear the cache
      /* Determine whether the token is about to expire */
      if (
        isRefreshTokenExpired() &&
        config.url.indexOf("public/regenerationToken") = = = -1
      ) {
        if (!window.isRefreshing) {
          // * Check whether the refresh is being performed */
          window.isRefreshing = true;
          /* Initiate a token refresh request */
          // config.headers.Authorization = ''
          getRefreshToken();

          /* Put the request (token)=>{.... } are pushed into an array */
          const retry = new Promise((resolve, reject) = > {
            /* (token) => {... } this function is the callback function */
            subscribeTokenRefresh((token) = > {
              // config.headers.common['Authorization'] = 'bearer ' + token;
              config.headers.access_token = token;
              /* Suspend the request */
              resolve(config);
            });
          });
          return retry;
        }
        return config;
      } else if (config.url.search(/\/public\/regenerationToken$/) > =0) {
        config.headers.refresh_token = refreshToken;
        return config;
      } else {
        returnconfig; }}else {
      returnconfig; }},(error) = > {
    return Promise.reject(error); });// response interceptor
service.interceptors.response.use(
  /** * Perform some operations */ based on the backend code
  (response) = > {
    endLoading();
    const res = response.data;
    // No identity token or expired
    if (res.code == 11002 || res.code == 11001) {
      store.commit("user/setAccessToken".null);
      store.commit("user/setRefreshToken".null);
      store.commit("user/setTokenTime".null);
      store.commit("user/setTokenUsable".null);
      localStorage.clear();
      removeCookie();
      Message({
        message: "Login information invalid. Please log in again.".type: "error".duration: 3 * 1000}); router.push("/login");
    }
    // alert(response.config.url)
    // console.log(response.config.url, 'response.config.url')
    if (
      (response.config.url.search(/\/user\/phoneLogin$/) > =0 && res.flag) ||
      (response.config.url.search(/\/user\/registerByVerificationCode$/) > =0 &&
        res.flag)
    ) {
      store.commit("user/setAccessToken", response.headers.access_token);
      store.commit("user/setRefreshToken", response.headers.refresh_token);
      store.commit("user/setTokenTime".new Date().getTime());
      store.commit("user/setTokenUsable", response.headers.token_usable);
    } else if (
      response.config.url.search(/\/public\/regenerationToken$/) > =0
    ) {
      if (res.code == "0") {
        store.commit("user/setAccessToken", response.headers.access_token);
        store.commit("user/setRefreshToken", response.headers.refresh_token);
        store.commit("user/setTokenTime".new Date().getTime());
        store.commit("user/setTokenUsable", response.headers.token_usable);
        onRrefreshed(response.headers.access_token);
        window.isRefreshing = false;
        refreshSubscribers = [];
      } else {
        /* Clears locally saved */
        store.commit("user/setAccessToken".null);
        store.commit("user/setRefreshToken".null);
        store.commit("user/setTokenTime".null);
        store.commit("user/setTokenUsable".null);
        localStorage.clear();
        removeCookie();
        Message({
          message: "Login information invalid. Please log in again.".type: "error".duration: 3 * 1000}); router.push("/login"); }}return Promise.resolve(res);
  },
  (error) = > {
    // Vue. Prototype.$log4b.error(" response error "+error.config.url+" error message "+ json.stringify (error))
    console.log("err", error); // for debug
    if (error && error.response) {
      switch (error.response.status) {
        case 400:
          error.message = "Request error (400)";
          break;
        case 401:
          return history.push("/login");
          break;
        case 403:
          error.message = "Access denied (403)";
          break;
        case 404:
          error.message = "Error request (404)";
          break;
        case 408:
          error.message = "Request timed out (408)";
          break;
        case 500:
          error.message = "Server error (500)";
          break;
        case 501:
          error.message = "Service Not Realized (501)";
          break;
        case 502:
          error.message = "Network error (502)";
          break;
        case 503:
          error.message = "Service unavailable (503)";
          break;
        case 504:
          error.message = "Network Timeout (504)";
          break;
        case 505:
          error.message = "HTTP version not supported (505)";
          break;
        default:
          error.message = 'Connection error (${error.response.status})! `;
      }
      Message({
        message: error.message,
        type: "error".duration: 3 * 1000}); }return Promise.reject(error); });export default service;
Copy the code

components

Components are global components that can be used by multiple pages, not just your current page (e.g. upload components). Some page-level components are suggested to be placed under their respective views files for easy management.

store

Used to write vuex

Usage:

1. ./store/index.js

Vuex-along: Solved the problem of vuex refresh disappearing, with 215 weekly downloads

Vuex-along uses the document address

Vuex-persistedstate: Solve the problem of vuex refresh disappearing, 10W + weekly downloads

Vuex-persistedstate Indicates the document address

Vuex-persistedstate is recommended

  • Vuex -along weekly downloads

  • Vuex-persistedstate Weekly downloads

import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
import createVuexAlong from "vuex-along";
Vue.use(Vuex);

// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context("./modules".true./\.js$/);

// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) = > {
  // set './app.js' => 'app'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/."$1");
  const value = modulesFiles(modulePath);
  modules[moduleName] = value.default;
  return modules;
}, {});

const store = new Vuex.Store({
  modules,
  getters,
  plugins: [createVuexAlong()],
});

export default store;
Copy the code
2.0. Create a new one in /store/modules/xxx.js, for instance,bind.js

2.1.bind.js code:
const state = {
  bindFilter: "".// Filter the conditional cache
  detailRouter: false.// Whether the details page is back
};

const mutations = {
  bindFilter_Fun: (state, data) = > {
    state.bindFilter = data;
    localStorage.setItem("bindFilter".JSON.stringify(data)); // Cache in localStorage to solve vuex refresh problem
  },
  detailRouter_Fun: (state, data) = > {
    state.detailRouter = data;
    localStorage.setItem("detailRouter".JSON.stringify(data)); }};const actions = {
  bindFilter({ commit, state }, data) {
    commit("bindFilter_Fun", data);
  },
  detailRouter({ commit, state }, data) {
    commit("detailRouter_Fun", data); }};export default {
  namespaced: true,
  state,
  mutations,
  actions,
};
Copy the code
3. /store/getters.js
const getters = {
  // Bind manager filters data
  getBindFilter: (state) = >
    state.bind.bindFilter || JSON.parse(localStorage.getItem("bindFilter")),
  getDetailRouter: (state) = >
    state.bind.detailRouter || JSON.parse(localStorage.getItem("detailRouter")),};export default getters;
Copy the code
4. Usage in components

Please see more method: www.jianshu.com/p/0f13a6bec…

Deposited in the data: this $store. Dispatch (" bind/detailRouter ", false) to get the data: this. $store. Getters. GetDetailRouterCopy the code
5.namespaced

Store in VUex is managed by modules, so each module needs to be introduced into store index.js. In order to solve the problem of naming conflicts between different modules, namespaced:true for different modules is used. For adding getter, actions, and mutations to different pages, add the name of the module that you belong to. On the other hand, namespaced:false means that you don’t need to add the module name.

Document links

Icon Indicates how to use the icon

Place the downloaded ICONS in the/SRC/ICONS/SVG/folder

1. Usage

<svg-icon icon-class="password" />// icon-class is the name of iconCopy the code

2. Change colors

By default, svG-icon will read its parent color Fill: currentColor;

You can change the parent’s color or just change the color of fill.

The related documents

B: The layout is better than the layout

Here is a simple look at the layout of the layout, easy to modify later;

In simple terms, app.vue contains layout, layout also contains TagsView, sideBar, AppMain; And then everything we write is inside AppMain

  • app.vue
    • layout
      • TagsView
      • sideBar
      • AppMain (Content container)

3. Environment variable configuration

1. Local development environment

.env.development– This corresponds to the local address packaging environment

# just a flag
ENV = 'development'

# base api
#VUE_APP_BASE_API = '/dev-api'VUE_APP_BASE_API ='/api'VUE_APP_BASE_API ='http://119.23.xxx.xxx:9001/service-soa'

# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
# It only does one thing by converting all import() to require().
# This configuration can significantly increase the speed of hot updates,
# when you have a large number of pages.
# Detail:  https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.jsThe development environment does not use routing lazy loading VUE_CLI_BABEL_TRANSPILE_MODULES =true
Copy the code

2. Formal environment for going online

.env.production—— This corresponds to the formal environment

# just a flag
ENV = 'production'VUE_APP_BASE_API ='http://119.23.xxx.xx:8080/service-soa'
Copy the code

3. Integrated test environment

.env.sit—— This corresponds to the formal environment

#
NODE_ENV = production

# just a flag
ENV = 'sit'VUE_APP_BASE_API ='http://119.23.xxx.71:9001/service-soa'
Copy the code

Fourth, the vue. Config. Js

Related documents -My

Related documents -Other

"use strict";
const path = require("path");
const defaultSettings = require("./src/settings.js");
function resolve(dir) {
  return path.join(__dirname, dir);
}
const name = defaultSettings.title || "vue Element Admin"; // page title
const port = process.env.port || process.env.npm_config_port || 9530; // Port number port
module.exports = {
  publicPath: "/".outputDir: "dist".assetsDir: "static".// lintOnSave: process.env.NODE_ENV === 'development',
  lintOnSave: false.productionSourceMap: false.devServer: {
    hot: true./ / thermal load
    port: port,
    https: false.// false Disables HTTPS. True enables HTTPS
    // open: true,
    overlay: {
      warnings: false.errors: true,},// before: require('./mock/mock-server.js'),
    proxy: {
      "/api": {
        target: "http://119.23.xxx.xxx:9001/service-soa".// A virtual server is created locally and sends the requested data and receives the requested data at the same time, so that there is no cross-domain problem between the server and the server
        changeOrigin: true.ws: true.pathRewrite: {
          / / replace the target address of the request, that is to say when you request http://api.jisuapi.com/XXXXX this address directly after written/API/XXX
          "^/api": "/",}},"/qq": {
        target: "https://xxx.qq.com/oauth2.0".// A virtual server is created locally and sends the requested data and receives the requested data at the same time, so that there is no cross-domain problem between the server and the server
        changeOrigin: true.ws: true.pathRewrite: {
          / / replace the target address of the request, that is to say when you request http://api.jisuapi.com/XXXXX this address directly after written/API/XXX
          "^/qq": "/",}},"/oss": {
        target: "http://xxx.xxx.aliyuncs.com".// A virtual server is created locally and sends the requested data and receives the requested data at the same time, so that there is no cross-domain problem between the server and the server
        changeOrigin: true.ws: true.pathRewrite: {
          / / replace the target address of the request, that is to say when you request http://api.jisuapi.com/XXXXX this address directly after written/API/XXX
          "^/oss": "/",},},},},configureWebpack: {
    // provide the app's title in webpack's name field, so that
    // it can be accessed in index.html to inject the correct title.
    name: name,
    resolve: {
      alias: {
        "@": resolve("src"),}}},chainWebpack(config) {
    config.plugins.delete("preload"); // TODO: need test
    config.plugins.delete("prefetch"); // TODO: need test

    // set svg-sprite-loader
    config.module.rule("svg").exclude.add(resolve("src/icons")).end();
    config.module
      .rule("icons")
      .test(/\.svg$/)
      .include.add(resolve("src/icons"))
      .end()
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: "icon-[name]",
      })
      .end();

    // set preserveWhitespace
    config.module
      .rule("vue")
      .use("vue-loader")
      .loader("vue-loader")
      .tap((options) = > {
        options.compilerOptions.preserveWhitespace = true;
        return options;
      })
      .end();

    config
      // https://webpack.js.org/configuration/devtool/#development
      .when(process.env.NODE_ENV === "development".(config) = >
        config.devtool("cheap-source-map")); config.when(process.env.NODE_ENV ! = ="development".(config) = > {
      config
        .plugin("ScriptExtHtmlWebpackPlugin")
        .after("html")
        .use("script-ext-html-webpack-plugin"[{// `runtime` must same as runtimeChunk name. default is `runtime`
            inline: /runtime\.. *\.js$/,
          },
        ])
        .end();
      config.optimization.splitChunks({
        chunks: "all".cacheGroups: {
          libs: {
            name: "chunk-libs".test: /[\\/]node_modules[\\/]/,
            priority: 10.chunks: "initial".// only package third parties that are initially dependent
          },
          elementUI: {
            name: "chunk-elementUI".// split elementUI into a single package
            priority: 20.// the weight needs to be larger than libs and app or it will be packaged into libs or app
            test: /[\\/]node_modules[\\/]_? element-ui(.*)/.// in order to adapt to cnpm
          },
          commons: {
            name: "chunk-commons".test: resolve("src/components"), // can customize your rules
            minChunks: 3.// minimum common number
            priority: 5.reuseExistingChunk: true,}}}); config.optimization.runtimeChunk("single"); }); }};Copy the code

Five, the package. The json

{
  "name": "vue-element-admin"."version": "2"."description": "A magical vue admin. An out-of-box UI solution for enterprise applications. Newest development stack of vue. Lots of awesome features"."author": "Pan <[email protected]>"."license": "MIT"."scripts": {
    "dev": "vue-cli-service serve --open"."sit": "vue-cli-service build --mode sit"."prod": "vue-cli-service build --mode production"."build:prod": "vue-cli-service build"."build:stage": "vue-cli-service build --mode staging"."preview": "node build/index.js --preview"."lint": "eslint --ext .js,.vue src"."test:unit": "jest --clearCache && vue-cli-service test:unit"."test:ci": "npm run lint && npm run test:unit"."svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"."new": "plop"
  },
  "lint-staged": {
    "src/**/*.{js,vue}": ["eslint --fix"."git add"]},"keywords": [
    "vue"."admin"."dashboard"."element-ui"."boilerplate"."admin-template"."management-system"]."repository": {
    "type": "git"."url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
  },
  "bugs": {
    "url": "https://github.com/PanJiaChen/vue-element-admin/issues"
  },
  "dependencies": {
    "@fortawesome/fontawesome-svg-core": "^ 1.2.29"."@fortawesome/free-brands-svg-icons": "^ 5.13.1." "."@fortawesome/free-regular-svg-icons": "^ 5.13.1." "."@fortawesome/free-solid-svg-icons": "^ 5.13.1." "."@fortawesome/vue-fontawesome": "^ 0.1.10"."arr2tree": "0.0.5"."axios": "0.18.1"."clipboard": "2.0.4"."codemirror": "5.45.0"."crypto-js": "^ 4.0.0"."driver.js": "0.9.5"."dropzone": "5.5.1"."echarts": "2"."element-ui": "2.13.0"."file-saver": "2.0.1"."fingerprintjs2": "^ 2.1.0." "."fundebug-javascript": "^" 2.4.2."fundebug-vue": "0.0.1"."fuse.js": "3.4.4"."js-cookie": "2.2.0"."jsonlint": "1.6.3"."jszip": 3.2.1 ""."kindeditor": "^ 4.1.10"."moment": "^ 2.27.0"."normalize.css": "7.0.0"."nprogress": "0.2.0"."path-to-regexp": "2.4.0"."qrcode": "^ 1.4.4." "."qrcodejs2": "Hundreds"."screenfull": "4.2.0"."script-loader": "0.7.2"."showdown": "1.9.0"."sortablejs": "1.8.4"."tui-editor": "1.3.3"."vue": "2.6.10"."vue-count-to": "1.0.13"."vue-router": "3.0.2"."vue-splitpane": "1.0.4"."vuedraggable": "2.20.0"."vuex": "3.1.0"."vuex-along": "^ 1.2.11"."wangeditor": "^ 3.1.1." "."xlsx": "0.14.1"
  },
  "devDependencies": {
    "@babel/core": "7.0.0"."@babel/register": "7.0.0"."@vue/cli-plugin-babel": "3.5.3"."@vue/cli-plugin-eslint": "^ 3.9.1." "."@vue/cli-plugin-unit-jest": "3.5.3"."@vue/cli-service": "3.5.3"."@vue/test-utils": "29 1.0.0 - beta."."autoprefixer": "^ 9.5.1"."babel-core": "7.0.0 - bridge. 0"."babel-eslint": "10.0.1"."babel-jest": "23.6.0"."chalk": "2.4.2"."chokidar": "2.1.5"."connect": "3.6.6"."eslint": "5.15.3"."eslint-plugin-vue": "5.2.2."."html-webpack-plugin": "3.2.0"."husky": "1.3.1"."lint-staged": "8.1.5"."mockjs": "-beta3 1.0.1"."node-sass": "^ 4.9.0"."plop": "2.3.0"."runjs": "^ 4.3.2." "."sass-loader": "^ 7.1.0"."script-ext-html-webpack-plugin": 2.1.3 ""."serve-static": "^ 1.13.2"."svg-sprite-loader": 4.1.3 ""."svgo": "1.2.0"."vue-template-compiler": "2.6.10"
  },
  "engines": {
    "node": "> = 8.9"."npm": "> = 3.0.0"
  },
  "browserslist": ["1%" >."last 2 versions"]}Copy the code

Six, permissions,

1. Route permission

Process:

Click the login button. 2. The login method in vuex is invoked. 3. Listen for route changes and obtain the current user role 5. Obtain the current user information Obtain the role group, save the login status, and return the current role information 6. Match the route permission of the role with all routes and return to the routing group. 7 Mount the route permission obtained from the role to the actual route

Routing permission involved files:

/ SRC/views/login/index. The entrance of the vue login page file

/ SRC/store/modules/user. Js vuex file global method

/ SRC /permission.js Listens for js after route changes

/ SRC/store/mudules/permission. Js through corresponding routing list of characters to return to the login method

SRC/views/permission/components/SwitchRoles vue file switch roles This login Switch roles will go

/src/router/index.js

Router: takes two parameters

Export const constantRouterMap = [] is the initial routing parameter, Export const asyncRouterMap = [] Dynamic route After a successful login, load different routes based on the back-end permission in router.beforeEach. Different menus are displayed on the left

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
/** * Note: sub-menu only appear when route children.length >= 1 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html * * hidden: True If set to true, the item is not displayed in the sidebar (default is false) * alwaysShow: True If set to true, the root menu will always be displayed * If alwaysShow is not set, * it will become nested when the project has multiple subpaths, otherwise the root menu will not be displayed * redirect: NoRedirect If set noRedirect will not be redirected in the breadcrumb * name:'router-name' name used by 
      
        (must be set!!) * Meta: {roles: ['admin',' Editor '] Control page roles (multiple roles can be set) Title: the name of the 'title' displayed in the sidebar and breadcrumbs (recommended set) icon: The 'svG-name' icon is displayed in the sidebar noCache: true If set to true, the page will not be cached (default is false) affix: true If set to true, the breadcrumb tag will be attached to the Tags view: False If set to false, the item will be hidden in the breadcrumb (default is true) activeMenu: '/example/list' If set to path, the sidebar will highlight the path you set} */
      

/** * constantRoutes * Base route without permission, accessible to all roles */
export const constantRoutes = [
  {
    path: '/login'.component: () = > import('@/views/login/login-index.vue'),
    hidden: true
  },
  {
    path: '/'.component: Layout,
    redirect: '/bind-management'
  },
  {
    path: '/auth-redirect'.component: () = > import('@/views/login/auth-redirect'),
    hidden: true
  },
  {
    path: '/ 404'.component: () = > import('@/views/error-page/404'),
    hidden: true
  },
  {
    path: '/ 401'.component: () = > import('@/views/error-page/401'),
    hidden: true
  },
  {
    path: '/notice'.component: Layout,
    hidden: true.children: [{
      path: 'index'.component: () = > import('@/views/noticeManagement/noticeList'),
      name: 'Notice'.meta: {
        title: 'Message notification'.icon: 'guide'.noCache: true}}}]]/** * asyncRoutes * has permission for admin to access */
export const asyncRoutes = [

  {
    path: '/icon'.component: Layout,
    children: [{
      path: 'index'.component: () = > import('@/views/icons/index'),
      name: 'Icons'.meta: {
        title: 'Icons'.icon: 'icon'.noCache: true.roles: ['noPremission']]}}},const strategyManagementRouter = {
  path: '/strategy-management'.component: Layout,
  redirect: '/strategy-management/index'.meta: {
    title: 'Policy Management'.icon: 'ctrl_icon_strategy'.roles: ['admin']},children: [{path: '/strategy-management/index'.component: () = > import('@/views/strategyManagement/index'),
      name: 'strategyManagement'.alwaysShow: true.meta: {
        title: 'Policy Management'.icon: 'ctrl_icon_strategy'.roles: ['admin'}}]}, {path: '/bind-management'.component: Layout,
  redirect: '/bind-management/index'.meta: {
    title: 'Binding Management'.icon: 'ctrl_icon_bindings'.roles: ['admin'].noCache: false
  },
  children: [{path: '/bind-management/index'.component: () = > import('@/views/bindManagement/index'),
      name: 'bindManagement'.alwaysShow: true.meta: {
        title: 'Binding Management'.icon: 'ctrl_icon_bindings'.roles: ['admin'].noCache: false}}, {path: '/bind-management/detail'.component: () = > import('@/views/bindManagement/bindDetail'),
      name: 'bindDetail'.hidden: true.meta: {
        title: 'Class Details'.icon: 'ctrl_icon_bindings'.roles: ['admin'].activeMenu: '/bind-management/index'.noCache: false}}},// 404 page must be placed at the end !!!
  {
    path: The '*'.redirect: '/ 404'.hidden: true}]const createRouter = () = > new Router({
  scrollBehavior: () = > ({
    y: 0
  }),
  routes: constantRoutes
})
const router = createRouter()
// Reset the route
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router
Copy the code

/src/permission.js

import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress"; // progress bar
import "nprogress/nprogress.css"; // progress bar style
import getPageTitle from "@/utils/get-page-title";
NProgress.configure({
  showSpinner: false});// NProgress Configuration

const whiteList = ["/login"."/auth-redirect"."/dashboard"]; // no redirect whitelist
let flag = 0;

router.beforeEach(async (to, from, next) => {
  // Route loading progress bar
  NProgress.start();
  // Set page title
  document.title = getPageTitle(to.meta.title);

  // Determine whether to log in
  const hasToken = store.getters.getAccessToken;
  if (hasToken) {
    if (to.path === "/login") {
      next({
        path: "/"}); NProgress.done(); }else {
      try {
        const hasAddRoutes =
          store.getters.addRoutes && store.getters.addRoutes.length > 0;
        if (flag === 0| |! hasAddRoutes) {const permissionRoutes = await store.dispatch(
            "user/queryFuncByRoles"
          ); // Trigger permission function to query route and button permission
          const buttonCode = permissionRoutes.buttonCode;
          localStorage.setItem("buttonCode".JSON.stringify(buttonCode)); // Save the permission button locally
          const accessRoutes = await store.dispatch(
            "permission/generateRoutes",
            permissionRoutes.sysFuncViewList
          ); // Get the dynamic route array
          if(! accessRoutes.length) {await store.dispatch("user/resetToken");
            Message.error("This account is not accessible.");
            NProgress.done();
            next(`/login? redirect=${to.path}`);
            return;
          }
          console.log("accessRoutes", accessRoutes); router.addRoutes(accessRoutes); flag++; next({ ... to,replace: true });
        } else{ next(); }}catch (error) {
        // Delete the token and go to the login page
        await store.dispatch("user/resetToken");
        Message.error({
          message: error || "An error occurred. Please try again later."}); next(`/login? redirect=${to.path}`); NProgress.done(); }}}else {
    // If you are not logged in to the whiteList route, you can go to the login page
    if(whiteList.indexOf(to.path) ! = = -1) {
      next();
    } else {
      // Redirect to home page without permission
      next(`/login`); NProgress.done(); }}}); router.afterEach(() = > {
  NProgress.done();
});
Copy the code

/src/store/mudules/permission.js

/* * @Author: your name * @Date: 2020-10-27 17:49:08 * @LastEditTime: 2020-11-18 16:09:40 * @LastEditors: Please set LastEditors * @Description: In User Settings Edit * @FilePath: \Git\plate-control-admin\src\store\modules\permission.js */
import { asyncRoutes, constantRoutes } from "@/router";

/** * Whether meta. Roles in a route meets the 'admin' condition. If so, return true and false@param Roles permission array ['admin'] *@param Route Route array */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some((role) = > route.meta.roles.includes(role));
  } else {
    return true; }}/** * Recursively filters the asynchronous routing table to return the routing table that matches the user role permission *@param routes asyncRoutes
 * @param roles* /
export function filterAsyncRoutes(routes, roles) {
  const res = [];

  routes.forEach((route) = > {
    consttmp = { ... route };if (hasPermission(roles, tmp)) {
      if(tmp.children) { tmp.children = filterAsyncRoutes(tmp.children, roles); } res.push(tmp); }});console.log("roles", res);

  return res;
}

export function getResultRouters(treeData, arr) {
  treeData.forEach((element) = > {
    arr.forEach((ele) = > {
      if (element.path == ele.funcUrl) {
        element.meta.roles = ["admin"]; }});if (element.children && element.children.length > 0) { getResultRouters(element.children, arr); }});return treeData;
}

const state = {
  routes: [].addRoutes: [],};const mutations = {
  SET_ROUTES: (state, routes) = > {
    // Combine the static route and dynamic route when saving the dynamic routestate.addRoutes = routes; state.routes = constantRoutes.concat(routes); }};const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise((resolve) = > {
      let resetRouters, accessedRoutes;
      resetRouters = getResultRouters(asyncRoutes, roles);
      if(! resetRouters.length) { resolve(resetRouters);return;
      }
      accessedRoutes = filterAsyncRoutes(resetRouters, ["admin"]);
      if(! accessedRoutes.length) { commit("SET_ROUTES"[]); }else {
        commit("SET_ROUTES", accessedRoutes); } resolve(accessedRoutes); }); }};export default {
  namespaced: true,
  state,
  mutations,
  actions,
};
Copy the code

Refer to link

2. Button level permission control

2.1 thinking:

  • All buttons that require authentication are displayed on the page. You need to display and hide the permissions on the authentication menu first.

  • Check each role or user can see the permissions saved in the database. The permission data is an array of permission fields.

  • Global custom directive (Directive) control button permission data method, login to get the back end of the button permission array.

  • The instruction is called in each button, and the permission field of the operation is passed in to match the permission field saved by the backend. If the operation button can match, it can be displayed

Our company does not need to judge the button permission according to the menu permission, but only according to the array of permission fields returned by the back end. Then, our company also does instruction encapsulation for this part, the code is as follows

2.2 Usage

  • In/SRC/directive/new/btnPermission/btnPermission. Js

    /src/directive/btnPermission/btnPermission.js

export const hasPermission = {
  install(Vue) {
    Vue.directive("hasPermission", {
      bind(el, binding, vnode) {
        const permissionsNameList = JSON.parse(
          localStorage.getItem("buttonCode"));// Array of buttons
        const permissions = Object.keys(permissionsNameList); // Returns an array of the given object's own enumerable properties, the object's key
        console.log(permissions, "permissions");
        const value = binding.value;
        let flag = true;
        for (const v of value) {
          // Iterate over the array passed in
          if(! permissions.includes(v)) {// Check whether the array given by the back end contains the field passed in. If the array contains the field, display it; if the array does not contain the field, hide it
            flag = false; }}if(! flag) {if(! el.parentNode) { el.style.display ="none";
          } else{ el.parentNode.removeChild(el); }}}}); }};Copy the code
  • in/src/main.jsThe introduction of
// Import permission button file
import { hasPermission } from ".. /src/directive/btnPermission/btnPermission.js"; // Button permission directive

Vue.use(hasPermission); // Button permission directive
Copy the code
  • Method of use
<el-button
  class="inquireButton"
  v-hasPermission="['platectrl_b_policy_search']"
  @click="inquire"
  >Query < / el - button ><el-button
  class="addNewButton"
  v-hasPermission="['platectrl_b_policy_add']"
  @click="addNew"
  >New < / el - button >Copy the code

Refer to link

Seven, media query mobile, PC compatible

Although the Element framework has some adaptive processing, it still needs to be adjusted, so I wrote a set of media queries myself. Where I need to do some styling, I just need to change the width of the screen. In this case, MY REM calculation method is PX /10/2 or adjust directly according to the media query.

1. Usage:

  • In the first/src/styles/In the newmedia.scss
  • Introduced to the/src/index.scss/inside

Just import it in index.scss

@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
@import './btn.scss';

@import "./media.scss"; /* Media query CSS */
Copy the code
  • The following code
/* -----------mobile----------- */
@media screen and (max-width: 480px) {
  /* Login ADAPTS */
  .login-right {
    min-width: 20rem;
    overflow: auto;
  }
  .bg-container {
    width: 100%;
    justify-content: center;
  }
  .login-left {
    display: none;
  }
  .login-left-title_phone {
    margin-bottom: 1rem;
    font-size: 1.4 rem;
    display: block;
  }
  /* Login adaptive end*/

  /* Popover adaptive dialog */
  .Dialog-box {
    max-height: 60%;

    .el-dialog {
      width: 80% ! important;
    }
    .el-form-item--medium .el-form-item__label {
      width: 106px ! important;
    }
    .el-form-item--medium .el-form-item__content {
      margin-left: 72px ! important; }}// Button position
  .el-form-item-btns {
    float: left ! important;
  }
  .inquireButton-father {
    // float: none ! important;
    // width: 22% ! important;}}/* -----------ipad small----------- */
@media screen and (min-device-width: 481px) and (max-device-width: 768px) {
  /* Login ADAPTS */
  .login-right {
    min-width: 20rem;
    overflow: auto;
  }
  .bg-container {
    width: 100%;
    justify-content: center;
  }
  .login-left {
    display: none;
  }
  .login-left-title_phone {
    margin-bottom: 1rem;
    font-size: 1.4 rem;
    display: block;
  }
  /* Popover adaptive dialog */
  .Dialog-box {
    max-height: 60%;

    .el-dialog {
      width: 80% ! important;
    }
    .el-form-item--medium .el-form-item__label {
      width: 106px ! important;
    }
    .el-form-item--medium .el-form-item__content {
      margin-left: 72px ! important; }}// Button position
  .el-form-item-btns {
    float: left ! important;
  }
  .inquireButton-father {
    // float: none ! important;
    width: 22% ! important; }}/* ----------- iPad big----------- */

@media screen and (min-device-width: 768px) and (max-device-width: 1024px) {
  /* Login ADAPTS */
  .user-input {
    background-color: #fff;
    padding: 0 2%;
    margin: 0;
    height: 9rem;
  }
  /* Popover adaptive dialog */
  .Dialog-box {
    max-height: 60%;

    .el-dialog {
      width: 80% ! important;
    }
    .el-form-item--medium .el-form-item__label {
      width: 106px ! important;
    }
    .el-form-item--medium .el-form-item__content {
      margin-left: 72px ! important; }}// Button position
  .el-form-item-btns {
    float: left ! important;
  }
  .inquireButton-father {
    // float: none ! important;
    width: 22% ! important; }}/* ----------- iPad Pro laptop with small screen ----------- */
/* Portrait and Landscape */
@media only screen and (min-device-width: 1025px) and (max-device-width: 1366px) and (-webkit-min-device-pixel-ratio: 1.5) {
  /* Login ADAPTS */
  .login-right {
    min-width: 27rem;
    overflow: auto; }}/* Portrait */
@media only screen and (min-device-width: 1024px) and (max-device-width: 1366px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1.5) {
  /* Login ADAPTS */
  .login-right {
    min-width: 27rem;
    overflow: auto; }}/* Landscape */
@media only screen and (min-device-width: 1024px) and (max-device-width: 1366px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 1.5) {
  /* Login ADAPTS */
  .login-right {
    min-width: 27rem;
    overflow: auto; }}Copy the code

Rem usage and SCSS in VUE

Personal advice:

Vue-admin-template is a minimalist version of vue-admin-Element. It contains only Element UI & Axios & Iconfont & Permission Control & Lint, which is necessary to build the background; If you want to use vue-admin-element, you can just take it and use it. There will be less code waste.

Vue-admin-template official document link

Conclusion:

The framework has rich functions and a complete community, which is worth learning. It is still in the early stage of use, and some technical points in detail will be continuously updated in use.

Reference links: www.jianshu.com/p/d3e3b2169…