之前有一些網友對我那個IT部門信息管理系統(http://caijt.com/it)的前端感興趣,我已經開源到github(https://github.com/Caijt/itsys-ui)
上面有兩個分支,master是對應php后端的,itsys-net是對應asp.net core后端的。
這里我簡單介紹下我這個系統前端代碼,當時我幾乎完全參考vue-element-admin的,不過沒用它的代碼,但寫法幾乎都參考了他的教程,不過有一點不同的是,我的路由跟菜單是動態生成的,是后台根據當前登錄的用戶,查詢用戶的角色,再查詢角色所具有的菜單列表,返回到前端,然后在前端生成Routers樹數據,再用router.addRouters方法掛載到router上,當然vue-element-admin的作者也有考慮到這個問題,看下圖。
那我來介紹下我的系統是怎么實現這種動態權限的需求的,當然我的代碼不完全跟vue-element-admin一樣,只是提供一種思路。
先介紹下我數據表(sys_menu)的結構,如下圖所示,很明顯,我的數據表(sys_menu)是一個樹型結構,主要的字段有id,title(決定你前端菜單的標題),path(很重要,前端會根據這個值去尋找views下面的vue文件,所以前端創建的文件夾名稱必須跟這個值一致),parent_id(父級菜單id),order(菜單的排序順序),還有一個parent_ids字段, 這個只是我在其它查詢中的一個輔助字段,在這里沒什么作用。
后端獲取菜單數據,以下sql不涉及角色菜單的判斷
select id,title,path,parent_id from sys_menu order by `order`
前端得到這樣的對象數組,如下圖所示
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組件中調用
還有我的系統跟vue-element-admin菜單顯示也有所不同,我是按模塊顯示菜單的,如下圖所示,vue-element-admin是菜單統一顯示左邊欄上
那么處理起來很容易,在我的系統里,根節點就是模塊,根節點的子菜單,就是該模塊下的菜單列表
頂部模塊跟左側菜單組件都是用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);