Browse Source

feat(laydate): 新增 `disabledDate`, `disabledTime` 选项 (#1569)

* feat(laydate): 新增 `disabledDate`, `disabledTime` 选项

feat(laydate): 优化年、月面板的禁用检测

refactor: 简化代码

fix: 修复 4d7ae6e 造成的按钮检测 BUG

options.disabledDate 中不应携带时分秒信息

fix(laydate): 修复范围联动模式, 右侧面板检测另外一个日期有效范围错误, 导致无法标记范围的问题

2.8.0 ~ 2.9.4 均有此问题

feat: disabledTime 返回值改为对象,简化写法

docs: 添加文档

feat: 优化点击确定按钮时的提示

fix: 修复一些边界情况

docs: typo

refactor: 优化年月禁用

refactor: 优化确定按钮 hint

chore: typo

docs: update

* refactor: 优化代码结构

* refactor: 优化 disabledTime,减少不必要的计算

* chore(laydate): 使用 prettier 格式化头部代码

* docs(laydate): 补充新选项文档描述

---------

Co-authored-by: 贤心 <3277200+sentsim@users.noreply.github.com>
pull/1740/head
morning-star 8 months ago committed by GitHub
parent
commit
e1562bdb48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 62
      docs/laydate/detail/options.md
  2. 48
      docs/laydate/examples/limit.md
  3. 1
      src/css/modules/laydate.css
  4. 285
      src/modules/laydate.js

62
docs/laydate/detail/options.md

@ -325,6 +325,66 @@ max: 7 // 最大日期为 7 天后
</td> </td>
</tr> </tr>
<tr> <tr>
<td>disabledDate <sup>2.9.8+</sup> </td>
<td>
用于设置不可选取的日期。示例:
```js
disabledDate: function(date, type){
// date - 当前的日期对象
// type - 面板类型,'start'/'end'
// 返回值为 true 的日期会被禁用
return date.getTime() < new Date(2024, 1).getTime(); // 2024-02-01
}
```
</td>
<td>function</td>
<td> - </td>
</tr>
<tr>
<td>disabledTime <sup>2.9.8+</sup> </td>
<td>
用于设置不可选取的时间。示例:
```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;
}
```
</td>
<td>function</td>
<td> - </td>
</tr>
<tr>
<td>trigger</td> <td>trigger</td>
<td> <td>
@ -704,4 +764,4 @@ done: function(value, date, endDate){
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

48
docs/laydate/examples/limit.md

@ -21,6 +21,18 @@
这里以控制在 9:30-17:30 为例 这里以控制在 9:30-17:30 为例
</div> </div>
</div> </div>
<div class="layui-inline">
<label class="layui-form-label">禁用日期</label>
<div class="layui-input-inline">
<input type="text" class="layui-input" id="ID-laydate-limit-4" placeholder="仅过去的时间可选">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">禁用指定时间</label>
<div class="layui-input-inline">
<input type="text" class="layui-input" id="ID-laydate-limit-5" placeholder="时间范围禁用">
</div>
</div>
</div> </div>
</div> </div>
@ -54,5 +66,41 @@ layui.use(function(){
max: '17:30:00', max: '17:30:00',
btns: ['clear', 'confirm'] 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;
}
}); });
</script> </script>

1
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 .layui-this,.layui-laydate .layui-this>div{background-color: #16baaa !important; color: #fff !important;}
.layui-laydate .laydate-disabled, .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 .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%;} .layui-laydate-content td>div{padding: 7px 0;height: 100%;}
/* 墨绿/自定义背景色主题 */ /* 墨绿/自定义背景色主题 */

285
src/modules/laydate.js

