Koa & Mongoose & Vue实现前后端分离--08前端状态管理&路由嵌套

米花儿团儿

上节回顾

  • 密码加密
  • 后端参数校验
  • 中间件的使用 & 错误处理

工作内容

  • vuex的简单使用
  • vue-router嵌套路由
  • vuex本地持久化

准备工作

  • npm install vuex --save //先切换到/client目录下
  • npm install --save vuex-persist //先切换到/client目录下

布局分析

期望布局

home
backstage

自己能实现布局的同学可以掠过"布局"这部分内容,几乎全是贴的代码。

布局分析

首页与登录页面没有公用结构,考虑使用同级路由。
首页为上下结构,可以考虑el-containerel-headerel-main布局。
layout
配置页面与首页公用导航栏,只是将el-main内分左右两部分,考虑使用嵌套路由。
配置页面将el-main分左右两部分,考虑el-containerel-asideel-main布局。
asideMain

嵌套路由

// 更新文件:client/src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/views/login'
import Layout from '@/views/layout'
import HomePage from '@/views/homepage'
import BackStage from '@/views/backstage'
import PersonalPanel from '@/views/personal-panel'
import ApprovePanel from '@/views/approve-panel'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      redirect: '/login'
    },
    {
      path: '/login',
      name: 'Login',
      component: Login
    },
    {
      path: '/home',
      name: 'Layout',
      component: Layout,
      children: [  // children决定子组件渲染位置(替换父组件中的<router-view>)
        {
          path: '',
          name: 'HomePage',
          component: HomePage
        },
        {
          path: '/backstage', // 绝对路径,决定跳转路径计算方式(不带前缀)
          name: 'BackStage',
          component: BackStage,
          children: [
            {
              path: '',
              name: 'personalPanel',
              component: PersonalPanel
            },
            {
              path: 'person', //相对路径,决定跳转路径计算方式(以父组件path为前缀)
              redirect: '/backstage'
            },
            {
              path: 'approve',
              name: 'approvePanel',
              component: ApprovePanel
            }
          ]
        }
      ]
    }
  ]
}
  • children控制组件的渲染的层级位置

    • 匹配哪一个router-view
    • 匹配路由配置父级组件中的router-view
  • path控制路由跳转的路径

    • 绝对路径:直接以path的值为跳转路径
    • 相对路径:需要加父级组件配置的path为前缀,才能作为跳转路径

页面设计

//新建样式重置文件:client/src/stylesheets/reset.css
// 篇幅有点大,网上找一份就好
...
//更新文件:client/src/main.js
...
import '@/stylesheets/reset.css'
...
//新建样式文件:client/src/stylesheets/layout.scss
@mixin flex($dir: row, $content: flex-start, $item: flex-start, $wrap: wrap) {
  display: flex;
  flex-direction: $dir;
  justify-content: $content;
  align-items: $item;
  flex-wrap: $wrap;
}
// 新建`/home`匹配的Layout组件:client/src/views/layout/index.vue
<template>
  <el-container class="page-container">
    <el-header class="page-header">
      <div class="page-brand" @click="backToHome">
        <figure class="page-logo-wrap">
          <img class="page-logo" src="../../assets/logo.png" alt="">
        </figure>
        <h2 class="page-title">Vue</h2>
      </div>
      <div class="page-controls-wrap">
        <el-dropdown @command="handleDropdown">
          <span class="el-dropdown-link">
            <i class="el-icon-setting"></i>
          </span>
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item disabled>用户信息</el-dropdown-item>
            <el-dropdown-item command='backstage'>后台管理</el-dropdown-item>
            <el-dropdown-item command='logout'>登出</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </div>
    </el-header>
    <el-main class="page-main">
      <router-view/>
    </el-main>
  </el-container>
</template>
<script>
export default {
  methods: {
    backToHome () {
      this.$router.push({
        path: '/home'
      })
    },
    async logout () {
      this.$router.push('/')
    },
    navigateToBackstage () {
      this.$router.push({
        path: '/backstage'
      })
    },
    handleDropdown (command) {
      this['dropdownStrategies'][command]()
    }
  },
  data () {
    return {
      activeIndex: '1',
      dropdownStrategies: {
        'backstage': this.navigateToBackstage,
        'logout': this.logout
      }
    }
  }
}
</script>
<style lang="scss" scoped>
@import '~@/stylesheets/layout.scss';

.page-container {
  height: 100%;
}

