authtree.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. /*
  2. * @Author: Jeffrey Wang
  3. * @Date: 2018-03-16 18:24:47
  4. * @Version: v1.2.4
  5. * @Last Modified by: Jeffrey Wang
  6. * @Last Modified time: 2019-04-29 14:33:00
  7. */
  8. // 节点树
  9. layui.define(['jquery', 'form'], function (exports) {
  10. var $ = layui.jquery;
  11. var form = layui.form;
  12. var MOD_NAME = 'authtree';
  13. var obj = {
  14. // 渲染 + 绑定事件
  15. openIconContent: '',
  16. closeIconContent: '',
  17. // 表单类型 checkbox: 多选,radio:单选
  18. checkType: 'checkbox',
  19. // 选中、半选中、未选中
  20. checkedIconContent: '',
  21. halfCheckedIconContent: '',
  22. notCheckedIconContent: '',
  23. // 保存节点数据
  24. checkedNode: {},
  25. notCheckedNode: {},
  26. // 临时保存最新操作影响的节点
  27. lastCheckedNode: {},
  28. lastNotCheckedNode: {},
  29. // 已经渲染过的树,可用来获取配置,{ dst: {trees: '树的节点数据', opt: '配置'} }
  30. renderedTrees: {},
  31. // 使用 layui 的监听事件
  32. on: function (events, callback) {
  33. return layui.onevent.call(this, MOD_NAME, events, callback);
  34. },
  35. /**
  36. * 渲染DOM并绑定事件
  37. * @param {[type]} dst [目标ID,如:#test1]
  38. * @param {[type]} trees [数据,格式:{}]
  39. * @param {[type]} inputname [上传表单名]
  40. * @param {[type]} layfilter [lay-filter的值]
  41. * @param {[type]} openall [是否展开全部]
  42. * @return {[type]} [description]
  43. */
  44. render: function (dst, trees, opt) {
  45. // 表单名称配置
  46. var inputname = opt.inputname ? opt.inputname : 'menuids[]';
  47. opt.inputname = inputname;
  48. // lay-filter 配置
  49. var layfilter = opt.layfilter ? opt.layfilter : 'checkauth';
  50. opt.layfilter = layfilter;
  51. // 默认展开全部 配置
  52. var openall = opt.openall ? opt.openall : false;
  53. opt.openall = openall;
  54. // 双击展开此层配置
  55. var dblshow = opt.dblshow ? opt.dblshow : false;
  56. opt.dblshow = dblshow;
  57. // 双击时间差 - 不能设置过长,否则单击延迟很感人
  58. var dbltimeout = opt.dbltimeout ? opt.dbltimeout : 120;
  59. opt.dbltimeout = dbltimeout;
  60. // 默认展开有选中数据的层
  61. var openchecked = typeof opt.openchecked !== 'undefined' ? opt.openchecked : true;
  62. opt.openchecked = openchecked;
  63. // 自动取消选中
  64. var autoclose = typeof opt.autoclose !== 'undefined' ? opt.autoclose : true;
  65. opt.autoclose = autoclose;
  66. // 自动选择直属父级节点
  67. var autochecked = typeof opt.autochecked !== 'undefined' ? opt.autochecked : true;
  68. opt.autochecked = autochecked;
  69. // 是否隐藏左侧 单选/多选的选框 -- 特殊需求,一般用于单选树并且不用
  70. var hidechoose = typeof opt.hidechoose !== 'undefined' ? opt.hidechoose : false;
  71. opt.hidechoose = hidechoose;
  72. // 是否开启半选
  73. var halfchoose = typeof opt.halfchoose !== 'undefined' ? opt.halfchoose : false;
  74. opt.halfchoose = halfchoose;
  75. // 收起叶子节点(排列于一行)
  76. var collapseLeafNode = typeof opt.collapseLeafNode !== 'undefined' ? opt.collapseLeafNode : false;
  77. opt.collapseLeafNode = collapseLeafNode;
  78. // 有子节点的前显字符配置
  79. opt.prefixChildStr = opt.prefixChildStr ? opt.prefixChildStr : '├─';
  80. // 单选、多选配置
  81. opt.checkType = opt.checkType ? opt.checkType : 'checkbox';
  82. this.checkType = opt.checkType;
  83. // 皮肤可选择
  84. opt.checkSkin = opt.checkSkin ? opt.checkSkin : 'primary';
  85. // 主题定制
  86. opt.theme = opt.theme ? opt.theme : '';
  87. opt.themePath = opt.themePath ? opt.themePath : 'layui_exts/tree_themes/';
  88. // 展开、折叠节点的前显字符配置
  89. opt.openIconContent = opt.openIconContent ? opt.openIconContent : '';
  90. this.openIconContent = opt.openIconContent;
  91. opt.closeIconContent = opt.closeIconContent ? opt.closeIconContent : '';
  92. this.closeIconContent = opt.closeIconContent;
  93. // 选中、半选中、未选中节点的图标配置
  94. opt.checkedIconContent = opt.checkedIconContent ? opt.checkedIconContent : '\e605';
  95. this.checkedIconContent = opt.checkedIconContent;
  96. opt.halfCheckedIconContent = opt.halfCheckedIconContent ? opt.halfCheckedIconContent : '\e605';
  97. this.halfCheckedIconContent = opt.halfCheckedIconContent;
  98. opt.notCheckedIconContent = opt.notCheckedIconContent ? opt.notCheckedIconContent : '';
  99. this.notCheckedIconContent = opt.notCheckedIconContent;
  100. // 渲染配置参数
  101. opt.checkedKey = opt.checkedKey ? opt.checkedKey : 'checked';
  102. opt.childKey = opt.childKey ? opt.childKey : 'list';
  103. opt.disabledKey = opt.disabledKey ? opt.disabledKey : 'disabled';
  104. opt.nameKey = opt.nameKey ? opt.nameKey : 'name';
  105. opt.valueKey = opt.valueKey ? opt.valueKey : 'value';
  106. //避免与其他form组件冲突
  107. opt.formFilter = opt.formFilter ? opt.formFilter : '';
  108. // 不启用双击展开,单击不用延迟
  109. var dblisten = true;
  110. if (dblshow) {
  111. // 开启双击展开,双击事件默认为120s
  112. } else {
  113. // 未开启双击展开且 dbltimeout <= 0,则说明不用监听双击事件
  114. if (opt.dbltimeout <= 0) {
  115. dblisten = false;
  116. }
  117. dbltimeout = 0;
  118. // opt.dbltimeout = dbltimeout;
  119. }
  120. // 记录渲染过的树
  121. obj.renderedTrees[dst] = {trees: trees, opt: opt};
  122. // 主题定制
  123. if (typeof opt.theme === 'string' && opt.theme !== '') {
  124. $(dst).addClass(opt.theme)
  125. layui.link(opt.themePath + opt.theme + '.css')
  126. }
  127. if (opt.hidechoose) {
  128. $(dst).addClass('auth-tree-hidechoose');
  129. }
  130. $(dst).html(obj.renderAuth(trees, 0, {
  131. inputname: inputname,
  132. layfilter: layfilter,
  133. openall: openall,
  134. openchecked: openchecked,
  135. checkType: this.checkType,
  136. prefixChildStr: opt.prefixChildStr,
  137. // 配置参数
  138. checkedKey: opt.checkedKey,
  139. childKey: opt.childKey,
  140. disabledKey: opt.disabledKey,
  141. nameKey: opt.nameKey,
  142. valueKey: opt.valueKey,
  143. collapseLeafNode: opt.collapseLeafNode,
  144. }));
  145. if (openchecked) {
  146. obj.showChecked(dst);
  147. }
  148. if (opt.formFilter) {
  149. form.render(null, opt.formFilter);
  150. } else {
  151. form.render()
  152. }
  153. // 变动则存一下临时状态
  154. obj._saveNodeStatus(dst);
  155. // 开启自动宽度优化
  156. obj.autoWidthAll();
  157. // 备注:如果使用form.on('checkbox()'),外部就无法使用form.on()监听同样的元素了(LAYUI不支持重复监听了)。
  158. // form.on('checkbox('+layfilter+')', function(data){
  159. // /*属下所有权限状态跟随,如果选中,往上走全部选中*/
  160. // var childs = $(data.elem).parent().next().find('input[type="checkbox"]').prop('checked', data.elem.checked);
  161. // if(data.elem.checked){
  162. // /*查找child的前边一个元素,并将里边的checkbox选中状态改为true。*/
  163. // $(data.elem).parents('.auth-child').prev().find('input[type="checkbox"]').prop('checked', true);
  164. // }
  165. // /*console.log(childs);*/
  166. // form.render('checkbox');
  167. // });
  168. // 解决单击和双击冲突问题的 timer 变量
  169. var timer = 0;
  170. $(dst).find('.auth-single:first').unbind('click').on('click', '.layui-form-checkbox,.layui-form-radio', function (event) {
  171. // window.event? window.event.cancelBubble = true : event.stopPropagation();
  172. var that = this;
  173. clearTimeout(timer);
  174. // 双击判断需要的延迟处理
  175. timer = setTimeout(function () {
  176. var elem = $(that).prev();
  177. var checked = elem.is(':checked');
  178. if (autochecked) {
  179. if (checked) {
  180. /*查找child的前边一个元素,并将里边的checkbox选中状态改为true。*/
  181. elem.parents('.auth-child').prev().find('.authtree-checkitem:not(:disabled)[type="checkbox"]').prop('checked', true);
  182. }
  183. elem.parent().next().find('.authtree-checkitem:not(:disabled)[type="checkbox"]').prop('checked', checked);
  184. }
  185. if (autoclose) {
  186. if (checked) {
  187. // pass
  188. } else {
  189. // 自动关闭父级选中节点
  190. obj._autoclose($(that).parent());
  191. }
  192. }
  193. form.render('checkbox');
  194. form.render('radio');
  195. // 变动则存一下临时状态
  196. obj._saveNodeStatus(dst);
  197. // 触发 change 事件
  198. obj._triggerEvent(dst, 'change', {
  199. othis: $(that),
  200. oinput: elem,
  201. value: elem.val(),
  202. });
  203. obj.autoWidthAll();
  204. }, dbltimeout);
  205. return false;
  206. });
  207. /*动态绑定展开事件*/
  208. $(dst).unbind('click').on('click', '.auth-icon', function () {
  209. obj.iconToggle(dst, this);
  210. });
  211. /*双击展开*/
  212. $(dst).find('.auth-single:first').unbind('dblclick').on('dblclick', '.layui-form-checkbox,.layui-form-radio', function (e) {
  213. // 触发时间 > 0,才触发双击事件
  214. // opt.dbltimeout 是用户真实设定的超时时间,与 dbltimeout 不一样
  215. // if (opt.dbltimeout > 0) {
  216. obj._triggerEvent(dst, 'dblclick', {
  217. othis: $(this),
  218. elem: $(this).prev(),
  219. value: $(this).prev().val(),
  220. });
  221. // }
  222. if (dblshow) {
  223. clearTimeout(timer);
  224. obj.iconToggle(dst, $(this).prevAll('.auth-icon:first'));
  225. }
  226. }).on('selectstart', function () {
  227. // 屏蔽双击选中文字
  228. return false;
  229. });
  230. },
  231. // 自动关闭 - 如果兄弟节点均没选中,递归取消上级元素选中状态,传入的是 .auth-status 节点,递归 .auth-status 上级节点
  232. _autoclose: function (obj) {
  233. var single = $(obj).parent().parent();
  234. var authStatus = single.parent().prev();
  235. if (!authStatus.hasClass('auth-status')) {
  236. return false;
  237. }
  238. // 仅一层
  239. if (single.find('div>.auth-status>input.authtree-checkitem:not(:disabled)[type="checkbox"]:checked').length === 0) {
  240. authStatus.find('.authtree-checkitem:not(:disabled)[type="checkbox"]').prop('checked', false);
  241. this._autoclose(authStatus);
  242. }
  243. },
  244. // 以 icon 的维度,切换显示下级空间
  245. iconToggle: function (dst, iconobj) {
  246. var origin = $(iconobj);
  247. var child = origin.parent().parent().find('.auth-child:first');
  248. if (origin.is('.active')) {
  249. /*收起*/
  250. origin.removeClass('active').html(obj.closeIconContent);
  251. child.slideUp('fast');
  252. } else {
  253. /*展开*/
  254. origin.addClass('active').html(obj.openIconContent);
  255. child.slideDown('fast');
  256. }
  257. obj._triggerEvent(dst, 'deptChange');
  258. return false;
  259. },
  260. // 递归创建格式
  261. renderAuth: function (tree, dept, opt) {
  262. var inputname = opt.inputname;
  263. var layfilter = opt.layfilter;
  264. var openall = opt.openall;
  265. var str = '<div class="auth-single">';
  266. // 参数配置
  267. var childKey = opt.childKey;
  268. var nameKey = opt.nameKey;
  269. var valueKey = opt.valueKey;
  270. var _this = this;
  271. layui.each(tree, function (index, item) {
  272. var hasChild = (item[childKey] && (item[childKey].length || !$.isEmptyObject(item[childKey].length))) ? 1 : 0;
  273. // 注意:递归调用时,this的环境会改变!
  274. var append = hasChild ? obj.renderAuth(item[childKey], dept + 1, opt) : '';
  275. var openstatus = openall || (opt.openchecked && item.checked);
  276. var isChecked = _this._getStatusByDynamicKey(item, opt.checkedKey, opt.valueKey);
  277. var isDisabled = _this._getStatusByDynamicKey(item, opt.disabledKey, opt.valueKey);
  278. var rowFlag = !hasChild && opt.collapseLeafNode;
  279. if (rowFlag) {
  280. str += '<div class="auth-row auth-skin"><div class="auth-row-item auth-status" style="display: flex;flex-direction: row;align-items: flex-end;">' +
  281. (hasChild ? '' : '<i class="layui-icon auth-leaf" style="opacity:0;color: transparent;">&#xe626;</i>');
  282. } else {
  283. // '+new Array(dept * 4).join('&nbsp;')+'
  284. str += '<div class="auth-skin"><div class="auth-status" style="display: flex;flex-direction: row;align-items: flex-end;"> ' +
  285. (hasChild ? '<i class="layui-icon auth-icon ' + (openstatus ? 'active' : '') + '" style="cursor:pointer;">' + (openstatus ? obj.openIconContent : obj.closeIconContent) + '</i>' : '<i class="layui-icon auth-leaf" style="opacity:0;color: transparent;">&#xe626;</i>') +
  286. (dept > 0 ? ('<span class="auth-prefix">' + opt.prefixChildStr + ' </span>') : '');
  287. }
  288. str +=
  289. '<input class="authtree-checkitem" type="' + opt.checkType + '" name="' + inputname + '" title="' + item[nameKey] + '" value="' + item[valueKey] + '" lay-skin="primary" lay-filter="' + layfilter + '" ' +
  290. (isChecked ? ' checked="checked"' : '') +
  291. (isDisabled ? ' disabled' : '') +
  292. '> </div>' +
  293. ' <div class="auth-child" style="' + (openstatus ? '' : 'display:none;') + '"> ' + append + '</div></div>'
  294. });
  295. str += '</div>';
  296. return str;
  297. },
  298. // 通过动态key,获取状态信息,dynamicKey支持:数字/字符时直接取属性,对象时查看是否在数组中
  299. _getStatusByDynamicKey: function (item, dynamicKey, valueKey) {
  300. var isChecked = false;
  301. if (typeof dynamicKey === "string" || typeof dynamicKey === 'number') {
  302. isChecked = item[dynamicKey];
  303. } else if (typeof dynamicKey === 'object') {
  304. isChecked = $.inArray(item[valueKey], dynamicKey) !== -1;
  305. } else {
  306. isChecked = false;
  307. }
  308. return isChecked;
  309. },
  310. /**
  311. * 显示到已选中的最高层级
  312. * @param {[type]} dst [description]
  313. * @return {[type]} [description]
  314. */
  315. showChecked: function (dst) {
  316. $(dst).find('.authtree-checkitem:checked').parents('.auth-child').show();
  317. },
  318. /**
  319. * 将普通列表无限递归转换为树
  320. * @param {[type]} list [普通的列表,必须包括 opt.primaryKey 指定的键和 opt.parentKey 指定的键]
  321. * @param {[type]} opt [配置参数,支持 primaryKey(主键 默认id) parentKey(父级id对应键 默认pid) nameKey(节点标题对应的key 默认name) valueKey(节点值对应的key 默认id) checkedKey、disabledKey(节点是否选中的字段 默认checked,传入数组则判断主键是否在此数组中) startPid(第一层扫描的PID 默认0) currentDept(当前层 默认0) maxDept(最大递归层 默认100) childKey(递归完成后子节点对应键 默认list) deptPrefix(根据层级重复的前缀 默认'')]
  322. * @return {[type]} [description]
  323. */
  324. listConvert: function (list, opt) {
  325. opt.primaryKey = opt.primaryKey ? opt.primaryKey : 'id';
  326. opt.parentKey = opt.parentKey ? opt.parentKey : 'pid';
  327. opt.startPid = opt.startPid ? opt.startPid : 0;
  328. opt.authType = opt.authType ? opt.authType : 'type';
  329. opt.currentDept = opt.currentDept ? parseInt(opt.currentDept) : 0;
  330. opt.maxDept = opt.maxDept ? opt.maxDept : 100;
  331. opt.childKey = opt.childKey ? opt.childKey : 'list';
  332. opt.checkedKey = opt.checkedKey ? opt.checkedKey : 'checked';
  333. opt.disabledKey = opt.disabledKey ? opt.disabledKey : 'disabled';
  334. opt.nameKey = opt.nameKey ? opt.nameKey : 'name';
  335. opt.valueKey = opt.valueKey ? opt.valueKey : 'id';
  336. return this._listToTree(list, opt.startPid, opt.currentDept, opt);
  337. },
  338. // 实际的递归函数,将会变化的参数抽取出来
  339. _listToTree: function (list, startPid, currentDept, opt) {
  340. if (opt.maxDept < currentDept) {
  341. return [];
  342. }
  343. var child = [];
  344. for (var index in list) {
  345. if (list.hasOwnProperty(index)) {
  346. // 筛查符合条件的数据(主键 = startPid)
  347. var item = list[index];
  348. if (typeof item[opt.parentKey] !== 'undefined' && item[opt.parentKey] === startPid) {
  349. // 满足条件则递归
  350. var nextChild = this._listToTree(list, item[opt.primaryKey], currentDept + 1, opt);
  351. // 节点信息保存
  352. var node = {};
  353. if (nextChild.length > 0) {
  354. node[opt.childKey] = nextChild;
  355. }
  356. node['name'] = item[opt.nameKey];
  357. node['value'] = item[opt.valueKey];
  358. node['type'] = item[opt.authType] || 0;
  359. // 禁用/选中节点的两种渲染方式
  360. node['checked'] = this._getStatusByDynamicKey(item, opt.checkedKey, opt.valueKey);
  361. node['disabled'] = this._getStatusByDynamicKey(item, opt.disabledKey, opt.valueKey);
  362. child.push(node);
  363. }
  364. }
  365. }
  366. return child;
  367. },
  368. /**
  369. * 将树转为单选可用的 select,如果后台返回列表数据,可以先转换为 tree
  370. * @param {[type]} tree [description]
  371. * @param {[type]} opt [description]
  372. * @return {[type]} [description]
  373. */
  374. treeConvertSelect: function (tree, opt) {
  375. if (typeof tree.length !== 'number' || tree.length <= 0) {
  376. return [];
  377. }
  378. // 初始化层级
  379. opt.currentDept = opt.currentDept ? parseInt(opt.currentDept) : 0;
  380. // 子节点列表的Key
  381. opt.childKey = opt.childKey ? opt.childKey : 'list';
  382. // 名称的key
  383. opt.nameKey = opt.valueKey ? opt.valueKey : 'name';
  384. // 值的key
  385. opt.valueKey = opt.valueKey ? opt.valueKey : 'value';
  386. // 选中的key - 仅支持字符串
  387. opt.checkedKey = opt.checkedKey ? opt.checkedKey : 'checked';
  388. // 禁用的key
  389. opt.disabledKey = opt.disabledKey ? opt.disabledKey : 'disabled';
  390. // 有子节点的前缀
  391. opt.prefixChildStr = opt.prefixChildStr ? opt.prefixChildStr : '├─ ';
  392. // 没有子节点的前缀
  393. opt.prefixNoChildStr = opt.prefixNoChildStr ? opt.prefixNoChildStr : '● ';
  394. // 树的深度影响的子节点数据
  395. opt.prefixDeptStr = opt.prefixDeptStr ? opt.prefixDeptStr : ' ';
  396. // 如果第一列就存在没有子节点的情况,加的特殊前缀
  397. opt.prefixFirstEmpty = opt.prefixFirstEmpty ? opt.prefixFirstEmpty : '  '
  398. return this._treeToSelect(tree, opt.currentDept, opt);
  399. },
  400. // 实际处理递归的函数
  401. _treeToSelect: function (tree, currentDept, opt) {
  402. var ansList = [];
  403. var prefix = '';
  404. for (var i = 0; i < currentDept; i++) {
  405. prefix += opt.prefixDeptStr;
  406. }
  407. for (var index in tree) {
  408. if (!tree.hasOwnProperty(index)) {
  409. continue;
  410. }
  411. var child_flag = 0;
  412. var item = tree[index];
  413. if (opt.childKey in item && item[opt.childKey] && item[opt.childKey].length > 0) {
  414. child_flag = 1;
  415. }
  416. var name = item[opt.nameKey];
  417. if (child_flag) {
  418. name = opt.prefixChildStr + name;
  419. } else {
  420. if (currentDept > 1) {
  421. name = opt.prefixNoChildStr + name;
  422. } else {
  423. name = opt.prefixFirstEmpty + name;
  424. }
  425. }
  426. ansList.push({
  427. name: prefix + name,
  428. value: item[opt.valueKey],
  429. checked: this._getStatusByDynamicKey(item, opt.checkedKey, opt.valueKey),
  430. disabled: this._getStatusByDynamicKey(item, opt.disabledKey, opt.valueKey),
  431. });
  432. // 添加子节点
  433. if (child_flag) {
  434. var child = this._treeToSelect(item[opt.childKey], currentDept + 1, opt);
  435. // apply 的骚操作,使用第二个参数可以用于合并两个数组
  436. ansList.push.apply(ansList, child);
  437. }
  438. }
  439. return ansList;
  440. },
  441. autoWidthAll: function () {
  442. for (var dst in this.renderedTrees) {
  443. if (this.renderedTrees.hasOwnProperty(dst)) {
  444. this.autoWidth(dst)
  445. }
  446. }
  447. },
  448. // 自动调整宽度以解决 form.render()生成元素兼容性问题,如果用户手动调用 form.render() 之后也需要调用此方法
  449. autoWidth: function (dst) {
  450. var tree = this.getRenderedInfo(dst);
  451. var opt = tree.opt;
  452. $(dst).css({
  453. 'whiteSpace': 'nowrap',
  454. 'maxWidth': '100%',
  455. });
  456. // 自动刷新多选框半选状态
  457. // this.autoNodeRender(dst)
  458. // 自动宽度调整的逻辑
  459. $(dst).find('.layui-form-checkbox,.layui-form-radio,.layui-form-audio').each(function (index, item) {
  460. var width = $(this).find('span').width() + $(this).find('i').width() + 25;
  461. if ($(this).is(':hidden')) {
  462. // 比较奇葩的获取隐藏元素宽度的手法,请见谅
  463. $('body').append('<div id="layui-authtree-get-width">' + $(this).html() + '</div>');
  464. width = $('#layui-authtree-get-width').find('span').width() + $('#layui-authtree-get-width').find('i').width() + 29;
  465. $('#layui-authtree-get-width').remove();
  466. } else {
  467. }
  468. //$(this).width(width);
  469. // 隐藏 单选/多选的左侧选框隐藏
  470. if (opt.hidechoose) {
  471. $(this).prevAll('i').css({
  472. zIndex: 2,
  473. });
  474. $(this).css({
  475. position: 'relative'
  476. , left: function () {
  477. return '-' + $(this).css('padding-left');// 避免点击抖动的骚操作
  478. }
  479. }).find('i').hide();
  480. }
  481. });
  482. },
  483. // 自动刷新多选框半选状态
  484. autoNodeRender: function (dst) {
  485. var tree = this.getRenderedInfo(dst);
  486. var opt = tree.opt;
  487. if (opt.halfchoose) {
  488. this._nodeRenderByParent($(dst).find('.auth-single'))
  489. }
  490. document.styleSheets[0].addRule(dst + ' .layui-icon-ok:before', 'content: ' + this.checkedIconContent)
  491. },
  492. _nodeRenderByParent: function (leaf) {
  493. },
  494. // 触发自定义事件
  495. _triggerEvent: function (dst, events, other) {
  496. var tree = this.getRenderedInfo(dst);
  497. var origin = $(dst);
  498. if (tree) {
  499. var opt = tree.opt;
  500. var data = {
  501. opt: opt,
  502. tree: tree.trees,
  503. dst: dst,
  504. othis: origin,
  505. };
  506. if (other && typeof other === 'object') {
  507. data = $.extend(data, other);
  508. }
  509. // 支持 dst 和 用户的配置的 layfilter 监听
  510. layui.event.call(origin, MOD_NAME, events + '(' + dst + ')', data);
  511. layui.event.call(origin, MOD_NAME, events + '(' + opt.layfilter + ')', data);
  512. } else {
  513. return false;
  514. }
  515. },
  516. // 获取渲染过的信息
  517. getRenderedInfo: function (dst) {
  518. return this.renderedTrees[dst];
  519. },
  520. // 动态获取最大深度
  521. getMaxDept: function (dst) {
  522. var next = $(dst);
  523. var dept = 0;
  524. while (next.length && dept < 100000) {
  525. next = this._getNext(next);
  526. if (next.length) {
  527. dept++;
  528. } else {
  529. break;
  530. }
  531. }
  532. return dept;
  533. },
  534. // 全选
  535. checkAll: function (dst) {
  536. var origin = $(dst);
  537. origin.find('.authtree-checkitem:not(:disabled):not(:checked)').prop('checked', true);
  538. form.render('checkbox');
  539. form.render('radio');
  540. obj.autoWidthAll();
  541. // 变动则存一下临时状态
  542. obj._saveNodeStatus(dst);
  543. obj._triggerEvent(dst, 'change');
  544. obj._triggerEvent(dst, 'checkAll');
  545. },
  546. // 全不选
  547. uncheckAll: function (dst) {
  548. var origin = $(dst);
  549. origin.find('.authtree-checkitem:not(:disabled):checked').prop('checked', false);
  550. form.render('checkbox');
  551. form.render('radio');
  552. obj.autoWidthAll();
  553. // 变动则存一下临时状态
  554. obj._saveNodeStatus(dst);
  555. obj._triggerEvent(dst, 'change');
  556. obj._triggerEvent(dst, 'uncheckAll');
  557. },
  558. // 显示整个树
  559. showAll: function (dst) {
  560. this.showDept(dst, this.getMaxDept(dst));
  561. },
  562. // 关闭整颗树
  563. closeAll: function (dst) {
  564. this.closeDept(dst, 1);
  565. },
  566. // 切换整颗树的显示/关闭
  567. toggleAll: function (dst) {
  568. if (this._shownDept(2)) {
  569. this.closeDept(dst);
  570. } else {
  571. this.showAll(dst);
  572. }
  573. },
  574. // 显示到第 dept 层
  575. showDept: function (dst, dept) {
  576. var next = $(dst);
  577. for (var i = 1; i < dept; i++) {
  578. next = this._getNext(next);
  579. if (next.length) {
  580. this._showSingle(next);
  581. } else {
  582. break;
  583. }
  584. }
  585. obj._triggerEvent(dst, 'deptChange', {dept: dept});
  586. },
  587. // 第 dept 层之后全部关闭
  588. closeDept: function (dst, dept) {
  589. var next = $(dst);
  590. for (var i = 0; i < dept; i++) {
  591. next = this._getNext(next);
  592. }
  593. while (next.length) {
  594. this._closeSingle(next);
  595. next = this._getNext(next);
  596. }
  597. obj._triggerEvent(dst, 'deptChange', {dept: dept});
  598. },
  599. // 临时保存所有节点信息状态
  600. _saveNodeStatus: function (dst) {
  601. var currentChecked = this.getChecked(dst);
  602. var currentNotChecked = this.getNotChecked(dst);
  603. // 保存新信息前,最新选择的信息
  604. this.lastCheckedNode[dst] = this._getLastChecked(dst, currentChecked, currentNotChecked);
  605. this.lastNotCheckedNode[dst] = this._getLastNotChecked(dst, currentChecked, currentNotChecked);
  606. this.checkedNode[dst] = currentChecked;
  607. this.notCheckedNode[dst] = currentNotChecked;
  608. // console.log('保存节点信息', this.checkedNode[dst], this.notCheckedNode[dst], this.lastCheckedNode[dst], this.lastNotCheckedNode[dst]);
  609. },
  610. // 判断某一层是否显示
  611. _shownDept: function (dst, dept) {
  612. var next = $(dst);
  613. for (var i = 0; i < dept; i++) {
  614. next = this._getNext(next);
  615. }
  616. return !next.is(':hidden');
  617. },
  618. // 获取
  619. _getNext: function (dst) {
  620. return $(dst).find('.auth-single:first>div>.auth-child');
  621. },
  622. // 显示某层 single
  623. _showSingle: function (dst) {
  624. layui.each(dst, function (index, item) {
  625. var origin = $(item).find('.auth-single:first');
  626. var parentChild = origin.parent();
  627. var parentStatus = parentChild.prev();
  628. if (!parentStatus.find('.auth-icon').hasClass('active')) {
  629. parentChild.show();
  630. // 显示上级的 .auth-child节点,并修改.auth-status的折叠状态
  631. parentStatus.find('.auth-icon').addClass('active').html(obj.openIconContent);
  632. }
  633. });
  634. },
  635. // 关闭某层 single
  636. _closeSingle: function (dst) {
  637. var origin = $(dst).find('.auth-single:first');
  638. var parentChild = origin.parent();
  639. var parentStatus = parentChild.prev();
  640. if (parentStatus.find('.auth-icon').hasClass('active')) {
  641. parentChild.hide();
  642. // 显示上级的 .auth-child节点,并修改.auth-status的折叠状态
  643. parentStatus.find('.auth-icon').removeClass('active').html(obj.closeIconContent);
  644. }
  645. },
  646. // 获取选中叶子结点
  647. getLeaf: function (dst) {
  648. var leafs = $(dst).find('.auth-leaf').parent().find('.authtree-checkitem:checked');
  649. var data = [];
  650. leafs.each(function (index, item) {
  651. // console.log(item);
  652. data.push(item.value);
  653. });
  654. // console.log(data);
  655. return data;
  656. },
  657. // 获取所有节点数据
  658. getAll: function (dst) {
  659. var inputs = $(dst).find('.authtree-checkitem');
  660. var data = [];
  661. inputs.each(function (index, item) {
  662. data.push(item.value);
  663. });
  664. // console.log(data);
  665. return data;
  666. },
  667. // 获取最新选中(之前取消-现在选中)
  668. getLastChecked: function (dst) {
  669. return this.lastCheckedNode[dst] || [];
  670. },
  671. // (逻辑)最新选中(之前取消-现在选中)
  672. _getLastChecked: function (dst, currentChecked, currentNotChecked) {
  673. var lastCheckedNode = currentChecked;
  674. var data = [];
  675. for (var i in lastCheckedNode) {
  676. if ($.inArray(lastCheckedNode[i], this.notCheckedNode[dst]) !== -1) {
  677. data.push(lastCheckedNode[i]);
  678. }
  679. }
  680. return data;
  681. },
  682. // 获取所有选中的数据
  683. getChecked: function (dst) {
  684. var inputs = $(dst).find('.authtree-checkitem:checked');
  685. var data = [];
  686. inputs.each(function (index, item) {
  687. data.push(item.value);
  688. });
  689. return data;
  690. },
  691. // 获取最新取消(之前取消-现在选中)
  692. getLastNotChecked: function (dst) {
  693. return this.lastNotCheckedNode[dst] || [];
  694. },
  695. // (逻辑)最新取消(之前选中-现在取消)
  696. _getLastNotChecked: function (dst, currentChecked, currentNotChecked) {
  697. var lastNotCheckedNode = currentNotChecked;
  698. var data = [];
  699. for (var i in lastNotCheckedNode) {
  700. if ($.inArray(lastNotCheckedNode[i], this.checkedNode[dst]) !== -1) {
  701. data.push(lastNotCheckedNode[i]);
  702. }
  703. }
  704. return data;
  705. },
  706. // 获取未选中数据
  707. getNotChecked: function (dst) {
  708. var inputs = $(dst).find('.authtree-checkitem:not(:checked)');
  709. var data = [];
  710. inputs.each(function (index, item) {
  711. data.push(item.value);
  712. });
  713. // console.log(data);
  714. return data;
  715. }
  716. };
  717. exports('authtree', obj);
  718. });