0
点赞
收藏
分享

微信扫一扫

vue实现升级版级联选择(二)

钎探穗 2022-02-14 阅读 38
vue.js前端

1 效果展示

2 组件源码

<template>
  <div class="page">
    <!-- 遮罩层,支持点击遮罩关闭级联选择 -->
    <div 
      class="mask"
      @click="close"
    />
     <!-- 级联选择部分 -->
    <div :class="['cascader', 'animation', isVisible ? 'moveInBottom' : 'moveOutBottom']">
      <!-- 顶部标题 -->
      <div class="title">
        {{ title }}
        <!-- 关闭按钮 -->
        <i 
          v-if="leftIcon"
          :class="['back', {'arrow': leftIcon === 'arrow', 'cross': leftIcon === 'cross'}]"
          @click="close"
        />
      </div>
      <!-- 展示各级选择的结果 -->
      <ul class="nav-list">
        <template v-for="(item, index) in (selectedOptions.length + 1)">
          <li
            :key="index"
            :class="['nav-list-item', {'active': index === tabIndex, 'more-hidden': moreHidden}]"
            @click="changeIndex(index)"
          >
            {{ (selectedOptions[index] && selectedOptions[index][fieldNames.text]) || placeholder }}
          </li>
        </template>
      </ul>
      <!-- 展示各级选择列表数据 -->
      <div 
        class="select-content"
        :style="styleObj"
      >
        <ol 
          class="select-list"
          v-for="(item1, index1) in pickerDataArr" :key="index1"
          ref="selectListRefs"
        >
          <template v-for="(item2, index2) in item1">
            <li
              :key="index2"
              class="select-list-item"
              @click="select(item2)"
            >
              <span class="text">{{ item2[fieldNames.text] }}</span>
              <i
                v-if="selectedOptions.includes(item2)"
                class="choose-active"
              />
            </li>
          </template>
        </ol>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'cascaderPicker',
  props: {
    // 可选项数据源
    options: {
      type: Array,
      default: () => []
    },
    // 顶部标题
    title: {
      type: String,
      default: ''
    },
    // 未选中时的提示文案	 
    placeholder: {
      type: String,
      default: '请选择'
    },
    // 各级选择的结果文字展示溢出是否隐藏
    moreHidden: {
      type: Boolean,
      default: false
    },
    // 自定义选择层级数
    level: {
      type: Number,
      default: 3
    },
    // 自定义options结构中的字段
    fieldNames: {
      type: Object,
      default: () => ({
        text: 'text',
        value: 'value',
        chlidren: 'children'
      })
    },
    // 关闭图标:arrow(左箭头)/cross(交叉)
    leftIcon: {
      type: String,
      default: 'arrow'
    },
    // 
  },
  data() {
    return {
      // 当前选择级索引
      tabIndex: 0,
      // 各级选择的结果组合
      selectedOptions: [],
      // 每一级列表组合
      pickerDataArr: [],
      isVisible: false,
      styleObj: {
        width: '',
        transform: '',
        transitionDuration: '0.3s'
      }
    }
  },
  created () {
    // 默认展示第一级列表数据(最外层)
    this.pickerDataArr.push(this.options)
    this.isVisible = true
  },
  mounted () {
    this.$nextTick(() => {
      // 设置多级选择列表并排的最大宽度
      this.styleObj.width = this.level * this.$refs['selectListRefs'][0].offsetWidth + 'px'
    })
  },
  watch: {
    tabIndex () {
      // 当选择级数改变时,需要切换当前列表位置
      this.styleObj.transform = `translateX(-${this.tabIndex * this.$refs['selectListRefs'][0].offsetWidth}px)`
    }
  },
  methods: {
    changeIndex (index) {
      // 改变当前选择级数索引
      this.tabIndex = index
    },
    select (item) {
      // 先删除当前级开始选中的旧数据,再保存当前级选中的结果
      this.selectedOptions.splice(this.tabIndex, this.selectedOptions.length, item)
      // 下一级没有子元素及子元素长度为0 或者 当前选择级数等于设定的层级数,结束选择
      if (!(item[this.fieldNames.children] && item[this.fieldNames.children].length) || this.tabIndex + 1 >= this.level) {
        this.isVisible = false
        setTimeout(() => {
          this.$emit('finish', this.selectedOptions)
        }, 300)
        return
      }
      // 下一级数据变更前,滚动位置需要置0
      this.$refs['selectListRefs'].forEach((ele, index) => {
        if (index > this.tabIndex) {
          ele.scrollTop = 0
        }
      })
      this.tabIndex++
      // 先删除下一级开始的旧选择列表数据,再保存当前的下一级列表数据
      this.pickerDataArr.splice(this.tabIndex, this.pickerDataArr.length, item[this.fieldNames.children])
    },
    close () {
      this.isVisible = false
			setTimeout(() => {
        this.$emit('close')
			}, 300)
    }
  }
}
</script>

