@@ -1,7 +1,7 @@
|
|||||||
import type { App } from "vue"
|
import type { App } from "vue"
|
||||||
import Button from "./button/index.vue"
|
import Button from "./button/index.vue"
|
||||||
import Tabs from "./tabs/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 Select from "./select/index.vue"
|
||||||
import Option from "./option/index.vue"
|
import Option from "./option/index.vue"
|
||||||
import Menu from "./menu/menu.vue"
|
import Menu from "./menu/menu.vue"
|
||||||
@@ -9,8 +9,9 @@ import Checkbox from "./checkbox/index.vue"
|
|||||||
import Collapse from "./collapse/index.vue"
|
import Collapse from "./collapse/index.vue"
|
||||||
import Dialog from "./dialog/index.vue"
|
import Dialog from "./dialog/index.vue"
|
||||||
import Tooltip from "./tooltip/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 Selection from "./selection/index.vue"
|
||||||
|
import Slider from "./slider/index.vue"
|
||||||
|
|
||||||
|
|
||||||
export const components = {
|
export const components = {
|
||||||
@@ -24,8 +25,9 @@ export const components = {
|
|||||||
Collapse,
|
Collapse,
|
||||||
Dialog,
|
Dialog,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Iconselct,
|
Iconselect,
|
||||||
Selection
|
Selection,
|
||||||
|
Slider
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CalcComponents = typeof components
|
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>
|
||||||
|
|
||||||
Reference in New Issue
Block a user