A post takes you through a full backstage using vue

Posted by waygood on Tue, 01 Mar 2022 19:06:06 +0100

introduce

vue-element-admin Is a back-end front-end solution based on vue and element-ui Realization. It uses the latest front-end technology stack, built-in i18 internationalization solutions, dynamic routing, privilege validation, extracts a typical business model, provides rich functional components, which can help you quickly build enterprise-level mid-background product prototypes. I believe this project will help you regardless of your needs

  • vue-element-admin positioning is a background integration scheme, which is not suitable for secondary development of basic templates. The project integrates many functions that are not needed and will cause code sinking
  • vue-admin-template is a background base template and is recommended for secondary development
  • Electronic-vue-admin is a desktop terminal that you can use for desktop terminal development

function

- Sign in / Cancellation

- Permission Verification
  - Page Permissions
  - Instruction Permissions
  - Permission Configuration
  - Two-step login

- Multi-Environment Publishing
  - dev sit stage prod

- Global functionality
  - Internationalized Multilingual
  - Various dynamic skin changes
  - Dynamic Sidebar (supports multi-level routing nesting)
  - Dynamic breadcrumbs
  - Quick Navigation(Tab Page)
  - Svg Sprite Icon
  - local/back-end mock data
  - Screenfull Full screen
  - Adaptive Shrink Sidebar

- editor
  - Rich text
  - Markdown
  - JSON Multi-format

- Excel
  - export excel
  - Import excel
  - Front End Visualization excel
  - export zip

- form
  - Dynamic Table
  - Drag Table
  - Inline Editing

- Error Page
  - 401
  - 404

- Components
  - picture upload
  - Return to top
  - Drag Dialog
  - Drag Select
  - Drag Kanban
  - List Drag
  - SplitPane
  - Dropzone
  - Sticky
  - CountTo

- Comprehensive example
- Error Log
- Dashboard
- Guide Page
- ECharts Chart
- Clipboard(Clip Copy)
- Markdown2html

directory structure

├── build                      # Build correlation
├── mock                       # Project mock simulation data
├── plop-templates             # Basic Template
├── public                     # Static Resources
│   │── favicon.ico            # favicon Icon
│   └── index.html             # html template
├── src                        # source code
│   ├── api                    # All Requests
│   ├── assets                 # Static resources such as theme fonts
│   ├── components             # Global Common Components
│   ├── directive              # Global directives
│   ├── filters                # Global filter
│   ├── icons                  # Project All svg icons
│   ├── lang                   # Internationalized language
│   ├── layout                 # Global layout
│   ├── router                 # Route
│   ├── store                  # Global store management
│   ├── styles                 # Global Style
│   ├── utils                  # Global Common Method
│   ├── vendor                 # Public vendor
│   ├── views                  # views All Pages
│   ├── App.vue                # Entry Page
│   ├── main.js                # Initialization of entry file load components, etc.
│   └── permission.js          # Rights Management
├── tests                      # test
├── .env.xxx                   # Environment variable configuration
├── .eslintrc.js               # eslint configuration item
├── .babelrc                   # babel-loader configuration
├── .travis.yml                # Automated CI Configuration
├── vue.config.js              # vue-cli configuration
├── postcss.config.js          # postcss configuration
└── package.json               # package.json

install

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

# Enter Project Directory
cd vue-element-admin

# Installation Dependency
npm install

# Slow enough to specify the download mirror using
# You can also use nrm to choose to download the mirror
# It is not recommended to install with cnpm. There are various weird bug s to solve the problem of slow download speed of npm by doing the following
npm install --registry=https://registry.npm.taobao.org

# Note: This framework starts differently from what we normally set ourselves up to do by using the following method
# Local Development Startup Project
npm run dev

Automatically open browser access after startup http://localhost:9527 , you can see the page to prove that your operation was successful

Layout layout

layout-based on most pages except: 404, login, etc.

layout integrates all layouts of the page for block display

The whole plate is divided into three parts

layout Major Arrangement

Src directory

Entry file main.js


There are custom mock file access, we want to comment it out
The mock folder under src is suggested to be deleted, we will not use it later

App.vue

Under src, except main.js has two more files, permission.js and settings.js

permission.js

permission.js is a file that controls the login rights of the page. We can comment it out first and add it slowly later

settings.js

settings.js is a configuration for some project information, which has three properties ** Title (project name), fixedHeader (fixed header), sidebarLogo (display left menu logo)
Configurations we'll use elsewhere, don't move

Introduction of API module and request encapsulation module

Individual requests for API modules and encapsulation of request modules

Axios Interceptor

axios interceptor principle:

Create a new axios instance from create

// A new axios instance was created
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
Primarily deals with token's _ Unified Injection Problem_

service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

Response Interceptor
Handle returned data exceptions and _ Data Structure_ problem

	// Response Interceptor
service.interceptors.response.use(
  response => {
    const res = response.data
    // if the custom code is not 20000, it is judged as an error.
    // Custom code return values are negotiated and written to suit your needs
    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
          // Custom code return values are negotiated and written to suit your needs
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            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)
  }
)

Above is src/utils/request. Source code under JS
We just need to keep:

// Export an instance of axios with request interceptor response interceptor
import axios from 'axios'
const service = axios.create() // Create an instance of axios
service.interceptors.request.use() // request interceptor
service.interceptors.response.use() // Response Interceptor
export default service // Export axios instance

Separate encapsulation of api

We are accustomed to putting all api requests under the api directory for unified management and use by module

api/user.js

import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/vue-admin-template/user/login',
    method: 'post',
    data
  })
}

export function getInfo(token) {
  return request({
    url: '/vue-admin-template/user/info',
    method: 'get',
    params: { token }
  })
}

export function logout() {
  return request({
    url: '/vue-admin-template/user/logout',
    method: 'post'
  })
}

We just need to keep the following code and add it later

import request from '@/utils/request'

export function login(data) {

}

export function getInfo(token) {

}

export function logout() {

}

Login module

Set a fixed local access port and site name

Set up a unified local access port and site title

Local service port: at vue. Config. Setting in JS

vue.config.js is the vue project related compilation, configuration, packaging, start service related configuration file, its core is the webpack, but different from the webpack, equivalent to the improved version of the webpack

We see that the above is an environment variable, not an actual address, so where do we set it?

Under the project we will find two files

Development =>development environment

Production =>production environment

When we run npm run dev for development debugging, execution is loaded**. env.development** file content

When we run npm run build:prod to package the production environment, the execution is loaded**. env.production** file contents

If you want to set up an interface for your development environment, go directly to **. Env. Write a direct assignment to a variable in the development** file

# just a flag
ENV = 'development'

# base api
VUE_APP_BASE_API = 'api/private/v1/'

If you want to set up an interface for your production environment**. Env. Write a direct assignment to a variable in the production** file

# just a flag
ENV = 'production'

# base api
VUE_APP_BASE_API = 'api/private/v1/'

