2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
@@ -0,0 +1,4 @@
export * from './use-slider-common-porps'
export * from './use-slider-node-info'
export * from './slider-custom'
export * from './use-slider'
@@ -0,0 +1,153 @@
import { computed, toRef } from 'vue'
import { useComponentColor, useNamespace } from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import { useSliderCommonProps } from './use-slider-common-porps'
import type { CSSProperties, Ref } from 'vue'
import type { SliderMode, SliderProps, SliderValueType } from '../slider'
export const useSliderCustomStyle = (
props: SliderProps,
sliderValue: Ref<SliderValueType>,
mode: Ref<SliderMode>
) => {
const ns = useNamespace('slider')
const { size, disabled } = useSliderCommonProps(props)
// 解析颜色
const [activeBgColorClass, activeBgColorStyle] = useComponentColor(
toRef(props, 'activeColor'),
'bg'
)
const [inactiveBgColorClass, inactiveBgColorStyle] = useComponentColor(
toRef(props, 'inactiveColor'),
'bg'
)
// 滑块的位置
const sliderBarPosition = computed<[string, string]>(() => {
if (mode.value === 'single') {
return [
`${
(((sliderValue.value as number) - props.min) /
(props.max - props.min)) *
100
}%`,
'0',
]
} else {
return [
`${
(((sliderValue.value as number[])[0] - props.min) /
(props.max - props.min)) *
100
}%`,
`${
(((sliderValue.value as number[])[1] - props.min) /
(props.max - props.min)) *
100
}%`,
]
}
})
// 滑动条所属类
const sliderClass = computed<string>(() => {
const cls: string[] = [ns.b()]
// 设置滑动条的尺寸
if (size.value) cls.push(ns.m(size.value))
// 设置禁用状态
if (disabled.value) cls.push(ns.m('disabled'))
// 设置未激活时的颜色
if (inactiveBgColorClass.value) cls.push(inactiveBgColorClass.value)
return cls.join(' ')
})
// 滑动条样式
const sliderStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置滑动条的高度
if (props.sliderHeight)
style.height = formatDomSizeValue(props.sliderHeight)
// 设置未激活时的颜色
if (!inactiveBgColorClass.value)
style.backgroundColor =
inactiveBgColorStyle.value || 'var(--tn-color-grey-light)'
return style
})
// 激活时滑动条所属类
const activeSliderClass = computed<string>(() => {
const cls: string[] = [ns.e('active')]
// 设置激活时的颜色
if (activeBgColorClass.value) cls.push(activeBgColorClass.value)
return cls.join(' ')
})
// 激活时滑动条样式
const activeSliderStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置激活时的颜色
if (!activeBgColorClass.value)
style.backgroundColor =
activeBgColorStyle.value || 'var(--tn-color-primary)'
// 激活时显示对应的宽度
if (mode.value === 'single') {
style.width = sliderBarPosition.value[0]
} else if (mode.value === 'range') {
style.width = `calc(${sliderBarPosition.value[1]} - ${sliderBarPosition.value[0]})`
style.left = sliderBarPosition.value[0]
}
return style
})
// 滑块所属类
const sliderBarClass = computed<string>(() => {
const cls: string[] = [ns.e('bar')]
return cls.join(' ')
})
// 滑块样式
const sliderBarStyle = computed<(type: 'min' | 'max') => CSSProperties>(
() => {
return (type) => {
const style: CSSProperties = {}
// 设置滑块的尺寸
if (props.sliderBarSize)
style.width = style.height = formatDomSizeValue(props.sliderBarSize)
// 更新滑块的位置
style.left =
type === 'min'
? sliderBarPosition.value[0]
: sliderBarPosition.value[1]
return style
}
}
)
return {
ns,
sliderClass,
sliderStyle,
activeSliderClass,
activeSliderStyle,
sliderBarClass,
sliderBarStyle,
}
}
@@ -0,0 +1,16 @@
import { useFormDisabled, useFormSize } from '../../../form'
import type { SliderProps } from '../slider'
export const useSliderCommonProps = (props: SliderProps) => {
// 滑动条的尺寸
const size = useFormSize(props.size)
// 滑动条是否禁用
const disabled = useFormDisabled(props.disabled)
return {
size,
disabled,
}
}
@@ -0,0 +1,170 @@
import { reactive, watch } from 'vue'
import { debugWarn, generateId } from '../../../../utils'
import { useSelectorQuery, useTouch } from '../../../../hooks'
import { useSliderCommonProps } from './use-slider-common-porps'
import { useSlider } from './use-slider'
import type { SliderProps } from '../slider'
export interface SliderNode {
left: number
top: number
right: number
bottom: number
width: number
height: number
}
export const useSliderNodeInfo = (props: SliderProps) => {
const sliderId = `slider-${generateId()}`
const { getSelectorNodeInfo } = useSelectorQuery()
const { disabled } = useSliderCommonProps(props)
const { mode, sliderValue, precision, updateSliderValue, changeSliderValue } =
useSlider(props)
const sliderNodeInfo = reactive<SliderNode>({
left: 0,
top: 0,
right: 0,
bottom: 0,
width: 0,
height: 0,
})
const {
currentX: sliderBarCurrentX,
updateOptions: sliderTouchUpdateOptions,
onTouchStart: sliderTouchStart,
onTouchMove: sliderTouchMove,
onTouchEnd: sliderTouchEnd,
} = useTouch()
// 更新禁用状态
watch(
() => disabled,
(val) => {
sliderTouchUpdateOptions({
disabled: val.value,
})
}
)
// 初始化滑动条的布局信息
// 初始化次数
let initCount = 0
const initSliderNodeInfo = async () => {
try {
const sliderRectInfo = await getSelectorNodeInfo(`#${sliderId}`)
if (!sliderRectInfo) {
throw new Error('获取滑动条的布局信息失败')
}
initCount = 0
sliderNodeInfo.left = sliderRectInfo.left || 0
sliderNodeInfo.top = sliderRectInfo.top || 0
sliderNodeInfo.right = sliderRectInfo.right || 0
sliderNodeInfo.bottom = sliderRectInfo.bottom || 0
sliderNodeInfo.width = sliderRectInfo.width || 0
sliderNodeInfo.height = sliderRectInfo.height || 0
// 初始化触摸的参数
sliderTouchUpdateOptions({
left: sliderNodeInfo.left,
right: sliderNodeInfo.right,
top: sliderNodeInfo.top,
bottom: sliderNodeInfo.bottom,
})
} catch (err) {
initCount++
if (initCount > 10) {
initCount = 0
debugWarn('TnSilider', `获取滑动条的布局信息失败: ${err}`)
return
}
setTimeout(() => {
initSliderNodeInfo()
}, 150)
}
}
// 滑块滑动中
const onSliderBarTouchMove = (event: TouchEvent, type: 'min' | 'max') => {
sliderTouchMove(event)
// 滑块的位置
const sliderBarPosition =
(sliderBarCurrentX.value / sliderNodeInfo.width) * 100
const value = setPosiiton(sliderBarPosition)
updateSliderValue(value, type)
}
// 滑块滑动结束
const onSliderBarTouchEnd = (event: TouchEvent, type: 'min' | 'max') => {
sliderTouchEnd(event)
// 滑块的位置
const sliderBarPosition =
(sliderBarCurrentX.value / sliderNodeInfo.width) * 100
const value = setPosiiton(sliderBarPosition)
changeSliderValue(value, type)
}
// 滑动条点击事件
const sliderClickEvent = (event: any) => {
if (disabled.value) return
let touchX = 0
// #ifndef MP-ALIPAY
touchX = event.detail.x
// #endif
// #ifdef MP-ALIPAY
touchX = event.detail.clientX
// #endif
const sliderBarPosition =
((touchX - sliderNodeInfo.left) / sliderNodeInfo.width) * 100
const value = setPosiiton(sliderBarPosition)
if (mode.value === 'single') {
updateSliderValue(value)
changeSliderValue(value)
} else if (mode.value === 'range') {
// 判断当前点击位置靠近那个值
const minValue = (sliderValue.value as number[])[0]
const maxValue = (sliderValue.value as number[])[1]
const minDistance = Math.abs(minValue - value)
const maxDistance = Math.abs(maxValue - value)
if (minDistance < maxDistance) {
updateSliderValue(value, 'min')
changeSliderValue(value, 'min')
} else {
updateSliderValue(value, 'max')
changeSliderValue(value, 'max')
}
}
}
// 设置滑块的位置
const setPosiiton = (position: number): number => {
if (position === null || Number.isNaN(+position)) return 0
if (position < 0) position = 0
else if (position > 100) position = 100
// 每一步的长度
const lengthPerStep = 100 / ((props.max - props.min) / props.step)
// 当前在第几步
const steps = Math.round(position / lengthPerStep)
// 计算当前的值
let value =
steps * lengthPerStep * (props.max - props.min) * 0.01 + props.min
value = Number.parseFloat(value.toFixed(precision.value))
return value
}
return {
sliderId,
initSliderNodeInfo,
onSliderBarTouchStart: sliderTouchStart,
onSliderBarTouchMove,
onSliderBarTouchEnd,
sliderClickEvent,
}
}
@@ -0,0 +1,129 @@
import { computed, getCurrentInstance, nextTick, ref, watch } from 'vue'
import { debugWarn, isArray, isEmptyVariableInDefault } from '../../../../utils'
import {
CHANGE_EVENT,
INPUT_EVENT,
UPDATE_MODEL_EVENT,
} from '../../../../constants'
import { useFormItem } from '../../../form'
import type { SliderMode, SliderProps, SliderValueType } from '../slider'
export const useSlider = (props: SliderProps) => {
const { emit } = getCurrentInstance()!
const { formItem } = useFormItem()
// 是否搭配FormItem一起使用
const isFormItem = computed(() => !!formItem)
const sliderValue = ref<SliderValueType>(
isEmptyVariableInDefault(props.modelValue, 0)
)
// 初始化数据
const initSliderValue = () => {
let val = props.modelValue
if (isArray(val)) {
// 判断是否为空数组
if (val.length === 0) {
val = [0, 0]
} else if (val.length === 1) {
val = [0, Math.min(val[0], props.max)]
} else {
val = [
Math.max(Math.min(val[0], val[1]), props.min),
Math.min(Math.max(val[0], val[1], props.min), props.max),
]
}
} else {
val = Math.min(
Math.max(isEmptyVariableInDefault(val, 0), props.min),
props.max
)
}
nextTick(() => {
emit(UPDATE_MODEL_EVENT, val)
})
}
initSliderValue()
watch(
() => props.modelValue,
(val) => {
sliderValue.value = val
}
)
// 滑动条的模式,如果modelValue是数组则为范围模式
const mode = computed<SliderMode>(() =>
isArray(sliderValue.value) ? 'range' : 'single'
)
// 精确的小数点位数
const precision = computed(() => {
const precision = [props.min, props.max, props.step].map((item) => {
const decimal = `${item}`.split('.')[1]
return decimal ? decimal.length : 0
})
return Math.max.apply(null, precision)
})
// 获取处理传递过来的值
const handleInputValue = (value: number, type?: 'min' | 'max') => {
if (mode.value === 'single') {
return value
} else {
const minValue = (sliderValue.value as number[])![0]
const maxValue = (sliderValue.value as number[])![1]
if (type === 'min') {
return [
Math.min(Math.max(props.min, value), maxValue),
(sliderValue.value as number[])![1],
]
} else {
return [
(sliderValue.value as number[])![0],
Math.max(Math.min(value, props.max), minValue),
]
}
}
}
// 更新滑动条的值
const updateSliderValue = (_value: number, type?: 'min' | 'max') => {
const value = handleInputValue(_value, type)
emit(UPDATE_MODEL_EVENT, value)
nextTick(() => {
emit(INPUT_EVENT, value)
})
if (props.validateEvent) {
formItem?.validate('input').catch((err) => {
debugWarn(err)
})
}
}
// 修改滑动条的值
const changeSliderValue = (_value: number, type?: 'min' | 'max') => {
const value = handleInputValue(_value, type)
emit(CHANGE_EVENT, value)
if (props.validateEvent) {
formItem?.validate('change').catch((err) => {
debugWarn(err)
})
}
}
return {
isFormItem,
sliderValue,
mode,
precision,
updateSliderValue,
changeSliderValue,
}
}
@@ -0,0 +1,3 @@
import type Slider from './slider.vue'
export type TnSliderInstance = InstanceType<typeof Slider>
+98
View File
@@ -0,0 +1,98 @@
import { buildProps, definePropType, isArray, isNumber } from '../../../utils'
import {
CHANGE_EVENT,
INPUT_EVENT,
UPDATE_MODEL_EVENT,
} from '../../../constants'
import { useFormSizeProps } from '../../base/composables/use-component-common-props'
import type { ExtractPropTypes } from 'vue'
import type { Arrayable } from '../../../utils'
export const sliderModes = ['single', 'range'] as const
export type SliderValueType = Arrayable<number>
export const sliderProps = buildProps({
/**
* @description 滑块绑定的值
*/
modelValue: {
type: definePropType<SliderValueType>([Number, Array]),
default: 0,
},
/**
* @description 滑动条的尺寸
*/
size: useFormSizeProps,
/**
* @description 滑块的尺寸
*/
sliderBarSize: {
type: [String, Number],
},
/**
* @description 滑动条的高度
*/
sliderHeight: {
type: [String, Number],
},
/**
* @description 激活时的颜色,以tn开头则使用图鸟内置的颜色只支持普通颜色
*/
activeColor: {
type: String,
default: '',
},
/**
* @description 非激活时的颜色,以tn开头则使用图鸟内置的颜色只支持普通颜色
*/
inactiveColor: {
type: String,
default: '',
},
/**
* @description 是否禁用
*/
disabled: Boolean,
/**
* @description 滑动条的步进值
*/
step: {
type: Number,
default: 1,
},
/**
* @description 滑动条的最小值
*/
min: {
type: Number,
default: 0,
},
/**
* @description 滑动条的最大值
*/
max: {
type: Number,
default: 100,
},
/**
* @description 值发生修改时是否触发表单验证
*/
validateEvent: {
type: Boolean,
default: true,
},
})
export const sliderEmits = {
[UPDATE_MODEL_EVENT]: (value: SliderValueType) =>
isArray(value) || isNumber(value),
[CHANGE_EVENT]: (value: SliderValueType) => isArray(value) || isNumber(value),
[INPUT_EVENT]: (value: SliderValueType) => isArray(value) || isNumber(value),
}
export type SliderProps = ExtractPropTypes<typeof sliderProps>
export type SliderEmit = typeof sliderEmits
export type SliderMode = (typeof sliderModes)[number]
+74
View File
@@ -0,0 +1,74 @@
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue'
import { sliderEmits, sliderProps } from './slider'
import {
useSlider,
useSliderCustomStyle,
useSliderNodeInfo,
} from './composables'
const props = defineProps(sliderProps)
defineEmits(sliderEmits)
const { isFormItem, sliderValue, mode } = useSlider(props)
const {
sliderId,
initSliderNodeInfo,
onSliderBarTouchStart,
onSliderBarTouchMove,
onSliderBarTouchEnd,
sliderClickEvent,
} = useSliderNodeInfo(props)
const {
ns,
sliderClass,
sliderStyle,
activeSliderClass,
activeSliderStyle,
sliderBarClass,
sliderBarStyle,
} = useSliderCustomStyle(props, sliderValue, mode)
onMounted(() => {
nextTick(() => {
initSliderNodeInfo()
})
})
</script>
<template>
<view
:id="sliderId"
:class="[sliderClass, ns.is('form-item', isFormItem)]"
:style="sliderStyle"
@tap.stop="sliderClickEvent"
>
<!-- 激活时的滑动条 -->
<view
:class="[activeSliderClass]"
:style="activeSliderStyle"
@tap.stop="sliderClickEvent"
/>
<!-- 开始滑块 -->
<view
:class="[sliderBarClass]"
:style="sliderBarStyle('min')"
@touchstart.prevent="onSliderBarTouchStart"
@touchmove.prevent="onSliderBarTouchMove($event, 'min')"
@touchend.prevent="onSliderBarTouchEnd($event, 'min')"
/>
<!-- 结束滑块 -->
<view
v-if="mode === 'range'"
:class="[sliderBarClass]"
:style="sliderBarStyle('max')"
@touchstart.prevent="onSliderBarTouchStart"
@touchmove.prevent="onSliderBarTouchMove($event, 'max')"
@touchend.prevent="onSliderBarTouchEnd($event, 'max')"
/>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/slider.scss';
</style>