@ -1,19 +1,19 @@
/** laydate 日期与时间控件 | MIT Licensed */ /** laydate 日期与时间控件 | MIT Licensed */
// @ts-expect-error
;!function(window, document){ // gulp build: laydate-header ;!function(window, document){ // gulp build: laydate-header
"use strict"; "use strict";
var isLayui = window.layui && layui.define, ready = { var isLayui = window.layui && layui.define;
getPath: (window.lay && lay.getPath) ? lay.getPath : '' var ready = {
getPath: window.lay && lay.getPath ? lay.getPath : '',
// 载入 CSS 依赖 // 载入 CSS 依赖
,link: function(href, fn, cssname){ link: function (href, fn, cssname) {
// 未设置路径,则不主动加载 css // 未设置路径,则不主动加载 css
if(!laydate.path) return; if (!laydate.path) return;
// 加载 css // 加载 css
if(window.lay && lay.layui){ if (window.lay && lay.layui) {
lay.layui.link(laydate.path + href, fn, cssname); lay.layui.link(laydate.path + href, fn, cssname);
} }
} }
@ -24,34 +24,34 @@
// 模块名 // 模块名
var MOD_NAME = 'laydate'; var MOD_NAME = 'laydate';
var MOD_ID = 'layui-'+ MOD_NAME +'-id' // 已渲染过的索引标记名 var MOD_ID = 'layui-' + MOD_NAME + '-id'; // 已渲染过的索引标记名
// 外部调用 // 外部调用
var laydate = { var laydate = {
v: '5.5.0' // layDate 版本号 v: '5.6.0', // layDate 版本号
,config: { config: {
weekStart: 0, // 默认周日一周的开始 weekStart: 0 // 默认周日一周的开始
} // 全局配置项 }, // 全局配置项
,index: (window.laydate && window.laydate.v) ? 100000 : 0 index: window.laydate && window.laydate.v ? 100000 : 0,
,path: GLOBAL.laydate_dir || ready.getPath path: GLOBAL.laydate_dir || ready.getPath,
// 设置全局项 // 设置全局项
,set: function(options){ set: function (options) {
var that = this; var that = this;
that.config = lay.extend({}, that.config, options); that.config = lay.extend({}, that.config, options);
return that; return that;
} },
// 主体 CSS 等待事件 // 主体 CSS 等待事件
,ready: function(callback){ ready: function (callback) {
var cssname = 'laydate'; var cssname = 'laydate';
var ver = '' var ver = '';
var path = (isLayui ? 'modules/' : '') + 'laydate.css?v='+ laydate.v + ver; var path = (isLayui ? 'modules/' : '') + 'laydate.css?v=' + laydate.v + ver;
isLayui ? ( isLayui ? (
layui['layui.all'] layui['layui.all'] ?
? (typeof callback === 'function' && callback()) (typeof callback === 'function' && callback()) :
: layui.addcss(path, callback, cssname) layui.addcss(path, callback, cssname)
) : ready.link(path, callback, cssname); ) : ready.link(path, callback, cssname);
return this; return this;
@ -1101,7 +1101,184 @@
return that; 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){ Class.prototype.limit = function(opts){
opts = opts || {}; opts = opts || {};
@ -1129,7 +1306,7 @@
}())).getTime(); //time:是否比较时分秒 }())).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); opts.elem && opts.elem[isOut ? 'addClass' : 'removeClass'](DISABLED);
return isOut; return isOut;
@ -1199,7 +1376,9 @@
month: YMD[1] - 1, month: YMD[1] - 1,
date: YMD[2] date: YMD[2]
}, },
index: index_ index: index_,
rangeType: index,
disabledType: 'date' // 日面板,检测当前日期是否禁用
}); });
}); });
@ -1260,13 +1439,15 @@
elem: lay(that.footer).find(ELEM_NOW), elem: lay(that.footer).find(ELEM_NOW),
date: that.systemDate(/^(datetime|time)$/.test(options.type) ? new Date() : null), date: that.systemDate(/^(datetime|time)$/.test(options.type) ? new Date() : null),
index: 0, index: 0,
time: timeParams time: timeParams,
disabledType: 'datetime' // 按钮,检测日期和时间
}); });
// 确认按钮 // 确认按钮
that.limit({ that.limit({
elem: lay(that.footer).find(ELEM_CONFIRM), elem: lay(that.footer).find(ELEM_CONFIRM),
index: 0, index: 0,
time: timeParams time: timeParams,
disabledType: 'datetime' // 按钮,检测日期和时间
}); });
} }
@ -1342,7 +1523,9 @@
elem: lay(li), elem: lay(li),
date: ymd, date: ymd,
index: index, index: index,
type: type type: type,
rangeType: index,
disabledType: 'date' // 年面板,检测当前年份中的所有日期是否禁用
}); });
yearNum++; yearNum++;
}); });
@ -1379,7 +1562,9 @@
elem: lay(li), elem: lay(li),
date: ymd, date: ymd,
index: index, index: index,
type: type type: type,
rangeType: index,
disabledType: 'date' // 月面板,检测当前月份中的所有日期是否禁用
}); });
}); });
@ -1406,6 +1591,8 @@
,seconds: ii ,seconds: ii
}][i], }][i],
index: index, index: index,
rangeType: index,
disabledType: 'time', // 时间面板,分别检测时分秒列表是否禁用
time: [ time: [
['hours'], ['hours'],
['hours', 'minutes'], ['hours', 'minutes'],
@ -1419,7 +1606,8 @@
elem: lay(that.footer).find(ELEM_CONFIRM), elem: lay(that.footer).find(ELEM_CONFIRM),
date: that[startEnd], date: that[startEnd],
index: 0, index: 0,
time: ['hours', 'minutes', 'seconds'] time: ['hours', 'minutes', 'seconds'],
disabledType: 'datetime' // 确认按钮,检测时分秒列表任意一项是否禁用
}); });
} }
}; };
@ -1601,17 +1789,25 @@
var that = this var that = this
,options = that.config ,options = that.config
,lang = that.lang() ,lang = that.lang()
,isOut, elemBtn = lay(that.footer).find(ELEM_CONFIRM); ,isOut
if(options.range && options.type !== 'time'){ ,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); start = start || (that.rangeLinked ? that.startDate : options.dateTime);
end = end || that.endDate; end = end || that.endDate;
isOut = !that.endState || that.newDate(start).getTime() > that.newDate(end).getTime(); isOut = !that.endState || that.newDate(start).getTime() > that.newDate(end).getTime();
//如果不在有效日期内,直接禁用按钮,否则比较开始和结束日期 //如果不在有效日期内,直接禁用按钮,否则比较开始和结束日期
(that.limit({ (that.limit({
date: start date: start,
disabledType: 'datetime', // 按钮,检测日期和时间
time: timeParams,
rangeType: 0
}) || that.limit({ }) || that.limit({
date: end date: end,
disabledType: 'datetime', // 按钮,检测日期和时间
time: timeParams,
rangeType: 1
})) }))
? elemBtn.addClass(DISABLED) ? elemBtn.addClass(DISABLED)
: elemBtn[isOut ? 'addClass' : 'removeClass'](DISABLED); : elemBtn[isOut ? 'addClass' : 'removeClass'](DISABLED);
@ -1885,7 +2081,8 @@
that.startDate = lay.extend({}, dateTime); // 同步startDate 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; var isChange;
if (that.endState && that.autoCalendarModel.auto) { if (that.endState && that.autoCalendarModel.auto) {
@ -1995,9 +2192,15 @@
//确定 //确定
,confirm: function(){ ,confirm: function(){
if(options.range){ if(options.range){
if(lay(btn).hasClass(DISABLED)) return that.hint( if(lay(btn).hasClass(DISABLED)){
options.type === 'time' ? lang.timeout.replace(/日期/g, '时间') : lang.timeout 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 { } else {
if(lay(btn).hasClass(DISABLED)) return that.hint(lang.invalidDate); if(lay(btn).hasClass(DISABLED)) return that.hint(lang.invalidDate);
} }
@ -2043,7 +2246,8 @@
elem: lay(that.footer).find(ELEM_CONFIRM), elem: lay(that.footer).find(ELEM_CONFIRM),
date: { date: {
year: listYM[0] year: listYM[0]
} },
disabledType: 'datetime' // 按钮,检测日期和时间
}); });
} }
@ -2372,4 +2576,3 @@
); );
}(window, window.document); }(window, window.document);

Loading…
Cancel
Save