Site Name

Src/settings. In JS
Tile is the name of the site

We need to restart after configuring, otherwise some configurations will not take effect

Logon Page


Set header name:

<!-- Place Title picture @Is the alias of the setting-->
<div class="title-container">
        <h3 class="title">Dolphin E-commerce Background Management Platform</h3>
 </div>

Set the background picture:
Can be changed as needed

/* reset element-ui css */
.login-container {
  background-image: url('~@/assets/common/bgc.jpg'); // Set Background Picture 
  background-position: center; // Set the picture position to fill the entire screen
}

Corresponding code:

Verification of login form

Conditions for el-form form checking

Verification of username and password:

<el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          v-focus
          placeholder="Username"
          name="username"
          type="text"
          tabindex="1"
          auto-complete="on"
        />
      </el-form-item>

      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password" />
        </span>
        <el-input
          :key="passwordType"
          ref="password"
          v-model="loginForm.password"
          :type="passwordType"
          placeholder="Password"
          name="password"
          tabindex="2"
          auto-complete="on"
          @keyup.enter.native="handleLogin"
        />
        <span class="show-pwd" @click="showPwd">
          <svg-icon
            :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
          />
        </span>
      </el-form-item>


const validateUsername = (rule, value, callback) => {
      if (value.length < 5) {
        callback(new Error('User name must have at least 5 digits'))
      } else if (value.length > 12) {
        callback(new Error('User name up to 12 digits'))
      } else {
        callback()
      }
    }
    const validatePassword = (rule, value, callback) => {
      if (value.length < 5) {
        callback(new Error('User name must have at least 5 digits'))
      } else if (value.length > 16) {
        callback(new Error('User name up to 16 digits'))
      } else {
        callback()
      }
    }

loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [
          { required: true, trigger: 'blur', validator: validatePassword },
          { min: 5, max: 12, trigger: 'blur', message: 'Password length should be 5-12 Between bits' }
        ]
      }

Vue-Cli Configuration Cross-Domain Agent

What is the reason for cross-domain?
Because the current trend is to separate front-end and back-end development, front-end projects and back-end interfaces are not under the same domain name, then front-end access back-end interfaces will appear cross-domain
So how can we solve the problem?
The cross-domain that we encounter is in the development environment, and when we actually deploy online, the cross-domain is in the production environment, and the solutions are different
Let's solve the development environment first, the production environment can be solved by packaging online, and then

Solving cross-domain issues in the development environment

Cross-domain issues encountered by our access interface when developing startup services in the vue-cli scaffolding environment. vue-cli has opened a service for us locally which can help us proxy requests and solve cross-domain problems
That is, vue-cli configures the reverse proxy for the webpack

In vue. Config. Reverse proxy configuration in JS

module.exports = {
  devServer: {
   proxy: {
      'api/private/v1/': {
        target: 'http://127.0.0.1:8888', //The address we want to proxy, when matched to'api/private/v1/'above, will http://localhost:9528 replace with http://127.0.0.1:8888
        changeOrigin: true, // Whether this value needs to be set to true before we can have the local service proxy send us requests
        pathRewrite: {
        // Reroute localhost:8888/api/login => http://127.0.0.1:8888/api/login
          '^/api': '/api',
          '/hr': ''
        }
      }
    }
  }
}

Also note that we also need to comment out the loading of mocks because mock-server s can cause proxy service exceptions

// before: require('./mock/mock-server.js'), //comment mock-server load

Encapsulate separate login interfaces

export function login(data) {
  // Return an axios object=> promise // Return a promise object
  return request({
    url: 'login', // Because all interfaces are cross-domain, all interfaces are banded/api
    method: 'post',
    data
  })
}

Encapsulate Vuex's login Action and process token

Managing token s in Vuex


In the figure above, the components work directly with the interface, which is okay, but ta uses keys to transfer each other, so we need vuex to intervene, share the user's token state, and read it more easily

store/modules/user.js configuration

// state
const state = {}
// modify state
const mutations = {}
// Execute Asynchronous
const actions = {}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

Set token sharing status

const state = {
  token: null
}

Operation token

Utils/auth. In js, the base template has provided us with a way to get token, set token, delete token, and use it directly

const TokenKey = 'haitun_token'

export function getToken() {
  // return Cookies.get(TokenKey)
  return localStorage.getItem(TokenKey)
}

export function setToken(token) {
  // return Cookies.set(TokenKey, token)
  return localStorage.setItem(TokenKey, token)
}

export function removeToken() {
  // return Cookies.remove(TokenKey)
  return localStorage.removeItem(TokenKey)
}

Initialize token state

store/modules/user.js

import { getToken, setToken, removeToken } from '@/utils/auth'
const state = {
  token: getToken() // Set token initial state token persistence=>put in cache
}

Provide mutations to modify token

// modify state
const mutations = {
  // Set token
  setToken(state, token) {
    state.token = token // Setting token only modifies state's data 123 ="1234
    setToken(token) // Synchronization of vuex and cached data
  },
  // Delete Cache
  removeToken(state) {
    state.token = null // Remove token of vuex
    removeToken() // Clear vuex before clearing cache vuex and synchronization of cached data
  }
}

Encapsulate login Action

What to do with the login action, call the login interface, set token to vuex after success, fail and return failure

// Execute Asynchronous
const actions = {
  // Defining a login action also requires parameters passed in when the action is called by the parameter
  async login(context, data) {
    const result = await login(data)  // A promise result is actually the result of execution
    // axios adds a layer of data to the data by default
    if (result.data.success) {
      // Indicates that the login interface call was successful which means that your username and password are correct
      // There is now a user token
      // actions must modify state through mutations
      context.commit('setToken', result.data.data)
    }
  }
}

For better access to token data for other modules and components, we need to store/getters. Release token values as public access properties in JS

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token // Develop properties of sub-modules on root-level getters to show others
}
export default getters

From this, we can have a brain map

Distinguishing axios'request base addresses in different environments


The front two main distinguish between environment, development environment and production environment

Environment variable $process.env.NODE_ENV #Development when production is development
We can do that at **. env.development and. env.production** Defines a variable that is automatically the value of the current environment
The base template defines the variable VUE_in the file above APP_ BASE_ API, which can be used as baseURL for axios requests

# Base address and proxy correspondence for development environment
VUE_APP_BASE_API = '/api'

---------

# The / API configuration here means that the reverse proxy corresponding / prod-api address of nginx needs to be configured on the Nginx server for the service 
VUE_APP_BASE_API = '/prod-api'  

Or they can all be written in the same manageable way

Setting baseUrl - benchmark in request

// Create an instance of axios
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // Set the base address for axios requests
  timeout: 5000 // Define 5 seconds timeout
}) 

Response interceptor for handling axios

