/** @ Name:layui.cron Cron表达式解析器 @ Author:贝哥哥 @ License:MIT */ layui.define(['form', 'element'], function (exports) { //假如该组件依赖 layui.form var $ = layui.$, form = layui.form, //字符常量 MOD_NAME = "cron", ELEM = ".layui-cron", //外部接口 cron = { v: "1.0.0", index: layui.cron ? layui.cron.index + 10000 : 0, //设置全局项 set: function (options) { var that = this; that.config = $.extend({}, that.config, options); return that; }, //事件监听 on: function (events, callback) { return layui.onevent.call(this, MOD_NAME, events, callback); }, //主体CSS等待事件 ready: function (fn) { var cssPath = layui.cache.base + "cron/cron.css?v=" + cron.v; layui.link(cssPath, fn, "cron"); //此处的“cron”要对应 cron.css 中的样式: html #layuicss-cron{} return this; }, }, // 返回当前实例 thisIns = function () { var that = this, options = that.config, id = options.id || options.index; return { reload: function (options) { that.reload.call(that, options); }, config: options, }; }, //构造器 Class = function (options) { var that = this; that.index = ++cron.index; that.config = $.extend({}, that.config, cron.config, options); cron.ready(function () { that.init(); }); }; //默认配置 Class.prototype.config = { value: "* * * * * ?", // 当前表达式值,每秒执行一次 lang: "cn", //语言,只支持cn/en,即中文和英文 trigger: "focus", //呼出控件的事件 done: null, //控件选择完毕后的回调,点击运行/确定也均会触发 run: null, // 最近运行时间接口 }; // 多语言 Class.prototype.lang = function () { var that = this, options = that.config, text = { cn: { tabs: ["秒", "分", "时", "日", "月", "周", "年"], tools: { confirm: "确定", parse: "解析", run: "运行", }, }, en: { tabs: [ "Seconds", "Minutes", "Hours", "Days", "Months", "Weeks", "Years", ], weeks: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], month: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ], tools: { confirm: "Confirm", parse: "Parse", run: "Run", }, }, }; return text[options.lang] || text["cn"]; }; // 初始准备 Class.prototype.init = function () { var that = this, options = that.config; that.$elem = $(options.elem); options.elem = that.$elem[0]; if (!options.elem) return; options.value || (options.value = "* * * * * ?"); // 初始化表达式 var arr = options.value.split(" "); that.cron = []; for (var i = 1; i <= 7; i++) { if (i < 6) { that.cron.push(arr[i - 1] || "*"); } else if (i < 7) { that.cron.push(arr[i - 1] || "?"); } else { that.cron.push(arr[i - 1] || ""); } } //如果不是input|textarea元素,则默认采用click事件 if (!that.isInput(options.elem)) { if (options.trigger === "focus") { options.trigger = "click"; } } console.log("绑定事件."); that.events(); }; // 绑定的元素事件处理 Class.prototype.events = function () { var that = this, options = that.config; if (!options.elem || options.elem.eventHandler) return; // 绑定触发事件 that.$elem.on(options.trigger, function () { that.render(); }); // 绑定关闭控件事件 $(document) .on("click", function (e) { if ( (that.elemCron && that.elemCron.contains(e.target)) || e.target === options.elem ) { return; // 点击的是当前绑定元素或cron容器内的元素则不关闭 } //that.remove(); }) .on("keydown", function (e) { if (e.keyCode === 13) { e.preventDefault(); layer.msg("取消事件默认动作,点击确定按钮"); // $(that.footer).find(ELEM_CONFIRM)[0].click(); } }); //自适应定位 $(window).on("resize", function () { if (!options.elem || !$(ELEM)[0]) { return false; } that.position(); }); options.elem.eventHandler = true; }; // 渲染视图 Class.prototype.render = function () { var that = this, options = that.config, lang = that.lang(); //that.remove(); var $elemCron = (that.$elemCron = $('
')), // 主区域 elemMain = [], // tabs容器 elemSeconds = that.getSecondsElem(), // 秒 elemMinutes = that.getMinutesElem(), // 分 elemHours = that.getHoursElem(), // 时 elemDays = that.getDaysElem(), // 日 elemMonths = that.getMonthsElem(), // 月 elemWeeks = that.getWeeksElem(), // 周 elemYears = that.getYearsElem(); // 年 // 组装容器 elemMain.push( '
', '", '
', elemSeconds, elemMinutes, elemHours, elemDays, elemMonths, elemWeeks, elemYears, "
", "
" ); $elemCron.append(elemMain.join("")); // 底部区域 var elemFooter = [ (!!options.run ?'
最近运行时间
':''), '
', '
', ]; $elemCron.append(elemFooter.join("")); // 渲染 $("body").append($elemCron); that.elemCron = that.$elemCron[0]; form.render(); // 定位 that.position(); // 监听 //点击底部按钮 $elemCron.find(".cron-footer-btns span").on("click", function () { var type = $(this).attr("lay-type"); that.tool(this, type); }); }; // 渲染秒 Class.prototype.getSecondsElem = function () { var that = this, options = that.config, elem = [ '
', ]; var radio = ["", "", "", ""]; val = ["", "", "", "", []]; if (that.cron[0] == "*") { radio[0] = "checked"; } else if (that.cron[0].split("-").length == 2) { radio[1] = "checked"; val[0] = that.cron[0].split("-")[0]; val[1] = that.cron[0].split("-")[1]; } else if (that.cron[0].split("/").length == 2) { radio[2] = "checked"; val[2] = that.cron[0].split("/")[0]; val[3] = that.cron[0].split("/")[1]; } else { radio[3] = "checked"; val[4] = that.cron[0].split(","); } elem.push( '
", '
", '
', '
-
', '
(0-59)
', '
", '
', '
秒开始,每
', '
秒执行一次
(0/60)
', '
" ); elem.push("
"); for (var i = 0; i < 60; i++) { elem.push( ' -1 ? "checked" : "", ">" ); } elem.push("
"); elem.push("
"); return elem.join(""); }; // 渲染分 Class.prototype.getMinutesElem = function () { var that = this, options = that.config, elem = [ '
', ]; var radio = ["", "", "", ""]; val = ["", "", "", "", []]; if (that.cron[1] == "*") { radio[0] = "checked"; } else if (that.cron[1].split("-").length == 2) { radio[1] = "checked"; val[0] = that.cron[1].split("-")[0]; val[1] = that.cron[1].split("-")[1]; } else if (that.cron[1].split("/").length == 2) { radio[2] = "checked"; val[2] = that.cron[1].split("/")[0]; val[3] = that.cron[1].split("/")[1]; } else { radio[3] = "checked"; val[4] = that.cron[1].split(","); } elem.push( '
", '
", '
', '
-
', '
(0-59)
', '
", '
', '
分开始,每
', '
分执行一次
(0/60)
', '
" ); elem.push("
"); for (var i = 0; i < 60; i++) { elem.push( ' -1 ? "checked" : "", ">" ); } elem.push("
"); elem.push("
"); return elem.join(""); }; // 渲染时 Class.prototype.getHoursElem = function () { var that = this, options = that.config, elem = [ '
', ]; var radio = ["", "", "", ""]; val = ["", "", "", "", []]; if (that.cron[2] == "*") { radio[0] = "checked"; } else if (that.cron[2].split("-").length == 2) { radio[1] = "checked"; val[0] = that.cron[2].split("-")[0]; val[1] = that.cron[2].split("-")[1]; } else if (that.cron[2].split("/").length == 2) { radio[2] = "checked"; val[2] = that.cron[2].split("/")[0]; val[3] = that.cron[2].split("/")[1]; } else { radio[3] = "checked"; val[4] = that.cron[2].split(","); } elem.push( '
", '
", '
', '
-
', '
(0-23)
', '
", '
', '
时开始,每
', '
时执行一次
(0/24)
', '
" ); elem.push("
"); for (var i = 0; i < 24; i++) { elem.push( ' -1 ? "checked" : "", ">" ); } elem.push("
"); elem.push("
"); return elem.join(""); }; // 渲染天 Class.prototype.getDaysElem = function () { var that = this, options = that.config, elem = [ '
', ]; var radio = ["", "", "", "", "", "", ""]; val = ["", "", "", "", "", []]; if (that.cron[3] == "*") { radio[0] = "checked"; } else if (that.cron[3] == "?") { radio[1] = "checked"; } else if (that.cron[3].split("-").length == 2) { radio[2] = "checked"; val[0] = that.cron[3].split("-")[0]; val[1] = that.cron[3].split("-")[1]; } else if (that.cron[3].split("/").length == 2) { radio[3] = "checked"; val[2] = that.cron[3].split("/")[0]; val[3] = that.cron[3].split("/")[1]; } else if (that.cron[3].indexOf("W") > -1) { radio[4] = "checked"; val[4] = that.cron[3].match(/(\d+)W$/)[1]; } else if (that.cron[3] == "L") { radio[5] = "checked"; } else { radio[6] = "checked"; val[5] = that.cron[3].split(","); } elem.push( '
", '
", '
", '
', '
-
', '
(1-31)
', '
", '
', '
日开始,每
', '
日执行一次
(1/31)
', '
", '
每月
', '
号最近的那个工作日
(1-31)
', '
", '
" ); elem.push("
"); for (var i = 1; i <= 31; i++) { elem.push( ' -1 ? "checked" : "", ">" ); } elem.push("
"); elem.push("
"); return elem.join(""); }; // 渲染月 Class.prototype.getMonthsElem = function () { var that = this, options = that.config, elem = [ '
', ]; var radio = ["", "", "", "", ""]; val = ["", "", "", "", []]; if (that.cron[4] == "*") { radio[0] = "checked"; } else if (that.cron[4] == "?") { radio[1] = "checked"; } else if (that.cron[4].split("-").length == 2) { radio[2] = "checked"; val[0] = that.cron[4].split("-")[0]; val[1] = that.cron[4].split("-")[1]; } else if (that.cron[4].split("/").length == 2) { radio[3] = "checked"; val[2] = that.cron[4].split("/")[0]; val[3] = that.cron[4].split("/")[1]; } else { radio[4] = "checked"; val[4] = that.cron[4].split(","); } elem.push( '
", '
", '
", '
', '
-
', '
(1-12)
', '
", '
', '
月开始,每
', '
月执行一次
(1/12)
', '
" ); elem.push("
"); for (var i = 1; i <= 12; i++) { elem.push( ' -1 ? "checked" : "", ">" ); } elem.push("
"); elem.push("
"); return elem.join(""); }; // 渲染周 Class.prototype.getWeeksElem = function () { var that = this, options = that.config, elem = [ '
', ]; var radio = ["", "", "", "", "", ""]; val = ["", "", "", "", "", []]; if (that.cron[5] == "*") { radio[0] = "checked"; } else if (that.cron[5] == "?") { radio[1] = "checked"; } else if (that.cron[5].split("-").length == 2) { radio[2] = "checked"; val[0] = that.cron[5].split("-")[0]; val[1] = that.cron[5].split("-")[1]; } else if (that.cron[5].split("#").length == 2) { radio[3] = "checked"; val[2] = that.cron[5].split("#")[0]; val[3] = that.cron[5].split("#")[1]; } else if (/\d+L/.test(that.cron[5])) { radio[4] = "checked"; val[4] = that.cron[5].match(/(\d+)L/)[1]; } else { radio[5] = "checked"; val[5] = that.cron[5].split(","); } elem.push( '
", '
", '
", '
', '
-
', '
(1-7)
', '
", '
', '
周的星期
', '(1-4/1-7)
', '
', '(1-7)
', '
" ); elem.push("
"); for (var i = 1; i <= 7; i++) { elem.push( ' -1 ? "checked" : "", ">" ); } elem.push("
"); elem.push("
"); return elem.join(""); }; // 渲染年 Class.prototype.getYearsElem = function () { var that = this, options = that.config, elem = [ '
', ]; var radio = ["", "", ""]; val = ["", ""]; if (that.cron[6] == "*") { radio[0] = "checked"; } else if (that.cron[6] == "" || that.cron[6] == " ") { radio[1] = "checked"; } else if (that.cron[6].split("-").length == 2) { radio[2] = "checked"; val[0] = that.cron[6].split("-")[0]; val[1] = that.cron[6].split("-")[1]; } elem.push( '
", '
", '
", '
', '
-
', '
' ); elem.push("
"); return elem.join(""); }; function isNotBlank(str) { return str != undefined && str !== ""; } // 底部按钮事件 Class.prototype.tool = function (btn, type) { var that = this, options = that.config, active = { // 计算秒 calSeconds: function () { var data = form.val("cronSecForm"), dataType = data["type[0]"]; if ( "range" == dataType && isNotBlank(data.rangeStart) && isNotBlank(data.rangeEnd) ) { that.cron[0] = data.rangeStart + "-" + data.rangeEnd; } else if ( "per" == dataType && isNotBlank(data.perFrom) && isNotBlank(data.perVal) ) { that.cron[0] = data.perFrom + "/" + data.perVal; } else if ("assign" == dataType) { var checkbox = []; layui.each(data, function (key, value) { if (/^seconds/.test(key)) { checkbox.push(value); } }); if (checkbox.length) { that.cron[0] = checkbox.join(","); } else { that.cron[0] = "*"; } } else if ("all" == dataType) { that.cron[0] = "*"; } }, calMinutes: function () { var data = form.val("cronMinForm"), dataType = data["type[1]"]; if ( "range" == dataType && isNotBlank(data.rangeStart) && isNotBlank(data.rangeEnd) ) { that.cron[1] = data.rangeStart + "-" + data.rangeEnd; } else if ( "per" == dataType && isNotBlank(data.perFrom) && isNotBlank(data.perVal) ) { that.cron[1] = data.perFrom + "/" + data.perVal; } else if ("assign" == dataType) { var checkbox = []; layui.each(data, function (key, value) { if (/^minutes/.test(key)) { checkbox.push(value); } }); if (checkbox.length) { that.cron[1] = checkbox.join(","); } else { that.cron[1] = "*"; } } else if ("all" == dataType) { that.cron[1] = "*"; } }, calHours: function () { var data = form.val("cronHourForm"), dataType = data["type[2]"]; if ( "range" == dataType && isNotBlank(data.rangeStart) && isNotBlank(data.rangeEnd) ) { that.cron[2] = data.rangeStart + "-" + data.rangeEnd; } else if ( "per" == dataType && isNotBlank(data.perFrom) && isNotBlank(data.perVal) ) { that.cron[2] = data.perFrom + "/" + data.perVal; } else if ("assign" == dataType) { var checkbox = []; layui.each(data, function (key, value) { if (/^hours/.test(key)) { checkbox.push(value); } }); if (checkbox.length) { that.cron[2] = checkbox.join(","); } else { that.cron[2] = "*"; } } else if ("all" == dataType) { that.cron[2] = "*"; } }, calDays: function () { var data = form.val("cronDayForm"), dataType = data["type[3]"]; if ( "range" == dataType && isNotBlank(data.rangeStart) && isNotBlank(data.rangeEnd) ) { that.cron[3] = data.rangeStart + "-" + data.rangeEnd; } else if ( "per" == dataType && isNotBlank(data.perFrom) && isNotBlank(data.perVal) ) { that.cron[3] = data.perFrom + "/" + data.perVal; } else if ("work" == dataType && isNotBlank(data.workDay)) { that.cron[3] = data.workDay + "W"; } else if ("last" == dataType) { that.cron[3] = "L"; } else if ("assign" == dataType) { var checkbox = []; layui.each(data, function (key, value) { if (/^days/.test(key)) { checkbox.push(value); } }); if (checkbox.length) { that.cron[3] = checkbox.join(","); } else { that.cron[3] = "*"; } } else if ("all" == dataType) { that.cron[3] = "*"; } else if ("none" == dataType) { that.cron[3] = "?"; } }, calMonths: function () { var data = form.val("cronMonthForm"), dataType = data["type[4]"]; if ( "range" == dataType && isNotBlank(data.rangeStart) && isNotBlank(data.rangeEnd) ) { that.cron[4] = data.rangeStart + "-" + data.rangeEnd; } else if ( "per" == dataType && isNotBlank(data.perFrom) && isNotBlank(data.perVal) ) { that.cron[4] = data.perFrom + "/" + data.perVal; } else if ("assign" == dataType) { var checkbox = []; layui.each(data, function (key, value) { if (/^months/.test(key)) { checkbox.push(value); } }); if (checkbox.length) { that.cron[4] = checkbox.join(","); } else { that.cron[4] = "*"; } } else if ("all" == dataType) { that.cron[4] = "*"; } else if ("none" == dataType) { that.cron[4] = "?"; } }, calWeeks: function () { var data = form.val("cronWeekForm"), dataType = data["type[5]"]; console.log(data); if ( "range" == dataType && isNotBlank(data.rangeStart) && isNotBlank(data.rangeEnd) ) { that.cron[5] = data.rangeStart + "-" + data.rangeEnd; } else if ( "per" == dataType && isNotBlank(data.perFrom) && isNotBlank(data.perVal) ) { that.cron[5] = data.perFrom + "#" + data.perVal; } else if ("last" == dataType && isNotBlank(data.lastVal)) { that.cron[5] = data.lastVal + "L"; } else if ("assign" == dataType) { var checkbox = []; layui.each(data, function (key, value) { if (/^weeks/.test(key)) { checkbox.push(value); } }); if (checkbox.length) { that.cron[5] = checkbox.join(","); } else { that.cron[5] = "*"; } } else if ("all" == dataType) { that.cron[5] = "*"; } else if ("none" == dataType) { that.cron[5] = "?"; } }, calYears: function () { var data = form.val("cronYearForm"), dataType = data["type[6]"]; if ( "range" == dataType && isNotBlank(data.rangeStart) && isNotBlank(data.rangeEnd) ) { that.cron[6] = data.rangeStart + "-" + data.rangeEnd; } else if ("all" == dataType) { that.cron[6] = "*"; } else if ("none" == dataType) { that.cron[6] = ""; } }, // 计算表达式 calculate: function () { active.calSeconds(); active.calMinutes(); active.calHours(); active.calDays(); active.calMonths(); active.calWeeks(); active.calYears(); if (that.cron[5] != "?" && that.cron[3] != "?") { layer.msg("不支持周参数和日参数同时存在"); return false; } return true; }, // 运行 run: function () { if (!active.calculate()) { return; } var cronStr = that.cron.join(" ").trim(); // TODO 请求接口获取最近运行时间,或js生成最近运行时间 if (options.url) { $.post( options.url, { cron: cronStr }, function (res) { if (res.code == 200) { $("#run-list").empty().append(res.data.join("
")); } else { layer.alert(res.msg, { icon: 2, title: "错误" }); } }, "json" ); } options.done(cronStr); }, //确定 confirm: function () { if (!active.calculate()) { return; } var cronStr = that.cron.join(" ").trim(); options.done && options.done(cronStr); that.remove(); }, }; active[type] && active[type](); }; // 定位算法 Class.prototype.position = function () { var that = this, options = that.config, elem = options.elem, rect = elem.getBoundingClientRect(), //绑定元素的坐标 cronWidth = that.elemCron.offsetWidth, //控件的宽度 cronHeight = that.elemCron.offsetHeight, //控件的高度 //滚动条高度 scrollArea = function (type) { type = type ? "scrollLeft" : "scrollTop"; return document.body[type] | document.documentElement[type]; }, winArea = function (type) { return document.documentElement[type ? "clientWidth" : "clientHeight"]; }, margin = 5, left = rect.left, top = rect.bottom; //如果右侧超出边界 if (left + cronWidth + margin > winArea("width")) { left = winArea("width") - cronWidth - margin; } //如果底部超出边界 if (top + cronHeight + margin > winArea()) { top = rect.top > cronHeight //顶部是否有足够区域显示完全 ? rect.top - cronHeight : winArea() - cronHeight; top = top - margin * 2; } that.elemCron.style.left = left + (options.position === "fixed" ? 0 : scrollArea(1)) + "px"; that.elemCron.style.top = top + (options.position === "fixed" ? 0 : scrollArea()) + "px"; }; // 控件移除 Class.prototype.remove = function () { var that = this, options = that.config; $(ELEM).remove(); }; // 是否输入框 Class.prototype.isInput = function (elem) { return /input|textarea/.test(elem.tagName.toLocaleLowerCase()); }; /** * 核心入口 * @param options * @returns {{reload: reload, config: *}} */ cron.render = function (options) { var ins = new Class(options); return thisIns.call(ins); }; exports("cron", cron); });