pull/8366/merge
yuanyi0821 2025-11-19 02:55:48 +00:00 committed by GitHub
commit e578216369
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 679 additions and 39 deletions

View File

@ -0,0 +1,123 @@
<docs>
---
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
</docs>
<template>
<a-space direction="vertical" :size="12">
<div>
<h4>Auto Fill 功能</h4>
<p>双击日期会自动设置为开始和结束日期</p>
<a-range-picker :auto-fill="true" @change="onAutoFillChange" />
</div>
<div>
<h4>Whole Day 功能</h4>
<p> showTime 模式下自动设置整天时间</p>
<a-range-picker
show-time
:is-whole-day="true"
format="YYYY/MM/DD HH:mm:ss"
@change="onWholeDayChange"
/>
</div>
<div>
<h4>组合使用</h4>
<p>同时启用 autoFill isWholeDay</p>
<a-range-picker
show-time
:auto-fill="true"
:is-whole-day="true"
format="YYYY/MM/DD HH:mm:ss"
@change="onCombinedChange"
/>
</div>
</a-space>
</template>
<script lang="ts" setup>
import { Dayjs } from 'dayjs';
type RangeValue = [Dayjs, Dayjs];
const onAutoFillChange = (
values: RangeValue,
dateStrings: [string, string],
currentPreset?: any,
) => {
if (values) {
console.log(
'Auto Fill - From: ',
values[0].format('YYYY-MM-DD'),
', to: ',
values[1].format('YYYY-MM-DD'),
);
console.log('Auto Fill - From: ', dateStrings[0], ', to: ', dateStrings[1]);
if (currentPreset) {
console.log('Auto Fill - Selected preset: ', currentPreset.label);
}
} else {
console.log('Auto Fill - Clear');
}
};
const onWholeDayChange = (
values: RangeValue,
dateStrings: [string, string],
currentPreset?: any,
) => {
if (values && values[0] && values[1]) {
console.log(
'Whole Day - From: ',
values[0].format('YYYY-MM-DD HH:mm:ss'),
', to: ',
values[1].format('YYYY-MM-DD HH:mm:ss'),
);
console.log('Whole Day - From: ', dateStrings[0], ', to: ', dateStrings[1]);
if (currentPreset) {
console.log('Whole Day - Selected preset: ', currentPreset.label);
}
} else {
console.log('Whole Day - Clear');
}
};
const onCombinedChange = (
values: RangeValue,
dateStrings: [string, string],
currentPreset?: any,
) => {
if (values && values[0] && values[1]) {
console.log(
'Combined - From: ',
values[0].format('YYYY-MM-DD HH:mm:ss'),
', to: ',
values[1].format('YYYY-MM-DD HH:mm:ss'),
);
console.log('Combined - From: ', dateStrings[0], ', to: ', dateStrings[1]);
if (currentPreset) {
console.log('Combined - Selected preset: ', currentPreset.label);
}
} else {
console.log('Combined - Clear');
}
};
</script>

View File

@ -17,6 +17,8 @@
<Suffix /> <Suffix />
<statusVue /> <statusVue />
<placementVue /> <placementVue />
<AutoFillWholeDay />
<PresetAutofill />
</demo-sort> </demo-sort>
</template> </template>
<script> <script>
@ -36,6 +38,8 @@ import Suffix from './suffix.vue';
import Bordered from './bordered.vue'; import Bordered from './bordered.vue';
import RangePicker from './range-picker.vue'; import RangePicker from './range-picker.vue';
import placementVue from './placement.vue'; import placementVue from './placement.vue';
import AutoFillWholeDay from './auto-fill-whole-day.vue';
import PresetAutofill from './preset-autofill.vue';
import statusVue from './status.vue'; import statusVue from './status.vue';
import CN from '../index.zh-CN.md'; import CN from '../index.zh-CN.md';
import US from '../index.en-US.md'; import US from '../index.en-US.md';
@ -62,6 +66,8 @@ export default defineComponent({
SelectInRnage, SelectInRnage,
Bordered, Bordered,
RangePicker, RangePicker,
AutoFillWholeDay,
PresetAutofill,
}, },
}); });
</script> </script>

