左右侧联动滚动
参数 | 描述 | 类型 | 可选值 | 必须 | 默认值 | 版本 |
---|---|---|---|---|---|---|
left | 左侧项 | Array | 是 | |||
right | 右侧项 | Array | 是 | |||
endTop | 结束位置到顶部的距离 | Number | 否 | 230 | ||
leftDefaultValue | 左侧初始选中项 | Number | 否 | 0 | ||
emptyProps | empty 组件的扩展属性 | Object | 否 | {description: ‘未获取到相关数据’,imageSize: 150,image:‘’} | ||
currentStyle | 左侧被选中项的样式 | Object | 否 |
名称 | 描述 | 参数 | 版本 |
---|---|---|---|
left-item | 左侧项 | ||
right-title | 右侧标题 | ||
right-item | 右侧项 | ||
right-bottom | 右侧底部 |
index.vue
<template>
<div class="dgz-linkage-rolling">
<div v-show="left.length !== 0">
<section class="dgz-linkage-rolling-content" :style="menuHeightStyle">
<div ref="left" class="dgz-linkage-rolling-content-left">
<ul>
<li
class="dgz-linkage-rolling-content-left-item left-item-hook"
:style="index === currentIndex && newCurrentStyle"
v-for="(item, index) in left"
:key="item.id"
@click="item && selectItem(index)"
>
<slot name="left-item" :item="item">
<div>{{ item.leftName }}</div>
</slot>
</li>
</ul>
</div>
<div ref="right" class="dgz-linkage-rolling-content-right">
<ul>
<li v-for="(item, index) in right" :key="index" class="right-item-hook">
<slot name="right-title" :item="item">
<div class="dgz-linkage-rolling-content-right-title">{{ item.leftName }}</div>
</slot>
<ul class="dgz-linkage-rolling-content-right-item">
<li v-for="items in item.children" :key="items.id">
<slot name="right-item" :item="items">
<div class="dgz-linkage-rolling-content-right-item-item">
<span>{{ items.rightName }}</span>
<van-icon name="arrow" class="dgz-linkage-rolling-content-right-item-item-arrow" />
</div>
</slot>
</li>
</ul>
<div v-show="index === right.length - 1">
<slot name="right-bottom">
<div class="dgz-linkage-rolling-content-right-bottom" :style="lastHeightStyle">
❤ 更多内容,敬请期待 ❤
</div>
</slot>
</div>
</li>
</ul>
</div>
</section>
</div>
<van-empty v-show="left.length === 0" v-bind="emptyProps" />
</div>
</template>
<script>
import BScroll from 'better-scroll'
import props from './props'
export default {
name: 'DgzLinkageRolling',
props: props,
data() {
return {
rightHeights: [],
scrollY: 0, // 实时获取当前y轴的高度
lastHeight: 0
}
},
computed: {
newCurrentStyle() {
return {
color: '#2689ff',
backgroundColor: '#fff',
fontSize: '13px',
...this.currentStyle
}
},
menuHeightStyle() {
return { height: `${window.innerHeight}px` }
},
lastHeightStyle() {
return { height: `${window.innerHeight - this.endTop}px` }
},
currentIndex() {
const i = this.rightHeights.findIndex((itemHeight, index) => {
if (index + 1 !== this.left.length) {
return this.scrollY >= itemHeight && this.scrollY < this.rightHeights[index + 1]
} else {
return this.left.length - 1
}
})
if (i !== -1 && i !== this.left.length) {
this.initLeftScroll(i) // 滑动左边移动右边的部门栏
}
return i
}
},
watch: {
left: {
immediate: true,
handler(newV, oldV) {
this.$nextTick(() => {
if (newV.length !== 0) {
this.initScroll()
this.rights.scrollToElement(
this.$refs.right.getElementsByClassName('right-item-hook')[this.leftDefaultValue],
200
)
}
})
}
},
right: {
immediate: true,
handler(newV, oldV) {
this.$nextTick(() => {
if (newV.length !== 0) {
this.getRightHeight()
}
})
}
}
},
methods: {
// 初始化
initScroll() {
this.lefts = new BScroll(this.$refs.left, { click: true, probeType: 2 })
this.rights = new BScroll(this.$refs.right, { click: true, probeType: 2 })
this.rights.on('scroll', pos => {
const y = Math.abs(Math.round(pos.y)) // 当前滚动的高度
const maxY = Math.abs(Math.round(this.rightHeights[this.rightHeights.length - 2])) // 最大可以滚动高度
if (maxY > y) {
if (this.timer !== null) clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.scrollY = y
}, 300)
}
})
this.rights.on('scrollEnd', pos => {
this.scrollY = Math.abs(Math.round(pos.y))
})
},
// 右侧每一项的高度
getRightHeight() {
const rightItems = this.$refs.right.getElementsByClassName('right-item-hook')
let rightHeight = 0
this.rightHeights.push(rightHeight)
for (let i = 0; i < rightItems.length; i++) {
const item = rightItems[i]
// 解决最后一个item数据不能点击
if (i === rightItems.length - 2) {
this.lastHeight = item.clientHeight
}
rightHeight += item.clientHeight
this.rightHeights.push(rightHeight)
}
},
// 选择左边的item项
selectItem(index) {
if (this.$refs.right) {
this.rights.scrollToElement(this.$refs.right.getElementsByClassName('right-item-hook')[index], 200)
}
},
// 左右联动滚动
initLeftScroll(index) {
if (this.$refs.left) {
this.lefts.scrollToElement(this.$refs.left.getElementsByClassName('left-item-hook')[index], 200)
}
}
}
}
</script>
<style lang="less">
@import './index.less';
</style>
props.js
export default {
left: {
type: Array,
require: true,
default: () => [],
description: '左侧项'
},
right: {
type: Array,
require: true,
default: () => [],
description: '右侧项'
},
endTop: {
type: Number,
default: 230,
description: '结束位置到顶部的距离'
},
leftDefaultValue: {
type: Number,
default: 0,
description: '左侧初始选中项,从0开始'
},
emptyProps: {
type: Object,
default: () => ({
description: '未获取到相关数据',
imageSize: 150,
image:
'https://cdn.digitalcnzz.com/static/upload/luban/images/2023-04-13/luban-b0378674-150c-0db7-b996-8fbc45ab9edc.png'
}),
description: 'Empty组件的扩展属性'
},
currentStyle: {
type: Object,
description: '左侧被选中项的样式'
}
}
index.less
.dgz-linkage-rolling {
&-content {
display: flex;
position: relative;
overflow: hidden;
&-left {
cursor: pointer;
background-color: #efeff3;
width: 110px;
font-size: 13px;
font-weight: 400;
color: #555555;
line-height: 24px;
li {
padding: 13px 16px;
}
}
&-right {
background-color: #fff;
flex: 1;
&-title {
padding: 13px 16px;
font-size: 14px;
font-weight: 700;
color: #555555;
line-height: 24px;
}
&-item {
padding: 0 16px;
&-item {
font-size: 15px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
&-arrow {
font-size: 13px;
color: #888888;
}
}
}
&-bottom {
text-align: center;
font-size: 14px;
font-weight: 400;
line-height: 24px;
margin-top: 40px;
color: red;
}
}
}
}
<template>
<div class="container">
<DgzScroll :right="right" :left="left" :leftDefaultValue="3">
<template #left-item="{ item }">
<div class="left-item">
<van-icon name="phone-o" />
{{ item.leftName }}
</div>
</template>
<template #right-title="{ item }">
<div class="right-title">{{ item.leftName }}</div>
</template>
<template #right-item="{ item }">
<div class="right-item" @click="clickRightItem(item)">
<van-row type="flex" align="center" justify="space-between" class="row">
<van-col span="4">
<van-icon name="newspaper-o" class="icon" size="28px" />
</van-col>
<van-col span="18">{{ item.rightName }}</van-col>
<van-col span="2"><van-icon name="arrow" class="arrow" /></van-col>
</van-row>
</div>
</template>
<template #right-bottom>
<div class="right-bottom">❤ 更多内容,敬请期待 ❤</div>
</template>
</DgzScroll>
</div>
</template>
<script>
import DgzScroll from '@/components/DgzScroll'
import { getMatterListApi } from '@/api'
export default {
components: { DgzScroll },
data() {
return {
right: [],
left: []
}
},
created() {
this.getMatterList()
},
computed: {},
mounted() {},
methods: {
getMatterList() {
getMatterListApi()
.then(res => {
const { code, data = [] } = res
if (code === 0) {
this.right = data
this.left = data.map(d => ({ id: d.id, leftName: d.leftName }))
}
})
.catch(err => {
if (err.code === '404') {
this.$toast('查询列表失败')
}
})
},
clickRightItem(item) {
this.$toast(`点击了${item.rightName}`)
}
}
}
</script>
<style lang="scss" scoped>
.container {
height: 100%;
width: 100%;
.left-item {
display: flex;
justify-content: center;
align-items: center;
}
.right-title {
padding: 13px 16px;
font-size: 14px;
font-weight: 700;
color: #555555;
line-height: 24px;
}
.right-item {
.row {
::v-deep .van-col--18 {
margin: 13px 0;
font-size: 14px;
font-weight: 400;
line-height: 24px;
color: #555555;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
.icon {
color: #2689ff;
}
.arrow {
font-size: 13px;
color: #888888;
}
}
.right-bottom {
color: red;
text-align: center;
font-size: 14px;
font-weight: 400;
line-height: 24px;
height: 750px;
margin-top: 40px;
}
}
</style>