织梦网站需要优化360搜索优化
antd框架
——实现自定义菜单功能
最近在写后台管理系统,看到同事写的antd
的框架,是在antd
原有框架的基础上进行的修改。由于原有的框架中的菜单是menu.js
的写法,类似于react
的形式,后面要进行改动样式,因此自定义了一个menuNew
的菜单组件,方便用于样式的修改。
1.左侧菜单可以通过a-layout-sider
组件来展示
代码如下:
1.1 html
部分的代码
<a-layout-sider:theme="sideTheme":class="['side-menu', isMobile ? null : 'shadow']"width="220px":collapsible="collapsible"v-model="collapsed":trigger="null"><div :class="['logo', theme]"><router-link to="/dashboard/workplace"><img src="@/assets/img/logo.png" /><h1>{{ systemName }}</h1></router-link></div><div :class="['side-menu-content', 'beauty-scroll']"><MenuItem:theme="theme":collapsed="collapsed":options="menuData"@select="onSelect"class="menu"/></div></a-layout-sider>
1.2 js
部分的代码
<script>
import MenuItem from './MenuItem';
import { mapState } from 'vuex';
export default {name: 'SideMenu',components: { MenuItem },props: {collapsible: {type: Boolean,required: false,default: false,},collapsed: {type: Boolean,required: false,default: false,},menuData: {type: Array,required: true,},theme: {type: String,required: false,default: 'dark',},},computed: {sideTheme() {return this.theme == 'light' ? this.theme : 'dark';},...mapState('setting', ['isMobile', 'systemName']),},methods: {onSelect(obj) {this.$emit('menuSelect', obj);},},
};
</script>
1.3 css
部分的代码
// .shadow{
// box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
// }
.side-menu{min-height: 100vh;// overflow-y: auto;z-index: 9;.logo{height: 64px;position: relative;line-height: 64px;padding-left: 24px;-webkit-transition: all .3s;transition: all .3s;overflow: hidden;background-color: #0B2540;&.light{background-color: #0B2540;h1{color: @primary-color;}}h1{color: @menu-dark-highlight-color;font-size: 20px;margin: 0 0 0 12px;display: inline-block;vertical-align: middle;}img{width:159px;vertical-align: middle;}}.side-menu-content{overflow-y: auto;height: calc(100vh - 64px);}
}
.ant-layout-sider{background: #213346;}
// .menu{
// padding: 16px 0;
// background-color: #fff;
// }
2.menuItem
组件部分
2.1 html
部分代码
<template><div class="menu-wrap"><divclass="menu-item-wrap"v-for="(item, index) in menuData":key="`menu-${index}`"><divclass="menu-item":class="{ active: item.active }"@click="handleRouter($event, item)"@mouseover="mouseover($event, item)"><SvgIconclass="menu-icon"v-if="item.meta && item.meta.icon":iconClass="item.meta.icon"/><div class="menu-text">{{ item.name }}</div><SubItemclass="menu-sub-item":style="{ left: '80px', top: pageY + 'px' }"v-if="item.children &&item.children.length > 0 &&item.meta.type != 'link'":list="item.children":index="index":collapsed="collapsed"/></div></div></div>
</template>
2.2 js
部分代码
<script>
import SubItem from './SubItem'
export default {components: {SubItem},props: {options: {type: Array,required: true},theme: {type: String,required: false,default: 'dark'},collapsed: {type: Boolean,required: false,default: false}},watch: {$route() {this.menuData.forEach(item => {let active = falseif (this.$route.fullPath.startsWith(item.fullPath)) {active = true}item.active = active})}},data() {return {menuData: [],openSelect: false,pageY: undefined,currentItem: null}},created() {this.menuData = JSON.parse(JSON.stringify(this.options)).map(item => {let active = falseif (this.$route.fullPath.startsWith(item.fullPath)) {active = true}return {...item,active: active}}).filter(item => {if (item.meta.type == 'link') {return true}if (item.meta.invisible) {return false}if (item.children?.length > 0) {return this.isShow(item.children)} else if (item.children) {return false} else {return true}})},methods: {isShow(list) {return list.some(item => {if (item.children?.length > 0) {return this.isShow(item.children)} else {return !item.meta.invisible}})},mouseover(e, item) {if (this.currentItem == item.name) {return}let height = window.innerHeight || document.body.clientHeightlet pageYlet domsif (e.currentTarget.getAttribute('class').includes('menu-item-wrap')) {pageY = e.clientY - e.offsetY} else if (e.currentTarget.parentElement.getAttribute('class').includes('menu-item-wrap')) {doms = e.currentTarget.parentElementpageY = doms.getBoundingClientRect().top}let domHeightif (doms && doms.getElementsByClassName('sub-item-content')[0]) {domHeight = doms.getElementsByClassName('sub-item-content')[0].getBoundingClientRect().height} else {domHeight = 0}if (domHeight + pageY > height) {this.pageY = height - domHeight} else if (pageY < 64) {this.pageY = 64} else {this.pageY = pageY}this.currentItem = item.name},handleRouter(e, item) {if (item.redirect) {this.$router.push(item.fullPath)} else {if (item.meta.type == 'link') {this.$router.push(item.fullPath)}}}}
}
</script>
这个代码中有以下相关内容:
2.2.1 vue项目——鼠标移入时判断是否含有某个类名
e.currentTarget.getAttribute('class').includes('menu-item-wrap')
2.2.2 vue项目——鼠标移入时判断当前元素的父级元素是否含有某个类名
当鼠标移入时,需要判断当前元素是否含有指定类名,如果不含有,则需要判断该元素的父级元素是否含有指定类名
e.currentTarget.parentElement.getAttribute('class').includes('menu-item-wrap')
判断当前元素的父级元素是否含有某个类名
2.2.3 vue项目——判断父级元素距离页面顶部的距离
let doms = e.currentTarget.parentElement;//获取当前元素的父级元素
let pageY = doms.getBoundingClientRect().top
//通过doms.getBoundingClientRect()可以获取该元素的很多参数,包含宽度高度 距离顶部 距离左边的大小等。
判断父级元素距离页面顶部的距离
2.2.4 vue项目——鼠标移入时判断当前元素距离页面顶部的距离
let pageY = e.clientY - e.offsetY
判断当前元素距离页面订单的距离
2.2.5 vue项目——判断当前元素含有sub-item-content
的元素的高度
let domHeight = doms.getElementsByClassName('sub-item-content')[0].getBoundingClientRect().height
判断当前元素的父级元素且类名为sub-item-content的元素的高度,如果该元素距离页面顶部的距离+元素高度超过了屏幕的高度,则该元素距离页面顶部的距离要改成 屏幕的可视高度-元素的高度,也就是元素与底部贴合,否则元素就是根据距离顶部的距离来渲染。
2.2.6 vue项目——获取浏览器的可视区域的高度
let height = window.innerHeight || document.body.clientHeight
获取浏览器的可视区域的高度
2.3 css
部分代码
<style lang="less" scoped>
.menu-wrap {position: relative;padding-top: 13px;.menu-item {width: 64px;height: 64px;padding-top: 12px;margin: 0 auto 10px;border-radius: 8px;text-align: center;cursor: pointer;.menu-icon {font-size: 20px;color: #fff;}.menu-text {color: #fff;font-size: 14px;line-height: 19px;}.menu-sub-item {display: none;}}.menu-item-wrap {&:hover {.menu-item {background-color: #2b3e51;}.menu-sub-item {display: block;position: fixed;}}}.menu-item.active {background-color: #2b3e51;}
}
</style>
3.SubItem
组件代码
3.1 html
部分代码
<template><divclass="sub-item-wrap":style="{ left: collapsed ? '80px' : '220px' }"v-if="visible"><!-- :style="{top: top + 'px',left:collapsed ? '80px' : '220px'}" --><div class="sub-item-content"><div class="item-box"><divclass="item-list"v-for="(item, index) in subMenuData":key="index"><divclass="sub-item":class="{'parent-node': subItem.hasChild,'child-node': !subItem.hasChild,'hide-node': subItem.childFlag,active: $route.path == subItem.fullPath}"v-for="(subItem, i) in item":key="i"@click.stop="handleRouter(subItem)"><span>{{ subItem.name }}</span></div></div></div></div></div>
</template>
3.2 js
部分代码
<script>
export default {props: {collapsed: {type: Boolean,required: false,default: false},list: Array,index: Number},data() {return {visible: false,subMenuData: [],top: 77}},mounted() {this.showSubItem(this.list, this.index)},methods: {showSubItem(data, index) {let top = 64 + 13 + index * 74 // 头部高度+搜索高度+当前点击下标*菜单高度const arr = this.getMenuData(JSON.parse(JSON.stringify(data)))const num = arr.lengthconst contentHeight = 40 * num // 子菜单内容高度const clientHeight =document.documentElement.clientHeight || document.body.clientHeight // 可视区高度if (top + contentHeight + 16 + 5 <= clientHeight) {this.top = top} else {if (contentHeight + 16 + 64 <= clientHeight) {this.top = clientHeight - contentHeight - 16 - 5} else {this.top = 69}}this.visible = true// 处理多列子菜单let subMenuData = []let i = 0subMenuData[i] = []arr.forEach((item, index) => {subMenuData[i].push(item)if ((index + 1) % 6 == 0) {i++if (arr[index + 1]) {subMenuData[i] = []}}})this.subMenuData = subMenuData},getMenuData(data) {const arr = []data.forEach((item, i) => {if (!item.meta?.invisible) {arr.push({...item,hasChild: item.children?.length > 0})if (item.children && item.children.length > 0) {arr.push(...this.getMenuData(item.children))}}})return arr},handleRouter(v) {if (v.hasChild) returnif (this.$route.fullPath == v.fullPath) {return false}this.$router.push(v.fullPath)}}
}
</script>
3.3 css
部分代码
<style lang="less" scoped>
.sub-item-wrap {position: absolute;top: 10px;.sub-item-content {width: 200px;// height: calc(100vh - 84px);max-height: 500px;padding: 8px 10px;box-sizing: border-box;box-shadow: 0px 4px 24px rgba(0, 0, 0, 0.16);border-radius: 8px;overflow: auto;background-color: #fff;border-radius: 10px;position: fixed;}.sub-item-close {position: absolute;top: 12px;right: 14px;}.sub-item {height: 40px;padding: 0 38px;box-sizing: border-box;display: flex;justify-content: space-between;align-items: center;}.parent-node {font-size: 14px;color: @primary-color;justify-content: flex-start;color: #999999;padding-left: 20px;&::before {content: '';display: block;width: 5px;height: 5px;background: #999999;border-radius: 50%;margin-right: 6px;}}.hide-node {display: none;}.child-node {color: #333;cursor: pointer;padding-left: 44px;&:hover {color: #f90 !important;background-color: #f5f5f5;border-radius: 4px;}}.sub-item.active {color: #f90 !important;// background-color: #f90;border-radius: 4px;}
}
</style>
效果图如下:
完成!!!多多积累,多多收获!!!