diff --git a/packages/devui-vue/devui/date-picker-pro/__tests__/date-picker-pro.spec.tsx b/packages/devui-vue/devui/date-picker-pro/__tests__/date-picker-pro.spec.tsx index 95efb3f013..657ae514fa 100644 --- a/packages/devui-vue/devui/date-picker-pro/__tests__/date-picker-pro.spec.tsx +++ b/packages/devui-vue/devui/date-picker-pro/__tests__/date-picker-pro.spec.tsx @@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'; import dayjs from 'dayjs'; import DDatePickerPro from '../src/date-picker-pro'; import { nextTick, ref, getCurrentInstance } from 'vue'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import DButton from '../../button/src/button'; import { Locale } from '../../locale'; import { getDateIndex, getSelectedDate, getSelectedIndex } from './utils'; diff --git a/packages/devui-vue/devui/date-picker-pro/__tests__/month-picker.spec.tsx b/packages/devui-vue/devui/date-picker-pro/__tests__/month-picker.spec.tsx index 8a5af37ac1..9aa5370093 100644 --- a/packages/devui-vue/devui/date-picker-pro/__tests__/month-picker.spec.tsx +++ b/packages/devui-vue/devui/date-picker-pro/__tests__/month-picker.spec.tsx @@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'; import DDatePickerPro from '../src/date-picker-pro'; import DRangeDatePickerPro from '../src/components/range-date-picker-pro'; import { nextTick, ref, getCurrentInstance } from 'vue'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import { Locale } from '../../locale'; const ns = useNamespace('date-picker-pro', true); diff --git a/packages/devui-vue/devui/date-picker-pro/__tests__/range-date-picker-pro.spec.tsx b/packages/devui-vue/devui/date-picker-pro/__tests__/range-date-picker-pro.spec.tsx index 1e681ccd6a..41f9521832 100644 --- a/packages/devui-vue/devui/date-picker-pro/__tests__/range-date-picker-pro.spec.tsx +++ b/packages/devui-vue/devui/date-picker-pro/__tests__/range-date-picker-pro.spec.tsx @@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'; import dayjs from 'dayjs'; import DRangeDatePickerPro from '../src/components/range-date-picker-pro'; import { nextTick, ref, getCurrentInstance } from 'vue'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import DButton from '../../button/src/button'; import { Locale } from '../../locale'; import { getDateIndex, getSelectedDate, getSelectedIndex } from './utils'; diff --git a/packages/devui-vue/devui/date-picker-pro/__tests__/utils.ts b/packages/devui-vue/devui/date-picker-pro/__tests__/utils.ts index 2f4ba9fedf..017f5e685c 100644 --- a/packages/devui-vue/devui/date-picker-pro/__tests__/utils.ts +++ b/packages/devui-vue/devui/date-picker-pro/__tests__/utils.ts @@ -6,9 +6,9 @@ export const getDateIndex = (date: Date): number => { }; export const getSelectedIndex = (todayIndex: number, intervalDay = 1): number => { - return todayIndex > 20 ? todayIndex : todayIndex + intervalDay; + return todayIndex > 20 ? todayIndex - 1 : todayIndex + intervalDay; }; export const getSelectedDate = (todayIndex: number, date: Date, intervalDay = 1): string => { - return todayIndex > 20 ? dayjs(date).format(DATE_FORMAT) : dayjs(date).add(intervalDay, 'day').format(DATE_FORMAT); + return todayIndex > 20 ? dayjs(date).subtract(1, 'day').format(DATE_FORMAT) : dayjs(date).add(intervalDay, 'day').format(DATE_FORMAT); }; diff --git a/packages/devui-vue/devui/date-picker-pro/__tests__/year-picker.spec.tsx b/packages/devui-vue/devui/date-picker-pro/__tests__/year-picker.spec.tsx index 659293c712..879ae46c2d 100644 --- a/packages/devui-vue/devui/date-picker-pro/__tests__/year-picker.spec.tsx +++ b/packages/devui-vue/devui/date-picker-pro/__tests__/year-picker.spec.tsx @@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'; import DDatePickerPro from '../src/date-picker-pro'; import DRangeDatePickerPro from '../src/components/range-date-picker-pro'; import { nextTick, ref, getCurrentInstance } from 'vue'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import { Locale } from '../../locale'; const ns = useNamespace('date-picker-pro', true); diff --git a/packages/devui-vue/devui/date-picker-pro/src/components/calendar-panel.tsx b/packages/devui-vue/devui/date-picker-pro/src/components/calendar-panel.tsx index 1a50454e5d..77b09dffbe 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/components/calendar-panel.tsx +++ b/packages/devui-vue/devui/date-picker-pro/src/components/calendar-panel.tsx @@ -1,11 +1,13 @@ import { defineComponent, getCurrentInstance } from 'vue'; import type { SetupContext } from 'vue'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; +import { RecycleScroller } from 'vue-virtual-scroller' import useCalendarPanel from '../composables/use-calendar-panel'; import { YearAndMonthItem, datePickerProPanelProps, DatePickerProPanelProps } from '../date-picker-pro-types'; import { createI18nTranslate } from '../../../locale/create'; import { yearListHeight, yearItemHeight, calendarListHeight, calendarItemHeight } from '../const'; import { VirtualList } from '../../../virtual-list'; +import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' export default defineComponent({ name: 'CalendarPanel', @@ -34,7 +36,8 @@ export default defineComponent({ } = useCalendarPanel(props, ctx); return () => { const yearItemSlots = { - item: (item: YearAndMonthItem) => { + default: (scoped: any) => { + const item = scoped.item; return (
{!item.isMonth && !isListCollapse.value && ( @@ -106,13 +109,14 @@ export default defineComponent({ return (
- +
+ +
diff --git a/packages/devui-vue/devui/date-picker-pro/src/components/date-picker-panel.tsx b/packages/devui-vue/devui/date-picker-pro/src/components/date-picker-panel.tsx index ebcaa293c0..f3d00987b8 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/components/date-picker-panel.tsx +++ b/packages/devui-vue/devui/date-picker-pro/src/components/date-picker-panel.tsx @@ -1,6 +1,6 @@ import { defineComponent, getCurrentInstance } from 'vue'; import type { SetupContext } from 'vue'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import CalendarPanel from './calendar-panel'; import TimerPickerPanel from './time-picker-panel'; import { Button } from '../../../button'; diff --git a/packages/devui-vue/devui/date-picker-pro/src/components/month-calendar-panel.tsx b/packages/devui-vue/devui/date-picker-pro/src/components/month-calendar-panel.tsx index b8a8826438..393a465de3 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/components/month-calendar-panel.tsx +++ b/packages/devui-vue/devui/date-picker-pro/src/components/month-calendar-panel.tsx @@ -1,6 +1,6 @@ import { defineComponent, getCurrentInstance } from 'vue'; import type { SetupContext } from 'vue'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import useMonthCalendarPanel from '../composables/use-month-calendar-panel'; import { datePickerProPanelProps, DatePickerProPanelProps, YearAndMonthItem } from '../date-picker-pro-types'; import { createI18nTranslate } from '../../../locale/create'; diff --git a/packages/devui-vue/devui/date-picker-pro/src/components/range-date-picker-pro.tsx b/packages/devui-vue/devui/date-picker-pro/src/components/range-date-picker-pro.tsx index 0ba472323f..40dcb86069 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/components/range-date-picker-pro.tsx +++ b/packages/devui-vue/devui/date-picker-pro/src/components/range-date-picker-pro.tsx @@ -1,4 +1,4 @@ -import { defineComponent, Transition, ref, renderSlot, useSlots, getCurrentInstance, Teleport, withModifiers } from 'vue'; +import { defineComponent, Transition, ref, renderSlot, useSlots, getCurrentInstance, Teleport, withModifiers, toRefs } from 'vue'; import type { SetupContext } from 'vue'; import { rangeDatePickerProProps, RangeDatePickerProProps } from '../range-date-picker-types'; import { FlexibleOverlay } from '../../../overlay'; @@ -6,7 +6,7 @@ import DatePickerProPanel from './date-picker-panel'; import { Input } from '../../../input'; import { IconCalendar } from './icon-calendar'; import { IconClose } from './icon-close'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import useRangePickerPro from '../composables/use-range-date-picker-pro'; import '../date-picker-pro.scss'; @@ -19,6 +19,7 @@ export default defineComponent({ setup(props: RangeDatePickerProProps, ctx: SetupContext) { const app = getCurrentInstance(); const t = createI18nTranslate('DDatePickerPro', app); + const { position } = toRefs(props) const ns = useNamespace('range-date-picker-pro'); const { @@ -42,8 +43,8 @@ export default defineComponent({ onSelectedDate, handlerClearTime, onChangeRangeFocusType, + onStartInputChange, } = useRangePickerPro(props, ctx); - const position = ref(['bottom-start', 'top-start']); return () => { const vSlots = { @@ -56,8 +57,8 @@ export default defineComponent({
(isMouseEnter.value = true)} - onMouseout={() => (isMouseEnter.value = false)}> + onMouseenter={() => (isMouseEnter.value = true)} + onMouseleave={() => (isMouseEnter.value = false)}> onStartInputChange(val, 'start')} + validate-event={false} onFocus={withModifiers( (e: MouseEvent) => { onFocus('start'); @@ -98,6 +101,8 @@ export default defineComponent({ ref={endInputRef} modelValue={displayDateValue.value[1]} placeholder={placeholder.value[1] || t('endPlaceholder')} + onUpate:modelValue={(val) => onStartInputChange(val, 'end')} + validate-event={false} onFocus={withModifiers( (e: MouseEvent) => { onFocus('end'); @@ -109,11 +114,11 @@ export default defineComponent({ disabled={pickerDisabled.value} v-slots={{ suffix: () => ( - - - + showCloseIcon.value ? + + + : null ), }} /> diff --git a/packages/devui-vue/devui/date-picker-pro/src/components/time-picker-panel.tsx b/packages/devui-vue/devui/date-picker-pro/src/components/time-picker-panel.tsx index 42f097aae7..13e5ea1f41 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/components/time-picker-panel.tsx +++ b/packages/devui-vue/devui/date-picker-pro/src/components/time-picker-panel.tsx @@ -1,6 +1,6 @@ import { defineComponent, getCurrentInstance } from 'vue'; import type { SetupContext } from 'vue'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import DTimeList from '../../../time-picker/src/components/popup-line'; import useTimePickerPanel from '../composables/use-time-picker-panel'; import { TimerPickerPanelProps, timerPickerPanelProps } from '../date-picker-pro-types'; diff --git a/packages/devui-vue/devui/date-picker-pro/src/components/year-calendar-panel.tsx b/packages/devui-vue/devui/date-picker-pro/src/components/year-calendar-panel.tsx index c4a82e22e1..1d1fc332e5 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/components/year-calendar-panel.tsx +++ b/packages/devui-vue/devui/date-picker-pro/src/components/year-calendar-panel.tsx @@ -1,6 +1,6 @@ import { defineComponent } from 'vue'; import type { SetupContext } from 'vue'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import useYearCalendarPanel from '../composables/use-year-calendar-panel'; import { datePickerProPanelProps, DatePickerProPanelProps } from '../date-picker-pro-types'; import { yearPickerHeight, yearCalendarItemHeight } from '../const'; diff --git a/packages/devui-vue/devui/date-picker-pro/src/composables/use-calendar-panel.ts b/packages/devui-vue/devui/date-picker-pro/src/composables/use-calendar-panel.ts index a68bd21e4e..a316a35325 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/composables/use-calendar-panel.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/composables/use-calendar-panel.ts @@ -1,10 +1,11 @@ import { ref, onBeforeMount, nextTick, watch, onMounted } from 'vue'; import type { SetupContext } from 'vue'; +import { v4 as uuidv4 } from 'uuid' import { DAY_DURATION, calendarItemHeight } from '../const'; import { CalendarDateItem, YearAndMonthItem, UseCalendarPanelReturnType, DatePickerProPanelProps } from '../date-picker-pro-types'; import dayjs from 'dayjs'; import type { Dayjs } from 'dayjs'; -import { throttle } from 'lodash'; +import throttle from 'lodash/throttle'; import useCalendarSelected from './use-calendar-selected'; export default function useCalendarPanel(props: DatePickerProPanelProps, ctx: SetupContext): UseCalendarPanelReturnType { @@ -68,6 +69,7 @@ export default function useCalendarPanel(props: DatePickerProPanelProps, ctx: Se for (let year = calendarRange.value[0]; year <= calendarRange.value[1]; year++) { const yearOption: YearAndMonthItem = { + id: uuidv4(), year, isMonth: false, active: false, @@ -81,6 +83,7 @@ export default function useCalendarPanel(props: DatePickerProPanelProps, ctx: Se }; allMonthList.value.push(monthOption); const yearMonthOption: YearAndMonthItem = { + id: uuidv4(), year, month, isMonth: true, @@ -121,7 +124,7 @@ export default function useCalendarPanel(props: DatePickerProPanelProps, ctx: Se } nextTick(() => { const scrollEl = yearScrollRef.value; - scrollEl?.scrollTo?.(scrollIndex); + scrollEl?.scrollToItem?.(scrollIndex); }); }; @@ -149,7 +152,9 @@ export default function useCalendarPanel(props: DatePickerProPanelProps, ctx: Se } const toDate = getToDate(props.dateValue); if (toDate) { - goToShowDate(toDate.toDate()); + nextTick(() => { + goToShowDate(toDate.toDate()); + }) } }; @@ -202,7 +207,12 @@ export default function useCalendarPanel(props: DatePickerProPanelProps, ctx: Se const selectedMonth = activeItem?.month; isListCollapse.value = !isListCollapse.value; if (isListCollapse.value) { - yearAndMonthList.value = yearAndMonthList.value.filter((child) => !child.isMonth); + yearAndMonthList.value = yearAndMonthList.value + .map((child) => { + child.active = false; + return child; + }) + .filter((child) => !child.isMonth); } else { initCalendarData(); } diff --git a/packages/devui-vue/devui/date-picker-pro/src/composables/use-month-calendar-panel.ts b/packages/devui-vue/devui/date-picker-pro/src/composables/use-month-calendar-panel.ts index dfe333200c..8bdd6af904 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/composables/use-month-calendar-panel.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/composables/use-month-calendar-panel.ts @@ -1,9 +1,9 @@ import { ref, onBeforeMount, nextTick, watch } from 'vue'; import type { SetupContext } from 'vue'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import { monthCalendarItemHeight } from '../const'; import { DatePickerProPanelProps, YearAndMonthItem, UseMonthCalendarPanelReturnType } from '../date-picker-pro-types'; -import { throttle } from 'lodash'; +import throttle from 'lodash/throttle'; import dayjs from 'dayjs'; import type { Dayjs } from 'dayjs'; import useCalendarSelected from './use-calendar-selected'; diff --git a/packages/devui-vue/devui/date-picker-pro/src/composables/use-range-date-picker-pro.ts b/packages/devui-vue/devui/date-picker-pro/src/composables/use-range-date-picker-pro.ts index de461c1d91..0e8999cd12 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/composables/use-range-date-picker-pro.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/composables/use-range-date-picker-pro.ts @@ -1,14 +1,19 @@ -import { shallowRef, ref, computed, inject, watch } from 'vue'; +import { shallowRef, ref, computed, inject, getCurrentInstance, watch, toRefs } from 'vue'; import type { SetupContext } from 'vue'; import { RangeDatePickerProProps, UseRangePickerProReturnType } from '../range-date-picker-types'; import { onClickOutside } from '@vueuse/core'; import type { Dayjs } from 'dayjs'; -import { formatDayjsToStr, isDateEquals, parserDate } from '../utils'; +import { debounce } from '@devui/shared' +import { formatDayjsToStr, isDateEquals, parserDate, isInputDateValid } from '../utils'; import { FORM_ITEM_TOKEN, FORM_TOKEN } from '../../../form'; +import { createI18nTranslate } from '../../../locale/create'; export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: SetupContext): UseRangePickerProReturnType { const formContext = inject(FORM_TOKEN, undefined); const formItemContext = inject(FORM_ITEM_TOKEN, undefined); + const { calendarRange, limitDateRange, allowClear } = toRefs(props); + const app = getCurrentInstance() + const t = createI18nTranslate('DCommon', app) const originRef = ref(); const startInputRef = shallowRef(); @@ -22,7 +27,9 @@ export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: S const pickerDisabled = computed(() => formContext?.disabled || props.disabled); const pickerSize = computed(() => formContext?.size || props.size); const isValidateError = computed(() => formItemContext?.validateState === 'error'); - + const toggle = () => { + toggleChange(!isPanelShow.value) + } const toggleChange = (isShow: boolean) => { isPanelShow.value = isShow; ctx.emit('toggleChange', isShow); @@ -42,9 +49,16 @@ export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: S const focusHandler = function (e: MouseEvent) { ctx.emit('focus', e); }; - + const langMap: Record = { + 'zh-cn': 'YYYY/MM/DD', + 'en-us': 'MMM DD, YYYY' + } + const langTimeMap: Record = { + 'zh-cn': 'YYYY/MM/DD HH:mm:ss', + 'en-us': 'MMM DD, YYYY HH:mm:ss' + } const format = computed(() => { - return props.showTime ? props.format || 'YYYY/MM/DD HH:mm:ss' : props.format || 'YYYY/MM/DD'; + return props.showTime ? props.format || langTimeMap[t('lang')] : props.format || langMap[t('lang')]; }); const dateValue = computed(() => { @@ -60,8 +74,8 @@ export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: S }); const displayDateValue = computed(() => { - const startFormatDate = formatDayjsToStr(dateValue.value[0], format.value, props.type); - const endFormatDate = formatDayjsToStr(dateValue.value[1], format.value, props.type); + const startFormatDate = formatDayjsToStr(dateValue.value[0], format.value, props.type, t('lang')); + const endFormatDate = formatDayjsToStr(dateValue.value[1], format.value, props.type, t('lang')); if (startFormatDate) { return endFormatDate ? [startFormatDate, endFormatDate] : [startFormatDate, '']; } else if (endFormatDate) { @@ -70,19 +84,36 @@ export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: S return ['', '']; }); - const showCloseIcon = computed(() => isMouseEnter.value && (displayDateValue.value[0] !== '' || displayDateValue.value[1] !== '')); + + const showCloseIcon = computed( + () => + !pickerDisabled.value && + isMouseEnter.value && + (displayDateValue.value[0] !== '' || displayDateValue.value[1] !== '') && + allowClear.value + ); const onSelectedDate = (date: Dayjs[], isConfirm?: boolean) => { const [startDate, endDate] = date; const selectStart = startDate ? startDate.toDate() : startDate; const selectEnd = endDate ? endDate.toDate() : endDate; const [start, end] = props.modelValue; + const temp: any[] = [] + if (selectStart) { + temp.push(selectStart) + if (selectEnd) { + temp.push(selectEnd) + } + } else if (selectEnd) { + temp.push('') + temp.push(selectEnd) + } if (!isDateEquals(start, selectStart) || !isDateEquals(end, selectEnd)) { - ctx.emit('update:modelValue', [selectStart ? selectStart : '', selectEnd ? selectEnd : '']); + ctx.emit('update:modelValue', temp); } if (isConfirm) { // 回调参数为Date类型 - ctx.emit('confirmEvent', [selectStart ? selectStart : '', selectEnd ? selectEnd : '']); + ctx.emit('confirmEvent', temp); toggleChange(false); } }; @@ -106,8 +137,8 @@ export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: S } e.stopPropagation(); e.preventDefault(); - ctx.emit('update:modelValue', ['', '']); - ctx.emit('confirmEvent', ['', '']); + ctx.emit('update:modelValue', []); + ctx.emit('confirmEvent', []); // 当面板未关闭时,清空后focusType置位start if (isPanelShow.value) { onChangeRangeFocusType('start'); @@ -115,6 +146,7 @@ export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: S }; ctx.expose({ + toggle, focusChange: onChangeRangeFocusType, }); @@ -125,11 +157,29 @@ export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: S onChangeRangeFocusType(type); toggleChange(true); }; + const onStartInputChange = debounce((val: string, type: 'start' | 'end') => { + isInputDateValid(val, format.value, calendarRange.value, limitDateRange?.value, (validDate: string) => { + const currentDate = [...props.modelValue]; + if (type === 'start') { + currentDate[0] = validDate; + } else { + currentDate[0] = currentDate[0] ?? ''; + currentDate[1] = validDate; + } + ctx.emit('update:modelValue', currentDate) + }); + }, 300); + + watch(isPanelShow, (show) => { + if (!show) { + formItemContext?.validate('blur').catch(() => { }) + } + }); watch( () => props.modelValue, () => { - formItemContext?.validate('change').catch((err) => console.warn(err)); + formItemContext?.validate('change').catch(() => { }); }, { deep: true } ); @@ -155,5 +205,6 @@ export default function useRangePickerPro(props: RangeDatePickerProProps, ctx: S onSelectedDate, handlerClearTime, onChangeRangeFocusType, + onStartInputChange, }; } diff --git a/packages/devui-vue/devui/date-picker-pro/src/composables/use-time-picker-panel.ts b/packages/devui-vue/devui/date-picker-pro/src/composables/use-time-picker-panel.ts index 61e92f9e71..0785eda16e 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/composables/use-time-picker-panel.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/composables/use-time-picker-panel.ts @@ -19,6 +19,11 @@ export default function useTimePickerPanel(props: TimerPickerPanelProps, ctx: Se watch( () => [props.visible, props.bindData], ([visible, newTimeVal], [, oldTimeVal]) => { + const reg = /(\d{2}):(\d{2}):(\d{2})/; + if (typeof newTimeVal === 'string') { + const result = newTimeVal.match(reg); + newTimeVal = result![0]; + } if (newTimeVal && (visible || newTimeVal !== oldTimeVal)) { timeListDom.value.setOuterTime(newTimeVal); } else { diff --git a/packages/devui-vue/devui/date-picker-pro/src/composables/use-year-calendar-panel.ts b/packages/devui-vue/devui/date-picker-pro/src/composables/use-year-calendar-panel.ts index e86a94ac0f..91f5c6a5f3 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/composables/use-year-calendar-panel.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/composables/use-year-calendar-panel.ts @@ -1,7 +1,7 @@ import { ref, onBeforeMount, nextTick, watch } from 'vue'; import type { SetupContext } from 'vue'; -import { chunk } from 'lodash'; -import { useNamespace } from '../../../shared/hooks/use-namespace'; +import chunk from 'lodash/chunk'; +import { useNamespace } from '@devui/shared/utils'; import { DatePickerProPanelProps, UseYearCalendarPanelReturnType } from '../date-picker-pro-types'; import dayjs from 'dayjs'; import type { Dayjs } from 'dayjs'; diff --git a/packages/devui-vue/devui/date-picker-pro/src/const.ts b/packages/devui-vue/devui/date-picker-pro/src/const.ts index ff49789c38..b09c25c849 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/const.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/const.ts @@ -9,6 +9,3 @@ export const monthCalendarItemHeight = 186; // 月选择器一个年份日历面 export const yearPickerHeight = 186; // 年选择器模式下年份虚拟列表的高度 export const yearCalendarItemHeight = 48; // 年选择器日历面板单行的高度 - -export const DEFAULT_DATE = 'YYYY/MM/DD'; -export const DEFAULT_TIME = `${DEFAULT_DATE} HH:mm:ss`; diff --git a/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro-types.ts b/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro-types.ts index 62d6109fc6..aa924e1b5e 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro-types.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro-types.ts @@ -3,6 +3,20 @@ import type { Dayjs } from 'dayjs'; import { ArrType } from '../../time-picker/src/types'; import type { InputSize } from '../../input/src/input-types'; +export type Placement = + | 'top' + | 'right' + | 'bottom' + | 'left' + | 'top-start' + | 'top-end' + | 'right-start' + | 'right-end' + | 'bottom-start' + | 'bottom-end' + | 'left-start' + | 'left-end'; + export const datePickerProCommonProps = { format: { type: String, @@ -30,6 +44,14 @@ export const datePickerProCommonProps = { type: String, default: 'date', }, + position: { + type: Array as PropType>, + default: ['bottom-start'] + }, + allowClear: { + type: Boolean, + default: true + } }; export const datePickerProProps = { @@ -63,6 +85,8 @@ export interface UseDatePickerProReturnType { onFocus: (e: MouseEvent) => void; onSelectedDate: (date: Dayjs, isConfirm?: boolean) => void; handlerClearTime: (e: MouseEvent) => void; + onInputChange: (e: string) => void; + toggle: () => void; } export interface CalendarDateItem { @@ -74,6 +98,7 @@ export interface CalendarDateItem { } export interface YearAndMonthItem { + id?: string, year: number; month?: number; isMonth?: boolean; diff --git a/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro.scss b/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro.scss index 66236ab5b0..e0cf7c3c63 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro.scss +++ b/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro.scss @@ -106,14 +106,17 @@ $input-height-lg: 46px; line-height: 30px; cursor: pointer; } + .#{$devui-prefix}-date-picker-pro__month-title { font-size: $devui-font-size-sm; line-height: 30px; cursor: pointer; } + &.#{$devui-prefix}-date-picker-pro__year-title-active { background-color: $devui-base-bg; } + &:hover:not(.#{$devui-prefix}-date-picker-pro__year-title-active) { background-color: $devui-list-item-selected-bg; } @@ -212,6 +215,7 @@ $input-height-lg: 46px; color: $devui-list-item-active-text; } } + &.#{$devui-prefix}-date-picker-pro__table-date-in-range { background-color: $devui-list-item-hover-bg; @@ -292,6 +296,7 @@ $input-height-lg: 46px; line-height: 30px; } } + .#{$devui-prefix}-time-list { border: none; @@ -370,6 +375,10 @@ $input-height-lg: 46px; .#{$devui-prefix}-range-date-picker-pro__input { flex: 1 1; + + .#{$devui-prefix}-input__inner { + text-align: center; + } } .#{$devui-prefix}-input { @@ -401,6 +410,7 @@ $input-height-lg: 46px; color: $devui-brand; } } + .#{$devui-prefix}-range-date-picker-pro__normal-input { .#{$devui-prefix}-input__inner { color: $devui-text; @@ -411,6 +421,7 @@ $input-height-lg: 46px; display: inline-block; padding: 0 4px; } + .#{$devui-prefix}-input-slot__suffix { cursor: pointer; } @@ -461,6 +472,7 @@ $input-height-lg: 46px; cursor: not-allowed; } } + &.#{$devui-prefix}-range-date-picker-pro--error { border-color: $devui-danger-line; background-color: $devui-danger-bg; @@ -501,6 +513,7 @@ $input-height-lg: 46px; padding: 4px 0; height: 48px; } + .#{$devui-prefix}-date-picker-pro__year-item-title { width: 60px; height: 40px; @@ -548,6 +561,7 @@ $input-height-lg: 46px; } } } + // 月选择器 .#{$devui-prefix}-date-picker-pro__month-calendar-panel { height: 300px; @@ -577,11 +591,13 @@ $input-height-lg: 46px; &.#{$devui-prefix}-date-picker-pro__year-item-active { background-color: $devui-base-bg; } + &:hover:not(.#{$devui-prefix}-date-picker-pro__year-item-active) { background-color: $devui-list-item-selected-bg; } } } + .#{$devui-prefix}-date-picker-pro__month-wrapper { height: 300px; width: 208px; @@ -690,4 +706,4 @@ $input-height-lg: 46px; } } } -} +} \ No newline at end of file diff --git a/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro.tsx b/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro.tsx index 459180d8a8..afee699e34 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro.tsx +++ b/packages/devui-vue/devui/date-picker-pro/src/date-picker-pro.tsx @@ -1,13 +1,13 @@ -import { defineComponent, Transition, ref, renderSlot, useSlots, getCurrentInstance, Teleport, withModifiers } from 'vue'; +import { defineComponent, Transition, ref, renderSlot, useSlots, getCurrentInstance, Teleport, withModifiers, toRefs } from 'vue'; import type { SetupContext } from 'vue'; import { datePickerProProps, DatePickerProProps } from './date-picker-pro-types'; import usePickerPro from './use-picker-pro'; import { Input } from '../../input'; -import { FlexibleOverlay, Placement } from '../../overlay'; +import { FlexibleOverlay } from '../../overlay'; import DatePickerProPanel from './components/date-picker-panel'; import { IconCalendar } from './components/icon-calendar'; import { IconClose } from './components/icon-close'; -import { useNamespace } from '../../shared/hooks/use-namespace'; +import { useNamespace } from '@devui/shared/utils'; import './date-picker-pro.scss'; import { createI18nTranslate } from '../../locale/create'; @@ -18,6 +18,8 @@ export default defineComponent({ setup(props: DatePickerProProps, ctx: SetupContext) { const app = getCurrentInstance(); const t = createI18nTranslate('DDatePickerPro', app); + const commonT = createI18nTranslate('DCommon', app) + const { position } = toRefs(props); const ns = useNamespace('date-picker-pro'); const { @@ -37,7 +39,10 @@ export default defineComponent({ onFocus, onSelectedDate, handlerClearTime, - } = usePickerPro(props, ctx, t); + onInputChange, + toggle, + } = usePickerPro(props, ctx, t, commonT); + ctx.expose({ toggle }) const position = ref(['bottom-start', 'top-start']); return () => { const vSlots = { @@ -49,8 +54,8 @@ export default defineComponent({
(isMouseEnter.value = true)} - onMouseout={() => (isMouseEnter.value = false)}> + onMouseenter={() => (isMouseEnter.value = true)} + onMouseleave={() => (isMouseEnter.value = false)}> ( @@ -66,9 +72,10 @@ export default defineComponent({ ), suffix: () => ( - - - + showCloseIcon.value ? + + + : null ), }} /> @@ -93,7 +100,7 @@ export default defineComponent({ -
+
); }; }, diff --git a/packages/devui-vue/devui/date-picker-pro/src/range-date-picker-types.ts b/packages/devui-vue/devui/date-picker-pro/src/range-date-picker-types.ts index 5d86df20f6..2595cc7fae 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/range-date-picker-types.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/range-date-picker-types.ts @@ -5,7 +5,7 @@ import { datePickerProCommonProps } from './date-picker-pro-types'; export const rangeDatePickerProProps = { modelValue: { type: Array as PropType<(Date | string)[]>, - default: ['', ''], + default: [], }, placeholder: { type: Array as PropType, @@ -41,4 +41,5 @@ export interface UseRangePickerProReturnType { onSelectedDate: (date: Dayjs[], isConfirm?: boolean) => void; handlerClearTime: (e: MouseEvent) => void; onChangeRangeFocusType: (type: string) => void; + onStartInputChange: (e: string, type: 'start' | 'end') => void; } diff --git a/packages/devui-vue/devui/date-picker-pro/src/use-picker-pro.ts b/packages/devui-vue/devui/date-picker-pro/src/use-picker-pro.ts index 42c774e53f..4f0c726fd4 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/use-picker-pro.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/use-picker-pro.ts @@ -1,19 +1,21 @@ -import { shallowRef, ref, computed, inject, watch } from 'vue'; +import { shallowRef, ref, computed, inject, watch, toRefs } from 'vue'; import type { SetupContext } from 'vue'; import { DatePickerProProps, UseDatePickerProReturnType } from './date-picker-pro-types'; import { onClickOutside } from '@vueuse/core'; import type { Dayjs } from 'dayjs'; -import { formatDayjsToStr, isDateEquals, parserDate } from './utils'; +import { debounce } from 'lodash'; +import { formatDayjsToStr, isDateEquals, parserDate, isInputDateValid } from './utils'; import { FORM_ITEM_TOKEN, FORM_TOKEN } from '../../form'; -import { DEFAULT_DATE, DEFAULT_TIME } from './const'; export default function usePickerPro( props: DatePickerProProps, ctx: SetupContext, - t: (path: string) => unknown + t: (path: string) => unknown, + commonT: (path: string) => string ): UseDatePickerProReturnType { const formContext = inject(FORM_TOKEN, undefined); const formItemContext = inject(FORM_ITEM_TOKEN, undefined); + const { calendarRange, limitDateRange, allowClear } = toRefs(props) const originRef = ref(); const inputRef = shallowRef(); @@ -26,6 +28,10 @@ export default function usePickerPro( const pickerSize = computed(() => formContext?.size || props.size); const isValidateError = computed(() => formItemContext?.validateState === 'error'); + const toggle = () => { + toggleChange(!isPanelShow.value) + } + const toggleChange = (isShow: boolean) => { isPanelShow.value = isShow; ctx.emit('toggleChange', isShow); @@ -46,9 +52,16 @@ export default function usePickerPro( toggleChange(true); ctx.emit('focus', e); }; - + const langMap: Record = { + 'zh-cn': 'YYYY/MM/DD', + 'en-us': 'MMM DD, YYYY' + } + const langTimeMap: Record = { + 'zh-cn': 'YYYY/MM/DD HH:mm:ss', + 'en-us': 'MMM DD, YYYY HH:mm:ss' + } const format = computed(() => { - return props.showTime ? props.format || DEFAULT_TIME : props.format || DEFAULT_DATE; + return props.showTime ? props.format || langTimeMap[commonT('lang')] : props.format || langMap[commonT('lang')]; }); const dateValue = computed(() => { @@ -60,14 +73,16 @@ export default function usePickerPro( }); const displayDateValue = computed(() => { - const formatDate = formatDayjsToStr(dateValue.value, format.value, props.type); + const formatDate = formatDayjsToStr(dateValue.value, format.value, props.type, commonT('lang')); if (formatDate) { return formatDate; } return ''; }); - const showCloseIcon = computed(() => isMouseEnter.value && (props.modelValue ? true : false)); + const showCloseIcon = computed( + () => !pickerDisabled.value && isMouseEnter.value && (props.modelValue ? true : false) && allowClear.value + ); const onSelectedDate = (date: Dayjs, isConfirm?: boolean) => { const result = date ? date.toDate() : date; @@ -95,10 +110,16 @@ export default function usePickerPro( } }; + const onInputChange = debounce((val: string) => { + isInputDateValid(val, format.value, calendarRange.value, limitDateRange?.value, (validDate: string) => { + ctx.emit('update:modelValue', validDate) + }); + }, 300); + watch( () => props.modelValue, () => { - formItemContext?.validate('change').catch((err) => console.warn(err)); + formItemContext?.validate('change').catch(() => { }); }, { deep: true } ); @@ -120,5 +141,7 @@ export default function usePickerPro( onFocus, onSelectedDate, handlerClearTime, + onInputChange, + toggle, }; } diff --git a/packages/devui-vue/devui/date-picker-pro/src/utils.ts b/packages/devui-vue/devui/date-picker-pro/src/utils.ts index 04749caef8..32f61545ea 100644 --- a/packages/devui-vue/devui/date-picker-pro/src/utils.ts +++ b/packages/devui-vue/devui/date-picker-pro/src/utils.ts @@ -1,17 +1,20 @@ import dayjs from 'dayjs'; import type { Dayjs } from 'dayjs'; import { ArrType } from '../../time-picker/src/types'; -import { DEFAULT_DATE } from './const'; +import { getCurrentInstance } from 'vue'; +import { createI18nTranslate } from '../../locale/create'; -export const formatDayjsToStr = (date: Dayjs | undefined, format: string, type: string): string | null => { +const app = getCurrentInstance() +export const t = createI18nTranslate('DDatePickerPro', app) +export const formatDayjsToStr = (date: Dayjs | undefined, format: string, type: string, lang: string): string | null => { + const monthEnArr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] if (!date) { return null; } if (type === 'year') { return date.year().toString(); } else if (type === 'month') { - const month = date.month() + 1 < 10 ? '0' + (date.month() + 1) : date.month() + 1; - return `${date.year()}-${month}`; + return lang === 'en-us' ? `${monthEnArr[date.month()]} ${date.year()}` : `${date.year()} ${t('year')} ${date.month() + 1} ${t('month')}`; } else { // 兼容非法格式(format),如果是非法格式,则使用默认格式(YYYY/MM/DD) return dayjs(date.format(format)).isValid() ? date.format(format) : date.format(DEFAULT_DATE); @@ -34,3 +37,30 @@ export const resetActiveTimeData = (list: ArrType[]): void => { item.isActive = false; }); }; + +export const isInputDateValid = ( + inputVal: string, + format: string, + calendarRange: Array, + limitDateRange: Array | undefined, + cb: (validDate: string) => void +) => { + const inputDate = parserDate(inputVal); + const formatedDate = inputDate?.format(format); + const year = inputDate?.year(); + const [startYear, endYear] = calendarRange; + const isInCalendarRange = year && startYear && endYear && year >= startYear && year <= endYear; + const [startDate, endDate] = limitDateRange ?? []; + const startDateTime = startDate?.valueOf(); + const endDateTime = endDate?.valueOf(); + const inputDateTime = inputDate?.valueOf(); + const isInLimitDateRange = + inputDateTime && startDateTime && endDateTime && inputDateTime >= startDateTime && inputDateTime <= endDateTime; + if (inputDate && formatedDate === inputVal) { + if (limitDateRange?.length === 2) { + isInLimitDateRange && cb(formatedDate) + } else if (isInCalendarRange) { + cb(formatedDate); + } + } +};