<template>
	<view
		class="slider-range-body"
		:class="{
			disabled: disabled,
			padding: tips || scaleInfo.show
		}">
		
		<!-- 刻度线 -->
		<view class="scale-bar" v-if="scaleInfo.show">
			<view
				class="scale-item"
				v-for="(scaleItem, scaleIndex) in scaleLine" :key="scaleIndex"
				:style="{
					left: scaleItem.left,
					color: scaleItem.color,
					fontSize: scaleItem.fontSize,
				}">
				{{ scaleItem.label }}
				<view
					class="scale-item-line"
					:style="{
						backgroundColor: scaleItem.tickColor
					}"
				/>
			</view>
		</view>
		
		<!-- 滑动提示 -->
		<view class="tips-bar" v-if="tips">
			<view
				class="tips-item"
				v-if="dragging == 'firstBlock'"
				:style="{
					left: getFirstBlockPosition,
					zIndex: firstBlock.zIndex
				}">
				<!-- #ifndef MP-WEIXIN -->
				<slot name="tips" :value="firstValue">
					<view class="default-tips">{{ firstValue }}</view>
				</slot>
				<!-- #endif -->
				<!-- #ifdef MP-WEIXIN -->
				<slot name="firstTips">
					<view class="default-tips">{{ firstValue }}</view>
				</slot>
				<!-- #endif -->
			</view>
			<view
				class="tips-item"
				v-if="dragging == 'secondBlock'"
				:style="{
					left: getSecondBlockPosition,
					zIndex: secondBlock.zIndex
				}">
				<!-- #ifndef MP-WEIXIN -->
				<slot name="tips" :value="secondValue">
					<view class="default-tips">{{ secondValue }}</view>
				</slot>
				<!-- #endif -->
				<!-- #ifdef MP-WEIXIN -->
				<slot name="secondTips">
					<view class="default-tips">{{ secondValue }}</view>
				</slot>
				<!-- #endif -->
			</view>
		</view>
		
		<!-- 滑动条 -->
		<view
			class="slider-bar"
			:style="[getBackgroundStyle]">
			<view
				class="slider-active-bar"
				:style="[getActiveStyle]"
			></view>
		</view>
		
		<!-- 滑块 -->
		<view class="block-bar">
			<view
				class="block-item"
				v-if="firstBlock.show"
				:style="{
					left: getFirstBlockPosition,
					zIndex: firstBlock.zIndex
				}"
				data-sort="firstBlock"
				@touchstart.stop.prevent="onTouchStart"
				@touchmove.stop.prevent="onTouchMove"
				@touchend.stop.prevent="onTouchEnd">
				<!-- #ifndef MP-WEIXIN -->
				<slot name="block">
					<view class="default-block block-font"></view>
				</slot>
				<!-- #endif -->
				<!-- #ifdef MP-WEIXIN -->
				<slot name="firstBlock">
					<view class="default-block block-font"></view>
				</slot>
				<!-- #endif -->
			</view>
			<view
				class="block-item"
				v-if="secondBlock.show"
				:style="{
					left: getSecondBlockPosition,
					zIndex: secondBlock.zIndex
				}"
				data-sort="secondBlock"
				@touchstart.stop.prevent="onTouchStart"
				@touchmove.stop.prevent="onTouchMove"
				@touchend.stop.prevent="onTouchEnd">
				<!-- #ifndef MP-WEIXIN -->
				<slot name="block">
					<view class="default-block block-font"></view>
				</slot>
				<!-- #endif -->
				<!-- #ifdef MP-WEIXIN -->
				<slot name="secondBlock">
					<view class="default-block block-font"></view>
				</slot>
				<!-- #endif -->
			</view>
			<!-- 这个元素用来撑高度 -->
			<view class="block-placeholder">
				<!-- #ifndef MP-WEIXIN -->
				<slot name="block">
					<view class="default-block block-font"></view>
				</slot>
				<!-- #endif -->
				<!-- #ifdef MP-WEIXIN -->
				<slot name="placeholderBlock">
					<view class="default-block block-font"></view>
				</slot>
				<!-- #endif -->
			</view>
		</view>
		
	</view>
</template>

