As the saying goes, “big things under heaven must be separated and separated for a long time “, so is WEB development. From JSP and PHP to vue+ back end, React + back end, Angular + back end… The development model of front and back end separation has existed for many years, during which time there have been many excellent front-end frameworks, back-end frameworks, development patterns; Now, however, server-side rendering is being mentioned again and used in various projects; It is believed that with the development of hardware technology and network, Web development will be unified at a certain node

Today’s topic is back to front separation (Vue+ back), but I think it might be clearer to look at things in terms of front to back separation;

“Front end, front end, I’m front end, back end, I’m back end, back end, I’m back end, front end, I’m back end.”

In my opinion, if we want to do front-end development well under the mode of front-end separation, we should know some basic knowledge of back-end. The following topics are discussed here:

  • Cross-domain problem

  • Interface, data specification

  • Data interconnection in the development environment

  • Vue has three kinds of data and three changes

  • The project practice

    • Add the vueDevtools console development tool
    • About form data reset
    • Echarts component encapsulation and data presentation examples
    • Handle the Promise asynchronous interface response
    • Recommendations for extracting large files as components
  • Some of the problems I encountered

    • Vue-cli3 Unvalidates ESLint code
    • The AXIos request sends two methods: the first Method is option and the second Method is GET
    • Vue development, through the proxy request back-end interface, return 307
    • Axios has a problem with carrying parameters
    • About paging queries
    • About the front and back end data types
  • remarks

Cross-domain problem

The first thing we should know is that cross-domain only exists on the browser side; Is affected by the same origin policy of the browser. There are several solutions for cross-domain solutions (see cross-domain solutions), and HTTP headers need to be understood. Here we focus on two solutions for our own projects;

Solutions in the development environment:

  1. Server Settings. If the server Settings are successful, the browser console will not display cross-domain error messages, such as
// NodeJs 
router.get('/getLists'.function(req, res, next) {

	res.header("Access-Control-Allow-Origin"."*");
    res.header("Access-Control-Allow-Headers"."X-Requested-With");
    res.header("Access-Control-Allow-Methods"."PUT,POST,GET,DELETE,OPTIONS");
    res.header("X-Powered-By".'3.2.1')
    res.header("Content-Type"."application/json; charset=utf-8"); / /... }); // Other programming languagesCopy the code
  1. Cross-domain of Vue framework (VUE-CLI scaffolding); Node + Webpack + Webpack-dev-server agent interface is mainly used across domains. In the development environment, since both vue rendering service and interface proxy service are under Webpack-dev-server, there is no need to set headers cross-domain information between the page and proxy interface.
module.exports = {
  publicPath: '/',
  assetsDir: './static',

  devServer: {
    open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    proxy: {
      '/api': {target: ` http://192.168.1.254:3000 `, / / agent cross-domain target interface changeOrigin:true,
        pathRewrite: {
          '^/api': ' '
        }
      }

    }
  }
}
Copy the code

Deploying the solution

  1. Nginx reverse proxy interfaces cross domains

    Cross-domain principle: The same Origin policy is a security policy of the browser, not a part of the HTTP protocol. The server invokes the HTTP interface only using THE HTTP protocol, and does not execute JS scripts. There is no need for the same origin policy, so there is no crossing problem.

  2. Nginx configure a proxy server (domain name and domain1 the same, different port) as a jumper, reverse proxy access to domain2 interface, and can incidentally modify the cookie in the domain information, convenient for the current domain cookie writing, cross-domain login

3. Configuration Reference

