3.使用ElementUI搭建前端系统框架三
本节内容是参数化布局管理器的菜单和响应事件,需要用到新技术Vuex,Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化,Vuex细节参考https://vuex.vuejs.org/zh/,我们在此只做简单介绍,重点是如何在前端项目中应用Vuex。
使用VUE设计前端项目,各组件的数据是彼此分离的,无法很好地共享数据,使用Vuex可以在不同vue组件间共享数据。
vuex包括state,mutation,action,getter,module。
state
用于存储数据,可以存储扁平和树状结构对象,各组件间使用全局变量可以共享state数据,同时支持mapState映射所有的数据变量
const state = {
token: '',
navList: [],
permissionList:[],
openedList:[]
}
mutation
用于修改state数据,vue组件必须使用mutation才可以修改vuex中的数据。mutation相对于计算属性,它的第一个参数必须是state,第二个参数是赋值对象
const mutations = {
setToken: (state, data) => {
state.token = data
},
setNavList: (state, data) => {
state.navList = data
sessionStorage.setItem('navList', data);
},
setPermissionList: (state, data) => {
state.permissionList = data
sessionStorage.setItem('permissionList', data);
},
addPage(state, data){
if (state.openedList.some(v => v.path === data.path))
return
data.visiable=true
data.url = data.path
state.openedList.push(data)
},
removePage(state, data){
if(data){
for(let [i, v] of state.openedList.entries()){
if(v.path === data.path){
state.openedList.splice(i, 1)
}
}
} else{
state.openedList = []
}
}
}
在vue组件中调用mutation必须使用commit方法
this.$store.commit("auth/removePage", item)
action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
也可以第一个参数定义为commit,同时支持mapAction映射。
const actions = {
getNavList({commit}){
return new Promise((resolve) =>{
axios({
url: '/acllist',
methods: 'post',
data: {}
}).then((res) => {
commit("setNavList", res.navList)
commit("setPermissionList", res.permissionList)
resolve(res)
})
})
},
login({ commit }, userInfo) {
return new Promise((resolve) => {
axios({
url: '/login',
method: 'post',
data: {
...userInfo
}
}).then(res => {
if(res && res.login){
commit('setToken', res.token)
sessionStorage.setItem('isLogin', res.login);
localStorage.setItem('userId', res.uid);
localStorage.setItem('orgName', res.orgname);
localStorage.setItem('userName', res.name);
localStorage.setItem('token', res.token);
}
resolve(res)
})
});
},
getter
访问vuex 状态数据的一种方式,可以对数据进行过滤处理。支持mapGetter操作
const getters = {
getNavList: state => {
return sessionStorage.getItem('navList');
},
getPermissionList: state => {
return sessionStorage.getItem('permissionList');
}
}
module
当数据结构比较复杂是,vuex支持使用module分组存储
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
使用Vuex步骤
1、新建目录/src/store
2、分module存储vuex状态数据,新建/src/store/modules/auth存储认证相关数据
3、在/src/store/modules/auth目录新建index.js文件,定义vuex中规定的组件state,mutation,action等,这个文件比较大,后面介绍。
4、在/src/store/modules/目录下新建index.js,引入模块auth中定义组件
import auth from './auth'
export default {
auth: auth
}
5、在/src/store目录下新建index.js,声明vuex
import Vue from 'vue'
import Vuex from 'vuex'
import vuexModules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
modules: vuexModules
})
6、在/src/main.js中引入vuex全局变量store
import store from ‘./store’
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import store from './store'
// 引入CSS
import 'static/css/gf-default.scss'
Vue.config.productionTip = false
Vue.use(ElementUI)
/* eslint-disable no-new */
//new Vue({
// el: '#app',
// router,
// components: { App },
// template: '<App/>'
//})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
第三步中定义的vuex组件
import Cookies from 'js-cookie'
const state = {
token: '',//登录成功后获取的令牌
navList: [],//菜单对象集合
permissionList:[],//授权访问菜单对象集合
openedList:[]//在TabPane中打开的页面集合
}
const getters = {
getNavList: state => {
return sessionStorage.getItem('navList');
},
getPermissionList: state => {
return sessionStorage.getItem('permissionList');
}
}
const mutations = {
//登录成功后设置token,第一个参数为state对象,第二个是赋值对象
setToken: (state, data) => {
state.token = data
},
//登录成功后,从后台获取菜单列表
setNavList: (state, data) => {
state.navList = data
sessionStorage.setItem('navList', data);
},
//登录成功后,从后台获取授权访问菜单列表
setPermissionList: (state, data) => {
state.permissionList = data
sessionStorage.setItem('permissionList', data);
},
//当点击左侧导航菜单时,向openedList中添加页面,TabPane组件上会相应显示
addPage(state, data){
if (state.openedList.some(v => v.path === data.path))
return
data.visiable=true
data.url = data.path
state.openedList.push(data)
},
//当点击TabPane组件上页面的关闭图标,从openedList中删除页面,TabPane组件上会相应显示
removePage(state, data){
if(data){
for(let [i, v] of state.openedList.entries()){
if(v.path === data.path){
state.openedList.splice(i, 1)
}
}
} else{
state.openedList = []
}
}
}
const actions = {
//支持异步操作,通过Ajax从后台获取菜单数据
getNavList({commit}){
return new Promise((resolve) =>{
})
},
//登录操作
login({ commit }, userInfo) {
return new Promise((resolve) => {
});
},
logout({commit}) {
return new Promise((resolve) => {
sessionStorage.removeItem('isLogin');
localStorage.removeItem('userId');
localStorage.removeItem('orgName');
localStorage.removeItem('userName');
localStorage.removeItem('token');
resolve()
})
},
}
export default {
namespaced: true,
state,
mutations,
actions
}
在/src/main.js中定义vuex全局访问变量store
修改LeftPane.vue组件
<template>
<div class="sidediv">
<div @mouseenter="fn" class="floatdiv">
<div style="margin-left:0.3rem;margin-top:1.875rem">
<img src="~static/images/demo.png" alt style="width:25px;" />
</div>
<div style="margin-left:0.3rem;margin-top:1.875rem">
<img src="~static/images/demo2.png" alt style="width:25px;" />
</div>
<div style="margin-left:0.3rem;margin-top:1.875rem">
<img src="~static/images/demo3.png" alt style="width:25px;" />
</div>
<div style="margin-left:0.3rem;margin-top:1.875rem">
<img src="~static/images/demo4.png" alt style="width:25px;" />
</div>
<div style="margin-left:0.3rem;margin-top:1.875rem">
<img src="~static/images/demo5.png" alt style="width:25px;" />
</div>
<div style="margin-left:0.3rem;margin-top:1.875rem">
<img src="~static/images/demo6.png" alt style="width:25px;" />
</div>
</div>
<div class="fly" v-show="hidde" @mouseleave="leave()">
<div style="height:25px"></div>
<ul class="uls">
<li v-for="(item,i) in arr" @click="fun(i)">{{item.name}}</li>
</ul>
</div>
<div class="menudiv">
<el-menu
router
ref="navbar"
:default-active="defActive"
:mode="navMode"
menu-trigger="click"
@select="selectMenu"
@open="openMenu"
@close="closeMenu"
unique-opened>
<div class="nav_css">
<left-menu v-for="(item, n) in navList" :item="item" :navIndex="String(n)" :key="n"></left-menu>
</div>
</el-menu>
</div>
</div>
</template>
<style scoped>
.sidediv{
}
.menudiv {
z-index: -1;
margin-left: 2.4rem;
width: 7rem;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
.nav_css{
max-height:33rem;
overflow: hidden;
width: 9rem;
overflow-y: scroll;
text-align:left;
line-height:33px;
border:0;
}
.floatdiv {
position: absolute;
width: 2.4rem;
height: 100%;
background: #001529;
float: left;
}
.fly {
width: 7rem;
height: 100%;
float: left;
background: #001529;
position: absolute;
left: 2.4rem;
z-index: 100;
}
.uls li {
width: 6rem;
height: 40px;
line-height: 40px;
padding-left: 5px;
margin-bottom: 14.5px;
color: white;
cursor:pointer;
}
</style>
<script>
import { mapState } from "vuex";
import LeftMenu from "./LeftMenu";
export default {
data() {
return {
navBgShow: false,
hidde: false,
tagNavList: [],
hometag: {},
currenttag: {},
arr: [
{ name: "待办工作" },
{ name: "关闭页签" },
{ name: "导航切换" }
],
};
},
props: ["layout"],
computed: {
...mapState({
navList: state => state.auth.navList
}),
defActive() {
return this.$route.path;
},
navMode() {
if (this.layout == "left") {
return "vertical";
}
if (this.layout == "top") {
return "horizontal";
}
},
isDark() {
return true;
}
},
watch: {
$route() {
}
},
methods: {
fn() {
this.hidde = !this.hidde;
},
leave() {
this.hidde = false;
},
fun(i) {
if (i == 0) {
this.$router.push("/home");
this.hidde = false;
}else if (i == 1) {
this.closeAllTag();
}else if (i == 2) {
this.changeMenu();
}
// this.$router.push('/'+this.arr.url[i])
},
selectMenu(index, indexPath) {
let openedMenus = this.$refs.navbar.openedMenus;
let openMenuList;
if (indexPath.length > 1) {
let parentPath = indexPath[indexPath.length - 2];
openMenuList = openedMenus.slice(openedMenus.indexOf(parentPath) + 1);
} else {
openMenuList = openedMenus;
}
openMenuList = openMenuList.reverse();
openMenuList.forEach(ele => {
this.$refs.navbar.closeMenu(ele);
});
if (this.navMode == "horizontal") {
this.navBgShow = false;
}
},
openMenu() {
if (this.navMode == "horizontal") {
this.navBgShow = true;
}
},
closeAllTag() {
this.hometag={};
this.currenttag={};
this.tagNavList = this.$store.state.tagNav.openedPageList;
this.tagNavList.forEach(item =>{
if (item.path == '/home'){
this.hometag = item;
}
if (item.path == this.$route.path){
this.currenttag = item;
}
})
this.$store.commit("tagNav/removeTagNav", null);
this.$store.commit("tagNav/addTagNav", this.hometag);
this.$store.commit("tagNav/addTagNav", this.currenttag);
},
changeMenu() {
if (this.isBig) {
this.isBig= false;
}else{
this.isBig= true;
}
if (this.isSmall) {
this.isSmall= false;
}else{
this.isSmall= true;
}
},
closeMenu() {
if (this.navMode == "horizontal") {
this.navBgShow = false;
}
},
closeAll() {
let openMenu = this.$refs.navbar.openedMenus.concat([]);
openMenu = openMenu.reverse();
openMenu.forEach(ele => {
this.$refs.navbar.closeMenu(ele);
});
if (this.navMode == "horizontal") {
this.navBgShow = false;
}
}
},
components: { LeftMenu }
};
</script>
修改TabPane.vue组件
<template>
<div class="tag-nav">
<tab-scroll-bar ref="scrollBar">
<router-link ref="tag" class="tag-nav-item"
:class="isActive(item) ? 'cur' : ''" v-for="(item, index) in openedList"
:to="item.path" :key="index">
<span class='ivu-tag-dot-inner'></span>
{{item.title}}
<span class='el-icon-close' @click.prevent.stop="closePage(item, index)"></span>
</router-link>
</tab-scroll-bar>
</div>
</template>
<script>
import TabScrollBar from './TabScrollBar'
export default {
data(){
return {
defaultPage: '/gf/home'
}
},
computed: {
openedList(){
return this.$store.state.auth.openedList;
}
},
mounted(){
//this.openPage()
},
watch: {
$route(){
this.openPage()
this.scrollToCurPage()
}
},
methods: {
openPage(){
if(this.$router.getMatchedComponents()[1])
{
this.$store.commit("auth/addPage", {
name: this.$router.getMatchedComponents()[1].name,
path: this.$route.path,
title: this.$route.meta.name
})
}
},
isActive(item){
return item.path === this.$route.path
},
closePage(item, index){
this.$store.commit("auth/removePage", item)
if(this.$route.path == item.path){
if(index){
this.$router.push(this.openedList[index-1].path)
} else {
this.$router.push(this.defaultPage)
if(this.$route.path == "/gf/home"){
this.openPage()
}
}
}
},
scrollToCurPage(){
this.$nextTick(() =>{
for (let item of this.$refs.tag) {
if (item.to === this.$route.path) {
this.$refs.scrollBar.scrollToCurPage(item.$el)
break
}
}
})
}
},
components: {TabScrollBar}
}
</script>
修改/src/store/modules/auth/index.js
路由需要定义beforeEach事件,处理页面添加逻辑
import Vue from 'vue'
import Router from 'vue-router'
import staticRoute from './staticRoutes'
import store from '../store'
import NProgress from 'nprogress'
Vue.use(Router)
const router = new Router({
mode: 'hash',
routes: staticRoute
})
const whiteList = [
'/login',
'/sso',
'/logout',
]
let permissionList = []
function initRoute(router){
return new Promise((resolve) => {
store.dispatch('auth/getNavList').then((res) => {
permissionList = res.permissionList;
permissionList.forEach(function(v)
{
if(v.url)
{
let routeItem = router.match(v.url)
if(routeItem){
routeItem.meta.permission = v.permission ? v.permission : []
routeItem.meta.name = v.name
}
}
resolve()
})
})
})
}
router.beforeEach((to, from, next) => {
NProgress.start();
if (true) {
console.log('登录成功 from='+from.path+',to='+to.path);
if (to.path === '/login') {
next({path: "/gf/home", replace: true})
} else if(to.path.indexOf("/error") >= 0){
next()
} else {
initRoute(router).then(() => {
let isPermission = false
permissionList.forEach((v) => {
if(v.url == to.fullPath){
isPermission = true
}
})
isPermission = true;
if(!isPermission){
next({path: "/error/401", replace: true})
} else {
next()
}
})
}
} else {
console.log('未登录 from='+from.path+',to='+to.path);
if (whiteList.indexOf(to.path) >= 0) {
next()
} else {
console.warn('当前未处于登录状态,请登录')
next({path: "/login", replace: true})
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done(); // 结束Progress
})
export default router
刷新网页
新建产品管理组件,/src/product/prdtlist.vue
添加路由
修改/src/store/modules/auth/index.js
定义菜单集合,此时还没有从后台获取
刷新页面可以演示效果
gfvue_v1.4.zip 代码下载
链接:https://pan.baidu.com/s/14hO3ZZv6KIcp1FmKEb0Dzg
提取码:o445
下一篇: android增量更新详细解读