前端 - Vue中的AJAX、VueX、路由

Vue文档

AJAX

配置代理

官方文档

方式一

服务器:

/* server1.js */
const express = require('express')
const app = express()

app.use((request,response,next)=>{
	console.log('有人请求服务器1了');
	next()
})

app.get('/students',(request,response)=>{
	const students = [
		{id:'001',name:'tom',age:18},
		{id:'002',name:'jerry',age:19},
		{id:'003',name:'tony',age:120},
	]
	response.send(students)
})

app.listen(9000,(err)=>{
	if(!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:9000/students');
})

客户端MyTest.vue

<template>
  <div>
    <button @click="sendAjax">发送AJAX请求</button>
  </div>
</template>

<script>
import axios from 'axios';  // 引入axios

export default {
  methods: {
    sendAjax() {
      axios.get('http://localhost:8080/students').then(
        response => {
          console.log('请求成功', response.data);
        },
        error => {
          console.log('请求失败', error.message);
        }
      );
    }
  },
}
</script>

需要配置代理服务器,端口号和客户端相同:

/* vue.config.js */

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  // ...
  devServer: {
    proxy: 'http://localhost:9000'
  }
})

如果在MyTest.vue直接请求http://localhost:9000/students,会出现跨域请求错误:

Access to XMLHttpRequest at 'http://localhost:9000/students' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

点击按钮成功请求,输出以下信息:

请求成功 (3) [{…}, {…}, {…}]

方式二

方式一的两个缺点:

  1. 只能配置一个代理,即只能给http://localhost:9000转发(AJAX请求)
  2. 如果本地有同名文件,则不会走代理,例如存在一个public/students文件,则方式一不会走代理,而是直接拿到本地的文件
sendAjax() {
  axios.get("http://localhost:8080/api/students").then(
    (response) => {
      console.log("请求成功", response.data);
    },
    (error) => {
      console.log("请求失败", error.message);
    }
  );
},
devServer: {
  proxy: {
    '/api': {  // 匹配所有以/api开头的请求路径
      target: 'http://localhost:9000',  // 目标端口
      ws: true, // 用于支持websocket
      changeOrigin: true,
      pathRewrite: {'^/api': ''}
    },
    '/foo': {
      target: 'http://localhost:9001',
      pathRewrite: {'^/foo': ''}
    }
  }
}

/api为例:

  • changeOrigin设置为false,服务器收到的请求头中的hostlocalhost:8080;设置为true时则为localhost:9000,默认为ture
  • pathRewrite的意思是将请求中的/api变为空串,即http://localhost:8080/api/students变成http://localhost:8080/students

插槽

默认插槽

<!-- App.vue -->
<div class="container">
  <Category>
    <a href="https://www.baidu.com">Baidu</a>
  </Category>
    
  <Category />
  <Category />
</div>
<!-- Category.vue -->  
<div class="category">
  <h3>分类</h3>
  <ul>
    <li>00001</li>
    <li>00002</li>
    <li>00003</li>
    <li>00004</li>
  </ul>
    
  <slot>默认插槽,当使用者没有传递具体结构时会出现</slot>
</div>

具名插槽

多个插槽需要起名:

<!-- App.vue -->
<div class="container">
  <Category>
    <a slot="baidu" href="https://www.baidu.com">Baidu</a>
    <a slot="bing" href="https://cn.bing.com">Bing</a>
  </Category>
    
  <Category>
    <a slot="bing" href="https://cn.bing.com">Bing</a>
  </Category>
</div>
<!-- Category.vue -->  
<div class="category">
  <h3>分类</h3>
  <ul>
    <li>00001</li>
    <li>00002</li>
    <li>00003</li>
    <li>00004</li>
  </ul>
    
  <slot name="baidu">baidu插槽,当使用者没有传递具体结构时会出现</slot>
  <slot name="bing">bing插槽,当使用者没有传递具体结构时会出现</slot>
