feat(components): [el-date-picker] support customized cell content (#4078)

re #4056
This commit is contained in:
msidolphin 2021-10-30 21:39:11 +08:00 committed by GitHub
parent cf726a0b42
commit 93a1aa9b2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 283 additions and 51 deletions

View File

@ -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 |

View File

@ -0,0 +1,83 @@
<template>
<div class="demo-date-picker">
<el-date-picker
v-model="value"
type="date"
placeholder="Pick a day"
format="YYYY/MM/DD"
value-format="YYYY-MM-DD"
>
<template #default="cell">
<div class="cell" :class="{ current: cell.isCurrent }">
<span class="text">{{ cell.text }}</span>
<span v-if="isHoliday(cell)" class="holiday"></span>
</div>
</template>
</el-date-picker>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const value = ref('2021-10-29')
const holidays = [
'2021-10-01',
'2021-10-02',
'2021-10-03',
'2021-10-04',
'2021-10-05',
'2021-10-06',
'2021-10-07',
]
function isHoliday({ dayjs }) {
return holidays.includes(dayjs.format('YYYY-MM-DD'))
}
return {
value,
isHoliday,
}
},
})
</script>
<style lang="scss" scoped>
.cell {
height: 30px;
padding: 3px 0;
box-sizing: border-box;
.text {
width: 24px;
height: 24px;
display: block;
margin: 0 auto;
line-height: 24px;
position: absolute;
left: 50%;
transform: translateX(-50%);
border-radius: 50%;
}
&.current {
.text {
background: purple;
color: #fff;
}
}
.holiday {
position: absolute;
width: 6px;
height: 6px;
background: red;
border-radius: 50%;
bottom: 0px;
left: 50%;
transform: translateX(-50%);
}
}
</style>

View File

@ -241,6 +241,39 @@ describe('DatePicker', () => {
expect(attr).toEqual('false')
})
it('custom content', async () => {
const wrapper = _mount(
`<el-date-picker
v-model="value"
ref="input">
<template #default="{ isCurrent, text }">
<div class="cell" :class="{ current: isCurrent }">
<div>{{ text }}</div>
</div>
</template>
</el-date-picker>`,
() => ({ 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()

View File

@ -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<DateCell>(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]
),
]
)
}
},
})

View File

@ -25,11 +25,7 @@
:key="key_"
:class="getCellClasses(cell)"
>
<div>
<span>
{{ cell.text }}
</span>
</div>
<el-date-picker-cell :cell="cell" />
</td>
</tr>
</tbody>
@ -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<Dayjs>,
@ -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<DateCell[][]>([[], [], [], [], [], []])
// 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
}

View File

@ -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,

View File

@ -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<DatePickerContext> =
Symbol()

View File

@ -97,8 +97,10 @@
@change="handleEndChange"
/>
<el-icon
v-if="showClose"
class="el-input__icon el-range__close-icon"
:class="{
'el-range__close-icon--hidden': !showClose,
}"
@click="onClearIconClick"
>
<component :is="clearIcon" />

View File

@ -32,7 +32,6 @@ export default defineComponent({
commonPicker.value?.handleBlur()
},
}
provide('ElPopperOptions', props.popperOptions)
ctx.expose(refProps)
return () => {

View File

@ -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;

View File

@ -125,6 +125,11 @@
svg {
vertical-align: middle;
}
&--hidden {
opacity: 0;
visibility: hidden;
}
}
}