admin.js 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. // +----------------------------------------------------------------------
  2. // | ThinkAdmin
  3. // +----------------------------------------------------------------------
  4. // | 版权所有 2014~2021 广州楚才信息科技有限公司 [ http://www.cuci.cc ]
  5. // +----------------------------------------------------------------------
  6. // | 官方网站: https://thinkadmin.top
  7. // +----------------------------------------------------------------------
  8. // | 开源协议 ( https://mit-license.org )
  9. // | 免费声明 ( https://thinkadmin.top/disclaimer )
  10. // +----------------------------------------------------------------------
  11. // | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
  12. // | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
  13. // +----------------------------------------------------------------------
  14. /*! 数组兼容处理 */
  15. if (typeof Array.prototype.forEach !== 'function') {
  16. Array.prototype.forEach = function (callback, context) {
  17. typeof context === "undefined" ? context = window : null;
  18. for (var i in this) callback.call(context, this[i], i, this)
  19. };
  20. }
  21. if (typeof Array.prototype.every !== 'function') {
  22. Array.prototype.every = function (callback) {
  23. for (var i in this) if (callback(this[i], i, this) === false) {
  24. return false;
  25. }
  26. return true;
  27. };
  28. }
  29. if (typeof Array.prototype.some !== 'function') {
  30. Array.prototype.some = function (callback) {
  31. for (var i in this) if (callback(this[i], i, this) === true) {
  32. return true;
  33. }
  34. return false;
  35. };
  36. }
  37. /*! LayUI & jQuery */
  38. if (typeof jQuery === 'undefined') window.$ = window.jQuery = layui.$;
  39. window.form = layui.form, window.layer = layui.layer, window.laydate = layui.laydate;
  40. /*! 脚本应用根路径 */
  41. window.appRoot = (function (src) {
  42. return src.pop(), src.pop(), src.join('/') + '/';
  43. })(document.scripts[document.scripts.length - 1].src.split('/'));
  44. /*! 静态插件库路径 */
  45. window.baseRoot = (function (src) {
  46. return src.substring(0, src.lastIndexOf("/") + 1);
  47. })(document.scripts[document.scripts.length - 1].src);
  48. /*! 动态插件库路径 */
  49. window.tapiRoot = window.tapiRoot || window.appRoot + "admin";
  50. /*! require 配置 */
  51. require.config({
  52. waitSeconds: 60,
  53. baseUrl: baseRoot,
  54. map: {'*': {css: baseRoot + 'plugs/require/css.js'}},
  55. paths: {
  56. 'md5': ['plugs/jquery/md5.min'],
  57. 'json': ['plugs/jquery/json.min'],
  58. 'xlsx': ['plugs/jquery/xlsx.min'],
  59. 'excel': ['plugs/jquery/excel.xlsx'],
  60. 'base64': ['plugs/jquery/base64.min'],
  61. 'upload': [tapiRoot + '/api.upload/index?'],
  62. 'angular': ['plugs/angular/angular.min'],
  63. 'cropper': ['plugs/cropper/cropper.min'],
  64. 'echarts': ['plugs/echarts/echarts.min'],
  65. 'ckeditor': ['plugs/ckeditor/ckeditor'],
  66. 'websocket': ['plugs/socket/websocket'],
  67. 'pcasunzips': ['plugs/jquery/pcasunzips'],
  68. 'jquery.ztree': ['plugs/ztree/ztree.all.min'],
  69. 'jquery.cropper': ['plugs/jquery/cropper.min'],
  70. 'jquery.masonry': ['plugs/jquery/masonry.min'],
  71. 'jquery.autocompleter': ['plugs/jquery/autocompleter.min'],
  72. },
  73. shim: {
  74. 'excel': {deps: [baseRoot + 'plugs/layui_exts/excel.js']},
  75. 'websocket': {deps: [baseRoot + 'plugs/socket/swfobject.min.js']},
  76. 'cropper': {deps: ['css!' + baseRoot + 'plugs/cropper/cropper.min.css']},
  77. 'jquery.ztree': {deps: ['jquery', 'css!' + baseRoot + 'plugs/ztree/zTreeStyle/zTreeStyle.css']},
  78. 'jquery.autocompleter': {deps: ['jquery', 'css!' + baseRoot + 'plugs/jquery/autocompleter.css']},
  79. }
  80. });
  81. /*! 注册 jquery 组件 */
  82. define('jquery', [], function () {
  83. return layui.$;
  84. });
  85. $(function () {
  86. window.$body = $('body');
  87. /*! 注册单项事件 */
  88. function onEvent(event, select, callback) {
  89. return $body.off(event, select).on(event, select, callback);
  90. }
  91. /*! 消息组件实例 */
  92. $.msg = new function (that) {
  93. that = this, this.idx = [], this.shade = [0.02, '#000'];
  94. /*! 关闭消息框 */
  95. this.close = function (index) {
  96. if (index !== null) return layer.close(index);
  97. for (var i in this.idx) that.close(this.idx[i]);
  98. this.idx = [];
  99. };
  100. /*! 弹出警告框 */
  101. this.alert = function (msg, call) {
  102. var idx = layer.alert(msg, {end: call, scrollbar: false});
  103. return that.idx.push(idx), idx;
  104. };
  105. /*! 显示成功类型的消息 */
  106. this.success = function (msg, time, call) {
  107. var idx = layer.msg(msg, {icon: 1, shade: this.shade, scrollbar: false, end: call, time: (time || 2) * 1000, shadeClose: true});
  108. return that.idx.push(idx), idx;
  109. };
  110. /*! 显示失败类型的消息 */
  111. this.error = function (msg, time, call) {
  112. var idx = layer.msg(msg, {icon: 2, shade: this.shade, scrollbar: false, time: (time || 3) * 1000, end: call, shadeClose: true});
  113. return that.idx.push(idx), idx;
  114. };
  115. /*! 状态消息提示 */
  116. this.tips = function (msg, time, call) {
  117. var idx = layer.msg(msg, {time: (time || 3) * 1000, shade: this.shade, end: call, shadeClose: true});
  118. return that.idx.push(idx), idx;
  119. };
  120. /*! 显示加载提示 */
  121. this.loading = function (msg, call) {
  122. var idx = msg ? layer.msg(msg, {icon: 16, scrollbar: false, shade: this.shade, time: 0, end: call}) : layer.load(2, {time: 0, scrollbar: false, shade: this.shade, end: call});
  123. return that.idx.push(idx), idx;
  124. };
  125. /*! 确认对话框 */
  126. this.confirm = function (msg, ok, no) {
  127. return layer.confirm(msg, {title: '操作确认', btn: ['确认', '取消']}, function (idx) {
  128. (typeof ok === 'function' && ok.call(this, idx)), that.close(idx);
  129. }, function (idx) {
  130. (typeof no === 'function' && no.call(this, idx)), that.close(idx);
  131. });
  132. };
  133. /*! 自动处理JSON数据 */
  134. this.auto = function (ret, time) {
  135. var url = ret.url || (typeof ret.data === 'string' ? ret.data : '');
  136. var msg = ret.msg || (typeof ret.info === 'string' ? ret.info : '');
  137. if (parseInt(ret.code) === 1 && time === 'false') {
  138. return url ? (location.href = url) : $.form.reload();
  139. }
  140. return (parseInt(ret.code) === 1) ? this.success(msg, time, function () {
  141. (url ? (location.href = url) : $.form.reload()), that.close(null);
  142. }) : this.error(msg, 3, function () {
  143. url ? location.href = url : '';
  144. });
  145. };
  146. };
  147. /*! 表单自动化组件 */
  148. $.form = new function (that) {
  149. that = this;
  150. /*! 内容区选择器 */
  151. this.selecter = '.layui-layout-admin>.layui-body';
  152. /*! 刷新当前页面 */
  153. this.reload = function (force) {
  154. if (force) top.location.reload();
  155. else if (self !== top) location.reload();
  156. else window.onhashchange.call(this);
  157. };
  158. /*! 内容区域动态加载后初始化 */
  159. this.reInit = function ($dom) {
  160. $(window).trigger('scroll'), $.vali.listen(this), $dom = $dom || $(this.selecter);
  161. $dom.find('[required]').map(function ($parent) {
  162. if (($parent = $(this).parent()) && $parent.is('label')) {
  163. $parent.addClass('label-required-prev');
  164. } else {
  165. $parent.prevAll('label').addClass('label-required-next');
  166. }
  167. }), $dom.find('input[data-date-range]').map(function () {
  168. this.setAttribute('autocomplete', 'off');
  169. laydate.render({
  170. type: this.dataset.dateRange || 'date', range: true, elem: this, done: function (value) {
  171. $(this.elem).val(value).trigger('change');
  172. }
  173. });
  174. }), $dom.find('input[data-date-input]').map(function () {
  175. this.setAttribute('autocomplete', 'off');
  176. laydate.render({
  177. type: this.dataset.dateInput || 'date', range: false, elem: this, done: function (value) {
  178. $(this.elem).val(value).trigger('change');
  179. }
  180. });
  181. }), $dom.find('[data-file]:not([data-inited])').map(function () {
  182. $(this).uploadFile();
  183. }), $dom.find('[data-lazy-src]:not([data-lazy-loaded])').each(function () {
  184. if (this.dataset.lazyLoaded !== 'true') {
  185. if (this.nodeName === 'IMG') {
  186. this.src = this.dataset.lazySrc;
  187. } else {
  188. this.style.backgroundImage = 'url(' + this.dataset.lazySrc + ')';
  189. }
  190. this.dataset.lazyLoaded = "true";
  191. }
  192. });
  193. };
  194. /*! 在内容区显示视图 */
  195. this.show = function (html) {
  196. $(this.selecter).html(html), this.reInit($(this.selecter)), setTimeout(function () {
  197. that.reInit($(that.selecter));
  198. }, 500);
  199. };
  200. /*! 以 HASH 打开新网页 */
  201. this.href = function (url, ele) {
  202. if (url !== '#') {
  203. location.href = '#' + $.menu.parseUri(url, ele);
  204. } else if (ele && ele.dataset.menuNode) {
  205. $('[data-menu-node^="' + ele.dataset.menuNode + '-"][data-open!="#"]:first').trigger('click');
  206. }
  207. };
  208. /*! 异步加载的数据 */
  209. this.load = function (url, data, method, callback, loading, tips, time, headers) {
  210. // 如果主页面 loader 显示中,绝对不显示 loading 图标
  211. loading = $('.layui-page-loader').is(':visible') ? false : loading;
  212. var index = loading !== false ? $.msg.loading(tips) : 0;
  213. if (typeof data === 'object' && typeof data['_token_'] === 'string') {
  214. headers = headers || {}, headers['User-Form-Token'] = data['_token_'], delete data['_token_'];
  215. }
  216. $.ajax({
  217. data: data || {}, type: method || 'GET', url: $.menu.parseUri(url), beforeSend: function (xhr, i) {
  218. if (typeof Pace === 'object' && loading !== false) Pace.restart();
  219. if (typeof headers === 'object') for (i in headers) xhr.setRequestHeader(i, headers[i]);
  220. }, error: function (XMLHttpRequest, $dialog, dialogIdx, iframe) {
  221. if (parseInt(XMLHttpRequest.status) !== 200 && XMLHttpRequest.responseText.indexOf('Call Stack') > -1) try {
  222. dialogIdx = layer.open({title: XMLHttpRequest.status + ' - ' + XMLHttpRequest.statusText, type: 2, move: false, content: 'javascript:;'});
  223. layer.full(dialogIdx), $dialog = $('#layui-layer' + dialogIdx), iframe = $dialog.find('iframe').get(0);
  224. (iframe.contentDocument || iframe.contentWindow.document).write(XMLHttpRequest.responseText);
  225. $dialog.find('.layui-layer-setwin').css({right: '35px', top: '28px'}).find('a').css({marginLeft: 0});
  226. $dialog.find('.layui-layer-title').css({color: 'red', height: '70px', lineHeight: '70px', fontSize: '22px', textAlign: 'center', fontWeight: 700});
  227. } catch (e) {
  228. layer.close(dialogIdx);
  229. }
  230. layer.closeAll('loading');
  231. if (parseInt(XMLHttpRequest.status) !== 200) {
  232. $.msg.tips('E' + XMLHttpRequest.status + ' - 服务器繁忙,请稍候再试!');
  233. } else {
  234. this.success(XMLHttpRequest.responseText);
  235. }
  236. }, success: function (ret) {
  237. if (typeof callback === 'function' && callback.call(that, ret) === false) return false;
  238. return typeof ret === 'object' ? $.msg.auto(ret, time || ret.wait || undefined) : that.show(ret);
  239. }, complete: function () {
  240. $.msg.close(index);
  241. }
  242. });
  243. };
  244. /*! 加载 HTML 到目标位置 */
  245. this.open = function (url, data, callback, loading, tips) {
  246. this.load(url, data, 'get', function (ret) {
  247. return (typeof ret === 'object' ? $.msg.auto(ret) : that.show(ret)), false;
  248. }, loading, tips);
  249. };
  250. /*! 打开一个iframe窗口 */
  251. this.iframe = function (url, title, area) {
  252. return layer.open({title: title || '窗口', type: 2, area: area || ['800px', '580px'], anim: 2, fixed: true, maxmin: false, content: url});
  253. };
  254. /*! 加载 HTML 到弹出层 */
  255. this.modal = function (url, data, title, callback, loading, tips, area) {
  256. this.load(url, data, 'GET', function (res) {
  257. if (typeof (res) === 'object') return $.msg.auto(res), false;
  258. $.msg.idx.push(layer.open({
  259. type: 1, btn: false, area: area || "800px", content: res, title: title || '', success: function ($dom, idx) {
  260. $dom.off('click', '[data-close]').on('click', '[data-close]', function () {
  261. (function (confirm, callback) {
  262. confirm ? $.msg.confirm(confirm, callback) : callback();
  263. })(this.dataset.confirm, function () {
  264. layer.close(idx);
  265. });
  266. }), $.form.reInit($dom);
  267. }
  268. }));
  269. return (typeof callback === 'function') && callback.call(that);
  270. }, loading, tips);
  271. };
  272. };
  273. /*! 后台菜单辅助插件 */
  274. $.menu = new function (that) {
  275. that = this;
  276. /*! 计算 URL 地址中有效的 URI */
  277. this.getUri = function (uri) {
  278. uri = uri || location.href;
  279. uri = (uri.indexOf(location.host) > -1 ? uri.split(location.host)[1] : uri);
  280. return (uri.indexOf('#') > -1 ? uri.split('#')[1] : uri).split('?')[0];
  281. };
  282. /*! 通过 URI 查询最有可能的菜单 NODE */
  283. this.queryNode = function (url, node) {
  284. node = node || location.href.replace(/.*spm=([\d\-m]+).*/ig, '$1');
  285. if (!/^m-/.test(node)) {
  286. var $menu = $('[data-menu-node][data-open*="' + url.replace(/\.html$/ig, '') + '"]');
  287. return $menu.size() ? $menu.get(0).dataset.menuNode : '';
  288. }
  289. return node;
  290. };
  291. /*! URL 转 URI */
  292. this.parseUri = function (uri, elem, vars, temp, attrs) {
  293. vars = {}, attrs = [], elem = elem || document.createElement('a');
  294. if (uri.indexOf('?') > -1) uri.split('?')[1].split('&').forEach(function (item) {
  295. if (item.indexOf('=') > -1 && (temp = item.split('=')) && typeof temp[0] === 'string' && temp[0].length > 0) {
  296. vars[temp[0]] = decodeURIComponent(temp[1].replace(/%2B/ig, '%20'));
  297. }
  298. });
  299. uri = this.getUri(uri);
  300. if (typeof vars.spm !== 'string') vars.spm = elem.dataset.menuNode || this.queryNode(uri) || '';
  301. if (typeof vars.spm !== 'string' || vars.spm.length < 1) delete vars.spm;
  302. for (var i in vars) attrs.push(i + '=' + vars[i]);
  303. return uri + (attrs.length > 0 ? '?' + attrs.join('&') : '');
  304. };
  305. /*! 后台菜单动作初始化 */
  306. this.listen = function () {
  307. /*! 菜单模式切换 */
  308. (function ($menu, miniClass) {
  309. /*! Mini 菜单模式切换及显示 */
  310. if (layui.data('admin-menu-type')['type-mini']) $menu.addClass(miniClass);
  311. onEvent('click', '[data-target-menu-type]', function () {
  312. $menu.toggleClass(miniClass), layui.data('admin-menu-type', {key: 'type-mini', value: $menu.hasClass(miniClass)});
  313. }).on('resize', function () {
  314. $body.width() > 1000 ? (layui.data('admin-menu-type')['type-mini'] ? $menu.addClass(miniClass) : $menu.removeClass(miniClass)) : $menu.addClass(miniClass);
  315. }).trigger('resize');
  316. /*! Mini 菜单模式时TIPS文字显示 */
  317. $('[data-target-tips]').mouseenter(function () {
  318. if ($menu.hasClass(miniClass)) $(this).attr('index', layer.tips(this.dataset.targetTips || '', this));
  319. }).mouseleave(function () {
  320. layer.close($(this).attr('index'));
  321. });
  322. })($('.layui-layout-admin'), 'layui-layout-left-mini');
  323. /*! 左则二级菜单展示 */
  324. $('[data-submenu-layout]>a').on('click', function () {
  325. that.syncOpenStatus(1);
  326. });
  327. /*! 同步二级菜单展示状态 */
  328. this.syncOpenStatus = function (mode) {
  329. $('[data-submenu-layout]').map(function (node) {
  330. node = this.dataset.submenuLayout;
  331. if (mode === 1) {
  332. layui.data('admin-menu-stat', {key: node, value: $(this).hasClass('layui-nav-itemed') ? 2 : 1});
  333. } else if ((layui.data('admin-menu-stat')[node] || 2) === 2) {
  334. $(this).addClass('layui-nav-itemed');
  335. }
  336. });
  337. };
  338. window.onhashchange = function (hash, node) {
  339. hash = location.hash || '';
  340. if (hash.length < 1) return $('[data-menu-node][data-open!="#"]:first').trigger('click');
  341. $.form.load(hash), that.syncOpenStatus(2);
  342. /*! 菜单选择切换 */
  343. node = that.queryNode(that.getUri());
  344. if (/^m-/.test(node)) {
  345. var $all = $('a[data-menu-node]').parent(), tmp = node.split('-'), tmpNode = tmp.shift();
  346. while (tmp.length > 0) {
  347. tmpNode = tmpNode + '-' + tmp.shift();
  348. $all = $all.not($('a[data-menu-node="' + tmpNode + '"]').parent().addClass('layui-this'));
  349. }
  350. $all.removeClass('layui-this');
  351. /*! 菜单模式切换 */
  352. if (node.split('-').length > 2) {
  353. var _tmp = node.split('-'), _node = _tmp.shift() + '-' + _tmp.shift();
  354. $('[data-menu-layout]').not($('[data-menu-layout="' + _node + '"]').removeClass('layui-hide')).addClass('layui-hide');
  355. $('[data-menu-node="' + node + '"]').parent().parent().parent().addClass('layui-nav-itemed');
  356. $('.layui-layout-admin').removeClass('layui-layout-left-hide');
  357. } else $('.layui-layout-admin').addClass('layui-layout-left-hide');
  358. that.syncOpenStatus(1);
  359. }
  360. };
  361. /*! URI初始化动作 */
  362. window.onhashchange.call(this);
  363. };
  364. };
  365. /*! 注册对象到Jq */
  366. $.vali = function (form, callback, options) {
  367. return (new function (that) {
  368. /*! 表单元素 */
  369. that = this, this.tags = 'input,textarea,select';
  370. /*! 检测元素事件 */
  371. this.checkEvent = {change: true, blur: true, keyup: false};
  372. /*! 去除字符串的空格 */
  373. this.trim = function (str) {
  374. return str.replace(/(^\s*)|(\s*$)/g, '');
  375. };
  376. /*! 标签元素是否可见 */
  377. this.isVisible = function (ele) {
  378. return $(ele).is(':visible');
  379. };
  380. /*! 检测属性是否有定义 */
  381. this.hasProp = function (ele, prop) {
  382. if (typeof prop !== "string") return false;
  383. var attrProp = ele.getAttribute(prop);
  384. return (typeof attrProp !== 'undefined' && attrProp !== null && attrProp !== false);
  385. };
  386. /*! 判断表单元素是否为空 */
  387. this.isEmpty = function (ele, value) {
  388. var trim = this.trim(ele.value);
  389. value = value || ele.getAttribute('placeholder');
  390. return (trim === "" || trim === value);
  391. };
  392. /*! 正则验证表单元素 */
  393. this.isRegex = function (ele, regex, params) {
  394. var input = $(ele).val(), real = this.trim(input);
  395. regex = regex || ele.getAttribute('pattern');
  396. if (real === "" || !regex) return true;
  397. return new RegExp(regex, params || 'i').test(real);
  398. };
  399. /*! 检侧所有表单元素 */
  400. this.checkAllInput = function () {
  401. var isPass = true;
  402. $(form).find(this.tags).each(function () {
  403. if (that.checkInput(this) === false) return $(this).focus(), isPass = false;
  404. });
  405. return isPass;
  406. };
  407. /*! 检测表单单元 */
  408. this.checkInput = function (input) {
  409. var tag = input.tagName.toLowerCase(), need = this.hasProp(input, "required");
  410. var type = (input.getAttribute("type") || '').replace(/\W+/, "").toLowerCase();
  411. if (this.hasProp(input, 'data-auto-none')) return true;
  412. var ingoreTags = ['select'], ingoreType = ['radio', 'checkbox', 'submit', 'reset', 'image', 'file', 'hidden'];
  413. for (var i in ingoreTags) if (tag === ingoreTags[i]) return true;
  414. for (var i in ingoreType) if (type === ingoreType[i]) return true;
  415. if (need && this.isEmpty(input)) return this.remind(input);
  416. return this.isRegex(input) ? (this.hideError(input), true) : this.remind(input);
  417. };
  418. /*! 显示验证标志 */
  419. this.remind = function (input) {
  420. if (!this.isVisible(input)) return true;
  421. return this.showError(input, input.getAttribute('title') || input.getAttribute('placeholder') || '输入错误'), false;
  422. };
  423. /*! 错误消息显示 */
  424. this.showError = function (ele, tips) {
  425. $(ele).addClass('validate-error');
  426. this.insertError(ele).addClass('layui-anim-fadein').css({width: 'auto'}).html(tips);
  427. };
  428. /*! 错误消息消除 */
  429. this.hideError = function (ele) {
  430. $(ele).removeClass('validate-error');
  431. this.insertError(ele).removeClass('layui-anim-fadein').css({width: '30px'}).html('');
  432. };
  433. /*! 错误标签插入 */
  434. this.insertError = function (ele) {
  435. if ($(ele).data('input-info')) return $(ele).data('input-info');
  436. var $next = $(ele).nextAll('.input-right-icon'), right = ($next ? $next.width() + parseFloat($next.css('right') || '0') : 0) + 10;
  437. var $html = $('<span class="absolute block layui-anim text-center font-s12" style="color:#a44;z-index:2;pointer-events:none"></span>');
  438. var style = {top: $(ele).position().top + 'px', right: right + 'px', lineHeight: $(ele).css('height'), paddingBottom: $(ele).css('paddingBottom')};
  439. return $(ele).data('input-info', $html.css(style).insertAfter(ele)), $html;
  440. };
  441. /*! 表单验证入口 */
  442. this.check = function (form, callback) {
  443. $(form).attr("novalidate", "novalidate").find(that.tags).map(function () {
  444. this.bindEventMethod = function () {
  445. that.checkInput(this);
  446. };
  447. for (var e in that.checkEvent) if (that.checkEvent[e] === true) {
  448. $(this).off(e, this.bindEventMethod).on(e, this.bindEventMethod);
  449. }
  450. });
  451. $(form).bind("submit", function (event) {
  452. if (that.checkAllInput() && typeof callback === 'function') {
  453. if (typeof CKEDITOR === 'object' && typeof CKEDITOR.instances === 'object') {
  454. for (var i in CKEDITOR.instances) CKEDITOR.instances[i].updateElement();
  455. }
  456. callback.call(this, $(form).formToJson());
  457. }
  458. return event.preventDefault(), false;
  459. }).find('[data-form-loaded]').map(function () {
  460. $(this).html(this.dataset.formLoaded || this.innerHTML);
  461. $(this).removeAttr('data-form-loaded').removeClass('layui-disabled');
  462. });
  463. return $(form).data('validate', this);
  464. };
  465. }).check(form, callback, options);
  466. };
  467. /*! 自动监听规则内表单 */
  468. $.vali.listen = function () {
  469. $('form[data-auto]').map(function (index, form) {
  470. if (this.dataset.listen === 'true') return true;
  471. $(this).attr('data-listen', 'true').vali(function (data) {
  472. var call = form.dataset.callback || '_default_callback';
  473. var type = form.method || 'POST', tips = form.dataset.tips || undefined;
  474. var time = form.dataset.time || undefined, href = form.action || location.href;
  475. $.form.load(href, data, type, window[call] || undefined, true, tips, time);
  476. });
  477. });
  478. };
  479. /*! 注册对象到JqFn */
  480. $.fn.vali = function (callback, options) {
  481. return $.vali(this, callback, options);
  482. };
  483. /*! 表单转JSON */
  484. $.fn.formToJson = function () {
  485. var self = this, data = {}, push = {};
  486. var patterns = {"key": /[a-zA-Z0-9_]+|(?=\[])/g, "push": /^$/, "fixed": /^\d+$/, "named": /^[a-zA-Z0-9_]+$/};
  487. this.build = function (base, key, value) {
  488. return (base[key] = value), base;
  489. };
  490. this.pushCounter = function (name) {
  491. if (push[name] === undefined) push[name] = 0;
  492. return push[name]++;
  493. };
  494. $.each($(this).serializeArray(), function () {
  495. var key, keys = this.name.match(patterns.key), merge = this.value, name = this.name;
  496. while ((key = keys.pop()) !== undefined) {
  497. name = name.replace(new RegExp("\\[" + key + "\\]$"), '');
  498. if (key.match(patterns.push)) { // push
  499. merge = self.build([], self.pushCounter(name), merge);
  500. } else if (key.match(patterns.fixed)) { // fixed
  501. merge = self.build([], key, merge);
  502. } else if (key.match(patterns.named)) { // named
  503. merge = self.build({}, key, merge);
  504. }
  505. }
  506. data = $.extend(true, data, merge);
  507. });
  508. return data;
  509. };
  510. /*! 全局文件上传入口 */
  511. $.fn.uploadFile = function (callable) {
  512. if (this.data('inited')) return false;
  513. var that = this, mode = this.data('file') || 'one';
  514. this.data('inited', true).data('multiple', mode !== 'one' ? 1 : 0);
  515. require(['upload'], function (apply) {
  516. apply.call(this, that, callable);
  517. });
  518. };
  519. /*! 上传单张图片 */
  520. $.fn.uploadOneImage = function () {
  521. return this.each(function ($in, $bt) {
  522. $in = $(this), $bt = $('<a data-file="one" class="uploadimage transition"><span class="layui-icon">&#x1006;</span></a>');
  523. $bt.attr('data-size', $in.data('size') || 0).attr('data-file', 'one').attr('data-type', $in.data('type') || 'png,jpg,gif');
  524. $bt.attr('input', $in.get(0)).data('input', this).find('span').on('click', function (event) {
  525. event.stopPropagation(), $bt.attr('style', ''), $in.val('');
  526. });
  527. $in.attr('name', $bt.attr('data-field')).after($bt).on('change', function () {
  528. if (this.value) $bt.css('backgroundImage', 'url(' + encodeURI(this.value) + ')');
  529. }).trigger('change');
  530. }), this;
  531. };
  532. /*! 上传多张图片 */
  533. $.fn.uploadMultipleImage = function () {
  534. return this.each(function () {
  535. var $in = $(this), $bt = $('<a class="uploadimage"></a>'), imgs = this.value ? this.value.split('|') : [];
  536. $bt.attr('data-size', $in.data('size') || 0).attr('data-file', 'mut').attr('data-type', $in.data('type') || 'png,jpg,gif');
  537. $in.after($bt), $bt.uploadFile(function (src) {
  538. imgs.push(src), $in.val(imgs.join('|')), showImageContainer([src]);
  539. });
  540. if (imgs.length > 0) showImageContainer(imgs);
  541. function showImageContainer(srcs) {
  542. $(srcs).each(function (idx, src, $image) {
  543. $image = $('<div class="uploadimage uploadimagemtl transition"><a class="layui-icon margin-right-5">&#xe602;</a><a class="layui-icon margin-right-5">&#x1006;</a><a class="layui-icon margin-right-5">&#xe603;</a></div>');
  544. $image.attr('data-tips-image', encodeURI(src)).css('backgroundImage', 'url(' + encodeURI(src) + ')').on('click', 'a', function (event, index, prevs, $item) {
  545. event.stopPropagation(), $item = $(this).parent(), index = $(this).index(), prevs = $bt.prevAll('div.uploadimage').length;
  546. if (index === 0 && $item.index() !== prevs) $item.next().after($item);
  547. else if (index === 2 && $item.index() > 1) $item.prev().before($item);
  548. else if (index === 1) $item.remove();
  549. imgs = [], $bt.prevAll('.uploadimage').map(function () {
  550. imgs.push($(this).attr('data-tips-image'));
  551. });
  552. imgs.reverse(), $in.val(imgs.join('|'));
  553. }), $bt.before($image);
  554. });
  555. };
  556. }), this;
  557. };
  558. /*! 标签输入插件 */
  559. $.fn.initTagInput = function () {
  560. return this.each(function () {
  561. var $box = $('<div class="layui-tags"></div>');
  562. var $this = $(this), tags = this.value ? this.value.split(',') : [];
  563. var $text = $('<textarea class="layui-input layui-input-inline layui-tag-input"></textarea>');
  564. $this.parent().append($box.append($text)), $text.off('keydown blur'), (tags.length > 0 && showTags(tags));
  565. $text.on('keydown blur', function (event, value) {
  566. if (event.keyCode === 13 || event.type === 'blur') {
  567. event.preventDefault(), (value = $text.val().replace(/^\s*|\s*$/g, ''));
  568. if (tags.indexOf($(this).val()) > -1) return layer.msg('该标签已经存在!');
  569. if (value.length > 0) tags.push(value), $this.val(tags.join(',')), showTags([value]), this.focus(), $text.val('');
  570. }
  571. });
  572. function showTags(tagsArr) {
  573. $(tagsArr).each(function (idx, text, element) {
  574. element = $('<div class="layui-tag"></div>').html(text + '<i class="layui-icon">&#x1006;</i>');
  575. element.on('click', 'i', function (tagText, tagIndex) {
  576. tagText = $(this).parent().text(), tagIndex = tags.indexOf(tagText);
  577. tags.splice(tagIndex, 1), $(this).parent().remove(), $this.val(tags.join(','));
  578. }), $box.append(element, $text);
  579. });
  580. }
  581. });
  582. };
  583. /*! 文本框插入内容 */
  584. $.fn.insertAtCursor = function (value) {
  585. return this.each(function () {
  586. if (document.selection) {
  587. this.focus();
  588. var selection = document.selection.createRange();
  589. (selection.text = value), selection.select();
  590. } else if (this.selectionStart || this.selectionStart === 0) {
  591. var startPos = this.selectionStart, afterPos = this.selectionEnd, scrollTop = this.scrollTop;
  592. this.value = this.value.substring(0, startPos) + value + this.value.substring(afterPos, this.value.length);
  593. if (scrollTop > 0) this.scrollTop = scrollTop;
  594. this.focus();
  595. this.selectionEnd = startPos + value.length;
  596. this.selectionStart = startPos + value.length;
  597. } else (this.value += value), this.focus();
  598. });
  599. }
  600. /*! 注册 data-load 事件行为 */
  601. onEvent('click', '[data-load]', function (e) {
  602. var dataset = e.currentTarget.dataset;
  603. (function (confirm, callback) {
  604. confirm ? $.msg.confirm(confirm, callback) : callback();
  605. })(dataset.confirm, function () {
  606. $.form.load(dataset.load, {}, 'get', null, true, dataset.tips, dataset.time);
  607. });
  608. });
  609. /*! 注册 data-serach 表单搜索行为 */
  610. onEvent('submit', 'form.form-search', function () {
  611. var url = $(this).attr('action').replace(/&?page=\d+/g, '');
  612. if ((this.method || 'get').toLowerCase() === 'get') {
  613. var split = url.indexOf('?') === -1 ? '?' : '&';
  614. if (location.href.indexOf('spm=') > -1) {
  615. return location.href = '#' + $.menu.parseUri(url + split + $(this).serialize());
  616. } else {
  617. return location.href = $.menu.parseUri(url + split + $(this).serialize());
  618. }
  619. }
  620. $.form.load(url, this, 'post');
  621. });
  622. /*! 注册 data-modal 事件行为 */
  623. onEvent('click', '[data-modal]', function () {
  624. var dataset = this.dataset, area = dataset.area || dataset.width || '800px';
  625. return $.form.modal(dataset.modal, 'open_type=modal', dataset.title || this.innerText || '编辑', undefined, undefined, undefined, area);
  626. });
  627. /*! 注册 data-open 事件行为 */
  628. onEvent('click', '[data-open]', function () {
  629. if (this.dataset.open.match(/^https?:/)) {
  630. location.href = this.dataset.open;
  631. } else {
  632. $.form.href(this.dataset.open, this);
  633. }
  634. });
  635. /*! 注册 data-dbclick 事件行为 */
  636. onEvent('dblclick', '[data-dbclick]', function () {
  637. $(this).find(this.dataset.dbclick || '[data-dbclick]').trigger('click');
  638. });
  639. /*! 注册 data-reload 事件行为 */
  640. onEvent('click', '[data-reload]', function () {
  641. $.form.reload();
  642. });
  643. /*! 注册 data-check 事件行为 */
  644. onEvent('click', '[data-check-target]', function (e) {
  645. var target = e.currentTarget;
  646. $(this.dataset.checkTarget).map(function () {
  647. (this.checked = !!target.checked), $(this).trigger('change');
  648. });
  649. });
  650. /*! 注册 data-action 事件行为 */
  651. onEvent('click', '[data-action]', function () {
  652. var data = {}, time = this.dataset.time, action = this.dataset.action;
  653. var loading = this.dataset.loading, method = this.dataset.method || 'post';
  654. var rule = this.dataset.value || (function (elem, rule, ids) {
  655. $(elem.dataset.target || 'input[type=checkbox].list-check-box').map(function () {
  656. (this.checked) && ids.push(this.value);
  657. });
  658. return ids.length > 0 ? rule.replace('{key}', ids.join(',')) : '';
  659. })(this, this.dataset.rule || '', []) || '';
  660. if (rule.length < 1) return $.msg.tips('请选择需要更改的数据!');
  661. rule.split(';').forEach(function (rule) {
  662. if (rule.length < 2) return $.msg.tips('异常的数据操作规则,请修改规则!');
  663. data[rule.split('#')[0]] = rule.split('#')[1];
  664. });
  665. data['_token_'] = this.dataset.token || this.dataset.csrf || '--';
  666. var load = loading !== 'false', tips = typeof loading === 'string' ? loading : undefined;
  667. this.dataset.confirm ? $.msg.confirm(this.dataset.confirm, function () {
  668. $.form.load(action, data, method, false, load, tips, time);
  669. }) : $.form.load(action, data, method, false, load, tips, time);
  670. });
  671. /*! 表单元素失焦时提交 */
  672. onEvent('blur', '[data-action-blur]', function () {
  673. var data = {}, that = this, $this = $(this), action = this.dataset.actionBlur;
  674. var time = this.dataset.time, loading = this.dataset.loading || false, load = loading !== 'false';
  675. var tips = typeof loading === 'string' ? loading : undefined, method = this.dataset.method || 'post';
  676. var attrs = (this.dataset.value || '').replace('{value}', $this.val()).split(';');
  677. for (var i in attrs) {
  678. if (attrs[i].length < 2) return $.msg.tips('异常的数据操作规则,请修改规则!');
  679. data[attrs[i].split('#')[0]] = attrs[i].split('#')[1];
  680. }
  681. that.callback = function (ret) {
  682. return $this.css('border', (ret && ret.code) ? '1px solid #e6e6e6' : '1px solid red'), false;
  683. };
  684. data['_token_'] = this.dataset.token || this.dataset.csrf || '--';
  685. this.dataset.confirm ? $.msg.confirm(this.dataset.confirm, function () {
  686. $.form.load(action, data, method, that.callback, load, tips, time);
  687. }) : $.form.load(action, data, method, that.callback, load, tips, time);
  688. });
  689. /*! 表单元素失去焦点时数字 */
  690. onEvent('blur', '[data-blur-number]', function () {
  691. var min = this.dataset.valueMin, max = this.dataset.valueMax;
  692. var value = parseFloat(this.value) || 0, fiexd = parseInt(this.dataset.blurNumber || 0);
  693. if (typeof min !== 'undefined' && value < min) value = min;
  694. if (typeof max !== 'undefined' && value > max) value = max;
  695. this.value = parseFloat(value).toFixed(fiexd);
  696. });
  697. /*! 注册 data-href 事件行为 */
  698. onEvent('click', '[data-href]', function () {
  699. if (this.dataset.href && this.dataset.href.indexOf('#') !== 0) {
  700. location.href = this.dataset.href;
  701. }
  702. });
  703. /*! 注册 data-iframe 事件行为 */
  704. onEvent('click', '[data-iframe]', function () {
  705. $(this).attr('data-index', $.form.iframe(this.dataset.iframe, this.dataset.title || this.innerText || '窗口', this.dataset.area || [
  706. this.dataset.width || '800px', this.dataset.height || '580px'
  707. ]));
  708. });
  709. /*! 注册 data-icon 事件行为 */
  710. onEvent('click', '[data-icon]', function () {
  711. var location = tapiRoot + '/api.plugs/icon', field = this.dataset.icon || this.dataset.field || 'icon';
  712. $.form.iframe(location + (location.indexOf('?') > -1 ? '&' : '?') + 'field=' + field, '图标选择', ['800px', '600px']);
  713. });
  714. /*! 注册 data-copy 事件行为 */
  715. onEvent('click', '[data-copy]', function () {
  716. $.copyToClipboard(this.dataset.copy);
  717. });
  718. $.copyToClipboard = function (content, input) {
  719. input = document.createElement('textarea');
  720. input.style.position = 'absolute', input.style.left = '-100000px';
  721. input.style.width = '1px', input.style.height = '1px', input.innerText = content;
  722. document.body.appendChild(input), input.select(), setTimeout(function () {
  723. document.execCommand('Copy') ? $.msg.tips('复制成功') : $.msg.tips('复制失败,请使用鼠标操作复制!');
  724. document.body.removeChild(input);
  725. }, 100);
  726. };
  727. /*! 注册 data-tips-text 事件行为 */
  728. onEvent('mouseenter', '[data-tips-text]', function () {
  729. var opt = {tips: [$(this).attr('data-tips-type') || 3, '#78BA32'], time: 0};
  730. $(this).attr('index', layer.tips($(this).attr('data-tips-text') || this.innerText, this, opt));
  731. }).on('mouseleave', '[data-tips-text]', function () {
  732. layer.close($(this).attr('index'));
  733. });
  734. /*! 注册 data-tips-image 事件行为 */
  735. onEvent('click', '[data-tips-image]', function () {
  736. $.previewImage(this.dataset.tipsImage || this.dataset.lazySrc || this.src, this.dataset.with);
  737. });
  738. $.previewImage = function (src, area) {
  739. var img = new Image(), defer = $.Deferred(), load = $.msg.loading();
  740. img.style.height = 'auto', img.style.width = area || '480px';
  741. img.style.display = 'none', img.style.background = '#FFFFFF';
  742. document.body.appendChild(img), img.onerror = function () {
  743. $.msg.close(load), defer.reject();
  744. }, img.onload = function () {
  745. layer.open({
  746. type: 1, title: false, shadeClose: true, content: $(img), success: function ($ele, idx) {
  747. $.msg.close(load), defer.notify($ele, idx);
  748. }, area: area || '480px', skin: 'layui-layer-nobg', closeBtn: 1, end: function () {
  749. document.body.removeChild(img), defer.resolve()
  750. }
  751. });
  752. };
  753. return (img.src = src), defer.resolve();
  754. };
  755. /*! 注册 data-phone-view 事件行为 */
  756. onEvent('click', '[data-phone-view]', function () {
  757. $.previewPhonePage(this.dataset.phoneView || this.href);
  758. });
  759. $.previewPhonePage = function (href, title, template) {
  760. template = '<div><div class="mobile-preview pull-left"><div class="mobile-header">_TITLE_</div><div class="mobile-body"><iframe id="phone-preview" src="_URL_" frameborder="0" marginheight="0" marginwidth="0"></iframe></div></div></div>';
  761. layer.style(layer.open({type: true, scrollbar: false, area: ['320px', '600px'], title: false, closeBtn: true, shadeClose: false, skin: 'layui-layer-nobg', content: $(template.replace('_TITLE_', title || '公众号').replace('_URL_', href)).html()}), {boxShadow: 'none'});
  762. };
  763. /*! 表单编辑返回操作 */
  764. onEvent('click', '[data-history-back]', function () {
  765. $.msg.confirm(this.dataset.historyBack || '确定要返回吗?', function () {
  766. history.back();
  767. })
  768. });
  769. /*! 异步任务状态监听与展示 */
  770. onEvent('click', '[data-queue]', function (e) {
  771. (function (confirm, callback) {
  772. confirm ? $.msg.confirm(confirm, callback) : callback();
  773. })(e.currentTarget.dataset.confirm, function () {
  774. $.form.load(e.currentTarget.dataset.queue, {}, 'post', function (ret) {
  775. if (typeof ret.data === 'string' && ret.data.indexOf('Q') === 0) {
  776. return $.loadQueue(ret.data, true), false;
  777. }
  778. });
  779. });
  780. });
  781. $.loadQueue = function (code, doScript, doAjax) {
  782. layer.open({
  783. type: 1, title: false, area: ['560px', '315px'], anim: 2, shadeClose: false, end: function () {
  784. doAjax = false;
  785. }, content: '' +
  786. '<div class="padding-30 padding-bottom-0" style="width:500px" data-queue-load="' + code + '">' +
  787. ' <div class="layui-elip nowrap" data-message-title></div>' +
  788. ' <div class="margin-top-15 layui-progress layui-progress-big" lay-showPercent="yes"><div class="layui-progress-bar transition" lay-percent="0.00%"></div></div>' +
  789. ' <div class="margin-top-15"><code class="layui-textarea layui-bg-black border-0" disabled style="resize:none;overflow:hidden;height:190px"></code></div>' +
  790. '</div>'
  791. });
  792. (function loadprocess(code, that) {
  793. that = this, this.$box = $('[data-queue-load=' + code + ']');
  794. if (doAjax === false || that.$box.length < 1) return false;
  795. this.$code = that.$box.find('code'), this.$name = that.$box.find('[data-message-title]');
  796. this.$percent = that.$box.find('.layui-progress div'), this.runCache = function (code, index, value) {
  797. this.ckey = code + '_' + index, this.ctype = 'admin-queue-script';
  798. return value !== undefined ? layui.data(this.ctype, {key: this.ckey, value: value}) : layui.data(this.ctype)[this.ckey] || 0;
  799. };
  800. this.setState = function (status, message) {
  801. if (message.indexOf('javascript:') === -1) if (status === 1) {
  802. that.$name.html('<b class="color-text">' + message + '</b>').addClass('text-center');
  803. that.$percent.addClass('layui-bg-blue').removeClass('layui-bg-green layui-bg-red');
  804. } else if (status === 2) {
  805. if (message.indexOf('>>>') > -1) {
  806. that.$name.html('<b class="color-blue">' + message + '</b>').addClass('text-center');
  807. } else {
  808. that.$name.html('<b class="color-blue">正在处理:</b>' + message).removeClass('text-center');
  809. }
  810. that.$percent.addClass('layui-bg-blue').removeClass('layui-bg-green layui-bg-red');
  811. } else if (status === 3) {
  812. that.$name.html('<b class="color-green">' + message + '</b>').addClass('text-center');
  813. that.$percent.addClass('layui-bg-green').removeClass('layui-bg-blue layui-bg-red');
  814. } else if (status === 4) {
  815. that.$name.html('<b class="color-red">' + message + '</b>').addClass('text-center');
  816. that.$percent.addClass('layui-bg-red').removeClass('layui-bg-blue layui-bg-green');
  817. }
  818. };
  819. $.form.load(tapiRoot + '/api.queue/progress', {code: code}, 'post', function (ret) {
  820. if (ret.code) {
  821. that.lines = [];
  822. for (this.lineIndex in ret.data.history) {
  823. this.line = ret.data.history[this.lineIndex], this.percent = '[ ' + this.line.progress + '% ] ';
  824. if (this.line.message.indexOf('javascript:') === -1) {
  825. that.lines.push(this.line.message.indexOf('>>>') > -1 ? this.line.message : this.percent + this.line.message);
  826. } else if (!that.runCache(code, this.lineIndex) && doScript !== false) {
  827. that.runCache(code, this.lineIndex, 1), location.href = this.line.message;
  828. }
  829. }
  830. if (ret.data.status > 0) {
  831. that.$code.html('<p class="layui-elip">' + that.lines.join('</p><p class="layui-elip">') + '</p>'), that.$code.animate({scrollTop: that.$code[0].scrollHeight + 'px'}, 200);
  832. that.$percent.attr('lay-percent', (parseFloat(ret.data.progress || '0.00').toFixed(2)) + '%'), layui.element.render();
  833. that.setState(parseInt(ret.data.status), ret.data.message);
  834. } else return setTimeout(function () {
  835. loadprocess(code);
  836. }, Math.floor(Math.random() * 500) + 200), false;
  837. if (parseInt(ret.data.status) === 3 || parseInt(ret.data.status) === 4) return false; else return setTimeout(function () {
  838. loadprocess(code);
  839. }, Math.floor(Math.random() * 200)), false;
  840. }
  841. }, false);
  842. })(code)
  843. };
  844. /*! 延时关闭加载动画 */
  845. window.addEventListener('load', function () {
  846. setTimeout(function () {
  847. $('body>.layui-page-loader').fadeOut();
  848. }, 200);
  849. }, true);
  850. /*! 图片加载异常处理 */
  851. document.addEventListener('error', function (event) {
  852. var elem = event.target;
  853. if (elem.nodeName === 'IMG') {
  854. elem.src = baseRoot + 'theme/img/404_icon.png';
  855. }
  856. }, true);
  857. /*! 系统菜单表单页面初始化 */
  858. $.menu.listen(), $.vali.listen(), $.form.reInit($body);
  859. });