<template>
  <view
    class="slider-range"
    :class="{ disabled: disabled }"
    :style="{ paddingLeft: blockSize / 2 + 'px', paddingRight: blockSize / 2 + 'px' }"
  >
    <view class="slider-range-inner" :style="{ height: height + 'px' }">
      <view
        class="slider-bar"
        :style="{
          height: barHeight + 'px',
        }"
      >
        <!-- 背景条 -->
        <view
          class="slider-bar-bg"
          :style="{
            backgroundColor: backgroundColor,
          }"
        ></view>

        <!-- 滑块实际区间 -->
        <view
          class="slider-bar-inner"
          :style="{
            width: ((values[1] - values[0]) / (max - min)) * 100 + '%',
            left: lowerHandlePosition + '%',
            backgroundColor: activeColor,
          }"
        ></view>
      </view>

      <!-- 滑动块-左 -->
      <view
        class="slider-handle-block"
        :class="{ decoration: decorationVisible }"
        :style="{
          backgroundColor: blockColor,
          width: blockSize + 'px',
          height: blockSize + 'px',
          left: lowerHandlePosition + '%',
        }"
        @touchstart="_onTouchStart"
        @touchmove="_onBlockTouchMove"
        @touchend="_onBlockTouchEnd"
        data-tag="lowerBlock"
      ></view>

      <!-- 滑动块-右 -->
      <view
        class="slider-handle-block"
        :class="{ decoration: decorationVisible }"
        :style="{
          backgroundColor: blockColor,
          width: blockSize + 'px',
          height: blockSize + 'px',
          left: higherHandlePosition + '%',
        }"
        @touchstart="_onTouchStart"
        @touchmove="_onBlockTouchMove"
        @touchend="_onBlockTouchEnd"
        data-tag="higherBlock"
      ></view>

      <!-- 滑块值提示 -->
      <view v-if="tipVisible" class="range-tip" :style="lowerTipStyle">{{ format(values[0]) }}</view>
      <view v-if="tipVisible" class="range-tip" :style="higherTipStyle">{{ format(values[1]) }}</view>
    </view>
  </view>
