欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解vue3+element-plus實(shí)現(xiàn)動(dòng)態(tài)菜單和動(dòng)態(tài)路由動(dòng)態(tài)按鈕(前后端分離)

 更新時(shí)間:2023年11月25日 15:38:29   作者:m0_72618563  
本文需要使用axios,路由,pinia,安裝element-plus,并且本文vue3是基于js而非ts的,這些環(huán)境如何搭建不做描述,需要讀者自己完成,感興趣的朋友跟隨小編一起看看吧

前言

本篇文章旨在從零搭建一個(gè)動(dòng)態(tài)路由動(dòng)態(tài)菜單的后臺管理系統(tǒng)初始環(huán)境,如果您只有個(gè)別地方?jīng)]有實(shí)現(xiàn),那么可以根據(jù)目錄選擇性的閱讀您所需的內(nèi)容

寫在前面

本文使用技術(shù):vue3,pinia狀態(tài)管理,element-plus,axios

注意在本文章中,有些方法并沒有粘出來,比如一些發(fā)送請求的方法,因?yàn)闆]有必要,需要根據(jù)你們自己的情況進(jìn)行修改,如果你看到一些方法并沒有寫出來,那多半就是發(fā)送請求的方法。

效果預(yù)覽

前期準(zhǔn)備

本文需要使用axios,路由,pinia,安裝element-plus,并且本文vue3是基于js而非ts的,這些環(huán)境如何搭建不做描述,需要讀者自己完成。下面開始

在這之前我們需要一個(gè)布局

假設(shè)我們的home組件,是如下

<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="200px">
        <div>
          <!-- 菜單側(cè)欄導(dǎo)航欄 -->
          <menus></menus>
        </div>
      </el-aside>
      <el-container>
        <el-header>
          <div class="head_class">
            <!-- 頭部內(nèi)容 可以忽略-->
            <Crumbs></Crumbs>
            <div>
              <el-button type="primary" text @click="logOut">注銷</el-button>
            </div>
          </div>
        </el-header>
        <el-main>
          <!-- 主要內(nèi)容 -->
          <div>
                <router-view></router-view>
          </div>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

 話不多說,下面正片開始

動(dòng)態(tài)菜單

功能:根據(jù)不同的角色顯示不同的菜單,菜單數(shù)據(jù)由后端提供,前端負(fù)責(zé)渲染即可

實(shí)現(xiàn)步驟:獲取菜單--->緩存--->渲染菜單

我們使用elemen-plus的menu菜單來渲染

獲取菜單:

首先我們需要先獲取到菜單,一般是在登錄的時(shí)候獲取??梢愿鶕?jù)自己的情況而定。由于我后端采用的是RBAC模型,也就是所謂的用戶-角色-權(quán)限模型,而菜單屬于權(quán)限,我需要先獲取角色信息,然后獲取菜單信息,下面是我的登錄組件的登錄方法,可能代碼比較多,如果你已經(jīng)獲取了菜單,可以直接跳過此處

import {reactive, ref, onMounted, watchEffect} from "vue";
import {getCaptcha, login} from "@/api";
import {useRouter} from "vue-router";
import {setToken, removeToken, getToken} from "@/util/token/getToken";
import {getMyRole} from '@/api/home/permission/role/index'
import {useMenusStore} from '@/store/permission'
import {getMYMenus} from "@/api/home/permission/menus/index";
const store = useMenusStore()//pinia的操作
async function loginsubmit() {
  /*登錄*/
  await login(loginForm).then((res) => {
    if (res.data.code == 200) {
      window.sessionStorage.clear()
      setToken("token", res.data.data.token);
    }
    else {
    }
  })
  // 簡單的健壯性判斷
  if (getToken(`token`)) {
    /*獲取角色信息*/
    await getMyRole().then((res) => {
      store.setRoles(res.data.data)
    })
    /*獲取菜單信息*/
    await getMYMenus(store.roleIds).then(res => {
      store.setMenuList(res.data.data.menuPermission);
      router.push("/home")
    })
  }
}

上面主要是發(fā)送請求獲取菜單,然后將菜單數(shù)據(jù)緩存到pinia中,也就是上面代碼的store,下面是pinia的代碼(緩存菜單數(shù)據(jù)以及路由數(shù)據(jù))

import {defineStore} from "pinia" // 定義容器
import {getRoleRouter} from "@/api/home/permission/menus";
import {logout} from '@/api/index'
export const useMenusStore = defineStore('permission', {
    /**
     * 存儲全局狀態(tài),類似于vue2中的data里面的數(shù)據(jù)
     * 1.必須是箭頭函數(shù): 為了在服務(wù)器端渲染的時(shí)候避免交叉請求導(dǎo)致數(shù)據(jù)狀態(tài)污染
     * 和 TS 類型推導(dǎo)
     */
    state: () => {
        return {
            menuList: [],//菜單信息
            roles: [],//角色信息
            menuRouter: [],//路由信息
            roleIds: [],//角色I(xiàn)d
        }
    },
    /**
     * 用來封裝計(jì)算屬性 有緩存功能  類似于computed
     */
    getters: {},
    /**
     * 編輯業(yè)務(wù)邏輯  類似于methods
     */
    actions: {
        //菜單
        setMenuList(data) {
            this.menuList = data
        },
        //設(shè)置路由:data-->后端傳入的路由信息
        setMenuRouter(data) {
            data.forEach(item => {
                //定義一個(gè)對象-->routerInfo格式化后端傳入的路由信息
                let routerInfo = {
                    path: item.path,
                    name: item.name,
                    meta: item.name,
                    component: () => import(`@/views/${item.linkUrl}`),
                }
                this.menuRouter.push(routerInfo)//加入到home子路由下
            })
        },
        setRoles(data) {
            this.roles = data
            this.roleIds = data.map(item => {
                return item.id
            })
        },
        generateRoutes() {
            //獲取路由信息
            return new Promise((resolve, reject) => {
                getRoleRouter(this.roleIds).then(res => {
                    if (res.data.code == 200) {
                        this.setMenuRouter(res.data.data)
                        resolve()
                    } else {
                        reject()
                    }
                })
            })
        },
        logout() {
            /*注銷登錄*/
           return  logout().then(res => {
                if (res.data.code == 200) {
                    this.$reset()//清除狀態(tài)管理所有數(shù)據(jù)
                    window.sessionStorage.clear()//清除本地所有緩存
                    return true
                }
                else return false
            })
        },
    },
    // 持久化設(shè)置
    persist: {
        enabled: true,	//開啟
        storage: sessionStorage,	//修改存儲位置
        key: 'permissions',	//設(shè)置存儲的key,在這里是存在sessionStorage時(shí)的鍵
        paths: ['menuList', 'hasRouter', `roles`, 'roleIds'],//指定要持久化的字段,menuRouter不需要緩存。因?yàn)槊看温酚商D(zhuǎn)我們都可以重新獲取
    },
})

你可能會說,這直接粘一些垃圾代碼,誰愛看啊,別擔(dān)心,在上面的代碼主要用到的是將角色、菜單信息緩存的操作,你需要關(guān)注的也就是下面的截圖,其他的代碼暫時(shí)不用理會,后面會講

在上面的代碼中,主要是將菜單信息和角色信息存入pinia,當(dāng)然角色信息是否存儲并不重要,因?yàn)樗覀兊闹黝}并沒有多大的相關(guān)

