<template>
  <view
    class="tn-slider-class tn-slider"
    :class="{'tn-slider--disabled': disabled}"
    :style="{
      backgroundColor: inactiveColor
    }"
    @tap="click"
  >
    <!-- slider滑动线 -->
    <view
      class="tn-slider__gap"
      :style="[
        barStyle,
        {
          height: this.$tn.string.getLengthUnitValue(lineHeight),
          backgroundColor: activeColor
        }
      ]"
    >
      <!-- slider滑块 -->
      <view
        class="tn-slider__button-wrap"
        @touchstart="touchStart"
        @touchmove="touchMove"
        @touchend="touchEnd"
        @touchcancel="touchEnd"
      >
        <view v-if="$slots.default || $slots.$default">
          <slot></slot>
        </view>
        <view
          v-else
          class="tn-slider__button"
          :style="[blockStyle, {
            height: this.$tn.string.getLengthUnitValue(blockWidth),
            width: this.$tn.string.getLengthUnitValue(blockWidth),
            backgroundColor: blockColor
          }]"
        ></view>
      </view>
    </view>
  </view>
</template>

<script>
  export default {
    name: 'tn-slider',
    props: {
      // 进度值
      value: {
        type: [Number, String],
        default: 0
      },
      // 最小值
      min: {
        type: Number,
        default: 0
      },
      // 最大值
      max: {
        type: Number,
        default: 100
      },
      // 步进值
      step: {
        type: Number,
        default: 1
      },
      // 禁用
      disabled: {
        type: Boolean,
        default: false
      },
      // 滑块宽度
      blockWidth: {
        type: Number,
        default: 30
      },
      // 滑动条高度
      lineHeight: {
        type: Number,
        default: 8
      },
      // 滑动条激活的颜色
      activeColor: {
        type: String,
        default: '#01BEFF'
      },
      // 滑动条未被激活的颜色
      inactiveColor: {
        type: String,
        default: '#E6E6E6'
      },
      // 滑块的颜色
      blockColor: {
        type: String,
        default: '#FFFFFF'
      },
      // 自定义滑块的样式
      blockStyle: {
        type: Object,
        default() {
          return {}
        }
      }
    },
    data() {
      return {
        startX: 0,
        status: 'end',
        newValue: 0,
        distanceX: 0,
        startValue: 0,
        barStyle: {},
        sliderRect: {
          left: 0,
          width: 0
        }
      }
    },
    watch: {
      value(val) {
        // 只有在非滑动状态时，才可以通过value更新滑块值，这里监听，是为了让用户触发
        if (this.status === 'end') this.updateValue(val, false)
      }
    },
    created() {
      this.updateValue(this.value, false)
    },
    mounted() {
      this._tGetRect('.tn-slider').then(res => {
        this.sliderRect = res
      })
    },
    methods: {
      // 开始滑动
      touchStart(event) {
        if (this.disabled) return
        if (!event.changedTouches[0]) return
        
        this.startX = 0
        // 触摸点
        this.startX = event.changedTouches[0].pageX
        this.startValue = this.format(this.value)
        
        // 标识当前开始触摸
        this.status = 'start'
      },
      // 滑动移动中
      touchMove(event) {
        if (this.disabled) return
        if (!event.changedTouches[0]) return
        
        // 连续触摸的过程会一直触发本方法，但只有手指触发且移动了才被认为是拖动了，才发出事件
        // 触摸后第一次移动已经将status设置为moving状态，故触摸第二次移动不会触发本事件
        if (this.status === 'start') this.$emit('start')
        let movePageX = event.changedTouches[0].pageX
        // 滑块的左边不一定跟屏幕左边接壤，所以需要减去最外层父元素的左边值
        this.distanceX = movePageX - this.sliderRect.left
        // 获得移动距离对整个滑块的百分比值，此为带有多位小数的值，不能用此更新视图
        // 否则造成通信阻塞，需要每改变一个step值时修改一次视图
        this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + this.min
        this.status = 'moving'
        this.$emit('moving')
        
        this.updateValue(this.newValue, true)
      },
      // 滑动结束
      touchEnd() {
        if(this.disabled) return
        if (this.status === 'moving') {
          this.updateValue(this.newValue, false)
          this.$emit('end')
        }
        this.status = 'end'
      },
      // 更新数值
      updateValue(value, drag) {
        // 去掉小数部分，对step进行步进处理
        value = this.format(value)
        const width = Math.round((value - this.min) / (this.max - this.min) * 100)
        // 不允许滑动的距离小于0和超过100
        if (width < 0 || width > 100) return
        // 设置移动的百分比
        let barStyle = {
          width: width + '%'
        }
        // 移动期间取消动画
        if (drag === true) {
          barStyle.transition = 'none'
        } else {
          // 非移动期间，删掉对过渡为空的声明，让css中的声明起效
          delete barStyle.transition
        }
        
        // 修改value值
        this.$emit('input', value)
        this.barStyle = barStyle
      },
      // 点击事件
      click(event) {
        if (this.disabled) return
        // 直接点击的情况，计算方式和touchMove方法一致
        const value = (((event.detail.x - this.sliderRect.left) / this.sliderRect.width) * (this.max - this.min)) + this.min
        this.updateValue(value, false)
      },
      // 格式化滑动的值
      format(value) {
        return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step
      }
    }
  }
</script>

<style lang="scss" scoped>
  .tn-slider {
    width: 100%;
    position: relative;
    border-radius: 1000rpx;
    // 增加点击的范围
    border-width: 20rpx;
    border-style: solid;
    border-color: transparent;
    background-color: #E6E6E6;
    background-clip: content-box;
    
    &__gap {
      position: relative;
      border-radius: inherit;
      transition: width 0.2s;
      background-color: #01BEFF;
    }
    
    &__button {
      width: 30rpx;
      height: 30rpx;
      border-radius: 50%;
      box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.6);
      background-color: #FFFFFF;
      cursor: pointer;
      
      &-wrap {
        position: absolute;
        top: 50%;
        right: 0;
        transform: translate3d(50%, -50%, 0);
      }
    }
    
    &--disabled {
      opacity: 0.6;
    }
  }
</style>