server { listen 8001; server_name localhost; location / { root html; index index.html index.htm; } ## /api/ not /api location /api/ { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; Proxy_pass http://10.10.1.116:3000/; } location /user/ { add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; Proxy_pass http://10.10.12.120:9000/; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; }}Copy the code

Interface data specification

As for the data format returned by the interface, based on some problems encountered in the project and the solutions of other similar products in the network environment, the following data format is advocated:

{
    status: true, / /true || false                  Boolean
    msg: 'Data request successful'String data: {} // Json Map Object}Copy the code

The data in data can be in the following form:

  1. Returns a list of large quantities
{
    status: true,
    msg: 'success',
    data: {
        lists: ['a'.'b'.'c'],
        // lists: [{id: 1, name: 'zhangsan'}, {id: 2, name: 'lisi'}] / / if a paging pagination: {the totals: 100, / / record the current total number of article: 5, / / the current page number: 20, / / article according to the number of pages per page: 5 // Total pages}}} // If the data is empty, please return fully formatted data, do not return null {status:true,
    msg: 'success', data: {lists: [], // lists: [] // If there are pagination cases pagination: {totals: 0, // Current page number: Pages: 1 // Total pages}}}Copy the code
  1. Return objects (such as personal information details)
{
    status: true,
    msg: 'success',
    data: {
        name: 'Michael Jordan', age: 30}} // If information items sometimes exist and sometimes do not exist, it is recommended that the front end list the fields that must be displayed one by one to avoid unnecessary error messages, such as: {book: {name:' ',
        author: ' ',
        category: ' ',
        publishers: ' ',
        time: ' '}}Copy the code
  1. Returns an enumeration object, such as school information, class information, and personal information
{
    status: true,
    msg: 'success',
    data: {
        'school': {
            name: 'MAO Tancheong Secondary School',
            address: 'luan', thewire:'45%'// Undergraduate enrollment rate},'class': {
            name: Class 9, Grade 3,
            studentNums: 45 
        },
        'user': {
            name: 'look crazy', age: 30}}} // If the data is empty please return the following format, do not return data: null or data: {}, please return {status:true,
    msg: 'success',
    data: {
        'school': {},
        'class': {},
        'user': {}}} // This can effectively avoid the page reported a large number of XXX is undefined errorCopy the code
  1. Returns an array like a list of types
{
    status: true,
    msg: 'success',
    data: [
        {value: 1, label: 'thermal map'},
        {value: 2, label: 'Satellite view'}}]Copy the code
  1. Return chart data (it is recommended that the front-end provide the data format to the server in advance)
{
    status: true,
    msg: 'success',
    data: {
        xAxis: [
            {name: "Crimes against the law", max: 100},
            {name: "Monitor", max: 100},
            {name: "At large",max: 100},
            {name: In the "escape",max: 100},
            {name: "Other",max: 100}
        ],
        series: [{
            name: "Total number of warnings",
            value: [65, 73, 80, 74, 79, 93]
          },
          {
            name: "Processed", value: [32, 43, 50, 64, 60, 62]}]} // If the data is null, do not return {data: null}true,
    msg: 'success',
    data: {
        xAxis: [],
        series: []
    }
}
Copy the code

Data interconnection in the development environment

In project development, we may encounter such a problem. In a project, we may access data from multiple back-end services. At this time, how to deal with the front-end project?

Set up multiple agents in vue.config.js

By setting the proxy, we will be able to set N proxies, access data of N services, such as:

  devServer: {
    port: port,
    //open: true,
    overlay: {
      warnings: false,
      errors: true
    },
    proxy: {
        '/api': {target: ` http://192.168.1.254:3000 `, / / the backend development changeOrigin unified environment:true,
            pathRewrite: {
              '^/api': ' '}},'/system': {target: 'http://10.10.12.120:9000', // Backend development unified environment changeOrigin:true,
            pathRewrite: {
              '^/system': '/system'}},'/axfkq': {target: 'http://10.10.14.201:8888', changeOrigin:true,
            pathRewrite: {
              '^/axfkq': ' '}}, / /... },} // It is important to note that every time we add a proxy, we need to add a prefix (such as API) for the service to identify where to proxy it; At this time, there will be many problems, such as: 1. How to add prefix to URL 2. First, is it necessary to use multiple addresses, and if so, can the number be reduced to a minimum as far as possible? Second, through the form of configuration files, to reduce the workload of url modification to a minimum. Third, through a single service to proxy all interfaces to the same addressCopy the code

Back-end unified processing

Back-end unified processing is the third type above, where a single Web service processes the interface as a unified address request

Vue has three kinds of data and three changes

Three kinds of data are mainly introduced:

  • Prop (data passed from parent to child)
  • Data Data defined by the component itself (data that can only be modified directly)
  • Computed attributes (dependence on other data, dynamically changed data, passively changed data)

What the three have in common:

1. All can be directly displayed in the template. 2

Differences among the three:

  1. The data in data is the only data that can be set and assigned directly
  2. Computed changes according to changes in the values of PROP, DATA, and other computed attributes; You can set but do not set its value
  3. Data, computed are inside the component itself, and Prop is passed in through the parent component
  4. Prop properties are modified by modifying the corresponding data in the parent component (because Vue, like React, is a one-way data flow)
  5. The prop property can be assigned directly to a property in data as the initial value of the component
  6. If you want to process the data returned by the interface before using it, you can: 1. Format the data and then assign it to data. 2. Use computed, use computed data in the template

The project practice

Add the vueDevtools console development tool

As the saying goes, the work must be good,Vue development is inseparable from Vue development debugging tools; Console for front-end development is extremely important, good at debugging will make our development twice the result with half the effort, the following introduction to install a VUE console debugging plug-in:

As hand party, online partners will be configured with good tools dedicated to the majority of hand party, here first thanks; 1. Open Google Chrome, type chrome:// Extensions /, and click to load the decompressed extension

The original address

About form data reset

Form initialization, form assignment, and form reset are widely used in Vue projects. If managing form values effectively becomes a problem that front-end developers must face, one of the following solutions is recommended today:

// Define a piece of raw data
let form = {
    name: ' '.age: 0.isStudent: true.loves: []};export default {
    data(){
        form: JSON.parse(JSON.stringify(form))  // This is the key{}, the methods: {edit ()// The value can be assigned directly when editing
            this.from = {
                name: 'Bruce Lee'.age: 33.isStudent: false.loves: ['dance'.'kongfu']}// ...
        },
        add(){
            // The main thing to focus on is adding
            // If the form has not been reset after an edit or add action, the previous value may remain on the form
            
            // Reset the form
            this.form = JSON.parse(JSON.stringify(form)); }}}Copy the code

Echarts component encapsulation and data presentation examples

Charts are frequently used components in projects, and are widely used in statistics and large-screen display projects. It is very necessary to package charts as components for Vue projects, and the benefits are obvious and within reach. The following takes Echarts as an example;

The following demo has echart.js imported by default

How have we used diagrams in the past

:HTML
<div id="echartContainer"></div>:JS function renderChart(id) {var option = {color: ['#3398DB'], left: 4, top: 4, textStyle: {color: '# 69DBdf ', fontSize: 16}}, tooltip: {trigger: 'axis', axisPointer: { 'shadow' / / default is linear, optional for: 'the line' | 'shadow'}}, the grid: {top: '20%', left: '3%', right: '4%', bottom: '6%' containLabel: true }, xAxis: { type: 'category', axisLabel: { color: '#93a8bf' }, axisLine: { lineStyle: { color: '# 2 f415b'}}, axisTick: {graphics.linestyle: {color: '# 2 f415b'}}, data: [', haidian district, chaoyang district, fengtai, shijingshan, 'vallely]}, yAxis: { type: 'value', axisLabel: { color: '#93a8bf' }, axisLine: { lineStyle: { color: '#2f415b' } }, axisTick: { lineStyle: { color: '#2f415b' } }, splitLine: { show: false } }, series: [ { type: 'bar', barWidth: 20, data: [100, 200, 100, 150, 300] } ] } var myChart = echarts.init(document.getElementById(id)); myChart.setOption(option); } : Call renderChart('echartContainer')Copy the code

