I was in charge of back-office projects at the company, and I took the opportunity to review and summarize the mistakes I had made

The directory structure

The initial directory is generated by the Vue CLI, but the default directory structure does not meet the requirements of the project. Here is the extended structure

├─ public // Static page ├─ scripts // Related Scripts To Configure ├─ SRC // Home Directory ├─ Assets // Static Resources ├─ API // Interface Information ├─ filters // Filter ├─ lib // ├─ Router // Route Config ├─ Styles // Style ├─ Utils ├─ views // Page ├─ app.vue // page Main entry ├─ main.js // Main entry ├─ tests // Test Cases ├─.EditorConfig // Edit related Configuration ├─ .postCSSLc.js // Postcss Configure ├── Babel.config. js // Preset Record ├─ package.json // Rely ├──.eslintrc.js // ESLint related Configure ├── Readme. md // project README ├ ─ vue.config.js // webpack configurationCopy the code

Rights management

A medium and large background is inevitably indispensable to the control of permissions, RBAC model is needed to understand, more complex model is evolved on this basis, because I have written related articles, here is not specific, you can click to view ramblings about permissions design

Axios and mock

axios

Axios is an HTTP library that can be used in browsers and Node.js, and is often used when using SRR isomorphism. The basic encapsulation is described below, depending on the business.

// utils/config.js
import http from "http";
import https from "https";
import qs from "qs";

const axiosConfig = {
  // Note that in the production environment, this is for demonstration only
  baseURL: "/mock/".// Data processing after request
  transformResponse: [
    function(data) {
      returndata; }].// Query the object serialization function
  paramsSerializer: function(params) {
    return qs.stringify(params);
  },
  // Set timeout
  timeout: 30000.// Whether to carry Token across domains
  withCredentials: true.responseType: "json"./ / XSRF Settings
  xsrfCookieName: "XSRF-TOKEN".xsrfHeaderName: "X-XSRF-TOKEN".// Maximum number of forwards, for Node.js
  maxRedirects: 5.// Maximum response data size
  maxContentLength: 2000.// Customize the error status code range
  validateStatus: function(status) {
    return status >= 200 && status < 300;
  },
  / / for the node. Js
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true})};export default axiosConfig;
Copy the code

The configuration file is defined above. The reason why we don’t call AXIos globally or modify the configuration is that it will cause pollution. The interceptor part needs to be processed below to cancel repeated requests (if there is a business code, it can also be configured inside, such as the need to send token and other parameters uniformly).

// utils/api.js
import axios from "axios";
import config from "./config";

// Cancel duplicate requests
let pending = [];
const cancelToken = axios.CancelToken;
const removePending = config= > {
  for (let p in pending) {
    let item = p;
    let list = pending[p];
    // Execute the function body when the current request exists in the array
    if (list.url === config.url + "&request_type=" + config.method) {
      // Perform the cancel operation
      list.cancel();
      // Remove the record from the array
      pending.splice(item, 1); }}};const service = axios.create(config);

// Add request interceptor
service.interceptors.request.use(
  config= > {
    removePending(config);
    config.cancelToken = new cancelToken(c= > {
      pending.push({
        url: config.url + "&request_type=" + config.method,
        cancel: c
      });
    });
    return config;
  },
  error => {
    return Promise.reject(error); });// Return status judgment (add response interceptor)
service.interceptors.response.use(
  res= > {
    removePending(res.config);
    return res;
  },
  error => {
    return Promise.reject(error); });export default service;
Copy the code

OK, this is the end of the basic encapsulation. In specific development, you can call Axios by introducing Action in vuex.

mock

Mocks are common in front and back end development, in the form of a convention document interface that allows the front end to develop quickly in order to decouple from unnecessary wait times before the back end.

Create a mock folder in SRC to store mock information. If the business is divided into multiple layers, remember to partition the mock folder. After that, expose the index.js file and import it globally in SRC /main.js. Here is a simple demo example

// src/mock/index.js
import Mock from "mockjs";
// Get the mock.Random object
const Random = Mock.Random;
// Set the asynchronous wait time
Mock.setup({
  timeout: "200-2000"
});
const produceNewsData = function() {
  let newNewsObject = {
    title: Random.ctitle(), // random.ctitle (min, Max) Randomly generates a Chinese title. The default length is between 3 and 7
    content: Random.cparagraph(), // random.cparagraph (min, Max) Generates a Chinese paragraph randomly. The number of sentences in the paragraph is 3-7 by default
    createdTime: Random.date() // random.date () indicates the format of the generated date string, which defaults to YYYY-MM-DD;
  };

  return newNewsObject;
};
// Random demo request image
const demoOther = function() {
  let newsList = [];
  for (let i = 0; i < 20; i++) {
    let newNewsObject = {
      img: Random.dataImage("300x250")}; newsList.push(newNewsObject); }return newsList;
};
// Request this URL and it will be mock
Mock.mock("/mock/user", produceNewsData);
Mock.mock("/mock/other", demoOther);
Copy the code
// src/main.js
// Omit the other code
import "./mock";
Copy the code

BaseURL is filled in under the wrapper axios config file config.js above, so you can omit the mock prefix and remember to distinguish between development and production environments

Global loading

First, I would like to talk about the common centralized management methods of loading

  • Manual management, one per pageloadingOpen when a request is about to be sent, close when the request ends, no problem except verbose and modification trouble
  • Interception is accomplished through some middleware, for exampledav-loading
  • Intercept by interceptor, for exampleaxios