注意:這個(gè)菜單是菜單樹的結(jié)構(gòu)哦!如果想看看后端響應(yīng)的數(shù)據(jù)結(jié)構(gòu),可以在文章最后面查看

 好了到這咱們已經(jīng)獲取到了菜單了,下面就是生成菜單了

生成(渲染)菜單

獲取了菜單,我們就需要在首頁中渲染菜單。

使用明確:element-plus中menu主要分為兩種狀態(tài):有子菜單(目錄)和沒有子菜單,我們需要根據(jù)這兩種分別渲染。

我們就可以粘代碼修修改就可以了,

新建一個(gè)vue文件:我們假定這個(gè)組件是menus

<template>
  <el-row>
    <el-col>
      <h5 class="mb-2">后臺管理系統(tǒng)</h5>
      <el-menu
          active-text-color="#ffd04b"
          background-color="#545c64"
          class="el-menu-vertical-demo"
          default-active="0"
          text-color="#fff"
          router="true"
      >
        <menuTree :menuList="menuList"></menuTree>
      </el-menu>
    </el-col>
  </el-row>
</template>
<script setup>
import menuTree from "@/components/menu/menuTree/index";//渲染菜單的組件
import {useMenusStore} from "@/store/permission";
let menuList = useMenusStore().menuList;//獲取pinia的緩存的菜單數(shù)據(jù)
</script>

你可能會問:怎么代碼這么少?而且只有<el-menu>標(biāo)簽?渲染菜單的<el-sub-menu>和<el-menu-item>標(biāo)簽?zāi)兀?/p>

由于我們會采用遞歸,如果把所有代碼都寫到一個(gè)組件里面,那么所有元素都會重復(fù)了,比如說上面這個(gè)“后臺管理系統(tǒng)”標(biāo)題會被重復(fù)顯示,那么這肯定是不行的,所以我們需要將渲染菜單的具體操作放在另一個(gè)vue文件里面,具體的代碼就在這個(gè)<menuTree> 組件里面,當(dāng)然我們還需要將pinia中存入的菜單信息傳給這個(gè)組件,讓其渲染

下面我們來看看這個(gè)<menuTree >組件

<template>
  <div>
    <template v-for="item in menuList" :key="item.path">
      <!--      分為兩種方式渲染:有子菜單和沒有子菜單-->
      <el-sub-menu
          :index="item.path"
          v-if="item.nodeType == 1"
      >
        <template #title>
          <span>{{ item.name }}</span>
        </template>
        <!--        有子菜單的繼續(xù)遍歷(遞歸)-->
        <menuTree :menuList="item.children"></menuTree>
      </el-sub-menu>
      <!--      沒有子菜單-->
      <el-menu-item :index="item.path" v-if="item.nodeType==2">
        <span>{{ item.name }}</span>
      </el-menu-item>
    </template>
  </div>
</template>
<script>
export default ({
  name: 'menuTree',
  props: {
    menuList: {
      type: Array
    }
  },
  setup(props) {
    return {};
  }
})
</script>

在上面的代碼中:

1、我使用nodeType的值來判斷是否存在子菜單。因?yàn)槲以跀?shù)據(jù)庫存儲的菜單屬性中規(guī)定nodeType=1就是目錄,具有子菜單,=2就是沒有子菜單,是頁面,=3就是按鈕。你也可以通過其他的方法來判斷,比如說,菜單children的長度是否大于零。如果有子菜單,那么我們還需要再一次遍歷,也就是遞歸,無論我們有多少級菜單都能夠遍歷出來,當(dāng)然,理論上層次深了會爆棧,但是正經(jīng)人誰會弄那么深是吧?

2、我采用父子組件的傳遞數(shù)據(jù)的方法把菜單數(shù)據(jù)傳入帶渲染菜單的子組件中

好了到這里,基本就實(shí)現(xiàn)了動(dòng)態(tài)菜單的效果了,但這還不行,雖然用戶不能通過菜單的方式來訪問與他無關(guān)的頁面,但是他可以通過手動(dòng)輸入路由信息來跳轉(zhuǎn),所以我們需要使用動(dòng)態(tài)路由來防止

動(dòng)態(tài)路由

功能:根據(jù)不同的權(quán)限創(chuàng)建對應(yīng)的路由

實(shí)現(xiàn)步驟:獲取路由----->處理路由格式并緩存------>創(chuàng)建路由(前置守衛(wèi))

獲取路由

我們查看pinia中的獲取路由方法,具體代碼就在上面的pinia那里

獲取路由的請求

 上面沒什么好說的,就一個(gè)獲取路由信息然后存入pinia中的操作,重點(diǎn)是下面的處理路由信息,這里我們需要將后端傳過來的數(shù)據(jù)處理為路由格式。

處理路由格式

注意:由于我這里所有路由都只有一層,并沒有子路由,所以這樣處理,如果你想要多嵌套幾個(gè)路由,那么你需要將后端傳來的路由信息處理為樹結(jié)構(gòu)(也可以在后端處理),然后再遍歷處理(可能還需要遞歸遍歷處理) 并添加到children屬性里面,這里就不多說

好了,到現(xiàn)在我們已經(jīng)做完準(zhǔn)備,到了最終的環(huán)節(jié):在哪里創(chuàng)建?怎么創(chuàng)建路由?

創(chuàng)建路由

老樣子先粘代碼,再解說

這里是router/index.js代碼,沒什么特別的,主要是定義一些靜態(tài)路由,和必要的路由配置創(chuàng)建

import {createRouter, createWebHistory} from "vue-router";
import {useMenusStore} from "@/store/index";
//靜態(tài)路由
const constRoutes = [
    {
        path: '/',
        redirect: '/login'
    },
    {
        path: '/login',
        component: () => import('@/views/login')
    },
    {
        path:'/404',
        component:()=>import('@/views/CommonViews/404')
    },
]
const routerHistory = createWebHistory()
const router = createRouter({
    history: routerHistory,
    routes: constRoutes
})
export default router

我們采用的是在路由守衛(wèi)中動(dòng)態(tài)創(chuàng)建路由,下面就是router/permission.js。主要是定義一個(gè)前置守衛(wèi),守衛(wèi)里面動(dòng)態(tài)創(chuàng)建路由,

import {createPinia} from 'pinia';
const pinia = createPinia();
import router from "@/router/index";
import {useMenusStore} from "@/store/permission";
import Home from '@/views/home/index.vue'
import NProgress from 'nprogress' // 進(jìn)度條
import 'nprogress/nprogress.css'
import {getUserMenus} from "@/api/home/permission/menus";
import {getToken} from "@/util/token/getToken";
//路由白名單
const Whitelist = ['/', '/login', '/error', '/404']
//全局路由守衛(wèi)
router.beforeEach((to, from, next) => {
    NProgress.start();
    const store = useMenusStore()
    const hasetoken = getToken('token')
    if (to.path == "/login" && hasetoken) {
        //如果已經(jīng)登錄還在訪問登錄頁,直接跳轉(zhuǎn)首頁
        next('/home')
    }
    if (Whitelist.indexOf(to.path) == -1) {
        //沒有在白名單里面
        if (store.menuRouter.length == 0 && hasetoken) {
            /*已經(jīng)登錄還未獲取路由,就獲取路由信息(pinia)*/
            store.generateRoutes().then(() => {
                const routerlist = store.menuRouter
                router.addRoute(
                    {
                        path: '/home',
                        name: 'Home',
                        component: Home,
                        redirect: '/home/homepage',
                        children: routerlist
                    }
                )
                next({...to, replace: true})//確保路由添加成功
            })
        } else {
            /*兩種情況進(jìn)入:1:已經(jīng)獲取了路由(一定有token),2,沒有登錄(一定沒有路由)*/
            NProgress.done()
            if (hasetoken && to.matched.length != 0) {
                /*to.matched.length === 0判斷當(dāng)前輸入的路由是否具有,即使登陸了,如果輸入不能訪問的路由也要404*/
                /*已經(jīng)獲取了路由,并且訪問路由合法,放行*/
                next()
            } else {
                next('/404')
            }/*沒有登錄、登錄了但是訪問路由不合法,都跳轉(zhuǎn)404*/
        }
    } else {
        NProgress.done()
        /*在白名單里面,直接放行*/
        next()
    }
})
 

