refactor: TimePicker component

This commit is contained in:
chenjiahan 2022-02-10 15:12:21 +08:00
parent 15e7461a93
commit 42d1dd836c
7 changed files with 599 additions and 5 deletions

View File

@ -1,15 +1,19 @@
import { extend } from '../utils';
import { pickerSharedProps } from '../picker/Picker';
import type { PropType } from 'vue';
import type { PickerInstance } from '../picker';
import type { PickerInstance, PickerOption } from '../picker';
import type { DatetimePickerColumnType } from './types';
export const sharedProps = extend({}, pickerSharedProps, {
filter: Function as PropType<(type: string, values: string[]) => string[]>,
filter: Function as PropType<
(columnType: string, options: PickerOption[]) => PickerOption[]
>,
columnsOrder: Array as PropType<DatetimePickerColumnType[]>,
formatter: {
type: Function as PropType<(type: string, value: string) => string>,
default: (type: string, value: string) => value,
type: Function as PropType<
(type: string, option: PickerOption) => PickerOption
>,
default: (type: string, option: PickerOption) => option,
},
});

View File

@ -0,0 +1,178 @@
# TimePicker
### Intro
Used to select time, usually used with the [Popup](#/en-US/popup) component.
### Install
Register component globally via `app.use`, refer to [Component Registration](#/en-US/advanced-usage#zu-jian-zhu-ce) for more registration ways.
```js
import { createApp } from 'vue';
import { TimePicker } from 'vant';
const app = createApp();
app.use(TimePicker);
```
## Usage
### Basic Usage
```html
<van-time-picker v-model="currentTime" title="Choose Time" />
```
```js
import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
return { currentTime };
},
};
```
### Time Range
```html
<van-time-picker
v-model="currentTime"
title="Choose Time"
:min-hour="10"
:max-hour="20"
:min-minute="30"
:max-minute="40"
/>
```
```js
import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:35');
return { currentTime };
},
};
```
### Options Formatter
Using `formatter` prop to format option text.
```html
<van-time-picker
v-model="currentTime"
title="Choose Time"
:formatter="formatter"
/>
```
```js
import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const formatter = (type, option) => {
if (type === 'hour') {
option.text += 'h';
}
if (type === 'minute') {
option.text += 'm';
}
return option;
};
return {
filter,
currentTime,
};
},
};
```
### Options Filter
Using `filter` prop to filter options.
```html
<van-time-picker v-model="currentTime" title="Choose Time" :filter="filter" />
```
```js
import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const filter = (type, options) => {
if (type === 'minute') {
return options.filter((option) => Number(option) % 10 === 0);
}
return options;
};
return {
filter,
currentTime,
};
},
};
```
## API
### Props
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| v-model | Current time | _string_ | - |
| title | Toolbar title | _string_ | `''` |
| confirm-button-text | Text of confirm button | _string_ | `Confirm` |
| cancel-button-text | Text of cancel button | _string_ | `Cancel` |
| show-toolbar | Whether to show toolbar | _boolean_ | `true` |
| loading | Whether to show loading prompt | _boolean_ | `false` |
| readonly | Whether to be readonly | _boolean_ | `false` |
| filter | Option filter | _(type: string, options: PickerOption[]) => PickerOption[]_ | - |
| formatter | Option text formatter | _(type: string, option: PickerOption) => PickerOption_ | - |
| columns-order | Array for ordering columns, where item can be set to<br> `hour``minute` | _string[]_ | - |
| option-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
| visible-option-num | Count of visible columns | _number \| string_ | `6` |
| swipe-duration | Duration of the momentum animationunit `ms` | _number \| string_ | `1000` |
| min-hour | Min hour for `time` type | _number \| string_ | `0` |
| max-hour | Max hour for `time` type | _number \| string_ | `23` |
| min-minute | Max minute for `time` type | _number \| string_ | `0` |
| max-minute | Max minute for `time` type | _number \| string_ | `59` |
### Events
| Event | Description | Arguments |
| --- | --- | --- |
| confirm | Emitted when the confirm button is clicked | _{ selectedValues, selectedOptions }_ |
| cancel | Emitted when the cancel button is clicked | _{ selectedValues, selectedOptions }_ |
| change | Emitted when current option is changed | _{ selectedValues, selectedOptions, columnIndex }_ |
### Slots
| Name | Description | SlotProps |
| -------------- | ---------------------------- | ---------------------- |
| default | Custom toolbar content | - |
| title | Custom title | - |
| confirm | Custom confirm button text | - |
| cancel | Custom cancel button text | - |
| option | Custom option content | _option: PickerOption_ |
| columns-top | Custom content above columns | - |
| columns-bottom | Custom content below columns | - |
### Types
The component exports the following type definitions:
```ts
import type { TimePickerProps } from 'vant';
```

View File

@ -0,0 +1,183 @@
# TimePicker 时间选择
### 介绍
时间选择器,通常与[弹出层](#/zh-CN/popup)组件配合使用。
### 引入
通过以下方式来全局注册组件,更多注册方式请参考[组件注册](#/zh-CN/advanced-usage#zu-jian-zhu-ce)。
```js
import { createApp } from 'vue';
import { TimePicker } from 'vant';
const app = createApp();
app.use(TimePicker);
```
## 代码演示
### 基础用法
```html
<van-time-picker v-model="currentTime" title="选择时间" />
```
```js
import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
return { currentTime };
},
};
```
### 时间范围
```html
<van-time-picker
v-model="currentTime"
title="选择时间"
:min-hour="10"
:max-hour="20"
:min-minute="30"
:max-minute="40"
/>
```
```js
import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:35');
return { currentTime };
},
};
```
### 格式化选项
通过传入 `formatter` 函数,可以对选项的文字进行格式化。
```html
<van-time-picker
v-model="currentTime"
title="选择时间"
:formatter="formatter"
/>
```
```js
import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const formatter = (type, option) => {
if (type === 'hour') {
option.text += '时';
}
if (type === 'minute') {
option.text += '分';
}
return option;
};
return {
filter,
currentTime,
};
},
};
```
### 过滤选项
通过传入 `filter` 函数,可以对选项数组进行过滤,剔除不需要的时间,实现自定义时间间隔。
```html
<van-time-picker v-model="currentTime" title="选择时间" :filter="filter" />
```
```js
import { ref } from 'vue';
export default {
setup() {
const currentTime = ref('12:00');
const filter = (type, options) => {
if (type === 'minute') {
return options.filter((option) => Number(option) % 10 === 0);
}
return options;
};
return {
filter,
currentTime,
};
},
};
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| v-model | 当前选中的时间 | _string_ | - |
| title | 顶部栏标题 | _string_ | `''` |
| confirm-button-text | 确认按钮文字 | _string_ | `确认` |
| cancel-button-text | 取消按钮文字 | _string_ | `取消` |
| show-toolbar | 是否显示顶部栏 | _boolean_ | `true` |
| loading | 是否显示加载状态 | _boolean_ | `false` |
| readonly | 是否为只读状态,只读状态下无法切换选项 | _boolean_ | `false` |
| filter | 选项过滤函数 | _(type: string, options: PickerOption[]) => PickerOption[]_ | - |
| formatter | 选项格式化函数 | _(type: string, option: PickerOption) => PickerOption_ | - |
| columns-order | 自定义列排序数组, 子项可选值为<br> `hour``minute` | _string[]_ | - |
| option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
| visible-option-num | 可见的选项个数 | _number \| string_ | `6` |
| swipe-duration | 快速滑动时惯性滚动的时长,单位 `ms` | _number \| string_ | `1000` |
| min-hour | 可选的最小小时 | _number \| string_ | `0` |
| max-hour | 可选的最大小时 | _number \| string_ | `23` |
| min-minute | 可选的最小分钟 | _number \| string_ | `0` |
| max-minute | 可选的最大分钟 | _number \| string_ | `59` |
### Events
| 事件名 | 说明 | 回调参数 |
| --- | --- | --- |
| confirm | 点击完成按钮时触发 | _{ selectedValues, selectedOptions }_ |
| cancel | 点击取消按钮时触发 | _{ selectedValues, selectedOptions }_ |
| change | 选项改变时触发 | _{ selectedValues, selectedOptions, columnIndex }_ |
### Slots
| 名称 | 说明 | 参数 |
| -------------- | ---------------------- | ---------------------- |
| default | 自定义整个顶部栏的内容 | - |
| title | 自定义标题内容 | - |
| confirm | 自定义确认按钮内容 | - |
| cancel | 自定义取消按钮内容 | - |
| option | 自定义选项内容 | _option: PickerOption_ |
| columns-top | 自定义选项上方内容 | - |
| columns-bottom | 自定义选项下方内容 | - |
### 类型定义
组件导出以下类型定义:
```ts
import type { TimePickerProps } from 'vant';
```
## 常见问题
### 在桌面端无法操作组件?
参见[桌面端适配](#/zh-CN/advanced-usage#zhuo-mian-duan-gua-pei)。

View File

@ -0,0 +1,129 @@
import {
ref,
watch,
computed,
defineComponent,
type ExtractPropTypes,
} from 'vue';
// Utils
import {
pick,
clamp,
extend,
padZero,
createNamespace,
makeNumericProp,
} from '../utils';
import {
times,
sharedProps,
pickerInheritKeys,
} from '../datetime-picker/utils';
// Components
import { Picker, PickerOption } from '../picker';
const [name] = createNamespace('time-picker');
const timePickerProps = extend({}, sharedProps, {
minHour: makeNumericProp(0),
maxHour: makeNumericProp(23),
minMinute: makeNumericProp(0),
maxMinute: makeNumericProp(59),
modelValue: String,
});
export type TimePickerProps = ExtractPropTypes<typeof timePickerProps>;
export default defineComponent({
name,
props: timePickerProps,
emits: ['confirm', 'cancel', 'change', 'update:modelValue'],
setup(props, { emit, slots }) {
const formatValues = (value?: string) => {
const { minHour, maxHour, maxMinute, minMinute } = props;
if (value) {
const [hour, minute] = value.split(':');
return [
padZero(clamp(+hour, +minHour, +maxHour)),
padZero(clamp(+minute, +minMinute, +maxMinute)),
];
}
return [padZero(minHour), padZero(minMinute)];
};
const currentValues = ref<string[]>(formatValues(props.modelValue));
const ranges = computed(() => [
{
type: 'hour',
range: [+props.minHour, +props.maxHour],
},
{
type: 'minute',
range: [+props.minMinute, +props.maxMinute],
},
]);
const columns = computed(() =>
ranges.value.map(({ type, range }) => {
const options = times(
range[1] - range[0] + 1,
(index): PickerOption => {
const value = padZero(range[0] + index);
return props.formatter(type, {
text: value,
value,
});
}
);
if (props.filter) {
return props.filter(type, options);
}
return options;
})
);
watch(
currentValues,
(values) => {
const newValue = values.join(':');
if (newValue !== props.modelValue) {
emit('update:modelValue', newValue);
}
},
{ deep: true }
);
watch(
() => props.modelValue,
(newValue) => {
currentValues.value = formatValues(newValue);
}
);
const onChange = (...args: unknown[]) => emit('change', ...args);
const onCancel = (...args: unknown[]) => emit('cancel', ...args);
const onConfirm = (...args: unknown[]) => emit('confirm', ...args);
return () => (
<Picker
v-model={currentValues.value}
v-slots={slots}
columns={columns.value}
onChange={onChange}
onCancel={onCancel}
onConfirm={onConfirm}
{...pick(props, pickerInheritKeys)}
/>
);
},
});

View File

@ -0,0 +1,80 @@
<script setup lang="ts">
import VanTimePicker from '..';
import { ref } from 'vue';
import { useTranslate } from '../../../docs/site/use-translate';
import type { PickerOption } from '../../picker';
const t = useTranslate({
'zh-CN': {
hour: '时',
minute: '分',
timeRange: '时间范围',
chooseTime: '选择时间',
optionsFilter: '过滤选项',
optionsFormatter: '格式化选项',
},
'en-US': {
hour: 'h',
minute: 'm',
timeRange: 'Time Range',
chooseTime: 'Choose Time',
optionsFilter: 'Options Filter',
optionsFormatter: 'Options Formatter',
},
});
const baseTime = ref('12:00');
const rangeTime = ref('12:35');
const filterTime = ref('12:00');
const formatterTime = ref('12:00');
const filter = (type: string, options: PickerOption[]) => {
if (type === 'minute') {
return options.filter((option) => Number(option.value) % 10 === 0);
}
return options;
};
const formatter = (type: string, option: PickerOption) => {
if (type === 'hour') {
option.text += t('hour');
}
if (type === 'minute') {
option.text += t('minute');
}
return option;
};
</script>
<template>
<demo-block card :title="t('basicUsage')">
<van-time-picker v-model="baseTime" :title="t('chooseTime')" />
</demo-block>
<demo-block card :title="t('timeRange')">
<van-time-picker
v-model="rangeTime"
:title="t('chooseTime')"
:min-hour="10"
:max-hour="20"
:min-minute="30"
:max-minute="40"
/>
</demo-block>
<demo-block card :title="t('optionsFormatter')">
<van-time-picker
v-model="formatterTime"
:title="t('chooseTime')"
:formatter="formatter"
/>
</demo-block>
<demo-block card :title="t('optionsFilter')">
<van-time-picker
v-model="filterTime"
:title="t('chooseTime')"
:filter="filter"
/>
</demo-block>
</template>

View File

@ -0,0 +1,12 @@
import { withInstall } from '../utils';
import _TimePicker, { TimePickerProps } from './TimePicker';
export const TimePicker = withInstall(_TimePicker);
export default TimePicker;
export type { TimePickerProps };
declare module 'vue' {
export interface GlobalComponents {
VanTimePicker: typeof TimePicker;
}
}

View File

@ -170,7 +170,7 @@ export default {
},
{
path: 'datetime-picker',
title: 'DatetimePicker 时间选择',
title: 'DatetimePicker 日期选择',
},
{
path: 'field',
@ -216,6 +216,10 @@ export default {
path: 'switch',
title: 'Switch 开关',
},
{
path: 'time-picker',
title: 'timePicker 时间选择',
},
{
path: 'uploader',
title: 'Uploader 文件上传',
@ -622,6 +626,10 @@ export default {
path: 'switch',
title: 'Switch',
},
{
path: 'time-picker',
title: 'timePicker',
},
{
path: 'uploader',
title: 'Uploader',