The third stop of front-end learning - Vue2 advanced chapter

Table of contents

1. Element-UI

1.1 Installation

1.2 Common components

2. Vue-Router Routing

3. Vuex


Warm reminder, pure code. I haven't passed the review of the basic chapter, but it's in the recycle bin~

1. Element-UI

1.1 Installation

npm install element-ui -S

Introducing a set price

import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(Element)

1.2 Common components

form component

<template>
    <div>
        <el-table :data="students">
            <el-table-column label="编号" prop="id"></el-table-column>
            <el-table-column label="姓名" prop="name"></el-table-column>
            <el-table-column label="性别" prop="sex"></el-table-column>
            <el-table-column label="年龄" prop="age"></el-table-column>
        </el-table>
    </div>
</template>
<script>
import axios from '../util/myaxios'
const options = {
    async mounted() {
        const resp = await axios.get('/api/students');
        this.students = resp.data.data
    },
    data() {
        return {
            students: []
        }
    }
}
export default options;
</script>

Paging component el-pagination

total: the total number of data

current-page : current page number

layout: layout, you can control what the plugin displays,

pagesize: how many pages are displayed,

page-size: switch each page display,

current-change: Synchronize page data in javaScript,

size-change: Synchronize the size data in javaScript,

<template>
    <div>
        <el-table :data="students">
            <el-table-column label="编号" prop="id"></el-table-column>
            <el-table-column label="姓名" prop="name"></el-table-column>
            <el-table-column label="性别" prop="sex"></el-table-column>
            <el-table-column label="年龄" prop="age"></el-table-column>
        </el-table>
        <!-- el分页 -->
        <el-pagination 
            :total="total"
            :page-size="pageInfo.size"
            :current-page="pageInfo.page"
            layout="prev,pager,sizes,jumper,next,->,total "
            :page-sizes="[5,10,15,20]"
            @current-change="currentChange"
            @size-change= "sizeChange"
        >
        </el-pagination>
    </div> 
</template>

<script>
import axios from '../util/myaxios';
const options = {
    mounted(){
        this.query();
    },
    data(){
        return {
            students:[],
            pageInfo:{
                name: '',
                age: '',
                sex: '',
                page :1,
                size: 5
            },
            total:0
        }
    },
    methods:{
        async query(){
            const resp =  await axios.get('stu/pageStudent',{
                params:this.pageInfo
            });
            this.students = resp.data.list
            this.total = resp.data.total
        },
        currentChange(page){
            this.pageInfo.page = page;
            this.query();
        },
        sizeChange(size){
            this.pageInfo.size = size;
            this.query();
        },
        search(){
            this.query();
        }
    }
}
export default options;
</script>

<style scoped>
.el-input--mini,
.el-select--mini {
    width: 193px;
    margin: 10px 10px 0 0;
}
</style>

 Paged search (fuzzy query added)

<template>
    <div>
        <el-input placeholder="请输入姓名" size="mini" v-model="queryDto.name"></el-input>
        <el-select placeholder="请选择性别" size="mini" v-model="queryDto.sex" clearable>
            <el-option value="男"></el-option>
            <el-option value="女"></el-option>
        </el-select>
        <el-select placeholder="请选择年龄" size="mini" v-model="queryDto.age" clearable>
            <el-option value="0,20" label="0到20岁"></el-option>
            <el-option value="21,30" label="21到30岁"></el-option>
            <el-option value="31,40" label="31到40岁"></el-option>
            <el-option value="41,120" label="41到120岁"></el-option>
        </el-select>
        <el-button type="primary" size="mini" @click="search()">搜索</el-button>
        <el-divider></el-divider>
        <el-table v-bind:data="students">
            <el-table-column label="编号" prop="id"></el-table-column>
            <el-table-column label="姓名" prop="name"></el-table-column>
            <el-table-column label="性别" prop="sex"></el-table-column>
            <el-table-column label="年龄" prop="age"></el-table-column>
        </el-table>
        <el-pagination :total="total" :page-size="queryDto.size" :current-page="queryDto.page"
            layout="prev,pager,next,sizes,->,total" :page-sizes="[5, 10, 15, 20]" @current-change="currentChange"
            @size-change="sizeChange"></el-pagination>
    </div>
