mirror of
https://github.com/element-plus/element-plus.git
synced 2025-01-18 10:59:10 +08:00
feat(components): [el-date-picker] support customized cell content (#4078)
re #4056
This commit is contained in:
parent
cf726a0b42
commit
93a1aa9b2f
@ -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 |
|
||||
|
83
docs/examples/date-picker/custom-content.vue
Normal file
83
docs/examples/date-picker/custom-content.vue
Normal 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>
|
@ -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()
|
||||
|
@ -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]
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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" />
|
||||
|
@ -32,7 +32,6 @@ export default defineComponent({
|
||||
commonPicker.value?.handleBlur()
|
||||
},
|
||||
}
|
||||
|
||||
provide('ElPopperOptions', props.popperOptions)
|
||||
ctx.expose(refProps)
|
||||
return () => {
|
||||
|
@ -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;
|
||||
|
@ -125,6 +125,11 @@
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&--hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user