在上面代碼中:

  • 我們采用addRoute方法來動(dòng)態(tài)創(chuàng)建路由,這方法好像只能一次創(chuàng)建一個(gè)路由,并且還是根路由(一級路由),由于我們菜單路由是home(一級路由)下的子路由,所以就放在home的children里面。
  • 我們采用next({...to, replace: true})來確保路由創(chuàng)建完畢,注意,他并不是直接放行,添加完路由后,他會再重新進(jìn)入路由守衛(wèi)。
  • 在上面2中說過,由于next({...to, replace: true})并不能直接放行,會再一次進(jìn)入守衛(wèi),所以在

中這個(gè)if()判斷尤為重要,不然一直循環(huán)進(jìn)入這個(gè)if()里面出不來,會導(dǎo)致瀏覽器白屏卡死 ,在if()之外一定要放行。在上面的代碼中,我是采用pinia里面存儲路由的數(shù)組長度判斷,當(dāng)然你也可以用其他的方法,比如當(dāng)前的路由長度減去靜態(tài)路由是否為0作為條件。當(dāng)然也需要判斷好其他情況,比如用戶輸入不存在的路由,我們需要將其跳轉(zhuǎn)到404等。

4. 路由刷新會丟失每次刷新需要重新創(chuàng)建。每一次刷新頁面都會丟失動(dòng)態(tài)添加的路由。在上面中我們使用的是判斷pinia中路由數(shù)組的長度來判斷的,在這樣的情況下,我們就不能持久化pinia這個(gè)路由數(shù)組(如果是其他判斷條件則可以持久化,一切以判斷條件而立),不然頁面刷新丟失路由,卻沒法進(jìn)入這個(gè)if()里面(因?yàn)槿绻志没?,這個(gè)menrouter就不會為空,if判斷條件不成立)。無法進(jìn)入if創(chuàng)建動(dòng)態(tài)創(chuàng)建路由,在上面的代碼中就會一直進(jìn)入404(如果沒有404頁面處理,就是白屏

 可以看到我并沒有將這路由數(shù)組持久化。

我還是再說一遍,如果你是其他判斷條件,比如說你是使用路由的長度判斷(是否大于靜態(tài)路由數(shù)組長度),那么就可以持久化,而在if()內(nèi)部也不用每次都發(fā)送請求,不然持久化也失去了意義。

當(dāng)然,就我而言,更傾向于每次刷新就發(fā)送請求,這樣可以確保數(shù)據(jù)的準(zhǔn)確性。

這里提一嘴,permission.js定義的路由守衛(wèi)想要生效就需要在min.js中引入,如果你覺得麻煩就全定義在router/index.js中也是可行的,如果你是單獨(dú)新建js實(shí)現(xiàn)的路由守衛(wèi),記得引入哦。

動(dòng)態(tài)按鈕

分析

動(dòng)態(tài)按鈕的實(shí)現(xiàn)可以通過v-if或者v-show指令來動(dòng)態(tài)渲染按鈕(根據(jù)后端返回的權(quán)限碼比對);

如果你覺得麻煩,你可以用vue中自定義組件來簡化。這里就不再贅述了

主要分為三個(gè)步驟:獲取權(quán)限碼,緩存權(quán)限碼,權(quán)限對比(v-if或者v-show)

獲取權(quán)限碼

假設(shè)我們在登錄的時(shí)候就獲取權(quán)限碼,下面是login頁面登錄時(shí)發(fā)送的請求,將這個(gè)結(jié)果存入pinia

緩存權(quán)限碼

在store/permission.js中

提供對應(yīng)的賦值方法以及緩存

權(quán)限對比

在需要的頁面中引入,并判斷

場景:假設(shè)我們的商品上架只有超級管理員才能使用

indexof表示判斷一個(gè)數(shù)組是否包含一個(gè)元素,如果包含就返回索引,不包含就返回-1。

大工告成!

實(shí)現(xiàn)效果

管理員登錄

商家登錄

 有沒有發(fā)現(xiàn)還是太丑了一點(diǎn)?接下來我們來嘗試美化一下它

+el-tabs美化

寫在前面

這部分,分為快速篇循序漸進(jìn)篇兩種,如果耐心不夠,請看快速篇,如果你覺得還行,可以看循序漸進(jìn)篇

使用element-plus的tabs標(biāo)簽

分析功能:

  • 點(diǎn)擊菜單欄的時(shí)候,如果沒有這個(gè)tab頁,就新增,如果有就跳轉(zhuǎn)
  • 點(diǎn)擊tabs的時(shí)候跳轉(zhuǎn)對應(yīng)的路由
  • tabs可以被刪除

快速篇

主要修改地方:

  • pinia中定義標(biāo)簽綁定所需的數(shù)據(jù)
  • 在路由展示地方添加標(biāo)簽頁代碼,并設(shè)置切換事件
  • 在菜單渲染組件中添加新增標(biāo)簽頁的事件

在pinia中定義標(biāo)識激活標(biāo)簽頁的值和標(biāo)簽頁的數(shù)據(jù)數(shù)組,

以及對應(yīng)的對外提供操作的接口

 路由展示的地方添加標(biāo)簽代碼如下

代碼如下

<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="200px">
        <div>
          <!-- 菜單側(cè)欄導(dǎo)航欄 -->
          <menus></menus>
        </div>
      </el-aside>
      <el-container>
        <el-header>
          <div class="head_class">
            <!-- 頭部 -->
            <Crumbs></Crumbs>
            <div>
              <el-button type="primary" text @click="logOut">注銷</el-button>
            </div>
          </div>
        </el-header>
        <el-main>
          <!-- 內(nèi)容 -->
          <div>
            <el-tabs type="border-card" v-model="tabsActive" @tab-change="gotoActive" @tab-remove="closeTabs">
              <el-tab-pane :label="item.name" :key="item.name"
                           v-for="item in tabsList" :name="item.path" :closable="item.isClose==1" >
                <router-view></router-view>
              </el-tab-pane>
            </el-tabs>
          </div>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
<script setup>
import menus from "@/components/menu/index.vue";
import {ElMessage, ElMessageBox} from 'element-plus'
import Crumbs from "@/components/CommonComponent/crumbs/index.vue";
import {useMenusStore} from '@/store/permission'
import {useRouter} from "vue-router";
import {ref} from "vue";
import { storeToRefs } from 'pinia'
const store = useMenusStore()
const router = useRouter()
let {tabsList,tabsActive}=storeToRefs(store)
function logOut() {
  ElMessageBox.confirm('確認(rèn)退出登錄?', '提示', {
    confirmButtonText: '確定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    /*點(diǎn)擊的確認(rèn)*/
    const b = await store.logout()
    if (b) {
      router.push('/login')
    } else {
      ElMessage({
        showClose: true,
        message: '注銷失敗',
        type: 'error',
      })
    }
  }).catch(() => {
    /*點(diǎn)擊的取消*/
  })
}
function closeTabs(name){
  //判斷刪除的是否是活動(dòng)頁,如果是則刪除并跳到首頁,
  if(name==store.tabsActive){
    store.setActive('homepage')
    console.log("刪除的是當(dāng)前頁",tabsActive)
  }
  store.delTabs(name)
}
function gotoActive(name){
  store.setActive(name)
  router.push(name)
}
</script>
<style scoped>
.head_class {
  display: flex;
  justify-content: space-between;
}
/*隱藏側(cè)邊欄滾動(dòng)條*/
.el-aside::-webkit-scrollbar {
  display: none;
}
.el-header {
  position: relative;
  width: 100%;
  height: 60px;
}
.el-aside {
  display: block;
}
.el-main {
  position: absolute;
  left: 200px;
  right: 0;
  top: 60px;
  bottom: 0;
  overflow-y: scroll;
}
</style>

 在菜單渲組件中定義事件

代碼如下

<template>
  <div>
    <template v-for="item in menuList" :key="item.path">
      <!--      分為兩種方式渲染:有子菜單和沒有子菜單-->
      <el-sub-menu
          :index="item.path"
          v-if="item.nodeType == 1"
      >
        <template #title>
<!--          <el-icon v-show="item.iconId!=null"><img :src="sendIcon(item.iconInfo)" alt="" class="icon_class"/></el-icon>-->
          <span>{{ item.name }}</span>
        </template>
        <!--        有子菜單的繼續(xù)遍歷(遞歸)-->
        <menuTree :menuList="item.children"></menuTree>
      </el-sub-menu>
      <!--      沒有子菜單-->
      <el-menu-item :index="item.path" v-if="item.nodeType==2" @click="clickOnMenu(item)">
<!--        <el-icon v-show="item.iconId!=null">-->
<!--          <img :src="sendIcon(item.iconInfo)" alt="" class="icon_class"/>-->
<!--        </el-icon>-->
        <span>{{ item.name }}</span>
      </el-menu-item>
    </template>
  </div>
</template>
<script>
import {getImage} from '@/api/home/file/index'
import {getImageToBase64} from '@/util/file'
import {useMenusStore} from "@/store/permission";
import { storeToRefs } from 'pinia'
export default ({
  name: 'menuTree',
  props: {
    menuList: {
      type: Array
    }
  },
  setup(props) {
    let store=useMenusStore()
    async function sendIcon(fileInfo) {
      // 獲取菜單圖片路徑
      if (fileInfo != null) {
        return await getImageToBase64(fileInfo)
      }
    }
    // 點(diǎn)擊菜單后添加進(jìn)入tabs數(shù)組,需要注意tabs數(shù)組里面是否已經(jīng)存在,
    function clickOnMenu(node){
      let hasNode=store.tabsList.filter(item=>item.path==node.path)
      if (hasNode.length==0 || hasNode==null){
        store.setTabs(node)
      }
      store.setActive(node.path)
    }
    return {
      sendIcon,
      clickOnMenu
    };
  }
})
</script>
<style scoped>
.icon_class {
  width: 30px;
  height: 30px;
}
</style>

 大功告成,如果你不明白這些步驟并且想要了解,請看循序漸進(jìn)篇。

循序漸進(jìn)篇

我們從官網(wǎng)上復(fù)制代碼,放在哪里呢?

<el-tabs type="border-card">
    <el-tab-pane label="User">User</el-tab-pane>
    <el-tab-pane label="Config">Config</el-tab-pane>
    <el-tab-pane label="Role">Role</el-tab-pane>
    <el-tab-pane label="Task">Task</el-tab-pane>
  </el-tabs>

放在路由展示的地方,還記得我們前面的前期準(zhǔn)備那里嗎?

<div class="common-layout">
    <el-container>
      <el-aside width="200px">
        <div>
          <!-- 菜單側(cè)欄導(dǎo)航欄 -->
          <menus></menus>
        </div>
      </el-aside>
      <el-container>
        <el-header>
          <div class="head_class">
            <!-- 頭部 -->
            <Crumbs></Crumbs>
            <div>
              <el-button type="primary" text @click="logOut">注銷</el-button>
            </div>
          </div>
        </el-header>
        <el-main>
          <!-- 內(nèi)容 -->
          <div>
            <el-tabs type="border-card">
                <el-tab-pane label="User">
                    <router-view></router-view>
                </el-tab-pane>
          </div>
        </el-main>
      </el-container>
    </el-container>
  </div>
 </el-tabs>

當(dāng)然,只是這樣是不行的,由于這個(gè)標(biāo)簽頁是動(dòng)態(tài)的,我們需要一個(gè)數(shù)組來代替它的源數(shù)據(jù),這個(gè)數(shù)組呢并不是固定的。在點(diǎn)擊菜單欄的時(shí)候就需要添加一個(gè),我們也可以在tabs上面點(diǎn)擊刪除按鈕,刪除一個(gè)標(biāo)簽頁。也就是說,對這個(gè)數(shù)組的操作是跨頁面跨組件的。所以我們有限考慮使用pinia來管理。

在前面的pinia基礎(chǔ)上,我們新增一個(gè)tabsList

并提供一個(gè)新增和刪除的方法

代碼如下:

import {defineStore} from "pinia" // 定義容器
import {getRoleRouter} from "@/api/home/permission/menus";
import {logout} from '@/api/index'
import {isObjectValueEqualNew} from '@/util/Utils'
export const useMenusStore = defineStore('permission', {
    /**
     * 存儲全局狀態(tài),類似于vue2中的data里面的數(shù)據(jù)
     * 1.必須是箭頭函數(shù): 為了在服務(wù)器端渲染的時(shí)候避免交叉請求導(dǎo)致數(shù)據(jù)狀態(tài)污染
     * 和 TS 類型推導(dǎo)
     */
    state: () => {
        return {
            menuList: [],//菜單信息
            roles: [],//角色信息
            menuRouter: [],//路由信息
            roleIds: [],//角色I(xiàn)d
            tabsList:[{id:1,name:'首頁',path:'homepage'}],
            tabsActive:'homepage'
        }
    },
    /**
     * 用來封裝計(jì)算屬性 有緩存功能  類似于computed
     */
    getters: {},
    /**
     * 編輯業(yè)務(wù)邏輯  類似于methods
     */
    actions: {
        //菜單
        setMenuList(data) {
            this.menuList = data
        },
        //設(shè)置路由:data-->后端傳入的路由信息
        setMenuRouter(data) {
            data.forEach(item => {
                //定義一個(gè)對象-->routerInfo格式化后端傳入的路由信息
                let routerInfo = {
                    path: '',
                    name: '',
                    meta: '',
                    component: '',
                }
                routerInfo.path = `${item.path}`
                routerInfo.meta = {name: item.name}
                routerInfo.name = item.name
                routerInfo.component = () => import(`@/views/${item.linkUrl}`)
                this.menuRouter.push(routerInfo)
            })
        },
        setRoles(data) {
            this.roles = data
            this.roleIds = data.map(item => {
                return item.id
            })
        },
        generateRoutes() {
            //獲取路由信息
            return new Promise((resolve, reject) => {
                getRoleRouter(this.roleIds).then(res => {
                    if (res.data.code == 200) {
                        this.setMenuRouter(res.data.data)
                        resolve()
                    } else {
                        reject()
                    }
                })
            })
        },
        logout() {
            /*注銷登錄*/
           return  logout().then(res => {
                if (res.data.code == 200) {
                    this.$reset()//清除狀態(tài)管理所有數(shù)據(jù)
                    window.sessionStorage.clear()//清除本地所有緩存
                    return true
                }
                else return false
            })
        },
        setTabs(node){
            this.tabsList.push(node)
        },
        delTabs(node){
            /*返回和node不相同的元素,就相當(dāng)于把相同的元素刪掉了*/
            this.tabsList=this.tabsList.filter(item=>{
                if (item.path==node){
                    return false
                }
                else return true
            })
        },
        setActive(value){
          this.tabsActive=value
        }
    },
    // 持久化設(shè)置
    persist: {
        enabled: true,	//開啟
        storage: sessionStorage,	//修改存儲位置
        key: 'permissions',	//設(shè)置存儲的key,在這里是存在sessionStorage時(shí)的鍵
        paths: ['menuList', 'hasRouter', `roles`, 'roleIds','tabsList','tabsActive'],//指定要持久化的字段,menuRouter不需要緩存。因?yàn)槊看温酚商D(zhuǎn)我們都可以重新獲取
    },
})

 現(xiàn)在,我們有了這樣一個(gè)數(shù)組,就可以遍歷生成了,我們在之前的基礎(chǔ)上,修改這部分代碼,

從pinia中引入tabsList數(shù)組,在標(biāo)簽頁中遍歷它

 <div>
            <el-tabs type="border-card">
              <el-tab-pane :label="item.name" :key="item.name"
                           v-for="item in tabsList" :name="item.path"  >
                <router-view></router-view>
              </el-tab-pane>
            </el-tabs>
          </div>
import {useMenusStore} from '@/store/permission'
import {useRouter} from "vue-router";
import {ref} from "vue";
import { storeToRefs } from 'pinia'
const store = useMenusStore()
const router = useRouter()
let {tabsList,tabsActive}=storeToRefs(store)

分析 

  • storeToRefs方法在這里的主要作用是將pinia中的數(shù)據(jù)變?yōu)轫憫?yīng)式。如果不使用這個(gè),數(shù)組修改后,標(biāo)簽頁并不會隨之渲染出來
  • 使用標(biāo)簽頁的name屬性綁定tabsList存儲的路由路徑

