上节回顾
- 密码加密
- 后端参数校验
- 中间件的使用 & 错误处理
工作内容
-
vuex
的简单使用 -
vue-router
嵌套路由 -
vuex
本地持久化
准备工作
-
npm install vuex --save
//先切换到/client
目录下 -
npm install --save vuex-persist
//先切换到/client
目录下
布局分析
期望布局
自己能实现布局的同学可以掠过"布局"这部分内容,几乎全是贴的代码。
布局分析
首页与登录页面没有公用结构,考虑使用同级路由。
首页为上下结构,可以考虑el-container
、el-header
、el-main
布局。
配置页面与首页公用导航栏,只是将el-main
内分左右两部分,考虑使用嵌套路由。
配置页面将el-main
分左右两部分,考虑el-container
、el-aside
、el-main
布局。
嵌套路由
// 更新文件: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>
最终展示效果
状态管理
登录信息公用
多个组件公用用户信息,将登录用户信息存储,取代下方图片中的用户信息(alias
> account
)
引入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
,会发现,用户信息仍存在。
若下次登录,用户信息比上次少,甚至没有,会有部分/全部用户信息没有被覆盖。
效果展示
持久化
页面刷新,状态会被清空,可以对状态进行本地持久化处理
// 更新文件: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')
},
...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。