layui.define(['jquery'],function(exports){ // 获取layui的jQuery模块 } let jQuery = layui.jquery; (function ($) { // 定义设置对象、根节点对象和缓存对象 var settings = {}, roots = {}, caches = {}, // 核心默认常量 _consts = { className: { BUTTON: "button", // 按钮类名 LEVEL: "level", // 层级类名 ICO_LOADING: "ico_loading", // 加载图标类名 SWITCH: "switch", // 开关类名 NAME: 'node_name' // 节点名称类名 }, event: { // 节点创建事件 NODECREATED: "ztree_nodeCreated", // 点击事件 CLICK: "ztree_click", // 展开事件 EXPAND: "ztree_expand", // 折叠事件 COLLAPSE: "ztree_collapse", // 异步加载成功事件 ASYNC_SUCCESS: "ztree_async_success", // 异步加载错误事件 ASYNC_ERROR: "ztree_async_error", // 移除事件 REMOVE: "ztree_remove", // 选中事件 SELECTED: "ztree_selected", // 取消选中事件 UNSELECTED: "ztree_unselected" }, id: { // 定义一个对象,用于存储不同元素的ID前缀 A: "_a", // 为元素A设置ID前缀为"_a" ICON: "_ico", // 为ICON元素设置ID前缀为"_ico" SPAN: "_span", // 为SPAN元素设置ID前缀为"_span" SWITCH: "_switch", // 为SWITCH元素设置ID前缀为"_switch" UL: "_ul" // 为UL元素设置ID前缀为"_ul" }, line: { // 定义一个对象,用于存储不同线条类型的名称 ROOT: "root", // 根线条类型 ROOTS: "roots", // 多根线条类型 CENTER: "center", // 中心线条类型 BOTTOM: "bottom", // 底部线条类型 NOLINE: "noline", // 无线条类型 LINE: "line" // 普通线条类型 }, folder: { // 定义文件夹状态为打开 OPEN: "open", // 定义文件夹状态为关闭 CLOSE: "close", // 定义文件夹类型为文档 DOCU: "docu" }, node: { // 当前选中的节点 CURSELECTED: "curSelectedNode" } }, //default setting of core _setting = { // 树的ID,用于标识树的唯一性 treeId: "", // 树的对象实例,初始化为null treeObj: null, // 视图设置对象 view: { // 自定义DOM节点添加函数,默认为null addDiyDom: null, // 是否自动取消选中状态,默认为true autoCancelSelected: true, // 双击展开节点,默认为true dblClickExpand: true, // 展开速度,可选值为"fast"或"slow" expandSpeed: "fast", // 字体样式设置,默认为空对象 fontCss: {}, // 节点类名设置,默认为空对象 nodeClasses: {}, // 节点名称是否作为HTML解析,默认为false nameIsHTML: false, // 是否允许多选,默认为true selectedMulti: true, // 是否显示图标,默认为true showIcon: true, // 是否显示连接线,默认为true showLine: true, // 是否显示标题,默认为true showTitle: true, // 文本是否可选择,默认为false txtSelectedEnable: false }, data: { // 定义树形结构数据的键名 key: { // 表示节点是否有子节点的键名 isParent: "isParent", // 表示子节点集合的键名 children: "children", // 表示节点名称的键名 name: "name", // 表示节点标题的键名,初始为空字符串 title: "", // 表示节点URL的键名 url: "url", // 表示节点图标的键名 icon: "icon" }, // 定义渲染时使用的键名 render: { // 渲染时使用的名称键名,初始为null name: null, // 渲染时使用的标题键名,初始为null title: null, }, simpleData: { // 启用或禁用简单数据模式 enable: false, // 节点的唯一标识键名 idKey: "id", // 父节点的标识键名 pIdKey: "pId", // 根节点的父节点标识,默认为null表示没有父节点 rootPId: null }, keep: { // 是否保留父节点信息 parent: false, // 是否保留叶子节点信息 leaf: false } }, async: { // 是否启用异步请求,默认为false enable: false, // 设置请求的内容类型,默认为"application/x-www-form-urlencoded" contentType: "application/x-www-form-urlencoded", // 设置请求的类型,默认为"post" type: "post", // 设置响应的数据类型,默认为"text" dataType: "text", // 设置请求的头部信息,默认为空对象 headers: {}, // 设置XHR对象的额外字段,默认为空对象 xhrFields: {}, // 设置请求的URL地址,默认为空字符串 url: "", // 自动添加到请求参数中的键值对数组,默认为空数组 autoParam: [], // 其他手动添加的请求参数键值对数组,默认为空数组 otherParam: [], // 数据过滤器函数,用于处理响应数据,默认为null dataFilter: null }, callback: { // 在异步操作之前执行的回调函数,默认为null beforeAsync: null, // 在节点被点击之前执行的回调函数,默认为null beforeClick: null, // 在节点被双击之前执行的回调函数,默认为null beforeDblClick: null, // 在节点被右键点击之前执行的回调函数,默认为null beforeRightClick: null, // 在鼠标按下节点之前执行的回调函数,默认为null beforeMouseDown: null, // 在鼠标释放节点之前执行的回调函数,默认为null beforeMouseUp: null, // 在节点展开之前执行的回调函数,默认为null beforeExpand: null, // 在节点折叠之前执行的回调函数,默认为null beforeCollapse: null, // 在节点移除之前执行的回调函数,默认为null beforeRemove: null, // 异步操作失败时执行的回调函数,默认为null onAsyncError: null, // 异步操作成功时执行的回调函数,默认为null onAsyncSuccess: null, // 节点创建后执行的回调函数,默认为null onNodeCreated: null, // 节点被点击时执行的回调函数,默认为null onClick: null, // 节点被双击时执行的回调函数,默认为null onDblClick: null, // 节点被右键点击时执行的回调函数,默认为null onRightClick: null, // 鼠标按下节点时执行的回调函数,默认为null onMouseDown: null, // 鼠标释放节点时执行的回调函数,默认为null onMouseUp: null, // 节点展开时执行的回调函数,默认为null onExpand: null, // 节点折叠时执行的回调函数,默认为null onCollapse: null, // 节点移除时执行的回调函数,默认为null onRemove: null } }, //default root of core //zTree use root to save full data /** * 初始化根节点 * @param {Object} setting - 配置对象 */ _initRoot = function (setting) { // 获取根节点,如果不存在则创建一个新的空对象 var r = data.getRoot(setting); if (!r) { r = {}; // 将新创建的根节点设置到数据中 data.setRoot(setting, r); } // 初始化根节点的子节点为空数组 data.nodeChildren(setting, r, []); // 初始化根节点的扩展触发标志为false r.expandTriggerFlag = false; // 初始化当前选中列表为空数组 r.curSelectedList = []; // 初始化无选择状态为true r.noSelection = true; // 初始化已创建节点列表为空数组 r.createdNodes = []; // 初始化zId为0 r.zId = 0; // 初始化版本号为当前时间的时间戳 r._ver = (new Date()).getTime(); }, //default cache of core _initCache = function (setting) { // 获取缓存对象,如果不存在则初始化一个新的空对象 var c = data.getCache(setting); if (!c) { // 如果缓存对象不存在,创建一个新的空对象 c = {}; // 将新的空对象设置到缓存中 data.setCache(setting, c); } // 初始化缓存对象的nodes属性为一个空数组 c.nodes = []; // 初始化缓存对象的doms属性为一个空数组 c.doms = []; }, //default bindEvent of core _bindEvent = function (setting) { // 获取树对象和事件常量 var o = setting.treeObj, c = consts.event; // 绑定节点创建事件 o.bind(c.NODECREATED, function (event, treeId, node) { tools.apply(setting.callback.onNodeCreated, [event, treeId, node]); }); // 绑定点击事件 o.bind(c.CLICK, function (event, srcEvent, treeId, node, clickFlag) { tools.apply(setting.callback.onClick, [srcEvent, treeId, node, clickFlag]); }); // 绑定展开事件 o.bind(c.EXPAND, function (event, treeId, node) { tools.apply(setting.callback.onExpand, [event, treeId, node]); }); // 绑定折叠事件 o.bind(c.COLLAPSE, function (event, treeId, node) { tools.apply(setting.callback.onCollapse, [event, treeId, node]); }); // 绑定异步加载成功事件 o.bind(c.ASYNC_SUCCESS, function (event, treeId, node, msg) { tools.apply(setting.callback.onAsyncSuccess, [event, treeId, node, msg]); }); // 绑定异步加载错误事件 o.bind(c.ASYNC_ERROR, function (event, treeId, node, XMLHttpRequest, textStatus, errorThrown) { tools.apply(setting.callback.onAsyncError, [event, treeId, node, XMLHttpRequest, textStatus, errorThrown]); }); // 绑定移除事件 o.bind(c.REMOVE, function (event, treeId, treeNode) { tools.apply(setting.callback.onRemove, [event, treeId, treeNode]); }); // 绑定选中事件 o.bind(c.SELECTED, function (event, treeId, node) { tools.apply(setting.callback.onSelected, [treeId, node]); }); // 绑定取消选中事件 o.bind(c.UNSELECTED, function (event, treeId, node) { tools.apply(setting.callback.onUnSelected, [treeId, node]); }); }, _unbindEvent = function (setting) { // 获取树对象 var o = setting.treeObj, // 获取事件常量 c = consts.event; // 解绑节点创建事件 o.unbind(c.NODECREATED) // 解绑点击事件 .unbind(c.CLICK) // 解绑展开事件 .unbind(c.EXPAND) // 解绑折叠事件 .unbind(c.COLLAPSE) // 解绑异步成功事件 .unbind(c.ASYNC_SUCCESS) // 解绑异步错误事件 .unbind(c.ASYNC_ERROR) // 解绑移除事件 .unbind(c.REMOVE) // 解绑选中事件 .unbind(c.SELECTED) // 解绑取消选中事件 .unbind(c.UNSELECTED); }, //default event proxy of core _eventProxy = function (event) { // 获取事件目标元素 var target = event.target, // 获取树的设置信息 // 获取指定树的设置信息 setting = data.getSetting(event.data.treeId), // 初始化节点ID为空字符串 tId = "", // 初始化节点对象为null node = null, // 初始化节点事件类型为空字符串 nodeEventType = "", // 初始化树事件类型为空字符串 treeEventType = "", // 初始化节点事件回调函数为null nodeEventCallback = null, // 初始化树事件回调函数为null treeEventCallback = null, // 初始化临时变量为null tmp = null; // 根据事件类型判断是哪种树事件 if (tools.eqs(event.type, "mousedown")) { // 如果事件类型是鼠标按下,则设置树事件类型为"mousedown" treeEventType = "mousedown"; } else if (tools.eqs(event.type, "mouseup")) { // 如果事件类型是鼠标抬起,则设置树事件类型为"mouseup" treeEventType = "mouseup"; } else if (tools.eqs(event.type, "contextmenu")) { // 如果事件类型是右键菜单,则设置树事件类型为"contextmenu" treeEventType = "contextmenu"; } else if (tools.eqs(event.type, "click")) { // 如果点击的是节点开关,则设置节点事件类型为switchNode if (tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.SWITCH) !== null) { // 获取主DOM元素的ID并赋值给tId tId = tools.getNodeMainDom(target).id; // 设置节点事件类型为"switchNode" nodeEventType = "switchNode"; } else { // 否则查找包含特定属性的DOM元素,并设置节点事件类型为clickNode tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); if (tmp) { // 获取主DOM元素的ID并赋值给tId tId = tools.getNodeMainDom(tmp).id; // 设置节点事件类型为"clickNode" nodeEventType = "clickNode"; } } } else if (tools.eqs(event.type, "dblclick")) { // 双击事件处理,设置树事件类型为dblclick treeEventType = "dblclick"; // 获取目标元素中包含特定属性的子元素 tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); if (tmp) { // 获取节点的主DOM元素的ID tId = tools.getNodeMainDom(tmp).id; // 设置节点事件类型为switchNode nodeEventType = "switchNode"; } } // 检查treeEventType数组是否有元素且tId字符串是否为空 if (treeEventType.length > 0 && tId.length == 0) { // 调用tools.getMDom方法,获取目标元素的子元素,该子元素具有指定的标签名和属性名 tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); // 如果找到了符合条件的元素 if (tmp) { // 获取该元素的主DOM节点的ID并赋值给tId tId = tools.getNodeMainDom(tmp).id; } } // event to node if (tId.length > 0) { // 获取节点缓存 node = data.getNodeCache(setting, tId); // 根据节点事件类型进行处理 switch (nodeEventType) { case "switchNode" : // 判断节点是否为父节点 var isParent = data.nodeIsParent(setting, node); if (!isParent) { // 如果不是父节点,则清空事件类型 nodeEventType = ""; } else if (tools.eqs(event.type, "click") || (tools.eqs(event.type, "dblclick") && tools.apply(setting.view.dblClickExpand, [setting.treeId, node], setting.view.dblClickExpand))) { // 如果事件类型是点击或双击且满足展开条件,设置回调函数为切换节点处理函数 nodeEventCallback = handler.onSwitchNode; } else { // 否则清空事件类型 nodeEventType = ""; } break; case "clickNode" : // 如果事件类型是点击节点,设置回调函数为点击节点处理函数 nodeEventCallback = handler.onClickNode; break; } } // event to zTree switch (treeEventType) { // 当事件类型为 "mousedown" 时,将 treeEventCallback 设置为 handler.onZTreeMousedown case "mousedown" : treeEventCallback = handler.onZTreeMousedown; break; // 当事件类型为 "mouseup" 时,将 treeEventCallback 设置为 handler.onZTreeMouseup case "mouseup" : treeEventCallback = handler.onZTreeMouseup; break; // 当事件类型为 "dblclick" 时,将 treeEventCallback 设置为 handler.onZTreeDblclick case "dblclick" : treeEventCallback = handler.onZTreeDblclick; break; // 当事件类型为 "contextmenu" 时,将 treeEventCallback 设置为 handler.onZTreeContextmenu case "contextmenu" : treeEventCallback = handler.onZTreeContextmenu; break; } var proxyResult = { // 停止标志,初始值为false stop: false, // 当前节点对象 node: node, // 节点事件类型 nodeEventType: nodeEventType, // 节点事件回调函数 nodeEventCallback: nodeEventCallback, // 树事件类型 treeEventType: treeEventType, // 树事件回调函数 treeEventCallback: treeEventCallback }; // 返回代理结果对象 return proxyResult }, //default init node of core _initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { // 如果节点不存在,直接返回 if (!n) return; // 获取树的根节点 var r = data.getRoot(setting), // 获取当前节点的子节点 children = data.nodeChildren(setting, n); // 设置当前节点的层级 n.level = level; // 生成并设置当前节点的唯一标识符 n.tId = setting.treeId + "_" + (++r.zId); // 设置当前节点的父节点标识符 n.parentTId = parentNode ? parentNode.tId : null; // 根据节点的open属性设置是否展开 n.open = (typeof n.open == "string") ? tools.eqs(n.open, "true") : !!n.open; // 判断当前节点是否是父节点 var isParent = data.nodeIsParent(setting, n); // 如果当前节点有子节点 if (tools.isArray(children)) { // 将当前节点标记为父节点 data.nodeIsParent(setting, n, true); // 标记当前节点为异步加载 n.zAsync = true; } else { // 更新当前节点的父节点状态 isParent = data.nodeIsParent(setting, n, isParent); // 根据父节点状态和异步加载设置决定是否展开当前节点 n.open = (isParent && !setting.async.enable) ? n.open : false; // 标记当前节点为非异步加载 n.zAsync = !isParent; } n.isFirstNode = isFirstNode; // 设置节点是否为第一个节点的函数 n.isLastNode = isLastNode; // 设置节点是否为最后一个节点的函数 n.getParentNode = function () { return data.getNodeCache(setting, n.parentTId); }; // 获取父节点的方法,通过缓存获取父节点信息 n.getPreNode = function () { return data.getPreNode(setting, n); }; // 获取前一个兄弟节点的方法 n.getNextNode = function () { return data.getNextNode(setting, n); }; // 获取下一个兄弟节点的方法 n.getIndex = function () { return data.getNodeIndex(setting, n); }; // 获取当前节点在同级中的索引位置 n.getPath = function () { return data.getNodePath(setting, n); }; // 获取从根节点到当前节点的路径 n.isAjaxing = false; // 初始化节点的Ajax请求状态为false data.fixPIdKeyValue(setting, n); // 修正节点的父ID键值对 }, _init = { bind: [_bindEvent], // 绑定事件的方法数组 unbind: [_unbindEvent], // 解绑事件的方法数组 caches: [_initCache], // 初始化缓存的方法数组 nodes: [_initNode], // 初始化节点的方法数组 proxys: [_eventProxy], // 事件代理的方法数组 roots: [_initRoot], // 初始化根节点的方法数组 beforeA: [], // 在操作前的回调方法数组 afterA: [], // 在操作后的回调方法数组 innerBeforeA: [], // 内部操作前的回调方法数组 innerAfterA: [], // 内部操作后的回调方法数组 zTreeTools: [] // ZTree工具方法数组 }, //method of operate data data = { // 添加节点缓存 addNodeCache: function (setting, node) { // 获取指定设置的缓存,并将节点添加到缓存中 data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = node; }, // 获取节点缓存ID getNodeCacheId: function (tId) { // 返回节点ID的最后一部分作为缓存ID return tId.substring(tId.lastIndexOf("_") + 1); }, // 添加afterA事件 addAfterA: function (afterA) { // 将afterA事件添加到初始化配置的afterA数组中 _init.afterA.push(afterA); }, // 添加beforeA事件 addBeforeA: function (beforeA) { // 将beforeA事件添加到初始化配置的beforeA数组中 _init.beforeA.push(beforeA); }, // 添加innerAfterA事件 addInnerAfterA: function (innerAfterA) { // 将innerAfterA事件添加到初始化配置的innerAfterA数组中 _init.innerAfterA.push(innerAfterA); }, // 添加innerBeforeA事件 addInnerBeforeA: function (innerBeforeA) { // 将innerBeforeA事件添加到初始化配置的innerBeforeA数组中 _init.innerBeforeA.push(innerBeforeA); }, // 添加初始化绑定事件 addInitBind: function (bindEvent) { // 将绑定事件添加到初始化配置的bind数组中 _init.bind.push(bindEvent); }, // 添加初始化解绑事件 addInitUnBind: function (unbindEvent) { // 将解绑事件添加到初始化配置的unbind数组中 _init.unbind.push(unbindEvent); }, // 添加初始化缓存 addInitCache: function (initCache) { // 将初始化缓存添加到初始化配置的caches数组中 _init.caches.push(initCache); }, // 添加初始化节点 addInitNode: function (initNode) { // 将初始化节点添加到初始化配置的nodes数组中 _init.nodes.push(initNode); }, // 添加初始化代理 addInitProxy: function (initProxy, isFirst) { if (!!isFirst) { // 如果isFirst为真,将代理插入到proxys数组的开头 _init.proxys.splice(0, 0, initProxy); } else { // 否则,将代理添加到proxys数组的末尾 _init.proxys.push(initProxy); } }, addInitRoot: function (initRoot) { // 将初始化根节点添加到_init.roots数组中 _init.roots.push(initRoot); }, addNodesData: function (setting, parentNode, index, nodes) { // 获取父节点的子节点数据 var children = data.nodeChildren(setting, parentNode), params; // 如果父节点没有子节点 if (!children) { // 为父节点创建一个新的空子节点数组 children = data.nodeChildren(setting, parentNode, []); // 将索引设置为-1,表示插入位置无效 index = -1; } else if (index >= children.length) { // 如果索引超出子节点数组的长度,将索引设置为-1 index = -1; } if (children.length > 0 && index === 0) { // 如果子节点数组不为空且索引为0,则将第一个子节点的isFirstNode属性设置为false children[0].isFirstNode = false; // 设置第一个子节点的图标样式 view.setNodeLineIcos(setting, children[0]); } else if (children.length > 0 && index < 0) { // 如果子节点数组不为空且索引小于0,则将最后一个子节点的isLastNode属性设置为false children[children.length - 1].isLastNode = false; // 设置最后一个子节点的图标样式 view.setNodeLineIcos(setting, children[children.length - 1]); } // 设置父节点为父节点 data.nodeIsParent(setting, parentNode, true); if (index < 0) { // 如果索引小于0,则将新节点添加到子节点数组的末尾 data.nodeChildren(setting, parentNode, children.concat(nodes)); } else { // 如果索引大于等于0,则在指定位置插入新节点 params = [index, 0].concat(nodes); children.splice.apply(children, params); } }, addSelectedNode: function (setting, node) { // 获取根节点 var root = data.getRoot(setting); // 如果节点未被选中,则将其添加到当前选中列表中 if (!data.isSelectedNode(setting, node)) { root.curSelectedList.push(node); } }, addCreatedNode: function (setting, node) { // 如果存在自定义的节点创建回调或添加自定义DOM的方法,则执行以下逻辑 if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) { // 获取根节点 var root = data.getRoot(setting); // 将新创建的节点添加到已创建节点列表中 root.createdNodes.push(node); } }, addZTreeTools: function (zTreeTools) { // 将传入的zTree工具对象添加到_init.zTreeTools数组中 _init.zTreeTools.push(zTreeTools); }, exSetting: function (s) { // 使用jQuery的extend方法,将传入的设置对象s合并到_setting对象中,实现深度拷贝 $.extend(true, _setting, s); }, fixPIdKeyValue: function (setting, node) { // 如果启用了简单数据模式 if (setting.data.simpleData.enable) { // 设置节点的父ID键值,如果节点有父节点则获取父节点的ID,否则设置为根节点的父ID node[setting.data.simpleData.pIdKey] = node.parentTId ? node.getParentNode()[setting.data.simpleData.idKey] : setting.data.simpleData.rootPId; } }, getAfterA: function (setting, node, array) { // 遍历_init.afterA数组,并调用每个元素作为函数 for (var i = 0, j = _init.afterA.length; i < j; i++) { // 使用apply方法将当前上下文和参数传递给每个函数 _init.afterA[i].apply(this, arguments); } }, getBeforeA: function (setting, node, array) { // 遍历_init.beforeA数组,并调用每个元素作为函数 for (var i = 0, j = _init.beforeA.length; i < j; i++) { // 使用apply方法将当前上下文和参数传递给每个函数 _init.beforeA[i].apply(this, arguments); } }, getInnerAfterA: function (setting, node, array) { // 遍历_init.innerAfterA数组,并调用每个元素作为函数 for (var i = 0, j = _init.innerAfterA.length; i < j; i++) { // 使用apply方法将当前上下文和参数传递给每个函数 _init.innerAfterA[i].apply(this, arguments); } }, getInnerBeforeA: function (setting, node, array) { // 遍历_init.innerBeforeA数组,并调用每个元素作为函数 for (var i = 0, j = _init.innerBeforeA.length; i < j; i++) { // 使用apply方法将当前上下文和参数传递给每个函数 _init.innerBeforeA[i].apply(this, arguments); } }, getCache: function (setting) { // 返回缓存中对应treeId的缓存数据 return caches[setting.treeId]; }, getNodeIndex: function (setting, node) { // 如果节点为空,返回null if (!node) return null; // 获取父节点,如果节点没有父节点则获取根节点 var p = node.parentTId ? node.getParentNode() : data.getRoot(setting), // 获取父节点的所有子节点 children = data.nodeChildren(setting, p); // 遍历子节点数组 for (var i = 0, l = children.length - 1; i <= l; i++) { // 如果找到匹配的节点,返回其索引 if (children[i] === node) { return i; } } // 如果没有找到匹配的节点,返回-1 return -1; }, getNextNode: function (setting, node) { // 如果节点为空,返回null if (!node) return null; // 获取父节点,如果节点没有父节点则获取根节点 var p = node.parentTId ? node.getParentNode() : data.getRoot(setting), // 获取父节点的所有子节点 children = data.nodeChildren(setting, p); // 遍历子节点数组 for (var i = 0, l = children.length - 1; i <= l; i++) { // 如果找到匹配的节点 if (children[i] === node) { // 如果是最后一个节点,返回null;否则返回下一个节点 return (i == l ? null : children[i + 1]); } } // 如果没有找到匹配的节点,返回null return null; }, /** * 根据指定的参数在节点数组中查找匹配的节点。 * @param {Object} setting - 配置对象,用于获取子节点。 * @param {Array} nodes - 要搜索的节点数组。 * @param {String} key - 要匹配的键名。 * @param {*} value - 要匹配的值。 * @returns {Object|null} 返回匹配的节点,如果没有找到则返回null。 */ getNodeByParam: function (setting, nodes, key, value) { // 如果节点数组或键名为空,直接返回null if (!nodes || !key) return null; // 遍历节点数组 for (var i = 0, l = nodes.length; i < l; i++) { // 获取当前节点 var node = nodes[i]; // 如果当前节点的指定键值与目标值相等,返回该节点 if (node[key] == value) { return nodes[i]; } // 获取当前节点的子节点 var children = data.nodeChildren(setting, node); // 递归调用自身在子节点中查找匹配的节点 var tmp = data.getNodeByParam(setting, children, key, value); // 如果找到匹配的节点,返回该节点 if (tmp) return tmp; } // 如果未找到匹配的节点,返回null return null; }, getNodeCache: function (setting, tId) { // 如果tId不存在,返回null if (!tId) return null; // 从缓存中获取节点对象 var n = caches[setting.treeId].nodes[data.getNodeCacheId(tId)]; // 如果节点存在,返回节点;否则返回null return n ? n : null; }, getNodePath: function (setting, node) { // 如果node不存在,返回null if (!node) return null; var path; // 如果节点有父节点,递归获取父节点的路径 if (node.parentTId) { path = node.getParentNode().getPath(); } else { // 如果没有父节点,初始化路径为空数组 path = []; } // 如果路径存在,将当前节点添加到路径中 if (path) { path.push(node); } // 返回节点路径 return path; }, getNodes: function (setting) { // 调用data.nodeChildren方法获取根节点的子节点 return data.nodeChildren(setting, data.getRoot(setting)); }, getNodesByParam: function (setting, nodes, key, value) { // 如果nodes或key为空,返回空数组 if (!nodes || !key) return []; var result = []; // 初始化结果数组 // 遍历所有节点 for (var i = 0, l = nodes.length; i < l; i++) { var node = nodes[i]; // 当前节点 // 如果当前节点的指定属性值等于目标值,则将该节点加入结果数组 if (node[key] == value) { result.push(node); } // 获取当前节点的子节点 var children = data.nodeChildren(setting, node); // 递归查找子节点中符合条件的节点并合并到结果数组中 result = result.concat(data.getNodesByParam(setting, children, key, value)); } // 返回符合条件的节点数组 return result; }, getNodesByParamFuzzy: function (setting, nodes, key, value) { // 如果节点或键为空,返回空数组 if (!nodes || !key) return []; // 初始化结果数组 var result = []; // 将搜索值转换为小写 value = value.toLowerCase(); // 遍历所有节点 for (var i = 0, l = nodes.length; i < l; i++) { var node = nodes[i]; // 如果节点的指定键是字符串且包含搜索值,则将该节点加入结果数组 if (typeof node[key] == "string" && nodes[i][key].toLowerCase().indexOf(value) > -1) { result.push(node); } // 获取当前节点的子节点 var children = data.nodeChildren(setting, node); // 递归调用函数以查找子节点中的匹配项,并将结果合并到结果数组中 result = result.concat(data.getNodesByParamFuzzy(setting, children, key, value)); } // 返回最终的结果数组 return result; }, /** * 根据过滤条件获取节点 * @param {Object} setting - 配置对象 * @param {Array} nodes - 节点数组 * @param {Function} filter - 过滤函数 * @param {Boolean} isSingle - 是否只返回一个节点 * @param {Any} invokeParam - 传递给过滤函数的参数 * @returns {Array|null} 符合条件的节点数组或单个节点,如果没有符合条件的节点则返回空数组或null */ getNodesByFilter: function (setting, nodes, filter, isSingle, invokeParam) { // 如果节点数组为空,根据isSingle决定返回null还是空数组 if (!nodes) return (isSingle ? null : []); // 初始化结果变量,根据isSingle决定是null还是空数组 var result = isSingle ? null : []; // 遍历所有节点 for (var i = 0, l = nodes.length; i < l; i++) { var node = nodes[i]; // 使用过滤函数检查当前节点是否符合条件 if (tools.apply(filter, [node, invokeParam], false)) { // 如果只需要一个节点且找到符合条件的节点,直接返回该节点 if (isSingle) { return node; } // 否则将符合条件的节点加入结果数组 result.push(node); } // 获取当前节点的子节点 var children = data.nodeChildren(setting, node); // 递归调用自身以处理子节点 var tmpResult = data.getNodesByFilter(setting, children, filter, isSingle, invokeParam); // 如果只需要一个节点且在子节点中找到符合条件的节点,直接返回该节点 if (isSingle && !!tmpResult) { return tmpResult; } // 根据isSingle决定是替换结果还是合并结果 result = isSingle ? tmpResult : result.concat(tmpResult); } // 返回最终的结果数组或单个节点 return result; }, getPreNode: function (setting, node) { // 如果节点为空,返回null if (!node) return null; // 获取父节点,如果当前节点没有父节点则获取根节点 var p = node.parentTId ? node.getParentNode() : data.getRoot(setting), // 获取父节点的子节点列表 children = data.nodeChildren(setting, p); // 遍历子节点列表 for (var i = 0, l = children.length; i < l; i++) { // 如果找到当前节点 if (children[i] === node) { // 返回前一个节点,如果是第一个节点则返回null return (i == 0 ? null : children[i - 1]); } } // 如果没有找到当前节点,返回null return null; }, /** * 获取树的根节点 * @param {Object} setting - 配置对象 * @returns {Object|null} 根节点或null */ getRoot: function (setting) { // 根据配置对象的treeId获取根节点 return setting ? roots[setting.treeId] : null; }, /** * 获取所有树的根节点集合 * @returns {Object} 根节点集合 */ getRoots: function () { // 返回所有根节点的集合 return roots; }, /** * 获取指定树的配置对象 * @param {string} treeId - 树的唯一标识符 * @returns {Object|null} 配置对象或null */ getSetting: function (treeId) { // 根据树的唯一标识符获取配置对象 return settings[treeId]; }, /** * 获取所有树的配置对象集合 * @returns {Object} 配置对象集合 */ getSettings: function () { // 返回所有配置对象的集合 return settings; }, /** * 获取指定树的工具对象 * @param {string} treeId - 树的唯一标识符 * @returns {Object|null} 工具对象或null */ getZTreeTools: function (treeId) { // 根据树的唯一标识符获取根节点 var r = this.getRoot(this.getSetting(treeId)); // 返回根节点的工具对象,如果不存在则返回null return r ? r.treeTools : null; }, /** * 初始化缓存 * @param {Object} setting - 配置对象 */ initCache: function (setting) { // 遍历并应用所有缓存初始化函数 for (var i = 0, j = _init.caches.length; i < j; i++) { _init.caches[i].apply(this, arguments); } }, /** * 初始化节点 * @param {Object} setting - 配置对象 * @param {number} level - 节点层级 * @param {Object} node - 当前节点 * @param {Object} parentNode - 父节点 * @param {Object} preNode - 前一个兄弟节点 * @param {Object} nextNode - 后一个兄弟节点 */ initNode: function (setting, level, node, parentNode, preNode, nextNode) { // 遍历并应用所有节点初始化函数 for (var i = 0, j = _init.nodes.length; i < j; i++) { _init.nodes[i].apply(this, arguments); } }, initRoot: function (setting) { // 遍历所有根节点初始化函数,并调用它们 for (var i = 0, j = _init.roots.length; i < j; i++) { _init.roots[i].apply(this, arguments); } }, isSelectedNode: function (setting, node) { // 获取当前设置的根节点 var root = data.getRoot(setting); // 遍历当前选中的节点列表,检查目标节点是否在其中 for (var i = 0, j = root.curSelectedList.length; i < j; i++) { if (node === root.curSelectedList[i]) return true; // 如果找到匹配的节点,返回true } return false; // 如果没有找到匹配的节点,返回false }, nodeChildren: function (setting, node, newChildren) { if (!node) { return null; // 如果节点不存在,返回null } var key = setting.data.key.children; // 获取子节点的键名 if (typeof newChildren !== 'undefined') { node[key] = newChildren; // 如果提供了新的子节点,更新节点的子节点 } return node[key]; // 返回节点的子节点 }, nodeIsParent: function (setting, node, newIsParent) { if (!node) { return false; // 如果节点不存在,返回false } var key = setting.data.key.isParent; // 检查 newIsParent 是否定义 if (typeof newIsParent !== 'undefined') { // 如果 newIsParent 是字符串类型,则将其与 "true" 比较并转换为布尔值 if (typeof newIsParent === "string") { newIsParent = tools.eqs(newIsParent, "true"); } // 将 newIsParent 转换为布尔值并赋值给 node[key] newIsParent = !!newIsParent; node[key] = newIsParent; } else if (typeof node[key] == "string"){ // 如果 node[key] 是字符串类型,则将其与 "true" 比较并转换为布尔值 node[key] = tools.eqs(node[key], "true"); } else { // 将 node[key] 转换为布尔值 node[key] = !!node[key]; } // 返回 node[key] 的布尔值 return node[key]; }, /** * 设置或获取节点的名称 * @param {Object} setting - 配置对象 * @param {Object} node - 节点对象 * @param {String} [newName] - 新的节点名称(可选) * @returns {String} - 节点名称 */ nodeName: function (setting, node, newName) { // 获取节点名称的键名 var key = setting.data.key.name; // 如果 newName 已定义,则更新节点名称 if (typeof newName !== 'undefined') { node[key] = newName; } // 将节点名称转换为字符串 var rawName = "" + node[key]; // 如果 render.name 是一个函数,则调用该函数并返回结果 if(typeof setting.data.render.name === 'function') { return setting.data.render.name.call(this,rawName,node); } // 否则直接返回原始名称 return rawName; }, nodeTitle: function (setting, node) { // 获取节点标题,如果标题为空则使用名称 var t = setting.data.key.title === "" ? setting.data.key.name : setting.data.key.title; // 将节点的标题转换为字符串 var rawTitle = "" + node[t]; // 如果设置了自定义渲染函数,则调用该函数渲染标题 if(typeof setting.data.render.title === 'function') { return setting.data.render.title.call(this,rawTitle,node); } // 返回原始标题 return rawTitle; }, removeNodeCache: function (setting, node) { // 获取节点的子节点 var children = data.nodeChildren(setting, node); // 如果存在子节点,递归删除每个子节点的缓存 if (children) { for (var i = 0, l = children.length; i < l; i++) { data.removeNodeCache(setting, children[i]); } } // 将当前节点的缓存设置为null data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = null; }, removeSelectedNode: function (setting, node) { // 获取树的根节点 var root = data.getRoot(setting); // 遍历当前选中的节点列表 for (var i = 0, j = root.curSelectedList.length; i < j; i++) { // 如果当前节点在选中列表中或其缓存不存在,则从选中列表中移除 if (node === root.curSelectedList[i] || !data.getNodeCache(setting, root.curSelectedList[i].tId)) { root.curSelectedList.splice(i, 1); // 触发取消选中事件 setting.treeObj.trigger(consts.event.UNSELECTED, [setting.treeId, node]); // 调整索引以反映已移除的元素 i--; j--; } } }, setCache: function (setting, cache) { // 将缓存对象存储到caches数组中,键为treeId caches[setting.treeId] = cache; }, setRoot: function (setting, root) { // 将根节点对象存储到roots数组中,键为treeId roots[setting.treeId] = root; }, setZTreeTools: function (setting, zTreeTools) { // 遍历_init.zTreeTools数组并调用每个工具函数,传递当前上下文和参数 for (var i = 0, j = _init.zTreeTools.length; i < j; i++) { _init.zTreeTools[i].apply(this, arguments); } }, transformToArrayFormat: function (setting, nodes) { // 如果nodes为空,返回空数组 if (!nodes) return []; // 初始化结果数组r var r = []; // 如果nodes是数组,则遍历每个节点并调用_do函数处理 if (tools.isArray(nodes)) { for (var i = 0, l = nodes.length; i < l; i++) { var node = nodes[i]; _do(node); } } else { // 如果nodes不是数组,直接调用_do函数处理 _do(nodes); } // 返回结果数组r return r; function _do(_node) { // 将当前节点添加到结果数组中 r.push(_node); // 获取当前节点的子节点 var children = data.nodeChildren(setting, _node); // 如果存在子节点,则将其转换为数组格式并合并到结果数组中 if (children) { r = r.concat(data.transformToArrayFormat(setting, children)); } } }, transformTozTreeFormat: function (setting, sNodes) { // 定义变量i和l,用于循环控制 var i, l, // 获取节点ID的键名 key = setting.data.simpleData.idKey, // 获取父节点ID的键名 parentKey = setting.data.simpleData.pIdKey; // 如果key为空或sNodes不存在,返回空数组 if (!key || key == "" || !sNodes) return []; // 如果sNodes是数组类型 if (tools.isArray(sNodes)) { // 初始化结果数组r var r = []; // 初始化临时映射对象tmpMap var tmpMap = {}; // 遍历sNodes数组 for (i = 0, l = sNodes.length; i < l; i++) { // 将每个节点按其ID存入tmpMap中 tmpMap[sNodes[i][key]] = sNodes[i]; } // 遍历sNodes数组中的每一个元素 for (i = 0, l = sNodes.length; i < l; i++) { // 获取当前节点的父节点在tmpMap中的引用 var p = tmpMap[sNodes[i][parentKey]]; // 如果父节点存在且当前节点不是其父节点 if (p && sNodes[i][key] != sNodes[i][parentKey]) { // 获取父节点的子节点列表 var children = data.nodeChildren(setting, p); // 如果父节点没有子节点列表,则初始化一个空列表 if (!children) { children = data.nodeChildren(setting, p, []); } // 将当前节点添加到父节点的子节点列表中 children.push(sNodes[i]); } else { // 如果父节点不存在或当前节点是其父节点,则将当前节点添加到结果数组r中 r.push(sNodes[i]); } } // 如果条件满足,返回变量r return r; } else { // 如果条件不满足,返回包含sNodes的数组 return [sNodes]; } } }, //method of event proxy event = { // 绑定事件的方法,接受一个设置参数 bindEvent: function (setting) { // 遍历_init.bind数组中的每一个元素 for (var i = 0, j = _init.bind.length; i < j; i++) { // 调用当前元素的apply方法,将当前上下文和传入的参数传递给它 _init.bind[i].apply(this, arguments); } }, unbindEvent: function (setting) { // 遍历_init.unbind数组,并调用每个元素的apply方法,将当前上下文和参数传递给它 for (var i = 0, j = _init.unbind.length; i < j; i++) { _init.unbind[i].apply(this, arguments); } }, bindTree: function (setting) { // 创建一个包含treeId属性的对象eventParam var eventParam = { treeId: setting.treeId }, // 获取树对象 o = setting.treeObj; // 如果视图中不允许选择文本 if (!setting.view.txtSelectedEnable) { // 绑定selectstart事件处理程序,防止文本被选中,并设置CSS样式以禁用文本选择 o.bind('selectstart', handler.onSelectStart).css({ "-moz-user-select": "-moz-none" }); } // 绑定点击事件到对象o,并使用event.proxy作为事件处理函数 o.bind('click', eventParam, event.proxy); // 绑定双击事件到对象o,并使用event.proxy作为事件处理函数 o.bind('dblclick', eventParam, event.proxy); // 绑定鼠标悬停事件到对象o,并使用event.proxy作为事件处理函数 o.bind('mouseover', eventParam, event.proxy); // 绑定鼠标移出事件到对象o,并使用event.proxy作为事件处理函数 o.bind('mouseout', eventParam, event.proxy); // 绑定鼠标按下事件到对象o,并使用event.proxy作为事件处理函数 o.bind('mousedown', eventParam, event.proxy); // 绑定鼠标抬起事件到对象o,并使用event.proxy作为事件处理函数 o.bind('mouseup', eventParam, event.proxy); // 绑定右键菜单事件到对象o,并使用event.proxy作为事件处理函数 o.bind('contextmenu', eventParam, event.proxy); unbindTree: function (setting) { // 获取树形对象的引用 var o = setting.treeObj; // 解绑'selectstart'事件,防止文本选择 o.unbind('selectstart', handler.onSelectStart) // 解绑'click'事件,取消点击事件的代理处理 .unbind('click', event.proxy) // 解绑'dblclick'事件,取消双击事件的代理处理 .unbind('dblclick', event.proxy) // 解绑'mouseover'事件,取消鼠标悬停事件的代理处理 .unbind('mouseover', event.proxy) // 解绑'mouseout'事件,取消鼠标移出事件的代理处理 .unbind('mouseout', event.proxy) // 解绑'mousedown'事件,取消鼠标按下事件的代理处理 .unbind('mousedown', event.proxy) // 解绑'mouseup'事件,取消鼠标抬起事件的代理处理 .unbind('mouseup', event.proxy) // 解绑'contextmenu'事件,取消右键菜单事件的代理处理 .unbind('contextmenu', event.proxy); }, doProxy: function (e) { // 初始化一个空数组,用于存储代理函数的返回结果 var results = []; // 遍历所有代理函数 for (var i = 0, j = _init.proxys.length; i < j; i++) { // 调用当前代理函数,并将结果存储在proxyResult中 var proxyResult = _init.proxys[i].apply(this, arguments); // 将当前代理函数的结果添加到results数组中 results.push(proxyResult); // 如果当前代理函数的返回结果包含stop属性且为true,则中断循环 if (proxyResult.stop) { break; } } // 返回所有代理函数的结果数组 return results; }, proxy: function (e) { // 获取当前树的设置信息 var setting = data.getSetting(e.data.treeId); // 检查用户是否有权限执行该操作,如果没有权限则返回true if (!tools.uCanDo(setting, e)) return true; // 执行代理事件并获取结果数组 var results = event.doProxy(e), r = true, // 初始化返回值为true x = false; // 初始化标志位为false // 遍历所有代理结果 for (var i = 0, l = results.length; i < l; i++) { var proxyResult = results[i]; // 如果存在节点事件回调函数,则调用它并更新返回值和标志位 if (proxyResult.nodeEventCallback) { x = true; r = proxyResult.nodeEventCallback.apply(proxyResult, [e, proxyResult.node]) && r; } // 如果存在树事件回调函数,则调用它并更新返回值和标志位 if (proxyResult.treeEventCallback) { x = true; r = proxyResult.treeEventCallback.apply(proxyResult, [e, proxyResult.node]) && r; } } // 返回最终的结果值 return r; } }, //method of event handler handler = { // 处理节点切换事件的方法 onSwitchNode: function (event, node) { // 获取当前树的设置信息 var setting = data.getSetting(event.data.treeId); // 如果节点是展开状态 if (node.open) { // 调用回调函数,判断是否允许折叠节点,如果返回false则终止操作 if (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false) return true; // 设置根节点的expandTriggerFlag为true,表示触发了展开操作 data.getRoot(setting).expandTriggerFlag = true; // 切换节点的状态(从展开到折叠) view.switchNode(setting, node); } else { // 调用回调函数,判断是否允许展开节点,如果返回false则终止操作 if (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false) return true; // 设置根节点的expandTriggerFlag为true,表示触发了展开操作 data.getRoot(setting).expandTriggerFlag = true; // 切换节点的状态(从折叠到展开) view.switchNode(setting, node); } // 返回true表示操作成功 return true; }, /** * 处理节点点击事件的函数 * @param {Object} event - 事件对象,包含触发事件的信息 * @param {Object} node - 被点击的节点对象 */ onClickNode: function (event, node) { // 获取当前树的设置信息 var setting = data.getSetting(event.data.treeId), // 根据是否按下Ctrl或Meta键以及节点是否已选中来确定点击标志 clickFlag = ((setting.view.autoCancelSelected && (event.ctrlKey || event.metaKey)) && data.isSelectedNode(setting, node)) ? 0 : (setting.view.autoCancelSelected && (event.ctrlKey || event.metaKey) && setting.view.selectedMulti) ? 2 : 1; // 如果beforeClick回调函数返回false,则终止执行并返回true if (tools.apply(setting.callback.beforeClick, [setting.treeId, node, clickFlag], true) == false) return true; // 如果点击标志为0,取消之前选中的节点 if (clickFlag === 0) { view.cancelPreSelectedNode(setting, node); } else { // 否则根据点击标志选择或取消选择节点 view.selectNode(setting, node, clickFlag === 2); } // 触发CLICK事件,传递事件对象、树ID、节点和点击标志 setting.treeObj.trigger(consts.event.CLICK, [event, setting.treeId, node, clickFlag]); // 返回true表示事件处理完成 return true; }, onZTreeMousedown: function (event, node) { // 获取当前树的设置信息 var setting = data.getSetting(event.data.treeId); // 在鼠标按下事件之前执行回调函数,如果返回true则继续执行后续操作 if (tools.apply(setting.callback.beforeMouseDown, [setting.treeId, node], true)) { // 执行鼠标按下事件的回调函数 tools.apply(setting.callback.onMouseDown, [event, setting.treeId, node]); } return true; // 始终返回true,表示事件处理完成 }, onZTreeMouseup: function (event, node) { // 获取当前树的设置信息 var setting = data.getSetting(event.data.treeId); // 在鼠标抬起事件之前执行回调函数,如果返回true则继续执行后续操作 if (tools.apply(setting.callback.beforeMouseUp, [setting.treeId, node], true)) { // 执行鼠标抬起事件的回调函数 tools.apply(setting.callback.onMouseUp, [event, setting.treeId, node]); } return true; // 始终返回true,表示事件处理完成 }, onZTreeDblclick: function (event, node) { // 获取当前树的设置信息 var setting = data.getSetting(event.data.treeId); // 在双击事件之前执行回调函数,如果返回true则继续执行后续操作 if (tools.apply(setting.callback.beforeDblClick, [setting.treeId, node], true)) { // 执行双击事件的回调函数 tools.apply(setting.callback.onDblClick, [event, setting.treeId, node]); } return true; // 始终返回true,表示事件处理完成 }, onZTreeContextmenu: function (event, node) { // 获取当前树的设置信息 var setting = data.getSetting(event.data.treeId); // 在右键点击事件之前执行回调函数,如果返回true则继续执行后续操作 if (tools.apply(setting.callback.beforeRightClick, [setting.treeId, node], true)) { // 执行右键点击事件的回调函数 tools.apply(setting.callback.onRightClick, [event, setting.treeId, node]); } // 检查是否有定义右键点击的回调函数,如果没有则返回false return (typeof setting.callback.onRightClick) != "function"; }, onSelectStart: function (e) { // 获取触发事件的元素节点名称并转换为小写 var n = e.originalEvent.srcElement.nodeName.toLowerCase(); // 如果元素是input或textarea,则允许选择文本 return (n === "input" || n === "textarea"); } }, //method of tools for zTree tools = { /** * 应用函数,如果传入的参数是函数则调用该函数并返回结果,否则返回默认值。 * @param {Function} fun - 要应用的函数 * @param {Array} param - 函数参数数组 * @param {*} defaultValue - 默认值 * @returns {*} 函数执行结果或默认值 */ apply: function (fun, param, defaultValue) { if ((typeof fun) == "function") { // 如果fun是函数,则使用apply方法调用它 return fun.apply(zt, param ? param : []); } // 如果fun不是函数,返回默认值 return defaultValue; }, /** * 判断节点是否可以异步加载子节点。 * @param {Object} setting - 配置对象 * @param {Object} node - 当前节点 * @returns {boolean} 是否可以异步加载子节点 */ canAsync: function (setting, node) { // 获取节点的子节点 var children = data.nodeChildren(setting, node); // 判断节点是否是父节点 var isParent = data.nodeIsParent(setting, node); // 判断是否启用了异步加载,并且节点存在且是父节点,同时没有异步标记且没有子节点 return (setting.async.enable && node && isParent && !(node.zAsync || (children && children.length > 0))); }, /** * 深度克隆一个对象。 * @param {Object} obj - 要克隆的对象 * @returns {Object} 克隆后的新对象 */ clone: function (obj) { if (obj === null) return null; // 如果对象为null,直接返回null // 根据对象类型创建新对象 var o = tools.isArray(obj) ? [] : {}; for (var i in obj) { // 递归克隆对象属性 o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? tools.clone(obj[i]) : obj[i]); } return o; // 返回克隆后的对象 }, /** * 比较两个字符串是否相等(忽略大小写)。 * @param {string} str1 - 第一个字符串 * @param {string} str2 - 第二个字符串 * @returns {boolean} 字符串是否相等 */ eqs: function (str1, str2) { return str1.toLowerCase() === str2.toLowerCase(); // 转换为小写后比较 }, /** * 判断一个对象是否是数组。 * @param {Object} arr - 要判断的对象 * @returns {boolean} 是否是数组 */ isArray: function (arr) { return Object.prototype.toString.apply(arr) === "[object Array]"; // 通过原型链判断是否为数组 }, /** * 判断一个对象是否是HTML元素。 * @param {Object} o - 要判断的对象 * @returns {boolean} 是否是HTML元素 */ isElement: function (o) { return ( typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string" //DOM3 ); }, $: function (node, exp, setting) { // 检查exp是否存在且不是字符串类型 if (!!exp && typeof exp != "string") { // 如果exp存在且不是字符串,则将setting赋值为exp,并将exp置为空字符串 setting = exp; exp = ""; } // 检查node是否为字符串类型 if (typeof node == "string") { // 如果node是字符串,则根据setting中的treeObj获取文档对象并返回相应的jQuery对象 return $(node, setting ? setting.treeObj.get(0).ownerDocument : null); } else { // 如果node不是字符串,则根据node的tId和exp构建选择器,并根据setting中的treeObj返回相应的jQuery对象 return $("#" + node.tId + exp, setting ? setting.treeObj : null); } }, getMDom: function (setting, curDom, targetExpr) { // 如果当前DOM节点为空,返回null if (!curDom) return null; // 循环遍历父节点直到找到匹配的树ID或到达根节点 while (curDom && curDom.id !== setting.treeId) { // 遍历目标表达式数组 for (var i = 0, l = targetExpr.length; curDom.tagName && i < l; i++) { // 检查当前DOM节点的标签名和属性是否匹配目标表达式 if (tools.eqs(curDom.tagName, targetExpr[i].tagName) && curDom.getAttribute(targetExpr[i].attrName) !== null) { // 如果匹配,返回当前DOM节点 return curDom; } } // 移动到父节点继续检查 curDom = curDom.parentNode; } // 如果没有找到匹配的节点,返回null return null; }, getNodeMainDom: function (target) { // 获取目标元素的父级
  • 元素,如果不存在则获取最近的祖先
  • 元素 return ($(target).parent("li").get(0) || $(target).parentsUntil("li").parent().get(0)); }, isChildOrSelf: function (dom, parentId) { // 判断给定的DOM节点是否是指定父节点的子节点或自身 return ($(dom).closest("#" + parentId).length > 0); }, uCanDo: function (setting, e) { // 默认返回true,表示可以执行操作 return true; } //method of operate ztree dom view = { // 添加节点的方法 addNodes: function (setting, parentNode, index, newNodes, isSilent) { // 判断父节点是否是父节点类型 var isParent = data.nodeIsParent(setting, parentNode); // 如果配置中要求保持叶子节点,并且父节点存在且不是父节点类型,则直接返回 if (setting.data.keep.leaf && parentNode && !isParent) { return; } // 如果新节点不是数组,则将其转换为数组 if (!tools.isArray(newNodes)) { newNodes = [newNodes]; } // 如果启用了简单数据格式,则将新节点转换为zTree格式 if (setting.data.simpleData.enable) { newNodes = data.transformTozTreeFormat(setting, newNodes); } // 如果父节点存在 if (parentNode) { // 获取父节点的开关对象、图标对象和子节点容器对象 var target_switchObj = $$(parentNode, consts.id.SWITCH, setting), target_icoObj = $$(parentNode, consts.id.ICON, setting), target_ulObj = $$(parentNode, consts.id.UL, setting); // 如果父节点未打开 if (!parentNode.open) { // 替换父节点的开关类为关闭状态 view.replaceSwitchClass(parentNode, target_switchObj, consts.folder.CLOSE); // 替换父节点的图标类为关闭状态 view.replaceIcoClass(parentNode, target_icoObj, consts.folder.CLOSE); // 设置父节点为关闭状态 parentNode.open = false; // 隐藏父节点的子节点容器 target_ulObj.css({ "display": "none" }); } data.addNodesData(setting, parentNode, index, newNodes); // 将新节点数据添加到父节点中 view.createNodes(setting, parentNode.level + 1, newNodes, parentNode, index); // 创建并显示新节点,设置其层级为父节点的层级加一 if (!isSilent) { view.expandCollapseParentNode(setting, parentNode, true); } // 如果不需要静默处理,则展开父节点以显示新添加的子节点 } else { data.addNodesData(setting, data.getRoot(setting), index, newNodes); // 如果没有父节点,则将新节点数据添加到根节点中 view.createNodes(setting, 0, newNodes, null, index); // 创建并显示新节点,设置其层级为0(根节点) } }, appendNodes: function (setting, level, nodes, parentNode, index, initFlag, openFlag) { if (!nodes) return []; // 如果节点列表为空,返回空数组 var html = []; // 初始化HTML数组,用于存储生成的HTML代码片段 var tmpPNode = (parentNode) ? parentNode : data.getRoot(setting), // 获取父节点,如果未指定父节点,则使用根节点 tmpPChild = data.nodeChildren(setting, tmpPNode), // 获取父节点的子节点列表 isFirstNode, isLastNode; // 定义两个变量,用于标记是否是第一个或最后一个节点 if (!tmpPChild || index >= tmpPChild.length - nodes.length) { index = -1; } // 如果父节点没有子节点,或者索引超出范围,则将索引设置为-1,表示在末尾追加节点 // 遍历节点数组 for (var i = 0, l = nodes.length; i < l; i++) { // 获取当前节点 var node = nodes[i]; // 如果初始化标志为真 if (initFlag) { // 判断是否为第一个节点 isFirstNode = ((index === 0 || tmpPChild.length == nodes.length) && (i == 0)); // 判断是否为最后一个节点 isLastNode = (index < 0 && i == (nodes.length - 1)); // 初始化节点 data.initNode(setting, level, node, parentNode, isFirstNode, isLastNode, openFlag); // 将节点添加到缓存中 data.addNodeCache(setting, node); } // 检查节点是否为父节点 var isParent = data.nodeIsParent(setting, node); // 存储子节点的HTML内容 var childHtml = []; // 获取子节点 var children = data.nodeChildren(setting, node); // 如果存在子节点 if (children && children.length > 0) { // 先生成子节点的HTML,因为需要检查类型 childHtml = view.appendNodes(setting, level + 1, children, node, -1, initFlag, openFlag && node.open); } // 如果打开标志为真 if (openFlag) { // 生成主节点前的DOM结构 view.makeDOMNodeMainBefore(html, setting, node); // 生成节点连接线 view.makeDOMNodeLine(html, setting, node); // 获取节点前的内容 data.getBeforeA(setting, node, html); // 生成节点名称前的DOM结构 view.makeDOMNodeNameBefore(html, setting, node); // 获取节点内部前的内容 data.getInnerBeforeA(setting, node, html); // 生成节点图标 view.makeDOMNodeIcon(html, setting, node); // 获取节点内部后的内容 data.getInnerAfterA(setting, node, html); // 生成节点名称后的DOM结构 view.makeDOMNodeNameAfter(html, setting, node); // 获取节点后的内容 data.getAfterA(setting, node, html); // 如果节点是父节点且已打开 if (isParent && node.open) { // 生成包含子节点的UL HTML结构 view.makeUlHtml(setting, node, html, childHtml.join('')); } // 生成主节点后的DOM结构 view.makeDOMNodeMainAfter(html, setting, node); // 将创建的节点添加到已创建节点列表中 data.addCreatedNode(setting, node); } } return html; }, /** * 将父节点的UL DOM元素追加到指定的节点上 * @param {Object} setting - 配置对象 * @param {Object} node - 当前节点对象 */ appendParentULDom: function (setting, node) { // 初始化HTML数组 var html = [], // 获取当前节点对应的jQuery对象 nObj = $$(node, setting); // 如果当前节点不存在且有父节点ID,则递归调用自身 if (!nObj.get(0) && !!node.parentTId) { view.appendParentULDom(setting, node.getParentNode()); nObj = $$(node, setting); } // 获取当前节点对应的UL jQuery对象 var ulObj = $$(node, consts.id.UL, setting); // 如果UL对象存在,则移除它 if (ulObj.get(0)) { ulObj.remove(); } // 获取子节点数据 var children = data.nodeChildren(setting, node), // 生成子节点的HTML内容 childHtml = view.appendNodes(setting, node.level + 1, children, node, -1, false, true); // 生成UL HTML内容并追加到html数组中 view.makeUlHtml(setting, node, html, childHtml.join('')); // 将生成的HTML内容追加到当前节点对象中 nObj.append(html.join('')); }, /** * 异步加载节点数据 * @param {Object} setting - 配置对象 * @param {Object} node - 当前节点对象 * @param {Boolean} isSilent - 是否静默加载 * @param {Function} callback - 回调函数 */ asyncNode: function (setting, node, isSilent, callback) { var i, l; // 判断当前节点是否是父节点 var isParent = data.nodeIsParent(setting, node); // 如果当前节点不是父节点,直接执行回调函数并返回false if (node && !isParent) { tools.apply(callback); return false; // 如果当前节点正在异步加载,直接返回false } else if (node && node.isAjaxing) { return false; // 如果beforeAsync回调函数返回false,则执行回调函数并返回false } else if (tools.apply(setting.callback.beforeAsync, [setting.treeId, node], true) == false) { tools.apply(callback); return false; } if (node) { // 如果节点存在,设置节点的isAjaxing属性为true,表示该节点正在进行异步请求 node.isAjaxing = true; // 获取节点对应的图标对象 var icoObj = $$(node, consts.id.ICON, setting); // 更新图标对象的样式和类名,显示加载中的动画效果 icoObj.attr({"style": "", "class": consts.className.BUTTON + " " + consts.className.ICO_LOADING}); } // 初始化一个空对象,用于存储参数 var tmpParam = {}; // 调用工具函数生成自动参数数组 var autoParam = tools.apply(setting.async.autoParam, [setting.treeId, node], setting.async.autoParam); // 遍历自动参数数组 for (i = 0, l = autoParam.length; node && i < l; i++) { // 将每个参数按等号分割成键值对 var pKey = autoParam[i].split("="), spKey = pKey; if (pKey.length > 1) { // 如果参数包含等号,则取等号后面的部分作为键 spKey = pKey[1]; // 取等号前面的部分作为值 pKey = pKey[0]; } // 将节点的对应属性值赋给临时参数对象 tmpParam[spKey] = node[pKey]; } // 调用工具函数生成其他参数数组 var otherParam = tools.apply(setting.async.otherParam, [setting.treeId, node], setting.async.otherParam); // 如果其他参数是数组形式 if (tools.isArray(otherParam)) { // 遍历数组,以键值对的形式添加到临时参数对象中 for (i = 0, l = otherParam.length; i < l; i += 2) { tmpParam[otherParam[i]] = otherParam[i + 1]; } } else { // 如果其他参数是对象形式 // 遍历对象,将每个属性添加到临时参数对象中 for (var p in otherParam) { tmpParam[p] = otherParam[p]; } } // 获取当前根节点的版本号 var _tmpV = data.getRoot(setting)._ver; // 发起异步请求 $.ajax({ // 设置请求的内容类型 contentType: setting.async.contentType, // 禁用缓存 cache: false, // 设置请求的类型(GET、POST等) type: setting.async.type, // 生成请求的URL,使用工具函数处理模板字符串 url: tools.apply(setting.async.url, [setting.treeId, node], setting.async.url), // 根据内容类型决定发送的数据格式 data: setting.async.contentType.indexOf('application/json') > -1 ? JSON.stringify(tmpParam) : tmpParam, // 设置期望的响应数据类型 dataType: setting.async.dataType, // 设置请求头信息 headers: setting.async.headers, // 设置XHR字段 xhrFields: setting.async.xhrFields, // 请求成功时的回调函数 success: function (msg) { // 如果版本号发生变化,则直接返回,不进行后续处理 if (_tmpV != data.getRoot(setting)._ver) { return; } // 初始化新节点数组 var newNodes = []; try { // 如果响应为空或长度为0,则新节点数组为空 if (!msg || msg.length == 0) { newNodes = []; // 如果响应是字符串,则尝试解析为JSON对象 } else if (typeof msg == "string") { newNodes = eval("(" + msg + ")"); // 否则直接将响应赋值给新节点数组 } else { newNodes = msg; } } catch (err) { // 如果解析过程中发生错误,则将原始响应赋值给新节点数组 newNodes = msg; } if (node) { // 将节点的isAjaxing属性设置为null,表示该节点不再进行异步操作 node.isAjaxing = null; // 设置节点的zAsync属性为true,表示该节点是异步加载的 node.zAsync = true; } // 更新节点的图标和线条样式 view.setNodeLineIcos(setting, node); if (newNodes && newNodes !== "") { // 应用数据过滤器处理新节点数据 newNodes = tools.apply(setting.async.dataFilter, [setting.treeId, node, newNodes], newNodes); // 添加新节点到视图中 view.addNodes(setting, node, -1, !!newNodes ? tools.clone(newNodes) : [], !!isSilent); } else { // 如果新节点数据为空,则添加空数组作为新节点 view.addNodes(setting, node, -1, [], !!isSilent); } // 触发异步成功事件 setting.treeObj.trigger(consts.event.ASYNC_SUCCESS, [setting.treeId, node, msg]); // 执行回调函数 tools.apply(callback); }, error: function (XMLHttpRequest, textStatus, errorThrown) { // 如果版本号不一致,直接返回 if (_tmpV != data.getRoot(setting)._ver) { return; } // 将节点的isAjaxing属性设置为null,表示该节点不再进行异步操作 if (node) node.isAjaxing = null; // 更新节点的图标和线条样式 view.setNodeLineIcos(setting, node); // 触发异步错误事件 setting.treeObj.trigger(consts.event.ASYNC_ERROR, [setting.treeId, node, XMLHttpRequest, textStatus, errorThrown]); } }, return true; }, cancelPreSelectedNode: function (setting, node, excludeNode) { // 获取当前选中的节点列表 var list = data.getRoot(setting).curSelectedList, i, n; // 从后向前遍历选中的节点列表 for (i = list.length - 1; i >= 0; i--) { n = list[i]; // 如果当前节点是需要取消选中的节点或排除节点,则进行处理 if (node === n || (!node && (!excludeNode || excludeNode !== n))) { // 移除节点的选中样式 $$(n, consts.id.A, setting).removeClass(consts.node.CURSELECTED); if (node) { // 从选中列表中移除该节点 data.removeSelectedNode(setting, node); break; } else { // 从选中列表中删除该节点并触发取消选中事件 list.splice(i, 1); setting.treeObj.trigger(consts.event.UNSELECTED, [setting.treeId, n]); } } } }, /** * 创建节点回调函数 * @param {Object} setting - 配置对象,包含回调和视图设置 */ createNodeCallback: function (setting) { // 检查是否有自定义的节点创建回调或添加自定义DOM的方法 if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) { // 获取根节点数据 var root = data.getRoot(setting); // 当根节点的已创建节点列表不为空时循环处理 while (root.createdNodes.length > 0) { // 从已创建节点列表中移除并获取第一个节点 var node = root.createdNodes.shift(); // 调用视图中的添加自定义DOM方法 tools.apply(setting.view.addDiyDom, [setting.treeId, node]); // 如果存在节点创建回调,则触发该回调事件 if (!!setting.callback.onNodeCreated) { setting.treeObj.trigger(consts.event.NODECREATED, [setting.treeId, node]); } } } }, createNodes: function (setting, level, nodes, parentNode, index) { // 如果节点为空或长度为0,则直接返回 if (!nodes || nodes.length == 0) return; // 获取根节点 var root = data.getRoot(setting), // 判断是否打开父节点的标志 openFlag = !parentNode || parentNode.open || !!$$(data.nodeChildren(setting, parentNode)[0], setting).get(0); // 初始化创建的节点数组 root.createdNodes = []; // 生成节点的HTML并追加到视图中 var zTreeHtml = view.appendNodes(setting, level, nodes, parentNode, index, true, openFlag), parentObj, nextObj; // 如果没有父节点,则将生成的HTML追加到树对象中 if (!parentNode) { parentObj = setting.treeObj; //setting.treeObj.append(zTreeHtml.join('')); } else { // 获取父节点的UL对象 var ulObj = $$(parentNode, consts.id.UL, setting); if (ulObj.get(0)) { parentObj = ulObj; //ulObj.append(zTreeHtml.join('')); } } // 如果父对象存在 if (parentObj) { // 如果索引大于等于0,则获取下一个兄弟节点 if (index >= 0) { nextObj = parentObj.children()[index]; } // 如果索引大于等于0且下一个兄弟节点存在,则在该节点之前插入生成的HTML if (index >= 0 && nextObj) { $(nextObj).before(zTreeHtml.join('')); } else { // 否则直接在父对象中追加生成的HTML parentObj.append(zTreeHtml.join('')); } } view.createNodeCallback(setting); }, destroy: function (setting) { // 如果 setting 为空,直接返回 if (!setting) return; // 初始化缓存 data.initCache(setting); // 初始化根节点 data.initRoot(setting); // 解绑树的事件 event.unbindTree(setting); // 解绑其他事件 event.unbindEvent(setting); // 清空树对象的内容 setting.treeObj.empty(); // 删除 settings 中对应的树设置 delete settings[setting.treeId]; }, expandCollapseNode: function (setting, node, expandFlag, animateFlag, callback) { // 获取根节点 var root = data.getRoot(setting); var tmpCb, _callback; // 如果 node 为空,执行回调并返回 if (!node) { tools.apply(callback, []); return; } // 获取子节点 var children = data.nodeChildren(setting, node); // 判断是否为父节点 var isParent = data.nodeIsParent(setting, node); // 如果根节点的 expandTriggerFlag 为真 if (root.expandTriggerFlag) { _callback = callback; tmpCb = function () { // 如果存在回调函数,则执行回调 if (_callback) _callback(); // 根据节点的 open 状态触发相应的事件 if (node.open) { setting.treeObj.trigger(consts.event.EXPAND, [setting.treeId, node]); } else { setting.treeObj.trigger(consts.event.COLLAPSE, [setting.treeId, node]); } }; callback = tmpCb; // 将临时回调函数赋值给回调变量 root.expandTriggerFlag = false; // 重置根节点的展开触发标志 } if (!node.open && isParent && ((!$$(node, consts.id.UL, setting).get(0)) || (children && children.length > 0 && !$$(children[0], setting).get(0)))) { // 如果节点未打开且是父节点,并且没有子节点或子节点的第一个元素不存在 view.appendParentULDom(setting, node); // 为父节点添加子节点的DOM结构 view.createNodeCallback(setting); // 创建节点的回调函数 } if (node.open == expandFlag) { // 如果节点的打开状态与期望的展开标志相同 tools.apply(callback, []); // 执行回调函数 return; // 结束函数执行 } var ulObj = $$(node, consts.id.UL, setting), // 获取节点的UL对象 switchObj = $$(node, consts.id.SWITCH, setting), // 获取节点的开关对象 icoObj = $$(node, consts.id.ICON, setting); // 获取节点的图标对象 if (isParent) { // 如果节点是父节点 node.open = !node.open; // 切换节点的打开状态 if (node.iconOpen && node.iconClose) { // 如果节点有打开和关闭的图标 icoObj.attr("style", view.makeNodeIcoStyle(setting, node)); // 根据节点的状态设置图标样式 } if (node.open) { // 如果节点是打开状态,替换开关类为打开状态的样式 view.replaceSwitchClass(node, switchObj, consts.folder.OPEN); // 替换图标类为打开状态的样式 view.replaceIcoClass(node, icoObj, consts.folder.OPEN); // 如果没有动画标志或展开速度为空,直接显示子节点 if (animateFlag == false || setting.view.expandSpeed == "") { ulObj.show(); // 执行回调函数 tools.apply(callback, []); } else { // 如果有子节点,使用滑动效果展开子节点 if (children && children.length > 0) { ulObj.slideDown(setting.view.expandSpeed, callback); } else { // 否则直接显示子节点 ulObj.show(); // 执行回调函数 tools.apply(callback, []); } } } else { // 如果节点是关闭状态,替换开关类为关闭状态的样式 view.replaceSwitchClass(node, switchObj, consts.folder.CLOSE); // 替换图标类为关闭状态的样式 view.replaceIcoClass(node, icoObj, consts.folder.CLOSE); // 如果没有动画标志、展开速度为空或没有子节点,直接隐藏子节点 if (animateFlag == false || setting.view.expandSpeed == "" || !(children && children.length > 0)) { ulObj.hide(); // 执行回调函数 tools.apply(callback, []); } else { // 否则使用滑动效果隐藏子节点 ulObj.slideUp(setting.view.expandSpeed, callback); } } } else { // 如果节点不存在,直接执行回调函数 tools.apply(callback, []); } expandCollapseParentNode: function (setting, node, expandFlag, animateFlag, callback) { // 如果节点不存在,直接返回 if (!node) return; // 如果节点没有父节点ID,则直接展开或折叠该节点 if (!node.parentTId) { view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback); return; } else { // 否则先展开或折叠当前节点 view.expandCollapseNode(setting, node, expandFlag, animateFlag); } // 如果节点有父节点ID,递归展开或折叠父节点 if (node.parentTId) { view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, animateFlag, callback); } }, /** * 展开或折叠子节点 * @param {Object} setting - 配置对象 * @param {Object} node - 当前节点 * @param {Boolean} expandFlag - 是否展开 * @param {Boolean} animateFlag - 是否带动画 * @param {Function} callback - 回调函数 */ expandCollapseSonNode: function (setting, node, expandFlag, animateFlag, callback) { // 获取根节点 var root = data.getRoot(setting), // 获取子节点列表 treeNodes = (node) ? data.nodeChildren(setting, node) : data.nodeChildren(setting, root), // 如果是根节点,则使用传入的动画标志,否则不使用动画 selfAnimateSign = (node) ? false : animateFlag, // 保存当前的展开触发标志 expandTriggerFlag = data.getRoot(setting).expandTriggerFlag; // 暂时禁用展开触发标志 data.getRoot(setting).expandTriggerFlag = false; // 如果存在子节点列表,遍历并展开或折叠每个子节点 if (treeNodes) { for (var i = 0, l = treeNodes.length; i < l; i++) { if (treeNodes[i]) view.expandCollapseSonNode(setting, treeNodes[i], expandFlag, selfAnimateSign); } } // 恢复原来的展开触发标志 data.getRoot(setting).expandTriggerFlag = expandTriggerFlag; // 最后展开或折叠当前节点 view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback); }, /** * 判断节点是否被选中 * @param {Object} setting - 配置对象 * @param {Object} node - 当前节点 * @returns {Boolean} - 是否被选中 */ isSelectedNode: function (setting, node) { // 如果节点不存在,返回false if (!node) { return false; } // 获取当前选中的节点列表 var list = data.getRoot(setting).curSelectedList, i; // 遍历选中的节点列表,检查是否存在与当前节点相同的节点 for (i = list.length - 1; i >= 0; i--) { if (node === list[i]) { return true; } } // 如果未找到匹配的节点,返回false return false; }, makeDOMNodeIcon: function (html, setting, node) { // 获取节点名称字符串 var nameStr = data.nodeName(setting, node), // 根据设置决定是否将名称作为HTML显示,否则进行转义处理 name = setting.view.nameIsHTML ? nameStr : nameStr.replace(/&/g, '&').replace(//g, '>'); // 生成包含图标和名称的HTML片段并推入html数组中 html.push("", name, ""); }, makeDOMNodeLine: function (html, setting, node) { // 生成节点线(用于表示树结构的连接线)的HTML片段并推入html数组中 html.push(""); }, makeDOMNodeMainAfter: function (html, setting, node) { // 在主节点的HTML片段后添加结束标签
  • html.push(""); }, makeDOMNodeMainBefore: function (html, setting, node) { // 在主节点的HTML片段前添加开始标签
  • ,并设置相关属性 html.push("
  • "); }, makeDOMNodeNameAfter: function (html, setting, node) { // 在节点名称的HTML片段后添加结束标签 html.push(""); }, makeDOMNodeNameBefore: function (html, setting, node) { // 获取节点标题、URL、字体CSS样式和节点类名 var title = data.nodeTitle(setting, node), url = view.makeNodeUrl(setting, node), fontcss = view.makeNodeFontCss(setting, node), nodeClasses = view.makeNodeClasses(setting, node), fontStyle = []; // 遍历字体CSS样式对象,将其转换为字符串形式并推入fontStyle数组中 for (var f in fontcss) { fontStyle.push(f, ":", fontcss[f], ";"); } html.push(" 0) ? " href='" + url + "'" : ""), " target='", view.makeNodeTarget(node), "' style='", fontStyle.join(''), "'"); // 如果需要显示标题并且标题存在,则添加title属性 if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle) && title) { html.push("title='", title.replace(/'/g, "'").replace(//g, '>'), "'"); } html.push(">"); }, makeNodeFontCss: function (setting, node) { // 获取节点的字体样式 var fontCss = tools.apply(setting.view.fontCss, [setting.treeId, node], setting.view.fontCss); // 如果fontCss存在且不是函数,则返回fontCss,否则返回空对象 return (fontCss && ((typeof fontCss) != "function")) ? fontCss : {}; }, makeNodeClasses: function (setting, node) { // 获取节点的类名 var classes = tools.apply(setting.view.nodeClasses, [setting.treeId, node], setting.view.nodeClasses); // 如果classes存在且不是函数,则返回classes,否则返回默认的类名对象 return (classes && (typeof classes !== "function")) ? classes : {add:[], remove:[]}; }, makeNodeIcoClass: function (setting, node) { // 初始化图标类名数组 var icoCss = ["ico"]; // 如果节点没有正在加载数据 if (!node.isAjaxing) { // 判断节点是否是父节点 var isParent = data.nodeIsParent(setting, node); // 根据节点是否有iconSkin设置前缀 icoCss[0] = (node.iconSkin ? node.iconSkin + "_" : "") + icoCss[0]; if (isParent) { // 如果是父节点,根据节点是否展开添加相应的图标类名 icoCss.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE); } else { // 如果不是父节点,添加文档图标类名 icoCss.push(consts.folder.DOCU); } } // 返回拼接后的按钮类名和图标类名 return consts.className.BUTTON + " " + icoCss.join('_'); }, makeNodeIcoStyle: function (setting, node) { // 初始化图标样式数组 var icoStyle = []; // 如果节点没有进行Ajax请求 if (!node.isAjaxing) { // 判断节点是否是父节点 var isParent = data.nodeIsParent(setting, node); // 根据节点状态选择相应的图标 var icon = (isParent && node.iconOpen && node.iconClose) ? (node.open ? node.iconOpen : node.iconClose) : node[setting.data.key.icon]; // 如果存在图标,则添加背景图片样式 if (icon) icoStyle.push("background:url(", icon, ") 0 0 no-repeat;"); // 如果视图设置中不显示图标或应用showIcon函数返回false,则隐藏图标 if (setting.view.showIcon == false || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)) { icoStyle.push("display:none;"); } } // 返回拼接后的图标样式字符串 return icoStyle.join(''); }, makeNodeLineClass: function (setting, node) { // 初始化线条样式数组 var lineClass = []; // 如果视图设置中显示线条 if (setting.view.showLine) { // 根据节点层级和位置添加相应的线条样式 // 检查节点是否为根节点且是第一个和最后一个节点 if (node.level == 0 && node.isFirstNode && node.isLastNode) { // 将根节点的样式类添加到lineClass数组中 lineClass.push(consts.line.ROOT); } else if (node.level == 0 && node.isFirstNode) { // 检查节点是否为根节点且是第一个节点 // 将根节点的第一个样式类添加到lineClass数组中 lineClass.push(consts.line.ROOTS); } else if (node.isLastNode) { // 检查节点是否是最后一个节点 // 将底部节点的样式类添加到lineClass数组中 lineClass.push(consts.line.BOTTOM); } else { // 默认情况下,将中心节点的样式类添加到lineClass数组中 lineClass.push(consts.line.CENTER); } } else { // 如果不显示线条,则添加无线条样式 lineClass.push(consts.line.NOLINE); } // 根据节点是否为父节点添加文件夹样式 if (data.nodeIsParent(setting, node)) { lineClass.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE); } else { lineClass.push(consts.folder.DOCU); } // 返回拼接后的线条样式字符串,并调用makeNodeLineClassEx方法进一步处理 return view.makeNodeLineClassEx(node) + lineClass.join('_'); }, makeNodeLineClassEx: function (node) { // 返回节点的按钮、层级和开关样式字符串 return consts.className.BUTTON + " " + consts.className.LEVEL + node.level + " " + consts.className.SWITCH + " "; }, makeNodeTarget: function (node) { // 返回节点的目标属性,如果不存在则默认为"_blank" return (node.target || "_blank"); }, makeNodeUrl: function (setting, node) { // 获取URL键值 var urlKey = setting.data.key.url; // 返回节点的URL,如果不存在则返回null return node[urlKey] ? node[urlKey] : null; }, makeUlHtml: function (setting, node, html, content) { // 生成UL元素的HTML代码,并根据节点状态设置显示或隐藏 html.push(""); }, makeUlLineClass: function (setting, node) { // 如果视图显示线条且节点不是最后一个节点,则返回线条类名,否则返回空字符串 return ((setting.view.showLine && !node.isLastNode) ? consts.line.LINE : ""); }, removeChildNodes: function (setting, node) { // 如果节点不存在,直接返回 if (!node) return; // 获取节点的子节点列表 var nodes = data.nodeChildren(setting, node); // 如果子节点列表不存在,直接返回 if (!nodes) return; // 遍历所有子节点 for (var i = 0, l = nodes.length; i < l; i++) { // 移除每个子节点的缓存 data.removeNodeCache(setting, nodes[i]); } data.removeSelectedNode(setting); // 移除选中的节点 delete node[setting.data.key.children]; // 删除节点的子节点属性 if (!setting.data.keep.parent) { // 如果设置中不保留父节点 data.nodeIsParent(setting, node, false); // 将当前节点设置为非父节点 node.open = false; // 关闭当前节点 var tmp_switchObj = $$(node, consts.id.SWITCH, setting), // 获取当前节点的开关对象 tmp_icoObj = $$(node, consts.id.ICON, setting); // 获取当前节点的图标对象 view.replaceSwitchClass(node, tmp_switchObj, consts.folder.DOCU); // 替换开关对象的类为文档文件夹样式 view.replaceIcoClass(node, tmp_icoObj, consts.folder.DOCU); // 替换图标对象的类为文档文件夹样式 $$(node, consts.id.UL, setting).remove(); // 移除当前节点的子节点列表 } else { $$(node, consts.id.UL, setting).empty(); // 如果保留父节点,则清空子节点列表 } scrollIntoView: function (setting, dom) { // 如果dom不存在,直接返回 if (!dom) { return; } // 支持IE 7 / 8 if (typeof Element === 'undefined' || typeof HTMLElement === 'undefined') { // 获取容器的边界矩形 var contRect = setting.treeObj.get(0).getBoundingClientRect(), // 获取目标元素的边界矩形 findMeRect = dom.getBoundingClientRect(); // 检查目标元素是否在容器视图之外 if (findMeRect.top < contRect.top || findMeRect.bottom > contRect.bottom || findMeRect.right > contRect.right || findMeRect.left < contRect.left) { // 将目标元素滚动到视图中 dom.scrollIntoView(); } return; } // CC-BY jocki84@googlemail.com, https://gist.github.com/jocki84/6ffafd003387179a988e if (!Element.prototype.scrollIntoViewIfNeeded) { // 如果浏览器不支持 scrollIntoViewIfNeeded 方法,则定义该方法 Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) { "use strict"; // 创建一个表示范围的对象,包含起始位置和长度 function makeRange(start, length) { return {"start": start, "length": length, "end": start + length}; } // 根据是否需要居中来调整范围的覆盖方式 function coverRange(inner, outer) { if ( false === centerIfNeeded || (outer.start < inner.end && inner.start < outer.end) ) { // 如果不居中或内外范围有重叠,返回最大值或最小值 return Math.max( inner.end - outer.length, Math.min(outer.start, inner.start) ); } // 如果需要居中,返回中间值 return (inner.start + inner.end - outer.length) / 2; } // 创建一个表示点的对象,包含 x 和 y 坐标以及平移方法 function makePoint(x, y) { return { "x": x, "y": y, "translate": function translate(dX, dY) { // 平移点的位置 return makePoint(x + dX, y + dY); } }; } /** * 计算元素在页面中的绝对位置。 * @param {HTMLElement} elem - 需要计算位置的元素。 * @param {Point} pt - 初始点,用于累加偏移量。 * @returns {Point} - 返回元素的绝对位置。 */ function absolute(elem, pt) { // 当元素存在时循环执行 while (elem) { // 将当前元素的偏移量累加到初始点上 pt = pt.translate(elem.offsetLeft, elem.offsetTop); // 获取当前元素的父级偏移容器 elem = elem.offsetParent; } // 返回计算后的绝对位置 return pt; } var target = absolute(this, makePoint(0, 0)), // 计算目标元素的绝对位置 extent = makePoint(this.offsetWidth, this.offsetHeight), // 获取目标元素的宽度和高度 elem = this.parentNode, // 获取目标元素的父节点 origin; // 定义变量origin用于存储当前元素的绝对位置 while (elem instanceof HTMLElement) { // 当elem是HTML元素时循环执行 // Apply desired scroll amount. origin = absolute(elem, makePoint(elem.clientLeft, elem.clientTop)); // 计算当前元素的绝对位置 elem.scrollLeft = coverRange( // 设置水平滚动条的位置,使其覆盖目标范围 makeRange(target.x - origin.x, extent.x), // 计算目标范围的左边界 makeRange(elem.scrollLeft, elem.clientWidth) // 计算当前可视区域的范围 ); elem.scrollTop = coverRange( // 设置垂直滚动条的位置,使其覆盖目标范围 makeRange(target.y - origin.y, extent.y), // 计算目标范围的上边界 makeRange(elem.scrollTop, elem.clientHeight) // 计算当前可视区域的范围 ); // Determine actual scroll amount by reading back scroll properties. target = target.translate(-elem.scrollLeft, -elem.scrollTop); // 将目标元素的位置平移,以考虑滚动条的偏移量 elem = elem.parentNode; // 将当前元素更新为其父节点,以便继续向上遍历DOM树 dom.scrollIntoViewIfNeeded(); // 如果需要,将DOM元素滚动到视图中 setFirstNode: function (setting, parentNode) { // 定义一个方法,用于设置父节点的第一个子节点 var children = data.nodeChildren(setting, parentNode); // 获取父节点的所有子节点 if (children.length > 0) { // 如果父节点有子节点 children[0].isFirstNode = true; // 将第一个子节点标记为第一个节点 } }, setLastNode: function (setting, parentNode) { // 获取父节点的所有子节点 var children = data.nodeChildren(setting, parentNode); // 如果子节点数组不为空,则将最后一个子节点标记为isLastNode if (children.length > 0) { children[children.length - 1].isLastNode = true; } }, removeNode: function (setting, node) { // 获取根节点 var root = data.getRoot(setting), // 获取父节点,如果当前节点有parentTId属性,则获取其父节点,否则获取根节点 parentNode = (node.parentTId) ? node.getParentNode() : root; // 将当前节点的isFirstNode和isLastNode属性设为false node.isFirstNode = false; node.isLastNode = false; // 定义一个方法,返回null,表示没有前一个节点 node.getPreNode = function () { return null; }; // 定义一个方法,返回null,表示没有下一个节点 node.getNextNode = function () { return null; }; // 检查节点缓存是否存在,如果不存在则直接返回 if (!data.getNodeCache(setting, node.tId)) { return; } // 从DOM中移除节点 $$(node, setting).remove(); // 从节点缓存中移除该节点 data.removeNodeCache(setting, node); // 从选中的节点列表中移除该节点 data.removeSelectedNode(setting, node); // 获取父节点的所有子节点 var children = data.nodeChildren(setting, parentNode); // 遍历所有子节点 for (var i = 0, l = children.length; i < l; i++) { // 如果找到与要删除节点相同的节点 if (children[i].tId == node.tId) { // 从子节点数组中移除该节点 children.splice(i, 1); // 退出循环 break; } } view.setFirstNode(setting, parentNode); // 设置第一个节点 view.setLastNode(setting, parentNode); // 设置最后一个节点 var tmp_ulObj, tmp_switchObj, tmp_icoObj, childLength = children.length; // 获取子节点的长度 // 修复旧父节点的子节点信息 if (!setting.data.keep.parent && childLength == 0) { // 如果旧父节点没有子节点 data.nodeIsParent(setting, parentNode, false); // 更新数据,标记该节点不是父节点 parentNode.open = false; // 关闭父节点 delete parentNode[setting.data.key.children]; // 删除父节点的子节点属性 tmp_ulObj = $$(parentNode, consts.id.UL, setting); // 获取父节点的UL对象 tmp_switchObj = $$(parentNode, consts.id.SWITCH, setting); // 获取父节点的SWITCH对象 tmp_icoObj = $$(parentNode, consts.id.ICON, setting); // 获取父节点的ICON对象 view.replaceSwitchClass(parentNode, tmp_switchObj, consts.folder.DOCU); // 替换SWITCH对象的类为文档文件夹样式 view.replaceIcoClass(parentNode, tmp_icoObj, consts.folder.DOCU); // 替换ICON对象的类为文档文件夹样式 tmp_ulObj.css("display", "none"); // 隐藏UL对象 } else if (setting.view.showLine && childLength > 0) { // 如果设置中显示线条且子节点数量大于0 var newLast = children[childLength - 1]; // 获取最后一个子节点 tmp_ulObj = $$(newLast, consts.id.UL, setting); // 获取最后一个子节点的UL对象 tmp_switchObj = $$(newLast, consts.id.SWITCH, setting); // 获取最后一个子节点的SWITCH对象 tmp_icoObj = $$(newLast, consts.id.ICON, setting); // 获取最后一个子节点的ICON对象 if (parentNode == root) { // 如果父节点是根节点 if (children.length == 1) { // 如果只有一个子节点,即该节点原本是根节点 view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.ROOT); // 替换为根节点样式 } else { var tmp_first_switchObj = $$(children[0], consts.id.SWITCH, setting); // 获取第一个子节点的SWITCH对象 view.replaceSwitchClass(children[0], tmp_first_switchObj, consts.line.ROOTS); // 替换第一个子节点为多根节点样式 view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM); // 替换最后一个子节点为底部节点样式 } } else { view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM); // 如果不是根节点,则替换最后一个子节点为底部节点样式 } tmp_ulObj.removeClass(consts.line.LINE); // 移除UL对象的线条样式 } }, /** * 替换节点的图标类名 * @param {Object} node - DOM节点对象 * @param {Object} obj - 包含class属性的对象 * @param {string} newName - 新的类名 */ replaceIcoClass: function (node, obj, newName) { // 如果obj为空或节点正在Ajax请求中,则直接返回 if (!obj || node.isAjaxing) return; // 获取对象的class属性值 var tmpName = obj.attr("class"); // 如果class属性值为undefined,则直接返回 if (tmpName == undefined) return; // 将class属性值按"_"分割成数组 var tmpList = tmpName.split("_"); // 根据newName的值进行不同的处理 switch (newName) { case consts.folder.OPEN: case consts.folder.CLOSE: case consts.folder.DOCU: // 将数组最后一个元素替换为newName tmpList[tmpList.length - 1] = newName; break; } // 将修改后的数组重新拼接成字符串并设置回对象的class属性 obj.attr("class", tmpList.join("_")); }, /** * 替换节点的类名 * @param {Object} node - 需要操作的节点对象 * @param {Object} obj - 包含类名属性的对象 * @param {string} newName - 新的类名 */ replaceSwitchClass: function (node, obj, newName) { // 如果obj为空,直接返回 if (!obj) return; // 获取当前对象的class属性值 var tmpName = obj.attr("class"); // 如果class属性值为undefined,直接返回 if (tmpName == undefined) return; // 将class属性值按"_"分割成数组 var tmpList = tmpName.split("_"); // 根据newName的值进行不同的处理 switch (newName) { case consts.line.ROOT: case consts.line.ROOTS: case consts.line.CENTER: case consts.line.BOTTOM: case consts.line.NOLINE: // 更新数组的第一个元素为新的类名 tmpList[0] = view.makeNodeLineClassEx(node) + newName; break; case consts.folder.OPEN: case consts.folder.CLOSE: case consts.folder.DOCU: // 更新数组的第二个元素为新的类名 tmpList[1] = newName; break; } // 将更新后的数组重新拼接成字符串并设置回class属性 obj.attr("class", tmpList.join("_")); // 如果newName不是DOCU,移除disabled属性;否则添加disabled属性 if (newName !== consts.folder.DOCU) { obj.removeAttr("disabled"); } else { obj.attr("disabled", "disabled"); } }, selectNode: function (setting, node, addFlag) { // 如果没有添加标志,则取消先前选择的节点 if (!addFlag) { view.cancelPreSelectedNode(setting, null, node); } // 为当前节点添加选中样式 $$(node, consts.id.A, setting).addClass(consts.node.CURSELECTED); // 将当前节点添加到已选节点列表中 data.addSelectedNode(setting, node); // 触发选中事件 setting.treeObj.trigger(consts.event.SELECTED, [setting.treeId, node]); }, setNodeFontCss: function (setting, treeNode) { // 获取节点对应的DOM对象 var aObj = $$(treeNode, consts.id.A, setting), // 生成节点字体样式 fontCss = view.makeNodeFontCss(setting, treeNode); // 如果存在字体样式,则应用到节点上 if (fontCss) { aObj.css(fontCss); } }, /** * 设置节点的CSS类 * @param {Object} setting - 配置对象 * @param {Object} treeNode - 树节点对象 */ setNodeClasses: function (setting, treeNode) { // 获取树节点对应的元素对象 var aObj = $$(treeNode, consts.id.A, setting), // 生成需要添加和移除的CSS类名集合 classes = view.makeNodeClasses(setting, treeNode); // 如果需要添加的CSS类名存在且不为空,则添加到元素中 if ('add' in classes && classes.add.length) { aObj.addClass(classes.add.join(' ')); } // 如果需要移除的CSS类名存在且不为空,则从元素中移除 if ('remove' in classes && classes.remove.length) { aObj.removeClass(classes.remove.join(' ')); } }, setNodeLineIcos: function (setting, node) { // 如果节点不存在,直接返回 if (!node) return; // 获取节点的开关对象、UL对象和图标对象 var switchObj = $$(node, consts.id.SWITCH, setting), ulObj = $$(node, consts.id.UL, setting), icoObj = $$(node, consts.id.ICON, setting), // 生成UL对象的行样式类名 ulLine = view.makeUlLineClass(setting, node); // 如果行样式类名为空,移除UL对象的行样式类名 if (ulLine.length == 0) { ulObj.removeClass(consts.line.LINE); } else { // 否则,添加行样式类名到UL对象 ulObj.addClass(ulLine); } // 设置开关对象的类名 switchObj.attr("class", view.makeNodeLineClass(setting, node)); // 如果节点是父节点,移除开关对象的禁用属性;否则,设置开关对象为禁用状态 if (data.nodeIsParent(setting, node)) { switchObj.removeAttr("disabled"); } else { switchObj.attr("disabled", "disabled"); } // 移除图标对象的内联样式 icoObj.removeAttr("style"); // 设置图标对象的内联样式和类名 icoObj.attr("style", view.makeNodeIcoStyle(setting, node)); icoObj.attr("class", view.makeNodeIcoClass(setting, node)); }, setNodeName: function (setting, node) { // 获取节点的标题 var title = data.nodeTitle(setting, node), // 获取节点对应的SPAN元素对象 nObj = $$(node, consts.id.SPAN, setting); // 清空SPAN元素的内容 nObj.empty(); // 如果视图名称是HTML格式 if (setting.view.nameIsHTML) { // 将节点的名称作为HTML内容设置到SPAN元素中 nObj.html(data.nodeName(setting, node)); } else { // 将节点的名称作为文本内容设置到SPAN元素中 nObj.text(data.nodeName(setting, node)); } // 如果需要显示标题 if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle)) { // 获取节点对应的A元素对象 var aObj = $$(node, consts.id.A, setting); // 设置A元素的title属性为节点的标题,如果标题为空则设置为空字符串 aObj.attr("title", !title ? "" : title); } }, setNodeTarget: function (setting, node) { // 获取节点对应的元素对象 var aObj = $$(node, consts.id.A, setting); // 设置元素的target属性为生成的节点目标值 aObj.attr("target", view.makeNodeTarget(node)); }, setNodeUrl: function (setting, node) { // 获取节点对应的元素对象 var aObj = $$(node, consts.id.A, setting), // 生成节点的URL url = view.makeNodeUrl(setting, node); // 如果URL为空或长度为0,则移除元素的href属性 if (url == null || url.length == 0) { aObj.removeAttr("href"); } else { // 否则,设置元素的href属性为生成的URL aObj.attr("href", url); } }, switchNode: function (setting, node) { // 如果节点已经打开,或者节点不能异步加载,则切换节点的展开/折叠状态 if (node.open || !tools.canAsync(setting, node)) { // 调用视图方法切换节点的展开/折叠状态 view.expandCollapseNode(setting, node, !node.open); } else if (setting.async.enable) { // 如果启用了异步加载,并且节点不能异步加载,则切换节点的展开/折叠状态 if (!view.asyncNode(setting, node)) { // 调用视图方法切换节点的展开/折叠状态 view.expandCollapseNode(setting, node, !node.open); return; // 结束函数执行 } } else if (node) { // 如果节点存在,则切换节点的展开/折叠状态 view.expandCollapseNode(setting, node, !node.open); } } }; // zTree defind $.fn.zTree = { // 常量对象,包含一些全局的常量值 consts: _consts, // 内部工具对象,包含各种工具方法 _z: { tools: tools, // 视图相关的方法 view: view, // 事件相关的方法 event: event, // 数据相关的方法 data: data }, /** * 获取指定树ID的zTree对象 * @param {string} treeId - 树的ID * @returns {Object|null} 返回对应的zTree对象,如果不存在则返回null */ getZTreeObj: function (treeId) { // 从data中获取zTree工具对象 var o = data.getZTreeTools(treeId); // 如果存在则返回该对象,否则返回null return o ? o : null; }, /** * 销毁指定的zTree实例或所有zTree实例 * @param {string} [treeId] - 可选参数,树的ID。如果提供则销毁指定ID的树,否则销毁所有树 */ destroy: function (treeId) { // 如果提供了treeId且其长度大于0 if (!!treeId && treeId.length > 0) { // 销毁指定ID的树 view.destroy(data.getSetting(treeId)); } else { // 遍历所有设置并销毁每一个树 for (var s in settings) { view.destroy(settings[s]); } } }, init: function (obj, zSetting, zNodes) { // 克隆默认设置对象 var setting = tools.clone(_setting); // 深度合并用户自定义设置到默认设置中 $.extend(true, setting, zSetting); // 设置树的ID为传入对象的ID setting.treeId = obj.attr("id"); // 将传入的对象赋值给树对象 setting.treeObj = obj; // 清空树对象的内容 setting.treeObj.empty(); // 将设置存储在全局settings对象中,以树ID为键 settings[setting.treeId] = setting; // 针对一些旧浏览器(例如ie6),如果不支持maxHeight样式属性,则禁用展开速度 if (typeof document.body.style.maxHeight === "undefined") { setting.view.expandSpeed = ""; } // 初始化根节点数据 data.initRoot(setting); // 获取根节点数据 var root = data.getRoot(setting); // 如果zNodes存在,则克隆并转换为数组格式;否则设置为空数组 zNodes = zNodes ? tools.clone(tools.isArray(zNodes) ? zNodes : [zNodes]) : []; // 如果启用了简单数据模式,则转换节点数据格式后添加到根节点 if (setting.data.simpleData.enable) { data.nodeChildren(setting, root, data.transformTozTreeFormat(setting, zNodes)); } else { // 否则直接添加节点数据到根节点 data.nodeChildren(setting, root, zNodes); } // 初始化缓存,传入设置参数 data.initCache(setting); // 解绑树形结构的事件,传入设置参数 event.unbindTree(setting); // 绑定树形结构的事件,传入设置参数 event.bindTree(setting); // 解绑其他事件,传入设置参数 event.unbindEvent(setting); // 绑定其他事件,传入设置参数 event.bindEvent(setting); var zTreeTools = { // 设置对象 setting: setting, /** * 添加节点函数 * @param {Object} parentNode - 父节点对象 * @param {number|string} index - 插入位置的索引或新节点数组 * @param {Array|Object} newNodes - 要添加的新节点,可以是单个节点对象或节点数组 * @param {boolean} isSilent - 是否静默添加(不触发事件) * @returns {null|undefined} 如果无法添加节点则返回null */ addNodes: function (parentNode, index, newNodes, isSilent) { // 如果未提供父节点,则默认为null if (!parentNode) parentNode = null; // 检查父节点是否是父节点类型 var isParent = data.nodeIsParent(setting, parentNode); // 如果父节点不是父节点且配置中要求保持叶子节点,则返回null if (parentNode && !isParent && setting.data.keep.leaf) return null; // 将index转换为整数 var i = parseInt(index, 10); // 如果index不是数字,则调整参数 if (isNaN(i)) { isSilent = !!newNodes; // 判断newNodes是否存在,存在则isSilent为true newNodes = index; // 将index赋值给newNodes index = -1; // 重置index为-1 } else { index = i; // 否则使用转换后的整数index } // 如果newNodes不存在,则返回null if (!newNodes) return null; // 克隆新的节点数组,确保其为数组形式 var xNewNodes = tools.clone(tools.isArray(newNodes) ? newNodes : [newNodes]); // 定义添加节点的回调函数 function addCallback() { // 调用视图层的addNodes方法实际添加节点 view.addNodes(setting, parentNode, index, xNewNodes, (isSilent == true)); } if (tools.canAsync(setting, parentNode)) { // 检查是否可以异步加载节点,如果可以则调用view.asyncNode方法进行异步加载 view.asyncNode(setting, parentNode, isSilent, addCallback); } else { // 如果不支持异步加载,直接调用回调函数 addCallback(); } // 返回新添加的节点 return xNewNodes; }, cancelSelectedNode: function (node) { // 取消选中的节点 view.cancelPreSelectedNode(setting, node); }, destroy: function () { // 销毁视图 view.destroy(setting); }, expandAll: function (expandFlag) { // 确保expandFlag为布尔值 expandFlag = !!expandFlag; // 展开或折叠所有子节点 view.expandCollapseSonNode(setting, null, expandFlag, true); // 返回最终的展开标志 return expandFlag; }, expandNode: function (node, expandFlag, sonSign, focus, callbackFlag) { // 如果节点不存在或者不是父节点,返回null if (!node || !data.nodeIsParent(setting, node)) return null; // 如果expandFlag既不是true也不是false,则取反当前节点的open状态 if (expandFlag !== true && expandFlag !== false) { expandFlag = !node.open; } // 确保callbackFlag为布尔值 callbackFlag = !!callbackFlag; // 检查是否需要在展开或折叠节点之前执行回调函数 if (callbackFlag && expandFlag && (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false)) { // 如果回调函数返回false,则不进行展开操作,直接返回null return null; } else if (callbackFlag && !expandFlag && (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false)) { // 如果回调函数返回false,则不进行折叠操作,直接返回null return null; } // 如果需要展开节点且该节点有父节点 if (expandFlag && node.parentTId) { // 递归展开父节点 view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, false); } // 如果节点的展开状态与目标状态相同且没有子节点标志 if (expandFlag === node.open && !sonSign) { // 不需要改变节点状态,直接返回null return null; } // 设置根节点的展开触发标志为回调标志 data.getRoot(setting).expandTriggerFlag = callbackFlag; // 如果当前节点不能异步加载并且有子节点 if (!tools.canAsync(setting, node) && sonSign) { // 展开或折叠子节点,并显示节点焦点 view.expandCollapseSonNode(setting, node, expandFlag, true, showNodeFocus); } else { // 否则,根据展开标志设置节点的打开状态 node.open = !expandFlag; // 切换节点的状态 view.switchNode(this.setting, node); // 显示节点焦点 showNodeFocus(); } // 返回展开标志 return expandFlag; function showNodeFocus() { // 获取节点对象 var a = $$(node, setting).get(0); // 如果节点存在且焦点不为false,则将视图滚动到该节点位置 if (a && focus !== false) { view.scrollIntoView(setting, a); } } }, getNodes: function () { // 返回所有节点数据 return data.getNodes(setting); }, getNodeByParam: function (key, value, parentNode) { // 如果键不存在,返回null if (!key) return null; // 根据参数获取节点,如果parentNode存在,则在子节点中查找,否则在所有节点中查找 return data.getNodeByParam(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value); }, getNodeByTId: function (tId) { // 根据节点ID获取缓存中的节点对象 return data.getNodeCache(setting, tId); }, getNodesByParam: function (key, value, parentNode) { // 如果键不存在,返回null if (!key) return null; // 根据参数获取节点数组,如果parentNode存在,则在子节点中查找,否则在所有节点中查找 return data.getNodesByParam(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value); }, getNodesByParamFuzzy: function (key, value, parentNode) { // 如果键不存在,返回null if (!key) return null; // 根据模糊参数获取节点数组,如果parentNode存在,则在子节点中查找,否则在所有节点中查找 return data.getNodesByParamFuzzy(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value); }, getNodesByFilter: function (filter, isSingle, parentNode, invokeParam) { // 将isSingle转换为布尔值 isSingle = !!isSingle; // 如果过滤器不存在或不是函数,返回null或空数组 if (!filter || (typeof filter != "function")) return (isSingle ? null : []); // 根据过滤器获取节点数组,如果parentNode存在,则在子节点中查找,否则在所有节点中查找 return data.getNodesByFilter(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), filter, isSingle, invokeParam); }, getNodeIndex: function (node) { // 如果节点为空,返回null if (!node) return null; // 获取父节点,如果节点有parentTId属性则调用getParentNode方法,否则获取根节点 var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting); // 获取父节点的子节点列表 var children = data.nodeChildren(setting, parentNode); // 遍历子节点列表,找到与目标节点匹配的索引并返回 for (var i = 0, l = children.length; i < l; i++) { if (children[i] == node) return i; } // 如果未找到匹配的节点,返回-1 return -1; }, getSelectedNodes: function () { // 初始化一个空数组用于存储选中的节点 var r = [], list = data.getRoot(setting).curSelectedList; // 遍历当前选中的节点列表,将每个节点添加到结果数组中 for (var i = 0, l = list.length; i < l; i++) { r.push(list[i]); } // 返回包含所有选中节点的数组 return r; }, isSelectedNode: function (node) { // 判断指定节点是否被选中,返回布尔值 return data.isSelectedNode(setting, node); }, reAsyncChildNodesPromise: function (parentNode, reloadType, isSilent) { // 创建一个新的Promise对象 var promise = new Promise(function (resolve, reject) { try { // 异步重新加载子节点,并在完成后解析Promise zTreeTools.reAsyncChildNodes(parentNode, reloadType, isSilent, function () { resolve(parentNode); }); } catch (e) { // 如果发生错误,拒绝Promise并传递错误信息 reject(e); } }); // 返回Promise对象 return promise; }, /** * 重新加载异步子节点 * @param {Object} parentNode - 父节点对象 * @param {string} reloadType - 重载类型,可以是 "refresh" * @param {boolean} isSilent - 是否静默加载 * @param {function} callback - 回调函数 */ reAsyncChildNodes: function (parentNode, reloadType, isSilent, callback) { // 如果异步加载未启用,则直接返回 if (!this.setting.async.enable) return; // 判断是否是根节点 var isRoot = !parentNode; // 如果是根节点,获取根节点数据 if (isRoot) { parentNode = data.getRoot(setting); } // 如果重载类型是 "refresh" if (reloadType == "refresh") { // 获取当前父节点的所有子节点 var children = data.nodeChildren(setting, parentNode); // 遍历所有子节点并移除缓存 for (var i = 0, l = children ? children.length : 0; i < l; i++) { data.removeNodeCache(setting, children[i]); } // 移除选中的节点 data.removeSelectedNode(setting); // 清空父节点的子节点列表 data.nodeChildren(setting, parentNode, []); // 如果是根节点,清空树对象 if (isRoot) { this.setting.treeObj.empty(); } else { // 否则清空父节点对应的UL元素 var ulObj = $$(parentNode, consts.id.UL, setting); ulObj.empty(); } } // 异步加载节点 view.asyncNode(this.setting, isRoot ? null : parentNode, !!isSilent, callback); }, refresh: function () { // 清空树对象 this.setting.treeObj.empty(); // 获取根节点 var root = data.getRoot(setting), // 获取根节点的子节点 nodes = data.nodeChildren(setting, root); // 初始化根节点 data.initRoot(setting); // 将子节点重新添加到根节点 data.nodeChildren(setting, root, nodes); // 初始化缓存 data.initCache(setting); // 创建节点视图,参数依次为:设置、层级、子节点数组、父节点、父节点ID view.createNodes(setting, 0, data.nodeChildren(setting, root), null, -1); }, removeChildNodes: function (node) { // 如果节点为空,返回null if (!node) return null; // 获取指定节点的子节点 var nodes = data.nodeChildren(setting, node); // 从视图中移除这些子节点 view.removeChildNodes(setting, node); // 如果有子节点则返回它们,否则返回null return nodes ? nodes : null; }, removeNode: function (node, callbackFlag) { // 如果节点为空,直接返回 if (!node) return; // 确保callbackFlag是布尔值 callbackFlag = !!callbackFlag; // 如果callbackFlag为true且beforeRemove回调函数返回false,则不继续执行 if (callbackFlag && tools.apply(setting.callback.beforeRemove, [setting.treeId, node], true) == false) return; // 从视图中移除该节点 view.removeNode(setting, node); // 如果callbackFlag为true,触发REMOVE事件 if (callbackFlag) { this.setting.treeObj.trigger(consts.event.REMOVE, [setting.treeId, node]); } }, selectNode: function (node, addFlag, isSilent) { // 如果节点为空,直接返回 if (!node) return; // 检查用户是否有权限进行操作 if (tools.uCanDo(setting)) { // 如果允许多选并且addFlag为true,则保持addFlag为true addFlag = setting.view.selectedMulti && addFlag; // 如果节点有父节点 if (node.parentTId) { // 展开父节点并聚焦到该节点 view.expandCollapseParentNode(setting, node.getParentNode(), true, false, showNodeFocus); } else if (!isSilent) { // 如果节点没有父节点且不静默处理 try { // 尝试让节点获取焦点并失去焦点 $$(node, setting).focus().blur(); } catch (e) { // 捕获异常但不做任何处理 } } // 选择节点,根据addFlag决定是否添加选中状态 view.selectNode(setting, node, addFlag); } function showNodeFocus() { // 如果当前处于静默模式,则直接返回,不执行后续操作 if (isSilent) { return; } // 获取指定节点的第一个元素 var a = $$(node, setting).get(0); // 将视图滚动到指定节点的位置 view.scrollIntoView(setting, a); } /** * 将简单节点数组转换为zTree节点格式 * @param {Array} simpleNodes - 简单节点数组 * @returns {Array} zTree节点格式的数组 */ transformTozTreeNodes: function (simpleNodes) { return data.transformTozTreeFormat(setting, simpleNodes); }, /** * 将节点数组转换为普通数组格式 * @param {Array} nodes - 节点数组 * @returns {Array} 普通数组格式的数组 */ transformToArray: function (nodes) { return data.transformToArrayFormat(setting, nodes); }, updateNode: function (node, checkTypeFlag) { // 如果节点不存在,直接返回 if (!node) return; // 获取节点对象 var nObj = $$(node, setting); // 如果节点对象存在且用户有权限进行操作 if (nObj.get(0) && tools.uCanDo(setting)) { // 设置节点名称 view.setNodeName(setting, node); // 设置节点目标 view.setNodeTarget(setting, node); // 设置节点URL view.setNodeUrl(setting, node); // 设置节点图标 view.setNodeLineIcos(setting, node); // 设置节点字体样式 view.setNodeFontCss(setting, node); // 设置节点类名 view.setNodeClasses(setting, node); } } root.treeTools = zTreeTools; // 将zTreeTools对象赋值给根节点的treeTools属性 data.setZTreeTools(setting, zTreeTools); // 设置zTree工具到数据对象中 var children = data.nodeChildren(setting, root); // 获取根节点的子节点 if (children && children.length > 0) { // 如果存在子节点且子节点数量大于0 view.createNodes(setting, 0, children, null, -1); // 创建子节点视图 } else if (setting.async.enable && setting.async.url && setting.async.url !== '') { // 如果启用了异步加载并且URL不为空 view.asyncNode(setting); // 异步加载节点 } return zTreeTools; // 返回zTree工具对象 var zt = $.fn.zTree, // 获取jQuery的zTree插件 $$ = tools.$, // 从tools对象中获取$符号,通常用于简化代码书写 consts = zt.consts; // 获取zTree插件中的常量定义 })(jQuery); // 立即执行函数,传入jQuery对象作为参数 /* * JQuery zTree excheck * v3.5.46 * http://treejs.cn/ * * Copyright (c) 2010 Hunter.z * * Licensed same as jquery - MIT License * http://www.opensource.org/licenses/mit-license.php * * Date: 2020-11-21 */ (function ($) { // 定义excheck的默认常量 var _consts = { event: { // 触发检查事件的名称 CHECK: "ztree_check" }, id: { // 检查框的ID前缀 CHECK: "_check" }, checkbox: { // 复选框样式类型 STYLE: "checkbox", // 默认复选框类名 DEFAULT: "chk", // 禁用状态的复选框类名 DISABLED: "disable", // 未选中状态的复选框类名 FALSE: "false", // 选中状态的复选框类名 TRUE: "true", // 完全选中状态的复选框类名 FULL: "full", // 部分选中状态的复选框类名 PART: "part", // 焦点状态的复选框类名 FOCUS: "focus" }, radio: { // 单选按钮样式类型 STYLE: "radio", // 所有节点都参与选择的类型 TYPE_ALL: "all", // 同级别节点参与选择的类型 TYPE_LEVEL: "level" } }, //default setting of excheck _setting = { // 配置检查选项 check: { // 是否启用检查功能,默认为false enable: false, // 是否自动触发检查,默认为false autoCheckTrigger: false, // 设置复选框的样式,使用常量中的样式定义 chkStyle: _consts.checkbox.STYLE, // 是否不继承未选中状态,默认为false nocheckInherit: false, // 是否不继承禁用状态,默认为false chkDisabledInherit: false, // 设置单选按钮的类型,使用常量中定义的类型 radioType: _consts.radio.TYPE_LEVEL, // 设置复选框类型,"Y"和"N"都对应到"ps" chkboxType: { "Y": "ps", "N": "ps" } }, data: { key: { checked: "checked" // 定义节点是否被选中的键名 } }, callback: { beforeCheck: null, // 在节点选中前执行的回调函数 onCheck: null // 在节点选中后执行的回调函数 } }, // default root of excheck _initRoot = function (setting) { var r = data.getRoot(setting); // 获取树的根节点 r.radioCheckedList = []; // 初始化根节点的单选列表为空数组 }, // default cache of excheck _initCache = function (treeId) { // 初始化缓存,当前未实现具体逻辑 }, // default bind event of excheck _bindEvent = function (setting) { var o = setting.treeObj, // 获取树对象 c = consts.event; // 获取事件常量 o.bind(c.CHECK, function (event, srcEvent, treeId, node) { event.srcEvent = srcEvent; // 将源事件附加到事件对象上 tools.apply(setting.callback.onCheck, [event, treeId, node]); // 调用选中后的回调函数 }); }, _unbindEvent = function (setting) { // 获取树对象 var o = setting.treeObj, // 获取事件常量 c = consts.event; // 解绑CHECK事件 o.unbind(c.CHECK); }, //default event proxy of excheck _eventProxy = function (e) { // 获取事件目标元素 var target = e.target, // 获取设置信息 setting = data.getSetting(e.data.treeId), tId = "", node = null, nodeEventType = "", treeEventType = "", nodeEventCallback = null, treeEventCallback = null; // 如果事件类型是mouseover if (tools.eqs(e.type, "mouseover")) { // 如果启用了复选框,并且目标元素是span标签且具有CHECK属性 if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) { // 获取节点主DOM的ID tId = tools.getNodeMainDom(target).id; // 设置节点事件类型为mouseoverCheck nodeEventType = "mouseoverCheck"; } } else if (tools.eqs(e.type, "mouseout")) { // 如果事件类型是mouseout // 如果启用了复选框,并且目标元素是span标签且具有CHECK属性 if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) { // 获取节点主DOM的ID tId = tools.getNodeMainDom(target).id; // 设置节点事件类型为mouseoutCheck nodeEventType = "mouseoutCheck"; } } else if (tools.eqs(e.type, "click")) { // 如果事件类型是click // 如果启用了复选框,并且目标元素是span标签且具有CHECK属性 if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) { // 获取节点主DOM的ID tId = tools.getNodeMainDom(target).id; // 设置节点事件类型为checkNode nodeEventType = "checkNode"; } } // 检查tId数组的长度是否大于0 if (tId.length > 0) { // 从缓存中获取节点对象 node = data.getNodeCache(setting, tId); // 根据节点事件类型选择相应的回调函数 switch (nodeEventType) { // 如果事件类型是"checkNode",则使用_handler.onCheckNode作为回调函数 case "checkNode": nodeEventCallback = _handler.onCheckNode; break; // 如果事件类型是"mouseoverCheck",则使用_handler.onMouseoverCheck作为回调函数 case "mouseoverCheck": nodeEventCallback = _handler.onMouseoverCheck; break; // 如果事件类型是"mouseoutCheck",则使用_handler.onMouseoutCheck作为回调函数 case "mouseoutCheck": nodeEventCallback = _handler.onMouseoutCheck; break; } } var proxyResult = { // 如果nodeEventType是"checkNode",则stop为true,否则为false stop: nodeEventType === "checkNode", // 当前节点对象 node: node, // 节点事件类型 nodeEventType: nodeEventType, // 节点事件回调函数 nodeEventCallback: nodeEventCallback, // 树事件类型 treeEventType: treeEventType, // 树事件回调函数 treeEventCallback: treeEventCallback }; // 返回包含上述属性的对象 return proxyResult }, //default init node of excheck _initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { // 如果节点不存在,直接返回 if (!n) return; // 获取节点的选中状态 var checked = data.nodeChecked(setting, n); // 保存节点的旧选中状态 n.checkedOld = checked; // 如果节点的nocheck属性是字符串类型,将其转换为布尔值 if (typeof n.nocheck == "string") n.nocheck = tools.eqs(n.nocheck, "true"); // 设置节点的nocheck属性,如果父节点存在且继承父节点的nocheck属性,则使用父节点的值 n.nocheck = !!n.nocheck || (setting.check.nocheckInherit && parentNode && !!parentNode.nocheck); // 如果节点的chkDisabled属性是字符串类型,将其转换为布尔值 if (typeof n.chkDisabled == "string") n.chkDisabled = tools.eqs(n.chkDisabled, "true"); // 设置节点的chkDisabled属性,如果父节点存在且继承父节点的chkDisabled属性,则使用父节点的值 n.chkDisabled = !!n.chkDisabled || (setting.check.chkDisabledInherit && parentNode && !!parentNode.chkDisabled); // 如果节点的halfCheck属性是字符串类型,将其转换为布尔值 if (typeof n.halfCheck == "string") n.halfCheck = tools.eqs(n.halfCheck, "true"); // 设置节点的halfCheck属性为布尔值 n.halfCheck = !!n.halfCheck; // 初始化子节点的状态为-1(未定义) n.check_Child_State = -1; // 初始化节点的焦点状态为false n.check_Focus = false; // 定义一个方法来获取节点的选中状态 n.getCheckStatus = function () { return data.getCheckStatus(setting, n); }; } if (setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL && checked) { // 获取根节点数据 var r = data.getRoot(setting); // 将当前节点添加到已选中的单选按钮列表中 r.radioCheckedList.push(n); } //add dom for check _beforeA = function (setting, node, html) { // 检查是否启用了check功能 if (setting.check.enable) { // 调用makeChkFlag方法,为节点生成check标记 data.makeChkFlag(setting, node); // 将生成的HTML片段推入html数组中 html.push(""); } }, //update zTreeObj, add method of check _zTreeTools = function (setting, zTreeTools) { // 定义一个方法,用于检查节点的状态 zTreeTools.checkNode = function (node, checked, checkTypeFlag, callbackFlag) { // 获取当前节点的选中状态 var nodeChecked = data.nodeChecked(setting, node); // 如果节点被禁用,则直接返回 if (node.chkDisabled === true) return; // 如果checked参数不是布尔值,则取反当前节点的选中状态 if (checked !== true && checked !== false) { checked = !nodeChecked; } // 确保callbackFlag为布尔值 callbackFlag = !!callbackFlag; // 如果节点的选中状态没有变化且不需要回调,则直接返回 if (nodeChecked === checked && !checkTypeFlag) { return; // 如果需要回调且beforeCheck回调函数返回false,则直接返回 } else if (callbackFlag && tools.apply(this.setting.callback.beforeCheck, [this.setting.treeId, node], true) == false) { return; } // 检查用户是否有权限进行操作,并且是否启用了节点选中功能,以及节点是否没有被禁用选中 if (tools.uCanDo(this.setting) && this.setting.check.enable && node.nocheck !== true) { // 更新节点的选中状态 data.nodeChecked(setting, node, checked); // 获取节点对应的复选框对象 var checkObj = $$(node, consts.id.CHECK, this.setting); // 如果需要检查类型标志或节点样式为单选,则更新节点关系 if (checkTypeFlag || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node); // 设置复选框的样式 view.setChkClass(this.setting, checkObj, node); // 修复父节点的复选框样式 view.repairParentChkClassWithSelf(this.setting, node); // 如果需要回调,则触发CHECK事件 if (callbackFlag) { this.setting.treeObj.trigger(consts.event.CHECK, [null, this.setting.treeId, node]); } } } zTreeTools.checkAllNodes = function (checked) { // 调用view对象的repairAllChk方法,修复所有节点的选中状态 view.repairAllChk(this.setting, !!checked); } zTreeTools.getCheckedNodes = function (checked) { // 如果checked参数不为false,则将其转换为布尔值true checked = (checked !== false); // 获取根节点的所有子节点 var children = data.nodeChildren(setting, data.getRoot(this.setting)); // 返回树中所有被选中的节点 return data.getTreeCheckedNodes(this.setting, children, checked); } zTreeTools.getChangeCheckedNodes = function () { // 获取根节点的所有子节点 var children = data.nodeChildren(setting, data.getRoot(this.setting)); // 返回树中所有改变选中状态的节点 return data.getTreeChangeCheckedNodes(this.setting, children); } zTreeTools.setChkDisabled = function (node, disabled, inheritParent, inheritChildren) { // 将disabled参数转换为布尔值 disabled = !!disabled; // 将inheritParent参数转换为布尔值 inheritParent = !!inheritParent; // 将inheritChildren参数转换为布尔值 inheritChildren = !!inheritChildren; // 修复子节点的禁用状态 view.repairSonChkDisabled(this.setting, node, disabled, inheritChildren); // 修复父节点的禁用状态 view.repairParentChkDisabled(this.setting, node.getParentNode(), disabled, inheritParent); } var _updateNode = zTreeTools.updateNode; zTreeTools.updateNode = function (node, checkTypeFlag) { // 如果存在原始的updateNode方法,则调用它 if (_updateNode) _updateNode.apply(zTreeTools, arguments); // 如果节点不存在或检查功能未启用,则直接返回 if (!node || !this.setting.check.enable) return; // 获取节点对应的jQuery对象 var nObj = $$(node, this.setting); // 如果节点存在且用户有权限操作 if (nObj.get(0) && tools.uCanDo(this.setting)) { // 获取节点的复选框对象 var checkObj = $$(node, consts.id.CHECK, this.setting); // 如果checkTypeFlag为true或复选框样式为单选按钮 if (checkTypeFlag == true || this.setting.check.chkStyle === consts.radio.STYLE) // 修复节点的选中关系 view.checkNodeRelation(this.setting, node); // 设置复选框的CSS类 view.setChkClass(this.setting, checkObj, node); // 修复父节点的CSS类 view.repairParentChkClassWithSelf(this.setting, node); } } }, //method of operate data _data = { // 获取选中的单选框列表 getRadioCheckedList: function (setting) { // 获取根节点的单选框选中列表 var checkedList = data.getRoot(setting).radioCheckedList; // 遍历选中列表,检查每个节点是否在缓存中存在 for (var i = 0, j = checkedList.length; i < j; i++) { // 如果节点不在缓存中,则从选中列表中移除 if (!data.getNodeCache(setting, checkedList[i].tId)) { checkedList.splice(i, 1); i--; j--; } } // 返回处理后的选中列表 return checkedList; }, // 获取节点的选中状态 getCheckStatus: function (setting, node) { // 如果未启用选中功能或节点不可选中,则返回null if (!setting.check.enable || node.nocheck || node.chkDisabled) return null; // 获取节点是否被选中的状态 var checked = data.nodeChecked(setting, node), r = { checked: checked, // 根据不同的选中样式和子节点状态计算半选状态 half: node.halfCheck ? node.halfCheck : (setting.check.chkStyle == consts.radio.STYLE ? (node.check_Child_State === 2) : (checked ? (node.check_Child_State > -1 && node.check_Child_State < 2) : (node.check_Child_State > 0))) }; // 返回选中状态对象 return r; }, // 获取树形结构中所有选中的节点 getTreeCheckedNodes: function (setting, nodes, checked, results) { // 如果节点列表为空,则返回空数组 if (!nodes) return []; // 判断是否只允许选择一个节点(单选模式) var onlyOne = (checked && setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL); // 初始化结果数组 results = !results ? [] : results; // 遍历节点列表 for (var i = 0, l = nodes.length; i < l; i++) { var node = nodes[i]; // 获取当前节点的子节点 var children = data.nodeChildren(setting, node); // 获取当前节点是否被选中 var nodeChecked = data.nodeChecked(setting, node); // 如果节点可选中且未禁用,并且其选中状态与目标状态一致,则将其加入结果数组 if (node.nocheck !== true && node.chkDisabled !== true && nodeChecked == checked) { results.push(node); // 如果只允许选择一个节点,则找到后立即退出循环 if (onlyOne) { break; } } // 递归处理子节点 data.getTreeCheckedNodes(setting, children, checked, results); // 如果只允许选择一个节点,且已找到符合条件的节点,则退出循环 if (onlyOne && results.length > 0) { break; } } // 返回结果数组 return results; }, getTreeChangeCheckedNodes: function (setting, nodes, results) { // 如果节点为空,返回空数组 if (!nodes) return []; // 如果结果未定义,初始化为空数组 results = !results ? [] : results; // 遍历所有节点 for (var i = 0, l = nodes.length; i < l; i++) { // 获取当前节点 var node = nodes[i]; // 获取当前节点的子节点 var children = data.nodeChildren(setting, node); // 检查当前节点是否被选中 var nodeChecked = data.nodeChecked(setting, node); // 如果节点没有被禁用且选中状态发生变化,则将节点加入结果数组 if (node.nocheck !== true && node.chkDisabled !== true && nodeChecked != node.checkedOld) { results.push(node); } // 递归处理子节点 data.getTreeChangeCheckedNodes(setting, children, results); } // 返回结果数组 return results; }, /** * 生成节点的检查标志。 * @param {Object} setting - 配置对象,包含树形结构的相关设置。 * @param {Object} node - 当前处理的节点对象。 */ makeChkFlag: function (setting, node) { // 如果节点不存在,直接返回 if (!node) return; // 初始化检查标志为-1 var chkFlag = -1; // 获取节点的子节点列表 var children = data.nodeChildren(setting, node); // 如果存在子节点 if (children) { // 遍历所有子节点 for (var i = 0, l = children.length; i < l; i++) { var cNode = children[i]; // 当前子节点 var nodeChecked = data.nodeChecked(setting, cNode); // 判断子节点是否被选中 var tmp = -1; // 临时变量,用于存储子节点的状态 // 如果检查样式是单选框 if (setting.check.chkStyle == consts.radio.STYLE) { // 如果子节点不可检查或禁用,则使用其子状态 if (cNode.nocheck === true || cNode.chkDisabled === true) { tmp = cNode.check_Child_State; } else if (cNode.halfCheck === true) { // 如果子节点半选,则状态为2 tmp = 2; } else if (nodeChecked) { // 如果子节点被选中,则状态为2 tmp = 2; } else { // 根据子节点的子状态决定状态 tmp = cNode.check_Child_State > 0 ? 2 : 0; } // 如果状态为2,更新检查标志并退出循环 if (tmp == 2) { chkFlag = 2; break; } else if (tmp == 0) { // 如果状态为0,更新检查标志为0 chkFlag = 0; } } else if (setting.check.chkStyle == consts.checkbox.STYLE) { // 如果检查样式是复选框 if (cNode.nocheck === true || cNode.chkDisabled === true) { tmp = cNode.check_Child_State; } else if (cNode.halfCheck === true) { // 如果子节点半选,则状态为1 tmp = 1; } else if (nodeChecked) { // 根据子节点的子状态决定状态 tmp = (cNode.check_Child_State === -1 || cNode.check_Child_State === 2) ? 2 : 1; } else { // 根据子节点的子状态决定状态 tmp = (cNode.check_Child_State > 0) ? 1 : 0; } // 如果状态为1,更新检查标志并退出循环 if (tmp === 1) { chkFlag = 1; break; } else if (tmp === 2 && chkFlag > -1 && i > 0 && tmp !== chkFlag) { // 如果状态为2且之前有其他状态,更新检查标志为1并退出循环 chkFlag = 1; break; } else if (chkFlag === 2 && tmp > -1 && tmp < 2) { // 如果之前状态为2且当前状态在0和2之间,更新检查标志为1并退出循环 chkFlag = 1; break; } else if (tmp > -1) { // 更新检查标志为当前状态 chkFlag = tmp; } } } } // 将计算得到的检查标志赋值给节点的check_Child_State属性 node.check_Child_State = chkFlag; } //method of event proxy _event = {}, // 事件处理程序的方法集合 _handler = { /** * 处理节点选中事件的函数 * @param {Object} event - 事件对象 * @param {Object} node - 被操作的节点对象 * @returns {boolean} - 返回是否成功处理事件 */ onCheckNode: function (event, node) { // 如果节点被禁用,则直接返回false if (node.chkDisabled === true) return false; // 获取当前树的设置信息 var setting = data.getSetting(event.data.treeId); // 调用beforeCheck回调函数,如果返回false,则终止操作 if (tools.apply(setting.callback.beforeCheck, [setting.treeId, node], true) == false) return true; // 获取节点当前的选中状态 var nodeChecked = data.nodeChecked(setting, node); // 切换节点的选中状态 data.nodeChecked(setting, node, !nodeChecked); // 更新节点关系 view.checkNodeRelation(setting, node); // 获取节点对应的复选框元素 var checkObj = $$(node, consts.id.CHECK, setting); // 设置复选框的样式 view.setChkClass(setting, checkObj, node); // 修复父节点的复选框样式 view.repairParentChkClassWithSelf(setting, node); // 触发CHECK事件 setting.treeObj.trigger(consts.event.CHECK, [event, setting.treeId, node]); return true; }, /** * 处理鼠标悬停在复选框上的事件 * @param {Object} event - 事件对象 * @param {Object} node - 被操作的节点对象 * @returns {boolean} - 返回是否成功处理事件 */ onMouseoverCheck: function (event, node) { // 如果节点被禁用,则直接返回false if (node.chkDisabled === true) return false; // 获取当前树的设置信息和复选框元素 var setting = data.getSetting(event.data.treeId), checkObj = $$(node, consts.id.CHECK, setting); // 设置节点的check_Focus属性为true node.check_Focus = true; // 设置复选框的样式 view.setChkClass(setting, checkObj, node); return true; }, /** * 处理鼠标移出复选框的事件 * @param {Object} event - 事件对象 * @param {Object} node - 被操作的节点对象 * @returns {boolean} - 返回是否成功处理事件 */ onMouseoutCheck: function (event, node) { // 如果节点被禁用,则直接返回false if (node.chkDisabled === true) return false; // 获取当前树的设置信息和复选框元素 var setting = data.getSetting(event.data.treeId), checkObj = $$(node, consts.id.CHECK, setting); // 设置节点的check_Focus属性为false node.check_Focus = false; // 设置复选框的样式 view.setChkClass(setting, checkObj, node); return true; } }, //method of tools for zTree _tools = {}, // 操作ztree DOM的方法 _view = { /** * 检查节点关系,确保单选模式下只有一个节点被选中 * @param {Object} setting - ztree的配置对象 * @param {Object} node - 当前操作的节点对象 */ checkNodeRelation: function (setting, node) { var pNode, i, l, r = consts.radio; // 获取常量中的单选模式配置 var nodeChecked = data.nodeChecked(setting, node); // 判断当前节点是否被选中 if (setting.check.chkStyle == r.STYLE) { // 如果配置为单选模式 var checkedList = data.getRadioCheckedList(setting); // 获取已选中的单选节点列表 if (nodeChecked) { // 如果当前节点被选中 if (setting.check.radioType == r.TYPE_ALL) { // 如果单选类型为全选 for (i = checkedList.length - 1; i >= 0; i--) { // 遍历已选中的单选节点列表 pNode = checkedList[i]; // 获取已选中的节点 var pNodeChecked = data.nodeChecked(setting, pNode); // 判断已选中的节点是否被选中 if (pNodeChecked && pNode != node) { // 如果已选中的节点被选中且不是当前节点 data.nodeChecked(setting, pNode, false); // 取消已选中节点的选中状态 checkedList.splice(i, 1); // 从已选中列表中移除该节点 view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode); // 更新节点的样式 if (pNode.parentTId != node.parentTId) { // 如果父节点不同 view.repairParentChkClassWithSelf(setting, pNode); // 修复父节点的样式 } } } // 将节点添加到已选中列表中 checkedList.push(node); } else { // 获取父节点,如果当前节点有父节点则获取其父节点,否则获取根节点 var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting); // 获取父节点的所有子节点 var children = data.nodeChildren(setting, parentNode); // 遍历所有子节点 for (i = 0, l = children.length; i < l; i++) { pNode = children[i]; // 检查子节点是否被选中 var pNodeChecked = data.nodeChecked(setting, pNode); // 如果子节点被选中且不是当前节点,则取消其选中状态并更新视图 if (pNodeChecked && pNode != node) { data.nodeChecked(setting, pNode, false); view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode); } } } else if (setting.check.radioType == r.TYPE_ALL) { // 如果设置为单选类型,则从已选中列表中移除当前节点 for (i = 0, l = checkedList.length; i < l; i++) { if (node == checkedList[i]) { checkedList.splice(i, 1); break; } } } } else { // 获取当前节点的子节点 var children = data.nodeChildren(setting, node); // 如果当前节点被选中,并且没有子节点或子节点为空,或者设置中允许部分选中状态 if (nodeChecked && (!children || children.length == 0 || setting.check.chkboxType.Y.indexOf("s") > -1)) { // 设置子节点的复选框为选中状态 view.setSonNodeCheckBox(setting, node, true); } // 如果当前节点未被选中,并且没有子节点或子节点为空,或者设置中允许部分未选中状态 if (!nodeChecked && (!children || children.length == 0 || setting.check.chkboxType.N.indexOf("s") > -1)) { // 设置子节点的复选框为未选中状态 view.setSonNodeCheckBox(setting, node, false); } // 如果当前节点被选中,并且设置中允许父节点选中状态 if (nodeChecked && setting.check.chkboxType.Y.indexOf("p") > -1) { // 设置父节点的复选框为选中状态 view.setParentNodeCheckBox(setting, node, true); } // 如果当前节点未被选中,并且设置中允许父节点未选中状态 if (!nodeChecked && setting.check.chkboxType.N.indexOf("p") > -1) { // 设置父节点的复选框为未选中状态 view.setParentNodeCheckBox(setting, node, false); } } }, makeChkClass: function (setting, node) { // 获取复选框和单选按钮的常量 var c = consts.checkbox, r = consts.radio, fullStyle = ""; // 检查节点是否被选中 var nodeChecked = data.nodeChecked(setting, node); // 如果节点被禁用,设置样式为禁用状态 if (node.chkDisabled === true) { fullStyle = c.DISABLED; // 如果节点处于半选状态,设置样式为部分选中 } else if (node.halfCheck) { fullStyle = c.PART; // 如果设置为单选按钮样式,根据子节点状态设置样式 } else if (setting.check.chkStyle == r.STYLE) { fullStyle = (node.check_Child_State < 1) ? c.FULL : c.PART; // 根据节点是否选中和子节点状态设置样式 } else { fullStyle = nodeChecked ? ((node.check_Child_State === 2 || node.check_Child_State === -1) ? c.FULL : c.PART) : ((node.check_Child_State < 1) ? c.FULL : c.PART); } // 生成复选框的类名 var chkName = setting.check.chkStyle + "_" + (nodeChecked ? c.TRUE : c.FALSE) + "_" + fullStyle; // 如果节点有焦点且未被禁用,添加焦点样式 chkName = (node.check_Focus && node.chkDisabled !== true) ? chkName + "_" + c.FOCUS : chkName; // 返回完整的类名字符串 return consts.className.BUTTON + " " + c.DEFAULT + " " + chkName; }, /** * 修复所有复选框的状态 * @param {Object} setting - 配置对象 * @param {Boolean} checked - 是否选中 */ repairAllChk: function (setting, checked) { // 如果启用了复选框并且样式为复选框样式 if (setting.check.enable && setting.check.chkStyle === consts.checkbox.STYLE) { // 获取根节点 var root = data.getRoot(setting); // 获取根节点的所有子节点 var children = data.nodeChildren(setting, root); // 遍历所有子节点 for (var i = 0, l = children.length; i < l; i++) { var node = children[i]; // 如果节点没有被禁用且没有禁止选中 if (node.nocheck !== true && node.chkDisabled !== true) { // 设置节点的选中状态 data.nodeChecked(setting, node, checked); } // 更新子节点的复选框状态 view.setSonNodeCheckBox(setting, node, checked); } } }, /** * 修复单个节点的复选框样式 * @param {Object} setting - 配置对象 * @param {Object} node - 节点对象 */ repairChkClass: function (setting, node) { // 如果节点不存在,直接返回 if (!node) return; // 标记节点的复选框状态 data.makeChkFlag(setting, node); // 如果节点没有被禁止选中 if (node.nocheck !== true) { // 获取节点的复选框对象 var checkObj = $$(node, consts.id.CHECK, setting); // 设置复选框的样式 view.setChkClass(setting, checkObj, node); } }, repairParentChkClass: function (setting, node) { // 如果节点不存在或没有父节点ID,则直接返回 if (!node || !node.parentTId) return; // 获取父节点 var pNode = node.getParentNode(); // 修复父节点的复选框样式 view.repairChkClass(setting, pNode); // 递归修复父节点的父节点的复选框样式 view.repairParentChkClass(setting, pNode); }, repairParentChkClassWithSelf: function (setting, node) { // 如果节点不存在,则直接返回 if (!node) return; // 获取子节点列表 var children = data.nodeChildren(setting, node); // 如果存在子节点,则修复第一个子节点的父节点复选框样式 if (children && children.length > 0) { view.repairParentChkClass(setting, children[0]); } else { // 否则修复当前节点的父节点复选框样式 view.repairParentChkClass(setting, node); } }, repairSonChkDisabled: function (setting, node, chkDisabled, inherit) { // 如果节点不存在,则直接返回 if (!node) return; // 如果节点的chkDisabled属性与传入值不同,则更新该属性 if (node.chkDisabled != chkDisabled) { node.chkDisabled = chkDisabled; } // 修复当前节点的复选框样式 view.repairChkClass(setting, node); // 获取子节点列表 var children = data.nodeChildren(setting, node); // 如果存在子节点且需要继承,则递归修复所有子节点的chkDisabled属性和复选框样式 if (children && inherit) { for (var i = 0, l = children.length; i < l; i++) { var sNode = children[i]; view.repairSonChkDisabled(setting, sNode, chkDisabled, inherit); } } }, repairParentChkDisabled: function (setting, node, chkDisabled, inherit) { // 如果节点不存在,则直接返回 if (!node) return; // 如果节点的chkDisabled属性与传入值不同且需要继承,则更新该属性 if (node.chkDisabled != chkDisabled && inherit) { node.chkDisabled = chkDisabled; } // 修复当前节点的复选框样式 view.repairChkClass(setting, node); // 递归修复父节点的chkDisabled属性和复选框样式 view.repairParentChkDisabled(setting, node.getParentNode(), chkDisabled, inherit); }, setChkClass: function (setting, obj, node) { // 如果对象不存在,直接返回 if (!obj) return; // 如果节点的nocheck属性为true,隐藏复选框 if (node.nocheck === true) { obj.hide(); } else { // 否则显示复选框 obj.show(); } // 设置复选框的class属性 obj.attr('class', view.makeChkClass(setting, node)); }, setParentNodeCheckBox: function (setting, node, value, srcNode) { // 获取当前节点的复选框对象 var checkObj = $$(node, consts.id.CHECK, setting); // 如果没有传入srcNode,则将当前节点赋值给srcNode if (!srcNode) srcNode = node; // 更新节点的检查状态标志 data.makeChkFlag(setting, node); // 如果节点没有禁用检查且未设置为不检查 if (node.nocheck !== true && node.chkDisabled !== true) { // 设置节点的选中状态 data.nodeChecked(setting, node, value); // 更新复选框的样式和类名 view.setChkClass(setting, checkObj, node); // 如果自动检查触发开启且当前节点不是源节点,触发CHECK事件 if (setting.check.autoCheckTrigger && node != srcNode) { setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); } } // 如果节点有父节点 if (node.parentTId) { var pSign = true; // 如果值为false if (!value) { // 获取父节点的所有子节点 var pNodes = data.nodeChildren(setting, node.getParentNode()); // 遍历所有子节点 for (var i = 0, l = pNodes.length; i < l; i++) { var pNode = pNodes[i]; // 获取子节点的选中状态 var nodeChecked = data.nodeChecked(setting, pNode); // 如果子节点未被禁用且选中,或者子节点被禁用但有子节点被选中,则pSign设为false并跳出循环 if ((pNode.nocheck !== true && pNode.chkDisabled !== true && nodeChecked) || ((pNode.nocheck === true || pNode.chkDisabled === true) && pNode.check_Child_State > 0)) { pSign = false; break; } } } if (pSign) { // 如果 pSign 为真,设置父节点的复选框状态 view.setParentNodeCheckBox(setting, node.getParentNode(), value, srcNode); } }, setSonNodeCheckBox: function (setting, node, value, srcNode) { // 如果节点不存在,直接返回 if (!node) return; // 获取当前节点的复选框对象 var checkObj = $$(node, consts.id.CHECK, setting); // 如果源节点未定义,则将当前节点设为源节点 if (!srcNode) srcNode = node; // 初始化 hasDisable 标志位,用于记录子节点是否有禁用的复选框 var hasDisable = false; // 获取当前节点的子节点列表 var children = data.nodeChildren(setting, node); if (children) { // 遍历所有子节点 for (var i = 0, l = children.length; i < l; i++) { var sNode = children[i]; // 递归设置子节点的复选框状态 view.setSonNodeCheckBox(setting, sNode, value, srcNode); // 如果子节点被禁用,则设置 hasDisable 为 true if (sNode.chkDisabled === true) hasDisable = true; } } // 如果当前节点不是根节点且未被禁用 if (node != data.getRoot(setting) && node.chkDisabled !== true) { // 如果存在禁用的子节点且当前节点未被禁用,更新当前节点的复选框状态 if (hasDisable && node.nocheck !== true) { data.makeChkFlag(setting, node); } // 如果当前节点未被禁用且未设置为不检查 if (node.nocheck !== true && node.chkDisabled !== true) { // 设置当前节点的选中状态 data.nodeChecked(setting, node, value); // 根据子节点的状态更新当前节点的子节点状态 if (!hasDisable) node.check_Child_State = (children && children.length > 0) ? (value ? 2 : 0) : -1; } else { // 如果当前节点被禁用或设置为不检查,则将其子节点状态设为 -1 node.check_Child_State = -1; } // 更新当前节点的复选框样式 view.setChkClass(setting, checkObj, node); // 如果自动触发检查事件,并且当前节点不是源节点且未被禁用或设置为不检查,则触发检查事件 if (setting.check.autoCheckTrigger && node != srcNode && node.nocheck !== true && node.chkDisabled !== true) { setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); } } } }, _z = { tools: _tools, view: _view, event: _event, data: _data }; // 将常量对象扩展到jQuery的zTree插件中 $.extend(true, $.fn.zTree.consts, _consts); // 将工具对象扩展到jQuery的zTree插件中 $.extend(true, $.fn.zTree._z, _z); var zt = $.fn.zTree, tools = zt._z.tools, consts = zt.consts, view = zt._z.view, data = zt._z.data, event = zt._z.event, $$ = tools.$; /** * 设置或获取节点的选中状态 * @param {Object} setting - 配置对象 * @param {Object} node - 节点对象 * @param {boolean|string} newChecked - 新的选中状态,可以是布尔值或字符串 * @returns {boolean} - 返回节点的最终选中状态 */ data.nodeChecked = function (setting, node, newChecked) { // 如果节点不存在,返回false if (!node) { return false; } var key = setting.data.key.checked; // 如果newChecked被定义了 if (typeof newChecked !== 'undefined') { // 如果newChecked是字符串类型,将其转换为布尔值 if (typeof newChecked === "string") { newChecked = tools.eqs(newChecked, "true"); } // 将newChecked转换为布尔值并赋值给节点的key属性 newChecked = !!newChecked; node[key] = newChecked; } else if (typeof node[key] == "string"){ // 如果节点的key属性是字符串类型,将其转换为布尔值 node[key] = tools.eqs(node[key], "true"); } else { // 否则,将节点的key属性转换为布尔值 node[key] = !!node[key]; } // 返回节点的最终选中状态 return node[key]; }; // 调用exSetting方法,传入_setting参数进行设置 data.exSetting(_setting); // 添加初始化绑定事件,传入_bindEvent作为回调函数 data.addInitBind(_bindEvent); // 添加初始化解绑事件,传入_unbindEvent作为回调函数 data.addInitUnBind(_unbindEvent); // 添加初始化缓存,传入_initCache作为回调函数 data.addInitCache(_initCache); // 添加初始化节点,传入_initNode作为回调函数 data.addInitNode(_initNode); // 添加初始化代理,传入_eventProxy作为回调函数,并设置第二个参数为true data.addInitProxy(_eventProxy, true); // 添加初始化根节点,传入_initRoot作为回调函数 data.addInitRoot(_initRoot); // 添加在A操作之前的回调函数,传入_beforeA作为回调函数 data.addBeforeA(_beforeA); // 添加ZTree工具,传入_zTreeTools作为回调函数 data.addZTreeTools(_zTreeTools); var _createNodes = view.createNodes; view.createNodes = function (setting, level, nodes, parentNode, index) { // 如果存在原始的 createNodes 方法,则调用它 if (_createNodes) _createNodes.apply(view, arguments); // 如果没有节点需要创建,直接返回 if (!nodes) return; // 修复父节点的选中状态类 view.repairParentChkClassWithSelf(setting, parentNode); } var _removeNode = view.removeNode; view.removeNode = function (setting, node) { // 获取要删除节点的父节点 var parentNode = node.getParentNode(); // 如果存在原始的 removeNode 方法,则调用它 if (_removeNode) _removeNode.apply(view, arguments); // 如果节点或其父节点不存在,直接返回 if (!node || !parentNode) return; // 修复父节点的选中状态类 view.repairChkClass(setting, parentNode); // 修复父节点的选中状态类(与上一个修复方法可能不同) view.repairParentChkClass(setting, parentNode); } var _appendNodes = view.appendNodes; // 保存原始的appendNodes方法 view.appendNodes = function (setting, level, nodes, parentNode, index, initFlag, openFlag) { // 定义新的appendNodes方法,接收多个参数 var html = ""; // 初始化html变量为空字符串 if (_appendNodes) { // 如果存在原始的appendNodes方法,则调用它并传递所有参数 html = _appendNodes.apply(view, arguments); } if (parentNode) { // 如果parentNode存在,则调用makeChkFlag方法 data.makeChkFlag(setting, parentNode); } return html; // 返回生成的html内容 } })(jQuery); // 立即执行函数,传入jQuery对象 /* * JQuery zTree exedit * v3.5.46 * http://treejs.cn/ * * Copyright (c) 2010 Hunter.z * * Licensed same as jquery - MIT License * http://www.opensource.org/licenses/mit-license.php * * Date: 2020-11-21 */ (function ($) { // 定义exedit的默认常量 var _consts = { event: { // 拖拽事件 DRAG: "ztree_drag", // 放置事件 DROP: "ztree_drop", // 重命名事件 RENAME: "ztree_rename", // 拖动移动事件 DRAGMOVE: "ztree_dragmove" }, id: { // 编辑状态ID EDIT: "_edit", // 输入框ID INPUT: "_input", // 删除按钮ID REMOVE: "_remove" }, move: { // 内部移动类型 TYPE_INNER: "inner", // 上一个节点移动类型 TYPE_PREV: "prev", // 下一个节点移动类型 TYPE_NEXT: "next" }, node: { // 当前选中节点编辑状态 CURSELECTED_EDIT: "curSelectedNode_Edit", // 临时目标树ID TMPTARGET_TREE: "tmpTargetzTree", // 临时目标节点ID TMPTARGET_NODE: "tmpTargetNode" } }, // 定义exedit的默认设置 _setting = { edit: { // 是否启用编辑功能 enable: false, // 是否在编辑时全选节点名称 editNameSelectAll: false, // 是否显示删除按钮 showRemoveBtn: true, // 是否显示重命名按钮 showRenameBtn: true, // 删除按钮的标题 removeTitle: "remove", // 重命名按钮的标题 renameTitle: "rename", drag: { // 自动展开触发器 autoExpandTrigger: false, // 是否允许复制操作 isCopy: true, // 是否允许移动操作 isMove: true, // 是否允许移动到前一个节点 prev: true, // 是否允许移动到下一个节点 next: true, // 是否允许移动到内部节点 inner: true, // 最小移动尺寸 minMoveSize: 5, // 最大边界值 borderMax: 10, // 最小边界值 borderMin: -5, // 最大显示节点数 maxShowNodeNum: 5, // 自动打开时间(毫秒) autoOpenTime: 500 } }, view: { // 添加悬停DOM元素的回调函数,初始值为null addHoverDom: null, // 移除悬停DOM元素的回调函数,初始值为null removeHoverDom: null }, callback: { // 拖动节点前的回调函数,初始值为null beforeDrag: null, // 打开拖动节点前的回调函数,初始值为null beforeDragOpen: null, // 放置节点前的回调函数,初始值为null beforeDrop: null, // 编辑节点名称前的回调函数,初始值为null beforeEditName: null, // 重命名节点前的回调函数,初始值为null beforeRename: null, // 拖动节点时的回调函数,初始值为null onDrag: null, // 拖动节点移动时的回调函数,初始值为null onDragMove: null, // 放置节点后的回调函数,初始值为null onDrop: null, // 重命名节点后的回调函数,初始值为null onRename: null } }, //default root of exedit _initRoot = function (setting) { // 获取根节点对象 var r = data.getRoot(setting), rs = data.getRoots(); // 当前编辑的节点设为空 r.curEditNode = null; // 当前编辑的输入框设为空 r.curEditInput = null; // 当前悬停的节点设为空 r.curHoverNode = null; // 拖拽标志设为0 r.dragFlag = 0; // 初始化拖拽显示前的节点数组 r.dragNodeShowBefore = []; // 初始化拖拽遮罩列表 r.dragMaskList = new Array(); // 设置是否显示悬停的DOM元素 rs.showHoverDom = true; }, //default cache of exedit _initCache = function (treeId) { // 初始化缓存,暂未实现具体逻辑 }, //default bind event of exedit _bindEvent = function (setting) { // 获取树对象 var o = setting.treeObj; // 获取事件常量 var c = consts.event; o.bind(c.RENAME, function (event, treeId, treeNode, isCancel) { // 绑定重命名事件,调用回调函数 tools.apply(setting.callback.onRename, [event, treeId, treeNode, isCancel]); }); o.bind(c.DRAG, function (event, srcEvent, treeId, treeNodes) { // 绑定拖拽事件,调用回调函数 tools.apply(setting.callback.onDrag, [srcEvent, treeId, treeNodes]); }); o.bind(c.DRAGMOVE, function (event, srcEvent, treeId, treeNodes) { // 绑定拖拽移动事件,调用回调函数 tools.apply(setting.callback.onDragMove, [srcEvent, treeId, treeNodes]); }); o.bind(c.DROP, function (event, srcEvent, treeId, treeNodes, targetNode, moveType, isCopy) { // 调用设置中的回调函数,处理节点拖放事件 tools.apply(setting.callback.onDrop, [srcEvent, treeId, treeNodes, targetNode, moveType, isCopy]); }); }, _unbindEvent = function (setting) { // 获取树对象和常量 var o = setting.treeObj; var c = consts.event; // 解绑重命名事件 o.unbind(c.RENAME); // 解绑拖拽开始事件 o.unbind(c.DRAG); // 解绑拖拽移动事件 o.unbind(c.DRAGMOVE); // 解绑拖拽放下事件 o.unbind(c.DROP); }, //default event proxy of exedit _eventProxy = function (e) { // 获取事件目标、相关目标、设置等变量 var target = e.target, setting = data.getSetting(e.data.treeId), relatedTarget = e.relatedTarget, tId = "", node = null, nodeEventType = "", treeEventType = "", nodeEventCallback = null, treeEventCallback = null, tmp = null; if (tools.eqs(e.type, "mouseover")) { // 处理鼠标悬停事件,获取目标节点的ID和事件类型 tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); if (tmp) { tId = tools.getNodeMainDom(tmp).id; nodeEventType = "hoverOverNode"; } } else if (tools.eqs(e.type, "mouseout")) { // 处理鼠标移出事件,判断是否移出了节点范围 tmp = tools.getMDom(setting, relatedTarget, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); if (!tmp) { tId = "remove"; nodeEventType = "hoverOutNode"; } } else if (tools.eqs(e.type, "mousedown")) { // 处理鼠标按下事件,获取目标节点的ID和事件类型 tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]); if (tmp) { tId = tools.getNodeMainDom(tmp).id; nodeEventType = "mousedownNode"; } } // 检查 tId 数组是否非空 if (tId.length > 0) { // 从缓存中获取节点对象 node = data.getNodeCache(setting, tId); // 根据节点事件类型选择相应的回调函数 switch (nodeEventType) { // 如果事件类型是鼠标按下节点 case "mousedownNode" : // 设置回调函数为鼠标按下节点的处理函数 nodeEventCallback = _handler.onMousedownNode; break; // 如果事件类型是鼠标悬停到节点上 case "hoverOverNode" : // 设置回调函数为鼠标悬停到节点上的处理函数 nodeEventCallback = _handler.onHoverOverNode; break; // 如果事件类型是鼠标移出节点 case "hoverOutNode" : // 设置回调函数为鼠标移出节点的处理函数 nodeEventCallback = _handler.onHoverOutNode; break; } } var proxyResult = { stop: false, // 初始化停止标志为false node: node, // 当前节点 nodeEventType: nodeEventType, // 节点事件类型 nodeEventCallback: nodeEventCallback, // 节点事件回调函数 treeEventType: treeEventType, // 树事件类型 treeEventCallback: treeEventCallback // 树事件回调函数 }; return proxyResult // 返回代理结果对象 }, //default init node of exedit _initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { if (!n) return; // 如果节点不存在,直接返回 n.isHover = false; // 初始化节点的悬停状态为false n.editNameFlag = false; // 初始化节点的编辑名称标志为false }, //update zTreeObj, add method of edit _zTreeTools = function (setting, zTreeTools) { zTreeTools.cancelEditName = function (newName) { var root = data.getRoot(this.setting); // 获取根节点 if (!root.curEditNode) return; // 如果没有正在编辑的节点,直接返回 view.cancelCurEditNode(this.setting, newName ? newName : null, true); // 取消当前编辑的节点 } zTreeTools.copyNode = function (targetNode, node, moveType, isSilent) { if (!node) return null; // 如果节点不存在,返回null var isParent = data.nodeIsParent(setting, targetNode); // 判断目标节点是否是父节点 if (targetNode && !isParent && this.setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) return null; // 如果目标节点不是父节点且不允许移动到叶子节点,返回null var _this = this, newNode = tools.clone(node); // 克隆新节点 if (!targetNode) { targetNode = null; // 如果目标节点不存在,设置为null moveType = consts.move.TYPE_INNER; // 设置移动类型为内部移动 } if (moveType == consts.move.TYPE_INNER) { function copyCallback() { view.addNodes(_this.setting, targetNode, -1, [newNode], isSilent); // 添加新节点到目标节点 } if (tools.canAsync(this.setting, targetNode)) { view.asyncNode(this.setting, targetNode, isSilent, copyCallback); // 如果目标节点支持异步加载,则异步加载后执行回调 } else { copyCallback(); // 否则直接执行回调 } } else { view.addNodes(this.setting, targetNode.parentNode, -1, [newNode], isSilent); // 添加新节点到目标节点的父节点 view.moveNode(this.setting, targetNode, newNode, moveType, false, isSilent); // 移动目标节点到新节点位置 } return newNode; // 返回新节点 } zTreeTools.editName = function (node) { if (!node || !node.tId || node !== data.getNodeCache(this.setting, node.tId)) return; // 如果节点不存在或节点ID无效或节点不在缓存中,直接返回 if (node.parentTId) view.expandCollapseParentNode(this.setting, node.getParentNode(), true); // 如果节点有父节点,展开父节点 view.editNode(this.setting, node) // 编辑节点名称 } zTreeTools.moveNode = function (targetNode, node, moveType, isSilent) { if (!node) return node; // 如果节点不存在,直接返回节点 var isParent = data.nodeIsParent(setting, targetNode); // 判断目标节点是否是父节点 if (targetNode && !isParent && this.setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) { return null; // 如果目标节点不是父节点且不允许移动到叶子节点,返回null } else if (targetNode && ((node.parentTId == targetNode.tId && moveType == consts.move.TYPE_INNER) || $$(node, this.setting).find("#" + targetNode.tId).length > 0)) { return null; // 如果目标节点是当前节点的父节点且移动类型为内部移动,或者目标节点包含当前节点,返回null } else if (!targetNode) { targetNode = null; // 如果目标节点不存在,设置为null var _this = this; function moveCallback() { // 调用view对象的moveNode方法,执行节点移动操作 view.moveNode(_this.setting, targetNode, node, moveType, false, isSilent); } // 判断是否可以异步处理节点移动,并且移动类型为内部移动 if (tools.canAsync(this.setting, targetNode) && moveType === consts.move.TYPE_INNER) { // 如果可以异步处理,则调用view对象的asyncNode方法进行异步节点移动 view.asyncNode(this.setting, targetNode, isSilent, moveCallback); } else { // 否则直接调用moveCallback方法进行同步节点移动 moveCallback(); } // 返回当前节点对象 return node; } zTreeTools.setEditable = function (editable) { // 设置树的编辑状态 this.setting.edit.enable = editable; // 刷新树以应用新的编辑状态 return this.refresh(); } }, //method of operate data _data = { /** * 设置子节点的层级 * @param {Object} setting - 配置对象 * @param {Object} parentNode - 父节点对象 * @param {Object} node - 当前节点对象 */ setSonNodeLevel: function (setting, parentNode, node) { // 如果当前节点不存在,直接返回 if (!node) return; // 获取当前节点的子节点列表 var children = data.nodeChildren(setting, node); // 设置当前节点的层级,如果存在父节点则层级加1,否则为0 node.level = (parentNode) ? parentNode.level + 1 : 0; // 如果子节点列表不存在,直接返回 if (!children) return; // 遍历所有子节点,递归设置每个子节点的层级 for (var i = 0, l = children.length; i < l; i++) { if (children[i]) data.setSonNodeLevel(setting, node, children[i]); } } }, //method of event proxy _event = {}, // 事件处理程序的方法 _handler = { /** * 当鼠标悬停在节点上时触发的事件处理函数 * @param {Object} event - 事件对象,包含事件相关的数据 * @param {Object} node - 当前悬停的节点对象 */ onHoverOverNode: function (event, node) { // 获取树的设置信息 var setting = data.getSetting(event.data.treeId), // 获取树的根节点 root = data.getRoot(setting); // 如果当前悬停的节点不是目标节点 if (root.curHoverNode != node) { // 调用onHoverOutNode方法处理之前的悬停节点 _handler.onHoverOutNode(event); } // 更新当前悬停的节点为目标节点 root.curHoverNode = node; // 在视图中添加悬停效果的DOM元素 view.addHoverDom(setting, node); }, /** * 当鼠标移出节点时触发的事件处理函数 * @param {Object} event - 事件对象,包含事件相关的数据 * @param {Object} node - 当前移出的节点对象(可选) */ onHoverOutNode: function (event, node) { // 获取树的设置信息 var setting = data.getSetting(event.data.treeId), // 获取树的根节点 root = data.getRoot(setting); // 如果当前有悬停的节点且该节点未被选中 if (root.curHoverNode && !data.isSelectedNode(setting, root.curHoverNode)) { // 从视图中移除悬停效果的DOM元素 view.removeTreeDom(setting, root.curHoverNode); // 清空当前悬停的节点 root.curHoverNode = null; } }, onMousedownNode: function (eventMouseDown, _node) { var i, l, setting = data.getSetting(eventMouseDown.data.treeId), root = data.getRoot(setting), roots = data.getRoots(); //right click can't drag & drop if (eventMouseDown.button == 2 || !setting.edit.enable || (!setting.edit.drag.isCopy && !setting.edit.drag.isMove)) return true; //input of edit node name can't drag & drop var target = eventMouseDown.target, _nodes = data.getRoot(setting).curSelectedList, nodes = []; if (!data.isSelectedNode(setting, _node)) { nodes = [_node]; } else { for (i = 0, l = _nodes.length; i < l; i++) { if (_nodes[i].editNameFlag && tools.eqs(target.tagName, "input") && target.getAttribute("treeNode" + consts.id.INPUT) !== null) { return true; } nodes.push(_nodes[i]); if (nodes[0].parentTId !== _nodes[i].parentTId) { nodes = [_node]; break; } } } view.editNodeBlur = true; // 设置编辑节点失去焦点标志为true view.cancelCurEditNode(setting); // 取消当前正在编辑的节点 // 获取文档对象和body对象 var doc = $(setting.treeObj.get(0).ownerDocument), body = $(setting.treeObj.get(0).ownerDocument.body), curNode, tmpArrow, tmpTarget, isOtherTree = false, // 标记是否为其他树 targetSetting = setting, // 目标设置 sourceSetting = setting, // 源设置 preNode, nextNode, // 前一个节点和后一个节点 preTmpTargetNodeId = null, // 临时目标节点ID preTmpMoveType = null, // 临时移动类型 tmpTargetNodeId = null, // 临时目标节点ID moveType = consts.move.TYPE_INNER, // 移动类型,默认为内部移动 mouseDownX = eventMouseDown.clientX, // 鼠标按下时的X坐标 mouseDownY = eventMouseDown.clientY, // 鼠标按下时的Y坐标 startTime = (new Date()).getTime(); // 记录开始时间 if (tools.uCanDo(setting)) { // 如果可以执行操作 doc.bind("mousemove", _docMouseMove); // 绑定鼠标移动事件到文档上 } function _docMouseMove(event) { // 避免在点击节点后开始拖动 if (root.dragFlag == 0 && Math.abs(mouseDownX - event.clientX) < setting.edit.drag.minMoveSize && Math.abs(mouseDownY - event.clientY) < setting.edit.drag.minMoveSize) { return true; // 如果鼠标移动距离小于最小拖动距离,则不进行拖动操作 } var i, l, tmpNode, tmpDom, tmpNodes; body.css("cursor", "pointer"); // 将鼠标指针样式设置为指针 if (root.dragFlag == 0) { // 检查是否允许拖动操作 if (tools.apply(setting.callback.beforeDrag, [setting.treeId, nodes], true) == false) { _docMouseUp(event); // 如果不允许拖动,则调用_docMouseUp方法结束拖动 return true; // 返回true表示处理完成 } // 遍历所有节点 for (i = 0, l = nodes.length; i < l; i++) { // 如果是第一个节点,初始化dragNodeShowBefore数组 if (i == 0) { root.dragNodeShowBefore = []; } // 获取当前节点 tmpNode = nodes[i]; // 如果当前节点是父节点且处于展开状态 if (data.nodeIsParent(setting, tmpNode) && tmpNode.open) { // 折叠该节点 view.expandCollapseNode(setting, tmpNode, !tmpNode.open); // 记录该节点在拖拽前的状态为展开 root.dragNodeShowBefore[tmpNode.tId] = true; } else { // 记录该节点在拖拽前的状态为折叠 root.dragNodeShowBefore[tmpNode.tId] = false; } } // 设置拖拽标志位为1 root.dragFlag = 1; // 隐藏悬停的DOM元素 roots.showHoverDom = false; // 显示iframe遮罩层 tools.showIfameMask(setting, true); // 排序逻辑 var isOrder = true, lastIndex = -1; if (nodes.length > 1) { // 获取父节点或根节点的子节点列表 var pNodes = nodes[0].parentTId ? data.nodeChildren(setting, nodes[0].getParentNode()) : data.getNodes(setting); tmpNodes = []; // 遍历父节点或根节点的子节点列表 for (i = 0, l = pNodes.length; i < l; i++) { // 如果当前节点在dragNodeShowBefore中有记录 if (root.dragNodeShowBefore[pNodes[i].tId] !== undefined) { // 检查顺序是否被打乱 if (isOrder && lastIndex > -1 && (lastIndex + 1) !== i) { isOrder = false; } // 将当前节点添加到临时节点数组中 tmpNodes.push(pNodes[i]); // 更新最后一个索引值 lastIndex = i; } // 如果临时节点数组的长度与原节点数组长度相同,则替换原节点数组并退出循环 if (nodes.length === tmpNodes.length) { nodes = tmpNodes; break; } } } if (isOrder) { // 获取第一个节点的前一个节点 preNode = nodes[0].getPreNode(); // 获取最后一个节点的下一个节点 nextNode = nodes[nodes.length - 1].getNextNode(); } // 创建一个新的