(系列十)Vue3中選單和路由的結合使用,實現選單的動態切換(附原始碼)

陈逸子风發表於2024-10-30

說明

該文章是屬於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(接上一篇文章程式碼),中修改程式碼如下

el-main中的內容替換為 <router-view></router-view>
el-menu中新增 router屬性
然後匯入base-routes.ts 檔案,並新增如下程式碼
  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>
在el-menu-item標籤中,加入選單切換事件menuItemClick()
setup()方法中的內容替換成
 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

關注公眾號:傳送【許可權】,獲取前後端程式碼

有興趣的朋友,請關注我微信公眾號吧(*^▽^*)。

關注我:一個全棧多端的寶藏博主,定時分享技術文章,不定時分享開源專案。關注我,帶你認識不一樣的程式世界

相關文章