You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fourth/layout/components/Sidebar/SidebarItem.vue

154 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="menu-wrapper">
<template v-for="item in routes" v-if="!item.hidden&&item.children">
<router-link v-if="hasOneShowingChildren(item.children) && !item.children[0].children&&!item.alwaysShow" :to="item.path+'/'+item.children[0].path"
:key="item.children[0].name">
<el-menu-item :index="item.path+'/'+item.children[0].path" :class="{'submenu-title-noDropdown':!isNest}">
<svg-icon v-if="item.children[0].meta&&item.children[0].meta.icon" :icon-class="item.children[0].meta.icon"></svg-icon>
<span v-if="item.children[0].meta&&item.children[0].meta.title" slot="title">{{item.children[0].meta.title}}</span>
</el-menu-item>
</router-link>
<el-submenu v-else :index="item.name||item.path" :key="item.name">
<template slot="title">
<svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
<span v-if="item.meta&&item.meta.title" slot="title">{{item.meta.title}}</span>
</template>
<template v-for="child in item.children" v-if="!child.hidden">
<sidebar-item :is-nest="true" class="nest-menu" v-if="child.children&&child.children.length>0" :routes="[child]" :key="child.path"></sidebar-item>
<!--支持外链功能-->
<a v-else-if="child.path.startsWith('http')" v-bind:href="child.path" target="_blank" :key="child.name">
<el-menu-item :index="item.path+'/'+child.path">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
</el-menu-item>
</a>
<router-link v-else :to="item.path+'/'+child.path" :key="child.name">
<el-menu-item :index="item.path+'/'+child.path">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</div>
</template>
<script>
export default {
name: 'SidebarItem',
props: {
routes: {
type: Array
},
isNest: {
type: Boolean,
default: false
}
},
methods: {
hasOneShowingChildren(children) {
const showingChildren = children.filter(item => {
return !item.hidden
})
if (showingChildren.length === 1) {
return true
}
return false
}
}
}
</script>
#abc
<template>
<!-- 最外层的 `div` 元素,类名为 `menu-wrapper`,它充当整个侧边栏菜单的包裹容器,起到组织和定位内部菜单元素的作用,使得菜单在页面布局中有一个明确的范围和样式框架。 -->
<div class="menu-wrapper">
<!-- 使用 `v-for` 指令对 `routes` 数组进行循环遍历,`routes` 数组应该是从父组件传入的数据,包含了侧边栏菜单各个层级的路由配置信息等内容。同时结合 `v-if` 指令进行条件判断,只有当当前遍历到的 `item` 元素的 `hidden` 属性为 `false`(意味着该菜单项需要显示出来,而非隐藏)并且 `item` 具有 `children` 属性(表示这个菜单项包含子菜单)时,才会渲染内部对应的菜单结构内容,以此实现根据数据动态生成侧边栏菜单的功能,有选择性地展示有效的菜单项及其子菜单。 -->
<template v-for="item in routes" v-if="!item.hidden&&item.children">
<!-- 使用 `router-link` 组件创建一个路由链接,这是 Vue Router 用于实现页面导航的组件。通过 `v-if` 指令添加了复杂的条件判断,只有当满足以下几个条件时才会渲染这个路由链接:
- `hasOneShowingChildren(item.children)` 方法返回 `true`,即当前菜单项(`item`)的子菜单数组(`item.children`)经过筛选后只有一个需要显示的子项(`hasOneShowingChildren` 方法会在后续的 `methods` 部分详细介绍,作用是筛选出未隐藏的子项并判断数量是否为 1
- `!item.children[0].children` 为 `true`,也就是当前菜单项的第一个子菜单项(`item.children[0]`)不存在它自己的子菜单,说明是一个没有下一级嵌套的子菜单项。
- `!item.alwaysShow` 为 `true`,可能表示该菜单项不是那种始终要显示特定链接形式的特殊菜单项(具体含义需结合整体业务逻辑判断)。
`:to` 属性绑定了一个拼接后的路由路径,由当前菜单项(`item`)的 `path` 属性和它第一个子菜单项(`item.children[0]`)的 `path` 属性通过 `+` 号拼接而成,用于指定点击这个路由链接后要跳转到的页面路径。`:key` 属性绑定了第一个子菜单项(`item.children[0]`)的 `name` 属性,在 Vue 的虚拟 DOM 渲染机制中,`key` 可以帮助更高效准确地识别每个列表项(这里的路由链接作为一种列表项),便于在数据变化时进行精准的更新、复用以及重新排序等操作,优化渲染性能。 -->
<router-link v-if="hasOneShowingChildren(item.children) &&!item.children[0].children&&!item.alwaysShow" :to="item.path+'/'+item.children[0].path"
:key="item.children[0].name">
<!-- 使用 Element UI 的 `el-menu-item` 组件来表示一个菜单项,它是侧边栏菜单中的具体可点击选项。`:index` 属性绑定了同样是由当前菜单项(`item`)的 `path` 和其第一个子菜单项(`item.children[0]`)的 `path` 拼接而成的字符串,这个 `index` 属性在菜单的选中状态管理等方面可能会起到标识作用(例如确定哪个菜单项当前处于激活状态等)。`:class` 属性通过对象语法绑定了一个类名控制逻辑,根据 `!isNest``isNest` 是从父组件传入的布尔类型属性,用于判断当前菜单项是否处于嵌套菜单的情境中,默认值为 `false`)的值来决定是否添加 `submenu-title-noDropdown` 类名,从而实现根据不同的菜单层级或状态应用不同的样式,比如改变外观样式以体现是否是嵌套菜单下的标题样式等。 -->
<el-menu-item :index="item.path+'/'+item.children[0].path" :class="{'submenu-title-noDropdown':!isNest}">
<!-- 使用 `svg-icon` 自定义组件(从代码上下文推测,应该是用于展示 SVG 格式的图标),通过 `v-if` 指令进行条件判断,只有当当前菜单项的第一个子菜单项(`item.children[0]`)的 `meta` 对象中存在 `icon` 属性(通常 `meta` 对象用于存放菜单项的一些额外元数据,比如图标、标题等相关信息)时,才会将该 `icon` 属性的值作为图标类名传递给 `svg-icon` 组件,进而展示对应的图标,用于增强菜单项的可视化效果,让用户更直观地识别菜单项对应的功能或页面类型等。 -->
<svg-icon v-if="item.children[0].meta&&item.children[0].meta.icon" :icon-class="item.children[0].meta.icon"></svg-icon>
<!-- 使用 `span` 元素来展示菜单项的标题文本内容,同样通过 `v-if` 指令判断,只有当当前菜单项的第一个子菜单项(`item.children[0]`)的 `meta` 对象中存在 `title` 属性时,才会将该 `title` 属性的值作为文本内容渲染到 `span` 元素中,显示在菜单项上,明确告知用户该菜单项所代表的含义或对应的页面名称等信息。通过 `slot="title"` 属性,将这个 `span` 元素放置在 `el-menu-item` 组件指定的标题插槽位置,确保文本正确显示在菜单项的合适位置上。 -->
<span v-if="item.children[0].meta&&item.children[0].meta.title" slot="title">{{item.children[0].meta.title}}</span>
</el-menu-item>
</router-link>
<!-- 使用 Element UI 的 `el-submenu` 组件来创建一个包含子菜单的菜单项容器,通过 `v-else` 指令,意味着当不满足上面 `router-link` 组件的渲染条件时(即当前菜单项的子菜单情况不符合前面所描述的那种只有一个无子菜单的显示子项的情况),就会渲染这个 `el-submenu` 组件,用于展示更复杂的、具有多个子菜单项或者嵌套子菜单的菜单结构。`:index` 属性绑定了当前菜单项(`item`)的 `name` 属性或者 `path` 属性(优先使用 `name`,如果 `name` 不存在则使用 `path`),用于在菜单体系中标识这个子菜单的唯一性,便于菜单状态管理、展开与折叠控制等操作能准确对应到这个子菜单上。`:key` 属性绑定了当前菜单项(`item`)的 `name` 属性,同样是为了在 Vue 的虚拟 DOM 渲染机制中给这个组件实例一个唯一标识,方便进行高效的更新和复用操作,提升渲染性能。 -->
<el-submenu v-else :index="item.name||item.path" :key="item.name">
<!-- 使用 `template` 元素并通过 `slot="title"` 属性定义了 `el-submenu` 组件的标题部分内容的插槽,用于自定义子菜单标题的展示样式和内容,包括图标和标题文本等信息,这些内容会展示在子菜单展开按钮等位置,方便用户直观地识别这个子菜单所代表的功能分类或相关页面集合等。 -->
<template slot="title">
<!-- 与前面类似,使用 `svg-icon` 自定义组件展示图标,通过 `v-if` 指令判断当前菜单项(`item`)的 `meta` 对象中是否存在 `icon` 属性,如果存在则将该 `icon` 属性的值作为图标类名传递给 `svg-icon` 组件,展示对应的图标,增强子菜单标题的可视化效果,帮助用户快速识别该子菜单相关信息。 -->
<svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
<!-- 使用 `span` 元素展示子菜单标题的文本内容,通过 `v-if` 指令判断当前菜单项(`item`)的 `meta` 对象中是否存在 `title` 属性,如果存在则将该 `title` 属性的值作为文本渲染到 `span` 元素中,显示在子菜单标题位置,明确告知用户这个子菜单所涉及的内容范围等信息。通过 `slot="title"` 将这个 `span` 元素放置在 `template` 元素指定的标题插槽位置,确保文本正确显示在子菜单标题的合适位置上。 -->
<span v-if="item.meta&&item.meta.title" slot="title">{{item.meta.title}}</span>
</template>
<!-- 再次使用 `v-for` 指令对当前菜单项(`el-submenu` 对应的 `item`)的 `children` 数组(即子菜单的各个子项)进行循环遍历,同时结合 `v-if` 指令进行条件判断,只有当子项的 `hidden` 属性为 `false`(表示该子项需要显示出来,而非隐藏)时,才会渲染内部对应的菜单相关组件,用于动态生成子菜单下的具体菜单项内容,实现有选择性地展示有效的子菜单项,过滤掉不需要显示的子项。 -->
<template v-for="child in item.children" v-if="!child.hidden">
<!-- 使用名为 `sidebar-item` 的自定义组件(也就是当前正在定义的这个组件自身,通过递归调用自身来处理嵌套菜单的情况),通过 `:is-nest="true"` 属性传递一个布尔值,表示当前处于嵌套菜单的状态(因为这里是在处理子菜单中的子菜单项,属于嵌套层级情况),同时添加 `nest-menu` 类名(从代码结构推测,这个类名应该是用于应用一些特定的样式,实现嵌套菜单相关的外观样式控制,比如缩进、字体大小调整等,具体样式定义需查看对应的 CSS 代码)。通过 `:routes="[child]"` 属性将当前子项(`child`)作为一个只包含一个元素的数组传递给 `sidebar-item` 组件,目的是让 `sidebar-item` 组件以这个子项为基础数据,递归地处理它可能存在的子菜单情况(如果 `child` 本身也有 `children` 属性,即还有下一层级的子菜单的话),`:key` 属性绑定当前子项(`child`)的 `path` 属性,用于在 Vue 的虚拟 DOM 渲染机制中给这个 `sidebar-item` 组件实例一个唯一标识,便于进行高效的更新、复用以及重新排序等操作,优化渲染性能,实现多级嵌套菜单的递归构建和渲染功能。 -->
<sidebar-item :is-nest="true" class="nest-menu" v-if="child.children&&child.children.length>0" :routes="[child]" :key="child.path"></sidebar-item>
<!-- 使用 `a` 元素(超链接)来创建一个外部链接菜单项,通过 `v-else-if` 指令进行条件判断,当当前子项(`child`)的 `path` 属性是以 `http` 开头时(表明这是一个指向外部网页的链接),就会渲染这个 `a` 元素。通过 `v-bind:href` 指令将当前子项(`child`)的 `path` 属性绑定为超链接的 `href` 属性值,即链接地址,同时设置 `target="_blank"` 属性,使得点击这个链接时会在新的浏览器标签页中打开对应的外部网页。`:key` 属性绑定当前子项(`child`)的 `name` 属性,用于在 Vue 的虚拟 DOM 渲染机制中给这个 `a` 元素一个唯一标识,方便进行高效的更新操作,确保在数据变化时能正确渲染这个外部链接菜单项。 -->
<a v-else-if="child.path.startsWith('http')" v-bind:href="child.path" target="_blank" :key="child.name">
<el-menu-item :index="item.path+'/'+child.path">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
</el-menu-item>
</a>
<!-- 使用 `router-link` 组件创建一个内部路由链接菜单项,通过 `v-else` 指令,当不满足上面 `sidebar-item` 和 `a` 元素的渲染条件时(即既不是嵌套菜单情况,也不是外部链接情况,而是内部页面路由链接的常规情况),就会渲染这个 `router-link` 组件。`:to` 属性绑定了由当前菜单项(`el-submenu` 对应的 `item`)的 `path` 属性和当前子项(`child`)的 `path` 属性通过 `+` 号拼接而成的字符串作为路由路径,用于指定点击这个路由链接后要跳转到的内部页面路径。`:key` 属性绑定当前子项(`child`)的 `name` 属性,用于在 Vue 的虚拟 DOM 渲染机制中给这个 `router-link` 组件一个唯一标识,方便进行高效的更新操作,确保在数据变化时能正确渲染这个内部路由链接菜单项,实现内部页面之间的导航功能。 -->
<router-link v-else :to="item.path+'/'+child.path" :key="child.name">
<el-menu-item :index="item.path+'/'+child.path">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
<span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</div>
</template>
<script>
export default {
name: 'SidebarItem',
props: {
// 定义一个名为 `routes` 的属性,其类型被指定为 `Array`(数组类型),这个属性是从父组件传入的数据,预期传入的是一个包含侧边栏菜单路由配置信息的数组,数组中的每个元素通常是一个对象,对象里包含了诸如 `path`(路由路径)、`meta`(包含图标、标题等额外元数据的对象)、`children`(子菜单相关信息,如果有的话)等属性,用于在模板中根据这些数据动态生成侧边栏菜单的各个层级和菜单项内容,实现菜单的灵活配置和渲染。
routes: {
type: Array
},
// 定义一个名为 `isNest` 的属性,其类型为 `Boolean`(布尔类型),默认值设置为 `false`。这个属性同样是从父组件传入,用于标识当前菜单项是否处于嵌套菜单的情境中,在模板中可以根据这个属性的值来应用不同的样式(例如通过添加或移除特定类名来改变外观样式)或者进行不同的逻辑处理(比如某些交互行为在嵌套和非嵌套情况下可能有所不同),以区分不同层级的菜单展示效果和功能逻辑。
isNest: {
type: Boolean,
default: false
}
},
methods: {
hasOneShowingChildren(children) {
// 定义一个名为 `hasOneShowingChildren` 的方法,该方法接收一个参数 `children`,这个参数预期是一个菜单项的子菜单数组(从调用该方法的地方来看,通常是 `item.children` 的形式传入,即某个菜单项下的所有子菜单项组成的数组),其作用是判断这个子菜单数组经过筛选后是否只有一个需要显示的子项。
const showingChildren = children.filter(item => {
// 使用 JavaScript 的数组 `filter` 方法对传入的 `children` 数组进行筛选操作,`filter` 方法会遍历数组中的每个元素(这里每个元素对应一个子菜单项,通常是一个对象),并执行传入的回调函数,回调函数中返回 `true` 的元素会被保留下来组成新的数组。在这里,回调函数判断每个子菜单项的 `hidden` 属性是否为 `false`(即是否不隐藏,需要显示),如果是,则该子菜单项会被保留在新的 `showingChildren` 数组中,这样就得到了经过筛选后的只包含需要显示的子菜单项的数组。
return!item.hidden
})
if (showingChildren.length === 1) {
// 判断经过筛选后的 `showingChildren` 数组的长度是否等于 `1`,如果等于 `1`,说明经过筛选后只有一个需要显示的子菜单项,此时返回 `true`,表示满足只有一个显示子项的条件。
return true
}
return false
// 如果 `showingChildren` 数组的长度不等于 `1`,则返回 `false`,表示不满足只有一个显示子项的条件,意味着子菜单的显示情况不符合特定的业务逻辑要求(例如前面在模板中使用该方法判断是否以某种特定方式渲染菜单项的情况)。
}
}
}
</script>