左右侧联动滚动 - Vue业务组件

一、组件展示

左右侧联动滚动

二、组件开发

props

参数 描述 类型 可选值 必须 默认值 版本
left 左侧项 Array
right 右侧项 Array
endTop 结束位置到顶部的距离 Number 230
leftDefaultValue 左侧初始选中项 Number 0
emptyProps empty 组件的扩展属性 Object {description: ‘未获取到相关数据’,imageSize: 150,image:‘’}
currentStyle 左侧被选中项的样式 Object

slot

名称 描述 参数 版本
left-item 左侧项
right-title 右侧标题
right-item 右侧项
right-bottom 右侧底部

2.1.视图

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>

2.2.接收参数

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: '左侧被选中项的样式'
  }
}

2.3.样式

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>

你可能感兴趣的:(vue.js,javascript,前端)