// Response Interceptor
service.interceptors.response.use(response => {
  // axios adds a layer of data by default
  const { success, message, data } = response.data
  //   The following actions are determined by success or failure
  if (success) {
    return data
  } else {
    // Business has gone wrong. Can we go in then? No! Should catch
    Message.error(message) // Prompt error message
    return Promise.reject(new Error(message))
  }
}, error => {
  Message.error(error.message) // Prompt error message
  return Promise.reject(error) // Return an execution error to jump out of the current execution chain and enter catch directly
})

Logon page calls logon action to handle exceptions


Introducing auxiliary functions

import { mapActions } from 'vuex'  // Introducing the auxiliary function of vuex
---------------------
methods: {
    ...mapActions(['user/login'])
}

Call login

  this.$refs.loginForm.validate(async isOK => {
        if (isOK) {
          try {
            this.loading = true
            // We call action only if the check passes
            await this['user/login'](this.loginForm)
            // Should be logged in after success
             // Jump to home page after successful login
            this.$router.push('/')
          } catch (error) {
            console.log(error)
          } finally {
            //  Turn the loop off whether you perform try or catch
            this.loading = false
          }
        }
      })

analysis

Write using the front form of the elementUI first

Use form validation in the foreground to compare user-entered account passwords, whether they meet the criteria, or if they do not meet the criteria we defined for a prompt

We use v-model for bidirectional data binding of input boxes in the form

Background validation is also required when the user clicks on the login button after the user has finished typing. When we click on the login to send a request to the backstage warehouse to check if the account password is correct, a prompt pops up if it is incorrect

Introduce icon icon with <svg>tag in form

We first created the SvgIcon component under srccomponents

We exposed two attributes

Monitor icon's name and its custom style through computed. When no custom style is specified, the default style is used, otherwise the custom class is added

 iconName() {
      return `#icon-${this.iconClass}`
    },
    svgClass() {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    }

Then write the default style

Index in srcicons. Introduce svg component import IconSvg from'@/components/IconSvg'in JS

Use global registration icon-svg Vue.component('icon-svg', IconSvg)

This allows you to use it anywhere in your project

For centralized icon management, place all icons in @/icons/svg

@Represents finding src directory

require.context has three parameters:

  • Parameter 1: Describe the directory to retrieve
  • Parameter 2: Whether to retrieve subdirectories
  • Parameter 3: Regular expression matching file

At @/main. Import'@/icons'is introduced in JS so that components can be used successfully on any page

Use it on the page to use it

<svg-icon icon-class="password" class-name="password" />

Complete Code

<template>
  <div class="login-container">
    <el-form
      ref="loginForm"
      :model="loginForm"
      :rules="loginRules"
      class="login-form"
      auto-complete="on"
      label-position="left"
    >
      <div class="title-container">
        <h3 class="title">Dolphin E-commerce Background Management Platform</h3>
      </div>

      <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          v-focus
          placeholder="Username"
          name="username"
          type="text"
          tabindex="1"
          auto-complete="on"
        />
      </el-form-item>

      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password" />
        </span>
        <el-input
          :key="passwordType"
          ref="password"
          v-model="loginForm.password"
          :type="passwordType"
          placeholder="Password"
          name="password"
          tabindex="2"
          auto-complete="on"
          @keyup.enter.native="handleLogin"
        />
        <span class="show-pwd" @click="showPwd">
          <svg-icon
            :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
          />
        </span>
      </el-form-item>

      <el-button
        :loading="loading"
        type="primary"
        style="width: 100%; margin-bottom: 30px"
        @click.native.prevent="handleLogin"
        >Sign in now</el-button
      >
      <!-- <div class="tips">
        <span style="margin-right: 20px">username: admin</span>
        <span> password: any</span>
      </div> -->
    </el-form>
  </div>
</template>

<script>
import { validUsername } from '@/utils/validate'

export default {
  name: 'Login',
  data () {
    const validateUsername = (rule, value, callback) => {
      if (value.length < 5) {
        callback(new Error('User name must have at least 5 digits'))
      } else if (value.length > 12) {
        callback(new Error('User name up to 12 digits'))
      } else {
        callback()
      }
    }
    const validatePassword = (rule, value, callback) => {
      if (value.length < 5) {
        callback(new Error('User name must have at least 5 digits'))
      } else if (value.length > 16) {
        callback(new Error('User name up to 16 digits'))
      } else {
        callback()
      }
    }
    return {
      loginForm: {
        username: 'admin',
        password: '123456'
      },
      loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [
          { required: true, trigger: 'blur', validator: validatePassword },
          { min: 5, max: 12, trigger: 'blur', message: 'Password length should be 5-12 Between bits' }
        ]
      },
      loading: false,
      passwordType: 'password',
      redirect: undefined
    }
  },
  watch: {
    $route: {
      handler: function (route) {
        this.redirect = route.query && route.query.redirect
      },
      immediate: true
    }
  },
  methods: {
    showPwd () {
      if (this.passwordType === 'password') {
        this.passwordType = ''
      } else {
        this.passwordType = 'password'
      }
      this.$nextTick(() => {
        this.$refs.password.focus()
      })
    },
    async handleLogin () {
      try {
        await this.$refs.loginForm.validate()
        this.loading = true
        await this.$store.dispatch('user/login', this.loginForm)
        // console.log('ssss')
        // Jump to home page after successful login
        this.$router.push({ path: '/' })
        this.loading = false
      } catch (err) {
        this.loading = false
        console.log(err)
        return false
      }
    }
  }
}
</script>

<style lang="scss">
/* Repair input background discordance and cursor discoloration */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */

$bg: #283443;
$light_gray: #fff;
$cursor: #fff;

@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
  .login-container .el-input input {
    color: $cursor;
  }
}

/* reset element-ui css */
.login-container {
  .el-input {
    display: inline-block;
    height: 47px;
    width: 85%;

    input {
      background: transparent;
      border: 0px;
      -webkit-appearance: none;
      border-radius: 0px;
      padding: 12px 5px 12px 15px;
      color: $light_gray;
      height: 47px;
      caret-color: $cursor;

      &:-webkit-autofill {
        box-shadow: 0 0 0px 1000px $bg inset !important;
        -webkit-text-fill-color: $cursor !important;
      }
    }
  }

  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.1);
    border-radius: 5px;
    color: #454545;
  }
}
</style>

<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;

  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin: 0 auto;
    overflow: hidden;
  }

  .tips {
    font-size: 14px;
    color: #fff;
    margin-bottom: 10px;

    span {
      &:first-of-type {
        margin-right: 16px;
      }
    }
  }

  .svg-container {
    padding: 6px 5px 6px 15px;
    color: $dark_gray;
    vertical-align: middle;
    width: 30px;
    display: inline-block;
  }

  .title-container {
    position: relative;

    .title {
      font-size: 26px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }
  }

  .show-pwd {
    position: absolute;
    right: 10px;
    top: 7px;
    font-size: 16px;
    color: $dark_gray;
    cursor: pointer;
    user-select: none;
  }
}
</style>

Home page module

Home page token intercepts and handles

