基于better-scroll实现的类似ios选择器

水平较低,主要是给自己看的。如果要用vue transition,必须每次弹出都初始化better-scroll实例。

<!--
li下必须加子元素包裹,否则选中样式会有延迟。
目前是在任何一列在滚动中都会禁用取消和确认,实际上可以使用stop()方法,不过需要下次弹出前想办法初始化
$data.show控制弹出和收起
$props.type: 场景类型
  1.YMD——年月日,默认initYMD是当前年月日,否则传入new Date(yyyy,mm,dd),注意mm为0-11
  2.SHX——省市区、三级联动,目前没做初始位置逻辑。由于地区名字可能会很长,由filter做了一些不恰当处理
-->
<template>
  <div class="selector">
    <div v-show="show" class="shade"></div>
    <div class="box" :class="{'up':show,'down':!show}">
      <div class="title">
        <button type="button" @click="cancel" :disabled="disableBtn">{{lb}}</button>
        <div>{{title}}</div>
        <button type="button" @click="deal" :disabled="disableBtn">{{rb}}</button>
      </div>
      <div class="section">
        <div class="line"></div>
        <div class="line"></div>
        <!--数据没有关联性的年月日-->
        <div class="lists three" v-if="type==='YMD'">
          <div class="wrapper" ref="first">
            <ul ref="firstUl">
              <li v-for="(v,i) in firstList" :key="i">
                <p :class="getClass(firstIndex, i)">{{v}}</p>
              </li>
            </ul>
          </div>
          <div class="wrapper" ref="second">
            <ul ref="secondUl">
              <li v-for="(v,i) in secondList" :key="i" :ref="i===0?'cell':null">
                <p :class="getClass(secondIndex, i)">{{v}}</p>
              </li>
            </ul>
          </div>
          <div class="wrapper" ref="third">
            <ul ref="thirdUl" class="wheel-scroll">
              <li v-for="(v,i) in thirdList" :key="i" class="wheel-item">
                <p :class="getClass(thirdIndex, i)">{{v}}</p>
              </li>
            </ul>
          </div>
        </div>
        <!--省市县三级联动-->
        <div class="lists threeLink" v-if="type==='SHX'">
          <div class="wrapper" ref="first">
            <ul ref="firstUl" class="wheel-scroll">
              <li v-for="(v,i) in firstList" :key="i" class="wheel-item">
                <p :class="getClass(firstIndex, i)">{{v.name | reduceLetter}}</p>
              </li>
            </ul>
          </div>
          <div class="wrapper" ref="second">
            <ul ref="secondUl">
              <li v-for="(v,i) in secondList" :key="i" :ref="i===0?'cell':null">
                <p :class="getClass(secondIndex, i)">{{v.name | reduceLetter}}</p>
              </li>
            </ul>
          </div>
          <div class="wrapper" ref="third">
            <ul ref="thirdUl">
              <li v-for="(v,i) in thirdList" :key="i">
                <p :class="getClass(thirdIndex, i)">{{v.name | reduceLetter}}</p>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import BScroll from 'better-scroll';
import SHX from 'ass/js/ChinaAreaList';

