UNPKG

lu2

Version:

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

1,193 lines (1,068 loc) 115 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', '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. 起始时间和结束时间是方框表示