</template>
<script>
import axios from '../util/myaxios'
const options = {
    mounted() {
        this.query();
    },
    methods: {
        currentChange(page) {
            this.queryDto.page = page;
            this.query();
        },
        sizeChange(size) {
            this.queryDto.size = size;
            this.query();
        },
        async query() {
            const resp = await axios.get('/api/students/q', {
                params: this.queryDto
            });
            this.students = resp.data.data.list;
            this.total = resp.data.data.total;
        },
        search() {
            this.query();
        }
    },
    data() {
        return {
            students: [],
            total: 0,
            queryDto: {
                name: '',
                sex: '',
                age: '',  
                page: 1,
                size: 5
            }
        }
    }
}
export default options;
</script>
  • Both sex and age are used to ''indicate that the user has no choice

  • The value of age 0,20will be converted by spring tonew int[]{0, 20}

  • The value of age ''will be converted by spring tonew int[0]

cascade selection

The data structure of the options in the cascading selector is

[
    {value:100, label:'主页',children:[
        {value:101, label:'菜单1', children:[
            {value:105, label:'子项1'},
            {value:106, label:'子项2'}
        ]},
        {value:102, label:'菜单2', children:[
            {value:107, label:'子项3'},
            {value:108, label:'子项4'},
            {value:109, label:'子项5'}
        ]},
        {value:103, label:'菜单3', children:[
            {value:110, label:'子项6'},
            {value:111, label:'子项7'}
        ]},
        {value:104, label:'菜单4'}
    ]}
]

The following example is the one-dimensional array returned by the backend [tree]

<template>
    <el-cascader :options="ops"></el-cascader>
</template>
<script>
import axios from '../util/myaxios'
const options = {
    async mounted() {
        const resp = await axios.get('/api/menu')
        console.log(resp.data.data)
        const array = resp.data.data;

        const map = new Map(); 

        // 1. 将所有数据存入 map 集合(为了接下来查找效率)
        for(const {id,name,pid} of array) {
            map.set(id, {value:id, label:name, pid:pid})
        }
        // 2. 建立父子关系
        // 3. 找到顶层对象
        const top = [];
        for(const obj of map.values()) {
            const parent = map.get(obj.pid);
            if(parent !== undefined) {
                parent.children ??= [];
                parent.children.push(obj);
            } else {
                top.push(obj)
            }
        }
        this.ops = top;
    },
    data(){
        return {
            ops: []
        }
    }
};
export default options;
</script>

2. Vue-Router Routing

Vue is a single-page application. The so-called routing is to replace the content display of this page with different view components according to different browser paths.

 Create a new route js file, such as src/router/example14.js, the content is as follows

import Vue from 'vue'
import VueRouter from 'vue-router'
import ContainerView from '@/views/example14/ContainerView.vue'
import LoginView from '@/views/example14/LoginView.vue'
import NotFoundView from '@/views/example14/NotFoundView.vue'

Vue.use(VueRouter)

const routes = [
  {
    path:'/',
    component: ContainerView
  },
  {
    path:'/login',
    component: LoginView
  },
  {
    path:'/404',
    component: NotFoundView
  }
]

const router = new VueRouter({
  routes
})

export default router
  • The most important thing is to establish the mapping relationship between [path] and [view component]

  • In this example, three paths and corresponding view components are mapped

Adopt our routing js in main.js  

import Vue from 'vue'
import e14 from './views/Example14View.vue'
import router from './router/example14'  // 修改这里
import store from './store'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false

Vue.use(Element)
new Vue({
  router,
  store,
  render: h => h(e14)
}).$mount('#app')

The root component must add <router-view></router-view> to display subcomponents

 The root component is Example14View.vue with the content:

<template>
    <div class="all">
        <router-view></router-view>
    </div>
</template>
  • Among them <router-view>, acts as a placeholder. After changing the path, the view component corresponding to this path will occupy <router-view>the position of and replace its previous content.

