17 其他内容

17.导航守卫

导航守卫 | Vue Router

  • 全局的导航守卫

场景: 大多数的路由都需要执行的业务时,可以使用全局导航守卫

比如说。后台管理系统,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. 别名

重定向和别名 | Vue Router

请求 别名,url地址显示未别名

{
    path: '/home',
    name: 'home', // 命名路由
    alias: '/h', // 别名
    component: Home // 先引入后调用
  },

19.命名视图

命名视图 | Vue Router

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 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。

  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。

  3. v-enter-to2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。

  4. v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。

  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。

  6. v-leave-to2.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)
  },
上一篇:openpyxl模块


下一篇:下载excel不保存到本地直接读取内容