View File

@ -0,0 +1,154 @@
<docs>
---
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.
</docs>
<template>
<a-space direction="vertical" :size="12">
<div>
<h4>Preset自动回填功能</h4>
<p>当传入的value包含preset信息时会根据preset自动计算日期范围</p>
<p>
<strong>当前设置</strong>
is-whole-day={{ isWholeDay }}show-time=true
</p>
<p>
<strong>行为</strong>
当isWholeDay为false时点击preset时会使用当前时间的时分秒而不是00:00:00 - 23:59:59
</p>
<a-range-picker
v-model:value="rangeValue"
show-time
:is-whole-day="isWholeDay"
format="YYYY/MM/DD HH:mm:ss"
:presets="rangePresets"
@change="onRangeChange"
/>
</div>
<div>
<h4>切换is-whole-day</h4>
<p>
<a-button @click="toggleIsWholeDay">is-whole-day</a-button>
</p>
<h4>测试按钮</h4>
<a-space>
<a-button @click="setTodayPreset">preset</a-button>
<a-button @click="setLast7DaysPreset">7preset</a-button>
<a-button @click="setLast30DaysPreset">30preset</a-button>
<a-button @click="setCurrentTimePreset">preset</a-button>
<a-button @click="clearValue"></a-button>
</a-space>
</div>
<div>
<h4>当前值</h4>
<pre>{{ JSON.stringify(rangeValue, null, 2) }}</pre>
</div>
</a-space>
</template>
<script lang="ts" setup>
import dayjs, { Dayjs } from 'dayjs';
import { ref } from 'vue';
type RangeValue = [Dayjs, Dayjs] | [Dayjs, Dayjs, any];
const isWholeDay = ref(false);
const rangeValue = ref<RangeValue | null>(null);
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 rangePresets = ref([
{
label: '今天',
value: [dayjs().startOf('day'), dayjs().endOf('day')],
key: 'today',
},
{
label: '最近7天',
value: [dayjs().subtract(7, 'day').startOf('day'), dayjs().endOf('day')],
key: 'last7days',
},
{
label: '最近30天',
value: [dayjs().subtract(30, 'day').startOf('day'), dayjs().endOf('day')],
key: 'last30days',
},
{
label: '最近90天',
value: [dayjs().subtract(90, 'day').startOf('day'), dayjs().endOf('day')],
key: 'last90days',
},
]);
const setTodayPreset = () => {
const todayPreset = rangePresets.value.find(p => p.key === 'today');
if (todayPreset) {
// presetvalueRangePickerpreset
rangeValue.value = [dayjs(), dayjs(), todayPreset] as any;
}
};
const setLast7DaysPreset = () => {
const last7DaysPreset = rangePresets.value.find(p => p.key === 'last7days');
if (last7DaysPreset) {
// presetvalueRangePickerpreset
rangeValue.value = [dayjs(), dayjs(), last7DaysPreset] as any;
}
};
const setLast30DaysPreset = () => {
const last30DaysPreset = rangePresets.value.find(p => p.key === 'last30days');
if (last30DaysPreset) {
// presetvalueRangePickerpreset
rangeValue.value = [dayjs(), dayjs(), last30DaysPreset] as any;
}
};
const setCurrentTimePreset = () => {
// preset
const currentTimePreset = {
label: '当前时间',
value: [dayjs(), dayjs()], // 使startOf/endOf
key: 'currentTime',
};
// presetvalueRangePickerpreset
rangeValue.value = [dayjs(), dayjs(), currentTimePreset] as any;
};
const clearValue = () => {
rangeValue.value = null;
};
const toggleIsWholeDay = () => {
isWholeDay.value = !isWholeDay.value;
};
</script>

View File

