lu2
Version:
Simple and flexible UI component library based on native HTML and JavaScript
1,193 lines (1,068 loc) • 115 kB
JavaScript
/**
* @DateTime.js
* @author zhangxinxu
* @version
* @created: 15-07-03
* @edited: 19-11-12
* @edited: 20-07-27 edit by wanghao
*/
import './Follow.js';
/**
* 日期,时间选择器
* 基于HTML5时间类输入框
* @example is="ui-datetime"
* @trigger 触发的元素,可以是文本框也可以是文本框容器(父级)
* @options 可选参数
*/
const DateTime = (() => {
// 样式类名统一处理
const CL = {
toString: () => 'ui-datetime'
};
['date', 'range', 'day', 'year', 'month', 'hour', 'minute', 'time', 'datetime'].forEach((key) => {
CL[key] = (...args) => ['ui', key, ...args].join('-');
});
const SELECTED = 'selected';
const ACTIVE = 'active';
const regDate = /-|\//g;
// 拓展方法之字符串变时间对象
String.prototype.toDate = function () {
let year, month, day;
const arrDate = this.replace(/年|月|日/g, '-').split(regDate);
// 字符串变数值
year = arrDate[0] * 1;
month = arrDate[1] || 1;
day = arrDate[2] || 1;
// 年份需要是数值字符串
if (!year) {
return new Date();
}
return new Date(year, month - 1, day);
};
String.prototype.toTime = function () {
let arrTime = this.trim().split(':').map((hm, index) => {
if (!hm || /\D/.test(hm)) {
return '';
}
if (hm < 0) {
return '00';
} else if (index === 0) {
if (hm > 23) {
hm = '23';
}
} else if (hm > 59) {
hm = '59';
}
return hm.padStart(2, '0');
}).filter(_ => _).slice(0, 3);
// 至少有时分
if (arrTime.length == 1) {
arrTime.push('00');
}
return arrTime;
};
// 日期对象变成年月日数组
Date.prototype.toArray = function () {
let year = this.getFullYear();
let month = this.getMonth() + 1;
let date = this.getDate();
if (month < 10) {
month = `0${month}`;
}
if (date < 10) {
date = `0${date}`;
}
return [year, month, date];
};
class Component extends HTMLInputElement {
constructor () {
super();
}
minMaxConvert (value) {
// 直接设置为数值
if (typeof value == 'number' && Number.isInteger(value)) {
// 认为是时间戳
if (value > 10000000) {
value = new Date(value);
} else if (value < 9999) {
// 认为是年份
value = String(value);
}
}
// 日期和时间
let arrDate = [];
let arrHourMin = [];
// value可以是Date对象
if (value.toArray) {
arrDate = value.toArray();
// 此时的对应的时间值
arrHourMin = (value.getHours() + ':' + value.getMinutes()).toTime();
} else if (value && typeof value == 'string') {
const arrDateTime = value.split(/\s+|T/);
arrDate = arrDateTime[0].toDate().toArray();
if (arrDateTime[1] && arrDateTime[1].includes(':')) {
arrHourMin = arrDateTime[1].toTime();
}
}
let strType = this.getAttribute('type') || 'date';
// 赋值
if (strType == 'date' || strType == 'date-range') {
value = arrDate.join('-');
} else if (strType == 'year') {
value = arrDate[0];
} else if (strType == 'month' || strType == 'month-range') {
value = arrDate.slice(0, 2).join('-');
} else if (/^datetime/.test(strType)) {
value = arrDate.join('-') + ' ' + arrHourMin.join(':');
} else {
if (value.toArray) {
// 其他日期类型转换成 时:分 模式
value = value.getHours() + ':' + value.getMinutes();
}
// 补0的处理
let arrHourMin = value.toTime();
// 时间类型
if (!arrHourMin[0]) {
return '';
}
value = arrHourMin.join(':');
}
return value;
}
get min () {
let strAttrMin = this.getAttribute('min') || '';
if (strAttrMin) {
return this.minMaxConvert(strAttrMin).toString();
}
return strAttrMin;
}
set min (value) {
if (!value) {
this.removeAttribute('min');
return;
}
this.setAttribute('min', this.minMaxConvert(value));
}
get max () {
let strAttrMax = this.getAttribute('max') || '';
if (strAttrMax) {
return this.minMaxConvert(strAttrMax).toString();
}
return strAttrMax;
}
set max (value) {
if (!value) {
this.removeAttribute('max');
return;
}
this.setAttribute('max', this.minMaxConvert(value));
}
get step () {
let strStep = this.getAttribute('step');
let strType = this.params.type;
let numStep = Number(strStep);
if (strStep && /^\d+$/.test(strStep)) {
if (strType == 'time') {
if (strStep > 60) {
if (strStep % 60 != 0 || strStep / 60 > 30) {
numStep = 1;
}
} else if (numStep > 30) {
numStep = 1;
}
} else if (strType == 'hour') {
if (numStep > 12) {
numStep = 1;
}
} else if (strType == 'minute' && numStep > 30) {
numStep = 1;
}
return numStep;
}
return '';
}
set step (value) {
if (!value) {
this.removeAttribute('step');
return;
}
this.setAttribute('step', value);
}
/**
* 事件
* @return {[type]} [description]
*/
events () {
// 具体元素们
const eleContainer = this.element.target;
// 点击容器的事件处理
eleContainer.addEventListener('click', (event) => {
// IE可能是文件节点
if (event.target.nodeType != 1 || !event.target.closest) {
return;
}
const eleClicked = event.target.closest('a, button');
if (!eleClicked) {
return;
}
// 各个分支中可能会用到的变量
let numYear = 0;
let numMonth = 0;
// var numDate = 0;
let numHour = 0;
let numDay = 0;
// 日期范围
let arrRange = [];
// 根据选中状态决定新的状态
let dataRange;
// 按钮类型
let strTypeButton = '';
// 选择的日期类型
const strType = eleContainer.dataset.type;
// 各种事件
switch (strType) {
case 'date': {
// 日期选择主体
// 1. 前后月份选择
if (/prev|next/.test(eleClicked.className)) {
numMonth = eleClicked.dataset.month;
// 设置选择月份
// 这样可以获得目标月份对应的日期数据
// 就是下面this.getMonthDay做的事情
this[SELECTED][1] = numMonth * 1;
// 日期和月份要匹配,例如,不能出现4月31日
// arrMonthDay是一个数组,当前年份12个月每月最大的天数
const arrMonthDay = this.getMonthDay(this[SELECTED]);
// 切分月份
// 日期正常是不会变化的
// 但是类似31号这样的日子不是每个月都有的
// 因此,需要边界判断
// 下面这段逻辑就是做这个事情的
// 1. 当前月份最大多少天
const numDayMax = (() => {
if (numMonth - 1 < 0) {
return arrMonthDay[11];
} else if (numMonth > arrMonthDay.length) {
return arrMonthDay[0];
}
return arrMonthDay[numMonth - 1];
})();
// 2. 之前选择的天数
numDay = this[SELECTED][2];
// 之前记住的日期
const numDayOverflow = eleContainer.dataDayOverflow;
// 例如,我们超出日期是31日,如果月份可以满足31日,使用31日
if (numDayOverflow) {
this[SELECTED][2] = Math.min(numDayOverflow, numDayMax);
} else if (this[SELECTED][2] > numDayMax) {
this[SELECTED][2] = numDayMax;
// 这里是对体验的升级,
// 虽然下月份变成了30号,但是再回来时候,原来的31号变成了30号
// 不完美,我们需要处理下
// 通过一个变量记住,点击item项的时候,移除
// 且只在第一次的时候记住
// 因为28,29,30,31都可能出现,每次记忆会混乱
eleContainer.dataDayOverflow = numDay;
}
// 更新选择的月日数据
this[SELECTED] = this[SELECTED].join('-').toDate().toArray();
// 刷新
this.date();
// 如果在时间范围内
if (eleContainer.querySelector(`.${SELECTED}[href]`)) {
this.setValue();
}
} else if (/item/.test(eleClicked.className)) {
// 选择某日期啦
numDay = eleClicked.innerHTML;
// 含有非数字,认为是今天
if (/\D/.test(numDay)) {
// 今天
this[SELECTED] = new Date().toArray();
} else {
if (numDay < 10) {
numDay = `0${numDay}`;
}
// 修改全局
this[SELECTED][2] = numDay;
}
// 赋值
this.setValue();
// 隐藏
this.hide();
delete eleContainer.dataDayOverflow;
} else if (eleClicked.dataset.type == 'month') {
// 切换到年份选择
this.month();
}
break;
}
case 'date-range': {
// 区域选择
// 1. 前后月份选择
if (/prev|next/.test(eleClicked.className)) {
numMonth = eleClicked.dataset.month * 1;
arrRange = eleContainer.dataDate || this[SELECTED][0];
// 跟其他面板不同,这里只刷新,点击确定再赋值
eleContainer.dataDate = new Date(arrRange[0], numMonth - 1, 1).toArray();
// 之前arrRange[2]存在跨多月风险,尤其31号的日子
// 刷新
this['date-range']();
} else if (/item/.test(eleClicked.className)) {
// 选择某日期
// 获得选中的年月日
numYear = eleClicked.dataset.year;
numMonth = eleClicked.dataset.month;
numDay = eleClicked.innerHTML;
// 位数不足补全
if (numMonth < 10) {
numMonth = `0${numMonth}`;
}
if (numDay < 10) {
numDay = `0${numDay}`;
}
// 根据选中状态决定新的状态
dataRange = this[SELECTED];
if (dataRange[0].join() == dataRange[1].join()) {
// 如果之前前后日期一样,说明只选中了一个日期
// 根据前后顺序改变其中一个日期
if (numYear + numMonth + numDay > dataRange[0].join('')) {
// 新时间靠后
dataRange[1] = [numYear, numMonth, numDay];
} else {
dataRange[0] = [numYear, numMonth, numDay];
}
} else {
// 如果前后时间不一样,说明现在有范围
// 则取消范围,变成单选
dataRange = [[numYear, numMonth, numDay], [numYear, numMonth, numDay]];
}
this[SELECTED] = dataRange;
this['date-range']();
} else if (/button/.test(eleClicked.className)) {
strTypeButton = eleClicked.dataset.type;
if (strTypeButton == 'primary') {
// 点击确定按钮
// 赋值
this.setValue();
// 修改存储值
this.dataRangeSelected = this[SELECTED];
// 关闭浮层
this.hide();
} else if (strTypeButton == 'normal') {
// 重置选中值
if (this.dataRangeSelected) {
this[SELECTED] = this.dataRangeSelected;
}
// 关闭浮层
this.hide();
}
}
break;
}
case 'month-range': {
// 区域选择
// 1. 前后年份选择
if (/prev|next/.test(eleClicked.className)) {
numYear = eleClicked.dataset.year * 1;
arrRange = eleContainer.dataDate || this[SELECTED][0];
// 跟其他面板不同,这里只刷新,点击确定再赋值
eleContainer.dataDate = new Date(numYear, arrRange[1], 1).toArray();
// 刷新
this['month-range']();
} else if (/item/.test(eleClicked.className)) {
// 选择某日期
// 获得选中的年月日
numYear = eleClicked.dataset.year;
numMonth = eleClicked.dataset.value;
numDay = '01';
// 根据选中状态决定新的状态
dataRange = this[SELECTED];
if (dataRange[0].join() == dataRange[1].join()) {
// 如果之前前后日期一样,说明只选中了一个日期
// 根据前后顺序改变其中一个日期
if (numYear + numMonth + numDay > dataRange[0].join('')) {
// 新时间靠后
dataRange[1] = [numYear, numMonth, numDay];
} else {
dataRange[0] = [numYear, numMonth, numDay];
}
} else {
// 如果前后时间不一样,说明现在有范围
// 则取消范围,变成单选
dataRange = [[numYear, numMonth, numDay], [numYear, numMonth, numDay]];
}
this[SELECTED] = dataRange;
this['month-range']();
} else if (/button/.test(eleClicked.className)) {
strTypeButton = eleClicked.dataset.type;
if (strTypeButton == 'primary') {
// 点击确定按钮
// 赋值
this.setValue();
// 修改存储值
this.dataRangeSelected = this[SELECTED];
// 关闭浮层
this.hide();
} else if (strTypeButton == 'normal') {
// 重置选中值
if (this.dataRangeSelected) {
this[SELECTED] = this.dataRangeSelected;
}
// 关闭浮层
this.hide();
}
}
break;
}
case 'month': {
// 选择月份,可能从年份,也可能从日期过来
// 1. 前后年份
if (/prev|next/.test(eleClicked.className)) {
numYear = eleClicked.dataset.year;
// 修改当前选中的年份数
this[SELECTED][0] = numYear * 1;
// 刷新
this.month();
// 文本框赋值
// 如果在区域内状态
if (eleContainer.querySelector(`.${SELECTED}[href]`)) {
this.setValue();
}
} else if (/item/.test(eleClicked.className)) {
// value实际上是月份两位数值
const value = eleClicked.dataset.value;
if (value) {
this[SELECTED][1] = value;
} else {
// 今月,只改变年月为今年和今月
const arrToday = new Date().toArray();
this[SELECTED][0] = arrToday[0];
this[SELECTED][1] = arrToday[1];
}
// 日是否匹配月的合法性判断
var day = this[SELECTED][2];
var arrMonthDay = this.getMonthDay(this[SELECTED]);
// 如果日超出当月限制,使用本月最后一天作为日期
if (day > arrMonthDay[this[SELECTED][1] - 1]) {
this[SELECTED][2] = arrMonthDay[this[SELECTED][1] - 1];
}
// 赋值
this.setValue();
// 根据是否是月份输入框,决定是面板切换,还是关闭
if (this.params.type == 'month') {
// 隐藏
this.hide();
} else {
this.date();
}
} else if (eleClicked.dataset.type == 'year') {
// 切换到年份选择
this.year();
}
break;
}
case 'year': {
// 选择年份,可能从月份过来,也可能直接打开
// 1. 前后12年翻页
if (/prev|next/.test(eleClicked.className)) {
numYear = eleClicked.dataset.year;
// 修改当前选中的年份数
this[SELECTED][0] = numYear * 1;
// 刷新
this.year();
// 文本框赋值
// 如果在区域内状态
if (eleContainer.querySelector(`.${SELECTED}[href]`)) {
this.setValue();
}
} else if (/item/.test(eleClicked.className)) {
if (eleClicked.innerHTML == '今年') {
this[SELECTED][0] = new Date().getFullYear();
} else {
this[SELECTED][0] = eleClicked.innerHTML * 1;
}
// 赋值
this.setValue();
// 如果是年份选择输入框
if (this.params.type == 'year') {
// 隐藏
this.hide();
} else {
// 回到月份面板
this.month();
}
}
break;
}
case 'time': {
if (eleClicked.tagName == 'BUTTON' && eleClicked.classList.contains(SELECTED) == false) {
let strTypeButton = eleClicked.parentElement.dataset.type;
let numIndexButton = eleClicked.dataset.index;
if (strTypeButton == 'ampm') {
if (numIndexButton == '0') {
this[SELECTED][0] -= 12;
} else {
this[SELECTED][0] = Number(this[SELECTED][0]) + 12;
}
this[SELECTED][0] = String(this[SELECTED][0]).padStart(2, '0');
} else if (strTypeButton == 'hour') {
this[SELECTED][0] = numIndexButton.padStart(2, '0');
} else if (strTypeButton == 'minute') {
this[SELECTED][1] = numIndexButton.padStart(2, '0');
} else if (strTypeButton == 'second') {
this[SELECTED][2] = numIndexButton.padStart(2, '0');
}
this.setValue();
this.time();
}
break;
}
case 'minute': {
// 选择分钟,可以是minute类型,或者time类型, 但不包括hour类型
// 1. 前后翻页
if (/prev|next/.test(eleClicked.className)) {
numHour = eleClicked.getAttribute('data-hour');
if (numHour.length == 1) {
numHour = `0${numHour}`;
}
// 修改当前选中的小时数
this[SELECTED][0] = numHour;
// 刷新
this.minute();
// 文本框赋值
// 如果在区域内状态
if (eleContainer.querySelector(`.${SELECTED}[href]`)) {
this.setValue();
}
} else if (/item/.test(eleClicked.className)) {
// 确定选择时间
this[SELECTED] = eleClicked.innerHTML.split(':');
this.setValue();
this.hide();
} else if (eleClicked.dataset.type == 'hour') {
// 切换到小时选择
this.hour();
}
break;
}
case 'hour': {
if (/item/.test(eleClicked.className)) {
// 修改选中的小时
this[SELECTED][0] = eleClicked.innerHTML.split(':')[0];
// 赋值
this.setValue();
// 如果是从分钟模式切换过来,则切换回去,否则,直接隐藏
if (this.params.type == 'hour') {
this.hide();
} else {
this.minute();
}
}
break;
}
case 'datetime': {
// 日期时间选择
const arrSelected = this[SELECTED];
const eleContainerDate = eleClicked.closest('[data-type="date"]');
const eleContainerMonth = eleClicked.closest('[data-type="month"]');
const eleContainerYear = eleClicked.closest('[data-type="year"]');
const eleContainerTime = eleContainer.querySelector('[data-type="time"]');
// 日期选择主体
if (eleContainerDate) {
// 日期数组项
const arrDate = arrSelected[0];
// 1. 前后月份选择
if (/prev|next/.test(eleClicked.className)) {
numMonth = eleClicked.dataset.month;
// 设置选择月份
// 这样可以获得目标月份对应的日期数据
// 就是下面this.getMonthDay做的事情
arrDate[1] = numMonth * 1;
// 日期和月份要匹配,例如,不能出现4月31日
// arrMonthDay是一个数组,当前年份12个月每月最大的天数
const arrMonthDay = this.getMonthDay(this[SELECTED]);
// 切分月份
// 日期正常是不会变化的
// 但是类似31号这样的日子不是每个月都有的
// 因此,需要边界判断
// 下面这段逻辑就是做这个事情的
// 1. 当前月份最大多少天
const numDayMax = (() => {
if (numMonth - 1 < 0) {
return arrMonthDay[11];
} else if (numMonth > arrMonthDay.length) {
return arrMonthDay[0];
}
return arrMonthDay[numMonth - 1];
})();
// 2. 之前选择的天数
numDay = arrDate[2];
// 之前记住的日期
const numDayOverflow = eleContainer.dataDayOverflow;
// 例如,我们超出日期是31日,如果月份可以满足31日,使用31日
if (numDayOverflow) {
arrDate[2] = Math.min(numDayOverflow, numDayMax);
} else if (arrDate[2] > numDayMax) {
arrDate[2] = numDayMax;
// 这里是对体验的升级,
// 虽然下月份变成了30号,但是再回来时候,原来的31号变成了30号
// 不完美,我们需要处理下
// 通过一个变量记住,点击item项的时候,移除
// 且只在第一次的时候记住
// 因为28,29,30,31都可能出现,每次记忆会混乱
eleContainer.dataDayOverflow = numDay;
}
// 更新选择的月日数据
this[SELECTED][0] = arrDate.join('-').toDate().toArray();
// 刷新
this.date(eleContainerDate);
// 时间也刷新,因为可能禁用态变化
this.time(eleContainerTime);
// 如果在时间范围内
if (eleContainer.querySelector(`[data-type="date"] .${SELECTED}[href]`)) {
this.setValue();
}
} else if (/item/.test(eleClicked.className)) {
// 选择某日期啦
numDay = eleClicked.innerHTML;
// 含有非数字,认为是今天
if (/\D/.test(numDay)) {
// 今天
this[SELECTED][0] = new Date().toArray();
} else {
if (numDay < 10) {
numDay = `0${numDay}`;
}
// 修改全局
this[SELECTED][0][2] = numDay;
}
// 赋值和选中态更新
this.setValue();
this.date(eleContainerDate);
// 时间也刷新,因为可能禁用态变化
this.time(eleContainerTime);
delete eleContainer.dataDayOverflow;
} else if (eleClicked.dataset.type == 'month') {
// 切换到年份选择
this.month(eleContainerDate);
}
} else if (eleContainerMonth) {
// 月份切换
// 选择月份,可能从年份,也可能从日期过来
// 1. 前后年份
if (/prev|next/.test(eleClicked.className)) {
numYear = eleClicked.dataset.year;
// 修改当前选中的年份数
this[SELECTED][0][0] = numYear * 1;
// 刷新
this.month(eleContainerMonth);
// 时间也刷新,因为可能禁用态变化
this.time(eleContainerTime);
// 文本框赋值
// 如果在区域内状态
if (eleContainerMonth.querySelector(`.${SELECTED}[href]`)) {
this.setValue();
}
} else if (/item/.test(eleClicked.className)) {
// value实际上是月份两位数值
const value = eleClicked.dataset.value;
if (value) {
this[SELECTED][0][1] = value;
} else {
// 今月,只改变年月为今年和今月
const arrToday = new Date().toArray();
this[SELECTED][0][0] = arrToday[0];
this[SELECTED][0][1] = arrToday[1];
}
// 赋值
this.setValue();
this.date(eleContainerMonth);
// 时间也刷新,因为可能禁用态变化
this.time(eleContainerTime);
} else if (eleClicked.dataset.type == 'year') {
// 切换到年份选择
this.year(eleContainerMonth);
}
} else if (eleContainerYear) {
// 选择年份,从月份过来
if (/prev|next/.test(eleClicked.className)) {
numYear = eleClicked.dataset.year;
// 修改当前选中的年份数
this[SELECTED][0][0] = numYear * 1;
// 刷新
this.year(eleContainerYear);
// 时间也刷新,因为可能禁用态变化
this.time(eleContainerTime);
// 文本框赋值
// 如果在区域内状态
if (eleContainerYear.querySelector(`.${SELECTED}[href]`)) {
this.setValue();
}
} else if (/item/.test(eleClicked.className)) {
if (eleClicked.innerHTML == '今年') {
this[SELECTED][0][0] = new Date().getFullYear();
} else {
this[SELECTED][0][0] = eleClicked.innerHTML * 1;
}
// 赋值
this.setValue();
// 回到月份面板
this.month(eleContainerYear);
// 时间也刷新,因为可能禁用态变化
this.time(eleContainerTime);
}
} else if (eleClicked.tagName == 'BUTTON' && eleClicked.classList.contains(SELECTED) == false) {
const arrTime = this[SELECTED][1];
let strTypeButton = eleClicked.parentElement.dataset.type;
let numIndexButton = eleClicked.dataset.index;
if (strTypeButton == 'ampm') {
if (numIndexButton == '0') {
arrTime[0] -= 12;
} else {
arrTime[0] = Number(arrTime[0]) + 12;
}
arrTime[0] = String(arrTime[0]).padStart(2, '0');
} else if (strTypeButton == 'hour') {
arrTime[0] = numIndexButton.padStart(2, '0');
} else if (strTypeButton == 'minute') {
arrTime[1] = numIndexButton.padStart(2, '0');
} else if (strTypeButton == 'second') {
arrTime[2] = numIndexButton.padStart(2, '0');
}
// 改变时间值
this[SELECTED][1] = arrTime;
// 赋值并刷新样式
this.setValue();
this.time(eleContainerTime);
}
}
}
});
// 显隐控制
this.addEventListener('click', (event) => {
event.preventDefault();
// 显隐控制
if (!this.display) {
this.show();
} else {
this.hide();
}
});
// 输入框元素行为
this.addEventListener('keydown', (event) => {
if (event.code == 'Enter') {
event.preventDefault();
this.click();
}
});
// 时间范围选择点击页面空白区域不会隐藏
document.addEventListener('mouseup', (event) => {
// 点击页面空白区域,隐藏
const eleTarget = event.target;
if (eleTarget && eleTarget != this && eleContainer.contains(eleTarget) == false) {
if (this.display) {
this.hide();
}
}
});
// time类型的上下左右快捷键处理
document.addEventListener('keydown', event => {
const strType = eleContainer.dataset.type;
if (!strType) {
return;
}
if (strType.includes('time') && this.display == true && eleContainer.contains(document.activeElement)) {
if (/^arrow/i.test(event.key)) {
event.preventDefault();
// 所有列选中元素
let eleButtonSelected = [...eleContainer.querySelectorAll('.' + SELECTED)];
if (strType.includes('datetime')) {
eleButtonSelected = [...eleContainer.querySelectorAll('[data-type="time"] .' + SELECTED)];
}
let numIndexButton = eleButtonSelected.findIndex(item => item == event.target);
// 当前列所有可点击元素
let eleButtonClickable = [...event.target.parentElement.querySelectorAll('button:enabled:not([data-visibility="false"])')];
let numIndexButtonClickable = eleButtonClickable.findIndex(item => item == event.target);
// 上下左右快捷键的处理
if (event.key == 'ArrowLeft') {
numIndexButton--;
if (eleButtonSelected[numIndexButton]) {
eleButtonSelected[numIndexButton].focus();
}
} else if (event.key == 'ArrowRight') {
numIndexButton++;
if (eleButtonSelected[numIndexButton]) {
eleButtonSelected[numIndexButton].focus();
}
} else if (event.key == 'ArrowUp') {
let eleButtonPrev = eleButtonClickable[numIndexButtonClickable - 1];
if (!eleButtonPrev) {
eleButtonPrev = eleButtonClickable[eleButtonClickable.length - 1];
}
if (eleButtonPrev) {
eleButtonPrev.click();
eleButtonPrev.focus();
}
} else if (event.key == 'ArrowDown') {
let eleButtonNext = eleButtonClickable[numIndexButtonClickable + 1];
if (!eleButtonNext) {
eleButtonNext = eleButtonClickable[0];
}
if (eleButtonNext) {
eleButtonNext.click();
eleButtonNext.focus();
}
}
}
if (event.key == 'Enter') {
this.hide();
}
}
});
// 窗口尺寸变化与重定位
window.addEventListener('resize', () => {
if (this.display) {
this.position();
}
});
return this;
}
/**
* 输入框的值根据日期类型进行格式化
* @return {Object} 返回当前DOM对象
*/
format () {
// 根据当前value值修改DOM元素对象缓存的选中值
// 特殊情况一般不处理
const strType = this.params.type;
// 此时输入框初始值
const strInitValue = this.value.trim();
if (!strInitValue) {
return this;
}
switch (strType) {
case 'date': case 'year': case 'month': {
// 日期
const objInitDate = strInitValue.toDate();
const arrDate = objInitDate.toArray();
// eg. [2015,07,20]
this[SELECTED] = arrDate;
break;
}
case 'time': case 'hour': case 'minute': {
// 时间
const arrTime = strInitValue.toTime();
// 补0
if (arrTime.length > 1) {
this[SELECTED] = [...arrTime];
}
break;
}
case 'datetime': case 'datetime-local': {
// 日期和时间
const arrDateTime = strInitValue.split(/\s+|T/);
const arrPart1 = arrDateTime[0].toDate().toArray();
let arrPart2 = ['00', '00'];
if (arrDateTime[1] && arrDateTime[1].includes(':')) {
arrPart2 = arrDateTime[1].toTime();
}
this[SELECTED] = [arrPart1, arrPart2];
break;
}
case 'date-range': case 'month-range': {
// 日期范围
let objBeginDate = new Date();
let objEndDate = new Date();
// 前后时间字符串
const arrRange = strInitValue.split(' ');
if (arrRange.length == 3) {
objBeginDate = arrRange[0].toDate();
objEndDate = arrRange[arrRange.length - 1].toDate();
// 存储
this[SELECTED] = [objBeginDate.toArray(), objEndDate.toArray()];
}
break;
}
}
return this;
}
/**
* 赋值
* @return {Object} 返回当前输入框的值
*/
setValue () {
const arrSelected = this[SELECTED];
const strValue = this.value;
switch (this.params.type) {
case 'date': {
this.value = arrSelected.join('-');
break;
}
case 'month': {
this.value = arrSelected.slice(0, 2).join('-');
break;
}
case 'year': {
this.value = arrSelected[0];
break;
}
case 'date-range': {
this.value = `${arrSelected[0].join('-')} 至 ${arrSelected[1].join('-')}`;
break;
}
case 'month-range': {
this.value = `${arrSelected[0].slice(0, 2).join('-')} 至 ${arrSelected[1].slice(0, 2).join('-')}`;
break;
}
case 'time': case 'minute': {
this.value = arrSelected.join(':');
break;
}
case 'hour': {
this.value = `${arrSelected[0]}:00`;
break;
}
case 'datetime': case 'datetime-local': {
this.value = arrSelected[0].join('-') + ' ' + arrSelected[1].join(':');
break;
}
}
if (this.value != strValue) {
this.dispatchEvent(new CustomEvent('change', {
'bubbles': true
}));
}
return this.value;
}
/**
* 返回日历HTML字符串等数据的私有方法
* 因date和range浮层主结构类似,因此这里公用下
* @param {Array} arrDate 格式化为数组的日期
* @return {Object} 返回包含需要的数据的对象,生成的HTML字符内容以及最大最小月份等
*/
getCalendarData (arrDate) {
let strHtml = '';
// 根据当前日期数据返回
// eg. [2015,'02', 23]
// 最大最小限制
let strMin = this.min;
let strMax = this.max;
// 类型
const strType = this.params.type;
// 如果是日期时间选择,则最大最小月份是前面部分内容
if (strType.includes('datetime')) {
if (strMin) {
strMin = strMin.split(/\s+/)[0];
}
if (strMax) {
strMax = this.max.split(/\s+/)[0];
}
}
// 最大日期和最小日期
let numMin = (strMin || '0001-01-01').toDate();
let numMax = (strMax || '9999-00-01').toDate();
const arrChinese = ['日', '一', '二', '三', '四', '五', '六'];
const arrMonthDay = this.getMonthDay(arrDate);
// 目前日期对象
const currentDate = arrDate.join('-').toDate();
const getStrHtmlDay = () => {
let strHtmlDay = '';
arrChinese.forEach((strChineseDay, indexDay) => {
strHtmlDay = `${strHtmlDay}<span class="${CL.day('item')} col${indexDay}">${strChineseDay}</span>`;
});
return strHtmlDay;
};
// 3 星期几七大罪
strHtml = `<div class="${CL.day('x')}">${getStrHtmlDay()}</div>`;
// 4. 日期详细
// 4.1 首先算出今月1号是星期几
const objNewDate = arrDate.join('-').toDate();
let numDayFirst = 0;
// 设置为1号
objNewDate.setDate(1);
if (objNewDate.getDate() == 2) {
objNewDate.setDate(0);
}
// 每月的第一天是星期几
numDayFirst = objNewDate.getDay();
// 上个月是几月份
let numLastMonth = objNewDate.getMonth() - 1;
if (numLastMonth < 0) {
numLastMonth = 11;
}
const strHtmlData = `data-year="${arrDate[0]}" data-month="${objNewDate.getMonth() + 1}"`;
const strHtmlYearMonthData = 'data-date=';
let strHtmlFullData = '';
const getStrHtmlDate = () => {
let strHtmlDate = '';
let strClass = '';
// 列表生成
for (let tr = 0; tr < 6; tr++) {
strHtmlDate = `${strHtmlDate}<div class="${CL.date('tr')}">`;
// 日期遍历
for (let td = 0; td < 7; td++) {
// 类名
strClass = `${CL.date('item')} col${td}`;
// 今天
const numYearNow = arrDate[0];
const numMonthNow = objNewDate.getMonth() + 1;
let numDayNow;
let objDateNow;
// 由于range选择和date选择UI上有比较大大差异
// 为了可读性以及后期维护
// 这里就不耦合在一起,而是分开处理
if (strType == 'date' || strType.includes('datetime')) {
// 第一行上个月一些日期补全
if (tr == 0 && td < numDayFirst) {
// 当前日子
numDayNow = arrMonthDay[numLastMonth] - numDayFirst + td + 1;
// 当前日期
objDateNow = new Date(numYearNow, numLastMonth, numDayNow);
// 完整data-date属性及其值
strHtmlFullData = strHtmlYearMonthData + objDateNow.toArray().join('-');
// HTML拼接
strHtmlDate = `${strHtmlDate}<span class="${strClass}" ${strHtmlFullData}>${numDayNow}</span>`;
} else {
// 当前日子
numDayNow = tr * 7 + td - numDayFirst + 1;
// 如果没有超过这个月末
if (numDayNow <= arrMonthDay[objNewDate.getMonth()]) {
// 这个日子对应的时间对象
objDateNow = new Date(numYearNow, objNewDate.getMonth(), numDayNow);
// 完整data-date属性及其值
strHtmlFullData = strHtmlYearMonthData + objDateNow.toArray().join('-');
// 如果日子匹配
if (currentDate.getDate() == numDayNow) {
strClass = `${strClass} ${SELECTED}`;
}
// 如果在日期范围内
// 直接使用时间对象 Date 类作比较
if (objDateNow >= numMin && objDateNow <= numMax) {
strHtmlDate = `${strHtmlDate}<a href="javascript:;" ${strHtmlData} class="${strClass}" ${strHtmlFullData}>${numDayNow}</a>`;
} else {
strHtmlDate = `${strHtmlDate}<span class="${strClass}" ${strHtmlFullData}>${numDayNow}</span>`;
}
} else {
numDayNow = numDayNow - arrMonthDay[objNewDate.getMonth()];
// 更新strHtmlFullData
strHtmlFullData = strHtmlYearMonthData + new Date(numYearNow, numMonthNow, numDayNow).toArray().join('-');
// 日期字符拼接
strHtmlDate = `${strHtmlDate}<span class="${strClass}" ${strHtmlFullData}>${numDayNow}</span>`;
}
}
} else if (strType == 'date-range') {
// 非当前月部分使用空格补全
if (tr == 0 && td < numDayFirst) {
strHtmlDate = `${strHtmlDate}<span class="${strClass}"></span>`;
} else {
numDayNow = tr * 7 + td - numDayFirst + 1;
// 如果没有超过这个月末
if (numDayNow <= arrMonthDay[objNewDate.getMonth()]) {
// 这个日子对应的时间对象
objDateNow = new Date(numYearNow, objNewDate.getMonth(), numDayNow);
// 完整data-date属性及其值
strHtmlFullData = strHtmlYearMonthData + objDateNow.toArray().join('-');
// range选择的匹配规则如下:
// 1. 获得已经选中到时间范围
// 2. 起始时间和结束时间是方框表示