<script>
	/**
	* @description 区间滑动选择组件
	* @example <slider-range></slider-range>
	* @property {number|array<number>} value 初始值，为 number 时表示滑动选择器，为 array<number> 时表示区间滑动选择器
	* @property {string} valueType 当 value 为 number 时，可滑动的是哪个值，可选值：min、max，默认 max
	* @property {number} min 最小值（最左侧值）默认 0
	* @property {number} max 最大值（最右侧值）默认 100
	* @property {number} step 步长，为 0 时不设步长，默认为 1
	* @property {number|string} height 滑动条高度，可自定义单位，默认单位 rpx
	* @property {boolean} disabled 是否禁用，默认 false
	* @property {boolean} tips 是否显示滑动提示，默认为 true
	* @property {boolean|object} scale 刻度条配置
	* @property {object} backgroundStyle 背景条自定义样式
	* @property {object} activeStyle 选中条自定义样式
	**/
	// 补全单位
	const completeUnit = (val = 0, unit = 'rpx') => {
		if(isNaN(Number(val))) return val
		return val + unit
	}
	// 乘法，防止小数计算失真
	const multiplication = (val1, val2) => {
		let dotIndex = String(val2).indexOf('.')
		if(dotIndex != -1){
			let floatLength = String(val2).length - (dotIndex + 1)
			return val1 * (val2 * Math.pow(10, floatLength)) / Math.pow(10, floatLength)
		}else{
			return val1 * val2
		}
	}
	export default {
		name:"slider-range",
		props: {
			value: {
				type: [Number, Array],
				default: () => []
			},
			valueType: {
				type: String,
				default: 'max'
			},
			min: {
				type: Number,
				default: 0
			},
			max: {
				type: Number,
				default: 100
			},
			step: {
				type: Number,
				default: 1
			},
			height: {
				type: [Number, String],
				default: 12
			},
			disabled: {
				type: Boolean,
				default: false
			},
			tips: {
				type: Boolean,
				default: true
			},
			scale: {
				type: [Boolean, Object],
				default: true
			},
			backgroundStyle: {
				type: Object,
				default: () => {
					return {width: '100%'}
				}
			},
			activeStyle: {
				type: Object,
				default: () => {
					return {}
				}
			}
		},
		data() {
			return {
				// 两个值
				firstValue: 0,
				secondValue: 0,
				// 两个滑点
				firstBlock: {
					show: true,
					zIndex: 1,
				},
				secondBlock: {
					show: true,
					zIndex: 2,
				},
				// 滑动中
				dragging: '',
				// 刻度线设置
				scaleInfo: {
					show: true,
					min: true,
					max: true,
					interval: 'auto',
					color: '#333',
					tickColor: '#999',
					fontSize: 22,
					format: null,
				},
				scaleLine: [],
			}
		},
		computed: {
			getStep(){
				if(this.max - this.min < this.step) return this.max - this.min
				return this.step
			},
			// 滑点坐标
			getFirstBlockPosition(){
				return (this.firstValue - this.min) / (this.max - this.min) * 100 + '%'
			},
			getSecondBlockPosition(){
				return (this.secondValue - this.min) / (this.max - this.min) * 100 + '%'
			},
			// 背景条样式
			getBackgroundStyle(){
				return {
					...this.backgroundStyle,
					height: completeUnit(this.height)
				}
			},
			// 选中条样式
			getActiveStyle(){
				let min = Math.min(this.firstValue, this.secondValue)
				let max = Math.max(this.firstValue, this.secondValue)
				return {
					...this.activeStyle,
					left: (min - this.min) / (this.max - this.min) * 100 + '%',
					width: (max - min) / (this.max - this.min) * 100 + '%',
				}
			}
		},
		mounted() {
			this.create()
		},
		methods: {
			create(){
				if(this.max <= this.min){
					throw '[slider-range] max 属性不应小于或等于 min 属性'
				}
				if(typeof this.value == 'number'){
					let value = this.value
					if(this.value > this.max) value = this.max
					if(this.value < this.min) value = this.min
					if(this.valueType == 'max'){
						this.firstBlock.show = false
						this.firstValue = this.min
						this.secondValue = value
					}else{
						this.secondBlock.show = false
						this.secondValue = this.max
						this.firstValue = value
					}
				}else{
					let firstValue = this.value[0] || this.min
					if(firstValue > this.max) firstValue = this.max
					if(firstValue < this.min) firstValue = this.min
					let secondValue = this.value[1] || this.max
					if(secondValue > this.max) secondValue = this.max
					if(secondValue < this.min) secondValue = this.min
					this.firstValue = firstValue
					this.secondValue = secondValue
					this.firstBlock.show = true
					this.secondBlock.show = true
				}
				// 计算刻度值
				if(typeof this.scale != 'object'){
					this.scaleInfo.show = !!this.scale
				}else{
					this.scaleInfo = {...this.scaleInfo, ...this.scale}
				}
				if(!this.scaleInfo.show) return
				let interval = this.scaleInfo.interval
				let step = this.getStep > 0 ? this.getStep : 1
				if(typeof interval != 'number'){
					interval = Math.ceil((this.max - this.min) / step / 10)
				}else{
					interval = interval + 1
				}
				let cumsum = this.min
				let arr = []
				while(cumsum <= this.max){
					arr.push({
						value: cumsum,
						label: cumsum,
						color: this.scaleInfo.color,
						fontSize: completeUnit(this.scaleInfo.fontSize),
						tickColor: this.scaleInfo.tickColor,
						left: (cumsum - this.min) / (this.max - this.min) * 100 + '%'
					})
					cumsum = cumsum + multiplication(interval, step)
				}
				if(arr[0].value == this.min && !this.scaleInfo.min) arr = arr.slice(1)
				if(arr[arr.length -1].value == this.max && !this.scaleInfo.max) arr = arr.slice(0, arr.length -1)
				if(arr[arr.length -1].value != this.max && this.scaleInfo.max) arr.push({
					value: this.max,
					label: this.max,
					color: this.scaleInfo.color,
					fontSize: completeUnit(this.scaleInfo.fontSize),
					tickColor: this.scaleInfo.tickColor,
					left: '100%'
				})
				let format = this.scaleInfo.format
				if(typeof format == 'function'){
					arr = arr.map((item, index) => {
						return format(item, index)
					})
				}
				this.scaleLine = [...arr]
				this.$forceUpdate()
			},
			onTouchStart(e){
				if(this.disabled) return
				this.dragging = e.currentTarget.dataset.sort
				e.currentTarget.dataset.sort == 'firstBlock' ? this.firstBlock.zIndex = this.secondBlock.zIndex + 1 : this.secondBlock.zIndex = this.firstBlock.zIndex + 1
				this.startDragPostion = e.changedTouches ? e.changedTouches[0].pageX : e.pageX
				this.startValue = e.currentTarget.dataset.sort == 'firstBlock' ? this.firstValue : this.secondValue
				this.$emit('start', {
					block: e.currentTarget.dataset.sort,
					value: this.startValue,
					values: [Math.min(this.firstValue, this.secondValue), Math.max(this.firstValue, this.secondValue)]
				})
			},
			onTouchMove(e){
				if(this.disabled) return
				this.onDrag(e)
			},
			onTouchEnd(e){
				this.dragging = ''
				if(this.disabled) return
				this.onDrag(e, true)
			},
			onDrag(e, end = false){
				let pageX = e.changedTouches ? e.changedTouches[0].pageX : e.pageX
				let view = uni.createSelectorQuery().in(this).select('.block-bar')
				view.boundingClientRect(data => {
					let diff = ((pageX - this.startDragPostion) / data.width) * (this.max - this.min)
					this.onUpdateValue(this.startValue + diff, e.currentTarget.dataset.sort, end)
				}).exec()
			},
			onUpdateValue(value, sort, end = false){
				if(value < this.min) value = this.min
				if(value > this.max) value = this.max
				if(this.getStep > 0 && value != this.min && value != this.max){
					value = this.min + multiplication(Math.round((value - this.min) / this.getStep), this.getStep)
				}else{
					value = Number(value.toFixed(2))
				}
				if(sort == 'firstBlock'){
					this.firstValue = value
				}else{
					this.secondValue = value
				}
				this.$emit('change', {
					firstValue: this.firstValue,
					secondValue: this.secondValue,
					values: [Math.min(this.firstValue, this.secondValue), Math.max(this.firstValue, this.secondValue)]
				})
				if(end){
					this.$emit('end', {
						block: sort,
						value: value,
						values: [Math.min(this.firstValue, this.secondValue), Math.max(this.firstValue, this.secondValue)]
					})
				}
			}
		}
	}