export default {
  name: 'Selector',
  data() {
    return {
      show: false,
      firstScroll: Function, // 第一列better-scroll对象
      secondScroll: Function,
      thirdScroll: Function,
      firstIndex: 0, // 第一列选中索引
      secondIndex: 0,
      thirdIndex: 0,
      firstList: null, // 第一列数据
      secondList: null,
      thirdList: null,
      lastDate: Date,
      ymdList: Array,
      firstScrolling: false,
      secondScrolling: false,
      thirdScrolling: false
    }
  },
  props: {
    type: {
      type: String,
      default: 'YMD'
    },
    config: {
      type: Object
    },
    lb: {
      type: String,
      default: '取消'
    },
    rb: {
      type: String,
      default: '确认'
    },
    title: {
      type: String,
      default: '日期选择'
    },
    initYMD: { // 初始时间年月日
      type: Date,
      default() {
        return new Date();
      }
    },
    initSHX: {
      type: Array,
      default() {
        return ['110000', '110000'];
      }
    },
    output: null // 输出对象
  },
  computed: {
    disableBtn() {
      if (!this.thirdScrolling && !this.secondScrolling && !this.firstScrolling) {
        return false;
      } else {
        return true;
      }
    }
  },
  filters: {
      reduceLetter(v) {
         const reg = /(省|市|县|自治区|特别行政区|壮族|回族|藏族|土族|维吾尔|自治县|自治州|蒙古族|自治)/g;
         return v.replace(reg, '');
      }
  },
  methods: {
    getYears(len, init) {
      return Array.from({length: len}).map((v, i) => i + init);
    },
    getMonth() {
      return Array.from({length: 12}).map((v, i) => i + 1);
    },
    createYMD(start, lang) { // start开始年份,lang展示
      const d31 = /^(0|2|4|6|7|9|11)$/;
      const d30 = /^(3|5|8|10)$/;
      return Array.from({length: lang}).map((yv, yi) => {
        yv = {};
        yv['year'] = yi + start;
        yv['first'] = Array.from({length: 12}).map((mv, mi) => {
          let len;
          mv = {};
          if (yv.year % 4 === 0 && mi === 1) {
            len = 29;
          } else if (yv.year % 4 !== 0 && mi === 1) {
            len = 28;
          } else if (d30.test(mi)) {
            len = 30;
          } else if (d31.test(mi)) {
            len = 31;
          }
          mv['month'] = mi + 1;
          mv['second'] = Array.from({length: len}).map((dv, di) => {
            dv = di + 1;
            return dv;
          });
          return mv;
        });
        return yv;
      });
    },
    getDate(y, m) {
      let len = 28;
      const d31 = /^(0|2|4|6|7|9|11)$/;
      const d30 = /^(3|5|8|10)$/;
      let year = y || this.initYMD.getFullYear();
      let mon = m || this.initYMD.getMonth();
      if (d31.test(mon)) {
        len = 31;
      } else if (d30.test(mon)) {
        len = 30;
      } else if (mon === 1 && year % 4 === 0) {
        len = 29;
      }
      return Array.from({length: len}).map((v, i) => i + 1);
    },
    getClass(index, key) {
      if ((key === index - 1) || (key === index + 1)) {
        return 'second';
      } else if (key !== index) {
        return 'third';
      }
    },
    initListYMD() {
      let time;
      switch (this.type) {
        case 'MD':
          time = this.initYMD;
          break;
        default:
          time = this.initYMD;
          this.firstList = this.getYears(300, 1900);
          this.secondList = this.getMonth();
          this.thirdList = this.getDate();
          this.firstIndex = time.getFullYear() - 1900;
          this.secondIndex = time.getMonth();
          this.thirdIndex = time.getDate() -1;
          break;
      }
    },
    initScroll(scroll) {
      this.$nextTick(() => {
        if (!this.$refs[scroll]) return;
        this[scroll + 'Scroll'] = new BScroll(this.$refs[scroll], {
          probeType: 3,
          wheel: {
            selectedIndex: this[scroll + 'Index']||0,
            wheelWrapperClass: 'wheel-scroll',
            wheelItemClass: 'wheel-item',
            rotate: 0,
            adjustTime: 0
          }
        });
        const ch = this.$refs.cell[0].clientHeight;
        this[scroll + 'Scroll'].on('scroll', ({y}) => {
          this[scroll + 'Index'] = Math.round(Math.abs(y) / ch);
          this[scroll + 'Scrolling'] = true;
        });
        this[scroll + 'Scroll'].on('scrollEnd', () => {
          this[scroll + 'Scrolling'] = false;
          this[scroll + 'Index'] = this[scroll + 'Scroll'].getSelectedIndex();
          if (this.type === 'YMD' && (scroll === 'first' || scroll === 'second')) {
            let len1 = this.thirdList.length;
            this.thirdList = this.getDate(this.firstIndex + 1900, this.secondIndex);
            let len2 = this.thirdList.length;
            if (len1 === len2) return;
            this.thirdScroll.refresh();
            this.thirdIndex = 0;
            this.thirdScroll.scrollTo(0, 0, 300); // 注意:最顶端是0,往下的坐标是负的,时间单位ms
          }
          if (this.type === 'SHX') {
              if (scroll === 'first') {
                  this.secondIndex = 0;
                  this.secondList = this.firstList[this.firstIndex].cityList;
                  this.secondScroll.scrollTo(0, 0, 300);
                  this.thirdIndex = 0;
                  this.thirdList = this.secondList[this.secondIndex].areaList;
                  this.thirdScroll.scrollTo(0, 0, 300);
              }
              if (scroll === 'second') {
                  this.thirdIndex = 0;
                  this.thirdList = this.secondList[this.secondIndex].areaList;
                  this.thirdScroll.scrollTo(0, 0, 300);
              }
          }
        });
      });
    },
    cancel() {
      this.show = false;
      this.$emit('cancel',this.lastDate);
    },
    deal() {
      this.show = false;
      if (this.type === 'YMD') {
        let date = `${this.firstList[this.firstIndex]}-${this.secondList[this.secondIndex]}-${this.thirdList[this.thirdIndex]}`;
        this.$emit('update:output',date);
      } else if (this.type === 'SHX') {
        let arr = [
          {
            code: this.firstList[this.firstIndex].code,
            name: this.firstList[this.firstIndex].name
          },
          {
            code: this.secondList[this.secondIndex].code,
            name: this.secondList[this.secondIndex].name
          },
          this.thirdList[this.thirdIndex]
        ];
        this.$emit('update:output',arr);
      }
    }
  },
  created() {
    console.error('', this);
    switch (this.type) {
      case 'SHX':
        this.firstList = SHX;
        this.secondList = this.firstList[0].cityList;
        this.thirdList = this.secondList[0].areaList;
        break;
      default:
        this.initListYMD();
        this.lastDate = this.initYMD;
    }
    this.initScroll('first');
    this.initScroll('second');
    this.initScroll('third');
    this.ymdList = this.createYMD(1900, 300);
  }
}
</script>

