Skip to content

Commit

Permalink
feat(DatePicker): support multiple API (#3407)
Browse files Browse the repository at this point in the history
* fix(datepicker): 增加多选模式

fix #3341

* refactor(datepicker): 修复props类型告警

* chore: update snapshot and fix type

* chore: update snapshot and fix type

* chore: fix render

* chore: fix type

* chore: fix

* chore: fix

* chore: update snapshot and fix type

---------

Co-authored-by: Uyarn <[email protected]>
  • Loading branch information
hkaikai and uyarn authored Dec 5, 2024
1 parent 33e9a50 commit cb99d34
Show file tree
Hide file tree
Showing 22 changed files with 378 additions and 147 deletions.
4 changes: 1 addition & 3 deletions src/avatar/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { ImageProps } from '../image';
import { PopupProps } from '../popup';
import { TNode } from '../common';
import { TNode, ShapeEnum } from '../common';

export interface TdAvatarProps {
/**
Expand Down Expand Up @@ -81,6 +81,4 @@ export interface TdAvatarGroupProps {
size?: string;
}

export type ShapeEnum = 'circle' | 'round';

export type CascadingValue = 'left-up' | 'right-up';
2 changes: 2 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export type TreeOptionData<T = string | number> = {

export type SizeEnum = 'small' | 'medium' | 'large';

export type ShapeEnum = 'circle' | 'round';

export type HorizontalAlignEnum = 'left' | 'center' | 'right';

export type VerticalAlignEnum = 'top' | 'middle' | 'bottom';
Expand Down
84 changes: 73 additions & 11 deletions src/date-picker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,30 @@ import dayjs from 'dayjs';
import { CalendarIcon as TdCalendarIcon } from 'tdesign-icons-vue';
import isDate from 'lodash/isDate';

import { usePrefixClass } from '../hooks/useConfig';
import { usePrefixClass, useConfig } from '../hooks/useConfig';
import { useGlobalIcon } from '../hooks/useGlobalIcon';
import useSingle from './hooks/useSingle';
import {
parseToDayjs, getDefaultFormat, formatTime, formatDate,
} from '../_common/js/date-picker/format';
import {
subtractMonth, addMonth, extractTimeObj, covertToDate,
subtractMonth, addMonth, extractTimeObj, covertToDate, isSame,
} from '../_common/js/date-picker/utils';
import type { DateValue } from './type';
import type { DateMultipleValue, DateValue } from './type';
import props from './props';

import TSelectInput from '../select-input';
import TSinglePanel from './panel/SinglePanel';
import useFormDisabled from '../hooks/useFormDisabled';
import type { TagInputRemoveContext } from '../tag-input';

export default defineComponent({
name: 'TDatePicker',
props,
setup(props, { emit }) {
const COMPONENT_NAME = usePrefixClass('date-picker');
const { CalendarIcon } = useGlobalIcon({ CalendarIcon: TdCalendarIcon });
const { global } = useConfig('datePicker');

const {
inputValue,
Expand All @@ -45,13 +47,14 @@ export default defineComponent({
mode: props.mode,
format: props.format,
valueType: props.valueType,
enableTimePicker: props.enableTimePicker,
enableTimePicker: props.multiple ? false : props.enableTimePicker,
}));

const { formDisabled } = useFormDisabled();
const isDisabled = computed(() => formDisabled.value || props.disabled);

watch(popupVisible, (visible) => {
if (props.multiple) return;
// Date valueType、week mode 、quarter mode nad empty string don't need to be parsed
const dateValue = value.value && !isDate(value.value) && !['week', 'quarter'].includes(props.mode)
? covertToDate(value.value as string, formatRef.value?.valueType)
Expand All @@ -66,8 +69,8 @@ export default defineComponent({

// 面板展开重置数据
if (visible) {
year.value = parseToDayjs(value.value, formatRef.value.format).year();
month.value = parseToDayjs(value.value, formatRef.value.format).month();
year.value = parseToDayjs(value.value as DateValue, formatRef.value.format).year();
month.value = parseToDayjs(value.value as DateValue, formatRef.value.format).month();
time.value = formatTime(value.value, formatRef.value.format, formatRef.value.timeFormat, props.defaultTime);
} else {
isHoverCell.value = false;
Expand All @@ -76,6 +79,7 @@ export default defineComponent({

// 日期 hover
function onCellMouseEnter(date: Date) {
if (props.multiple) return;
isHoverCell.value = true;
inputValue.value = formatDate(date, {
format: formatRef.value.format,
Expand All @@ -84,6 +88,7 @@ export default defineComponent({

// 日期 leave
function onCellMouseLeave() {
if (props.multiple) return;
isHoverCell.value = false;
inputValue.value = formatDate(cacheValue.value, {
format: formatRef.value.format,
Expand All @@ -103,6 +108,14 @@ export default defineComponent({
format: formatRef.value.format,
});
} else {
if (props.multiple) {
const newDate = processDate(date);
onChange?.(newDate, {
dayjsValue: parseToDayjs(date, formatRef.value.format),
trigger: 'pick',
});
return;
}
onChange?.(
formatDate(date, {
format: formatRef.value.format,
Expand Down Expand Up @@ -228,21 +241,56 @@ export default defineComponent({
month.value = nextMonth;
}

function processDate(date: Date) {
const val = value.value as DateMultipleValue;
const isSameDate = val.some((val) => isSame(dayjs(val).toDate(), date));
let currentDate: DateMultipleValue;

if (!isSameDate) {
currentDate = val.concat(
formatDate(date, { format: formatRef.value.format, targetFormat: formatRef.value.valueType }),
);
} else {
currentDate = val.filter(
(val) => formatDate(val, { format: formatRef.value.format, targetFormat: formatRef.value.valueType })
!== formatDate(date, { format: formatRef.value.format, targetFormat: formatRef.value.valueType }),
);
}

return currentDate.sort((a, b) => dayjs(a).valueOf() - dayjs(b).valueOf());
}

const onTagRemoveClick = (ctx: TagInputRemoveContext) => {
const removeDate = dayjs(ctx.item).toDate();
const newDate = processDate(removeDate);
onChange?.(newDate, {
dayjsValue: parseToDayjs(removeDate, formatRef.value.format),
trigger: 'tag-remove',
});
};

const onTagClearClick = ({ e }: { e: MouseEvent }) => {
e.stopPropagation();
popupVisible.value = false;
onChange?.([], { dayjsValue: dayjs(), trigger: 'clear' });
};

const panelProps: any = computed(() => ({
value: cacheValue.value as string,
value: cacheValue.value,
year: year.value,
month: month.value,
format: formatRef.value.format,
mode: props.mode,
presets: props.presets,
time: time.value as string,
time: props.multiple ? '' : time.value,
disableDate: props.disableDate,
disableTime: props.disableTime,
firstDayOfWeek: props.firstDayOfWeek,
timePickerProps: props.timePickerProps,
enableTimePicker: props.enableTimePicker,
enableTimePicker: props.multiple ? false : props.enableTimePicker,
presetsPlacement: props.presetsPlacement,
popupVisible: popupVisible.value,
multiple: props.multiple,
onCellClick,
onCellMouseEnter,
onCellMouseLeave,
Expand All @@ -263,7 +311,10 @@ export default defineComponent({
popupVisible,
panelProps,
isDisabled,
onTagRemoveClick,
onTagClearClick,
CalendarIcon,
global,
};
},
render() {
Expand All @@ -275,6 +326,8 @@ export default defineComponent({
popupVisible,
panelProps,
isDisabled,
onTagRemoveClick,
onTagClearClick,
CalendarIcon,
} = this;

Expand All @@ -292,16 +345,25 @@ export default defineComponent({
disabled={isDisabled}
readonly={this.readonly}
value={inputValue}
inputValue={inputValue}
inputValue={this.multiple ? '' : inputValue}
label={this.label}
status={this.status}
tips={this.tips}
popupProps={datePickerPopupProps}
inputProps={{ suffixIcon: renderSuffixIcon(), ...datePickerInputProps }}
inputProps={{ ...datePickerInputProps }}
popupVisible={popupVisible}
clearable={this.clearable}
allowInput={this.allowInput && !this.readonly}
panel={() => <TSinglePanel {...{ props: panelProps }} />}
multiple={this.multiple}
placeholder={
this.placeholder ?? (this.global.placeholder as { [key in typeof this.mode]: string })[this.mode]
}
suffixIcon={renderSuffixIcon()}
tagInputProps={{
onRemove: onTagRemoveClick,
}}
onClear={onTagClearClick}
/>
</div>
);
Expand Down
22 changes: 11 additions & 11 deletions src/date-picker/DatePickerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default defineComponent({
}

// 头部快速切换
function onJumperClick({ trigger }: { trigger: string }) {
function onJumperClick({ trigger }: { trigger: 'prev' | 'next' | 'current' }) {
const triggerMap = {
prev: 'arrow-previous',
next: 'arrow-next',
Expand Down Expand Up @@ -95,24 +95,24 @@ export default defineComponent({
if (year.value !== nextYear) {
props.onYearChange?.({
year: nextYear,
date: dayjs(value.value).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: trigger === 'current' ? 'today' : (`year-${triggerMap[trigger]}` as DatePickerYearChangeTrigger),
});
emit('year-change', {
year: nextYear,
date: dayjs(value.value).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: trigger === 'current' ? 'today' : (`year-${triggerMap[trigger]}` as DatePickerYearChangeTrigger),
});
}
if (month.value !== nextMonth) {
props.onMonthChange?.({
month: nextMonth,
date: dayjs(value.value).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: trigger === 'current' ? 'today' : (`month-${triggerMap[trigger]}` as DatePickerMonthChangeTrigger),
});
emit('month-change', {
month: nextMonth,
date: dayjs(value.value).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: trigger === 'current' ? 'today' : (`month-${triggerMap[trigger]}` as DatePickerMonthChangeTrigger),
});
}
Expand Down Expand Up @@ -142,12 +142,12 @@ export default defineComponent({

props.onTimeChange?.({
time: val,
date: parseToDayjs(value.value, formatRef.value.format).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: 'time-hour',
});
emit('time-change', {
time: val,
date: parseToDayjs(value.value, formatRef.value.format).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: 'time-hour',
});
}
Expand Down Expand Up @@ -183,12 +183,12 @@ export default defineComponent({

props.onYearChange?.({
year: year.value,
date: parseToDayjs(value.value, formatRef.value.format).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: 'year-select',
});
emit('year-change', {
year: year.value,
date: parseToDayjs(value.value, formatRef.value.format).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: 'year-select',
});
}
Expand All @@ -198,12 +198,12 @@ export default defineComponent({

props.onMonthChange?.({
month: month.value,
date: parseToDayjs(value.value, formatRef.value.format).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: 'month-select',
});
emit('month-change', {
month: month.value,
date: parseToDayjs(value.value, formatRef.value.format).toDate(),
date: parseToDayjs(value.value as DateValue, formatRef.value.format).toDate(),
trigger: 'month-select',
});
}
Expand Down
3 changes: 2 additions & 1 deletion src/date-picker/DateRangePickerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import TRangePanel from './panel/RangePanel';
import useRangeValue from './hooks/useRangeValue';
import { formatDate, getDefaultFormat, parseToDayjs } from '../_common/js/date-picker/format';
import { subtractMonth, addMonth, extractTimeObj } from '../_common/js/date-picker/utils';
import type { TdDateRangePickerProps } from './type';
import { dateCorrection } from './utils';

export default defineComponent({
Expand All @@ -37,7 +38,7 @@ export default defineComponent({
panelPreselection: dateRangePickerProps.panelPreselection,
...dateRangePickerPanelProps,
},
setup(props: TdDateRangePickerPanelProps, { emit, attrs }) {
setup(props: TdDateRangePickerPanelProps & { disableTime: TdDateRangePickerProps['disableTime'] }, { emit, attrs }) {
const {
value, year, month, time, cacheValue, isFirstValueSelected, onChange,
} = useRangeValue(props);
Expand Down
16 changes: 16 additions & 0 deletions src/date-picker/_example-composition/multiple.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<t-space direction="vertical">
<t-date-picker v-model="date" placeholder="可清除、可输入的日期选择器" clearable multiple @change="handleChange" />
</t-space>
</template>

<script setup>
import { ref } from 'vue';
const date = ref(['2000-01-04', '2000-01-03', '2000-01-05']);
const handleChange = (value, context) => {
console.log('onChange:', value, context);
console.log('timestamp:', context.dayjsValue.valueOf());
console.log('YYYYMMDD:', context.dayjsValue.format('YYYYMMDD'));
};
</script>
2 changes: 1 addition & 1 deletion src/date-picker/_example/base.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<t-space direction="vertical">
<t-date-picker v-model="date2" @change="handleChange" :firstDayOfWeek="3" :inputProps="inputProps" />
<t-date-picker v-model="date2" @change="handleChange" :firstDayOfWeek="3" :inputProps="inputProps" readonly />
<t-date-picker
v-model="date"
placeholder="可清除、可输入的日期选择器"
Expand Down
22 changes: 22 additions & 0 deletions src/date-picker/_example/multiple.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<t-space direction="vertical">
<t-date-picker v-model="date" placeholder="支持日期的多选" clearable multiple @change="handleChange" />
</t-space>
</template>

<script>
export default {
data() {
return {
date: ['2024-01-04'],
};
},
methods: {
handleChange(value, context) {
console.log('onChange:', value, context);
console.log('timestamp:', context.dayjsValue.valueOf());
console.log('YYYYMMDD:', context.dayjsValue.format('YYYYMMDD'));
},
},
};
</script>
Loading

0 comments on commit cb99d34

Please sign in to comment.