17.导航守卫
-
全局的导航守卫
场景: 大多数的路由都需要执行的业务时,可以使用全局导航守卫
比如说。后台管理系统,app首先必须先登录后使用
import Vue from 'vue' import VueRouter from 'vue-router' import store from '../store' import Home from '../views/home/index.vue' import Kind from '../views/kind/index.vue' import Cart from '../views/cart/index.vue' import User from '../views/user/index.vue' import Footer from '../components/Footer.vue' const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push (location) { return originalPush.call(this, location).catch((err) => err) } Vue.use(VueRouter) const routes = [ { path: '/', // 路由的重定向 redirect: '/home' }, { path: '/home', name: 'home', alias: '/h', components: { default: Home, footer: Footer }, meta: { type: 'fade' } }, { path: '/kind', name: 'kind', components: { default: Kind, footer: Footer }, meta: { type: 'fade' } }, { path: '/cart', name: 'cart', component: Cart, components: { default: Cart, footer: Footer }, meta: { type: 'fade' } }, { path: '/user', name: 'user', component: User, components: { default: User, footer: Footer }, meta: { type: 'fade' } }, { path: '/detail/:proid', name: 'detail', component: () => import(/* webpackChunkName: 'detail' */'../views/detail/index.vue'), meta: { type: 'slide' } }, { path: '/register', name: 'register', component: () => import(/* webpackChunkName: 'user' */'../views/register/index.vue'), redirect: '/register/step1', children: [ { path: 'step1', name: 'registerStep1', component: () => import(/* webpackChunkName: 'user' */'../views/register/step1.vue') }, { path: 'step2', name: 'registerStep2', component: () => import(/* webpackChunkName: 'user' */'../views/register/step2.vue') }, { path: 'step3', name: 'registerStep3', component: () => import(/* webpackChunkName: 'user' */'../views/register/step3.vue') } ] }, { path: '/login', name: 'login', component: () => import(/* webpackChunkName: 'user' */'../views/login/index.vue') }, { path: '*', // 404 ---- 路由懒加载 ---- 一般用不到 component: () => import(/* webpackChunkName: 'error' */'../views/error/notFound.vue') } ] const router = new VueRouter({ mode: 'hash', base: process.env.BASE_URL, routes }) // 全局导航守卫 // 除了登录注册以外所有的页面都需要验证登录时 router.beforeEach((to, from, next) => { console.log(to) console.log(from) if (to.path === '/login' || to.path === '/register/step1' || to.path === '/register/step2' || to.path === '/register/step3') { next() } else { if (store.state.user.loginState) { next() } else { next('/login') // 跳转到登录页面 } } }) export default router
-
路由独享的导航守卫
假如一个项目有非常多的路由,此处是定义路由规则的地方,你却写了其他的业务逻辑
{ path: '/cart', name: 'cart', component: Cart, components: { default: Cart, footer: Footer }, meta: { type: 'fade' }, beforeEnter (to, from, next) { // 路由独享的导航守卫 -- 一般不建议使用 console.log('asnfkjshdskjfhsjkfghsfkjfhdk') if (store.state.user.loginState) { next() } else { next('/login') } } },
// src/views/cart/index.vue
mounted () { this.getCartListData() // if (localStorage.getItem('loginState') === 'true') { // // 前端校验为登录状体啊 // this.getCartListData() // } else { // this.$router.push('/login') // } getRecommendList().then(res => { this.proList = res.data.data }) }
-
组件内的导航守卫
-
beforeRouteEnter
-
beforeRouteUpdate
(2.2 新增) -
beforeRouteLeave
-
// s r c/views/cart/index.vue <template> <div class="box"> <header class="header"> <van-nav-bar :title = 'totalNum > 0 ? "购物车-" + totalNum : "购物车"' left-arrow @click-left="$router.back()" ></van-nav-bar> </header> <div class="content"> <div class="npShop" v-if="empty"> <van-empty description="购物车空空如也"> <van-button round type="danger" class="bottom-button">立即购物</van-button> </van-empty> </div> <div class="shop" v-else> <van-notice-bar mode="closeable" left-icon="volume-o" text="亲爱的用户,像左滑动视图可以展示删除按钮,祝您在嗨购商场中购物愉快" /> <van-swipe-cell v-for="item of cartList" :key="item.cartid" > <van-row> <van-col span="2"> <div class="my-checkbox"> <van-checkbox v-model="item.flag" @change="chooseOne(item.cartid, item.flag)"></van-checkbox> </div> </van-col> <van-col span="22"> <van-card :price="item.originprice" :title="item.proname" :thumb="item.img1" > <template #num> <van-stepper v-model="item.num" theme="round" button-size="22" @change="updateCartNum(item.num, item.cartid)"/> </template> </van-card> </van-col> </van-row> <template #right> <van-button square text="删除" type="danger" class="delete-button" @click="deleteCart(item.cartid)"/> </template> </van-swipe-cell> <van-submit-bar :price="totalPrice" button-text="提交订单" @submit="onSubmit"> <van-checkbox v-model="checked" @click="chooseAll">全选</van-checkbox> </van-submit-bar> </div> <van-divider>猜你喜欢</van-divider> <ProList :proList="proList"></ProList> </div> </div> </template> <script> import Vue from 'vue' import { Divider, NavBar, Empty, Button, Card, Stepper, SwipeCell, NoticeBar, SubmitBar, Checkbox, Col, Row } from 'vant' import { getCartList, remove, updateNum, selectAll, selectOne, getRecommendList } from '../../api/cart' import ProList from '@/components/ProList.vue' import { mapState } from 'vuex' Vue.use(Divider) Vue.use(NavBar) Vue.use(Empty) Vue.use(Button) Vue.use(Card) Vue.use(Stepper) Vue.use(SwipeCell) Vue.use(NoticeBar) Vue.use(SubmitBar) Vue.use(Checkbox) Vue.use(Col) Vue.use(Row) export default { components: { ProList }, data () { return { empty: true, cartList: [], checked: false, // 全选 proList: [] } }, computed: { ...mapState({ loginState: state => state.user.loginState }), totalNum () { return this.cartList.reduce((sum, item) => { return item.flag ? sum + item.num : sum + 0 }, 0) }, totalPrice () { return this.cartList.reduce((sum, item) => { return item.flag ? sum + item.num * item.originprice : sum + 0 }, 0) * 100 } }, methods: { onSubmit () { console.log('提交订单') }, chooseOne (cartid, flag) { console.log(cartid, flag) selectOne({ cartid, flag }).then(() => this.getCartListData()) }, chooseAll () { selectAll({ userid: localStorage.getItem('userid'), type: this.checked // 数据的双向绑定 }).then(() => this.getCartListData()) }, deleteCart (cartid) { remove({ cartid }).then(() => this.getCartListData()) }, updateCartNum (num, cartid) { console.log(num, cartid) updateNum({ num, cartid }).then(() => this.getCartListData()) }, getCartListData () { getCartList({ userid: localStorage.getItem('userid') }).then(res => { console.log(res.data) if (res.data.code === '10020') { this.empty = true this.cartList = [] // 临界值变化 - 否则删除完所有的商品,商品总数量有问题 } else { this.empty = false this.cartList = res.data.data // 判断全选的状态 this.checked = this.cartList.every(item => item.flag) } }) } }, mounted () { this.getCartListData() // if (localStorage.getItem('loginState') === 'true') { // // 前端校验为登录状体啊 // this.getCartListData() // } else { // this.$router.push('/login') // } getRecommendList().then(res => { this.proList = res.data.data }) }, beforeRouteEnter (to, from, next) { next(vm => { // vm可以看作是this if (vm.loginState) { next() } else { console.log('y5u34i467547867326576584365878') next('/login') } }) } } </script> <style lang="stylus"> .delete-button height 100% .my-checkbox height 1.04rem background-color #fff display flex justify-content center align-items center </style>
思考:axios拦截器和导航守卫有什么关系
Axios拦截器时必须发起 ajax请求时才会触发,导航守卫时切换路由时,进入不同的组件时触发
18. 别名
请求 别名,url地址显示未别名
{ path: '/home', name: 'home', // 命名路由 alias: '/h', // 别名 component: Home // 先引入后调用 },
19.命名视图
Router-view
一个路由控制多个试图发生改变
App.vue
<template> <!-- div.container>div.box>header.header+div.content --> <div class="container"> <!-- <div class="box"> <header class="header">header</header> <div class="content">content</div> </div> --> <router-view></router-view> <router-view name="footer"></router-view> <!-- 命名视图 --> <div class="tip">请将屏幕竖向浏览</div> </div> </template> <style lang="stylus"> 。。。。 </style>
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/home/index.vue' import Kind from '../views/kind/index.vue' import Cart from '../views/cart/index.vue' import User from '../views/user/index.vue' import Footer from '../components/Footer.vue' const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push (location) { return originalPush.call(this, location).catch((err) => err) } Vue.use(VueRouter) const routes = [ { path: '/', // 路由的重定向 redirect: '/home' }, { path: '/home', name: 'home', alias: '/h', components: { default: Home, footer: Footer } }, { path: '/kind', name: 'kind', components: { default: Kind, footer: Footer } }, { path: '/cart', name: 'cart', component: Cart, components: { default: Cart, footer: Footer } }, { path: '/user', name: 'user', component: User, components: { default: User, footer: Footer } }, { path: '/detail/:proid', name: 'detail', component: () => import(/* webpackChunkName: 'detail' */'../views/detail/index.vue') }, { path: '/register', name: 'register', component: () => import(/* webpackChunkName: 'user' */'../views/register/index.vue'), redirect: '/register/step1', children: [ { path: 'step1', name: 'registerStep1', component: () => import(/* webpackChunkName: 'user' */'../views/register/step1.vue') }, { path: 'step2', name: 'registerStep2', component: () => import(/* webpackChunkName: 'user' */'../views/register/step2.vue') }, { path: 'step3', name: 'registerStep3', component: () => import(/* webpackChunkName: 'user' */'../views/register/step3.vue') } ] }, { path: '/login', name: 'login', component: () => import(/* webpackChunkName: 'user' */'../views/login/index.vue') }, { path: '*', // 404 ---- 路由懒加载 ---- 一般用不到 component: () => import(/* webpackChunkName: 'error' */'../views/error/notFound.vue') } ] const router = new VueRouter({ mode: 'hash', base: process.env.BASE_URL, routes }) export default router
// s r c/components/Footer.vue
<template> <footer class="footer" > <ul> <router-link to="/home" tag="li"> <span class="iconfont icon-shouye"></span> <p>首页</p> </router-link> <router-link to="/kind" tag="li"> <span class="iconfont icon-fenlei"></span> <p>分类</p> </router-link> <router-link to="/cart" tag="li"> <span class="iconfont icon-gouwuche"></span> <p>购物车</p> </router-link> <router-link v-if="loginState" to="/user" tag="li"> <span class="iconfont icon-My"></span> <p>我的</p> </router-link> <router-link v-else to="/login" tag="li"> <span class="iconfont icon-My"></span> <p>未登录</p> </router-link> </ul> </footer> </template> <script> import { mapState } from 'vuex' export default { // 使用计算属性获取状态管理器中的状态 computed: { // loginState () { // return this.$store.state.user.loginState // } ...mapState({ loginState: state => state.user.loginState }) } } </script>
20 过渡效果
在进入/离开的过渡中,会有 6 个 class 切换。
-
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。 -
v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。 -
v-enter-to
:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。 -
v-leave
:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。 -
v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。 -
v-leave-to
:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
<template> <div class="container"> <transition name="slide"> <router-view/> </transition> <router-view name="footer"/> <div class="tip">请将屏幕竖向浏览</div> </div> </template> <script> ... </script> <style lang="stylus"> .... // 过度 .slide-enter transform translateX(100%) opacity 0 .slide-enter-active transition all .5s .slide-enter-to transform translateX(0%) opacity 1 .slide-leave transform translateX(0%) opacity 1 .slide-leave-active transition all 0s .slide-leave-to transform translateX(-100%) opacity 0 </style>
// 根据不同的路由实现不同的动画 - meta 元信息
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/home/index.vue' import Kind from '../views/kind/index.vue' import Cart from '../views/cart/index.vue' import User from '../views/user/index.vue' import Footer from '../components/Footer.vue' const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push (location) { return originalPush.call(this, location).catch((err) => err) } Vue.use(VueRouter) const routes = [ { path: '/', // 路由的重定向 redirect: '/home' }, { path: '/home', name: 'home', alias: '/h', components: { default: Home, footer: Footer }, meta: { type: 'fade' } }, { path: '/kind', name: 'kind', components: { default: Kind, footer: Footer }, meta: { type: 'fade' } }, { path: '/cart', name: 'cart', component: Cart, components: { default: Cart, footer: Footer }, meta: { type: 'fade' } }, { path: '/user', name: 'user', component: User, components: { default: User, footer: Footer }, meta: { type: 'fade' } }, { path: '/detail/:proid', name: 'detail', component: () => import(/* webpackChunkName: 'detail' */'../views/detail/index.vue'), meta: { type: 'slide' } }, { path: '/register', name: 'register', component: () => import(/* webpackChunkName: 'user' */'../views/register/index.vue'), redirect: '/register/step1', children: [ { path: 'step1', name: 'registerStep1', component: () => import(/* webpackChunkName: 'user' */'../views/register/step1.vue') }, { path: 'step2', name: 'registerStep2', component: () => import(/* webpackChunkName: 'user' */'../views/register/step2.vue') }, { path: 'step3', name: 'registerStep3', component: () => import(/* webpackChunkName: 'user' */'../views/register/step3.vue') } ] }, { path: '/login', name: 'login', component: () => import(/* webpackChunkName: 'user' */'../views/login/index.vue') }, { path: '*', // 404 ---- 路由懒加载 ---- 一般用不到 component: () => import(/* webpackChunkName: 'error' */'../views/error/notFound.vue') } ] const router = new VueRouter({ mode: 'hash', base: process.env.BASE_URL, routes }) export default router
<template> <!-- div.container>div.box>header.header+div.content --> <div class="container"> <!-- <div class="box"> <header class="header">header</header> <div class="content">content</div> </div> --> <!-- <transition name="slide"> --> <transition :name="$route.meta.type"> <router-view></router-view> </transition> <router-view name="footer"></router-view> <!-- 命名视图 --> <div class="tip">请将屏幕竖向浏览</div> </div> </template> <style lang="stylus"> html, body, .container height 100% // 设置rem的基准值 html // font-size 100px // 1rem = 100px // 如果设计稿是以 iphone6 为基础设计的,那么 设置为 100 / 375 * 100 = 26.6666666vw // 如果设计稿是以 iphone5 为基础设计的,那么 设置为 100 / 320 * 100 = 31.25vw font-size 26.6666666vw @media all and (min-width 960px) html font-size 100px // 重置页面中的字体大小 body font-size 12px // 取决于设计稿中的最小的使用最多的字体的大小 // 移动端布局主要使用弹性盒布局 .container // 竖直方向弹性盒 display flex flex-direction column // 设置两边留白 max-width 1080px margin 0 auto .box flex 1 display flex flex-direction column overflow auto .header height 0.44rem background-color #ff6666 .content flex 1 overflow auto // 内容高度大于目前屏幕的高度产生滚动条 background-color #efefef .footer height 0.5rem background-color #efefef ul list-style none height 100% display flex margin 0 padding 0 li flex 1 height 100% display flex flex-direction column justify-content center align-items center &.router-link-active color #f66 span font-size 0.20rem p font-size 12px margin 0 margin-top 5px .tip position fixed top 0 left 0 right 0 bottom 0 background rgba(0, 0, 0, 0.5) color #ffffff font-size 30px font-weight bold display none // 默认不显示 justify-content center align-items center @media all and (orientation landscape) .tip display flex // 显示 .slide-enter transform translateX(100%) opacity 0 .slide-enter-active transition all 5s .slide-enter-to transform translateX(0%) opacity 1 .slide-leave transform translateX(0%) opacity 1 .slide-leave-active transition all 0s .slide-leave-to transform translateX(-100%) opacity 0 .fade-enter opacity 0 .fade-enter-active transition all 5s .fade-enter-to opacity 1 .fade-leave opacity 1 .fade-leave-active transition all 0s .fade-leave-to opacity 0 </style>
补充
1.nextTick
{ path: '/nextTick', name: 'nextTick', component: () => import(/* webpackChunkName: 'user' */'../views/nextTick/index.vue') },
<template> <div> <h1>nextTick</h1> <button @click="add">加1</button> <div id="te">{{ count }}</div> </div> </template> <script> export default { data () { return { count: 100 } }, methods: { add () { this.count++ console.log(this.count) // console.log('do', document.getElementById('te').innerHTML) this.$nextTick(function () { // 获取真实的DOM的数据 console.log('do', document.getElementById('te').innerHTML) }) } } } </script>
2.forceUpdate
如果值发生了改变,但是视图没有更新的话,通过 this.$forceUpdate() 强制更新
3.首页进入详情,返回还在原来的位置
<transition name="slide"> <keep-alive include="home"> <router-view></router-view> </keep-alive> </transition>
name: 'home', activated () { this.$refs.content.scrollTop = localStorage.getItem('top') || 0 localStorage.removeItem('top') }, deactivated () { localStorage.setItem('top', this.$refs.content.scrollTop) },