1\2\3 = 1\2\3 = 1\2\3

  • How do I dynamically set up diagrams to be re-rendered when I modify the data?

    Solution: (Extract data into variables, update options, call setOption once)

    New question: If the change, need to determine the parameters of the less fine, if more? I’m busy with options

  • What if you want to keep a copy of the default data in case the chart fails to display due to data problems?

    Solution: Reserve a default value when defining a variable. If the value is false, use the default value

    New problem: how to deal with the above variables? Variables have arrays, strings, and objects, so it’s not a lot of judgment to reserve a default

  • If you want to update a chart dynamically when the data in a series changes, how do you do that?

  • If I copy the code directly, will I be able to work seamlessly with the new project and instantly display the corresponding type of chart?

Don’t panic, today’s topic is Vue, and Vue can help us do these things:

  • Packaged Echarts components, as a whole, can be used freely in a VUE project without the lack of data to make the diagram invisible
  • We can make a judgment on any configuration item and display different effects according to the judgment value
  • When the data changes, we don’t have to do anything
  • Easy to transfer value, easy to modify

The following code is simply encapsulated for demonstration purposes, and can be copied directly if echarts is installed.

: VuE-echarts component SimpleBar <template> <div class="polyline-chart-container" id="boundChartContainer" ref="boundChartContainer"></div> <! --/polylineChartContainer--> </template> <script> import echarts from"echarts";
export default {
  name: "PolicStatisticsSpreadChat",
  props: {
    everyAnimation: {
      type: Boolean,
      default: true
    },
    title: {
      type: String,
      default: 'Regional Passenger Numbers - Component Titles'
    },
    xAxis: {
      type: Object,
      default: () => ["Tam Mun Harbour"."New Harbour"."Nangang"."Boao Port"."Newport"."Qinglan Harbour"."Port Gate Port"]
    },
    series: {
      type: Array,
      default: () => [220, 310, 301, 230, 120, 120, 310]
    }
  },
  data() {
    return {
      chart: null
    };
  },
  computed: {
    chartOptions() {
      return {
        color: ['#3398DB'],
        title: {
            text: this.title,
            left: 4,
            top: 4,
            textStyle: {
                color: '#69dbdf',
                fontSize: 16
            }
        },
        tooltip: {
            trigger: 'axis', axisPointer: {// Axis indicator, axis trigger workstype: 'shadow'// The default is a straight line, which can be:'line' | 'shadow'
            }
        },
        grid: {
            top: '20%',
            left: '3%',
            right: '4%',
            bottom: '6%',
            containLabel: true
        },
        xAxis: {
            type: 'category',
            axisLabel: {
                color: '#93a8bf'
            },
            axisLine: {
                lineStyle: {
                    color: '#2f415b'
                }
            },
            axisTick: {
                lineStyle: {
                    color: '#2f415b'
                }
            },
            data: this.xAxis || ['Haidian District'.'Chaoyang district'.'Fengtai District'.'Shijingshan'.'Mentougou']
        },
        yAxis: {
            type: 'value',
            axisLabel: {
                color: '#93a8bf'
            },
            axisLine: {
                lineStyle: {
                    color: '#2f415b'
                }
            },
            axisTick: {
                lineStyle: {
                    color: '#2f415b'
                }
            },
            splitLine: {
                show: false
            }
        },
        series: [
            {
                type: 'bar',
                barWidth: 20,
                data: this.series || [100, 200, 100, 150, 300]
            }
        ]
      }
    }
  },
  mountedThis.initchart () {// Create must be in mounted, and not in created. }, methods: {initChart() {
      this.chart = echarts.init(this.$refs.boundChartContainer);
      this.chart.setOption(this.chartOptions);
    },
    renderChart() {
      if(this.everyAnimation) this.chart.clear(); // Whether to enable animation this.chartoption (this.chartoptions); // The core of the chart setup is, in fact, stillsetOption}}, watch: {// Can listen for any property changes, update the chart when the listening property changesxAxis() {
      this.renderChart();
    },
    series() { this.renderChart(); }}}; </script> <style lang="scss" scoped>
.polyline-chart-container {
  height: 100%;
}
#boundChartContainer {width: 100%; height: 100%; } </style> : component reference <template> <SimpleBar :title="simpleBar.title"
      :xAxis="simpleBar.xAxis"
      :series="simpleBar.series"
    ></SimpleBar>
</template>

<script>
import SimpleBar from "./SimpleBar";
export default {
  name: "demoEchart",
  components: {
    SimpleBar
  },
  data() {return {
      simpleBar: {
        title: 'Salary and treatment of Java Development in hefei',
        xAxis: ['Shu Shan District'.'Yao Zone'.'Baohe District'.'Luyang District'.'Open zone'.'High-tech Zone'.'New station area'],
        series: [800, 600, 500, 650, 700, 900, 450]
      }
    };
  },
  mounted() {setTimeout(()=>{ this.simpleBar = {... JSON.parse(JSON.stringify(this.simpleBar)), ... {title:'2020 Java Development salary in Hefei City '}}; }, 10000).setTimeout(()=>{
      this.simpleBar = {
        title: 'Salary of Java Development in Hefei in 2000',
        xAxis: ['Shu Shan District'.'Yao Zone'.'Baohe District'.'Luyang District'],
        series: [600, 500, 500, 450]
      }
    }, 15000)
  }  
};
Copy the code

