feat: DatetimePicker 支持time和date-time类型下配置选择到秒 (#1117)

 Closes: #844
This commit is contained in:
不如摸鱼去 2025-06-17 13:39:51 +08:00 committed by GitHub
parent 60261374ac
commit f2e8fdad80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 545 additions and 141 deletions

View File

@ -64,10 +64,30 @@ const value = ref<number>(Date.now())
const value4 = ref<string>('11:12')
```
## time 类型(带秒)
`time` 类型设置 `use-second` 属性可以展示时分秒,绑定值为 `HH:mm:ss` 格式。
```html
<wd-datetime-picker-view type="time" v-model="value" label="时分秒" use-second />
```
```typescript
const value = ref<string>('11:12:30')
```
## datetime 类型(带秒)
`datetime` 类型设置 `use-second` 属性可以展示年月日时分秒,绑定值为时间戳。
```html
<wd-datetime-picker-view type="datetime" v-model="value" label="年月日时分秒" use-second />
```
```typescript
const value = ref<number>(Date.now())
```
## 修改内部格式
`formatter` 属性传入一个函数,接收 `type``value` 值,返回展示的文本内容。`type``year``month``date``hour``minute` 类型,`value``number` 类型。
使用自定义`formatter`会关闭内置的默认`display-format`函数。
@ -133,6 +153,8 @@ const filter = (type, values) => {
| minMinute | 最小分钟time类型时生效 | number | - | 0 | - |
| maxMinute | 最大分钟time类型时生效 | number | - | 59 | - |
| immediate-change | 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件1.2.25版本起提供,仅微信小程序和支付宝小程序支持。 | boolean | - | false | 1.2.25 |
| use-second | 是否显示秒选择,仅在 time 和 datetime 类型下生效 | boolean | - | false | $LOWEST_VERSION$ |
## Events
| 事件名称 | 说明 | 参数 | 最低版本 |

View File

@ -78,6 +78,28 @@ const value = ref<number>(Date.now())
const value4 = ref<string>('09:20')
```
## time 类型(带秒)
`time` 类型设置 `use-second` 属性可以展示时分秒,绑定值为 `HH:mm:ss` 格式。
```html
<wd-datetime-picker type="time" v-model="value" label="时分秒" use-second />
```
```typescript
const value = ref<string>('09:20:30')
```
## datetime 类型(带秒)
`datetime` 类型设置 `use-second` 属性可以展示年月日时分秒,绑定值为时间戳。
```html
<wd-datetime-picker type="datetime" v-model="value" label="年月日时分秒" use-second />
```
```typescript
const value = ref<number>(Date.now())
```
## 修改展示格式
@ -295,6 +317,7 @@ const displayFormatTabLabel = (items) => {
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则,结合`wd-form`组件使用 | `FormItemRule []` | - | `[]` | - |
| immediate-change | 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件1.2.25版本起提供,仅微信小程序和支付宝小程序支持。 | boolean | - | false | 1.2.25 |
| use-second | 是否显示秒选择,仅在 time 和 datetime 类型下生效 | boolean | - | false | $LOWEST_VERSION$ |
### FormItemRule 数据结构

View File

@ -65,6 +65,28 @@ const value = ref<number>(Date.now())
const value4 = ref<string>('11:12')
```
## Time Type (with Seconds)
`time` type with `use-second` property displays hour, minute and second, the binding value is in `HH:mm:ss` format.
```html
<wd-datetime-picker-view type="time" v-model="value" label="Hour Minute Second" use-second />
```
```typescript
const value = ref<string>('11:12:30')
```
## Datetime Type (with Seconds)
`datetime` type with `use-second` property displays year, month, day, hour, minute and second, the binding value is timestamp.
```html
<wd-datetime-picker-view type="datetime" v-model="value" label="Year Month Day Hour Minute Second" use-second />
```
```typescript
const value = ref<number>(Date.now())
```
## Modify Internal Format
Pass a function to the `formatter` property, which receives `type` and `value` values and returns the display text content. `type` can be `year`, `month`, `date`, `hour`, `minute`, and `value` is of type `number`.
@ -131,6 +153,7 @@ const filter = (type, values) => {
| minMinute | Minimum minute, effective for time type | number | - | 0 | - |
| maxMinute | Maximum minute, effective for time type | number | - | 59 | - |
| immediate-change | Whether to trigger the picker-view's change event immediately when the finger is released. If not enabled, the change event will be triggered after the scrolling animation ends. Available from version 1.2.25, only supported on WeChat Mini Program and Alipay Mini Program. | boolean | - | false | 1.2.25 |
| use-second | Whether to display the second selection, only effective for time and datetime types | boolean | - | false | $LOWEST_VERSION$ |
## Events

View File

@ -78,6 +78,28 @@ const value = ref<number>(Date.now())
const value4 = ref<string>('09:20')
```
## Time Type (with Seconds)
`time` type with `use-second` property displays hour, minute and second, the binding value is in `HH:mm:ss` format.
```html
<wd-datetime-picker type="time" v-model="value" label="Hour Minute Second" use-second />
```
```typescript
const value = ref<string>('09:20:30')
```
## Datetime Type (with Seconds)
`datetime` type with `use-second` property displays year, month, day, hour, minute and second, the binding value is timestamp.
```html
<wd-datetime-picker type="datetime" v-model="value" label="Year Month Day Hour Minute Second" use-second />
```
```typescript
const value = ref<number>(Date.now())
```
## Modify Display Format
Pass a function to the `display-format` property, which receives an array of all selected items and returns the display text content.
@ -290,6 +312,7 @@ const displayFormatTabLabel = (items) => {
| prop | Form field `model` field name, required when using form validation | string | - | - | - |
| rules | Form validation rules, used with `wd-form` component | `FormItemRule []` | - | `[]` | - |
| immediate-change | Whether to trigger the picker-view's change event immediately when the finger is released. If not enabled, the change event will be triggered after the scrolling animation ends. Available from version 1.2.25, only supported on WeChat Mini Program and Alipay Mini Program. | boolean | - | false | 1.2.25 |
| use-second | Whether to display the second selection, only effective for time and datetime types | boolean | - | false | $LOWEST_VERSION$ |
### FormItemRule Data Structure

View File

@ -969,6 +969,7 @@
"ri-qi-xuan-ze-1": "Date selection",
"ri-qi-xuan-ze-2": "Date selection",
"ri-qi-xuan-ze-3": "Date selection",
"ri-qi-xuan-ze-dai-miao": "Date selection (with seconds)",
"ri-zhou-yue-qie-huan": "Day Week Month Switching",
"ring-lei-xing-loading": "Ring type loading",
"ru-ding-dan-chu-yu-zan-ting-zhuang-tai-jin-ru-wo-de-ding-dan-ye-mian-zhao-dao-yao-qu-xiao-de-ding-dan-dian-ji-qu-xiao-ding-dan-an-niu-xuan-ze-ding-dan-qu-xiao-yuan-yin-hou-dian-ji-xia-yi-bu-ti-jiao-shen-qing-ji-ke": "If the order is in a suspended state, go to the \"My Orders\" page, find the order you want to cancel, and click the \"Cancel Order\" button; After selecting the reason for canceling the order, click \"Next\" to submit the application.",
@ -1035,6 +1036,7 @@
"shi-jian-fan-wei-yi-nian": "Time Range: One Year",
"shi-jian-he-di-zhi": "Time and Address",
"shi-jian-lei-xing": "TIME",
"shi-jian-xuan-ze-dai-miao": "Time selection (with seconds)",
"shi-jiao-xiao-yan": "Out of focus verification",
"shi-jiao-xiao-yan-0": "Out of focus verification",
"shi-jing-shan-qu": "SHIJINGSHAN",

View File

@ -969,6 +969,7 @@
"ri-qi-xuan-ze-1": "日期选择",
"ri-qi-xuan-ze-2": "日期选择",
"ri-qi-xuan-ze-3": "日期选择",
"ri-qi-xuan-ze-dai-miao": "日期选择(带秒)",
"ri-zhou-yue-qie-huan": "日周月切换",
"ring-lei-xing-loading": "ring类型loading",
"ru-ding-dan-chu-yu-zan-ting-zhuang-tai-jin-ru-wo-de-ding-dan-ye-mian-zhao-dao-yao-qu-xiao-de-ding-dan-dian-ji-qu-xiao-ding-dan-an-niu-xuan-ze-ding-dan-qu-xiao-yuan-yin-hou-dian-ji-xia-yi-bu-ti-jiao-shen-qing-ji-ke": "如订单处于暂停状态,进入“我的订单”页面,找到要取消的订单,点击“取消订单”按钮;选择订单取消原因后,点击“下一步”提交申请即可。",
@ -1035,6 +1036,7 @@
"shi-jian-fan-wei-yi-nian": "时间范围一年",
"shi-jian-he-di-zhi": "时间和地址",
"shi-jian-lei-xing": "时间类型",
"shi-jian-xuan-ze-dai-miao": "时间选择(带秒)",
"shi-jiao-xiao-yan": "失焦校验",
"shi-jiao-xiao-yan-0": "失焦校验",
"shi-jing-shan-qu": "石景山区",

View File

@ -4,10 +4,12 @@
<demo-block transparent>
<wd-cell-group border>
<wd-datetime-picker :label="$t('ri-qi-xuan-ze')" v-model="value1" @confirm="handleConfirm1" />
<wd-datetime-picker :label="$t('ri-qi-xuan-ze-dai-miao')" use-second v-model="value18" />
<wd-datetime-picker :label="$t('nian-yue-ri')" v-model="value2" type="date" @confirm="handleConfirm2" />
<wd-datetime-picker :label="$t('nian-yue')" v-model="value3" type="year-month" @confirm="handleConfirm3" />
<wd-datetime-picker :label="$t('nian')" v-model="value16" type="year" @confirm="handleConfirm16" />
<wd-datetime-picker :label="$t('shi-fen')" v-model="value4" type="time" @confirm="handleConfirm4" />
<wd-datetime-picker :label="$t('shi-jian-xuan-ze-dai-miao')" v-model="value19" type="time" use-second />
<wd-datetime-picker :label="$t('zhan-shi-ge-shi')" v-model="value5" :display-format="displayFormat" @confirm="handleConfirm5" />
<wd-datetime-picker :label="$t('nei-bu-ge-shi')" v-model="value6" :formatter="formatter" @confirm="handleConfirm6" />
<wd-datetime-picker :label="$t('guo-lv-xuan-xiang')" v-model="value7" :filter="filter" @confirm="handleConfirm7" />
@ -34,7 +36,13 @@
<wd-datetime-picker :label="$t('ri-qi-xuan-ze-1')" align-right v-model="value13" @confirm="handleConfirm13" />
</demo-block>
<demo-block :title="$t('qu-yu-xuan-ze')" transparent>
<wd-datetime-picker :label="$t('ri-qi-xuan-ze-2')" :title="$t('qing-xuan-ze-qu-jian')" v-model="value14" @confirm="handleConfirm14" />
<wd-datetime-picker
:label="$t('ri-qi-xuan-ze-2')"
:title="$t('qing-xuan-ze-qu-jian')"
v-model="value14"
use-second
@confirm="handleConfirm14"
/>
</demo-block>
<demo-block :title="$t('fan-wei-tab-zhan-shi-ge-shi')" transparent>
<wd-datetime-picker
@ -76,7 +84,8 @@ const value14 = ref<any[]>(['', ''])
const value15 = ref<any[]>(['', Date.now()])
const value16 = ref(Date.now())
const value17 = ref(Date.now())
const value18 = ref(Date.now())
const value19 = ref('09:20:26')
const minDate = ref<number>(Date.now())
const maxDate = ref<number>(new Date(new Date().getFullYear() + 1, new Date().getMonth(), new Date().getDate()).getTime())

View File

@ -5,6 +5,10 @@
<wd-datetime-picker-view v-model="value1" @change="onChange1" />
</demo-block>
<demo-block :title="$t('ri-qi-xuan-ze-dai-miao')" transparent>
<wd-datetime-picker-view v-model="value8" use-second />
</demo-block>
<demo-block :title="$t('nian-yue-ri')" transparent>
<wd-datetime-picker-view type="date" v-model="value2" @change="onChange2" />
</demo-block>
@ -21,6 +25,10 @@
<wd-datetime-picker-view type="time" v-model="value4" @change="onChange4" />
</demo-block>
<demo-block :title="$t('shi-jian-xuan-ze-dai-miao')" transparent>
<wd-datetime-picker-view type="time" v-model="value9" use-second @change="onChange4" />
</demo-block>
<demo-block :title="$t('nei-bu-ge-shi')" transparent>
<wd-datetime-picker-view v-model="value5" :formatter="formatter" @change="onChange5" />
</demo-block>
@ -44,6 +52,9 @@ const value4 = ref<string>('11:12')
const value5 = ref<number>(Date.now())
const value6 = ref<number>(Date.now())
const value7 = ref<string>('')
const value8 = ref<number>(Date.now())
const value9 = ref<string>('11:12:13')
const formatter: DatetimePickerViewFormatter = (type, value) => {
switch (type) {
case 'year':

View File

@ -84,6 +84,7 @@ $-fw-semibold: var(--wot-fw-semibold, 600) !default; // PingFangSC-Semibold
/* 尺寸 */
$-size-side-padding: var(--wot-size-side-padding, 15px) !default; // 屏幕两边留白
$-size-side-padding-small: var(--wot-size-side-padding-small, 6px) !default; // 屏幕两边留白小值
/*-------------------------------- Theme color application size. end --------------------------------*/
@ -438,7 +439,7 @@ $-picker-column-height: var(--wot-picker-column-height, 210px) !default; // 列
$-picker-column-item-height: var(--wot-picker-column-item-height, 35px) !default; // 列高 滚筒外部的高度
$-picker-column-select-bg: var(--wot-picker-column-select-bg, #f5f5f5) !default;
$-picker-loading-button-color: var(--wot-picker-loading-button-color, rgba(0, 0, 0, 0.25)) !default; // loading 背景颜色
$-picker-column-padding: var(--wot-picker-column-padding, 0 $-size-side-padding) !default; // 选项内间距
$-picker-column-padding: var(--wot-picker-column-padding, 0 $-size-side-padding-small) !default; // 选项内间距
$-picker-column-disabled-color: var(--wot-picker-column-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 选择器选项禁用的颜色
$-picker-mask: var(--wot-picker-mask, linear-gradient(180deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.25)))

View File

@ -3,29 +3,6 @@ import { baseProps, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStrin
export type DateTimeType = 'date' | 'year-month' | 'time' | 'datetime' | 'year'
/**
* @description 便 pickerView
* @param value
* @param type picker类型
* @return {Array} pickerValue
*/
export function getPickerValue(value: string | number, type: DateTimeType) {
const values: number[] = []
const date = new Date(value)
if (type === 'time') {
const pair = String(value).split(':')
values.push(parseInt(pair[0]), parseInt(pair[1]))
} else {
values.push(date.getFullYear(), date.getMonth() + 1)
if (type === 'date') {
values.push(date.getDate())
} else if (type === 'datetime') {
values.push(date.getDate(), date.getHours(), date.getMinutes())
}
}
return values
}
export const datetimePickerViewProps = {
...baseProps,
/**
@ -44,7 +21,13 @@ export const datetimePickerViewProps = {
* picker内部滚筒高
*/
columnsHeight: makeNumberProp(217),
/**
* key
*/
valueKey: makeStringProp('value'),
/**
* label
*/
labelKey: makeStringProp('label'),
/**
* date / year-month / time
@ -86,6 +69,18 @@ export const datetimePickerViewProps = {
* time类型时生效
*/
maxMinute: makeNumberProp(59),
/**
* time datetime
*/
useSecond: makeBooleanProp(false),
/**
* time datetime
*/
minSecond: makeNumberProp(0),
/**
* time datetime
*/
maxSecond: makeNumberProp(59),
/**
* picker-view的 change change 1.2.25
*/
@ -93,7 +88,7 @@ export const datetimePickerViewProps = {
startSymbol: makeBooleanProp(false)
}
export type DatetimePickerViewColumnType = 'year' | 'month' | 'date' | 'hour' | 'minute'
export type DatetimePickerViewColumnType = 'year' | 'month' | 'date' | 'hour' | 'minute' | 'second'
export type DatetimePickerViewOption = {
label: string

View File

@ -0,0 +1,27 @@
import { type DateTimeType } from './types'
/**
* @description 便 pickerView
* @param value
* @param type picker类型
* @return {Array} pickerValue
*/
export function getPickerValue(value: string | number, type: DateTimeType) {
const values: number[] = []
const date = new Date(value)
if (type === 'time') {
const pair = String(value).split(':')
values.push(parseInt(pair[0]), parseInt(pair[1]))
if (pair[2]) {
values.push(parseInt(pair[2]))
}
} else {
values.push(date.getFullYear(), date.getMonth() + 1)
if (type === 'date') {
values.push(date.getDate())
} else if (type === 'datetime') {
values.push(date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds())
}
}
return values
}

View File

@ -1,21 +1,19 @@
<template>
<view>
<wd-picker-view
ref="datePickerview"
:custom-class="customClass"
:custom-style="customStyle"
:immediate-change="immediateChange"
v-model="pickerValue"
:columns="columns"
:columns-height="columnsHeight"
:columnChange="columnChange"
:loading="loading"
:loading-color="loadingColor"
@change="onChange"
@pickstart="onPickStart"
@pickend="onPickEnd"
></wd-picker-view>
</view>
<wd-picker-view
ref="datePickerview"
:custom-class="customClass"
:custom-style="customStyle"
:immediate-change="immediateChange"
v-model="pickerValue"
:columns="columns"
:columns-height="columnsHeight"
:columnChange="columnChange"
:loading="loading"
:loading-color="loadingColor"
@change="onChange"
@pickstart="onPickStart"
@pickend="onPickEnd"
></wd-picker-view>
</template>
<script lang="ts">
export default {
@ -30,14 +28,9 @@ export default {
import wdPickerView from '../wd-picker-view/wd-picker-view.vue'
import { getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
import { debounce, isFunction, isDef, padZero, range, isArray, isString } from '../common/util'
import {
getPickerValue,
datetimePickerViewProps,
type DatetimePickerViewColumnType,
type DatetimePickerViewOption,
type DatetimePickerViewExpose
} from './types'
import { datetimePickerViewProps, type DatetimePickerViewColumnType, type DatetimePickerViewOption, type DatetimePickerViewExpose } from './types'
import type { PickerViewInstance } from '../wd-picker-view/types'
import { getPickerValue } from './util'
//
/** @description 判断时间戳是否合法 */
@ -78,20 +71,11 @@ const innerValue = ref<null | string | number>(null)
const columns = ref<DatetimePickerViewOption[][]>([])
// pickerViewvalue
const pickerValue = ref<string | number | boolean | string[] | number[] | boolean[]>([])
// created hook
//
const created = ref<boolean>(false)
const { proxy } = getCurrentInstance() as any
defineExpose<DatetimePickerViewExpose>({
updateColumns,
setColumns,
getSelects,
correctValue,
getPickerValue,
getOriginColumns,
...props
})
/**
* @description updateValue 防抖函数的占位符
*/
@ -171,8 +155,10 @@ watch(
() => props.minHour,
() => props.maxHour,
() => props.minMinute,
() => props.minMinute,
() => props.maxMinute
() => props.maxMinute,
() => props.minSecond,
() => props.maxSecond,
() => props.useSecond
],
() => {
updateValue()
@ -260,7 +246,7 @@ function getOriginColumns() {
*/
function getRanges(): Array<{ type: DatetimePickerViewColumnType; range: number[] }> {
if (props.type === 'time') {
return [
const result: Array<{ type: DatetimePickerViewColumnType; range: number[] }> = [
{
type: 'hour',
range: [props.minHour, props.maxHour]
@ -270,10 +256,17 @@ function getRanges(): Array<{ type: DatetimePickerViewColumnType; range: number[
range: [props.minMinute, props.maxMinute]
}
]
if (props.useSecond) {
result.push({
type: 'second',
range: [props.minSecond, props.maxSecond]
})
}
return result
}
const { maxYear, maxDate, maxMonth, maxHour, maxMinute } = getBoundary('max', innerValue.value as number)
const { minYear, minDate, minMonth, minHour, minMinute } = getBoundary('min', innerValue.value as number)
const { maxYear, maxDate, maxMonth, maxHour, maxMinute, maxSecond } = getBoundary('max', innerValue.value as number)
const { minYear, minDate, minMonth, minHour, minMinute, minSecond } = getBoundary('min', innerValue.value as number)
const result: Array<{ type: DatetimePickerViewColumnType; range: number[] }> = [
{
@ -298,6 +291,13 @@ function getRanges(): Array<{ type: DatetimePickerViewColumnType; range: number[
}
]
if (props.type === 'datetime' && props.useSecond) {
result.push({
type: 'second',
range: [minSecond, maxSecond]
})
}
if (props.type === 'date') result.splice(3, 2)
if (props.type === 'year-month') result.splice(2, 3)
if (props.type === 'year') result.splice(1, 4)
@ -316,15 +316,19 @@ function correctValue(value: string | number | Date): string | number {
value = props.minDate
} else if (!isDateType && !value) {
// Date使
value = `${padZero(props.minHour)}:00`
value = props.useSecond ? `${padZero(props.minHour)}:00:00` : `${padZero(props.minHour)}:00`
}
// typetime
if (!isDateType) {
// Date
let [hour, minute] = (isString(value) ? value : value.toString()).split(':')
let [hour, minute, second = '00'] = (isString(value) ? value : value.toString()).split(':')
hour = padZero(range(Number(hour), props.minHour, props.maxHour))
minute = padZero(range(Number(minute), props.minMinute, props.maxMinute))
if (props.useSecond) {
second = padZero(range(Number(second), props.minSecond, props.maxSecond))
return `${hour}:${minute}:${second}`
}
return `${hour}:${minute}`
}
@ -347,12 +351,14 @@ function getBoundary(type: 'min' | 'max', innerValue: number) {
let date: number = 1
let hour: number = 0
let minute: number = 0
let second: number = 0
if (type === 'max') {
month = 12
date = getMonthEndDay(value.getFullYear(), value.getMonth() + 1)
hour = 23
minute = 59
second = 59
}
if (value.getFullYear() === year) {
@ -363,6 +369,9 @@ function getBoundary(type: 'min' | 'max', innerValue: number) {
hour = boundary.getHours()
if (value.getHours() === hour) {
minute = boundary.getMinutes()
if (value.getMinutes() === minute) {
second = boundary.getSeconds()
}
}
}
}
@ -372,7 +381,8 @@ function getBoundary(type: 'min' | 'max', innerValue: number) {
[`${type}Month`]: month,
[`${type}Date`]: date,
[`${type}Hour`]: hour,
[`${type}Minute`]: minute
[`${type}Minute`]: minute,
[`${type}Second`]: second
}
}
@ -401,13 +411,17 @@ function updateColumnValue(value: string | number) {
* @return {date} innerValue
*/
function updateInnerValue() {
const { type } = props
const { type, useSecond } = props
let innerValue: string | number = ''
const pickerVal = datePickerview.value?.getValues() || []
const values = isArray(pickerVal) ? pickerVal : [pickerVal]
if (type === 'time') {
innerValue = `${padZero(values[0])}:${padZero(values[1])}`
if (useSecond) {
innerValue = `${padZero(values[0])}:${padZero(values[1])}:${padZero(values[2])}`
} else {
innerValue = `${padZero(values[0])}:${padZero(values[1])}`
}
return innerValue
}
@ -425,15 +439,19 @@ function updateInnerValue() {
date = (Number(values[2]) && parseInt(String(values[2]))) > maxDate ? maxDate : values[2] && parseInt(String(values[2]))
}
// 34
// 345
let hour = 0
let minute = 0
let second = 0
if (type === 'datetime') {
hour = Number(values[3]) && parseInt(values[3])
minute = Number(values[4]) && parseInt(values[4])
if (useSecond) {
second = Number(values[5]) && parseInt(values[5])
}
}
const value = new Date(Number(year), Number(month) - 1, Number(date), hour, minute).getTime()
const value = new Date(Number(year), Number(month) - 1, Number(date), hour, minute, second).getTime()
innerValue = correctValue(value)
return innerValue
@ -456,11 +474,15 @@ function columnChange(picker: PickerViewInstance) {
date = date > maxDate ? maxDate : date
let hour: number = 0
let minute: number = 0
let second: number = 0
if (props.type === 'datetime') {
hour = Number(values[3])
minute = Number(values[4])
if (props.useSecond) {
second = Number(values[5])
}
}
const value = new Date(year, month - 1, date, hour, minute).getTime()
const value = new Date(year, month - 1, date, hour, minute, second).getTime()
/** 根据计算选中项的时间戳,重新计算所有的选项列表 */
//
innerValue.value = correctValue(value)
@ -497,8 +519,13 @@ function getSelects() {
if (isArray(pickerVal)) return pickerVal
return [pickerVal]
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
defineExpose<DatetimePickerViewExpose>({
updateColumns,
setColumns,
getSelects,
correctValue,
getPickerValue,
getOriginColumns
})
</script>

View File

@ -53,14 +53,6 @@ export const datetimePickerProps = {
*
*/
labelWidth: makeStringProp('33%'),
/**
* 使
*/
useDefaultSlot: makeBooleanProp(false),
/**
* label 使
*/
useLabelSlot: makeBooleanProp(false),
/**
*
*/
@ -85,7 +77,13 @@ export const datetimePickerProps = {
* picker内部滚筒高
*/
columnsHeight: makeNumberProp(217),
/**
* key
*/
valueKey: makeStringProp('value'),
/**
* label
*/
labelKey: makeStringProp('label'),
/**
* type time type Array
@ -119,6 +117,18 @@ export const datetimePickerProps = {
* time类型时生效
*/
maxMinute: makeNumberProp(59),
/**
* time datetime
*/
useSecond: makeBooleanProp(false),
/**
* time datetime
*/
minSecond: makeNumberProp(0),
/**
* time datetime
*/
maxSecond: makeNumberProp(59),
/**
*
*/

View File

@ -101,6 +101,9 @@
:min-date="minDate"
:max-minute="maxMinute"
:min-minute="minMinute"
:use-second="useSecond"
:min-second="minSecond"
:max-second="maxSecond"
:start-symbol="true"
:immediate-change="immediateChange"
@change="onChangeStart"
@ -128,6 +131,9 @@
:min-date="minDate"
:max-minute="maxMinute"
:min-minute="minMinute"
:use-second="useSecond"
:min-second="minSecond"
:max-second="maxSecond"
:start-symbol="false"
:immediate-change="immediateChange"
@change="onChangeEnd"
@ -158,7 +164,6 @@ import { computed, getCurrentInstance, nextTick, onBeforeMount, onMounted, ref,
import { deepClone, isArray, isDef, isEqual, isFunction, padZero } from '../common/util'
import { useCell } from '../composables/useCell'
import {
getPickerValue,
type DatetimePickerViewInstance,
type DatetimePickerViewColumnFormatter,
type DatetimePickerViewColumnType
@ -168,6 +173,7 @@ import { useParent } from '../composables/useParent'
import { useTranslate } from '../composables/useTranslate'
import { datetimePickerProps, type DatetimePickerExpose } from './types'
import { dayjs } from '../common/dayjs'
import { getPickerValue } from '../wd-datetime-picker-view/util'
const props = defineProps(datetimePickerProps)
const emit = defineEmits(['change', 'open', 'toggle', 'cancel', 'confirm', 'update:modelValue'])
@ -334,11 +340,11 @@ function handleBoundaryValue(
currentArray: number[],
boundary: number[]
): boolean {
const { type } = props
const { type, useSecond } = props
switch (type) {
case 'datetime': {
const [year, month, date, hour, minute] = boundary
const [year, month, date, hour, minute, second] = boundary
if (columnType === 'year') {
return isStart ? value > year : value < year
}
@ -354,6 +360,17 @@ function handleBoundaryValue(
if (columnType === 'minute' && currentArray[0] === year && currentArray[1] === month && currentArray[2] === date && currentArray[3] === hour) {
return isStart ? value > minute : value < minute
}
if (
useSecond &&
columnType === 'second' &&
currentArray[0] === year &&
currentArray[1] === month &&
currentArray[2] === date &&
currentArray[3] === hour &&
currentArray[4] === minute
) {
return isStart ? value > second : value < second
}
break
}
case 'year-month': {
@ -387,13 +404,16 @@ function handleBoundaryValue(
break
}
case 'time': {
const [hour, minute] = boundary
const [hour, minute, second] = boundary
if (columnType === 'hour') {
return isStart ? value > hour : value < hour
}
if (columnType === 'minute' && currentArray[0] === hour) {
return isStart ? value > minute : value < minute
}
if (useSecond && columnType === 'second' && currentArray[0] === hour && currentArray[1] === minute) {
return isStart ? value > second : value < second
}
break
}
}
@ -725,9 +745,9 @@ function defaultDisplayFormat(items: Record<string, any>[], tabLabel: boolean =
if (props.formatter) {
const typeMaps = {
year: ['year'],
datetime: ['year', 'month', 'date', 'hour', 'minute'],
datetime: props.useSecond ? ['year', 'month', 'date', 'hour', 'minute', 'second'] : ['year', 'month', 'date', 'hour', 'minute'],
date: ['year', 'month', 'date'],
time: ['hour', 'minute'],
time: props.useSecond ? ['hour', 'minute', 'second'] : ['hour', 'minute'],
'year-month': ['year', 'month']
}
return items
@ -745,9 +765,11 @@ function defaultDisplayFormat(items: Record<string, any>[], tabLabel: boolean =
case 'year-month':
return `${items[0].label}-${items[1].label}`
case 'time':
return `${items[0].label}:${items[1].label}`
return props.useSecond ? `${items[0].label}:${items[1].label}:${items[2].label}` : `${items[0].label}:${items[1].label}`
case 'datetime':
return `${items[0].label}-${items[1].label}-${items[2].label} ${items[3].label}:${items[4].label}`
return props.useSecond
? `${items[0].label}-${items[1].label}-${items[2].label} ${items[3].label}:${items[4].label}:${items[5].label}`
: `${items[0].label}-${items[1].label}-${items[2].label} ${items[3].label}:${items[4].label}`
}
}

View File

@ -1,7 +1,10 @@
import { mount } from '@vue/test-utils'
import WdDatetimePickerView from '@/uni_modules/wot-design-uni/components/wd-datetime-picker-view/wd-datetime-picker-view.vue'
import { describe, expect, test, vi } from 'vitest'
import { DatetimePickerViewFilter, DatetimePickerViewFormatter } from '@/uni_modules/wot-design-uni/components/wd-datetime-picker-view/types'
import {
type DatetimePickerViewFilter,
type DatetimePickerViewFormatter
} from '@/uni_modules/wot-design-uni/components/wd-datetime-picker-view/types'
import { nextTick } from 'vue'
describe('WdDatetimePickerView 日期时间选择器视图', () => {
@ -314,4 +317,108 @@ describe('WdDatetimePickerView 日期时间选择器视图', () => {
expect(wrapper.props('type')).toBe('year-month')
})
test('useSecond 属性 - 时间类型', async () => {
const wrapper = mount(WdDatetimePickerView, {
props: {
modelValue: '12:30:45',
type: 'time',
useSecond: true
}
})
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('type')).toBe('time')
expect(wrapper.props('modelValue')).toBe('12:30:45')
})
test('useSecond 属性 - 日期时间类型', async () => {
const now = Date.now()
const wrapper = mount(WdDatetimePickerView, {
props: {
modelValue: now,
type: 'datetime',
useSecond: true
}
})
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('type')).toBe('datetime')
expect(wrapper.props('modelValue')).toBe(now)
})
test('useSecond 属性 - 时间范围限制', async () => {
const wrapper = mount(WdDatetimePickerView, {
props: {
modelValue: '12:30:45',
type: 'time',
useSecond: true,
minSecond: 0,
maxSecond: 30
}
})
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('minSecond')).toBe(0)
expect(wrapper.props('maxSecond')).toBe(30)
})
test('useSecond 属性 - 日期时间范围限制', async () => {
const now = Date.now()
const wrapper = mount(WdDatetimePickerView, {
props: {
modelValue: now,
type: 'datetime',
useSecond: true,
minSecond: 0,
maxSecond: 30
}
})
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('minSecond')).toBe(0)
expect(wrapper.props('maxSecond')).toBe(30)
})
test('useSecond 属性 - 时间格式化', async () => {
const formatter: DatetimePickerViewFormatter = (type, value) => {
if (type === 'second') {
return value + '秒'
}
return value
}
const wrapper = mount(WdDatetimePickerView, {
props: {
modelValue: '12:30:45',
type: 'time',
useSecond: true,
formatter
}
})
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('formatter')).toBe(formatter)
})
test('useSecond 属性 - 时间过滤', async () => {
const filter: DatetimePickerViewFilter = (type, values) => {
if (type === 'second') {
return values.filter((value) => value % 5 === 0)
}
return values
}
const wrapper = mount(WdDatetimePickerView, {
props: {
modelValue: '12:30:45',
type: 'time',
useSecond: true,
filter
}
})
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('filter')).toBe(filter)
})
})

View File

@ -668,4 +668,148 @@ describe('WdDatetimePicker 日期时间选择器', () => {
expect(wrapper.props('prop')).toBe('date')
expect(wrapper.props('rules')).toEqual(rules)
})
test('useSecond 属性 - 时间类型', async () => {
const wrapper = mount(WdDatetimePicker, {
props: {
modelValue: '12:30:45',
type: 'time',
useSecond: true
},
global: {
components: globalComponents
}
})
await wrapper.vm.$nextTick()
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('type')).toBe('time')
expect(wrapper.props('modelValue')).toBe('12:30:45')
expect(wrapper.find('.wd-picker__value').text()).toBe('12:30:45')
})
test('useSecond 属性 - 日期时间类型', async () => {
const now = Date.now()
const wrapper = mount(WdDatetimePicker, {
props: {
modelValue: now,
type: 'datetime',
useSecond: true
},
global: {
components: globalComponents
}
})
await wrapper.vm.$nextTick()
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('type')).toBe('datetime')
expect(wrapper.props('modelValue')).toBe(now)
})
test('useSecond 属性 - 时间范围限制', async () => {
const wrapper = mount(WdDatetimePicker, {
props: {
modelValue: '12:30:45',
type: 'time',
useSecond: true,
minSecond: 0,
maxSecond: 30
},
global: {
components: globalComponents
}
})
await wrapper.vm.$nextTick()
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('minSecond')).toBe(0)
expect(wrapper.props('maxSecond')).toBe(30)
})
test('useSecond 属性 - 日期时间范围限制', async () => {
const now = Date.now()
const wrapper = mount(WdDatetimePicker, {
props: {
modelValue: now,
type: 'datetime',
useSecond: true,
minSecond: 0,
maxSecond: 30
},
global: {
components: globalComponents
}
})
await wrapper.vm.$nextTick()
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('minSecond')).toBe(0)
expect(wrapper.props('maxSecond')).toBe(30)
})
test('useSecond 属性 - 自定义显示格式', async () => {
const displayFormat = vi.fn((items) => {
return `${items[0].label}${items[1].label}${items[2].label}`
})
const wrapper = mount(WdDatetimePicker, {
props: {
modelValue: '12:30:45',
type: 'time',
useSecond: true,
displayFormat
},
global: {
components: globalComponents
}
})
await wrapper.vm.$nextTick()
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('displayFormat')).toBe(displayFormat)
expect(displayFormat).toHaveBeenCalled()
})
test('useSecond 属性 - 范围选择', async () => {
const startDate = new Date(2024, 0, 1, 12, 30, 45).getTime()
const endDate = new Date(2024, 0, 1, 13, 30, 45).getTime()
const wrapper = mount(WdDatetimePicker, {
props: {
modelValue: [startDate, endDate],
type: 'datetime',
useSecond: true
},
global: {
components: globalComponents
}
})
await wrapper.vm.$nextTick()
expect(wrapper.props('useSecond')).toBe(true)
expect(Array.isArray(wrapper.props('modelValue'))).toBe(true)
expect(wrapper.props('modelValue')).toEqual([startDate, endDate])
})
test('useSecond 属性 - 表单验证', async () => {
const rules = [{ required: true, message: '请选择时间' }]
const wrapper = mount(WdDatetimePicker, {
props: {
modelValue: '',
type: 'time',
useSecond: true,
prop: 'time',
rules
},
global: {
components: globalComponents
}
})
await wrapper.vm.$nextTick()
expect(wrapper.props('useSecond')).toBe(true)
expect(wrapper.props('prop')).toBe('time')
expect(wrapper.props('rules')).toEqual(rules)
})
})

View File

@ -355,50 +355,6 @@ describe('useUpload', () => {
})
})
// 测试选择媒体文件
it('should choose media files', async () => {
const mockChooseMedia = vi.fn().mockImplementation((options) => {
options.success({
tempFiles: [
{
fileType: 'image',
tempFilePath: 'temp/image.jpg',
size: 1024,
duration: 0
},
{
fileType: 'video',
tempFilePath: 'temp/video.mp4',
thumbTempFilePath: 'temp/thumb.jpg',
size: 10240,
duration: 15
}
]
})
})
;(global as any).uni.chooseMedia = mockChooseMedia
const files = await chooseFile({
accept: 'media',
multiple: true,
maxCount: 9,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
compressed: true,
maxDuration: 60,
camera: 'back'
})
expect(files).toHaveLength(2)
expect(files[1]).toEqual({
type: 'video',
path: 'temp/video.mp4',
thumb: 'temp/thumb.jpg',
size: 10240,
duration: 15
})
})
// 测试选择文件失败的情况
it('should handle choose file failure', async () => {
const mockChooseImage = vi.fn().mockImplementation((options) => {