本章技術棧:Vue-Router + VueX + Tabs + 動態菜單渲染
背景:上週寫了一篇基於 Iframe實現單頁面多tab切換界面無刷新 的功能,如今SPA盛行的時代,感受Iframe實現SPA有點Low了(不過基於傳統多頁面實現SPA也是無奈之舉),因此最近想着基於VUE實現多tab功能,隨便也實現了菜單欄動態渲染、路由管理、狀態管理等項目框架基礎功能。這樣看來,這均可以用做通常中小型web後臺管理系統的框架基本骨架了。基於這套骨架後面我會持續增長登陸(Token)、HTTP請求(axios)、用戶、角色、權限管理等。還會封裝一些分頁、上傳等組建,打造一款真正的開箱即用的後臺管理系統框架,讓開發者更多的關注項目需求,提升開發效率,節省開發成本。
先上效果圖vue
讀完這篇文章你能收穫什麼?
源碼註釋很詳細,詳情請戳:https://gitbook.cn/gitchat/activity/5db1a2469fc75b4c0af61ee4ios
核心代碼:
<template> <el-menu :default-active="activeItem" class="el-menu-vertical-demo" background-color="#545c64" text-color="#fff" router :unique-opened='true' @select="clickMenuItem" > <template v-for="(item,index) in menu"> <el-submenu v-if="item.hasChilder" :index="item.index" :key="index"> <template slot="title"> <i class="el-icon-document"></i> <span>{{item.name}}</span> </template> <template v-for="(v,i) in item.children"> <el-menu-item :index="v.index" :key="i">{{v.name}}</el-menu-item> </template> </el-submenu> <template v-else> <el-menu-item :key="index" :index="item.index" > <template slot="title"> <i class="el-icon-location"></i> <span>{{item.name}}</span> </template> </el-menu-item> </template> </template> </el-menu> </template> <script> import { mapState, mapActions } from 'vuex' export default { data () { return { currFatherIndex: '' } }, mounted () { this.getMenu() }, methods: { ...mapActions('menu', { getMenu: 'getMenu', clickMenuItem: 'clickMenuItem' }) }, computed: { ...mapState('menu', { menu: 'menu', activeItem: 'activeItem' }) } } </script> <style scoped> .el-menu > ul, .el-menu { height: 100%; } .el-aside { height: 100%; } </style>
<template> <!-- 參考element-ui中Tabs組件 --> <el-tabs :value="activeItem" @tab-remove="tabRemove" class='content-body' @tab-click="tabClick"> <el-tab-pane v-for="item in tabs" :label="item.label" :key="item.index" :name="item.index" :closable="item.closable"> </el-tab-pane> </el-tabs> </template> <script> import { mapActions, mapState, mapMutations } from 'vuex' export default { computed: { ...mapState('menu', { tabs: 'tabs', activeItem: 'activeItem' }) }, created () { console.log(this.tabs) }, methods: { ...mapActions('menu', { closeTab: 'closeTab' }), ...mapMutations('menu', { switchTab: 'switchTab' }), tabClick (e) { this.switchTab(e.name) this.$router.push({ path: e.name }) }, tabRemove (e) { let t = this setTimeout(function () { t.$router.push({ path: t.activeItem }) }, 1) this.closeTab(e) } } } </script> <style scoped> .content-body { height: 40px !important; } </style>
import Store from './store' // 菜單列表,可經過後臺返回,返回格式相似就行,還可增長icon圖標等字段 const menumap = [ { name: '首頁', hasChilder: false, index: 'index', children: [] }, { name: '菜單一', hasChilder: false, index: 'one', children: [] }, { name: '菜單二', hasChilder: false, index: 'two', children: [] }, { name: '菜單三', hasChilder: true, index: 'three', children: [ { name: '子菜單3-1', hasChilder: false, index: 'three3-1' }, { name: '子菜單3-2', hasChilder: false, index: 'three3-2' } ] }, { name: '菜單四', hasChilder: true, index: 'four', children: [ { name: '子菜單4-1', hasChilder: false, index: 'four4-1' }, { name: '子菜單4-2', hasChilder: false, index: 'four4-2' } ] } ] Store.registerModule('menu', { namespaced: true, state: { menu: [], // 默認tabs裏面有‘首頁’,且沒有closable屬性,不能刪除 tabs: [ { label: '首頁', index: 'index' } ], activeItem: 'index' // 默認選中首頁 }, getters: { }, mutations: { initMenu (state, menu) { state.menu = menu }, initTabs (state, tabs) { state.tabs = tabs }, addTab (state, tab) { state.tabs.push(tab) }, switchTab (state, nowIndex) { state.activeItem = nowIndex } }, actions: { getMenu (context) { context.commit('initMenu', menumap) }, clickMenuItem (context, index) { if (index !== 'index') { var tab = context.state.tabs.find(f => f.index === index) if (!tab) { let menu = {} menu = context.state.menu.find(f => f.index === index) if (!menu) { menu = context.state.menu.map(a => a.children).flat().find(f => f.index === index) } let newTab = { label: menu.name, index: menu.index, closable: true } context.commit('addTab', newTab) } } context.commit('switchTab', index) }, closeTab (context, index) { let indexNum = context.state.tabs.findIndex(f => f.index === index) let activeItem = context.state.activeItem let newTabs = context.state.tabs.filter(f => f.index !== index) context.commit('initTabs', newTabs) if (activeItem === index) { context.commit('switchTab', indexNum === 0 ? 'index' : newTabs[indexNum - 1].index) } } } })
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [ { path: '/', name: 'home', redirect: 'index', component: () => import('../views/Home.vue'), children: [ { path: 'index', name: 'index', component: () => import('../views/Index.vue') }, { path: 'one', name: 'one', component: () => import('../views/One.vue') }, { path: 'two', name: 'two', component: () => import('../views/Two.vue') }, { path: 'three3-1', name: 'three3-1', component: () => import('../views/three/Three3-1.vue') }, { path: 'three3-2', name: 'three3-2', component: () => import('../views/three/Three3-2.vue') }, { path: 'four4-1', name: 'four4-1', component: () => import('../views/four/Four4-1.vue') }, { path: 'four4-2', name: 'four4-2', component: () => import('../views/four/Four4-2.vue') } ] } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push (location) { return originalPush.call(this, location).catch(err => err) } export default router
10月29日晚,更新如下內容:
總體效果圖git
目前總體功能包括:web
1.登陸(路由守衛);
2.封裝 Axios 請求;
3.Tabs 動態管理,切換界面;
4.動態渲染菜單;
5.利用 <keep-alive>
; 動態緩存指定組件;
6.Vue-Router 路由管理;
7.VueX 狀態管理;
8.圖表拖拽效果;vue-router
特色:結構簡單,通用性強,適合用做中小型 Web 後臺管理系統框架,開箱即用。vuex
import axios from 'axios' import { Message, Loading } from 'element-ui' import router from '../router/index' let loading // 定義loading變量 function startLoading () { // 使用Element loading-start 方法 loading = Loading.service({ lock: true, text: '加載中...', background: 'rgba(0, 0, 0, 0.7)' }) } function endLoading () { // 使用Element loading-close 方法 loading.close() } // 請求攔截 設置統一header axios.interceptors.request.use(config => { // 加載 startLoading() if (localStorage.eleToken) { config.headers.Authorization = localStorage.eleToken } return config }, error => { return Promise.reject(error) }) // 響應攔截 token過時處理 axios.interceptors.response.use(response => { endLoading() if (response.data.success) { return response.data.message } else { Message.error(response.data.message) return Promise.reject(response.data.message) } }, error => { // 錯誤提醒 endLoading() // Message.error(error.response.message) const { status } = error.response if (status === '401') { //狀態嗎根據後臺返回而定 Message.error('token值無效,請從新登陸') // 清除token localStorage.removeItem('eleToken') // 頁面跳轉 router.push('/login') } else { Message.error('系統錯誤') } return Promise.reject(error) }) export default axios
源碼註釋很詳細,詳情請戳:https://gitbook.cn/gitchat/activity/5db1a2469fc75b4c0af61ee4element-ui
未完待續......
歡迎交流,歡迎 Star (^_^)
經驗總結,代碼加工廠!axios