preface

The content of the first stage of the back-end part has been almost completed, and the front-end part has not officially started. During this period of time, I will first turn to the front-end part, and continue to add follow-up content with the front-end scaffolding built before. This article focuses on the idea of front-end login and routing modularity, and in passing looks at the use of custom ICONS for the elementui-Admin footreback.

The login

As a background management system, the login module is indispensable. Here is a brief description of the login module needs to do and matters needing attention.

User state

  1. Not logged in

Users who are not logged in will be redirected to the login page if they visit all pages.

  1. Is logged in

Users who have logged in can view only the menus and buttons of their own rights and perform operations on them.

User status storage

After the front and back ends are separated, logged in users need to store session information locally. Cookies are commonly used to store tokens. Cookies are also used here in this framework. Of course, in addition to using Cookies, you can also use Windows. LocalStorage in H5, etc.

Clearing User Status

If a user has logged in, the login status of the user is cleared in the following cases

  1. Users voluntarily quit;
  2. The interface returned the token expiration status code. Procedure

In both cases, the code is called to clear the local session information and redirect to the login page to re-log in.

Login interface document

Requested address:

{{api_base_url}}/sys/login

Data type:

application/json

Example request:

{
	"password": "123456"."userName": "admin"
}
Copy the code

Sample response:

{" code ": 0, / / return a status code 0, successful" MSG ", "login succeeds", / / the message description "data" : {" token ": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtbGRvbmciLCJleHAiOjE1OTI0OTIyMDEsInVzZXJOYW1lIjoiYWRtaW4iLCJpYXQiOjE1OT I0ODUwMDEsInVzZXJJZCI6MX0. HldmyCcL2EV8rtIeIiYsei963Cb3qIDHJRMOYo0iXkU ", / / temporary token, after logging in, other interfaces need to carry the parameter "userId" : 1, // user id "userName": "admin", // userName" realName": "avatar": "", // user avatar": "accessList": [], // Permission identifier "menuList": [] // menu set}}Copy the code

The login module involves modified files

  • src/views/login.vue

Login page, login form, code snippet.

 handleLogin() {
     // Do form validation here
     this.$refs.loginForm.validate(valid= > {
         if (valid) {
             this.loading = true
             / / check through, calls to action - > user/login, corresponding to the SRC/store/modules/user. The action of js. The login method
             this.$store.dispatch('user/login'.this.loginForm).then((a)= > {
                 this.$router.push({ path: this.redirect || '/' })
                 this.loading = false
             }).catch((a)= > {
                 this.loading = false})}else {
             console.log('error submit!! ')
             return false}})}Copy the code
  • src/store/modules/user.js

Vue state management, code snippets

const getDefaultState = (a)= > {
  return {
    token: getToken(),
    name: ' '.avatar: ' '}}const state = getDefaultState()

const mutations = {
  RESET_STATE: (state) = > {
    Object.assign(state, getDefaultState())
  },
  SET_TOKEN: (state, token) = > {
    state.token = token
  },
  SET_NAME: (state, name) = > {
    state.name = name
  },
  SET_AVATAR: (state, avatar) = > {
    state.avatar = avatar
  }
}
const actions = {
  // user login
  login({ commit }, userInfo) {
    // this.$store.dispatch('user/login', this.loginform)
    const { username, password } = userInfo
    return new Promise((resolve, reject) = > {
      // call SRC/API /user.js login
      login({ username: username.trim(), password: password }).then(response= > {
        const { data } = response
        / / set the token
        commit('SET_TOKEN', data.token)
        // Set cookies. This calls the setToken method of the SRC /utils/auth.js file
        setToken(data.token)
        resolve()
      }).catch(error= > {
        reject(error)
      })
    })
  },
  // Get user's personal information
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) = > {
      getInfo(state.token).then(response= > {
        const { data } = response

        if(! data) { reject('Verification failed, please Login again.')}// Here the backend returns information for modification
        const { userName, avatar } = data

        commit('SET_NAME', userName)
        commit('SET_AVATAR', avatar)
        resolve(data)
      }).catch(error= > {
        reject(error)
      })
    })
  },
  // User logs in to the system
  logout({ commit, state }) {
    return new Promise((resolve, reject) = > {
      logout(state.token).then((a)= > {
        removeToken() // must remove token first
        resetRouter()
        commit('RESET_STATE')
        resolve()
      }).catch(error= > {
        reject(error)
      })
    })
  },
