以前有一些網友對我那個IT部門信息管理系統(http://caijt.com/it)的前端感興趣,我已經開源到github(https://github.com/Caijt/itsys-ui)php
上面有兩個分支,master是對應php後端的,itsys-net是對應asp.net core後端的。html
這裏我簡單介紹下我這個系統前端代碼,當時我幾乎徹底參考vue-element-admin的,不過沒用它的代碼,但寫法幾乎都參考了他的教程,不過有一點不一樣的是,個人路由跟菜單是動態生成的,是後臺根據當前登陸的用戶,查詢用戶的角色,再查詢角色所具備的菜單列表,返回到前端,而後在前端生成Routers樹數據,再用router.addRouters方法掛載到router上,固然vue-element-admin的做者也有考慮到這個問題,看下圖。前端
那我來介紹下個人系統是怎麼實現這種動態權限的需求的,固然個人代碼不徹底跟vue-element-admin同樣,只是提供一種思路。vue
先介紹下我數據表(sys_menu)的結構,以下圖所示,很明顯,個人數據表(sys_menu)是一個樹型結構,主要的字段有id,title(決定你前端菜單的標題),path(很重要,前端會根據這個值去尋找views下面的vue文件,因此前端建立的文件夾名稱必須跟這個值一致),parent_id(父級菜單id),order(菜單的排序順序),還有一個parent_ids字段, 這個只是我在其它查詢中的一個輔助字段,在這裏沒什麼做用。git
後端獲取菜單數據,如下sql不涉及角色菜單的判斷github
select id,title,path,parent_id from sys_menu order by `order`
前端獲得這樣的對象數組,以下圖所示sql
let menuList = res.data.menuList //這是後端的菜單數據 let menuRouters = [] //定義一個空數組,這個是用來裝真正路由數據的 //下面就要根據後端的菜單數據組裝樹型路由數據 //先取出根節點,沒有父id的就是根節點 menuList.forEach((m, i) => { if (m.parent_id == null) { m.fullPath = '/' + m.path let module = { path: '/' + m.path, component: layout, meta: { id: m.id, title: m.title, fullPath: '/' + m.path }, children: [ { path: '', component:() => import('@/views/' + m.path + '/index') meta: { menuHide: true, title: m.title } } ] } menuRouters.push(module) } }) //定義一個遞歸方法 function convertTree(routers) { routers.forEach(r => { menuList.forEach((m, i) => { if (m.parent_id && m.parent_id == r.meta.id) { if (!r.children) r.children = [] m.fullPath = r.meta.fullPath + '/' + m.path let menu = { path: m.path, component: () => import('@/views'+r.meta.fullPath+'/'+m.path), meta: { id: m.id, title: m.title, fullPath: r.meta.fullPath + '/' + m.path } } r.children.push(menu) } }) if (r.children) convertTree(r.children) }) } convertTree(menuRouters) //用遞歸填充 router.addRoutes(menuRouters) //掛載到router
路由掛載好後,我同時把menuRouters賦值給vuex($store.state.user.routers),方便我在menu組件中調用vuex
還有個人系統跟vue-element-admin菜單顯示也有所不一樣,我是按模塊顯示菜單的,以下圖所示,vue-element-admin是菜單統一顯示左邊欄上element-ui
那麼處理起來很容易,在個人系統裏,根節點就是模塊,根節點的子菜單,就是該模塊下的菜單列表後端
頂部模塊跟左側菜單組件都是用element-ui的NavMenu組件,只是模塊的是用一個horizontal水平的,菜單是vertical垂直的
//如下是頂部模塊菜單的代碼
<el-menu class="_layout-header" router mode="horizontal" :default-active="modulePath" background-color="#304156" text-color="#fff" active-text-color="#409EFF" style="border:none" ref="elHeader" > <el-menu-item v-for="m in $store.state.user.routers.slice(0,maxShowHeaderMenu)" :index="m.path" :key="m.meta.id" >{{ m.meta.title }}</el-menu-item> <el-submenu index="/more" v-if="$store.state.user.routers.length>maxShowHeaderMenu"> <template slot="title">更多</template> <el-menu-item v-for="m in $store.state.user.routers.slice(maxShowHeaderMenu)" :index="m.path" :key="m.meta.id" >{{ m.meta.title }}</el-menu-item> </el-submenu> <el-submenu index="/my" style="float:right;"> <template slot="title">{{$store.state.user.name}}</template> <el-menu-item index @click="logout">註銷</el-menu-item> </el-submenu> </el-menu>
//maxShowHeaderMenu 值是根據當前頁面大小跟模塊數量計算出最多能夠顯示多少個模塊菜單,其他模塊菜單會放進一個更多的摺疊按鈕下,是爲了防止模塊太多,致使頂部模塊菜單超出,致使樣式變形
//this.maxShowHeaderMenu = Math.floor(document.body.clentWidth / 100) -3;
如下是左側菜單的組件,可實現哪一個模塊就顯示哪些菜單
//左側菜單 <el-scrollbar class="_scroll"> <el-menu class="_layout-nav" :default-active="$route.path" :default-openeds="openedMenus" router ref="menu" style="border:none" @select="select" > <el-menu-tree v-for="menu in $store.state.user.routers" v-show="menu.path==modulePath" :menus="menu.children||[]" :key="menu.path" ></el-menu-tree> </el-menu> </el-scrollbar>
//上面代碼裏封裝的一個el-menu-tree組件 <template> <div> <template v-for="m in filterMenus"> <el-menu-item v-if="typeof(m.children)=='undefined' || m.children.length==0" :key="m.meta.id" :index="m.meta.fullPath" >{{m.meta.title}}</el-menu-item> <el-submenu v-else :index="m.meta.fullPath" :key="m.meta.id"> <template slot="title">{{m.meta.title}}</template> <el-menu-tree :menus="m.children"></el-menu-tree> </el-submenu> </template> </div> </template> <script> export default { name: "elMenuTree", props: { menus: { type: Array } }, computed: { filterMenus() { return this.menus.filter(item => !item.meta.menuHide); } } }; </script>
對了,還有一個麪包屑
這個很容易,主要是根據$route.matched數組
<el-breadcrumb separator="/" style="margin-bottom: 20px"> <el-breadcrumb-item v-for="m in breadItems" :key="m.meta.id" :to="m.path" @click.native="itemClick" >{{m.meta.title}}</el-breadcrumb-item> </el-breadcrumb>
//breadItems = this.$route.matched.filter(item=>!item.meta.menuHide);