現(xiàn)在我們能夠便遍歷顯示了,但是還不夠,我們需要點(diǎn)擊菜單的時(shí)候,想這個(gè)數(shù)組新增元素

我們找到前面的渲染菜單的組件,在點(diǎn)擊沒有子菜單的菜單上面添加按鈕事件

<el-main>
          <!-- 內(nèi)容 -->
          <div>
            <el-tabs type="border-card" v-model="tabsActive" @tab-change="gotoActive" @tab-remove="closeTabs">
              <el-tab-pane :label="item.name" :key="item.name"
                           v-for="item in tabsList" :name="item.path" :closable="item.isClose==1" >
                <router-view></router-view>
              </el-tab-pane>
            </el-tabs>
          </div>
        </el-main>
<script setup>
import menus from "@/components/menu/index.vue";
import {ElMessage, ElMessageBox} from 'element-plus'
import Crumbs from "@/components/CommonComponent/crumbs/index.vue";
import {useMenusStore} from '@/store/permission'
import {useRouter} from "vue-router";
import {ref} from "vue";
import { storeToRefs } from 'pinia'
const store = useMenusStore()
const router = useRouter()
let {tabsList,tabsActive}=storeToRefs(store)
function logOut() {
  ElMessageBox.confirm('確認(rèn)退出登錄?', '提示', {
    confirmButtonText: '確定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(async () => {
    /*點(diǎn)擊的確認(rèn)*/
    const b = await store.logout()
    if (b) {
      router.push('/login')
    } else {
      ElMessage({
        showClose: true,
        message: '注銷失敗',
        type: 'error',
      })
    }
  }).catch(() => {
    /*點(diǎn)擊的取消*/
  })
}
function closeTabs(name){
  //判斷刪除的是否是活動(dòng)頁,如果是則刪除并跳到首頁,
  if(name==store.tabsActive){
    store.setActive('homepage')
    console.log("刪除的是當(dāng)前頁",tabsActive)
  }
  store.delTabs(name)
}
function gotoActive(name){
  store.setActive(name)
  router.push(name)
}
</script>

