In our last article we wrote about building a VUE project from scratch using Webpack [juejin.cn/post/703209… Flex layout, VUE-Router, VUex, parameter transfer between VUE components, Watch, computed, Webpack configuration path alias, axiOS encapsulation Each step is divided according to commit, which is very suitable for students who have not built by themselves. Finally, we will complete a CRM system framework.

Install less – loader

Before layout, let’s make the project support less

npm i less less-loader -D
Copy the code

Add rules to webpack.config.js

    {
      test: /\.less$/,
      use: [
          'style-loader',
          'css-loader',
          'less-loader'
      ]
    },
Copy the code

Flex implements page layout

Use Flex for left-right, up-down layouts

  • Achieve a fixed column, a column adaptive layout of two columns
<div class="right">
  <div class="top"></div>
  <div class="bottom"></div>
</div>
Copy the code
.right {
  flex: 1;
  display: flex;
  flex-direction: column;
  .top {
    height: 48px;
  }
  .bottom {
    flex: 1; }}Copy the code

Add left menu

Our project is built on the basis of Element-UI and is now implemented directly using el-Menu:

<el-menu
  default-active="2"
  class="el-menu-vertical-demo"
  :router="true"
  @open="handleOpen"
  @close="handleClose"
  @select="handleSelect"
>
  <el-menu-item index="/home">
    <i class="el-icon-menu"></i>
    <span slot="title">Home page</span>
  </el-menu-item>
  <el-menu-item index="/report">
    <i class="el-icon-s-data"></i>
    <span slot="title">The report</span>
  </el-menu-item>
</el-menu>
Copy the code

If the router is set to true and the vue-Router mode is enabled, index is used as the path to redirect routes when the navigation is activated. Install the vue – the router:

npm i vue-router
Copy the code

The introduction of vue – the router

import VueRouter from "vue-router";
let routes = [
  {
    path: "/home".name: "",},];let Routes = new VueRouter({
  mode: "history".routes: routes,
});
Vue.use(Routes); // The use method is used to inject global plug-ins
new Vue({
  router: routes,
});
Copy the code

Router-link functions:

<router-link to="/home">Home page</router-link>
Copy the code

Click Link to jump to the specified path. Router-view functions as follows:

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

Render the component content corresponding to path at that location.

Implement menu expansion and collapse (parent and child component parameter transfer)

By transferring parameters between parent and child components, achieve menu folding:

<! --> <template> <Header :collapse="collapse" @toggalecollapsed ="changeCollapsed"></Header> </template> <script> import Header from './header.vue'; export default { components: { Header, } } </script>Copy the code

Child components:

<! -- Subcomponent -->
<template>
  <div>
    <div class="btnBox" style="margin-right: 10px">
      <el-button type="primary" size="mini" @click="toggleCollapsed">
        <el-icon :class="collapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'" />
      </el-button>
    </div>
  </div>
</template>
<script>
  export default {
    props: ["collapse"].methods: {
      toggleCollapsed() {
        this.collapse = !this.collapse;
        this.$emit("toggaleCollapsed".this.collapse); ,}}};</script>
Copy the code

Add menu tabs (Watch)

Regular CRM systems will include menu tabs to make switching easier and viewing more intuitive. The interaction logic for the menu TAB is:

  • Click the left menu to add the corresponding TAB and activate it

  • Click the TAB to open the corresponding menu on the left:

    In the menu component: activeMenu uses the menu passed by the parent component.

<template>
    <el-menu :default-active="defaultActive"></el-menu>
</template>
<script>
    export default {
        props: ['defaultActive'],
    }
</script>
Copy the code

In the APP component: Responsible for managing activeMenu

<template>
    <Menu style="flex:1" :isCollapse="collapse" :defaultActive="defaultActive"></Menu>
    <menu-tabs @changeActiveMenu="changeActiveMenu"></menu-tabs>
</template>
<script>
import MenuTabs from "./menuTabs.vue"
    export default {
        props: ['defaultActive'],
        components: {
            MenuTabs,
        },
        data(){
            return {
                defaultActive: "/home",
            }
        },
        methods: {
            changeActiveMenu(path){
                this.defaultActive = path;
            }
        }
    }
</script>
Copy the code

In the menu TAB component: Use the Watch method to listen for path changes, modify the tabList and activate the corresponding TAB when it changes. When TAB is clicked, the App component is notified to modify activeMenu.

<template> <div> <el-tabs v-model="activeName" type="card" @tab-click="handleClick"> <el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.label" :name="tab.name" > </el-tab-pane> </el-tabs> </div> </template> <script> export Default {data() {return {activeName: "/home", tabs: [{label: "home", name: "/home", path: "/home",},],}; default {data() {return {activeName: "/home", tabs: [{label:" home", name: "/home", path: "/home",}; }, watch: { $route(to, from) { this.handleTabList(to); }, }, methods: { handleClick(tab, event) { console.log(tab, event); this.$router.push({ path: tab.name }); this.$emit("changeActiveMenu", tab.name); }, handleTabList(to) { let flag = this.tabs.find((item) => item.path === to.path); if (! flag) { this.tabs.push({ path: to.path, label: to.name, name: to.path, }); } this.activeName = to.path; ,}}}; </script>Copy the code

This functionality looks complicated and is easily implemented when actually broken down.

Adding a Path alias

To make it easier to find relative paths when importing webpack.config.js, add an alias to the path and modify webpack.config.js:

export default {
    resolve: {
        alias: {
            "@": path.resolve("src"),}}}Copy the code

Now we can use @ instead of SRC directory.

Axios encapsulation

import axios from 'axios';
import { Message } from 'element-ui';

/ / set the baseURL
axios.defaults.baseURL = '/';

// Set timeout
axios.defaults.timeout = 30000;

// The default request header for post requests
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';

// Request interceptor
axios.interceptors.request.use(
    config= > {
        return config;
    },
    error= > {
        Message.error({
            message: error.message || 'Request error'.duration: 1000});return Promise.error(error);
    })

// Response interceptor
axios.interceptors.response.use(
    response= > {
        // If the returned status code is 200, the interface request succeeds
        // Otherwise, an error is thrown
        if (response && response.status === 200) {
            return Promise.resolve(response);
        } else {
            return Promise.reject(response); }},error= > {
        Message({
            type: 'error'.message: error || 'Response error'.duration: 5000});if (error.response.status) {
            switch (error.response.status) {
                case 404:
                    Message.error({
                        message: 'Request not found'.duration: 1500});break;
                default:
                    Message.error({
                        message: error.response.data.message,
                        duration: 1500}); }return Promise.reject(error.response); }});/ / get methods
export function get(url, params) {
    return new Promise((resolve, reject) = > {
        axios.get(url, {
            params: params
        }).then(res= > {
            resolve(res.data);
        }).catch(err= > {
            reject(err.data)
        })
    });
}

/ / post method
export function post(url, params) {
    return new Promise((resolve, reject) = > {
        axios.post(url, JSON.stringify(params), { headers: { 'Content-Type': 'application/json; charset=UTF-8' } })
            .then(res= > {
                resolve(res.data);
            })
            .catch(err= > {
                reject(err.data)
            })
    });
}

/ / put method
export function put(url, params) {
    return new Promise((resolve, reject) = > {
        axios.put(url, JSON.stringify(params), { headers: { 'Content-Type': 'application/json; charset=UTF-8' } })
            .then(res= > {
                resolve(res.data);
            })
            .catch(err= > {
                reject(err.data)
            })
    });
}

/ / delete method
export function del(url, params) {
    return new Promise((resolve, reject) = > {
        axios.delete(`${url}? id=${params.id}`,)
            .then(res= > {
                resolve(res.data);
            })
            .catch(err= > {
                reject(err.data)
            })
    });
}
Copy the code

This encapsulates the four methods of adding, deleting, modifying, and checking HTTP requests. Now put all THE API requests in one file:

import { get, post, put, del } from './axios';

// Add a member
export  const addMember = p= > post('/api/add', p);

// Delete member
export  const deleteMember = p= > del('/api/delete', p); 

// Modify the member
export  const updateMemberInfo = p= > put('/api/edit', p);

// Query the member list
export const getMemberList = p= > get('/mock/api/query', p);

Copy the code

Use MockJS to simulate the interface to request data

Since we don’t have a backend now, we have to mock ourselves to get the data. Mockjs is used to implement this:

  • Install mockjs
npm i mockjs -D
Copy the code
  • mock api
/ / introduce mockjs
const Mock = require('mockjs')
// Get the mock.Random object
const Random = Mock.Random
// Mock data, including title, content, createdTime
const getData = function () {
  let newsList = []
  for (let i = 0; i < 10; i++) {
    let newObject = {
      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;
    }
    newsList.push(newObject)
  }
  return newsList
}
// Request this URL to return newsList
Mock.mock('/mock/api/query', getData) // More on how to use this API
Copy the code

Modify the report page and use the table display to test it:

<template> <div> <el-table border :data="tableData" :header-cell-style="{ background: '#f2f2f2 ! important', fontSize: '12px', }" >< el-table-column label=" title"></el-table-column> <el-table-column label=" content" prop="content" </el-table-column> <el-table-column label=" create time "prop="createdTime"></el-table-column> </el-table> </div> </template> <script> import { getMemberList } from "@/request/api.js"; export default { data() { return { tableData: [], }; }, created() { getMemberList() .then((res) => { console.log(res); this.tableData = res; }) .catch((e) => {}); }}; </script>Copy the code

This is just to show query mocks, but you can also add and remove mocks.

conclusion

At this point, we have built a vue2 development environment out of the box. The complete code has been synchronized to Github, and you can clone it if you are interested. In the next article, we will look at component encapsulation.

tips

This series is relatively basic, but it is still very fruitful to do, and strive to achieve the degree of reuse. Haha, a little more progress!