!1 feat: 实现滑块组件

Merge pull request !1 from 知月/feat/slider
This commit is contained in:
知月
2025-04-05 07:03:39 +00:00
committed by 西瓜
parent 77cc5e1134
commit 41d14c947c
2 changed files with 210 additions and 4 deletions
+6 -4
View File
@@ -1,7 +1,7 @@
import type { App } from "vue"
import Button from "./button/index.vue"
import Tabs from "./tabs/index.vue"
import Tab from "./tab.vue"
import Tab from "./tab/index.vue"
import Select from "./select/index.vue"
import Option from "./option/index.vue"
import Menu from "./menu/menu.vue"
@@ -9,8 +9,9 @@ import Checkbox from "./checkbox/index.vue"
import Collapse from "./collapse/index.vue"
import Dialog from "./dialog/index.vue"
import Tooltip from "./tooltip/index.vue"
import Iconselct from "./iconselct/index.vue"
import Iconselect from "./iconselect/index.vue"
import Selection from "./selection/index.vue"
import Slider from "./slider/index.vue"
export const components = {
@@ -24,8 +25,9 @@ export const components = {
Collapse,
Dialog,
Tooltip,
Iconselct,
Selection
Iconselect,
Selection,
Slider
}
export type CalcComponents = typeof components
@@ -0,0 +1,204 @@
<script lang="tsx">
import { defineComponent, ref, computed, shallowRef, watch } from 'vue'
import { useVModel, useDraggable, clamp } from '@vueuse/core'
export default defineComponent({
name: "ISlider",
props: {
modelValue: {
type: Number,
default: 0
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1
},
disabled: {
type: Boolean,
default: false
},
showPercent: {
type: Boolean,
default: true
}
},
emits: ['update:modelValue', 'change'],
setup(props, { emit }) {
const buttonRef = shallowRef<HTMLDivElement | null>(null)
const runwayRef = shallowRef<HTMLDivElement | null>(null)
const modelValue = useVModel(props, 'modelValue', emit, { passive: true })
const percentage = computed(() => {
return ((modelValue.value - props.min) / (props.max - props.min)) * 100
})
useDraggable(buttonRef, {
onMove(position) {
if (!runwayRef.value || props.disabled){
return
}
const runwayRect = runwayRef.value?.getBoundingClientRect()
if (!runwayRect){
return
}
// 使用runway的位置和宽度来计算百分比
const percentage = Math.max(0, Math.min(100, ((position.x - runwayRect.left) / runwayRect.width) * 100))
const range = props.max - props.min
let value = (percentage * range) / 100 + props.min
// 应用步长
value = Math.round(value / props.step) * props.step
// 确保值在范围内
value = Math.max(props.min, Math.min(props.max, value))
modelValue.value = value
emit('change', value)
}
})
return () => (
<div
class={[
'w-full h-8 relative cursor-pointer flex items-center gap-3 py-2',
{ 'cursor-not-allowed': props.disabled }
]}
>
<div
class="flex-1 h-1 bg-[#122438] rounded relative"
ref={runwayRef}
>
<div
class="h-1 bg-[#c6b083] rounded absolute left-0 top-0"
style={{ width: `${percentage.value}%` }}
></div>
<div
class="h-6 w-6 absolute top-1/2 -translate-y-1/2 bg-transparent text-center select-none"
style={{ left: `${percentage.value}%` }}
ref={buttonRef}
>
<div class={[
'w-4 h-4 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
'bg-gradient-to-b from-[#223768] to-[#122438]',
'border border-[#755f44] rounded-full transition-all duration-200',
'shadow-[0_2px_4px_rgba(0,0,0,0.2)]',
'hover:(bg-gradient-to-b from-[#2d4e9f] to-[#153051] border-[#e3c18a] scale-110)',
'active:(bg-gradient-to-b from-[#2d4e9f] to-[#122438] border-[#e3c18a])',
{ 'bg-gradient-to-b from-[#353535] to-[#1c1c1c] border-[#414141]': props.disabled }
]}></div>
</div>
</div>
{props.showPercent && (
<div class={[
'min-w-[45px] text-right text-sm font-medium',
props.disabled ? 'text-[#696969]' : 'text-[#e9c556]'
]}>
{percentage.value.toFixed(0)}%
</div>
)}
</div>
)
}
})
</script>
<style scoped>
.i-slider {
width: 100%;
height: 32px;
position: relative;
cursor: pointer;
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
}
.i-slider.is-disabled {
cursor: not-allowed;
}
.i-slider.is-disabled .i-slider-button {
background: linear-gradient(#353535, #1c1c1c);
border-color: #414141;
}
.i-slider.is-disabled .i-slider-value {
color: #696969;
}
.i-slider-runway {
flex: 1;
height: 4px;
background-color: #122438;
border-radius: 2px;
position: relative;
}
.i-slider-bar {
height: 4px;
background: #c6b083;
border-radius: 2px;
position: absolute;
left: 0;
top: 0;
}
.i-slider-button-wrapper {
height: 24px;
width: 24px;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
background: transparent;
text-align: center;
user-select: none;
line-height: normal;
}
.i-slider-button {
width: 16px;
height: 16px;
background: linear-gradient(#223768, #122438);
border: 1px solid #755f44;
border-radius: 50%;
transition: all 0.2s ease-in-out;
user-select: none;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.i-slider-button:hover {
background: linear-gradient(#2d4e9f, #153051);
border-color: #e3c18a;
transform: translate(-50%, -50%) scale(1.1);
}
.i-slider-button:active {
background: linear-gradient(#2d4e9f, #122438);
border-color: #e3c18a;
}
.i-slider-value {
min-width: 45px;
text-align: right;
font-size: 14px;
color: #e9c556;
font-weight: 500;
}
</style>