Handle the Promise asynchronous interface response

The response of the front-end to the back-end interface should consider a variety of states, for example: using more AXIos, each call actually returns a Promise instance; For interface responses, we should not only deal with successful returns, but also handle exceptions and failed states. Especially for large screen projects, we should take many aspects into consideration to ensure that the diagram does not become too ugly due to data problems. [Specific requirements according to the project]

// Get simulated data
function getModalLists() {
    return {
      xAxis: ['Beijing'.'Shanghai'.'tianjin'.'wuhan'.'guangzhou'.'shenzhen'.'chongqing'].data: [400.200.350.850.500.700.3000]}};import { getChartLists } from '@/api';  // Suppose there is an asynchronous method to get chart data under SRC/API /index.js
export default {
    data(){
        return {
            chartData: {
                xAxis: [].data: []}}},methods: {
        async getChartLists(){
            let response = await getChartLists().catch((err) = >{
                // Mock data is called on error
                this.chartData = getModalLists();
            });
            
            if(response.status === 'success') {this.chartData = response.data;
            } else if(response.status === 'fail') {this.chartData = getModalLists(); }}}}Copy the code

Recommendations for extracting large files as components

In project development, we often come across a vue file with too many elements. For example, a background admin page usually contains the following sections: data list, list query form, add/edit form popover, and more… .

  • For simple business pages, all operations are placed on a page, all data are managed in a page, it will be very convenient; However, once the business logic complex page, that all the data in one page, I am afraid it is not so easy to do;

  • In the development of MVVM framework, how to manage data is a very important thing, if the amount of data in a file is too much, it is very laborious to manage, still if the naming, annotation and other specifications do not do well, that later this file will become a maintenance staff nightmare;

For this reason, it is recommended to separate the functions that can be separated into a component. The following uses a form in a list page as an example:

<div> <h2> <el-main style="padding: 0;">
      <div style="text-align: right; overflow: hidden;">
        <h3 style="float: left;"</h3> < el-button@click ="showAddForm"</el-button> </div> <el-table :data="tableData">
        <el-table-column type="index" label="Serial number" width="100"></el-table-column>
        <el-table-column prop="name" label="Title" width="300"></el-table-column>
        <el-table-column prop="author" label="The author"></el-table-column>
        <el-table-column prop="category" label="Classification"></el-table-column>
        <el-table-column prop="publishers" label="Publishing house"></el-table-column>
        <el-table-column label="Publication Date" width="140">
          <template slot-scope="scope">{{ scope.row.time | timeToCH}}</template>
        </el-table-column>
        <el-table-column label="Shelves or not" width="140">
          <template slot-scope="scope">{{ scope.row.status ? 'Already on shelves':'Not available'}}</template>
        </el-table-column>
        <el-table-column label="Operation" width="100">
          <template slot-scope="scope">
            <el-button @click="handleClick(scope.row)" type="text" size="small"</el-button> <el-buttontype="text" size="small" @click="handleEditClick(scope.row)"> Edit </el-button> </template> </el-table-column> </el-table> </el-main> <el-dialog :title="title" :visible.sync="dialogFormVisible" width="740px">
      <el-form :model="form" ref="form">
        <div class="el-form--inline">
          <el-form-item label="Title" :label-width="formLabelWidth">
            <el-input v-model="form.name" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="The author" :label-width="formLabelWidth">
            <el-input v-model="form.author" autocomplete="off"></el-input>
          </el-form-item>
        </div>
        <div class="el-form--inline">
          <el-form-item label="Publishing house" :label-width="formLabelWidth">
            <el-input v-model="form.publishers" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="Publication Date" :label-width="formLabelWidth">
            <el-date-picker
              v-model="form.time"
              type="date"
              placeholder="Select date"
              value-format="yyyy-MM-dd"
            ></el-date-picker>
          </el-form-item>
        </div>
        <div class="el-form--inline">
          <el-form-item label="Book Classification" :label-width="formLabelWidth">
            <el-select v-model="form.category" placeholder="Please select book category">
              <el-option
                :label="item.label"
                :value="item.value"
                v-for="item in categories"
                :key="item.value"></el-option> <! -- <el-option label="Wuxia" value="wuxia"></el-option>
              <el-option label="Literature" value="wenxue"></el-option>-->
            </el-select>
          </el-form-item>
          <el-form-item label="Shelves or not" :label-width="formLabelWidth">
            <el-switch v-model="form.status"></el-switch>
          </el-form-item>
        </div>
        <div style="text-align: left;" v-if="form.status">
          <el-form-item label="Shopping mall" prop="type" :label-width="formLabelWidth" style="width: 100%;">
            <el-checkbox-group v-model="form.shops">
              <el-checkbox
                name="type"
                v-for="shop in shopLists"
                :label="shop.value"
                :key="shop.value"
              >{{shop.label}}</el-checkbox>
            </el-checkbox-group>
          </el-form-item>
        </div>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false"</el-button> <el-buttontype="primary" @click="addBook"</el-button> </div> </el-dialog> </div> </template> <script> import axios from"axios";
import { mapGetters } from "vuex";
import { getBookLists } from "@/api/book.api";