dynamic import

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path:'/',
    component: () => import('@/views/example14/ContainerView.vue')
  },
  {
    path:'/login',
    component: () => import('@/views/example14/LoginView.vue')
  },
  {
    path:'/404',
    component: () => import('@/views/example14/NotFoundView.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router
  • Static import is to package the js code of all components together. If there are many components, the packaged js file will be very large, which will affect the page loading speed

  • Dynamic import is to put the js code of the component into a separate file and load it when it is used

nested routes

 To switch content within the component, you need to use nested routes (sub-routes). The following example defines 3 sub-routes in the [ContainerView component]

const routes = [
  {
    path:'/',
    component: () => import('@/views/example14/ContainerView.vue'),
    redirect: '/c/p1',
    children: [
      { 
        path:'c/p1',
        component: () => import('@/views/example14/container/P1View.vue')
      },
      { 
        path:'c/p2',
        component: () => import('@/views/example14/container/P2View.vue')
      },
      { 
        path:'c/p3',
        component: () => import('@/views/example14/container/P3View.vue')
      }
    ]
  },
  {
    path:'/login',
    component: () => import('@/views/example14/LoginView.vue')
  },
  {
    path:'/404',
    component: () => import('@/views/example14/NotFoundView.vue')
  },
  {
    path:'*',
    redirect: '/404'
  }
]

Remember to add <router-view></router-view> to the ContainerView component

<template>
    <div class="container">
        <router-view></router-view>
    </div>
</template>
  • redirect can be used to redirect (jump) to a new address

  • The value of path is *, which means that when no other path can be matched, it will be matched

Element UI layout

 Usually the home page needs to be laid out. The code below is the [Top-[Left-Right]] layout provided by ElementUI.

<template>
    <div class="container">
        <el-container>
            <el-header></el-header>
            <el-container>
                <el-aside width="200px"></el-aside>
                <el-main>
                    <router-view></router-view>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>

Routing Jump

There are two methods:

Tabbed

<el-aside width="200px">
    <router-link to="/c1/p1">P1</router-link>
    <router-link to="/c1/p2">P2</router-link>
    <router-link to="/c1/p3">P3</router-link>
</el-aside>

Programmatically:

<el-header>
    <el-button type="primary" icon="el-icon-edit" 
               circle size="mini" @click="jump('/c1/p1')"></el-button>
    <el-button type="success" icon="el-icon-check" 
               circle size="mini" @click="jump('/c1/p2')"></el-button>
    <el-button type="warning" icon="el-icon-star-off" 
               circle size="mini" @click="jump('/c1/p3')"></el-button>
</el-header>

 Jump method

<script>
const options = {
    methods : {
        jump(url) {
            this.$router.push(url);
        }
    }
}
export default options;
</script>
  • Where this.$router is to get the routing object

  • The push method jumps according to the url

Dynamic routing and menus (important)

 Store menu, routing information (for home page only) in database

insert into menu(id, name, pid, path, component, icon) values
    (101, '菜单1', 0,   '/m1',    null,         'el-icon-platform-eleme'),
    (102, '菜单2', 0,   '/m2',    null,         'el-icon-delete-solid'),
    (103, '菜单3', 0,   '/m3',    null,         'el-icon-s-tools'),
    (104, '菜单4', 0,   '/m4',    'M4View.vue', 'el-icon-user-solid'),
    (105, '子项1', 101, '/m1/c1', 'C1View.vue', 'el-icon-s-goods'),
    (106, '子项2', 101, '/m1/c2', 'C2View.vue', 'el-icon-menu'),
    (107, '子项3', 102, '/m2/c3', 'C3View.vue', 'el-icon-s-marketing'),
    (108, '子项4', 102, '/m2/c4', 'C4View.vue', 'el-icon-s-platform'),
    (109, '子项5', 102, '/m2/c5', 'C5View.vue', 'el-icon-picture'),
    (110, '子项6', 103, '/m3/c6', 'C6View.vue', 'el-icon-upload'),
    (111, '子项7', 103, '/m3/c7', 'C7View.vue', 'el-icon-s-promotion');

The menu and routing information queried by different users are different  

For example: access /api/menu/adminreturns all data

[
    {
        "id": 102,
        "name": "菜单2",
        "icon": "el-icon-delete-solid",
        "path": "/m2",
        "pid": 0,
        "component": null
    },
    {
        "id": 107,
        "name": "子项3",
        "icon": "el-icon-s-marketing",
        "path": "/m2/c3",
        "pid": 102,
        "component": "C3View.vue"
    },
    {
        "id": 108,
        "name": "子项4",
        "icon": "el-icon-s-platform",
        "path": "/m2/c4",
        "pid": 102,
        "component": "C4View.vue"
    },
    {
        "id": 109,
        "name": "子项5",
        "icon": "el-icon-picture",
        "path": "/m2/c5",
        "pid": 102,
        "component": "C5View.vue"
    }
]

visit /api/menu/wangback  

[
    {
        "id": 103,
        "name": "菜单3",
        "icon": "el-icon-s-tools",
        "path": "/m3",
        "pid": 0,
        "component": null
    },
    {
        "id": 110,
        "name": "子项6",
        "icon": "el-icon-upload",
        "path": "/m3/c6",
        "pid": 103,
        "component": "C6View.vue"
    },
    {
        "id": 111,
        "name": "子项7",
        "icon": "el-icon-s-promotion",
        "path": "/m3/c7",
        "pid": 103,
        "component": "C7View.vue"
    }
]

The front end dynamically adds routes and displays menus according to their identities.

dynamic routing

1. The backend gets the data as an array object. (slightly)

export function addServerRoutes(array) {
  for (const { id, path, component } of array) {
    if (component !== null) {
      // 动态添加路由
      // 参数1:父路由名称
      // 参数2:路由信息对象
      router.addRoute('c', {
        path: path,
        name: id,
        component: () => import(`@/views/example15/container/${component}`)
      });
    }
  }
}
  • There are only a few fixed routes on the js side, such as the home page, 404 and login

  • When the above method is executed, the routing information returned by the server is added to the parent route named c

  • Pay attention to the component path here, the previous @/views must be spliced ​​on the js side, otherwise the import function will fail

reset route

The route should be reset when the user logs out

export function resetRouter() {
  router.matcher = new VueRouter({ routes }).matcher
}

 After the page is refreshed, the dynamically added route will become invalid. The solution is to store the route data in sessionStorage

<script>
import axios from '@/util/myaxios'
import {resetRouter, addServerRoutes} from '@/router/example15'
const options = {
    data() {
        return {
            username: 'admin'
        }
    },
    methods: {
        async login() {       
            resetRouter(); // 重置路由     
            const resp = await axios.get(`/api/menu/${this.username}`)
            const array = resp.data.data;
            // localStorage     即使浏览器关闭,存储的数据仍在
            // sessionStorage   以标签页为单位,关闭标签页时,数据被清除
            sessionStorage.setItem('serverRoutes', JSON.stringify(array))
            addServerRoutes(array); // 动态添加路由
            this.$router.push('/');
        }
    }
}
export default options;
</script>

When the page is refreshed and the routing object is recreated, the routing data is restored from sessionStorage  

const router = new VueRouter({
  routes
})

// 从 sessionStorage 中恢复路由数据
const serverRoutes = sessionStorage.getItem('serverRoutes');
if(serverRoutes) {
  const array = JSON.parse(serverRoutes);
  addServerRoutes(array) // 动态添加路由
}

dynamic menu

Now convert the obtained array data into a tree type, which is the above-mentioned cascading code

<script>
const options = {
    mounted() {
        const serverRoutes = sessionStorage.getItem('serverRoutes');
        const array = JSON.parse(serverRoutes);
        const map = new Map();
        for(const obj of array) {
            map.set(obj.id, obj);
        }
        const top = [];
        for(const obj of array) {
            const parent = map.get(obj.pid);
            if(parent) {
                parent.children ??= [];
                parent.children.push(obj);
            } else {
                top.push(obj);
            }
        }
        this.top = top;
    },
    data() {
        return {
            top: []
        }
    }
}
export default options;
</script>

In the menu part there are v-for, v-if, v-esle

<el-menu router background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" :unique-opened="true">
    <template v-for="m1 of top">
<el-submenu v-if="m1.children" :key="m1.id" :index="m1.path">
    <span slot="title">
        <i :class="m1.icon"></i> {
   
   {m1.name}}
        </span>
    <el-menu-item v-for="m2 of m1.children" :key="m2.id" :index="m2.path">
        <span slot="title">
            <i :class="m2.icon"></i> {
   
   {m2.name}}
        </span>
        </el-menu-item>
        </el-submenu>
<el-menu-item v-else :key="m1.id" :index="m1.path">
    <span slot="title">
        <i :class="m1.icon"></i> {
   
   {m1.name}}
        </span>
        </el-menu-item>
    </template>
</el-menu>
  • Did not consider the recursive menu problem, thinking that the menu has only two levels

3. Vuex

getting Started

Vuex can share data between multiple components, and the shared data is [responsive], that is, data changes can be rendered to the template in time

  • In contrast, localStorage and sessionStorage can also share data, but the disadvantage is that the data is not [responsive]

First, you need to define state and mutations. One is used to read shared data, and the other is used to modify shared data.

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

/*
  读取数据,走 state, getters
  修改数据,走 mutations, actions
*/
export default new Vuex.Store({
  state: {
    name: '',
    age: 18
  },
  getters: {
  },
  mutations: {
    updateName(state, name) {
      state.name = name;
    }
  },
  actions: {
  },
  modules: {
  }
})

modify shared data

<template>
    <div class="p">
        <el-input placeholder="请修改用户姓名" 
            size="mini" v-model="name"></el-input>
        <el-button type="primary" size="mini" @click="update()">修改</el-button>
    </div>
</template>
<script>
const options = {
    methods: {
        update(){
            this.$store.commit('updateName', this.name);
        }
    },
    data () {
        return {
            name:''
        }
    }
}
export default options;
</script>
  • The mutations method cannot be called directly, it can only store.commit(mutation方法名, 参数)be called indirectly through

read shared data  

<template>
    <div class="container">
        <el-container>
            <el-header>
                <div class="t">
                    欢迎您:{
   
   { $store.state.name }}, {
   
   { $store.state.age }}
    			</div>
            </el-header>
            <el-container>
                <el-aside width="200px">
                </el-aside>
                <el-main>
                    <router-view></router-view>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>

mapState (the method provided by Vuex to obtain shared data)

It is very cumbersome to write $store.state.namesuch code every time, we can use vuex to help us generate computed properties

<template>
    <div class="container">
        <el-container>
            <el-header>
                <div class="t">欢迎您:{
   
   { name }}, {
   
   { age }}</div>
            </el-header>
            <el-container>
                <el-aside width="200px">
                </el-aside>
                <el-main>
                    <router-view></router-view>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>
<script>
import { mapState } from 'vuex'
const options = {
    computed: {
        ...mapState(['name', 'age'])
    }
}
export default options;
</script>
  • What mapState returns is an object that contains the two methods of name() and age() as computed properties

  • This object cooperates with ...the expansion operator, fills in computed and can be used

mapMutations (Vuex modify data)

<template>
    <div class="p">
        <el-input placeholder="请修改用户姓名" 
            size="mini" v-model="name"></el-input>
        <el-button type="primary" size="mini" @click="updateName(name)">修改</el-button>
    </div>
</template>
<script>
import {mapMutations} from 'vuex'
const options = {
    methods: {
        ...mapMutations(['updateName'])
    },
    data () {
        return {
            name:''
        }
    }
}
export default options;
</script>

actions

The mutations method cannot include codes that cannot be modified immediately, otherwise it will cause the Vuex debugging tool to work inaccurately , so these codes must be written in the actions method

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

/*
  读取数据,走 state, getters
  修改数据,走 mutations, actions
*/
import axios from '@/util/myaxios'
export default new Vuex.Store({
  state: {
    name: '',
    age: 18
  },
  getters: {
  },
  mutations: {
    updateName(state, name) {
      state.name = name;
    },
    // 错误的用法,如果在mutations方法中包含了异步操作,会造成开发工具不准确
    /* async updateServerName(state) {
      const resp = await axios.get('/api/user');
      const {name, age} = resp.data.data;
      state.name = name;
      state.age = age;
    } */
    updateServerName(state, user) {
      const { name, age } = user;
      state.name = name;
      state.age = age;
    }
  },
  actions: {
    async updateServerName(context) {
      const resp = await axios.get('/api/user');
      context.commit('updateServerName', resp.data.data)
    }
  },
  modules: {
  }
})
  • First, the updateServerName of actions should be called to get the data

  • Then it indirectly calls updateServerName of mutations to update shared data

The method of using actions on the page can be written like this (mapAction)

<template>
    <div class="p">
        <el-button type="primary" size="mini"
            @click="updateServerName()">从服务器获取数据,存入store</el-button>
    </div>
</template>
<script>
import { mapActions } from 'vuex'
const options = {
    methods: {
        ...mapActions(['updateServerName'])
    }
}
export default options;
</script>
  • mapActions will generate code that calls the methods in actions

  • The internal equivalent of calling actions is that it returns a Promise object, which can receive the result synchronously or asynchronously

this.$store.dispatch('action名称', 参数)

 

Guess you like

Origin blog.csdn.net/qq_59212867/article/details/128666157