mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-11-30 02:08:12 +08:00
feat(components): [date-picker] type
add months
params (#17342)
* feat(components): [date-picker] `type` add `months` params closed #17317 * feat(components): [date-picker] optimized code * docs(components): [date-picker] enhanced multiple selection example * test(components): [date-picker] add test --------- Co-authored-by: Panzer_Jack <shenchang@bilibili.com>
This commit is contained in:
parent
ff4aadb6ab
commit
dbfa93bab1
1
docs/components.d.ts
vendored
1
docs/components.d.ts
vendored
@ -30,6 +30,7 @@ declare module '@vue/runtime-core' {
|
||||
Icons: typeof import('./.vitepress/vitepress/components/globals/icons.vue')['default']
|
||||
IRiCodeLine: typeof import('~icons/ri/code-line')['default']
|
||||
IRiExternalLinkLine: typeof import('~icons/ri/external-link-line')['default']
|
||||
IRiFileCopyLine: typeof import('~icons/ri/file-copy-line')['default']
|
||||
IRiFlaskLine: typeof import('~icons/ri/flask-line')['default']
|
||||
IRiGithubLine: typeof import('~icons/ri/github-line')['default']
|
||||
IRiTranslate2: typeof import('~icons/ri/translate2')['default']
|
||||
|
@ -158,7 +158,7 @@ Note, date time locale (month name, first day of the week ...) are also configur
|
||||
| placeholder | placeholder in non-range mode | ^[string] | '' |
|
||||
| start-placeholder | placeholder for the start date in range mode | ^[string] | — |
|
||||
| end-placeholder | placeholder for the end date in range mode | ^[string] | — |
|
||||
| type | type of the picker | ^[enum]`'year' \| 'years' \|'month' \| 'date' \| 'dates' \| 'datetime' \| 'week' \| 'datetimerange' \| 'daterange' \| 'monthrange'` | date |
|
||||
| type | type of the picker | ^[enum]`'year' \| 'years' \| 'month' \| 'months' \| 'date' \| 'dates' \| 'datetime' \| 'week' \| 'datetimerange' \| 'daterange' \| 'monthrange'` | date |
|
||||
| format | format of the displayed value in the input box | ^[string] see [date formats](/en-US/component/date-picker#date-formats) | YYYY-MM-DD |
|
||||
| popper-class | custom class name for DatePicker's dropdown | ^[string] | — |
|
||||
| popper-options | Customized popper option see more at [popper.js](https://popper.js.org/docs/v2/) | ^[object]`Partial<PopperOptions>` | {} |
|
||||
|
@ -11,11 +11,11 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="block">
|
||||
<span class="demonstration">Month</span>
|
||||
<span class="demonstration">Dates</span>
|
||||
<el-date-picker
|
||||
v-model="value2"
|
||||
type="month"
|
||||
placeholder="Pick a month"
|
||||
type="dates"
|
||||
placeholder="Pick one or more dates"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -29,11 +29,29 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="block">
|
||||
<span class="demonstration">Dates</span>
|
||||
<span class="demonstration">Years</span>
|
||||
<el-date-picker
|
||||
v-model="value4"
|
||||
type="dates"
|
||||
placeholder="Pick one or more dates"
|
||||
type="years"
|
||||
placeholder="Pick one or more years"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="block">
|
||||
<span class="demonstration">Month</span>
|
||||
<el-date-picker
|
||||
v-model="value5"
|
||||
type="month"
|
||||
placeholder="Pick a month"
|
||||
/>
|
||||
</div>
|
||||
<div class="block">
|
||||
<span class="demonstration">Months</span>
|
||||
<el-date-picker
|
||||
v-model="value6"
|
||||
type="months"
|
||||
placeholder="Pick one or more months"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -46,6 +64,8 @@ const value1 = ref('')
|
||||
const value2 = ref('')
|
||||
const value3 = ref('')
|
||||
const value4 = ref('')
|
||||
const value5 = ref('')
|
||||
const value6 = ref('')
|
||||
</script>
|
||||
<style scoped>
|
||||
.demo-date-picker {
|
||||
@ -61,6 +81,7 @@ const value4 = ref('')
|
||||
border-right: solid 1px var(--el-border-color);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.demo-date-picker .block:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
@ -69,12 +90,15 @@ const value4 = ref('')
|
||||
flex: 1;
|
||||
border-right: solid 1px var(--el-border-color);
|
||||
}
|
||||
|
||||
.demo-date-picker .container .block {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.demo-date-picker .container .block:last-child {
|
||||
border-top: solid 1px var(--el-border-color);
|
||||
}
|
||||
|
||||
.demo-date-picker .container:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
@ -1025,6 +1025,58 @@ describe('DatePicker dates', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('DatePicker months', () => {
|
||||
it('create', async () => {
|
||||
const wrapper = _mount(
|
||||
`<el-date-picker
|
||||
type='months'
|
||||
v-model="value"
|
||||
/>`,
|
||||
() => ({ value: '' })
|
||||
)
|
||||
const input = wrapper.find('input')
|
||||
input.trigger('blur')
|
||||
input.trigger('focus')
|
||||
await nextTick()
|
||||
const td = document.querySelectorAll(
|
||||
'.el-month-table tr td'
|
||||
) as NodeListOf<HTMLElement>
|
||||
const vm = wrapper.vm as any
|
||||
td[0].click()
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(1)
|
||||
td[1].click()
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(2)
|
||||
expect(
|
||||
document.querySelectorAll('.el-month-table tr .current').length
|
||||
).toBe(2)
|
||||
td[0].click()
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(1)
|
||||
td[1].click()
|
||||
await nextTick()
|
||||
expect(vm.value.length).toBe(0)
|
||||
})
|
||||
|
||||
it('selected', async () => {
|
||||
const wrapper = _mount(
|
||||
`<el-date-picker
|
||||
type="months"
|
||||
v-model="value"
|
||||
/>`,
|
||||
() => ({ value: [new Date()] })
|
||||
)
|
||||
const input = wrapper.find('input')
|
||||
input.trigger('blur')
|
||||
input.trigger('focus')
|
||||
await nextTick()
|
||||
expect(
|
||||
document.querySelectorAll('.el-month-table tr .current').length
|
||||
).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('DatePicker keyboard events', () => {
|
||||
it('enter', async () => {
|
||||
const wrapper = _mount(
|
||||
|
@ -223,7 +223,20 @@ const handleMonthTableClick = (event: MouseEvent | KeyboardEvent) => {
|
||||
const row = (target.parentNode as HTMLTableRowElement).rowIndex
|
||||
const month = row * 4 + column
|
||||
const newDate = props.date.startOf('year').month(month)
|
||||
if (props.selectionMode === 'range') {
|
||||
if (props.selectionMode === 'months') {
|
||||
if (event.type === 'keydown') {
|
||||
emit('pick', castArray(props.parsedValue), false)
|
||||
return
|
||||
}
|
||||
const newMonth = props.date.startOf('month').month(month)
|
||||
|
||||
const newValue = hasClass(target, 'current')
|
||||
? castArray(props.parsedValue).filter(
|
||||
(d) => Number(d) !== Number(newMonth)
|
||||
)
|
||||
: castArray(props.parsedValue).concat([dayjs(newMonth)])
|
||||
emit('pick', newValue)
|
||||
} else if (props.selectionMode === 'range') {
|
||||
if (!props.rangeState.selecting) {
|
||||
emit('pick', { minDate: newDate, maxDate: null })
|
||||
emit('select', true)
|
||||
|
@ -151,6 +151,7 @@
|
||||
<month-table
|
||||
v-if="currentView === 'month'"
|
||||
ref="currentViewRef"
|
||||
:selection-mode="selectionMode"
|
||||
:date="innerDate"
|
||||
:parsed-value="parsedValue"
|
||||
:disabled-date="disabledDate"
|
||||
@ -161,7 +162,7 @@
|
||||
</div>
|
||||
<div v-show="footerVisible" :class="ppNs.e('footer')">
|
||||
<el-button
|
||||
v-show="selectionMode !== 'dates' && selectionMode !== 'years'"
|
||||
v-show="!isMultipleType"
|
||||
text
|
||||
size="small"
|
||||
:class="ppNs.e('link-btn')"
|
||||
@ -225,6 +226,7 @@ import type { PanelDatePickProps } from '../props/panel-date-pick'
|
||||
import type {
|
||||
DateTableEmits,
|
||||
DatesPickerEmits,
|
||||
MonthsPickerEmits,
|
||||
WeekPickerEmits,
|
||||
YearsPickerEmits,
|
||||
} from '../props/basic-date-table'
|
||||
@ -393,10 +395,19 @@ const handleShortcutClick = (shortcut: Shortcut) => {
|
||||
|
||||
const selectionMode = computed<DatePickType>(() => {
|
||||
const { type } = props
|
||||
if (['week', 'month', 'year', 'years', 'dates'].includes(type)) return type
|
||||
if (['week', 'month', 'months', 'year', 'years', 'dates'].includes(type))
|
||||
return type
|
||||
return 'date' as DatePickType
|
||||
})
|
||||
|
||||
const isMultipleType = computed(() => {
|
||||
return (
|
||||
selectionMode.value === 'dates' ||
|
||||
selectionMode.value === 'months' ||
|
||||
selectionMode.value === 'years'
|
||||
)
|
||||
})
|
||||
|
||||
const keyboardMode = computed<string>(() => {
|
||||
return selectionMode.value === 'date'
|
||||
? currentView.value
|
||||
@ -405,11 +416,17 @@ const keyboardMode = computed<string>(() => {
|
||||
|
||||
const hasShortcuts = computed(() => !!shortcuts.length)
|
||||
|
||||
const handleMonthPick = async (month: number) => {
|
||||
innerDate.value = innerDate.value.startOf('month').month(month)
|
||||
const handleMonthPick = async (
|
||||
month: number | MonthsPickerEmits,
|
||||
keepOpen?: boolean
|
||||
) => {
|
||||
if (selectionMode.value === 'month') {
|
||||
innerDate.value = innerDate.value.startOf('month').month(month as number)
|
||||
emit(innerDate.value, false)
|
||||
} else if (selectionMode.value === 'months') {
|
||||
emit(month as MonthsPickerEmits, keepOpen ?? true)
|
||||
} else {
|
||||
innerDate.value = innerDate.value.startOf('month').month(month as number)
|
||||
currentView.value = 'date'
|
||||
if (['month', 'year', 'date', 'week'].includes(selectionMode.value)) {
|
||||
emit(innerDate.value, true)
|
||||
@ -454,9 +471,15 @@ const showTime = computed(
|
||||
const footerVisible = computed(() => {
|
||||
const showDateFooter = showTime.value || selectionMode.value === 'dates'
|
||||
const showYearFooter = selectionMode.value === 'years'
|
||||
const showMonthFooter = selectionMode.value === 'months'
|
||||
const isDateView = currentView.value === 'date'
|
||||
const isYearView = currentView.value === 'year'
|
||||
return (showDateFooter && isDateView) || (showYearFooter && isYearView)
|
||||
const isMonthView = currentView.value === 'month'
|
||||
return (
|
||||
(showDateFooter && isDateView) ||
|
||||
(showYearFooter && isYearView) ||
|
||||
(showMonthFooter && isMonthView)
|
||||
)
|
||||
})
|
||||
|
||||
const disabledConfirm = computed(() => {
|
||||
@ -468,7 +491,7 @@ const disabledConfirm = computed(() => {
|
||||
return disabledDate(props.parsedValue.toDate())
|
||||
})
|
||||
const onConfirm = () => {
|
||||
if (selectionMode.value === 'dates' || selectionMode.value === 'years') {
|
||||
if (isMultipleType.value) {
|
||||
emit(props.parsedValue as Dayjs[])
|
||||
} else {
|
||||
// deal with the scenario where: user opens the date time picker, then confirm without doing anything
|
||||
@ -740,6 +763,9 @@ watch(
|
||||
} else if (val === 'years') {
|
||||
currentView.value = 'year'
|
||||
return
|
||||
} else if (val === 'months') {
|
||||
currentView.value = 'month'
|
||||
return
|
||||
}
|
||||
currentView.value = 'date'
|
||||
},
|
||||
@ -767,8 +793,7 @@ watch(
|
||||
() => props.parsedValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (selectionMode.value === 'dates' || selectionMode.value === 'years')
|
||||
return
|
||||
if (isMultipleType.value) return
|
||||
if (Array.isArray(val)) return
|
||||
innerDate.value = val
|
||||
} else {
|
||||
|
@ -4,6 +4,7 @@ export declare type IDatePickerType =
|
||||
| 'year'
|
||||
| 'years'
|
||||
| 'month'
|
||||
| 'months'
|
||||
| 'date'
|
||||
| 'dates'
|
||||
| 'week'
|
||||
|
@ -21,6 +21,7 @@ export type BasicDateTableEmits = typeof basicDateTableEmits
|
||||
export type RangePickerEmits = { minDate: Dayjs; maxDate: null }
|
||||
export type DatePickerEmits = Dayjs
|
||||
export type DatesPickerEmits = Dayjs[]
|
||||
export type MonthsPickerEmits = Dayjs[]
|
||||
export type YearsPickerEmits = Dayjs[]
|
||||
export type WeekPickerEmits = {
|
||||
year: number
|
||||
|
@ -11,6 +11,7 @@ const selectionModes = [
|
||||
'year',
|
||||
'years',
|
||||
'month',
|
||||
'months',
|
||||
'week',
|
||||
'range',
|
||||
]
|
||||
|
@ -37,6 +37,7 @@
|
||||
!editable ||
|
||||
readonly ||
|
||||
isDatesPicker ||
|
||||
isMonthsPicker ||
|
||||
isYearsPicker ||
|
||||
type === 'week'
|
||||
"
|
||||
@ -483,7 +484,7 @@ const displayValue = computed<UserInput>(() => {
|
||||
if (!isTimePicker.value && valueIsEmpty.value) return ''
|
||||
if (!pickerVisible.value && valueIsEmpty.value) return ''
|
||||
if (formattedValue) {
|
||||
return isDatesPicker.value || isYearsPicker.value
|
||||
return isDatesPicker.value || isMonthsPicker.value || isYearsPicker.value
|
||||
? (formattedValue as Array<string>).join(', ')
|
||||
: formattedValue
|
||||
}
|
||||
@ -496,6 +497,8 @@ const isTimePicker = computed(() => props.type.startsWith('time'))
|
||||
|
||||
const isDatesPicker = computed(() => props.type === 'dates')
|
||||
|
||||
const isMonthsPicker = computed(() => props.type === 'months')
|
||||
|
||||
const isYearsPicker = computed(() => props.type === 'years')
|
||||
|
||||
const triggerIcon = computed(
|
||||
|
@ -9,6 +9,7 @@ export const DEFAULT_FORMATS_DATEPICKER = {
|
||||
year: 'YYYY',
|
||||
years: 'YYYY',
|
||||
month: 'YYYY-MM',
|
||||
months: 'YYYY-MM',
|
||||
datetime: `${DEFAULT_FORMATS_DATE} ${DEFAULT_FORMATS_TIME}`,
|
||||
monthrange: 'YYYY-MM',
|
||||
daterange: DEFAULT_FORMATS_DATE,
|
||||
|
@ -2,6 +2,7 @@ export const datePickTypes = [
|
||||
'year',
|
||||
'years',
|
||||
'month',
|
||||
'months',
|
||||
'date',
|
||||
'dates',
|
||||
'week',
|
||||
|
@ -13,6 +13,7 @@ describe('validator', () => {
|
||||
expect(isValidDatePickType('year')).toBe(true)
|
||||
expect(isValidDatePickType('years')).toBe(true)
|
||||
expect(isValidDatePickType('month')).toBe(true)
|
||||
expect(isValidDatePickType('months')).toBe(true)
|
||||
expect(isValidDatePickType('date')).toBe(true)
|
||||
expect(isValidDatePickType('dates')).toBe(true)
|
||||
expect(isValidDatePickType('week')).toBe(true)
|
||||
|
Loading…
Reference in New Issue
Block a user