Vue3 无限递归菜单组件及权限控制

88 阅读2分钟

以下仅供参考,主要是用来自己保存知识点用的.

<!-- Menu.vue 菜单组件 -->
<template>
  <ul class="menu-container" v-if="hasPermission">
    <li v-for="item in menuData" :key="item.id" class="menu-item">
      <!-- 菜单项内容 -->
      <div 
        class="menu-label" 
        :class="{ 'has-children': item.children && item.children.length }"
        @click="toggleChildren(item)"
      >
        <span class="menu-icon" v-if="item.icon">
          <i :class="item.icon"></i>
        </span>
        <span class="menu-text">{{ item.name }}</span>
        <span 
          class="menu-arrow" 
          v-if="item.children && item.children.length"
          :class="{ 'expanded': item.expanded }"
        >
          <i class="arrow-icon"></i>
        </span>
      </div>
      
      <!-- 递归子菜单 -->
      <transition name="slide">
        <recursive-menu
          v-if="item.children && item.children.length && item.expanded"
          :menu-data="item.children"
          :permissions="permissions"
          class="sub-menu"
        />
      </transition>
    </li>
  </ul>
</template>

<script>
import { defineComponent, computed } from 'vue';

export default defineComponent({
  name: 'RecursiveMenu',
  
  props: {
    menuData: {
      type: Array,
      required: true,
      default: () => []
    },
    permissions: {
      type: Array,
      default: () => []
    }
  },
  
  setup(props) {
    // 检查当前菜单是否有权限显示
    const hasPermission = computed(() => {
      return props.menuData.some(item => {
        return !item.permission || props.permissions.includes(item.permission);
      });
    });

    // 切换子菜单展开状态
    const toggleChildren = (item) => {
      if (item.children && item.children.length) {
        item.expanded = !item.expanded;
      } else if (item.path) {
        // 这里可以添加路由跳转逻辑
        console.log('Navigate to:', item.path);
      }
    };

    return {
      hasPermission,
      toggleChildren
    };
  }
});
</script>

<style scoped>
/* 基础样式 */
.menu-container {
  list-style: none;
  padding: 0;
  margin: 0;
  width: 100%;
}

.menu-item {
  position: relative;
  cursor: pointer;
}

.menu-label {
  display: flex;
  align-items: center;
  padding: 12px 16px;
  color: #333;
  transition: all 0.3s ease;
  border-radius: 4px;
}

.menu-label:hover {
  background-color: #f5f5f5;
  color: #1890ff;
}

.menu-icon {
  margin-right: 8px;
  font-size: 16px;
}

.menu-text {
  flex: 1;
}

.menu-arrow {
  transition: transform 0.3s ease;
}

.menu-arrow.expanded {
  transform: rotate(90deg);
}

.arrow-icon {
  display: inline-block;
  width: 0;
  height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 5px solid #666;
}

/* 子菜单样式 */
.sub-menu {
  padding-left: 20px;
  overflow: hidden;
}

/* 过渡动画 */
.slide-enter-active, .slide-leave-active {
  transition: all 0.3s ease;
  max-height: 500px;
}

.slide-enter-from, .slide-leave-to {
  max-height: 0;
  opacity: 0;
}

/* 权限控制样式 */
.no-permission {
  display: none;
}
</style>

使用方法:

<template>
	<div class="app-container">
		<h1>无限递归菜单示例</h1>

		<div class="menu-wrapper">
			<recursive-menu :menu-data="menuData" :permissions="userPermissions" />
		</div>

		<div class="permission-control">
			<h3>权限控制</h3>
			<label v-for="perm in allPermissions" :key="perm">
				<input type="checkbox" v-model="userPermissions" :value="perm" />
				{{ perm }}
			</label>
		</div>
	</div>
</template>

<script>
import { ref } from "vue";
import RecursiveMenu from "./components/RecursiveMenu.vue";

export default {
	components: {
		RecursiveMenu,
	},

	setup() {
		// 所有可能的权限 可配合后端接口使用
		const allPermissions = ref(["dashboard_view", "user_manage", "role_manage", "product_edit", "order_view", "report_view"]);

		// 当前用户权限
		const userPermissions = ref(["dashboard_view", "order_view"]);

		// 菜单数据
		const menuData = ref([
			{
				id: 1,
				name: "仪表盘",
				icon: "el-icon-monitor",
				path: "/dashboard",
				permission: "dashboard_view",
			},
			{
				id: 2,
				name: "用户管理",
				icon: "el-icon-user",
				expanded: false,
				permission: "user_manage",
				children: [
					{
						id: 21,
						name: "用户列表",
						path: "/user/list",
						permission: "user_manage",
					},
					{
						id: 22,
						name: "用户组",
						path: "/user/group",
						permission: "user_manage",
					},
				],
			},
			{
				id: 3,
				name: "产品管理",
				icon: "el-icon-goods",
				expanded: false,
				permission: "product_edit",
				children: [
					{
						id: 31,
						name: "产品列表",
						path: "/product/list",
						permission: "product_edit",
					},
					{
						id: 32,
						name: "分类管理",
						path: "/product/category",
						permission: "product_edit",
						children: [
							{
								id: 321,
								name: "一级分类",
								path: "/product/category/level1",
							},
							{
								id: 322,
								name: "二级分类",
								path: "/product/category/level2",
							},
						],
					},
				],
			},
			{
				id: 4,
				name: "订单管理",
				icon: "el-icon-tickets",
				expanded: false,
				permission: "order_view",
				children: [
					{
						id: 41,
						name: "订单列表",
						path: "/order/list",
					},
					{
						id: 42,
						name: "退货管理",
						path: "/order/return",
					},
				],
			},
			{
				id: 5,
				name: "系统设置",
				icon: "el-icon-setting",
				children: [
					{
						id: 51,
						name: "角色管理",
						path: "/system/role",
						permission: "role_manage",
					},
					{
						id: 52,
						name: "权限设置",
						path: "/system/permission",
						permission: "role_manage",
					},
				],
			},
		]);

		return {
			menuData,
			allPermissions,
			userPermissions,
		};
	},
};
</script>

<style>
.app-container {
	max-width: 1200px;
	margin: 0 auto;
	padding: 20px;
	font-family: Arial, sans-serif;
}

.menu-wrapper {
	width: 300px;
	border: 1px solid #eee;
	border-radius: 4px;
	padding: 10px;
	margin-bottom: 20px;
}

.permission-control {
	margin-top: 30px;
	padding: 15px;
	border: 1px solid #eee;
	border-radius: 4px;
}

.permission-control label {
	display: block;
	margin: 8px 0;
	cursor: pointer;
}

.permission-control input {
	margin-right: 8px;
}
</style>