說明
該文章是屬於OverallAuth2.0系列文章,每週更新一篇該系列文章(從0到1完成系統開發)。
該系統文章,我會盡量說的非常詳細,做到不管新手、老手都能看懂。
說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+視覺化流程管理系統。
友情提醒:本篇文章是屬於系列文章,看該文章前,建議先看之前文章,可以更好理解專案結構。
qq群:801913255
有興趣的朋友,請關注我吧(*^▽^*)。
關注我,學不會你來打我
上篇回顧
在上一篇:(系列九)使用Vue3+Element Plus建立前端框架(附原始碼) 部落格中,我們說道,使用vue3+element plus 建立專案,成功實現了佈局元件container+選單元件Menu搭建框架。
佈局樣式如下:
然而我們只是實現了介面的搭建,並沒有實現任何互動。
也因此有很多人在詢問,如何做動態切換選單。
我想說,不要慌,一切需求都會安排到位。
接下來我們就要實現選單和路由的結合使用,做到動態切換選單。
安裝路由
命令:npm install vue-router
安裝成功後,手動建立以下目錄及檔案
base-routes.ts 內容
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; import Panel from '../../views/panel/index.vue'; export const routes: RouteRecordRaw[] = []; routes.push( { path: '/panel', component: Panel, name: "皮膚", }, { path: '/menu', redirect: '/menu/index', meta: { title: '選單管理' }, name: "選單管理", children: [ { path: '/menu', name: '選單', component: () => import('../../views/menu/index.vue'), meta: { title: '選單', requireAuth: true, affix: true, closable: false }, } ] }, { path: '/user', meta: { title: '使用者管理' }, name: "使用者管理", children: [ { path: '/user', name: '使用者', component: () => import('../../views/user/index.vue'), meta: { title: '使用者' }, }] }, ) //建立路由,並且暴露出去 const router = createRouter({ history: createWebHashHistory(), //開發環境 //history:createWebHistory(), //正式環境 routes }) export default router
該檔案主要是配置選單的json檔案,及暴露路由。裡面的屬性應該不必多說,很容易看懂。
至於views資料夾中的vue檔案內容,大家隨便填寫什麼都可以,只要三個頁面的內容不一樣即可。
然後在main.ts中配置路由,全域性變數。
如下圖:
使用路由
做完以上步驟,接下來的工作就很簡單了,我們只需要,在HelloWorld.vue(接上一篇文章程式碼),中修改程式碼如下
const menu = routes; return { menu, };
完整的HelloWorld.vue程式碼如下
<template> <div style="height: calc(100vh); overflow: hidden"> <el-container style="height: 100%; overflow: hidden"> <el-aside width="auto"> <el-menu class="el-menu-vertical-demo" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" style="height: 100%" router > <div class="el-menu-box"> <div class="logo-image" style="width: 18px; height: 18px; background-size: 18px 18px" ></div> <div style="padding-left: 5px; padding-top: 7px"> OverallAuth2.0 </div> </div> <div v-for="menuItem in menu" :key="menuItem.path"> <el-sub-menu v-if="menuItem.children && menuItem.children.length" :index="menuItem.path" :key="menuItem.name" > <template #title> <el-icon><location /></el-icon>{{ menuItem.name }}</template > <el-menu-item v-for="subMenuItem in menuItem.children" :index="subMenuItem.path" :route="{ name: subMenuItem.name }" :key="subMenuItem.name" style="cursor: pointer" > {{ subMenuItem.name }} </el-menu-item> </el-sub-menu> <el-menu-item v-else :index="menuItem.path" :key="menuItem.path" :route="{ name: menuItem.name }" style="cursor: pointer" > {{ menuItem.name }} </el-menu-item> </div> </el-menu> </el-aside> <el-container> <el-header class="headerCss"> <div style="display: flex; height: 100%; align-items: center"> <div style=" text-align: left; width: 50%; font-size: 18px; display: flex; " > <div class="logo-image" style="width: 32px; height: 32px"></div> <div style="padding-left: 10px; padding-top: 7px"> OverallAuth2.0 許可權管理系統 </div> </div> <div style=" text-align: right; width: 50%; display: flex; justify-content: right; cursor: pointer; " > <div class="user-image" style="width: 22px; height: 22px; background-size: 22px 22px" ></div> <div style="padding-left: 5px; padding-top: 3px"> 微信公眾號:不只是碼農 </div> </div> </div> </el-header> <el-main> <router-view></router-view> </el-main> </el-container> </el-container> </div> </template> <script lang="ts"> import { defineComponent, ref } from "vue"; import { routes } from "../router/module/base-routes"; export default defineComponent({ setup() { const menu = routes; return { menu, }; }, components: {}, }); </script> <style scoped> .el-menu-vertical-demo:not(.el-menu--collapse) { width: 200px; min-height: 400px; } .el-menu-box { display: flex; padding-left: 25px; align-items: center; height: 57px; box-shadow: 0 1px 4px #00152914; border: 1px solid #00152914; color: white; } .el-main { padding-top: 0px; padding-left: 1px; padding-right: 1px; margin: 0; } .headerCss { font-size: 12px; border: 1px solid #00152914; box-shadow: 0 1px 4px #00152914; justify-content: right; align-items: center; /* display: flex; */ } .logo-image { background-image: url("../components/許可權分配.png"); } .user-image { background-image: url("../components/使用者.png"); } .demo-tabs /deep/ .el-tabs__header { color: #333; /* 標籤頁頭部字型顏色 */ margin: 0 0 5px !important; } .demo-tabs /deep/ .el-tabs__nav-wrap { padding-left: 10px; } </style>
做好這些後,我們就能夠動態切換選單。
效果如下圖
是不是很完美。
不,我想說,還沒有完,還差的遠。
接著看往下看
加入tab標籤
可能大家也發現了,在我們點選左側選單時,訪問過的選單,在系統中沒有歷史訪問標籤。
現在我們做以下操作,把訪問過的選單記錄到tab標籤中,以防止系統重新對介面進行請求。
同樣修改HelloWorld.vue檔案。
把el-main標籤中的內容換成
<el-tabs v-if="tabsList.length > 0" v-model="defaultActive" class="demo-tabs" @click="tabsClick(defaultActive)" @tab-remove="tabRemoveClick" > <el-tab-pane v-for="item in tabsList" :label="item.name" :name="item.path" :key="item.path" :closable="item.path == '/panel' ? false : true" style="font-size: 16px;" > <router-view></router-view> </el-tab-pane> </el-tabs>
setup() { const defaultActive = ref("/panel"); const menu = routes; const tabsList = ref<RouteRecordRaw[]>([]); onMounted(() => { tabsList.value.push(routes[0]); router.push(routes[0]); }); //選單項點選事件 function menuItemClick(subMenuItem: RouteRecordRaw) { // tabList中不存在則追加 if (!tabsList.value.some((sub) => sub.path == subMenuItem.path)) { tabsList.value.push(subMenuItem); } defaultActive.value = subMenuItem.path; } //選單標籤點選事件 const tabsClick = (item: string) => { defaultActive.value = item; router.push({ path: item }); }; //選單標籤移除事件 const tabRemoveClick = (path: any) => { tabsList.value.map((item: { path: string }, index: any) => { if (item.path == path) tabsList.value.splice(index, 1); //index 當前元素索引;1:需要刪除的元素個數 }); defaultActive.value = "/panel"; router.push({ path: "/panel" }); }; return { menu, tabsList, defaultActive, tabsClick, tabRemoveClick, menuItemClick, }; },
以上是HelloWorld.vue檔案中變動的程式碼。
我們來看下完整的HelloWorld.vue檔案程式碼
<template> <div style="height: calc(100vh); overflow: hidden"> <el-container style="height: 100%; overflow: hidden"> <el-aside width="auto"> <el-menu :default-active="defaultActive" class="el-menu-vertical-demo" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b" style="height: 100%" router > <div class="el-menu-box"> <div class="logo-image" style="width: 18px; height: 18px; background-size: 18px 18px" ></div> <div style="padding-left: 5px; padding-top: 7px"> OverallAuth2.0 </div> </div> <div v-for="menuItem in menu" :key="menuItem.path"> <el-sub-menu v-if="menuItem.children && menuItem.children.length" :index="menuItem.path" :key="menuItem.name" > <template #title> <el-icon><location /></el-icon>{{ menuItem.name }}</template > <el-menu-item v-for="subMenuItem in menuItem.children" :index="subMenuItem.path" :route="{ name: subMenuItem.name }" :key="subMenuItem.name" @click="menuItemClick(subMenuItem)" style="cursor: pointer" > {{ subMenuItem.name }} </el-menu-item> </el-sub-menu> <el-menu-item v-else :index="menuItem.path" :key="menuItem.path" :route="{ name: menuItem.name }" @click="menuItemClick(menuItem)" style="cursor: pointer" > {{ menuItem.name }} </el-menu-item> </div> </el-menu> </el-aside> <el-container> <el-header class="headerCss"> <div style="display: flex; height: 100%; align-items: center"> <div style=" text-align: left; width: 50%; font-size: 18px; display: flex; " > <div class="logo-image" style="width: 32px; height: 32px"></div> <div style="padding-left: 10px; padding-top: 7px"> OverallAuth2.0 許可權管理系統 </div> </div> <div style=" text-align: right; width: 50%; display: flex; justify-content: right; cursor: pointer; " > <div class="user-image" style="width: 22px; height: 22px; background-size: 22px 22px" ></div> <div style="padding-left: 5px; padding-top: 3px"> 微信公眾號:不只是碼農 </div> </div> </div> </el-header> <el-main> <el-tabs v-if="tabsList.length > 0" v-model="defaultActive" class="demo-tabs" @click="tabsClick(defaultActive)" @tab-remove="tabRemoveClick" > <el-tab-pane v-for="item in tabsList" :label="item.name" :name="item.path" :key="item.path" :closable="item.path == '/panel' ? false : true" style="font-size: 16px" > <router-view></router-view> </el-tab-pane> </el-tabs> </el-main> </el-container> </el-container> </div> </template> <script lang="ts"> import { defineComponent, onMounted, ref } from "vue"; import router, { routes } from "../router/module/base-routes"; import { RouteRecordRaw } from "vue-router"; export default defineComponent({ setup() { const defaultActive = ref("/panel"); const menu = routes; const tabsList = ref<RouteRecordRaw[]>([]); //初始載入dom onMounted(() => { tabsList.value.push(routes[0]); //預設開啟第一個標籤 router.push(routes[0]); }); //選單項點選事件 function menuItemClick(subMenuItem: RouteRecordRaw) { // tabList中不存在則追加 if (!tabsList.value.some((sub) => sub.path == subMenuItem.path)) { tabsList.value.push(subMenuItem); } defaultActive.value = subMenuItem.path; } //選單標籤點選事件 const tabsClick = (item: string) => { defaultActive.value = item; router.push({ path: item }); }; //選單標籤移除事件 const tabRemoveClick = (path: any) => { tabsList.value.map((item: { path: string }, index: any) => { if (item.path == path) tabsList.value.splice(index, 1); //index 當前元素索引;1:需要刪除的元素個數 }); defaultActive.value = "/panel"; router.push({ path: "/panel" }); }; return { menu, tabsList, defaultActive, tabsClick, tabRemoveClick, menuItemClick, }; }, components: {}, }); </script> <style scoped> .el-menu-vertical-demo:not(.el-menu--collapse) { width: 200px; min-height: 400px; } .el-menu-box { display: flex; padding-left: 25px; align-items: center; height: 57px; box-shadow: 0 1px 4px #00152914; border: 1px solid #00152914; color: white; } .el-main { padding-top: 0px; padding-left: 1px; padding-right: 1px; margin: 0; } .headerCss { font-size: 12px; border: 1px solid #00152914; box-shadow: 0 1px 4px #00152914; justify-content: right; align-items: center; /* display: flex; */ } .logo-image { background-image: url("../components/許可權分配.png"); } .user-image { background-image: url("../components/使用者.png"); } .demo-tabs /deep/ .el-tabs__header { color: #333; /* 標籤頁頭部字型顏色 */ margin: 0 0 5px !important; } .demo-tabs /deep/ .el-tabs__nav-wrap { padding-left: 10px; } </style>
我們看下效果
後端WebApi 預覽地址:http://139.155.137.144:8880/swagger/index.html
前端vue 預覽地址:http://139.155.137.144:8881
關注公眾號:傳送【許可權】,獲取前後端程式碼
有興趣的朋友,請關注我微信公眾號吧(*^▽^*)。
關注我:一個全棧多端的寶藏博主,定時分享技術文章,不定時分享開源專案。關注我,帶你認識不一樣的程式世界