</div>

当插槽内有多个结构,但是又不希望使用<div>包裹破坏结构时,可以使用<template>标签的v-slot属性:

<!-- App.vue -->
<div class="container">
  <Category>
    <template v-slot:baidu>
      <a href="https://www.baidu.com">Baidu</a>
      <h1>baidu</h1>
    </template>
      
    <a slot="bing" href="https://cn.bing.com">Bing</a>
  </Category>
</div>

简写,将v-slot:改为#

<template #baidu>
    <a href="https://www.baidu.com">Baidu</a>
    <h1>baidu</h1>
</template>

作用域插槽

父级的插槽无法访问子级数据,通过下面的方法传输:

<!-- Category.vue 通过v-bind绑定数据 -->
<slot :users="users"></slot>
<!-- App.vue 接收数据 -->
<Category>
  <template v-slot:default="slotProps">
    <ul>
    <li v-for="(u, index) of slotProps.users" :key="index">
      {{u}}
    </li>
  </ul>
  </template>
</Category>

Vuex

Vuex官方文档

专门在Vue中实现集中式状态管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理,也是一种组件间通信方式。

使用场景:

  1. 多个组件依赖于同一状态
  2. 来自不同组件的行为需要变更同一状态

环境安装

使用Vue2时,需要安装Vuex 3.x,输入下面的命令

npm i vuex@3

新建src/store/index.js文件:

/* src/store/index.js 创建store */
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);  // 使用插件

const actions = {};  // 用于响应组件中的动作
const mutations = {};  // 用于操作数据(state)
const state = {};  // 用于存储数据

// 创建并暴露store
export default new Vuex.Store({
    actions,
    mutations,
    state
});

修改main.js,引入store对象:

/* main.js */
import Vue from 'vue'
import App from './App.vue'
import store from './store';

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  // beforeCreate() {
  //   Vue.prototype.$bus = this;
  // }
  store,  // store对象
}).$mount('#app');

举例

  • getters:用于加工state的数据, 实现类似计算属性的功能
/* src/store/index.js */
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);  // 使用插件

const actions = {
    // 当前和是奇数再加
    increaseOdd(context, value) {
        if (context.state.sum % 2) {
            context.commit('INCREASE', value);
        } 
    },
    // 等2s后再加
    increaseWait(context, value) {
        setTimeout(() => {
            context.commit('INCREASE', value);
        }, 2000);
    },
};  // 用于响应组件中的动作
const mutations = {
    INCREASE(state, value) {
        state.sum += value;
    },
};  // 用于操作数据(state)
const state = {
    sum: 0
};  // 用于存储数据
const getters ={
    bigSum(state) {
        return state.sum * 10;
    }
}  // 用于加工state的数据, 实现类似计算属性的功能

// 创建并暴露store
export default new Vuex.Store({
    actions,
    mutations,
    state,
    getters
});

Counter.vue中对$store进行操作:

<template>
  <div>
    <h1>当前和为:{{ $store.state.sum }}</h1>
    <h2>当前和的10倍为:{{ $store.getters.bigSum }}</h2>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="add">+</button>
    <button @click="subtract">-</button>
    <button @click="addOdd">sum为奇数才加</button>
    <button @click="addWait">等2s再加</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      n: 1,
    };
  },
  methods: {
    add() {
      /* 如果没有复杂的业务逻辑, 直接commit, 跳过action */
      this.$store.commit("INCREASE", this.n);
    },
    subtract() {
      this.$store.commit("INCREASE", -this.n);
    },
    addOdd() {
      this.$store.dispatch("increaseOdd", this.n);
    },
    addWait() {
      this.$store.dispatch("increaseWait", this.n);
    },
  },
};
</script>

<style scoped>
button {
  margin-left: 5px;
}
</style>

mapState与mapGetters

使用mapStatemapGetters简化模板,防止在模板里写类似{{$store.state.sum}}等复杂的属性。

