From b36f10cd1d7ad81ba24bafc893488ca0b64ddf81 Mon Sep 17 00:00:00 2001 From: Uyarn Date: Thu, 26 Dec 2024 00:02:06 +0800 Subject: [PATCH] feat: support multiple select --- src/date-picker/DatePicker.tsx | 6 +- src/date-picker/DatePickerPanel.tsx | 12 +-- src/date-picker/base/Table.tsx | 4 +- src/date-picker/hooks/useSingle.tsx | 131 ++++++++++++----------- src/date-picker/hooks/useSingleValue.tsx | 33 ++++-- src/date-picker/panel/SinglePanel.tsx | 15 ++- src/date-picker/type.ts | 1 - src/tag-input/tag-input.tsx | 2 +- 8 files changed, 118 insertions(+), 86 deletions(-) diff --git a/src/date-picker/DatePicker.tsx b/src/date-picker/DatePicker.tsx index d030aa06f0..43168bf305 100644 --- a/src/date-picker/DatePicker.tsx +++ b/src/date-picker/DatePicker.tsx @@ -148,7 +148,7 @@ export default defineComponent({ if (props.multiple) { const newDate = processDate(date); onChange(newDate, { - dayjsValue: parseToDayjs(date, props.format), + dayjsValue: parseToDayjs(date, formatRef.value.format), trigger: 'pick', }); return; @@ -171,6 +171,7 @@ export default defineComponent({ } function processDate(date: Date) { + if (!value.value) return []; const isSameDate = (value.value as DateMultipleValue).some((val) => isSame(dayjs(val).toDate(), date)); let currentDate: DateMultipleValue; @@ -311,7 +312,8 @@ export default defineComponent({ format: formatRef.value.format, mode: props.mode, presets: props.presets, - time: props.multiple ? false : time.value, + multiple: props.multiple, + time: props.multiple ? '' : time.value, disableDate: props.disableDate, firstDayOfWeek: props.firstDayOfWeek, timePickerProps: props.timePickerProps, diff --git a/src/date-picker/DatePickerPanel.tsx b/src/date-picker/DatePickerPanel.tsx index d1b19b18ae..60a12536e9 100644 --- a/src/date-picker/DatePickerPanel.tsx +++ b/src/date-picker/DatePickerPanel.tsx @@ -66,7 +66,7 @@ export default defineComponent({ } // 头部快速切换 - function onJumperClick({ trigger }: { trigger: string }) { + function onJumperClick({ trigger }: { trigger: 'prev' | 'next' | 'current' }) { const triggerMap = { prev: 'arrow-previous', next: 'arrow-next', @@ -91,14 +91,14 @@ export default defineComponent({ if (year.value !== nextYear) { props.onYearChange?.({ year: nextYear, - date: dayjs(value.value).toDate(), + date: dayjs(value.value as DateValue).toDate(), trigger: trigger === 'current' ? 'today' : (`year-${triggerMap[trigger]}` as DatePickerYearChangeTrigger), }); } if (month.value !== nextMonth) { props.onMonthChange?.({ month: nextMonth, - date: dayjs(value.value).toDate(), + date: dayjs(value.value as DateValue).toDate(), trigger: trigger === 'current' ? 'today' : (`month-${triggerMap[trigger]}` as DatePickerMonthChangeTrigger), }); } @@ -125,7 +125,7 @@ export default defineComponent({ props.onTimeChange?.({ time: val, - date: dayjs(value.value).toDate(), + date: dayjs(value.value as DateValue).toDate(), trigger: 'time-hour', }); } @@ -159,7 +159,7 @@ export default defineComponent({ props.onYearChange?.({ year: year.value, - date: dayjs(value.value).toDate(), + date: dayjs(value.value as DateValue).toDate(), trigger: 'year-select', }); } @@ -169,7 +169,7 @@ export default defineComponent({ props.onMonthChange?.({ month: month.value, - date: dayjs(value.value).toDate(), + date: dayjs(value.value as DateValue).toDate(), trigger: 'month-select', }); } diff --git a/src/date-picker/base/Table.tsx b/src/date-picker/base/Table.tsx index 173953be1d..37044d3620 100644 --- a/src/date-picker/base/Table.tsx +++ b/src/date-picker/base/Table.tsx @@ -74,9 +74,7 @@ export default defineComponent({ } return { - [`${COMPONENT_NAME.value}-${props.mode}-row--active`]: - parseToDayjs(value, format).locale(dayjsLocale).week() === - parseToDayjs(targetValue, format).locale(dayjsLocale).week(), + [`${COMPONENT_NAME.value}-${props.mode}-row--active`]: true, }; }; diff --git a/src/date-picker/hooks/useSingle.tsx b/src/date-picker/hooks/useSingle.tsx index d823f3ee43..06b58a3bc9 100644 --- a/src/date-picker/hooks/useSingle.tsx +++ b/src/date-picker/hooks/useSingle.tsx @@ -34,7 +34,7 @@ export default function useSingle(props: TdDatePickerProps) { mode: props.mode, format: props.format, valueType: props.valueType, - enableTimePicker: props.enableTimePicker, + enableTimePicker: props.multiple ? false : props.enableTimePicker, }), ); @@ -44,77 +44,82 @@ export default function useSingle(props: TdDatePickerProps) { const inputValue = ref(formatDate(value.value, { format: formatRef.value.format })); // input 设置 - const inputProps = computed(() => ({ - ...props.inputProps, - size: props.size, - ref: inputRef, - prefixIcon: () => renderTNodeJSX('prefixIcon'), - readonly: isReadOnly.value || !props.allowInput, - suffixIcon: () => { - return renderTNodeJSX('suffixIcon') || ; - }, - class: [ - { - [`${COMPONENT_NAME.value}__input--placeholder`]: isHoverCell.value, + const inputProps = computed(() => { + const defaultInputProps = { + ...props.inputProps, + size: props.size, + ref: inputRef, + prefixIcon: () => renderTNodeJSX('prefixIcon'), + readonly: isReadOnly.value || !props.allowInput, + suffixIcon: () => { + return renderTNodeJSX('suffixIcon') || ; }, - ], - onClear: (context: { e: InputEvent }) => { - context?.e?.stopPropagation(); - popupVisible.value = false; - onChange?.('', { dayjsValue: dayjs(), trigger: 'clear' }); - }, - onBlur: (val: string, context: { e: FocusEvent }) => { - props.onBlur?.({ value: val, e: context.e }); - }, - onFocus: (_: string, { e }: { e: FocusEvent }) => { - props.onFocus?.({ value: value.value, e }); - }, - onChange: (val: string) => { - // 输入事件 - inputValue.value = val; - - // 跳过不符合格式化的输入框内容 - if (!isValidDate(val, formatRef.value.format)) return; - cacheValue.value = val; - const newMonth = parseToDayjs(val, formatRef.value.format).month(); - const newYear = parseToDayjs(val, formatRef.value.format).year(); - const newTime = formatTime(val, formatRef.value.format, formatRef.value.timeFormat, props.defaultTime); - !Number.isNaN(newYear) && (year.value = newYear); - !Number.isNaN(newMonth) && (month.value = newMonth); - !Number.isNaN(newTime) && (time.value = newTime); - }, - onEnter: (val: string) => { - if (!val) { - onChange('', { dayjsValue: dayjs(), trigger: 'enter' }); + class: [ + { + [`${COMPONENT_NAME.value}__input--placeholder`]: isHoverCell.value, + }, + ], + onClear: (context: { e: InputEvent }) => { + context?.e?.stopPropagation(); popupVisible.value = false; - return; - } + onChange?.('', { dayjsValue: dayjs(), trigger: 'clear' }); + }, + onBlur: (val: string, context: { e: FocusEvent }) => { + props.onBlur?.({ value: val, e: context.e }); + }, + onFocus: (_: string, { e }: { e: FocusEvent }) => { + props.onFocus?.({ value: value.value, e }); + }, + onChange: (val: string) => { + // 输入事件 + inputValue.value = val; - if (!isValidDate(val, formatRef.value.format) && !isValidDate(value.value, formatRef.value.format)) return; + // 跳过不符合格式化的输入框内容 + if (!isValidDate(val, formatRef.value.format)) return; + cacheValue.value = val; + const newMonth = parseToDayjs(val, formatRef.value.format).month(); + const newYear = parseToDayjs(val, formatRef.value.format).year(); + const newTime = formatTime(val, formatRef.value.format, formatRef.value.timeFormat, props.defaultTime); + !Number.isNaN(newYear) && (year.value = newYear); + !Number.isNaN(newMonth) && (month.value = newMonth); + !Number.isNaN(newTime) && (time.value = newTime); + }, + onEnter: (val: string) => { + if (!val) { + onChange('', { dayjsValue: dayjs(), trigger: 'enter' }); + popupVisible.value = false; + return; + } + + if (!isValidDate(val, formatRef.value.format) && !isValidDate(value.value, formatRef.value.format)) return; + + popupVisible.value = false; + if (isValidDate(val, formatRef.value.format)) { + onChange?.( + formatDate(val, { format: formatRef.value.format, targetFormat: formatRef.value.valueType }) as DateValue, + { + dayjsValue: parseToDayjs(val, formatRef.value.format), + trigger: 'enter', + }, + ); + } else if (isValidDate(value.value, formatRef.value.format)) { + inputValue.value = formatDate(value.value, { + format: formatRef.value.format, + }); + } else { + inputValue.value = ''; + } + }, + }; + return props.multiple ? omit(defaultInputProps, ['ref', 'class']) : defaultInputProps; + }); - popupVisible.value = false; - if (isValidDate(val, formatRef.value.format)) { - onChange?.( - formatDate(val, { format: formatRef.value.format, targetFormat: formatRef.value.valueType }) as DateValue, - { - dayjsValue: parseToDayjs(val, formatRef.value.format), - trigger: 'enter', - }, - ); - } else if (isValidDate(value.value, formatRef.value.format)) { - inputValue.value = formatDate(value.value, { - format: formatRef.value.format, - }); - } else { - inputValue.value = ''; - } - }, - })); // popup 设置 const popupProps = computed(() => ({ expandAnimation: true, ...omit(props.popupProps, 'on-visible-change'), disabled: disabled.value, + trigger: 'click', overlayInnerStyle: props.popupProps?.overlayInnerStyle ?? { width: 'auto' }, overlayClassName: [props.popupProps?.overlayClassName, `${COMPONENT_NAME.value}__panel-container`], onVisibleChange: (visible: boolean, context: any) => { diff --git a/src/date-picker/hooks/useSingleValue.tsx b/src/date-picker/hooks/useSingleValue.tsx index dfe9eb05ba..1af4f2ef9c 100644 --- a/src/date-picker/hooks/useSingleValue.tsx +++ b/src/date-picker/hooks/useSingleValue.tsx @@ -7,7 +7,7 @@ import { parseToDayjs, } from '../../_common/js/date-picker/format'; import useVModel from '../../hooks/useVModel'; -import { TdDatePickerProps } from '../type'; +import { TdDatePickerProps, DateMultipleValue, DateValue } from '../type'; import { extractTimeFormat } from '../../_common/js/date-picker/utils'; export default function useSingleValue(props: TdDatePickerProps) { @@ -18,7 +18,7 @@ export default function useSingleValue(props: TdDatePickerProps) { getDefaultFormat({ mode: props.mode, format: props.format, - enableTimePicker: props.enableTimePicker, + enableTimePicker: props.multiple ? false : props.enableTimePicker, }), ); @@ -27,10 +27,31 @@ export default function useSingleValue(props: TdDatePickerProps) { console.error(`format: ${formatRef.value.format} 不规范,包含时间选择必须要有时间格式化 HH:mm:ss`); } - const time = ref(formatTime(value.value, formatRef.value.format, formatRef.value.timeFormat, props.defaultTime)); - const month = ref(parseToDayjs(value.value, formatRef.value.format).month()); - const year = ref(parseToDayjs(value.value, formatRef.value.format).year()); - const cacheValue = ref(formatDate(value.value, { format: formatRef.value.format })); // 缓存选中值,panel 点击时更改 + const time = ref( + formatTime( + props.multiple ? (value.value as DateMultipleValue)[0] : value.value, + formatRef.value.format, + formatRef.value.timeFormat, + props.defaultTime, + ), + ); + const month = ref( + parseToDayjs( + props.multiple ? (value.value as DateMultipleValue)[0] : (value.value as DateValue), + formatRef.value.format, + ).month(), + ); + const year = ref( + parseToDayjs( + props.multiple ? (value.value as DateMultipleValue)[0] : (value.value as DateValue), + formatRef.value.format, + ).year(), + ); + const cacheValue = ref( + formatDate(props.multiple ? (value.value as DateMultipleValue)[0] : value.value, { + format: formatRef.value.format, + }), + ); // 缓存选中值,panel 点击时更改 // 输入框响应 value 变化 watchEffect(() => { diff --git a/src/date-picker/panel/SinglePanel.tsx b/src/date-picker/panel/SinglePanel.tsx index 1f15e5ca00..4e4012d216 100644 --- a/src/date-picker/panel/SinglePanel.tsx +++ b/src/date-picker/panel/SinglePanel.tsx @@ -2,11 +2,12 @@ import { defineComponent, PropType, computed } from 'vue'; import { useConfig, usePrefixClass } from '../../hooks/useConfig'; import TPanelContent from './PanelContent'; import TExtraContent from './ExtraContent'; -import { TdDatePickerProps } from '../type'; import { getDefaultFormat, parseToDayjs } from '../../_common/js/date-picker/format'; import useTableData from '../hooks/useTableData'; import useDisableDate from '../hooks/useDisableDate'; +import type { TdDatePickerProps, DateMultipleValue, DateValue } from '../type'; + export default defineComponent({ name: 'TSinglePanel', props: { @@ -29,6 +30,7 @@ export default defineComponent({ month: Number, time: String, popupVisible: Boolean, + multiple: Boolean, needConfirm: { type: Boolean, default: true, @@ -64,14 +66,19 @@ export default defineComponent({ disableDate: props.disableDate, }), ); - const tableData = computed(() => useTableData({ year: props.year, month: props.month, mode: props.mode, - start: props.value ? parseToDayjs(props.value, format.value).toDate() : undefined, + start: props.value + ? parseToDayjs( + props.multiple ? (props.value as DateMultipleValue)[0] : (props.value as DateValue), + format.value, + ).toDate() + : undefined, firstDayOfWeek: props.firstDayOfWeek || globalConfig.value.firstDayOfWeek, + multiple: props.multiple, ...disableDateOptions.value, }), ); @@ -85,7 +92,7 @@ export default defineComponent({ firstDayOfWeek: props.firstDayOfWeek || globalConfig.value.firstDayOfWeek, tableData: tableData.value, popupVisible: props.popupVisible, - + multiple: props.multiple, enableTimePicker: props.enableTimePicker, timePickerProps: props.timePickerProps, time: props.time, diff --git a/src/date-picker/type.ts b/src/date-picker/type.ts index a7f3166e8d..020adc5058 100644 --- a/src/date-picker/type.ts +++ b/src/date-picker/type.ts @@ -367,7 +367,6 @@ export interface TdDatePickerPanelProps | 'value' | 'defaultValue' | 'disableDate' - | 'disableTime' | 'enableTimePicker' | 'firstDayOfWeek' | 'format' diff --git a/src/tag-input/tag-input.tsx b/src/tag-input/tag-input.tsx index 88b7d88dba..c8cead580f 100644 --- a/src/tag-input/tag-input.tsx +++ b/src/tag-input/tag-input.tsx @@ -123,7 +123,7 @@ export default defineComponent({ const onClick: TdInputProps['onClick'] = (ctx) => { if (isDisabled.value) return; isFocused.value = true; - tagInputRef.value.focus(); + tagInputRef.value?.focus(); props.onClick?.(ctx); };