@ -40,25 +40,31 @@ const onChange = (date: Dayjs) => {
console.log('Clear'); console.log('Clear');
} }
}; };
const onRangeChange = (dates: RangeValue, dateStrings: string[]) => { const onRangeChange = (values: RangeValue, dateStrings: [string, string], currentPreset?: any) => {
if (dates) { if (values) {
console.log('From: ', dates[0], ', to: ', dates[1]); console.log('From: ', values[0], ', to: ', values[1]);
console.log('From: ', dateStrings[0], ', to: ', dateStrings[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 { } else {
console.log('Clear'); console.log('Clear');
} }
}; };
const presets = ref([ const presets = ref([
{ label: 'Yesterday', value: dayjs().add(-1, 'd') }, { label: 'Yesterday', value: dayjs().add(-1, 'd'), key: 'yesterday' },
{ label: 'Last Week', value: dayjs().add(-7, 'd') }, { label: 'Last Week', value: dayjs().add(-7, 'd'), key: 'lastweek' },
{ label: 'Last Month', value: dayjs().add(-1, 'month') }, { label: 'Last Month', value: dayjs().add(-1, 'month'), key: 'lastmonth' },
]); ]);
const rangePresets = ref([ const rangePresets = ref([
{ label: 'Last 7 Days', value: [dayjs().add(-7, 'd'), dayjs()] }, { label: 'Last 7 Days', value: [dayjs().add(-7, 'd'), dayjs()], key: 'last7days' },
{ label: 'Last 14 Days', value: [dayjs().add(-14, 'd'), dayjs()] }, { label: 'Last 14 Days', value: [dayjs().add(-14, 'd'), dayjs()], key: 'last14days' },
{ label: 'Last 30 Days', value: [dayjs().add(-30, 'd'), dayjs()] }, { label: 'Last 30 Days', value: [dayjs().add(-30, 'd'), dayjs()], key: 'last30days' },
{ label: 'Last 90 Days', value: [dayjs().add(-90, 'd'), dayjs()] }, { label: 'Last 90 Days', value: [dayjs().add(-90, 'd'), dayjs()], key: 'last90days' },
]); ]);
</script> </script>

View File

@ -13,7 +13,7 @@ import useConfigInject from '../../config-provider/hooks/useConfigInject';
import classNames from '../../_util/classNames'; import classNames from '../../_util/classNames';
import type { CommonProps, RangePickerProps } from './props'; import type { CommonProps, RangePickerProps } from './props';
import { 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 type { RangePickerSharedProps } from '../../vc-picker/RangePicker';
import { FormItemInputContext, useInjectFormItemContext } from '../../form/FormItemContext'; import { FormItemInputContext, useInjectFormItemContext } from '../../form/FormItemContext';
import omit from '../../_util/omit'; import omit from '../../_util/omit';
@ -84,13 +84,18 @@ export default function generateRangePicker<DateType, ExtraProps = {}>(
pickerRef.value?.blur(); pickerRef.value?.blur();
}, },
}); });
const maybeToStrings = (dates: DateType[]) => { const maybeToStrings = (dates: RangeValue<DateType>) => {
return props.valueFormat ? generateConfig.toString(dates, props.valueFormat) : dates; return props.valueFormat ? generateConfig.toString(dates, props.valueFormat) : dates;
}; };
const onChange = (dates: RangeValue<DateType>, dateStrings: [string, string]) => { const onChange: RangePickerOnChange<DateType> = (values, formatStrings) => {
const values = maybeToStrings(dates); const [startValue, endValue, currentPreset] = values;
emit('update:value', values); const [startStr, endStr] = formatStrings;
emit('change', values, dateStrings); const dates: RangeValue<DateType> = [startValue, endValue];
const dateStrings: [string, string] = [startStr, endStr];
const processedValues = maybeToStrings(dates);
emit('update:value', processedValues);
emit('change', processedValues, dateStrings, currentPreset);
formItemContext.onFieldChange(); formItemContext.onFieldChange();
}; };
const onOpenChange = (open: boolean) => { const onOpenChange = (open: boolean) => {
@ -109,7 +114,7 @@ export default function generateRangePicker<DateType, ExtraProps = {}>(
emit('panelChange', values, modes); emit('panelChange', values, modes);
}; };
const onOk = (dates: DateType[]) => { const onOk = (dates: DateType[]) => {
const value = maybeToStrings(dates); const value = props.valueFormat ? generateConfig.toString(dates, props.valueFormat) : dates;
emit('ok', value); emit('ok', value);
}; };
const onCalendarChange: RangePickerSharedProps<DateType>['onCalendarChange'] = ( const onCalendarChange: RangePickerSharedProps<DateType>['onCalendarChange'] = (

View File

@ -269,8 +269,13 @@ export interface RangePickerProps<DateType> {
onChange?: ( onChange?: (
value: RangeValue<DateType> | RangeValue<string> | null, value: RangeValue<DateType> | RangeValue<string> | null,
dateString: [string, string], dateString: [string, string],
currentPreset?: any,
) => void; ) => void;
'onUpdate:value'?: (value: RangeValue<DateType> | RangeValue<string> | null) => void; 'onUpdate:value'?: (value: RangeValue<DateType> | RangeValue<string> | null) => void;
/** 双击日期时自动设置为开始和结束日期 */
autoFill?: boolean;
/** 在 showTime 模式下,是否设置为整天(开始时间 00:00:00结束时间 23:59:59 */
isWholeDay?: boolean;
onCalendarChange?: ( onCalendarChange?: (
values: RangeValue<DateType> | RangeValue<string>, values: RangeValue<DateType> | RangeValue<string>,
formatString: [string, string], formatString: [string, string],

View File

@ -961,6 +961,7 @@ const genPickerStyle: GenerateStyle<PickerToken> = token => {
controlItemBgHover, controlItemBgHover,
presetsWidth, presetsWidth,
presetsMaxWidth, presetsMaxWidth,
fontWeightStrong,
} = token; } = token;
return [ return [
@ -1326,6 +1327,12 @@ const genPickerStyle: GenerateStyle<PickerToken> = token => {
'&:hover': { '&:hover': {
background: controlItemBgHover, background: controlItemBgHover,
}, },
[`&${componentCls}-preset-active`]: {
background: controlItemBgActive,
color: colorPrimary,
fontWeight: fontWeightStrong,
},
}, },
}, },
}, },

View File

@ -186,7 +186,7 @@ function injectSorter<RecordType>(
const cell = (column.customHeaderCell && column.customHeaderCell(col)) || {}; const cell = (column.customHeaderCell && column.customHeaderCell(col)) || {};
const originOnClick = cell.onClick; const originOnClick = cell.onClick;
const originOKeyDown = cell.onKeydown; const originOKeyDown = cell.onKeydown;
cell.onClick = (event: MouseEvent) => { cell.onClick = (event: PointerEvent) => {
triggerSorter({ triggerSorter({
column, column,
key: columnKey, key: columnKey,

View File

@ -23,6 +23,9 @@ export type PanelContextProps = {
/** Only used for TimePicker and this is a deprecated prop */ /** Only used for TimePicker and this is a deprecated prop */
defaultOpenValue?: Ref<any>; defaultOpenValue?: Ref<any>;
/** Double click state for RangePicker */
isDoubleClickRef?: Ref<boolean>;
}; };
const PanelContextKey: InjectionKey<PanelContextProps> = Symbol('PanelContextProps'); const PanelContextKey: InjectionKey<PanelContextProps> = Symbol('PanelContextProps');

View File

@ -1,13 +1,18 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { PresetDate } from './interface';
export default defineComponent({ export default defineComponent({
name: 'PresetPanel', name: 'PresetPanel',
props: { props: {
prefixCls: String, prefixCls: String,
presets: { presets: {
type: Array, type: Array as () => PresetDate<any>[],
default: () => [], default: () => [],
}, },
currentPreset: {
type: Object as () => PresetDate<any> | null,
default: null,
},
onClick: Function, onClick: Function,
onHover: Function, onHover: Function,
}, },
@ -19,21 +24,24 @@ export default defineComponent({
return ( return (
<div class={`${props.prefixCls}-presets`}> <div class={`${props.prefixCls}-presets`}>
<ul> <ul>
{props.presets.map(({ label, value }, index) => ( {props.presets.map(preset => (
<li <li
key={index} key={preset.key}
class={{
[`${props.prefixCls}-preset-active`]: props.currentPreset?.key === preset.key,
}}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
props.onClick(value); props.onClick(preset.value, preset);
}} }}
onMouseenter={() => { onMouseenter={() => {
props.onHover?.(value); props.onHover?.(preset.value);
}} }}
onMouseleave={() => { onMouseleave={() => {
props.onHover?.(null); props.onHover?.(null);
}} }}
> >
{label} {preset.label}
</li> </li>
))} ))}
</ul> </ul>

View File

@ -5,6 +5,7 @@ import type {
RangeValue, RangeValue,
EventValue, EventValue,
PresetDate, PresetDate,
RangePickerOnChange,
} from './interface'; } from './interface';
import type { PickerBaseProps, PickerDateProps, PickerTimeProps } from './Picker'; import type { PickerBaseProps, PickerDateProps, PickerTimeProps } from './Picker';
import type { SharedTimeProps } from './panels/TimePanel'; import type { SharedTimeProps } from './panels/TimePanel';
@ -108,7 +109,7 @@ export type RangePickerSharedProps<DateType> = {
separator?: VueNode; separator?: VueNode;
allowEmpty?: [boolean, boolean]; allowEmpty?: [boolean, boolean];
mode?: [PanelMode, PanelMode]; mode?: [PanelMode, PanelMode];
onChange?: (values: RangeValue<DateType>, formatString: [string, string]) => void; onChange?: RangePickerOnChange<DateType>;
onCalendarChange?: ( onCalendarChange?: (
values: RangeValue<DateType>, values: RangeValue<DateType>,
formatString: [string, string], formatString: [string, string],
@ -133,6 +134,10 @@ export type RangePickerSharedProps<DateType> = {
nextIcon?: VueNode; nextIcon?: VueNode;
superPrevIcon?: VueNode; superPrevIcon?: VueNode;
superNextIcon?: VueNode; superNextIcon?: VueNode;
/** 双击日期时自动设置为开始和结束日期 */
autoFill?: boolean;
/** 在 showTime 模式下,是否设置为整天(开始时间 00:00:00结束时间 23:59:59 */
isWholeDay?: boolean;
}; };
type OmitPickerProps<Props> = Omit< type OmitPickerProps<Props> = Omit<
@ -258,11 +263,15 @@ function RangerPicker<DateType>() {
'nextIcon', 'nextIcon',
'superPrevIcon', 'superPrevIcon',
'superNextIcon', 'superNextIcon',
'autoFill',
'isWholeDay',
] as any, ] as any,
setup(props, { attrs, expose }) { setup(props, { attrs, expose }) {
const needConfirmButton = computed( const needConfirmButton = computed(
() => (props.picker === 'date' && !!props.showTime) || props.picker === 'time', () => (props.picker === 'date' && !!props.showTime) || props.picker === 'time',
); );
const isDoubleClickRef = ref(false);
const presets = computed(() => props.presets); const presets = computed(() => props.presets);
const ranges = computed(() => props.ranges); const ranges = computed(() => props.ranges);
const presetList = usePresets(presets, ranges); const presetList = usePresets(presets, ranges);
@ -311,12 +320,91 @@ function RangerPicker<DateType>() {
const [mergedValue, setInnerValue] = useMergedState<RangeValue<DateType>>(null, { const [mergedValue, setInnerValue] = useMergedState<RangeValue<DateType>>(null, {
value: toRef(props, 'value'), value: toRef(props, 'value'),
defaultValue: props.defaultValue, defaultValue: props.defaultValue,
postState: values => postState: values => {
props.picker === 'time' && !props.order // presetvalue [date, date, preset]
if (
values &&
Array.isArray(values) &&
(values as any).length === 3 &&
(values as any)[2]
) {
const preset = (values as any)[2];
// presetvalue使presetvalue
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.isWholeDaytruefalse使
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 ? values
: reorderValues(values, props.generateConfig), : reorderValues(values, props.generateConfig);
},
}); });
// ========================= Current Preset =========================
const [currentPreset, setCurrentPreset] = useState<PresetDate<RangeValue<DateType>> | null>(
null,
);
// preset
const checkAndSetPreset = (values: RangeValue<DateType>) => {
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 =========================== // =========================== View Date ===========================
// Config view panel // Config view panel
const [startViewDate, endViewDate, setViewDate] = useRangeViewDates({ const [startViewDate, endViewDate, setViewDate] = useRangeViewDates({
@ -489,7 +577,11 @@ function RangerPicker<DateType>() {
}, 0); }, 0);
} }
function triggerChange(newValue: RangeValue<DateType>, sourceIndex: 0 | 1) { function triggerChange(
newValue: RangeValue<DateType>,
sourceIndex: 0 | 1,
fromPreset = false,
) {
let values = newValue; let values = newValue;
let startValue = getValue(values, 0); let startValue = getValue(values, 0);
let endValue = getValue(values, 1); let endValue = getValue(values, 1);
@ -539,8 +631,88 @@ function RangerPicker<DateType>() {
} }
} }
// 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];
}
// presetpresetvalue
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) {
// isWholeDayshowTime使
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 {
// showTimepreset
values = [presetStartValue, presetEndValue];
}
}
}
setSelectedValue(values); setSelectedValue(values);
// preset currentPreset
if (!fromPreset) {
setCurrentPreset(null);
}
const startStr = const startStr =
values && values[0] values && values[0]
? formatValue(values[0], { generateConfig, locale, format: formatList.value[0] }) ? formatValue(values[0], { generateConfig, locale, format: formatList.value[0] })
@ -575,7 +747,12 @@ function RangerPicker<DateType>() {
(!isEqual(generateConfig, getValue(mergedValue.value, 0), startValue) || (!isEqual(generateConfig, getValue(mergedValue.value, 0), startValue) ||
!isEqual(generateConfig, getValue(mergedValue.value, 1), endValue)) !isEqual(generateConfig, getValue(mergedValue.value, 1), endValue))
) { ) {
onChange(values, [startStr, endStr]); // presetpreset
const presetToPass = fromPreset ? currentPreset.value : currentPreset.value;
onChange(
[startValue, endValue, presetToPass],
[startStr, endStr, presetToPass?.key || null],
);
} }
} }
@ -718,7 +895,7 @@ function RangerPicker<DateType>() {
) { ) {
return false; return false;
} }
triggerChange(selectedValue.value, index); triggerChange(selectedValue.value, index, false);
resetText(); resetText();
}, },
onCancel: () => { onCancel: () => {
@ -822,6 +999,11 @@ function RangerPicker<DateType>() {
setSelectedValue(mergedValue.value); setSelectedValue(mergedValue.value);
}); });
// mergedValue preset
watch(mergedValue, newValue => {
checkAndSetPreset(newValue);
});
// ============================ Warning ============================ // ============================ Warning ============================
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
watchEffect(() => { watchEffect(() => {
@ -887,6 +1069,37 @@ function RangerPicker<DateType>() {
}; };
} }
// 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<DateType> | null = null; let panelDateRender: DateRender<DateType> | null = null;
if (dateRender) { if (dateRender) {
panelDateRender = ({ current: date, today }) => panelDateRender = ({ current: date, today }) =>
@ -969,11 +1182,46 @@ function RangerPicker<DateType>() {
} }
const onContextSelect = (date: DateType, type: 'key' | 'mouse' | 'submit') => { 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;
if (type === 'submit' || (type !== 'key' && !needConfirmButton.value)) { // 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 will also update selected values
triggerChange(values, mergedActivePickerIndex.value); triggerChange(values, mergedActivePickerIndex.value, false);
// 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;
if (!bothValuesComplete) {
const nextIndex = ((currentIndex + 1) % 2) as 0 | 1;
setMergedActivePickerIndex(nextIndex);
}
}
// clear hover value style // clear hover value style
if (mergedActivePickerIndex.value === 0) { if (mergedActivePickerIndex.value === 0) {
onStartLeave(); onStartLeave();
@ -993,6 +1241,7 @@ function RangerPicker<DateType>() {
hideRanges: computed(() => true), hideRanges: computed(() => true),
onSelect: onContextSelect, onSelect: onContextSelect,
open: mergedOpen, open: mergedOpen,
isDoubleClickRef,
}); });
return () => { return () => {
@ -1051,7 +1300,7 @@ function RangerPicker<DateType>() {
onOk: () => { onOk: () => {
if (getValue(selectedValue.value, mergedActivePickerIndex.value)) { if (getValue(selectedValue.value, mergedActivePickerIndex.value)) {
// triggerChangeOld(selectedValue.value); // triggerChangeOld(selectedValue.value);
triggerChange(selectedValue.value, mergedActivePickerIndex.value); triggerChange(selectedValue.value, mergedActivePickerIndex.value, false);
if (onOk) { if (onOk) {
onOk(selectedValue.value); onOk(selectedValue.value);
} }
@ -1106,8 +1355,66 @@ function RangerPicker<DateType>() {
<PresetPanel <PresetPanel
prefixCls={prefixCls} prefixCls={prefixCls}
presets={presetList.value} presets={presetList.value}
onClick={nextValue => { currentPreset={currentPreset.value}
triggerChange(nextValue, null); onClick={(nextValue, preset) => {
setCurrentPreset(preset);
// presetvalue使presetvalue
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) {
// isWholeDayshowTime使
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 {
// showTimepreset
valuesToUse = [presetStartValue, presetEndValue];
}
}
}
triggerChange(valuesToUse, null, true);
triggerOpen(false, mergedActivePickerIndex.value); triggerOpen(false, mergedActivePickerIndex.value);
}} }}
onHover={hoverValue => { onHover={hoverValue => {
@ -1184,7 +1491,7 @@ function RangerPicker<DateType>() {
values = updateValues(values, null, 1); values = updateValues(values, null, 1);
} }
triggerChange(values, null); triggerChange(values, null, false);
triggerOpen(false, mergedActivePickerIndex.value); triggerOpen(false, mergedActivePickerIndex.value);
}} }}
class={`${prefixCls}-clear`} class={`${prefixCls}-clear`}

View File

@ -22,6 +22,7 @@ export default function usePresets<T>(
return { return {
label, label,
value: newValues, value: newValues,
key: label, // 添加 key 属性
}; };
}); });
} }

View File

@ -112,4 +112,11 @@ export type CustomFormat<DateType> = (value: DateType) => string;
export interface PresetDate<T> { export interface PresetDate<T> {
label: VueNode; label: VueNode;
value: T; value: T;
key: string; // 重要需要用key来高亮选中状态
} }
// 扩展的 onChange 回调类型values 和 formatString 都包含第三个 preset 元素
export type RangePickerOnChange<DateType> = (
values: [DateType | null, DateType | null, PresetDate<RangeValue<DateType>> | null],
formatString: [string, string, string | null],
) => void;

View File

@ -49,7 +49,7 @@ function PanelBody<DateType>(_props: PanelBodyProps<DateType>) {
titleCell, titleCell,
headerCells, headerCells,
} = useMergeProps(_props); } = useMergeProps(_props);
const { onDateMouseenter, onDateMouseleave, mode } = useInjectPanel(); const { onDateMouseenter, onDateMouseleave, mode, isDoubleClickRef } = useInjectPanel();
const cellPrefixCls = `${prefixCls}-cell`; const cellPrefixCls = `${prefixCls}-cell`;
@ -99,6 +99,14 @@ function PanelBody<DateType>(_props: PanelBodyProps<DateType>) {
onSelect(currentDate); onSelect(currentDate);
} }
}} }}
onDblclick={e => {
e.stopPropagation();
if (!disabled && isDoubleClickRef) {
//
isDoubleClickRef.value = true;
onSelect(currentDate);
}
}}
onMouseenter={() => { onMouseenter={() => {
if (!disabled && onDateMouseenter) { if (!disabled && onDateMouseenter) {
onDateMouseenter(currentDate); onDateMouseenter(currentDate);