/* Count.vue */
import { mapState } from "vuex";
export default {
  // data() {...},
  // methods: {...},
  computed: {
    /* 借助mapState生成计算属性 */
    // 对象写法
    ...mapState({ mySum: 'sum' }), // 通过rest展开对象(不能简写, 因为'sum'必须是字符串, 不能是变量)
    // 更简单的数组写法(在模板中需要写{{sum}})
    // ...mapState(['sum']),
      
    /* 借助mapGetters生成计算属性 */  
    ...mapGetters(['bigSum']),
  },
};

上面的代码相当于:

/* Count.vue */
export default {
  // data() {...},
  // methods: {...},
  computed: {
    mySum() {
        return this.$store.state.sum;
    },
    bigSum() {
        return this.$store.getters.bigSum;
    }
  },
};

于是在模板直接使用该属性:

<h1>当前和为:{{ mySum }}</h1>
<h2>当前和的10倍为:{{ bigSum }}</h2>

mapActions与mapMutations

使用mapActionsmapMutations简化方法的书写:

import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
  // data() {...},
  methods: {
    /* 借助mapMutations生成方法 */
    ...mapMutations({add: 'INCREASE', subtract: 'INCREASE'}),

    /* 借助mapActions生成方法 */
    ...mapActions({addOdd: 'increaseOdd', addWait: 'increaseWait'}),
  },
  // computed: {...},
};

这里要注意,由于不写参数默认传入$event,所以需要在模板中写明参数:

<button @click="add(n)">+</button>
<button @click="subtract(-n)">-</button>
<button @click="addOdd(n)">sum为奇数才加</button>
<button @click="addWait(n)">等2s再加</button>

这时,使用mapActionsmapMutations帮我们生成了方法:

export default {
  // ...
  methods: {
    add(value) {
      this.$store.commit("INCREASE", value);
    },
    subtract(value) {
      this.$store.commit("INCREASE", value);
    },
    addOdd(value) {
      this.$store.dispatch("increaseOdd", value);
    },
    addWait(value) {
      this.$store.dispatch("increaseWait", value);
    },
  }
};

也可以使用数组写法。

Vuex模块化

VueX模块化官方文档

目的:让代码更好维护,数据分类更明确

  1. 修改store.js,注意开启命名空间
const countAbout = {
    namespces: true,  // 开启命名空间
    state: {},
    mutations: {},
    actions: {},
    getters: {}
}

const personAbout = {
    namespces: true,  // 开启命名空间
    state: {},
    mutations: {},
    actions: {},
    getters: {}
}

export default new Vuex.Store({
    modules: {
        countAbout,
        personAbout
    }
});
  1. 组件读取state数据:
// 方式一:直接读
this.$store.state.personAbout.list
// 方式二:借助mapState
...mapState('countAbout', ['sum'])  // 注意开启命名空间
  1. 组件读取getters数据:
// 方式一:直接读
this.$store.state['personAbout/list']
// 方式二:借助mapGetters
...mapGetters('countAbout', ['bigSum'])  // 注意开启命名空间
  1. 组件调用dispatch
// 方式一:直接读
this.$store.dispatch('personAbout/setList', p)
// 方式二:借助mapActions
...mapActions('countAbout', {myAdd: 'add'})  // 注意开启命名空间
  1. 组件调用commit
// 方式一:直接读
this.$store.commit('personAbout/ADD_PERSON', p)
// 方式二:借助mapMutations
...mapMutations('countAbout', {mySub: 'SUB'})  // 注意开启命名空间

路由

路由简介

Vue对应vue-router的3.x版本:

npm i vue-router@3

SPA应用

vue-router是插件,专门用来实现SPA应用

  1. 单页Web应用 (Single Page Web application)
  2. 整个应用只有一个完整的页面
  3. 点击页面的导航链接不会刷新页面,只会局部更新
  4. 数据请求通过ajax获取

路由的理解