</script>

<style lang="scss" scoped>
@font-face {
	font-family: 'block-font';
	src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAK8AAsAAAAABnAAAAJvAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACCcApsgQgBNgIkAwgLBgAEIAWEZwcvG8MFyJ6aPCkSYgMLUMxWfTCChHj4WkPf39sgkItGUKiiTMrOVnmWnRpxV/v99mPaxBdpKnZP/N7cW6x5SV7/UAI0T6aVIZMZ2movuV6lLBp1KX/zBrwzj+/46Y16pUYVDyjxgOQB04slFMJMxEMeBfVaXwOQEc8I1FpWL+CwvXcUYt6WyIEkQezRIGuON11dgjCdlmVaqxYq0b1ZfFNBek+3wNfo8/FfcYSRVBTstpPXNgnOf+KdBYGb5SbFD5vz9dDHEipQYB7IxEV3+UigaJIC1YaDjNLOLPgX/18GIbFrv1B/nd1YD/qp4Z6MnulWawlua6JhYG1UGzHs5uiDw3/gSRZb18/XZ8qc05vx1z2uG3vcbsrWSqdLJiZKpkrvXDVUUzV/ljaCI8mt5X0x7WM8rmu/cb8rjlNrjKHW/BKOQCNAeVLsFYvgQ/Djd7pqIbb26roM/hrt71GyPMmARUH1fsPBT3I+gMRsZEn5WAY5nAyf11br5CjBjrf6Gutpu5l6qNY2IlqlL0Oh2iiVuVlUqLOKStUOUGtO2+E6bVyBItdg0g5BaOZH0ug7Cs0uqMw9o0Knb1Rq9odaVxF9Xp3JYC1CXiJZwuUN1Bl3FNdNkdZhWV2xlkRdYVwWnk5zWJRfWK51yI4sptjgrarFRAoqgtvYLh+TLYujK7ghM8rXiNzqggKl6S35jNvQQpAtITIJWrYB6RjOoQRkKn7fMJlqhWWJAKnxsuDRqXZUJF8hQHU4HGjgTl7xrFIVI0SBFAJnQ+16lsxi4ZDbPMsgY0g+bUTRVa3AISmoI399l/1px6CW7U3hTClUPFqcmQAAAA==') format('woff2');
}
.block-font {
	font-family: "block-font" !important;
	font-style: normal;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
  &:before{
	content: "\e917";
  }
}

