UNPKG

vxe-pc-ui

Version:
1,210 lines 115 kB
import { ref, h, reactive, computed, watch, onBeforeUnmount, nextTick, onMounted, provide } from 'vue'; import { defineVxeComponent } from '../../ui/src/comp'; import { VxeUI, createEvent, useSize, globalEvents, globalResize, renderEmptyElement } from '../../ui'; import { calcTreeLine, enNodeValue, deNodeValue } from './util'; import { errLog } from '../../ui/src/log'; import { getCrossTreeDragNodeInfo } from './store'; import XEUtils from 'xe-utils'; import { getSlotVNs } from '../../ui/src/vn'; import { toCssUnit, isScale, getPaddingTopBottomSize, addClass, removeClass, getTpImg, hasControlKey, getEventTargetNode } from '../../ui/src/dom'; import { isEnableConf } from '../../ui/src/utils'; import { moveRowAnimateToTb, clearRowAnimate } from '../../ui/src/anime'; import VxeLoadingComponent from '../../loading'; const { menus, getConfig, getI18n, getIcon } = VxeUI; /** * 生成节点的唯一主键 */ function getNodeUniqueId() { return XEUtils.uniqueId('node_'); } function createInternalData() { return { // initialized: false, // lastFilterValue: '', treeFullData: [], afterTreeList: [], afterVisibleList: [], nodeMaps: {}, selectCheckboxMaps: {}, indeterminateRowMaps: {}, treeExpandedMaps: {}, treeExpandLazyLoadedMaps: {}, lastScrollLeft: 0, lastScrollTop: 0, scrollYStore: { startIndex: 0, endIndex: 0, visibleSize: 0, offsetSize: 0, rowHeight: 0 }, // prevDragNode: null, // prevDragToChild: false, // prevDragPos: '' lastScrollTime: 0 // hpTimeout: undefined }; } function createReactData() { return { parentHeight: 0, customHeight: 0, customMinHeight: 0, customMaxHeight: 0, currentNode: null, scrollYLoad: false, bodyHeight: 0, topSpaceHeight: 0, selectRadioKey: null, treeList: [], updateExpandedFlag: 1, updateCheckboxFlag: 1, dragNode: null, dragTipText: '' }; } // let crossTreeDragNodeObj: { // $oldTree: VxeTreeConstructor & VxeTreePrivateMethods // $newTree: (VxeTreeConstructor & VxeTreePrivateMethods) | null // } | null = null export default defineVxeComponent({ name: 'VxeTree', props: { data: Array, autoResize: { type: Boolean, default: () => getConfig().tree.autoResize }, height: [String, Number], maxHeight: { type: [String, Number], default: () => getConfig().tree.maxHeight }, minHeight: { type: [String, Number], default: () => getConfig().tree.minHeight }, loading: Boolean, loadingConfig: Object, accordion: { type: Boolean, default: () => getConfig().tree.accordion }, childrenField: { type: String, default: () => getConfig().tree.childrenField }, valueField: { type: String, default: () => getConfig().tree.valueField }, keyField: { type: String, default: () => getConfig().tree.keyField }, parentField: { type: String, default: () => getConfig().tree.parentField }, titleField: { type: String, default: () => getConfig().tree.titleField }, hasChildField: { type: String, default: () => getConfig().tree.hasChildField }, mapChildrenField: { type: String, default: () => getConfig().tree.mapChildrenField }, transform: Boolean, // 已废弃 isCurrent: Boolean, // 已废弃 isHover: Boolean, expandAll: Boolean, expandNodeKeys: Array, showLine: { type: Boolean, default: () => getConfig().tree.showLine }, trigger: String, indent: { type: Number, default: () => getConfig().tree.indent }, showRadio: { type: Boolean, default: () => getConfig().tree.showRadio }, checkNodeKey: { type: [String, Number], default: () => getConfig().tree.checkNodeKey }, radioConfig: Object, showCheckbox: { type: Boolean, default: () => getConfig().tree.showCheckbox }, checkNodeKeys: { type: Array, default: () => getConfig().tree.checkNodeKeys }, checkboxConfig: Object, nodeConfig: Object, lazy: Boolean, toggleMethod: Function, loadMethod: Function, drag: { type: Boolean, default: () => getConfig().tree.drag }, dragConfig: Object, menuConfig: Object, showIcon: { type: Boolean, default: true }, iconOpen: { type: String, default: () => getConfig().tree.iconOpen }, iconClose: { type: String, default: () => getConfig().tree.iconClose }, iconLoaded: { type: String, default: () => getConfig().tree.iconLoaded }, filterValue: [String, Number], filterConfig: Object, size: { type: String, default: () => getConfig().tree.size || getConfig().size }, virtualYConfig: Object }, emits: [ 'update:modelValue', 'update:checkNodeKey', 'update:checkNodeKeys', 'node-click', 'node-dblclick', 'current-change', 'radio-change', 'checkbox-change', 'load-success', 'load-error', 'scroll', 'node-dragstart', 'node-dragover', 'node-dragend', 'node-expand', 'node-menu', 'menu-click' ], setup(props, context) { const { emit, slots } = context; const xID = XEUtils.uniqueId(); const { computeSize } = useSize(props); const refElem = ref(); const refHeaderWrapperElem = ref(); const refFooterWrapperElem = ref(); const refVirtualWrapper = ref(); const refVirtualBody = ref(); const refDragNodeLineElem = ref(); const refDragTipElem = ref(); const crossTreeDragNodeInfo = getCrossTreeDragNodeInfo(); const internalData = createInternalData(); const reactData = reactive(createReactData()); const refMaps = { refElem }; const computeTitleField = computed(() => { return props.titleField || 'title'; }); const computeKeyField = computed(() => { return props.keyField || 'id'; }); const computeValueField = computed(() => { const keyField = computeKeyField.value; return props.valueField || keyField; }); const computeParentField = computed(() => { return props.parentField || 'parentId'; }); const computeChildrenField = computed(() => { return props.childrenField || 'children'; }); const computeMapChildrenField = computed(() => { return props.mapChildrenField || 'mapChildren'; }); const computeHasChildField = computed(() => { return props.hasChildField || 'hasChild'; }); const computeVirtualYOpts = computed(() => { return Object.assign({}, getConfig().tree.virtualYConfig, props.virtualYConfig); }); const computeIsRowCurrent = computed(() => { const nodeOpts = computeNodeOpts.value; const { isCurrent } = nodeOpts; if (XEUtils.isBoolean(isCurrent)) { return isCurrent; } return props.isCurrent; }); const computeIsRowHover = computed(() => { const nodeOpts = computeNodeOpts.value; const { isHover } = nodeOpts; if (XEUtils.isBoolean(isHover)) { return isHover; } return props.isHover; }); const computeRadioOpts = computed(() => { return Object.assign({ showIcon: true }, getConfig().tree.radioConfig, props.radioConfig); }); const computeCheckboxOpts = computed(() => { return Object.assign({ showIcon: true }, getConfig().tree.checkboxConfig, props.checkboxConfig); }); const computeNodeOpts = computed(() => { return Object.assign({}, getConfig().tree.nodeConfig, props.nodeConfig); }); const computeLoadingOpts = computed(() => { return Object.assign({}, getConfig().tree.loadingConfig, props.loadingConfig); }); const computeDragOpts = computed(() => { return Object.assign({}, getConfig().tree.dragConfig, props.dragConfig); }); const computeMenuOpts = computed(() => { return Object.assign({}, getConfig().tree.menuConfig, props.menuConfig); }); const computeTreeStyle = computed(() => { const { indent } = props; const { customHeight, customMinHeight, customMaxHeight } = reactData; const stys = {}; if (customHeight) { stys.height = toCssUnit(customHeight); } if (customMinHeight) { stys.minHeight = toCssUnit(customMinHeight); } if (customMaxHeight) { stys.maxHeight = toCssUnit(customMaxHeight); } if (indent) { stys['--vxe-ui-tree-node-indent'] = toCssUnit(indent); } return stys; }); const computeFilterOpts = computed(() => { return Object.assign({}, getConfig().tree.filterConfig, props.filterConfig); }); const computeMaps = { computeKeyField, computeParentField, computeChildrenField, computeMapChildrenField, computeRadioOpts, computeCheckboxOpts, computeNodeOpts, computeDragOpts }; const $xeTree = { xID, props, context, internalData, reactData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps }; const getNodeId = (node) => { const valueField = computeValueField.value; const nodeKey = XEUtils.get(node, valueField); return enNodeValue(nodeKey); }; const isExpandByNode = (node) => { const { updateExpandedFlag } = reactData; const { treeExpandedMaps } = internalData; const nodeid = getNodeId(node); return !!(updateExpandedFlag && treeExpandedMaps[nodeid]); }; const isCheckedByRadioNodeId = (nodeid) => { const { selectRadioKey } = reactData; return selectRadioKey === nodeid; }; const isCheckedByRadioNode = (node) => { return isCheckedByRadioNodeId(getNodeId(node)); }; const isCheckedByCheckboxNodeId = (nodeid) => { const { updateCheckboxFlag } = reactData; const { selectCheckboxMaps } = internalData; return !!(updateCheckboxFlag && selectCheckboxMaps[nodeid]); }; const isCheckedByCheckboxNode = (node) => { return isCheckedByCheckboxNodeId(getNodeId(node)); }; const isIndeterminateByCheckboxNodeid = (nodeid) => { const { updateCheckboxFlag } = reactData; const { indeterminateRowMaps } = internalData; return !!(updateCheckboxFlag && indeterminateRowMaps[nodeid]); }; const isIndeterminateByCheckboxNode = (node) => { return isIndeterminateByCheckboxNodeid(getNodeId(node)); }; const emitCheckboxMode = (value) => { emit('update:checkNodeKeys', value); }; const emitRadioMode = (value) => { emit('update:checkNodeKey', value); }; const handleSetCheckboxByNodeId = (nodeKeys, checked) => { const { nodeMaps } = internalData; if (nodeKeys) { if (!XEUtils.isArray(nodeKeys)) { nodeKeys = [nodeKeys]; } const nodeList = []; nodeKeys.forEach((nodeKey) => { const nodeid = enNodeValue(nodeKey); const nodeItem = nodeMaps[nodeid]; if (nodeItem) { nodeList.push(nodeItem.item); } }); handleCheckedCheckboxNode(nodeList, checked); } return nextTick(); }; const handleCheckedCheckboxNode = (nodeList, checked) => { const { transform } = props; const { selectCheckboxMaps } = internalData; const mapChildrenField = computeMapChildrenField.value; const childrenField = computeChildrenField.value; const checkboxOpts = computeCheckboxOpts.value; const { checkStrictly } = checkboxOpts; const handleSelect = (node) => { const nodeid = getNodeId(node); if (checked) { if (!selectCheckboxMaps[nodeid]) { selectCheckboxMaps[nodeid] = node; } } else { if (selectCheckboxMaps[nodeid]) { delete selectCheckboxMaps[nodeid]; } } }; if (checkStrictly) { nodeList.forEach(handleSelect); } else { XEUtils.eachTree(nodeList, handleSelect, { children: transform ? mapChildrenField : childrenField }); } reactData.updateCheckboxFlag++; updateCheckboxStatus(); }; const updateCheckboxChecked = (nodeKeys) => { internalData.selectCheckboxMaps = {}; handleSetCheckboxByNodeId(nodeKeys, true); }; const handleSetExpand = (nodeid, expanded, expandedMaps) => { if (expanded) { if (!expandedMaps[nodeid]) { expandedMaps[nodeid] = true; } } else { if (expandedMaps[nodeid]) { delete expandedMaps[nodeid]; } } }; const dispatchEvent = (type, params, evnt) => { emit(type, createEvent(evnt, { $tree: $xeTree }, params)); }; const getParentElem = () => { const el = refElem.value; return el ? el.parentElement : null; }; const calcTreeHeight = (key) => { const { parentHeight } = reactData; const val = props[key]; let num = 0; if (val) { if (val === '100%' || val === 'auto') { num = parentHeight; } else { if (isScale(val)) { num = Math.floor((XEUtils.toInteger(val) || 1) / 100 * parentHeight); } else { num = XEUtils.toNumber(val); } num = Math.max(40, num); } } return num; }; const updateHeight = () => { reactData.customHeight = calcTreeHeight('height'); reactData.customMinHeight = calcTreeHeight('minHeight'); reactData.customMaxHeight = calcTreeHeight('maxHeight'); // 如果启用虚拟滚动,默认高度 if (reactData.scrollYLoad && !(reactData.customHeight || reactData.customMinHeight)) { reactData.customHeight = 300; } }; const createNode = (records) => { const valueField = computeValueField.value; return Promise.resolve(records.map(obj => { const item = Object.assign({}, obj); let nodeid = getNodeId(item); if (!nodeid) { nodeid = getNodeUniqueId(); XEUtils.set(item, valueField, nodeid); } return item; })); }; const cacheNodeMap = () => { const { treeFullData } = internalData; const valueField = computeValueField.value; const childrenField = computeChildrenField.value; const keyMaps = {}; XEUtils.eachTree(treeFullData, (item, index, items, path, parent, nodes) => { let nodeid = getNodeId(item); if (!nodeid) { nodeid = getNodeUniqueId(); XEUtils.set(item, valueField, nodeid); } keyMaps[nodeid] = { item, index, $index: -1, _index: -1, items, parent, nodes, level: nodes.length - 1, treeIndex: index, lineCount: 0, treeLoaded: false }; }, { children: childrenField }); internalData.nodeMaps = keyMaps; }; const updateAfterDataIndex = () => { const { transform } = props; const { afterTreeList, nodeMaps } = internalData; const childrenField = computeChildrenField.value; const mapChildrenField = computeMapChildrenField.value; let vtIndex = 0; XEUtils.eachTree(afterTreeList, (item, index, items) => { const nodeid = getNodeId(item); const nodeItem = nodeMaps[nodeid]; if (nodeItem) { nodeItem.items = items; nodeItem.treeIndex = index; nodeItem._index = vtIndex; } else { const rest = { item, index, $index: -1, _index: vtIndex, items, parent, nodes: [], level: 0, treeIndex: index, lineCount: 0, treeLoaded: false }; nodeMaps[nodeid] = rest; } vtIndex++; }, { children: transform ? mapChildrenField : childrenField }); }; const updateAfterFullData = () => { const { transform, filterValue } = props; const { treeFullData, lastFilterValue } = internalData; const titleField = computeTitleField.value; const childrenField = computeChildrenField.value; const mapChildrenField = computeMapChildrenField.value; const filterOpts = computeFilterOpts.value; const { autoExpandAll, beforeFilterMethod, filterMethod, afterFilterMethod } = filterOpts; let fullList = treeFullData; let treeList = fullList; let filterStr = ''; if (filterValue || filterValue === 0) { filterStr = `${filterValue}`; const handleSearch = filterMethod ? (item) => { return filterMethod({ $tree: $xeTree, node: item, filterValue: filterStr }); } : (item) => { return String(item[titleField]).toLowerCase().indexOf(filterStr.toLowerCase()) > -1; }; const bafParams = { $tree: $xeTree, filterValue: filterStr }; if (beforeFilterMethod) { beforeFilterMethod(bafParams); } if (transform) { treeList = XEUtils.searchTree(treeFullData, handleSearch, { original: true, isEvery: true, children: childrenField, mapChildren: mapChildrenField }); fullList = treeList; } else { fullList = treeFullData.filter(handleSearch); } internalData.lastFilterValue = filterStr; nextTick(() => { // 筛选时自动展开 if (autoExpandAll) { $xeTree.setAllExpandNode(true).then(() => { if (afterFilterMethod) { afterFilterMethod(bafParams); } }); } else { if (afterFilterMethod) { afterFilterMethod(bafParams); } } }); } else { if (transform) { treeList = XEUtils.searchTree(treeFullData, () => true, { original: true, isEvery: true, children: childrenField, mapChildren: mapChildrenField }); fullList = treeList; if (lastFilterValue) { const bafParams = { $tree: $xeTree, filterValue: filterStr }; if (beforeFilterMethod) { beforeFilterMethod(bafParams); } // 取消筛选时自动收起 nextTick(() => { if (autoExpandAll) { $xeTree.clearAllExpandNode().then(() => { if (afterFilterMethod) { afterFilterMethod(bafParams); } }); } else { if (afterFilterMethod) { afterFilterMethod(bafParams); } } }); } } internalData.lastFilterValue = ''; } internalData.afterVisibleList = fullList; internalData.afterTreeList = treeList; updateAfterDataIndex(); }; /** * 如果为虚拟树、则将树结构拍平 */ const handleTreeToList = () => { const { transform } = props; const { afterTreeList, treeExpandedMaps } = internalData; const mapChildrenField = computeMapChildrenField.value; const expandMaps = {}; if (transform) { const fullData = []; XEUtils.eachTree(afterTreeList, (item, index, items, path, parentRow) => { const nodeid = getNodeId(item); const parentNodeid = getNodeId(parentRow); if (!parentRow || (expandMaps[parentNodeid] && treeExpandedMaps[parentNodeid])) { expandMaps[nodeid] = 1; fullData.push(item); } }, { children: mapChildrenField }); updateScrollYStatus(fullData); internalData.afterVisibleList = fullData; return fullData; } return internalData.afterVisibleList; }; const handleData = (force) => { const { scrollYLoad } = reactData; const { scrollYStore, nodeMaps } = internalData; let fullList = internalData.afterVisibleList; if (force) { // 更新数据,处理筛选和排序 updateAfterFullData(); // 如果为虚拟树,将树结构拍平 fullList = handleTreeToList(); } const treeList = scrollYLoad ? fullList.slice(scrollYStore.startIndex, scrollYStore.endIndex) : fullList.slice(0); treeList.forEach((item, $index) => { const nodeid = getNodeId(item); const itemRest = nodeMaps[nodeid]; if (itemRest) { itemRest.$index = $index; } }); reactData.treeList = treeList; }; const triggerSearchEvent = XEUtils.debounce(() => handleData(true), 350, { trailing: true }); const loadData = (list) => { const { expandAll, expandNodeKeys, transform } = props; const { initialized, scrollYStore } = internalData; const keyField = computeKeyField.value; const parentField = computeParentField.value; const childrenField = computeChildrenField.value; const fullData = transform ? XEUtils.toArrayTree(list, { key: keyField, parentKey: parentField, mapChildren: childrenField }) : list ? list.slice(0) : []; internalData.treeFullData = fullData; Object.assign(scrollYStore, { startIndex: 0, endIndex: 1, visibleSize: 0 }); const sYLoad = updateScrollYStatus(fullData); cacheNodeMap(); handleData(true); if (sYLoad) { if (!(props.height || props.maxHeight)) { errLog('vxe.error.reqProp', ['[tree] height | max-height | virtual-y-config.enabled=false']); } } return computeScrollLoad().then(() => { if (!initialized) { if (list && list.length) { internalData.initialized = true; if (expandAll) { $xeTree.setAllExpandNode(true); } else if (expandNodeKeys && expandNodeKeys.length) { $xeTree.setExpandByNodeId(expandNodeKeys, true); } handleSetCheckboxByNodeId(props.checkNodeKeys || [], true); } } updateHeight(); refreshScroll(); }); }; const updateScrollYStatus = (fullData) => { const { transform } = props; const virtualYOpts = computeVirtualYOpts.value; const allList = fullData || internalData.treeFullData; // 如果gt为0,则总是启用 const scrollYLoad = !!transform && !!virtualYOpts.enabled && virtualYOpts.gt > -1 && (virtualYOpts.gt === 0 || virtualYOpts.gt < allList.length); reactData.scrollYLoad = scrollYLoad; return scrollYLoad; }; const updateYSpace = () => { const { scrollYLoad } = reactData; const { scrollYStore, afterVisibleList } = internalData; reactData.bodyHeight = scrollYLoad ? afterVisibleList.length * scrollYStore.rowHeight : 0; reactData.topSpaceHeight = scrollYLoad ? Math.max(scrollYStore.startIndex * scrollYStore.rowHeight, 0) : 0; }; const updateYData = () => { handleData(); updateYSpace(); }; const computeScrollLoad = () => { return nextTick().then(() => { const { scrollYLoad } = reactData; const { scrollYStore } = internalData; const virtualBodyElem = refVirtualBody.value; const virtualYOpts = computeVirtualYOpts.value; let rowHeight = 0; let firstItemElem; if (virtualBodyElem) { if (!firstItemElem) { firstItemElem = virtualBodyElem.children[0]; } } if (firstItemElem) { rowHeight = firstItemElem.offsetHeight; } rowHeight = Math.max(20, rowHeight); scrollYStore.rowHeight = rowHeight; // 计算 Y 逻辑 if (scrollYLoad) { const scrollBodyElem = refVirtualWrapper.value; const visibleYSize = Math.max(8, scrollBodyElem ? Math.ceil(scrollBodyElem.clientHeight / rowHeight) : 0); const offsetYSize = Math.max(0, Math.min(2, XEUtils.toNumber(virtualYOpts.oSize))); scrollYStore.offsetSize = offsetYSize; scrollYStore.visibleSize = visibleYSize; scrollYStore.endIndex = Math.max(scrollYStore.startIndex, visibleYSize + offsetYSize, scrollYStore.endIndex); updateYData(); } else { updateYSpace(); } }); }; /** * 如果有滚动条,则滚动到对应的位置 */ const handleScrollTo = (scrollLeft, scrollTop) => { const scrollBodyElem = refVirtualWrapper.value; if (scrollLeft) { if (!XEUtils.isNumber(scrollLeft)) { scrollTop = scrollLeft.top; scrollLeft = scrollLeft.left; } } if (scrollBodyElem) { if (XEUtils.isNumber(scrollLeft)) { scrollBodyElem.scrollLeft = scrollLeft; } if (XEUtils.isNumber(scrollTop)) { scrollBodyElem.scrollTop = scrollTop; } } if (reactData.scrollYLoad) { return new Promise(resolve => { setTimeout(() => { nextTick(() => { resolve(); }); }, 50); }); } return nextTick(); }; /** * 刷新滚动条 */ const refreshScroll = () => { const { lastScrollLeft, lastScrollTop } = internalData; return clearScroll().then(() => { if (lastScrollLeft || lastScrollTop) { internalData.lastScrollLeft = 0; internalData.lastScrollTop = 0; return scrollTo(lastScrollLeft, lastScrollTop); } }); }; /** * 重新计算列表 */ const recalculate = () => { const { scrollYStore } = internalData; const { rowHeight } = scrollYStore; const el = refElem.value; if (el && el.clientWidth && el.clientHeight) { const parentEl = getParentElem(); const headerWrapperEl = refHeaderWrapperElem.value; const footerWrapperEl = refFooterWrapperElem.value; const headHeight = headerWrapperEl ? headerWrapperEl.clientHeight : 0; const footHeight = footerWrapperEl ? footerWrapperEl.clientHeight : 0; if (parentEl) { const parentPaddingSize = getPaddingTopBottomSize(parentEl); reactData.parentHeight = Math.max(headHeight + footHeight + rowHeight, parentEl.clientHeight - parentPaddingSize - headHeight - footHeight); } updateHeight(); return computeScrollLoad().then(() => { updateHeight(); updateYSpace(); }); } return nextTick(); }; const loadYData = () => { const { scrollYStore } = internalData; const { startIndex, endIndex, visibleSize, offsetSize, rowHeight } = scrollYStore; const scrollBodyElem = refVirtualWrapper.value; if (!scrollBodyElem) { return; } const scrollTop = scrollBodyElem.scrollTop; const toVisibleIndex = Math.floor(scrollTop / rowHeight); const offsetStartIndex = Math.max(0, toVisibleIndex - 1 - offsetSize); const offsetEndIndex = toVisibleIndex + visibleSize + offsetSize; if (toVisibleIndex <= startIndex || toVisibleIndex >= endIndex - visibleSize - 1) { if (startIndex !== offsetStartIndex || endIndex !== offsetEndIndex) { scrollYStore.startIndex = offsetStartIndex; scrollYStore.endIndex = offsetEndIndex; updateYData(); } } }; const scrollEvent = (evnt) => { const scrollBodyElem = evnt.target; const scrollTop = scrollBodyElem.scrollTop; const scrollLeft = scrollBodyElem.scrollLeft; const isX = scrollLeft !== internalData.lastScrollLeft; const isY = scrollTop !== internalData.lastScrollTop; internalData.lastScrollTop = scrollTop; internalData.lastScrollLeft = scrollLeft; if (reactData.scrollYLoad) { loadYData(); } internalData.lastScrollTime = Date.now(); dispatchEvent('scroll', { scrollLeft, scrollTop, isX, isY }, evnt); }; const clearScroll = () => { const scrollBodyElem = refVirtualWrapper.value; if (scrollBodyElem) { scrollBodyElem.scrollTop = 0; scrollBodyElem.scrollLeft = 0; } internalData.lastScrollTop = 0; internalData.lastScrollLeft = 0; return nextTick(); }; const handleNodeMousedownEvent = (evnt, node) => { const { drag } = props; const { nodeMaps } = internalData; const targetEl = evnt.currentTarget; const dragConfig = computeDragOpts.value; const { trigger, isCrossDrag, isPeerDrag, disabledMethod } = dragConfig; const nodeid = getNodeId(node); const triggerTreeNode = getEventTargetNode(evnt, targetEl, 'vxe-tree--node-item-switcher').flag; let isNodeDrag = false; if (drag) { isNodeDrag = trigger === 'node'; } if (!triggerTreeNode) { const params = { node, $tree: $xeTree }; const itemRest = nodeMaps[nodeid]; if (isNodeDrag && (isCrossDrag || isPeerDrag || (itemRest && !itemRest.level)) && !(disabledMethod && disabledMethod(params))) { handleNodeDragMousedownEvent(evnt, { node }); } } }; const handleNodeClickEvent = (evnt, node) => { const { showRadio, showCheckbox, trigger } = props; const radioOpts = computeRadioOpts.value; const checkboxOpts = computeCheckboxOpts.value; const isRowCurrent = computeIsRowCurrent.value; let triggerCurrent = false; let triggerRadio = false; let triggerCheckbox = false; let triggerExpand = false; if (isRowCurrent) { triggerCurrent = true; changeCurrentEvent(evnt, node); } else if (reactData.currentNode) { reactData.currentNode = null; } if (trigger === 'node') { triggerExpand = true; toggleExpandEvent(evnt, node); } if (showRadio && radioOpts.trigger === 'node') { triggerRadio = true; changeRadioEvent(evnt, node); } if (showCheckbox && checkboxOpts.trigger === 'node') { triggerCheckbox = true; changeCheckboxEvent(evnt, node); } dispatchEvent('node-click', { node, triggerCurrent, triggerRadio, triggerCheckbox, triggerExpand }, evnt); }; const handleNodeDblclickEvent = (evnt, node) => { dispatchEvent('node-dblclick', { node }, evnt); }; const handleContextmenuEvent = (evnt, node) => { const { menuConfig } = props; const isRowCurrent = computeIsRowCurrent.value; const menuOpts = computeMenuOpts.value; if (menuConfig ? isEnableConf(menuOpts) : menuOpts.enabled) { const { options, visibleMethod } = menuOpts; if (!visibleMethod || visibleMethod({ $tree: $xeTree, options, node })) { if (isRowCurrent) { changeCurrentEvent(evnt, node); } else if (reactData.currentNode) { reactData.currentNode = null; } if (VxeUI.contextMenu) { VxeUI.contextMenu.openByEvent(evnt, { options, events: { optionClick(eventParams) { const { option } = eventParams; const gMenuOpts = menus.get(option.code); const tmMethod = gMenuOpts ? gMenuOpts.treeMenuMethod : null; const params = { menu: option, node, $event: evnt, $tree: $xeTree, /** * @@deprecated */ option }; if (tmMethod) { tmMethod(params, evnt); } dispatchEvent('menu-click', params, eventParams.$event); } } }); } } } dispatchEvent('node-menu', { node }, evnt); }; const handleAsyncTreeExpandChilds = (node) => { const checkboxOpts = computeCheckboxOpts.value; const { loadMethod } = props; const { checkStrictly } = checkboxOpts; return new Promise(resolve => { if (loadMethod) { const { nodeMaps } = internalData; const nodeid = getNodeId(node); const nodeItem = nodeMaps[nodeid]; internalData.treeExpandLazyLoadedMaps[nodeid] = true; Promise.resolve(loadMethod({ $tree: $xeTree, node })).then((childRecords) => { const { treeExpandLazyLoadedMaps } = internalData; nodeItem.treeLoaded = true; if (treeExpandLazyLoadedMaps[nodeid]) { treeExpandLazyLoadedMaps[nodeid] = false; } if (!XEUtils.isArray(childRecords)) { childRecords = []; } if (childRecords) { return $xeTree.loadChildrenNode(node, childRecords).then(childRows => { const { treeExpandedMaps } = internalData; if (childRows.length && !treeExpandedMaps[nodeid]) { treeExpandedMaps[nodeid] = true; } reactData.updateExpandedFlag++; // 如果当前节点已选中,则展开后子节点也被选中 if (!checkStrictly && $xeTree.isCheckedByCheckboxNodeId(nodeid)) { handleCheckedCheckboxNode(childRows, true); } dispatchEvent('load-success', { node, data: childRecords }, new Event('load-success')); return nextTick(); }); } else { dispatchEvent('load-success', { node, data: childRecords }, new Event('load-success')); } }).catch((e) => { const { treeExpandLazyLoadedMaps } = internalData; nodeItem.treeLoaded = false; if (treeExpandLazyLoadedMaps[nodeid]) { treeExpandLazyLoadedMaps[nodeid] = false; } dispatchEvent('load-error', { node, data: e }, new Event('load-error')); }).finally(() => { handleTreeToList(); handleData(); return recalculate(); }); } else { resolve(); } }); }; /** * 展开与收起树节点 * @param nodeList * @param expanded * @returns */ const handleBaseTreeExpand = (nodeList, expanded) => { const { lazy, accordion, toggleMethod } = props; const { treeExpandLazyLoadedMaps, treeExpandedMaps } = internalData; const { nodeMaps } = internalData; const childrenField = computeChildrenField.value; const hasChildField = computeHasChildField.value; const result = []; let validNodes = toggleMethod ? nodeList.filter((node) => toggleMethod({ $tree: $xeTree, expanded, node })) : nodeList; if (accordion) { validNodes = validNodes.length ? [validNodes[validNodes.length - 1]] : []; // 同一级只能展开一个 const nodeid = getNodeId(validNodes[0]); const nodeItem = nodeMaps[nodeid]; if (nodeItem) { nodeItem.items.forEach(item => { const itemNodeId = getNodeId(item); if (treeExpandedMaps[itemNodeId]) { delete treeExpandedMaps[itemNodeId]; } }); } } const expandNodes = []; if (expanded) { validNodes.forEach((item) => { const itemNodeId = getNodeId(item); if (!treeExpandedMaps[itemNodeId]) { const nodeItem = nodeMaps[itemNodeId]; const isLoad = lazy && item[hasChildField] && !nodeItem.treeLoaded && !treeExpandLazyLoadedMaps[itemNodeId]; // 是否使用懒加载 if (isLoad) { result.push(handleAsyncTreeExpandChilds(item)); } else { if (item[childrenField] && item[childrenField].length) { treeExpandedMaps[itemNodeId] = true; expandNodes.push(item); } } } }); } else { validNodes.forEach(item => { const itemNodeId = getNodeId(item); if (treeExpandedMaps[itemNodeId]) { delete treeExpandedMaps[itemNodeId]; expandNodes.push(item); } }); } reactData.updateExpandedFlag++; handleTreeToList(); handleData(); return Promise.all(result).then(() => recalculate()); }; const toggleExpandEvent = (evnt, node) => { const { lazy } = props; const { treeExpandedMaps, treeExpandLazyLoadedMaps } = internalData; const nodeid = getNodeId(node); const expanded = !treeExpandedMaps[nodeid]; evnt.stopPropagation(); if (!lazy || !treeExpandLazyLoadedMaps[nodeid]) { handleBaseTreeExpand([node], expanded); } dispatchEvent('node-expand', { node, expanded }, evnt); }; const updateCheckboxStatus = () => { const { transform } = props; const { selectCheckboxMaps, indeterminateRowMaps, afterTreeList } = internalData; const childrenField = computeChildrenField.value; const mapChildrenField = computeMapChildrenField.value; const checkboxOpts = computeCheckboxOpts.value; const { checkStrictly, checkMethod } = checkboxOpts; if (!checkStrictly) { const childRowMaps = {}; const childRowList = []; XEUtils.eachTree(afterTreeList, (node) => { const nodeid = getNodeId(node); const childList = node[childrenField]; if (childList && childList.length && !childRowMaps[nodeid]) { childRowMaps[nodeid] = 1; childRowList.unshift([node, nodeid, childList]); } }, { children: transform ? mapChildrenField : childrenField }); childRowList.forEach(vals => { const node = vals[0]; const nodeid = vals[1]; const childList = vals[2]; let sLen = 0; // 已选 let hLen = 0; // 半选 let vLen = 0; // 有效子行 const cLen = childList.length; // 子行 childList.forEach(checkMethod ? (item) => { const childNodeid = getNodeId(item); const isSelect = selectCheckboxMaps[childNodeid]; if (checkMethod({ $tree: $xeTree, node: item })) { if (isSelect) { sLen++; } else if (indeterminateRowMaps[childNodeid]) { hLen++; } vLen++; } else { if (isSelect) { sLen++; } else if (indeterminateRowMaps[childNodeid]) { hLen++; } } } : item => { const childNodeid = getNodeId(item); const isSelect = selectCheckboxMaps[childNodeid]; if (isSelect) { sLen++; } else if (indeterminateRowMaps[childNodeid]) { hLen++; } vLen++; }); let isSelected = false; if (cLen > 0) { if (vLen > 0) { isSelected = (sLen > 0 || hLen > 0) && sLen >= vLen; } else { // 如果存在子项禁用 if ((sLen > 0 && sLen >= vLen)) { isSelected = true; } else if (selectCheckboxMaps[nodeid]) { isSelected = true; } else { isSelected = false; } } } else { // 如果无子项 isSelected = selectCheckboxMaps[nodeid]; } const halfSelect = !isSelected && (sLen > 0 || hLen > 0); if (isSelected) { selectCheckboxMaps[nodeid] = node; if (indeterminateRowMaps[nodeid]) { delete indeterminateRowMaps[nodeid]; } } else { if (selectCheckboxMaps[nodeid]) { delete selectCheckboxMaps[nodeid]; } if (halfSelect) { indeterminateRowMaps[nodeid] = node; } else { if (indeterminateRowMaps[nodeid]) { delete indeterminateRowMaps[nodeid]; } } } }); reactData.updateCheckboxFlag++; } }; const changeCheckboxEvent = (evnt, node) => { evnt.preventDefault(); evnt.stopPropagation(); const { transform } = props; const { selectCheckboxMaps } = internalData; const childrenField = computeChildrenField.value; const mapChildrenField = computeMapChildre