kui-vue
Version:
A high quality UI Toolkit built on Vue.js 2.0
487 lines (472 loc) • 17.2 kB
JSX
import {Button} from '../button'
import dayjs from 'dayjs'
import { t } from '../locale'
import {
ChevronDoubleBack, ChevronBack, ChevronForward,
ChevronDoubleForward
} from "kui-icons";
export default {
name: "Calendar",
props: {
v1: [String, Date, Number, Object],
v2: [String, Date, Number, Object],
value: [String, Date, Number, Object],
disabledDate: { type: Function, default: e => 0 },
disabledTime: { type: Function, default: e => { } },
mode: {
type: String, default: 'date', validator(value) {
return ["year", "month", "date", 'time', 'dateTime', "dateRange", 'dateTimeRange'].indexOf(value) >= 0;
}
},
format: String,
isRight: Boolean,
date: Object,
h2: Object,
pickerSize: String,
opened: Boolean,
},
data() {
return {
showYears: false,
showTimes: false,
currentValue: this.value || dayjs(),
};
},
watch: {
opened(v) {
if (v) {
this.initYearMonth()
} else {
if (this.mode != 'year' || this.mode != 'month') {
this.hideSubPicker()
}
}
},
v1() {
if (this.showTimes) {
this.initToCenter('timepicker', 1)
}
if (this.showYears) {
this.initToCenter('ympicker', 1)
}
},
v2() {
if (this.showTimes) {
this.initToCenter('timepicker', 1)
}
if (this.showYears) {
this.initToCenter('ympicker', 1)
}
},
value(c, o) {
if (c != o && c) {
this.currentValue = c
}
},
},
computed: {
isRange() {
return this.mode.indexOf('Range') >= 0
},
isTime() {
return this.mode == 'time'
},
withTime() {
return this.mode.indexOf('Time') >= 0 && !this.isTime
}
},
methods: {
hideSubPicker() {
this.showTimes = false
this.showYears = false
},
fix(v) {
return ('0' + v).slice(-2)
},
setShowTime() {
this.showYears = false
this.showTimes = !this.showTimes
if (this.showTimes) {
this.initToCenter('timepicker')
}
},
setShowYear() {
if (this.mode == 'year' || this.mode == 'month') {
return;
}
this.showTimes = false
this.showYears = !this.showYears
this.initYearMonth()
},
initToCenter(key, animate = false) {
this.$nextTick(() => {
let childs = (this.$refs[key] || {}).children || []
for (let m of childs) {
// console.log(m.children)
for (let n of m.children) {
// console.log(n)
if (n.className.indexOf('selected') > -1 || (!this.value && n.className.indexOf('this') > -1)) {
// console.log(n)
this.scrollToCenter({ target: n }, animate)
break
}
}
}
})
},
scrollToCenter(e, animate = true) {
// 计算 span 元素相对于 div 元素的偏移
const offset = e.target.offsetTop;
const ul = e.target.parentNode
// 计算滚动距离使 span 元素垂直居中
const scrollDistance = offset - parseFloat((ul.clientHeight / 2).toFixed(2)) + parseFloat((e.target.clientHeight / 2).toFixed(2)) - (this.pickerSize == 'small' ? 84 / 2 : 84 / 2);
// 滚动到计算出的位置
// console.log(scrollDistance)
if (animate) {
ul.scrollTo({ top: scrollDistance, behavior: 'smooth' });
} else {
ul.scrollTop = scrollDistance
}
// console.log(ul.scrollTop)
},
ok(e) {
this.$emit('close', true, e)
},
nextAndPrev(isNext, t) {
let date = this.date
date = isNext ? date.add(1, t) : date.subtract(1, t)
// if (this.isRange) {
this.$emit('np', date)
// }
},
// back() {
// this.showYears = false
// this.showTimes = false
// },
initYearMonth() {
this.initToCenter('ympicker')
},
getWeekDaysNode() {
let weeks = t('k.datePicker.weeks');
let weekNode = weeks.map(w => <span class="k-calendar-weekday" key={w}>{w}</span>)
const weeksNode = <div class="k-calendar-weekdays">{weekNode}</div>
return weeksNode
},
isSelectDay(sdate) {
let { v1, v2, isRight, isRange, value } = this
if (isRange) {
return (v1 && dayjs(v1).isSame(sdate, 'date')) || (v2 && dayjs(v2).isSame(sdate, 'date'))
} else {
return v1 && value && dayjs(value).isSame(sdate, 'date')
}
},
isDisabled(date) {
return (this.v1 && this.isRange && dayjs(date).isBefore(this.v1, 'date') && !this.v2)
|| (this.v2 && this.isRange && dayjs(date).isAfter(this.v2, 'date') && !this.v1)
|| this.disabledDate(date)
},
dayMouseHover(date) {
if (this.v1 && !this.v2) {
this.$emit('hd', dayjs(date))
}
},
dayMouseOut() {
this.$emit('hd', null)
},
isInRange(date) {
//range v1, h2
let rv1h2 = this.h2 && dayjs(date).isAfter(this.v1, 'date') && dayjs(date).isBefore(this.h2, 'date')
//range v1,v2
let rv1v2 = this.v2 && dayjs(date).isAfter(this.v1, 'date') && dayjs(date).isBefore(this.v2, 'date')
// console.log(rv1h2, rv1v2)
return this.isRange && this.v1 && (rv1h2 || rv1v2)
},
getDaysNode() {
// console.log(this.currentDay)
let year = this.date.$y
let month = this.date.$M
const weeks = [];
//这个月有多少天
const daysInMonth = new Date(year, month + 1, 0).getDate();
//这个月的第一天是周几
const firstDayOfWeek = new Date(year, month, 1).getDay(); // 0-based index (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
let currentWeek = [];
const calendar = [];
// 上个月
const time = new Date(year, month, 0);
const lastMonthDays = time.getDate(); //上个月有多少天
const startDay = lastMonthDays - firstDayOfWeek + 1; //不足7天补上
for (let i = 0; i < firstDayOfWeek; i++) {
let date = time.setDate(startDay + i)
let cls = {
'k-calendar-day-item': 1,
'k-calendar-day-out': 1,
'k-calendar-day-disabled': this.isDisabled(date),
// 'k-calendar-day-in': this.isInRange(date)
}
currentWeek.push(<span class={cls} onMouseleave={this.dayMouseOut} onMouseenter={() => this.dayMouseHover(date)} onClick={e => this.setDay(e, date)}>{startDay + i}</span>);
// currentWeek.push((startDay + i).toString().padStart(2, '0'));
}
time.setMonth(time.getMonth() + 1, 1)
// 这个月的天数
for (let day = 1; day <= daysInMonth; day++) {
let date = time.setDate(day);;
let cls = {
'k-calendar-day-item': 1,
'k-calendar-day-this': dayjs(date).isSame(dayjs(), 'day'),
'k-calendar-day-selected': this.isSelectDay(date),
'k-calendar-day-disabled': this.isDisabled(date),
'k-calendar-day-in': this.isInRange(date)
}
currentWeek.push(<span class={cls} onMouseleave={this.dayMouseOut} onMouseenter={() => this.dayMouseHover(date)} onClick={e => this.setDay(e, date)}>{day}</span>);
// currentWeek.push(day.toString().padStart(2, '0'));
// Start a new week on Sunday (index 0)
if (currentWeek.length == 7) {
calendar.push([...currentWeek]);
currentWeek = [];
}
}
// 下个月要补上
time.setMonth(time.getMonth() + 1, 1)
let nextMonthDay = 1;
let lostDay = 42 - firstDayOfWeek - daysInMonth
for (let i = 1; i <= lostDay; i++) {
let date = time.setDate(nextMonthDay)
let cls = {
'k-calendar-day-item': 1,
'k-calendar-day-out': 1,
'k-calendar-day-disabled': this.isDisabled(date),
// 'k-calendar-day-in': this.isInRange(date)
}
currentWeek.push(<span class={cls} onMouseleave={this.dayMouseOut} onMouseenter={() => this.dayMouseHover(date)} onClick={e => this.setDay(e, date)}>{nextMonthDay}</span>);
nextMonthDay++
if (currentWeek.length == 7) {
calendar.push([...currentWeek]);
currentWeek = [];
}
// currentWeek.push((nextMonthDay++).toString().padStart(2, '0'));
}
if (currentWeek.length) {
calendar.push([...currentWeek]);
}
for (const week of calendar) {
weeks.push(<span class={'k-calendar-week-item'}>{week}</span>)
}
return weeks
},
getYearsNode() {
let thisYear = new Date().getFullYear()
let years = []
let min = thisYear - 100, max = thisYear + 100;
let { $y } = this.date || {}
for (let j = min; j <= max; j++) {
let cls = {
'k-calendar-year-item': 1,
'k-calendar-year-this': thisYear == j,
'k-calendar-item-selected': this.value && $y == j,
'k-calendar-year-disabled': this.isDisabled(new Date().setFullYear(j))
}
years.push(<span class={cls} onClick={(e) => this.setYear(e, j)}>{j}</span>)
}
let node = <div class="k-calendar-years" ref="yearspicker">{years}</div>
return node
},
getMonthsNode() {
let months = t('k.datePicker.months');
let month = []
let { $M } = this.date || {}
let _month = new Date().getMonth()
for (let i = 0; i < 12; i++) {
let cls = {
'k-calendar-month-item': 1,
'k-calendar-month-this': _month == i,
'k-calendar-item-selected': this.value && $M == i,
'k-calendar-year-disabled': this.disabledDate(new Date().setMonth(i))
}
month.push(<span class={cls} onClick={(e) => { this.setMonth(e, i) }}>{months[i]}</span >)
}
const monthNode = <div class="k-calendar-months" ref="monthspicker">{month}</div>
return monthNode
},
fix(v) {
return ('0' + v).slice(-2)
},
isDisabledTime(types, value) {
let fdtime = this.disabledTime()
if (fdtime && types in fdtime && typeof fdtime[types] === 'function') {
let [a, b] = fdtime[types]() || []
if (a && b) {
return value >= a && value <= b
}
return false
}
return false
},
getTimesNode() {
let { $H, $m, $s } = this.date
// console.log($H, $m, $s)
let hours = [], minus = [], sec = [];
//hour
for (let j = 0; j < 24; j++) {
let cls = {
'k-calendar-time-item': 1,
'k-calendar-time-this': $H == j,
'k-calendar-item-selected': this.value && $H == j,
'k-calendar-time-disabled': this.isDisabledTime('disabledHours', j),
}
hours.push(<span class={cls} onClick={(e) => this.setTime(e, j, 'hour')}>{this.fix(j)}</span>)
}
//minus
for (let m = 0; m < 60; m++) {
let cls = {
'k-calendar-time-item': 1,
'k-calendar-time-this': $m == m,
'k-calendar-item-selected': this.value && $m == m,
'k-calendar-time-disabled': this.isDisabledTime('disabledMinutes', m),
}
minus.push(<span class={cls} onClick={(e) => this.setTime(e, m, 'minute')}>{this.fix(m)}</span>)
}
//sec
for (let s = 0; s < 60; s++) {
let cls = {
'k-calendar-time-item': 1,
'k-calendar-time-this': $s == s,
'k-calendar-item-selected': this.value && $s == s,
'k-calendar-time-disabled': this.isDisabledTime('disabledSeconds', s),
}
sec.push(<span class={cls} onClick={(e) => this.setTime(e, s, 'second')}>{this.fix(s)}</span>)
}
hours = <div class='k-calendar-time-picker-select'>{hours}</div>
minus = <div class='k-calendar-time-picker-select'>{minus}</div>
sec = <div class='k-calendar-time-picker-select'>{sec}</div>
let timeNode = <div class="k-calendar-time-picker" ref="timepicker">{hours}{minus}{sec}</div>
return timeNode
},
setMonth(e, month) {
e.stopPropagation();
if (e.target.className.indexOf('disabled') >= 0) {
return
}
// let date = this.date.month(month)
this.$emit('input', month, 'month')
this.$emit('close', this.mode == 'month')
if (this.mode != 'month' && this.mode != 'year') {
this.showYears = false
}
this.scrollToCenter(e)
},
setYear(e, year) {
e.stopPropagation();
if (e.target.className.indexOf('disabled') >= 0) {
return
}
// let date = this.date.year(year)
this.$emit('input', year, 'year')
this.$emit('close', this.mode == 'year')
this.scrollToCenter(e)
},
setDay(e, day) {
e.stopPropagation();
if (e.target.className.indexOf('disabled') >= 0) {
return
}
let date = this.date
let { $y, $M, $D } = dayjs(day)
// console.log($y, $M, $D)
// return
date = date.year($y).month($M).date($D)
// console.log(date)
// return
this.$emit('input', date)
this.$emit('close', !this.withTime && !this.isRange)
},
setTime(e, value, type) {
e.stopPropagation();
if (e.target.className.indexOf('disabled') >= 0) {
return;
}
// let date = this.date[type](value)
this.$emit('input', value, type)
this.scrollToCenter(e)
},
},
mounted() {
if (this.mode == 'month' || this.mode == 'year') {
this.initYearMonth()
}
if (this.isTime) {
this.initToCenter('timepicker')
}
},
render() {
let { showYears, pickerSize, showTimes, isTime, mode, isRange, withTime } = this
let months = t('k.datePicker.months');
showYears = showYears || mode == 'year' || mode == 'month'
let { $y, $M, $D } = this.date || dayjs()
//header
let headNodeChilds = []
if (!isTime) {
if (!showTimes && !showYears) {
headNodeChilds.push(<Button icon={ChevronDoubleBack} size={pickerSize} theme="normal" class="k-calendar-prev-year-btn" onClick={() => this.nextAndPrev(0, 'year')}></Button>)
headNodeChilds.push(<Button icon={ChevronBack} size={pickerSize} theme="normal" class="k-calendar-prev-month-btn" onClick={() => this.nextAndPrev(0, 'month')}></Button>)
}
// else if (mode != 'year' && mode != 'month') {
// headNode.push(<Button class="k-calendar-back" size={pickerSize} icon={ChevronBack} theme="normal" onClick={this.back}>{t('k.datePicker.back')} </Button>)
// }
headNodeChilds.push(<Button class="k-calendar-year-select" size={pickerSize} theme="normal" onClick={this.setShowYear}>{$y}{t('k.datePicker.year')} {mode != 'year' ? months[$M] : ''} {(!showYears && showTimes) ? $D : ''}</Button>)
if (!showTimes && !showYears) {
headNodeChilds.push(<Button theme="normal" size={pickerSize} icon={ChevronForward} class="k-calendar-next-month-btn" onClick={() => this.nextAndPrev(1, 'month')}></Button>)
headNodeChilds.push(<Button icon={ChevronDoubleForward} size={pickerSize} theme="normal" class="k-calendar-next-year-btn" onClick={() => this.nextAndPrev(1, 'year')}></Button>)
}
} else {
headNodeChilds.push(<div class="k-calendar-time-label">{t('k.datePicker.selectTime')}</div>)
}
const headNode = <div class="k-calendar-head">{headNodeChilds}</div>
//days and week body
let dayWeekBodyNode = null
if (!showYears && !showTimes && !isTime) {
const weeksNode = this.getWeekDaysNode()
const daysNode = this.getDaysNode()
dayWeekBodyNode = <div class="k-calendar-body">{weeksNode}{daysNode}</div>
}
// console.log(showYears)
let yearsMonthNode = null
if (showYears) {
let childs = []
const yearNode = this.getYearsNode()
childs.push(yearNode)
if (mode != 'year') {
const monthNode = this.getMonthsNode()
childs.push(monthNode)
}
yearsMonthNode = <div class="k-calendar-yearmonth-picker" ref="ympicker">{childs}</div>
}
let timeNode = null
if (showTimes || isTime) {
timeNode = this.getTimesNode()
}
//footer
let footerNode = []
if (withTime) {
// footerNode.push(<Button theme="normal" size={pickerSize} disabled={disabled} class="k-calendar-btn-today" onClick={this.setToday}>{t('k.datePicker.now')}</Button>)
footerNode.push(<Button theme="normal" size={pickerSize} onClick={this.setShowTime}>{showTimes ? t('k.datePicker.selectDate') : t('k.datePicker.selectTime')}</Button>)
if (!isRange || this.isRight) footerNode.push(<Button type="primary" class="k-calendar-btn-ok" size={pickerSize} onClick={e => this.ok(e)}>{t('k.datePicker.ok')}</Button>)
}
footerNode = footerNode.length || (isRange && withTime) ? <div class="k-calendar-footer">{footerNode}</div> : null
let rootCls = ['k-calendar', {
'k-calendar-small': pickerSize == 'small',
'k-calendar-only-year': mode == 'year',
'k-calendar-only-time': isTime,
'k-calendar-yearmonth': mode == 'month'
}]
return (
<div class={rootCls}>
{headNode}
{dayWeekBodyNode}
{yearsMonthNode}
{timeNode}
{footerNode}
</div>
)
}
};