當(dāng)點(diǎn)擊菜單的時(shí)候,就需要新增一個(gè)標(biāo)簽頁,如果已經(jīng)存在,就直接跳轉(zhuǎn)到這個(gè)標(biāo)簽頁。那么問題來了,在上面的代碼中,我們能夠新增標(biāo)簽頁了,但是如何跳轉(zhuǎn)到這個(gè)標(biāo)簽頁呢?

我們需要借助標(biāo)簽頁的這個(gè)屬性:

 name屬性標(biāo)識了每一個(gè)標(biāo)簽頁(tab-pane),而tab的v-model屬性綁定了當(dāng)前激活的標(biāo)簽頁。tab綁定的值是哪一個(gè)tab-pane的name,那么哪一個(gè)tab-pane就是激活頁

先不要著急定義這個(gè)激活值,我們仔細(xì)分析:在點(diǎn)擊菜單的時(shí)候,不僅要向數(shù)組中添加一個(gè)新的標(biāo)簽頁,還要切換到這個(gè)激活的標(biāo)簽頁。也就是說,對這個(gè)激活值執(zhí)行賦值的動(dòng)作是在菜單組件中,而綁定(獲?。┻@個(gè)激活值是在展示標(biāo)簽頁的組件中,這又是一個(gè)跨頁面跨組件的共享數(shù)據(jù),我們?nèi)匀粌?yōu)先定義在pinia中。我們給他一個(gè)初始值,在進(jìn)入頁面的時(shí)候默認(rèn)打開首頁,

仍然需要提供一個(gè)修改的接口

代碼在上面已經(jīng)給出,這里就不在重復(fù)了

我們有了這個(gè)激活值,就需要綁定,并且考慮到在點(diǎn)擊不同的標(biāo)簽頁的時(shí)候需要切換路由,我們需要在激活值改變的時(shí)候,就切換到這個(gè)激活值對應(yīng)的路由上。

修改標(biāo)簽頁的代碼,我們將數(shù)組中的路由路徑(path)綁定為name值,closable表示當(dāng)前標(biāo)簽是否可以被刪除,我這里只有首頁是不能被刪除的

