UNPKG

lu2

Version:

Simple and flexible UI component library based on native HTML and JavaScript

1,179 lines (1,062 loc) 139 kB
/** * @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