生成菜单方法createMenu.jsimport navMenu from "./navMenu";
/** * 菜单组件 * @module widgets/my-menu * @example * * // 使用说明 */ export default { name: "createMenu", mixins: [navMenu], /** * 属性参数 * @property {Array} [data = []] data 构成菜单内容的数组 * @property {Object} [props = { id: 'id', text: 'text', icon: 'icon', children: 'children', group: 'group', route: 'route' }] props 指引菜单组件根据data数组中元素的键名作为菜单中对应的显示内容。例如:数组内元素为 {g_id:1, str:'第一组'}, 可以通过设置"props={id: 'g_id', text: 'str'}"来将数组的g_id对应为id,str对应为text * @property {String} [mode = 'verticle'] mode 默认为垂直模式,横向 ‘horizontal’ * @property {Boolean} [collapse = false] 是否水平折叠收起菜单(仅在 mode 为 vertical 时可用) * @property {String} [backgroundColor] backgroundColor='#FFFFFF' 菜单的背景色(仅支持 hex 格式) * @property {String} [textColor] 菜单的文字颜色(仅支持 hex 格式) * @property {String} [activeTextColor] 当前激活菜单的文字颜色(仅支持 hex 格式) * @property {String} [defaultActive] 当前激活菜单的 index。 defaultActive = '1' * @property {Array} [defaultOpeneds] 当前打开的sub-menu的 key 数组。 defaultOpeneds = ['2', '4'] * @property {Boolean} [uniqueOpened = false] 是否只保持一个子菜单的展开 * @property {String} [menuTrigger = 'click'] 子菜单打开的触发方式(只在 mode 为 horizontal 时有效) * @property {Boolean} [router = false] 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 */ props: { // 菜单数据 data: { type: Array, default() { return []; } }, // 数据字段名称映射 props: { type: Object, default() { return { id: "id", text: "text", icon: "icon", children: "children", group: "group", route: "route" }; } }, // 模式 horizontal / vertical mode: String, // 是否水平折叠收起菜单(仅在 mode 为 vertical 时可用) collapse: Boolean, // 菜单的背景色(仅支持 hex 格式) backgroundColor: String, // 菜单的文字颜色(仅支持 hex 格式) textColor: String, // 当前激活菜单的文字颜色(仅支持 hex 格式) activeTextColor: String, // 当前激活菜单的 index defaultActive: String, // 当前打开的sub-menu的 key 数组 defaultOpeneds: Array, // 是否只保持一个子菜单的展开 uniqueOpened: Boolean, // 子菜单打开的触发方式(只在 mode 为 horizontal 时有效) menuTrigger: String, // 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 router: Boolean, // 弹出菜单的自定义类名 popperClass: String }, methods: { grouping(array) { let groups = { default: [] }; let props = this.props; array.forEach(n => { let key = n[props.group]; if (key) { groups[key] = groups[key] || []; groups[key].push(n); } else { groups["default"].push(n); } }); return groups; }, createTitle(h, item) { return [ h("i", { class: item[this.props.icon] }), h( "span", { slot: "title" }, item[this.props.text] ) ]; }, createItem(h, item) { const key = (item[this.props.id] || "").toString(); return h( "el-menu-item", { props: { index: key, route: item[this.props.route], name: item[this.props.text] }, key: key, on: { // eslint-disable-next-line no-undef
//绑定点击事件 点击事件来源navMenu.js
click: (index, indexPath) => this.clickMenu(index, indexPath, item[this.props.text]) } }, this.createTitle(h, item) ); }, createSubmenu(h, item) { const key = (item[this.props.id] || "").toString(); return h( "el-submenu", { props: { index: (item[this.props.id] || "").toString(), popperClass: this.popperClass }, key: key }, [ h( "template", { slot: "title" }, this.createTitle(h, item) ), this.createNodes(h, item[this.props.children]) ] ); }, createGroup(h, title, items) { let nodes = []; let props = this.props; items.forEach(item => { if (item[props.children] && item[props.children].length > 0) { nodes.push(this.createSubmenu(h, item)); } else { nodes.push(this.createItem(h, item)); } }); nodes.unshift( h( "template", { slot: "title" }, title ) ); return h("el-menu-item-group", null, nodes); }, createNodes(h, array) { let nodes = [], groups = this.grouping(array); let props = this.props; for (let key in groups) { let items = groups[key] || []; if (key === "default") { items.forEach(item => { if (item[props.children] && item[props.children].length > 0) { nodes.push(this.createSubmenu(h, item)); } else { nodes.push(this.createItem(h, item)); } }); } else { nodes.push(this.createGroup(h, key, items)); } } return nodes; }, open(index) { console.log(index); this.$refs.menu.open(index); }, close(index) { this.$refs.menu.close(index); } }, render(h) { return h( "el-menu", { props: { ...this.$props }, class: "my-menu", on: { /** * 触发选中事件 * @event select * @param index {String} 选中列表id * @param indexPath {Array} 选中列表对应的路径 如:['item1', 'item1-1'] */ select: (index, indexPath) => this.$emit("select", index, indexPath), /** * 触发列表组(子菜单)展开事件 * @event open * @param index {String} 选中列表id * @param indexPath {Array} 选中列表对应的路径 如:['item1', 'item1-1'] */ open: (index, indexPath) => this.$emit("open", index, indexPath), /** * 触发列表组(子菜单)收起事件 * @event close * @param index {String} 选中列表id * @param indexPath {Array} 选中列表对应的路径 如:['item1', 'item1-1'] */ close: (index, indexPath) => this.$emit("close", index, indexPath) }, ref: "menu" }, this.createNodes(h, this.data) ); } };
绑定点击事件js navMenus.js
export default { name: "navMenu", methods: { clickMenu(index, indexPath, text) { // console.log(index, indexPath, text); console.log("左边", text); let componentName = index.$options.propsData.route; this.openedTab = this.$store.state.openedTab; // tabNum 为当前点击的列表项在openedTab中的index,若不存在则为-1 let tabNum = this.openedTab.indexOf(componentName); if (tabNum === -1) { // 该标签当前没有打开 // 将componentName加入到已打开标签页state.openedTab数组中 this.$store.commit("addTab", { name: text, componentName }); } else { // 该标签是已经打开过的,需要激活此标签页 this.$store.commit("changeTab", componentName); } } }, data() { return { openedTab: [] }; } };
菜单数组配置JS menu-config.js
module.exports = [ { id: "Index", text: "首页", icon: "el-icon-star-on", route: "BasicIndex" }, { id: "log", text: "日志", icon: "el-icon-s-order", route: "BasicLayout" }, { id: "basic", text: "个人中心", icon: "el-icon-s-custom", children: [ { id: 21, text: "选项一" }, { id: 22, text: "选项二" }, { id: 23, text: "选项三" } ] } ];
生成菜单 navMenu.vue
<!--本页为左侧下拉菜单--> <template> <el-row class="tac" style=" 200px;"> <el-col :span="24"> <createMenu :data="menu" backgroundColor="#545c64" textColor="#FFFFFF" ></createMenu> </el-col> </el-row> </template> <script> import menu from "@/config/menu-config.js"; import createMenu from "./createMenu"; export default { components: { createMenu }, name: "navMenu", data() { return { menu: menu }; } }; </script> <style scoped> .over-hide { overflow-x: hidden; } .el-submenu__title { border-bottom: 1px solid #8d98a2; } </style>
渲染组件页面navMain.vue
<!--本页为tab标签--> <template> <div> <el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="handleClickTab($event.name)" > <el-tab-pane :key="item.name" v-for="item in editableTabs" :label="item.title" :name="item.name" > </el-tab-pane> </el-tabs> <!--动态组件 ,更具is的值来确定渲染哪个组件 --> <component :is="componentId"></component> </div> </template> <script> export default { name: "navMain", data() { return { componentId: () => import("@/components/navMain/mainComponents/BasicIndex.vue"), editableTabsValue: "BasicIndex", editableTabs: [ { title: "首页", name: "BasicIndex" } ], tabIndex: 1, openedTab: ["BasicIndex"] }; }, methods: { handleClickTab(route) { this.$store.commit("changeTab", route); this.IsShowComponents(route); // this.$router.push(route); }, removeTab(targetName) { // 首页不允许被关闭(为了防止el-tabs栏中一个tab都没有) if (targetName === "BasicIndex") { return false; } let tabs = this.editableTabs; let activeName = this.editableTabsValue; if (activeName === targetName) { tabs.forEach((tab, index) => { if (tab.name === targetName) { let nextTab = tabs[index + 1] || tabs[index - 1]; if (nextTab) { activeName = nextTab.name; } } }); } this.$store.commit("deductTab", targetName); let deductIndex = this.openedTab.indexOf(targetName); this.openedTab.splice(deductIndex, 1); this.IsShowComponents(activeName); this.editableTabsValue = activeName; this.editableTabs = tabs.filter(tab => tab.name !== targetName); if (this.openedTab.length === 0) { this.$store.commit("addTab", "BasicIndex"); this.IsShowComponents("BasicIndex"); // this.$router.push("BasicIndex"); } }, IsShowComponents(newTab) { this.componentId = () => import("@/components/navMain/mainComponents/" + newTab + ".vue"); } }, computed: { getOpenedTab() { return this.$store.state.openedTab; }, changeTab() { return this.$store.state.activeTab; } }, watch: { getOpenedTab(val) { if (val.length > this.openedTab.length) { // 添加了新的tab页 // 导致$store.state中的openedTab长度 // 大于 // 标签页中的openedTab长度 // 因此需要新增标签页 let newTitle = this.$store.state.openedTitle[val.length - 1]; let newTab = val[val.length - 1]; // 新增的肯定在数组最后一个 this.IsShowComponents(newTab); ++this.tabIndex; this.editableTabs.push({ title: newTitle, name: newTab, content: "" }); this.editableTabsValue = newTab; this.openedTab.push(newTab); } }, changeTab(val) { // 监听activetab以实现点击左侧栏时激活已存在的标签 if (val !== this.editableTabsValue) { this.editableTabsValue = val; this.IsShowComponents(val); } } }, created() { // 刷新页面时(F11) // 因为<router-view>的<keep-alive>,会保留刷新时所在的router // 但是tab标签页因为刷新而被重构了,tab没有了 // 因此需要将router回到index this.$router.push("Home"); } }; </script> <style scoped></style>
效果: