Koa & Mongoose & Vue實現先後端分離--08前端狀態管理&路由嵌套

上節回顧

  • 密碼加密
  • 後端參數校驗
  • 中間件的使用 & 錯誤處理

工做內容

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

準備工做

  • npm install vuex --save //先切換到/client目錄下
  • npm install --save vuex-persist //先切換到/client目錄下

佈局分析

指望佈局

home
backstage

本身能實現佈局的同窗能夠掠過"佈局"這部份內容,幾乎全是貼的代碼。css

佈局分析

首頁與登陸頁面沒有公用結構,考慮使用同級路由。
首頁爲上下結構,能夠考慮el-containerel-headerel-main佈局。
layout
配置頁面與首頁公用導航欄,只是將el-main內分左右兩部分,考慮使用嵌套路由。
配置頁面將el-main分左右兩部分,考慮el-containerel-asideel-main佈局。
asideMainhtml

嵌套路由

// 更新文件: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控制組件的渲染的層級位置vue

    • 匹配哪個router-view
    • 匹配路由配置父級組件中的router-view
  • path控制路由跳轉的路徑webpack

    • 絕對路徑:直接以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.gifgit

狀態管理

登陸信息公用

多個組件公用用戶信息,將登陸用戶信息存儲,取代下方圖片中的用戶信息(alias > account
Usergithub

引入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,會發現,用戶信息仍存在。
若下次登陸,用戶信息比上次少,甚至沒有,會有部分/所有用戶信息沒有被覆蓋。web

效果展現

vuex.gif

持久化

頁面刷新,狀態會被清空,能夠對狀態進行本地持久化處理vue-router

// 更新文件: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] //新增
})

高頻率更新的數據,並不建議持久化處理vuex

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

持久化效果展現

persist.gif

參考文檔

vuex
vuex-persistnpm

相關文章
相關標籤/搜索