diff --git a/docs/laydate/detail/options.md b/docs/laydate/detail/options.md index 1be45c1a..1b9e4aba 100644 --- a/docs/laydate/detail/options.md +++ b/docs/laydate/detail/options.md @@ -325,6 +325,66 @@ max: 7 // 最大日期为 7 天后 +disabledDate 2.9.8+ + + +用于设置不可选取的日期。示例: + +```js +disabledDate: function(date, type){ + // date - 当前的日期对象 + // type - 面板类型,'start'/'end' + + // 返回值为 true 的日期会被禁用 + return date.getTime() < new Date(2024, 1).getTime(); // 2024-02-01 +} + +``` + + +function + - + + +disabledTime 2.9.8+ + + +用于设置不可选取的时间。示例: + +```js +disabledTime: function(date, type){ + // date - 当前的日期对象 + // type - 面板类型,'start'/'end' + + // 数组中指定的时间会被禁用 + return { + hours: function(){ + return range(0, 10); + }, + minutes:function(hour){ + return hour > 5 ? range(0, 20) : []; + }, + seconds:function(hour, minute){ + return range(0, 2); + } + }; +} + +function range(start, end) { + var result = []; + for (var i = start; i < end; i++) { + result.push(i); + } + return result; +} + +``` + + +function + - + + trigger @@ -704,4 +764,4 @@ done: function(value, date, endDate){ - \ No newline at end of file + diff --git a/docs/laydate/examples/limit.md b/docs/laydate/examples/limit.md index 6a99f983..06cb288d 100644 --- a/docs/laydate/examples/limit.md +++ b/docs/laydate/examples/limit.md @@ -21,6 +21,18 @@ 这里以控制在 9:30-17:30 为例 +
+ +
+ +
+
+
+ +
+ +
+
@@ -54,5 +66,41 @@ layui.use(function(){ max: '17:30:00', btns: ['clear', 'confirm'] }); + + // 禁用日期 + laydate.render({ + elem: '#ID-laydate-limit-4', + disabledDate: function(date, type){ + return date.getTime() > Date.now(); + } + }); + + // 禁用指定时间 + laydate.render({ + elem: '#ID-laydate-limit-5', + type: 'time', + range: true, + disabledTime: function(date, type){ + return { + hours: function(){ + return range(0, 10); + }, + minutes:function(hour){ + return hour > 5 ? range(0, 20) : []; + }, + seconds:function(hour, minute){ + return range(0, 2); + } + }; + } + }); + + function range(start, end) { + var result = []; + for (var i = start; i < end; i++) { + result.push(i); + } + return result; + } }); \ No newline at end of file diff --git a/src/css/modules/laydate.css b/src/css/modules/laydate.css index 998b8b98..b6884b44 100644 --- a/src/css/modules/laydate.css +++ b/src/css/modules/laydate.css @@ -139,6 +139,7 @@ html #layuicss-laydate{display: none; position: absolute; width: 1989px;} .layui-laydate .layui-this,.layui-laydate .layui-this>div{background-color: #16baaa !important; color: #fff !important;} .layui-laydate .laydate-disabled, .layui-laydate .laydate-disabled:hover{background:none !important; color: #d2d2d2 !important; cursor: not-allowed !important; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none;} +.layui-laydate .layui-this.laydate-disabled,.layui-laydate .layui-this.laydate-disabled>div{background-color: #eee !important} .layui-laydate-content td>div{padding: 7px 0;height: 100%;} /* 墨绿/自定义背景色主题 */ diff --git a/src/modules/laydate.js b/src/modules/laydate.js index 51f667bc..5bd24d9f 100644 --- a/src/modules/laydate.js +++ b/src/modules/laydate.js @@ -1,19 +1,19 @@ -/** laydate 日期与时间控件 | MIT Licensed */ - +/** laydate 日期与时间控件 | MIT Licensed */ +// @ts-expect-error ;!function(window, document){ // gulp build: laydate-header "use strict"; - var isLayui = window.layui && layui.define, ready = { - getPath: (window.lay && lay.getPath) ? lay.getPath : '' + var isLayui = window.layui && layui.define; + var ready = { + getPath: window.lay && lay.getPath ? lay.getPath : '', // 载入 CSS 依赖 - ,link: function(href, fn, cssname){ - + link: function (href, fn, cssname) { // 未设置路径,则不主动加载 css - if(!laydate.path) return; + if (!laydate.path) return; // 加载 css - if(window.lay && lay.layui){ + if (window.lay && lay.layui) { lay.layui.link(laydate.path + href, fn, cssname); } } @@ -24,34 +24,34 @@ // 模块名 var MOD_NAME = 'laydate'; - var MOD_ID = 'layui-'+ MOD_NAME +'-id' // 已渲染过的索引标记名 + var MOD_ID = 'layui-' + MOD_NAME + '-id'; // 已渲染过的索引标记名 // 外部调用 var laydate = { - v: '5.5.0' // layDate 版本号 - ,config: { - weekStart: 0, // 默认周日一周的开始 - } // 全局配置项 - ,index: (window.laydate && window.laydate.v) ? 100000 : 0 - ,path: GLOBAL.laydate_dir || ready.getPath + v: '5.6.0', // layDate 版本号 + config: { + weekStart: 0 // 默认周日一周的开始 + }, // 全局配置项 + index: window.laydate && window.laydate.v ? 100000 : 0, + path: GLOBAL.laydate_dir || ready.getPath, // 设置全局项 - ,set: function(options){ + set: function (options) { var that = this; that.config = lay.extend({}, that.config, options); return that; - } + }, // 主体 CSS 等待事件 - ,ready: function(callback){ + ready: function (callback) { var cssname = 'laydate'; - var ver = '' - var path = (isLayui ? 'modules/' : '') + 'laydate.css?v='+ laydate.v + ver; + var ver = ''; + var path = (isLayui ? 'modules/' : '') + 'laydate.css?v=' + laydate.v + ver; isLayui ? ( - layui['layui.all'] - ? (typeof callback === 'function' && callback()) - : layui.addcss(path, callback, cssname) + layui['layui.all'] ? + (typeof callback === 'function' && callback()) : + layui.addcss(path, callback, cssname) ) : ready.link(path, callback, cssname); return this; @@ -1101,7 +1101,184 @@ return that; }; - // 无效日期范围的标记 + /** + * 给定年份的开始日期 + * @param {Date} date + */ + Class.prototype.startOfYear = function(date){ + var newDate = new Date(date); + newDate.setFullYear(newDate.getFullYear(), 0, 1); + newDate.setHours(0, 0, 0, 0); + return newDate; + } + + /** + * 给定年份的结束日期 + * @param {Date} date + */ + Class.prototype.endOfYear = function(date){ + var newDate = new Date(date); + var year = newDate.getFullYear(); + newDate.setFullYear(year + 1, 0, 0); + newDate.setHours(23, 59, 59, 999); + return newDate; + } + + /** + * 给定月份的开始日期 + * @param {Date} date + */ + Class.prototype.startOfMonth = function(date){ + var newDate = new Date(date); + newDate.setDate(1); + newDate.setHours(0, 0, 0, 0); + return newDate; + } + + /** + * 给定月份的结束日期 + * @param {Date} date + */ + Class.prototype.endOfMonth = function(date){ + var newDate = new Date(date); + var month = newDate.getMonth(); + newDate.setFullYear(newDate.getFullYear(), month + 1, 0); + newDate.setHours(23, 59, 59, 999); + return newDate; + } + + /** + * 将指定的天数添加到给定日期 + * @param {Date} date 要更改的日期 + * @param {number} amount 天数 + */ + Class.prototype.addDays = function(date, amount){ + var newDate = new Date(date); + if(!amount) return newDate; + newDate.setDate(newDate.getDate() + amount); + return newDate; + } + + /** + * 不可选取的年或月。年或月中的所有日期都禁用时,才判定为不可选取。 + * @param {Date} date 要检测的年或月 + * @param {'year' | 'month'} type 面板类型 + * @param {'start' | 'end'} position 面板位置 + */ + Class.prototype.isDisabledYearOrMonth = function(date, type, position){ + var that = this; + var options = that.config; + var millisecondsInDay = 24 * 60 * 60 * 1000; + + var startDay = type === 'year' ? that.startOfYear(date) : that.startOfMonth(date); + var endDay = type === 'year' ? that.endOfYear(date) : that.endOfMonth(date); + var numOfDays = Math.floor((endDay.getTime() - startDay.getTime()) / millisecondsInDay) + 1; + var disabledCount = 0; + + for(var i = 0; i < numOfDays; i++){ + var day = that.addDays(startDay, i); + if(options.disabledDate.call(options, day, position)){ + disabledCount++; + } + } + + return disabledCount === numOfDays; + } + + /** + * @typedef limitOptions + * @prop {JQuery} [elem] - 检测的元素, 例如面板中年月日时分秒元素,“现在”,“确认” 按钮等 + * @prop {number} [index] - 元素集合中,当前检测元素的索引,years:0,month:0,date:0-41,hms:0 + * @prop {['hours', 'minutes', 'seconds'] | ['hours', 'minutes'] | ['hours']} [time] - 是否比较时分秒 + * @prop {'year'|'month'|string} [type] - 面板类型? + * @prop {0 | 1} [rangeType] - 面板索引, 0 表示 start, 1 表示 end + * @prop {Partial<{year:number,month: number,date:number,hours:number,minutes:number,seconds:number}>} [date] - 检测的日期时间对象 + * @prop {'date' | 'time' | 'datetime'} disabledType - 禁用类型,按钮应使用 datetime + */ + /** + * 不可选取的日期 + * @param {number} date 当前检测的日期的时间戳 + * @param {limitOptions} opts + * @returns {boolean} + */ + Class.prototype.isDisabledDate = function(date, opts){ + opts = opts || {}; + + var that = this; + var options = that.config; + var position = options.range ? (opts.rangeType === 0 ? 'start' : 'end') : 'start'; + + if(!options.disabledDate) return false; + if(options.type === 'time') return false; + if(!(opts.disabledType === 'date' || opts.disabledType === 'datetime')) return false; + + // 不需要时分秒 + var normalizedDate = new Date(date); + normalizedDate.setHours(0, 0, 0, 0); + + return opts.type === 'year' || opts.type === 'month' + ? that.isDisabledYearOrMonth(normalizedDate, opts.type, position) + : options.disabledDate.call(options, normalizedDate, position); + } + + /** + * 不可选取的时间 + * @param {number} date 当前检测的日期的时间戳 + * @param {limitOptions} opts + * @returns {boolean} + */ + Class.prototype.isDisabledTime = function(date, opts){ + opts = opts || {}; + + var that = this; + var options = that.config; + var position = options.range ? (opts.rangeType === 0 ? 'start' : 'end') : 'start'; + + if(!options.disabledTime) return false; + if(!(options.type === "time" || options.type === "datetime")) return false; + if(!(opts.disabledType === 'time' || opts.disabledType === 'datetime')) return false; + + var isDisabledItem = function(compareVal, rangeFn, rangeFnParam){ + return function(){ + return (typeof rangeFn === 'function' && rangeFn.apply(options, rangeFnParam) || []).indexOf(compareVal) !== -1; + } + } + + var dateObj = that.systemDate(new Date(date)); + var disabledTime = options.disabledTime.call(options, that.newDate(dateObj), position) || {}; + + // 面板中的时分秒 HTML 元素需要分别检测是否禁用 + // 按钮检测任意一项是否禁用即可 + return opts.disabledType === 'datetime' + ? isDisabledItem(dateObj.hours, disabledTime.hours)() + || isDisabledItem(dateObj.minutes, disabledTime.minutes, [dateObj.hours])() + || isDisabledItem(dateObj.seconds, disabledTime.seconds, [dateObj.hours, dateObj.minutes])() + : [isDisabledItem(dateObj.hours, disabledTime.hours), + isDisabledItem(dateObj.minutes, disabledTime.minutes, [dateObj.hours]), + isDisabledItem(dateObj.seconds, disabledTime.seconds, [dateObj.hours, dateObj.minutes])][opts.time.length - 1](); + } + + /** + * 不可选取的日期时间 + * @param {number} timestamp 当前检测的日期的时间戳 + * @param {limitOptions} opts + * @returns + */ + Class.prototype.isDisabledDateTime = function(timestamp, opts){ + opts = opts || {}; + + var that = this; + var options = that.config; + + return that.isDisabledDate(timestamp, opts) || that.isDisabledTime(timestamp, opts); + } + + + /** + * 无效日期范围的标记 + * @param {limitOptions} opts + * + */ Class.prototype.limit = function(opts){ opts = opts || {}; @@ -1129,7 +1306,7 @@ }())).getTime(); //time:是否比较时分秒 }); - isOut = timestamp.now < timestamp.min || timestamp.now > timestamp.max; + isOut = timestamp.now < timestamp.min || timestamp.now > timestamp.max || that.isDisabledDateTime(timestamp.now, opts); opts.elem && opts.elem[isOut ? 'addClass' : 'removeClass'](DISABLED); return isOut; @@ -1199,7 +1376,9 @@ month: YMD[1] - 1, date: YMD[2] }, - index: index_ + index: index_, + rangeType: index, + disabledType: 'date' // 日面板,检测当前日期是否禁用 }); }); @@ -1260,13 +1439,15 @@ elem: lay(that.footer).find(ELEM_NOW), date: that.systemDate(/^(datetime|time)$/.test(options.type) ? new Date() : null), index: 0, - time: timeParams + time: timeParams, + disabledType: 'datetime' // 按钮,检测日期和时间 }); // 确认按钮 that.limit({ elem: lay(that.footer).find(ELEM_CONFIRM), index: 0, - time: timeParams + time: timeParams, + disabledType: 'datetime' // 按钮,检测日期和时间 }); } @@ -1342,7 +1523,9 @@ elem: lay(li), date: ymd, index: index, - type: type + type: type, + rangeType: index, + disabledType: 'date' // 年面板,检测当前年份中的所有日期是否禁用 }); yearNum++; }); @@ -1379,7 +1562,9 @@ elem: lay(li), date: ymd, index: index, - type: type + type: type, + rangeType: index, + disabledType: 'date' // 月面板,检测当前月份中的所有日期是否禁用 }); }); @@ -1406,6 +1591,8 @@ ,seconds: ii }][i], index: index, + rangeType: index, + disabledType: 'time', // 时间面板,分别检测时分秒列表是否禁用 time: [ ['hours'], ['hours', 'minutes'], @@ -1419,7 +1606,8 @@ elem: lay(that.footer).find(ELEM_CONFIRM), date: that[startEnd], index: 0, - time: ['hours', 'minutes', 'seconds'] + time: ['hours', 'minutes', 'seconds'], + disabledType: 'datetime' // 确认按钮,检测时分秒列表任意一项是否禁用 }); } }; @@ -1601,17 +1789,25 @@ var that = this ,options = that.config ,lang = that.lang() - ,isOut, elemBtn = lay(that.footer).find(ELEM_CONFIRM); - if(options.range && options.type !== 'time'){ + ,isOut + ,elemBtn = lay(that.footer).find(ELEM_CONFIRM) + ,timeParams = options.type === 'datetime' || options.type === 'time' ? ['hours', 'minutes', 'seconds'] : undefined; + if(options.range){ start = start || (that.rangeLinked ? that.startDate : options.dateTime); end = end || that.endDate; isOut = !that.endState || that.newDate(start).getTime() > that.newDate(end).getTime(); //如果不在有效日期内,直接禁用按钮,否则比较开始和结束日期 (that.limit({ - date: start + date: start, + disabledType: 'datetime', // 按钮,检测日期和时间 + time: timeParams, + rangeType: 0 }) || that.limit({ - date: end + date: end, + disabledType: 'datetime', // 按钮,检测日期和时间 + time: timeParams, + rangeType: 1 })) ? elemBtn.addClass(DISABLED) : elemBtn[isOut ? 'addClass' : 'removeClass'](DISABLED); @@ -1885,7 +2081,8 @@ that.startDate = lay.extend({}, dateTime); // 同步startDate } // 校验另外一个日期是否在有效的范围内 - if (that.endState && !that.limit({date: that.thisDateTime(1 - index)})) { + // 此处为范围选择的日期面板点击选中处理,所以 disabledType 为 date + if (that.endState && !that.limit({date: that.rangeLinked ? that.startDate : that.thisDateTime(1 - index), disabledType:'date'})) { // 根据选择之后判断是否需要切换模式 var isChange; if (that.endState && that.autoCalendarModel.auto) { @@ -1995,9 +2192,15 @@ //确定 ,confirm: function(){ if(options.range){ - if(lay(btn).hasClass(DISABLED)) return that.hint( - options.type === 'time' ? lang.timeout.replace(/日期/g, '时间') : lang.timeout - ); + if(lay(btn).hasClass(DISABLED)){ + var isTimeout = options.type === 'time' + ? that.startTime && that.endTime && that.newDate(that.startTime) > that.newDate(that.endTime) + : that.startDate && that.endDate && that.newDate(lay.extend({},that.startDate, that.startTime || {})) > that.newDate(lay.extend({},that.endDate, that.endTime || {})); + + return isTimeout + ? that.hint(options.type === 'time' ? lang.timeout.replace(/日期/g, '时间') : lang.timeout) + : that.hint(lang.invalidDate); + } } else { if(lay(btn).hasClass(DISABLED)) return that.hint(lang.invalidDate); } @@ -2043,7 +2246,8 @@ elem: lay(that.footer).find(ELEM_CONFIRM), date: { year: listYM[0] - } + }, + disabledType: 'datetime' // 按钮,检测日期和时间 }); } @@ -2372,4 +2576,3 @@ ); }(window, window.document); -