Feat/datepicker && datetimepicker (#326)

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* chore: update

* Feat/form (#342)

* feat(form): add form component

fix #125

* test(form): add test code

* docs(form): add form doc

* feat: add uitls merge

* fix(form): fix style

* test(form): add form test code

* refactor(form): review changes

* test(form): use idiomatic vue-test-util methods

* feat(core): bump vue version

* feat(form): rewrite label wrap

* feat(form): fix tons of bugs

* fix(form): reuse ts extension

* refactor(form): move out label width computation

* fix(form): fix tons of bugs

* fix(form): test

Co-authored-by: 286506460 <286506460@qq.com>

* Feat/select (#381)

* fix: resove conflict

* feat: select basic usage

* feat: select basic usage

* feat: select feature create item

* fix: fix option data insert

* refactor: select

* fix: fix parse error

* feat: select test

* fix: select add popper

* fix: update select option

* fix: add select dependency

* fix: add index.ts file

* fix(select): clean up

* fix(select): some refactor

* fix(select): some update

* fix(select): fix all test cases

Co-authored-by: helen <yinhelen.hlj@qq.com>

Co-authored-by: Herrington Darkholme <2883231+HerringtonDarkholme@users.noreply.github.com>
Co-authored-by: 286506460 <286506460@qq.com>
Co-authored-by: helen <yinhelen.hlj@qq.com>
This commit is contained in:
zazzaz 2020-10-03 20:13:19 +08:00 committed by GitHub
parent ff4d4d89da
commit 355a778a2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 3936 additions and 551 deletions

View File

@ -130,6 +130,7 @@ export default defineComponent({
}
return {
elFormItemSize_,
buttonSize,
buttonDisabled,
handleClick,

View File

@ -48,12 +48,9 @@ import {
} from 'vue'
import dayjs, { Dayjs } from 'dayjs'
import localeData from 'dayjs/plugin/localeData'
import { rangeArr } from '@element-plus/time-picker/src/common/date-utils'
dayjs.extend(localeData)
const rangeArr = n => {
return Array.from(Array(n).keys())
}
export const getPrevMonthLastDays = (date: Dayjs, amount) => {
const lastDay = date.subtract(1, 'month').endOf('month').date()
return rangeArr(amount).map((_, index) => lastDay - (amount - index - 1))

View File

@ -0,0 +1,572 @@
import Picker from '@element-plus/time-picker/src/common/picker.vue'
import { mount } from '@vue/test-utils'
import dayjs from 'dayjs'
import { nextTick } from 'vue'
import DatePicker from '../src/date-picker'
const _mount = (template: string, data = () => ({}), otherObj?) => mount({
components: {
'el-date-picker': DatePicker,
},
template,
data,
...otherObj,
}, {
global: {
provide: {
elForm: {},
elFormItem: {},
},
},
})
afterEach(() => {
document.documentElement.innerHTML = ''
})
describe('DatePicker', () => {
it('create', async () => {
const wrapper = _mount(`<el-date-picker
:readonly="true"
placeholder='test_'
format='HH-mm-ss'
/>`)
const input = wrapper.find('input')
expect(input.attributes('placeholder')).toBe('test_')
expect(input.attributes('readonly')).not.toBeUndefined()
})
it('select date', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
/>`, () => ({ value: '' }))
const date = dayjs()
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const spans = document.querySelectorAll('.el-date-picker__header-label')
const arrowLeftElm = document.querySelector('.el-date-picker__prev-btn.el-icon-arrow-left') as HTMLElement
const arrowRightElm = document.querySelector('.el-date-picker__next-btn.el-icon-arrow-right') as HTMLElement
expect(spans[0].textContent).toContain(date.year())
expect(spans[1].textContent).toContain(date.format('MMMM'))
const arrowLeftYeayElm = document.querySelector('.el-date-picker__prev-btn.el-icon-d-arrow-left') as HTMLElement
arrowLeftYeayElm.click()
let count = 20
while (--count) {
arrowLeftElm.click()
}
count = 20
while (--count) {
arrowRightElm.click()
}
await nextTick()
expect(spans[0].textContent).toContain(date.add(-1, 'year').year())
expect(spans[1].textContent).toContain(date.format('MMMM'));
(document.querySelector('td.available') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value).toBeDefined()
})
it('defaultTime and clear value', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
:default-time="new Date(2011,1,1,12,0,1)"
/>`, () => ({ value: '' }))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick();
(document.querySelector('td.available') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value).toBeDefined()
expect(vm.value.getHours()).toBe(12)
expect(vm.value.getMinutes()).toBe(0)
expect(vm.value.getSeconds()).toBe(1)
const picker = wrapper.findComponent(Picker);
(picker.vm as any).showClose = true
await nextTick();
(picker.element.querySelector('.el-icon-circle-close') as HTMLElement).click()
expect(vm.value).toBeNull()
})
it('event change, focus, blur', async () => {
const changeHandler = jest.fn()
const focusHandler = jest.fn()
const blurHandler = jest.fn()
let onChangeValue
const wrapper = _mount(`<el-date-picker
v-model="value"
@change="onChange"
@focus="onFocus"
@blur="onBlur"
/>`, () => ({ value: new Date(2016, 9, 10, 18, 40) }), {
methods: {
onChange(e) {
onChangeValue = e
return changeHandler(e)
},
onFocus(e) {
return focusHandler(e)
},
onBlur(e) {
return blurHandler(e)
},
},
})
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
expect(focusHandler).toHaveBeenCalledTimes(1);
(document.querySelector('td.available') as HTMLElement).click()
await nextTick()
expect(changeHandler).toHaveBeenCalledTimes(1)
expect(blurHandler).toHaveBeenCalledTimes(1)
expect(onChangeValue.getTime()).toBe(new Date(2016, 9, 1).getTime())
})
it('shortcuts', async () => {
const text = 'Yesterday'
const value = new Date(Date.now() - 86400000)
value.setHours(0,0,0,0)
const wrapper = _mount(`<el-date-picker
v-model="value"
:shortcuts="shortcuts"
/>`, () => ({ value: '', shortcuts: [{
text,
value,
}] }))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const shortcut = document.querySelector('.el-picker-panel__shortcut')
expect(shortcut.textContent).toBe(text)
expect(document.querySelector('.el-picker-panel__sidebar')).not.toBeNull();
(shortcut as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value.valueOf()).toBe(value.valueOf())
})
it('disabledDate', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
:disabledDate="disabledDate"
/>`, () => ({ value: '', disabledDate(time) {
return time.getTime() < Date.now() - 8.64e7
} }))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
expect(document.querySelector('.disabled')).not.toBeNull()
})
})
describe('DatePicker Navigation', () => {
let prevMonth, prevYear, nextMonth, nextYear, getYearLabel, getMonthLabel
const initNavigationTest = async value => {
const wrapper = _mount(`<el-date-picker
v-model="value"
/>`, () => ({ value }))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
prevMonth = document.querySelector('button.el-icon-arrow-left')
prevYear = document.querySelector('button.el-icon-d-arrow-left')
nextMonth = document.querySelector('button.el-icon-arrow-right')
nextYear = document.querySelector('button.el-icon-d-arrow-right')
getYearLabel = () => document.querySelectorAll('.el-date-picker__header-label')[0].textContent
getMonthLabel = () => document.querySelectorAll('.el-date-picker__header-label')[1].textContent
}
it('month, year', async() => {
await initNavigationTest(new Date(2000, 0, 1))
expect(getYearLabel()).toContain('2000')
expect(getMonthLabel()).toContain('January')
prevMonth.click()
await nextTick()
expect(getYearLabel()).toContain('1999')
expect(getMonthLabel()).toContain('December')
prevYear.click()
await nextTick()
expect(getYearLabel()).toContain('1998')
expect(getMonthLabel()).toContain('December')
nextMonth.click()
await nextTick()
expect(getYearLabel()).toContain('1999')
expect(getMonthLabel()).toContain('January')
nextYear.click()
await nextTick()
expect(getYearLabel()).toContain('2000')
expect(getMonthLabel()).toContain('January')
})
it('month with fewer dates', async() => {
// July has 31 days, June has 30
await initNavigationTest(new Date(2000, 6, 31))
prevMonth.click()
await nextTick()
expect(getYearLabel()).toContain('2000')
expect(getMonthLabel()).toContain('June')
})
it('year with fewer Feburary dates', async() => {
// Feburary 2008 has 29 days, Feburary 2007 has 28
await initNavigationTest(new Date(2008, 1, 29))
prevYear.click()
await nextTick()
expect(getYearLabel()).toContain('2007')
expect(getMonthLabel()).toContain('February')
})
it('month label with fewer dates', async() => {
await initNavigationTest(new Date(2000, 6, 31))
const yearLabel = document.querySelectorAll('.el-date-picker__header-label')[0];
(yearLabel as HTMLElement).click()
await nextTick()
const year1999Label = document.querySelectorAll('.el-year-table td a')[1];
(year1999Label as HTMLElement).click()
await nextTick()
const juneLabel = document.querySelectorAll('.el-month-table td a')[5];
(juneLabel as HTMLElement).click()
await nextTick()
expect(getYearLabel()).toContain('2001')
expect(getMonthLabel()).toContain('June')
const monthLabel = document.querySelectorAll('.el-date-picker__header-label')[1];
(monthLabel as HTMLElement).click()
await nextTick()
const janLabel = document.querySelectorAll('.el-month-table td a')[0];
(janLabel as HTMLElement).click()
await nextTick()
expect(getYearLabel()).toContain('2001')
expect(getMonthLabel()).toContain('January')
})
})
describe('MonthPicker', () => {
it('basic', async () => {
const wrapper = _mount(`<el-date-picker
type='month'
v-model="value"
/>`, () => ({ value: new Date(2020, 7, 1) }))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
expect((document.querySelector('.el-month-table') as HTMLElement).style.display).toBe('')
expect(document.querySelector('.el-year-table')).toBeNull();
(document.querySelector('.el-month-table a.cell') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value.getMonth()).toBe(0)
})
})
describe('YearPicker', () => {
it('basic', async () => {
const wrapper = _mount(`<el-date-picker
type='year'
v-model="value"
/>`, () => ({ value: new Date(2020, 7, 1) }))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
expect((document.querySelector('.el-year-table') as HTMLElement).style.display).toBe('')
expect(document.querySelector('.el-month-table')).toBeNull()
const leftBtn = document.querySelector('.el-icon-d-arrow-left') as HTMLElement
const rightBtn = document.querySelector('.el-icon-d-arrow-right') as HTMLElement
let count = 2
while (--count) {
leftBtn.click()
}
count = 3
while (--count) {
rightBtn.click()
}
await nextTick();
(document.querySelector('.el-year-table a.cell') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value.getFullYear()).toBe(2030)
})
})
describe('WeekPicker', () => {
it('create', async () => {
const wrapper = _mount(`<el-date-picker
type='week'
v-model="value"
/>`, () => ({ value: new Date(2020, 7, 15) }))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
expect(document.querySelector('.is-week-mode')).not.toBeNull();
// select month still is in week-mode
(document.querySelectorAll('.el-date-picker__header-label')[1] as HTMLElement).click()
await nextTick();
(document.querySelectorAll('.el-month-table .cell')[7] as HTMLElement).click()
await nextTick()
expect(document.querySelector('.is-week-mode')).not.toBeNull()
const numberOfHighlightRows = () => document.querySelectorAll('.el-date-table__row.current').length;
(document.querySelector('.el-date-table__row ~ .el-date-table__row td.available') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value).not.toBeNull()
input.trigger('blur')
input.trigger('focus')
await nextTick()
expect(numberOfHighlightRows()).toBe(1);
// test: next month should not have highlight
(document.querySelector('.el-icon-arrow-right') as HTMLElement).click()
await nextTick()
expect(numberOfHighlightRows()).toBe(0);
// test: next year should not have highlight
(document.querySelector('.el-icon-arrow-left') as HTMLElement).click()
await nextTick();
(document.querySelector('.el-icon-d-arrow-right') as HTMLElement).click()
await nextTick()
expect(numberOfHighlightRows()).toBe(0)
})
})
describe('DatePicker dates', () => {
it('create', async () => {
const wrapper = _mount(`<el-date-picker
type='dates'
v-model="value"
/>`, () => ({ value: '' }))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const td = (document.querySelectorAll('.el-date-table__row .available') 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-date-table__row .selected').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)
})
})
describe('DateRangePicker', () => {
it('create', async () => {
const wrapper = _mount(`<el-date-picker
type='daterange'
v-model="value"
/>`, () => ({ value: '' }))
const inputs = wrapper.findAll('input')
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
const panels = document.querySelectorAll('.el-date-range-picker__content')
expect(panels.length).toBe(2);
(panels[0].querySelector('td.available') as HTMLElement).click()
await nextTick();
(panels[1].querySelector('td.available') as HTMLElement).click()
await nextTick()
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
// correct highlight
const startDate = document.querySelectorAll('.start-date')
const endDate = document.querySelectorAll('.end-date')
const inRangeDate = document.querySelectorAll('.in-range')
expect(startDate.length).toBe(1)
expect(endDate.length).toBe(1)
expect(inRangeDate.length).toBeGreaterThan(28)
// value is array
const vm = wrapper.vm as any
expect(Array.isArray(vm.value)).toBeTruthy()
// input text is something like date string
expect(inputs[0].element.value.length).toBe(10)
expect(inputs[1].element.value.length).toBe(10)
})
it('reverse selection', async () => {
const wrapper = _mount(`<el-date-picker
type='daterange'
v-model="value"
/>`, () => ({ value: '' }))
const inputs = wrapper.findAll('input')
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
const panels = document.querySelectorAll('.el-date-range-picker__content');
(panels[1].querySelector('td.available') as HTMLElement).click()
await nextTick();
(panels[0].querySelector('td.available') as HTMLElement).click()
await nextTick()
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
// correct highlight
const startDate = document.querySelectorAll('.start-date')
const endDate = document.querySelectorAll('.end-date')
const inRangeDate = document.querySelectorAll('.in-range')
expect(startDate.length).toBe(1)
expect(endDate.length).toBe(1)
expect(inRangeDate.length).toBeGreaterThan(28)
const vm = wrapper.vm as any
expect(vm.value[0].getTime() < vm.value[1].getTime()).toBeTruthy()
})
it('unlink:true', async () => {
const wrapper = _mount(`<el-date-picker
type='daterange'
v-model="value"
unlink-panels
/>`, () => ({ value: [new Date(2000, 9, 1), new Date(2000, 11, 2)] }))
const inputs = wrapper.findAll('input')
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
const panels = document.querySelectorAll('.el-date-range-picker__content')
const left = panels[0].querySelector('.el-date-range-picker__header')
const right = panels[1].querySelector('.is-right .el-date-range-picker__header')
expect(left.textContent).toBe('2000 October')
expect(right.textContent).toBe('2000 December');
(panels[1].querySelector('.el-icon-d-arrow-right') as HTMLElement).click()
await nextTick();
(panels[1].querySelector('.el-icon-arrow-right') as HTMLElement).click()
await nextTick()
expect(left.textContent).toBe('2000 October')
expect(right.textContent).toBe('2002 January')
})
it('daylight saving time highlight', async() => {
// Run test with environment variable TZ=Australia/Sydney
// The following test uses Australian Eastern Daylight Time (AEDT)
// AEST -> AEDT shift happened on 2016-10-02 02:00:00
const wrapper = _mount(`<el-date-picker
type='daterange'
v-model="value"
unlink-panels
/>`, () => ({ value: [new Date(2016, 9, 1), new Date(2016, 9, 3)] }))
const inputs = wrapper.findAll('input')
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
const startDate = document.querySelectorAll('.start-date')
const endDate = document.querySelectorAll('.end-date')
expect(startDate.length).toBe(1)
expect(endDate.length).toBe(1)
})
})
describe('MonthRange', () => {
it('works', async () => {
const wrapper = _mount(`<el-date-picker
type='monthrange'
v-model="value"
/>`, () => ({ value: '' }))
const inputs = wrapper.findAll('input')
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
const panels = document.querySelectorAll('.el-date-range-picker__content')
expect(panels.length).toBe(2)
const p0 = <HTMLElement>panels[0].querySelector('td:not(.disabled)')
p0.click()
await nextTick()
const p1 = (<HTMLElement>panels[1].querySelector('td:not(.disabled)'))
p1.click()
await nextTick()
inputs[0].trigger('blur')
inputs[0].trigger('focus')
// correct highlight
const startDate = document.querySelectorAll('.start-date')
const endDate = document.querySelectorAll('.end-date')
const inRangeDate = document.querySelectorAll('.in-range')
expect(startDate.length).toBe(1)
expect(endDate.length).toBe(1)
expect(inRangeDate.length).toBeGreaterThan(0)
// value is array
const vm = wrapper.vm as any
expect(Array.isArray(vm.value)).toBeTruthy()
// input text is something like date string
expect(inputs[0].element.value.length).toBe(7)
expect(inputs[1].element.value.length).toBe(7)
// reverse selection
p1.click()
await nextTick()
p0.click()
await nextTick()
expect(vm.value[0].getTime() < vm.value[1].getTime()).toBeTruthy()
})
it('type:monthrange unlink:true', async () => {
const wrapper = _mount(`<el-date-picker
type='monthrange'
v-model="value"
unlink-panels
/>`, () => ({ value: [new Date(2000, 9), new Date(2002, 11)] }))
const inputs = wrapper.findAll('input')
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
const panels = document.querySelectorAll('.el-date-range-picker__content')
const left = panels[0].querySelector('.el-date-range-picker__header')
const right = panels[1].querySelector('.is-right .el-date-range-picker__header')
expect(left.textContent).toContain(2000)
expect(right.textContent).toContain(2002);
(panels[1].querySelector('.el-icon-d-arrow-right') as HTMLElement).click()
await nextTick()
expect(left.textContent).toContain(2000)
expect(right.textContent).toContain(2003)
})
it('daylight saving time highlight', async () => {
const wrapper = _mount(`<el-date-picker
type='monthrange'
v-model="value"
unlink-panels
/>`, () => ({ value: [new Date(2016, 6), new Date(2016, 12)] }))
const inputs = wrapper.findAll('input')
inputs[0].trigger('blur')
inputs[0].trigger('focus')
await nextTick()
const startDate = document.querySelectorAll('.start-date')
const endDate = document.querySelectorAll('.end-date')
expect(startDate.length).toBe(1)
expect(endDate.length).toBe(1)
})
})

View File

@ -0,0 +1,480 @@
import { triggerEvent } from '@element-plus/test-utils'
import { mount } from '@vue/test-utils'
import dayjs from 'dayjs'
import { nextTick } from 'vue'
import DatePicker from '../src/date-picker'
const formatStr = 'YYYY-MM-DD HH:mm:ss'
const makeRange = (start, end) => {
const result = []
for (let i = start; i <= end; i++) {
result.push(i)
}
return result
}
const _mount = (template: string, data = () => ({}), otherObj?) => mount({
components: {
'el-date-picker': DatePicker,
},
template,
data,
...otherObj,
}, {
global: {
provide: {
elForm: {},
elFormItem: {},
},
},
})
afterEach(() => {
document.documentElement.innerHTML = ''
})
describe('Datetime Picker', () => {
it('both picker show correct formated value (extract date-format and time-format from format property', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetime"
:format="format"
/>`, () => ({
value: new Date(2018, 2, 5, 10, 15, 24),
format: 'YYYY/MM/DD HH:mm A',
}))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const dateInput = document.querySelector('.el-date-picker__time-header > span:nth-child(1) input')
const timeInput = document.querySelector('.el-date-picker__time-header > span:nth-child(2) input');
(timeInput as HTMLElement).focus()
await nextTick()
// both input shows correct value
expect((dateInput as HTMLInputElement).value).toBe('2018/03/05')
expect((timeInput as HTMLInputElement).value).toBe('10:15 AM')
wrapper.setProps({
format: 'MM-DD-YYYY HH a',
})
await nextTick()
expect((dateInput as HTMLInputElement).value).toBe('03-05-2018')
expect((timeInput as HTMLInputElement).value).toBe('10 am')
})
it('both picker show correct value', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetime"
/>`, () => ({
value: new Date(2000, 9, 1, 10, 0, 1),
}))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const dateInput = document.querySelector('.el-date-picker__time-header > span:nth-child(1) input')
const timeInput = document.querySelector('.el-date-picker__time-header > span:nth-child(2) input');
(timeInput as HTMLElement).focus()
await nextTick()
// both input shows correct value
expect((dateInput as HTMLInputElement).value).toBe('2000-10-01')
expect((timeInput as HTMLInputElement).value).toBe('10:00:01')
// time spinner highlight is correct
let spinners = document.querySelectorAll('.el-time-spinner ul li.active') as any
expect(spinners[0].textContent).toBe('10')
expect(spinners[1].textContent).toBe('00')
expect(spinners[2].textContent).toBe('01')
wrapper.setProps({
modelValue: new Date(2001, 10, 2, 11, 1, 2),
})
await nextTick()
spinners = document.querySelectorAll('.el-time-spinner ul li.active') as any
expect((dateInput as HTMLInputElement).value).toBe('2001-11-02')
expect((timeInput as HTMLInputElement).value).toBe('11:01:02')
expect(spinners[0].textContent).toBe('11')
expect(spinners[1].textContent).toBe('01')
expect(spinners[2].textContent).toBe('02')
})
it('click now button', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetime"
/>`, () => ({
value: '',
}))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick();
(document.querySelector('.el-picker-panel__link-btn') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
// test if is current time (deviation 10 seconds)
expect(dayjs(vm.value).diff(dayjs()) < 10).toBeTruthy()
})
it('timepicker select && input time && input date', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetime"
/>`, () => ({
value: '',
}))
const vm = wrapper.vm as any
expect(vm.value).toBe('')
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const input_ = document.querySelectorAll('.el-date-picker__editor-wrap input')[1];
(input_ as HTMLElement).focus()
await nextTick()
const timePanel = document.querySelector('.el-time-panel')
expect(timePanel.querySelector('.el-time-spinner').innerHTML).not.toBeNull()
const button = document.querySelector('.el-time-panel .confirm') as HTMLElement
button.click()
await nextTick()
expect(vm.value).not.toBe('')
const timeInput = document.querySelectorAll('.el-date-picker__editor-wrap input')[1] as HTMLInputElement
timeInput.value = '20:30:33'
timeInput.dispatchEvent(new Event('change'))
await nextTick()
const valueResult = dayjs(vm.value)
expect(valueResult.hour()).toBe(20)
expect(valueResult.minute()).toBe(30)
expect(valueResult.second()).toBe(33)
const dateInput = document.querySelector('.el-date-picker__editor-wrap input') as HTMLInputElement
dateInput.value = '2017-02-02'
dateInput.dispatchEvent(new Event('change'))
await nextTick()
const valueResult2 = dayjs(vm.value)
expect(valueResult2.year()).toBe(2017)
expect(valueResult2.month()).toBe(1)
expect(valueResult2.date()).toBe(2)
})
it('now button: can not choose disabled date', async () => {
let isDisable = true
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetime"
:disabledDate="disabledDate"
/>`, () => ({
value: '',
disabledDate() {
return isDisable
},
}))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
// click now button
const btn = document.querySelector('.el-picker-panel__footer .el-button--text') as HTMLElement
btn.click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value).toBe('')
isDisable = false
await nextTick()
btn.click()
await nextTick()
expect(vm.value).not.toBe('')
})
it('confirm button honors picked date', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetime"
/>`, () => ({
value: new Date(2000, 9, 1, 12, 0, 0), // 2010-10-01 12:00:00
}))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick();
// changed month / year should not effect picked time
(document.querySelector('.el-date-picker__header .el-icon-arrow-right') as HTMLElement).click();
(document.querySelector('.el-date-picker__header .el-icon-d-arrow-right') as HTMLElement).click();
// click confirm button
(document.querySelector('.el-picker-panel__footer .el-button--default') as HTMLElement).click()
const vm = wrapper.vm as any
expect(dayjs(vm.value).format(formatStr)).toBe('2000-10-01 12:00:00')
})
it('selectableRange', async () => {
const disabledHoursArr = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,23]
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetime"
:disabledHours="disabledHours"
:disabledMinutes="disabledMinutes"
:disabledSeconds="disabledSeconds"
/>`, () => ({
value: new Date(2019, 0, 1, 18, 50) }),
{
methods: {
disabledHours() {
return disabledHoursArr
},
disabledMinutes (hour) {
// ['17:30:00 - 18:30:00', '18:50:00 - 20:30:00', '21:00:00 - 22:00:00']
if (hour === 17) {
return makeRange(0, 29)
}
if (hour === 18) {
return makeRange(31, 49)
}
if (hour === 20) {
return makeRange(31, 59)
}
if (hour === 22) {
return makeRange(1, 59)
}
},
disabledSeconds(hour, minute) {
if (hour === 18 && minute === 30) {
return makeRange(1, 59)
}
if (hour === 20 && minute === 30) {
return makeRange(1, 59)
}
if (hour === 22 && minute === 0) {
return makeRange(1, 59)
}
},
},
})
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const input1 = document.querySelectorAll('.el-date-picker__editor-wrap input')[1] as HTMLInputElement
input1.blur()
input1.focus()
await nextTick()
const list = document.querySelectorAll('.el-time-spinner__list')
const hoursEl = list[0]
const disabledHours = [].slice
.call(hoursEl.querySelectorAll('.disabled'))
.map(node => Number(node.textContent))
expect(disabledHours).toStrictEqual(disabledHoursArr)
const minutesEl = list[1]
const disabledMinutes = [].slice
.call(minutesEl.querySelectorAll('.disabled'))
.map(node => Number(node.textContent))
expect(disabledMinutes.length).toBe(19)
})
})
describe('Datetimerange', () => {
it('select daterange and default Time and input format', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetimerange"
:defaultTime="new Date(2020, 1, 1, 1, 1, 1)"
format="YYYY/MM/DD HH:mm A"
/>`, () => ({
value: [new Date(2000, 10, 8, 10, 10), new Date(2000, 10, 11, 10, 10)],
}))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const pickers = document.querySelectorAll('.el-date-range-picker__content')
const leftCell = pickers[0].querySelector('td.available')
const rightCell = pickers[1].querySelector('td.available')
triggerEvent(leftCell, 'mousemove', true)
triggerEvent(leftCell, 'click', true)
await nextTick()
triggerEvent(rightCell, 'mousemove', true)
triggerEvent(rightCell, 'click', true)
await nextTick();
(document.querySelector('.el-picker-panel__footer .el-button--default') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value.map(_ => dayjs(_).format(formatStr)))
.toStrictEqual(['2000-11-01 01:01:01', '2000-12-01 01:01:01'])
const pickerss = document.querySelectorAll('.el-date-range-picker__time-header .el-date-range-picker__editors-wrap')
const left = {
dateInput: pickerss[0].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(1) input'),
timeInput: pickerss[0].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(2) input'),
}
const right = {
dateInput: pickerss[1].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(1) input'),
timeInput: pickerss[1].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(2) input'),
}
await nextTick()
// both input shows correct value
expect((left.dateInput as HTMLInputElement).value).toBe('2000/11/01')
expect((left.timeInput as HTMLInputElement).value).toBe('01:01 AM')
expect((right.dateInput as HTMLInputElement).value).toBe('2000/12/01')
expect((right.timeInput as HTMLInputElement).value).toBe('01:01 AM')
})
it('input date', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetimerange"
/>`, () => ({
value: '',
}))
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const pickerss = document.querySelectorAll('.el-date-range-picker__time-header .el-date-range-picker__editors-wrap')
const leftDateInput = pickerss[0].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(1) input') as HTMLInputElement
const rightDateInput = pickerss[0].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(1) input') as HTMLInputElement
leftDateInput.value = '1999-03-04'
triggerEvent(leftDateInput, 'input', true)
triggerEvent(leftDateInput, 'change', true)
await nextTick()
const pickers = document.querySelectorAll('.el-date-range-picker__content')
const leftCell = pickers[0].querySelector('td.available')
const rightCell = pickers[1].querySelector('td.available')
triggerEvent(leftCell, 'mousemove', true)
triggerEvent(leftCell, 'click', true)
await nextTick()
triggerEvent(rightCell, 'mousemove', true)
triggerEvent(rightCell, 'click', true)
await nextTick()
const btn = document.querySelector('.el-picker-panel__footer .el-button--default') as HTMLElement
btn.click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value.map(_ => dayjs(_).format(formatStr)))
.toStrictEqual(['1999-03-01 00:00:00','1999-04-01 00:00:00'])
// input date when minDate > maxDate
rightDateInput.value = '1998-01-01'
triggerEvent(rightDateInput, 'input', true)
triggerEvent(rightDateInput, 'change', true)
await nextTick()
btn.click()
await nextTick()
expect(dayjs(vm.value[0]).isBefore(vm.value[1])).toBeTruthy()
})
it('select time', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetimerange"
/>`, () => ({
value: '',
}))
const vm = wrapper.vm as any
expect(vm.value).toBe('')
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const timeInput = document.querySelectorAll('.el-date-range-picker__editors-wrap input')[1] as HTMLInputElement
timeInput.blur()
timeInput.focus()
timeInput.blur()
await nextTick()
const button = document.querySelector('.el-date-range-picker__time-picker-wrap .el-time-panel .confirm') as HTMLElement
button.click()
await nextTick()
const btn = document.querySelector('.el-picker-panel__footer .el-button--default') as HTMLElement
btn.click()
await nextTick()
expect(vm.value).not.toBe('')
})
it('confirm honors disabledDate', async () => {
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetimerange"
:disabledDate="disabledDate"
/>`, () => ({
value: '',
disabledDate: date => {
return date.getTime() < new Date(2000, 9, 1) // 2000-10-01
},
}))
const vm = wrapper.vm as any
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
// simulate user input of invalid date
const pickerss = document.querySelectorAll('.el-date-range-picker__time-header .el-date-range-picker__editors-wrap')
const leftDateInput = pickerss[0].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(1) input') as HTMLInputElement
leftDateInput.value = '2000-09-01'
triggerEvent(leftDateInput, 'input', true)
triggerEvent(leftDateInput, 'change', true)
await nextTick()
const btn = document.querySelector('.el-picker-panel__footer .el-button--default') as HTMLElement
expect(btn.getAttribute('disabled')).not.toBeUndefined() // invalid input disables button
btn.click()
await nextTick()
const rangePanel = document.querySelector('.el-date-range-picker')
expect(rangePanel.getAttribute('visible')).toBe('true') // popper still open
expect(vm.value).toBe('')
leftDateInput.value = '2001-09-01'
triggerEvent(leftDateInput, 'input', true)
triggerEvent(leftDateInput, 'change', true)
await nextTick()
expect(btn.getAttribute('disabled')).not.toBeUndefined()
btn.click()
await nextTick()
expect(rangePanel.getAttribute('visible')).toBe('false') // popper dismiss
expect(vm.value).not.toBe('')
})
it('selectableRange', async () => {
const disabledHoursArr = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,23]
const disabledHoursRightArr = [0,1,2]
const wrapper = _mount(`<el-date-picker
v-model="value"
type="datetimerange"
:disabledHours="disabledHours"
/>`, () => ({
value: '' }),
{
methods: {
disabledHours(role) {
if (role === 'end') {
return disabledHoursRightArr
}
return disabledHoursArr
},
},
})
const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
await nextTick()
const pickerss = document.querySelectorAll('.el-date-range-picker__time-header .el-date-range-picker__editors-wrap')
const leftDateInput = pickerss[0].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(2) input') as HTMLInputElement
const rightDateInput = pickerss[1].querySelector('.el-date-range-picker__time-picker-wrap:nth-child(2) input') as HTMLInputElement
leftDateInput.blur()
leftDateInput.focus()
await nextTick()
const listleft = document.querySelectorAll('.el-date-range-picker__editors-wrap .el-time-spinner__list')
const hoursEl = listleft[0]
const disabledHours = [].slice
.call(hoursEl.querySelectorAll('.disabled'))
.map(node => Number(node.textContent))
expect(disabledHours).toStrictEqual(disabledHoursArr)
const button = document.querySelector('.el-date-range-picker__time-picker-wrap .el-time-panel .confirm') as HTMLElement
button.click()
await nextTick()
rightDateInput.blur()
rightDateInput.focus()
await nextTick()
const listright = document.querySelectorAll('.el-date-range-picker__editors-wrap.is-right .el-time-spinner__list')
const hoursEl2 = listright[0]
const disabledHours2 = [].slice
.call(hoursEl2.querySelectorAll('.disabled'))
.map(node => Number(node.textContent))
expect(disabledHours2).toStrictEqual(disabledHoursRightArr)
})
})