The concrete implementation of the third kind is introduced here, because there is only one kind of background project loading in nature, so it is not necessary to introduce the second kind to affect the flexibility of dividing components.

The implementation idea is very simple. By dividing the API directory, place the interface that needs loading into a file separately, and use AXIos request and response interceptor to match URL to display and hide loading. The following is the specific implementation

// src/utils/app.js
import axios from "axios";
import config from "./config";
import vue from "vue";
import * as loadingAPI from "@/api/loading";

// Use this change to remind loading whether to display
const state = vue.observable({ content: 0 });

function changeState(config, add = true) {
  const { url } = config;
  const value = Object.values(loadingAPI);

  if(! value.includes(url)) {return;
  }
  if (add) {
    state.content += 1;
    return;
  }
  state.content -= 1;
  return;
}

const service = axios.create(config);

// Add request interceptor
service.interceptors.request.use(
  config= > {
    changeState(config);
    return config;
  },
  error => {
    return Promise.reject(error); });// Return status judgment (add response interceptor)
service.interceptors.response.use(
  res= > {
    changeState(res.config, false);
    try {
      const { data } = res;
      Reflect.set(res, "data".JSON.parse(data));
    } catch (e) {
      //
    }
    return res;
  },
  error => {
    return Promise.reject(error); });// Omit some code
export default service;
export { state };
Copy the code

Var. Observable generates an object that can be used by components to render functions and calculate properties. Displays and conceals loading by changing the internal content of the object. This is why you need to separate the API directory and then add loading components and state objects to the page

<template>
  <div>
    <loading v-model="show" />
    <router-view class="router"></router-view>
  </div>
</template>

<script>
  import loading from "./loading";
  import { state } from "@/utils/server";
  export default {
    components: {
      loading
    },
    data: (a)= > ({ state }),
    computed: {
      show() {
        return this.state.content > 0; }}};</script>
Copy the code

The details of the loading component implementation are skipped here. Finally, the page request can be directly consistent with the normal page, for example

serve.get(user).then(user= > {
  this.content = user.data.content;
  this.title = user.data.title;
});
// Some other operations will not affect loading
Promise.all([serve.get(other)]).then(all= > {
  console.log(all);
});
Copy the code

Virtual list

Name has not been, of course, may be a bit before the author writing project in a scene, in the background project (according to the business) image management functions may be often encountered, back-end returned to a certain number of pictures of your address and your rolling load this information, this logic is very simple, but will need to think about this performance problem? Obviously it will, and when enough scrolling is done, the persistence of the previous DOM node will cause the browser to freeze.

Virtual list solution is similar to the scene, the user perception of the area is always limited, only need to change when the need to change the display of data can be, here posted before the article about the list optimization

Routing component custom cache

A very common scenario is in the e-commerce page, when you click on the list to enter the details, when you return, the list information must exist without being cleared, that is

Home -> List -> Details

We obviously want the page to not be cleared. We want to go from the list to the home page and enter the list. We don’t want to keep the list information

keep-alive

Vue has a built-in keep-alive component, which is used to cache component information. In the official guide, it is combined with dynamic components to save unnecessary rendering, and it can also be combined with
to cache routing pages.

<keep-alive>
  <router-view></router-view>
</keep-alive>
Copy the code

However, using and this alone can only do caching, but it can’t do what we want to do to specify a page cache. Is there any other way?

It is possible to define two
tags to determine whether a cache is needed or not, and then dynamically adjust them using the include attribute of the keep-Alive built-in component.

That’s a little abstract, but let’s do an example

// route
// Omit some code
 {
      path: "/keepAlive/".component: keepAlive,
      children: [{path: "".component: (a)= >
            import(/* webpackChunkName: "keep-alive-home" */ "@/view/keep-alive/home"),
          name: "keepAlive".meta: {
            depth: 1}}, {path: "list".component: (a)= >
            import(/* webpackChunkName: "keep-alive-list" */ "@/view/keep-alive/list"),
          name: "list".meta: {
            name: "list".keepAlive: true.depth: 2}}, {path: "details".component: (a)= >
            import(/* webpackChunkName: "keep-alive-details" */ "@/view/keep-alive/details"),
          name: "details".meta: {
            depth: 3}}},Copy the code














// index.vue
<template>
  <! -- Define two exits, one can be cached and one normal route -->
  <div>
    <keep-alive :include="include">
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if=! "" $route.meta.keepAlive"></router-view>
  </div>
</template>

<script>
  export default {
    data: (a)= > ({ include: []}),watch: {
      $route(to, from) {
        // Determine the entry condition
        const { keepAlive, name, depth } = to.meta;
        if (keepAlive && !this.include.includes(name)) {
          this.include.push(name);
        }
        // Determine the rollback condition
        if (from.meta.keepAlive && from.meta.depth > depth) {
          / / delete
          const index = this.include.indexOf(from.meta.name);
          if(index ! = =- 1) {
            this.include.splice(index, 1); }}}}};</script>
Copy the code

One caveat here! The cached page must have the name attribute value

First check the name option of the component itself, and if the name option is not available, match its locally registered name (the key value of the parent component’s components option). The anonymous component could not be matched.

To facilitate management, the name attribute of the component is consistent with the meta attribute of the route

The last

The example shown above has been put into my warehouse. If you are interested, you can clone it and debug it yourself.

I am looking for a job. If you have a suitable position, please help me push it inside. You can comment on my resume privately or contact me at [email protected]