From 93a1aa9b2f1ef10f0a373ac158d8bd8d15ce8e07 Mon Sep 17 00:00:00 2001 From: msidolphin Date: Sat, 30 Oct 2021 21:39:11 +0800 Subject: [PATCH] feat(components): [el-date-picker] support customized cell content (#4078) re #4056 --- docs/en-US/component/date-picker.md | 33 ++++++++ docs/examples/date-picker/custom-content.vue | 83 +++++++++++++++++++ .../date-picker/__tests__/date-picker.spec.ts | 33 ++++++++ .../src/date-picker-com/basic-cell-render.ts | 36 ++++++++ .../src/date-picker-com/basic-date-table.vue | 44 ++++++---- .../components/date-picker/src/date-picker.ts | 5 +- .../date-picker/src/date-picker.type.ts | 29 +++++++ .../time-picker/src/common/picker.vue | 4 +- .../components/time-picker/src/time-picker.ts | 1 - .../src/date-picker/date-table.scss | 61 +++++++------- .../theme-chalk/src/date-picker/picker.scss | 5 ++ 11 files changed, 283 insertions(+), 51 deletions(-) create mode 100644 docs/examples/date-picker/custom-content.vue create mode 100644 packages/components/date-picker/src/date-picker-com/basic-cell-render.ts diff --git a/docs/en-US/component/date-picker.md b/docs/en-US/component/date-picker.md index 9dfbfe08b2..6c4c2275c0 100644 --- a/docs/en-US/component/date-picker.md +++ b/docs/en-US/component/date-picker.md @@ -130,6 +130,38 @@ date-picker/default-time ::: +## Custom content + +The content of cell can be customized, in scoped-slot you can get the cell data. + +:::demo + +date-picker/custom-content + +::: + +For data details, please refer: + +```ts +interface DateCell { + column: number + customClass: string + disabled: boolean + end: boolean + inRange: boolean + row: number + selected: Dayjs + isCurrent: boolean + isSelected: boolean + start: boolean + text: number + timestamp: number + date: Date + dayjs: Dayjs + type: 'normal' | 'today' | 'week' | 'next-month' | 'prev-month' +} +``` + ## Localization The default locale of is English, if you need to use other languages, please check [Internationalization](/en-US/guide/i18n) @@ -183,4 +215,5 @@ Note, date time locale (month name, first day of the week ...) are also configur | Name | Description | | --------------- | ------------------------------ | +| default | custom cell content | | range-separator | custom range separator content | diff --git a/docs/examples/date-picker/custom-content.vue b/docs/examples/date-picker/custom-content.vue new file mode 100644 index 0000000000..c062664d76 --- /dev/null +++ b/docs/examples/date-picker/custom-content.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/packages/components/date-picker/__tests__/date-picker.spec.ts b/packages/components/date-picker/__tests__/date-picker.spec.ts index 18fdb6f55d..6ed88607fa 100644 --- a/packages/components/date-picker/__tests__/date-picker.spec.ts +++ b/packages/components/date-picker/__tests__/date-picker.spec.ts @@ -241,6 +241,39 @@ describe('DatePicker', () => { expect(attr).toEqual('false') }) + it('custom content', async () => { + const wrapper = _mount( + ` + + `, + () => ({ value: '' }), + { + mounted() { + this.$refs.input.focus() + }, + } + ) + await nextTick() + const input = wrapper.find('input') + input.trigger('blur') + input.trigger('focus') + await nextTick() + { + ;(document.querySelector('td.available .cell') as HTMLElement).click() + } + input.trigger('focus') + await nextTick() + expect( + document.querySelector('td.available .cell').classList.contains('current') + ).toBeTruthy() + }) + describe('value-format', () => { it('with literal string', async () => { const day = dayjs() diff --git a/packages/components/date-picker/src/date-picker-com/basic-cell-render.ts b/packages/components/date-picker/src/date-picker-com/basic-cell-render.ts new file mode 100644 index 0000000000..df8bf2df60 --- /dev/null +++ b/packages/components/date-picker/src/date-picker-com/basic-cell-render.ts @@ -0,0 +1,36 @@ +import { h, defineComponent, inject } from 'vue' +import { buildProps, definePropType } from '@element-plus/utils/props' +import { ROOT_PICKER_INJECTION_KEY } from '../date-picker.type' +import type { DateCell } from '../date-picker.type' + +export default defineComponent({ + name: 'ElDatePickerCell', + props: buildProps({ + cell: { + type: definePropType(Array), + }, + }), + setup(props) { + const picker = inject(ROOT_PICKER_INJECTION_KEY) + return () => { + const cell = props.cell + return picker?.ctx.slots.default + ? picker.ctx.slots.default(cell) + : h( + 'div', + { + class: 'el-date-table-cell', + }, + [ + h( + 'span', + { + class: 'el-date-table-cell__text', + }, + [cell?.text] + ), + ] + ) + } + }, +}) diff --git a/packages/components/date-picker/src/date-picker-com/basic-date-table.vue b/packages/components/date-picker/src/date-picker-com/basic-date-table.vue index ff35c9d25d..208e61c3ef 100644 --- a/packages/components/date-picker/src/date-picker-com/basic-date-table.vue +++ b/packages/components/date-picker/src/date-picker-com/basic-date-table.vue @@ -25,11 +25,7 @@ :key="key_" :class="getCellClasses(cell)" > -
- - {{ cell.text }} - -
+ @@ -41,11 +37,16 @@ import { defineComponent, computed, ref } from 'vue' import dayjs from 'dayjs' import { useLocaleInject } from '@element-plus/hooks' import { coerceTruthyValueToArray } from '@element-plus/utils/util' +import ElDatePickerCell from './basic-cell-render' import type { PropType } from 'vue' import type { Dayjs } from 'dayjs' +import type { DateCell } from '../date-picker.type' export default defineComponent({ + components: { + ElDatePickerCell, + }, props: { date: { type: Object as PropType, @@ -81,7 +82,6 @@ export default defineComponent({ }), }, }, - emits: ['changerange', 'pick', 'select'], setup(props, ctx) { @@ -89,7 +89,7 @@ export default defineComponent({ // data const lastRow = ref(null) const lastColumn = ref(null) - const tableRows = ref([[], [], [], [], [], []]) + const tableRows = ref([[], [], [], [], [], []]) // todo better way to get Day.js locale object const firstDayOfWeek = (props.date as any).$locale().weekStart || 7 @@ -162,6 +162,9 @@ export default defineComponent({ } const index = i * 7 + j const calTime = startDate.value.add(index - offset, 'day') + cell.dayjs = calTime + cell.date = calTime.toDate() + cell.timestamp = calTime.valueOf() cell.type = 'normal' const calEndDate = @@ -222,6 +225,8 @@ export default defineComponent({ cell.selected = selectedDate.find( (_) => _.valueOf() === calTime.valueOf() ) + cell.isSelected = !!cell.selected + cell.isCurrent = isCurrent(cell) cell.disabled = props.disabledDate && props.disabledDate(cellDate) cell.customClass = props.cellClassName && props.cellClassName(cellDate) @@ -241,6 +246,14 @@ export default defineComponent({ return rows_ }) + const isCurrent = (cell): boolean => { + return ( + props.selectionMode === 'day' && + (cell.type === 'normal' || cell.type === 'today') && + cellMatchesDate(cell, props.parsedValue) + ) + } + const cellMatchesDate = (cell, date) => { if (!date) return false return dayjs(date) @@ -249,7 +262,7 @@ export default defineComponent({ } const getCellClasses = (cell) => { - const classes = [] + const classes: string[] = [] if ((cell.type === 'normal' || cell.type === 'today') && !cell.disabled) { classes.push('available') if (cell.type === 'today') { @@ -259,11 +272,7 @@ export default defineComponent({ classes.push(cell.type) } - if ( - props.selectionMode === 'day' && - (cell.type === 'normal' || cell.type === 'today') && - cellMatchesDate(cell, props.parsedValue) - ) { + if (isCurrent(cell)) { classes.push('current') } @@ -337,10 +346,11 @@ export default defineComponent({ const handleClick = (event) => { let target = event.target - if (target.tagName === 'SPAN') { - target = target.parentNode.parentNode - } - if (target.tagName === 'DIV') { + + while (target) { + if (target.tagName === 'TD') { + break + } target = target.parentNode } diff --git a/packages/components/date-picker/src/date-picker.ts b/packages/components/date-picker/src/date-picker.ts index ca3024f5b1..267fe55390 100644 --- a/packages/components/date-picker/src/date-picker.ts +++ b/packages/components/date-picker/src/date-picker.ts @@ -17,7 +17,7 @@ import { import DatePickPanel from './date-picker-com/panel-date-pick.vue' import DateRangePickPanel from './date-picker-com/panel-date-range.vue' import MonthRangePickPanel from './date-picker-com/panel-month-range.vue' - +import { ROOT_PICKER_INJECTION_KEY } from './date-picker.type' import type { PropType } from 'vue' import type { IDatePickerType } from './date-picker.type' @@ -52,6 +52,9 @@ export default defineComponent({ emits: ['update:modelValue'], setup(props, ctx) { provide('ElPopperOptions', props.popperOptions) + provide(ROOT_PICKER_INJECTION_KEY, { + ctx, + }) const commonPicker = ref(null) const refProps = { ...props, diff --git a/packages/components/date-picker/src/date-picker.type.ts b/packages/components/date-picker/src/date-picker.type.ts index ed29ce7f20..2c19405e6b 100644 --- a/packages/components/date-picker/src/date-picker.type.ts +++ b/packages/components/date-picker/src/date-picker.type.ts @@ -1,3 +1,6 @@ +import type { InjectionKey, SetupContext } from 'vue' +import type { Dayjs } from 'dayjs' + export declare type IDatePickerType = | 'year' | 'month' @@ -8,3 +11,29 @@ export declare type IDatePickerType = | 'datetimerange' | 'daterange' | 'monthrange' + +type DateCellType = 'normal' | 'today' | 'week' | 'next-month' | 'prev-month' +export interface DateCell { + column?: number + customClass?: string + disabled?: boolean + end?: boolean + inRange?: boolean + row?: number + selected?: Dayjs + isCurrent?: boolean + isSelected?: boolean + start?: boolean + text?: number + timestamp?: number + date?: Date + dayjs?: Dayjs + type?: DateCellType +} + +interface DatePickerContext { + ctx: SetupContext +} + +export const ROOT_PICKER_INJECTION_KEY: InjectionKey = + Symbol() diff --git a/packages/components/time-picker/src/common/picker.vue b/packages/components/time-picker/src/common/picker.vue index 8d95bb81d9..417b25d2cf 100644 --- a/packages/components/time-picker/src/common/picker.vue +++ b/packages/components/time-picker/src/common/picker.vue @@ -97,8 +97,10 @@ @change="handleEndChange" /> diff --git a/packages/components/time-picker/src/time-picker.ts b/packages/components/time-picker/src/time-picker.ts index 19eb8fd56b..9ac9dc2f54 100644 --- a/packages/components/time-picker/src/time-picker.ts +++ b/packages/components/time-picker/src/time-picker.ts @@ -32,7 +32,6 @@ export default defineComponent({ commonPicker.value?.handleBlur() }, } - provide('ElPopperOptions', props.popperOptions) ctx.expose(refProps) return () => { diff --git a/packages/theme-chalk/src/date-picker/date-table.scss b/packages/theme-chalk/src/date-picker/date-table.scss index 65936d5ef3..1607a48e06 100644 --- a/packages/theme-chalk/src/date-picker/date-table.scss +++ b/packages/theme-chalk/src/date-picker/date-table.scss @@ -8,25 +8,25 @@ @include when(week-mode) { .#{$namespace}-date-table__row { &:hover { - div { + .el-date-table-cell { background-color: var(--el-datepicker-inrange-background-color); } td.available:hover { color: var(--el-datepicker-font-color); } - td:first-child div { + td:first-child .el-date-table-cell { margin-left: 5px; border-top-left-radius: 15px; border-bottom-left-radius: 15px; } - td:last-child div { + td:last-child .el-date-table-cell { margin-right: 5px; border-top-right-radius: 15px; border-bottom-right-radius: 15px; } } - &.current div { + &.current .el-date-table-cell { background-color: var(--el-datepicker-inrange-background-color); } } @@ -41,22 +41,21 @@ cursor: pointer; position: relative; - & div { + @include b(date-table-cell) { height: 30px; padding: 3px 0; box-sizing: border-box; - } - - & span { - width: 24px; - height: 24px; - display: block; - margin: 0 auto; - line-height: 24px; - position: absolute; - left: 50%; - transform: translateX(-50%); - border-radius: 50%; + @include b(date-table-cell__text) { + width: 24px; + height: 24px; + display: block; + margin: 0 auto; + line-height: 24px; + position: absolute; + left: 50%; + transform: translateX(-50%); + border-radius: 50%; + } } &.next-month, @@ -66,12 +65,12 @@ &.today { position: relative; - span { + .el-date-table-cell__text { color: var(--el-color-primary); font-weight: bold; } - &.start-date span, - &.end-date span { + &.start-date .el-date-table-cell__text, + &.end-date .el-date-table-cell__text { color: $color-white; } } @@ -80,47 +79,47 @@ color: var(--el-datepicker-hover-font-color); } - &.in-range div { + &.in-range .el-date-table-cell { background-color: var(--el-datepicker-inrange-background-color); &:hover { background-color: var(--el-datepicker-inrange-hover-background-color); } } - &.current:not(.disabled) span { + &.current:not(.disabled) .el-date-table-cell__text { color: $color-white; background-color: var(--el-datepicker-active-color); } - &.start-date div, - &.end-date div { + &.start-date .el-date-table-cell, + &.end-date .el-date-table-cell { color: $color-white; } - &.start-date span, - &.end-date span { + &.start-date .el-date-table-cell__text, + &.end-date .el-date-table-cell__text { background-color: var(--el-datepicker-active-color); } - &.start-date div { + &.start-date .el-date-table-cell { margin-left: 5px; border-top-left-radius: 15px; border-bottom-left-radius: 15px; } - &.end-date div { + &.end-date .el-date-table-cell { margin-right: 5px; border-top-right-radius: 15px; border-bottom-right-radius: 15px; } - &.disabled div { + &.disabled .el-date-table-cell { background-color: $background-color-base; opacity: 1; cursor: not-allowed; color: var(--el-text-color-placeholder); } - &.selected div { + &.selected .el-date-table-cell { margin-left: 5px; margin-right: 5px; background-color: var(--el-datepicker-inrange-background-color); @@ -130,7 +129,7 @@ } } - &.selected span { + &.selected .el-date-table-cell__text { background-color: var(--el-datepicker-active-color); color: $color-white; border-radius: 15px; diff --git a/packages/theme-chalk/src/date-picker/picker.scss b/packages/theme-chalk/src/date-picker/picker.scss index 4cd6e17498..e7d6e4d913 100644 --- a/packages/theme-chalk/src/date-picker/picker.scss +++ b/packages/theme-chalk/src/date-picker/picker.scss @@ -125,6 +125,11 @@ svg { vertical-align: middle; } + + &--hidden { + opacity: 0; + visibility: hidden; + } } }