{
	"menuPermission": [{
		"id": "1",
		"name": "首頁",
		"menuCode": "homepage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/index.vue",
		"iconId": "1",
		"level": 0,
		"path": "homepage",
		"createTime": null,
		"isClose": 0,
		"children": [],
		"iconInfo": null
	}, {
		"id": "2",
		"name": "用戶管理",
		"menuCode": "user_manage",
		"parentId": "0",
		"nodeType": 1,
		"sort": 0,
		"linkUrl": "home/UserManage",
		"iconId": "8",
		"level": 0,
		"path": "",
		"createTime": "2023-07-08 19:37:26",
		"isClose": 1,
		"children": [{
			"id": "201",
			"name": "后臺用戶管理",
			"menuCode": "system_user_manage",
			"parentId": "2",
			"nodeType": 2,
			"sort": 1,
			"linkUrl": "home/content/UserManage/System/index.vue",
			"iconId": "3",
			"level": 1,
			"path": "systemuser",
			"createTime": "2023-07-08 20:02:04",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "202",
			"name": "商家管理",
			"menuCode": "business_user_manage",
			"parentId": "2",
			"nodeType": 2,
			"sort": 1,
			"linkUrl": "home/content/UserManage/Business/index.vue",
			"iconId": "3",
			"level": 1,
			"path": "businessuser",
			"createTime": "2023-07-08 20:04:40",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "203",
			"name": "前臺會員管理",
			"menuCode": "member_user_manage",
			"parentId": "2",
			"nodeType": 2,
			"sort": 1,
			"linkUrl": "home/content/UserManage/Member/index.vue",
			"iconId": "3",
			"level": 1,
			"path": "memberuser",
			"createTime": "2023-07-08 20:07:20",
			"isClose": 1,
			"children": [{
				"id": "20301",
				"name": "新增前臺用戶",
				"menuCode": "member_user_add",
				"parentId": "203",
				"nodeType": 3,
				"sort": 2,
				"linkUrl": null,
				"iconId": "3",
				"level": 2,
				"path": "",
				"createTime": "2023-07-18 10:30:56",
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}, {
				"id": "20302",
				"name": "刪除用戶",
				"menuCode": "member_user_delete",
				"parentId": "203",
				"nodeType": 3,
				"sort": 2,
				"linkUrl": null,
				"iconId": "3",
				"level": 2,
				"path": "\n",
				"createTime": null,
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}],
			"iconInfo": null
		}, {
			"id": "20303",
			"name": "用戶新增",
			"menuCode": "user_add",
			"parentId": "2",
			"nodeType": 3,
			"sort": null,
			"linkUrl": null,
			"iconId": null,
			"level": null,
			"path": "",
			"createTime": "2023-08-24 21:06:30",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20304",
			"name": "用戶修改",
			"menuCode": "user_update",
			"parentId": "2",
			"nodeType": 3,
			"sort": null,
			"linkUrl": null,
			"iconId": null,
			"level": null,
			"path": null,
			"createTime": "2023-08-24 21:25:00",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}],
		"iconInfo": null
	}, {
		"id": "3",
		"name": "字典管理",
		"menuCode": "dictionary_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/DictionaryManage/index.vue",
		"iconId": "9",
		"level": 0,
		"path": "dictionary",
		"createTime": "2023-07-13 16:45:54",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "4",
		"name": "菜單管理",
		"menuCode": "menu_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/MenuManage/index.vue",
		"iconId": "3",
		"level": 0,
		"path": "menu",
		"createTime": "2023-07-08 19:05:18",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "5",
		"name": "店鋪管理",
		"menuCode": "store_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/ShopManage/index.vue",
		"iconId": "6",
		"level": 0,
		"path": "shop",
		"createTime": "2023-07-13 16:29:38",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "6",
		"name": "商品管理",
		"menuCode": "goods_manage",
		"parentId": "0",
		"nodeType": 1,
		"sort": 0,
		"linkUrl": "",
		"iconId": "5",
		"level": 0,
		"path": "goods",
		"createTime": "2023-07-13 16:42:17",
		"isClose": 1,
		"children": [{
			"id": "20305",
			"name": "商品發(fā)布",
			"menuCode": "goods_add",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/AddGoods.vue",
			"iconId": null,
			"level": null,
			"path": "addgoods",
			"createTime": "2023-08-26 14:39:21",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20307",
			"name": "分類管理",
			"menuCode": "category_manage",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/CategoryManage.vue",
			"iconId": null,
			"level": null,
			"path": "category",
			"createTime": "2023-08-28 11:49:34",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20308",
			"name": "屬性分組",
			"menuCode": "attrGroup",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/AttrGroup",
			"iconId": null,
			"level": null,
			"path": "attr-group",
			"createTime": "2023-09-09 10:09:52",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20310",
			"name": "規(guī)格參數(shù)",
			"menuCode": "attribute_manage",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/AttributeManage.vue",
			"iconId": null,
			"level": null,
			"path": "attribute",
			"createTime": "2023-09-12 22:45:52",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20311",
			"name": "spu管理",
			"menuCode": "spu-manage",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/SpuManage.vue",
			"iconId": null,
			"level": null,
			"path": "spu-manage",
			"createTime": "2023-10-05 22:07:56",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20312",
			"name": "sku管理",
			"menuCode": "sku-manage",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/SkuManage.vue",
			"iconId": null,
			"level": null,
			"path": "sku-manage",
			"createTime": "2023-10-08 20:41:06",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}],
		"iconInfo": null
	}, {
		"id": "7",
		"name": "訂單管理",
		"menuCode": "order_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/OrderManage/index.vue",
		"iconId": "10",
		"level": 0,
		"path": "order",
		"createTime": "2023-07-13 16:43:15",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "8",
		"name": "庫存系統(tǒng)",
		"menuCode": "warehouse_system",
		"parentId": "0",
		"nodeType": 1,
		"sort": 0,
		"linkUrl": "",
		"iconId": "4",
		"level": 0,
		"path": "warehouse",
		"createTime": "2023-07-13 16:44:36",
		"isClose": 1,
		"children": [{
			"id": "20313",
			"name": "商品庫存",
			"menuCode": "goods_inventory",
			"parentId": "8",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/WarehouseManage/Inventory.vue",
			"iconId": null,
			"level": null,
			"path": "goods_inventory",
			"createTime": "2023-10-11 20:42:12",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20314",
			"name": "倉庫維護(hù)",
			"menuCode": "warehouse_manage",
			"parentId": "8",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/WarehouseManage/Warehouse.vue",
			"iconId": null,
			"level": null,
			"path": "warehouse",
			"createTime": "2023-10-13 18:00:55",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20315",
			"name": "采購單維護(hù)",
			"menuCode": "procurement",
			"parentId": "8",
			"nodeType": 1,
			"sort": null,
			"linkUrl": null,
			"iconId": null,
			"level": null,
			"path": null,
			"createTime": "2023-10-14 11:06:24",
			"isClose": 1,
			"children": [{
				"id": "20316",
				"name": "采購需求",
				"menuCode": "purchase-detail",
				"parentId": "20315",
				"nodeType": 2,
				"sort": null,
				"linkUrl": "home/content/WarehouseManage/ProcurementManage/PurchaseDetail.vue",
				"iconId": null,
				"level": null,
				"path": "purchase-detail",
				"createTime": "2023-10-14 20:50:05",
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}, {
				"id": "20317",
				"name": "采購單",
				"menuCode": "purchase",
				"parentId": "20315",
				"nodeType": 2,
				"sort": null,
				"linkUrl": "home/content/WarehouseManage/ProcurementManage/Purchase.vue",
				"iconId": null,
				"level": null,
				"path": "purchase",
				"createTime": "2023-10-14 20:52:59",
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}, {
				"id": "20318",
				"name": "我的采購單",
				"menuCode": "my-purchase",
				"parentId": "20315",
				"nodeType": 2,
				"sort": null,
				"linkUrl": "home/content/WarehouseManage/ProcurementManage/MyPurchase.vue",
				"iconId": null,
				"level": null,
				"path": "my-purchase",
				"createTime": "2023-10-17 10:57:03",
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}],
			"iconInfo": null
		}],
		"iconInfo": null
	}, {
		"id": "9",
		"name": "文件管理",
		"menuCode": "file_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/FilesManage/index.vue",
		"iconId": "7",
		"level": 0,
		"path": "files",
		"createTime": "2023-07-28 12:25:50",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "10",
		"name": "角色管理",
		"menuCode": "role_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/RoleManage/index.vue",
		"iconId": "2",
		"level": null,
		"path": "role",
		"createTime": "2023-07-31 16:21:36",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "20306",
		"name": "品牌管理",
		"menuCode": "brand_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": null,
		"linkUrl": "home/content/Brand/BrandManage",
		"iconId": null,
		"level": null,
		"path": "brand",
		"createTime": "2023-08-28 09:45:03",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}]
}