let formDefault = {
  name: "",
  author: "",
  category: "",
  status: true,
  time: "",
  publishers: "",
  shops: []
};
export default {
  name: "demoIndex".data() {
    return {
      title: 'New Book',
      categories: this.$store.state.app.categories,
      formLabelWidth: "120px",
      dialogFormVisible: false, bookLists: [], form: JSON.parse(JSON.stringify(formDefault)) }; }, computed: { ... mapGetters(["shopLists"]),
    tableData() {// Please note the copy herelet books = JSON.parse(JSON.stringify(this.bookLists));
      returnBooks. map(item => {item.name = '"${item.name}"`; item.category = this.categories.find( cate => cate.value === item.category )["label"];
        returnitem; }); }},created() {
    this.getBookLists();
  },
  methods: {
    showAddForm() {
      
      this.form = JSON.parse(JSON.stringify(formDefault));
      this.dialogFormVisible = true;
    },
    handleClick(row) {
      this.$router.push({ path: `/demo/detail/${row.id}`}); }, handleEditClick(row) {let id = row.id;
      let book = this.bookLists.find(item => item.id === id);
      this.title = "Edit the book.";
      this.form = JSON.parse(JSON.stringify(book));
      this.dialogFormVisible = true;
    },
    async getBookLists() {
      let response = await getBookLists();

      if(response.status) { this.bookLists = response.data; }},addBook() {
      console.log(this.form);
      this.dialogFormVisible = false;
      if(this.form.id){
          this.bookLists = this.bookLists.map(item => {
              if(item.id === this.form.id) return this.form;
              return item;
          })

          this.title = "Add books";
      } else{ this.bookLists.push({ ... { id: this.bookLists.length }, ... this.form }); } } }, filters: { timeToCH(time) {return (
        time.substr(0, 4) +
        "Year" +
        time.substr(5, 2) +
        "Month" +
        time.substr(8, 2) +
        "Day"); }}}; </script> <style lang="scss"scoped> /deep/ .el-form--inline { text-align: left; .el-form-item__content { width: 220px; margin-left: 0 ! important; }} </style> // split code List Vue <template> <div> <h2> demo: Ajax cross-domain access (Dev)</h2> <el-main style="padding: 0;">
      <div style="text-align: right; overflow: hidden;">
        <h3 style="float: left;"</h3> < el-button@click ="dialogFormVisible = true"</el-button> </div> <el-table :data="tableData">
        <el-table-column type="index" label="Serial number" width="100"></el-table-column>
        <el-table-column prop="name" label="Title" width="300"></el-table-column>
        <el-table-column prop="author" label="The author"></el-table-column>
        <el-table-column prop="category" label="Classification"></el-table-column>
        <el-table-column prop="publishers" label="Publishing house"></el-table-column>
        <el-table-column label="Publication Date" width="140">
          <template slot-scope="scope">{{ scope.row.time | timeToCH}}</template>
        </el-table-column>
        <el-table-column label="Shelves or not" width="140">
          <template slot-scope="scope">{{ scope.row.status ? 'Already on shelves':'Not available'}}</template>
        </el-table-column>
        <el-table-column label="Operation" width="100">
          <template slot-scope="scope">
            <el-button @click="handleClick(scope.row)" type="text" size="small"</el-button> <el-buttontype="text" size="small" @click="handleEditClick(scope.row)"> Edit </el-button> </template> </el-table-column> </el-table> </el-main> <BookForm :visible="dialogFormVisible"
        :title="title"
        :categories="categories"
        :shops="shopLists"
        :form="form"
        @save-from="saveBook" 
        @close-dialog="dialogFormVisible = false"
    ></BookForm>
  </div>
</template>

<script>
import axios from "axios";
import { mapGetters } from "vuex";
import { getBookLists } from "@/api/book.api";
import BookForm from './BookForm';

