mirror of
https://github.com/tenrok/vue2-datepicker.git
synced 2026-06-13 07:22:26 +03:00
refactor: 2.0
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
<template>
|
||||
<div class="mx-calendar">
|
||||
<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 !== 'TIME'"
|
||||
class="mx-current-month"
|
||||
@click="handleBtnMonth">{{months[calendarMonth]}}</a>
|
||||
<a
|
||||
v-show="panel !== 'TIME'"
|
||||
class="mx-current-year"
|
||||
@click="handleBtnYear">{{calendarYear}}</a>
|
||||
<a
|
||||
v-show="panel === 'TIME'"
|
||||
class="mx-time-header"
|
||||
@click="showPanelDate">{{timeHeader}}</a>
|
||||
</div>
|
||||
<div class="mx-calendar-content">
|
||||
<panel-date
|
||||
v-show="panel === 'DATE'"
|
||||
:value="value"
|
||||
: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"
|
||||
:first-year="firstYear"
|
||||
@select="selectYear" />
|
||||
<panel-month
|
||||
v-show="panel === 'MONTH'"
|
||||
:value="value"
|
||||
:calendar-year="calendarYear"
|
||||
@select="selectMonth" />
|
||||
<panel-time
|
||||
v-show="panel === 'TIME'"
|
||||
:minute-step="minuteStep"
|
||||
:time-picker-options="timePickerOptions"
|
||||
:value="value"
|
||||
:disabled-time="isDisabledTime"
|
||||
@select="selectTime" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isValidDate, isDateObejct } from '@/utils/index'
|
||||
import { t } from '@/locale/index'
|
||||
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 },
|
||||
props: {
|
||||
value: {
|
||||
default: null,
|
||||
validator: function (val) {
|
||||
return val === null || isValidDate(val)
|
||||
}
|
||||
},
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// below user set
|
||||
type: {
|
||||
type: String,
|
||||
default: 'date' // ['date', 'datetime']
|
||||
},
|
||||
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
|
||||
},
|
||||
timePickerOptions: {
|
||||
type: [Object, Function],
|
||||
default () {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const now = new Date()
|
||||
const calendarYear = now.getFullYear()
|
||||
const calendarMonth = now.getMonth()
|
||||
const firstYear = Math.floor(calendarYear / 10) * 10
|
||||
const months = t('months')
|
||||
return {
|
||||
panel: 'DATE',
|
||||
dates: [],
|
||||
months,
|
||||
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()
|
||||
}
|
||||
},
|
||||
timeHeader () {
|
||||
return this.value && new Date(this.value).toLocaleDateString()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: 'updateNow'
|
||||
},
|
||||
visible: {
|
||||
immediate: true,
|
||||
handler: 'init'
|
||||
},
|
||||
panel: {
|
||||
immediate: true,
|
||||
handler: 'handelPanelChange'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handelPanelChange (panel) {
|
||||
if (panel === 'YEAR') {
|
||||
this.firstYear = Math.floor(this.calendarYear / 10) * 10
|
||||
} else if (panel === 'TIME') {
|
||||
this.$nextTick(() => {
|
||||
[...this.$el.querySelectorAll('.mx-panel-time .mx-time-list')].forEach(el => {
|
||||
scrollIntoView(el, el.querySelector('.actived'))
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
init () {
|
||||
this.panel = 'DATE'
|
||||
this.updateNow(this.value)
|
||||
},
|
||||
// 根据value更新日历
|
||||
updateNow (value) {
|
||||
this.now = value ? new Date(value) : new Date()
|
||||
},
|
||||
isDisabledTime (date, startAt, endAt) {
|
||||
const time = new Date(date).getTime()
|
||||
const notBefore = this.notBefore && (time < new Date(this.notBefore))
|
||||
const notAfter = this.notAfter && (time > new Date(this.notAfter))
|
||||
startAt = startAt === undefined ? this.startAt : startAt
|
||||
startAt = startAt && (time < new Date(startAt))
|
||||
endAt = endAt === undefined ? this.endAt : endAt
|
||||
endAt = endAt && (time > new Date(endAt))
|
||||
return notBefore || notAfter || startAt || endAt
|
||||
},
|
||||
isDisabledDate (date, startAt, endAt) {
|
||||
const time = new Date(date).getTime()
|
||||
const notBefore = this.notBefore && (time < new Date(this.notBefore).setHours(0, 0, 0, 0))
|
||||
const notAfter = this.notAfter && (time > new Date(this.notAfter).setHours(0, 0, 0, 0))
|
||||
startAt = startAt === undefined ? this.startAt : startAt
|
||||
startAt = startAt && (time < new Date(startAt).setHours(0, 0, 0, 0))
|
||||
endAt = endAt === undefined ? this.endAt : endAt
|
||||
endAt = endAt && (time > new Date(endAt).setHours(0, 0, 0, 0))
|
||||
let disabledDays = false
|
||||
if (Array.isArray(this.disabledDays)) {
|
||||
disabledDays = this.disabledDays.some(v => new Date(v).setHours(0, 0, 0, 0) === time)
|
||||
} else if (typeof this.disabledDays === 'function') {
|
||||
disabledDays = this.disabledDays(new Date(date))
|
||||
}
|
||||
|
||||
return notBefore || notAfter || disabledDays || startAt || endAt
|
||||
},
|
||||
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.$emit('select-time', time)
|
||||
this.panel = 'TIME'
|
||||
return
|
||||
}
|
||||
this.$emit('select-date', date)
|
||||
},
|
||||
selectYear (year) {
|
||||
this.changeCalendarYear(year)
|
||||
this.showPanelMonth()
|
||||
},
|
||||
selectMonth (month) {
|
||||
this.changeCalendarMonth(month)
|
||||
this.showPanelDate()
|
||||
},
|
||||
selectTime (time) {
|
||||
this.$emit('select-time', time)
|
||||
},
|
||||
changeCalendarYear (year) {
|
||||
this.now = new Date(year, this.calendarMonth)
|
||||
},
|
||||
changeCalendarMonth (month) {
|
||||
this.now = new Date(this.calendarYear, month)
|
||||
},
|
||||
handleIconMonth (flag) {
|
||||
this.changeCalendarMonth(this.calendarMonth + flag)
|
||||
},
|
||||
handleIconYear (flag) {
|
||||
if (this.panel === 'YEAR') {
|
||||
this.changePanelYears(flag)
|
||||
} else {
|
||||
this.changeCalendarYear(this.calendarYear + flag)
|
||||
}
|
||||
},
|
||||
handleBtnYear () {
|
||||
if (this.panel === 'YEAR') {
|
||||
this.showPanelDate()
|
||||
} else {
|
||||
this.showPanelYear()
|
||||
}
|
||||
},
|
||||
handleBtnMonth () {
|
||||
if (this.panel === 'MONTH') {
|
||||
this.showPanelDate()
|
||||
} else {
|
||||
this.showPanelMonth()
|
||||
}
|
||||
},
|
||||
changePanelYears (flag) {
|
||||
this.firstYear = this.firstYear + flag * 10
|
||||
},
|
||||
showPanelDate () {
|
||||
this.panel = 'DATE'
|
||||
},
|
||||
showPanelYear () {
|
||||
this.panel = 'YEAR'
|
||||
},
|
||||
showPanelMonth () {
|
||||
this.panel = 'MONTH'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
export default {
|
||||
bind (el, binding, vnode) {
|
||||
el['@clickoutside'] = e => {
|
||||
if (
|
||||
!el.contains(e.target) &&
|
||||
binding.expression &&
|
||||
vnode.context[binding.expression]
|
||||
) {
|
||||
binding.value()
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', el['@clickoutside'], true)
|
||||
},
|
||||
unbind (el) {
|
||||
document.removeEventListener('click', el['@clickoutside'], true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import DatePicker from './index.vue'
|
||||
import './index.scss'
|
||||
|
||||
DatePicker.install = function (Vue) {
|
||||
Vue.component(DatePicker.name, DatePicker)
|
||||
}
|
||||
|
||||
export default DatePicker
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
$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-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;
|
||||
}
|
||||
}
|
||||
|
||||
.mx-input-append {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
.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 {
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: mix(#fff, $primary-color, 20%);
|
||||
}
|
||||
&:after {
|
||||
content: '|';
|
||||
margin: 0 10px;
|
||||
color: #48576a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.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 {
|
||||
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,.05);
|
||||
border-left: 1px solid rgba(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);
|
||||
}
|
||||
}
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<div
|
||||
class="mx-datepicker"
|
||||
:class="{
|
||||
'mx-datepicker-range': range,
|
||||
'disabled': disabled
|
||||
}"
|
||||
:style="{
|
||||
'width': computedWidth
|
||||
}"
|
||||
v-clickoutside="closePopup">
|
||||
<div class="mx-input-wrapper"
|
||||
@click="showPopup">
|
||||
<input
|
||||
:class="inputClass"
|
||||
ref="input"
|
||||
type="text"
|
||||
:name="inputName"
|
||||
:disabled="disabled"
|
||||
:readonly="!editable"
|
||||
:value="text"
|
||||
:placeholder="innerPlaceholder"
|
||||
@input="handleInput"
|
||||
@change="handleChange">
|
||||
<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">{{new Date().getDate()}}</text>
|
||||
</svg>
|
||||
</slot>
|
||||
</span>
|
||||
<span
|
||||
v-if="showClearIcon"
|
||||
class="mx-input-append mx-clear-wrapper"
|
||||
@click.stop="clearDate">
|
||||
<slot name="mx-clear-icon">
|
||||
<i class="mx-input-icon mx-clear-icon"></i>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mx-datepicker-popup"
|
||||
:style="position"
|
||||
v-show="popupVisible"
|
||||
ref="calendar">
|
||||
<slot name="header">
|
||||
<div class="mx-shortcuts-wrapper"
|
||||
v-if="range && innnerShortcuts.length">
|
||||
<span
|
||||
class="mx-shortcuts"
|
||||
v-for="(range, index) in innnerShortcuts"
|
||||
:key="index"
|
||||
@click="selectRange(range)">{{range.text}}</span>
|
||||
</div>
|
||||
</slot>
|
||||
<calendar-panel
|
||||
v-if="!range"
|
||||
v-bind="$attrs"
|
||||
: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"
|
||||
: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"
|
||||
:value="currentValue[1]"
|
||||
:start-at="currentValue[0]"
|
||||
:end-at="null"
|
||||
:visible="popupVisible"
|
||||
@select-date="selectEndDate"
|
||||
@select-time="selectEndTime"></calendar-panel>
|
||||
</div>
|
||||
<slot name="footer">
|
||||
<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 clickoutside from '@/directives/clickoutside'
|
||||
import { isValidDate, isValidRange, isDateObejct } from '@/utils/index'
|
||||
import { use, t } from '@/locale/index'
|
||||
import CalendarPanel from './calendar.vue'
|
||||
|
||||
export default {
|
||||
name: 'DatePicker',
|
||||
components: { CalendarPanel },
|
||||
directives: {
|
||||
clickoutside
|
||||
},
|
||||
props: {
|
||||
value: null,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
lang: {
|
||||
type: [String, Object],
|
||||
default: 'zh'
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD'
|
||||
},
|
||||
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'
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
innerPlaceholder () {
|
||||
if (typeof this.placeholder === 'string') {
|
||||
return this.placeholder
|
||||
}
|
||||
return this.range ? t('placeholder.dateRange') : t('placeholder.date')
|
||||
},
|
||||
text () {
|
||||
if (this.userInput !== null) {
|
||||
return this.userInput
|
||||
}
|
||||
if (!this.range) {
|
||||
return isValidDate(this.value) ? this.stringify(this.value) : ''
|
||||
}
|
||||
return isValidRange(this.value)
|
||||
? `${this.stringify(this.value[0])} ${this.rangeSeparator} ${this.stringify(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 ? isValidRange(this.value) : isValidDate(this.value))
|
||||
},
|
||||
innnerShortcuts () {
|
||||
if (Array.isArray(this.shortcuts)) {
|
||||
return this.shortcuts
|
||||
}
|
||||
if (this.shortcuts === false) {
|
||||
return []
|
||||
}
|
||||
const pickers = t('pickers')
|
||||
const arr = [
|
||||
{
|
||||
text: pickers[0],
|
||||
start: new Date(),
|
||||
end: new Date(Date.now() + 3600 * 1000 * 24 * 7)
|
||||
},
|
||||
{
|
||||
text: pickers[1],
|
||||
start: new Date(),
|
||||
end: new Date(Date.now() + 3600 * 1000 * 24 * 30)
|
||||
},
|
||||
{
|
||||
text: pickers[2],
|
||||
start: new Date(Date.now() - 3600 * 1000 * 24 * 7),
|
||||
end: new Date()
|
||||
},
|
||||
{
|
||||
text: pickers[3],
|
||||
start: new Date(Date.now() - 3600 * 1000 * 24 * 30),
|
||||
end: new Date()
|
||||
}
|
||||
]
|
||||
return arr
|
||||
}
|
||||
},
|
||||
created () {
|
||||
use(this.lang)
|
||||
},
|
||||
methods: {
|
||||
initCalendar () {
|
||||
this.handleValueChange(this.value)
|
||||
this.displayPopup()
|
||||
},
|
||||
stringify (date, format) {
|
||||
format = format || this.format
|
||||
return fecha.format(date, format)
|
||||
},
|
||||
parseDate (value, format) {
|
||||
try {
|
||||
format = format || this.format
|
||||
return fecha.parse(value, format)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
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) {
|
||||
this.currentValue = [ new Date(range.start), new Date(range.end) ]
|
||||
this.updateDate(true)
|
||||
},
|
||||
clearDate () {
|
||||
const date = this.range ? [null, null] : null
|
||||
this.currentValue = date
|
||||
this.updateDate(true)
|
||||
},
|
||||
confirmDate () {
|
||||
const valid = this.range ? isValidRange(this.currentValue) : isValidDate(this.currentValue)
|
||||
if (valid) {
|
||||
this.updateDate(true)
|
||||
}
|
||||
this.$emit('confirm', this.currentValue)
|
||||
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.$emit('input', this.currentValue)
|
||||
this.$emit('change', this.currentValue)
|
||||
return true
|
||||
},
|
||||
handleValueChange (value) {
|
||||
if (!this.range) {
|
||||
this.currentValue = isValidDate(value) ? new Date(value) : null
|
||||
} else {
|
||||
this.currentValue = isValidRange(value) ? [new Date(value[0]), new Date(value[1])] : [null, 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) {
|
||||
this.currentValue = time
|
||||
this.updateDate()
|
||||
},
|
||||
selectStartTime (time) {
|
||||
this.selectStartDate(time)
|
||||
},
|
||||
selectEndTime (time) {
|
||||
this.selectEndDate(time)
|
||||
},
|
||||
showPopup () {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
this.popupVisible = true
|
||||
},
|
||||
closePopup () {
|
||||
this.popupVisible = false
|
||||
},
|
||||
displayPopup () {
|
||||
const dw = document.documentElement.clientWidth
|
||||
const dh = document.documentElement.clientHeight
|
||||
const InputRect = this.$el.getBoundingClientRect()
|
||||
const PopupRect = this.$refs.calendar.getBoundingClientRect()
|
||||
this.position = {}
|
||||
if (
|
||||
dw - InputRect.left < PopupRect.width &&
|
||||
InputRect.right < PopupRect.width
|
||||
) {
|
||||
this.position.left = 1 - InputRect.left + 'px'
|
||||
} else if (InputRect.left + InputRect.width / 2 <= dw / 2) {
|
||||
this.position.left = 0
|
||||
} else {
|
||||
this.position.right = 0
|
||||
}
|
||||
if (
|
||||
InputRect.top <= PopupRect.height + 1 &&
|
||||
dh - InputRect.bottom <= PopupRect.height + 1
|
||||
) {
|
||||
this.position.top = dh - InputRect.top - PopupRect.height - 1 + 'px'
|
||||
} else if (InputRect.top + InputRect.height / 2 <= dh / 2) {
|
||||
this.position.top = '100%'
|
||||
} else {
|
||||
this.position.bottom = '100%'
|
||||
}
|
||||
},
|
||||
handleInput (event) {
|
||||
this.userInput = event.target.value
|
||||
},
|
||||
handleChange (event) {
|
||||
const value = event.target.value
|
||||
if (this.editable && this.userInput !== null) {
|
||||
const calendar = this.$children[0]
|
||||
const checkDate = calendar.type === 'date' ? calendar.isDisabledDate : calendar.isDisabledTime
|
||||
if (this.range) {
|
||||
const range = value.split(` ${this.rangeSeparator} `)
|
||||
if (range.length === 2) {
|
||||
const start = this.parseDate(range[0], this.format)
|
||||
const end = this.parseDate(range[1], this.format)
|
||||
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.parseDate(value, this.format)
|
||||
if (date && !checkDate(date, null, null)) {
|
||||
this.currentValue = date
|
||||
this.updateDate(true)
|
||||
this.closePopup()
|
||||
return
|
||||
}
|
||||
}
|
||||
this.$emit('input-error', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
import Languages from './languages'
|
||||
import { isPlainObject } from '@/utils/index'
|
||||
|
||||
let lang = 'zh'
|
||||
|
||||
export function use (target) {
|
||||
if (isPlainObject(target)) {
|
||||
lang = { ...Languages.en, ...target }
|
||||
} else {
|
||||
lang = Languages[target] || Languages.en
|
||||
}
|
||||
}
|
||||
|
||||
export function t (path) {
|
||||
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 ''
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
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': ['Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm', 'Dum'],
|
||||
'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,140 @@
|
||||
import { t } from '@/locale/index'
|
||||
|
||||
export default {
|
||||
name: 'panelDate',
|
||||
props: {
|
||||
value: null,
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
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 = 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 new Date(year, month, day).toLocaleDateString()
|
||||
}
|
||||
},
|
||||
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}
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { t } from '@/locale/index'
|
||||
|
||||
export default {
|
||||
name: 'panelMonth',
|
||||
props: {
|
||||
value: null,
|
||||
calendarYear: {
|
||||
default: new Date().getFullYear()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectMonth (month) {
|
||||
this.$emit('select', month)
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
let months = 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
|
||||
}}
|
||||
onClick={this.selectMonth.bind(this, i)}>
|
||||
{v}
|
||||
</span>
|
||||
})
|
||||
return <div class="mx-panel mx-panel-month">{months}</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { formatTime, parseTime } from '@/utils/index'
|
||||
|
||||
export default {
|
||||
name: 'panelTime',
|
||||
props: {
|
||||
timePickerOptions: {
|
||||
type: [Object, Function],
|
||||
default () {
|
||||
return null
|
||||
}
|
||||
},
|
||||
minuteStep: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: val => val >= 0 && val <= 60
|
||||
},
|
||||
value: null,
|
||||
disabledTime: Function
|
||||
},
|
||||
computed: {
|
||||
currentHours () {
|
||||
return new Date(this.value).getHours()
|
||||
},
|
||||
currentMinutes () {
|
||||
return new Date(this.value).getMinutes()
|
||||
},
|
||||
currentSeconds () {
|
||||
return new Date(this.value).getSeconds()
|
||||
}
|
||||
},
|
||||
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))
|
||||
},
|
||||
getTimeSelectOptions () {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
},
|
||||
render (h) {
|
||||
const date = new Date(this.value)
|
||||
const disabledTime = typeof this.disabledTime === 'function' && this.disabledTime
|
||||
|
||||
let pickers = this.getTimeSelectOptions()
|
||||
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.selectTime.bind(this, time)}>{picker.label}</li>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div class="mx-panel mx-panel-time">
|
||||
<ul class="mx-time-list">
|
||||
{pickers}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const hours = Array.apply(null, { length: 24 }).map((_, i) => {
|
||||
const time = new Date(date).setHours(i)
|
||||
return <li
|
||||
class={{
|
||||
'cell': true,
|
||||
'actived': i === this.currentHours,
|
||||
'disabled': disabledTime && disabledTime(time)
|
||||
}}
|
||||
onClick={this.selectTime.bind(this, time)}
|
||||
>{this.stringifyText(i)}</li>
|
||||
})
|
||||
|
||||
const step = this.minuteStep || 1
|
||||
const length = parseInt(60 / step)
|
||||
const minutes = Array.apply(null, { length }).map((_, i) => {
|
||||
const value = i * step
|
||||
const time = new Date(date).setMinutes(value)
|
||||
return <li
|
||||
class={{
|
||||
'cell': true,
|
||||
'actived': value === this.currentMinutes,
|
||||
'disabled': disabledTime && disabledTime(time)
|
||||
}}
|
||||
onClick={this.selectTime.bind(this, time)}
|
||||
>{this.stringifyText(value)}</li>
|
||||
})
|
||||
|
||||
const seconds = Array.apply(null, { length: 60 }).map((_, i) => {
|
||||
const time = new Date(date).setSeconds(i)
|
||||
return <li
|
||||
class={{
|
||||
'cell': true,
|
||||
'actived': i === this.currentSeconds,
|
||||
'disabled': disabledTime && disabledTime(time)
|
||||
}}
|
||||
onClick={this.selectTime.bind(this, time)}
|
||||
>{this.stringifyText(i)}</li>
|
||||
})
|
||||
|
||||
let times = [hours, minutes]
|
||||
if (this.minuteStep === 0) {
|
||||
times.push(seconds)
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
name: 'panelYear',
|
||||
props: {
|
||||
value: null,
|
||||
firstYear: Number
|
||||
},
|
||||
methods: {
|
||||
selectYear (year) {
|
||||
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
|
||||
}}
|
||||
onClick={this.selectYear.bind(this, year)}
|
||||
>{year}</span>
|
||||
})
|
||||
return <div class="mx-panel mx-panel-year">{years}</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
export function isPlainObject (obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]'
|
||||
}
|
||||
|
||||
export function isDateObejct (value) {
|
||||
return value instanceof Date
|
||||
}
|
||||
|
||||
export function isValidDate (date) {
|
||||
if (date === null || date === undefined) {
|
||||
return false
|
||||
}
|
||||
return !!new Date(date).getTime()
|
||||
}
|
||||
|
||||
export function isValidRange (date) {
|
||||
return (
|
||||
Array.isArray(date) &&
|
||||
date.length === 2 &&
|
||||
isValidDate(date[0]) &&
|
||||
isValidDate(date[1]) &&
|
||||
(new Date(date[1]).getTime() >= new Date(date[0]).getTime())
|
||||
)
|
||||
}
|
||||
|
||||
export function parseTime (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
|
||||
}
|
||||
|
||||
export function formatTime (time, type = '24') {
|
||||
let hours = time.hours
|
||||
hours = (type === '24') ? hours : (hours % 12 || 12)
|
||||
hours = hours < 10 ? '0' + hours : hours
|
||||
let minutes = time.minutes < 10 ? '0' + time.minutes : time.minutes
|
||||
let result = hours + ':' + minutes
|
||||
if (type === '12') {
|
||||
result += time.hours >= 12 ? ' pm' : ' am'
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export default function scrollIntoView (container, selected) {
|
||||
if (!selected) {
|
||||
container.scrollTop = 0
|
||||
return
|
||||
}
|
||||
|
||||
const offsetParents = []
|
||||
let pointer = selected.offsetParent
|
||||
while (pointer && container !== pointer && container.contains(pointer)) {
|
||||
offsetParents.push(pointer)
|
||||
pointer = pointer.offsetParent
|
||||
}
|
||||
const top = selected.offsetTop + offsetParents.reduce((prev, curr) => (prev + curr.offsetTop), 0)
|
||||
const bottom = top + selected.offsetHeight
|
||||
const viewRectTop = container.scrollTop
|
||||
const viewRectBottom = viewRectTop + container.clientHeight
|
||||
|
||||
if (top < viewRectTop) {
|
||||
container.scrollTop = top
|
||||
} else if (bottom > viewRectBottom) {
|
||||
container.scrollTop = bottom - container.clientHeight
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user