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.
408 lines
10 KiB
408 lines
10 KiB
<template>
|
|
<aside class="sidebar">
|
|
<ul style="margin: 0;">
|
|
<li v-for="(item, index) in menuList" :key="item.path + random()">
|
|
<div
|
|
:class="[curIndex === index ? 'active' : '','flex']" @click="gotoPath(item, index)">
|
|
<SvgIcon1 :iconPath="require(`@/assets/${item.img}`)" defaultColor="#8A9099" :size="16" activeColor="#006AFF"
|
|
:isActive="curIndex === index" />
|
|
<span class="title_text">{{ item.name }}</span>
|
|
</div>
|
|
<p v-for="(item1) in item.list" :key="item1.name" :class="[
|
|
'flex',
|
|
activeFloor === (item1.desc || item1.path || item1.componentsName) ? 'curActive' : ''
|
|
]" @click="setActiveCur(item1, item, index)">
|
|
{{ item1.name }}
|
|
</p>
|
|
</li>
|
|
</ul>
|
|
</aside>
|
|
</template>
|
|
|
|
<script>
|
|
import SvgIcon1 from '@/components/SvgIcon1.vue';
|
|
import { mapState } from 'vuex';
|
|
import store from '../store';
|
|
|
|
export default {
|
|
name: 'Sidebar',
|
|
props: {
|
|
menuList: {
|
|
type: Array,
|
|
required: true
|
|
}
|
|
},
|
|
components: {
|
|
SvgIcon1,
|
|
},
|
|
data() {
|
|
return {
|
|
activeFloor: null,
|
|
curIndex: -1, // 初始化为 -1,表示未找到匹配项
|
|
scrollLock: false,
|
|
componentsName: '',
|
|
// 记录每个子菜单项对应的父菜单索引
|
|
pathToIndexMap: {}
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
'$route.path': {
|
|
immediate: true, // 立即执行一次
|
|
handler(newPath) {
|
|
this.updateActiveMenu(newPath);
|
|
}
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
// 初始化路径映射表
|
|
this.buildPathMap();
|
|
|
|
// 根据当前路由更新激活状态
|
|
this.updateActiveMenu(this.$route.path);
|
|
|
|
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);
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
...mapState(['pageTitle', 'carryParam'])
|
|
},
|
|
|
|
methods: {
|
|
// 构建路径到父菜单索引的映射
|
|
buildPathMap() {
|
|
const map = {};
|
|
this.menuList.forEach((item, parentIndex) => {
|
|
if (item.list && item.list.length) {
|
|
item.list.forEach(subItem => {
|
|
if (subItem.path) {
|
|
map[subItem.path] = parentIndex;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
this.pathToIndexMap = map;
|
|
},
|
|
|
|
// 根据当前路由更新激活的菜单项
|
|
updateActiveMenu(currentPath) {
|
|
// 1. 先尝试匹配二级菜单路径
|
|
const parentIndex = this.pathToIndexMap[currentPath];
|
|
if (parentIndex !== undefined) {
|
|
this.curIndex = parentIndex;
|
|
|
|
// 找到对应的二级菜单项
|
|
const parentItem = this.menuList[parentIndex];
|
|
const subItem = parentItem.list.find(item => item.path === currentPath);
|
|
|
|
if (subItem) {
|
|
// 如果是组件切换模式
|
|
if (subItem.componentsName) {
|
|
this.activeFloor = subItem.componentsName;
|
|
store.commit('SET_COMPONENTS_NAME', subItem.componentsName);
|
|
} else {
|
|
// 如果是路由模式
|
|
this.activeFloor = subItem.desc || subItem.path;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 2. 如果找不到匹配的二级路径,尝试匹配一级路径
|
|
for (let i = 0; i < this.menuList.length; i++) {
|
|
const item = this.menuList[i];
|
|
if (item.path && item.path === currentPath) {
|
|
this.curIndex = i;
|
|
this.activeFloor = item.path;
|
|
|
|
// 如果有子菜单,默认选中第一个
|
|
if (item.list && item.list.length) {
|
|
const firstSubItem = item.list[0];
|
|
if (firstSubItem.componentsName) {
|
|
store.commit('SET_COMPONENTS_NAME', firstSubItem.componentsName);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 3. 如果都没有匹配,默认选中第一个菜单项
|
|
if (this.curIndex === -1 && this.menuList.length > 0) {
|
|
this.curIndex = 0;
|
|
const firstItem = this.menuList[0];
|
|
if (firstItem.list && firstItem.list.length) {
|
|
const firstSubItem = firstItem.list[0];
|
|
this.activeFloor = firstSubItem.desc || firstSubItem.path || firstSubItem.componentsName;
|
|
if (firstSubItem.componentsName) {
|
|
store.commit('SET_COMPONENTS_NAME', firstSubItem.componentsName);
|
|
}
|
|
} else {
|
|
this.activeFloor = firstItem.path;
|
|
}
|
|
}
|
|
},
|
|
|
|
random() {
|
|
return Math.random();
|
|
},
|
|
|
|
calculateFloorOffsets() {
|
|
if (this.curIndex === -1 || !this.menuList[this.curIndex]?.list) return;
|
|
|
|
this.menuList[this.curIndex].list?.forEach(every => {
|
|
const el = document.getElementById(every.desc);
|
|
if (el) {
|
|
every.offsetTop = el.getBoundingClientRect().top + window.pageYOffset;
|
|
}
|
|
});
|
|
},
|
|
|
|
handleScroll() {
|
|
if (this.scrollLock || this.curIndex === -1 || !this.menuList[this.curIndex]?.list) return;
|
|
|
|
const scrollPosition = this.getScrollPosition();
|
|
let activeFloor = null;
|
|
|
|
// 从上往下查找当前可见区域对应的楼层
|
|
for (let i = 0; i < this.menuList[this.curIndex].list.length; i++) {
|
|
const item = this.menuList[this.curIndex].list[i];
|
|
if (item.offsetTop && scrollPosition + 100 >= item.offsetTop) {
|
|
activeFloor = item.desc || item.componentsName;
|
|
}
|
|
}
|
|
|
|
if (activeFloor && this.activeFloor !== activeFloor) {
|
|
this.activeFloor = activeFloor;
|
|
}
|
|
},
|
|
|
|
getScrollPosition() {
|
|
if (this.scrollContainer === window) {
|
|
return window.pageYOffset || document.documentElement.scrollTop;
|
|
}
|
|
return this.scrollContainer.scrollTop;
|
|
},
|
|
|
|
gotoPath(item, index) {
|
|
this.curIndex = index;
|
|
let path = item.path;
|
|
|
|
// 如果菜单项没有自己的path但有子菜单,默认选中第一个子项
|
|
if (!path && item.list && item.list.length) {
|
|
const firstSubItem = item.list[0];
|
|
if (firstSubItem.componentsName) {
|
|
this.activeFloor = firstSubItem.componentsName;
|
|
store.commit('SET_COMPONENTS_NAME', firstSubItem.componentsName);
|
|
} else if (firstSubItem.path) {
|
|
// 如果有二级路径,跳转到二级路径
|
|
this.setActiveCur(firstSubItem, item, index);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.activeFloor = path;
|
|
if (!path) {
|
|
this.scrollLock = false;
|
|
this.handleScroll();
|
|
return;
|
|
}
|
|
|
|
if (this.$route.path !== path) {
|
|
this.$router.push({
|
|
path: path,
|
|
query: { ...this.$route.query },
|
|
params: { ...this.$route.params }
|
|
});
|
|
|
|
const dom = document.getElementById('main-content');
|
|
if (dom) dom.scrollTop = 0;
|
|
}
|
|
},
|
|
|
|
setActiveCur(item1, item, index) {
|
|
this.curIndex = index;
|
|
let componentsName = item1?.componentsName;
|
|
|
|
// 组件切换模式
|
|
if (componentsName) {
|
|
this.activeFloor = componentsName;
|
|
store.commit('SET_COMPONENTS_NAME', componentsName);
|
|
return;
|
|
}
|
|
|
|
// 如果 是路由跳转
|
|
// 如果 二级菜单同时存在路由 那么跳转以二级路由为优先
|
|
// 如果 是同一页面 只是页面区域的滚动展示 那么跳转一级路由
|
|
let path = item1.path || item.path;
|
|
if (path && this.$route.path !== path) {
|
|
if (this.carryParam) {
|
|
this.$router.push({
|
|
path: path,
|
|
query: { ...this.$route.query },
|
|
params: { ...this.$route.params }
|
|
});
|
|
} else {
|
|
this.$router.push(path);
|
|
}
|
|
|
|
store.commit('SET_PAGETITLE', item.name);
|
|
|
|
// 如果是跳转到新页面,重置滚动
|
|
const dom = document.getElementById('main-content');
|
|
if (dom) dom.scrollTop = 0;
|
|
}
|
|
|
|
this.scrollLock = true;
|
|
if (item1.desc) {
|
|
this.activeFloor = item1.desc;
|
|
}
|
|
if (item1.path) {
|
|
this.activeFloor = item1.path;
|
|
return;
|
|
}
|
|
|
|
this.$nextTick(() => {
|
|
if (item1.desc) {
|
|
this.setHighActive(item1.desc);
|
|
}
|
|
setTimeout(() => {
|
|
this.scrollLock = false;
|
|
}, 1000);
|
|
});
|
|
},
|
|
|
|
setHighActive(dom) {
|
|
const ele = document.getElementById(dom);
|
|
if (ele) {
|
|
const eles = document.getElementsByClassName('siteMessage');
|
|
for (let i = 0; i < eles.length; i++) {
|
|
eles[i].classList.remove('siteMessage-active');
|
|
}
|
|
ele.classList.add('siteMessage-active');
|
|
setTimeout(() => {
|
|
ele.classList.remove('siteMessage-active');
|
|
}, 1000);
|
|
ele.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.sidebar {
|
|
min-width: 158px;
|
|
padding: 21px;
|
|
box-sizing: border-box;
|
|
background: #FFFFFF;
|
|
box-shadow: 0px 0px 11px 2px rgba(147, 147, 147, 0.11);
|
|
}
|
|
|
|
ul {
|
|
height: 100%;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.ceshi {
|
|
// animation: fadeInOut 2s infinite;
|
|
}
|
|
|
|
.title_text {
|
|
margin-left: 6px;
|
|
}
|
|
|
|
ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
}
|
|
|
|
.not-point {
|
|
pointer-events: none;
|
|
/* 阻止鼠标事件 */
|
|
opacity: 0.5;
|
|
/* 可选,降低透明度以视觉上表示不可用 */
|
|
cursor: not-allowed;
|
|
/* 改变鼠标光标样式,表示不可用 */
|
|
}
|
|
|
|
li {
|
|
margin-bottom: 10px;
|
|
|
|
div {
|
|
letter-spacing: 0.08em;
|
|
color: #1E2226;
|
|
// margin: 12px 0;
|
|
height: 40px;
|
|
cursor: pointer;
|
|
|
|
img {
|
|
margin-right: 6px;
|
|
}
|
|
}
|
|
|
|
p {
|
|
letter-spacing: 0.08em;
|
|
line-height: 18px;
|
|
height: 36px;
|
|
color: #8A9099;
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
color: #006AFF;
|
|
}
|
|
}
|
|
|
|
.curActive {
|
|
color: #006AFF;
|
|
}
|
|
}
|
|
|
|
/* {
|
|
display: block;
|
|
padding: 8px 12px;
|
|
text-decoration: none;
|
|
color: #333;
|
|
border-radius: 4px;
|
|
} */
|
|
/*
|
|
:hover {
|
|
background: #e0e0e0;
|
|
} */
|
|
|
|
.active {
|
|
font-weight: bold;
|
|
letter-spacing: 0.08em;
|
|
color: #006AFF;
|
|
}
|
|
|
|
.item-active {
|
|
color: #006AFF;
|
|
}
|
|
|
|
/*
|
|
.exact-active {
|
|
background: #1976d2;
|
|
color: white;
|
|
} */
|
|
</style>
|