let formDefault = {
  name: "",
  author: "",
  category: "",
  status: true,
  time: "",
  publishers: "",
  shops: []
};
export default {
  name: "demoIndex",
  components:{
      BookForm
  },
  data() {
    return {
        title: 'Add book',
      categories: this.$store.state.app.categories,
      dialogFormVisible: false, bookLists: [], form: JSON.parse(JSON.stringify(formDefault)) }; }, computed: { ... mapGetters(["shopLists"]),
    tableData() {// Please note the copy herelet books = JSON.parse(JSON.stringify(this.bookLists));
      returnBooks. map(item => {item.name = '"${item.name}"`; item.category = this.categories.find( cate => cate.value === item.category )["label"];
        returnitem; }); }},created() {
    this.getBookLists();
  },
  methods: {
    handleClick(row) {
      this.$router.push({ path: `/demo/detail/${row.id}`}); }, handleEditClick(row) {let id = row.id;
      let book = this.bookLists.find(item => item.id === id);

      this.title = 'Edit book';
      this.form = JSON.parse(JSON.stringify(book));
      this.dialogFormVisible = true;
    },
    async getBookLists() {
      let response = await getBookLists();

      if (response.status) {
        this.bookLists = response.data;
      }
    },
    saveBook(params) {
      console.log(params);
      this.dialogFormVisible = false;
      if(params.id){
          this.bookLists = this.bookLists.map(item => {
              if(item.id === params.id) return params;
              return item;
          })

          this.title = "Add books";
      } else{ this.bookLists.push({ ... { id: this.bookLists.length }, ... params }); } } }, filters: { timeToCH(time) {return (
        time.substr(0, 4) +
        "Year" +
        time.substr(5, 2) +
        "Month" +
        time.substr(8, 2) +
        "Day"); }}}; </script> Form vue <template> <el-dialog :title="title" :visible.sync="dialogVisible" :close-on-click-modal="false" width="700px" @close="closeDialog">
      <el-form :model="editForm" ref="form">
        <div class="el-form--inline">
          <el-form-item label="Title" :label-width="formLabelWidth">
            <el-input v-model="editForm.name" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="The author" :label-width="formLabelWidth">
            <el-input v-model="editForm.author" autocomplete="off"></el-input>
          </el-form-item>
        </div>
        <div class="el-form--inline">
          <el-form-item label="Publishing house" :label-width="formLabelWidth">
            <el-input v-model="editForm.publishers" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="Publication Date" :label-width="formLabelWidth">
            <el-date-picker
              v-model="editForm.time"
              type="date"
              placeholder="Select date"
              value-format="yyyy-MM-dd"
            ></el-date-picker>
          </el-form-item>
        </div>
        <div class="el-form--inline">
          <el-form-item label="Book Classification" :label-width="formLabelWidth">
            <el-select v-model="editForm.category" placeholder="Please select book category">
              <el-option
                :label="item.label"
                :value="item.value"
                v-for="item in categories"
                :key="item.value"
              ></el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="Shelves or not" :label-width="formLabelWidth">
            <el-switch v-model="editForm.status"></el-switch>
          </el-form-item>
        </div>
        <div style="text-align: left;" v-if="editForm.status">
          <el-form-item label="Shopping mall" prop="type" :label-width="formLabelWidth" style="width: 100%;">
            <el-checkbox-group v-model="editForm.shops">
              <el-checkbox
                name="type"
                v-for="shop in shops"
                :label="shop.value"
                :key="shop.value"
              >{{shop.label}}</el-checkbox>
            </el-checkbox-group>
          </el-form-item>
        </div>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="closeDialog"</el-button> <el-buttontype="primary" @click="saveForm"</el-button> </div> </el-dialog> </template> <script>export default {
    name: 'bookForm',
    props: {
      title: {
        type: String,
        default: 'title'
      },
      visible: {
        type: Boolean,
        default: false
      },
      form: {
        type: Object, default: () => {// It is recommended to list all the form items. 2. If you want to use it in other places, you can clearly know what the form is for. What fields are compared to {}return {
            name: "",
            author: "",
            category: "",
            status: true,
            time: "",
            publishers: "", shops: []}}}, // The type list is passed in categories: {type: Array,
        default: () => []   // categories: [{ value: "computer", label: "Computer science"}] It's a good habit to reserve a piece of data, no matter how long it takes to see the component.type: Array,
        default: () => []  // shops: [{value: 1, label: 'jingdong'}}},data() {
      return {
                formLabelWidth: "120px", // Note that this is mandatory; Because of the use of element-UI, the close button in the upper right corner is used to close the popover, and the real control of the popover is visible, so it is necessary to accept the properties in data (prop). this.visible ||falseEditForm: json.parse (json.stringify (this.form))}}, methods: {saveForm() {
        this.$emit('save-from', this.editForm)
      },
      closeDialog() {
        this.$emit('close-dialog');
      }
    },
    watch: {
      visible(val) {
        this.dialogVisible = val;
        if (val) {
          this.editForm = JSON.parse(JSON.stringify(this.form));
        }
      }
    }
  }
</script>


<style lang="scss"scoped> /deep/ .el-form--inline { text-align: left; .el-form-item__content { width: 220px; margin-left: 0 ! important; } } </style>Copy the code

Some of the problems I encountered

Vue-cli3 Unvalidates ESLint code

The vue Create app creates the project using Linter/Formatter, so there will be a code specification check when writing the code. How can I turn it off?

1. The.eslintrc.js file will be generated after the project is created

module.exports = {
  root: true,
  env: {
    node: true
  },
  'extends': [
    'plugin:vue/essential', / /'@vue/standard'Rules: {'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off'.'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'.'no-extra-semi': 'off'
  },
  parserOptions: {
    parser: 'babel-eslint'}}Copy the code

Quick generation of vue modules in VSCode

  1. Install the necessary plug-ins: search the plug-ins library for Vetur, the first one in the image below, click Install, and click Reload when the installation is complete
  2. New Snippet file -> Preferences -> User Snippet -> Click New Snippet – Type vue in the input box, then press Enter
  3. Paste code format
{
	"Print to console": {
		"prefix": "vue"."body": [
			"<template>"."  <div>$0</div>"."</template>".""."<script>"."export default {"." components: {},"." props: {},"." data() {"." return {"."};."},"." watch: {},"." computed: {},"." methods: {},"." created() {},"." mounted() {}"."};."</script>"."<style lang=\"scss\" scoped>"."</style>"]."description": "A vue file template"}}Copy the code

Switching between vUE components (siblings), properties saved in VUex, no way to update in real time

'Vue's watch has the same immediate: trueCopy the code

When using vue-router to do a retreat, passthis.$router.back()To do backward processing, found that there is no reaction when clicking, after investigation, found that the problem is here

<a href="#" class="app-header-router-link" @click="goBack"><i class="icon icon-back"></ I ></a> <a href="#" class="app-header-router-link" @click.prevent="goBack"><i class="icon icon-back"> < / I > < / a > or < span class ="app-header-router-link" @click.prevent="goBack"><i class="icon icon-back"></i></span>

Copy the code

The first Method is option, and the second Method is GET. The second Method is get.

1, the reason why