.slider-range-body{
	--main-color: #333333;
	--primary-color: #2979ff;
	width: 100%;
	box-sizing: border-box;
	position: relative;
	&.disabled{
		opacity: .7;
	}
	&.padding{
		padding-top: 72rpx;
	}
	.slider-bar{
		width: 100%;
		height: 12rpx;
		background-color: #f8f8f8;
		border-radius: 999999rpx;
		overflow: hidden;
		position: relative;
		.slider-active-bar{
			height: 100%;
			background-color: var(--primary-color);
			border-radius: 999999rpx;
			position: absolute;
		}
	}
	.block-bar{
		width: 100%;
		margin-top: 8rpx;
		position: relative;
		.block-item{
			position: absolute;
			top: 0;
			left: 0;
			transform: translateX(-50%);
		}
		.block-placeholder{
			opacity: 0!important;
			user-select: none;
			pointer-events: none;
		}
		.block-item, .block-placeholder{
			.default-block{
				color: var(--primary-color);
				font-size: 42rpx;
			}
		}
	}
	.tips-bar{
		width: 100%;
		height: 60rpx;
		position: absolute;
		top: 0;
		.tips-item{
			position: absolute;
			top: 0;
			left: 0;
			transform: translateX(-50%);
			.default-tips{
				width: 60rpx;
				height: 60rpx;
				display: flex;
				align-items: center;
				justify-content: center;
				border-radius: 50%;
				background-color: #ffffff;
				box-shadow: 0 0 20rpx rgba(0, 0, 0, .4);
				color: var(--main-color);
				font-size: 22rpx;
				font-weight: 500;
			}
		}
	}
	.scale-bar{
		width: 100%;
		height: 60rpx;
		position: absolute;
		top: 0;
		.scale-item{
			display: flex;
			flex-direction: column;
			align-items: center;
			color: #333333;
			font-size: 22rpx;
			position: absolute;
			bottom: 0;
			transform: translateX(-50%);
			.scale-item-line{
				width: 2rpx;
				height: 8rpx;
				background-color: #999999;
			}
		}
	}
}
</style>