From 53c4256f10080781992fac09058ba271d5390c90 Mon Sep 17 00:00:00 2001 From: KurumiRin <1548627528@qq.com> Date: Tue, 16 Sep 2025 14:01:23 +0400 Subject: [PATCH 1/3] feat: allow range picker switch with doubleClick --- components/vc-picker/PanelContext.tsx | 3 +++ components/vc-picker/RangePicker.tsx | 25 ++++++++++++++++++++++- components/vc-picker/panels/PanelBody.tsx | 10 ++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/components/vc-picker/PanelContext.tsx b/components/vc-picker/PanelContext.tsx index 5ea2f23b6d..c312724c65 100644 --- a/components/vc-picker/PanelContext.tsx +++ b/components/vc-picker/PanelContext.tsx @@ -23,6 +23,9 @@ export type PanelContextProps = { /** Only used for TimePicker and this is a deprecated prop */ defaultOpenValue?: Ref; + + /** Double click state for RangePicker */ + isDoubleClickRef?: Ref; }; const PanelContextKey: InjectionKey = Symbol('PanelContextProps'); diff --git a/components/vc-picker/RangePicker.tsx b/components/vc-picker/RangePicker.tsx index 69cb823dd9..84a65d4f27 100644 --- a/components/vc-picker/RangePicker.tsx +++ b/components/vc-picker/RangePicker.tsx @@ -263,6 +263,8 @@ function RangerPicker() { const needConfirmButton = computed( () => (props.picker === 'date' && !!props.showTime) || props.picker === 'time', ); + + const isDoubleClickRef = ref(false); const presets = computed(() => props.presets); const ranges = computed(() => props.ranges); const presetList = usePresets(presets, ranges); @@ -970,10 +972,30 @@ function RangerPicker() { const onContextSelect = (date: DateType, type: 'key' | 'mouse' | 'submit') => { const values = updateValues(selectedValue.value, date, mergedActivePickerIndex.value); + const currentIndex = mergedActivePickerIndex.value; + const isDoubleClick = isDoubleClickRef.value; + const shouldSwitch = type === 'mouse' && needConfirmButton.value && isDoubleClick; + + // Reset double click state + isDoubleClickRef.value = false; - if (type === 'submit' || (type !== 'key' && !needConfirmButton.value)) { + if (type === 'submit' || (type !== 'key' && !needConfirmButton.value) || shouldSwitch) { // triggerChange will also update selected values triggerChange(values, mergedActivePickerIndex.value); + + // If double click, switch to next input + // But check if both inputs are complete, if so don't switch to avoid animation before popup closes + if (shouldSwitch) { + const startValue = getValue(values, 0); + const endValue = getValue(values, 1); + const bothValuesComplete = startValue && endValue; + + if (!bothValuesComplete) { + const nextIndex = ((currentIndex + 1) % 2) as 0 | 1; + setMergedActivePickerIndex(nextIndex); + } + } + // clear hover value style if (mergedActivePickerIndex.value === 0) { onStartLeave(); @@ -993,6 +1015,7 @@ function RangerPicker() { hideRanges: computed(() => true), onSelect: onContextSelect, open: mergedOpen, + isDoubleClickRef, }); return () => { diff --git a/components/vc-picker/panels/PanelBody.tsx b/components/vc-picker/panels/PanelBody.tsx index b05be4a720..5dc3d5d6ad 100644 --- a/components/vc-picker/panels/PanelBody.tsx +++ b/components/vc-picker/panels/PanelBody.tsx @@ -49,7 +49,7 @@ function PanelBody(_props: PanelBodyProps) { titleCell, headerCells, } = useMergeProps(_props); - const { onDateMouseenter, onDateMouseleave, mode } = useInjectPanel(); + const { onDateMouseenter, onDateMouseleave, mode, isDoubleClickRef } = useInjectPanel(); const cellPrefixCls = `${prefixCls}-cell`; @@ -99,6 +99,14 @@ function PanelBody(_props: PanelBodyProps) { onSelect(currentDate); } }} + onDblclick={e => { + e.stopPropagation(); + if (!disabled && isDoubleClickRef) { + // 设置双击状态 + isDoubleClickRef.value = true; + onSelect(currentDate); + } + }} onMouseenter={() => { if (!disabled && onDateMouseenter) { onDateMouseenter(currentDate); From 5843bf5fd9a0555addc4462986720abe1998b229 Mon Sep 17 00:00:00 2001 From: yuanyi Date: Wed, 17 Sep 2025 16:12:17 +0400 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=8F=8C?= =?UTF-8?q?=E5=87=BB=E8=87=AA=E5=8A=A8=E5=A1=AB=E5=85=85=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=EF=BC=8C=E4=BB=A5=E5=8F=8A=E8=87=AA=E5=8A=A8=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E5=85=A8=E5=A4=A9=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../date-picker/demo/auto-fill-whole-day.vue | 123 +++++++++++++++ components/date-picker/demo/index.vue | 3 + .../date-picker/demo/presetted-ranges.vue | 26 ++-- .../generatePicker/generateRangePicker.tsx | 19 ++- .../date-picker/generatePicker/props.ts | 5 + components/date-picker/style/index.ts | 7 + components/vc-picker/PresetPanel.tsx | 20 ++- components/vc-picker/RangePicker.tsx | 143 ++++++++++++++++-- components/vc-picker/hooks/usePresets.ts | 1 + components/vc-picker/interface.ts | 7 + 10 files changed, 318 insertions(+), 36 deletions(-) create mode 100644 components/date-picker/demo/auto-fill-whole-day.vue diff --git a/components/date-picker/demo/auto-fill-whole-day.vue b/components/date-picker/demo/auto-fill-whole-day.vue new file mode 100644 index 0000000000..4c380a8690 --- /dev/null +++ b/components/date-picker/demo/auto-fill-whole-day.vue @@ -0,0 +1,123 @@ + +--- +order: 7 +title: + zh-CN: 自动填充和整天模式 + en-US: Auto Fill and Whole Day Mode +--- + +## zh-CN + +RangePicker 支持两个新功能: +1. `autoFill`:双击日期时自动设置为开始和结束日期 +2. `isWholeDay`:在 showTime 模式下,自动设置开始时间为 00:00:00,结束时间为 23:59:59 + +## en-US + +RangePicker supports two new features: +1. `autoFill`: Double-click a date to automatically set it as both start and end date +2. `isWholeDay`: In showTime mode, automatically set start time to 00:00:00 and end time to 23:59:59 + + + + + + diff --git a/components/date-picker/demo/index.vue b/components/date-picker/demo/index.vue index 92808d6baa..a7632d5eee 100644 --- a/components/date-picker/demo/index.vue +++ b/components/date-picker/demo/index.vue @@ -17,6 +17,7 @@ + diff --git a/components/date-picker/demo/presetted-ranges.vue b/components/date-picker/demo/presetted-ranges.vue index 1e27a39c38..8fd512fe3a 100644 --- a/components/date-picker/demo/presetted-ranges.vue +++ b/components/date-picker/demo/presetted-ranges.vue @@ -40,25 +40,31 @@ const onChange = (date: Dayjs) => { console.log('Clear'); } }; -const onRangeChange = (dates: RangeValue, dateStrings: string[]) => { - if (dates) { - console.log('From: ', dates[0], ', to: ', dates[1]); +const onRangeChange = (values: RangeValue, dateStrings: [string, string], currentPreset?: any) => { + if (values) { + console.log('From: ', values[0], ', to: ', values[1]); console.log('From: ', dateStrings[0], ', to: ', dateStrings[1]); + if (currentPreset) { + console.log('Selected preset key: ', currentPreset.key); + console.log('Selected preset label: ', currentPreset.label); + } else { + console.log('Manual selection (no preset)'); + } } else { console.log('Clear'); } }; const presets = ref([ - { label: 'Yesterday', value: dayjs().add(-1, 'd') }, - { label: 'Last Week', value: dayjs().add(-7, 'd') }, - { label: 'Last Month', value: dayjs().add(-1, 'month') }, + { label: 'Yesterday', value: dayjs().add(-1, 'd'), key: 'yesterday' }, + { label: 'Last Week', value: dayjs().add(-7, 'd'), key: 'lastweek' }, + { label: 'Last Month', value: dayjs().add(-1, 'month'), key: 'lastmonth' }, ]); const rangePresets = ref([ - { label: 'Last 7 Days', value: [dayjs().add(-7, 'd'), dayjs()] }, - { label: 'Last 14 Days', value: [dayjs().add(-14, 'd'), dayjs()] }, - { label: 'Last 30 Days', value: [dayjs().add(-30, 'd'), dayjs()] }, - { label: 'Last 90 Days', value: [dayjs().add(-90, 'd'), dayjs()] }, + { label: 'Last 7 Days', value: [dayjs().add(-7, 'd'), dayjs()], key: 'last7days' }, + { label: 'Last 14 Days', value: [dayjs().add(-14, 'd'), dayjs()], key: 'last14days' }, + { label: 'Last 30 Days', value: [dayjs().add(-30, 'd'), dayjs()], key: 'last30days' }, + { label: 'Last 90 Days', value: [dayjs().add(-90, 'd'), dayjs()], key: 'last90days' }, ]); diff --git a/components/date-picker/generatePicker/generateRangePicker.tsx b/components/date-picker/generatePicker/generateRangePicker.tsx index a321706c34..b25d2cbd6c 100644 --- a/components/date-picker/generatePicker/generateRangePicker.tsx +++ b/components/date-picker/generatePicker/generateRangePicker.tsx @@ -13,7 +13,7 @@ import useConfigInject from '../../config-provider/hooks/useConfigInject'; import classNames from '../../_util/classNames'; import type { CommonProps, RangePickerProps } from './props'; import { commonProps, rangePickerProps } from './props'; -import type { PanelMode, RangeValue } from '../../vc-picker/interface'; +import type { PanelMode, RangeValue, RangePickerOnChange } from '../../vc-picker/interface'; import type { RangePickerSharedProps } from '../../vc-picker/RangePicker'; import { FormItemInputContext, useInjectFormItemContext } from '../../form/FormItemContext'; import omit from '../../_util/omit'; @@ -84,13 +84,18 @@ export default function generateRangePicker( pickerRef.value?.blur(); }, }); - const maybeToStrings = (dates: DateType[]) => { + const maybeToStrings = (dates: RangeValue) => { return props.valueFormat ? generateConfig.toString(dates, props.valueFormat) : dates; }; - const onChange = (dates: RangeValue, dateStrings: [string, string]) => { - const values = maybeToStrings(dates); - emit('update:value', values); - emit('change', values, dateStrings); + const onChange: RangePickerOnChange = (values, formatStrings) => { + const [startValue, endValue, currentPreset] = values; + const [startStr, endStr] = formatStrings; + const dates: RangeValue = [startValue, endValue]; + const dateStrings: [string, string] = [startStr, endStr]; + + const processedValues = maybeToStrings(dates); + emit('update:value', processedValues); + emit('change', processedValues, dateStrings, currentPreset); formItemContext.onFieldChange(); }; const onOpenChange = (open: boolean) => { @@ -109,7 +114,7 @@ export default function generateRangePicker( emit('panelChange', values, modes); }; const onOk = (dates: DateType[]) => { - const value = maybeToStrings(dates); + const value = props.valueFormat ? generateConfig.toString(dates, props.valueFormat) : dates; emit('ok', value); }; const onCalendarChange: RangePickerSharedProps['onCalendarChange'] = ( diff --git a/components/date-picker/generatePicker/props.ts b/components/date-picker/generatePicker/props.ts index 1af653de55..0086abf196 100644 --- a/components/date-picker/generatePicker/props.ts +++ b/components/date-picker/generatePicker/props.ts @@ -269,8 +269,13 @@ export interface RangePickerProps { onChange?: ( value: RangeValue | RangeValue | null, dateString: [string, string], + currentPreset?: any, ) => void; 'onUpdate:value'?: (value: RangeValue | RangeValue | null) => void; + /** 双击日期时自动设置为开始和结束日期 */ + autoFill?: boolean; + /** 在 showTime 模式下,是否设置为整天(开始时间 00:00:00,结束时间 23:59:59) */ + isWholeDay?: boolean; onCalendarChange?: ( values: RangeValue | RangeValue, formatString: [string, string], diff --git a/components/date-picker/style/index.ts b/components/date-picker/style/index.ts index 4e92852fd3..e6ec1d394b 100644 --- a/components/date-picker/style/index.ts +++ b/components/date-picker/style/index.ts @@ -961,6 +961,7 @@ const genPickerStyle: GenerateStyle = token => { controlItemBgHover, presetsWidth, presetsMaxWidth, + fontWeightStrong, } = token; return [ @@ -1326,6 +1327,12 @@ const genPickerStyle: GenerateStyle = token => { '&:hover': { background: controlItemBgHover, }, + + [`&${componentCls}-preset-active`]: { + background: controlItemBgActive, + color: colorPrimary, + fontWeight: fontWeightStrong, + }, }, }, }, diff --git a/components/vc-picker/PresetPanel.tsx b/components/vc-picker/PresetPanel.tsx index a5d98d02a6..55a1e0e233 100644 --- a/components/vc-picker/PresetPanel.tsx +++ b/components/vc-picker/PresetPanel.tsx @@ -1,13 +1,18 @@ import { defineComponent } from 'vue'; +import type { PresetDate } from './interface'; export default defineComponent({ name: 'PresetPanel', props: { prefixCls: String, presets: { - type: Array, + type: Array as () => PresetDate[], default: () => [], }, + currentPreset: { + type: Object as () => PresetDate | null, + default: null, + }, onClick: Function, onHover: Function, }, @@ -19,21 +24,24 @@ export default defineComponent({ return (
    - {props.presets.map(({ label, value }, index) => ( + {props.presets.map(preset => (
  • { e.stopPropagation(); - props.onClick(value); + props.onClick(preset.value, preset); }} onMouseenter={() => { - props.onHover?.(value); + props.onHover?.(preset.value); }} onMouseleave={() => { props.onHover?.(null); }} > - {label} + {preset.label}
  • ))}
diff --git a/components/vc-picker/RangePicker.tsx b/components/vc-picker/RangePicker.tsx index 84a65d4f27..145693fd02 100644 --- a/components/vc-picker/RangePicker.tsx +++ b/components/vc-picker/RangePicker.tsx @@ -5,6 +5,7 @@ import type { RangeValue, EventValue, PresetDate, + RangePickerOnChange, } from './interface'; import type { PickerBaseProps, PickerDateProps, PickerTimeProps } from './Picker'; import type { SharedTimeProps } from './panels/TimePanel'; @@ -108,7 +109,7 @@ export type RangePickerSharedProps = { separator?: VueNode; allowEmpty?: [boolean, boolean]; mode?: [PanelMode, PanelMode]; - onChange?: (values: RangeValue, formatString: [string, string]) => void; + onChange?: RangePickerOnChange; onCalendarChange?: ( values: RangeValue, formatString: [string, string], @@ -133,6 +134,10 @@ export type RangePickerSharedProps = { nextIcon?: VueNode; superPrevIcon?: VueNode; superNextIcon?: VueNode; + /** 双击日期时自动设置为开始和结束日期 */ + autoFill?: boolean; + /** 在 showTime 模式下,是否设置为整天(开始时间 00:00:00,结束时间 23:59:59) */ + isWholeDay?: boolean; }; type OmitPickerProps = Omit< @@ -258,6 +263,8 @@ function RangerPicker() { 'nextIcon', 'superPrevIcon', 'superNextIcon', + 'autoFill', + 'isWholeDay', ] as any, setup(props, { attrs, expose }) { const needConfirmButton = computed( @@ -319,6 +326,31 @@ function RangerPicker() { : reorderValues(values, props.generateConfig), }); + // ========================= Current Preset ========================= + const [currentPreset, setCurrentPreset] = useState> | null>( + null, + ); + + // 检查当前值是否匹配某个 preset + const checkAndSetPreset = (values: RangeValue) => { + if (!values || !values[0] || !values[1]) { + setCurrentPreset(null); + return; + } + + const matchedPreset = presetList.value.find(preset => { + if (!preset.value || !preset.value[0] || !preset.value[1]) { + return false; + } + return ( + isEqual(props.generateConfig, values[0], preset.value[0]) && + isEqual(props.generateConfig, values[1], preset.value[1]) + ); + }); + + setCurrentPreset(matchedPreset || null); + }; + // =========================== View Date =========================== // Config view panel const [startViewDate, endViewDate, setViewDate] = useRangeViewDates({ @@ -491,7 +523,11 @@ function RangerPicker() { }, 0); } - function triggerChange(newValue: RangeValue, sourceIndex: 0 | 1) { + function triggerChange( + newValue: RangeValue, + sourceIndex: 0 | 1, + fromPreset = false, + ) { let values = newValue; let startValue = getValue(values, 0); let endValue = getValue(values, 1); @@ -541,8 +577,33 @@ function RangerPicker() { } } + // Handle isWholeDay: set time to 00:00:00 for start and 23:59:59 for end when showTime is true + if (props.isWholeDay && showTime && values && values[0] && values[1]) { + const startDate = values[0]; + const endDate = values[1]; + + // Set start time to 00:00:00 + const startWithTime = generateConfig.setHour( + generateConfig.setMinute(generateConfig.setSecond(startDate, 0), 0), + 0, + ); + + // Set end time to 23:59:59 + const endWithTime = generateConfig.setHour( + generateConfig.setMinute(generateConfig.setSecond(endDate, 59), 59), + 23, + ); + + values = [startWithTime, endWithTime]; + } + setSelectedValue(values); + // 如果不是通过 preset 触发的,清除 currentPreset + if (!fromPreset) { + setCurrentPreset(null); + } + const startStr = values && values[0] ? formatValue(values[0], { generateConfig, locale, format: formatList.value[0] }) @@ -577,7 +638,10 @@ function RangerPicker() { (!isEqual(generateConfig, getValue(mergedValue.value, 0), startValue) || !isEqual(generateConfig, getValue(mergedValue.value, 1), endValue)) ) { - onChange(values, [startStr, endStr]); + onChange( + [startValue, endValue, currentPreset.value], + [startStr, endStr, currentPreset.value?.key || null], + ); } } @@ -720,7 +784,7 @@ function RangerPicker() { ) { return false; } - triggerChange(selectedValue.value, index); + triggerChange(selectedValue.value, index, false); resetText(); }, onCancel: () => { @@ -824,6 +888,11 @@ function RangerPicker() { setSelectedValue(mergedValue.value); }); + // 当 mergedValue 变化时,检查是否匹配某个 preset + watch(mergedValue, newValue => { + checkAndSetPreset(newValue); + }); + // ============================ Warning ============================ if (process.env.NODE_ENV !== 'production') { watchEffect(() => { @@ -889,6 +958,37 @@ function RangerPicker() { }; } + // Handle isWholeDay: set default time values for start and end + if (props.isWholeDay && showTime) { + const now = generateConfig.getNow(); + let defaultTime: DateType; + + if (mergedActivePickerIndex.value === 0) { + // Start time: 00:00:00 + defaultTime = generateConfig.setHour( + generateConfig.setMinute(generateConfig.setSecond(now, 0), 0), + 0, + ); + } else { + // End time: 23:59:59 + defaultTime = generateConfig.setHour( + generateConfig.setMinute(generateConfig.setSecond(now, 59), 59), + 23, + ); + } + + if (typeof showTime === 'object') { + panelShowTime = { + ...showTime, + defaultValue: defaultTime, + }; + } else { + panelShowTime = { + defaultValue: defaultTime, + }; + } + } + let panelDateRender: DateRender | null = null; if (dateRender) { panelDateRender = ({ current: date, today }) => @@ -971,7 +1071,7 @@ function RangerPicker() { } const onContextSelect = (date: DateType, type: 'key' | 'mouse' | 'submit') => { - const values = updateValues(selectedValue.value, date, mergedActivePickerIndex.value); + let values = updateValues(selectedValue.value, date, mergedActivePickerIndex.value); const currentIndex = mergedActivePickerIndex.value; const isDoubleClick = isDoubleClickRef.value; const shouldSwitch = type === 'mouse' && needConfirmButton.value && isDoubleClick; @@ -979,13 +1079,28 @@ function RangerPicker() { // Reset double click state isDoubleClickRef.value = false; + // Handle autoFill: when double-clicking and autoFill is enabled, set the same date for both start and end + if (props.autoFill && isDoubleClick && type === 'mouse') { + values = [date, date]; + } + if (type === 'submit' || (type !== 'key' && !needConfirmButton.value) || shouldSwitch) { // triggerChange will also update selected values - triggerChange(values, mergedActivePickerIndex.value); + triggerChange(values, mergedActivePickerIndex.value, false); - // If double click, switch to next input - // But check if both inputs are complete, if so don't switch to avoid animation before popup closes - if (shouldSwitch) { + // If autoFill is enabled and we have both values, close the panel + if ( + props.autoFill && + isDoubleClick && + type === 'mouse' && + values && + values[0] && + values[1] + ) { + triggerOpen(false, mergedActivePickerIndex.value); + } else if (shouldSwitch) { + // If double click, switch to next input + // But check if both inputs are complete, if so don't switch to avoid animation before popup closes const startValue = getValue(values, 0); const endValue = getValue(values, 1); const bothValuesComplete = startValue && endValue; @@ -1074,7 +1189,7 @@ function RangerPicker() { onOk: () => { if (getValue(selectedValue.value, mergedActivePickerIndex.value)) { // triggerChangeOld(selectedValue.value); - triggerChange(selectedValue.value, mergedActivePickerIndex.value); + triggerChange(selectedValue.value, mergedActivePickerIndex.value, false); if (onOk) { onOk(selectedValue.value); } @@ -1129,8 +1244,10 @@ function RangerPicker() { { - triggerChange(nextValue, null); + currentPreset={currentPreset.value} + onClick={(nextValue, preset) => { + setCurrentPreset(preset); + triggerChange(nextValue, null, true); triggerOpen(false, mergedActivePickerIndex.value); }} onHover={hoverValue => { @@ -1207,7 +1324,7 @@ function RangerPicker() { values = updateValues(values, null, 1); } - triggerChange(values, null); + triggerChange(values, null, false); triggerOpen(false, mergedActivePickerIndex.value); }} class={`${prefixCls}-clear`} diff --git a/components/vc-picker/hooks/usePresets.ts b/components/vc-picker/hooks/usePresets.ts index 5b95de32d3..933e3ab29f 100644 --- a/components/vc-picker/hooks/usePresets.ts +++ b/components/vc-picker/hooks/usePresets.ts @@ -22,6 +22,7 @@ export default function usePresets( return { label, value: newValues, + key: label, // 添加 key 属性 }; }); } diff --git a/components/vc-picker/interface.ts b/components/vc-picker/interface.ts index 58b7106da3..3f1255cdad 100644 --- a/components/vc-picker/interface.ts +++ b/components/vc-picker/interface.ts @@ -112,4 +112,11 @@ export type CustomFormat = (value: DateType) => string; export interface PresetDate { label: VueNode; value: T; + key: string; // 重要,需要用key来高亮选中状态 } + +// 扩展的 onChange 回调类型,values 和 formatString 都包含第三个 preset 元素 +export type RangePickerOnChange = ( + values: [DateType | null, DateType | null, PresetDate> | null], + formatString: [string, string, string | null], +) => void; From b4603d2eebfedb68337cb42755597fa7f5d05ed5 Mon Sep 17 00:00:00 2001 From: yuanyi Date: Fri, 19 Sep 2025 18:06:13 +0400 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DRangePicker?= =?UTF-8?q?=E5=9B=9E=E5=A1=AB=E6=97=B6=E9=97=B4=E6=97=B6=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../date-picker/demo/auto-fill-whole-day.vue | 6 +- components/date-picker/demo/index.vue | 3 + .../date-picker/demo/preset-autofill.vue | 154 +++++++++++++++ components/table/hooks/useSorter.tsx | 2 +- components/vc-picker/RangePicker.tsx | 179 +++++++++++++++++- 5 files changed, 334 insertions(+), 10 deletions(-) create mode 100644 components/date-picker/demo/preset-autofill.vue diff --git a/components/date-picker/demo/auto-fill-whole-day.vue b/components/date-picker/demo/auto-fill-whole-day.vue index 4c380a8690..75de034492 100644 --- a/components/date-picker/demo/auto-fill-whole-day.vue +++ b/components/date-picker/demo/auto-fill-whole-day.vue @@ -54,7 +54,7 @@ RangePicker supports two new features: diff --git a/components/date-picker/demo/preset-autofill.vue b/components/date-picker/demo/preset-autofill.vue new file mode 100644 index 0000000000..c3ed01629e --- /dev/null +++ b/components/date-picker/demo/preset-autofill.vue @@ -0,0 +1,154 @@ + +--- +order: 8 +title: + zh-CN: Preset自动回填 + en-US: Preset Auto Fill +--- + +## zh-CN + +RangePicker 现在支持preset自动回填功能。当传入的value包含preset信息时,会根据preset自动计算对应的日期范围。 + +## en-US + +RangePicker now supports preset auto fill functionality. When the passed value contains preset information, it will automatically calculate the corresponding date range based on the preset. + + + + + + diff --git a/components/table/hooks/useSorter.tsx b/components/table/hooks/useSorter.tsx index 8f307af7e4..6406784d47 100644 --- a/components/table/hooks/useSorter.tsx +++ b/components/table/hooks/useSorter.tsx @@ -186,7 +186,7 @@ function injectSorter( const cell = (column.customHeaderCell && column.customHeaderCell(col)) || {}; const originOnClick = cell.onClick; const originOKeyDown = cell.onKeydown; - cell.onClick = (event: MouseEvent) => { + cell.onClick = (event: PointerEvent) => { triggerSorter({ column, key: columnKey, diff --git a/components/vc-picker/RangePicker.tsx b/components/vc-picker/RangePicker.tsx index 145693fd02..648543a5e4 100644 --- a/components/vc-picker/RangePicker.tsx +++ b/components/vc-picker/RangePicker.tsx @@ -320,10 +320,64 @@ function RangerPicker() { const [mergedValue, setInnerValue] = useMergedState>(null, { value: toRef(props, 'value'), defaultValue: props.defaultValue, - postState: values => - props.picker === 'time' && !props.order + postState: values => { + // 处理包含preset的value格式 [date, date, preset] + if ( + values && + Array.isArray(values) && + (values as any).length === 3 && + (values as any)[2] + ) { + const preset = (values as any)[2]; + // 如果preset有value属性,使用preset的value作为日期范围 + if (preset.value && Array.isArray(preset.value) && preset.value.length === 2) { + const presetValues = preset.value; + + // 检查preset.value是否是函数,如果是则执行函数获取当前值 + let startValue = + typeof presetValues[0] === 'function' ? presetValues[0]() : presetValues[0]; + let endValue = + typeof presetValues[1] === 'function' ? presetValues[1]() : presetValues[1]; + + // 判断props.isWholeDay是否为true,如果为false,则使用当前时间的时分秒 + if (!props.isWholeDay) { + const now = props.generateConfig.getNow(); + const currentHour = props.generateConfig.getHour(now); + const currentMinute = props.generateConfig.getMinute(now); + const currentSecond = props.generateConfig.getSecond(now); + + startValue = props.generateConfig.setHour( + props.generateConfig.setMinute( + props.generateConfig.setSecond(startValue, currentSecond), + currentMinute, + ), + currentHour, + ); + endValue = props.generateConfig.setHour( + props.generateConfig.setMinute( + props.generateConfig.setSecond(endValue, currentSecond), + currentMinute, + ), + currentHour, + ); + } + + if (startValue && endValue) { + // 设置当前preset + setCurrentPreset(preset); + // 返回preset计算出的日期范围 + return props.picker === 'time' && !props.order + ? [startValue, endValue] + : reorderValues([startValue, endValue], props.generateConfig); + } + } + } + + // 处理普通格式的value + return props.picker === 'time' && !props.order ? values - : reorderValues(values, props.generateConfig), + : reorderValues(values, props.generateConfig); + }, }); // ========================= Current Preset ========================= @@ -597,6 +651,61 @@ function RangerPicker() { values = [startWithTime, endWithTime]; } + // 如果通过preset触发,且preset有value属性,重新计算日期范围 + if ( + fromPreset && + currentPreset.value && + Array.isArray(currentPreset.value) && + currentPreset.value.length === 2 + ) { + const presetValues = currentPreset.value; + // 检查preset.value是否是函数,如果是则执行函数获取当前值 + const presetStartValue = + typeof presetValues[0] === 'function' ? presetValues[0]() : presetValues[0]; + const presetEndValue = + typeof presetValues[1] === 'function' ? presetValues[1]() : presetValues[1]; + + if (presetStartValue && presetEndValue) { + // 如果启用了isWholeDay,应用时间设置 + if (props.isWholeDay && showTime) { + const startWithTime = generateConfig.setHour( + generateConfig.setMinute(generateConfig.setSecond(presetStartValue, 0), 0), + 0, + ); + const endWithTime = generateConfig.setHour( + generateConfig.setMinute(generateConfig.setSecond(presetEndValue, 59), 59), + 23, + ); + values = [startWithTime, endWithTime]; + } else if (showTime) { + // 如果未启用isWholeDay但启用了showTime,使用当前时间的时分秒 + const now = generateConfig.getNow(); + const currentHour = generateConfig.getHour(now); + const currentMinute = generateConfig.getMinute(now); + const currentSecond = generateConfig.getSecond(now); + + const startWithCurrentTime = generateConfig.setHour( + generateConfig.setMinute( + generateConfig.setSecond(presetStartValue, currentSecond), + currentMinute, + ), + currentHour, + ); + const endWithCurrentTime = generateConfig.setHour( + generateConfig.setMinute( + generateConfig.setSecond(presetEndValue, currentSecond), + currentMinute, + ), + currentHour, + ); + values = [startWithCurrentTime, endWithCurrentTime]; + } else { + // 如果没有启用showTime,保持preset的原始时间 + values = [presetStartValue, presetEndValue]; + } + } + } + setSelectedValue(values); // 如果不是通过 preset 触发的,清除 currentPreset @@ -638,9 +747,11 @@ function RangerPicker() { (!isEqual(generateConfig, getValue(mergedValue.value, 0), startValue) || !isEqual(generateConfig, getValue(mergedValue.value, 1), endValue)) ) { + // 如果通过preset触发,传递preset信息 + const presetToPass = fromPreset ? currentPreset.value : currentPreset.value; onChange( - [startValue, endValue, currentPreset.value], - [startStr, endStr, currentPreset.value?.key || null], + [startValue, endValue, presetToPass], + [startStr, endStr, presetToPass?.key || null], ); } } @@ -1247,7 +1358,63 @@ function RangerPicker() { currentPreset={currentPreset.value} onClick={(nextValue, preset) => { setCurrentPreset(preset); - triggerChange(nextValue, null, true); + // 如果preset有value属性,使用preset的value作为日期范围 + let valuesToUse = nextValue; + if (preset.value && Array.isArray(preset.value) && preset.value.length === 2) { + const presetValues = preset.value; + // 检查preset.value是否是函数,如果是则执行函数获取当前值 + const presetStartValue = + typeof presetValues[0] === 'function' ? presetValues[0]() : presetValues[0]; + const presetEndValue = + typeof presetValues[1] === 'function' ? presetValues[1]() : presetValues[1]; + + if (presetStartValue && presetEndValue) { + // 如果启用了isWholeDay,应用时间设置 + if (props.isWholeDay && props.showTime) { + const startWithTime = props.generateConfig.setHour( + props.generateConfig.setMinute( + props.generateConfig.setSecond(presetStartValue, 0), + 0, + ), + 0, + ); + const endWithTime = props.generateConfig.setHour( + props.generateConfig.setMinute( + props.generateConfig.setSecond(presetEndValue, 59), + 59, + ), + 23, + ); + valuesToUse = [startWithTime, endWithTime]; + } else if (props.showTime) { + // 如果未启用isWholeDay但启用了showTime,使用当前时间的时分秒 + const now = props.generateConfig.getNow(); + const currentHour = props.generateConfig.getHour(now); + const currentMinute = props.generateConfig.getMinute(now); + const currentSecond = props.generateConfig.getSecond(now); + + const startWithCurrentTime = props.generateConfig.setHour( + props.generateConfig.setMinute( + props.generateConfig.setSecond(presetStartValue, currentSecond), + currentMinute, + ), + currentHour, + ); + const endWithCurrentTime = props.generateConfig.setHour( + props.generateConfig.setMinute( + props.generateConfig.setSecond(presetEndValue, currentSecond), + currentMinute, + ), + currentHour, + ); + valuesToUse = [startWithCurrentTime, endWithCurrentTime]; + } else { + // 如果没有启用showTime,保持preset的原始时间 + valuesToUse = [presetStartValue, presetEndValue]; + } + } + } + triggerChange(valuesToUse, null, true); triggerOpen(false, mergedActivePickerIndex.value); }} onHover={hoverValue => {