Flow Chart for Privilege Interception

We have completed the login process and stored token, but at this time the home page is not controlled for token access

Intercept Processing Code

src/permission.js

import Vue from 'vue'

import 'normalize.css/normalize.css' // A modern alternative to CSS resets

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n

import '@/styles/index.scss' // global css

import App from './App'
import store from './store'
import router from './router'
import i18n from '@/lang/index'
import '@/icons' // icon
import '@/permission' // permission control
import directives from './directives'
import Commponent from '@/components'
import filters from './filter'
import Print from 'vue-print-nb' // Introducing Printing

// set ElementUI lang to EN
Vue.use(ElementUI, { locale })

// If you want a Chinese version of element-ui, declare it as follows
// Vue.use(ElementUI)
Vue.use(Print)
Vue.config.productionTip = false
// Traversal Register Custom Instructions
for (const key in directives) {
  Vue.directive(key, directives[key])
}
Vue.use(Commponent) // Register your own plug-ins
// Register Global Filters
// Traverse registration filters
for (const key in filters) {
  Vue.filter(key, filters[key])
}
// Set element to current language
Vue.use(ElementUI, {
  i18n: (key, value) => i18n.t(key)
})
new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

Left Navigation

Style file styles/siderbar.scss
Set Background Picture

.scrollbar-wrapper { 
    background: url('~@/assets/common/leftnavBg.png') no-repeat 0 100%;
}

Left logo picture src/setttings.js

module.exports = {

  title: 'Dolphin E-commerce Background Management Platform',

  /**
   * @type {boolean} true | false
   * @description Whether fix the header
   */
  fixedHeader: false,

  /**
   * @type {boolean} true | false
   * @description Whether show the logo in sidebar
   */
  sidebarLogo: true   // Show logo
}

Set the header picture structure src/layout/components/Sidebar/Logo.vue

<div class="sidebar-logo-container" :class="{ collapse: collapse }">
    <transition name="sidebarLogoFade">
      <router-link
        v-if="collapse"
        key="collapse"
        class="sidebar-logo-link"
        to="/"
      >
        <img v-if="logo" src="@/assets/common/hai.png" class="sidebar-logo" />
        <h1 v-else class="sidebar-title">{{ title }}</h1>
      </router-link>
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
        <img v-if="logo" src="@/assets/common/hai.png" class="sidebar-logo" />
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
    </transition>
  </div>

Complete Code

<template>
  <div class="sidebar-logo-container" :class="{ collapse: collapse }">
    <transition name="sidebarLogoFade">
      <router-link
        v-if="collapse"
        key="collapse"
        class="sidebar-logo-link"
        to="/"
      >
        <img v-if="logo" src="@/assets/common/hai.png" class="sidebar-logo" />
        <h1 v-else class="sidebar-title">{{ title }}</h1>
      </router-link>
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
        <img v-if="logo" src="@/assets/common/hai.png" class="sidebar-logo" />
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'SidebarLogo',
  props: {
    collapse: {
      type: Boolean,
      required: true
    }
  },
  data () {
    return {
      title: 'Dolphin E-commerce Background Management Platform',
      logo: '@/assets/common/hai.png'
    }
  }
}
</script>

<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
  transition: opacity 1.5s;
}

.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
  opacity: 0;
}

.sidebar-logo-container {
  position: relative;
  width: 100%;
  height: 50px;
  line-height: 50px;
  background: #2b2f3a;
  text-align: center;
  overflow: hidden;

  & .sidebar-logo-link {
    height: 100%;
    width: 100%;

    & .sidebar-logo {
      width: 32px;
      height: 32px;
      vertical-align: middle;
      margin-right: 12px;
    }

    & .sidebar-title {
      display: inline-block;
      margin: 0;
      color: #fff;
      font-weight: 600;
      line-height: 50px;
      font-size: 14px;
      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
      vertical-align: middle;
    }
  }

  &.collapse {
    .sidebar-logo {
      margin-right: 0px;
    }
  }
}
</style>

Layout and style of header content

Header Component Location layout/components/Navbar.vue

Crumbs when adding company name

<!-- <breadcrumb class="breadcrumb-container" /> -->   <!--Crumbs-->
    <div class="app-breadcrumb">
      Beijing Mengna Network Co., Ltd.
      <span class="breadBtn">v1.0.0</span>
    </div>

Right avatar and drop-down menu settings

<div class="right-menu">
      <!-- Language Switching Plugin -->
      <lang class="right-menu-item lang_item" />
      <!-- Full Screen Plugin -->
      <screen-full class="right-menu-item" />
      <!-- Dynamic Theme Plugin -->
      <theme-picker class="right-menu-item" />
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
          <img
            v-imgerr="defaultImg"
            src="https://bing.ioliu.cn/v1/rand?w=100&h=100"
            class="user-avatar"
          />
          <span class="name">{{ username }}</span>

          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown" class="user-dropdown">
          <router-link to="/">
            <el-dropdown-item> homepage </el-dropdown-item>
          </router-link>
          <a href="javascript:;">
            <el-dropdown-item>mailbox</el-dropdown-item>
          </a>
          <a href="javascript:;">
            <el-dropdown-item>Set up</el-dropdown-item>
          </a>
          <el-dropdown-item @click.native="logout">
            <span style="display: block">Sign out</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>

Complete code: Style + Events

<template>
  <div class="navbar">
    <hamburger
      :is-active="sidebar.opened"
      class="hamburger-container"
      @toggleClick="toggleSideBar"
    />
    <!-- <breadcrumb class="breadcrumb-container" /> -->
    <div class="app-breadcrumb">
      Beijing Mengna Network Co., Ltd.
      <span class="breadBtn">v1.0.0</span>
    </div>
    <div class="right-menu">
      <!-- Language Switching Plugin -->
      <lang class="right-menu-item lang_item" />
      <!-- Full Screen Plugin -->
      <screen-full class="right-menu-item" />
      <!-- Dynamic Theme Plugin -->
      <theme-picker class="right-menu-item" />
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
          <img
            v-imgerr="defaultImg"
            src="https://bing.ioliu.cn/v1/rand?w=100&h=100"
            class="user-avatar"
          />
          <span class="name">{{ username }}</span>

          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown" class="user-dropdown">
          <router-link to="/">
            <el-dropdown-item> homepage </el-dropdown-item>
          </router-link>
          <a href="javascript:;">
            <el-dropdown-item>mailbox</el-dropdown-item>
          </a>
          <a href="javascript:;">
            <el-dropdown-item>Set up</el-dropdown-item>
          </a>
          <el-dropdown-item @click.native="logout">
            <span style="display: block">Sign out</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
