vxe-pc-ui
Version:
A vue based PC component library
1,210 lines • 115 kB
JavaScript
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