页面需求:
- 点击左侧版本号,右侧展示对应版本内容并置于顶部
- 右侧某一内容滚动到顶部时,左侧需要展示高亮
实现效果:

实现代码:
<template>
<div>
<div class="historyBox pd-20 bg-white">
<div class="w100 flex h100" v-if="versionList.length > 0">
<div class="left size-14">
<div
v-for="(item, index) in versionList"
:key="index"
class="leftItem pd-10 pointer"
:class="index == activeIndex ? 'isActive' : ''"
@click="gotoTarget(index)"
>
<div>{{ item.versionNumber }}</div>
<div>{{ item.releaseTime }}</div>
</div>
</div>
<div class="right">
<div
v-for="(item, index) in versionList"
:key="index"
class="rightItem pd-20 center"
:class="index == activeIndex ? 'isActive' : ''"
>
<div v-html="item.versionDescription" class="ql-editor w60"></div>
</div>
</div>
</div>
<div class="w100 h100 center size-16 gray-2" v-else>暂无版本记录</div>
</div>
</div>
</template>
<script>
import { listAllVersion } from "./components/api";
export default {
name: "VersionHistory",
data() {
return {
versionList: [],
activeIndex: 0,
clickIndex: 0,
scrollIndex: 0,
scrollStopTimer: null,
};
},
created() {
this.getList();
},
mounted() {},
methods: {
checkItemsHover() {
const rightBox = document.querySelector(".right");
rightBox.addEventListener("scroll", this.checkItemsAtTop);
window.addEventListener("load", this.checkItemsAtTop);
},
checkItemsAtTop() {
const rightBox = document.querySelector(".right");
const rightItems = Array.from(rightBox.querySelectorAll(".rightItem"));
rightItems.forEach((item, index) => {
const rect = item.getBoundingClientRect();
const containerRect = rightBox.getBoundingClientRect();
if (
rect.top - containerRect.top <= 0 &&
rect.bottom - containerRect.top >= 0
) {
this.scrollIndex = index;
}
if (this.scrollStopTimer) {
clearTimeout(this.scrollStopTimer);
}
this.scrollStopTimer = setTimeout(this.onScrollStopped, 150);
});
},
onScrollStopped() {
if (this.scrollIndex < this.clickIndex) {
this.activeIndex = this.clickIndex;
this.clickIndex = 0;
} else {
this.activeIndex = this.scrollIndex;
}
const leftItems = document.querySelectorAll(".leftItem");
const leftBox = document.querySelector(".left");
const targetLeftItem = leftItems[this.activeIndex];
const offsetTop = targetLeftItem.offsetTop - leftBox.offsetTop;
leftBox.scrollTo({
top: offsetTop,
behavior: "smooth",
});
},
gotoTarget(index) {
this.clickIndex = index;
this.activeIndex = index;
const rightItems = document.querySelectorAll(".rightItem");
const rightBox = document.querySelector(".right");
const targetRightItem = rightItems[index];
const offsetTop = targetRightItem.offsetTop - rightBox.offsetTop + 1;
rightBox.scrollTo({
top: offsetTop,
behavior: "smooth",
});
},
getList() {
listAllVersion().then((response) => {
this.versionList = response.data;
this.$nextTick(() => {
this.checkItemsHover();
});
});
},
},
};
</script>
<style lang="scss" scoped>
@import "./components/quill.snow.css";
.historyBox {
height: calc(100vh - 90px);
::-webkit-scrollbar {
width: 6px;
// height: 24px;
}
::-webkit-scrollbar-track {
background: #eee;
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
background: rgb(158, 203, 255);
border-radius: 12px;
&:hover {
background: rgb(95, 169, 253);
}
}
.left {
width: 260px;
min-width: 260px;
height: 100%;
// height: calc(100vh - 117px);
overflow-y: auto;
.leftItem {
display: flex;
justify-content: space-evenly;
align-items: center;
border: 1px solid #eee;
}
.isActive {
border: 1px solid #3f8cff;
border-left: 4px solid #3f8cff;
color: #3f8cff;
background: rgba(63, 140, 255, 0.1);
font-weight: bold;
}
}
.right {
box-sizing: border-box;
width: calc(100% - 260px);
height: 100%;
// height: calc(100vh - 117px);
overflow-y: auto;
background: #eef6ff;
.rightItem {
border: 1px dotted #eef6ff;
&:hover {
border: 1px dotted #ddd;
}
.w60 {
width: 60%;
}
}
.isActive {
// border: 1px dotted #ddd;
border: 1px dotted #3f8cff;
// box-shadow: 0px 0px 20px #3f8cff;
// box-shadow: 0px 5.04px 10.08px rgba(55, 114, 233, 0.22),
// inset 0px 5.04px 10.08px rgba(211, 221, 242, 1);
}
}
}
</style>