The browser initiates a PreFlight (also known as an Option request) to enable the server to return the allowed methods (such as GET and POST), the Origin (or domain) to be accessed across the domain, and the Credentials(authentication information) required. Cross-domain resource sharing standards have added a new set of HTTP header fields that allow servers to declare which source sites have access to which resources. In addition, the specification requires that HTTP request methods (especially HTTP requests other than GET, or POST requests paired with certain MIME types) that may have adverse effects on server data, The browser must first issue a preflight request using the OPTIONS method to know whether the server will allow the cross-domain request. The actual HTTP request is made only after the server confirms that it is allowed. In the return of the precheck request, the server side can also inform the client whether it needs to carry identity credentials (including Cookies and HTTP authentication related data). In other words, it will use options to test whether the interface can communicate properly. If not, it will not send a real request. If the test communication is normal, it will start normal requestCopy the code

2. Solutions

OPTIONS is used to check whether cross-domain requests are allowed. If you do not want OPTIONS requests, let the backend directly return OPTIONS, and the front end does not process them.Copy the code
  • Vue development, through the proxy request back-end interface, return 307;
For example, if the user information needs to be verified when requesting an interface, the user is redirected to the login interface if the session does not contain a userIdCopy the code

Variable height textarea

$configTable.on("keydown paste cut drop"."textarea".function(){
	delayedResize(this);
});

$configTable.on("change"."textarea".function(){
	delayedResize(this);
});

function delayedResize (text) {
	var scrollHeight = text.scrollHeight;
	setTimeout(function(){
		text.style.height = 'auto';
		text.style.height = scrollHeight + 'px'; }}, 0)Copy the code

Axios has a problem with carrying parameters

Send a get requestAxios.get ();'/user? Id=12345').then(function(response) { // handle success console.log(response); }) 2, pass axios.get(params)'/user', {
    params: {
      id: 12345
    }
}).then(function(response) { console.log(response); }) //"params"Is the URL argument sent with the request // must be a pure object or URLSearchParams object axios.request({URL:'/user',
    method: 'get',
    params: {
      id: 12345
    }
}).then(function (response) {
    console.log(response);
})



Send a POST request1, axios.post simplified request axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function(response) { console.log(response); }) //"data"Yes Data to be sent as the request body // Only applies to request methods "PUT", "POST", and "PATCH" // If "transformRequest" is not set, it must be one of the following types: // String, normal object, ArrayBuffer, ArrayBufferView, URLSearchParams // Browser only: FormData, File, Blob //- only nodes: stream, buffer axios({method:'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
}).then(function (response) {
    console.log(response);
  });
Copy the code

Note that the body, query, and params in the HTTP request are Json objects are usually submitted as parameters in the project. At this time, many partners may not know when to use data and params, so that the back-end end often fails to receive the front-end request parameters when connecting with the front end

The back-end reception uses the Node Express framework as an example

