first commit
This commit is contained in:
63
uni_modules/tdesign-uniapp/components/slider/README.en-US.md
Normal file
63
uni_modules/tdesign-uniapp/components/slider/README.en-US.md
Normal file
@@ -0,0 +1,63 @@
|
||||
:: BASE_DOC ::
|
||||
|
||||
## API
|
||||
|
||||
### Slider Props
|
||||
|
||||
name | type | default | description | required
|
||||
-- | -- | -- | -- | --
|
||||
custom-style | Object | - | CSS(Cascading Style Sheets) | N
|
||||
colors | Array | [] | `deprecated`。Typescript:`Array<string>` | N
|
||||
disabled | Boolean | undefined | \- | N
|
||||
disabled-color | Array | [] | `deprecated`。Typescript:`Array<string>` | N
|
||||
label | String / Boolean / Function | false | Typescript:`string \| boolean` | N
|
||||
marks | Object / Array | {} | Typescript:`Record<number, string> \| Array<number>` | N
|
||||
max | Number | 100 | \- | N
|
||||
min | Number | 0 | \- | N
|
||||
range | Boolean | false | \- | N
|
||||
show-extreme-value | Boolean | false | \- | N
|
||||
step | Number | 1 | \- | N
|
||||
theme | String | default | options: default/capsule | N
|
||||
value | Number / Array | 0 | `v-model:value` is supported。Typescript:`SliderValue` `type SliderValue = number \| Array<number>`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/slider/type.ts) | N
|
||||
default-value | Number / Array | 0 | uncontrolled property。Typescript:`SliderValue` `type SliderValue = number \| Array<number>`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/slider/type.ts) | N
|
||||
vertical | Boolean | false | \- | N
|
||||
|
||||
### Slider Events
|
||||
|
||||
name | params | description
|
||||
-- | -- | --
|
||||
change | `(context: { value: SliderValue })` | \-
|
||||
dragend | `(context: { value: SliderValue, e: TouchEvent })` | \-
|
||||
dragstart | `(context: { e: TouchEvent })` | \-
|
||||
|
||||
### Slider External Classes
|
||||
|
||||
className | Description
|
||||
-- | --
|
||||
t-class | \-
|
||||
t-class-bar | \-
|
||||
t-class-bar-active | \-
|
||||
t-class-bar-disabled | \-
|
||||
t-class-cursor | \-
|
||||
|
||||
### CSS Variables
|
||||
|
||||
The component provides the following CSS variables, which can be used to customize styles.
|
||||
Name | Default Value | Description
|
||||
-- | -- | --
|
||||
--td-slider-active-color | @brand-color | -
|
||||
--td-slider-bar-height | 8rpx | -
|
||||
--td-slider-bar-width | 8rpx | -
|
||||
--td-slider-capsule-bar-color | @bg-color-component | -
|
||||
--td-slider-capsule-bar-heihgt | 48rpx | -
|
||||
--td-slider-capsule-bar-width | 48rpx | -
|
||||
--td-slider-capsule-line-heihgt | 36rpx | -
|
||||
--td-slider-default-color | @bg-color-component | -
|
||||
--td-slider-disabled-color | @brand-color-disabled | -
|
||||
--td-slider-disabled-text-color | @text-color-disabled | -
|
||||
--td-slider-dot-bg-color | #fff | -
|
||||
--td-slider-dot-color | @component-border | -
|
||||
--td-slider-dot-disabled-bg-color | #fff | -
|
||||
--td-slider-dot-disabled-border-color | #f3f3f3 | -
|
||||
--td-slider-dot-size | 40rpx | -
|
||||
--td-slider-text-color | @text-color-primary | -
|
||||
127
uni_modules/tdesign-uniapp/components/slider/README.md
Normal file
127
uni_modules/tdesign-uniapp/components/slider/README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Slider 滑动选择器
|
||||
description: 用于选择横轴上的数值、区间、档位。
|
||||
spline: data
|
||||
isComponent: true
|
||||
---
|
||||
|
||||
|
||||
## 引入
|
||||
|
||||
可在 `main.ts` 或在需要使用的页面或组件中引入。
|
||||
|
||||
```js
|
||||
import TSlider from '@tdesign/uniapp/slider/slider.vue';
|
||||
```
|
||||
|
||||
### 组件类型
|
||||
|
||||
单游标滑块
|
||||
|
||||
{{ base }}
|
||||
|
||||
双游标滑块
|
||||
|
||||
{{ range }}
|
||||
|
||||
带数值滑动选择器
|
||||
|
||||
{{ label }}
|
||||
|
||||
带刻度滑动选择器
|
||||
|
||||
{{ step }}
|
||||
|
||||
### 组件状态
|
||||
|
||||
滑块禁用状态
|
||||
|
||||
{{ disabled }}
|
||||
|
||||
#### 特殊样式
|
||||
|
||||
胶囊型滑块
|
||||
|
||||
{{ capsule }}
|
||||
|
||||
#### 垂直状态
|
||||
|
||||
垂直方向的滑块
|
||||
|
||||
{{ vertical }}
|
||||
|
||||
## FAQ
|
||||
|
||||
当 slider 外层使用 `hidden` 包裹,需要在 `hidden = false` 时,重新调用组件的 `init` 方法,才能正常渲染(在t-popup/t-dialog中同理)。如下:
|
||||
|
||||
```html
|
||||
<t-slider id="slider" />
|
||||
```
|
||||
|
||||
```js
|
||||
const $slider = this.selectComponent('#slider');
|
||||
|
||||
$slider.init();
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Slider Props
|
||||
|
||||
名称 | 类型 | 默认值 | 描述 | 必传
|
||||
-- | -- | -- | -- | --
|
||||
custom-style | Object | - | 自定义样式 | N
|
||||
colors | Array | [] | 已废弃。颜色,[已选择, 未选择]。TS 类型:`Array<string>` | N
|
||||
disabled | Boolean | undefined | 是否禁用组件 | N
|
||||
disabled-color | Array | [] | 已废弃。禁用状态滑动条的颜色,[已选, 未选]。TS 类型:`Array<string>` | N
|
||||
label | String / Boolean / Function | false | 滑块当前值文本。<br />值为 true 显示默认文案;值为 false 不显示滑块当前值文本;<br />值为 `${value}%` 则表示组件会根据占位符渲染文案;<br />值类型为函数时,参数 `value` 标识滑块值,参数 `position=start` 表示范围滑块的起始值,参数 `position=end` 表示范围滑块的终点值。TS 类型:`string \| boolean` | N
|
||||
marks | Object / Array | {} | 刻度标记,示例:`[0, 10, 40, 200]` 或者 `{ 5: '5¥', 10: '10%' }`。TS 类型:`Record<number, string> \| Array<number>` | N
|
||||
max | Number | 100 | 滑块范围最大值 | N
|
||||
min | Number | 0 | 滑块范围最小值 | N
|
||||
range | Boolean | false | 双游标滑块 | N
|
||||
show-extreme-value | Boolean | false | 是否边界值 | N
|
||||
step | Number | 1 | 步长 | N
|
||||
theme | String | default | 滑块风格。可选项:default/capsule | N
|
||||
value | Number / Array | 0 | 滑块值。支持语法糖 `v-model:value`。TS 类型:`SliderValue` `type SliderValue = number \| Array<number>`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/slider/type.ts) | N
|
||||
default-value | Number / Array | 0 | 滑块值。非受控属性。TS 类型:`SliderValue` `type SliderValue = number \| Array<number>`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/slider/type.ts) | N
|
||||
vertical | Boolean | false | 是否是垂直的滑块(渲染垂直滑块时,默认高度为400rpx,可通过修改`--td-slider-bar-height`来自定义高度) | N
|
||||
|
||||
### Slider Events
|
||||
|
||||
名称 | 参数 | 描述
|
||||
-- | -- | --
|
||||
change | `(context: { value: SliderValue })` | 滑块值变化时触发
|
||||
dragend | `(context: { value: SliderValue, e: TouchEvent })` | 结束拖动时触发
|
||||
dragstart | `(context: { e: TouchEvent })` | 开始拖动时触发
|
||||
|
||||
### Slider External Classes
|
||||
|
||||
类名 | 描述
|
||||
-- | --
|
||||
t-class | 根节点样式类
|
||||
t-class-bar | 滑道底部样式类
|
||||
t-class-bar-active | 滑道激活态样式类
|
||||
t-class-bar-disabled | 滑道禁用态样式类
|
||||
t-class-cursor | 游标样式类
|
||||
|
||||
### CSS Variables
|
||||
|
||||
组件提供了下列 CSS 变量,可用于自定义样式。
|
||||
名称 | 默认值 | 描述
|
||||
-- | -- | --
|
||||
--td-slider-active-color | @brand-color | -
|
||||
--td-slider-bar-height | 8rpx | -
|
||||
--td-slider-bar-width | 8rpx | -
|
||||
--td-slider-capsule-bar-color | @bg-color-component | -
|
||||
--td-slider-capsule-bar-heihgt | 48rpx | -
|
||||
--td-slider-capsule-bar-width | 48rpx | -
|
||||
--td-slider-capsule-line-heihgt | 36rpx | -
|
||||
--td-slider-default-color | @bg-color-component | -
|
||||
--td-slider-disabled-color | @brand-color-disabled | -
|
||||
--td-slider-disabled-text-color | @text-color-disabled | -
|
||||
--td-slider-dot-bg-color | #fff | -
|
||||
--td-slider-dot-color | @component-border | -
|
||||
--td-slider-dot-disabled-bg-color | #fff | -
|
||||
--td-slider-dot-disabled-border-color | #f3f3f3 | -
|
||||
--td-slider-dot-size | 40rpx | -
|
||||
--td-slider-text-color | @text-color-primary | -
|
||||
9
uni_modules/tdesign-uniapp/components/slider/computed.js
Normal file
9
uni_modules/tdesign-uniapp/components/slider/computed.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { getRegExp } from '../common/runtime/wxs-polyfill';
|
||||
|
||||
const REGEXP = getRegExp('[$][{value}]{7}');
|
||||
|
||||
export function getValue(label, value) {
|
||||
if (label && label === 'true') return value;
|
||||
if (REGEXP.test(label)) return label.replace(REGEXP, value);
|
||||
}
|
||||
|
||||
79
uni_modules/tdesign-uniapp/components/slider/props.ts
Normal file
79
uni_modules/tdesign-uniapp/components/slider/props.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
|
||||
* */
|
||||
|
||||
import type { TdSliderProps } from './type';
|
||||
export default {
|
||||
/** 是否禁用组件 */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
/** 滑块当前值文本。<br />值为 true 显示默认文案;值为 false 不显示滑块当前值文本;<br />值为 `${value}%` 则表示组件会根据占位符渲染文案;<br />值类型为函数时,参数 `value` 标识滑块值,参数 `position=start` 表示范围滑块的起始值,参数 `position=end` 表示范围滑块的终点值 */
|
||||
label: {
|
||||
type: [String, Boolean, Function],
|
||||
default: false as TdSliderProps['label'],
|
||||
},
|
||||
/** 刻度标记,示例:`[0, 10, 40, 200]` 或者 `{ 5: '5¥', 10: '10%' }` */
|
||||
marks: {
|
||||
type: [Object, Array],
|
||||
default: () => ({}) as TdSliderProps['marks'],
|
||||
},
|
||||
/** 滑块范围最大值 */
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
/** 滑块范围最小值 */
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
/** 双游标滑块 */
|
||||
range: Boolean,
|
||||
/** 是否边界值 */
|
||||
showExtremeValue: Boolean,
|
||||
/** 步长 */
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
/** 滑块风格 */
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'default' as TdSliderProps['theme'],
|
||||
validator(val: TdSliderProps['theme']): boolean {
|
||||
if (!val) return true;
|
||||
return ['default', 'capsule'].includes(val);
|
||||
},
|
||||
},
|
||||
/** 滑块值 */
|
||||
value: {
|
||||
type: [Number, Array],
|
||||
default: 0 as TdSliderProps['value'],
|
||||
},
|
||||
/** 滑块值,非受控属性 */
|
||||
defaultValue: {
|
||||
type: [Number, Array],
|
||||
default: 0 as TdSliderProps['defaultValue'],
|
||||
},
|
||||
/** 是否是垂直的滑块(渲染垂直滑块时,默认高度为400rpx,可通过修改`--td-slider-bar-height`来自定义高度) */
|
||||
vertical: Boolean,
|
||||
/** 滑块值变化时触发 */
|
||||
onChange: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
/** 结束拖动时触发 */
|
||||
onDragend: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
/** 开始拖动时触发 */
|
||||
onDragstart: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
};
|
||||
239
uni_modules/tdesign-uniapp/components/slider/slider.css
Normal file
239
uni_modules/tdesign-uniapp/components/slider/slider.css
Normal file
@@ -0,0 +1,239 @@
|
||||
.t-slider {
|
||||
width: 100%;
|
||||
font: var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.t-slider--disabled .t-slider__value,
|
||||
.t-slider--disabled .t-slider__range-extreme,
|
||||
.t-slider--disabled .t-slider__dot-value,
|
||||
.t-slider--disabled .t-slider__scale-desc {
|
||||
color: var(--td-slider-disabled-text-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
|
||||
}
|
||||
.t-slider--disabled .t-slider__dot {
|
||||
background-color: var(--td-slider-dot-disabled-bg-color, #fff);
|
||||
border-color: var(--td-slider-dot-disabled-border-color, #f3f3f3);
|
||||
}
|
||||
.t-slider--top {
|
||||
padding-top: 40rpx;
|
||||
}
|
||||
.t-slider__line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: var(--td-slider-bar-height, 8rpx);
|
||||
border-radius: calc(var(--td-slider-bar-height, 8rpx) / 2);
|
||||
background-color: var(--td-slider-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
}
|
||||
.t-slider__line--disabled {
|
||||
background-color: var(--td-slider-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
|
||||
}
|
||||
.t-slider__line--capsule {
|
||||
height: var(--td-slider-capsule-line-heihgt, 36rpx);
|
||||
}
|
||||
.t-slider__line--capsule.t-slider__line--single {
|
||||
border-top-left-radius: calc(var(--td-slider-capsule-line-heihgt, 36rpx) / 2);
|
||||
border-bottom-left-radius: calc(var(--td-slider-capsule-line-heihgt, 36rpx) / 2);
|
||||
}
|
||||
.t-slider__dot {
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid var(--td-slider-dot-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate3d(50%, -50%, 0);
|
||||
z-index: 2;
|
||||
background-color: var(--td-slider-dot-bg-color, #fff);
|
||||
width: var(--td-slider-dot-size, 40rpx);
|
||||
height: var(--td-slider-dot-size, 40rpx);
|
||||
box-shadow: var(--td-shadow-1, 0 1px 10px rgba(0, 0, 0, 0.05), 0 4px 5px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.12));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.t-slider__dot--left {
|
||||
left: 0;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
.t-slider__dot-value {
|
||||
position: relative;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: -52rpx;
|
||||
text-align: center;
|
||||
width: 96rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
.t-slider__value,
|
||||
.t-slider__range-extreme,
|
||||
.t-slider__dot-value {
|
||||
color: var(--td-slider-text-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
|
||||
}
|
||||
.t-slider__value--sr-only,
|
||||
.t-slider__range-extreme--sr-only,
|
||||
.t-slider__dot-value--sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
clip-path: inset(50%);
|
||||
border: 0;
|
||||
}
|
||||
.t-slider__dot-slider {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.t-slider__value--min {
|
||||
margin-left: 32rpx;
|
||||
}
|
||||
.t-slider__value--max {
|
||||
margin-right: 32rpx;
|
||||
}
|
||||
.t-slider__bar {
|
||||
margin: 16rpx 32rpx;
|
||||
flex: 10;
|
||||
background-clip: content-box;
|
||||
height: var(--td-slider-bar-height, 8rpx);
|
||||
border-radius: calc(var(--td-slider-bar-height, 8rpx) / 2);
|
||||
position: relative;
|
||||
background-color: var(--td-slider-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
|
||||
}
|
||||
.t-slider__bar--capsule {
|
||||
height: var(--td-slider-capsule-bar-heihgt, 48rpx);
|
||||
border-radius: calc(var(--td-slider-capsule-bar-heihgt, 48rpx) / 2);
|
||||
background-color: var(--td-slider-capsule-bar-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
|
||||
border: 6rpx solid var(--td-slider-capsule-bar-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.t-slider__bar--marks {
|
||||
background-color: var(--td-slider-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
|
||||
}
|
||||
.t-slider__bar--disabled {
|
||||
background-color: var(--td-slider-default-color, var(--td-bg-color-component-disabled, var(--td-gray-color-2, #eeeeee)));
|
||||
}
|
||||
.t-slider__range-extreme--min {
|
||||
margin-left: 32rpx;
|
||||
text-align: left;
|
||||
}
|
||||
.t-slider__range-extreme--max {
|
||||
margin-right: 32rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.t-slider__scale-item {
|
||||
background-color: var(--td-slider-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
|
||||
width: var(--td-slider-bar-height, 8rpx);
|
||||
height: 16rpx;
|
||||
width: 16rpx;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -8rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
.t-slider__scale-item--active {
|
||||
background-color: var(--td-slider-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
}
|
||||
.t-slider__scale-item--disabled {
|
||||
background-color: var(--td-slider-default-color, var(--td-bg-color-component-disabled, var(--td-gray-color-2, #eeeeee)));
|
||||
}
|
||||
.t-slider__scale-item--active.t-slider__scale-item--disabled {
|
||||
background-color: var(--td-slider-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
|
||||
}
|
||||
.t-slider__scale-item--capsule {
|
||||
height: var(--td-slider-capsule-line-heihgt, 36rpx);
|
||||
width: 4rpx;
|
||||
border-radius: 0;
|
||||
background-color: var(--td-slider-capsule-bar-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
|
||||
margin-top: calc(-0.5 * var(--td-slider-capsule-line-heihgt, 36rpx));
|
||||
}
|
||||
.t-slider__scale-item--hidden {
|
||||
background-color: transparent;
|
||||
}
|
||||
.t-slider__scale-desc {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
color: var(--td-slider-text-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
|
||||
transform: translateX(-50%);
|
||||
bottom: 32rpx;
|
||||
}
|
||||
.t-slider__scale-desc--capsule {
|
||||
bottom: 46rpx;
|
||||
}
|
||||
.t-slider--vertical {
|
||||
--td-slider-bar-height: 400rpx;
|
||||
height: var(--td-slider-bar-height, 8rpx);
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
.t-slider--vertical .t-slider__bar {
|
||||
flex: none;
|
||||
height: 100%;
|
||||
width: var(--td-slider-bar-width, 8rpx);
|
||||
}
|
||||
.t-slider--vertical .t-slider__bar--capsule {
|
||||
width: var(--td-slider-capsule-bar-width, 48rpx);
|
||||
border-radius: calc(var(--td-slider-capsule-bar-width, 48rpx) / 2);
|
||||
}
|
||||
.t-slider--vertical .t-slider__line {
|
||||
width: 100%;
|
||||
height: unset;
|
||||
left: 0;
|
||||
border-radius: calc(var(--td-slider-bar-width, 8rpx) / 2);
|
||||
}
|
||||
.t-slider--vertical .t-slider__line--capsule.t-slider__line--single {
|
||||
border-top-left-radius: calc(var(--td-slider-capsule-line-heihgt, 36rpx) / 2);
|
||||
border-top-right-radius: calc(var(--td-slider-capsule-line-heihgt, 36rpx) / 2);
|
||||
}
|
||||
.t-slider--vertical .t-slider__dot {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.t-slider--vertical .t-slider__dot--left {
|
||||
top: 0;
|
||||
transform: translate(-50%, -50%);
|
||||
left: 50%;
|
||||
}
|
||||
.t-slider--vertical .t-slider__dot--right {
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 100%;
|
||||
}
|
||||
.t-slider--vertical .t-slider__dot-value {
|
||||
left: 54rpx;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
width: auto;
|
||||
}
|
||||
.t-slider--vertical .t-slider__range-extreme {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin: 0;
|
||||
}
|
||||
.t-slider--vertical .t-slider__range-extreme--min {
|
||||
top: 0;
|
||||
}
|
||||
.t-slider--vertical .t-slider__range-extreme--max {
|
||||
bottom: 0;
|
||||
}
|
||||
.t-slider--vertical .t-slider__scale-item {
|
||||
left: 50%;
|
||||
margin-top: 0;
|
||||
}
|
||||
.t-slider--vertical .t-slider__scale-item--capsule {
|
||||
height: 4rpx;
|
||||
width: var(--td-slider-capsule-line-heihgt, 36rpx);
|
||||
}
|
||||
.t-slider--vertical .t-slider__scale-desc {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
bottom: unset;
|
||||
left: 38rpx;
|
||||
}
|
||||
.t-slider--vertical .t-slider__scale-desc--capsule {
|
||||
left: 52rpx;
|
||||
}
|
||||
719
uni_modules/tdesign-uniapp/components/slider/slider.vue
Normal file
719
uni_modules/tdesign-uniapp/components/slider/slider.vue
Normal file
@@ -0,0 +1,719 @@
|
||||
<template>
|
||||
<view
|
||||
:style="tools._style([customStyle])"
|
||||
:class="
|
||||
tools.cls(classPrefix, [
|
||||
['top', label || scaleTextArray.length],
|
||||
['disabled', disabled],
|
||||
['range', range]
|
||||
]) +
|
||||
' ' +
|
||||
tClass +
|
||||
' ' +
|
||||
(vertical ? classPrefix + '--vertical' : '')
|
||||
"
|
||||
>
|
||||
<block v-if="!range">
|
||||
<text
|
||||
v-if="showExtremeValue"
|
||||
:class="classPrefix + '__value ' + classPrefix + '__value--min'"
|
||||
>
|
||||
{{ label ? getValue(label, min) : min }}
|
||||
</text>
|
||||
<view
|
||||
id="sliderLine"
|
||||
:class="tools.cls(classPrefix + '__bar', [['disabled', disabled], theme, ['marks', isScale && theme == 'capsule']]) + ' ' + tClassBar"
|
||||
@click="onSingleLineTap"
|
||||
>
|
||||
<block v-if="isScale">
|
||||
<view
|
||||
v-for="(item, index) in scaleArray"
|
||||
:key="index"
|
||||
:class="
|
||||
tools.cls(classPrefix + '__scale-item', [
|
||||
['active', _value >= item.val],
|
||||
['disabled', disabled],
|
||||
theme,
|
||||
['hidden', ((index == 0 || index == scaleArray.length - 1) && theme == 'capsule') || value == item.val]
|
||||
])
|
||||
"
|
||||
:style="(vertical ? 'top' : 'left') + ':' + item.left + 'px; ' + (vertical ? 'transform: translate(-50%, -50%);' : 'transform: translateX(-50%);')"
|
||||
:aria-hidden="true"
|
||||
>
|
||||
<view
|
||||
v-if="scaleTextArray.length"
|
||||
:class="tools.cls(classPrefix + '__scale-desc', [theme])"
|
||||
>
|
||||
{{ scaleTextArray[index] }}
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view
|
||||
:class="tools.cls(classPrefix + '__line', [['disabled', disabled], theme, 'single']) + ' ' + tClassBarActive"
|
||||
:style="(vertical ? 'height' : 'width') + ': ' + lineBarWidth"
|
||||
>
|
||||
<view
|
||||
id="singleDot"
|
||||
:class="classPrefix + '__dot ' + tClassCursor"
|
||||
@touchstart.stop.prevent="onTouchStart"
|
||||
@touchmove.stop.prevent="onSingleLineTap"
|
||||
@touchend.stop.prevent="onTouchEnd"
|
||||
@touchcancel.stop.prevent="onTouchEnd"
|
||||
>
|
||||
<view
|
||||
v-if="label || isVisibleToScreenReader"
|
||||
:class="tools.cls(classPrefix + '__dot-value', [['sr-only', !label]])"
|
||||
aria-role="alert"
|
||||
aria-live="assertive"
|
||||
:aria-hidden="!isVisibleToScreenReader"
|
||||
>
|
||||
{{ getValue(label, _value) || _value }}
|
||||
</view>
|
||||
<view
|
||||
:class="classPrefix + '__dot-slider'"
|
||||
aria-role="slider"
|
||||
:aria-disabled="disabled"
|
||||
:aria-valuemax="max"
|
||||
:aria-valuemin="min"
|
||||
:aria-valuenow="_value"
|
||||
:aria-valuetext="getValue(label, _value) || _value"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text
|
||||
v-if="showExtremeValue"
|
||||
:class="classPrefix + '__value ' + classPrefix + '__value--max'"
|
||||
>
|
||||
{{ label ? getValue(label, max) : max }}
|
||||
</text>
|
||||
</block>
|
||||
<block v-if="range">
|
||||
<view
|
||||
v-if="showExtremeValue"
|
||||
:class="classPrefix + '__range-extreme ' + classPrefix + '__range-extreme--min'"
|
||||
>
|
||||
{{ min }}
|
||||
</view>
|
||||
<view
|
||||
id="sliderLine"
|
||||
:class="tools.cls(classPrefix + '__bar', [['disabled', disabled], theme, ['marks', isScale && theme == 'capsule']]) + ' ' + tClassBar"
|
||||
@click="onLineTap"
|
||||
>
|
||||
<block v-if="isScale">
|
||||
<view
|
||||
v-for="(item, index) in scaleArray"
|
||||
:key="index"
|
||||
:class="
|
||||
tools.cls(classPrefix + '__scale-item', [
|
||||
['active', dotTopValue[1] >= item.val && item.val >= dotTopValue[0]],
|
||||
['disabled', disabled],
|
||||
theme,
|
||||
['hidden', ((index == 0 || index == scaleArray.length - 1) && theme == 'capsule') || value == item.val]
|
||||
])
|
||||
"
|
||||
:style="(vertical ? 'top' : 'left') + ':' + item.left + 'px; ' + (vertical ? 'transform: translate(-50%, -50%);' : 'transform: translateX(-50%);')"
|
||||
:aria-hidden="true"
|
||||
>
|
||||
<view
|
||||
v-if="scaleTextArray.length"
|
||||
:class="tools.cls(classPrefix + '__scale-desc', [theme])"
|
||||
>
|
||||
{{ scaleTextArray[index] }}
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view
|
||||
:class="tools.cls(classPrefix + '__line', [['disabled', disabled], theme]) + ' ' + tClassBarActive"
|
||||
:style="(vertical ? 'top' : 'left') + ': ' + lineLeft + 'px; ' + (vertical ? 'bottom' : 'right') + ': ' + lineRight + 'px'"
|
||||
>
|
||||
<view
|
||||
id="leftDot"
|
||||
:class="classPrefix + '__dot ' + classPrefix + '__dot--left ' + tClassCursor"
|
||||
@touchstart.stop.prevent="onTouchStart"
|
||||
@touchmove.stop.prevent="onTouchMoveLeft"
|
||||
@touchend.stop.prevent="onTouchEnd"
|
||||
@touchcancel.stop.prevent="onTouchEnd"
|
||||
>
|
||||
<view
|
||||
v-if="label || isVisibleToScreenReader"
|
||||
:class="tools.cls(classPrefix + '__dot-value', [['sr-only', !label]])"
|
||||
aria-role="alert"
|
||||
aria-live="assertive"
|
||||
:aria-hidden="!isVisibleToScreenReader"
|
||||
>
|
||||
{{ getValue(label, dotTopValue[0]) || dotTopValue[0] }}
|
||||
</view>
|
||||
<view
|
||||
:class="classPrefix + '__dot-slider'"
|
||||
aria-role="slider"
|
||||
:aria-disabled="disabled"
|
||||
:aria-valuemax="max"
|
||||
:aria-valuemin="min"
|
||||
:aria-valuenow="dotTopValue[0]"
|
||||
:aria-valuetext="getValue(label, dotTopValue[0]) || dotTopValue[0]"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
id="rightDot"
|
||||
:class="classPrefix + '__dot ' + classPrefix + '__dot--right ' + tClassCursor"
|
||||
@touchstart.stop.prevent="onTouchStart"
|
||||
@touchmove.stop.prevent="onTouchMoveRight"
|
||||
@touchend.stop.prevent="onTouchEnd"
|
||||
@touchcancel.stop.prevent="onTouchEnd"
|
||||
>
|
||||
<view
|
||||
v-if="label || isVisibleToScreenReader"
|
||||
:class="tools.cls(classPrefix + '__dot-value', [['sr-only', !label]])"
|
||||
aria-role="alert"
|
||||
aria-live="assertive"
|
||||
:aria-hidden="!isVisibleToScreenReader"
|
||||
>
|
||||
{{ getValue(label, dotTopValue[1]) || dotTopValue[1] }}
|
||||
</view>
|
||||
<view
|
||||
:class="classPrefix + '__dot-slider'"
|
||||
aria-role="slider"
|
||||
:aria-disabled="disabled"
|
||||
:aria-valuemax="max"
|
||||
:aria-valuemin="min"
|
||||
:aria-valuenow="dotTopValue[1]"
|
||||
:aria-valuetext="getValue(label, dotTopValue[1]) || dotTopValue[1]"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showExtremeValue"
|
||||
:class="classPrefix + '__range-extreme ' + classPrefix + '__range-extreme--max'"
|
||||
>
|
||||
{{ max }}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { uniComponent } from '../common/src/index';
|
||||
import { prefix } from '../common/config';
|
||||
import { trimSingleValue, trimValue } from './tool';
|
||||
import props from './props';
|
||||
import { getRect, coalesce, nextTick } from '../common/utils';
|
||||
import Bus from '../common/bus';
|
||||
import tools from '../common/utils.wxs';
|
||||
import { getValue } from './computed.js';
|
||||
import { isString, isFunction } from '../common/validator';
|
||||
|
||||
|
||||
const name = `${prefix}-slider`;
|
||||
|
||||
export default uniComponent({
|
||||
name,
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
controlledProps: [
|
||||
{
|
||||
key: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
],
|
||||
externalClasses: [
|
||||
`${prefix}-class`,
|
||||
`${prefix}-class-bar`,
|
||||
`${prefix}-class-bar-active`,
|
||||
`${prefix}-class-bar-disabled`,
|
||||
`${prefix}-class-cursor`,
|
||||
],
|
||||
props: {
|
||||
...props,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 按钮样式列表
|
||||
sliderStyles: '',
|
||||
classPrefix: name,
|
||||
initialLeft: null,
|
||||
initialRight: null,
|
||||
activeLeft: 0,
|
||||
activeRight: 0,
|
||||
maxRange: 0,
|
||||
lineLeft: 0,
|
||||
lineRight: 0,
|
||||
dotTopValue: [0, 0],
|
||||
_value: 0,
|
||||
blockSize: 20,
|
||||
isScale: false,
|
||||
scaleArray: [],
|
||||
scaleTextArray: [],
|
||||
prefix,
|
||||
realLabel: '',
|
||||
extremeLabel: [],
|
||||
isVisibleToScreenReader: false,
|
||||
identifier: [-1, -1],
|
||||
__inited: false,
|
||||
tools,
|
||||
|
||||
lineBarWidth: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(newValue) {
|
||||
this.handlePropsChange(newValue);
|
||||
},
|
||||
// immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
_value: {
|
||||
handler(newValue) {
|
||||
this.bus.on('initial', () => this.renderLine(newValue));
|
||||
this.toggleA11yTips();
|
||||
},
|
||||
// immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
marks: {
|
||||
handler(val) {
|
||||
this.bus.on('initial', () => this.handleMark(val));
|
||||
},
|
||||
// immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
label: {
|
||||
handler(val) {
|
||||
this.isShowLabel = Boolean(val);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
showExtremeValue: 'getwExtremeLabel',
|
||||
min: 'getwExtremeLabel',
|
||||
max: 'getwExtremeLabel',
|
||||
},
|
||||
created() {
|
||||
this.bus = new Bus();
|
||||
this.bus.on('initial', () => this.handleMark(this.marks));
|
||||
this.bus.on('initial', () => {
|
||||
nextTick().then(() => {
|
||||
this.renderLine(this._value);
|
||||
});
|
||||
});
|
||||
this.toggleA11yTips();
|
||||
},
|
||||
mounted() {
|
||||
const { value, defaultValue } = this;
|
||||
// if (!value)
|
||||
this.handlePropsChange(coalesce(value, defaultValue, 0));
|
||||
this.init();
|
||||
this.injectPageScroll();
|
||||
},
|
||||
|
||||
methods: {
|
||||
getValue,
|
||||
getwExtremeLabel() {
|
||||
const { showExtremeValue, min, max } = this;
|
||||
if (!showExtremeValue) return;
|
||||
|
||||
this.extremeLabel = [this.getLabelByValue(Number(min), 'min'), this.getLabelByValue(Number(max), 'max')];
|
||||
},
|
||||
injectPageScroll() {
|
||||
const { range, vertical } = this;
|
||||
if (!range || !vertical) return;
|
||||
const pages = getCurrentPages() || [];
|
||||
let curPage = null;
|
||||
if (pages && pages.length - 1 >= 0) {
|
||||
curPage = pages[pages.length - 1];
|
||||
}
|
||||
if (!curPage) return;
|
||||
const originPageScroll = curPage?.onPageScroll;
|
||||
curPage.onPageScroll = (rest) => {
|
||||
originPageScroll?.call(this, rest);
|
||||
this.observerScrollTop(rest);
|
||||
};
|
||||
},
|
||||
|
||||
observerScrollTop(rest) {
|
||||
const { scrollTop } = rest || {};
|
||||
this.pageScrollTop = scrollTop;
|
||||
},
|
||||
|
||||
toggleA11yTips() {
|
||||
this.isVisibleToScreenReader = true;
|
||||
|
||||
setTimeout(() => {
|
||||
this.isVisibleToScreenReader = false;
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
renderLine(val) {
|
||||
const { min, max, range } = this;
|
||||
const { maxRange } = this;
|
||||
|
||||
if (range) {
|
||||
const left = (maxRange * (val[0] - Number(min))) / (Number(max) - Number(min));
|
||||
const right = (maxRange * (Number(max) - val[1])) / (Number(max) - Number(min));
|
||||
// 因为要计算点相对于线的绝对定位,所以要取整条线的长度而非可滑动的范围
|
||||
this.setLineStyle(left, right);
|
||||
} else {
|
||||
this.setSingleBarWidth(val);
|
||||
}
|
||||
},
|
||||
|
||||
triggerValue(value) {
|
||||
const { min, max, range } = this;
|
||||
const trimmedValue = trimValue(value, {
|
||||
min, max, range,
|
||||
});
|
||||
|
||||
if (JSON.stringify(this.preval) === JSON.stringify(trimmedValue)) return;
|
||||
this.preval = value;
|
||||
|
||||
this._trigger('change', {
|
||||
value: trimmedValue,
|
||||
});
|
||||
if (this._selfControlled) {
|
||||
this._value = trimmedValue;
|
||||
}
|
||||
},
|
||||
|
||||
getLabelByValue(value, position) {
|
||||
const { label } = this;
|
||||
|
||||
if (isString(label)) {
|
||||
let text = String(value);
|
||||
try {
|
||||
const rule = /\${value}%/g;
|
||||
const enableToReplace = rule.test(label);
|
||||
if (enableToReplace) {
|
||||
text = label.replace(rule, String(value));
|
||||
} else {
|
||||
text = label;
|
||||
throw new Error();
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
if (isFunction(label)) {
|
||||
return label(value, position);
|
||||
}
|
||||
|
||||
return String(value);
|
||||
},
|
||||
|
||||
handlePropsChange(newValue) {
|
||||
const { min, max, range } = this;
|
||||
|
||||
const value = trimValue(newValue, {
|
||||
min, max, range,
|
||||
});
|
||||
|
||||
const realLabel = this.getLabelByValue(value);
|
||||
|
||||
// 避免受控模式下死循环,同时不影响初始化后的首次点击
|
||||
if (this.preval !== undefined) {
|
||||
this.preval = value;
|
||||
}
|
||||
|
||||
const setValueAndTrigger = () => {
|
||||
this._value = value;
|
||||
this.realLabel = realLabel;
|
||||
};
|
||||
|
||||
// 基本样式未初始化,等待初始化后在改变数据。
|
||||
if (this.maxRange === 0) {
|
||||
this.init().then(setValueAndTrigger);
|
||||
return;
|
||||
}
|
||||
|
||||
setValueAndTrigger();
|
||||
},
|
||||
|
||||
valueToPosition(value) {
|
||||
const { min, max, theme } = this;
|
||||
const { blockSize, maxRange } = this;
|
||||
const halfBlock = (theme) === 'capsule' ? Number(blockSize) / 2 : 0;
|
||||
|
||||
return Math.round(((Number(value) - Number(min)) / (Number(max) - Number(min))) * maxRange) + halfBlock;
|
||||
},
|
||||
|
||||
handleMark(marks) {
|
||||
const calcPos = arr => arr.map(item => ({
|
||||
val: item,
|
||||
left: this.valueToPosition(item),
|
||||
}));
|
||||
|
||||
if (marks?.length && Array.isArray(marks)) {
|
||||
this.isScale = true;
|
||||
this.scaleArray = calcPos(marks);
|
||||
this.scaleTextArray = [];
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(marks) === '[object Object]') {
|
||||
const scaleArray = Object.keys(marks).map(item => Number(item));
|
||||
const scaleTextArray = scaleArray.map(item => marks[item]);
|
||||
|
||||
this.isScale = scaleArray.length > 0;
|
||||
this.scaleArray = calcPos(scaleArray);
|
||||
this.scaleTextArray = scaleTextArray;
|
||||
}
|
||||
},
|
||||
|
||||
setSingleBarWidth(value) {
|
||||
const width = this.valueToPosition(value);
|
||||
|
||||
this.lineBarWidth = `${width}px`;
|
||||
},
|
||||
|
||||
async init() {
|
||||
if (this.__inited) return;
|
||||
await this.getInitialInfo();
|
||||
this.__inited = true;
|
||||
this.bus.emit('initial');
|
||||
},
|
||||
|
||||
async getInitialInfo() {
|
||||
const line = await getRect(this, '#sliderLine');
|
||||
const { blockSize } = this;
|
||||
const { theme, vertical } = this;
|
||||
const halfBlock = Number(blockSize) / 2;
|
||||
const { top, bottom, right, left } = line;
|
||||
let maxRange = vertical ? bottom - top : right - left;
|
||||
let initialLeft = vertical ? top : left;
|
||||
let initialRight = vertical ? bottom : right;
|
||||
if (initialLeft === 0 && initialRight === 0) return;
|
||||
|
||||
if ((theme) === 'capsule') {
|
||||
maxRange = maxRange - Number(blockSize) - 6; // 6 是边框宽度
|
||||
initialLeft -= halfBlock;
|
||||
initialRight -= halfBlock;
|
||||
}
|
||||
|
||||
this.maxRange = maxRange;
|
||||
this.initialLeft = initialLeft;
|
||||
this.initialRight = initialRight;
|
||||
},
|
||||
|
||||
stepValue(value) {
|
||||
const { step, min, max } = this;
|
||||
const decimal = String(step).indexOf('.') > -1 ? String(step).length - String(step).indexOf('.') - 1 : 0;
|
||||
const closestStep = trimSingleValue(
|
||||
Number((Math.round(value / Number(step)) * Number(step)).toFixed(decimal)),
|
||||
Number(min),
|
||||
Number(max),
|
||||
);
|
||||
return closestStep;
|
||||
},
|
||||
|
||||
// 点击滑动条的事件
|
||||
async onSingleLineTap(e) {
|
||||
await this.getInitialInfo();
|
||||
const { disabled } = this;
|
||||
if (disabled) return;
|
||||
const isSingleLineTap = this.identifier[0] === -1; // 区分点击滑动条和单游标的滑动
|
||||
if (isSingleLineTap) {
|
||||
const [touch] = e.changedTouches;
|
||||
this.identifier[0] = touch.identifier;
|
||||
}
|
||||
const value = await this.getSingleChangeValue(e);
|
||||
|
||||
if (isSingleLineTap) {
|
||||
this.identifier[0] = -1;
|
||||
}
|
||||
this.triggerValue(value);
|
||||
},
|
||||
|
||||
async getSingleChangeValue(e) {
|
||||
// await this.getInitialInfo();
|
||||
const { min, max, theme, vertical } = this;
|
||||
const { initialLeft, maxRange, blockSize } = this;
|
||||
const touch = e.changedTouches.find(item => item.identifier === this.identifier[0]);
|
||||
if (!touch) return;
|
||||
const pagePosition = this.getPagePosition(touch);
|
||||
|
||||
let offset = 0;
|
||||
if ((theme) === 'capsule') {
|
||||
offset = Number(blockSize);
|
||||
if (vertical) {
|
||||
}
|
||||
offset += 3;
|
||||
} else if (vertical) {
|
||||
}
|
||||
|
||||
const currentLeft = pagePosition - initialLeft - offset;
|
||||
let value = 0;
|
||||
if (currentLeft <= 0) {
|
||||
value = Number(min);
|
||||
} else if (currentLeft >= maxRange) {
|
||||
value = Number(max);
|
||||
} else {
|
||||
value = (currentLeft / maxRange) * (Number(max) - Number(min)) + Number(min);
|
||||
}
|
||||
return this.stepValue(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* 将位置转换为值
|
||||
*
|
||||
* @param {number} posValue 位置数据
|
||||
* @param {(0 | 1)} dir 方向: 0-left, 1-right
|
||||
* @return {number}
|
||||
* @memberof Slider
|
||||
*/
|
||||
convertPosToValue(posValue, dir) {
|
||||
const { maxRange } = this;
|
||||
const { max, min } = this;
|
||||
return dir === 0
|
||||
? (posValue / maxRange) * (Number(max) - Number(min)) + Number(min)
|
||||
: Number(max) - (posValue / maxRange) * (Number(max) - Number(min));
|
||||
},
|
||||
|
||||
// 点击范围选择滑动条的事件
|
||||
onLineTap(e) {
|
||||
const { disabled, theme, vertical } = this;
|
||||
const { initialLeft, initialRight, maxRange, blockSize } = this;
|
||||
if (disabled) return;
|
||||
|
||||
const [touch] = e.changedTouches;
|
||||
const pagePosition = this.getPagePosition(touch);
|
||||
const halfBlock = (theme) === 'capsule' ? Number(blockSize) / 2 : 0;
|
||||
|
||||
const currentLeft = pagePosition - initialLeft;
|
||||
const currentRight = -(pagePosition - initialRight);
|
||||
if (currentLeft < 0 || currentRight > maxRange + Number(blockSize)) return;
|
||||
|
||||
Promise.all([getRect(this, '#leftDot'), getRect(this, '#rightDot')]).then(([leftDot, rightDot]) => {
|
||||
const pageScrollTop = this.pageScrollTop || 0;
|
||||
// 点击处-halfblock 与 leftDot左侧的距离(绝对值)
|
||||
const leftDotPosition = vertical ? leftDot.top + pageScrollTop : leftDot.left;
|
||||
const distanceLeft = Math.abs(pagePosition - leftDotPosition - halfBlock);
|
||||
// 点击处-halfblock 与 rightDot左侧的距离(绝对值)
|
||||
const rightDotPosition = vertical ? rightDot.top + pageScrollTop : rightDot.left;
|
||||
const distanceRight = Math.abs(rightDotPosition - pagePosition + halfBlock);
|
||||
// 哪个绝对值小就移动哪个Dot
|
||||
const isMoveLeft = distanceLeft < distanceRight;
|
||||
|
||||
let offset = 0;
|
||||
if ((theme) === 'capsule') {
|
||||
offset = Number(blockSize);
|
||||
if (vertical) {
|
||||
offset *= 2;
|
||||
}
|
||||
offset += 6;
|
||||
} else if (vertical) {
|
||||
offset = Number(blockSize);
|
||||
}
|
||||
|
||||
if (isMoveLeft) {
|
||||
// 当前leftdot中心 + 左侧偏移量 = 目标左侧中心距离
|
||||
const left = pagePosition - initialLeft - offset;
|
||||
const leftValue = this.convertPosToValue(left, 0);
|
||||
this.triggerValue([this.stepValue(leftValue), this._value[1]]);
|
||||
} else {
|
||||
let right = -(pagePosition - initialRight);
|
||||
if (vertical) {
|
||||
right += offset / 2;
|
||||
}
|
||||
const rightValue = this.convertPosToValue(right, 1);
|
||||
|
||||
this.triggerValue([this._value[0], this.stepValue(rightValue)]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async onTouchStart(e) {
|
||||
await this.getInitialInfo();
|
||||
this.$emit('dragstart', { e });
|
||||
const [touch] = e.changedTouches;
|
||||
if (e.currentTarget.id === 'rightDot') {
|
||||
this.identifier[1] = touch.identifier;
|
||||
} else {
|
||||
this.identifier[0] = touch.identifier;
|
||||
}
|
||||
},
|
||||
|
||||
onTouchMoveLeft(e) {
|
||||
const { disabled, theme } = this;
|
||||
const { initialLeft, _value, blockSize } = this;
|
||||
if (disabled) return;
|
||||
|
||||
const touch = e.changedTouches.find(item => item.identifier === this.identifier[0]);
|
||||
if (!touch) return;
|
||||
const pagePosition = this.getPagePosition(touch);
|
||||
let offset = 0;
|
||||
if ((theme) === 'capsule') {
|
||||
offset = Number(blockSize) + 3;
|
||||
}
|
||||
const currentLeft = pagePosition - initialLeft - offset;
|
||||
|
||||
const newData = [...(_value)];
|
||||
const leftValue = this.convertPosToValue(currentLeft, 0);
|
||||
|
||||
newData[0] = this.stepValue(leftValue);
|
||||
|
||||
this.triggerValue(newData);
|
||||
},
|
||||
|
||||
onTouchMoveRight(e) {
|
||||
const { disabled, vertical, theme } = this;
|
||||
const { initialRight, _value } = this;
|
||||
if (disabled) return;
|
||||
|
||||
const touch = e.changedTouches.find(item => item.identifier === this.identifier[1]);
|
||||
if (!touch) return;
|
||||
|
||||
const pagePosition = this.getPagePosition(touch);
|
||||
let offset = 0;
|
||||
if ((theme) === 'capsule') {
|
||||
offset -= 3;
|
||||
} else if (vertical) {
|
||||
}
|
||||
const currentRight = -(pagePosition - initialRight - offset);
|
||||
|
||||
const newData = [...(_value)];
|
||||
const rightValue = this.convertPosToValue(currentRight, 1);
|
||||
newData[1] = this.stepValue(rightValue);
|
||||
|
||||
this.triggerValue(newData);
|
||||
},
|
||||
|
||||
setLineStyle(left, right) {
|
||||
const { theme } = this;
|
||||
const { blockSize, maxRange } = this;
|
||||
const halfBlock = (theme) === 'capsule' ? Number(blockSize) / 2 : 0;
|
||||
const [a, b] = this._value ;
|
||||
const cut = v => parseInt(v, 10);
|
||||
|
||||
this.dotTopValue = [a, b];
|
||||
this.realLabel = [this.getLabelByValue(a, 'start'), this.getLabelByValue(b, 'end')];
|
||||
|
||||
if (left + right <= maxRange) {
|
||||
this.lineLeft = cut(left + halfBlock);
|
||||
this.lineRight = cut(right + halfBlock);
|
||||
} else {
|
||||
this.lineLeft = cut(maxRange + halfBlock - right);
|
||||
this.lineRight = cut(maxRange - left + halfBlock * 1.5);
|
||||
}
|
||||
},
|
||||
|
||||
onTouchEnd(e) {
|
||||
this.$emit('dragend', { e, value: this._value });
|
||||
if (e.currentTarget.id === 'rightDot') {
|
||||
this.identifier[1] = -1;
|
||||
} else {
|
||||
this.identifier[0] = -1;
|
||||
}
|
||||
},
|
||||
|
||||
getPagePosition(touch) {
|
||||
const { clientX, clientY } = touch;
|
||||
const { vertical } = this;
|
||||
return vertical ? clientY : clientX;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
@import './slider.css';
|
||||
</style>
|
||||
43
uni_modules/tdesign-uniapp/components/slider/tool.js
Normal file
43
uni_modules/tdesign-uniapp/components/slider/tool.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 处理单个number的超限和异常
|
||||
* @param {any} value
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @return {number}
|
||||
*/
|
||||
export const trimSingleValue = (value, min, max) => {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理超限和异常value
|
||||
* @param value
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const trimValue = (value, props) => {
|
||||
const { min, max, range } = props;
|
||||
|
||||
if (range && Array.isArray(value)) {
|
||||
value[0] = trimSingleValue(value[0], min, max);
|
||||
value[1] = trimSingleValue(value[1], min, max);
|
||||
|
||||
return value[0] <= value[1] ? value : [value[1], value[0]];
|
||||
}
|
||||
|
||||
if (range) {
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
if (!range) {
|
||||
return trimSingleValue(value, min, max);
|
||||
}
|
||||
};
|
||||
81
uni_modules/tdesign-uniapp/components/slider/type.ts
Normal file
81
uni_modules/tdesign-uniapp/components/slider/type.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
|
||||
* */
|
||||
|
||||
export interface TdSliderProps {
|
||||
/**
|
||||
* 是否禁用组件
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* 滑块当前值文本。<br />值为 true 显示默认文案;值为 false 不显示滑块当前值文本;<br />值为 `${value}%` 则表示组件会根据占位符渲染文案;<br />值类型为函数时,参数 `value` 标识滑块值,参数 `position=start` 表示范围滑块的起始值,参数 `position=end` 表示范围滑块的终点值
|
||||
* @default false
|
||||
*/
|
||||
label?: string | boolean;
|
||||
/**
|
||||
* 刻度标记,示例:`[0, 10, 40, 200]` 或者 `{ 5: '5¥', 10: '10%' }`
|
||||
* @default {}
|
||||
*/
|
||||
marks?: Record<number, string> | Array<number>;
|
||||
/**
|
||||
* 滑块范围最大值
|
||||
* @default 100
|
||||
*/
|
||||
max?: number;
|
||||
/**
|
||||
* 滑块范围最小值
|
||||
* @default 0
|
||||
*/
|
||||
min?: number;
|
||||
/**
|
||||
* 双游标滑块
|
||||
* @default false
|
||||
*/
|
||||
range?: boolean;
|
||||
/**
|
||||
* 是否边界值
|
||||
* @default false
|
||||
*/
|
||||
showExtremeValue?: boolean;
|
||||
/**
|
||||
* 步长
|
||||
* @default 1
|
||||
*/
|
||||
step?: number;
|
||||
/**
|
||||
* 滑块风格
|
||||
* @default default
|
||||
*/
|
||||
theme?: 'default' | 'capsule';
|
||||
/**
|
||||
* 滑块值
|
||||
* @default 0
|
||||
*/
|
||||
value?: SliderValue;
|
||||
/**
|
||||
* 滑块值,非受控属性
|
||||
* @default 0
|
||||
*/
|
||||
defaultValue?: SliderValue;
|
||||
/**
|
||||
* 是否是垂直的滑块(渲染垂直滑块时,默认高度为400rpx,可通过修改`--td-slider-bar-height`来自定义高度)
|
||||
* @default false
|
||||
*/
|
||||
vertical?: boolean;
|
||||
/**
|
||||
* 滑块值变化时触发
|
||||
*/
|
||||
onChange?: (context: { value: SliderValue }) => void;
|
||||
/**
|
||||
* 结束拖动时触发
|
||||
*/
|
||||
onDragend?: (context: { value: SliderValue; e: TouchEvent }) => void;
|
||||
/**
|
||||
* 开始拖动时触发
|
||||
*/
|
||||
onDragstart?: (context: { e: TouchEvent }) => void;
|
||||
}
|
||||
|
||||
export type SliderValue = number | Array<number>;
|
||||
Reference in New Issue
Block a user