Copy the code
  • src/utils/auth.js

Operations related to cookies

import Cookies from 'js-cookie'

const TokenKey = 'vue_admin_template_token'
/ / access token
export function getToken() {
  return Cookies.get(TokenKey)
}
/ / set the token
export function setToken(token) {
  return Cookies.set(TokenKey, token)
}
/ / delete the token
export function removeToken() {
  return Cookies.remove(TokenKey)
}

Copy the code
  • src/api/user.js

The interface for logging in, logging out, and obtaining personal information can be modified according to the back-end interface.

import request from '@/utils/request'
/ / login
export function login(data) {
  return request({
    url: '/sys/login'.method: 'post'.data: {
        // We need to modify the input parameter username-> username
        userName: data.username,
        password: data.password
    }
  })
}
// Get user's personal information
export function getInfo(token) {
  return request({
    url: '/sys/user/info'.method: 'post'})}// Log in to the system
export function logout() {
  return request({
    url: '/sys/logout'.method: 'post'})}Copy the code

src/utils/request.js

HTTP request tool class, here to do global request header, request parameters, return data processing

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// 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: 5000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config= > {
    // do something before request is sent

    if (store.getters.token) {
      // If there is a token, put it in the request header
      X-token -> auth-token
      config.headers['Auth-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  // do global return data processing here
  response => {
    const res = response.data

    // If the status code is not 0, it is abnormal.
    if(res.code ! = =0) {
      Message({
        message: res.msg || 'Server exception'.type: 'error'.duration: 5 * 1000
      })

      // The status code here can be modified according to the back-end status code
      if (res.code === 401) {
        // to re-login
        MessageBox.confirm('You have logged out and will leave this page, are you sure to log out? ', {
          confirmButtonText: 'Log in again'.cancelButtonText: 'cancel'.type: 'warning'
        }).then((a)= > {
          store.dispatch('user/resetToken').then((a)= > {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))}else {
      return res
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error'.duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

Copy the code

Routing modularization

Scaffold made most of the functionality, but for the routing of modularization, and functional module directory hierarchy or not, this needs us to according to the situation of the project, the following is my own hierarchical scheme, here only involves routing and page, the next talk about specific CURD sample will be complete when stratification.

The directory structure

├─ SRC/Router ├── CMS. Router. Js Content Management Route ├─ Index.js Route Main Entry ├─ oms.router Sys.router. Js System Management ├── all views/ Modules ├─ article ├─ vue ├─ class ├─ vue ├─ Vue ├── vue ├─ vue ├─ vue ├─ PMS ├─ brand ├─ index. Vue ├─ product ├─ index. Vue ├─ dict ├─ Vue ├── all, all, all, all, all, all, all, all, all, allCopy the code

The above modules are not meant to be implemented immediately, but to explain routing modularity and page modularity.

The sample

  • src/router/sys.router.js
import Layout from '@/layout'

export default[{path: '/sys'.// The modular identifier is consistent with the background menu management permission identifier
    name: 'sys'.meta: {
      icon: 'sys'.title: 'System Settings'.access: ['admin'.'sys'].notCache: true.showAlways: true
    },
    component: Layout,
    children: [{path: '/sys/menu/index'.name: 'sys:menu:index'.// Consistent with the menu management permission identifier
        meta: {
          icon: ' '.title: 'Menu Management'.access: ['admin'.'sys:menu:index'].notCache: true
        },
        component: (resolve) = > {
          import('@/views/modules/sys/menu/index.vue').then(m= > {
            resolve(m)
          })
        }
      },
      {
        path: '/sys/user/index'.name: 'sys:user:index'.meta: {
          icon: ' '.title: 'User Management'.access: ['admin'.'sys:user:index'].notCache: true
        },
        component: (resolve) = > {
          import('@/views/modules/sys/user/index.vue').then(m= > {
            resolve(m)
          })
        }
      },
      {
        path: '/sys/role/index'.name: 'sys:role:index'.meta: {
          icon: ' '.title: 'Role Management'.access: ['admin'.'sys:role:index'].notCache: true
        },
        component: (resolve) = > {
          import('@/views/modules/sys/role/index.vue').then(m= > {
            resolve(m)
          })
        }
      },
      {
        path: '/sys/dict/index'.name: 'sys:dict:index'.meta: {
          icon: ' '.title: 'Dictionary Management'.access: ['admin'.'sys:dict:index'].notCache: true
        },
        component: (resolve) = > {
          import('@/views/modules/sys/dict/index.vue').then(m= > {
            resolve(m)
          })
        }
      }
    ]
  }
]
Copy the code
  • src/router/index.js

Route management main entry, where webpack_require is used to dynamically load routing files

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 true, item will not show in the sidebar(default is false) * alwaysShow: true if set true, will always show the root menu * if not set alwaysShow, when item has more than one children route, * it will becomes nested mode, otherwise not show the root menu * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb * name:'router-name' the name is used by 
      
        (must set!!!) * meta : { roles: ['admin','editor'] control the page roles (you can set multiple roles) title: 'title' the name show in sidebar and breadcrumb (recommend set) icon: 'svg-name' the icon show in the sidebar breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) activeMenu: '/example/list' if set path, the sidebar will highlight the path you set } */
      

/** * constantRoutes * a base page that does not have permission requirements * all roles can be accessed */
export const constantRoutes = [
  {
    path: '/login'.component: (a)= > import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/ 404'.component: (a)= > import('@/views/404'),
    hidden: true
  },

  {
    path: '/'.component: Layout,
    redirect: '/dashboard'.hidden: true.children: [{
      path: 'dashboard'.name: 'Dashboard'.component: (a)= > import('@/views/dashboard/index'),
      meta: { title: 'home'.icon: 'dashboard'}}}]]/** * webpack_require dynamically loads routing files */
const routersFiles = require.context('/'.true, /\.js$/)
const routerList = []
const routers = routersFiles.keys().reduce((modules, routerPath) = > {
  // set './app.js' => 'app'
  const routerName = routerPath.replace(/^\.\/(.*)\.\w+$/.'$1')
  const value = routersFiles(routerPath)
  if(routerName ! = ='index') { routerList.push(... value.default) }return routers
}, {})
Router. AddRoutes (accessRoutes) */
export const asyncRoutes = [
  ... routerList,
  // The 404 page must be placed at the end of the page, otherwise asynchronous loading, refresh page is not found directly to constantRoutes 404
  { path: The '*'.redirect: {
    name: 'm404'
  }, hidden: true}]const createRouter = (a)= > new Router({
  // mode: 'history', // require service support
  scrollBehavior: (a)= > ({ y: 0 }),
  routes: [
    ... constantRoutes,
    // Because the permission has not been done, here is directly placed here, later when the permission is done, then modify. asyncRoutes ] })const router = createRouter()

export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

Copy the code

Route interceptor

The route interceptor, similar to the request interceptor, is placed in the scaffolding

  • 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 { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login'] // no redirect whitelist

// enter page-front interception
router.beforeEach(async(to, from, next) => {
  // Progress bar starts
  NProgress.start()

  // Reset the page title
  document.title = getPageTitle(to.meta.title)

  // Obtain the token to check whether you have logged in
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // If you have logged in and it is a login page, redirect to the home page
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasGetUserInfo = store.getters.name
      // Check whether the user information exists. If yes, go to the page
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // User information does not exist and needs to be retrieved
          await store.dispatch('user/getInfo')
          // If yes, the page is displayed
          next()
        } catch (error) {
          // If the user information fails to be obtained, the session information is deleted and the login page is displayed
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login? redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    / * no token * /
    if(whiteList.indexOf(to.path) ! = =- 1) {
      // If the page is whitelist, you can enter the page
      next()
    } else {
      // If it is not whitelisted, jump to the login page
      next(`/login? redirect=${to.path}`)
      NProgress.done()
    }
  }
})

// Enter the page
router.afterEach((a)= > {
  // Progress bar completed
  NProgress.done()
})

Copy the code

About custom ICONS

The custom ICONS used in the left menu are defined in SVG files, which can be obtained from Ali’s vector icon library.

www.iconfont.cn/

Directory for Storing ICONS

src/icons/svg/xxxx.svg

rendering

summary

This paper only briefly introduces the login module and routing modular layering, the basic layering idea is borrowed from the back end. The permission section is not expanded in this article because it involves too much content. A follow-up article will be devoted to explain in detail the permission management after the separation of the front and back ends.

Project source code address

  • The back-end

Gitee.com/mldong/mldo…

  • The front end

Gitee.com/mldong/mldo…

Related articles

Create a suitable for their own rapid development framework – the pilot

Build a suitable for their own rapid development framework – front-end scaffolding