一条路由规则就是一组key-value的映射关系,一般key指的是请求路径,

  • 前端路由:value是组件,当浏览器路径改变时,对应的组件展示相应的页面内容
  • 后端路由:value是函数,服务器收到请求时,根据请求路径找到对应的函数来处理请求,返回相应数据

路由的基本使用

注意:

  1. 路由组件通常存放在pages/文件夹,一般组件存放在components/文件夹
  2. 切换时,路由组件默认被销毁,需要时重新挂载
  3. 每个路由组件都有单独的$route属性,里面存储着自己的路由信息
  4. 整个应用只有一个$router,通过路由组件访问

下面是一个基本路由的例子,首先是两个路由组件:

<!-- src/pages/About.vue -->
<div>
  <h1>About!!!!</h1>
</div>
<!-- src/pages/Home.vue -->
<div>
  <h1>Home!!!!</h1>
</div>

接下来是router配置:

/* src/router/index.js */
import VueRouter from 'vue-router';
import About from '../pages/About.vue';
import Home from '../pages/Home.vue';

export default new VueRouter({
    routes: [  // 两条路由规则
        {
            path: '/about',
            component: About
        },
        {
            path: '/home',
            component: Home
        }
    ]
});

main.js配置:

/* src/main.js */
import VueRouter from 'vue-router';
import router from './router'

Vue.config.productionTip = false;

Vue.use(VueRouter);  // 使用插件

new Vue({
  render: h => h(App),
  router,
}).$mount('#app');

App.vue中:

<template>
  <!-- App.vue -->
  <div>
    <Banner />
    <div class="row">
      <div class="col-xs-2 col-xs-offset-2">
        <div class="list-group">
          <!-- 原来使用<a>标签实现页面跳转 -->
          <!-- <a class="list-group-item" href="./about.html">About</a>
          <a class="list-group-item active" href="./home.html">Home</a> -->

          <!-- 使用<router-link>标签, href改为to属性 -->
          <router-link class="list-group-item" active-class="active" to="/about"
            >About</router-link
          >
          <router-link class="list-group-item" active-class="active" to="/home"
            >Home</router-link
          >
        </div>
      </div>
      <div class="col-xs-6">
        <div class="panel">
          <div class="panel-body">
            <!-- 指定组件的呈现位置 -->
            <router-view></router-view>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Banner from "./components/Banner.vue";
export default {
  name: "App",
  components: {  // 不需要注册路由组件
    Banner,
  },
};
</script>

注意<router-link>标签最终会转化为<a>标签。

嵌套路由

路由可以多级嵌套,并且可以携带query参数。

二级路由组件:pages/News.vue

<template>
  <ul>
    <li v-for="(news, index) of newsList" :key="index">
      {{ news }}
    </li>
  </ul>
</template>

<script>
export default {
  name: "News",
  data() {
    return {
      newsList: ["news001", "news002", "news003"],
    };
  },
};
</script>

二级路由组件:pages/Message.vue,并且携带query参数,传给三级路由:

<template>
  <div>
    <ul>
      <li v-for="msg of messageList" :key="msg.id">
        <!-- 携带query参数, to的字符串写法, 注意模板字符串 -->
        <!-- <router-link
          :to="`/home/message/detail?title=${msg.title}&context=${msg.context}`"
          >{{ msg.title }}</router-link
        > -->

        <!-- 携带query参数, to的对象写法, 更推荐 -->
        <router-link
          :to="{
            path: '/home/message/detail',
            query: { title: msg.title, context: msg.context },
          }"
        >
          {{ msg.title }}
        </router-link>
      </li>
    </ul>
    <hr />
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "Message",
  data() {
    return {
      messageList: [
        { id: "001", title: "message001", context: "0000000001" },
        { id: "002", title: "message002", context: "0000000002" },
        { id: "003", title: "message003", context: "0000000003" },
      ],
    };
  },
};
</script>

