mirror of
https://github.com/tenrok/vue2-datepicker.git
synced 2026-06-12 21:32:26 +03:00
refactor: 3.0
This commit is contained in:
@@ -1,458 +0,0 @@
|
||||
<template>
|
||||
<div class="mx-calendar" :class="'mx-calendar-panel-' + panel.toLowerCase()">
|
||||
<div class="mx-calendar-header">
|
||||
<a v-show="panel !== 'TIME'" class="mx-icon-last-year" @click="handleIconYear(-1)">«</a>
|
||||
<a v-show="panel === 'DATE'" class="mx-icon-last-month" @click="handleIconMonth(-1)">‹</a>
|
||||
<a v-show="panel !== 'TIME'" class="mx-icon-next-year" @click="handleIconYear(1)">»</a>
|
||||
<a v-show="panel === 'DATE'" class="mx-icon-next-month" @click="handleIconMonth(1)">›</a>
|
||||
<a
|
||||
v-show="panel === 'DATE'"
|
||||
class="mx-current-month"
|
||||
@click="handleBtnMonth"
|
||||
>{{months[calendarMonth]}}</a>
|
||||
<a
|
||||
v-show="panel === 'DATE' || panel === 'MONTH'"
|
||||
class="mx-current-year"
|
||||
@click="handleBtnYear"
|
||||
>{{calendarYear}}</a>
|
||||
<a v-show="panel === 'YEAR'" class="mx-current-year">{{yearHeader}}</a>
|
||||
<a v-show="panel === 'TIME'" class="mx-time-header" @click="handleTimeHeader">{{timeHeader}}</a>
|
||||
</div>
|
||||
<div class="mx-calendar-content">
|
||||
<panel-date
|
||||
v-show="panel === 'DATE'"
|
||||
:value="value"
|
||||
:date-format="dateFormat"
|
||||
:calendar-month="calendarMonth"
|
||||
:calendar-year="calendarYear"
|
||||
:start-at="startAt"
|
||||
:end-at="endAt"
|
||||
:first-day-of-week="firstDayOfWeek"
|
||||
:disabled-date="isDisabledDate"
|
||||
@select="selectDate"
|
||||
/>
|
||||
<panel-year
|
||||
v-show="panel === 'YEAR'"
|
||||
:value="value"
|
||||
:disabled-year="isDisabledYear"
|
||||
:first-year="firstYear"
|
||||
@select="selectYear"
|
||||
/>
|
||||
<panel-month
|
||||
v-show="panel === 'MONTH'"
|
||||
:value="value"
|
||||
:disabled-month="isDisabledMonth"
|
||||
:calendar-year="calendarYear"
|
||||
@select="selectMonth"
|
||||
/>
|
||||
<panel-time
|
||||
v-show="panel === 'TIME'"
|
||||
:minute-step="minuteStep"
|
||||
:time-picker-options="timePickerOptions"
|
||||
:time-select-options="timeSelectOptions"
|
||||
:value="value"
|
||||
:disabled-time="isDisabledTime"
|
||||
:time-type="timeType"
|
||||
@select="selectTime"
|
||||
@pick="pickTime"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isValidDate, isDateObejct, formatDate } from '@/utils/index'
|
||||
import locale from '@/mixins/locale'
|
||||
import emitter from '@/mixins/emitter'
|
||||
import scrollIntoView from '@/utils/scroll-into-view'
|
||||
import PanelDate from '@/panel/date'
|
||||
import PanelYear from '@/panel/year'
|
||||
import PanelMonth from '@/panel/month'
|
||||
import PanelTime from '@/panel/time'
|
||||
|
||||
export default {
|
||||
name: 'CalendarPanel',
|
||||
components: { PanelDate, PanelYear, PanelMonth, PanelTime },
|
||||
mixins: [locale, emitter],
|
||||
props: {
|
||||
value: {
|
||||
default: null,
|
||||
validator: function (val) {
|
||||
return val === null || isValidDate(val)
|
||||
}
|
||||
},
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'date' // ['date', 'datetime'] zendy added 'month', 'year', mxie added "time"
|
||||
},
|
||||
dateFormat: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD'
|
||||
},
|
||||
index: Number,
|
||||
// below user set
|
||||
defaultValue: {
|
||||
validator: function (val) {
|
||||
return isValidDate(val)
|
||||
}
|
||||
},
|
||||
firstDayOfWeek: {
|
||||
default: 7,
|
||||
type: Number,
|
||||
validator: val => val >= 1 && val <= 7
|
||||
},
|
||||
notBefore: {
|
||||
default: null,
|
||||
validator: function (val) {
|
||||
return !val || isValidDate(val)
|
||||
}
|
||||
},
|
||||
notAfter: {
|
||||
default: null,
|
||||
validator: function (val) {
|
||||
return !val || isValidDate(val)
|
||||
}
|
||||
},
|
||||
disabledDays: {
|
||||
type: [Array, Function],
|
||||
default: function () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
minuteStep: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: val => val >= 0 && val <= 60
|
||||
},
|
||||
timeSelectOptions: {
|
||||
type: Object,
|
||||
default () {
|
||||
return null
|
||||
}
|
||||
},
|
||||
timePickerOptions: {
|
||||
type: [Object, Function],
|
||||
default () {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const now = this.getNow(this.value)
|
||||
const calendarYear = now.getFullYear()
|
||||
const calendarMonth = now.getMonth()
|
||||
const firstYear = Math.floor(calendarYear / 10) * 10
|
||||
return {
|
||||
panel: 'NONE',
|
||||
dates: [],
|
||||
calendarMonth,
|
||||
calendarYear,
|
||||
firstYear
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
now: {
|
||||
get () {
|
||||
return new Date(this.calendarYear, this.calendarMonth).getTime()
|
||||
},
|
||||
set (val) {
|
||||
const now = new Date(val)
|
||||
this.calendarYear = now.getFullYear()
|
||||
this.calendarMonth = now.getMonth()
|
||||
}
|
||||
},
|
||||
timeType () {
|
||||
const h = /h+/.test(this.$parent.format) ? '12' : '24'
|
||||
const a = /A/.test(this.$parent.format) ? 'A' : 'a'
|
||||
return [h, a]
|
||||
},
|
||||
timeHeader () {
|
||||
if (this.type === 'time') {
|
||||
return this.$parent.format
|
||||
}
|
||||
return this.value && formatDate(this.value, this.dateFormat)
|
||||
},
|
||||
yearHeader () {
|
||||
return this.firstYear + ' ~ ' + (this.firstYear + 9)
|
||||
},
|
||||
months () {
|
||||
return this.t('months')
|
||||
},
|
||||
notBeforeTime () {
|
||||
return this.getCriticalTime(this.notBefore)
|
||||
},
|
||||
notAfterTime () {
|
||||
return this.getCriticalTime(this.notAfter)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: 'updateNow'
|
||||
},
|
||||
defaultValue: {
|
||||
handler: 'updateNow'
|
||||
},
|
||||
visible: {
|
||||
immediate: true,
|
||||
handler: 'init'
|
||||
},
|
||||
panel: {
|
||||
handler: 'handelPanelChange'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handelPanelChange (panel, oldPanel) {
|
||||
this.dispatch('DatePicker', 'panel-change', [panel, oldPanel])
|
||||
if (panel === 'YEAR') {
|
||||
this.firstYear = Math.floor(this.calendarYear / 10) * 10
|
||||
} else if (panel === 'TIME') {
|
||||
this.$nextTick(() => {
|
||||
const list = this.$el.querySelectorAll('.mx-panel-time .mx-time-list')
|
||||
for (let i = 0, len = list.length; i < len; i++) {
|
||||
const el = list[i]
|
||||
scrollIntoView(el, el.querySelector('.actived'))
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
init (val) {
|
||||
if (val) {
|
||||
const type = this.type
|
||||
if (type === 'month') {
|
||||
this.showPanelMonth()
|
||||
} else if (type === 'year') {
|
||||
this.showPanelYear()
|
||||
} else if (type === 'time') {
|
||||
this.showPanelTime()
|
||||
} else {
|
||||
this.showPanelDate()
|
||||
}
|
||||
} else {
|
||||
this.showPanelNone()
|
||||
this.updateNow(this.value)
|
||||
}
|
||||
},
|
||||
getNow (value) {
|
||||
return value
|
||||
? new Date(value)
|
||||
: this.defaultValue && isValidDate(this.defaultValue)
|
||||
? new Date(this.defaultValue)
|
||||
: new Date()
|
||||
},
|
||||
// 根据value更新日历
|
||||
updateNow (value) {
|
||||
const oldNow = this.now
|
||||
this.now = this.getNow(value)
|
||||
if (this.visible && this.now !== oldNow) {
|
||||
this.dispatch('DatePicker', 'calendar-change', [
|
||||
new Date(this.now),
|
||||
new Date(oldNow)
|
||||
])
|
||||
}
|
||||
},
|
||||
getCriticalTime (value) {
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
const date = new Date(value)
|
||||
if (this.type === 'year') {
|
||||
return new Date(date.getFullYear(), 0).getTime()
|
||||
} else if (this.type === 'month') {
|
||||
return new Date(date.getFullYear(), date.getMonth()).getTime()
|
||||
} else if (this.type === 'date') {
|
||||
return date.setHours(0, 0, 0, 0)
|
||||
}
|
||||
return date.getTime()
|
||||
},
|
||||
inBefore (time, startAt) {
|
||||
if (startAt === undefined) {
|
||||
startAt = this.startAt
|
||||
}
|
||||
return (
|
||||
(this.notBeforeTime && time < this.notBeforeTime) ||
|
||||
(startAt && time < this.getCriticalTime(startAt))
|
||||
)
|
||||
},
|
||||
inAfter (time, endAt) {
|
||||
if (endAt === undefined) {
|
||||
endAt = this.endAt
|
||||
}
|
||||
return (
|
||||
(this.notAfterTime && time > this.notAfterTime) ||
|
||||
(endAt && time > this.getCriticalTime(endAt))
|
||||
)
|
||||
},
|
||||
inDisabledDays (time) {
|
||||
if (Array.isArray(this.disabledDays)) {
|
||||
return this.disabledDays.some(v => this.getCriticalTime(v) === time)
|
||||
} else if (typeof this.disabledDays === 'function') {
|
||||
return this.disabledDays(new Date(time))
|
||||
}
|
||||
return false
|
||||
},
|
||||
isDisabledYear (year) {
|
||||
const time = new Date(year, 0).getTime()
|
||||
const maxTime = new Date(year + 1, 0).getTime() - 1
|
||||
return (
|
||||
this.inBefore(maxTime) ||
|
||||
this.inAfter(time) ||
|
||||
(this.type === 'year' && this.inDisabledDays(time))
|
||||
)
|
||||
},
|
||||
isDisabledMonth (month) {
|
||||
const time = new Date(this.calendarYear, month).getTime()
|
||||
const maxTime = new Date(this.calendarYear, month + 1).getTime() - 1
|
||||
return (
|
||||
this.inBefore(maxTime) ||
|
||||
this.inAfter(time) ||
|
||||
(this.type === 'month' && this.inDisabledDays(time))
|
||||
)
|
||||
},
|
||||
isDisabledDate (date) {
|
||||
const time = new Date(date).getTime()
|
||||
const maxTime = new Date(date).setHours(23, 59, 59, 999)
|
||||
return (
|
||||
this.inBefore(maxTime) ||
|
||||
this.inAfter(time) ||
|
||||
this.inDisabledDays(time)
|
||||
)
|
||||
},
|
||||
isDisabledTime (date, startAt, endAt) {
|
||||
const time = new Date(date).getTime()
|
||||
return (
|
||||
this.inBefore(time, startAt) ||
|
||||
this.inAfter(time, endAt) ||
|
||||
this.inDisabledDays(time)
|
||||
)
|
||||
},
|
||||
selectDate (date) {
|
||||
if (this.type === 'datetime') {
|
||||
let time = new Date(date)
|
||||
if (isDateObejct(this.value)) {
|
||||
time.setHours(
|
||||
this.value.getHours(),
|
||||
this.value.getMinutes(),
|
||||
this.value.getSeconds()
|
||||
)
|
||||
}
|
||||
if (this.isDisabledTime(time)) {
|
||||
time.setHours(0, 0, 0, 0)
|
||||
if (
|
||||
this.notBefore &&
|
||||
time.getTime() < new Date(this.notBefore).getTime()
|
||||
) {
|
||||
time = new Date(this.notBefore)
|
||||
}
|
||||
if (
|
||||
this.startAt &&
|
||||
time.getTime() < new Date(this.startAt).getTime()
|
||||
) {
|
||||
time = new Date(this.startAt)
|
||||
}
|
||||
}
|
||||
this.selectTime(time)
|
||||
this.showPanelTime()
|
||||
return
|
||||
}
|
||||
this.$emit('select-date', date)
|
||||
},
|
||||
selectYear (year) {
|
||||
this.changeCalendarYear(year)
|
||||
if (this.type.toLowerCase() === 'year') {
|
||||
return this.selectDate(new Date(this.now))
|
||||
}
|
||||
this.dispatch('DatePicker', 'select-year', [year, this.index])
|
||||
this.showPanelMonth()
|
||||
},
|
||||
selectMonth (month) {
|
||||
this.changeCalendarMonth(month)
|
||||
if (this.type.toLowerCase() === 'month') {
|
||||
return this.selectDate(new Date(this.now))
|
||||
}
|
||||
this.dispatch('DatePicker', 'select-month', [month, this.index])
|
||||
this.showPanelDate()
|
||||
},
|
||||
selectTime (time) {
|
||||
this.$emit('select-time', time, false)
|
||||
},
|
||||
pickTime (time) {
|
||||
this.$emit('select-time', time, true)
|
||||
},
|
||||
changeCalendarYear (year) {
|
||||
this.updateNow(new Date(year, this.calendarMonth))
|
||||
},
|
||||
changeCalendarMonth (month) {
|
||||
this.updateNow(new Date(this.calendarYear, month))
|
||||
},
|
||||
getSibling () {
|
||||
const calendars = this.$parent.$children.filter(
|
||||
v => v.$options.name === this.$options.name
|
||||
)
|
||||
const index = calendars.indexOf(this)
|
||||
const sibling = calendars[index ^ 1]
|
||||
return sibling
|
||||
},
|
||||
handleIconMonth (flag) {
|
||||
const month = this.calendarMonth
|
||||
this.changeCalendarMonth(month + flag)
|
||||
this.$parent.$emit('change-calendar-month', {
|
||||
month,
|
||||
flag,
|
||||
vm: this,
|
||||
sibling: this.getSibling()
|
||||
})
|
||||
},
|
||||
handleIconYear (flag) {
|
||||
if (this.panel === 'YEAR') {
|
||||
this.changePanelYears(flag)
|
||||
} else {
|
||||
const year = this.calendarYear
|
||||
this.changeCalendarYear(year + flag)
|
||||
this.$parent.$emit('change-calendar-year', {
|
||||
year,
|
||||
flag,
|
||||
vm: this,
|
||||
sibling: this.getSibling()
|
||||
})
|
||||
}
|
||||
},
|
||||
handleBtnYear () {
|
||||
this.showPanelYear()
|
||||
},
|
||||
handleBtnMonth () {
|
||||
this.showPanelMonth()
|
||||
},
|
||||
handleTimeHeader () {
|
||||
if (this.type === 'time') {
|
||||
return
|
||||
}
|
||||
this.showPanelDate()
|
||||
},
|
||||
changePanelYears (flag) {
|
||||
this.firstYear = this.firstYear + flag * 10
|
||||
},
|
||||
showPanelNone () {
|
||||
this.panel = 'NONE'
|
||||
},
|
||||
showPanelTime () {
|
||||
this.panel = 'TIME'
|
||||
},
|
||||
showPanelDate () {
|
||||
this.panel = 'DATE'
|
||||
},
|
||||
showPanelYear () {
|
||||
this.panel = 'YEAR'
|
||||
},
|
||||
showPanelMonth () {
|
||||
this.panel = 'MONTH'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'mx-calendar',
|
||||
`mx-calendar-panel-${panel}`,
|
||||
{ 'mx-calendar-week-mode': type === 'week' },
|
||||
]"
|
||||
>
|
||||
<div class="mx-calendar-header">
|
||||
<button
|
||||
v-show="showIconDoubleArrow"
|
||||
class="mx-btn mx-btn-text mx-btn-icon-double-left"
|
||||
@click="handleIconDoubleLeftClick"
|
||||
>
|
||||
<i class="mx-icon-double-left"></i>
|
||||
</button>
|
||||
<button
|
||||
v-show="showIconArrow"
|
||||
class="mx-btn mx-btn-text mx-btn-icon-left"
|
||||
@click="handleIconLeftClick"
|
||||
>
|
||||
<i class="mx-icon-left"></i>
|
||||
</button>
|
||||
<button
|
||||
v-show="showIconDoubleArrow"
|
||||
class="mx-btn mx-btn-text mx-btn-icon-double-right"
|
||||
@click="handleIconDoubleRightClick"
|
||||
>
|
||||
<i class="mx-icon-double-right"></i>
|
||||
</button>
|
||||
<button
|
||||
v-show="showIconArrow"
|
||||
class="mx-btn mx-btn-text mx-btn-icon-right"
|
||||
@click="handleIconRightClick"
|
||||
>
|
||||
<i class="mx-icon-right"></i>
|
||||
</button>
|
||||
<span class="mx-calendar-header-label">
|
||||
<span v-if="panel === 'year'">{{ yearHeader }}</span>
|
||||
<button
|
||||
v-else-if="panel === 'month'"
|
||||
class="mx-btn mx-btn-text"
|
||||
@click="handelPanelChange('year')"
|
||||
>
|
||||
{{ monthHeader }}
|
||||
</button>
|
||||
<template v-else-if="panel === 'date'">
|
||||
<button
|
||||
v-for="item in dateHeader"
|
||||
:key="item.panel"
|
||||
:class="`mx-btn mx-btn-text mx-btn-current-${item.panel}`"
|
||||
@click="handelPanelChange(item.panel)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mx-calendar-content">
|
||||
<table-year
|
||||
v-show="panel === 'year'"
|
||||
:decade="calendarDecade"
|
||||
:get-cell-classes="getYearClasses"
|
||||
@select="handleSelectYear"
|
||||
></table-year>
|
||||
<table-month
|
||||
v-if="type !== 'year'"
|
||||
v-show="panel === 'month'"
|
||||
:get-cell-classes="getMonthClasses"
|
||||
@select="handleSelectMonth"
|
||||
></table-month>
|
||||
<table-date
|
||||
v-if="type !== 'year' && type !== 'month'"
|
||||
v-show="panel === 'date'"
|
||||
:calendar-year="calendarYear"
|
||||
:calendar-month="calendarMonth"
|
||||
:title-format="titleFormat"
|
||||
:show-week-number="typeof showWeekNumber === 'boolean' ? showWeekNumber : type === 'week'"
|
||||
:get-cell-classes="getDateClasses"
|
||||
:get-row-classes="getWeekState"
|
||||
@select="handleSelectDate"
|
||||
></table-date>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
subMonths,
|
||||
addMonths,
|
||||
subYears,
|
||||
addYears,
|
||||
setMonth,
|
||||
setYear,
|
||||
startOfYear,
|
||||
startOfMonth,
|
||||
startOfDay,
|
||||
} from 'date-fns';
|
||||
import localeMixin from '../mixin/locale';
|
||||
import formatMixin from '../mixin/format';
|
||||
import { getValidDate, isValidDate } from '../util/date';
|
||||
import TableDate from './table-date';
|
||||
import TableMonth from './table-month';
|
||||
import TableYear from './table-year';
|
||||
|
||||
export default {
|
||||
name: 'CalendarPanel',
|
||||
components: {
|
||||
TableDate,
|
||||
TableMonth,
|
||||
TableYear,
|
||||
},
|
||||
mixins: [localeMixin, formatMixin],
|
||||
props: {
|
||||
value: {},
|
||||
defaultValue: {
|
||||
validator: value => isValidDate(value),
|
||||
default() {
|
||||
const date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return date;
|
||||
},
|
||||
},
|
||||
disabledDate: {
|
||||
type: Function,
|
||||
default: () => false,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'date',
|
||||
},
|
||||
getClasses: {
|
||||
type: Function,
|
||||
default: () => [],
|
||||
},
|
||||
showWeekNumber: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
titleFormat: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD',
|
||||
},
|
||||
calendar: Date,
|
||||
},
|
||||
data() {
|
||||
const panels = ['date', 'year', 'month'];
|
||||
const panel = panels.indexOf(this.type) !== -1 ? this.type : 'date';
|
||||
return {
|
||||
panel,
|
||||
innerCalendar: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
innerValue() {
|
||||
const value = Array.isArray(this.value) ? this.value : [this.value];
|
||||
const map = {
|
||||
year: startOfYear,
|
||||
month: startOfMonth,
|
||||
date: startOfDay,
|
||||
};
|
||||
const start = map[this.type] || map.date;
|
||||
return value.filter(isValidDate).map(v => start(v));
|
||||
},
|
||||
calendarYear() {
|
||||
return this.innerCalendar.getFullYear();
|
||||
},
|
||||
calendarMonth() {
|
||||
return this.innerCalendar.getMonth();
|
||||
},
|
||||
calendarDecade() {
|
||||
return Math.floor(this.calendarYear / 10) * 10;
|
||||
},
|
||||
showIconDoubleArrow() {
|
||||
return this.panel === 'date' || this.panel === 'month' || this.panel === 'year';
|
||||
},
|
||||
showIconArrow() {
|
||||
return this.panel === 'date';
|
||||
},
|
||||
yearHeader() {
|
||||
return `${this.calendarDecade} ~ ${this.calendarDecade + 9}`;
|
||||
},
|
||||
monthHeader() {
|
||||
return this.calendarYear;
|
||||
},
|
||||
dateHeader() {
|
||||
const monthBeforeYear = this.t('monthBeforeYear');
|
||||
const yearFormat = this.t('yearFormat');
|
||||
const monthFormat = this.t('monthFormat') || 'MMM';
|
||||
const yearLabel = {
|
||||
panel: 'year',
|
||||
label: this.formatDate(this.innerCalendar, yearFormat),
|
||||
};
|
||||
const monthLabel = {
|
||||
panel: 'month',
|
||||
label: this.formatDate(this.innerCalendar, monthFormat),
|
||||
};
|
||||
return monthBeforeYear ? [monthLabel, yearLabel] : [yearLabel, monthLabel];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: 'initCalendar',
|
||||
},
|
||||
calendar: {
|
||||
handler: 'initCalendar',
|
||||
},
|
||||
defaultValue: {
|
||||
handler: 'initCalendar',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initCalendar() {
|
||||
let calendarDate = this.calendar;
|
||||
if (!isValidDate(calendarDate)) {
|
||||
calendarDate = getValidDate(this.innerValue[0], this.defaultValue);
|
||||
}
|
||||
this.innerCalendar = calendarDate;
|
||||
},
|
||||
emitDate(date, type) {
|
||||
if (!this.disabledDate(new Date(date))) {
|
||||
this.$emit('select', date, type);
|
||||
}
|
||||
},
|
||||
updateCalendar(date) {
|
||||
this.innerCalendar = date;
|
||||
this.$emit('update:calendar', date);
|
||||
},
|
||||
handelPanelChange(panel) {
|
||||
this.panel = panel;
|
||||
},
|
||||
handleIconLeftClick() {
|
||||
const nextCalendar = subMonths(this.innerCalendar, 1);
|
||||
this.updateCalendar(nextCalendar);
|
||||
},
|
||||
handleIconRightClick() {
|
||||
const nextCalendar = addMonths(this.innerCalendar, 1);
|
||||
this.updateCalendar(nextCalendar);
|
||||
},
|
||||
handleIconDoubleLeftClick() {
|
||||
const nextCalendar = subYears(this.innerCalendar, this.panel === 'year' ? 10 : 1);
|
||||
this.updateCalendar(nextCalendar);
|
||||
},
|
||||
handleIconDoubleRightClick() {
|
||||
const nextCalendar = addYears(this.innerCalendar, this.panel === 'year' ? 10 : 1);
|
||||
this.updateCalendar(nextCalendar);
|
||||
},
|
||||
handleSelectYear(year) {
|
||||
if (this.type === 'year') {
|
||||
const date = this.getCellDate(year, 'year');
|
||||
this.emitDate(date, 'year');
|
||||
} else {
|
||||
const nextCalendar = setYear(this.innerCalendar, year);
|
||||
this.updateCalendar(nextCalendar);
|
||||
this.handelPanelChange('month');
|
||||
}
|
||||
},
|
||||
handleSelectMonth(month) {
|
||||
if (this.type === 'month') {
|
||||
const date = this.getCellDate(month, 'month');
|
||||
this.emitDate(date, 'month');
|
||||
} else {
|
||||
const nextCalendar = setMonth(this.innerCalendar, month);
|
||||
this.updateCalendar(nextCalendar);
|
||||
this.handelPanelChange('date');
|
||||
}
|
||||
},
|
||||
handleSelectDate(day) {
|
||||
const date = this.getCellDate(day, 'date');
|
||||
this.emitDate(date, this.type === 'week' ? 'week' : 'date');
|
||||
},
|
||||
getCellDate(value, type) {
|
||||
if (type === 'year') {
|
||||
return new Date(value, 0);
|
||||
}
|
||||
if (type === 'month') {
|
||||
return new Date(this.calendarYear, value);
|
||||
}
|
||||
return new Date(this.calendarYear, this.calendarMonth, value);
|
||||
},
|
||||
getDateClasses(day) {
|
||||
const cellDate = this.getCellDate(day, 'date');
|
||||
const notCurrentMonth = cellDate.getMonth() !== this.calendarMonth;
|
||||
const classes = [];
|
||||
if (cellDate.getTime() === new Date().setHours(0, 0, 0, 0)) {
|
||||
classes.push('today');
|
||||
}
|
||||
if (notCurrentMonth) {
|
||||
classes.push('not-current-month');
|
||||
}
|
||||
const state = this.getStateClass(cellDate);
|
||||
if (!(state === 'active' && notCurrentMonth)) {
|
||||
classes.push(state);
|
||||
}
|
||||
return classes.concat(this.getClasses(cellDate, this.innerValue, classes.join(' ')));
|
||||
},
|
||||
getMonthClasses(month) {
|
||||
if (this.type !== 'month') {
|
||||
return this.calendarMonth === month ? 'active' : '';
|
||||
}
|
||||
const classes = [];
|
||||
const cellDate = this.getCellDate(month, 'month');
|
||||
classes.push(this.getStateClass(cellDate));
|
||||
return classes.concat(this.getClasses(cellDate, this.innerValue, classes.join(' ')));
|
||||
},
|
||||
getYearClasses(year) {
|
||||
if (this.type !== 'year') {
|
||||
return this.calendarYear === year ? 'active' : '';
|
||||
}
|
||||
const classes = [];
|
||||
const cellDate = this.getCellDate(year, 'year');
|
||||
classes.push(this.getStateClass(cellDate));
|
||||
return classes.concat(this.getClasses(cellDate, this.innerValue, classes.join(' ')));
|
||||
},
|
||||
getStateClass(cellDate) {
|
||||
if (this.disabledDate(new Date(cellDate))) {
|
||||
return 'disabled';
|
||||
}
|
||||
if (this.innerValue.some(v => v.getTime() === cellDate.getTime())) {
|
||||
return 'active';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
getWeekState(row) {
|
||||
if (this.type !== 'week') return '';
|
||||
const start = this.getCellDate(row[0].day, 'date').getTime();
|
||||
const end = this.getCellDate(row[6].day, 'date').getTime();
|
||||
const active = this.innerValue.some(v => {
|
||||
const time = v.getTime();
|
||||
return time >= start && time <= end;
|
||||
});
|
||||
return active ? 'mx-active-week' : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,119 @@
|
||||
import { addMonths, subMonths, differenceInCalendarMonths } from 'date-fns';
|
||||
import CalendarPanel from './calendar-panel';
|
||||
import { getValidDate, isValidDate, isValidRangeDate } from '../util/date';
|
||||
|
||||
export default {
|
||||
components: { CalendarPanel },
|
||||
props: {
|
||||
...CalendarPanel.props,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
innerValue: [],
|
||||
calendars: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// Minimum difference between start and end calendars
|
||||
calendarMinDiff() {
|
||||
const map = {
|
||||
date: 1, // type:date min 1 month
|
||||
month: 1 * 12, // type:month min 1 year
|
||||
year: 10 * 12, // type:year min 10 year
|
||||
};
|
||||
return map[this.type] || map.date;
|
||||
},
|
||||
calendarMaxDiff() {
|
||||
return Infinity;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.innerValue = isValidRangeDate(this.value)
|
||||
? this.value
|
||||
: [new Date(NaN), new Date(NaN)];
|
||||
this.calendars = this.innerValue.map(v => getValidDate(v, this.defaultValue));
|
||||
this.validateCalendars(1);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleSelect(date, type) {
|
||||
const [startValue, endValue] = this.innerValue;
|
||||
if (isValidDate(startValue) && !isValidDate(endValue)) {
|
||||
if (startValue.getTime() > date.getTime()) {
|
||||
this.innerValue = [date, startValue];
|
||||
} else {
|
||||
this.innerValue = [startValue, date];
|
||||
}
|
||||
this.emitDate(this.innerValue, type);
|
||||
} else {
|
||||
this.innerValue = [date, new Date(NaN)];
|
||||
}
|
||||
},
|
||||
emitDate(dates, type) {
|
||||
this.$emit('select', dates, type);
|
||||
},
|
||||
updateStartCalendar(value) {
|
||||
this.calendars.splice(0, 1, value);
|
||||
this.validateCalendars(1);
|
||||
},
|
||||
updateEndCalendar(value) {
|
||||
this.calendars.splice(1, 1, value);
|
||||
this.validateCalendars(0);
|
||||
},
|
||||
validateCalendars(index) {
|
||||
const gap = this.getCalendarGap();
|
||||
if (gap) {
|
||||
let calendar = this.calendars[index];
|
||||
if (index === 0) {
|
||||
calendar = subMonths(calendar, gap);
|
||||
} else {
|
||||
calendar = addMonths(calendar, gap);
|
||||
}
|
||||
this.calendars.splice(index, 1, calendar);
|
||||
}
|
||||
},
|
||||
getCalendarGap() {
|
||||
const diff = differenceInCalendarMonths(this.calendars[1], this.calendars[0]);
|
||||
const min = this.calendarMinDiff;
|
||||
const max = this.calendarMaxDiff;
|
||||
if (diff < min) {
|
||||
return min - diff;
|
||||
}
|
||||
if (diff > max) {
|
||||
return max - diff;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
getRangeClasses(cellDate, currentDates, classes) {
|
||||
if (
|
||||
!/disabled|active|not-current-month/.test(classes) &&
|
||||
currentDates.length === 2 &&
|
||||
cellDate.getTime() > currentDates[0].getTime() &&
|
||||
cellDate.getTime() < currentDates[1].getTime()
|
||||
) {
|
||||
return 'in-range';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const calendarRange = this.calendars.map((calendar, index) => {
|
||||
const props = {
|
||||
...this.$props,
|
||||
calendar,
|
||||
value: this.innerValue,
|
||||
getClasses: this.getRangeClasses,
|
||||
};
|
||||
const on = {
|
||||
select: this.handleSelect,
|
||||
'update:calendar': index === 0 ? this.updateStartCalendar : this.updateEndCalendar,
|
||||
};
|
||||
return <calendar-panel {...{ props, on }}></calendar-panel>;
|
||||
});
|
||||
return <div class="mx-range-wrapper">{calendarRange}</div>;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<table class="mx-table mx-table-date">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="showWeekNumber" class="mx-week-number-header"></th>
|
||||
<th v-for="day in days" :key="day">{{ day }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody @click="handleCellClick">
|
||||
<tr v-for="(row, i) in dates" :key="i" class="mx-date-row" :class="getRowClasses(row)">
|
||||
<td v-if="showWeekNumber" class="mx-week-number">
|
||||
{{ getWeekNumber(row[0].day) }}
|
||||
</td>
|
||||
<td
|
||||
v-for="(cell, j) in row"
|
||||
:key="j"
|
||||
:data-day="cell.day"
|
||||
class="cell"
|
||||
:class="getCellClasses(cell.day)"
|
||||
:title="getCellTitle(cell.day)"
|
||||
>
|
||||
<div>{{ cell.text }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getWeek } from 'date-format-parse';
|
||||
import localeMixin from '../mixin/locale';
|
||||
import formatMixin from '../mixin/format';
|
||||
import { chunk } from '../util/base';
|
||||
|
||||
export default {
|
||||
name: 'TableDate',
|
||||
mixins: [localeMixin, formatMixin],
|
||||
props: {
|
||||
calendarYear: {
|
||||
type: Number,
|
||||
default() {
|
||||
return new Date().getFullYear();
|
||||
},
|
||||
},
|
||||
calendarMonth: {
|
||||
type: Number,
|
||||
default() {
|
||||
return new Date().getMonth();
|
||||
},
|
||||
},
|
||||
showWeekNumber: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
titleFormat: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD',
|
||||
},
|
||||
getRowClasses: {
|
||||
type: Function,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
getCellClasses: {
|
||||
type: Function,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
firstDayOfWeek() {
|
||||
return this.t('formatLocale.firstDayOfWeek') || 0;
|
||||
},
|
||||
days() {
|
||||
const days = this.t('days') || this.t('formatLocale.weekdaysMin');
|
||||
return days.concat(days).slice(this.firstDayOfWeek, this.firstDayOfWeek + 7);
|
||||
},
|
||||
dates() {
|
||||
const arr = [];
|
||||
const { firstDayOfWeek } = this;
|
||||
const year = this.calendarYear;
|
||||
const month = this.calendarMonth;
|
||||
|
||||
// change to the last day of the last month
|
||||
const calendar = new Date(year, month, 0);
|
||||
const lastDayInLastMonth = calendar.getDate();
|
||||
// getDay() 0 is Sunday, 1 is Monday
|
||||
const firstDayInLastMonth =
|
||||
lastDayInLastMonth - ((calendar.getDay() + 7 - firstDayOfWeek) % 7);
|
||||
for (let i = firstDayInLastMonth; i <= lastDayInLastMonth; i++) {
|
||||
const day = i - lastDayInLastMonth;
|
||||
arr.push({ day, text: i });
|
||||
}
|
||||
// change to the last day of the current month
|
||||
calendar.setMonth(month + 1, 0);
|
||||
const lastDayInCurrentMonth = calendar.getDate();
|
||||
for (let i = 1; i <= lastDayInCurrentMonth; i++) {
|
||||
arr.push({ day: i, text: i });
|
||||
}
|
||||
|
||||
const lastMonthLength = lastDayInLastMonth - firstDayInLastMonth + 1;
|
||||
const nextMonthLength = 6 * 7 - lastMonthLength - lastDayInCurrentMonth;
|
||||
for (let i = 1; i <= nextMonthLength; i++) {
|
||||
arr.push({ day: lastDayInCurrentMonth + i, text: i });
|
||||
}
|
||||
|
||||
return chunk(arr, 7);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleCellClick(evt) {
|
||||
let { target } = evt;
|
||||
if (target.tagName === 'DIV') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
const day = target.getAttribute('data-day');
|
||||
if (day) {
|
||||
this.$emit('select', parseInt(day, 10));
|
||||
}
|
||||
},
|
||||
getCellTitle(day) {
|
||||
const year = this.calendarYear;
|
||||
const month = this.calendarMonth;
|
||||
const format = this.titleFormat;
|
||||
const date = new Date(year, month, day);
|
||||
return this.formatDate(date, format);
|
||||
},
|
||||
getWeekNumber(day) {
|
||||
const year = this.calendarYear;
|
||||
const month = this.calendarMonth;
|
||||
const date = new Date(year, month, day);
|
||||
return getWeek(date, this.t('formatLocale'));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<table class="mx-table mx-table-month" @click="handleClick">
|
||||
<tr v-for="(row, i) in months" :key="i">
|
||||
<td
|
||||
v-for="(cell, j) in row"
|
||||
:key="j"
|
||||
:data-month="cell.month"
|
||||
class="cell"
|
||||
:class="getCellClasses(cell.month)"
|
||||
>
|
||||
<div>{{ cell.text }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import localeMixin from '../mixin/locale';
|
||||
import { chunk } from '../util/base';
|
||||
|
||||
export default {
|
||||
name: 'TableMonth',
|
||||
mixins: [localeMixin],
|
||||
props: {
|
||||
getCellClasses: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
months() {
|
||||
const monthsLocale = this.t('months') || this.t('formatLocale.monthsShort');
|
||||
const months = monthsLocale.map((text, month) => {
|
||||
return { text, month };
|
||||
});
|
||||
return chunk(months, 3);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick(evt) {
|
||||
let { target } = evt;
|
||||
if (target.tagName === 'DIV') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
const month = target.getAttribute('data-month');
|
||||
if (month) {
|
||||
this.$emit('select', parseInt(month, 10));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<table class="mx-table mx-table-year" @click="handleClick">
|
||||
<tr v-for="(row, i) in years" :key="i">
|
||||
<td
|
||||
v-for="(cell, j) in row"
|
||||
:key="j"
|
||||
:data-year="cell"
|
||||
class="cell"
|
||||
:class="getCellClasses(cell)"
|
||||
>
|
||||
<div>{{ cell }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { chunk } from '../util/base';
|
||||
|
||||
export default {
|
||||
name: 'TableYear',
|
||||
props: {
|
||||
decade: Number,
|
||||
getCellClasses: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
years() {
|
||||
const firstYear = this.decade;
|
||||
const years = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
years.push(firstYear + i);
|
||||
}
|
||||
return chunk(years, 2);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick(evt) {
|
||||
let { target } = evt;
|
||||
if (target.tagName === 'DIV') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
const year = target.getAttribute('data-year');
|
||||
if (year) {
|
||||
this.$emit('select', parseInt(year, 10));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'mx-datepicker': true,
|
||||
'mx-datepicker-range': range,
|
||||
'mx-datepicker-inline': inline,
|
||||
disabled: disabled,
|
||||
}"
|
||||
>
|
||||
<div v-if="!inline" class="mx-input-wrapper" @click="openPopup">
|
||||
<input
|
||||
ref="input"
|
||||
v-bind="{ name: 'date', type: 'text', autocomplete: 'off', ...inputAttr }"
|
||||
:class="inputClass"
|
||||
:disabled="disabled"
|
||||
:readonly="!editable"
|
||||
:value="text"
|
||||
:placeholder="placeholder"
|
||||
@keydown="handleInputKeydown"
|
||||
@focus="handleInputFocus"
|
||||
@blur="handleInputBlur"
|
||||
@input="handleInputInput"
|
||||
@change="handleInputChange"
|
||||
/>
|
||||
<i v-if="showClearIcon" class="mx-icon-clear" @click.stop="handleClear">
|
||||
<slot name="icon-clear">
|
||||
<icon-close></icon-close>
|
||||
</slot>
|
||||
</i>
|
||||
<i class="mx-icon-calendar">
|
||||
<slot name="icon-calendar">
|
||||
<icon-calendar></icon-calendar>
|
||||
</slot>
|
||||
</i>
|
||||
</div>
|
||||
<Popup
|
||||
ref="popup"
|
||||
:class="popupClass"
|
||||
:style="popupStyle"
|
||||
:inline="inline"
|
||||
:visible="popupVisible"
|
||||
:append-to-body="appendToBody"
|
||||
@clickoutside="handleClickOutSide"
|
||||
>
|
||||
<div v-if="hasSlot('header')" class="mx-datepicker-header">
|
||||
<slot name="header" :value="currentValue" :emit="emitValue"></slot>
|
||||
</div>
|
||||
<div class="mx-datepicker-content-wrapper">
|
||||
<div v-if="hasSlot('sidebar') || shortcuts.length" class="mx-datepicker-sidebar">
|
||||
<slot name="sidebar" :value="currentValue" :emit="emitValue"></slot>
|
||||
<button
|
||||
v-for="(v, i) in shortcuts"
|
||||
:key="i"
|
||||
type="button"
|
||||
class="mx-btn mx-btn-text mx-btn-shortcut"
|
||||
@click="handleSelectShortcut(v)"
|
||||
>
|
||||
{{ v.text }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mx-datepicker-content">
|
||||
<slot name="content" :value="currentValue" :emit="emitValue">
|
||||
<component
|
||||
:is="currentComponent"
|
||||
ref="picker"
|
||||
v-bind="currentComponentProps"
|
||||
@select="handleSelectDate"
|
||||
></component>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hasSlot('footer') || confirm" class="mx-datepicker-footer">
|
||||
<slot name="footer" :value="currentValue" :emit="emitValue"></slot>
|
||||
<button
|
||||
v-if="confirm"
|
||||
type="button"
|
||||
class="mx-btn mx-datepicker-btn-confirm"
|
||||
@click="handleConfirmDate"
|
||||
>
|
||||
{{ confirmText }}
|
||||
</button>
|
||||
</div>
|
||||
</Popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { parse, format } from 'date-format-parse';
|
||||
import { isValidDate, isValidRangeDate, getValidDate } from './util/date';
|
||||
import { pick, isObject, mergeDeep } from './util/base';
|
||||
import { locale as getLocale } from './locale';
|
||||
import Popup from './popup';
|
||||
import IconCalendar from './icon/icon-calendar';
|
||||
import IconClose from './icon/icon-close';
|
||||
import CalendarPanel from './calendar/calendar-panel';
|
||||
import CalendarRange from './calendar/calendar-range';
|
||||
import TimePanel from './time/time-panel';
|
||||
import TimeRange from './time/time-range';
|
||||
import DatetimePanel from './datetime/datetime-panel';
|
||||
import DatetimeRange from './datetime/datetime-range';
|
||||
|
||||
const componentMap = {
|
||||
default: CalendarPanel,
|
||||
time: TimePanel,
|
||||
datetime: DatetimePanel,
|
||||
};
|
||||
const componentRangeMap = {
|
||||
default: CalendarRange,
|
||||
time: TimeRange,
|
||||
datetime: DatetimeRange,
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'DatePicker',
|
||||
components: {
|
||||
IconCalendar,
|
||||
IconClose,
|
||||
Popup,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
locale: this.locale,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
...DatetimePanel.props,
|
||||
value: {},
|
||||
valueType: {
|
||||
type: String,
|
||||
default: 'date', // date, format, timestamp, or token like 'YYYY-MM-DD'
|
||||
},
|
||||
type: {
|
||||
type: String, // ['date', 'datetime', 'time', 'year', 'month', 'week']
|
||||
default: 'date',
|
||||
},
|
||||
format: {
|
||||
type: [String, Object],
|
||||
default() {
|
||||
const map = {
|
||||
date: 'YYYY-MM-DD',
|
||||
datetime: 'YYYY-MM-DD HH:mm:ss',
|
||||
year: 'YYYY',
|
||||
month: 'YYYY-MM',
|
||||
time: 'HH:mm:ss',
|
||||
week: 'w',
|
||||
};
|
||||
return map[this.type] || map.date;
|
||||
},
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
rangeSeparator: {
|
||||
type: String,
|
||||
default: ' ~ ',
|
||||
},
|
||||
lang: {
|
||||
type: [String, Object],
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
inputClass: {
|
||||
default: 'mx-input',
|
||||
},
|
||||
inputAttr: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
appendToBody: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
popupClass: {},
|
||||
popupStyle: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
confirm: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: 'OK',
|
||||
},
|
||||
shortcuts: {
|
||||
type: Array,
|
||||
validator(value) {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.every(
|
||||
v => isObject(v) && typeof v.text === 'string' && typeof v.onClick === 'function'
|
||||
)
|
||||
);
|
||||
},
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// cache the innervalue, wait to confirm
|
||||
currentValue: null,
|
||||
userInput: null,
|
||||
defaultOpen: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentComponent() {
|
||||
const map = this.range ? componentRangeMap : componentMap;
|
||||
return map[this.type] || map.default;
|
||||
},
|
||||
currentComponentProps() {
|
||||
const props = {
|
||||
...pick(this, Object.keys(this.currentComponent.props)),
|
||||
value: this.currentValue,
|
||||
};
|
||||
return props;
|
||||
},
|
||||
popupVisible() {
|
||||
return !this.disabled && (typeof this.open === 'boolean' ? this.open : this.defaultOpen);
|
||||
},
|
||||
innerValue() {
|
||||
let { value } = this;
|
||||
if (this.range) {
|
||||
value = Array.isArray(value) ? value.slice(0, 2) : [null, null];
|
||||
return value.map(this.value2date);
|
||||
}
|
||||
return this.value2date(this.value);
|
||||
},
|
||||
innerValueValid() {
|
||||
if (!this.range && isValidDate(this.innerValue)) {
|
||||
return true;
|
||||
}
|
||||
if (this.range && isValidRangeDate(this.innerValue)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
text() {
|
||||
if (this.userInput !== null) {
|
||||
return this.userInput;
|
||||
}
|
||||
if (!this.innerValueValid) {
|
||||
return '';
|
||||
}
|
||||
const fmt = this.format;
|
||||
if (Array.isArray(this.innerValue)) {
|
||||
return this.innerValue.map(v => this.formatDate(v, fmt)).join(this.rangeSeparator);
|
||||
}
|
||||
return this.formatDate(this.innerValue, fmt);
|
||||
},
|
||||
showClearIcon() {
|
||||
return !this.disabled && this.clearable && this.innerValueValid;
|
||||
},
|
||||
locale() {
|
||||
if (isObject(this.lang)) {
|
||||
return mergeDeep(getLocale(), this.lang);
|
||||
}
|
||||
return getLocale(this.lang);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.currentValue = val;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClickOutSide(evt) {
|
||||
const { target } = evt;
|
||||
if (!this.$el.contains(target)) {
|
||||
this.closePopup();
|
||||
}
|
||||
},
|
||||
parseDate(value, fmt) {
|
||||
if (isObject(this.format) && typeof this.format.parse === 'function') {
|
||||
return this.format.parse(value, fmt);
|
||||
}
|
||||
const backupDate = getValidDate(this.defaultValue);
|
||||
return parse(value, fmt, { locale: this.locale.formatLocale, backupDate });
|
||||
},
|
||||
formatDate(date, fmt) {
|
||||
if (isObject(this.format) && typeof this.format.stringify === 'function') {
|
||||
return this.format.stringify(date, fmt);
|
||||
}
|
||||
return format(date, fmt, { locale: this.locale.formatLocale });
|
||||
},
|
||||
// transform the outer value to inner date
|
||||
value2date(value) {
|
||||
switch (this.valueType) {
|
||||
case 'date':
|
||||
return value instanceof Date ? new Date(value.getTime()) : new Date(NaN);
|
||||
case 'timestamp':
|
||||
return typeof value === 'number' ? new Date(value) : new Date(NaN);
|
||||
case 'format':
|
||||
return typeof value === 'string' ? this.parseDate(value, this.format) : new Date(NaN);
|
||||
default:
|
||||
return typeof value === 'string' ? this.parseDate(value, this.valueType) : new Date(NaN);
|
||||
}
|
||||
},
|
||||
// transform the inner date to outer value
|
||||
date2value(date) {
|
||||
if (!isValidDate(date)) return null;
|
||||
switch (this.valueType) {
|
||||
case 'date':
|
||||
return date;
|
||||
case 'timestamp':
|
||||
return date.getTime();
|
||||
case 'format':
|
||||
return this.formatDate(date, this.format);
|
||||
default:
|
||||
return this.formatDate(date, this.valueType);
|
||||
}
|
||||
},
|
||||
emitValue(date, type) {
|
||||
const value = Array.isArray(date) ? date.map(this.date2value) : this.date2value(date);
|
||||
this.$emit('input', value);
|
||||
this.$emit('change', value, type);
|
||||
this.afterEmitValue(type);
|
||||
return value;
|
||||
},
|
||||
afterEmitValue(type) {
|
||||
if (!type || type === this.type) {
|
||||
this.closePopup();
|
||||
}
|
||||
},
|
||||
handleSelectDate(val, type) {
|
||||
if (this.confirm) {
|
||||
this.currentValue = val;
|
||||
} else {
|
||||
this.emitValue(val, type);
|
||||
}
|
||||
},
|
||||
handleClear() {
|
||||
this.emitValue(null);
|
||||
},
|
||||
handleConfirmDate() {
|
||||
const value = this.emitValue(this.currentValue);
|
||||
this.$emit('confirm', value);
|
||||
},
|
||||
handleSelectShortcut(item) {
|
||||
if (isObject(item) && typeof item.onClick === 'function') {
|
||||
const date = item.onClick(this);
|
||||
if (date instanceof Date) {
|
||||
this.emitValue(date);
|
||||
}
|
||||
}
|
||||
},
|
||||
openPopup() {
|
||||
this.defaultOpen = true;
|
||||
this.$emit('open');
|
||||
this.$emit('update:open', true);
|
||||
},
|
||||
closePopup() {
|
||||
this.defaultOpen = false;
|
||||
this.$emit('close');
|
||||
this.$emit('update:open', false);
|
||||
},
|
||||
blur() {
|
||||
this.$refs.input.blur();
|
||||
},
|
||||
focus() {
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
handleInputChange() {
|
||||
if (!this.editable || this.userInput === null) return;
|
||||
const text = this.userInput;
|
||||
this.userInput = null;
|
||||
if (text === '') {
|
||||
this.handleClear();
|
||||
return;
|
||||
}
|
||||
let date = null;
|
||||
if (this.range) {
|
||||
date = text.split(this.rangeSeparator).map(v => this.parseDate(v, this.format));
|
||||
date = isValidRangeDate(date) ? date : null;
|
||||
} else {
|
||||
date = this.parseDate(text, this.format);
|
||||
date = isValidDate(date) ? date : null;
|
||||
}
|
||||
if (date !== null) {
|
||||
this.emitValue(date);
|
||||
this.blur();
|
||||
} else {
|
||||
this.$emit('input-error', text);
|
||||
}
|
||||
},
|
||||
handleInputInput(evt) {
|
||||
this.userInput = evt.target.value;
|
||||
},
|
||||
handleInputKeydown(evt) {
|
||||
const { keyCode } = evt;
|
||||
// Tab 9 or Enter 13
|
||||
if (keyCode === 9) {
|
||||
this.closePopup();
|
||||
} else if (keyCode === 13) {
|
||||
this.handleInputChange();
|
||||
}
|
||||
},
|
||||
handleInputBlur(evt) {
|
||||
// tab close
|
||||
this.$emit('blur', evt);
|
||||
},
|
||||
handleInputFocus(evt) {
|
||||
this.openPopup();
|
||||
this.$emit('focus', evt);
|
||||
},
|
||||
hasSlot(name) {
|
||||
return !!(this.$slots[name] || this.$scopedSlots[name]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,86 @@
|
||||
import CalendarPanel from '../calendar/calendar-panel';
|
||||
import TimePanel from '../time/time-panel.vue';
|
||||
import { isValidDate } from '../util/date';
|
||||
import { pick } from '../util/base';
|
||||
|
||||
export default {
|
||||
name: 'DatetimePanel',
|
||||
props: {
|
||||
...CalendarPanel.props,
|
||||
...TimePanel.props,
|
||||
showTimePanel: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultTimeVisible: false,
|
||||
currentValue: this.value,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeVisible() {
|
||||
return typeof this.showTimePanel === 'boolean' ? this.showTimePanel : this.defaultTimeVisible;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.currentValue = val;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeTimePanel() {
|
||||
this.defaultTimeVisible = false;
|
||||
},
|
||||
openTimePanel() {
|
||||
this.defaultTimeVisible = true;
|
||||
},
|
||||
emitDate(date, type) {
|
||||
this.$emit('select', date, type);
|
||||
},
|
||||
handleSelect(date, type) {
|
||||
if (type === 'date') {
|
||||
this.openTimePanel();
|
||||
const time = isValidDate(this.value) ? this.value : new Date(this.defaultValue);
|
||||
const datetime = new Date(date);
|
||||
datetime.setHours(time.getHours(), time.getMinutes(), time.getSeconds());
|
||||
if (this.disabledTime(new Date(datetime))) {
|
||||
this.currentValue = date;
|
||||
} else {
|
||||
this.emitDate(datetime, type);
|
||||
}
|
||||
} else {
|
||||
this.emitDate(date, type);
|
||||
}
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const calendarProps = {
|
||||
props: {
|
||||
...pick(this, Object.keys(CalendarPanel.props)),
|
||||
value: this.currentValue,
|
||||
},
|
||||
on: {
|
||||
select: this.handleSelect,
|
||||
},
|
||||
};
|
||||
const timeProps = {
|
||||
props: {
|
||||
...pick(this, Object.keys(TimePanel.props)),
|
||||
showTimeHeader: true,
|
||||
value: this.currentValue,
|
||||
},
|
||||
on: {
|
||||
select: this.handleSelect,
|
||||
'title-click': this.closeTimePanel,
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<CalendarPanel {...calendarProps} />
|
||||
{this.timeVisible && <TimePanel class="mx-calendar-time" {...timeProps} />}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import CalendarRange from '../calendar/calendar-range';
|
||||
import TimeRange from '../time/time-range';
|
||||
import { pick } from '../util/base';
|
||||
import { isValidRangeDate } from '../util/date';
|
||||
|
||||
export default {
|
||||
name: 'DatetimeRange',
|
||||
props: {
|
||||
...CalendarRange.props,
|
||||
...TimeRange.props,
|
||||
showTimePanel: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultTimeVisible: false,
|
||||
currentValue: this.value,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeVisible() {
|
||||
return typeof this.showTimePanel === 'boolean' ? this.showTimePanel : this.defaultTimeVisible;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.currentValue = val;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeTimePanel() {
|
||||
this.defaultTimeVisible = false;
|
||||
},
|
||||
openTimePanel() {
|
||||
this.defaultTimeVisible = true;
|
||||
},
|
||||
emitDate(dates, type) {
|
||||
this.$emit('select', dates, type);
|
||||
},
|
||||
handleSelect(dates, type) {
|
||||
if (type === 'date') {
|
||||
this.openTimePanel();
|
||||
let datetimes = dates.map((v, i) => {
|
||||
const datetime = new Date(v);
|
||||
const time = isValidRangeDate(this.value) ? this.value[i] : new Date(this.defaultValue);
|
||||
datetime.setHours(time.getHours(), time.getMinutes(), time.getSeconds());
|
||||
return datetime;
|
||||
});
|
||||
if (datetimes[1].getTime() < datetimes[0].getTime()) {
|
||||
datetimes = [datetimes[0], datetimes[0]];
|
||||
}
|
||||
if (datetimes.some(this.disabledTime)) {
|
||||
this.currentValue = dates;
|
||||
} else {
|
||||
this.emitDate(datetimes, type);
|
||||
}
|
||||
} else {
|
||||
this.emitDate(dates, type);
|
||||
}
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const calendarProps = {
|
||||
props: {
|
||||
...pick(this, Object.keys(CalendarRange.props)),
|
||||
value: this.currentValue,
|
||||
},
|
||||
on: {
|
||||
select: this.handleSelect,
|
||||
},
|
||||
};
|
||||
const timeProps = {
|
||||
props: {
|
||||
...pick(this, Object.keys(TimeRange.props)),
|
||||
value: this.currentValue,
|
||||
showTimeHeader: true,
|
||||
},
|
||||
on: {
|
||||
select: this.handleSelect,
|
||||
'title-click': this.closeTimePanel,
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<CalendarRange {...calendarProps} />
|
||||
{this.timeVisible && <TimeRange class="mx-calendar-time" {...timeProps} />}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
d="M940.218182 107.054545h-209.454546V46.545455h-65.163636v60.50909H363.054545V46.545455H297.890909v60.50909H83.781818c-18.618182 0-32.581818 13.963636-32.581818 32.581819v805.236363c0 18.618182 13.963636 32.581818 32.581818 32.581818h861.090909c18.618182 0 32.581818-13.963636 32.581818-32.581818V139.636364c-4.654545-18.618182-18.618182-32.581818-37.236363-32.581819zM297.890909 172.218182V232.727273h65.163636V172.218182h307.2V232.727273h65.163637V172.218182h176.872727v204.8H116.363636V172.218182h181.527273zM116.363636 912.290909V442.181818h795.927273v470.109091H116.363636z"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
d="M810.005333 274.005333l-237.994667 237.994667 237.994667 237.994667-60.010667 60.010667-237.994667-237.994667-237.994667 237.994667-60.010667-60.010667 237.994667-237.994667-237.994667-237.994667 60.010667-60.010667 237.994667 237.994667 237.994667-237.994667z"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
+10
-8
@@ -1,13 +1,15 @@
|
||||
import DatePicker from './index.vue'
|
||||
import './index.scss'
|
||||
/* istanbul ignore file */
|
||||
import DatePicker from './date-picker.vue';
|
||||
import { locale } from './locale';
|
||||
|
||||
DatePicker.install = function (Vue) {
|
||||
Vue.component(DatePicker.name, DatePicker)
|
||||
}
|
||||
DatePicker.locale = locale;
|
||||
|
||||
DatePicker.install = function install(Vue) {
|
||||
Vue.component(DatePicker.name, DatePicker);
|
||||
};
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
DatePicker.install(window.Vue)
|
||||
DatePicker.install(window.Vue);
|
||||
}
|
||||
|
||||
export default DatePicker
|
||||
export default DatePicker;
|
||||
|
||||
-336
@@ -1,336 +0,0 @@
|
||||
$default-color: #73879c;
|
||||
$primary-color: #1284e7;
|
||||
|
||||
.mx-datepicker {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 210px;
|
||||
color: $default-color;
|
||||
font: 14px/1.5 'Helvetica Neue', Helvetica, Arial, 'Microsoft Yahei',
|
||||
sans-serif;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.mx-datepicker-range {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.mx-datepicker-popup {
|
||||
position: absolute;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
border: 1px solid #d9d9d9;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.mx-input-wrapper {
|
||||
position: relative;
|
||||
.mx-clear-wrapper {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
.mx-clear-wrapper {
|
||||
display: block;
|
||||
}
|
||||
.mx-clear-wrapper + .mx-input-append {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-input {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
padding: 6px 30px;
|
||||
padding-left: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-input-append {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.mx-input-icon {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-style: normal;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx-calendar-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #555;
|
||||
stroke-width: 8px;
|
||||
stroke: currentColor;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.mx-clear-icon {
|
||||
&::before {
|
||||
display: inline-block;
|
||||
content: '\2716';
|
||||
vertical-align: middle;
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-range-wrapper {
|
||||
width: 248px * 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mx-shortcuts-wrapper {
|
||||
text-align: left;
|
||||
padding: 0 12px;
|
||||
line-height: 34px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
.mx-shortcuts {
|
||||
background: none;
|
||||
outline: none;
|
||||
border: 0;
|
||||
color: #48576a;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: mix(#fff, $primary-color, 20%);
|
||||
}
|
||||
&:after {
|
||||
content: '|';
|
||||
margin: 0 10px;
|
||||
color: #48576a;
|
||||
}
|
||||
&:last-child::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-datepicker-footer {
|
||||
padding: 4px;
|
||||
clear: both;
|
||||
text-align: right;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.mx-datepicker-btn {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
padding: 7px 15px;
|
||||
margin: 0 5px;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.mx-datepicker-btn-confirm {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
color: #73879c;
|
||||
&:hover {
|
||||
color: #1284e7;
|
||||
border-color: #1284e7;
|
||||
}
|
||||
}
|
||||
|
||||
/* 日历组件 */
|
||||
.mx-calendar {
|
||||
float: left;
|
||||
color: $default-color;
|
||||
padding: 6px 12px;
|
||||
font: 14px/1.5 Helvetica Neue, Helvetica, Arial, Microsoft Yahei, sans-serif;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-calendar-header {
|
||||
padding: 0 4px;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
> a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: mix(#fff, $primary-color, 20%);
|
||||
}
|
||||
}
|
||||
@at-root {
|
||||
.mx-icon-last-month,
|
||||
.mx-icon-next-month {
|
||||
padding: 0 6px;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
user-select: none;
|
||||
}
|
||||
.mx-icon-last-month {
|
||||
float: left;
|
||||
}
|
||||
.mx-icon-next-month {
|
||||
float: right;
|
||||
}
|
||||
.mx-icon-last-year {
|
||||
@extend .mx-icon-last-month;
|
||||
}
|
||||
.mx-icon-next-year {
|
||||
@extend .mx-icon-next-month;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-calendar-content {
|
||||
width: 32px * 7;
|
||||
height: 32px * 7;
|
||||
.cell {
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #eaf8fe;
|
||||
}
|
||||
&.actived {
|
||||
color: #fff;
|
||||
background-color: $primary-color;
|
||||
}
|
||||
&.inrange {
|
||||
background-color: #eaf8fe;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #ccc;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mx-panel-date {
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
td,
|
||||
th {
|
||||
font-size: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
td {
|
||||
&.today {
|
||||
color: mix(#fff, $primary-color, 10%);
|
||||
}
|
||||
&.last-month,
|
||||
&.next-month {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-panel-year {
|
||||
padding: 7px 0;
|
||||
.cell {
|
||||
display: inline-block;
|
||||
width: 40%;
|
||||
margin: 1px 5%;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-panel-month {
|
||||
.cell {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
line-height: 40px;
|
||||
margin: 8px 1.5%;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-time-list {
|
||||
position: relative; // 定位 offsetParent
|
||||
float: left;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.05);
|
||||
overflow-y: auto;
|
||||
.mx-time-picker-item {
|
||||
display: block;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
&:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
.cell {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
/* 滚动条滑块 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 1px 1px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
-621
@@ -1,621 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="mx-datepicker"
|
||||
@mousedown="showPopup"
|
||||
@touchstart="showPopup"
|
||||
:class="{
|
||||
'mx-datepicker-range': range,
|
||||
'disabled': disabled
|
||||
}"
|
||||
:style="{
|
||||
'width': computedWidth
|
||||
}"
|
||||
>
|
||||
<div class="mx-input-wrapper">
|
||||
<input
|
||||
:class="inputClass"
|
||||
:name="inputName"
|
||||
v-bind="inputAttr"
|
||||
ref="input"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
:disabled="disabled"
|
||||
:readonly="!editable"
|
||||
:value="text"
|
||||
:placeholder="innerPlaceholder"
|
||||
@keydown="handleKeydown"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@input="handleInput"
|
||||
@change="handleChange">
|
||||
<span
|
||||
v-if="showClearIcon"
|
||||
class="mx-input-append mx-clear-wrapper"
|
||||
@mousedown.stop="clearDate">
|
||||
<slot name="mx-clear-icon">
|
||||
<i class="mx-input-icon mx-clear-icon"></i>
|
||||
</slot>
|
||||
</span>
|
||||
<span class="mx-input-append">
|
||||
<slot name="calendar-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 200 200" class="mx-calendar-icon">
|
||||
<rect x="13" y="29" rx="14" ry="14" width="174" height="158" fill="transparent" />
|
||||
<line x1="46" x2="46" y1="8" y2="50" />
|
||||
<line x1="154" x2="154" y1="8" y2="50" />
|
||||
<line x1="13" x2="187" y1="70" y2="70" />
|
||||
<text x="50%" y="135" font-size="90" stroke-width="1" text-anchor="middle" dominant-baseline="middle">{{iconDay}}</text>
|
||||
</svg>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mx-datepicker-popup"
|
||||
:style="innerPopupStyle"
|
||||
v-show="popupVisible"
|
||||
@click.stop.prevent
|
||||
ref="calendar">
|
||||
<slot name="header">
|
||||
<div class="mx-shortcuts-wrapper"
|
||||
v-if="range && innerShortcuts.length">
|
||||
<button
|
||||
type="button"
|
||||
class="mx-shortcuts"
|
||||
v-for="(range, index) in innerShortcuts"
|
||||
:key="index"
|
||||
@click="selectRange(range)">{{range.text}}</button>
|
||||
</div>
|
||||
</slot>
|
||||
<calendar-panel
|
||||
v-if="!range"
|
||||
v-bind="$attrs"
|
||||
ref="calendarPanel"
|
||||
:index="-1"
|
||||
:type="innerType"
|
||||
:date-format="innerDateFormat"
|
||||
:value="currentValue"
|
||||
:visible="popupVisible"
|
||||
@select-date="selectDate"
|
||||
@select-time="selectTime"></calendar-panel>
|
||||
<div class="mx-range-wrapper"
|
||||
v-else>
|
||||
<calendar-panel
|
||||
style="box-shadow:1px 0 rgba(0, 0, 0, .1)"
|
||||
v-bind="$attrs"
|
||||
ref="calendarPanel"
|
||||
:index="0"
|
||||
:type="innerType"
|
||||
:date-format="innerDateFormat"
|
||||
:value="currentValue[0]"
|
||||
:end-at="currentValue[1]"
|
||||
:start-at="null"
|
||||
:visible="popupVisible"
|
||||
@select-date="selectStartDate"
|
||||
@select-time="selectStartTime"></calendar-panel>
|
||||
<calendar-panel
|
||||
v-bind="$attrs"
|
||||
:index="1"
|
||||
:type="innerType"
|
||||
:date-format="innerDateFormat"
|
||||
:value="currentValue[1]"
|
||||
:start-at="currentValue[0]"
|
||||
:end-at="null"
|
||||
:visible="popupVisible"
|
||||
@select-date="selectEndDate"
|
||||
@select-time="selectEndTime"></calendar-panel>
|
||||
</div>
|
||||
<slot name="footer" :confirm="confirmDate">
|
||||
<div class="mx-datepicker-footer"
|
||||
v-if="confirm">
|
||||
<button type="button"
|
||||
class="mx-datepicker-btn mx-datepicker-btn-confirm"
|
||||
@click="confirmDate">{{ confirmText }}</button>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import fecha from 'fecha'
|
||||
import { isValidDate, isValidRangeDate, isDateObejct, isPlainObject, formatDate, parseDate, throttle } from '@/utils/index'
|
||||
import { transformDate } from '@/utils/transform'
|
||||
import CalendarPanel from './calendar.vue'
|
||||
import locale from '@/mixins/locale'
|
||||
import Languages from '@/locale/languages'
|
||||
|
||||
export default {
|
||||
fecha,
|
||||
name: 'DatePicker',
|
||||
components: { CalendarPanel },
|
||||
mixins: [locale],
|
||||
props: {
|
||||
value: null,
|
||||
valueType: {
|
||||
default: 'date',
|
||||
validator: function (value) {
|
||||
return ['timestamp', 'format', 'date'].indexOf(value) !== -1 || isPlainObject(value)
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
lang: {
|
||||
type: [String, Object],
|
||||
default: 'zh'
|
||||
},
|
||||
format: {
|
||||
type: [String, Object],
|
||||
default: 'YYYY-MM-DD'
|
||||
},
|
||||
dateFormat: {
|
||||
type: String // format the time header and date tooltip
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'date' // ['date', 'datetime'] zendy added 'month', 'year', mxie added "time"
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rangeSeparator: {
|
||||
type: String,
|
||||
default: '~'
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: 'OK'
|
||||
},
|
||||
confirm: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
shortcuts: {
|
||||
type: [Boolean, Array],
|
||||
default: true
|
||||
},
|
||||
inputName: {
|
||||
type: String,
|
||||
default: 'date'
|
||||
},
|
||||
inputClass: {
|
||||
type: [String, Array],
|
||||
default: 'mx-input'
|
||||
},
|
||||
inputAttr: Object,
|
||||
appendToBody: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
popupStyle: {
|
||||
type: Object
|
||||
},
|
||||
iconDay: {
|
||||
type: [Number, String]
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
currentValue: this.range ? [null, null] : null,
|
||||
userInput: null,
|
||||
popupVisible: false,
|
||||
position: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: 'handleValueChange'
|
||||
},
|
||||
popupVisible (val) {
|
||||
if (val) {
|
||||
this.initCalendar()
|
||||
} else {
|
||||
this.userInput = null
|
||||
this.blur()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
transform () {
|
||||
const type = this.valueType
|
||||
if (isPlainObject(type)) {
|
||||
return { ...transformDate.date, ...type }
|
||||
}
|
||||
if (type === 'format') {
|
||||
return {
|
||||
value2date: this.parse.bind(this),
|
||||
date2value: this.stringify.bind(this)
|
||||
}
|
||||
}
|
||||
return transformDate[type] || transformDate.date
|
||||
},
|
||||
language () {
|
||||
if (isPlainObject(this.lang)) {
|
||||
return { ...Languages.en, ...this.lang }
|
||||
}
|
||||
return Languages[this.lang] || Languages.en
|
||||
},
|
||||
innerPlaceholder () {
|
||||
if (typeof this.placeholder === 'string') {
|
||||
return this.placeholder
|
||||
}
|
||||
return this.range ? this.t('placeholder.dateRange') : this.t('placeholder.date')
|
||||
},
|
||||
text () {
|
||||
if (this.userInput !== null) {
|
||||
return this.userInput
|
||||
}
|
||||
const { value2date } = this.transform
|
||||
if (!this.range) {
|
||||
return this.isValidValue(this.value)
|
||||
? this.stringify(value2date(this.value))
|
||||
: ''
|
||||
}
|
||||
return this.isValidRangeValue(this.value)
|
||||
? `${this.stringify(value2date(this.value[0]))} ${this.rangeSeparator} ${this.stringify(value2date(this.value[1]))}`
|
||||
: ''
|
||||
},
|
||||
computedWidth () {
|
||||
if (typeof this.width === 'number' || (typeof this.width === 'string' && /^\d+$/.test(this.width))) {
|
||||
return this.width + 'px'
|
||||
}
|
||||
return this.width
|
||||
},
|
||||
showClearIcon () {
|
||||
return !this.disabled && this.clearable && (this.range ? this.isValidRangeValue(this.value) : this.isValidValue(this.value))
|
||||
},
|
||||
innerType () {
|
||||
return String(this.type).toLowerCase()
|
||||
},
|
||||
innerShortcuts () {
|
||||
if (Array.isArray(this.shortcuts)) {
|
||||
return this.shortcuts
|
||||
}
|
||||
if (this.shortcuts === false) {
|
||||
return []
|
||||
}
|
||||
const pickers = this.t('pickers')
|
||||
const arr = [
|
||||
{
|
||||
text: pickers[0],
|
||||
onClick (self) {
|
||||
self.currentValue = [new Date(), new Date(Date.now() + 3600 * 1000 * 24 * 7)]
|
||||
self.updateDate(true)
|
||||
}
|
||||
},
|
||||
{
|
||||
text: pickers[1],
|
||||
onClick (self) {
|
||||
self.currentValue = [new Date(), new Date(Date.now() + 3600 * 1000 * 24 * 30)]
|
||||
self.updateDate(true)
|
||||
}
|
||||
},
|
||||
{
|
||||
text: pickers[2],
|
||||
onClick (self) {
|
||||
self.currentValue = [new Date(Date.now() - 3600 * 1000 * 24 * 7), new Date()]
|
||||
self.updateDate(true)
|
||||
}
|
||||
},
|
||||
{
|
||||
text: pickers[3],
|
||||
onClick (self) {
|
||||
self.currentValue = [new Date(Date.now() - 3600 * 1000 * 24 * 30), new Date()]
|
||||
self.updateDate(true)
|
||||
}
|
||||
}
|
||||
]
|
||||
return arr
|
||||
},
|
||||
innerDateFormat () {
|
||||
if (this.dateFormat) {
|
||||
return this.dateFormat
|
||||
}
|
||||
if (typeof this.format !== 'string') {
|
||||
return 'YYYY-MM-DD'
|
||||
}
|
||||
if (this.innerType === 'date') {
|
||||
return this.format
|
||||
}
|
||||
return this.format.replace(/[Hh]+.*[msSaAZ]|\[.*?\]/g, '').trim() || 'YYYY-MM-DD'
|
||||
},
|
||||
innerPopupStyle () {
|
||||
return { ...this.position, ...this.popupStyle }
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.appendToBody) {
|
||||
this.popupElm = this.$refs.calendar
|
||||
document.body.appendChild(this.popupElm)
|
||||
}
|
||||
// clickoutside close popup
|
||||
let mousedownTarget
|
||||
this._bindDocmentMousedown = evt => {
|
||||
mousedownTarget = evt.target
|
||||
}
|
||||
this._bindDocumentMouseup = evt => {
|
||||
const mouseupTarget = evt.target
|
||||
const el = this.$el
|
||||
const { popupElm } = this
|
||||
if (
|
||||
!mousedownTarget ||
|
||||
!mouseupTarget ||
|
||||
el.contains(mousedownTarget) ||
|
||||
el.contains(mouseupTarget) ||
|
||||
(popupElm && (popupElm.contains(mousedownTarget) || popupElm.contains(mouseupTarget)))
|
||||
) {
|
||||
return
|
||||
}
|
||||
mousedownTarget = null
|
||||
this.closePopup()
|
||||
}
|
||||
this._startEvt = 'mousedown'
|
||||
this._endEvt = 'mouseup'
|
||||
if ('ontouchend' in document) {
|
||||
this._startEvt = 'touchstart'
|
||||
this._endEvt = 'touchend'
|
||||
}
|
||||
document.addEventListener(this._startEvt, this._bindDocmentMousedown)
|
||||
document.addEventListener(this._endEvt, this._bindDocumentMouseup)
|
||||
|
||||
this._displayPopup = throttle(() => {
|
||||
if (this.popupVisible) {
|
||||
this.displayPopup()
|
||||
}
|
||||
}, 200)
|
||||
window.addEventListener('resize', this._displayPopup)
|
||||
window.addEventListener('scroll', this._displayPopup)
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.popupElm && this.popupElm.parentNode === document.body) {
|
||||
document.body.removeChild(this.popupElm)
|
||||
}
|
||||
document.removeEventListener(this._startEvt, this._bindDocmentMousedown)
|
||||
document.removeEventListener(this._endEvt, this._bindDocumentMouseup)
|
||||
window.removeEventListener('resize', this._displayPopup)
|
||||
window.removeEventListener('scroll', this._displayPopup)
|
||||
},
|
||||
methods: {
|
||||
initCalendar () {
|
||||
this.handleValueChange(this.value)
|
||||
this.displayPopup()
|
||||
},
|
||||
stringify (date) {
|
||||
return (isPlainObject(this.format) && typeof this.format.stringify === 'function')
|
||||
? this.format.stringify(date)
|
||||
: formatDate(date, this.format)
|
||||
},
|
||||
parse (value) {
|
||||
return (isPlainObject(this.format) && typeof this.format.parse === 'function')
|
||||
? this.format.parse(value)
|
||||
: parseDate(value, this.format)
|
||||
},
|
||||
isValidValue (value) {
|
||||
const { value2date } = this.transform
|
||||
return isValidDate(value2date(value))
|
||||
},
|
||||
isValidRangeValue (value) {
|
||||
const { value2date } = this.transform
|
||||
return Array.isArray(value) && value.length === 2 && this.isValidValue(value[0]) &&
|
||||
this.isValidValue(value[1]) && (value2date(value[1]).getTime() >= value2date(value[0]).getTime())
|
||||
},
|
||||
dateEqual (a, b) {
|
||||
return isDateObejct(a) && isDateObejct(b) && a.getTime() === b.getTime()
|
||||
},
|
||||
rangeEqual (a, b) {
|
||||
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => this.dateEqual(item, b[index]))
|
||||
},
|
||||
selectRange (range) {
|
||||
if (typeof range.onClick === 'function') {
|
||||
const close = range.onClick(this)
|
||||
if (close !== false) {
|
||||
this.closePopup()
|
||||
}
|
||||
} else {
|
||||
this.currentValue = [new Date(range.start), new Date(range.end)]
|
||||
this.updateDate(true)
|
||||
this.closePopup()
|
||||
}
|
||||
},
|
||||
clearDate () {
|
||||
const date = this.range ? [null, null] : null
|
||||
this.currentValue = date
|
||||
this.updateDate(true)
|
||||
this.$emit('clear')
|
||||
},
|
||||
confirmDate () {
|
||||
const valid = this.range ? isValidRangeDate(this.currentValue) : isValidDate(this.currentValue)
|
||||
if (valid) {
|
||||
this.updateDate(true)
|
||||
}
|
||||
this.emitDate('confirm')
|
||||
this.closePopup()
|
||||
},
|
||||
updateDate (confirm = false) {
|
||||
if ((this.confirm && !confirm) || this.disabled) {
|
||||
return false
|
||||
}
|
||||
const equal = this.range ? this.rangeEqual(this.value, this.currentValue) : this.dateEqual(this.value, this.currentValue)
|
||||
if (equal) {
|
||||
return false
|
||||
}
|
||||
this.emitDate('input')
|
||||
this.emitDate('change')
|
||||
return true
|
||||
},
|
||||
emitDate (eventName) {
|
||||
const { date2value } = this.transform
|
||||
const value = this.range
|
||||
? this.currentValue.map(date2value)
|
||||
: date2value(this.currentValue)
|
||||
this.$emit(eventName, value)
|
||||
},
|
||||
handleValueChange (value) {
|
||||
const { value2date } = this.transform
|
||||
if (this.range) {
|
||||
this.currentValue = this.isValidRangeValue(value) ? value.map(value2date) : [null, null]
|
||||
} else {
|
||||
this.currentValue = this.isValidValue(value) ? value2date(value) : null
|
||||
}
|
||||
},
|
||||
selectDate (date) {
|
||||
this.currentValue = date
|
||||
this.updateDate() && this.closePopup()
|
||||
},
|
||||
selectStartDate (date) {
|
||||
this.$set(this.currentValue, 0, date)
|
||||
if (this.currentValue[1]) {
|
||||
this.updateDate()
|
||||
}
|
||||
},
|
||||
selectEndDate (date) {
|
||||
this.$set(this.currentValue, 1, date)
|
||||
if (this.currentValue[0]) {
|
||||
this.updateDate()
|
||||
}
|
||||
},
|
||||
selectTime (time, close) {
|
||||
this.currentValue = time
|
||||
this.updateDate() && close && this.closePopup()
|
||||
},
|
||||
selectStartTime (time) {
|
||||
this.selectStartDate(time)
|
||||
},
|
||||
selectEndTime (time) {
|
||||
this.selectEndDate(time)
|
||||
},
|
||||
showPopup () {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
this.popupVisible = true
|
||||
},
|
||||
closePopup () {
|
||||
this.popupVisible = false
|
||||
},
|
||||
getPopupSize (element) {
|
||||
const originalDisplay = element.style.display
|
||||
const originalVisibility = element.style.visibility
|
||||
element.style.display = 'block'
|
||||
element.style.visibility = 'hidden'
|
||||
const styles = window.getComputedStyle(element)
|
||||
const width = element.offsetWidth + parseInt(styles.marginLeft) + parseInt(styles.marginRight)
|
||||
const height = element.offsetHeight + parseInt(styles.marginTop) + parseInt(styles.marginBottom)
|
||||
const result = { width, height }
|
||||
element.style.display = originalDisplay
|
||||
element.style.visibility = originalVisibility
|
||||
return result
|
||||
},
|
||||
displayPopup () {
|
||||
const dw = document.documentElement.clientWidth
|
||||
const dh = document.documentElement.clientHeight
|
||||
const InputRect = this.$el.getBoundingClientRect()
|
||||
const PopupRect = this._popupRect || (this._popupRect = this.getPopupSize(this.$refs.calendar))
|
||||
const position = {}
|
||||
let offsetRelativeToInputX = 0
|
||||
let offsetRelativeToInputY = 0
|
||||
if (this.appendToBody) {
|
||||
offsetRelativeToInputX = window.pageXOffset + InputRect.left
|
||||
offsetRelativeToInputY = window.pageYOffset + InputRect.top
|
||||
}
|
||||
if (
|
||||
dw - InputRect.left < PopupRect.width &&
|
||||
InputRect.right < PopupRect.width
|
||||
) {
|
||||
position.left = offsetRelativeToInputX - InputRect.left + 1 + 'px'
|
||||
} else if (InputRect.left + InputRect.width / 2 <= dw / 2) {
|
||||
position.left = offsetRelativeToInputX + 'px'
|
||||
} else {
|
||||
position.left = offsetRelativeToInputX + InputRect.width - PopupRect.width + 'px'
|
||||
}
|
||||
if (
|
||||
InputRect.top <= PopupRect.height &&
|
||||
dh - InputRect.bottom <= PopupRect.height
|
||||
) {
|
||||
position.top = offsetRelativeToInputY + dh - InputRect.top - PopupRect.height + 'px'
|
||||
} else if (InputRect.top + InputRect.height / 2 <= dh / 2) {
|
||||
position.top = offsetRelativeToInputY + InputRect.height + 'px'
|
||||
} else {
|
||||
position.top = offsetRelativeToInputY - PopupRect.height + 'px'
|
||||
}
|
||||
if (position.top !== this.position.top || position.left !== this.position.left) {
|
||||
this.position = position
|
||||
}
|
||||
},
|
||||
blur () {
|
||||
this.$refs.input.blur()
|
||||
},
|
||||
handleBlur (event) {
|
||||
this.$emit('blur', event)
|
||||
},
|
||||
handleFocus (event) {
|
||||
if (!this.popupVisible) {
|
||||
this.showPopup()
|
||||
}
|
||||
this.$emit('focus', event)
|
||||
},
|
||||
handleKeydown (event) {
|
||||
const keyCode = event.keyCode
|
||||
// Tab 9 or Enter 13
|
||||
if (keyCode === 9 || keyCode === 13) {
|
||||
// ie emit the watch before the change event
|
||||
this.handleChange()
|
||||
this.userInput = null
|
||||
this.closePopup()
|
||||
}
|
||||
},
|
||||
handleInput (event) {
|
||||
this.userInput = event.target.value
|
||||
},
|
||||
handleChange () {
|
||||
if (this.editable && this.userInput !== null) {
|
||||
const value = this.text
|
||||
const checkDate = this.$refs.calendarPanel.isDisabledTime
|
||||
if (!value) {
|
||||
this.clearDate()
|
||||
return
|
||||
}
|
||||
if (this.range) {
|
||||
const range = value.split(` ${this.rangeSeparator} `)
|
||||
if (range.length === 2) {
|
||||
const start = this.parse(range[0])
|
||||
const end = this.parse(range[1])
|
||||
if (start && end && !checkDate(start, null, end) && !checkDate(end, start, null)) {
|
||||
this.currentValue = [start, end]
|
||||
this.updateDate(true)
|
||||
this.closePopup()
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const date = this.parse(value)
|
||||
if (date && !checkDate(date, null, null)) {
|
||||
this.currentValue = date
|
||||
this.updateDate(true)
|
||||
this.closePopup()
|
||||
return
|
||||
}
|
||||
}
|
||||
this.$emit('input-error', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
import enUS from './locale/en';
|
||||
|
||||
let defaultLocale = 'en';
|
||||
const locales = {};
|
||||
locales[defaultLocale] = enUS;
|
||||
|
||||
export function locale(name, object) {
|
||||
if (typeof name !== 'string') return locales[defaultLocale];
|
||||
if (object) {
|
||||
locales[name] = object;
|
||||
defaultLocale = name;
|
||||
}
|
||||
return locales[name] || locales[defaultLocale];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import af from 'date-format-parse/lib/locale/af';
|
||||
|
||||
const lang = {
|
||||
formatLocale: af,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('af', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import arDZ from 'date-format-parse/lib/locale/ar-dz';
|
||||
|
||||
const lang = {
|
||||
formatLocale: arDZ,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ar-dz', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import arSA from 'date-format-parse/lib/locale/ar-sa';
|
||||
|
||||
const lang = {
|
||||
formatLocale: arSA,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ar-sa', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ar from 'date-format-parse/lib/locale/ar';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ar,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ar', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import az from 'date-format-parse/lib/locale/az';
|
||||
|
||||
const lang = {
|
||||
formatLocale: az,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('az', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import be from 'date-format-parse/lib/locale/be';
|
||||
|
||||
const lang = {
|
||||
formatLocale: be,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('be', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import bg from 'date-format-parse/lib/locale/bg';
|
||||
|
||||
const lang = {
|
||||
formatLocale: bg,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('bg', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import bm from 'date-format-parse/lib/locale/bm';
|
||||
|
||||
const lang = {
|
||||
formatLocale: bm,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('bm', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import bn from 'date-format-parse/lib/locale/bn';
|
||||
|
||||
const lang = {
|
||||
formatLocale: bn,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('bn', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ca from 'date-format-parse/lib/locale/ca';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ca,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ca', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import cs from 'date-format-parse/lib/locale/cs';
|
||||
|
||||
const lang = {
|
||||
formatLocale: cs,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('cs', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import cy from 'date-format-parse/lib/locale/cy';
|
||||
|
||||
const lang = {
|
||||
formatLocale: cy,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('cy', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import da from 'date-format-parse/lib/locale/da';
|
||||
|
||||
const lang = {
|
||||
formatLocale: da,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('da', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import de from 'date-format-parse/lib/locale/de';
|
||||
|
||||
const lang = {
|
||||
formatLocale: de,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('de', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import el from 'date-format-parse/lib/locale/el';
|
||||
|
||||
const lang = {
|
||||
formatLocale: el,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('el', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,10 @@
|
||||
import en from 'date-format-parse/lib/locale/en';
|
||||
|
||||
const lang = {
|
||||
formatLocale: en,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import eo from 'date-format-parse/lib/locale/eo';
|
||||
|
||||
const lang = {
|
||||
formatLocale: eo,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('eo', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import es from 'date-format-parse/lib/locale/es';
|
||||
|
||||
const lang = {
|
||||
formatLocale: es,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('es', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import et from 'date-format-parse/lib/locale/et';
|
||||
|
||||
const lang = {
|
||||
formatLocale: et,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('et', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import fi from 'date-format-parse/lib/locale/fi';
|
||||
|
||||
const lang = {
|
||||
formatLocale: fi,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('fi', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import fr from 'date-format-parse/lib/locale/fr';
|
||||
|
||||
const lang = {
|
||||
formatLocale: fr,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('fr', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import gl from 'date-format-parse/lib/locale/gl';
|
||||
|
||||
const lang = {
|
||||
formatLocale: gl,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('gl', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import gu from 'date-format-parse/lib/locale/gu';
|
||||
|
||||
const lang = {
|
||||
formatLocale: gu,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('gu', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import he from 'date-format-parse/lib/locale/he';
|
||||
|
||||
const lang = {
|
||||
formatLocale: he,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('he', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import hi from 'date-format-parse/lib/locale/hi';
|
||||
|
||||
const lang = {
|
||||
formatLocale: hi,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('hi', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import hr from 'date-format-parse/lib/locale/hr';
|
||||
|
||||
const lang = {
|
||||
formatLocale: hr,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('hr', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import hu from 'date-format-parse/lib/locale/hu';
|
||||
|
||||
const lang = {
|
||||
formatLocale: hu,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: false,
|
||||
};
|
||||
|
||||
DatePicker.locale('hu', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import id from 'date-format-parse/lib/locale/id';
|
||||
|
||||
const lang = {
|
||||
formatLocale: id,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('id', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import is from 'date-format-parse/lib/locale/is';
|
||||
|
||||
const lang = {
|
||||
formatLocale: is,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('is', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import it from 'date-format-parse/lib/locale/it';
|
||||
|
||||
const lang = {
|
||||
formatLocale: it,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('it', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ja from 'date-format-parse/lib/locale/ja';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ja,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: false,
|
||||
};
|
||||
|
||||
DatePicker.locale('ja', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ka from 'date-format-parse/lib/locale/ka';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ka,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ka', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import kk from 'date-format-parse/lib/locale/kk';
|
||||
|
||||
const lang = {
|
||||
formatLocale: kk,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('kk', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ko from 'date-format-parse/lib/locale/ko';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ko,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: false,
|
||||
};
|
||||
|
||||
DatePicker.locale('ko', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -1,101 +0,0 @@
|
||||
export default {
|
||||
'zh': {
|
||||
'days': ['日', '一', '二', '三', '四', '五', '六'],
|
||||
'months': ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||
'pickers': ['未来7天', '未来30天', '最近7天', '最近30天'],
|
||||
'placeholder': {
|
||||
'date': '请选择日期',
|
||||
'dateRange': '请选择日期范围'
|
||||
}
|
||||
},
|
||||
'en': {
|
||||
'days': ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
'months': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
'pickers': ['next 7 days', 'next 30 days', 'previous 7 days', 'previous 30 days'],
|
||||
'placeholder': {
|
||||
'date': 'Select Date',
|
||||
'dateRange': 'Select Date Range'
|
||||
}
|
||||
},
|
||||
'ro': {
|
||||
'days': ['Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm'],
|
||||
'months': ['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sep', 'Oct', 'Noi', 'Dec'],
|
||||
'pickers': ['urmatoarele 7 zile', 'urmatoarele 30 zile', 'ultimele 7 zile', 'ultimele 30 zile'],
|
||||
'placeholder': {
|
||||
'date': 'Selectați Data',
|
||||
'dateRange': 'Selectați Intervalul De Date'
|
||||
}
|
||||
},
|
||||
'fr': {
|
||||
'days': ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
|
||||
'months': ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Aout', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
'pickers': ['7 jours suivants', '30 jours suivants', '7 jours précédents', '30 jours précédents'],
|
||||
'placeholder': {
|
||||
'date': 'Sélectionnez une date',
|
||||
'dateRange': 'Sélectionnez une période'
|
||||
}
|
||||
},
|
||||
'es': {
|
||||
'days': ['Dom', 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab'],
|
||||
'months': ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
|
||||
'pickers': ['próximos 7 días', 'próximos 30 días', '7 días anteriores', '30 días anteriores'],
|
||||
'placeholder': {
|
||||
'date': 'Seleccionar fecha',
|
||||
'dateRange': 'Seleccionar un rango de fechas'
|
||||
}
|
||||
},
|
||||
'pt-br': {
|
||||
'days': ['Dom', 'Seg', 'Ter', 'Qua', 'Quin', 'Sex', 'Sáb'],
|
||||
'months': ['Jan', 'Fev', 'Mar', 'Abr', 'Maio', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
|
||||
'pickers': ['próximos 7 dias', 'próximos 30 dias', '7 dias anteriores', ' 30 dias anteriores'],
|
||||
'placeholder': {
|
||||
'date': 'Selecione uma data',
|
||||
'dateRange': 'Selecione um período'
|
||||
}
|
||||
},
|
||||
'ru': {
|
||||
'days': ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
|
||||
'months': ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
|
||||
'pickers': ['след. 7 дней', 'след. 30 дней', 'прош. 7 дней', 'прош. 30 дней'],
|
||||
'placeholder': {
|
||||
'date': 'Выберите дату',
|
||||
'dateRange': 'Выберите период'
|
||||
}
|
||||
},
|
||||
'de': {
|
||||
'days': ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
|
||||
'months': ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
|
||||
'pickers': ['nächsten 7 Tage', 'nächsten 30 Tage', 'vorigen 7 Tage', 'vorigen 30 Tage'],
|
||||
'placeholder': {
|
||||
'date': 'Datum auswählen',
|
||||
'dateRange': 'Zeitraum auswählen'
|
||||
}
|
||||
},
|
||||
'it': {
|
||||
'days': ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
|
||||
'months': ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
|
||||
'pickers': ['successivi 7 giorni', 'successivi 30 giorni', 'precedenti 7 giorni', 'precedenti 30 giorni'],
|
||||
'placeholder': {
|
||||
'date': 'Seleziona una data',
|
||||
'dateRange': 'Seleziona un intervallo date'
|
||||
}
|
||||
},
|
||||
'cs': {
|
||||
'days': ['Ned', 'Pon', 'Úte', 'Stř', 'Čtv', 'Pát', 'Sob'],
|
||||
'months': ['Led', 'Úno', 'Bře', 'Dub', 'Kvě', 'Čer', 'Čerc', 'Srp', 'Zář', 'Říj', 'Lis', 'Pro'],
|
||||
'pickers': ['příštích 7 dní', 'příštích 30 dní', 'předchozích 7 dní', 'předchozích 30 dní'],
|
||||
'placeholder': {
|
||||
'date': 'Vyberte datum',
|
||||
'dateRange': 'Vyberte časové rozmezí'
|
||||
}
|
||||
},
|
||||
'sl': {
|
||||
'days': ['Ned', 'Pon', 'Tor', 'Sre', 'Čet', 'Pet', 'Sob'],
|
||||
'months': ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
'pickers': ['naslednjih 7 dni', 'naslednjih 30 dni', 'prejšnjih 7 dni', 'prejšnjih 30 dni'],
|
||||
'placeholder': {
|
||||
'date': 'Izberite datum',
|
||||
'dateRange': 'Izberite razpon med 2 datumoma'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import lt from 'date-format-parse/lib/locale/lt';
|
||||
|
||||
const lang = {
|
||||
formatLocale: lt,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('lt', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import lv from 'date-format-parse/lib/locale/lv';
|
||||
|
||||
const lang = {
|
||||
formatLocale: lv,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: false,
|
||||
};
|
||||
|
||||
DatePicker.locale('lv', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import mk from 'date-format-parse/lib/locale/mk';
|
||||
|
||||
const lang = {
|
||||
formatLocale: mk,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('mk', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ms from 'date-format-parse/lib/locale/ms';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ms,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ms', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import nb from 'date-format-parse/lib/locale/nb';
|
||||
|
||||
const lang = {
|
||||
formatLocale: nb,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('nb', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import nlBE from 'date-format-parse/lib/locale/nl-be';
|
||||
|
||||
const lang = {
|
||||
formatLocale: nlBE,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('nl-be', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import nl from 'date-format-parse/lib/locale/nl';
|
||||
|
||||
const lang = {
|
||||
formatLocale: nl,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('nl', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import pl from 'date-format-parse/lib/locale/pl';
|
||||
|
||||
const lang = {
|
||||
formatLocale: pl,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('pl', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ptBR from 'date-format-parse/lib/locale/pt-br';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ptBR,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('pt-br', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import pt from 'date-format-parse/lib/locale/pt';
|
||||
|
||||
const lang = {
|
||||
formatLocale: pt,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('pt', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ro from 'date-format-parse/lib/locale/ro';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ro,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ro', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ru from 'date-format-parse/lib/locale/ru';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ru,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ru', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import sl from 'date-format-parse/lib/locale/sl';
|
||||
|
||||
const lang = {
|
||||
formatLocale: sl,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('sl', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import sr from 'date-format-parse/lib/locale/sr';
|
||||
|
||||
const lang = {
|
||||
formatLocale: sr,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('sr', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import sv from 'date-format-parse/lib/locale/sv';
|
||||
|
||||
const lang = {
|
||||
formatLocale: sv,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('sv', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ta from 'date-format-parse/lib/locale/ta';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ta,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ta', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import te from 'date-format-parse/lib/locale/te';
|
||||
|
||||
const lang = {
|
||||
formatLocale: te,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('te', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import th from 'date-format-parse/lib/locale/th';
|
||||
|
||||
const lang = {
|
||||
formatLocale: th,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('th', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import tr from 'date-format-parse/lib/locale/tr';
|
||||
|
||||
const lang = {
|
||||
formatLocale: tr,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('tr', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import ugCN from 'date-format-parse/lib/locale/ug-cn';
|
||||
|
||||
const lang = {
|
||||
formatLocale: ugCN,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('ug-cn', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import uk from 'date-format-parse/lib/locale/uk';
|
||||
|
||||
const lang = {
|
||||
formatLocale: uk,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: true,
|
||||
};
|
||||
|
||||
DatePicker.locale('uk', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import vi from 'date-format-parse/lib/locale/vi';
|
||||
|
||||
const lang = {
|
||||
formatLocale: vi,
|
||||
yearFormat: 'YYYY',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: false,
|
||||
};
|
||||
|
||||
DatePicker.locale('vi', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import zhCN from 'date-format-parse/lib/locale/zh-cn';
|
||||
|
||||
const lang = {
|
||||
formatLocale: zhCN,
|
||||
yearFormat: 'YYYY年',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: false,
|
||||
};
|
||||
|
||||
DatePicker.locale('zh-cn', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,13 @@
|
||||
import DatePicker from 'vue2-datepicker';
|
||||
import zhTW from 'date-format-parse/lib/locale/zh-tw';
|
||||
|
||||
const lang = {
|
||||
formatLocale: zhTW,
|
||||
yearFormat: 'YYYY年',
|
||||
monthFormat: 'MMM',
|
||||
monthBeforeYear: false,
|
||||
};
|
||||
|
||||
DatePicker.locale('zh-tw', lang);
|
||||
|
||||
export default lang;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { format } from 'date-format-parse';
|
||||
import { locale as getLocale } from '../locale';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
locale: {
|
||||
default: getLocale(),
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatDate(date, fmt) {
|
||||
return format(date, fmt, { locale: this.t('formatLocale') });
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { locale as getLocale } from '../locale';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
locale: {
|
||||
default: getLocale(),
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
t(path) {
|
||||
const arr = path.split('.');
|
||||
let current = this.locale;
|
||||
let value;
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
const prop = arr[i];
|
||||
value = current[prop];
|
||||
if (i === len - 1) {
|
||||
return value;
|
||||
}
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
current = value;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
dispatch (componentName, eventName, params) {
|
||||
let parent = this.$parent || this.$root
|
||||
let name = parent.$options.name
|
||||
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent
|
||||
|
||||
if (parent) {
|
||||
name = parent.$options.name
|
||||
}
|
||||
}
|
||||
if (name && name === componentName) {
|
||||
parent = parent || this
|
||||
parent.$emit.apply(parent, [eventName].concat(params))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import Languages from '@/locale/languages'
|
||||
|
||||
const defaultLang = Languages.zh
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
t (path) {
|
||||
let component = this
|
||||
let name = component.$options.name
|
||||
while (component && (!name || name !== 'DatePicker')) {
|
||||
component = component.$parent
|
||||
if (component) {
|
||||
name = component.$options.name
|
||||
}
|
||||
}
|
||||
const lang = (component && component.language) || defaultLang
|
||||
const arr = path.split('.')
|
||||
let current = lang
|
||||
let value
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
const prop = arr[i]
|
||||
value = current[prop]
|
||||
if (i === len - 1) {
|
||||
return value
|
||||
}
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
current = value
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import locale from '@/mixins/locale'
|
||||
import { formatDate } from '@/utils/index'
|
||||
|
||||
export default {
|
||||
name: 'panelDate',
|
||||
mixins: [locale],
|
||||
props: {
|
||||
value: null,
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
dateFormat: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD'
|
||||
},
|
||||
calendarMonth: {
|
||||
default: new Date().getMonth()
|
||||
},
|
||||
calendarYear: {
|
||||
default: new Date().getFullYear()
|
||||
},
|
||||
firstDayOfWeek: {
|
||||
default: 7,
|
||||
type: Number,
|
||||
validator: val => val >= 1 && val <= 7
|
||||
},
|
||||
disabledDate: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectDate ({ year, month, day }) {
|
||||
const date = new Date(year, month, day)
|
||||
if (this.disabledDate(date)) {
|
||||
return
|
||||
}
|
||||
this.$emit('select', date)
|
||||
},
|
||||
getDays (firstDayOfWeek) {
|
||||
const days = this.t('days')
|
||||
const firstday = parseInt(firstDayOfWeek, 10)
|
||||
return days.concat(days).slice(firstday, firstday + 7)
|
||||
},
|
||||
getDates (year, month, firstDayOfWeek) {
|
||||
const arr = []
|
||||
const time = new Date(year, month)
|
||||
|
||||
time.setDate(0) // 把时间切换到上个月最后一天
|
||||
const lastMonthLength = (time.getDay() + 7 - firstDayOfWeek) % 7 + 1 // time.getDay() 0是星期天, 1是星期一 ...
|
||||
const lastMonthfirst = time.getDate() - (lastMonthLength - 1)
|
||||
for (let i = 0; i < lastMonthLength; i++) {
|
||||
arr.push({ year, month: month - 1, day: lastMonthfirst + i })
|
||||
}
|
||||
|
||||
time.setMonth(time.getMonth() + 2, 0) // 切换到这个月最后一天
|
||||
const curMonthLength = time.getDate()
|
||||
for (let i = 0; i < curMonthLength; i++) {
|
||||
arr.push({ year, month, day: 1 + i })
|
||||
}
|
||||
|
||||
time.setMonth(time.getMonth() + 1, 1) // 切换到下个月第一天
|
||||
const nextMonthLength = 42 - (lastMonthLength + curMonthLength)
|
||||
for (let i = 0; i < nextMonthLength; i++) {
|
||||
arr.push({ year, month: month + 1, day: 1 + i })
|
||||
}
|
||||
|
||||
return arr
|
||||
},
|
||||
getCellClasses ({ year, month, day }) {
|
||||
const classes = []
|
||||
const cellTime = new Date(year, month, day).getTime()
|
||||
const today = new Date().setHours(0, 0, 0, 0)
|
||||
const curTime = this.value && new Date(this.value).setHours(0, 0, 0, 0)
|
||||
const startTime = this.startAt && new Date(this.startAt).setHours(0, 0, 0, 0)
|
||||
const endTime = this.endAt && new Date(this.endAt).setHours(0, 0, 0, 0)
|
||||
|
||||
if (month < this.calendarMonth) {
|
||||
classes.push('last-month')
|
||||
} else if (month > this.calendarMonth) {
|
||||
classes.push('next-month')
|
||||
} else {
|
||||
classes.push('cur-month')
|
||||
}
|
||||
|
||||
if (cellTime === today) {
|
||||
classes.push('today')
|
||||
}
|
||||
|
||||
if (this.disabledDate(cellTime)) {
|
||||
classes.push('disabled')
|
||||
}
|
||||
|
||||
if (curTime) {
|
||||
if (cellTime === curTime) {
|
||||
classes.push('actived')
|
||||
} else if (startTime && cellTime <= curTime) {
|
||||
classes.push('inrange')
|
||||
} else if (endTime && cellTime >= curTime) {
|
||||
classes.push('inrange')
|
||||
}
|
||||
}
|
||||
return classes
|
||||
},
|
||||
getCellTitle ({ year, month, day }) {
|
||||
return formatDate(new Date(year, month, day), this.dateFormat)
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
const ths = this.getDays(this.firstDayOfWeek).map(day => {
|
||||
return <th>{day}</th>
|
||||
})
|
||||
|
||||
const dates = this.getDates(this.calendarYear, this.calendarMonth, this.firstDayOfWeek)
|
||||
const tbody = Array.apply(null, { length: 6 }).map((week, i) => {
|
||||
const tds = dates.slice(7 * i, 7 * i + 7).map(date => {
|
||||
const attrs = {
|
||||
class: this.getCellClasses(date)
|
||||
}
|
||||
return (
|
||||
<td
|
||||
class="cell"
|
||||
{...attrs}
|
||||
data-year={date.year}
|
||||
data-month={date.month}
|
||||
title={this.getCellTitle(date)}
|
||||
onClick={this.selectDate.bind(this, date)}>
|
||||
{date.day}
|
||||
</td>
|
||||
)
|
||||
})
|
||||
return <tr>{tds}</tr>
|
||||
})
|
||||
|
||||
return (
|
||||
<table class="mx-panel mx-panel-date">
|
||||
<thead>
|
||||
<tr>{ths}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tbody}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import locale from '@/mixins/locale'
|
||||
|
||||
export default {
|
||||
name: 'panelMonth',
|
||||
mixins: [locale],
|
||||
props: {
|
||||
value: null,
|
||||
calendarYear: {
|
||||
default: new Date().getFullYear()
|
||||
},
|
||||
disabledMonth: Function
|
||||
},
|
||||
methods: {
|
||||
isDisabled (month) {
|
||||
if (typeof this.disabledMonth === 'function' && this.disabledMonth(month)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
selectMonth (month) {
|
||||
if (this.isDisabled(month)) {
|
||||
return
|
||||
}
|
||||
this.$emit('select', month)
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
let months = this.t('months')
|
||||
const currentYear = this.value && new Date(this.value).getFullYear()
|
||||
const currentMonth = this.value && new Date(this.value).getMonth()
|
||||
months = months.map((v, i) => {
|
||||
return <span
|
||||
class={{
|
||||
'cell': true,
|
||||
'actived': currentYear === this.calendarYear && currentMonth === i,
|
||||
'disabled': this.isDisabled(i)
|
||||
}}
|
||||
onClick={this.selectMonth.bind(this, i)}>
|
||||
{v}
|
||||
</span>
|
||||
})
|
||||
return <div class="mx-panel mx-panel-month">{months}</div>
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import { formatTime, parseTime } from '@/utils/index'
|
||||
|
||||
export default {
|
||||
name: 'panelTime',
|
||||
props: {
|
||||
timePickerOptions: {
|
||||
type: [Object, Function],
|
||||
default () {
|
||||
return null
|
||||
}
|
||||
},
|
||||
timeSelectOptions: {
|
||||
type: Object,
|
||||
default () {
|
||||
return null
|
||||
}
|
||||
},
|
||||
minuteStep: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: val => val >= 0 && val <= 60
|
||||
},
|
||||
value: null,
|
||||
timeType: {
|
||||
type: Array,
|
||||
default () {
|
||||
return ['24', 'a']
|
||||
}
|
||||
},
|
||||
disabledTime: Function
|
||||
},
|
||||
computed: {
|
||||
currentHours () {
|
||||
return this.value ? new Date(this.value).getHours() : 0
|
||||
},
|
||||
currentMinutes () {
|
||||
return this.value ? new Date(this.value).getMinutes() : 0
|
||||
},
|
||||
currentSeconds () {
|
||||
return this.value ? new Date(this.value).getSeconds() : 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
stringifyText (value) {
|
||||
return ('00' + value).slice(String(value).length)
|
||||
},
|
||||
selectTime (time) {
|
||||
if (typeof this.disabledTime === 'function' && this.disabledTime(time)) {
|
||||
return
|
||||
}
|
||||
this.$emit('select', new Date(time))
|
||||
},
|
||||
pickTime (time) {
|
||||
if (typeof this.disabledTime === 'function' && this.disabledTime(time)) {
|
||||
return
|
||||
}
|
||||
this.$emit('pick', new Date(time))
|
||||
},
|
||||
getTimePickerOptions () {
|
||||
const result = []
|
||||
const options = this.timePickerOptions
|
||||
if (!options) {
|
||||
return []
|
||||
}
|
||||
if (typeof options === 'function') {
|
||||
return options() || []
|
||||
}
|
||||
const start = parseTime(options.start)
|
||||
const end = parseTime(options.end)
|
||||
const step = parseTime(options.step)
|
||||
if (start && end && step) {
|
||||
const startMinutes = start.minutes + start.hours * 60
|
||||
const endMinutes = end.minutes + end.hours * 60
|
||||
const stepMinutes = step.minutes + step.hours * 60
|
||||
const len = Math.floor((endMinutes - startMinutes) / stepMinutes)
|
||||
for (let i = 0; i <= len; i++) {
|
||||
let timeMinutes = startMinutes + i * stepMinutes
|
||||
let hours = Math.floor(timeMinutes / 60)
|
||||
let minutes = timeMinutes % 60
|
||||
let value = {
|
||||
hours,
|
||||
minutes
|
||||
}
|
||||
result.push({
|
||||
value,
|
||||
label: formatTime(value, ...this.timeType)
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
const date = this.value
|
||||
? new Date(this.value)
|
||||
: new Date().setHours(0, 0, 0, 0)
|
||||
const disabledTime =
|
||||
typeof this.disabledTime === 'function' && this.disabledTime
|
||||
|
||||
let pickers = this.getTimePickerOptions()
|
||||
if (Array.isArray(pickers) && pickers.length) {
|
||||
pickers = pickers.map(picker => {
|
||||
const pickHours = picker.value.hours
|
||||
const pickMinutes = picker.value.minutes
|
||||
const time = new Date(date).setHours(pickHours, pickMinutes, 0)
|
||||
return (
|
||||
<li
|
||||
class={{
|
||||
'mx-time-picker-item': true,
|
||||
cell: true,
|
||||
actived:
|
||||
pickHours === this.currentHours &&
|
||||
pickMinutes === this.currentMinutes,
|
||||
disabled: disabledTime && disabledTime(time)
|
||||
}}
|
||||
onClick={this.pickTime.bind(this, time)}
|
||||
>
|
||||
{picker.label}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div class="mx-panel mx-panel-time">
|
||||
<ul class="mx-time-list">{pickers}</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const minuteStep = this.minuteStep || 1
|
||||
const minuteLength = parseInt(60 / minuteStep)
|
||||
let hours = Array.apply(null, { length: 24 }).map((_, i) => i)
|
||||
let minutes = Array.apply(null, { length: minuteLength }).map(
|
||||
(_, i) => i * minuteStep
|
||||
)
|
||||
let seconds =
|
||||
this.minuteStep === 0
|
||||
? Array.apply(null, { length: 60 }).map((_, i) => i)
|
||||
: []
|
||||
let columns = { hours, minutes, seconds }
|
||||
|
||||
if (this.timeSelectOptions && typeof this.timeSelectOptions === 'object') {
|
||||
columns = { ...columns, ...this.timeSelectOptions }
|
||||
}
|
||||
|
||||
const hoursColumn = columns.hours.map(v => {
|
||||
const time = new Date(date).setHours(v)
|
||||
return (
|
||||
<li
|
||||
class={{
|
||||
cell: true,
|
||||
actived: v === this.currentHours,
|
||||
disabled: disabledTime && disabledTime(time)
|
||||
}}
|
||||
onClick={this.selectTime.bind(this, time)}
|
||||
>
|
||||
{this.stringifyText(v)}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
const minutesColumn = columns.minutes.map(v => {
|
||||
const time = new Date(date).setMinutes(v)
|
||||
return (
|
||||
<li
|
||||
class={{
|
||||
cell: true,
|
||||
actived: v === this.currentMinutes,
|
||||
disabled: disabledTime && disabledTime(time)
|
||||
}}
|
||||
onClick={this.selectTime.bind(this, time)}
|
||||
>
|
||||
{this.stringifyText(v)}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
const secondsColumn = columns.seconds.map(v => {
|
||||
const time = new Date(date).setSeconds(v)
|
||||
return (
|
||||
<li
|
||||
class={{
|
||||
cell: true,
|
||||
actived: v === this.currentSeconds,
|
||||
disabled: disabledTime && disabledTime(time)
|
||||
}}
|
||||
onClick={this.selectTime.bind(this, time)}
|
||||
>
|
||||
{this.stringifyText(v)}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
let times = [hoursColumn, minutesColumn, secondsColumn].filter(
|
||||
v => v.length > 0
|
||||
)
|
||||
|
||||
times = times.map(list => (
|
||||
<ul class="mx-time-list" style={{ width: 100 / times.length + '%' }}>
|
||||
{list}
|
||||
</ul>
|
||||
))
|
||||
|
||||
return <div class="mx-panel mx-panel-time">{times}</div>
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
export default {
|
||||
name: 'panelYear',
|
||||
props: {
|
||||
value: null,
|
||||
firstYear: Number,
|
||||
disabledYear: Function
|
||||
},
|
||||
methods: {
|
||||
isDisabled (year) {
|
||||
if (typeof this.disabledYear === 'function' && this.disabledYear(year)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
selectYear (year) {
|
||||
if (this.isDisabled(year)) {
|
||||
return
|
||||
}
|
||||
this.$emit('select', year)
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
// 当前年代
|
||||
const firstYear = Math.floor(this.firstYear / 10) * 10
|
||||
const currentYear = this.value && new Date(this.value).getFullYear()
|
||||
const years = Array.apply(null, { length: 10 }).map((_, i) => {
|
||||
const year = firstYear + i
|
||||
return <span
|
||||
class={{
|
||||
'cell': true,
|
||||
'actived': currentYear === year,
|
||||
'disabled': this.isDisabled(year)
|
||||
}}
|
||||
onClick={this.selectYear.bind(this, year)}
|
||||
>{year}</span>
|
||||
})
|
||||
return <div class="mx-panel mx-panel-year">{years}</div>
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import { rafThrottle } from './util/throttle';
|
||||
import { getPopupElementSize, getRelativePosition, getScrollParent } from './util/dom';
|
||||
|
||||
export default {
|
||||
name: 'Popup',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
appendToBody: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
top: '',
|
||||
left: '',
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
visible: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.$nextTick(() => {
|
||||
if (val) {
|
||||
this.displayPopup();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.inline) {
|
||||
return;
|
||||
}
|
||||
if (this.appendToBody) {
|
||||
document.body.appendChild(this.$el);
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', this.handleClickOutside);
|
||||
|
||||
// change the popup position when resize or scroll
|
||||
const relativeElement = this.$parent.$el;
|
||||
this._displayPopup = rafThrottle(() => this.displayPopup());
|
||||
this._scrollParent = getScrollParent(relativeElement) || window;
|
||||
this._scrollParent.addEventListener('scroll', this._displayPopup);
|
||||
window.addEventListener('resize', this._displayPopup);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.inline) {
|
||||
return;
|
||||
}
|
||||
if (this.appendToBody && this.$el.parentNode) {
|
||||
this.$el.parentNode.removeChild(this.$el);
|
||||
}
|
||||
|
||||
document.removeEventListener('mousedown', this.handleClickOutside);
|
||||
|
||||
this._scrollParent.removeEventListener('scroll', this._displayPopup);
|
||||
window.removeEventListener('resize', this._displayPopup);
|
||||
},
|
||||
methods: {
|
||||
handleClickOutside(evt) {
|
||||
if (!this.visible) return;
|
||||
const { target } = evt;
|
||||
const el = this.$el;
|
||||
if (el && !el.contains(target)) {
|
||||
this.$emit('clickoutside', evt);
|
||||
}
|
||||
},
|
||||
displayPopup() {
|
||||
if (this.inline || !this.visible) return;
|
||||
const popup = this.$el;
|
||||
const relativeElement = this.$parent.$el;
|
||||
const { appendToBody } = this;
|
||||
if (!this._popupRect) {
|
||||
this._popupRect = getPopupElementSize(popup);
|
||||
}
|
||||
const { width, height } = this._popupRect;
|
||||
const { left, top } = getRelativePosition(relativeElement, width, height, appendToBody);
|
||||
this.left = left;
|
||||
this.top = top;
|
||||
},
|
||||
},
|
||||
render() {
|
||||
if (this.inline) {
|
||||
return <div>{this.$slots.default}</div>;
|
||||
}
|
||||
return (
|
||||
<transition name="mx-zoom-in-down">
|
||||
{this.visible && (
|
||||
<div class="mx-datepicker-popup" style={{ top: this.top, left: this.left }}>
|
||||
{this.$slots.default}
|
||||
</div>
|
||||
)}
|
||||
</transition>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div
|
||||
class="mx-scrollbar"
|
||||
:style="{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
ref="wrap"
|
||||
class="mx-scrollbar-wrap"
|
||||
:style="{ overflow: 'hidden scroll', height: '100%', marginRight: `-${scrollbarWidth}px` }"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="mx-scrollbar-track">
|
||||
<div
|
||||
ref="thumb"
|
||||
class="mx-scrollbar-thumb"
|
||||
:style="{ height: thumbHeight, top: thumbTop }"
|
||||
@mousedown="handleDragstart"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* istanbul ignore file */
|
||||
import getScrollbarWidth from '../util/scrollbar-width';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
scrollbarWidth: 0,
|
||||
thumbTop: '',
|
||||
thumbHeight: '',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.scrollbarWidth = getScrollbarWidth();
|
||||
document.addEventListener('mouseup', this.handleDragend);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.addEventListener('mouseup', this.handleDragend);
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(this.getThumbSize);
|
||||
},
|
||||
methods: {
|
||||
getThumbSize() {
|
||||
const { wrap } = this.$refs;
|
||||
if (!wrap) return;
|
||||
const heightPercentage = (wrap.clientHeight * 100) / wrap.scrollHeight;
|
||||
this.thumbHeight = heightPercentage < 100 ? `${heightPercentage}%` : '';
|
||||
},
|
||||
handleScroll(evt) {
|
||||
const el = evt.currentTarget;
|
||||
const { scrollHeight, scrollTop } = el;
|
||||
this.thumbTop = `${(scrollTop * 100) / scrollHeight}%`;
|
||||
},
|
||||
handleDragstart(evt) {
|
||||
evt.stopImmediatePropagation();
|
||||
this._draggable = true;
|
||||
const { offsetTop } = this.$refs.thumb;
|
||||
this._prevY = evt.clientY - offsetTop;
|
||||
document.addEventListener('mousemove', this.handleDraging);
|
||||
},
|
||||
handleDraging(evt) {
|
||||
if (!this._draggable) return;
|
||||
const { clientY } = evt;
|
||||
const { wrap } = this.$refs;
|
||||
const { scrollHeight, clientHeight } = wrap;
|
||||
const offsetY = clientY - this._prevY;
|
||||
const top = (offsetY * scrollHeight) / clientHeight;
|
||||
wrap.scrollTop = top;
|
||||
},
|
||||
handleDragend() {
|
||||
if (this._draggable) {
|
||||
this._draggable = false;
|
||||
document.removeEventListener('mousemove', this.handleDraging);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
.mx-zoom-in-down-enter-active,
|
||||
.mx-zoom-in-down-leave-active {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
|
||||
opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
transform-origin: center top;
|
||||
}
|
||||
|
||||
.mx-zoom-in-down-enter,
|
||||
.mx-zoom-in-down-leave-to {
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
.mx-btn {
|
||||
box-sizing: border-box;
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 7px 15px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
color: $default-color;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
border-color: $primary-color;
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-btn-text {
|
||||
border: 0;
|
||||
padding: 0 4px;
|
||||
text-align: left;
|
||||
line-height: inherit;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
.mx-icon-left:before,
|
||||
.mx-icon-right:before,
|
||||
.mx-icon-double-left:before,
|
||||
.mx-icon-double-right:before,
|
||||
.mx-icon-double-left:after,
|
||||
.mx-icon-double-right:after {
|
||||
content: '';
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
vertical-align: middle;
|
||||
border-style: solid;
|
||||
border-color: currentColor;
|
||||
border-width: 2px 0 0 2px;
|
||||
border-radius: 1px;
|
||||
box-sizing: border-box;
|
||||
transform-origin: center;
|
||||
transform: rotate(-45deg) scale(0.7);
|
||||
}
|
||||
|
||||
.mx-icon-double-left:after {
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.mx-icon-double-right:before {
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
.mx-icon-right:before,
|
||||
.mx-icon-double-right:before,
|
||||
.mx-icon-double-right:after {
|
||||
transform: rotate(135deg) scale(0.7);
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
@import './var.scss';
|
||||
@import './icon.scss';
|
||||
@import './btn.scss';
|
||||
@import './scrollbar.scss';
|
||||
@import './animation.scss';
|
||||
|
||||
.mx-datepicker {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 210px;
|
||||
color: $default-color;
|
||||
font: 14px/1.5 'Helvetica Neue', Helvetica, Arial, 'Microsoft Yahei', sans-serif;
|
||||
svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
&.disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-datepicker-range {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.mx-datepicker-inline {
|
||||
width: auto;
|
||||
border: 1px solid $border-color;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.mx-input-wrapper {
|
||||
position: relative;
|
||||
.mx-icon-clear {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
.mx-icon-clear {
|
||||
display: block;
|
||||
}
|
||||
.mx-icon-clear + .mx-icon-calendar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-input {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
padding: 6px 30px;
|
||||
padding-left: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: #40a9ff;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
background-color: #f5f5f5;
|
||||
border-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-icon-calendar,
|
||||
.mx-icon-clear {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 8px;
|
||||
transform: translateY(-50%);
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx-icon-clear {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.mx-datepicker-popup {
|
||||
color: $default-color;
|
||||
font: 14px/1.5 'Helvetica Neue', Helvetica, Arial, 'Microsoft Yahei', sans-serif;
|
||||
position: absolute;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
border: 1px solid $border-color;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.mx-datepicker-content-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mx-datepicker-content {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mx-datepicker-sidebar {
|
||||
box-sizing: border-box;
|
||||
width: 100px;
|
||||
padding: 6px;
|
||||
overflow: auto;
|
||||
border-right: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.mx-btn-shortcut {
|
||||
display: block;
|
||||
padding: 0 6px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.mx-range-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mx-datepicker-header {
|
||||
padding: 6px 8px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.mx-datepicker-footer {
|
||||
padding: 6px 8px;
|
||||
text-align: right;
|
||||
border-top: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.mx-calendar {
|
||||
padding: 6px 12px;
|
||||
& + & {
|
||||
border-left: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-calendar-header {
|
||||
box-sizing: border-box;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mx-btn-icon-left {
|
||||
float: left;
|
||||
}
|
||||
.mx-btn-icon-right {
|
||||
float: right;
|
||||
}
|
||||
.mx-btn-icon-double-left {
|
||||
@extend .mx-btn-icon-left;
|
||||
}
|
||||
.mx-btn-icon-double-right {
|
||||
@extend .mx-btn-icon-right;
|
||||
}
|
||||
|
||||
.mx-calendar-header-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mx-calendar-content {
|
||||
position: relative;
|
||||
width: 224px;
|
||||
height: 224px;
|
||||
box-sizing: border-box;
|
||||
.cell {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: $hover-color;
|
||||
}
|
||||
&.active {
|
||||
color: #fff;
|
||||
background-color: $primary-color;
|
||||
}
|
||||
&.in-range {
|
||||
background-color: $range-color;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #ccc;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-calendar-week-mode {
|
||||
.mx-date-row {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: $hover-color;
|
||||
}
|
||||
&.mx-active-week {
|
||||
background-color: $range-color;
|
||||
}
|
||||
.cell {
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
&.active {
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-week-number {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mx-table {
|
||||
table-layout: fixed;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
th {
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
td {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-table-date {
|
||||
td,
|
||||
th {
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.today {
|
||||
color: mix(#fff, $primary-color, 10%);
|
||||
}
|
||||
.not-current-month {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-time {
|
||||
flex: 1;
|
||||
width: 224px;
|
||||
background: #fff;
|
||||
& + & {
|
||||
border-left: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
.mx-calendar-time {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.mx-time-header {
|
||||
@extend .mx-calendar-header;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.mx-time-content {
|
||||
height: 224px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mx-time-columns {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mx-time-column {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
border-left: 1px solid $border-color;
|
||||
text-align: center;
|
||||
|
||||
&:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 32 * 6px;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 12px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-time-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
li {
|
||||
padding: 8px 10px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-time-list,
|
||||
.mx-time-column {
|
||||
li {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: $hover-color;
|
||||
}
|
||||
&.active {
|
||||
color: $primary-color;
|
||||
font-weight: 700;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #ccc;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
@import './var.scss';
|
||||
|
||||
@mixin state {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #eaf8fe;
|
||||
}
|
||||
&.active {
|
||||
color: #fff;
|
||||
background-color: $primary-color;
|
||||
}
|
||||
&.in-range {
|
||||
background-color: #eaf8fe;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #ccc;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
.mx-scrollbar {
|
||||
height: 100%;
|
||||
&:hover {
|
||||
.mx-scrollbar-track {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx-scrollbar-track {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
width: 6px;
|
||||
z-index: 1;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.24s ease-out;
|
||||
.mx-scrollbar-thumb {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
cursor: pointer;
|
||||
border-radius: inherit;
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
$default-color: #73879c;
|
||||
$primary-color: #1284e7;
|
||||
$range-color: #dff2fd;
|
||||
$hover-color: #eaf8fe;
|
||||
$border-color: #e8e8e8;
|
||||
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div class="mx-time-columns">
|
||||
<scrollbar-vertical v-for="(col, i) in columns" :key="i" class="mx-time-column">
|
||||
<ul :data-type="col.type" @click="handleSelect">
|
||||
<li
|
||||
v-for="item in col.list"
|
||||
:key="item.value"
|
||||
class="cell"
|
||||
:data-value="item.value"
|
||||
:class="getClasses(item.value)"
|
||||
>
|
||||
{{ item.text }}
|
||||
</li>
|
||||
</ul>
|
||||
</scrollbar-vertical>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScrollbarVertical from '../scrollbar/scrollbar-vertical';
|
||||
import { getScrollParent } from '../util/dom';
|
||||
|
||||
const padNumber = value => {
|
||||
value = parseInt(value, 10);
|
||||
return value < 10 ? `0${value}` : `${value}`;
|
||||
};
|
||||
|
||||
const generateOptions = (length, step, options) => {
|
||||
if (Array.isArray(options)) {
|
||||
return options.filter(v => v >= 0 && v < length);
|
||||
}
|
||||
if (step <= 0) {
|
||||
step = 1;
|
||||
}
|
||||
const arr = [];
|
||||
for (let i = 0; i < length; i += step) {
|
||||
arr.push(i);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
const scrollTo = (element, to, duration = 0) => {
|
||||
// jump to target if duration zero
|
||||
if (duration <= 0) {
|
||||
requestAnimationFrame(() => {
|
||||
element.scrollTop = to;
|
||||
});
|
||||
return;
|
||||
}
|
||||
const difference = to - element.scrollTop;
|
||||
const tick = (difference / duration) * 10;
|
||||
requestAnimationFrame(() => {
|
||||
const scrollTop = element.scrollTop + tick;
|
||||
if (scrollTop >= to) {
|
||||
element.scrollTop = to;
|
||||
return;
|
||||
}
|
||||
element.scrollTop = scrollTop;
|
||||
scrollTo(element, to, duration - 10);
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'ListColumns',
|
||||
|
||||
components: { ScrollbarVertical },
|
||||
|
||||
props: {
|
||||
date: Date,
|
||||
getClasses: {
|
||||
type: Function,
|
||||
default: () => [],
|
||||
},
|
||||
hourOptions: Array,
|
||||
minuteOptions: Array,
|
||||
secondOptions: Array,
|
||||
showHour: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showMinute: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showSecond: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hourStep: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
minuteStep: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
secondStep: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
use12h: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
const cols = [];
|
||||
if (this.showHour) cols.push({ type: 'hour', list: this.getHoursList() });
|
||||
if (this.showMinute) cols.push({ type: 'minute', list: this.getMinutesList() });
|
||||
if (this.showSecond) cols.push({ type: 'second', list: this.getSecondsList() });
|
||||
if (this.use12h) cols.push({ type: 'ampm', list: this.getAMPMList() });
|
||||
|
||||
return cols.filter(v => v.list.length > 0);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
date: {
|
||||
handler() {
|
||||
this.$nextTick(() => {
|
||||
this.scrollToSelected(100);
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToSelected(0);
|
||||
},
|
||||
methods: {
|
||||
getHoursList() {
|
||||
return generateOptions(this.use12h ? 12 : 24, this.hourStep, this.hourOptions).map(num => {
|
||||
const date = new Date(this.date);
|
||||
let text = padNumber(num);
|
||||
if (this.use12h) {
|
||||
if (num === 0) {
|
||||
text = '12';
|
||||
}
|
||||
if (date.getHours() >= 12) {
|
||||
num += 12;
|
||||
}
|
||||
}
|
||||
const value = date.setHours(num);
|
||||
return { value, text };
|
||||
});
|
||||
},
|
||||
getMinutesList() {
|
||||
return generateOptions(60, this.minuteStep, this.minuteOptions).map(num => {
|
||||
const value = new Date(this.date).setMinutes(num);
|
||||
return { value, text: padNumber(num) };
|
||||
});
|
||||
},
|
||||
getSecondsList() {
|
||||
return generateOptions(60, this.secondStep, this.secondOptions).map(num => {
|
||||
const value = new Date(this.date).setSeconds(num);
|
||||
return { value, text: padNumber(num) };
|
||||
});
|
||||
},
|
||||
getAMPMList() {
|
||||
return ['AM', 'PM'].map((text, i) => {
|
||||
const date = new Date(this.date);
|
||||
const value = date.setHours((date.getHours() % 12) + i * 12);
|
||||
return { text, value };
|
||||
});
|
||||
},
|
||||
scrollToSelected(duration) {
|
||||
const elements = this.$el.querySelectorAll('.active');
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
const scrollElement = getScrollParent(element, this.$el);
|
||||
if (scrollElement) {
|
||||
const to = element.offsetTop;
|
||||
scrollTo(scrollElement, to, duration);
|
||||
}
|
||||
}
|
||||
},
|
||||
handleSelect(evt) {
|
||||
const { target, currentTarget } = evt;
|
||||
if (target.tagName.toUpperCase() !== 'LI') return;
|
||||
const type = currentTarget.getAttribute('data-type');
|
||||
const value = parseInt(target.getAttribute('data-value'), 10);
|
||||
this.$emit('select', value, type);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<scrollbar-vertical>
|
||||
<ul class="mx-time-list">
|
||||
<li
|
||||
v-for="item in list"
|
||||
:key="item.value"
|
||||
class="cell"
|
||||
:class="getClasses(item.value)"
|
||||
@click="handleSelect(item.value)"
|
||||
>
|
||||
{{ item.text }}
|
||||
</li>
|
||||
</ul>
|
||||
</scrollbar-vertical>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import localeMixin from '../mixin/locale';
|
||||
import formatMixin from '../mixin/format';
|
||||
import ScrollbarVertical from '../scrollbar/scrollbar-vertical';
|
||||
import { getScrollParent } from '../util/dom';
|
||||
|
||||
function parseOption(time = '') {
|
||||
const values = time.split(':');
|
||||
if (values.length >= 2) {
|
||||
const hours = parseInt(values[0], 10);
|
||||
const minutes = parseInt(values[1], 10);
|
||||
return {
|
||||
hours,
|
||||
minutes,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const scrollTo = (element, to) => {
|
||||
if (element) {
|
||||
element.scrollTop = to;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'ListOptions',
|
||||
components: { ScrollbarVertical },
|
||||
mixins: [localeMixin, formatMixin],
|
||||
props: {
|
||||
date: Date,
|
||||
options: {
|
||||
type: [Object, Function],
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss',
|
||||
},
|
||||
getClasses: {
|
||||
type: Function,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
list() {
|
||||
const result = [];
|
||||
const { options } = this;
|
||||
if (typeof options === 'function') {
|
||||
return options() || [];
|
||||
}
|
||||
const start = parseOption(options.start);
|
||||
const end = parseOption(options.end);
|
||||
const step = parseOption(options.step);
|
||||
const format = options.format || this.format;
|
||||
if (start && end && step) {
|
||||
const startMinutes = start.minutes + start.hours * 60;
|
||||
const endMinutes = end.minutes + end.hours * 60;
|
||||
const stepMinutes = step.minutes + step.hours * 60;
|
||||
const len = Math.floor((endMinutes - startMinutes) / stepMinutes);
|
||||
for (let i = 0; i <= len; i++) {
|
||||
const timeMinutes = startMinutes + i * stepMinutes;
|
||||
const hours = Math.floor(timeMinutes / 60);
|
||||
const minutes = timeMinutes % 60;
|
||||
const value = new Date(this.date).setHours(hours, minutes, 0);
|
||||
result.push({
|
||||
value,
|
||||
text: this.formatDate(value, format),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToSelected();
|
||||
},
|
||||
methods: {
|
||||
scrollToSelected() {
|
||||
const element = this.$el.querySelector('.active');
|
||||
if (!element) return;
|
||||
const scrollElement = getScrollParent(element, this.$el);
|
||||
if (!scrollElement) return;
|
||||
const to = element.offsetTop;
|
||||
scrollTo(scrollElement, to);
|
||||
},
|
||||
handleSelect(value) {
|
||||
this.$emit('select', value, 'time');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="mx-time">
|
||||
<div v-if="showTimeHeader" class="mx-time-header">
|
||||
<button class="mx-btn mx-btn-text mx-time-header-title" @click="handleClickTitle">
|
||||
{{ title }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mx-time-content">
|
||||
<list-options
|
||||
v-if="timePickerOptions"
|
||||
:date="innerValue"
|
||||
:get-classes="getClasses"
|
||||
:options="timePickerOptions"
|
||||
:format="format"
|
||||
@select="handleSelect"
|
||||
></list-options>
|
||||
<list-columns
|
||||
v-else
|
||||
:date="innerValue"
|
||||
:get-classes="getClasses"
|
||||
:hour-options="hourOptions"
|
||||
:minute-options="minuteOptions"
|
||||
:second-options="secondOptions"
|
||||
:hour-step="hourStep"
|
||||
:minute-step="minuteStep"
|
||||
:second-step="secondStep"
|
||||
v-bind="ShowHourMinuteSecondAMPM"
|
||||
@select="handleSelect"
|
||||
></list-columns>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getValidDate } from '../util/date';
|
||||
import localeMixin from '../mixin/locale';
|
||||
import formatMixin from '../mixin/format';
|
||||
import ListColumns from './list-columns';
|
||||
import ListOptions from './list-options';
|
||||
|
||||
export default {
|
||||
name: 'TimePanel',
|
||||
components: { ListColumns, ListOptions },
|
||||
mixins: [localeMixin, formatMixin],
|
||||
props: {
|
||||
value: {},
|
||||
defaultValue: {
|
||||
type: [Date, Number],
|
||||
default() {
|
||||
const date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return date;
|
||||
},
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss',
|
||||
},
|
||||
timeTitleFormat: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD',
|
||||
},
|
||||
showTimeHeader: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabledTime: {
|
||||
type: Function,
|
||||
default: () => false,
|
||||
},
|
||||
timePickerOptions: {
|
||||
type: [Object, Function],
|
||||
default() {
|
||||
return null;
|
||||
},
|
||||
},
|
||||
hourOptions: Array,
|
||||
minuteOptions: Array,
|
||||
secondOptions: Array,
|
||||
hourStep: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
minuteStep: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
secondStep: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
showHour: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
showMinute: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
showSecond: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
use12h: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
innerValue() {
|
||||
return getValidDate(this.value, this.defaultValue);
|
||||
},
|
||||
title() {
|
||||
const titleFormat = this.timeTitleFormat;
|
||||
const date = new Date(this.innerValue);
|
||||
return this.formatDate(date, titleFormat);
|
||||
},
|
||||
ShowHourMinuteSecondAMPM() {
|
||||
const { format } = this;
|
||||
const defaultProps = {
|
||||
showHour: /[HhKk]/.test(format),
|
||||
showMinute: /m/.test(format),
|
||||
showSecond: /s/.test(format),
|
||||
use12h: /a/i.test(format),
|
||||
};
|
||||
const obj = {};
|
||||
Object.keys(defaultProps).forEach(key => {
|
||||
obj[key] = typeof this[key] === 'boolean' ? this[key] : defaultProps[key];
|
||||
});
|
||||
return obj;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleSelect(value, type) {
|
||||
const date = new Date(value);
|
||||
if (!this.disabledTime(new Date(value))) {
|
||||
this.$emit('select', date, type);
|
||||
}
|
||||
},
|
||||
handleClickTitle() {
|
||||
this.$emit('title-click');
|
||||
},
|
||||
getClasses(value) {
|
||||
const cellDate = new Date(value);
|
||||
if (this.disabledTime(new Date(value))) {
|
||||
return 'disabled';
|
||||
}
|
||||
if (cellDate.getTime() === this.innerValue.getTime()) {
|
||||
return 'active';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,90 @@
|
||||
import TimePanel from './time-panel';
|
||||
import { isValidRangeDate } from '../util/date';
|
||||
|
||||
export default {
|
||||
name: 'TimeRange',
|
||||
props: {
|
||||
...TimePanel.props,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
startValue: new Date(NaN),
|
||||
endValue: new Date(NaN),
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (isValidRangeDate(this.value)) {
|
||||
const [startValue, endValue] = this.value;
|
||||
this.startValue = startValue;
|
||||
this.endValue = endValue;
|
||||
} else {
|
||||
this.startValue = new Date(NaN);
|
||||
this.endValue = new Date(NaN);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emitChange(type, index) {
|
||||
const date = [this.startValue, this.endValue];
|
||||
this.$emit('select', date, type, index);
|
||||
},
|
||||
handleSelectStart(date, type) {
|
||||
this.startValue = date;
|
||||
// check the NaN
|
||||
if (!(this.endValue.getTime() >= date.getTime())) {
|
||||
this.endValue = date;
|
||||
}
|
||||
this.emitChange(type, 0);
|
||||
},
|
||||
handleSelectEnd(date, type) {
|
||||
// check the NaN
|
||||
this.endValue = date;
|
||||
if (!(this.startValue.getTime() <= date.getTime())) {
|
||||
this.startValue = date;
|
||||
}
|
||||
this.emitChange(type, 1);
|
||||
},
|
||||
disabledStartTime(date) {
|
||||
return this.disabledTime(date, 0);
|
||||
},
|
||||
disabledEndTime(date) {
|
||||
return date.getTime() < this.startValue.getTime() || this.disabledTime(date, 1);
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div class="mx-range-wrapper">
|
||||
<TimePanel
|
||||
{...{
|
||||
props: {
|
||||
...this.$props,
|
||||
value: this.startValue,
|
||||
disabledTime: this.disabledStartTime,
|
||||
},
|
||||
on: {
|
||||
...this.$listeners,
|
||||
select: this.handleSelectStart,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TimePanel
|
||||
{...{
|
||||
props: {
|
||||
...this.$props,
|
||||
value: this.endValue,
|
||||
disabledTime: this.disabledEndTime,
|
||||
},
|
||||
on: {
|
||||
...this.$listeners,
|
||||
select: this.handleSelectEnd,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* chunk the array
|
||||
* @param {Array} arr
|
||||
* @param {Number} size
|
||||
*/
|
||||
export function chunk(arr, size) {
|
||||
if (!Array.isArray(arr)) {
|
||||
return [];
|
||||
}
|
||||
const result = [];
|
||||
const len = arr.length;
|
||||
let i = 0;
|
||||
size = size || len;
|
||||
while (i < len) {
|
||||
result.push(arr.slice(i, (i += size)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* isObject
|
||||
* @param {*} obj
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isObject(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||
}
|
||||
|
||||
/**
|
||||
* pick object
|
||||
* @param {Object} obj
|
||||
* @param {Array|String} props
|
||||
*/
|
||||
export function pick(obj, props) {
|
||||
if (!isObject(obj)) return {};
|
||||
if (!Array.isArray(props)) {
|
||||
props = [props];
|
||||
}
|
||||
const res = {};
|
||||
props.forEach(prop => {
|
||||
if (prop in obj) {
|
||||
res[prop] = obj[prop];
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* deep merge two object without merging array
|
||||
* @param {object} target
|
||||
* @param {object} source
|
||||
*/
|
||||
export function mergeDeep(target, source) {
|
||||
if (!isObject(target)) {
|
||||
return {};
|
||||
}
|
||||
let result = target;
|
||||
if (isObject(source)) {
|
||||
Object.keys(source).forEach(key => {
|
||||
let value = source[key];
|
||||
if (isObject(value) && isObject(target[key])) {
|
||||
value = mergeDeep(target[key], value);
|
||||
}
|
||||
result = { ...result, [key]: value };
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export function isValidDate(date) {
|
||||
return date instanceof Date && !isNaN(date);
|
||||
}
|
||||
|
||||
export function isValidRangeDate(date) {
|
||||
return Array.isArray(date) && date.length === 2 && date.every(isValidDate) && date[0] <= date[1];
|
||||
}
|
||||
|
||||
export function getValidDate(value, ...backup) {
|
||||
const date = new Date(value);
|
||||
return isValidDate(date) ? date : getValidDate(...backup);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* get the hidden element width, height
|
||||
* @param {HTMLElement} element dom
|
||||
*/
|
||||
export function getPopupElementSize(element) {
|
||||
const originalDisplay = element.style.display;
|
||||
const originalVisibility = element.style.visibility;
|
||||
element.style.display = 'block';
|
||||
element.style.visibility = 'hidden';
|
||||
const styles = window.getComputedStyle(element);
|
||||
const width =
|
||||
element.offsetWidth + parseInt(styles.marginLeft, 10) + parseInt(styles.marginRight, 10);
|
||||
const height =
|
||||
element.offsetHeight + parseInt(styles.marginTop, 10) + parseInt(styles.marginBottom, 10);
|
||||
element.style.display = originalDisplay;
|
||||
element.style.visibility = originalVisibility;
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
/**
|
||||
* get the popup position
|
||||
* @param {HTMLElement} el relative element
|
||||
* @param {Number} targetWidth target element's width
|
||||
* @param {Number} targetHeight target element's height
|
||||
* @param {Boolean} fixed
|
||||
*/
|
||||
export function getRelativePosition(el, targetWidth, targetHeight, fixed) {
|
||||
let left = 0;
|
||||
let top = 0;
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
const relativeRect = el.getBoundingClientRect();
|
||||
const dw = document.documentElement.clientWidth;
|
||||
const dh = document.documentElement.clientHeight;
|
||||
if (fixed) {
|
||||
offsetX = window.pageXOffset + relativeRect.left;
|
||||
offsetY = window.pageYOffset + relativeRect.top;
|
||||
}
|
||||
if (dw - relativeRect.left < targetWidth && relativeRect.right < targetWidth) {
|
||||
left = offsetX - relativeRect.left + 1;
|
||||
} else if (relativeRect.left + relativeRect.width / 2 <= dw / 2) {
|
||||
left = offsetX;
|
||||
} else {
|
||||
left = offsetX + relativeRect.width - targetWidth;
|
||||
}
|
||||
if (relativeRect.top <= targetHeight && dh - relativeRect.bottom <= targetHeight) {
|
||||
top = offsetY + dh - relativeRect.top - targetHeight;
|
||||
} else if (relativeRect.top + relativeRect.height / 2 <= dh / 2) {
|
||||
top = offsetY + relativeRect.height;
|
||||
} else {
|
||||
top = offsetY - targetHeight;
|
||||
}
|
||||
return { left: `${left}px`, top: `${top}px` };
|
||||
}
|
||||
|
||||
export function getScrollParent(node, until = document) {
|
||||
if (!node || node === until) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (node.scrollHeight > node.clientHeight) {
|
||||
return node;
|
||||
}
|
||||
return getScrollParent(node.parentNode, until);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
let scrollBarWidth;
|
||||
|
||||
export default function() {
|
||||
if (typeof window === 'undefined') return 0;
|
||||
if (scrollBarWidth !== undefined) return scrollBarWidth;
|
||||
|
||||
const outer = document.createElement('div');
|
||||
outer.style.visibility = 'hidden';
|
||||
outer.style.overflow = 'scroll';
|
||||
outer.style.width = '100px';
|
||||
outer.style.position = 'absolute';
|
||||
outer.style.top = '-9999px';
|
||||
document.body.appendChild(outer);
|
||||
|
||||
const inner = document.createElement('div');
|
||||
inner.style.width = '100%';
|
||||
outer.appendChild(inner);
|
||||
|
||||
scrollBarWidth = outer.offsetWidth - inner.offsetWidth;
|
||||
outer.parentNode.removeChild(outer);
|
||||
|
||||
return scrollBarWidth;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* istanbul ignore file */
|
||||
export function rafThrottle(fn) {
|
||||
let isRunning = false;
|
||||
return function fnBinfRaf(...args) {
|
||||
if (isRunning) return;
|
||||
isRunning = true;
|
||||
requestAnimationFrame(() => {
|
||||
isRunning = false;
|
||||
fn.apply(this, args);
|
||||
});
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user