diff --git a/devui/slider/index.ts b/devui/slider/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..557c9fd29459e8ea2789140e638f93da9f830101 --- /dev/null +++ b/devui/slider/index.ts @@ -0,0 +1,17 @@ +import type { App } from 'vue' +import Slider from './src/slider' + +Slider.install = function(app: App): void { + app.component(Slider.name, Slider) +} + +export { Slider } + +export default { + title: 'Slider 滑块', + category: '数据录入', + install(app: App): void { + + app.use(Slider as any) + } +} diff --git a/devui/slider/src/slider-types.ts b/devui/slider/src/slider-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..9bdc4bfa08e84c3be84587fda3df6891a6495340 --- /dev/null +++ b/devui/slider/src/slider-types.ts @@ -0,0 +1,30 @@ +import type { ExtractPropTypes } from 'vue' + +export const sliderProps = { + /* test: { + type: Object as PropType<{ xxx: xxx }> + } */ + min:{ + type:Number, + default:0 + }, + max:{ + type:Number, + default:50 + }, + step:{ + type:Number, + default:1 + }, + disabled:{ + type:Boolean, + default:false + }, + showInput:{ + type:Boolean, + default:false + } + +} as const + +export type SliderProps = ExtractPropTypes diff --git a/devui/slider/src/slider.scss b/devui/slider/src/slider.scss new file mode 100644 index 0000000000000000000000000000000000000000..11bc6926a631d02f05a52dd541f2305ee798f31e --- /dev/null +++ b/devui/slider/src/slider.scss @@ -0,0 +1,77 @@ +@import '../../style/mixins/index'; +@import '../../style/theme/color'; +@import '../../style/theme/shadow'; +@import '../../style/theme/corner'; +@import '../../style/theme/font'; + +.devui-slider { + position: relative; + width: 70%; + display: block; + // + .devui-slider__runway { + position: relative; + width: 100%; + padding: 4px 0; + margin: 4px 0; + cursor: pointer; + box-sizing: border-box; + height: 5px; + display: flex; + align-items: center; + background-color: $devui-default-bg; + + .devui-slider__bar { + height: 6px; + background-color: $devui-default-line; + border-top-left-radius: $devui-border-radius; + border-bottom-left-radius: $devui-border-radius; + position: absolute; + } + + .devui-slider__button { + position: absolute; + width: 14px; + height: 14px; + border: 2px solid $devui-default-line; + background-color: $devui-default-bg; + border-radius: 50%; + margin-left: -7px; + transition: transform 0.2s ease-in-out; + } + + .devui-slider__button:hover { + transform: scale(1.2); + } + } + + .devui-min_count { + position: absolute; + top: 15px; + font-size: $devui-font-size; + color: $devui-text; + font-family: HuaweiFont, Helvetica, Arial, PingFangSC-Regular, Hiragino Sans GB, Microsoft YaHei, 微软雅黑, Microsoft JhengHei; + } + + .devui-max_count { + position: absolute; + top: 15px; + right: 0; + font-size: $devui-font-size; + color: $devui-text; + font-family: HuaweiFont, Helvetica, Arial, PingFangSC-Regular, Hiragino Sans GB, Microsoft YaHei, 微软雅黑, Microsoft JhengHei; + } + + .devui-input__wrap { + position: absolute; + right: -60px; + top: -12px; + padding: 5px 10px; + cursor: text; + margin-left: 20px; + + input { + width: 40px; + } + } +} diff --git a/devui/slider/src/slider.tsx b/devui/slider/src/slider.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a65b4db227422c131bcf31a101f1f01d44d900d1 --- /dev/null +++ b/devui/slider/src/slider.tsx @@ -0,0 +1,200 @@ +import './slider.scss' + +import { defineComponent,ref,computed,toRefs} from 'vue' +import { sliderProps, SliderProps } from './slider-types' +import { Input } from '../../input/index'; + +export default defineComponent({ + name: 'DSlider', + components:{Input}, + props: sliderProps, + emits: [], + setup(props: SliderProps) { + + let isClick=true; + + let startPosition=0; + //用以定位button的位置 + const currentPosition=ref(0); + //当前的位置以百分比显示 + const percentDispaly=ref('') + let currentX=0; + let startX=0; + + //移动或者点击后的实际偏移的像素 + let pxOffset=0 + //输入后的值 + const inputValue=ref(props.min) + + const newPostion=ref(0) + + const renderShowInput=()=>{ + return props.showInput? :'' + } + + function handleonMousedown(event:MouseEvent){ + //props.disabled状态是不能点击拖拽的 + if(props.disabled) return + //阻止默认事件 + event.preventDefault(); + dragStart(event); + //当鼠标开始移动时,进行坐标计算 + window.addEventListener('mousemove',onDragging) + //当鼠标抬起时,停止计算 + window.addEventListener('mouseup',onDragEnd) + + } + + + function dragStart(event:MouseEvent){ + + + + //防止mouseup触发父元素的click事件 + isClick=false; + //获取当前的x坐标值 + startX=event.clientX; + //把当前值给startPosition,以便后面再重新拖拽时,会以当前的位置计算偏移 + startPosition=currentPosition.value + newPostion.value=startPosition + + + } + + + /** + * + * @param event 鼠标事件 + * currentPosition:当前移动的X的坐标 + * offset:当前x坐标减去初始x坐标的偏移 + * + */ + function onDragging(event:MouseEvent){ + + currentX=event.clientX; + + + + pxOffset=currentX-startX; + //移动的x方向上的偏移+初始位置等于新位置 + newPostion.value=startPosition+pxOffset; + + setPostion(newPostion.value); + } + function onDragEnd(){ + //防止mouseup后立即执行click事件,mouseup后 + //会立即执行click,但是isClick=true 是100ms才出发,因此不会执行click事件,就跳出来了 + setTimeout(() => { + isClick=true; + }, 100); + + window.removeEventListener('mousemove',onDragging) + window.removeEventListener('mouseup',onDragEnd) + } + + function setPostion(newPosition:number){ + //获取slider的实际长度的像素 + const sliderWidth:number=document.querySelector('.devui-slider__runway').clientWidth + + if(newPosition<0){ + newPosition=0; + }else if(newPosition>=sliderWidth){ + + + //当到达类似98%时,进行边界判定,设置值为100% + currentPosition.value=sliderWidth + inputValue.value=props.max + percentDispaly.value= '100%' + + return + + } + //计算slider的实际像素每段的长度 + const LengthPerStep=sliderWidth/((props.max-props.min)/props.step) + //计算实际位移的取整段数 + const steps=Math.round(newPosition/LengthPerStep) + + + //实际的偏移像素 + + const value:number=steps*LengthPerStep + + + //这个是向左偏移百分比的值 + percentDispaly.value= Math.round(value*100/sliderWidth)+'%' + + + + //更新输入框的值 + + inputValue.value=Math.round(value*(props.max-props.min)/sliderWidth)+props.min + //设置当前所在的位置 + currentPosition.value=value; + + //比如props.max为50 setp等于3 滑到48时,再滑动不能到51 + + } + + + function handleClick(event){ + if(isClick){ + startX=event.target.getBoundingClientRect().left + currentX=event.clientX + + + + setPostion(currentX-startX) + }else + { + return + } + } + //输入框内的值 + function handleOnInput(event) { + inputValue.value=parseInt(event.target.value) + if(!inputValue.value){ + inputValue.value=props.min + percentDispaly.value='0%' + }else{ + if(inputValue.valueprops.max){ + inputValue.value=props.max; + } + const re=/^(?:[1-9]?\d|100)$/; + if(re.test(`${inputValue.value}`)){ + + percentDispaly.value=(inputValue.value-props.min)*100/(props.max-props.min)+'%' + } + } + + } + return ()=>( +
+ {/* 整个的长度 */} +
+ {/* 滑动后左边的进度条 */} +
+
+
+ {props.min} + {props.max} + + {/* */} + {renderShowInput()} +
+ + ) + }, +}) diff --git a/sites/components/slider/index.md b/sites/components/slider/index.md new file mode 100644 index 0000000000000000000000000000000000000000..202f05c1b81ccba2deb8e2b32f630b6f9936a397 --- /dev/null +++ b/sites/components/slider/index.md @@ -0,0 +1,37 @@ +# Slider滑动输入条 + +滑动输入条 + +### 何时使用 +当用户需要在数值区间内进行选择时使用 + +### 基本用法 +
+ +
+ +### 带有输入框的滑动组件 +
+ +
+ +### 可设置Step的滑动组件 +
+ +
+ +```html + + + + + +``` + + + + + + + +