路由规则配置,修改router/index.js,通过childeren属性配置子路由:

// ...
import Message from '../pages/Message.vue';
import News from '../pages/News.vue';
import MessageDetail from '../pages/MessageDetail.vue';

export default new VueRouter({
    routes: [
        {
            path: '/about',
            component: About
        },
        {
            path: '/home',
            component: Home,
            children: [  // 配置二级路由
                {
                    path: 'message',  // 二级路由地址不要加'/'
                    component: Message,
                    children: [  // 配置三级路由
                        {
                            path: 'detail',
                            component: MessageDetail
                        }
                    ]
                },
                {
                    path: 'news',
                    component: News
                },
            ]
        },

    ]
});

修改pages/Home.vue

<template>
  <div>
    <h1>Home!!!!!</h1>
    <ul class="nav nav-tabs">
      <li>
        <!-- 二级路由 -->
        <router-link
          class="list-group-item"
          active-class="active"
          to="/home/news"
          >News</router-link
        >
      </li>
      <li>
        <router-link
          class="list-group-item"
          active-class="active"
          to="/home/message"
          >Message</router-link
        >
      </li>
    </ul>
    <!-- 指定组件的呈现位置 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "Home",
};
</script>

query参数

在上面的二级路由pages/Message.vue中,将query参数传给了三级路由,三级路由/pages/MessageDetail.vue需要接收参数并展示:

<template>
  <div>
    <h3>消息编号:{{ $route.query.title }}</h3>
    <p>消息内容:{{ $route.query.context }}</p>
  </div>
</template>

<script>
export default {
  name: "MessageDetail",
};
</script>

命名路由

可以简化路由跳转的路径,to属性必须以对象形式写。

修改router/index.js,给三级路由增加name属性:

import VueRouter from 'vue-router';
// ...
export default new VueRouter({
    routes: [
        // {...},
        {
            // ...
            children: [  // 配置二级路由
                {
                    // ...
                    children: [  // 配置三级路由
                        {
                            name: 'myDetail',  // 命名路由
                            path: 'detail',
                            component: MessageDetail
                        }
                    ]
                },
                // {...},
            ]
        },
    ]
});

在二级路由组件:pages/Message.vue中不写path: '/home/message/detail',直接写name

<router-link
  :to="{
    name: 'myDetail',
    query: { title: msg.title, context: msg.context },
  }"
>
  {{ msg.title }}
</router-link>

params参数

修改router/index.js

children: [  // 配置三级路由
    {
        name: 'myDetail',  // 命名路由
        path: 'detail/:title/:context',  // 必须声明params参数
        component: MessageDetail
    }
]

二级路由传递params参数:

<!-- 携带params参数 -->
<!-- <router-link :to="`/home/message/detail/${msg.title}/${msg.context}`">
{{msg.title}}
</router-link> -->
<router-link
  :to="{
    name: 'myDetail',
    params: { title: msg.title, context: msg.context },
    }"
  >
    {{ msg.title }}
</router-link>

注意:携带params参数,如果to属性采取对象写法,则必须命名路由,并且写name属性,而不能写path属性。

最后在三级路由/pages/MessageDetail.vue接收params参数:

<div>
  <!-- <h3>消息编号:{{ $route.query.title }}</h3>
  <p>消息内容:{{ $route.query.context }}</p> -->
  <h3>消息编号:{{ $route.params.title }}</h3>
  <p>消息内容:{{ $route.params.context }}</p>
</div>

props配置

给三级路由配置props:

第一种:对象写法

第一种写法:值为对象,该对象的key-value以props形式传给MessageDetail组件

修改router/index.js

children: [  // 配置三级路由
    {
        name: 'myDetail',  // 命名路由
        path: 'detail/:title/:context',  // 必须声明params参数
        component: MessageDetail,

        // props第一种写法, 该对象的key-value以props形式传给MessageDetail组件
        props: {a: 1, b: 'hello'}, 

    }
]

