import { cjkEncodeDO, Controller, createWidget, emptyFn, Events, extend, UUID, isNotNull, jsonEncode, delay, each, replaceAll, isUndefined, isNotEmptyArray, deepClone, map, Tree, isNull, shortcut, VerticalLayout, Layout, DefaultLayout, some, Widget, STYLE_CONSTANTS } from "@/core"; import { Msg, Pane, LoadingBar, Text } from "@/base"; import $ from "jquery"; @shortcut() export class TreeView extends Pane { static xtype = "bi.tree_view"; static REQ_TYPE_INIT_DATA = 1; static REQ_TYPE_ADJUST_DATA = 2; static REQ_TYPE_SELECT_DATA = 3; static REQ_TYPE_GET_SELECTED_DATA = 4; static EVENT_CHANGE = "EVENT_CHANGE"; static EVENT_INIT = Events.INIT; static EVENT_AFTERINIT = Events.AFTERINIT; _defaultConfig() { return extend(super._defaultConfig(...arguments), { _baseCls: "bi-tree", paras: { selectedValues: {}, }, itemsCreator: emptyFn, showLine: true, }); } _init() { super._init(...arguments); const o = this.options; this._stop = false; this._createTree(); this.tip = createWidget({ type: LoadingBar.xtype, invisible: true, handler: () => this._loadMore(), }); createWidget({ type: VerticalLayout.xtype, scrollable: true, scrolly: false, element: this, items: [this.tip], }); if (isNotNull(o.value)) { this.setSelectedValue(o.value); } } _createTree() { this.id = `bi-tree${UUID()}`; if (this.nodes) { this.nodes.destroy(); } if (this.tree) { this.tree.destroy(); } this.tree = createWidget({ type: Layout.xtype, element: ``, }); createWidget({ type: DefaultLayout.xtype, element: this, items: [this.tree], }); } // 选择节点触发方法 _selectTreeNode(treeId, treeNode) { this.fireEvent(Controller.EVENT_CHANGE, Events.CLICK, treeNode, this); this.fireEvent(TreeView.EVENT_CHANGE, treeNode, this); } // 配置属性 _configSetting() { const paras = this.options.paras; const self = this; const o = this.options; const setting = { async: { enable: true, url: getUrl, autoParam: ["id", "name"], // 节点展开异步请求自动提交id和name otherParam: cjkEncodeDO(paras), // 静态参数 }, check: { enable: true, }, data: { key: { title: "title", name: "text", // 节点的name属性替换成text }, simpleData: { enable: true, // 可以穿id,pid属性的对象数组 }, }, view: { showIcon: false, expandSpeed: "", nameIsHTML: true, // 节点可以用html标签代替 dblClickExpand: false, showLine: o.showLine, }, callback: { beforeExpand, onAsyncSuccess, onAsyncError, beforeCheck, onCheck, onExpand, onCollapse, onClick, }, }; const className = "dark", perTime = 100; function onClick(event, treeId, treeNode) { // 当前点击节点的状态是半选,且为true_part, 则将其改为false_part,使得点击半选后切换到的是全选 let checked = treeNode.checked; const status = treeNode.getCheckStatus(); if (status.half === true && status.checked === true) { checked = false; } // 更新此node的check状态, 影响父子关联,并调用beforeCheck和onCheck回调 self.nodes.checkNode(treeNode, !checked, true, true); } function getUrl(treeId, treeNode) { const parentNode = self._getParentValues(treeNode); treeNode.times = treeNode.times || 1; const param = `id=${treeNode.id}×=${treeNode.times++}&parentValues= ${_global.encodeURIComponent( jsonEncode(parentNode) )}&checkState=${_global.encodeURIComponent(jsonEncode(treeNode.getCheckStatus()))}`; return `&${param}`; } function beforeExpand(treeId, treeNode) { if (!treeNode.isAjaxing) { if (!treeNode.children) { treeNode.times = 1; ajaxGetNodes(treeNode, "refresh"); } return true; } Msg.toast("Please Wait。", { level: "warning", }); // 不展开节点,也不触发onExpand事件 return false; } function onAsyncSuccess(event, treeId, treeNode, msg) { treeNode.halfCheck = false; if (!msg || msg.length === 0 || /^[\s,\S]*<\/html>$/gi.test(msg) || self._stop) { return; } const zTree = self.nodes; const totalCount = treeNode.count || 0; // 尝试去获取下一组节点,若获取值为空数组,表示获取完成 // TODO by GUY if (treeNode.children.length > totalCount) { treeNode.count = treeNode.children.length; delay(() => { ajaxGetNodes(treeNode); }, perTime); } else { // treeNode.icon = ""; zTree.updateNode(treeNode); zTree.selectNode(treeNode.children[0]); // className = (className === "dark" ? "":"dark"); } } function onAsyncError(event, treeId, treeNode, XMLHttpRequest, textStatus, errorThrown) { const zTree = self.nodes; Msg.toast("Error!", "warning"); // treeNode.icon = ""; // zTree.updateNode(treeNode); } function ajaxGetNodes(treeNode, reloadType) { const zTree = self.nodes; if (reloadType === "refresh") { zTree.updateNode(treeNode); // 刷新一下当前节点,如果treeNode.xxx被改了的话 } zTree.reAsyncChildNodes(treeNode, reloadType, true); // 强制加载子节点,reloadType === refresh为先清空再加载,否则为追加到现有子节点之后 } function beforeCheck(treeId, treeNode) { if (treeNode.disabled) { return false; } // 下面主动修改了node的halfCheck属性, 节点属性的判断依赖halfCheck,改之前就获取一下 const status = treeNode.getCheckStatus(); treeNode.halfCheck = false; if (treeNode.checked === true) { // 将展开的节点halfCheck设为false,解决展开节点存在halfCheck=true的情况 guy // 所有的半选状态都需要取消halfCheck=true的情况 function track(children) { each(children, (i, ch) => { if (ch.halfCheck === true) { ch.halfCheck = false; track(ch.children); } }); } track(treeNode.children); const treeObj = self.nodes; const nodes = treeObj.getSelectedNodes(); $.each(nodes, (index, node) => { node.halfCheck = false; }); } // 当前点击节点的状态是半选,且为true_part, 则将其改为false_part,使得点击半选后切换到的是全选 if (status.half === true && status.checked === true) { treeNode.checked = false; } } function onCheck(event, treeId, treeNode) { if (treeNode.disabled) { return false; } self._selectTreeNode(treeId, treeNode); } function onExpand(event, treeId, treeNode) { treeNode.halfCheck = false; } function onCollapse(event, treeId, treeNode) {} return setting; } _getParentValues(treeNode) { if (!treeNode.getParentNode()) { return []; } const parentNode = treeNode.getParentNode(); let result = this._getParentValues(parentNode); result = result.concat([this._getNodeValue(parentNode)]); return result; } _getNodeValue(node) { // 去除标红 return isUndefined(node.value) ? replaceAll(node.text.replace(/<[^>]+>/g, ""), " ", " ") : node.value; } // 获取半选框值 _getHalfSelectedValues(map, node) { const self = this; const checkState = node.getCheckStatus(); // 将未选的去掉 if (checkState.checked === false && checkState.half === false) { return; } // 如果节点已展开,并且是半选 if (isNotEmptyArray(node.children) && checkState.half === true) { const children = node.children; each(children, (i, ch) => { self._getHalfSelectedValues(map, ch); }); return; } const parent = node.parentValues || self._getParentValues(node); const path = parent.concat(this._getNodeValue(node)); // 当前节点是全选的,因为上面的判断已经排除了不选和半选 if (isNotEmptyArray(node.children) || checkState.half === false) { this._buildTree(map, path); return; } // 剩下的就是半选不展开的节点,因为不知道里面是什么情况,所以借助selectedValues(这个是完整的选中情况) const storeValues = deepClone(this.options.paras.selectedValues); const treeNode = this._getTree(storeValues, path); this._addTreeNode(map, parent, this._getNodeValue(node), treeNode); } // 获取的是以values最后一个节点为根的子树 _getTree(map, values) { let cur = map; some(values, (i, value) => { if (cur[value] == null) { return true; } cur = cur[value]; }); return cur; } // 以values为path一路向里补充map, 并在末尾节点添加key: value节点 _addTreeNode(map, values, key, value) { let cur = map; each(values, (i, value) => { if (cur[value] == null) { cur[value] = {}; } cur = cur[value]; }); cur[key] = value; } // 构造树节点 _buildTree(map, values) { let cur = map; each(values, (i, value) => { if (cur[value] == null) { cur[value] = {}; } cur = cur[value]; }); } // 获取选中的值 _getSelectedValues() { const self = this; const hashMap = {}; const rootNoots = this.nodes.getNodes(); track(rootNoots); // 可以看到这个方法没有递归调用,所以在_getHalfSelectedValues中需要关心全选的节点 function track(nodes) { each(nodes, (i, node) => { const checkState = node.getCheckStatus(); if (checkState.checked === true || checkState.half === true) { if (checkState.half === true) { self._getHalfSelectedValues(hashMap, node); } else { const parentValues = node.parentValues || self._getParentValues(node); const values = parentValues.concat([self._getNodeValue(node)]); self._buildTree(hashMap, values); } } }); } return hashMap; } // 处理节点 _dealWidthNodes(nodes) { const self = this, o = this.options; const ns = Tree.arrayFormat(nodes); return map(ns, (i, n) => { const newNode = extend({}, n); newNode.isParent = newNode.isParent || newNode.parent; // n.value = BI.isUndefined(n.value) ? n.text : n.value; // n.text = BI.isUndefined(n.text) ? n.value : n.text; // if (n.text === null) { // n.text = ""; // } if (isNull(newNode.title)) { newNode.title = newNode.text; } if (newNode.disabled) { newNode.title = newNode.warningTitle || newNode.title; } const text = createWidget( extend( { cls: "tree-node-text", tagName: "span", whiteSpace: "nowrap", root: true, keyword: o.paras.keyword, }, newNode, { type: Text.xtype, text: replaceAll(newNode.text, "\n", " "), } ) ); const fragment = Widget._renderEngine.createElement("
"); fragment.append(text.element[0]); newNode.text = fragment.html(); // // 处理标红 // if (BI.isNotNull(n.text)) { // if (BI.isKey(o.paras.keyword)) { // n.text = $("
").__textKeywordMarked__(BI.Text.formatText(n.text + ""), o.paras.keyword, n.py).html(); // } else { // n.text = BI.htmlEncode(BI.Text.formatText(n.text + "")); // } // } return newNode; }); } _loadMore() { const self = this, o = this.options; this.tip.setLoading(); const op = extend({}, o.paras, { times: ++this.times, }); o.itemsCreator(op, res => { if (self._stop === true) { return; } const hasNext = !!res.hasNext, nodes = res.items || []; if (!hasNext) { self.tip.setEnd(); } else { self.tip.setLoaded(); } if (nodes.length > 0) { self.nodes.addNodes(null, self._dealWidthNodes(nodes)); } }); } // 生成树内部方法 _initTree(setting) { const self = this, o = this.options; self.fireEvent(Events.INIT); this.times = 1; const tree = this.tree; tree.empty(); this.loading(); this.tip.setVisible(false); function callback(nodes) { if (self._stop === true) { return; } self.nodes = $.fn.zTree.init(tree.element, setting, nodes); } const op = extend({}, o.paras, { times: 1, }); o.itemsCreator(op, res => { if (self._stop === true) { return; } const hasNext = !!res.hasNext, nodes = res.items || []; if (nodes.length > 0) { callback(self._dealWidthNodes(nodes)); } self.setTipVisible(nodes.length <= 0); self.loaded(); if (!hasNext) { self.tip.invisible(); } else { self.tip.setLoaded(); } op.times === 1 && self.fireEvent(Events.AFTERINIT); }); } // 构造树结构, initTree(nodes, setting) { const defaultSetting = { async: { enable: false, }, check: { enable: false, }, data: { key: { title: "title", name: "text", }, simpleData: { enable: true, }, }, view: { showIcon: false, expandSpeed: "", nameIsHTML: true, }, callback: {}, }; this.nodes = $.fn.zTree.init(this.tree.element, setting || defaultSetting, nodes); } start() { this._stop = false; } stop() { this._stop = true; } // 生成树方法 stroke(config) { delete this.options.keyword; extend(this.options.paras, config); const setting = this._configSetting(); this._createTree(); this.start(); this._initTree(setting); } populate() { this.stroke(...arguments); } hasChecked() { const treeObj = this.nodes; return treeObj.getCheckedNodes(true).length > 0; } checkAll(checked) { function setNode(children) { each(children, (i, child) => { child.halfCheck = false; setNode(child.children); }); } if (!this.nodes) { return; } each(this.nodes.getNodes(), (i, node) => { node.halfCheck = false; setNode(node.children); }); this.nodes.checkAllNodes(checked); } expandAll(flag) { this.nodes && this.nodes.expandAll(flag); } // 设置树节点的状态 setValue(value, param) { this.checkAll(false); this.updateValue(value, param); this.refresh(); } setSelectedValue(value) { this.options.paras.selectedValues = deepClone(value || {}); } updateValue(values, param) { if (!this.nodes) { return; } param || (param = "value"); const treeObj = this.nodes; each(values, (v, op) => { const nodes = treeObj.getNodesByParam(param, v, null); each(nodes, (j, node) => { extend(node, { checked: true }, op); treeObj.updateNode(node); }); }); } refresh() { this.nodes && this.nodes.refresh(); } getValue() { if (!this.nodes) { return null; } return this._getSelectedValues(); } destroyed() { this.stop(); this.nodes && this.nodes.destroy(); } }