<style lang="less" scoped>
.page{
  height: 100%;
}
.mask{
  position: fixed;
  right: 0;
  left: 0;
  top: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, .7);
  z-index: 99;
}
.cascader{
  position: fixed;
  right: 0;
  left: 0;
  bottom: 0;
  height: 60%;
  background-color: #fff;
  border-radius: 16px 16px 0 0;
  display: flex;
  flex-direction: column;
  z-index: 999;
  .title{
    position: relative;
    padding: 20px 0;
    font-size: 18px;
    line-height: 18px;
    text-align: center;
    .back{
      position: absolute;
      top: 19px;
      left: 20px;
      width: 20px;
      height: 20px;
    }
    .arrow{
      background: url('./images/icon-arrow.png') no-repeat;
      background-size: 100%;
    }
    .cross{
      background: url('./images/icon-close.png') no-repeat;
      background-size: 100%;
    }
  }
  .nav-list{
    display: flex;
    padding: 0 20px;
    font-size: 16px;
    text-align: center;
    .nav-list-item{
      padding: 14px 0;
      margin-right: 16px;
      max-width: 64px;
    }
    .nav-list-item:last-child{
      margin-right: 0;
    }
    .active{
      position: relative;
      color: #1752ff;
    }
    .active::after{
      position: absolute;
      left: 0;
      bottom: 0;
      display: block;
      content: '';
      width: 100%;
      height: 3px;
      background-color: #1752ff;
    }
    .more-hidden{
      align-items: center;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }
  .select-content{
    flex: 1;
    overflow: hidden;
    display: flex;
    .select-list{
      height: 100%;
      width: 100vw;
      overflow-y: auto;
      font-size: 16px;
      .select-list-item{
        padding: 12px 20px;
        display: flex;
        align-items: center;
        justify-content: space-between;
        .text{
          max-width: 264px;
        }
        .choose-active{
          width: 20px;
          height: 20px;
          background: url('./images/icon-choose.png') no-repeat;
          background-size: 100%;
        }
      }
    }
    .line{
      position: relative;
    }
    .line::after{
      position: absolute;
      left: 0;
      bottom: 0;
      display: block;
      content: '';
      width: 100%;
      height: 1px;
      background-color: #ccc;
      transform: scaleY(0.5);
    }
    .select-list::-webkit-scrollbar{
      display: none;
    }
  }
}
.animation{
  animation-duration: 0.3s;
  animation-fill-mode: forwards;
}
.moveInBottom{
  animation-name: moveInBottom;
}
.moveOutBottom{
  animation-name: moveOutBottom;
}
@keyframes moveInBottom{
  0%{
    transform: translateY(100%);
    opacity: 0;
  }
  100%{
    transform: translateY(0);
    opacity: 1;
  }
}
@keyframes moveOutBottom{
  0%{
    transform: translateY(0);
    opacity: 1;
  }
  100%{
    transform: translateY(100%);
    opacity: 0;
  }
}
</style>

3 使用说明

  • 可以参考 vue实现简易版级联选择(一),组件调用方式保持不变。
举报

相关推荐

0 条评论