還記得之前我們菜單點(diǎn)擊哪里嗎?那里只實(shí)現(xiàn)了添加標(biāo)簽頁的功能還沒有實(shí)現(xiàn)切換的功能,我們現(xiàn)在只需要在點(diǎn)擊事件里面添加切換激活值就行了

后端響應(yīng)的菜單數(shù)據(jù)
{
	"menuPermission": [{
		"id": "1",
		"name": "首頁",
		"menuCode": "homepage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/index.vue",
		"iconId": "1",
		"level": 0,
		"path": "homepage",
		"createTime": null,
		"isClose": 0,
		"children": [],
		"iconInfo": null
	}, {
		"id": "2",
		"name": "用戶管理",
		"menuCode": "user_manage",
		"parentId": "0",
		"nodeType": 1,
		"sort": 0,
		"linkUrl": "home/UserManage",
		"iconId": "8",
		"level": 0,
		"path": "",
		"createTime": "2023-07-08 19:37:26",
		"isClose": 1,
		"children": [{
			"id": "201",
			"name": "后臺用戶管理",
			"menuCode": "system_user_manage",
			"parentId": "2",
			"nodeType": 2,
			"sort": 1,
			"linkUrl": "home/content/UserManage/System/index.vue",
			"iconId": "3",
			"level": 1,
			"path": "systemuser",
			"createTime": "2023-07-08 20:02:04",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "202",
			"name": "商家管理",
			"menuCode": "business_user_manage",
			"parentId": "2",
			"nodeType": 2,
			"sort": 1,
			"linkUrl": "home/content/UserManage/Business/index.vue",
			"iconId": "3",
			"level": 1,
			"path": "businessuser",
			"createTime": "2023-07-08 20:04:40",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "203",
			"name": "前臺會員管理",
			"menuCode": "member_user_manage",
			"parentId": "2",
			"nodeType": 2,
			"sort": 1,
			"linkUrl": "home/content/UserManage/Member/index.vue",
			"iconId": "3",
			"level": 1,
			"path": "memberuser",
			"createTime": "2023-07-08 20:07:20",
			"isClose": 1,
			"children": [{
				"id": "20301",
				"name": "新增前臺用戶",
				"menuCode": "member_user_add",
				"parentId": "203",
				"nodeType": 3,
				"sort": 2,
				"linkUrl": null,
				"iconId": "3",
				"level": 2,
				"path": "",
				"createTime": "2023-07-18 10:30:56",
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}, {
				"id": "20302",
				"name": "刪除用戶",
				"menuCode": "member_user_delete",
				"parentId": "203",
				"nodeType": 3,
				"sort": 2,
				"linkUrl": null,
				"iconId": "3",
				"level": 2,
				"path": "\n",
				"createTime": null,
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}],
			"iconInfo": null
		}, {
			"id": "20303",
			"name": "用戶新增",
			"menuCode": "user_add",
			"parentId": "2",
			"nodeType": 3,
			"sort": null,
			"linkUrl": null,
			"iconId": null,
			"level": null,
			"path": "",
			"createTime": "2023-08-24 21:06:30",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20304",
			"name": "用戶修改",
			"menuCode": "user_update",
			"parentId": "2",
			"nodeType": 3,
			"sort": null,
			"linkUrl": null,
			"iconId": null,
			"level": null,
			"path": null,
			"createTime": "2023-08-24 21:25:00",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}],
		"iconInfo": null
	}, {
		"id": "3",
		"name": "字典管理",
		"menuCode": "dictionary_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/DictionaryManage/index.vue",
		"iconId": "9",
		"level": 0,
		"path": "dictionary",
		"createTime": "2023-07-13 16:45:54",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "4",
		"name": "菜單管理",
		"menuCode": "menu_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/MenuManage/index.vue",
		"iconId": "3",
		"level": 0,
		"path": "menu",
		"createTime": "2023-07-08 19:05:18",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "5",
		"name": "店鋪管理",
		"menuCode": "store_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/ShopManage/index.vue",
		"iconId": "6",
		"level": 0,
		"path": "shop",
		"createTime": "2023-07-13 16:29:38",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "6",
		"name": "商品管理",
		"menuCode": "goods_manage",
		"parentId": "0",
		"nodeType": 1,
		"sort": 0,
		"linkUrl": "",
		"iconId": "5",
		"level": 0,
		"path": "goods",
		"createTime": "2023-07-13 16:42:17",
		"isClose": 1,
		"children": [{
			"id": "20305",
			"name": "商品發(fā)布",
			"menuCode": "goods_add",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/AddGoods.vue",
			"iconId": null,
			"level": null,
			"path": "addgoods",
			"createTime": "2023-08-26 14:39:21",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20307",
			"name": "分類管理",
			"menuCode": "category_manage",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/CategoryManage.vue",
			"iconId": null,
			"level": null,
			"path": "category",
			"createTime": "2023-08-28 11:49:34",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20308",
			"name": "屬性分組",
			"menuCode": "attrGroup",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/AttrGroup",
			"iconId": null,
			"level": null,
			"path": "attr-group",
			"createTime": "2023-09-09 10:09:52",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20310",
			"name": "規(guī)格參數(shù)",
			"menuCode": "attribute_manage",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/AttributeManage.vue",
			"iconId": null,
			"level": null,
			"path": "attribute",
			"createTime": "2023-09-12 22:45:52",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20311",
			"name": "spu管理",
			"menuCode": "spu-manage",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/SpuManage.vue",
			"iconId": null,
			"level": null,
			"path": "spu-manage",
			"createTime": "2023-10-05 22:07:56",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20312",
			"name": "sku管理",
			"menuCode": "sku-manage",
			"parentId": "6",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/GoodsManage/SkuManage.vue",
			"iconId": null,
			"level": null,
			"path": "sku-manage",
			"createTime": "2023-10-08 20:41:06",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}],
		"iconInfo": null
	}, {
		"id": "7",
		"name": "訂單管理",
		"menuCode": "order_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/OrderManage/index.vue",
		"iconId": "10",
		"level": 0,
		"path": "order",
		"createTime": "2023-07-13 16:43:15",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "8",
		"name": "庫存系統(tǒng)",
		"menuCode": "warehouse_system",
		"parentId": "0",
		"nodeType": 1,
		"sort": 0,
		"linkUrl": "",
		"iconId": "4",
		"level": 0,
		"path": "warehouse",
		"createTime": "2023-07-13 16:44:36",
		"isClose": 1,
		"children": [{
			"id": "20313",
			"name": "商品庫存",
			"menuCode": "goods_inventory",
			"parentId": "8",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/WarehouseManage/Inventory.vue",
			"iconId": null,
			"level": null,
			"path": "goods_inventory",
			"createTime": "2023-10-11 20:42:12",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20314",
			"name": "倉庫維護(hù)",
			"menuCode": "warehouse_manage",
			"parentId": "8",
			"nodeType": 2,
			"sort": null,
			"linkUrl": "home/content/WarehouseManage/Warehouse.vue",
			"iconId": null,
			"level": null,
			"path": "warehouse",
			"createTime": "2023-10-13 18:00:55",
			"isClose": 1,
			"children": [],
			"iconInfo": null
		}, {
			"id": "20315",
			"name": "采購單維護(hù)",
			"menuCode": "procurement",
			"parentId": "8",
			"nodeType": 1,
			"sort": null,
			"linkUrl": null,
			"iconId": null,
			"level": null,
			"path": null,
			"createTime": "2023-10-14 11:06:24",
			"isClose": 1,
			"children": [{
				"id": "20316",
				"name": "采購需求",
				"menuCode": "purchase-detail",
				"parentId": "20315",
				"nodeType": 2,
				"sort": null,
				"linkUrl": "home/content/WarehouseManage/ProcurementManage/PurchaseDetail.vue",
				"iconId": null,
				"level": null,
				"path": "purchase-detail",
				"createTime": "2023-10-14 20:50:05",
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}, {
				"id": "20317",
				"name": "采購單",
				"menuCode": "purchase",
				"parentId": "20315",
				"nodeType": 2,
				"sort": null,
				"linkUrl": "home/content/WarehouseManage/ProcurementManage/Purchase.vue",
				"iconId": null,
				"level": null,
				"path": "purchase",
				"createTime": "2023-10-14 20:52:59",
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}, {
				"id": "20318",
				"name": "我的采購單",
				"menuCode": "my-purchase",
				"parentId": "20315",
				"nodeType": 2,
				"sort": null,
				"linkUrl": "home/content/WarehouseManage/ProcurementManage/MyPurchase.vue",
				"iconId": null,
				"level": null,
				"path": "my-purchase",
				"createTime": "2023-10-17 10:57:03",
				"isClose": 1,
				"children": [],
				"iconInfo": null
			}],
			"iconInfo": null
		}],
		"iconInfo": null
	}, {
		"id": "9",
		"name": "文件管理",
		"menuCode": "file_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/FilesManage/index.vue",
		"iconId": "7",
		"level": 0,
		"path": "files",
		"createTime": "2023-07-28 12:25:50",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "10",
		"name": "角色管理",
		"menuCode": "role_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": 0,
		"linkUrl": "home/content/RoleManage/index.vue",
		"iconId": "2",
		"level": null,
		"path": "role",
		"createTime": "2023-07-31 16:21:36",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}, {
		"id": "20306",
		"name": "品牌管理",
		"menuCode": "brand_manage",
		"parentId": "0",
		"nodeType": 2,
		"sort": null,
		"linkUrl": "home/content/Brand/BrandManage",
		"iconId": null,
		"level": null,
		"path": "brand",
		"createTime": "2023-08-28 09:45:03",
		"isClose": 1,
		"children": [],
		"iconInfo": null
	}]
}