export default {
  components: {
    Breadcrumb,
    Hamburger
  },
  data () {
    return {
      username: 'Super Administrator',
      defaultImg: require('@/assets/common/bigUserHeader.png')
    }
  },
  created () {
    this.usereee()
  },
  computed: {
    ...mapGetters([
      'sidebar',
      'avatar'
    ])
  },
  methods: {
    usereee () {
      const res = localStorage.getItem('haitunuser')
      // const res = sessionStorage.getItem('user_info')
      const username = JSON.parse(res).username
      this.username = username
    },
    toggleSideBar () {
      this.$store.dispatch('app/toggleSideBar')
    },
    async logout () {
      await this.$store.dispatch('user/logout')
      this.$router.push(`/login`)
    }
  }
}
</script>

<style lang="scss" scoped>
.navbar {
  height: 50px;
  overflow: hidden;
  position: relative;
  background-image: linear-gradient(left, #3d6df8, #5b8cff);
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .app-breadcrumb {
    display: inline-block;
    font-size: 18px;
    line-height: 50px;
    margin-left: 15px;
    color: #fff;
    cursor: text;
    .breadBtn {
      background: #84a9fe;
      font-size: 14px;
      padding: 0 10px;
      display: inline-block;
      height: 30px;
      line-height: 30px;
      border-radius: 10px;
      margin-left: 15px;
    }
  }
  .hamburger-container {
    line-height: 46px;
    height: 100%;
    float: left;
    cursor: pointer;
    transition: background 0.3s;
    -webkit-tap-highlight-color: transparent;

    &:hover {
      background: rgba(0, 0, 0, 0.025);
    }
  }

  .breadcrumb-container {
    float: left;
  }

  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;

    &:focus {
      outline: none;
    }

    .right-menu-item {
      display: inline-block;
      vertical-align: middle;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      vertical-align: text-bottom;

      &.hover-effect {
        cursor: pointer;
        transition: background 0.3s;

        &:hover {
          background: rgba(0, 0, 0, 0.025);
        }
      }
    }

    .avatar-container {
      margin-right: 30px;

      .avatar-wrapper {
        display: flex;
        margin-top: 5px;
        position: relative;
        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 10px;
          vertical-align: middle;
          margin-bottom: 10px;
        }
        .name {
          color: #fff;
          vertical-align: middle;
          margin-left: 5px;
        }
        .user-dropdown {
          color: #fff;
        }
        .el-icon-caret-bottom {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          font-size: 12px;
        }
      }
    }
  }
}
.lang_item {
  // background-color: aqua;
}
</style>

Store user information

New variable: src/store/modules/user.js

const getDefaultState = () => {
  return {
    token: getToken(),
    userInfo: {}, // Store user information
  }
}

Setting and deleting user data mutations

// Set User Information
 set_userInfo (state, user) {
    state.userInfo = user
    setUSERINFO(user)
  }
    // Delete user information
 removeUserInfo (state) {
    this.userInfo = {}
  }

Create a mapping of user names src/store/getters.js

const getters = {  
  token: state => state.user.token,
  username: state => state.user.userInfo.username
}
export default getters

Finally, we can change to the real name.

<div class="avatar-wrapper">
    <img src="@/assets/common/bigUserHeader.png" class="user-avatar" />
    <span class="name">{{ username }}</span>
    <i class="el-icon-caret-bottom" style="color: #fff" />
</div>

There may be a problem here. We can't get the data in the page refresh, so we can save it locally and take it out

Implement exit function


Exit: src/store/modules/user.js

 // user logout
  logout (context) {
    // Delete token
    context.commit('removeToken') // Not only has the vuex been deleted, but the cache has also been deleted
    // Delete User Data
    context.commit('removeUserInfo') // Delete user information
  },

mutation

 removeToken (state) {
    state.token = null
    removeToken()
    removeUSERINFO()
    removeLocalMenus()
  },
  removeUserInfo (state) {
    this.userInfo = {}
  },

Header menu calls src/layout/components/Navbar.vue

  async logout () {
      await this.$store.dispatch('user/logout')
      this.$router.push(`/login`)
    }

Full code:

<template>
  <div class="navbar">
    <hamburger
      :is-active="sidebar.opened"
      class="hamburger-container"
      @toggleClick="toggleSideBar"
    />
    <!-- <breadcrumb class="breadcrumb-container" /> -->
    <div class="app-breadcrumb">
      Beijing Mengna Network Co., Ltd.
      <span class="breadBtn">v1.0.0</span>
    </div>
    <div class="right-menu">
      <!-- Language Switching Plugin -->
      <lang class="right-menu-item lang_item" />
      <!-- Full Screen Plugin -->
      <screen-full class="right-menu-item" />
      <!-- Dynamic Theme Plugin -->
      <theme-picker class="right-menu-item" />
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
          <img
            v-imgerr="defaultImg"
            src="https://bing.ioliu.cn/v1/rand?w=100&h=100"
            class="user-avatar"
          />
          <span class="name">{{ username }}</span>

          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown" class="user-dropdown">
          <router-link to="/">
            <el-dropdown-item> homepage </el-dropdown-item>
          </router-link>
          <a href="javascript:;">
            <el-dropdown-item>mailbox</el-dropdown-item>
          </a>
          <a href="javascript:;">
            <el-dropdown-item>Set up</el-dropdown-item>
          </a>
          <el-dropdown-item @click.native="logout">
            <span style="display: block">Sign out</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
export default {
  components: {
    Breadcrumb,
    Hamburger
  },
  data () {
    return {
      username: 'Super Administrator',
      defaultImg: require('@/assets/common/bigUserHeader.png')
    }
  },
  created () {
    this.usereee()
  },
  computed: {
    ...mapGetters([
      'sidebar',
      'avatar'
    ])
  },
  methods: {
    usereee () {
      const res = localStorage.getItem('haitunuser')
      // const res = sessionStorage.getItem('user_info')
      const username = JSON.parse(res).username
      this.username = username
    },
    toggleSideBar () {
      this.$store.dispatch('app/toggleSideBar')
    },
    async logout () {
      await this.$store.dispatch('user/logout')
      this.$router.push(`/login`)
    }
  }
}
</script>

<style lang="scss" scoped>
.navbar {
  height: 50px;
  overflow: hidden;
  position: relative;
  background-image: linear-gradient(left, #3d6df8, #5b8cff);
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .app-breadcrumb {
    display: inline-block;
    font-size: 18px;
    line-height: 50px;
    margin-left: 15px;
    color: #fff;
    cursor: text;
    .breadBtn {
      background: #84a9fe;
      font-size: 14px;
      padding: 0 10px;
      display: inline-block;
      height: 30px;
      line-height: 30px;
      border-radius: 10px;
      margin-left: 15px;
    }
  }
  .hamburger-container {
    line-height: 46px;
    height: 100%;
    float: left;
    cursor: pointer;
    transition: background 0.3s;
    -webkit-tap-highlight-color: transparent;

    &:hover {
      background: rgba(0, 0, 0, 0.025);
    }
  }

  .breadcrumb-container {
    float: left;
  }

  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;

    &:focus {
      outline: none;
    }

    .right-menu-item {
      display: inline-block;
      vertical-align: middle;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      vertical-align: text-bottom;

      &.hover-effect {
        cursor: pointer;
        transition: background 0.3s;

        &:hover {
          background: rgba(0, 0, 0, 0.025);
        }
      }
    }

    .avatar-container {
      margin-right: 30px;

      .avatar-wrapper {
        display: flex;
        margin-top: 5px;
        position: relative;
        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 10px;
          vertical-align: middle;
          margin-bottom: 10px;
        }
        .name {
          color: #fff;
          vertical-align: middle;
          margin-left: 5px;
        }
        .user-dropdown {
          color: #fff;
        }
        .el-icon-caret-bottom {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          font-size: 12px;
        }
      }
    }
  }
}
.lang_item {
  // background-color: aqua;
}
</style>