</template>
<script>
export default {
  components: {},
  props: {
    //滑块区间当前取值
    value: {
      type: Array,
      default: function() {
        return [0, 100]
      },
    },
    //最小值
    min: {
      type: Number,
      default: 0,
    },
    //最大值
    max: {
      type: Number,
      default: 100,
    },
    step: {
      type: Number,
      default: 1,
    },
    format: {
      type: Function,
      default: function(val) {
        return val
      },
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    //滑块容器高度
    height: {
      height: Number,
      default: 50,
    },
    //区间进度条高度
    barHeight: {
      type: Number,
      default: 5,
    },
    //背景条颜色
    backgroundColor: {
      type: String,
      default: '#e9e9e9',
    },
    //已选择的颜色
    activeColor: {
      type: String,
      default: '#1aad19',
    },
    //滑块大小
    blockSize: {
      type: Number,
      default: 20,
    },
    blockColor: {
      type: String,
      default: '#fff',
    },
    tipVisible: {
      type: Boolean,
      default: true,
    },
    decorationVisible: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      values: [this.min, this.max],
      startDragPos: 0, // 开始拖动时的坐标位置
      startVal: 0, //开始拖动时较小点的值
    }
  },
  computed: {
    // 较小点滑块的坐标
    lowerHandlePosition() {
      return ((this.values[0] - this.min) / (this.max - this.min)) * 100
    },
    // 较大点滑块的坐标
    higherHandlePosition() {
      return ((this.values[1] - this.min) / (this.max - this.min)) * 100
    },
    lowerTipStyle() {
      if (this.lowerHandlePosition < 90) {
        return `left: ${this.lowerHandlePosition}%;`
      }
      return `right: ${100 - this.lowerHandlePosition}%;transform: translate(50%, -100%);`
    },
    higherTipStyle() {
      if (this.higherHandlePosition < 90) {
        return `left: ${this.higherHandlePosition}%;`
      }
      return `right: ${100 - this.higherHandlePosition}%;transform: translate(50%, -100%);`
    },
  },
  created: function() {},
  onLoad: function(option) {},
  watch: {
    //滑块当前值
    value: {
      immediate: true,
      handler(newVal, oldVal) {
        if (this._isValuesValid(newVal) && (newVal[0] !== this.values[0] || newVal[1] !== this.values[1])) {
          this._updateValue(newVal)
        }
      },
    },
  },
  methods: {
    _updateValue(newVal) {
      // 步长大于区间差,或者区间最大值和最小值相等情况
      if (this.step >= this.max - this.min) {
        throw new RangeError('Invalid slider step or slider range')
      }

      let newValues = []
      if (Array.isArray(newVal)) {
        newValues = [newVal[0], newVal[1]]
      }
      if (typeof newValues[0] !== 'number') {
        newValues[0] = this.values[0]
      } else {
        newValues[0] = Math.round((newValues[0] - this.min) / this.step) * this.step + this.min
      }
      if (typeof newValues[1] !== 'number') {
        newValues[1] = this.values[1]
      } else {
        newValues[1] = Math.round((newValues[1] - this.min) / this.step) * this.step + this.min
      }

      // 新值与原值相等,不做处理
      if (this.values[0] === newValues[0] && this.values[1] === newValues[1]) {
        return
      }
      // 左侧滑块值小于最小值时,设置为最小值
      if (newValues[0] < this.min) {
        newValues[0] = this.min
      }
      // 右侧滑块值大于最大值时,设置为最大值
      if (newValues[1] > this.max) {
        newValues[1] = this.max
      }
      // 两个滑块重叠或左右交错,使两个滑块保持最小步长的间距
      if (newValues[0] >= newValues[1]) {
        // 左侧未动,右侧滑块滑到左侧滑块之左
        if (newValues[0] === this.values[0]) {
          newValues[1] = newValues[0] + this.step
        } else {
          // 右侧未动, 左侧滑块滑到右侧之右
          newValues[0] = newValues[1] - this.step
        }
      }

      this.values = newValues
      this.$emit('change', this.values)
    },
    _onTouchStart: function(event) {
      if (this.disabled) {
        return
      }

      this.isDragging = true
      let tag = event.target.dataset.tag

      //兼容h5平台及某版本微信
      let e = event.changedTouches ? event.changedTouches[0] : event
      this.startDragPos = e.pageX

      this.startVal = tag === 'lowerBlock' ? this.values[0] : this.values[1]
    },
    _onBlockTouchMove: function(e) {
      if (this.disabled) {
        return
      }
      this._onDrag(e)
    },
    _onBlockTouchEnd: function(e) {
      if (this.disabled) {
        return
      }
      this.isDragging = false
      this._onDrag(e)
    },
    _onDrag(event) {
      if (!this.isDragging) {
        return
      }
      let view = uni
        .createSelectorQuery()
        .in(this)
        .select('.slider-range-inner')

      view
        .boundingClientRect(data => {
          let sliderWidth = data.width
          const tag = event.target.dataset.tag
          let e = event.changedTouches ? event.changedTouches[0] : event
          let diff = ((e.pageX - this.startDragPos) / sliderWidth) * (this.max - this.min)
          let nextVal = this.startVal + diff

          if (tag === 'lowerBlock') {
            this._updateValue([nextVal, null])
          } else {
            this._updateValue([null, nextVal])
          }
        })
        .exec()
    },
    _isValuesValid: function(values) {
      return Array.isArray(values) && values.length == 2
    },
  },
}
</script>

<style scoped>
.slider-range {
  position: relative;
  padding-top: 40rpx;
}

.slider-range-inner {
  position: relative;
  width: 100%;
}

.slider-range.disabled .slider-bar-inner {
  opacity: 0.35;
}

.slider-range.disabled .slider-handle-block {
  cursor: not-allowed;
}

.slider-bar {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
}

.slider-bar-bg {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 10000px;
  z-index: 10;
}

.slider-bar-inner {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 10000px;
  z-index: 11;
}

.slider-handle-block {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  box-shadow: 0 0 3px 2px rgba(227, 229, 241, 0.5);
  z-index: 12;
}

.slider-handle-block.decoration::before {
  position: absolute;
  content: '';
  width: 6upx;
  height: 24upx;
  top: 50%;
  left: 29%;
  transform: translateY(-50%);
  background: #eeedf2;
  border-radius: 3upx;
  z-index: 13;
}

.slider-handle-block.decoration::after {
  position: absolute;
  content: '';
  width: 6upx;
  height: 24upx;
  top: 50%;
  right: 29%;
  transform: translateY(-50%);
  background: #eeedf2;
  border-radius: 3upx;
  z-index: 13;
}

.range-tip {
  position: absolute;
  top: 0;
  font-size: 24upx;
  color: #666;
  transform: translate(-50%, -100%);
}
</style>