<style scoped lang="less">
  @import "~ass/style/base.less";
  .selector{
    .shade {
      height: 100%;
      width: 100%;
      background: #333;
      opacity: 0.5;
      position: fixed;
      top: 0;
      left: 0;
      z-index:9;
    }
    .box{
      height: 491*@rpx;
      width: 750*@rpx;
      position: absolute;
      bottom: -500*@rpx;
      z-index: 10;
      font-size: 32*@rpx;
      background: @cff;
      display: flex;
      flex-direction: column;
      .title{
        height: 88*@rpx;
        width: 100%;
        display: flex;
        justify-content: space-between;
        border-bottom: 1px solid @ce5;
        div{
          line-height: 88*@rpx;
        }
        button{
          padding: 0 42*@rpx;
          color:@c80;
        }
        button:last-of-type{
          color:@c10;
        }
      }
      .section{
        width: 100%;
        flex:1;
        padding: 0 40*@rpx;
        position: relative;
        .wrapper{
          padding: 160*@rpx 0;
          height: 402*@rpx;
          overflow: hidden;
          ul{
            li{
              p{
                height: 100%;
                width: 100%;
                line-height: 80*@rpx;
                text-align: center;
                font-size: 40*@rpx;
                white-space:nowrap;
                overflow: hidden;
                pointer-events: none;
              }
              .second{
                color: @c99;
                font-size: 36*@rpx;
              }
              .third{
                color: @ccc;
                font-size: 32*@rpx;
              }
            }
          }
        }
        .lists{
          width: 100%;
          display: flex;
          justify-content: space-between;
        }
        .three{
          padding: 0 40*@rpx 0 80*@rpx;
          li{
            width: 126*@rpx;
          }
        }
        .threeLink{
          li{
            width: 220*@rpx;
            overflow: hidden;
          }
        }
        .line{
          width: 670*@rpx;
          height: 0;
          border-bottom: 1px solid @ccc;
          position: absolute;
          top: 160*@rpx;
        }
        .line:nth-of-type(2){
          top: 240*@rpx;
        }
      }
    }
    .up{
      transform: translateY(-500*@rpx);
      transition: transform .3s ease-in-out;
    }
    .down{
      transform: translateY(500*@rpx);
      transition: transform .3s ease-in-out;
    }
  }
</style>


复制代码
<!--HTML font-size,rem-->
    var htmlFontSize = document.documentElement.clientWidth / 375 * 100 + 'px';
    var bodyFontSize = '16px';
    var styleDom = document.createElement('style');
    styleDom.innerHTML = 'html{font-size:' + htmlFontSize + '!important;}body{font-size:' + bodyFontSize + '!important;}';
    document.getElementsByTagName('head')[0].appendChild(styleDom)
复制代码
<!--SHX格式-->
[{
    "code": "820000",
    "name": "澳门特别行政区",
    "cityList": [
      {
        "code": "820000",
        "name": "澳门特别行政区",
        "areaList": [
          {
            "code": "820000",
            "name": "澳门特别行政区"
          }
        ]
      }
    ]
  }]
复制代码

转载于:https://juejin.im/post/5cda16b3f265da03ae74e3bc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值