.page-header {
  @include flex($item: center, $content: space-between);
  background: #fff;
  border-bottom: 1px solid #eaeaea;

  .page-brand {
    @include flex($item: center, $content: space-between);
    cursor: pointer;

    .page-logo-wrap {
      width: 46px;
      height: 46px;

      .page-logo {
        max-width: 100%;
      }
    }
  }

  .page-menu {
    flex: 1;
    background: transparent;
    border: 0;
  }
}
</style>
  • @import '~@/stylesheets/layout.scss';~为前缀,样式可以使用webpack配置的alias
// 新建文件:client/src/views/homePage/index.vue
<template>
  <div>Home</div>
</template>
// 新建文件:client/src/views/backstage/index.vue
<template>
  <el-container class="backstage-container">
    <el-aside class="backstage-aside">
      <el-menu
        default-active="1"
        class="backstage-menu"
        :router="true"
        :unique-opened="true">
        <el-menu-item index="1" :route="{
          path: '/backstage/person'
        }">
          <template slot="title">
            <i class="el-icon-location"></i>
            <span>个人设置</span>
          </template>
        </el-menu-item>
        <el-menu-item index="2" :route="{
          path: '/backstage/approve'
        }">
          <i class="el-icon-document"></i>
          <span slot="title">审批</span>
        </el-menu-item>
      </el-menu>
    </el-aside>
    <el-main class="backstage-main">
      <router-view />
    </el-main>
  </el-container>
</template>
<style lang="scss" scoped>
.backstage-container {
  height: 100%;

  .backstage-menu {
    height: 100%;
    overflow-y: auto;
  }

  .backstage-main {
    margin-left: 20px;
    background: #fff;
  }
}
</style>
  • el-menu:router="true"配置 & el-menu-item:route="<路由配置对象>"可以直接实现菜单跳转。
// 新建文件:client/src/views/approve-panel/index.vue
<template>
  <div>Approve</div>
</template>
// 新建文件:client/src/views/personal-panel/index.vue
<template>
  <div>Person</div>
</template>

最终展示效果
router.gif

状态管理

登录信息公用

多个组件公用用户信息,将登录用户信息存储,取代下方图片中的用户信息(alias > account
User

引入vuex

// 新建文件:client/src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)


const InitState = {
  loginer: {}
}
export default new Vuex.Store({
  state: {
    ...InitState
  },
  mutations: {
    putLoginer (state, loginer) {
      state.loginer = Object.assign(
        {},
        state.loginer,
        loginer
      )
    },
    resetVuex (state) {
      Object.assign(state, InitState)
    }
  }
})
// 更新文件:client/src/main.js
...
import store from './store'
...
new Vue({
  el: '#app',
  router,
  store, //新增
  components: { App },
  template: '<App/>'
})
// 存储状态
// 更新文件:client/src/views/login/index.vue
...
async onLogin () {
  ...
  console.log(res)
  if (res && res.code === '200') {
    this.$store.commit('putLoginer', res.data)
    this.$router.replace('/home')
  } else {
  ...
}
...
// 展示用户信息
//更新文件:client/src/views/layout/index.vue
...
<template>
...
<!-- <el-dropdown-item disabled>用户信息</el-dropdown-item> -->
  <el-dropdown-item disabled>{{user}}</el-dropdown-item>
...
</template>
...
<script>
...
  computed: {
    user () {
      if (this.$store.state.loginer) {
        const { account, alias } = this.$store.state.loginer
        return alias || account
      }
    }
  }
...
</script>
...

登出清除状态

// 更新文件:client/src/views/layout/index.vue
    async logout () {
      this.$router.push('/')
      this.$store.commit('resetVuex')
    },

若不清除状态,直接进入/home,会发现,用户信息仍存在。
若下次登录,用户信息比上次少,甚至没有,会有部分/全部用户信息没有被覆盖。

效果展示

vuex.gif

持久化

页面刷新,状态会被清空,可以对状态进行本地持久化处理

// 更新文件:client/src/store/index.js
...
import VuexPersistence from 'vuex-persist'//新增

Vue.use(Vuex)

const vuexLocal = new VuexPersistence({ //新增
  storage: window.localStorage
})
...
  mutations: {
    putLoginer (state, loginer) {
      state.loginer = Object.assign(
        {},
        state.loginer,
        loginer
      )
    },
    resetVuex (state) {
      Object.assign(state, InitState)
    }
  },
  plugins: [vuexLocal.plugin] //新增
})

高频率更新的数据,并不建议持久化处理

// 登出时,需要清除本地化
// 更新文件:client/src/views/layout/index.vue
...
async logout () {
  this.$router.push('/')
  await localStorage.clear()
  this.$store.commit('resetVuex')
},
...

持久化效果展示

persist.gif

参考文档

vuex
vuex-persist

阅读 1.1k

1.2k 声望
68 粉丝
0 条评论
1.2k 声望
68 粉丝
文章目录
宣传栏