
组件封装完整代码
<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<{
(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,
() => {
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;
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;
// 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,
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>