input range 滑块vue组件封装

1,163 阅读1分钟

image.png

组件封装完整代码
<template>
	<div class="common_slider">
		<span class="common_slider_label" :style="{ width: option.labelWidth }">{{ option.labelContent }}</span>
		<div class="common_slider_main">
			<input
				type="range"
				v-model="mainValue"
				:max="option.max"
				:min="option.min"
				:dataValue="pageValue"
				:disabled="option.isDisabled"
				ref="inputRangeRef"
			/>
			<div class="main_btn">
				<el-icon @click="minusHandle"><CaretLeft /></el-icon>
				<el-icon @click="addHandle"><CaretRight /></el-icon>
			</div>
		</div>
	</div>
</template>

<script setup lang="ts">
import { reactive, ref, toRefs, watch, computed } from 'vue';
import { ISliderProps } from '../types';
const inputRangeRef = ref();
const emits = defineEmits<{
	// eslint-disable-next-line
	(e: 'updateData', key: string, value: number): void;
}>();
interface IProps {
	option: ISliderProps;
}
const props = defineProps<IProps>();
const state = reactive({
	scale: '0%',
	mainValue: 0,
	option: {
		max: 100,
		min: 0,
		valueContent: 10,
		labelContent: '',
		labelWidth: '80px',
		step: 1,
		isDisabled: false,
		units: '',
		valueKey: '',
		ratio: 1,
	},
});
const { scale, mainValue, option } = toRefs(state);
const minusHandle = () => {
	if (mainValue.value <= option.value.min) return false;
	mainValue.value = Number(mainValue.value) - option.value.step;
};
const addHandle = () => {
	if (mainValue.value >= option.value.max) return false;
	mainValue.value = Number(mainValue.value) + option.value.step;
};
// 滑块显示的数据处理
const pageValue = computed(() => {
	return `${(mainValue.value / option.value.ratio).toFixed(1)}${option.value.units}`;
});
const updateScale = () => {
	scale.value = `${((mainValue.value - option.value.min) / (option.value.max - option.value.min)) * 100}%`;
};
watch(
	props,
	() => {
		// @ts-ignore
		option.value = {
			...option.value,
			...props.option,
		};
		if (option.value.ratio > 1) {
			let tempData = {
				max: option.value.max * option.value.ratio,
				min: option.value.min * option.value.ratio,
				valueContent: option.value.valueContent * option.value.ratio,
			};
			option.value = {
				...option.value,
				...tempData,
			};
		}
		mainValue.value = option.value.valueContent;
		updateScale();
	},
	{
		deep: true,
		immediate: true,
	}
);
watch(
	mainValue,
	() => {
		emits('updateData', option.value.valueKey, mainValue.value / option.value.ratio);
		updateScale();
	},
	{
		deep: true,
	}
);
</script>

<style scoped lang="scss">
.common_slider {
	display: flex;
	height: 30px;
	align-items: center;
	margin-bottom: 20px;
	&_label {
		font-size: 14px;
		font-family: Source Han Sans CN-Normal, Source Han Sans CN;
		font-weight: 350;
		color: #f3f9fc;
		line-height: 30px;
	}
	&_main {
		flex: 1;
		position: relative;
		height: 100%;
		.main_btn {
			display: flex;
			position: absolute;
			top: 50%;
			right: 10px;
			transform: translateY(-50%);
			& > .el-icon {
				cursor: pointer;
				font-size: 18px;
				& > svg {
					color: #0afdc9;
				}
			}
		}
		/*横条样式*/
		input[type='range'] {
			-webkit-appearance: none; /*清除系统默认样式*/
			width: 100%;
			background: -webkit-linear-gradient(#195650, #195650) no-repeat, #20433f; /*设置左边颜色为#61bd12,右边颜色为#ddd*/
			background-size: v-bind(scale) 100%; /*设置左右宽度比例*/
			height: 100%; /*横条的高度*/
			border-radius: 4px;
			position: absolute;
			border: 1px solid #07876c;
			top: 0;
			left: 0;
			cursor: pointer;
			&::after {
				content: attr(dataValue);
				color: #fff;
				position: absolute;
				top: 0;
				left: 20px;
				font-size: 14px;
				transform: translateY(50%);
			}
		}
		// input[type='range']:focus {
		// 	outline: none; /* 去掉默认蓝色边框 */
		// 	background: -webkit-linear-gradient(#56fefe, #56fefe) no-repeat, #20433f; /*设置左边颜色为#61bd12,右边颜色为#ddd*/
		// 	background-size: v-bind(scale) 100%; /*设置左右宽度比例*/
		// }
		/* 设置滑块样式 */
		input[type='range']::-webkit-slider-thumb {
			-webkit-appearance: none;
			// margin-top: -1px; /*使滑块超出轨道部分的偏移量相等*/
			width: 2px;
			// height: 32px;
			// background: #56fefe;
			// border-radius: 1px;
			// opacity: 1;
		}
	}
}
</style>

使用示例
<template>
	<CommonSlider :option="option" @updateData="updateData" />
</template>
<script setup lang="ts">
const viewModel = reactive({
	heightValue: 1, // 观察者高度,默认1.8m
	visibleAreaColor: '', // 可视域颜色
	hiddenAreaColor: '', // 非可视域颜色
});
const { heightValue } = toRefs(viewModel);
const option: ISliderProps = {
	labelContent: '观察高度',
	valueContent: heightValue // 响应式数据,
	valueKey: 'heightValue',
	max: 3,
	min: 0,
	units: 'm',
	sort: 1,
	ratio: 10,
};
</script>