lu2
Version:
Simple and flexible UI component library based on native HTML and JavaScript
1,179 lines (1,062 loc) • 139 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', 'week', 'hour', 'minute', 'time', 'datetime'].forEach((key) => {
CL[key] = (...args) => ['ui', key, ...args].join('-');
});
const SELECTED = 'selected';
const ACTIVE = 'active';
const regDate = /-|\//g;
// 日期转第xxxx-xx周的方法
function dateToWeek(date) {
// 将输入的日期字符串转换为 Date 对象
const inputDate = typeof date == 'string' ? new Date(date) : date;
// 获取年份
const year = inputDate.getFullYear();
// 计算 ISO 周数
const d = new Date(date);
d.setHours(0, 0, 0, 0);
d.setDate(d.getDate() + 4 - (d.getDay() || 7));
const yearStart = new Date(d.getFullYear(), 0, 1);
const weekNo = Math.ceil(((d - yearStart) / 8.64e7 + 1) / 7);
// 补全周数为两位数
const paddedWeek = String(weekNo).padStart(2, '0');
return `${year}-${paddedWeek}W`;
}
function getRangeByWeek(year, weekNumber) {
// 获取当年第一个星期四,以此确定第一周
const firstThursday = new Date(year, 0, 4);
firstThursday.setDate(firstThursday.getDate() - (firstThursday.getDay() || 7) + 4);
// 计算目标周的星期四
const targetThursday = new Date(firstThursday);
targetThursday.setDate(targetThursday.getDate() + (weekNumber - 1) * 7);
// 计算目标周的起始日期和结束日期
const startDate = new Date(targetThursday);
startDate.setDate(targetThursday.getDate() - 3);
const endDate = new Date(targetThursday);
endDate.setDate(targetThursday.getDate() + 3);
// 格式化日期
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}年${month}月${day}日`;
};
return [formatDate(startDate), formatDate(endDate)];
}
// 拓展方法之字符串变时间对象
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;
};
// 将中文的2025年第xx周转换成英文的[2025, xx]格式
String.prototype.toWeek = function () {
// 如果2025-xxW格式
if (this.includes('-') && this.includes('W')) {
// 直接返回
return this.split('-').map(item => item.replace(/\D/g, '').trim()).slice(0, 2);
}
if (this.includes('第') && this.includes('周')) {
// 先去掉中文的“第”和“周”
const strWeek = this.replace(/第|周/g, '').trim();
return strWeek.split('年').map(item => item.trim()).slice(0, 2);
}
// 按照普通日期处理
return dateToWeek(this.toDate()).toWeek();
}
// 日期对象变成年月日数组
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();
}
}
const 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 (strType == 'week' || strType == 'week-range') {
// 周类型,返回
if (value.includes('W')) {
// 如果已经是周格式
return value;
}
value = dateToWeek(arrDate.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;
let numWeek = 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 (eleClicked.classList.contains(CL.date('outside'))) {
// 非当前月日期,需要使用 data-date 中的完整年月日
const strDataDate = eleClicked.dataset.date;
if (strDataDate) {
this[SELECTED] = strDataDate.split('-');
} else {
if (numDay < 10) {
numDay = `0${numDay}`;
}
this[SELECTED][2] = numDay;
}
} 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': case 'week-range': {
// 区域选择
// 1. 前后年份选择
if (/prev|next/.test(eleClicked.className)) {
numYear = eleClicked.dataset.year * 1;
arrRange = eleContainer.dataDate || this[SELECTED][0];
// 跟其他面板不同,这里只刷新,点击确定再赋值
if (strType == 'month-range') {
eleContainer.dataDate = new Date(numYear, arrRange[1], 1).toArray();
} else {
// 周范围选择
// 这里的numYear是周数
eleContainer.dataDate = [numYear, arrRange[1]];
}
// 刷新
this[strType]();
} else if (/item/.test(eleClicked.className)) {
// 选择某日期
// 获得选中的年月日
numYear = eleClicked.dataset.year;
numMonth = (eleClicked.dataset.value || '1').padStart(2, '0');
numDay = '01';
// 根据选中状态决定新的状态
dataRange = this[SELECTED];
if (strType == 'month-range') {
// 月份选择
// 如果之前前后日期一样,说明只选中了一个日期
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]];
}
} else {
numWeek = numMonth;
// 如果之前前后日期一样,说明只选中了一个日期
if (dataRange[0].join() == dataRange[1].join()) {
// 根据前后顺序改变其中一个日期
if (numYear + numWeek > dataRange[0].join('')) {
// 新时间靠后
dataRange[1] = [numYear, numWeek];
} else {
dataRange[0] = [numYear, numWeek];
}
} else {
// 如果前后时间不一样,说明现在有范围
// 则取消范围,变成单选
dataRange = [[numYear, numWeek], [numYear, numWeek]];
}
}
this[SELECTED] = dataRange;
this[strType]();
} 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 'week': {
numYear = eleClicked.dataset.year;
// 1. 前后年份
if (/prev|next/.test(eleClicked.className)) {
// 修改当前选中的年份数
this[SELECTED][0] = Number(numYear);
// 刷新
this.week();
// 文本框赋值
// 如果在区域内状态
if (eleContainer.querySelector(`.${SELECTED}[href]`)) {
this.setValue();
}
} else if (/item/.test(eleClicked.className)) {
// value实际上是月份两位数值
const value = eleClicked.dataset.value;
if (numYear) {
// 如果有年份,直接使用
this[SELECTED][0] = Number(numYear);
}
if (value) {
this[SELECTED][1] = Number(value);
}
// 赋值
this.setValue();
// 根据是否是月份输入框,决定是面板切换,还是关闭
if (this.params.type == 'week') {
// 隐藏
this.hide();
}
} else if (eleClicked.dataset.type == 'year') {
// 切换到年份选择
this.year();
}
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 if (this.params.type == 'week') {
// 隐藏
this.week();
} 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 (eleClicked.classList.contains(CL.date('outside'))) {
// 非当前月日期,需要使用 data-date 中的完整年月日
const strDataDate = eleClicked.dataset.date;
if (strDataDate) {
this[SELECTED][0] = strDataDate.split('-');
} else {
if (numDay < 10) {
numDay = `0${numDay}`;
}
this[SELECTED][0][2] = numDay;
}
} 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);
}
}
}
});
// week week-range类型hover时候,时间跟着变化
if (window.matchMedia('(hover: hover)').matches && this.params.type.includes('week')) {
eleContainer.addEventListener('mouseover', (event) => {
const eleItem = event.target;
if (!eleItem || !eleItem.closest) {
return;
}
const eleTime = eleContainer.querySelector('.' + CL.week('time') + ' time');
// 只处理item
if (eleTime && eleItem.className.includes('item')) {
// 选中态
const year = eleItem.dataset.year;
const value = eleItem.dataset.value;
if (year && value) {
// 改变eleTime的内容
if (this.params.type == 'week') {
eleTime.textContent = getRangeByWeek(Number(year), Number(value)).join(' 至 ');
}
}
} else if (this.params.type == 'week') {
// 还原成现在选中的内容
eleTime.textContent = getRangeByWeek.apply(null, this[SELECTED]).join(' 至 ');
}
});
eleContainer.addEventListener('mouseout', () => {
// 只处理item
if (this.params.type == 'week' && this[SELECTED].length == 2) {
const eleTime = eleContainer.querySelector(`.${CL.week('time')} time`);
if (eleTime) {
// 还原成现在选中的内容
eleTime.textContent = getRangeByWeek.apply(null, this[SELECTED]).join(' 至 ');
}
}
});
}
// 显隐控制
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 'week': {
// 星期
this[SELECTED] = strInitValue.toWeek();
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