vue搭建一个树形菜单项目
首先搭建项目需要先通过步骤搭建一个vue的项目,然后创建一个component文件,里面新建一个index.vue页面来。
这是引入的element-ui组件库里的组件,来实现我的路由,渲染的是我存储的动态路由,所以需要先安装并且引用。
npm install element-plus @element-plus/icons-vue
这是菜单管理的
<template>
<div class="sidebar-container">
<!-- 折叠控制 -->
<div class="collapse-control" style="background-color:#293246" @click="toggleCollapse">
<el-icon :size="20" :color="isCollapse ? '#fff' : '#ffd04b'">
<component :is="isCollapse ? Expand : Fold" />
</el-icon>
</div>
<!-- 导航菜单 -->
<div>
<el-menu router :default-active="$route.path" background-color="#293246" text-color="#fff" style="height: 100vh"
active-text-color="#ffd04b" :collapse="isCollapse">
<template v-for="menu in menuArray" :key="menu.path">
<!-- 有子级的多层菜单项 -->
<el-sub-menu v-if="menu.children?.length" index="/layout">
<template #title>
<span>{{ menu.meta.title }}</span>
</template>
<el-menu-item v-for="sub in menu.children" :key="'/layout' + sub.path" :index="'/layout' + sub.path">
<span>{{ sub.title }}</span>
</el-menu-item>
</el-sub-menu>
<!-- 没子级的单层菜单项 -->
<el-menu-item v-else :index="menu.path == '/home' ? menu.path : menu.path">
<span>{{ menu.meta.title }}</span>
</el-menu-item>
</template>
</el-menu>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Expand, Fold } from '@element-plus/icons-vue'
const isCollapse = ref(false)
const toggleCollapse = () => {
isCollapse.value = !isCollapse.value
}
const menuArray = ref([]);
render();//初始化渲染
function render() {
try {
const menuList = JSON.parse(sessionStorage.getItem('menuPath') || '[]');
const menuName = JSON.parse(sessionStorage.getItem('menuName') || '[]');
console.log(menuList, menuName);
if (!Array.isArray(menuList) || !Array.isArray(menuName)) {
throw new Error('存储数据格式不正确');
}
const nameMap = new Map(menuName.map(item => [item.name, item]));
menuArray.value = menuList
.filter(item => item?.name && nameMap.has(item.name))
.map(item => ({
...item,
...nameMap.get(item.name)
}));
console.log('安全筛选结果:', menuArray.value);
} catch (error) {
console.error('数据处理失败:', error);
// 可以在这里设置默认值或进行错误上报
return [];
}
}
</script>
<style scoped>
.sidebar-container {
transition: width 0.3s;
}
.collapse-control {
padding: 15px;
cursor: pointer;
border-bottom: 1px solid #1f2d3d;
}
.el-menu--collapse {
width: 64px;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
}
</style>
组件面包屑
<template>
<!-- <div style="margin-bottom: 20px;width: 100%;">
<el-button size="small" @click="addTab(editableTabsValue)">
添加标签页
</el-button>
</div> -->
<el-tabs v-model="editableTabsValue" @tab-click="tabBread" type="card" @tab-remove="removeTab">
<el-tab-pane v-for="item in editableTabs" :key="item.name" :label="item.title"
:name="item.name" :closable="item.name !== '/home'">
<!-- 主页面 -->
<el-main>
<router-view />
</el-main>
</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref, reactive, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const editableTabsValue = ref(route.path);
const editableTabs = reactive([
{ title: '首页', name: '/home' }
]);
// 监听路由变化,同步标签页
watch(
() => route.path,
(newPath) => {
editableTabsValue.value = newPath;
if (!editableTabs.some(tab => tab.name === newPath)) {
const title = getTitleFromPath(newPath);
editableTabs.push({ title, name: newPath });
}
},
{ immediate: true }
);
function getTitleFromPath(path) {
const titleMap = {
'/home': '首页',
'/layout/user/list': '用户列表',
'/layout/user/role': '角色列表',
// 扩展其他路由...
};
return titleMap[path] || '新标签';
}
// 点击面包屑标签跳转路由
const tabBread = () => {
console.log(editableTabsValue.value);
router.push(editableTabsValue.value);
}
const removeTab = (targetName) => {
const tabs = editableTabs;
let activeName = editableTabsValue.value;
if (activeName === targetName) {
const currentIndex = tabs.findIndex(tab => tab.name === targetName);
const nextTab = tabs[currentIndex + 1] || tabs[currentIndex - 1];
activeName = nextTab?.name || '/home';
router.push(activeName);
}
editableTabsValue.value = activeName;
editableTabs.splice(0, editableTabs.length, ...tabs.filter(tab => tab.name !== targetName));
};
</script>
<style scoped>
/* 可以添加自定义样式 */
.el-tabs {
margin: 20px;
}
</style>
这部分代码是用来布局菜单框架结构的,然后我们在路由部分引入这个文件的路由即可。
<!-- src/layouts/MainLayout.vue -->
<template>
<div class="app">
<el-container style="height: 100vh;">
<!-- 左侧导航栏 -->
<el-aside width="200px">
<aside-nav />
</el-aside>
<div class="menu">
<!-- 顶部菜单 -->
<MyHeader />
</div>
</el-container>
</div>
</template>
<script>
import MyHeader from '@/Layout/topHeader.vue'
import AsideNav from '@/components/MenuItem.vue'
export default {
components: {
MyHeader,
AsideNav
}
}
</script>
<style>
html,
body {
padding: 0;
margin: 0;
}
.menu {
width: 100%;
}
</style>