
8 changed files with 455 additions and 15 deletions
After Width: | Height: | Size: 711 B |
@ -0,0 +1,311 @@ |
|||||
|
<template> |
||||
|
<el-collapse v-model="activeNames" @change="handleChange"> |
||||
|
<el-collapse-item v-for="(item, index) in menuList" :key="index" :name="item[renderKeyNew.domName]"> |
||||
|
<template #title> |
||||
|
<!-- <div class="flex gap8" @click.stop="handleTitleClick"> --> |
||||
|
<div class="flex-between gap8 collapse-item"> |
||||
|
<div class="flex gap8 "> |
||||
|
<GuipToolTip :content="item[renderKeyNew.menuTitle]"> |
||||
|
<span class="menu-name">{{ item[renderKeyNew.menuTitle] }}</span> |
||||
|
</GuipToolTip> |
||||
|
<span v-if="item[renderKeyNew.selected]" class="menu-select"></span> |
||||
|
</div> |
||||
|
<img class="arrow_img" :class="{ 'is-active': activeNames.includes(item[renderKeyNew.domName]) }" |
||||
|
src="@/assets/input_ex_ic.png" alt=""> |
||||
|
</div> |
||||
|
</template> |
||||
|
<div v-for="(item1, index1) in item.list" @click="setActiveCur(item1, item, index)" |
||||
|
:class="['flex-between point', activeFloor == item1[renderKeyNew.subtitle] ? 'curActive' : '']" |
||||
|
:key="index1"> |
||||
|
<span class="l-menu-name">{{ item1[renderKeyNew.subtitle] }}</span> |
||||
|
<img v-if="item1[renderKeyNew.SubSelected]" class="selected" src="../assets/menu/is_selected.png" alt=""> |
||||
|
</div> |
||||
|
</el-collapse-item> |
||||
|
</el-collapse> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import store from '../store'; |
||||
|
import GuipToolTip from "@/components/GuipToolTip.vue"; |
||||
|
|
||||
|
export default { |
||||
|
options: { styleIsolation: "shared" }, |
||||
|
props: { |
||||
|
menuData: { |
||||
|
type: [Array, Object], |
||||
|
required: true |
||||
|
}, |
||||
|
// 后续如果渲染的值不同可以通过调整这个修改取值 |
||||
|
renderKey: { |
||||
|
type: [Object], |
||||
|
default: () => {} |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
activeNames: [],//获取到的值 同 绑定的name属性 |
||||
|
curIndex: 0, |
||||
|
activeFloor: '', |
||||
|
scrollLock: false, |
||||
|
componentsName: '' |
||||
|
}; |
||||
|
}, |
||||
|
computed: { |
||||
|
// 处理options为空的情况 |
||||
|
menuList() { |
||||
|
let flag = Array.isArray(this.menuData || []); |
||||
|
let data = this.menuData || [] |
||||
|
if (!flag) { |
||||
|
data = Object.values(this.menuData) |
||||
|
return data |
||||
|
} |
||||
|
return this.menuData; |
||||
|
}, |
||||
|
renderKeyNew() { |
||||
|
return Object.assign({ |
||||
|
menuTitle: 'name',//渲染标题 |
||||
|
subtitle: 'name',//二级标题 |
||||
|
selected: 'is_select',//大标题:是否展示绿色状态 |
||||
|
SubSelected: 'is_select',//二级标题:是否展示绿色状态 |
||||
|
domName: 'type',//未来获取dom绑定依据 |
||||
|
domAppend: 'section_' //dom 前缀 |
||||
|
// 后续获取dom =》 domAppend + domName 在元素上绑定的 id名称 |
||||
|
}, this.renderKey); |
||||
|
} |
||||
|
}, |
||||
|
components: { |
||||
|
GuipToolTip |
||||
|
}, |
||||
|
watch: { |
||||
|
menuList(newVal) { |
||||
|
let subtitle = this.renderKeyNew.subtitle; |
||||
|
this.activeFloor = newVal[this.curIndex]?.list?.[0][subtitle]; |
||||
|
this.componentsName = newVal[this.curIndex]?.list?.[0]?.componentsName || ''; |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.$nextTick(() => { |
||||
|
// 更可靠的获取滚动容器方式 |
||||
|
this.scrollContainer = document.querySelector('.main-content') || |
||||
|
document.getElementById('main-content') || |
||||
|
window; |
||||
|
|
||||
|
if (this.scrollContainer) { |
||||
|
this.scrollContainer.addEventListener('scroll', this.handleScroll); |
||||
|
this.calculateFloorOffsets(); |
||||
|
} else { |
||||
|
console.error('未找到滚动容器'); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
beforeDestroy() { |
||||
|
if (this.scrollContainer) { |
||||
|
this.scrollContainer.removeEventListener('scroll', this.handleScroll); |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
handleChange(val) { |
||||
|
console.log("面板状态变化:", val); |
||||
|
}, |
||||
|
handleTitleClick() { |
||||
|
console.log("标题点击"); |
||||
|
}, |
||||
|
random() { |
||||
|
var randomNumber = Math.random(); |
||||
|
return randomNumber |
||||
|
}, |
||||
|
calculateFloorOffsets() { |
||||
|
this.menuList.forEach(item => { |
||||
|
item.list?.forEach(every => { |
||||
|
const el = document.getElementById(every.desc); |
||||
|
if (el) { |
||||
|
// 获取元素相对于视口顶部的距离 |
||||
|
every.offsetTop = el.getBoundingClientRect().top + window.pageYOffset; |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
handleScroll() { |
||||
|
if (this.scrollLock || !this.menuList[this.curIndex]?.list) return; |
||||
|
|
||||
|
const scrollPosition = this.getScrollPosition(); |
||||
|
let activeFloor = null; |
||||
|
console.log(this.curIndex, scrollPosition, 'this.curIndex==='); |
||||
|
|
||||
|
// 从下往上查找当前可见区域对应的楼层 |
||||
|
// for (let i = this.menuList[this.curIndex].list.length - 1; i >= 0; i--) { |
||||
|
// const item = this.menuList[this.curIndex].list[i]; |
||||
|
// if (scrollPosition + 100 >= (item.offsetTop || 0)) { // 100是提前量 |
||||
|
// activeFloor = item.desc; |
||||
|
// break; |
||||
|
// } |
||||
|
// } |
||||
|
// 从上往下 |
||||
|
for (let i = 0; i < this.menuList[this.curIndex].list.length; i++) { |
||||
|
const item = this.menuList[this.curIndex].list[i]; |
||||
|
if (scrollPosition + 100 >= (item.offsetTop || 0)) { // 100是提前量 |
||||
|
activeFloor = item.desc; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (activeFloor && this.activeFloor !== activeFloor) { |
||||
|
this.activeFloor = activeFloor; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getScrollPosition() { |
||||
|
if (this.scrollContainer === window) { |
||||
|
return window.pageYOffset || document.documentElement.scrollTop; |
||||
|
} |
||||
|
return this.scrollContainer.scrollTop; |
||||
|
}, |
||||
|
// setActiveCur(dom, item) { |
||||
|
// if (this.$route.path != item.path) { |
||||
|
// this.$router.push(item.path) |
||||
|
// store.commit('SET_PAGETITLE', item.name); |
||||
|
// } |
||||
|
// setTimeout(() => { |
||||
|
// this.activeFloor = dom; |
||||
|
// this.setHighActive(dom) |
||||
|
// }, 500) |
||||
|
// }, |
||||
|
|
||||
|
// setHighActive(dom) { |
||||
|
// this.scrollLock = true; |
||||
|
// const ele = document.getElementById(dom) |
||||
|
// if (!ele) return |
||||
|
// ele.classList.add('ceshi') |
||||
|
// ele.scrollIntoView({ behavior: 'smooth', block: 'start' }) |
||||
|
// setTimeout(() => { |
||||
|
// ele.classList.remove('ceshi') |
||||
|
// }, 1000) |
||||
|
// } |
||||
|
setActiveCur(item1, item, index) { |
||||
|
this.curIndex = index; |
||||
|
let subtitle = this.renderKeyNew.subtitle; |
||||
|
let domName = this.renderKeyNew.domName; |
||||
|
let componentsName = item1?.componentsName; |
||||
|
// 如果采用的是 组件切换显示的办法 |
||||
|
if (componentsName) { |
||||
|
this.activeFloor = componentsName; |
||||
|
// 同步 当前切换的组件名称 |
||||
|
store.commit('SET_COMPONENTS_NAME', componentsName); |
||||
|
return |
||||
|
} |
||||
|
this.scrollLock = true; |
||||
|
this.activeFloor = item1[subtitle]; |
||||
|
this.$nextTick(() => { |
||||
|
this.setHighActive(item1[domName]); |
||||
|
setTimeout(() => { |
||||
|
this.scrollLock = false; |
||||
|
}, 1000); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
setHighActive(dom) { |
||||
|
const ele = document.getElementById(this.renderKeyNew.domAppend + dom); |
||||
|
if (ele) { |
||||
|
ele.scrollIntoView({ |
||||
|
behavior: 'smooth', |
||||
|
block: 'start' |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
/* 自定义折叠图标 */ |
||||
|
::v-deep .el-collapse-item__header .el-collapse-item__arrow { |
||||
|
display: none !important; |
||||
|
} |
||||
|
|
||||
|
::v-deep .el-collapse-item__header { |
||||
|
max-width: 120px; |
||||
|
} |
||||
|
|
||||
|
.arrow_img { |
||||
|
width: 14px; |
||||
|
height: 14px; |
||||
|
} |
||||
|
|
||||
|
.arrow_img.is-active { |
||||
|
transform: rotate(90deg); |
||||
|
} |
||||
|
|
||||
|
.collapse-item { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.menu-name { |
||||
|
font-size: 14px; |
||||
|
font-weight: normal; |
||||
|
letter-spacing: 0.08em; |
||||
|
color: #1E2226; |
||||
|
padding: 11px 0; |
||||
|
height: 40px; |
||||
|
line-height: 18px; |
||||
|
max-width: 80px; |
||||
|
box-sizing: border-box; |
||||
|
overflow: hidden; |
||||
|
white-space: nowrap; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
.l-menu-name { |
||||
|
max-width: 90px; |
||||
|
overflow: hidden; |
||||
|
white-space: nowrap; |
||||
|
text-overflow: ellipsis; |
||||
|
color: #8A9099; |
||||
|
padding: 9px 0; |
||||
|
height: 36px; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.curActive { |
||||
|
.l-menu-name { |
||||
|
color: #006AFF; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.menu-select { |
||||
|
width: 8px; |
||||
|
height: 8px; |
||||
|
border-radius: 100%; |
||||
|
background: #00C261; |
||||
|
} |
||||
|
|
||||
|
.el-collapse { |
||||
|
min-width: 158px; |
||||
|
max-width: 158px; |
||||
|
background-color: #fff; |
||||
|
height: 100%; |
||||
|
overflow-y: auto; |
||||
|
padding: 21px 22px 21px 16px; |
||||
|
box-sizing: border-box; |
||||
|
box-shadow: 0px 0px 11px 2px rgba(147, 147, 147, 0.11); |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
width: 20px; |
||||
|
height: 14px; |
||||
|
} |
||||
|
|
||||
|
::v-deep .el-collapse-item__header { |
||||
|
height: 40px; |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
::v-deep .el-collapse-item__content { |
||||
|
padding-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
::v-deep .el-collapse-item__wrap { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
</style> |
Loading…
Reference in new issue