在三级路由/pages/MessageDetail.vue使用props:

<template>
  <div>
    <h3>消息编号:{{ $route.params.title }}</h3>
    <p>消息内容:{{ $route.params.context }}</p>
    <p>{{a}}, {{b}}</p>
  </div>
</template>

<script>
export default {
  name: "MessageDetail",
  props: ['a', 'b']
};
</script>

第二种:布尔值写法

第二种写法为布尔值类型,如果为true, 表示所有params参数以props形式传给MessageDetail组件。

修改router/index.js

children: [  // 配置三级路由
    {
        name: 'myDetail',  // 命名路由
        path: 'detail/:title/:context',  // 必须声明params参数
        component: MessageDetail,

        // props第二种写法, 如果为true, 表示所有params参数以props形式传给MessageDetail组件
        props: true
    }
]

在三级路由/pages/MessageDetail.vue使用props:

<template>
  <div>
    <!-- <h3>消息编号:{{ $route.params.title }}</h3>
    <p>消息内容:{{ $route.params.context }}</p> -->
    <!-- <p>{{a}}, {{b}}</p> -->
    <h3>消息编号:{{ title }}</h3>
    <p>消息内容:{{ context }}</p>
  </div>
</template>

<script>
export default {
  name: "MessageDetail",
  props: ['title', 'context']
};
</script>

第三种:函数写法

第三种写法为函数写法,可以指定query或者params,函数返回对象的key-value值以props形式传给MessageDetail组件。

修改router/index.js

children: [  // 配置三级路由
    {
        name: 'myDetail',  // 命名路由
        path: 'detail/',
        component: MessageDetail,

        // props第三种写法, 函数返回对象的key-value值以props形式传给MessageDetail组件
        props($route) {
            return {
                title: $route.query.title,
                context: $route.query.context
            };
        }
    }
]

可以用对象解构简写:

children: [  // 配置三级路由
    {
     	// ...   
        props({query: {title, context}}) {
            return {title, context};
        }
    }
]

replace属性

作用:控制路由跳转时操作浏览器记录的方式,浏览器默认有两种方式写入历史记录:

  • push:追加记录
  • replace:替换当前记录
<router-link replace ...> ... </router-link>

编程式路由导航

不使用<router-link>标签,完成路由跳转。

pushShow(msg) {
  /* 参数就是to属性中要写的对象 */
  this.$router.push({
    name: "myDetail",
    query: { title: msg.title, context: msg.context },
  });
},
replaceShow(msg) {
  this.$router.replace({
    name: "myDetail",
    query: { title: msg.title, context: msg.context },
  });
},

另外,$router中的也有能实现类似BOM中页面跳转的API:

this.$router.forward();  // 前进
this.$router.back();  // 后退
this.$router.go(3);  // 前进/后退指定页数

注意:以上的方式都存在于$router的原型对象。

缓存路由组件

之前讲到过,路由组件在切换时,默认被销毁,需要时重新挂载。

News.vue添加一个输入框,当我们在News组件和Message组件之间来回切换时,不希望输入框中的内容被清空,可以在调用上一级路由组件Home中,使用<keep-alive>标签,让不展示的路由组件保持挂载,不被销毁:

<!-- News是组件名 -->
<keep-alive include="News">
  <router-view></router-view>
</keep-alive>

<!-- 缓存多个组件 -->
<keep-alive :include="['News', 'Message']">
  <router-view></router-view>
</keep-alive>

注意:如果不写include属性,则News组件和Message组件都会被缓存,但我们希望的是只缓存有<input>标签的News组件。

两个新的生命周期

路由组件所独有的两个生命周期钩子:

  1. activated:路由组件被激活时触发
  2. deactivated:路由组件失活时触发
export default {
  name: "News",
  // data() {...},
  activated() {
    console.log('activated');
    // this.timer = setInterval(() => {...}, 100);
  },
  deactivated() {
    console.log('deactivated');
    // clearInterval(this.timmer);  // 清空定时器
  },
};