Method Parameter names The back end accepts parameters instructions
get params req.params Request parameters, URL followed by /, for example: user/:id
get query req.query Request parameters, URL followed by? , e.g. : User? id
post data req.body Data in the request body
// Please note the following codeexport function getBookDetail2(params = {}){
    return request({
        url: `${apiAddressSettings.getBookDetail}`,
        method: 'post'// params: params}); }; ! [pictured] (HTTP: / / https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/5/22/172381244a9ccd96~tplv-t2oaga2asx-image.ima ge)Copy the code

About paging queries

In the project, the list and query are almost an essential part of the management system. When obtaining the list, we usually involve querying according to the current page and querying the list according to some query conditions. Attention should be paid to the query conditions and paging information: 2. When clicking query by Condition, if there is paging, please set the current page number of paging to 1(otherwise there may be paging information but no data).

Sample reference code

About the front and back end data types

In the project of front and back end separation, the main business of the back end is to provide data interface (return data), and the front end is mainly responsible for (obtain data) page display. However, if the programming language of the front and back end is different, it often has different data types. Here we focus on: String Number NULL

For example, PHP returns “true” and “false”, which is converted to Boolean true when used in the front end for conditional judgment

NULL in PHP is the same as NULL, and JS can only recognize NULL

For example, the back end is used to null to indicate that the data is empty, and the front end likes to use “” to indicate that the data is empty, if the interface processing is not perfect, the front end accepts the data judgment, the back end receives the parameter is likely to appear unexpected error, or even can not find the cause of the problem for a long time;

Advice:

* represents the state of {status: true} rather than {status: "true"} * represents {of the data.size: 100} rather than {size: "100"} * denotes data null {startTime: ' '} rather than {startTime: null }
Copy the code

[Most critically, communicate with the front-end and back-end before development to agree on data formats and types]

The start time must not be longer than the end time

Queries by time range are all too common in projects, and the company uses VUE + ElementUI front-end technology. Here’s a look at how ElementUI handles time ranges:

  • Use range types directly
  • Custom processing
// Customize the processing method
1, the time range of the whole project, form and field names (agreed) should be kept consistent, for example:'Query form searchForm','add editForm or editForm'StartTime and endTime in the form. StartTime and timeStart are common in multiplayer development.2Add picker-options to the time input box specific to the current time and date picker; It has a property disabledDate(Function) "Set disabled status, parameter to current date, requiring return Boolean"Such as: picker - options ="pickerOptionsStart"And: picker - options ="pickerOptionsEnd"

  <el-form-item label="Reporting time" prop="startTime"Box-sizing: border-box! Important; word-wrap: break-word! Important; "> < span style=" margin: 0.0px 0.0px 0.0px 0.0px 0.0px 0.0px; value-format="yyyy-MM-dd HH:mm:ss" :picker-options="pickerOptionsStart" /> <span style="margin: 0 5px;" > to </span> </el-form-item> <el-form-item prop="endTime"> <el-date-picker size="small" V-model =" form.endtime" Value -format=" YYYY-MM-DD HH: MM: SS ":picker-options="pickerOptionsEnd" /> </el-form-item> select pickerOptionsStart, pickerOptionsEnd, pickerOptionsEnd, pickerOptionSsend, pickerOptionSsend, pickerOptionSSend, pickerOptionSSend, pickerOptionSSend, pickerOptionSSend, pickerOptionSSend Write it in computed. Computed: {pickerOptionsStart() {let _this = this; return { disabledDate(time) { let selectItemTimeStamp = Date.parse(time); let nowTimeStamp = Date.now(); if (_this.form.endTime) { let endTimeStamp = Date.parse(_this.form.endTime); return selectItemTimeStamp > endTimeStamp; } else { return selectItemTimeStamp > nowTimeStamp; } } } }, pickerOptionsEnd() { let _this = this; return { disabledDate(time) { let checkItemTimeStamp = Date.parse(time); let nowTimeStamp = Date.now(); if (_this.form.startTime) { let startTimeObj = new Date(_this.form.startTime); // If the format is year, month, day, hour, minute and second, the end time and start time must be allowed to be the same day. If the date is the same day, let timeDetailStamp = (startTimeobj.gethours () * 3600 + startTimeObj.getMinutes() * 60 + startTimeObj.getSeconds()) * 1000 + 1000; let startTimeStatmp = Date.parse(_this.form.startTime); return checkItemTimeStamp + timeDetailStamp < startTimeStatmp; } else { return checkItemTimeStamp > nowTimeStamp; }}}}} 4, the time when the choice according to the start time and end time for judgment [: because it allows you to select the same day, so need to be processed on the same day time] [: reference elelment - UI judging time range, if you choose to start time is greater than end time, will be set to the same as the start time and end time and vice versa) Watch: {'form.startTime': function(val) { let selectItemTimeStamp = Date.parse(val) let startTimeStamp = Date.parse(this.form.startTime); let nowTimeStamp = Date.now(); // Compare with the current time (if start time > current time is selected, If (selectItemTimeStamp > nowTimeStamp) {this.form.startTime = this.$moment. Format (' YYYY-MM-DD HH: MM :ss'); } if (this.form.endTime === "") {this.form.endTime = val; } else {// Determine the size let endTimeStamp = date.parse (this.form.endtime); if (startTimeStamp > endTimeStamp) { this.form.endTime = val; If (this.form.startTime === "") {this.form.startTime = val; } else {// Determine the size let startTimeStamp = date.parse (this.form.starttime); let endTimeStamp = Date.parse(this.form.endTime); if (endTimeStamp < startTimeStamp) { this.form.startTime = val; }}}} // Complete the above code to limit the size range of the start and end times to..... But... Isn't it too tiring to write that on every page? 5. Mixin helps by creating public method files with duplicate code computed and watch, for example: datetimeCompare.js function formatTimes(time){ let yy = time.getFullYear(); let mm = time.getMonth() + 1; let dd = time.getDate(); let week = time.getDay(); let hh = time.getHours(); let mf = time.getMinutes() < 10 ? "0" + time.getMinutes() : time.getMinutes(); let ss = time.getSeconds() < 10 ? "0" + time.getSeconds() : time.getSeconds(); return yy+'-'+mm+'-'+dd + ' '+hh+':'+mf+':'+ss; } export default { computed: { pickerOptionsStart() { let _this = this; return { disabledDate(time) { let selectItemTimeStamp = Date.parse(time); let nowTimeStamp = Date.now(); if (_this.form.endTime) { let endTimeStamp = Date.parse(_this.form.endTime); return selectItemTimeStamp > endTimeStamp; } else { return selectItemTimeStamp > nowTimeStamp; } } } }, pickerOptionsEnd() { let _this = this; return { disabledDate(time) { let checkItemTimeStamp = Date.parse(time); let nowTimeStamp = Date.now(); if (_this.form.startTime) { let startTimeObj = new Date(_this.form.startTime); let timeDetailStamp = (startTimeObj.getHours() * 3600 + startTimeObj.getMinutes() * 60 + startTimeObj.getSeconds()) * 1000 + 1000; let startTimeStatmp = Date.parse(_this.form.startTime); return checkItemTimeStamp + timeDetailStamp < startTimeStatmp; } else { return checkItemTimeStamp > nowTimeStamp; } } } } }, watch: { 'form.startTime': function(val) { let selectItemTimeStamp = Date.parse(val) let startTimeStamp = Date.parse(this.form.startTime); let nowTimeStamp = Date.now(); // Compare with the current time (if start time > current time is selected, If (selectItemTimeStamp > nowTimeStamp) {this.form.startTime = formatTimes(new Date()); } if (this.form.endTime === "") {this.form.endTime = val; } else {// Determine the size let endTimeStamp = date.parse (this.form.endtime); if (startTimeStamp > endTimeStamp) { this.form.endTime = val; If (this.form.startTime === "") {this.form.startTime = val; } else {// Determine the size let startTimeStamp = date.parse (this.form.starttime); let endTimeStamp = Date.parse(this.form.endTime); if (endTimeStamp < startTimeStamp) { this.form.startTime = val; }}}}} 6. Import the file (or object) and use it, for example: import datetimeCompare from '@/utils/datetimeCompare' export default { mixins:[datetimeCompare], data() { ... }, methods: { .... }Copy the code

7. At this point, the time limit is complete;

Start time :picker-options="pickerOptionsStart"End time :picker-options="pickerOptionsEnd"

Copy the code

remarks

The road is long, I see no end, I will search high and low