vxe-pc-ui
Version:
A vue based PC component library
1,222 lines (1,221 loc) • 50 kB
JavaScript
import { defineComponent, ref, h, reactive, computed, createCommentVNode, watch, onUnmounted, nextTick } from 'vue';
import { createEvent, getIcon, getConfig, useSize } from '../../ui';
import XEUtils from 'xe-utils';
import { getSlotVNs } from '../../ui/src/vn';
import { toCssUnit } from '../../ui/src/dom';
import VxeLoadingComponent from '../../loading/src/loading';
/**
* 生成节点的唯一主键
*/
function getNodeUniqueId() {
return XEUtils.uniqueId('node_');
}
export default defineComponent({
name: 'VxeTree',
props: {
data: Array,
height: [String, Number],
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 as PropType<VxeTreePropTypes.MapChildrenField>,
// default: () => getConfig().tree.mapChildrenField
// },
transform: Boolean,
// 已废弃
isCurrent: Boolean,
// 已废弃
isHover: Boolean,
expandAll: Boolean,
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,
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
},
size: {
type: String,
default: () => getConfig().tree.size || getConfig().size
}
},
emits: [
'update:modelValue',
'update:checkNodeKey',
'update:checkNodeKeys',
'node-click',
'node-dblclick',
'current-change',
'radio-change',
'checkbox-change',
'load-success',
'load-error'
],
setup(props, context) {
const { emit, slots } = context;
const xID = XEUtils.uniqueId();
const { computeSize } = useSize(props);
const refElem = ref();
const reactData = reactive({
currentNode: null,
nodeMaps: {},
selectRadioKey: props.checkNodeKey,
treeList: [],
treeExpandedMaps: {},
treeExpandLazyLoadedMaps: {},
selectCheckboxMaps: {},
indeterminateCheckboxMaps: {}
});
const internalData = {
// initialized: false
};
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 computeHasChildField = computed(() => {
return props.hasChildField || 'hasChild';
});
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 computeTreeStyle = computed(() => {
const { height, minHeight } = props;
const stys = {};
if (height) {
stys.height = toCssUnit(height);
}
if (minHeight) {
stys.minHeight = toCssUnit(minHeight);
}
return stys;
});
const computeMaps = {
computeRadioOpts,
computeCheckboxOpts,
computeNodeOpts
};
const $xeTree = {
xID,
props,
context,
internalData,
reactData,
getRefMaps: () => refMaps,
getComputeMaps: () => computeMaps
};
const getNodeId = (node) => {
const valueField = computeValueField.value;
const nodeid = XEUtils.get(node, valueField);
return XEUtils.eqNull(nodeid) ? '' : encodeURIComponent(nodeid);
};
const isExpandByNode = (node) => {
const { treeExpandedMaps } = reactData;
const nodeid = getNodeId(node);
return !!treeExpandedMaps[nodeid];
};
const isCheckedByRadioNodeId = (nodeid) => {
const { selectRadioKey } = reactData;
return selectRadioKey === nodeid;
};
const isCheckedByRadioNode = (node) => {
return isCheckedByRadioNodeId(getNodeId(node));
};
const isCheckedByCheckboxNodeId = (nodeid) => {
const { selectCheckboxMaps } = reactData;
return !!selectCheckboxMaps[nodeid];
};
const isCheckedByCheckboxNode = (node) => {
return isCheckedByCheckboxNodeId(getNodeId(node));
};
const isIndeterminateByCheckboxNodeid = (nodeid) => {
const { indeterminateCheckboxMaps } = reactData;
return !!indeterminateCheckboxMaps[nodeid];
};
const isIndeterminateByCheckboxNode = (node) => {
return isIndeterminateByCheckboxNodeid(getNodeId(node));
};
const emitCheckboxMode = (value) => {
emit('update:checkNodeKeys', value);
};
const emitRadioMode = (value) => {
emit('update:checkNodeKey', value);
};
const setRadioNode = (node) => {
if (node) {
reactData.selectRadioKey = getNodeId(node);
}
return nextTick();
};
const setCheckboxNode = (nodeList, checked) => {
if (nodeList) {
if (!XEUtils.isArray(nodeList)) {
nodeList = [nodeList];
}
handleCheckedCheckboxNode(nodeList.map((item) => getNodeId(item)), checked);
}
return nextTick();
};
const setCheckboxByNodeId = (nodeIds, checked) => {
if (nodeIds) {
if (!XEUtils.isArray(nodeIds)) {
nodeIds = [nodeIds];
}
handleCheckedCheckboxNode(nodeIds, checked);
}
return nextTick();
};
const handleCheckedCheckboxNode = (nodeIds, checked) => {
const selectKeyMaps = Object.assign({}, reactData.selectCheckboxMaps);
nodeIds.forEach((key) => {
if (checked) {
selectKeyMaps[key] = true;
}
else if (selectKeyMaps[key]) {
delete selectKeyMaps[key];
}
});
reactData.selectCheckboxMaps = selectKeyMaps;
};
const updateCheckboxChecked = (nodeIds) => {
const selectKeyMaps = {};
if (nodeIds) {
nodeIds.forEach((key) => {
selectKeyMaps[key] = true;
});
}
reactData.selectCheckboxMaps = selectKeyMaps;
};
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 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 treeMethods = {
dispatchEvent,
clearCurrentNode() {
reactData.currentNode = null;
return nextTick();
},
getCurrentNodeId() {
const { currentNode } = reactData;
if (currentNode) {
return getNodeId(currentNode);
}
return null;
},
getCurrentNode() {
const { currentNode, nodeMaps } = reactData;
if (currentNode) {
const nodeItem = nodeMaps[getNodeId(currentNode)];
if (nodeItem) {
return nodeItem.item;
}
}
return null;
},
setCurrentNodeId(nodeKey) {
const { nodeMaps } = reactData;
const nodeItem = nodeMaps[nodeKey];
reactData.currentNode = nodeItem ? nodeItem.item : null;
return nextTick();
},
setCurrentNode(node) {
reactData.currentNode = node;
return nextTick();
},
clearRadioNode() {
reactData.selectRadioKey = null;
return nextTick();
},
getRadioNodeId() {
return reactData.selectRadioKey || null;
},
getRadioNode() {
const { selectRadioKey, nodeMaps } = reactData;
if (selectRadioKey) {
const nodeItem = nodeMaps[selectRadioKey];
if (nodeItem) {
return nodeItem.item;
}
}
return null;
},
setRadioNodeId(nodeKey) {
reactData.selectRadioKey = nodeKey;
return nextTick();
},
setRadioNode,
setCheckboxNode,
setCheckboxByNodeId,
getCheckboxNodeIds() {
const { selectCheckboxMaps } = reactData;
return Object.keys(selectCheckboxMaps);
},
getCheckboxNodes() {
const { nodeMaps, selectCheckboxMaps } = reactData;
const list = [];
XEUtils.each(selectCheckboxMaps, (item, nodeid) => {
const nodeItem = nodeMaps[nodeid];
if (nodeItem) {
list.push(nodeItem.item);
}
});
return list;
},
clearCheckboxNode() {
reactData.selectCheckboxMaps = {};
return nextTick();
},
setAllCheckboxNode(checked) {
const selectMaps = {};
const childrenField = computeChildrenField.value;
if (checked) {
XEUtils.eachTree(reactData.treeList, (node) => {
const nodeid = getNodeId(node);
selectMaps[nodeid] = true;
}, { children: childrenField });
}
reactData.selectCheckboxMaps = selectMaps;
return nextTick();
},
clearExpandNode() {
return treeMethods.clearAllExpandNode();
},
clearAllExpandNode() {
XEUtils.each(reactData.nodeMaps, (nodeItem) => {
nodeItem.treeLoaded = false;
});
reactData.treeExpandedMaps = {};
return nextTick();
},
setExpandByNodeId(nodeids, expanded) {
const expandedMaps = Object.assign({}, reactData.treeExpandedMaps);
if (nodeids) {
if (!XEUtils.isArray(nodeids)) {
nodeids = [nodeids];
}
nodeids.forEach((nodeid) => {
handleSetExpand(nodeid, expanded, expandedMaps);
});
reactData.treeExpandedMaps = expandedMaps;
}
return nextTick();
},
getExpandNodeIds() {
const { treeExpandedMaps } = reactData;
return Object.keys(treeExpandedMaps);
},
getExpandNodes() {
const { nodeMaps, treeExpandedMaps } = reactData;
const list = [];
XEUtils.each(treeExpandedMaps, (item, nodeid) => {
const nodeItem = nodeMaps[nodeid];
if (nodeItem) {
list.push(nodeItem.item);
}
});
return list;
},
setExpandNode(nodes, expanded) {
const expandedMaps = Object.assign({}, reactData.treeExpandedMaps);
if (nodes) {
if (!XEUtils.isArray(nodes)) {
nodes = [nodes];
}
nodes.forEach((node) => {
const nodeid = getNodeId(node);
handleSetExpand(nodeid, expanded, expandedMaps);
});
reactData.treeExpandedMaps = expandedMaps;
}
return nextTick();
},
toggleExpandByNodeId(nodeids) {
const expandedMaps = Object.assign({}, reactData.treeExpandedMaps);
if (nodeids) {
if (!XEUtils.isArray(nodeids)) {
nodeids = [nodeids];
}
nodeids.forEach((nodeid) => {
handleSetExpand(nodeid, !expandedMaps[nodeid], expandedMaps);
});
reactData.treeExpandedMaps = expandedMaps;
}
return nextTick();
},
toggleExpandNode(nodes) {
const expandedMaps = Object.assign({}, reactData.treeExpandedMaps);
if (nodes) {
if (!XEUtils.isArray(nodes)) {
nodes = [nodes];
}
nodes.forEach((node) => {
const nodeid = getNodeId(node);
handleSetExpand(nodeid, !expandedMaps[nodeid], expandedMaps);
});
reactData.treeExpandedMaps = expandedMaps;
}
return nextTick();
},
setAllExpandNode(expanded) {
const expandedMaps = {};
const childrenField = computeChildrenField.value;
if (expanded) {
XEUtils.eachTree(reactData.treeList, (node) => {
const childList = XEUtils.get(node, childrenField);
const hasChild = childList && childList.length;
if (hasChild) {
const nodeid = getNodeId(node);
expandedMaps[nodeid] = true;
}
}, { children: childrenField });
}
reactData.treeExpandedMaps = expandedMaps;
return nextTick();
},
reloadExpandNode(node) {
const { lazy } = props;
if (lazy) {
treeMethods.clearExpandLoaded(node);
return handleAsyncTreeExpandChilds(node);
}
return nextTick();
},
clearExpandLoaded(node) {
const { lazy } = props;
const { nodeMaps } = reactData;
if (lazy) {
const nodeItem = nodeMaps[getNodeId(node)];
if (nodeItem) {
nodeItem.treeLoaded = false;
}
}
return nextTick();
},
/**
* 用于树结构,给行数据加载子节点
*/
loadChildrenNode(node, childRecords) {
const { lazy, transform } = props;
const { nodeMaps } = reactData;
if (!lazy) {
return Promise.resolve([]);
}
const childrenField = computeChildrenField.value;
const parentNodeItem = nodeMaps[getNodeId(node)];
const parentLevel = parentNodeItem ? parentNodeItem.level : 0;
const parentNodes = parentNodeItem ? parentNodeItem.nodes : [];
return createNode(childRecords).then((nodeList) => {
XEUtils.eachTree(nodeList, (childRow, index, items, path, parent, nodes) => {
const itemNodeId = getNodeId(childRow);
nodeMaps[itemNodeId] = {
item: node,
itemIndex: -1,
items,
parent: parent || parentNodeItem.item,
nodes: parentNodes.concat(nodes),
level: parentLevel + nodes.length,
lineCount: 0,
treeLoaded: false
};
}, { children: childrenField });
node[childrenField] = nodeList;
if (transform) {
node[childrenField] = nodeList;
}
updateNodeLine(node);
return nodeList;
});
},
isExpandByNode,
isCheckedByRadioNodeId,
isCheckedByRadioNode,
isCheckedByCheckboxNodeId,
isIndeterminateByCheckboxNode,
isCheckedByCheckboxNode,
getCheckboxIndeterminateNodes() {
const { treeList, indeterminateCheckboxMaps } = reactData;
const indeterminateNodes = [];
XEUtils.eachTree(treeList, (node) => {
if (indeterminateCheckboxMaps[getNodeId(node)]) {
indeterminateNodes.push(node);
}
});
return indeterminateNodes;
}
};
const cacheNodeMap = () => {
const { treeList } = reactData;
const valueField = computeValueField.value;
const childrenField = computeChildrenField.value;
const keyMaps = {};
XEUtils.eachTree(treeList, (item, itemIndex, items, path, parent, nodes) => {
let nodeid = getNodeId(item);
if (!nodeid) {
nodeid = getNodeUniqueId();
XEUtils.set(item, valueField, nodeid);
}
keyMaps[nodeid] = {
item,
itemIndex,
items,
parent,
nodes,
level: nodes.length,
lineCount: 0,
treeLoaded: false
};
}, { children: childrenField });
reactData.nodeMaps = keyMaps;
};
const updateData = (list) => {
const { expandAll, transform } = props;
const { initialized } = internalData;
const keyField = computeKeyField.value;
const parentField = computeParentField.value;
const childrenField = computeChildrenField.value;
if (transform) {
reactData.treeList = XEUtils.toArrayTree(list, { key: keyField, parentKey: parentField, mapChildren: childrenField });
}
else {
reactData.treeList = list ? list.slice(0) : [];
}
cacheNodeMap();
if (expandAll && !initialized) {
if (list && list.length) {
internalData.initialized = true;
$xeTree.setAllExpandNode(true);
}
}
};
const handleCountLine = (item, isRoot, nodeItem) => {
const { treeExpandedMaps } = reactData;
const childrenField = computeChildrenField.value;
const nodeid = getNodeId(item);
nodeItem.lineCount++;
if (treeExpandedMaps[nodeid]) {
XEUtils.arrayEach(item[childrenField], (childItem, childIndex, childList) => {
if (!isRoot || childIndex < childList.length - 1) {
handleCountLine(childItem, false, nodeItem);
}
});
}
};
const updateNodeLine = (node) => {
const { nodeMaps } = reactData;
if (node) {
const nodeid = getNodeId(node);
const nodeItem = nodeMaps[nodeid];
if (nodeItem) {
XEUtils.lastArrayEach(nodeItem.nodes, childItem => {
const nodeid = getNodeId(childItem);
const nodeItem = nodeMaps[nodeid];
if (nodeItem) {
nodeItem.lineCount = 0;
handleCountLine(childItem, true, nodeItem);
}
});
}
}
};
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 handleAsyncTreeExpandChilds = (node) => {
const checkboxOpts = computeCheckboxOpts.value;
const { loadMethod } = props;
const { checkStrictly } = checkboxOpts;
return new Promise(resolve => {
if (loadMethod) {
const tempExpandLazyLoadedMaps = Object.assign({}, reactData.treeExpandLazyLoadedMaps);
const { nodeMaps } = reactData;
const nodeid = getNodeId(node);
const nodeItem = nodeMaps[nodeid];
tempExpandLazyLoadedMaps[nodeid] = true;
reactData.treeExpandLazyLoadedMaps = tempExpandLazyLoadedMaps;
Promise.resolve(loadMethod({ $tree: $xeTree, node })).then((childRecords) => {
const { treeExpandLazyLoadedMaps } = reactData;
nodeItem.treeLoaded = true;
if (treeExpandLazyLoadedMaps[nodeid]) {
treeExpandLazyLoadedMaps[nodeid] = false;
}
if (!XEUtils.isArray(childRecords)) {
childRecords = [];
}
if (childRecords) {
return treeMethods.loadChildrenNode(node, childRecords).then(childRows => {
const tempExpandedMaps = Object.assign({}, reactData.treeExpandedMaps);
if (childRows.length && !tempExpandedMaps[nodeid]) {
tempExpandedMaps[nodeid] = true;
}
reactData.treeExpandedMaps = tempExpandedMaps;
// 如果当前节点已选中,则展开后子节点也被选中
if (!checkStrictly && treeMethods.isCheckedByCheckboxNodeId(nodeid)) {
handleCheckedCheckboxNode(childRows.map((item) => getNodeId(item)), true);
}
updateNodeLine(node);
dispatchEvent('load-success', { node, data: childRecords }, new Event('load-success'));
return nextTick();
});
}
else {
updateNodeLine(node);
dispatchEvent('load-success', { node, data: childRecords }, new Event('load-success'));
}
}).catch((e) => {
const { treeExpandLazyLoadedMaps } = reactData;
nodeItem.treeLoaded = false;
if (treeExpandLazyLoadedMaps[nodeid]) {
treeExpandLazyLoadedMaps[nodeid] = false;
}
updateNodeLine(node);
dispatchEvent('load-error', { node, data: e }, new Event('load-error'));
}).finally(() => {
return nextTick();
});
}
else {
resolve();
}
});
};
/**
* 展开与收起树节点
* @param nodeList
* @param expanded
* @returns
*/
const handleBaseTreeExpand = (nodeList, expanded) => {
const { lazy, accordion, toggleMethod } = props;
const { nodeMaps, treeExpandLazyLoadedMaps } = reactData;
const tempExpandedMaps = Object.assign({}, reactData.treeExpandedMaps);
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 (tempExpandedMaps[itemNodeId]) {
delete tempExpandedMaps[itemNodeId];
}
});
}
}
const expandNodes = [];
if (expanded) {
validNodes.forEach((item) => {
const itemNodeId = getNodeId(item);
if (!tempExpandedMaps[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) {
tempExpandedMaps[itemNodeId] = true;
expandNodes.push(item);
}
}
}
});
}
else {
validNodes.forEach(item => {
const itemNodeId = getNodeId(item);
if (tempExpandedMaps[itemNodeId]) {
delete tempExpandedMaps[itemNodeId];
expandNodes.push(item);
}
});
}
reactData.treeExpandedMaps = tempExpandedMaps;
expandNodes.forEach(updateNodeLine);
return Promise.all(result);
};
const toggleExpandEvent = (evnt, node) => {
const { lazy } = props;
const { treeExpandedMaps, treeExpandLazyLoadedMaps } = reactData;
const nodeid = getNodeId(node);
const expanded = !treeExpandedMaps[nodeid];
evnt.stopPropagation();
if (!lazy || !treeExpandLazyLoadedMaps[nodeid]) {
handleBaseTreeExpand([node], expanded);
}
};
const handleNodeCheckboxStatus = (node, selectKeyMaps, indeterminateMaps) => {
const childrenField = computeChildrenField.value;
const childList = XEUtils.get(node, childrenField);
const nodeid = getNodeId(node);
if (childList && childList.length) {
let checkSome = false;
let checkSize = 0;
childList.forEach(childNode => {
const childNodeid = getNodeId(childNode);
const isChecked = selectKeyMaps[childNodeid];
if (isChecked || indeterminateMaps[childNodeid]) {
if (isChecked) {
checkSize++;
}
checkSome = true;
}
});
const checkAll = checkSize === childList.length;
if (checkAll) {
if (!selectKeyMaps[nodeid]) {
selectKeyMaps[nodeid] = true;
}
if (indeterminateMaps[nodeid]) {
delete indeterminateMaps[nodeid];
}
}
else {
if (selectKeyMaps[nodeid]) {
delete selectKeyMaps[nodeid];
}
indeterminateMaps[nodeid] = checkSome;
}
}
else {
if (indeterminateMaps[nodeid]) {
delete indeterminateMaps[nodeid];
}
}
};
const updateCheckboxStatus = () => {
const { treeList } = reactData;
const childrenField = computeChildrenField.value;
const checkboxOpts = computeCheckboxOpts.value;
const { checkStrictly } = checkboxOpts;
if (!checkStrictly) {
const selectKeyMaps = Object.assign({}, reactData.selectCheckboxMaps);
const indeterminateMaps = {};
XEUtils.eachTree(treeList, (node, index, items, path, parent, nodes) => {
const childList = XEUtils.get(node, childrenField);
if (!childList || !childList.length) {
handleNodeCheckboxStatus(node, selectKeyMaps, indeterminateMaps);
}
if (index === items.length - 1) {
for (let len = nodes.length - 2; len >= 0; len--) {
const parentItem = nodes[len];
handleNodeCheckboxStatus(parentItem, selectKeyMaps, indeterminateMaps);
}
}
});
reactData.selectCheckboxMaps = selectKeyMaps;
reactData.indeterminateCheckboxMaps = indeterminateMaps;
}
};
const changeCheckboxEvent = (evnt, node) => {
evnt.preventDefault();
evnt.stopPropagation();
const checkboxOpts = computeCheckboxOpts.value;
const { checkStrictly, checkMethod } = checkboxOpts;
let isDisabled = !!checkMethod;
if (checkMethod) {
isDisabled = !checkMethod({ node });
}
if (isDisabled) {
return;
}
const selectKeyMaps = Object.assign({}, reactData.selectCheckboxMaps);
const childrenField = computeChildrenField.value;
const nodeid = getNodeId(node);
let isChecked = false;
if (selectKeyMaps[nodeid]) {
delete selectKeyMaps[nodeid];
}
else {
isChecked = true;
selectKeyMaps[nodeid] = isChecked;
}
if (!checkStrictly) {
XEUtils.eachTree(XEUtils.get(node, childrenField), (childNode) => {
const childNodeid = getNodeId(childNode);
if (isChecked) {
if (!selectKeyMaps[childNodeid]) {
selectKeyMaps[childNodeid] = true;
}
}
else {
if (selectKeyMaps[childNodeid]) {
delete selectKeyMaps[childNodeid];
}
}
}, { children: childrenField });
}
reactData.selectCheckboxMaps = selectKeyMaps;
updateCheckboxStatus();
const value = Object.keys(reactData.selectCheckboxMaps);
emitCheckboxMode(value);
dispatchEvent('checkbox-change', { node, value, checked: isChecked }, evnt);
};
const changeCurrentEvent = (evnt, node) => {
evnt.preventDefault();
const nodeOpts = computeNodeOpts.value;
const { currentMethod, trigger } = nodeOpts;
const childrenField = computeChildrenField.value;
const childList = XEUtils.get(node, childrenField);
const hasChild = childList && childList.length;
let isDisabled = !!currentMethod;
if (trigger === 'child') {
if (hasChild) {
return;
}
}
else if (trigger === 'parent') {
if (!hasChild) {
return;
}
}
if (currentMethod) {
isDisabled = !currentMethod({ node });
}
if (isDisabled) {
return;
}
const isChecked = true;
reactData.currentNode = node;
dispatchEvent('current-change', { node, checked: isChecked }, evnt);
};
const changeRadioEvent = (evnt, node) => {
evnt.preventDefault();
evnt.stopPropagation();
const radioOpts = computeRadioOpts.value;
const { checkMethod } = radioOpts;
let isDisabled = !!checkMethod;
if (checkMethod) {
isDisabled = !checkMethod({ node });
}
if (isDisabled) {
return;
}
const isChecked = true;
const value = getNodeId(node);
reactData.selectRadioKey = value;
emitRadioMode(value);
dispatchEvent('radio-change', { node, value, checked: isChecked }, evnt);
};
const treePrivateMethods = {};
Object.assign($xeTree, treeMethods, treePrivateMethods);
const renderRadio = (node, nodeid, isChecked) => {
const { showRadio } = props;
const radioOpts = computeRadioOpts.value;
const { showIcon, checkMethod, visibleMethod } = radioOpts;
const isVisible = !visibleMethod || visibleMethod({ node });
let isDisabled = !!checkMethod;
if (showRadio && showIcon && isVisible) {
if (checkMethod) {
isDisabled = !checkMethod({ node });
}
return h('div', {
class: ['vxe-tree--radio-option', {
'is--checked': isChecked,
'is--disabled': isDisabled
}],
onClick: (evnt) => {
if (!isDisabled) {
changeRadioEvent(evnt, node);
}
}
}, [
h('span', {
class: ['vxe-radio--icon', isChecked ? getIcon().RADIO_CHECKED : getIcon().RADIO_UNCHECKED]
})
]);
}
return createCommentVNode();
};
const renderCheckbox = (node, nodeid, isChecked) => {
const { showCheckbox } = props;
const checkboxOpts = computeCheckboxOpts.value;
const { showIcon, checkMethod, visibleMethod } = checkboxOpts;
const isIndeterminate = isIndeterminateByCheckboxNodeid(nodeid);
const isVisible = !visibleMethod || visibleMethod({ node });
let isDisabled = !!checkMethod;
if (showCheckbox && showIcon && isVisible) {
if (checkMethod) {
isDisabled = !checkMethod({ node });
}
return h('div', {
class: ['vxe-tree--checkbox-option', {
'is--checked': isChecked,
'is--indeterminate': isIndeterminate,
'is--disabled': isDisabled
}],
onClick: (evnt) => {
if (!isDisabled) {
changeCheckboxEvent(evnt, node);
}
}
}, [
h('span', {
class: ['vxe-checkbox--icon', isIndeterminate ? getIcon().CHECKBOX_INDETERMINATE : (isChecked ? getIcon().CHECKBOX_CHECKED : getIcon().CHECKBOX_UNCHECKED)]
})
]);
}
return createCommentVNode();
};
const renderNode = (node) => {
const { lazy, showRadio, showCheckbox, showLine, indent, iconOpen, iconClose, iconLoaded, showIcon } = props;
const { nodeMaps, treeExpandedMaps, currentNode, selectRadioKey, treeExpandLazyLoadedMaps } = reactData;
const childrenField = computeChildrenField.value;
const titleField = computeTitleField.value;
const hasChildField = computeHasChildField.value;
const childList = XEUtils.get(node, childrenField);
const hasChild = childList && childList.length;
const iconSlot = slots.icon;
const titleSlot = slots.title;
const extraSlot = slots.extra;
const nodeid = getNodeId(node);
const isExpand = treeExpandedMaps[nodeid];
const nodeItem = nodeMaps[nodeid];
const nodeValue = XEUtils.get(node, titleField);
const childVns = [];
if (hasChild && treeExpandedMaps[nodeid]) {
if (showLine) {
childVns.push(h('div', {
key: 'line',
class: 'vxe-tree--node-child-line',
style: {
height: `calc(${nodeItem.lineCount} * var(--vxe-ui-tree-node-height) - var(--vxe-ui-tree-node-height) / 2)`,
left: `${(nodeItem.level + 1) * (indent || 1)}px`
}
}));
}
childList.forEach(childItem => {
childVns.push(renderNode(childItem));
});
}
let isRadioChecked = false;
if (showRadio) {
// eslint-disable-next-line eqeqeq
isRadioChecked = nodeid == selectRadioKey;
}
let isCheckboxChecked = false;
if (showCheckbox) {
isCheckboxChecked = isCheckedByCheckboxNodeId(nodeid);
}
let hasLazyChilds = false;
let isLazyLoading = false;
let isLazyLoaded = false;
if (lazy) {
isLazyLoading = !!treeExpandLazyLoadedMaps[nodeid];
hasLazyChilds = node[hasChildField];
isLazyLoaded = !!nodeItem.treeLoaded;
}
return h('div', {
class: ['vxe-tree--node-wrapper', `node--level-${nodeItem.level}`],
nodeid
}, [
h('div', {
class: ['vxe-tree--node-item', {
'is--current': currentNode && nodeid === getNodeId(currentNode),
'is-radio--checked': isRadioChecked,
'is-checkbox--checked': isCheckboxChecked
}],
style: {
paddingLeft: `${(nodeItem.level - 1) * (indent || 1)}px`
},
onClick(evnt) {
handleNodeClickEvent(evnt, node);
},
onDblclick(evnt) {
handleNodeDblclickEvent(evnt, node);
}
}, [
showIcon || showLine
? h('div', {
class: 'vxe-tree--node-item-switcher'
}, showIcon && (lazy ? (isLazyLoaded ? hasChild : hasLazyChilds) : hasChild)
? [
h('div', {
class: 'vxe-tree--node-item-icon',
onClick(evnt) {
toggleExpandEvent(evnt, node);
}
}, iconSlot
? iconSlot({ node, isExpand })
: [
h('i', {
class: isLazyLoading ? (iconLoaded || getIcon().TREE_NODE_LOADED) : (isExpand ? (iconOpen || getIcon().TREE_NODE_OPEN) : (iconClose || getIcon().TREE_NODE_CLOSE))
})
])
]
: [])
: createCommentVNode(),
renderRadio(node, nodeid, isRadioChecked),
renderCheckbox(node, nodeid, isCheckboxChecked),
h('div', {
class: 'vxe-tree--node-item-inner'
}, [
h('div', {
class: 'vxe-tree--node-item-title'
}, titleSlot ? getSlotVNs(titleSlot({ node, isExpand })) : `${nodeValue}`),
extraSlot
? h('div', {
class: 'vxe-tree--node-item-extra'
}, getSlotVNs(extraSlot({ node, isExpand })))
: createCommentVNode()
])
]),
hasChild && treeExpandedMaps[nodeid]
? h('div', {
class: 'vxe-tree--node-child-wrapper'
}, childVns)
: createCommentVNode()
]);
};
const renderNodeList = () => {
const { treeList } = reactData;
return h('div', {
class: 'vxe-tree--node-list-wrapper'
}, treeList.map(node => renderNode(node)));
};
const renderVN = () => {
const { loading, trigger, showLine } = props;
const vSize = computeSize.value;
const radioOpts = computeRadioOpts.value;
const checkboxOpts = computeCheckboxOpts.value;
const treeStyle = computeTreeStyle.value;
const loadingOpts = computeLoadingOpts.value;
const isRowHover = computeIsRowHover.value;
const loadingSlot = slots.loading;
return h('div', {
ref: refElem,
class: ['vxe-tree', {
[`size--${vSize}`]: vSize,
'show--line': showLine,
'checkbox--highlight': checkboxOpts.highlight,
'radio--highlight': radioOpts.highlight,
'node--hover': isRowHover,
'node--trigger': trigger === 'node',
'is--loading': loading
}],
style: treeStyle
}, [
renderNodeList(),
/**
* 加载中
*/
h(VxeLoadingComponent, {
class: 'vxe-tree--loading',
modelValue: loading,
icon: loadingOpts.icon,
text: loadingOpts.text
}, loadingSlot
? {
default: () => loadingSlot({ $tree: $xeTree })
}
: {})
]);
};
const dataFlag = ref(0);
watch(() => props.data ? props.data.length : 0, () => {
dataFlag.value++;
});
watch(() => props.data, () => {
dataFlag.value++;
});
watch(dataFlag, () => {
updateData(props.data || []);
});
watch(() => props.checkNodeKey, (val) => {
reactData.selectRadioKey = val;
});
const checkboxFlag = ref(0);
watch(() => props.checkNodeKeys ? props.checkNodeKeys.length : 0, () => {
checkboxFlag.value++;
});
watch(() => props.checkNodeKeys, () => {
checkboxFlag.value++;
});
watch(checkboxFlag, () => {
updateCheckboxChecked(props.checkNodeKeys || []);
});
onUnmounted(() => {
reactData.treeList = [];
reactData.treeExpandedMaps = {};
reactData.nodeMaps = {};
});
updateData(props.data || []);
updateCheckboxChecked(props.checkNodeKeys || []);
$xeTree.renderVN = renderVN;
return $xeTree;
},
render() {
return this.renderVN();
}
});