Writing in the front

In the recent Vue project, I used a few tricks to meet the requirements. Take notes and maybe help my friends too.

Read the key

Requirement 1: Configure an alias for a path

In the development process, we often need to introduce various files, such as images, CSS, JS, etc., to avoid writing long relative paths (.. /), we can configure an alias for different directories.

Go to the resolve configuration item in webpack.base.config.js and add an alias to its alias as follows:

Create a CSS file with arbitrary styles:

.avatar
  display: flex;
  justify-content: center;
  align-items: center;

.avatar-img
  padding 20px
  border solid 1px #ccc
  border-radius 5px
Copy the code

Next, we can use it directly in the files we need to import:

<template> <div class="avatar"> <img class="avatar-img" src="~img/avatar.png" alt=""> </div> </template> <script> export  default { name: "Home" } </script> <style scoped lang="stylus"> @import "~css/avatar"; </style>Copy the code

Note that the alias must be preceded by ~ if it is not imported as import:

Requirement 2: The implementation is required to modify the API address directly in the production package

This need, how can I say, is a need, find a way to achieve it.

Suppose you have an apiconfig.js file that does some configuration to AXIOS as follows:

import axios from 'axios';

axios.defaults.timeout = 10000;
axios.defaults.retry = 3;
axios.defaults.retryDelay = 2000;
axios.defaults.responseType = 'json';
axios.defaults.withCredentials = true;
axios.defaults.headers.post["Content-type"] = "application/json";

// Add a request interceptor
axios.interceptors.request.use(function (config) {
  // Do something before request is sent
  return config;
}, function (error) {
  // Do something with request error
  return Promise.reject(error);
});

// Add a response interceptor
axios.interceptors.response.use(function (response) {
  // Do something with response data
  return response;
}, function (error) {
  // Do something with response error
  return Promise.reject(error);
});

export default axios
Copy the code

Add a config.json file to the static folder to centrally manage all API addresses:

{
  "base": "/api"."static": "//static.com/api"."news": "//news.com.api"
}
Copy the code

Open main.js and write the following code:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from 'js/apiConfig';      //import is directly introduced without adding ~

Vue.config.productionTip = false;
Vue.use(ElementUI);

/* eslint-disable no-new */
let startApp = function () {
  let randomStamp = new Date().getTime();
  axios.get(`/static/config.json? t=${randomStamp}`).then((data) = > {
    axios.defaults.baseURL = data.base;     // Set a default root path
    Vue.prototype.$axios = axios;
    Vue.prototype.$apiURL = data;   // Mount all path configurations to the Vue prototype
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      components: {App},
      template: '<App/>'}); })}; startApp();Copy the code

Get the API file using AXIos and then initialize it.

Requirement 3: The background returns to the menu based on the user permission value

The menu is a tree structure (PS: even if it’s not a tree structure, you have to deal with it as a tree structure). I’m using ElementUI as a reference to the daoyou article, which is implemented as follows:

Create a new menu. vue file and write the following code:

