From 42d1dd836c141f499343aa6a8278d1b3be4953ac Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Thu, 10 Feb 2022 15:12:21 +0800 Subject: [PATCH] refactor: TimePicker component --- packages/vant/src/datetime-picker/utils.ts | 12 +- packages/vant/src/time-picker/README.md | 178 +++++++++++++++++ packages/vant/src/time-picker/README.zh-CN.md | 183 ++++++++++++++++++ packages/vant/src/time-picker/TimePicker.tsx | 129 ++++++++++++ packages/vant/src/time-picker/demo/index.vue | 80 ++++++++ packages/vant/src/time-picker/index.ts | 12 ++ packages/vant/vant.config.mjs | 10 +- 7 files changed, 599 insertions(+), 5 deletions(-) create mode 100644 packages/vant/src/time-picker/README.md create mode 100644 packages/vant/src/time-picker/README.zh-CN.md create mode 100644 packages/vant/src/time-picker/TimePicker.tsx create mode 100644 packages/vant/src/time-picker/demo/index.vue create mode 100644 packages/vant/src/time-picker/index.ts diff --git a/packages/vant/src/datetime-picker/utils.ts b/packages/vant/src/datetime-picker/utils.ts index f6691796b..0ca03e9fb 100644 --- a/packages/vant/src/datetime-picker/utils.ts +++ b/packages/vant/src/datetime-picker/utils.ts @@ -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, 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, }, }); diff --git a/packages/vant/src/time-picker/README.md b/packages/vant/src/time-picker/README.md new file mode 100644 index 000000000..2afe1655c --- /dev/null +++ b/packages/vant/src/time-picker/README.md @@ -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 + +``` + +```js +import { ref } from 'vue'; + +export default { + setup() { + const currentTime = ref('12:00'); + return { currentTime }; + }, +}; +``` + +### Time Range + +```html + +``` + +```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 + +``` + +```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 + +``` + +```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
`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 animation,unit `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'; +``` diff --git a/packages/vant/src/time-picker/README.zh-CN.md b/packages/vant/src/time-picker/README.zh-CN.md new file mode 100644 index 000000000..7cac9b8a6 --- /dev/null +++ b/packages/vant/src/time-picker/README.zh-CN.md @@ -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 + +``` + +```js +import { ref } from 'vue'; + +export default { + setup() { + const currentTime = ref('12:00'); + return { currentTime }; + }, +}; +``` + +### 时间范围 + +```html + +``` + +```js +import { ref } from 'vue'; + +export default { + setup() { + const currentTime = ref('12:35'); + return { currentTime }; + }, +}; +``` + +### 格式化选项 + +通过传入 `formatter` 函数,可以对选项的文字进行格式化。 + +```html + +``` + +```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 + +``` + +```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 | 自定义列排序数组, 子项可选值为
`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)。 diff --git a/packages/vant/src/time-picker/TimePicker.tsx b/packages/vant/src/time-picker/TimePicker.tsx new file mode 100644 index 000000000..8ff3bd6cf --- /dev/null +++ b/packages/vant/src/time-picker/TimePicker.tsx @@ -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; + +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(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 () => ( + + ); + }, +}); diff --git a/packages/vant/src/time-picker/demo/index.vue b/packages/vant/src/time-picker/demo/index.vue new file mode 100644 index 000000000..8a2629a05 --- /dev/null +++ b/packages/vant/src/time-picker/demo/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/packages/vant/src/time-picker/index.ts b/packages/vant/src/time-picker/index.ts new file mode 100644 index 000000000..e8ed3267c --- /dev/null +++ b/packages/vant/src/time-picker/index.ts @@ -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; + } +} diff --git a/packages/vant/vant.config.mjs b/packages/vant/vant.config.mjs index ef219e6b5..acff145dd 100644 --- a/packages/vant/vant.config.mjs +++ b/packages/vant/vant.config.mjs @@ -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',