chat-app/src/uni_modules/wot-design-uni/components/wd-slider/wd-slider.vue

358 lines
11 KiB
Vue
Raw Normal View History

2024-11-11 06:46:14 +00:00
<template>
<view :class="rootClass" :style="customStyle" :id="sliderId">
<!-- #ifdef MP-DINGTALK -->
<view :id="sliderId" style="flex: 1" :class="rootClass">
<!-- #endif -->
<view :class="`wd-slider__label-min ${customMinClass}`" v-if="!hideMinMax">
{{ minValue }}
</view>
<view class="wd-slider__bar-wrapper" :style="barWrapperStyle">
<view class="wd-slider__bar" :style="barCustomStyle"></view>
<!-- 左边 -->
<view
class="wd-slider__button-wrapper"
:style="buttonLeftStyle"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
@touchcancel="onTouchEnd"
>
<view class="wd-slider__label" v-if="!hideLabel">{{ leftNewValue }}</view>
<view class="wd-slider__button" />
</view>
<!-- 右边 -->
<view
v-if="showRight"
class="wd-slider__button-wrapper"
:style="buttonRightStyle"
@touchstart="onTouchStartRight"
@touchmove="onTouchMoveRight"
@touchend="onTouchEndRight"
@touchcancel="onTouchEndRight"
>
<view class="wd-slider__label" v-if="!hideLabel">{{ rightNewValue }}</view>
<view class="wd-slider__button" />
</view>
</view>
<view :class="`wd-slider__label-max ${customMaxClass}`" v-if="!hideMinMax">
{{ maxValue }}
</view>
<!-- #ifdef MP-DINGTALK -->
</view>
<!-- #endif -->
</view>
</template>
<script lang="ts">
export default {
name: 'wd-slider',
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { computed, getCurrentInstance, onMounted, ref } from 'vue'
import { getRect, isArray, isDef, isNumber, uuid } from '../common/util'
import { useTouch } from '../composables/useTouch'
import { watch } from 'vue'
import { sliderProps, type SliderExpose } from './types'
const props = defineProps(sliderProps)
const emit = defineEmits(['dragstart', 'dragmove', 'dragend', 'update:modelValue'])
// 存放右滑轮中的所有属性
const rightSlider = {
startValue: 0,
deltaX: 0,
newValue: 0
}
const sliderId = ref<string>(`sliderId${uuid()}`)
const touchLeft = useTouch()
const touchRight = useTouch()
const showRight = ref<boolean>(false)
const barStyle = ref<string>('')
const leftNewValue = ref<number>(0)
const rightNewValue = ref<number>(0)
const rightBarPercent = ref<number>(0)
const leftBarPercent = ref<number>(0)
const trackWidth = ref<number>(0)
const trackLeft = ref<number>(0)
const startValue = ref<number>(0)
const currentValue = ref<number | number[]>(0)
const newValue = ref<number>(0)
const maxValue = ref<number>(100) // 最大值
const minValue = ref<number>(0) // 最小值
const stepValue = ref<number>(1) // 步长
watch(
() => props.max,
(newValue) => {
if (newValue <= props.min) {
maxValue.value = props.min // 交换最大值和最小值
minValue.value = newValue
console.warn('[wot design] warning(wd-slider): max value must be greater than min value')
} else {
maxValue.value = newValue // 更新最大值
}
calcBarPercent()
},
{ immediate: true }
)
watch(
() => props.min,
(newValue) => {
if (newValue >= props.max) {
minValue.value = props.max // 交换最小值和最大值
maxValue.value = newValue
console.warn('[wot design] warning(wd-slider): min value must be less than max value')
} else {
minValue.value = newValue // 更新最小值
}
calcBarPercent()
},
{ immediate: true }
)
watch(
() => props.step,
(newValue) => {
if (newValue < 1) {
stepValue.value = 1
console.warn('[wot design] warning(wd-slider): step must be greater than 0')
} else {
stepValue.value = Math.floor(newValue)
}
},
{ immediate: true }
)
watch(
() => props.modelValue,
(newValue, oldValue) => {
// 类型校验,支持所有值(除null、undefined。undefined建议统一写成void (0)防止全局undefined被覆盖)
if (newValue === null || newValue === undefined) {
emit('update:modelValue', oldValue)
console.warn('[wot design] warning(wd-slider): value can nott be null or undefined')
} else if (isArray(newValue) && newValue.length !== 2) {
console.warn('[wot design] warning(wd-slider): value must be dyadic array')
} else if (!isNumber(newValue) && !isArray(newValue)) {
emit('update:modelValue', oldValue)
console.warn('[wot design] warning(wd-slider): value must be dyadic array Or Number')
}
currentValue.value = newValue
// 动态传值后修改
if (isArray(newValue)) {
if (oldValue && isArray(oldValue) && equal(newValue, oldValue)) return
showRight.value = true
if (leftBarPercent.value <= rightBarPercent.value) {
leftBarSlider(newValue[0])
rightBarSlider(newValue[1])
} else {
leftBarSlider(newValue[1])
rightBarSlider(newValue[0])
}
} else {
if (newValue === oldValue) return
leftBarSlider(newValue)
}
},
{ deep: true, immediate: true }
)
const { proxy } = getCurrentInstance() as any
const rootClass = computed(() => {
const rootClass = `wd-slider ${!props.hideLabel ? 'wd-slider__has-label' : ''} ${props.disabled ? 'wd-slider--disabled' : ''} ${props.customClass}`
return rootClass
})
const barWrapperStyle = computed(() => {
const barWrapperStyle = `${props.inactiveColor ? 'background:' + props.inactiveColor : ''}`
return barWrapperStyle
})
const barCustomStyle = computed(() => {
const barWrapperStyle = `${barStyle.value};${props.activeColor ? 'background:' + props.activeColor : ''}`
return barWrapperStyle
})
const buttonLeftStyle = computed(() => {
const buttonLeftStyle = `left: ${leftBarPercent.value}%; visibility: ${!props.disabled ? 'show' : 'hidden'};`
return buttonLeftStyle
})
const buttonRightStyle = computed(() => {
const buttonRightStyle = `left: ${rightBarPercent.value}%; visibility: ${!props.disabled ? 'show' : 'hidden'}`
return buttonRightStyle
})
onMounted(() => {
initSlider()
})
/**
* 初始化slider宽度
*/
function initSlider() {
getRect(`#${sliderId.value}`, false, proxy).then((data) => {
// trackWidth: 轨道全长
trackWidth.value = Number(data.width)
// trackLeft: 轨道距离左侧的距离
trackLeft.value = Number(data.left)
})
}
function onTouchStart(event: any) {
const { disabled, modelValue } = props
if (disabled) return
touchLeft.touchStart(event)
startValue.value = !isArray(modelValue)
? format(modelValue)
: leftBarPercent.value < rightBarPercent.value
? format(modelValue[0])
: format(modelValue[1])
emit('dragstart', {
value: currentValue.value
})
}
function onTouchMove(event: any) {
const { disabled } = props
if (disabled) return
touchLeft.touchMove(event)
// 移动间距 deltaX 就是向左(-)向右(+)
const diff = (touchLeft.deltaX.value / trackWidth.value) * (maxValue.value - minValue.value)
newValue.value = startValue.value + diff
// 左滑轮滑动控制
leftBarSlider(newValue.value)
emit('dragmove', {
value: currentValue.value
})
}
function onTouchEnd() {
if (props.disabled) return
emit('dragend', {
value: currentValue.value
})
}
// 右边滑轮滑动状态监听
function onTouchStartRight(event: any) {
if (props.disabled) return
const { modelValue } = props
// 右滑轮移动时数据绑定
touchRight.touchStart(event)
// 记录开始数据值
rightSlider.startValue = leftBarPercent.value < rightBarPercent.value ? format((modelValue as number[])[1]) : format((modelValue as number[])[0])
emit('dragstart', {
value: currentValue.value
})
}
function onTouchMoveRight(event: any) {
if (props.disabled) return
touchRight.touchMove(event)
// 移动间距 deltaX 就是向左向右
const diff = (touchRight.deltaX.value / trackWidth.value) * (maxValue.value - minValue.value)
rightSlider.newValue = format(rightSlider.startValue + diff)
// 右滑轮滑动控制
rightBarSlider(rightSlider.newValue)
emit('dragmove', {
value: currentValue.value
})
}
function onTouchEndRight() {
if (props.disabled) return
emit('dragend', {
value: currentValue.value
})
}
/**
* 控制右侧滑轮滑动 value校验
* @param {Number} value 当前滑轮绑定值
*/
function rightBarSlider(value: number) {
value = format(value)
rightNewValue.value = value
emit('update:modelValue', [Math.min(leftNewValue.value, rightNewValue.value), Math.max(leftNewValue.value, rightNewValue.value)])
rightBarPercent.value = formatPercent(value)
styleControl()
}
/**
* 控制左滑轮滑动更新渲染数据 value 进行校验取整
* @param {Number} value 当前滑轮绑定值
*/
function leftBarSlider(value: number) {
value = format(value)
// 把 value 转换成百分比
const percent = formatPercent(value)
if (!showRight.value) {
emit('update:modelValue', value)
leftNewValue.value = value
leftBarPercent.value = percent
barStyle.value = `width: ${percent}%; `
} else {
leftNewValue.value = value
leftBarPercent.value = percent
emit('update:modelValue', [Math.min(leftNewValue.value, rightNewValue.value), Math.max(leftNewValue.value, rightNewValue.value)])
styleControl()
}
}
// 样式控制
function styleControl() {
// 左右滑轮距离左边最短为当前激活条所处位置
const barLeft =
leftBarPercent.value < rightBarPercent.value ? [leftBarPercent.value, rightBarPercent.value] : [rightBarPercent.value, leftBarPercent.value]
// 通过左右滑轮的间距控制 激活条宽度 barLeft[1] - barLeft[0]
const barStyleTemp = `width: ${barLeft[1] - barLeft[0]}%; left: ${barLeft[0]}%`
currentValue.value =
leftNewValue.value < rightNewValue.value ? [leftNewValue.value, rightNewValue.value] : [rightNewValue.value, leftNewValue.value]
barStyle.value = barStyleTemp
}
function equal(arr1: number[], arr2: number[]) {
if (!isDef(arr1) || !isDef(arr2)) {
return false
}
let i = 0
arr1.forEach((item, index) => {
item === arr2[index] && i++
})
return i === 2
}
function format(value: number) {
const formatValue = Math.round(Math.max(minValue.value, Math.min(value, maxValue.value)) / stepValue.value) * stepValue.value
return formatValue
}
// 计算占比
function formatPercent(value: number) {
const percentage = ((value - minValue.value) / (maxValue.value - minValue.value)) * 100
return percentage
}
// 计算滑块位置和进度长度
function calcBarPercent() {
const { modelValue } = props
let value = !isArray(modelValue) ? format(modelValue) : leftBarPercent.value < rightBarPercent.value ? format(modelValue[0]) : format(modelValue[1])
value = format(value)
// 把 value 转换成百分比
const percent = formatPercent(value)
leftBarPercent.value = percent
barStyle.value = `width: ${percent}%; `
}
defineExpose<SliderExpose>({
initSlider
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>