<script> export default { name: "MenuItem", props: { data: { type: Array }, collapse: { type: Boolean } }, methods: {// createMenuItem createMenuItem(data, createElement) { return data.map(item => { if (item.children && item.children.length) { return createElement('el-submenu', {props: {index: item.id.toString()}}, [ createElement('template', {slot: 'title'}, [ createElement('i', {class: item.icon}), createElement('span', [item.title]), ] ), this.createMenuItem(item.children, CreateElement) // recursive])} else {return createElement('el-menu-item', {props: {index: item.path}}, [ createElement('i', {class: item.icon}), createElement('span', {slot: 'title'}, [item.title]),])}}), // select menu onSelect(key, keyPath) {console.log(key, keyPath); } }, render(createElement) { return createElement( 'el-menu', { props: { backgroundColor: "#545c64", textColor: "#fff", activeTextColor: "#ffd04b", collapse: this.collapse, router:true }, class:'el-menu-vertical-demo', on: { select: this.onSelect } }, this.createMenuItem(this.data, createElement) ) } } </script> <style scoped lang="stylus"> .el-menu-vertical-demo:not(.el-menu--collapse) { width: 200px; min-height: 400px; } </style>Copy the code

Here we mainly use two things, one is the render function, one is recursion, if you are not familiar with the render function, please click here. There is only one root element in template, and Vue limits the use of V-for for root elements. Also, by looking at the code in the browser, you can see that the menu is ul plus Li, and having the root element will break the tag structure (it doesn’t affect functionality, but it’s still uncomfortable 😂). Then, where needed:

<template> <el-container> <el-aside width="auto"> <Menu :data="menu" :collapse="isCollapsed"></Menu> </el-aside> <el-container> <el-header> <el-button type="text" icon="el-icon-d-arrow-left" @click="isCollapsed=! isCollapsed"></el-button> <h3>MenuName</h3> <span>MeFelixWang</span> </el-header> <el-main> <router-view></router-view> </el-main> </el-container> </el-container> </template> <script> import Menu from '@/components/Menu'; Export default {name: 'App', data() {return {menu: [{title: 'navigation 1 ', id: 1, path: '', icon: 'el - icon - search, children: [{title:' navigation bar one, id: 2, path: "', icon:" ', children: [{title: 'navigation and a bar', id: 4, path: '/ test' icon: "', children: []}, {title: 'a navigation bar bar 2, id: 5, path:"', icon: "', children: [{title: 'navigation and a bar two poles, id: 6, path: '/ 6' icon: "', children: []}, {title: 'a navigation bar and two poles two', id: 7, path: '/ 7' icon: ' ', the children: []},]}}, {title: 'navigation bar 2, id: 3, path:' / 3, icon: "', children: []}}, {title: 'navigation 2, id: 8, path:' / 8 'icon: 'el-icon-setting', children: []}, {title: 'navigate ', id: 9, path: '/9', icon: 'el-icon-document', children: []}, {title:' navigate ', children: []}, {title: 'navigate ', children: []}, {title: Children: [{title: 'id ', id: 11, path: '/11', icon: '', children: [{title:' id ', path: '/11', icon: '', children: [{title: 'id ', path: '/11', icon: '', children: [{title:' id ', path: '/11', icon: '', children: []}, {title: 'navigation four poles two, id: 12, path: "', icon: "', children: [{title: 'navigation four poles two poles, id: 14, path:' / 14 'icon: 'children: []}}, {title:' navigation four poles, id: 13, path: '/', icon: "', children: []},]}], isCollapsed: false } }, methods: { handleOpen(key, keyPath) { console.log(key, keyPath); }, handleClose(key, keyPath) { console.log(key, keyPath); } }, components: { Menu } } </script> <style lang="stylus"> * margin 0 padding 0 html, body, .el-container, .el-aside height 100% .el-aside background-color rgb(84, 92, 100) .el-menu border-right solid 1px rgb(84, 92, 100) .el-header display flex justify-content space-between align-items center background-color aliceblue .el-button--text color: #606266; i font-weight bold </style>Copy the code

The effect is as follows:

Requirement 4: The Select option is a tree structure, it must be a tree structure

Tree structure tree structure bar, is not the style, change should be ok.

<template> <div> <el-select V-model ="tree" placeholder=" placeholder "> <el-option v-for="(item,index) in options" :key="index" :label="item.label" :value="item.id" :style="{paddingLeft:(item.level*10+20)+'px'}" :class="item.level? {{tree}} </div> </template> <script> export default {name: "Home", data() {return {tree: ", options: [], originData: [{label: 'this is the root ', id: 1, children: [{label:' this is the stem ', id: 1, children: [{label: 'this is the stem ', id: 2, children: []}, {label: 'this is a stem ', id: 3, children: []}, {label:' this is a stem ', id: 4, children: [{label: 'this is a stem ', id: 4, children: []}, {label:' this is a stem ', id: 4, children: [{label: 'this is a stem ', id: 4, children: []}, {label:' this is a stem ', id: 4, children: [] 6, children: []}, {label: 'this is leaves the 132', id: 7, children: []},]}, {label: 'this is success stems, id: 5, children: []},]}, {label: 'This is root 2 ', id: 8, children: [],}, {label:' this is root 3 ', id: 9, children: []}, {label: 'this is root 3 ', id: 10, children: []}, {label:' this is root 3 ', id: 10, children: []}, {label: 'This is stem 3 2 ', id: 11, children: [{label:' this is leaf 3 2 1 ', id: 12, children: []} ] }, ], }, ] } }, created() { this.options = this.decomposeTree(this.originData, 0); }, methods: {// decomposeTree(array, level) {let tmpArr = []; (function decompose(arr, lev) { for (let i = 0; i < arr.length; i++) { let tmpObj = {}; let item = arr[i]; item.level = lev; tmpObj = Object.assign({}, item); tmpArr.push(tmpObj); if (item.children) { decompose(item.children, lev + 1); } delete tmpObj. Children; }})(array, level); return tmpArr; } } } </script> <style scoped lang="stylus"> .is-sub:before content '- ' </style>Copy the code

Since option receives a one-dimensional array, the hierarchy of each item is set during the flattening by recursively flattening the tree structure, and the indentation and prefix symbols are set through the hierarchy. The effect is as follows:

The reason for doing so, because it is a management system, simple and effective, there is no need for this component to introduce a new plug-in or write a (later needed except ha); This can also be simulated using input plus tree controls (PS: finally introducing a plugin, haha 😂).

Requirement 5: Allow users to customize the display template

This requirement is to allow users to write their own templates, yes, yes, is to let the user write their own templates, well, and according to the demand of the user interface following change color, dynamic component and asynchronous components, their tried several ways and still won’t do, later in the community BBS see there is a big answer (and I do really somebody in demand (┬ _ ┬)), This is the address, implemented as follows:

<template> <component :is="dynComponent" v-bind="data"></component> </template> <script> export default { name: "test", props: ['test'], data() { return { template: '', data: {}}}, created() {this.getTemplate()}, methods: { getTemplate() { this.$axios.get('http://localhost:8080/static/test.json').then((result) => { this.template = result.template; this.data = result; }); } }, computed: { dynComponent() { const template = this.template ? `<div>${this.template}</div>` : `<div>nothing here yet</div>`; Return {template, // template is the template props: ['data'] // pass data}},}} </script> <style scoped lang="stylus"> </style>Copy the code

Because JS is single-threaded, the page will be rendered before the asynchronous template comes back. This method computs the responsive properties of the properties and lets Vue re-render once the template is retrieved. Give the big guy a knee. If tao friends have a better way, please tell me, thank you!

Requirement 6: Remote image loading failed, set default image

Some of the images may be from another website or something, so you should set a default image as follows:

<template>
  <div>
    <img v-bind:src="imgUrl" @error="handleError" alt="">
  </div>
</template>

<script>
  export default {
    name: "userList",
    data() {
      return {
        imgUrl: 'url of the image'
      }
    },
    methods: {
      handleError(e) {
        e.target.src = '/static/default.png'
      }
    }
  }
</script>

<style scoped lang="stylus">

</style>
Copy the code

Place a default image in the static folder, then handle the img onError event and set SRC to the default image path in static.

Requirement 7: Single-page applications should terminate previous requests when switching pages

This demand, very reasonable! If you don’t terminate the previous request, you might see some of the messages that pop up on the new page after the previous request succeeded (or failed), which is definitely not reasonable. How do you do that? Axios provides a way to cancel the request:

But there is a small problem, a page may have a lot of requests, so when switching pages can not be cancelled one by one (and you do not know which interface is called specifically), netizens here provide a method, I made some optimization:

 init() {
        let self = this;
        // Configure the global cancel array
        window.__axiosPromiseArr = [];
        // Request interception
        this.$axios.interceptors.request.use(function (config) {
          // Set cancelToken for each request
          config.cancelToken = new self.$axios.CancelToken(cancel= > {
            window.__axiosPromiseArr.push({cancel})     // Put it into a global array so that it can be cancelled uniformly later
          });
          return config;
        }, function (error) {
          return Promise.reject(error);
        });
        // Response interception
        this.$axios.interceptors.response.use((response) = > {
          switch (response.status) {
            case 204:
              this.$message.success('Operation successful! ');
              break;
            default:
              return response.data;
          }
        }, (error) => {
          if (error.message === 'cancel') {     
                // Abort requests throw an error, catch it and keep it from showing on the console}}); },Copy the code

Then in the route guard:

vueRouter.beforeEach((to, from, next) = > {
  // Terminates all requests during route switchover
  let axiosPromiseArr = window.__axiosPromiseArr;
  if (axiosPromiseArr) {
    console.log(axiosPromiseArr);
    let len = axiosPromiseArr.length;
    while (len--) {     CancelToken cancelToken cancelToken cancelToken cancelToken cancelToken cancelToken cancelToken cancelToken
      axiosPromiseArr[len].cancel('cancel');
      axiosPromiseArr.splice(len, 1);
    }
    // or: window.__axiospromisearr = [];
  }
  next()
});
Copy the code

So far, it seems that this method is still practical. If you have a better method, please leave a message and tell me.

The last words

This article is my recent use of some small skills, if the road friends have a better way to achieve, welcome in the comments section of the message discussion, the error is also welcome to point out, common learning (of course, there are difficult needs can also leave a message, discuss solutions 😄), this article will be updated from time to time, as a notebook 😏.