token invalidation intervention


src/utils/auth.js

const timeKey = 'haitun-setTimeStamp' // Set a unique key
// Store the token's timestamp (when the setToken method executes)
// Get Timestamp
export function setTimeStamp () {
  return localStorage.setItem(timeKey, Date.now())
}
// Get expiration time of token
export function getTimeStamp () {
  return localStorage.getItem(timeKey)
}

src/utils/request.js

import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
import router from '../router'
import { getToken, getTimeStamp, removeToken } from '@/utils/auth'

// Define token timeout
const timeOut = 3600 * 24 * 3
// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  timeout: 5000 // request timeout
})

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

    if (store.getters.token) {
      // Determine if the current token timestamp is out of date
      // Get the time set by token
      const tokenTime = getTimeStamp()
      // Get the current time
      const currenTime = Date.now()
      if ((currenTime - tokenTime) / 1000 > timeOut) {
        // If it is true, it means expired
        // token is useless because it timed out
        store.dispatch('user/logout') // Logout Action
        // Jump to the login page
        router.push('/login')
        return Promise.reject(new Error('Logon expired, please log in again'))
      }
      config.headers['Authorization'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  response => {
    const { meta: { status, msg }, data } = response.data
    // if the custom code is not 20000, it is judged as an error.
    if (status !== 200 && status !== 201) {
      // Handle token expiration
      if (status === 400 && msg === 'Invalid token') {
        removeToken()
        store.dispatch('user/logout')
        router.push('login')
      }
      Message({
        message: msg || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      return Promise.reject(new Error(msg || 'Error'))
    } else {
      return data
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service

At login time, if login is successful, we should set timestamp
src/store/modules

async login (context, userInfo) {
    const { username, password } = userInfo
    const res = await login({ username: username.trim(), password: password })
    // Set User Information
    const token = res.token
    context.commit('set_token', token)
    context.commit('set_userInfo', res)
    // Set User Rights Information
    const permission = await getMenus()
    const menus = filterPermission(permission)
    context.commit('set_menus', menus)
  },

token failure handling

src/utils/request.js

response => {
    const { meta: { status, msg }, data } = response.data
    // if the custom code is not 20000, it is judged as an error.
    if (status !== 200 && status !== 201) {
      // Handle token expiration
      if (status === 400 && msg === 'Invalid token') {
        removeToken()
        store.dispatch('user/logout')
        router.push('login')
      }
      Message({
        message: msg || 'Error',
        type: 'error',
        duration: 5 * 1000
      })
      return Promise.reject(new Error(msg || 'Error'))
    } else {
      return data
    }

Routing, page, user management, rights management, etc. What pages do you need to develop by yourself, steps are similar

Multilingual switching, tab full screen

Reference to Full Screen Plugin

Install global plugin screenfull

npm i screenfull

Encapsulate the full screen plugin src/components/ScreenFull/index.vue

<template>
  <!-- Place an icon -->
  <div>
    <!-- Place one svg Icons -->
    <svg-icon
      icon-class="fullscreen"
      style="color: #fff; width: 20px; height: 20px"
      @click="changeScreen"
    />
    <!-- <i class="el-icon-rank" @click="changeScreen" /> -->
  </div>
</template>

<script>
import ScreenFull from 'screenfull'
export default {
  methods: {
    //   Change Full Screen
    changeScreen () {
      if (!ScreenFull.isEnabled) {
        // Full screen is not available at this time
        this.$message.warning('Full screen components are not available at this time')
        return
      }
      // document.documentElement.requestFullscreen() native js call
      //   Full screen if available
      ScreenFull.toggle()
    }
  }
}
</script>

<style>
</style>

Register the component src/components/index globally. JS

import ScreenFull from './ScreenFull'
Vue.component('ScreenFull', ScreenFull) // Register Full Screen Components

Place layout/navbar.vue

<screen-full class="right-menu-item" />

-------------------------------

.right-menu-item {
   vertical-align: middle;
}

Set Dynamic Theme

Encapsulate the full screen plugin src/components/ThemePicker/index.vue

<template>
  <el-color-picker
    v-model="theme"
    :predefine="[
      '#409EFF',
      '#1890ff',
      '#304156',
      '#212121',
      '#11a983',
      '#13c2c2',
      '#6959CD',
      '#f5222d',
    ]"
    class="theme-picker"
    popper-class="theme-picker-dropdown"
  />
</template>

<script>
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
  data () {
    return {
      chalk: '', // content of theme-chalk css
      theme: ''
    }
  },
  computed: {
    defaultTheme () {
      return this.$store.state.settings.theme
    }
  },
  watch: {
    defaultTheme: {
      handler: function (val, oldVal) {
        this.theme = val
      },
      immediate: true
    },
    async theme (val) {
      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
      if (typeof val !== 'string') return
      const themeCluster = this.getThemeCluster(val.replace('#', ''))
      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
      console.log(themeCluster, originalCluster)
      const $message = this.$message({
        message: '  Compiling the theme',
        customClass: 'theme-message',
        type: 'success',
        duration: 0,
        iconClass: 'el-icon-loading'
      })
      const getHandler = (variable, id) => {
        return () => {
          const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
          const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
          let styleTag = document.getElementById(id)
          if (!styleTag) {
            styleTag = document.createElement('style')
            styleTag.setAttribute('id', id)
            document.head.appendChild(styleTag)
          }
          styleTag.innerText = newStyle
        }
      }
      if (!this.chalk) {
        const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
        await this.getCSSString(url, 'chalk')
      }
      const chalkHandler = getHandler('chalk', 'chalk-style')
      chalkHandler()
      const styles = [].slice.call(document.querySelectorAll('style'))
        .filter(style => {
          const text = style.innerText
          return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
        })
      styles.forEach(style => {
        const { innerText } = style
        if (typeof innerText !== 'string') return
        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
      })
      this.$emit('change', val)
      $message.close()
    }
  },
  methods: {
    updateStyle (style, oldCluster, newCluster) {
      let newStyle = style
      oldCluster.forEach((color, index) => {
        newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
      })
      return newStyle
    },
    getCSSString (url, variable) {
      return new Promise(resolve => {
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
            resolve()
          }
        }
        xhr.open('GET', url)
        xhr.send()
      })
    },
    getThemeCluster (theme) {
      const tintColor = (color, tint) => {
        let red = parseInt(color.slice(0, 2), 16)
        let green = parseInt(color.slice(2, 4), 16)
        let blue = parseInt(color.slice(4, 6), 16)
        if (tint === 0) { // when primary color is in its rgb space
          return [red, green, blue].join(',')
        } else {
          red += Math.round(tint * (255 - red))
          green += Math.round(tint * (255 - green))
          blue += Math.round(tint * (255 - blue))
          red = red.toString(16)
          green = green.toString(16)
          blue = blue.toString(16)
          return `#${red}${green}${blue}`
        }
      }
      const shadeColor = (color, shade) => {
        let red = parseInt(color.slice(0, 2), 16)
        let green = parseInt(color.slice(2, 4), 16)
        let blue = parseInt(color.slice(4, 6), 16)
        red = Math.round((1 - shade) * red)
        green = Math.round((1 - shade) * green)
        blue = Math.round((1 - shade) * blue)
        red = red.toString(16)
        green = green.toString(16)
        blue = blue.toString(16)
        return `#${red}${green}${blue}`
      }
      const clusters = [theme]
      for (let i = 0; i <= 9; i++) {
        clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
      }
      clusters.push(shadeColor(theme, 0.1))
      return clusters
    }
  }
}
</script>

<style>
.theme-message,
.theme-picker-dropdown {
  z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
  height: 26px !important;
  width: 26px !important;
  padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
  display: none;
}
.el-color-picker {
  height: auto !important;
}
</style>

Register the component src/components/index globally. JS

import ThemePicker from './ThemePicker'
Vue.component('ThemePicker', ThemePicker)

Place layout/navbar.vue

  <theme-picker class="right-menu-item" />

Multilingual implementation

Install international language pack i18n

npm i vue-i18n

Multilingual instantiation file src/lang/index is required. JS

import Vue from 'vue' // Introducing Vue
import VueI18n from 'vue-i18n' // Introducing Internationalized Packages
import Cookie from 'js-cookie' // Introducing cookie packages
import elementEN from 'element-ui/lib/locale/lang/en' // Bring in hungry English bags
import elementZH from 'element-ui/lib/locale/lang/zh-CN' // Introducing hungry Chinese bags
import customZH from './zh' // Introducing custom Chinese packages
import customEN from './en' // Introduction of Custom English Pack
Vue.use(VueI18n) // Global Registration Internationalization Package
export default new VueI18n({
  locale: Cookie.get('language') || 'zh', // Get the language type from the cookie is not Chinese
  messages: {
    en: {
      ...elementEN, // Bring in hungry English language packs
      ...customEN
    },
    zh: {
      ...elementZH, // Introduction of hungry Chinese language packs
      ...customZH
    }
  }
})

main.js plugin to mount i18n and set element to current language

// Set element to current language
Vue.use(ElementUI, {
  i18n: (key, value) => i18n.t(key, value)
})

new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

Introducing custom language packs
src/lang/zh.js , src/lang/en.js
zh

export default {
  route: {
    Dashboard: 'home page',
    manage: 'Back-stage management',
    users: 'user management',
    menus: 'Menu Management',
    logs: 'Log Management',
    example: 'Example',
    table: 'Data List',
    // permissions:'rights management',
    // Employees:'employees',
    // employeesList:'Employee Management',
    // employeesInfo:'Personal Information',
    goods: 'Commodity Management',
    postInfo: 'Job Information',
    manageSelf: 'Manager Self-Service',
    setting: 'Set up',
    reports: 'Report Analysis',
    employeesAdd: 'Add Employee',
    EditiNfo: 'Edit Information',
    rights: 'Rights Management',
    print: 'Print Page',
    form: 'form',
    basicForm: 'Basic Form',
    stepForm: 'Step-by-step form',
    advancedList: 'Advanced Forms',
    step: 'step',
    details: 'Detail Page',
    BasicsDetails: 'Basic Details Page',
    seniorDetails: 'Advanced Details Page',
    import: 'Import',
    // register
    register: 'Personnel-register',
    login: 'Personnel-Sign in',
    // Approval
    approvals: 'Approval', // Approval
    salaryApproval: 'Wage Review',
    enterApproval: 'Enrollment Review',
    leaveApproval: 'Request Leave',
    quitApproval: 'Apply for resignation',
    overtimeApproval: 'Overtime application',
    securitySetting: 'Approval Settings',
    // staff
    employees: 'staff',
    employeesList: 'Employee List',
    employeesInfo: 'Personal information',
    employeesAdjust: 'Relocation',
    employeesLeave: 'Quit',
    employeesPrint: 'Print',

    // wages
    salarys: 'wages',
    salarysList: 'Wage List',
    salarysSetting: 'Wage Settings',
    salarysDetails: 'Wage details',
    salarysHistorical: 'Historical Archives',
    salarysMonthStatement: 'Monthly Report',
    // social security
    'social_securitys': 'social security',
    socialSecuritys: 'social insurance management',
    socialDetail: 'details',
    socialHistorical: 'Historical Archives',
    socialMonthStatement: 'Current Month Report',
    // organizational structure
    departments: 'organizational structure',
    'departments-import': 'Introduce',
    // company
    settings: 'Company Settings',
    // Check work attendance
    attendances: 'Check work attendance',
    usersApprovals: 'User Approval',
    // saas enterprise
    'saas-clients': 'enterprise',
    'saas-clients-details': 'Business Details',
    // Jurisdiction
    'permissions': 'Rights Management' // Rights Management
  },
  navbar: {
    search: 'Search this site',
    logOut: 'Log out',
    dashboard: 'home page',
    github: 'Project Address',
    screenfull: 'Full screen',
    theme: 'Skin peeler',
    lang: 'Multilingual',
    error: 'Error Log'
  },
  login: {
    title: 'Human Resource Management System',
    login: 'Sign in',
    username: 'Account number',
    password: 'Password',
    any: 'Fill in freely',
    thirdparty: 'Third party login',
    thirdpartyTips: 'Local cannot simulate, please simulate with your own business!!!'
  },
  tagsView: {
    close: 'Close',
    closeOthers: 'Close Others',
    closeAll: 'Close All',
    refresh: 'Refresh'
  },
  table: {
    title: 'Please enter a user',
    search: 'search',
    add: 'Add to',
    addUser: 'New Users',
    id: 'Sequence Number',

    email: 'mailbox',
    phone: 'Mobile phone',
    name: 'Full name',
    entryTime: 'Enrollment Time',
    hireForm: 'Form of employment',
    jobNumber: 'Work Number',
    department: 'department',
    managementForm: 'Management Form',
    city: 'Work City',
    turnPositiveTime: 'Correction time',

    permissionNew: 'Add Permission Group',
    permissionUser: 'Permission Group Name',
    imdsAi: 'Advanced Interface Authorization',
    avatar: 'Head portrait',
    introduction: 'introduce',
    paddword: 'Password',
    powerCode: 'Permission Code',
    powerDistriB: 'Permission Assignment',
    powerTitle: 'Permission Title',
    powerNav: 'Main Navigation',
    actions: 'operation',
    edit: 'edit',
    delete: 'delete',
    cancel: 'Cancel',
    confirm: 'Determine',
    return: 'Return',
    operationType: 'Operation type',
    operationDate: 'Operation time',
    date: 'date',
    submit: 'Submit',
    operator: 'Operator',
    results: 'results of enforcement',
    describe: 'describe',
    save: 'Preservation',
    signOut: 'Sign out',
    reset: 'Reset',
    know: 'I got it!',
    view: 'See'

  }
}

en

export default {
  route: {
    dashboard: 'Dashboard',
    manage: 'manage',
    users: 'users',
    menus: 'menus',
    // permissions: 'permissions',
    logs: 'logs',
    example: 'example',
    table: 'table',

    postInfo: 'Job information',
    manageSelf: 'Manager self-help',
    setting: 'setting',
    reports: 'report',
    employeesAdd: 'add employees',
    EditiNfo: 'Edit information',
    print: 'print',

    form: 'form',
    basicForm: 'basic form',
    stepForm: 'step form',
    advancedList: 'advanced form',
    step: 'step',

    details: 'details',
    BasicsDetails: 'Basic details page',
    seniorDetails: 'Advanced details page',
    import: 'Import',
    register: 'HRM-Register',

    // Sign in
    login: 'HRM-Login',
    // Approval
    approvals: 'Approvals', // Approval
    salaryApproval: 'Salary-Approval',
    enterApproval: 'Enter-Approval',
    leaveApproval: 'Leave-Approval',
    quitApproval: 'Quit-Approval',
    overtimeApproval: 'Overtime-Approval',
    securitySetting: 'Security-Setting',
    // staff
    employees: 'Employees',
    employeesList: 'Employees-List',
    employeesInfo: 'Employees-Info',
    employeesAdjust: 'Employees-Adjust',
    employeesLeave: 'Employees-Leave',
    employeesPrint: 'Employees-Print',
    // wages
    salarys: 'salarys',
    salarysList: 'Salarys-List',
    salarysSetting: 'Salarys-Setting',
    salarysDetails: 'Salarys-Details',
    salarysHistorical: 'Salarys-Historical',
    salarysMonthStatement: 'Salarys-Month',
    // social security
    'social_securitys': 'Social',
    socialSecuritys: 'Social-Securitys',
    socialDetail: 'Social-Detail',
    socialHistorical: 'Social-Historical',
    socialMonthStatement: 'Social-Month',
    // organizational structure
    departments: 'departments',
    'departments-import': 'import',

    // company
    settings: 'Company-Settings',
    // Check work attendance
    attendances: 'Attendances',
    // User Approval
    usersApprovals: 'Users-Approvals',
    // enterprise
    'saas-clients': 'Saas-Clients',
    'saas-clients-details': 'Saas-Details',
    'permissions': 'permissions' // Rights Management

  },
  navbar: {
    search: 'search',
    logOut: 'Log Out',
    dashboard: 'Dashboard',
    github: 'Github',
    screenfull: 'screenfull',
    theme: 'theme',
    lang: 'i18n',
    error: 'error log'
  },
  login: {
    title: 'itheima login',
    login: 'Log in',
    name: 'name',
    entryTime: 'entry time',
    hireForm: 'hire form',
    jobNumber: 'job number',
    department: 'department',
    managementForm: 'management form',
    city: 'city',
    turnPositiveTime: 'turn positive time',

    password: 'Password',
    any: 'any',
    thirdparty: 'Third',
    thirdpartyTips: 'Can not be simulated on local, so please combine you own business simulation! ! !'
  },
  tagsView: {
    close: 'Close',
    closeOthers: 'Close Others',
    closeAll: 'Close All',
    refresh: 'refresh'

  },
  table: {
    title: 'Title',
    search: 'Search',
    add: 'add',
    addUser: 'addUser',
    id: 'ID',
    email: 'Email',
    phone: 'Phone',
    username: 'User',
    permissionNew: 'permissionNew',
    permissionUser: 'Permission',
    imdsAi: 'Advanced interface authorization',
    avatar: 'Avatar',
    introduction: 'Introduction',
    paddword: 'paddWord',
    powerCode: 'Permission code',
    powerTitle: 'Permission title',
    actions: 'Actions',
    edit: 'Edit',
    delete: 'Delete',
    cancel: 'Cancel',
    confirm: 'Confirm',
    operationType: 'operationType',
    operationDate: 'operationDate',
    date: 'Date',
    operator: 'operator',
    results: 'results of enforcement',
    describe: 'Pedagogical operation',
    save: 'save',
    signOut: 'sign out',
    submit: 'submit',
    reset: 'reset',
    know: 'I Know',
    return: 'return',
    view: 'view'

  }
}

Index. The same language pack was introduced in JS

import customZH from './zh' // Introducing custom Chinese packages
import customEN from './en' // Introduction of Custom English Pack
Vue.use(VueI18n) // Global Registration Internationalization Package
export default new VueI18n({
  locale: Cookie.get('language') || 'zh', // Get the language type from the cookie is not Chinese
  messages: {
    en: {
      ...elementEN, // Bring in hungry English language packs
      ...customEN
    },
    zh: {
      ...elementZH, // Introduction of hungry Chinese language packs
      ...customZH
    }
  }
})

Turn left menu into multilingual presentation text
layout/components/SidebarItem.vue

<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="$t('route.'+onlyOneChild.name)" />

Encapsulate multilingual component src/components/lang/index.vue

<template>
  <el-dropdown trigger="click" @command="changeLanguage">
    <!-- You have to add one here div -->
    <div>
      <svg-icon style="color: #fff; font-size: 20px" icon-class="language" />
    </div>
    <el-dropdown-menu slot="dropdown">
      <el-dropdown-item command="zh" :disabled="'zh' === $i18n.locale"
        >Chinese</el-dropdown-item
      >
      <el-dropdown-item command="en" :disabled="'en' === $i18n.locale"
        >en</el-dropdown-item
      >
    </el-dropdown-menu>
  </el-dropdown>
</template>

<script>
import Cookie from 'js-cookie'
export default {
  methods: {
    changeLanguage (lang) {
      Cookie.set('language', lang) // Switch Multilingual
      this.$i18n.locale = lang // Set to Local i18n Plugin
      this.$message.success('Successful switch to multiple languages')
    }
  }
}
</script>

Register the component src/components/index globally. JS

import lang from './lang'
Vue.component('lang', lang) // Register Full Screen Components

Place layout/navbar.vue

<lang class="right-menu-item" />

Topics: Front-end Linux Operation & Maintenance html server