View File

@ -0,0 +1,5 @@
import { App } from 'vue'
import DatePicker from './src/date-picker'
export default (app: App): void => {
app.component(DatePicker.name, DatePicker)
}

View File

@ -0,0 +1,12 @@
{
"name": "@element-plus/date-picker",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0-rc.9"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.3"
}
}

View File

@ -0,0 +1,385 @@
<template>
<table
cellspacing="0"
cellpadding="0"
class="el-date-table"
:class="{ 'is-week-mode': selectionMode === 'week' }"
@click="handleClick"
@mousemove="handleMouseMove"
>
<tbody>
<tr>
<th v-if="showWeekNumber">{{ t('el.datepicker.week') }}</th>
<th v-for="(week, key) in WEEKS" :key="key">{{ t('el.datepicker.weeks.' + week) }}</th>
</tr>
<tr
v-for="(row, key) in rows"
:key="key"
class="el-date-table__row"
:class="{ current: isWeekActive(row[1]) }"
>
<td
v-for="(cell, key_) in row"
:key="key_"
:class="getCellClasses(cell)"
>
<div>
<span>
{{ cell.text }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<script lang="ts">
import { t } from '@element-plus/locale'
import {
coerceTruthyValueToArray,
} from '@element-plus/utils/util'
import {
defineComponent,
computed,
ref,
PropType,
} from 'vue'
import dayjs, { Dayjs } from 'dayjs'
export default defineComponent({
props: {
date: {
type: Dayjs as PropType<Dayjs>,
},
minDate: {
type: Dayjs as PropType<Dayjs>,
},
maxDate: {
type: Dayjs as PropType<Dayjs>,
},
parsedValue: {
type: [Object, Array] as PropType<Dayjs | Dayjs[]>,
},
selectionMode: {
type: String,
default: 'day',
},
showWeekNumber: {
type: Boolean,
default: false,
},
disabledDate: {
type: Function,
},
cellClassName: {
type: Function,
},
rangeState: {
type: Object,
default: () => ({
endDate: null,
selecting: false,
}),
},
},
emits: ['changerange', 'pick', 'select'],
setup(props, ctx) {
// data
const lastRow = ref(null)
const lastColumn = ref(null)
const tableRows = ref([ [], [], [], [], [], [] ])
// todo better way to get Day.js locale object
const firstDayOfWeek = (props.date as any).$locale().weekStart || 7
const WEEKS_CONSTANT = props.date.locale('en').localeData().weekdaysShort().map(_=>_.toLowerCase())
const offsetDay = computed(() => {
// Sunday 7(0), cal the left and right offset days, 3217654, such as Monday is -1, the is to adjust the position of the first two rows of dates
return firstDayOfWeek > 3 ? 7 - firstDayOfWeek : -firstDayOfWeek
})
const startDate = computed(() => {
const startDayOfMonth = props.date.startOf('month')
return startDayOfMonth.subtract(startDayOfMonth.day() || 7, 'day')
})
const WEEKS = computed(() => {
return WEEKS_CONSTANT.concat(WEEKS_CONSTANT).slice(firstDayOfWeek, firstDayOfWeek + 7)
})
const rows = computed(()=> {
// TODO: refactory rows / getCellClasses
const startOfMonth = props.date.startOf('month')
const startOfMonthDay = startOfMonth.day() || 7 // day of first day
const dateCountOfMonth = startOfMonth.daysInMonth()
const dateCountOfLastMonth = startOfMonth.subtract(1, 'month').daysInMonth()
const offset = offsetDay.value
const rows_ = tableRows.value
let count = 1
const selectedDate: Dayjs[] = props.selectionMode === 'dates' ? coerceTruthyValueToArray(props.parsedValue) : []
const calNow = dayjs().startOf('day')
for (let i = 0; i < 6; i++) {
const row = rows_[i]
if (props.showWeekNumber) {
if (!row[0]) {
row[0] = {
type: 'week',
text: startDate.value.add(i * 7 + 1, 'day').week(),
}
}
}
for (let j = 0; j < 7; j++) {
let cell = row[props.showWeekNumber ? j + 1 : j]
if (!cell) {
cell = {
row: i,
column: j,
type: 'normal',
inRange: false,
start: false,
end: false,
}
}
const index = i * 7 + j
const calTime = startDate.value.add(index - offset, 'day')
cell.type = 'normal'
const calEndDate = props.rangeState.endDate || props.maxDate
|| props.rangeState.selecting && props.minDate
cell.inRange = (
props.minDate &&
calTime.isSameOrAfter(props.minDate, 'day')
) && (calEndDate &&
calTime.isSameOrBefore(calEndDate, 'day')
)
cell.start = props.minDate
&& calTime.isSame(props.minDate, 'day')
cell.end = calEndDate && calTime.isSame(calEndDate, 'day')
const isToday = calTime.isSame(calNow, 'day')
if (isToday) {
cell.type = 'today'
}
if (i >= 0 && i <= 1) {
const numberOfDaysFromPreviousMonth = startOfMonthDay + offset < 0 ? 7 + startOfMonthDay + offset : startOfMonthDay + offset
if (j + i * 7 >= numberOfDaysFromPreviousMonth) {
cell.text = count++
} else {
cell.text = dateCountOfLastMonth - (numberOfDaysFromPreviousMonth - j % 7) + 1 + i * 7
cell.type = 'prev-month'
}
} else {
if (count <= dateCountOfMonth) {
cell.text = count++
} else {
cell.text = count++ - dateCountOfMonth
cell.type = 'next-month'
}
}
const cellDate = calTime.toDate()
cell.selected = selectedDate.find(_ => _.valueOf() === calTime.valueOf())
cell.disabled = props.disabledDate && props.disabledDate(cellDate)
cell.customClass = props.cellClassName && props.cellClassName(cellDate)
row[props.showWeekNumber ? j + 1 : j] = cell
}
if (props.selectionMode === 'week') {
const start = props.showWeekNumber ? 1 : 0
const end = props.showWeekNumber ? 7 : 6
const isActive = isWeekActive(row[start + 1])
row[start].inRange = isActive
row[start].start = isActive
row[end].inRange = isActive
row[end].end = isActive
}
}
return rows_
})
const cellMatchesDate = (cell, date) => {
if (!date) return false
return dayjs(date)
.isSame(
props.date.date(Number(cell.text))
, 'day',
)
}
const getCellClasses = cell => {
let classes = []
if ((cell.type === 'normal' || cell.type === 'today') && !cell.disabled) {
classes.push('available')
if (cell.type === 'today') {
classes.push('today')
}
} else {
classes.push(cell.type)
}
if (props.selectionMode === 'day' && (cell.type === 'normal' || cell.type === 'today') && cellMatchesDate(cell, props.parsedValue)) {
classes.push('current')
}
if (cell.inRange && ((cell.type === 'normal' || cell.type === 'today') || props.selectionMode === 'week')) {
classes.push('in-range')
if (cell.start) {
classes.push('start-date')
}
if (cell.end) {
classes.push('end-date')
}
}
if (cell.disabled) {
classes.push('disabled')
}
if (cell.selected) {
classes.push('selected')
}
if (cell.customClass) {
classes.push(cell.customClass)
}
return classes.join(' ')
}
const getDateOfCell = (row, column) => {
const offsetFromStart = row * 7 + (column - (props.showWeekNumber ? 1 : 0)) - offsetDay.value
return startDate.value.add(offsetFromStart, 'day')
}
const handleMouseMove = event => {
if (!props.rangeState.selecting) return
let target = event.target
if (target.tagName === 'SPAN') {
target = target.parentNode.parentNode
}
if (target.tagName === 'DIV') {
target = target.parentNode
}
if (target.tagName !== 'TD') return
const row = target.parentNode.rowIndex - 1
const column = target.cellIndex
// can not select disabled date
if (rows.value[row][column].disabled) return
// only update rangeState when mouse moves to a new cell
// this avoids frequent Date object creation and improves performance
if (row !== lastRow.value || column !== lastColumn.value) {
lastRow.value = row
lastColumn.value = column
ctx.emit('changerange', {
selecting: true,
endDate: getDateOfCell(row, column),
})
}
}
const handleClick = event => {
let target = event.target
if (target.tagName === 'SPAN') {
target = target.parentNode.parentNode
}
if (target.tagName === 'DIV') {
target = target.parentNode
}
if (target.tagName !== 'TD') return
const row = target.parentNode.rowIndex - 1
const column = props.selectionMode === 'week' ? 1 : target.cellIndex
const cell = rows.value[row][column]
if (cell.disabled || cell.type === 'week') return
const newDate = getDateOfCell(row, column)
if (props.selectionMode === 'range') {
if (!props.rangeState.selecting) {
ctx.emit('pick', { minDate: newDate, maxDate: null })
ctx.emit('select', true)
} else {
if (newDate >= props.minDate) {
ctx.emit('pick', { minDate: props.minDate, maxDate: newDate })
} else {
ctx.emit('pick', { minDate: newDate, maxDate: props.minDate })
}
ctx.emit('select', false)
}
} else if (props.selectionMode === 'day') {
ctx.emit('pick', newDate)
} else if (props.selectionMode === 'week') {
const weekNumber = newDate.week()
const value = newDate.year() + 'w' + weekNumber
ctx.emit('pick', {
year: newDate.year(),
week: weekNumber,
value: value,
date: newDate,
})
} else if (props.selectionMode === 'dates') {
const newValue = cell.selected
? coerceTruthyValueToArray(props.parsedValue).filter(_ => _.valueOf() !== newDate.valueOf())
: coerceTruthyValueToArray(props.parsedValue).concat([newDate])
ctx.emit('pick', newValue)
}
}
const isWeekActive = cell => {
if (props.selectionMode !== 'week') return false
let newDate = props.date.startOf('day')
if (cell.type === 'prev-month') {
newDate = newDate.subtract(1, 'month')
}
if (cell.type === 'next-month') {
newDate = newDate.add(1, 'month')
}
newDate = newDate.date(parseInt(cell.text, 10))
if (props.parsedValue && !Array.isArray(props.parsedValue)) {
const dayOffset = (props.parsedValue.day() - firstDayOfWeek + 7) % 7 - 1
const weekDate = props.parsedValue.subtract(dayOffset, 'day')
return weekDate.isSame(newDate, 'day')
}
return false
}
return {
handleMouseMove,
t,
rows,
isWeekActive,
getCellClasses,
WEEKS,
handleClick,
}
},
})
</script>

View File

@ -0,0 +1,222 @@
<template>
<table class="el-month-table" @click="handleMonthTableClick" @mousemove="handleMouseMove">
<tbody>
<tr v-for="(row, key) in rows" :key="key">
<td v-for="(cell, key_) in row" :key="key_" :class="getCellStyle(cell)">
<div>
<a class="cell">{{ t('el.datepicker.months.' + months[cell.text]) }}</a>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<script lang="ts">
import { hasClass } from '@element-plus/utils/dom'
import { coerceTruthyValueToArray } from '@element-plus/utils/util'
import { rangeArr } from '@element-plus/time-picker/src/common/date-utils'
import { t } from '@element-plus/locale'
import dayjs, { Dayjs } from 'dayjs'
import {
defineComponent,
computed,
ref,
PropType,
} from 'vue'
const datesInMonth = (year, month) => {
const firstDay = dayjs().startOf('month').month(month).year(year)
const numOfDays = firstDay.daysInMonth()
return rangeArr(numOfDays).map(n => firstDay.add(n, 'day').toDate())
}
export default defineComponent({
props: {
disabledDate: {
type: Function as PropType<(_: Date) => void>,
},
selectionMode: {
type: String,
default: 'month',
},
minDate: {
type: Dayjs as PropType<Dayjs>,
},
maxDate: {
type: Dayjs as PropType<Dayjs>,
},
date: {
type: Dayjs as PropType<Dayjs>,
},
parsedValue: {
type: Dayjs as PropType<Dayjs>,
},
rangeState: {
type: Object,
default: () => ({
endDate: null,
selecting: false,
}),
},
},
emits: ['changerange', 'pick', 'select'],
setup(props, ctx) {
const months = ref(props.date.locale('en').localeData().monthsShort().map(_=>_.toLowerCase()))
const tableRows = ref([ [], [], [] ])
const lastRow = ref(null)
const lastColumn = ref(null)
const rows = computed(() =>{
// TODO: refactory rows / getCellClasses
const rows = tableRows.value
const now = dayjs().startOf('month')
for (let i = 0; i < 3; i++) {
const row = rows[i]
for (let j = 0; j < 4; j++) {
let cell = row[j]
if (!cell) {
cell = {
row: i,
column: j,
type: 'normal',
inRange: false,
start: false,
end: false,
}
}
cell.type = 'normal'
const index = i * 4 + j
const calTime = props.date.startOf('year').month(index)
const calEndDate = props.rangeState.endDate || props.maxDate
|| props.rangeState.selecting && props.minDate
cell.inRange = (
props.minDate &&
calTime.isSameOrAfter(props.minDate, 'month')
&& (
calEndDate &&
calTime.isSameOrBefore(calEndDate, 'month')
))
cell.start = props.minDate && calTime.isSame(props.minDate, 'month')
cell.end = calEndDate && calTime.isSame(calEndDate, 'month')
const isToday = now.isSame(calTime)
if (isToday) {
cell.type = 'today'
}
cell.text = index
let cellDate = calTime.toDate()
cell.disabled = props.disabledDate && props.disabledDate(cellDate)
row[j] = cell
}
}
return rows
})
const getCellStyle = cell => {
const style = {} as any
const year = props.date.year()
const today = new Date()
const month = cell.text
style.disabled = props.disabledDate
? datesInMonth(year, month).every(props.disabledDate)
: false
style.current = coerceTruthyValueToArray(props.parsedValue).findIndex(date => date.year() === year && date.month() === month) >= 0
style.today = today.getFullYear() === year && today.getMonth() === month
if (cell.inRange) {
style['in-range'] = true
if (cell.start) {
style['start-date'] = true
}
if (cell.end) {
style['end-date'] = true
}
}
return style
}
const handleMouseMove = event => {
if (!props.rangeState.selecting) return
let target = event.target
if (target.tagName === 'A') {
target = target.parentNode.parentNode
}
if (target.tagName === 'DIV') {
target = target.parentNode
}
if (target.tagName !== 'TD') return
const row = target.parentNode.rowIndex
const column = target.cellIndex
// can not select disabled date
if (rows.value[row][column].disabled) return
// only update rangeState when mouse moves to a new cell
// this avoids frequent Date object creation and improves performance
if (row !== lastRow.value || column !== lastColumn.value) {
lastRow.value = row
lastColumn.value = column
ctx.emit('changerange', {
selecting: true,
endDate: props.date.startOf('year').month(row * 4 + column),
})
}
}
const handleMonthTableClick = event => {
let target = event.target
if (target.tagName === 'A') {
target = target.parentNode.parentNode
}
if (target.tagName === 'DIV') {
target = target.parentNode
}
if (target.tagName !== 'TD') return
if (hasClass(target, 'disabled')) return
const column = target.cellIndex
const row = target.parentNode.rowIndex
const month = row * 4 + column
const newDate = props.date.startOf('year').month(month)
if (props.selectionMode === 'range') {
if (!props.rangeState.selecting) {
ctx.emit('pick', { minDate: newDate, maxDate: null })
ctx.emit('select', true)
} else {
if (newDate >= props.minDate) {
ctx.emit('pick', { minDate: props.minDate, maxDate: newDate })
} else {
ctx.emit('pick', { minDate: newDate, maxDate: props.minDate })
}
ctx.emit('select', false)
}
} else {
ctx.emit('pick', month)
}
}
return {
handleMouseMove,
handleMonthTableClick,
rows,
getCellStyle,
t,
months,
}
},
})
</script>

View File

@ -0,0 +1,112 @@
<template>
<table class="el-year-table" @click="handleYearTableClick">
<tbody>
<tr>
<td class="available" :class="getCellStyle(startYear + 0)">
<a class="cell">{{ startYear }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 1)">
<a class="cell">{{ startYear + 1 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 2)">
<a class="cell">{{ startYear + 2 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 3)">
<a class="cell">{{ startYear + 3 }}</a>
</td>
</tr>
<tr>
<td class="available" :class="getCellStyle(startYear + 4)">
<a class="cell">{{ startYear + 4 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 5)">
<a class="cell">{{ startYear + 5 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 6)">
<a class="cell">{{ startYear + 6 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 7)">
<a class="cell">{{ startYear + 7 }}</a>
</td>
</tr>
<tr>
<td class="available" :class="getCellStyle(startYear + 8)">
<a class="cell">{{ startYear + 8 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 9)">
<a class="cell">{{ startYear + 9 }}</a>
</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</template>
<script lang="ts">
import { hasClass } from '@element-plus/utils/dom'
import { rangeArr } from '@element-plus/time-picker/src/common/date-utils'
import { coerceTruthyValueToArray } from '@element-plus/utils/util'
import {
defineComponent,
computed,
PropType,
} from 'vue'
import dayjs, { Dayjs } from 'dayjs'
const datesInYear = year => {
const firstDay = dayjs().startOf('year')
const numOfDays = dayjs(year).isLeapYear() ? 366 : 365
return rangeArr(numOfDays).map(n => firstDay.add(n, 'day').toDate())
}
export default defineComponent({
props: {
disabledDate: {
type: Function as PropType<(_: Date) => void>,
},
parsedValue: {
type: Dayjs as PropType<Dayjs>,
},
date: {
type: Dayjs as PropType<Dayjs>,
},
},
emits: ['pick'],
setup(props, ctx) {
const startYear = computed(() =>{
return Math.floor(props.date.year() / 10) * 10
})
const getCellStyle = year => {
const style = {} as any
const today = dayjs()
style.disabled = props.disabledDate
? datesInYear(year).every(props.disabledDate)
: false
style.current = coerceTruthyValueToArray(props.parsedValue).findIndex(_ => _.year() === year) >= 0
style.today = today.year() === year
return style
}
const handleYearTableClick = event => {
const target = event.target
if (target.tagName === 'A') {
if (hasClass(target.parentNode, 'disabled')) return
const year = target.textContent || target.innerText
ctx.emit('pick', Number(year))
}
}
return {
startYear,
getCellStyle,
handleYearTableClick,
}
},
})
</script>

View File

@ -0,0 +1,585 @@
<template>
<transition name="el-zoom-in-top">
<div
v-if="visible"
class="el-picker-panel el-date-picker"
:class="[{
'has-sidebar': $slots.sidebar || hasShortcuts,
'has-time': showTime
}]"
>
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div v-if="hasShortcuts" class="el-picker-panel__sidebar">
<button
v-for="(shortcut, key) in shortcuts"
:key="key"
type="button"
class="el-picker-panel__shortcut"
@click="handleShortcutClick(shortcut)"
>
{{ shortcut.text }}
</button>
</div>
<div class="el-picker-panel__body">
<div v-if="showTime" class="el-date-picker__time-header">
<span class="el-date-picker__editor-wrap">
<el-input
:placeholder="t('el.datepicker.selectDate')"
:model-value="visibleDate"
size="small"
@input="val => userInputDate = val"
@change="handleVisibleDateChange"
/>
</span>
<span
v-clickoutside="handleTimePickClose"
class="el-date-picker__editor-wrap"
>
<el-input
:placeholder="t('el.datepicker.selectTime')"
:model-value="visibleTime"
size="small"
@focus="onTimePickerInputFocus"
@input="val => userInputTime = val"
@change="handleVisibleTimeChange"
/>
<time-pick-panel
:visible="timePickerVisible"
:format="timeFormat"
:time-arrow-control="arrowControl"
:parsed-value="innerDate"
@pick="handleTimePick"
/>
</span>
</div>
<div
v-show="currentView !== 'time'"
class="el-date-picker__header"
:class="{ 'el-date-picker__header--bordered': currentView === 'year' || currentView === 'month' }"
>
<button
type="button"
:aria-label="t(`el.datepicker.prevYear`)"
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-d-arrow-left"
@click="prevYear_"
>
</button>
<button
v-show="currentView === 'date'"
type="button"
:aria-label="t(`el.datepicker.prevMonth`)"
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-arrow-left"
@click="prevMonth_"
>
</button>
<span
role="button"
class="el-date-picker__header-label"
@click="showYearPicker"
>{{ yearLabel }}</span>
<span
v-show="currentView === 'date'"
role="button"
class="el-date-picker__header-label"
:class="{ active: currentView === 'month' }"
@click="showMonthPicker"
>{{ t(`el.datepicker.month${ month + 1 }`) }}</span>
<button
type="button"
:aria-label="t(`el.datepicker.nextYear`)"
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-d-arrow-right"
@click="nextYear_"
>
</button>
<button
v-show="currentView === 'date'"
type="button"
:aria-label="t(`el.datepicker.nextMonth`)"
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-arrow-right"
@click="nextMonth_"
>
</button>
</div>
<div class="el-picker-panel__content">
<date-table
v-if="currentView === 'date'"
:selection-mode="selectionMode"
:date="innerDate"
:parsed-value="parsedValue"
:disabled-date="disabledDate"
@pick="handleDatePick"
/>
<year-table
v-if="currentView === 'year'"
:date="innerDate"
:disabled-date="disabledDate"
:parsed-value="parsedValue"
@pick="handleYearPick"
/>
<month-table
v-if="currentView === 'month'"
:date="innerDate"
:parsed-value="parsedValue"
:disabled-date="disabledDate"
@pick="handleMonthPick"
/>
</div>
</div>
</div>
<div
v-show="footerVisible && currentView === 'date'"
class="el-picker-panel__footer"
>
<el-button
v-show="selectionMode !== 'dates'"
size="mini"
type="text"
class="el-picker-panel__link-btn"
@click="changeToNow"
>
{{ t('el.datepicker.now') }}
</el-button>
<el-button
plain
size="mini"
class="el-picker-panel__link-btn"
@click="onConfirm"
>
{{ t('el.datepicker.confirm') }}
</el-button>
</div>
</div>
</transition>
</template>
<script lang="ts">
import {
extractDateFormat,
extractTimeFormat,
} from '@element-plus/time-picker/src/common/date-utils'
import { t } from '@element-plus/locale'
import ElInput from '@element-plus/input/src/index.vue'
import { ClickOutside } from '@element-plus/directives'
import { EVENT_CODE } from '@element-plus/utils/aria'
import { Button as ElButton } from '@element-plus/button'
import dayjs, { Dayjs } from 'dayjs'
import DateTable from './basic-date-table.vue'
import MonthTable from './basic-month-table.vue'
import YearTable from './basic-year-table.vue'
import TimePickPanel from '@element-plus/time-picker/src/time-picker-com/panel-time-pick.vue'
import {
defineComponent,
computed,
ref,
PropType,
watch,
inject,
} from 'vue'
// todo
const timeWithinRange = () => true
export default defineComponent({
components: {
DateTable, ElInput, ElButton, TimePickPanel, MonthTable, YearTable,
},
directives: { clickoutside: ClickOutside },
props: {
visible: {
type: Boolean,
default: false,
},
parsedValue: {
type: [Object, Array] as PropType<Dayjs | Dayjs[]>,
},
format: {
type: String,
default: '',
},
type: {
type: String,
required: true,
},
},
emits: ['pick', 'set-picker-option'],
setup(props, ctx) {
const innerDate = ref(dayjs())
const month = computed(() => {
return innerDate.value.month()
})
const year = computed(() => {
return innerDate.value.year()
})
const selectableRange = ref([])
const userInputDate = ref(null)
const userInputTime = ref(null)
// todo update to disableHour
const checkDateWithinRange = date => {
return selectableRange.value.length > 0
? timeWithinRange(date, selectableRange.value, props.format || 'HH:mm:ss')
: true
}
const formatEmit = (emitDayjs: Dayjs) => {
if (showTime.value) return emitDayjs.millisecond(0)
if (defaultTime) {
const defaultTimeD = dayjs(defaultTime)
return defaultTimeD.year(emitDayjs.year()).month(emitDayjs.month()).date(emitDayjs.date())
}
return emitDayjs.startOf('day')
}
const emit = (value, ...args) => {
if (!value) {
ctx.emit('pick', value, ...args)
} else if (Array.isArray(value)) {
const dates = value.map(formatEmit)
ctx.emit('pick', dates, ...args)
} else {
ctx.emit('pick', formatEmit(value), ...args)
}
userInputDate.value = null
userInputTime.value = null
}
const handleDatePick = (value: Dayjs) => {
if (selectionMode.value === 'day') {
let newDate = props.parsedValue ? (props.parsedValue as Dayjs).year(value.year()).month(value.month()).date(value.date()) : value
// change default time while out of selectableRange
if (!checkDateWithinRange(newDate)) {
newDate = (selectableRange.value[0][0] as Dayjs).year(value.year()).month(value.month()).date(value.date())
}
innerDate.value = newDate
emit(newDate, showTime.value)
} else if (selectionMode.value === 'week') {
emit(value.date)
} else if (selectionMode.value === 'dates') {
emit(value, true) // set false to keep panel open
}
}
const prevMonth_ = () => {
innerDate.value = innerDate.value.subtract(1, 'month')
}
const nextMonth_ = () => {
innerDate.value = innerDate.value.add(1, 'month')
}
const prevYear_ = () => {
if (currentView.value === 'year') {
innerDate.value = innerDate.value.subtract(10, 'year')
} else {
innerDate.value = innerDate.value.subtract(1, 'year')
}
}
const nextYear_ = () => {
if (currentView.value === 'year') {
innerDate.value = innerDate.value.add(10, 'year')
} else {
innerDate.value = innerDate.value.add(1, 'year')
}
}
const currentView = ref('date')
const yearLabel = computed(() => {
const yearTranslation = t('el.datepicker.year')
if (currentView.value === 'year') {
const startYear = Math.floor(year.value / 10) * 10
if (yearTranslation) {
return startYear + ' ' + yearTranslation + ' - ' + (startYear + 9) + ' ' + yearTranslation
}
return startYear + ' - ' + (startYear + 9)
}
return year.value + ' ' + yearTranslation
})
const handleShortcutClick = shortcut => {
if (shortcut.value) {
emit(dayjs(shortcut.value))
return
}
if (shortcut.onClick) {
shortcut.onClick(ctx)
}
}
const selectionMode = computed(() => {
if (['week', 'month', 'year', 'dates'].includes(props.type)) {
return props.type
}
return 'day'
})
watch(() => selectionMode.value, val => {
if(['month', 'year'].includes(val)) {
currentView.value = val
return
}
currentView.value = 'date'
}, { immediate: true })
const hasShortcuts = computed(() => !!shortcuts.length)
const handleMonthPick = month => {
innerDate.value = innerDate.value.startOf('month').month(month)
if (selectionMode.value === 'month') {
emit(innerDate.value)
} else {
currentView.value = 'date'
}
}
const handleYearPick = year => {
if (selectionMode.value === 'year') {
innerDate.value = innerDate.value.startOf('year').year(year)
emit(innerDate.value)
}
else {
innerDate.value = innerDate.value.year(year)
currentView.value = 'month'
}
}
const showMonthPicker = () => {
currentView.value = 'month'
}
const showYearPicker = () => {
currentView.value = 'year'
}
const showTime = computed(() => props.type === 'datetime' || props.type === 'datetimerange')
const footerVisible = computed(() => {
return showTime.value || selectionMode.value === 'dates'
})
const onConfirm = () => {
if (selectionMode.value === 'dates') {
emit(props.parsedValue)
} else {
// deal with the scenario where: user opens the date time picker, then confirm without doing anything
let result = props.parsedValue as Dayjs
if (!result) {
const defaultTimeD = dayjs(defaultTime)
const defaultValueD = getDefaultValue()
result = defaultTimeD.year(defaultValueD.year()).month(defaultValueD.month()).date(defaultValueD.date())
}
innerDate.value = result
emit(result)
}
}
const changeToNow = () => {
// NOTE: not a permanent solution
// consider disable "now" button in the future
const now = dayjs()
const nowDate = now.toDate()
if ((!disabledDate || !disabledDate(nowDate)) && checkDateWithinRange(nowDate)) {
innerDate.value = dayjs()
emit(innerDate.value)
}
}
const timeFormat = computed(() => {
return extractTimeFormat(props.format)
})
const dateFormat = computed(() => {
return extractDateFormat(props.format)
})
const visibleTime = computed(() => {
if (userInputTime.value) return userInputTime.value
if (!props.parsedValue && !defaultValue) return
return ((props.parsedValue || innerDate.value) as Dayjs).format(timeFormat.value)
})
const visibleDate = computed(() => {
if (userInputDate.value) return userInputDate.value
if (!props.parsedValue && !defaultValue) return
return ((props.parsedValue || innerDate.value) as Dayjs).format(dateFormat.value)
})
const timePickerVisible = ref(false)
const onTimePickerInputFocus = () => {
timePickerVisible.value = true
}
const handleTimePickClose = () => {
timePickerVisible.value = false
}
const handleTimePick = (value, visible, first) => {
const newDate = props.parsedValue ? (props.parsedValue as Dayjs).hour(value.hour()).minute(value.minute()).second(value.second()) : value
innerDate.value = newDate
emit(innerDate.value, true)
if (!first) {
timePickerVisible.value = visible
}
}
const handleVisibleTimeChange = value => {
const newDate = dayjs(value, timeFormat.value)
if (newDate.isValid() && checkDateWithinRange(newDate)) {
innerDate.value = newDate.year(innerDate.value.year()).month(innerDate.value.month()).date(innerDate.value.date())
userInputTime.value = null
timePickerVisible.value = false
emit(innerDate.value, true)
}
}
const handleVisibleDateChange = value => {
const newDate = dayjs(value, dateFormat.value)
if (newDate.isValid()) {
if (disabledDate && disabledDate(newDate.toDate())) {
return
}
innerDate.value = newDate.hour(innerDate.value.hour()).minute(innerDate.value.minute()).second(innerDate.value.second())
userInputDate.value = null
emit(innerDate.value, true)
}
}
const isValidValue = date_ => {
return date_.isValid() && (
disabledDate
? !disabledDate(date_.toDate())
: true
)
return false
}
const formatToString = value => {
if (selectionMode.value === 'dates') {
return value.map(_ => _.format(props.format))
}
return value.format(props.format)
}
const parseUserInput = value => {
return dayjs(value, props.format)
}
const getDefaultValue = () => {
return dayjs(defaultValue)
}
const handleKeydown = event => {
const { code, keyCode } = event
const list = [EVENT_CODE.up, EVENT_CODE.down, EVENT_CODE.left, EVENT_CODE.right]
if (props.visible && !timePickerVisible.value) {
if (list.includes(code)) {
handleKeyControl(keyCode)
event.stopPropagation()
event.preventDefault()
}
if (code === EVENT_CODE.enter
&& userInputDate.value === null
&& userInputTime.value === null
) { // Enter
emit(innerDate, false)
}
}
}
const handleKeyControl = keyCode => {
const mapping = {
'year': {
38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => date.setFullYear(date.getFullYear() + step),
},
'month': {
38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => date.setMonth(date.getMonth() + step),
},
'week': {
38: -1, 40: 1, 37: -1, 39: 1, offset: (date, step) => date.setDate(date.getDate() + step * 7),
},
'day': {
38: -7, 40: 7, 37: -1, 39: 1, offset: (date, step) => date.setDate(date.getDate() + step),
},
}
const newDate = innerDate.value.toDate()
while (Math.abs(innerDate.value.diff(newDate, 'year', true)) < 1) {
const map = mapping[selectionMode.value]
map.offset(newDate, map[keyCode])
if (disabledDate && disabledDate(newDate)) {
continue
}
const result = dayjs(newDate)
innerDate.value = result
ctx.emit('pick', result, true)
break
}
}
ctx.emit('set-picker-option', ['isValidValue', isValidValue])
ctx.emit('set-picker-option', ['formatToString', formatToString])
ctx.emit('set-picker-option', ['parseUserInput', parseUserInput])
ctx.emit('set-picker-option',['handleKeydown', handleKeydown])
const pickerBase = inject('EP_PICKER_BASE') as any
const { shortcuts, disabledDate, cellClassName, defaultTime, defaultValue, arrowControl } = pickerBase.props
watch(() => props.parsedValue, val => {
if (val) {
if (selectionMode.value === 'dates') return
if (Array.isArray(val)) return
innerDate.value = val
} else {
innerDate.value = getDefaultValue()
}
}, { immediate: true })
return {
handleTimePick,
handleTimePickClose,
onTimePickerInputFocus,
timePickerVisible,
visibleTime,
visibleDate,
showTime,
changeToNow,
onConfirm,
footerVisible,
handleYearPick,
showMonthPicker,
showYearPicker,
handleMonthPick,
hasShortcuts,
shortcuts,
arrowControl,
disabledDate,
cellClassName,
selectionMode,
handleShortcutClick,
prevYear_,
nextYear_,
prevMonth_,
nextMonth_,
innerDate,
t,
yearLabel,
currentView,
month,
handleDatePick,
handleVisibleTimeChange,
handleVisibleDateChange,
timeFormat,
userInputTime,
userInputDate,
}
},
})
</script>
<style scoped>
.el-time-panel {
position: absolute;
}
</style>

View File

@ -0,0 +1,666 @@
<template>
<transition name="el-zoom-in-top">
<div
class="el-picker-panel el-date-range-picker"
:class="[{
'has-sidebar': $slots.sidebar || hasShortcuts,
'has-time': showTime
}]"
>
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div v-if="hasShortcuts" class="el-picker-panel__sidebar">
<button
v-for="(shortcut, key) in shortcuts"
:key="key"
type="button"
class="el-picker-panel__shortcut"
@click="handleShortcutClick(shortcut)"
>
{{ shortcut.text }}
</button>
</div>
<div class="el-picker-panel__body">
<div v-if="showTime" class="el-date-range-picker__time-header">
<span class="el-date-range-picker__editors-wrap">
<span class="el-date-range-picker__time-picker-wrap">
<el-input
size="small"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.startDate')"
class="el-date-range-picker__editor"
:model-value="minVisibleDate"
@input="val => handleDateInput(val, 'min')"
@change="val => handleDateChange(val, 'min')"
/>
</span>
<span v-clickoutside="handleMinTimeClose" class="el-date-range-picker__time-picker-wrap">
<el-input
size="small"
class="el-date-range-picker__editor"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.startTime')"
:model-value="minVisibleTime"
@focus="minTimePickerVisible = true"
@input="val => handleTimeInput(val, 'min')"
@change="val => handleTimeChange(val, 'min')"
/>
<time-pick-panel
:visible="minTimePickerVisible"
:format="timeFormat"
datetime-role="start"
:time-arrow-control="arrowControl"
:parsed-value="leftDate"
@pick="handleMinTimePick"
/>
</span>
</span>
<span class="el-icon-arrow-right"></span>
<span class="el-date-range-picker__editors-wrap is-right">
<span class="el-date-range-picker__time-picker-wrap">
<el-input
size="small"
class="el-date-range-picker__editor"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.endDate')"
:model-value="maxVisibleDate"
:readonly="!minDate"
@input="val => handleDateInput(val, 'max')"
@change="val => handleDateChange(val, 'max')"
/>
</span>
<span v-clickoutside="handleMaxTimeClose" class="el-date-range-picker__time-picker-wrap">
<el-input
size="small"
class="el-date-range-picker__editor"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.endTime')"
:model-value="maxVisibleTime"
:readonly="!minDate"
@focus="minDate && (maxTimePickerVisible = true)"
@input="val => handleTimeInput(val, 'max')"
@change="val => handleTimeChange(val, 'max')"
/>
<time-pick-panel
datetime-role="end"
:visible="maxTimePickerVisible"
:format="timeFormat"
:time-arrow-control="arrowControl"
:parsed-value="rightDate"
@pick="handleMaxTimePick"
/>
</span>
</span>
</div>
<div class="el-picker-panel__content el-date-range-picker__content is-left">
<div class="el-date-range-picker__header">
<button
type="button"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"
@click="leftPrevYear"
></button>
<button
type="button"
class="el-picker-panel__icon-btn el-icon-arrow-left"
@click="leftPrevMonth"
></button>
<button
v-if="unlinkPanels"
type="button"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"
@click="leftNextYear"
></button>
<button
v-if="unlinkPanels"
type="button"
:disabled="!enableMonthArrow"
:class="{ 'is-disabled': !enableMonthArrow }"
class="el-picker-panel__icon-btn el-icon-arrow-right"
@click="leftNextMonth"
></button>
<div>{{ leftLabel }}</div>
</div>
<date-table
selection-mode="range"
:date="leftDate"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
:cell-class-name="cellClassName"
@changerange="handleChangeRange"
@pick="handleRangePick"
@select="onSelect"
/>
</div>
<div class="el-picker-panel__content el-date-range-picker__content is-right">
<div class="el-date-range-picker__header">
<button
v-if="unlinkPanels"
type="button"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"
@click="rightPrevYear"
></button>
<button
v-if="unlinkPanels"
type="button"
:disabled="!enableMonthArrow"
:class="{ 'is-disabled': !enableMonthArrow }"
class="el-picker-panel__icon-btn el-icon-arrow-left"
@click="rightPrevMonth"
></button>
<button
type="button"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"
@click="rightNextYear"
></button>
<button
type="button"
class="el-picker-panel__icon-btn el-icon-arrow-right"
@click="rightNextMonth"
></button>
<div>{{ rightLabel }}</div>
</div>
<date-table
selection-mode="range"
:date="rightDate"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
:cell-class-name="cellClassName"
@changerange="handleChangeRange"
@pick="handleRangePick"
@select="onSelect"
/>
</div>
</div>
</div>
<div v-if="showTime" class="el-picker-panel__footer">
<el-button
size="mini"
type="text"
class="el-picker-panel__link-btn"
@click="handleClear"
>
{{ t('el.datepicker.clear') }}
</el-button>
<el-button
plain
size="mini"
class="el-picker-panel__link-btn"
:disabled="btnDisabled"
@click="handleConfirm(false)"
>
{{ t('el.datepicker.confirm') }}
</el-button>
</div>
</div>
</transition>
</template>
<script lang="ts">
import {
defineComponent,
computed,
ref,
PropType,
inject,
watch,
} from 'vue'
import { t } from '@element-plus/locale'
import {
extractDateFormat,
extractTimeFormat,
} from '@element-plus/time-picker/src/common/date-utils'
import { ClickOutside } from '@element-plus/directives'
import TimePickPanel from '@element-plus/time-picker/src/time-picker-com/panel-time-pick.vue'
import dayjs, { Dayjs } from 'dayjs'
import DateTable from './basic-date-table.vue'
import ElInput from '@element-plus/input/src/index.vue'
import { Button as ElButton } from '@element-plus/button'
export default defineComponent({
directives: { clickoutside: ClickOutside },
components: { TimePickPanel, DateTable, ElInput, ElButton },
props:{
unlinkPanels: Boolean,
parsedValue: {
type: Array as PropType<Dayjs[]>,
},
type: {
type: String,
required: true,
},
},
emits: ['pick', 'set-picker-option'],
setup(props, ctx) {
const leftDate = ref(dayjs())
const rightDate = ref(dayjs().add(1, 'month'))
const minDate = ref(null)
const maxDate = ref(null)
const dateUserInput = ref({
min: null,
max: null,
})
const timeUserInput = ref({
min: null,
max: null,
})
const leftLabel = computed(() => {
return leftDate.value.year() + ' ' + t('el.datepicker.year') + ' ' + t(`el.datepicker.month${ leftDate.value.month() + 1 }`)
})
const rightLabel = computed(() => {
return rightDate.value.year() + ' ' + t('el.datepicker.year') + ' ' + t(`el.datepicker.month${ rightDate.value.month() + 1 }`)
})
const leftYear = computed(() => {
return leftDate.value.year()
})
const leftMonth = computed(() => {
return leftDate.value.month()
})
const rightYear = computed(() => {
return rightDate.value.year()
})
const rightMonth = computed(() => {
return rightDate.value.month()
})
const hasShortcuts = computed(() => !!shortcuts.length)
const minVisibleDate = computed(() => {
if (dateUserInput.value.min !== null) return dateUserInput.value.min
if (minDate.value) return minDate.value.format(dateFormat.value)
return ''
})
const maxVisibleDate = computed(() => {
if (dateUserInput.value.max !== null) return dateUserInput.value.max
if (maxDate.value || minDate.value) return ((maxDate.value || minDate.value)).format(dateFormat.value)
return ''
})
const minVisibleTime = computed(() => {
if (timeUserInput.value.min !== null) return timeUserInput.value.min
if (minDate.value) return minDate.value.format(timeFormat.value)
return ''
})
const maxVisibleTime = computed(() => {
if (timeUserInput.value.max !== null) return timeUserInput.value.max
if (maxDate.value || minDate.value) return ((maxDate.value || minDate.value)).format(timeFormat.value)
return ''
})
const timeFormat = computed(() => {
return extractTimeFormat(format)
})
const dateFormat = computed(() => {
return extractDateFormat(format)
})
const leftPrevYear = () => {
leftDate.value = leftDate.value.subtract(1, 'year')
if (!props.unlinkPanels) {
rightDate.value = leftDate.value.add(1, 'month')
}
}
const leftPrevMonth = () => {
leftDate.value = leftDate.value.subtract(1, 'month')
if (!props.unlinkPanels) {
rightDate.value = leftDate.value.add(1, 'month')
}
}
const rightNextYear = () => {
if (!props.unlinkPanels) {
leftDate.value = leftDate.value.add(1, 'year')
rightDate.value = leftDate.value.add(1, 'month')
} else {
rightDate.value = rightDate.value.add(1, 'year')
}
}
const rightNextMonth = () => {
if (!props.unlinkPanels) {
leftDate.value = leftDate.value.add(1, 'month')
rightDate.value = leftDate.value.add(1, 'month')
} else {
rightDate.value = rightDate.value.add(1, 'month')
}
}
const leftNextYear = () => {
leftDate.value = leftDate.value.add(1, 'year')
}
const leftNextMonth = () => {
leftDate.value = leftDate.value.add(1, 'month')
}
const rightPrevYear = () => {
rightDate.value = rightDate.value.subtract(1, 'year')
}
const rightPrevMonth = () => {
rightDate.value = rightDate.value.subtract(1, 'month')
}
const enableMonthArrow = computed(() => {
const nextMonth = (leftMonth.value + 1) % 12
const yearOffset = leftMonth.value + 1 >= 12 ? 1 : 0
return props.unlinkPanels && new Date(leftYear.value + yearOffset, nextMonth) < new Date(rightYear.value, rightMonth.value)
})
const enableYearArrow = computed(() => {
return props.unlinkPanels && rightYear.value * 12 + rightMonth.value - (leftYear.value * 12 + leftMonth.value + 1) >= 12
})
const isValidValue = value => {
return Array.isArray(value) &&
value && value[0] && value[1] &&
value[0].valueOf() <= value[1].valueOf()
}
const rangeState = ref({
endDate: null,
selecting: false,
})
const btnDisabled = computed(() => {
return !(minDate.value && maxDate.value && !rangeState.value.selecting && isValidValue([minDate.value, maxDate.value]))
})
const handleChangeRange = val => {
rangeState.value = val
}
const onSelect = selecting => {
rangeState.value.selecting = selecting
if (!selecting) {
rangeState.value.endDate = null
}
}
const showTime = computed(() => props.type === 'datetime' || props.type === 'datetimerange')
const handleConfirm = (visible = false) => {
if (isValidValue([minDate.value, maxDate.value])) {
ctx.emit('pick', [minDate.value, maxDate.value], visible)
}
}
const formatEmit = (emitDayjs: Dayjs, index?) => {
if (!emitDayjs) return
if (defaultTime) {
const defaultTimeD = dayjs(defaultTime[index] || defaultTime)
return defaultTimeD.year(emitDayjs.year()).month(emitDayjs.month()).date(emitDayjs.date())
}
return emitDayjs
}
const handleRangePick = (val, close = true) => {
const minDate_ = formatEmit(val.minDate, 0)
const maxDate_ = formatEmit(val.maxDate, 1)
if (maxDate.value === maxDate_ && minDate.value === minDate_) {
return
}
maxDate.value = maxDate_
minDate.value = minDate_
if (!close || showTime.value) return
handleConfirm()
}
const handleShortcutClick = shortcut => {
if (shortcut.value) {
ctx.emit('pick', [dayjs(shortcut.value[0]), dayjs(shortcut.value[1])])
return
}
if (shortcut.onClick) {
shortcut.onClick(ctx)
}
}
const minTimePickerVisible = ref(false)
const maxTimePickerVisible = ref(false)
const handleMinTimeClose = () => {
minTimePickerVisible.value = false
}
const handleMaxTimeClose = () => {
maxTimePickerVisible.value = false
}
const handleDateInput = (value, type) => {
dateUserInput.value[type] = value
const parsedValueD = dayjs(value, dateFormat.value)
if (parsedValueD.isValid()) {
if (disabledDate &&
disabledDate(parsedValueD.toDate())) {
return
}
if (type === 'min') {
leftDate.value = parsedValueD
minDate.value = (minDate.value || leftDate.value).year(parsedValueD.year()).month(parsedValueD.month()).date(parsedValueD.date())
if (!props.unlinkPanels) {
rightDate.value = parsedValueD.add(1, 'month')
maxDate.value = minDate.value.add(1, 'month')
}
} else {
rightDate.value = parsedValueD
maxDate.value = (maxDate.value || rightDate.value).year(parsedValueD.year()).month(parsedValueD.month()).date(parsedValueD.date())
if (!props.unlinkPanels) {
leftDate.value = parsedValueD.subtract(1, 'month')
minDate.value = maxDate.value.subtract(1, 'month')
}
}
}
}
const handleDateChange = (value, type) => {
dateUserInput.value[type] = null
}
const handleTimeInput = (value, type) => {
timeUserInput.value[type] = value
const parsedValueD = dayjs(value, timeFormat.value)
if (parsedValueD.isValid()) {
if (type === 'min') {
minTimePickerVisible.value = true
minDate.value = (minDate.value || leftDate.value).hour(parsedValueD.hour()).minute(parsedValueD.minute()).second(parsedValueD.second())
if (!maxDate.value || maxDate.value.isBefore(minDate.value)) {
maxDate.value = minDate.value
}
} else {
maxTimePickerVisible.value = true
maxDate.value = (maxDate.value || rightDate.value).hour(parsedValueD.hour()).minute(parsedValueD.minute()).second(parsedValueD.second())
rightDate.value = maxDate.value
if (maxDate.value && maxDate.value.isBefore(minDate.value)) {
minDate.value = maxDate.value
}
}
}
}
const handleTimeChange = (value, type) => {
timeUserInput.value[type] = null
if (type === 'min') {
leftDate.value = minDate.value
minTimePickerVisible.value = false
} else {
rightDate.value = maxDate.value
maxTimePickerVisible.value = false
}
}
const handleMinTimePick = (value, visible, first) => {
if (timeUserInput.value.min) return
if (value) {
leftDate.value = value
minDate.value = (minDate.value || leftDate.value).hour(value.hour()).minute(value.minute()).second(value.second())
}
if (!first) {
minTimePickerVisible.value = visible
}
if (!maxDate.value || maxDate.value.isBefore(minDate.value)) {
maxDate.value = minDate.value
}
}
const handleMaxTimePick = (value, visible, first) => {
if (timeUserInput.value.max) return
if (value) {
rightDate.value = value
maxDate.value = (maxDate.value || rightDate.value).hour(value.hour()).minute(value.minute()).second(value.second())
}
if (!first) {
maxTimePickerVisible.value = visible
}
if (maxDate.value && maxDate.value.isBefore(minDate.value)) {
minDate.value = maxDate.value
}
}
const handleClear = () => {
minDate.value = null
maxDate.value = null
leftDate.value = getDefaultValue()[0]
rightDate.value = leftDate.value.add(1, 'month')
ctx.emit('pick', null)
}
const formatToString = value => {
return value.map(_=> _.format(format))
}
const getDefaultValue = () => {
let start
if (Array.isArray(defaultValue)) {
const left = dayjs(defaultValue[0])
let right = dayjs(defaultValue[1])
if (!props.unlinkPanels) {
right = left.add(1, 'month')
}
return [left, right]
} else if (defaultValue) {
start = dayjs(defaultValue)
} else {
start = dayjs()
}
return [start, start.add(1, 'month')]
}
// pickerBase.hub.emit('SetPickerOption', ['isValidValue', isValidValue])
ctx.emit('set-picker-option', ['formatToString', formatToString])
const pickerBase = inject('EP_PICKER_BASE') as any
const { shortcuts, disabledDate, cellClassName, format, defaultTime, defaultValue, arrowControl } = pickerBase.props
watch(() => props.parsedValue, newVal => {
if (newVal && newVal.length === 2) {
minDate.value = newVal[0]
maxDate.value = newVal[1]
leftDate.value = minDate.value
if (props.unlinkPanels && maxDate.value) {
const minDateYear = minDate.value.year()
const minDateMonth = minDate.value.month()
const maxDateYear = maxDate.value.year()
const maxDateMonth = maxDate.value.month()
rightDate.value = minDateYear === maxDateYear && minDateMonth === maxDateMonth
? maxDate.value.add(1, 'month')
: maxDate.value
} else {
rightDate.value = leftDate.value.add(1, 'month')
}
} else {
const defaultArr = getDefaultValue()
leftDate.value = defaultArr[0]
rightDate.value = defaultArr[1]
}
}, { immediate: true })
return {
shortcuts,
disabledDate,
cellClassName,
minTimePickerVisible,
maxTimePickerVisible,
handleMinTimeClose,
handleMaxTimeClose,
handleShortcutClick,
rangeState,
minDate,
maxDate,
handleRangePick,
onSelect,
handleChangeRange,
btnDisabled,
enableYearArrow,
enableMonthArrow,
rightPrevMonth,
rightPrevYear,
rightNextMonth,
rightNextYear,
leftPrevMonth,
leftPrevYear,
leftNextMonth,
leftNextYear,
hasShortcuts,
leftLabel,
rightLabel,
leftDate,
rightDate,
showTime,
t,
minVisibleDate,
maxVisibleDate,
minVisibleTime,
maxVisibleTime,
arrowControl,
handleDateInput,
handleDateChange,
handleTimeInput,
handleTimeChange,
handleMinTimePick,
handleMaxTimePick,
handleClear,
handleConfirm,
timeFormat,
}
},
})
</script>
<style scoped>
.el-time-panel {
position: absolute;
}
</style>

View File

@ -0,0 +1,289 @@
<template>
<transition name="el-zoom-in-top">
<div
class="el-picker-panel el-date-range-picker"
:class="[{
'has-sidebar': $slots.sidebar || hasShortcuts
}]"
>
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div v-if="hasShortcuts" class="el-picker-panel__sidebar">
<button
v-for="(shortcut, key) in shortcuts"
:key="key"
type="button"
class="el-picker-panel__shortcut"
@click="handleShortcutClick(shortcut)"
>
{{ shortcut.text }}
</button>
</div>
<div class="el-picker-panel__body">
<div class="el-picker-panel__content el-date-range-picker__content is-left">
<div class="el-date-range-picker__header">
<button
type="button"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"
@click="leftPrevYear"
></button>
<button
v-if="unlinkPanels"
type="button"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"
@click="leftNextYear"
></button>
<div>{{ leftLabel }}</div>
</div>
<month-table
selection-mode="range"
:date="leftDate"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
@pick="handleRangePick"
@select="onSelect"
/>
</div>
<div class="el-picker-panel__content el-date-range-picker__content is-right">
<div class="el-date-range-picker__header">
<button
v-if="unlinkPanels"
type="button"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"
@click="rightPrevYear"
></button>
<button
type="button"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"
@click="rightNextYear"
></button>
<div>{{ rightLabel }}</div>
</div>
<month-table
selection-mode="range"
:date="rightDate"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
@pick="handleRangePick"
@select="onSelect"
/>
</div>
</div>
</div>
</div>
</transition>
</template>
<script lang="ts">
import { t } from '@element-plus/locale'
import MonthTable from './basic-month-table.vue'
import dayjs, { Dayjs } from 'dayjs'
import {
defineComponent,
computed,
ref,
PropType,
watch,
inject,
} from 'vue'
export default defineComponent({
components: { MonthTable },
props:{
unlinkPanels: Boolean,
parsedValue: {
type: Array as PropType<Dayjs[]>,
},
},
emits: ['pick', 'set-picker-option'],
setup(props, ctx) {
const leftDate = ref(dayjs())
const rightDate = ref(dayjs().add(1, 'year'))
const hasShortcuts = computed(() => !!shortcuts.length)
const handleShortcutClick = shortcut => {
if (shortcut.value) {
ctx.emit('pick', [dayjs(shortcut.value[0]), dayjs(shortcut.value[1])])
return
}
if (shortcut.onClick) {
shortcut.onClick(ctx)
}
}
const leftPrevYear = () => {
leftDate.value = leftDate.value.subtract(1, 'year')
if (!props.unlinkPanels) {
rightDate.value = rightDate.value.subtract(1, 'year')
}
}
const rightNextYear = () => {
if (!props.unlinkPanels) {
leftDate.value = leftDate.value.add(1, 'year')
}
rightDate.value = rightDate.value.add(1, 'year')
}
const leftNextYear = () => {
leftDate.value = leftDate.value.add(1, 'year')
}
const rightPrevYear = () =>{
rightDate.value = rightDate.value.subtract(1, 'year')
}
const leftLabel = computed(() => {
return `${leftDate.value.year()} ${t('el.datepicker.year')}`
})
const rightLabel = computed(() => {
return `${rightDate.value.year()} ${t('el.datepicker.year')}`
})
const leftYear = computed(() =>{
return leftDate.value.year()
})
const rightYear = computed(() => {
return rightDate.value.year() === leftDate.value.year() ? leftDate.value.year() + 1 : rightDate.value.year()
})
const enableYearArrow = computed(() => {
return props.unlinkPanels && rightYear.value > leftYear.value + 1
})
const minDate = ref(null)
const maxDate = ref(null)
const rangeState = ref({
endDate: null,
selecting: false,
})
const handleChangeRange = val => {
rangeState.value = val
}
const handleRangePick = (val, close = true) => {
// const defaultTime = props.defaultTime || []
// const minDate_ = modifyWithTimeString(val.minDate, defaultTime[0])
// const maxDate_ = modifyWithTimeString(val.maxDate, defaultTime[1])
// todo
const minDate_ = val.minDate
const maxDate_ = val.maxDate
if (maxDate.value === maxDate_ && minDate.value === minDate_) {
return
}
maxDate.value = maxDate_
minDate.value = minDate_
if (!close) return
handleConfirm()
}
const isValidValue = value => {
return Array.isArray(value) &&
value && value[0] && value[1] &&
value[0].valueOf() <= value[1].valueOf()
}
const handleConfirm = (visible = false) => {
if (isValidValue([minDate.value, maxDate.value])) {
ctx.emit('pick', [minDate.value, maxDate.value], visible)
}
}
const onSelect = selecting => {
rangeState.value.selecting = selecting
if (!selecting) {
rangeState.value.endDate = null
}
}
const formatToString = value => {
return value.map(_=> _.format(format))
}
const getDefaultValue = () => {
let start
if (Array.isArray(defaultValue)) {
const left = dayjs(defaultValue[0])
let right = dayjs(defaultValue[1])
if (!props.unlinkPanels) {
right = left.add(1, 'year')
}
return [left, right]
} else if (defaultValue) {
start = dayjs(defaultValue)
} else {
start = dayjs()
}
return [start, start.add(1, 'year')]
}
// pickerBase.hub.emit('SetPickerOption', ['isValidValue', isValidValue])
ctx.emit('set-picker-option', ['formatToString', formatToString])
const pickerBase = inject('EP_PICKER_BASE') as any
const { shortcuts, disabledDate, format, defaultValue } = pickerBase.props
watch(() => props.parsedValue, newVal => {
if (newVal && newVal.length === 2) {
minDate.value = newVal[0]
maxDate.value = newVal[1]
leftDate.value = minDate.value
if (props.unlinkPanels && maxDate.value) {
const minDateYear = minDate.value.year()
const maxDateYear = maxDate.value.year()
rightDate.value = minDateYear === maxDateYear
? maxDate.value.add(1, 'year')
: maxDate.value
} else {
rightDate.value = leftDate.value.add(1, 'year')
}
} else {
const defaultArr = getDefaultValue()
leftDate.value = defaultArr[0]
rightDate.value = defaultArr[1]
}
}, { immediate: true })
return {
shortcuts,
disabledDate,
onSelect,
handleRangePick,
rangeState,
handleChangeRange,
minDate,
maxDate,
enableYearArrow,
leftLabel,
rightLabel,
leftNextYear,
leftPrevYear,
rightNextYear,
rightPrevYear,
t,
leftDate,
rightDate,
hasShortcuts,
handleShortcutClick,
}
},
})
</script>

View File

@ -0,0 +1,53 @@
import { DEFAULT_FORMATS_DATE, DEFAULT_FORMATS_DATEPICKER } from '@element-plus/time-picker/src/common/constant'
import Picker from '@element-plus/time-picker/src/common/picker.vue'
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 dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import advancedFormat from 'dayjs/plugin/advancedFormat'
import localeData from 'dayjs/plugin/localeData'
import weekOfYear from 'dayjs/plugin/weekOfYear'
import weekYear from 'dayjs/plugin/weekYear'
import isLeapYear from 'dayjs/plugin/isLeapYear'
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
import { h } from 'vue'
dayjs.extend(isLeapYear)
dayjs.extend(localeData)
dayjs.extend(advancedFormat)
dayjs.extend(customParseFormat)
dayjs.extend(weekOfYear)
dayjs.extend(weekYear)
dayjs.extend(isSameOrAfter)
dayjs.extend(isSameOrBefore)
const getPanel = function(type) {
if (type === 'daterange' || type === 'datetimerange') {
return DateRangePickPanel
} else if (type === 'monthrange') {
return MonthRangePickPanel
}
return DatePickPanel
}
export default {
name: 'ElDatePicker',
props: {
type: {
type: String,
default: 'date',
},
},
setup(props) {
const format = DEFAULT_FORMATS_DATEPICKER[props.type] || DEFAULT_FORMATS_DATE
return () => h(Picker, {
format,
type: props.type,
...props,
},
{
default: scopedProps => h(getPanel(props.type), scopedProps),
})
},
}

View File

@ -28,6 +28,7 @@ import ElSteps from '@element-plus/steps'
import ElCollapse from '@element-plus/collapse'
import ElPopper from '@element-plus/popper'
import ElTimePicker from '@element-plus/time-picker'
import ElDatePicker from '@element-plus/date-picker'
import ElTabs from '@element-plus/tabs'
import ElTooltip from '@element-plus/tooltip'
import ElSlider from '@element-plus/slider'
@ -71,6 +72,7 @@ export {
ElRadio,
ElCollapse,
ElTimePicker,
ElDatePicker,
ElTabs,
ElTooltip,
ElSlider,
@ -116,6 +118,7 @@ const install = (app: App): void => {
ElCollapse(app)
ElPopper(app)
ElTimePicker(app)
ElDatePicker(app)
ElTabs(app)
ElTooltip(app)
ElSlider(app)

View File

@ -161,8 +161,8 @@ export default defineComponent({
default: 'text',
},
size: {
type: String as PropType<'large' | 'medium' | 'small' | 'mini'>,
validator: (val: string) => ['large', 'medium', 'small', 'mini'].includes(val),
type: String as PropType<'large' | 'medium' | 'small' | 'mini' | null>,
validator: (val: string) => !val || ['large', 'medium', 'small', 'mini'].includes(val),
},
resize: {
type: String as PropType<'none' | 'both' | 'horizontal' | 'vertical'>,

View File

@ -3,3 +3,4 @@ export { default as defineGetter } from './define-getter'
export { default as makeScroll } from './make-scroll'
export { default as sleep } from './sleep'
export { default as tick } from './tick'
export { default as triggerEvent } from './trigger-event'

View File

@ -0,0 +1,27 @@
/**
* Trigger event
* mouseenter, mouseleave, mouseover, keyup, change, click
* @param {Element} elm
* @param {String} name
* @param {*} opts
*/
const triggerEvent = (elm, name, ...opts) => {
let eventName
if (/^mouse|click/.test(name)) {
eventName = 'MouseEvents'
} else if (/^key/.test(name)) {
eventName = 'KeyboardEvent'
} else {
eventName = 'HTMLEvents'
}
const evt = document.createEvent(eventName)
evt.initEvent(name, ...opts)
elm.dispatchEvent
? elm.dispatchEvent(evt)
: elm.fireEvent('on' + name, evt)
return elm
}
export default triggerEvent

View File

@ -1,6 +1,7 @@
@import "../common/var";
@include b(picker-panel) {
position: relative;
color: $--color-text-regular;
border: 1px solid $--datepicker-border-color;
box-shadow: $--box-shadow-light;

View File

@ -1,12 +1,12 @@
// daterange: 'YYYY-MM-DD',
// monthrange: 'YYYY-MM',
// datetimerange: 'yyyy-MM-DD HH:mm:ss',
export const DEFAULT_FORMATS_TIME = 'HH:mm:ss'
export const DEFAULT_FORMATS_DATE = 'YYYY-MM-DD'
export const DEFAULT_FORMATS_DATEPICKER = {
'date': DEFAULT_FORMATS_DATE,
'week': 'YYYYwWW',
'year': 'YYYY',
'month': 'YYYY-MM',
'datetime': 'YYYY-MM-DD HH:mm:ss',
date: DEFAULT_FORMATS_DATE,
week: 'gggg[w]ww',
year: 'YYYY',
month: 'YYYY-MM',
datetime: `${DEFAULT_FORMATS_DATE} ${DEFAULT_FORMATS_TIME}`,
monthrange: 'YYYY-MM',
daterange: DEFAULT_FORMATS_DATE,
datetimerange: `${DEFAULT_FORMATS_DATE} ${DEFAULT_FORMATS_TIME}`,
}

View File

@ -0,0 +1,16 @@
export const rangeArr = n => {
return Array.from(Array(n).keys())
}
export const extractDateFormat = format => {
return format
.replace(/\W?m{1,2}|\W?ZZ/g, '')
.replace(/\W?h{1,2}|\W?s{1,3}|\W?a/gi, '')
.trim()
}
export const extractTimeFormat = format => {
return format
.replace(/\W?D{1,2}|\W?Do|\W?d{1,4}|\W?M{1,4}|\W?Y{2,4}/g, '')
.trim()
}

View File

@ -3,6 +3,7 @@
<!-- todo popper custom popper-class -->
<!-- todo bug handleKeydown event twice -->
<el-popper
ref="popper"
v-model:visible="pickerVisible"
pure
manual-mode
@ -21,7 +22,7 @@
:placeholder="placeholder"
class="el-date-editor"
:class="'el-date-editor--' + type"
:readonly="!editable || readonly || type === 'dates' || type === 'week'"
:readonly="!editable || readonly || isDatesPicker || type === 'week'"
@input="onUserInput"
@focus="handleFocus"
@keydown="handleKeydown"
@ -108,6 +109,7 @@
v-bind="$attrs"
@pick="onPick"
@select-range="setSelectionRange"
@set-picker-option="onSetPickerOption"
@mousedown.stop
></slot>
</template>
@ -128,7 +130,6 @@ import { ClickOutside } from '@element-plus/directives'
import ElInput from '@element-plus/input/src/index.vue'
import { Popper as ElPopper } from '@element-plus/popper'
import { EVENT_CODE } from '@element-plus/utils/aria'
import mitt from 'mitt'
// Date object and string
const dateEquals = function(a, b) {
const aIsDate = a instanceof Date
@ -166,6 +167,7 @@ interface PickerOptions {
parseUserInput: any
formatToString: any
getRangeAvaliableTime: any
getDefaultValue: any
panelReady: boolean
}
export default defineComponent({
@ -232,7 +234,9 @@ export default defineComponent({
endPlaceholder: String,
defaultValue: {
type: [Date, Array] as PropType<Date | Date[]>,
default: new Date(),
},
defaultTime: {
type: [Date, Array] as PropType<Date | Date[]>,
},
isRange: {
type: Boolean,
@ -240,20 +244,30 @@ export default defineComponent({
},
disabledHours: {
type: Function,
default: null,
},
disabledMinutes: {
type: Function,
default: null,
},
disabledSeconds: {
type: Function,
default: null,
},
disabledDate: {
type: Function,
},
cellClassName: {
type: Function,
},
shortcuts: {
type: Array,
default: () => ([]),
},
arrowControl: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'change', 'focus', 'blur'],
setup(props, ctx) {
const oldValue = ref(props.modelValue)
const refContainer = ref(null)
const pickerVisible = ref(false)
const valueOnOpen = ref(null)
@ -295,17 +309,14 @@ export default defineComponent({
_inputs[1].focus()
}
}
const onPick = (date: any = '', visible = false, useOldValue = false) => {
const onPick = (date: any = '', visible = false) => {
pickerVisible.value = visible
let result
if (useOldValue) {
result = oldValue.value
if (Array.isArray(date)) {
result = date.map(_ => _.toDate())
} else {
if (Array.isArray(date)) {
result = date.map(_ => _.toDate())
} else {
result = date.toDate()
}
// clear btn emit null
result = date ? date.toDate() : date
}
userInput.value = null
emitInput(result)
@ -323,26 +334,18 @@ export default defineComponent({
const parsedValue = computed(() => {
let result
if (isRangeInput.value) {
if (!props.modelValue) {
if (Array.isArray(props.defaultValue)) {
result = (props.defaultValue as Array<Date>).map(_=> dayjs(_))
} else {
result = [
dayjs(props.defaultValue as Date),
dayjs(props.defaultValue as Date).add(60,'m'),
]
}
} else {
result = (props.modelValue as Array<Date>).map(_=> dayjs(_))
if (valueIsEmpty.value) {
if (pickerOptions.value.getDefaultValue) {
result = pickerOptions.value.getDefaultValue()
}
} else {
if (!props.modelValue) {
result = dayjs(props.defaultValue as Date)
if (Array.isArray(props.modelValue)) {
result = props.modelValue.map(_=>dayjs(_))
} else {
result = dayjs(props.modelValue as Date)
}
}
if (pickerOptions.value.getRangeAvaliableTime) {
result = pickerOptions.value.getRangeAvaliableTime(result)
}
@ -351,7 +354,8 @@ export default defineComponent({
const displayValue = computed(() => {
if (!pickerOptions.value.panelReady) return
if (!pickerVisible.value && !props.modelValue) return
if (!isTimePicker.value && valueIsEmpty.value) return
if (!pickerVisible.value && valueIsEmpty.value) return
const formattedValue = formatDayjsToString(parsedValue.value)
if (Array.isArray(userInput.value)) {
return [
@ -362,14 +366,27 @@ export default defineComponent({
return userInput.value
}
if (formattedValue) {
return props.type === 'dates'
return isDatesPicker.value
? (formattedValue as Array<string>).join(', ')
: formattedValue
}
return ''
})
const isTimeLikePicker = computed(() => {
return props.type.indexOf('time') !== -1
})
const isTimePicker = computed(() => {
return props.type.indexOf('time') === 0
})
const isDatesPicker = computed(() => {
return props.type === 'dates'
})
const triggerClass = computed(() => {
return props.prefixIcon || (props.type.indexOf('time') !== -1 ? 'el-icon-time' : 'el-icon-date')
return props.prefixIcon || (isTimeLikePicker.value ? 'el-icon-time' : 'el-icon-date')
})
const showClose = ref(false)
const onClearIconClick = event =>{
@ -383,7 +400,7 @@ export default defineComponent({
}
}
const valueIsEmpty = computed(() => {
return !props.modelValue
return !props.modelValue || (Array.isArray(props.modelValue) && !props.modelValue.length)
})
const onMouseEnter = () => {
if (props.readonly || pickerDisabled.value) return
@ -410,7 +427,7 @@ export default defineComponent({
pickerVisible.value = false
}
const userInput =ref(null)
const userInput = ref(null)
const handleChange = () => {
if (userInput.value) {
@ -434,10 +451,12 @@ export default defineComponent({
}
const parseUserInputToDayjs = value => {
if (!value) return null
return pickerOptions.value.parseUserInput(value)
}
const formatDayjsToString = value => {
if (!value) return null
return pickerOptions.value.formatToString(value)
}
@ -535,16 +554,16 @@ export default defineComponent({
}
const pickerOptions = ref({} as PickerOptions)
const pickerHub = mitt()
pickerHub.on('SetPickerOption', e => {
const onSetPickerOption = e => {
pickerOptions.value[e[0]] = e[1]
pickerOptions.value.panelReady = true
})
}
provide('EP_PICKER_BASE', {
hub: pickerHub,
props,
})
return {
isDatesPicker,
handleEndChange,
handleStartChange,
handleStartInput,
@ -568,6 +587,7 @@ export default defineComponent({
setSelectionRange,
refContainer,
pickerDisabled,
onSetPickerOption,
}
},
})

View File

@ -59,7 +59,6 @@ import {
nextTick,
computed,
onMounted,
inject,
Ref,
PropType,
watch,
@ -97,9 +96,18 @@ export default defineComponent({
type: String,
default: '', // 'a': am/pm; 'A': AM/PM
},
disabledHours: {
type: Function,
},
disabledMinutes: {
type: Function,
},
disabledSeconds: {
type: Function,
},
},
emits: ['change', 'select-range'],
emits: ['change', 'select-range', 'set-option'],
setup(props, ctx) {
// data
@ -312,18 +320,17 @@ export default defineComponent({
return `list${item.charAt(0).toUpperCase() + item.slice(1)}Ref`
}
const pickerPanel = inject('EP_TIMEPICK_PANEL') as any
pickerPanel.hub.emit('SetOption',[`${props.role}_scrollDown`, scrollDown])
pickerPanel.hub.emit('SetOption',[`${props.role}_emitSelectRange`, emitSelectRange])
ctx.emit('set-option',[`${props.role}_scrollDown`, scrollDown])
ctx.emit('set-option',[`${props.role}_emitSelectRange`, emitSelectRange])
const {
getHoursList,
getMinutesList,
getSecondsList,
} = getTimeLists(
pickerPanel.methods.disabledHours,
pickerPanel.methods.disabledMinutes,
pickerPanel.methods.disabledSeconds,
props.disabledHours,
props.disabledMinutes,
props.disabledSeconds,
)
watch(() => props.spinnerDate, () => {

View File

@ -7,12 +7,16 @@
<div class="el-time-panel__content" :class="{ 'has-seconds': showSeconds }">
<time-spinner
ref="spinner"
role="start"
:role="datetimeRole || 'start'"
:arrow-control="arrowControl"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
:spinner-date="parsedValue"
:disabled-hours="disabledHours"
:disabled-minutes="disabledMinutes"
:disabled-seconds="disabledSeconds"
@change="handleChange"
@set-option="onSetOption"
@select-range="setSelectionRange"
/>
</div>
@ -42,12 +46,10 @@ import {
ref,
computed,
inject,
provide,
PropType,
} from 'vue'
import { EVENT_CODE } from '@element-plus/utils/aria'
import { t } from '@element-plus/locale'
import mitt from 'mitt'
import TimeSpinner from './basic-time-spinner.vue'
import dayjs, { Dayjs } from 'dayjs'
import { getAvaliableArrs } from './useTimePicker'
@ -59,32 +61,31 @@ export default defineComponent({
props: {
visible: {
type: [Boolean],
type: Boolean,
default: false,
},
datetimeRole: {
type: String,
},
parsedValue: {
type: Dayjs as PropType<Dayjs>,
default: '',
type: [Object, String] as PropType<string | Dayjs>,
},
arrowControl: {
type: [Boolean],
type: Boolean,
default: false,
},
pickerOptions: {
type: Object,
default: () => ({}),
},
format: {
type: String,
default: '',
},
},
emits: ['pick', 'select-range'],
emits: ['pick', 'select-range', 'set-picker-option'],
setup(props, ctx) {
// data
const selectionRange = ref([0, 2])
const oldValue = ref(props.parsedValue)
// computed
const showSeconds = computed(() => {
return props.format.includes('ss')
@ -101,7 +102,7 @@ export default defineComponent({
return parsedDate.isSame(result)
}
const handleCancel = () => {
ctx.emit('pick', '', false, true)
ctx.emit('pick', oldValue.value, false)
}
const handleConfirm = (visible = false, first) => {
if (first) return
@ -157,11 +158,11 @@ export default defineComponent({
let avaliableArr
const method = avaliableMap[_]
if (_ === 'minute') {
avaliableArr = method(result.hour())
avaliableArr = method(result.hour(), props.datetimeRole)
} else if (_ === 'second') {
avaliableArr = method(result.hour(), result.minute())
avaliableArr = method(result.hour(), result.minute(), props.datetimeRole)
} else {
avaliableArr = method()
avaliableArr = method(props.datetimeRole)
}
if (avaliableArr && avaliableArr.length && !avaliableArr.includes(result[_]())) {
result = result[_](avaliableArr[0])
@ -181,31 +182,30 @@ export default defineComponent({
return value.format(props.format)
}
const pickerBase = inject('EP_PICKER_BASE') as any
pickerBase.hub.emit('SetPickerOption', ['isValidValue', isValidValue])
pickerBase.hub.emit('SetPickerOption', ['formatToString', formatToString])
pickerBase.hub.emit('SetPickerOption', ['parseUserInput', parseUserInput])
pickerBase.hub.emit('SetPickerOption',['handleKeydown', handleKeydown])
pickerBase.hub.emit('SetPickerOption',['getRangeAvaliableTime', getRangeAvaliableTime])
const timePickeOptions = {} as any
const pickerHub = mitt()
pickerHub.on('SetOption', e => {
timePickeOptions[e[0]] = e[1]
})
const getDefaultValue = () => {
return dayjs(defaultValue)
}
const { disabledHours, disabledMinutes, disabledSeconds } = pickerBase.props
ctx.emit('set-picker-option', ['isValidValue', isValidValue])
ctx.emit('set-picker-option', ['formatToString', formatToString])
ctx.emit('set-picker-option', ['parseUserInput', parseUserInput])
ctx.emit('set-picker-option',['handleKeydown', handleKeydown])
ctx.emit('set-picker-option',['getRangeAvaliableTime', getRangeAvaliableTime])
ctx.emit('set-picker-option',['getDefaultValue', getDefaultValue])
const timePickeOptions = {} as any
const onSetOption = e => {
timePickeOptions[e[0]] = e[1]
}
const pickerBase = inject('EP_PICKER_BASE') as any
const { disabledHours, disabledMinutes, disabledSeconds, defaultValue } = pickerBase.props
const {
getAvaliableHours,
getAvaliableMinutes,
getAvaliableSeconds,
} = getAvaliableArrs(disabledHours, disabledMinutes, disabledSeconds)
provide('EP_TIMEPICK_PANEL', {
hub: pickerHub,
methods: {
disabledHours, disabledMinutes, disabledSeconds,
},
})
return {
onSetOption,
t,
handleConfirm,
handleChange,
@ -213,6 +213,9 @@ export default defineComponent({
amPmMode,
showSeconds,
handleCancel,
disabledHours,
disabledMinutes,
disabledSeconds,
}
},
})

View File

@ -20,7 +20,11 @@
:am-pm-mode="amPmMode"
:arrow-control="arrowControl"
:spinner-date="minDate"
:disabled-hours="disabledHours_"
:disabled-minutes="disabledMinutes_"
:disabled-seconds="disabledSeconds_"
@change="handleMinChange"
@set-option="onSetOption"
@select-range="setMinSelectionRange"
/>
</div>
@ -38,7 +42,11 @@
:am-pm-mode="amPmMode"
:arrow-control="arrowControl"
:spinner-date="maxDate"
:disabled-hours="disabledHours_"
:disabled-minutes="disabledMinutes_"
:disabled-seconds="disabledSeconds_"
@change="handleMaxChange"
@set-option="onSetOption"
@select-range="setMaxSelectionRange"
/>
</div>
@ -72,10 +80,8 @@ import {
computed,
PropType,
inject,
provide,
} from 'vue'
import dayjs, { Dayjs } from 'dayjs'
import mitt from 'mitt'
import union from 'lodash/union'
import { t } from '@element-plus/locale'
import { EVENT_CODE } from '@element-plus/utils/aria'
@ -103,8 +109,7 @@ export default defineComponent({
default: false,
},
parsedValue: {
type: Array as PropType<Array<Dayjs>>,
default: '',
type: [Array, String] as PropType<string | Array<Dayjs>>,
},
format: {
type: String,
@ -112,13 +117,14 @@ export default defineComponent({
},
},
emits: ['pick', 'select-range'],
emits: ['pick', 'select-range', 'set-picker-option'],
setup(props, ctx) {
const minDate = computed(() => props.parsedValue[0])
const maxDate = computed(() => props.parsedValue[1])
const oldValue = ref(props.parsedValue)
const handleCancel = () =>{
ctx.emit('pick', null, null, true)
ctx.emit('pick', oldValue.value, null)
}
const showSeconds = computed(() => {
return props.format.includes('ss')
@ -290,31 +296,33 @@ export default defineComponent({
return value.format(props.format)
}
const pickerBase = inject('EP_PICKER_BASE') as any
pickerBase.hub.emit('SetPickerOption',['formatToString', formatToString])
pickerBase.hub.emit('SetPickerOption',['parseUserInput', parseUserInput])
pickerBase.hub.emit('SetPickerOption',['isValidValue', isValidValue])
pickerBase.hub.emit('SetPickerOption',['handleKeydown', handleKeydown])
pickerBase.hub.emit('SetPickerOption',['getRangeAvaliableTime', getRangeAvaliableTime])
const getDefaultValue = () => {
if (Array.isArray(defaultValue)) {
return defaultValue.map(_=> dayjs(_))
}
return [
dayjs(defaultValue),
dayjs(defaultValue).add(60,'m'),
]
}
ctx.emit('set-picker-option',['formatToString', formatToString])
ctx.emit('set-picker-option',['parseUserInput', parseUserInput])
ctx.emit('set-picker-option',['isValidValue', isValidValue])
ctx.emit('set-picker-option',['handleKeydown', handleKeydown])
ctx.emit('set-picker-option',['getDefaultValue', getDefaultValue])
ctx.emit('set-picker-option',['getRangeAvaliableTime', getRangeAvaliableTime])
const timePickeOptions = {} as any
const pickerHub = mitt()
pickerHub.on('SetOption', e => {
const onSetOption = e => {
timePickeOptions[e[0]] = e[1]
})
}
const { disabledHours, disabledMinutes, disabledSeconds } = pickerBase.props
provide('EP_TIMEPICK_PANEL', {
hub: pickerHub,
methods: {
disabledHours: disabledHours_,
disabledMinutes: disabledMinutes_,
disabledSeconds: disabledSeconds_,
},
})
const pickerBase = inject('EP_PICKER_BASE') as any
const { disabledHours, disabledMinutes, disabledSeconds, defaultValue } = pickerBase.props
return {
onSetOption,
setMaxSelectionRange,
setMinSelectionRange,
btnConfirmDisabled,
@ -329,6 +337,9 @@ export default defineComponent({
handleMaxChange,
minSelectableRange,
maxSelectableRange,
disabledHours_,
disabledMinutes_,
disabledSeconds_,
}
},
})

View File

@ -2,10 +2,10 @@ export const EVENT_CODE = {
tab: 'Tab',
enter: 'Enter',
space: 'Space',
left: 'ArrowLeft',
up: 'ArrowUp',
right: 'ArrowRight',
down: 'ArrowDown',
left: 'ArrowLeft', // 37
up: 'ArrowUp', // 38
right: 'ArrowRight', // 39
down: 'ArrowDown', // 40
esc: 'Escape',
delete: 'Delete',
backspace: 'Backspace',

View File

@ -25,7 +25,9 @@ Basic date picker measured by 'day'.
v-model="value2"
type="date"
placeholder="Pick a day"
:picker-options="pickerOptions">
:disabled-date="disabledDate"
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -34,31 +36,27 @@ Basic date picker measured by 'day'.
export default {
data() {
return {
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
shortcuts: [{
text: 'Today',
onClick(picker) {
picker.$emit('pick', new Date());
}
}, {
text: 'Yesterday',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
picker.emit('pick', date);
}
}, {
text: 'A week ago',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
picker.emit('pick', date);
}
}]
disabledDate(time) {
return time.getTime() > Date.now()
},
shortcuts: [{
text: 'Today',
value: new Date(),
}, {
text: 'Yesterday',
value: (() => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24)
return date
})(),
}, {
text: 'A week ago',
value: (() => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
return date
})(),
}],
value1: '',
value2: '',
};
@ -81,7 +79,7 @@ You can choose week, month, year or multiple dates by extending the standard dat
<el-date-picker
v-model="value1"
type="week"
format="Week WW"
format="[Week] ww"
placeholder="Pick a week">
</el-date-picker>
</div>
@ -157,7 +155,8 @@ Picking a date range is supported.
range-separator="To"
start-placeholder="Start date"
end-placeholder="End date"
:picker-options="pickerOptions">
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -166,33 +165,31 @@ Picking a date range is supported.
export default {
data() {
return {
pickerOptions: {
shortcuts: [{
text: 'Last week',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last month',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last 3 months',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
shortcuts: [{
text: 'Last week',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
})(),
}, {
text: 'Last month',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
return [start, end]
})(),
}, {
text: 'Last 3 months',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
return [start, end]
})(),
}],
value1: '',
value2: ''
};
@ -230,7 +227,8 @@ Picking a month range is supported.
range-separator="To"
start-placeholder="Start month"
end-placeholder="End month"
:picker-options="pickerOptions">
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -239,29 +237,25 @@ Picking a month range is supported.
export default {
data() {
return {
pickerOptions: {
shortcuts: [{
text: 'This month',
onClick(picker) {
picker.$emit('pick', [new Date(), new Date()]);
}
}, {
text: 'This year',
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last 6 months',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit('pick', [start, end]);
}
}]
},
shortcuts: [{
text: 'This month',
value: [new Date(), new Date()],
}, {
text: 'This year',
value: (() => {
const end = new Date()
const start = new Date(new Date().getFullYear(), 0)
return [start, end]
})(),
}, {
text: 'Last 6 months',
value: (() => {
const end = new Date()
const start = new Date()
start.setMonth(start.getMonth() - 6)
return [start, end]
})(),
}],
value1: '',
value2: ''
};
@ -286,7 +280,7 @@ If type is `daterange`, `default-value` sets the left side calendar.
v-model="value1"
type="date"
placeholder="Pick a date"
default-value="2010-10-01">
:default-value="new Date(2010, 9, 1)">
</el-date-picker>
</div>
<div class="block">
@ -297,7 +291,7 @@ If type is `daterange`, `default-value` sets the left side calendar.
align="right"
start-placeholder="Start Date"
end-placeholder="End Date"
default-value="2010-10-01">
:default-value="[new Date(2010, 9, 1), new Date(2010, 10, 1)]">
</el-date-picker>
</div>
</template>
@ -358,29 +352,7 @@ Pay attention to capitalization
v-model="value1"
type="date"
placeholder="Pick a Date"
format="yyyy/MM/dd">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Use value-format</span>
<div class="demonstration">Value: {{ value2 }}</div>
<el-date-picker
v-model="value2"
type="date"
placeholder="Pick a Date"
format="yyyy/MM/dd"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Timestamp</span>
<div class="demonstration">Value{{ value3 }}</div>
<el-date-picker
v-model="value3"
type="date"
placeholder="Pick a Date"
format="yyyy/MM/dd"
value-format="timestamp">
format="YYYY/MM/DD">
</el-date-picker>
</div>
</template>
@ -413,7 +385,7 @@ When picking a date range, you can assign the time part for start date and end d
type="daterange"
start-placeholder="Start date"
end-placeholder="End date"
:default-time="['00:00:00', '23:59:59']">
:default-time="[new Date(2000, 1, 1, 0 , 0,0), new Date(2000, 2, 1, 23 , 59,59)]">
</el-date-picker>
</div>
</template>

View File

@ -26,7 +26,9 @@ Date Picker básico por "día".
v-model="value2"
type="date"
placeholder="Pick a day"
:picker-options="pickerOptions">
:disabled-date="disabledDate"
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -35,31 +37,27 @@ Date Picker básico por "día".
export default {
data() {
return {
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
shortcuts: [{
text: 'Today',
onClick(picker) {
picker.$emit('pick', new Date());
}
}, {
text: 'Yesterday',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
picker.emit('pick', date);
}
}, {
text: 'A week ago',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
picker.emit('pick', date);
}
}]
disabledDate(time) {
return time.getTime() > Date.now()
},
shortcuts: [{
text: 'Today',
value: new Date(),
}, {
text: 'Yesterday',
value: (() => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24)
return date
})(),
}, {
text: 'A week ago',
value: (() => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
return date
})(),
}],
value1: '',
value2: '',
};
@ -83,7 +81,7 @@ Puede elegir la semana, el mes, el año o varias fechas ampliando el componente
<el-date-picker
v-model="value1"
type="week"
format="Week WW"
format="[Week] ww"
placeholder="Pick a week">
</el-date-picker>
</div>
@ -159,7 +157,8 @@ Se soporta la selección de un rango de fechas.
range-separator="To"
start-placeholder="Start date"
end-placeholder="End date"
:picker-options="pickerOptions">
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -168,33 +167,31 @@ Se soporta la selección de un rango de fechas.
export default {
data() {
return {
pickerOptions: {
shortcuts: [{
text: 'Last week',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last month',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last 3 months',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
shortcuts: [{
text: 'Last week',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
})(),
}, {
text: 'Last month',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
return [start, end]
})(),
}, {
text: 'Last 3 months',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
return [start, end]
})(),
}],
value1: '',
value2: ''
};
@ -232,7 +229,8 @@ Se admite la selección de un intervalo de un mes.
range-separator="To"
start-placeholder="Start month"
end-placeholder="End month"
:picker-options="pickerOptions">
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -241,29 +239,25 @@ Se admite la selección de un intervalo de un mes.
export default {
data() {
return {
pickerOptions: {
shortcuts: [{
text: 'This month',
onClick(picker) {
picker.$emit('pick', [new Date(), new Date()]);
}
}, {
text: 'This year',
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Last 6 months',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit('pick', [start, end]);
}
}]
},
shortcuts: [{
text: 'This month',
value: [new Date(), new Date()],
}, {
text: 'This year',
value: (() => {
const end = new Date()
const start = new Date(new Date().getFullYear(), 0)
return [start, end]
})(),
}, {
text: 'Last 6 months',
value: (() => {
const end = new Date()
const start = new Date()
start.setMonth(start.getMonth() - 6)
return [start, end]
})(),
}],
value1: '',
value2: ''
};
@ -288,7 +282,7 @@ Si el tipo es `daterange`, `default-value` establece el calendario del lado izqu
v-model="value1"
type="date"
placeholder="Pick a date"
default-value="2010-10-01">
:default-value="new Date(2010, 9, 1)">
</el-date-picker>
</div>
<div class="block">
@ -299,7 +293,7 @@ Si el tipo es `daterange`, `default-value` establece el calendario del lado izqu
align="right"
start-placeholder="Start Date"
end-placeholder="End Date"
default-value="2010-10-01">
:default-value="[new Date(2010, 9, 1), new Date(2010, 10, 1)]">
</el-date-picker>
</div>
</template>
@ -358,29 +352,7 @@ Preste atención a la capitalización
v-model="value1"
type="date"
placeholder="Pick a Date"
format="yyyy/MM/dd">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Use value-format</span>
<div class="demonstration">Value: {{ value2 }}</div>
<el-date-picker
v-model="value2"
type="date"
placeholder="Pick a Date"
format="yyyy/MM/dd"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Timestamp</span>
<div class="demonstration">Value{{ value3 }}</div>
<el-date-picker
v-model="value3"
type="date"
placeholder="Pick a Date"
format="yyyy/MM/dd"
value-format="timestamp">
format="YYYY/MM/DD">
</el-date-picker>
</div>
</template>
@ -414,7 +386,7 @@ Al seleccionar un intervalo de fechas, puede asignar la hora para la fecha de in
type="daterange"
start-placeholder="Start date"
end-placeholder="End date"
:default-time="['00:00:00', '23:59:59']">
:default-time="[new Date(2000, 1, 1, 0 , 0,0), new Date(2000, 2, 1, 23 , 59,59)]">
</el-date-picker>
</div>
</template>

View File

@ -24,7 +24,9 @@ L'unité de base du DatePicker est le jour.
v-model="value2"
type="date"
placeholder="Choississez un jour"
:picker-options="pickerOptions">
:disabled-date="disabledDate"
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -33,31 +35,27 @@ L'unité de base du DatePicker est le jour.
export default {
data() {
return {
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
shortcuts: [{
text: 'Aujourd\'hui',
onClick(picker) {
picker.$emit('pick', new Date());
}
}, {
text: 'Hier',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
picker.emit('pick', date);
}
}, {
text: 'Il y a une semaine',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
picker.emit('pick', date);
}
}]
disabledDate(time) {
return time.getTime() > Date.now()
},
shortcuts: [{
text: 'Today',
value: new Date(),
}, {
text: 'Yesterday',
value: (() => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24)
return date
})(),
}, {
text: 'A week ago',
value: (() => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
return date
})(),
}],
value1: '',
value2: '',
};
@ -80,7 +78,7 @@ Vous pouvez sélectionner une semaine, un mois, une année ou plusieurs dates en
<el-date-picker
v-model="value1"
type="week"
format="Week WW"
format="[Week] ww"
placeholder="Sélectionnez une semaine">
</el-date-picker>
</div>
@ -156,7 +154,8 @@ Vous pouvez sélectionner une plage de dates.
range-separator="à"
start-placeholder="Date de début"
end-placeholder="Date de fin"
:picker-options="pickerOptions">
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -165,33 +164,31 @@ Vous pouvez sélectionner une plage de dates.
export default {
data() {
return {
pickerOptions: {
shortcuts: [{
text: 'Semaine dernière',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Mois dernier',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Trois derniers mois',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
shortcuts: [{
text: 'Last week',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
})(),
}, {
text: 'Last month',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
return [start, end]
})(),
}, {
text: 'Last 3 months',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
return [start, end]
})(),
}],
value1: '',
value2: ''
};
@ -229,7 +226,8 @@ Vous pouvez sélectionner une plage de mois.
range-separator="à"
start-placeholder="Mois de début"
end-placeholder="Mois de fin"
:picker-options="pickerOptions">
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -238,29 +236,25 @@ Vous pouvez sélectionner une plage de mois.
export default {
data() {
return {
pickerOptions: {
shortcuts: [{
text: 'Ce mois',
onClick(picker) {
picker.$emit('pick', [new Date(), new Date()]);
}
}, {
text: 'Cette année',
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit('pick', [start, end]);
}
}, {
text: 'Les derniers 6 mois',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit('pick', [start, end]);
}
}]
},
shortcuts: [{
text: 'This month',
value: [new Date(), new Date()],
}, {
text: 'This year',
value: (() => {
const end = new Date()
const start = new Date(new Date().getFullYear(), 0)
return [start, end]
})(),
}, {
text: 'Last 6 months',
value: (() => {
const end = new Date()
const start = new Date()
start.setMonth(start.getMonth() - 6)
return [start, end]
})(),
}],
value1: '',
value2: ''
};
@ -285,7 +279,7 @@ Si le type est `daterange`, `default-value` configure la panneau de gauche.
v-model="value1"
type="date"
placeholder="Sélectionnez une date"
default-value="2010-10-01">
:default-value="new Date(2010, 9, 1)">
</el-date-picker>
</div>
<div class="block">
@ -296,7 +290,7 @@ Si le type est `daterange`, `default-value` configure la panneau de gauche.
align="right"
start-placeholder="Date de début"
end-placeholder="Date de fin"
default-value="2010-10-01">
:default-value="[new Date(2010, 9, 1), new Date(2010, 10, 1)]">
</el-date-picker>
</div>
</template>
@ -358,29 +352,7 @@ Attention à la capitalisation !
v-model="value1"
type="date"
placeholder="Sélectionnez une date"
format="yyyy/MM/dd">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Utilise value-format</span>
<div class="demonstration">Value: {{ value2 }}</div>
<el-date-picker
v-model="value2"
type="date"
placeholder="Sélectionnez une date"
format="yyyy/MM/dd"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Timestamp</span>
<div class="demonstration">Value{{ value3 }}</div>
<el-date-picker
v-model="value3"
type="date"
placeholder="Sélectionnez une date"
format="yyyy/MM/dd"
value-format="timestamp">
format="YYYY/MM/DD">
</el-date-picker>
</div>
</template>
@ -413,7 +385,7 @@ Lorsque vous choisissez une plage de dates, vous pouvez assigner l'horaire de d
type="daterange"
start-placeholder="Date de début"
end-placeholder="Date de fin"
:default-time="['00:00:00', '23:59:59']">
:default-time="[new Date(2000, 1, 1, 0 , 0,0), new Date(2000, 2, 1, 23 , 59,59)]">
</el-date-picker>
</div>
</template>

View File

@ -25,7 +25,9 @@
align="right"
type="date"
placeholder="选择日期"
:picker-options="pickerOptions">
:disabled-date="disabledDate"
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -34,31 +36,27 @@
export default {
data() {
return {
pickerOptions: {
disabledDate(time) {
return time.getTime() > Date.now();
},
shortcuts: [{
text: '今天',
onClick(picker) {
picker.$emit('pick', new Date());
}
}, {
text: '昨天',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
picker.emit('pick', date);
}
}, {
text: '一周前',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
picker.emit('pick', date);
}
}]
disabledDate(time) {
return time.getTime() > Date.now()
},
shortcuts: [{
text: 'Today',
value: new Date(),
}, {
text: 'Yesterday',
value: (() => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24)
return date
})(),
}, {
text: 'A week ago',
value: (() => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
return date
})(),
}],
value1: '',
value2: '',
};
@ -80,7 +78,7 @@
<el-date-picker
v-model="value1"
type="week"
format="yyyy 第 WW 周"
format="gggg 第 ww 周"
placeholder="选择周">
</el-date-picker>
</div>
@ -154,7 +152,8 @@
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:picker-options="pickerOptions">
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -163,33 +162,31 @@
export default {
data() {
return {
pickerOptions: {
shortcuts: [{
text: '最近一周',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近三个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
shortcuts: [{
text: '最近一周',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
})(),
}, {
text: '最近一个月',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
return [start, end]
})(),
}, {
text: '最近三个月',
value: (() => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
return [start, end]
})(),
}],
value1: '',
value2: ''
};
@ -227,7 +224,8 @@
range-separator="至"
start-placeholder="开始月份"
end-placeholder="结束月份"
:picker-options="pickerOptions">
:shortcuts="shortcuts"
>
</el-date-picker>
</div>
</template>
@ -236,29 +234,25 @@
export default {
data() {
return {
pickerOptions: {
shortcuts: [{
text: '本月',
onClick(picker) {
picker.$emit('pick', [new Date(), new Date()]);
}
}, {
text: '今年至今',
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近六个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit('pick', [start, end]);
}
}]
},
shortcuts: [{
text: '本月',
value: [new Date(), new Date()],
}, {
text: '今年至今',
value: (() => {
const end = new Date()
const start = new Date(new Date().getFullYear(), 0)
return [start, end]
})(),
}, {
text: '最近六个月',
value: (() => {
const end = new Date()
const start = new Date()
start.setMonth(start.getMonth() - 6)
return [start, end]
})(),
}],
value1: '',
value2: ''
};
@ -268,6 +262,49 @@
```
:::
### Default Value (需要翻译)
If user hasn't picked a date, shows today's calendar by default. You can use `default-value` to set another date. Its value should be parsable by `new Date()`.
If type is `daterange`, `default-value` sets the left side calendar.
:::demo
```html
<template>
<div class="block">
<span class="demonstration">date</span>
<el-date-picker
v-model="value1"
type="date"
placeholder="Pick a date"
:default-value="new Date(2010, 9, 1)">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">daterange</span>
<el-date-picker
v-model="value2"
type="daterange"
align="right"
start-placeholder="Start Date"
end-placeholder="End Date"
:default-value="[new Date(2010, 9, 1), new Date(2010, 10, 1)]">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
value1: '',
value2: ''
};
}
};
</script>
```
:::
### 日期格式
@ -311,29 +348,7 @@
v-model="value1"
type="date"
placeholder="选择日期"
format="yyyy 年 MM 月 dd 日">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">使用 value-format</span>
<div class="demonstration">值:{{ value2 }}</div>
<el-date-picker
v-model="value2"
type="date"
placeholder="选择日期"
format="yyyy 年 MM 月 dd 日"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">时间戳</span>
<div class="demonstration">值:{{ value3 }}</div>
<el-date-picker
v-model="value3"
type="date"
placeholder="选择日期"
format="yyyy 年 MM 月 dd 日"
value-format="timestamp">
format="YYYY 年 MM 月 DD 日">
</el-date-picker>
</div>
</template>
@ -366,7 +381,7 @@
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="['00:00:00', '23:59:59']">
:default-time="[new Date(2000, 1, 1, 0 , 0,0), new Date(2000, 2, 1, 23 , 59,59)]">
</el-date-picker>
</div>
</template>

View File

@ -1,14 +0,0 @@
<template>
<div>
change me
</div>
</template>
<script>
export default {
data() {
return {}
},
}
</script>