源碼地址

我的畢業(yè)設(shè)計(jì): 后面再說

進(jìn)入這個(gè)倉庫找到

到此這篇關(guān)于vue3+element-plus實(shí)現(xiàn)動(dòng)態(tài)菜單和動(dòng)態(tài)路由動(dòng)態(tài)按鈕(前后端分離)的文章就介紹到這了,更多相關(guān)vue3 element-plus動(dòng)態(tài)菜單內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue 組件中添加樣式不生效的解決方法

    vue 組件中添加樣式不生效的解決方法

    這篇文章主要介紹了vue 組件中添加樣式不生效的解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-07-07
  • 如何通過shell腳本自動(dòng)生成vue文件詳解

    如何通過shell腳本自動(dòng)生成vue文件詳解

    這篇文章主要給大家介紹了關(guān)于如何通過shell腳本自動(dòng)生成vue文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Vue如何通過Vue.prototype定義原型屬性實(shí)現(xiàn)全局變量

    Vue如何通過Vue.prototype定義原型屬性實(shí)現(xiàn)全局變量

    在Vue.js開發(fā)中,通過原型屬性為Vue實(shí)例添加全局變量是一種常見做法,使用$前綴命名,可以避免與組件內(nèi)部的數(shù)據(jù)、方法或計(jì)算屬性產(chǎn)生命名沖突,這種方式簡單有效,確保了變量在所有Vue實(shí)例中的可用性,同時(shí)保持全局作用域的整潔
    2024-10-10
  • Vue 3開發(fā)中VueUse強(qiáng)大Hooks庫

    Vue 3開發(fā)中VueUse強(qiáng)大Hooks庫

    VueUse提供了一個(gè)豐富且強(qiáng)大的Hooks庫,可以幫助開發(fā)者快速實(shí)現(xiàn)各種功能,提高開發(fā)效率,本文來詳細(xì)的介紹一下,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • 解決vux 中popup 組件Mask 遮罩在最上層的問題

    解決vux 中popup 組件Mask 遮罩在最上層的問題

    這篇文章主要介紹了解決vux 中popup 組件Mask 遮罩在最上層的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • vue項(xiàng)目實(shí)現(xiàn)添加圖片裁剪組件

    vue項(xiàng)目實(shí)現(xiàn)添加圖片裁剪組件

    這篇文章主要為大家詳細(xì)介紹了vue項(xiàng)目實(shí)現(xiàn)添加圖片裁剪組件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Vue實(shí)現(xiàn)導(dǎo)出excel表格功能

    Vue實(shí)現(xiàn)導(dǎo)出excel表格功能

    這篇文章主要介紹了Vue實(shí)現(xiàn)導(dǎo)出excel表格的功能,在文章末尾給大家提到了vue中excel表格的導(dǎo)入和導(dǎo)出代碼,需要的朋友可以參考下
    2018-03-03
  • vue嵌入第三方頁面幾種常見方法

    vue嵌入第三方頁面幾種常見方法

    在Vue中嵌入第三方頁面可以采用多種方法,例如使用<iframe>、Vue插件、動(dòng)態(tài)加載第三方腳本或WebComponents,不同方法適用于不同類型的內(nèi)容和項(xiàng)目需求,如<iframe>適用于整個(gè)網(wǎng)頁,而動(dòng)態(tài)腳本和WebComponents適合特定功能,選擇合適的方法可以有效整合外部資源
    2024-09-09
  • Vue 兄弟組件通信的方法(不使用Vuex)

    Vue 兄弟組件通信的方法(不使用Vuex)

    本篇文章主要介紹了Vue 兄弟組件通信的方法(不使用Vuex),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-10-10
  • Vue自定義彈窗指令的實(shí)現(xiàn)代碼

    Vue自定義彈窗指令的實(shí)現(xiàn)代碼

    使用vue2.0實(shí)現(xiàn)自定義彈窗指令,當(dāng)標(biāo)簽有該指令時(shí),點(diǎn)擊標(biāo)簽可以彈出彈窗。下面通過實(shí)例代碼給大家介紹Vue自定義彈窗指令的相關(guān)知識,感興趣的朋友一起看看吧
    2018-08-08

最新評論