前言:
項目中,當(dāng)每一個角色得到的界面不一致的時候,我們就不能使用靜態(tài)菜單了,而是要從后端得到動態(tài)的菜單數(shù)據(jù),然后動態(tài)的將菜單數(shù)據(jù)展示在界面上。
除了在界面展示,也還要將界面的路由動態(tài)添加,在路由動態(tài)添加之后,你可能會出現(xiàn)刷新界面,界面變白的情況,頁面刷新白屏其實是因為vuex引起的,由于刷新頁面vuex數(shù)據(jù)會丟失,所以動態(tài)添加路由這一步也就失效了。這種情況我會在最后給一個處理方法。
所以這個博客會做兩個部分的處理:
- ????????動態(tài)菜單生成
- ? ? ? ? 動態(tài)添加路由
?
?
動態(tài)菜單生成
1.獲得后端數(shù)據(jù)(有mock模擬數(shù)據(jù),也可以使用后端接口)
1.1使用mock得到模擬數(shù)據(jù)
沒有下載mock的可以查看:Vue項目中使用mockjs實現(xiàn)mock模擬數(shù)據(jù) - ykCoder - 博客園 (cnblogs.com)
mock/modules/menu.js? 保存模擬的后端數(shù)據(jù)
function list(res) {
// res是一個請求對象,包含: url, type, body
return {
code: 200,
message: "請求成功",
//菜單數(shù)據(jù),可以修改成你自己要的菜單
data: [
{
id: "600d4075e218daaf4ec77e50",
menuType: "1",
menuName: "首頁",
path: "/Home",
icon: "house",
},
{
id: "600d4075e218daaf4ec77e51",
menuType: "1",
menuName: "公司管理",
path: "/company",
icon: "location",
children: [
{
id: "600d525e602f452aaeeffcd9",
menuType: "1",
menuName: "公司資料",
path: "/company/Company",
},
{
id: "601bc4f8a794e23c2e42efa9",
menuType: "1",
menuName: "個人資料",
path: "/company/Person",
},
],
},
};
}
//暴露list
export default { list };
mock/index.js? 引入mock/menu.js
// 引入mockjs
import Mock from 'mockjs'
// 引入模板函數(shù)類
import menu from './modules/menu'
// Mock函數(shù)
const { mock } = Mock
// 設(shè)置延時
Mock.setup({
timeout: 400
})
// 使用攔截規(guī)則攔截命中的請求,mock(url, post/get, 返回的數(shù)據(jù));
Mock.mock('/mock/menu', 'get', menu.list)
在界面引用
<script>
export default {
data() {
return {
menuData:[],
};
},
methods: {
getMenu() {
this.$http.get('/mock/menu').then((res) => {
console.log(res)
if (res.data.code === 200) {
this.menuData = res.data.data;
// console.log(this.menuData,"menuData")
//獲取菜單的數(shù)據(jù),存入store中
this.$store.commit("setMenu",this.menuData)
//動態(tài)生成路由
this.$store.commit("addMenu",this.$router)
}
})
},
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
},
};
</script>
1.2連接后端接口(統(tǒng)一接口管理),從后端得到菜單數(shù)據(jù)
如果對統(tǒng)一接口管理,有不明白的可以查看:
016-尚硅谷-尚品匯-API接口統(tǒng)一管理_嗶哩嗶哩_bilibili??????
Vue封裝接口思路(包括請求(響應(yīng)攔截器))_vue接口封裝_憂郁火龍果的博客-CSDN博客
api/request.js
import axios from 'axios';
//1.利用axios對象的方法create,去創(chuàng)建一個axios實例。
const requests = axios.create({
//配置對象
//接口當(dāng)中:路徑都帶有/api 基礎(chǔ)路徑,發(fā)送請求的時候,路徑當(dāng)中會出現(xiàn)api
baseURL:"/api",
//代表請求超時的時間
timeout:5000,
})
//請求攔截器:
requests.interceptors.request.use((config) =>{
//config:配置對象,對象里面有一個屬性很重要,header請求頭
return config;
})
//響應(yīng)攔截器
requests.interceptors.response.use((res)=>{
//成功的回調(diào)函數(shù):服務(wù)器相應(yīng)數(shù)據(jù)回來以后,響應(yīng)攔截器可以檢測,可以做一些事情
return res.data;
},(error)=>{
//失敗的回調(diào)函數(shù)
return Promise.reject(new Error('faile'));
})
//對外暴露
export default requests;
api/menu.js
import requests from "./request";
export const menuList = (data) => {
return requests({
url: "/user/menus",
method: 'GET',
data: data,
});
};
在界面引用
<script>
import { menuList } from "@/api/menu.js";
export default {
data() {
return {
menuData:[],
};
},
methods: {
getMenu() {
const id = 1;
//假數(shù)據(jù)
const res = menuList(id);
console.log(res.data);
if (res.code == 200) {//獲得菜單導(dǎo)航數(shù)據(jù)
this.menuData = res;
} else {//沒有獲得菜單數(shù)據(jù)
}
},
},
};
</script>
2.接收界面數(shù)據(jù),實現(xiàn)動態(tài)界面
2.1界面實現(xiàn)
我的菜單界面是用兩個vue文件寫的。組件間的傳值要用vuex,這里建議去看一下官網(wǎng)學(xué)習(xí)一下。
組件間的傳值:Vue組件之間的傳值 - 掘金 (juejin.cn)
HomeMenu.vue?
這里實現(xiàn)二級菜單用的是遞歸的方法,這個是我覺得很神奇的地方,我第一次在vue中使用到了遞歸。在此之前我覺得vue就只能實現(xiàn)做界面的功能,沒有想到過vue也可以這么靈活。
<home-menu :menuData="item.children" />//在組件中調(diào)用組件本身,使用遞歸的方法實現(xiàn)二級目錄。
全部代碼?
<template>
<div>
<template v-for="item in menuData" :key="item">
<el-sub-menu
:index="item.id"
v-if="
item.children &&
item.children.length > 0 &&
item.children[0].menuType.toString() === '1'
"
>
<template #title>
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.menuName }}</span>
</template>
<home-menu :menuData="item.children" />
</el-sub-menu>
<el-menu-item
@click="clickMenu(item)"
v-else-if="item.menuType.toString() === '1'"
:index="item.path"
:key="item.id"
>
<template #title>
<el-icon><component :is="item.icon" /></el-icon>
<span>{{ item.menuName }}</span>
</template>
</el-menu-item>
</template>
</div>
</template>
<script>
export default {
name: "home-menu",
//為了實現(xiàn)組件間的傳值
props: ["menuData"],
methods: {
//點擊菜單
clickMenu(item) {
console.log("item:" + item);
//當(dāng)前路由與跳轉(zhuǎn)路由不一致時跳轉(zhuǎn)
if (this.$route.path !== item.path && !(this.$route.path === '/home' && (item.path === '/'))) {
this.$router.push(item.path);
}
},
},
};
</script>
HomeAside.vue
<template>
<div>
<el-menu
active-text-color="#ffd04b"
background-color="#545c64"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
@open="handleOpen"
@close="handleClose"
>
<h3>{{ isCollapse ? "排班" : "智能排班系統(tǒng)" }}</h3>
<home-menu :menuData="menuData"></home-menu>
</el-menu>
</div>
</template>
<script>
import HomeMenu from "@/components/menu/HomeMenu.vue";
import { thisTypeAnnotation } from "@babel/types";
import { mapState } from 'vuex';
export default {
components: {
"home-menu": HomeMenu,
},
data() {
return {
menuData:[],
};
},
mounted() {
//獲得菜單
this.getMenu();
},
computed: {
//給store傳遞menuData的值
...mapState({
menuData: (state) => state.menu.menuData
}),
},
methods: {
getMenu() {
this.$http.get('/mock/menu').then((res) => {
console.log(res)
if (res.data.code === 200) {
this.menuData = res.data.data;
// console.log(this.menuData,"menuData")
//獲取菜單的數(shù)據(jù),存入store中
this.$store.commit("setMenu",this.menuData)
//動態(tài)生成路由
this.$store.commit("addMenu",this.$router)
}
})
},
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
},
},
};
</script>
<style lang="less" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
.el-menu {
height: 100vh;
border-right: none;
h3 {
color: #fff;
text-align: center;
line-height: 48px;
font-size: 16px;
font-weight: 400px;
}
}
</style>
2.2組價間傳值的使用
store/menu.js??這里的addMenu函數(shù)值實現(xiàn)動態(tài)路由的關(guān)鍵,在下面會有分析
export default {
state: {
// 動態(tài)菜單
menuData: [],
},
//修改字段
mutations: {
//設(shè)置菜單的數(shù)據(jù)
setMenu(state, val) {
state.menuData = val;
},
//動態(tài)注冊路由
addMenu(state, router) {
// 處理動態(tài)路由的數(shù)據(jù)
const menuData = JSON.parse(JSON.stringify(state.menuData));
const menuArray = [];
menuData.forEach((item) => {
if (item.children && item.children.length >= 1) {
menuArray.push(...item.children);
} else {
menuArray.push(item);
}
});
console.log(menuArray, "menuArray");
// 路由的動態(tài)添加
if (menuArray[0] !== "") {
menuArray.forEach((item) => {
router.addRoute("main", { path: `${item.path}`,component: () => import(`@/views${item.path}.vue`) });
});
}
},
},
};
menu/index.js
import { createStore } from 'vuex'
import createPersistedState from "vuex-persistedstate"
import menu from './menu'
export default createStore({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
menu
},
/* vuex數(shù)據(jù)持久化配置 */
plugins: [
createPersistedState({
// 存儲方式:localStorage、sessionStorage、cookies
storage: window.sessionStorage,
// 存儲的 key 的key值
key: "store",
reducer(state) { //render錯誤修改
// 要存儲的數(shù)據(jù):本項目采用es6擴(kuò)展運算符的方式存儲了state中所有的數(shù)據(jù)
return { ...state };
}
})
]
})
3.實現(xiàn)動態(tài)路由
3.1實現(xiàn)動態(tài)路由的代碼分析
上面的代碼已經(jīng)實現(xiàn)了動態(tài)路由,這里是解釋一下動態(tài)路由的關(guān)鍵上面,網(wǎng)上關(guān)于動態(tài)路由的代碼很多,但是對于第一次做動態(tài)路由的人來說,想要去看懂事有點難度的。
創(chuàng)建一個新的空數(shù)組,遍歷menuData的時候根據(jù)元素有沒有children來分別處理,將需要的數(shù)據(jù)保存到新數(shù)組中,通過傳入的path路徑來添加路由。
最重要的部分來了,vue router4的版本不再使用router.addRoutes而是router.addRoute,這個地方建議看官方文檔
動態(tài)路由 | Vue Router (vuejs.org)
router.addRoute("main", { path: `${item.path}`,component: () => import(`@/views${item.path}.vue`) });
//動態(tài)注冊路由
addMenu(state, router) {
// 處理動態(tài)路由的數(shù)據(jù)
const menuData = JSON.parse(JSON.stringify(state.menuData));
const menuArray = [];
menuData.forEach((item) => {
if (item.children && item.children.length >= 1) {
menuArray.push(...item.children);
} else {
menuArray.push(item);
}
});
console.log(menuArray, "menuArray");
// 路由的動態(tài)添加
if (menuArray[0] !== "") {
menuArray.forEach((item) => {
router.addRoute("main", { path: `${item.path}`,component: () => import(`@/views${item.path}.vue`) });
});
}
},
下面貼一下vue2項目的寫法,這里我使用時先把元素中添加component屬性,這里和上面的寫法有點不一樣,但是我建議還是用上面的好一點,這個寫法可能會出bug。
item.component = (resolve) => require([`@/views/home/${item.url}`], resolve)
//動態(tài)注冊路由
addMenu(state, router) {
// 處理動態(tài)路由的數(shù)據(jù)
const menuArray = []
state.menuData.forEach(item => {
if (item.children) {
item.children = item.children.map(item => {
item.component = (resolve) => require([`@/views/home/${item.url}`], resolve)
return item
})
menuArray.push(...item.children)
} else {
item.component = (resolve) => require([`@/views/home/${item.url}`], resolve)
menuArray.push(item)
}
})
console.log(menuArray, 'menuArray')
// 路由的動態(tài)添加
menuArray.forEach(item => {
router.addRoute('main', item)
})
},
還有一個處理方法,你可以在傳過來的數(shù)據(jù)中就傳path,component的值,最后直接使用
router.addRoute() 調(diào)用就行了。
在HomeAside.vue界面之間的引用
//獲取菜單的數(shù)據(jù),存入store中
this.$store.commit("setMenu",this.menuData)
//動態(tài)生成路由
this.$store.commit("addMenu",this.$router)
3.2刷新界面后,白屏處理
恭喜你,來到最后一步,在最前面的時候說過,白屏問題是因為vuex引起的,由于刷新頁面vuex數(shù)據(jù)會丟失,所以動態(tài)添加路由這一步也就失效了。我在網(wǎng)上找了很多方法都沒有解決,最后回歸到問題的本質(zhì),刷新界面vuex的數(shù)據(jù)會丟失,那么我們不讓數(shù)據(jù)丟失不就行了,我的處理方法是在
mian.ts中再保存一遍路由的數(shù)據(jù)。
這個是vue3的處理:
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import * as ElIconModules from "@element-plus/icons";
import '@/mock';
import axios from 'axios';
import VueAxios from 'vue-axios';
//動態(tài)菜單路由的生成
const addMenu = () => {
store.commit("addMenu",router)
}
addMenu()
const app = createApp(App);
app.use(store).use(router).use(ElementPlus).use(VueAxios,axios).mount("#app");
// 統(tǒng)一注冊Icon圖標(biāo)
for (const iconName in ElIconModules) {
if (Reflect.has(ElIconModules, iconName)) {
const item = ElIconModules[iconName];
app.component(iconName, item);
}
}
這個是vue2的處理:文章來源:http://www.zghlxwxcb.cn/news/detail-737906.html
import Vue from 'vue';
import router from './router'
import store from './store'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI);
import axios from 'axios'
//配置請求根路徑
axios.defaults.baseURL = "http://localhost:8088"
//將axios作為全局的自定義屬性,每個組件可以在內(nèi)部組件訪問
Vue.prototype.$http = axios
//添加全局前置導(dǎo)航守衛(wèi)
router.beforeEach((to, from, next) => {
//判斷token是否存在
// localStorage.setItem("token", message.data.token);
const token = localStorage.getItem("token");
// localStorage.clear();
console.log(token,'token')
if( !token && to.name !== 'login' ){//token不存在,沒有登錄
next({ name : 'login' })
} else {
next();
}
})
new Vue({
router,
store,
el: '#app',
created() {
store.commit('addMenu',router)
},
render: h => h(App)
});
好了,最后希望大家都能看懂,如果有什么問題可以提出來。文章來源地址http://www.zghlxwxcb.cn/news/detail-737906.html
到了這里,關(guān)于vue3實現(xiàn)動態(tài)菜單和動態(tài)路由和刷新后白屏處理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!