
<template>
<div class="custom-slider-container" :style="{ height: height }">
<div class="custom-slider-vertical" v-if="layout === 'vertical'">
<t-input-number
v-model="inputValue"
:max="max"
:min="min"
:theme="inputTheme"
class="slider-input"
@blur="handleInputBlur"
@input="handleInputChange"
/>
<div class="slider-track" ref="track" @click="handleTrackClick">
<div class="slider-fill" :style="{ height: fillPercentage }"></div>
<div
class="slider-thumb"
:style="{ bottom: fillPercentage }"
@mousedown="startDrag"
></div>
</div>
</div>
<div class="custom-slider-horizontal" v-else>
<div class="slider-track-wrapper">
<div class="slider-track" ref="track" @click="handleTrackClick">
<div class="slider-fill" :style="{ width: fillPercentage }"></div>
<div
class="slider-thumb"
:style="{ left: fillPercentage }"
@mousedown="startDrag"
></div>
</div>
</div>
<div class="slider-input-wrapper">
<t-input-number
v-model="inputValue"
:max="max"
:min="min"
:theme="inputTheme"
class="slider-input"
@blur="handleInputBlur"
@input="handleInputChange"
/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CustomSlider',
props: {
value: {
type: Number,
default: 15
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
layout: {
type: String,
default: 'horizontal',
validator: value => ['horizontal', 'vertical'].includes(value)
},
height: {
type: String,
default: '200px'
},
inputTheme: {
type: String,
default: 'column'
},
showTooltip: {
type: Boolean,
default: true
}
},
data() {
return {
inputValue: this.value,
isDragging: false,
tempInputValue: '',
displayValue: this.value
};
},
computed: {
fillPercentage() {
const percentage = ((this.displayValue - this.min) / (this.max - this.min)) * 100;
return `${Math.max(0, Math.min(100, percentage))}%`;
}
},
watch: {
value(newVal) {
this.inputValue = newVal;
this.displayValue = newVal;
}
},
mounted() {
document.addEventListener('mousemove', this.handleDrag);
document.addEventListener('mouseup', this.stopDrag);
},
beforeDestroy() {
document.removeEventListener('mousemove', this.handleDrag);
document.removeEventListener('mouseup', this.stopDrag);
},
methods: {
handleInputBlur() {
this.validateAndUpdateInput();
},
handleInputChange(value) {
this.tempInputValue = String(value);
this.inputValue = value;
const numValue = parseInt(value, 10);
if (!isNaN(numValue)) {
this.displayValue = Math.max(this.min, Math.min(this.max, numValue));
}
},
validateAndUpdateInput() {
if (this.tempInputValue === '') {
this.inputValue = this.min;
this.displayValue = this.min;
this.$emit('input', this.min);
this.$emit('change', this.min);
return;
}
const numValue = parseInt(this.tempInputValue, 10);
let finalValue;
if (isNaN(numValue)) {
finalValue = this.min;
} else if (numValue < this.min) {
finalValue = this.min;
} else if (numValue > this.max) {
finalValue = this.max;
} else {
finalValue = numValue;
}
this.inputValue = finalValue;
this.displayValue = finalValue;
this.tempInputValue = '';
this.$emit('input', finalValue);
this.$emit('change', finalValue);
},
startDrag(event) {
this.isDragging = true;
event.preventDefault();
},
stopDrag() {
if (this.isDragging) {
this.isDragging = false;
this.$emit('change', this.displayValue);
}
},
handleDrag(event) {
if (!this.isDragging) return;
const track = this.$refs.track;
const rect = track.getBoundingClientRect();
let percentage;
if (this.layout === 'vertical') {
const position = rect.bottom - event.clientY;
percentage = Math.max(0, Math.min(1, position / rect.height));
} else {
const position = event.clientX - rect.left;
percentage = Math.max(0, Math.min(1, position / rect.width));
}
const newValue = Math.round(this.min + percentage * (this.max - this.min));
this.inputValue = newValue;
this.displayValue = newValue;
this.$emit('input', newValue);
},
handleTrackClick(event) {
const track = this.$refs.track;
const rect = track.getBoundingClientRect();
let percentage;
if (this.layout === 'vertical') {
const position = rect.bottom - event.clientY;
percentage = Math.max(0, Math.min(1, position / rect.height));
} else {
const position = event.clientX - rect.left;
percentage = Math.max(0, Math.min(1, position / rect.width));
}
const newValue = Math.round(this.min + percentage * (this.max - this.min));
this.inputValue = newValue;
this.displayValue = newValue;
this.$emit('input', newValue);
this.$emit('change', newValue);
}
}
};
</script>
<style scoped>
.custom-slider-container {
display: flex;
align-items: center;
justify-content: center;
}
.custom-slider-vertical {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
.custom-slider-horizontal {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
}
.slider-track-wrapper {
flex: 1;
min-width: 0;
}
.slider-input-wrapper {
width: 80px;
flex-shrink: 0;
margin-left: 10px;
}
.slider-track {
position: relative;
background-color: #e9ecef;
border-radius: 4px;
}
.custom-slider-vertical .slider-track {
width: 6px;
height: 100%;
margin: 0 10px;
}
.custom-slider-horizontal .slider-track {
height: 6px;
width: 100%;
margin: 10px 0;
}
.slider-fill {
position: absolute;
background-color: #0052d9;
border-radius: 4px;
}
.custom-slider-vertical .slider-fill {
width: 100%;
bottom: 0;
}
.custom-slider-horizontal .slider-fill {
height: 100%;
left: 0;
}
.slider-thumb {
position: absolute;
width: 16px;
height: 16px;
background-color: #fff;
border: 2px solid #0052d9;
border-radius: 50%;
cursor: pointer;
z-index: 2;
transition: none;
}
.custom-slider-vertical .slider-thumb {
left: 50%;
transform: translateX(-50%) translateY(50%);
}
.custom-slider-horizontal .slider-thumb {
top: 50%;
transform: translateY(-50%) translateX(-50%);
}
.slider-input {
width: 100%;
}
</style>