想象以下场景:News组件中由于有<input>标签,所以使用<keep-alive>标签缓存,但News组件中还包含着一个计时器,由于没有被销毁,定时器一直没有被清空,于是就需要上面的两个生命周期钩子。

路由守卫

全局前置路由守卫

在暴露router对象之前,进行一些操作:

/* router/index.js */
// ...
// const router = new VueRouter({...});

/* 全局前置路由守卫: 初始化前/路由发生切换时调用 */
router.beforeEach((to, from, next) => {
    // 通过name或者path来判断目标路径
    if (to.name === 'myNews' || to.path === '/home/message') {
        // 进行身份验证操作
        if (localStorage.getItem('age') >= 18) {
            next();  // 调用next表示允许前往
        }
        else {
            alert('年龄未满18岁, 无权限查看!');
        }
    }
    else {
        next();  // 调用next表示允许前往
    }
    
});

export default router;

上面的写法的缺点在于,对于每个希望进行权限验证的目标路径,都需要进行一次判断,可以通过给路由规则增加meta属性来解决:

children: [  // 配置二级路由
    // {...},
    {
        name: 'myNews',
        path: 'news',
        component: News,
        meta: {isAuth: true},  // 需要授权
    },
]

router.beforeEach((to, from, next) => {
	// 判断目标路径是否需要授权
    if(to.meta.isAuth){
        // 进行身份验证操作...
    }
    else { next(); }
    
});
export default router;

注意meta属性允许我们将任意信息附加到路由上。

全局后置路由守卫

通过全局后置路由守卫,配合meta属性,实现点击页面,更改文档标题的功能:

// meta: {isAuth: true}  // 首先在meta数据添加标题

/* 全局后置路由守卫: 初始化前/路由发生切换之后调用 */
router.afterEach((to, from) => {
    document.title = to.meta.title || 'Title';
});

独享路由守卫

为路由组件配置独享的路由守卫:

children: [  // 配置二级路由
    // {...},
    {
        name: 'myNews',
        path: 'news',
        component: News,
        meta: {isAuth: true},  // 需要授权
        beforeEnter(to, from, next) {
        	// 逻辑与beforeEach完全相同
        }        
    },
]

注意:独享路由守卫没有后置

组件内路由守卫

/* News.vue */
export default {
  name: "News",
  // ...
  /* 通过路由规则, 进入该组件时被调用 */
  beforeRouteEnter(to, from, next) {
    // 逻辑与beforeEach完全相同
  },
  /* 通过路由规则, 离开该组件时被调用 */
  beforeRouteLeave(to, from, next) {
    // 逻辑类似与beforeEach, 离开当前组件, 前往另一个组件的权限验证
  },
};

history模式与hash模式

之前的路由默认是hash模式,例如http://localhost:8080/#/home/message

  • 对于URL来说,#之后的都是hash值,不会带给服务器

可以修改mode,变为history模式:

/* router/index.js */
// ...
export default new VueRouter({
    mode: 'history',
    // routes: [...]
});

可以发现,URL变为http://localhost:8080/home/message

hash模式:

  1. 地址中有#
  2. 兼容性更好
  3. 如果将地址通过手机的第三方app分享,如果app校验严格,则地址会被标记为不合法

history模式:

  1. 地址干净
  2. 兼容性较差,因为没有#,直接访问服务器,相当于请求服务器对应路径下的资源
  3. 应用部署后需要配合后端人员,解决404问题,例如使用npm安装connect-history-api-fallback

UI组件库

移动端

  1. 有赞 Vant
  2. 京东 Nut UI
  3. Cube UI
  4. Mint UI

PC端

  1. 饿了么 Element UI
  2. IView UI
posted @ 2022-03-18 10:25  lv6laserlotus  阅读(247)  评论(0)    收藏  举报