先看效果:
lp20240428
我在开发过程中,遇到这样一个需求,表格的数据根据类别来定位。当用户在列表上上下滑动的时候,右侧的类别也跟着上下变更;当用户点击右侧的类别的时候,左侧的列表也要迅速定位到该类别的数据上。
实现步骤:
1.处理数据
我这里用for循环模拟了一部分数据,具体数据如下图所示,其中最重要的是groupId,判断这条数据的类别。
接下来就是groupArray右侧显示的类别,通过groupArray循环来设置列表数据里每个group第一个科目的group名称。
2.实现表格滚动导航,右侧的类别跟着上下变更
这里我们就需要用到IntersectionObserver(交叉观察器),observe:开始监听特定元素(需要接收一个target参数,值是Element类型,用来指定被监听
的目标元素)。具体如下图所示:
3.实现点击右侧的类别,左侧的列表也要迅速定位到该类别的数据上
添加右侧的类别点击事件
通过HTMLCollectionOf<HTMLElement>获取offsetTop,在表格里使用setScrollTop直接定位到该类别的数据。
完整代码如下:
<template>
<div class="voucher">
<div class="content-box">
<el-table
id="accountListTable"
class="account-list-table"
ref="dataListTableRef"
:data="dataList"
row-key="id"
border
style="width: 100%"
header-cell-class-name="custome-table-header"
:row-class-name="
({ row }) => (row._groupAnchorsId ? row._groupAnchorsId : '')
"
>
<el-table-column prop="name" label="姓名" />
<el-table-column
prop="age"
align="center"
label="年龄"
min-width="100"
/>
<el-table-column
prop="sex"
align="center"
label="性别"
min-width="100"
/>
<el-table-column
prop="work"
align="center"
label="工作经历"
min-width="100"
/>
<el-table-column
prop="address"
align="center"
label="住址"
min-width="100"
/>
</el-table>
</div>
<div class="side-bar-index">
<div class="anchor-box">
<div class="anchor-content">
<template v-for="(item, index) of groupArray" :key="item.id">
<div
class="anchor-link"
@click="scrollTableTo(`groupAnchors_${item.value}`)"
>
<span>{{
currentTableScrollId === `groupAnchors_${item.value}`
? item.value
: ''
}}</span>
</div>
<div v-if="index + 1 < groupArray.length" class="line"></div>
</template>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { ElTable } from 'element-plus'
const currentTableScrollId = ref('groupAnchors_类一')
// 大类别id
let groupArray = ref<any>([
{
key: 1,
value: '类一',
},
{
key: 2,
value: '类二',
},
{
key: 3,
value: '类三',
},
{
key: 4,
value: '类四',
},
{
key: 5,
value: '类五',
},
])
const dataListTableRef = ref<InstanceType<typeof ElTable>>()
//
const dataList = ref<any>([])
// 数据
function getList() {
for (let i = 0; i < 100; i++) {
dataList.value.push({
id: `${i + 1}`,
name: `姓名${i + 1}`,
age: `${i + 1}`,
sex: `${i % 2 == 0 ? '女' : '男'}`,
work: `工作${i + 1}`,
address: `住址${i + 1}`,
groupId: `${
i < 20
? '1'
: i >= 20 && i < 40
? '2'
: i >= 40 && i < 60
? '3'
: i >= 60 && i < 80
? '4'
: '5'
}`,
})
}
groupArray.value.forEach((item: { key: number; value: string }) => {
const findIndex = dataList.value.findIndex(
(_: any) => +_.groupId === item.key
)
if (findIndex !== -1) {
// 设置每个group第一个科目的group名称
dataList.value[findIndex]._groupAnchorsId = `groupAnchors_${item.value}`
}
})
// 新建交叉监视器用于滚动导航
const io = new IntersectionObserver(
(entries) => {
// 如果达到显示比例
if (entries[0].isIntersecting) {
// 获取节点 class
const targetClass = entries[0].target.getAttribute('class')
// 获取 name
const targetId = targetClass
? targetClass
.split(' ')
.find((item) => item.includes('groupAnchors_'))
: ''
// 赋值设置右侧导航
currentTableScrollId.value = targetId || currentTableScrollId.value
}
},
// 显示比例为 0.5 时 isIntersecting 为 true
{ threshold: 0.5 }
)
nextTick(() => {
groupArray.value.forEach((item: { value: any }) => {
const target = document.getElementsByClassName(
`groupAnchors_${item.value}`
)[0]
if (target) {
io.observe(target)
}
})
})
}
// 表格锚点定位
const scrollTableTo = (id: string) => {
const element = document.getElementsByClassName(
id
) as HTMLCollectionOf<HTMLElement>
if (dataList.value && dataList.value.length > 0 && element) {
dataListTableRef.value!.setScrollTop(element[0].offsetTop)
nextTick(() => {
currentTableScrollId.value = id
})
}
}
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
.voucher {
display: flex;
align-items: flex-start;
width: 100%;
height: 100%;
padding: 16px;
overflow: auto;
.content-box {
display: flex;
flex-direction: column;
width: calc(100% - 38px - 16px);
height: 100%;
padding: 16px;
background: #fff;
border-radius: 4px;
}
.account-list-table {
width: 100%;
height: calc(100vh - 200px);
}
.side-bar-index {
display: flex;
flex-direction: column;
margin-left: 16px;
.anchor-box {
padding: 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
}
.anchor-content {
display: flex;
flex-direction: column;
align-items: center;
.anchor-link {
display: flex;
align-items: center;
justify-content: center;
min-width: 10px;
min-height: 10px;
cursor: pointer;
border: 1px solid red;
border-radius: 50%;
}
span {
padding: 2px;
font-family: PingFangSC, 'PingFang SC';
font-size: 12px;
font-weight: 400;
line-height: 24px;
color: red;
}
.line {
width: 1px;
height: 16px;
background-color: red;
}
}
}
}
</style>