refactor: tree
parent
99f034deac
commit
ae36627763
|
@ -1,251 +0,0 @@
|
||||||
import type { VNode } from 'vue';
|
|
||||||
import { defineComponent, inject } from 'vue';
|
|
||||||
import omit from 'omit.js';
|
|
||||||
import debounce from 'lodash-es/debounce';
|
|
||||||
import FolderOpenOutlined from '@ant-design/icons-vue/FolderOpenOutlined';
|
|
||||||
import FolderOutlined from '@ant-design/icons-vue/FolderOutlined';
|
|
||||||
import FileOutlined from '@ant-design/icons-vue/FileOutlined';
|
|
||||||
import PropTypes from '../_util/vue-types';
|
|
||||||
import classNames from '../_util/classNames';
|
|
||||||
import { conductExpandParent, convertTreeToEntities } from '../vc-tree/src/util';
|
|
||||||
import type { CheckEvent, ExpendEvent, SelectEvent } from './Tree';
|
|
||||||
import Tree, { TreeProps } from './Tree';
|
|
||||||
import {
|
|
||||||
calcRangeKeys,
|
|
||||||
getFullKeyList,
|
|
||||||
convertDirectoryKeysToNodes,
|
|
||||||
getFullKeyListByTreeData,
|
|
||||||
} from './util';
|
|
||||||
import BaseMixin from '../_util/BaseMixin';
|
|
||||||
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
|
|
||||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
|
||||||
|
|
||||||
// export type ExpandAction = false | 'click' | 'dblclick'; export interface
|
|
||||||
// DirectoryTreeProps extends TreeProps { expandAction?: ExpandAction; }
|
|
||||||
// export interface DirectoryTreeState { expandedKeys?: string[];
|
|
||||||
// selectedKeys?: string[]; }
|
|
||||||
|
|
||||||
export interface DirectoryTreeState {
|
|
||||||
_expandedKeys?: (string | number)[];
|
|
||||||
_selectedKeys?: (string | number)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) {
|
|
||||||
const { isLeaf, expanded } = props;
|
|
||||||
if (isLeaf) {
|
|
||||||
return <FileOutlined />;
|
|
||||||
}
|
|
||||||
return expanded ? <FolderOpenOutlined /> : <FolderOutlined />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ADirectoryTree',
|
|
||||||
mixins: [BaseMixin],
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: initDefaultProps(
|
|
||||||
{
|
|
||||||
...TreeProps(),
|
|
||||||
expandAction: PropTypes.oneOf([false, 'click', 'doubleclick', 'dblclick']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
showIcon: true,
|
|
||||||
expandAction: 'click',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
children: null,
|
|
||||||
onDebounceExpand: null,
|
|
||||||
tree: null,
|
|
||||||
lastSelectedKey: '',
|
|
||||||
cachedSelectedKeys: [],
|
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
const props = getOptionProps(this);
|
|
||||||
const { defaultExpandAll, defaultExpandParent, expandedKeys, defaultExpandedKeys } = props;
|
|
||||||
const children = getSlot(this);
|
|
||||||
const { keyEntities } = convertTreeToEntities(children);
|
|
||||||
const state: DirectoryTreeState = {};
|
|
||||||
// Selected keys
|
|
||||||
state._selectedKeys = props.selectedKeys || props.defaultSelectedKeys || [];
|
|
||||||
|
|
||||||
// Expanded keys
|
|
||||||
if (defaultExpandAll) {
|
|
||||||
if (props.treeData) {
|
|
||||||
state._expandedKeys = getFullKeyListByTreeData(props.treeData, props.replaceFields);
|
|
||||||
} else {
|
|
||||||
state._expandedKeys = getFullKeyList(children);
|
|
||||||
}
|
|
||||||
} else if (defaultExpandParent) {
|
|
||||||
state._expandedKeys = conductExpandParent(expandedKeys || defaultExpandedKeys, keyEntities);
|
|
||||||
} else {
|
|
||||||
state._expandedKeys = expandedKeys || defaultExpandedKeys;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
_selectedKeys: [],
|
|
||||||
_expandedKeys: [],
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
expandedKeys(val) {
|
|
||||||
this.setState({ _expandedKeys: val });
|
|
||||||
},
|
|
||||||
selectedKeys(val) {
|
|
||||||
this.setState({ _selectedKeys: val });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.onDebounceExpand = debounce(this.expandFolderNode, 200, { leading: true });
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleExpand(expandedKeys: (string | number)[], info: ExpendEvent) {
|
|
||||||
this.setUncontrolledState({ _expandedKeys: expandedKeys });
|
|
||||||
this.$emit('update:expandedKeys', expandedKeys);
|
|
||||||
this.$emit('expand', expandedKeys, info);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleClick(event: MouseEvent, node: VNode) {
|
|
||||||
const { expandAction } = this.$props;
|
|
||||||
|
|
||||||
// Expand the tree
|
|
||||||
if (expandAction === 'click') {
|
|
||||||
this.onDebounceExpand(event, node);
|
|
||||||
}
|
|
||||||
this.$emit('click', event, node);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDoubleClick(event: MouseEvent, node: VNode) {
|
|
||||||
const { expandAction } = this.$props;
|
|
||||||
|
|
||||||
// Expand the tree
|
|
||||||
if (expandAction === 'dblclick' || expandAction === 'doubleclick') {
|
|
||||||
this.onDebounceExpand(event, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('doubleclick', event, node);
|
|
||||||
this.$emit('dblclick', event, node);
|
|
||||||
},
|
|
||||||
|
|
||||||
hanldeSelect(keys: (string | number)[], event: SelectEvent) {
|
|
||||||
const { multiple } = this.$props;
|
|
||||||
const children = this.children || [];
|
|
||||||
const { _expandedKeys: expandedKeys = [] } = this.$data;
|
|
||||||
const { node, nativeEvent } = event;
|
|
||||||
const { eventKey = '' } = node;
|
|
||||||
|
|
||||||
const newState: DirectoryTreeState = {};
|
|
||||||
|
|
||||||
// We need wrap this event since some value is not same
|
|
||||||
const newEvent = {
|
|
||||||
...event,
|
|
||||||
selected: true, // Directory selected always true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Windows / Mac single pick
|
|
||||||
const ctrlPick = nativeEvent.ctrlKey || nativeEvent.metaKey;
|
|
||||||
const shiftPick = nativeEvent.shiftKey;
|
|
||||||
|
|
||||||
// Generate new selected keys
|
|
||||||
let newSelectedKeys: (string | number)[];
|
|
||||||
if (multiple && ctrlPick) {
|
|
||||||
// Control click
|
|
||||||
newSelectedKeys = keys;
|
|
||||||
this.lastSelectedKey = eventKey;
|
|
||||||
this.cachedSelectedKeys = newSelectedKeys;
|
|
||||||
newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys);
|
|
||||||
} else if (multiple && shiftPick) {
|
|
||||||
// Shift click
|
|
||||||
newSelectedKeys = Array.from(
|
|
||||||
new Set([
|
|
||||||
...(this.cachedSelectedKeys || []),
|
|
||||||
...calcRangeKeys(children, expandedKeys, eventKey, this.lastSelectedKey),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys);
|
|
||||||
} else {
|
|
||||||
// Single click
|
|
||||||
newSelectedKeys = [eventKey];
|
|
||||||
this.lastSelectedKey = eventKey;
|
|
||||||
this.cachedSelectedKeys = newSelectedKeys;
|
|
||||||
newEvent.selectedNodes = [event.node];
|
|
||||||
}
|
|
||||||
newState._selectedKeys = newSelectedKeys;
|
|
||||||
|
|
||||||
this.$emit('update:selectedKeys', newSelectedKeys);
|
|
||||||
this.$emit('select', newSelectedKeys, newEvent);
|
|
||||||
|
|
||||||
this.setUncontrolledState(newState);
|
|
||||||
},
|
|
||||||
setTreeRef(node: VNode) {
|
|
||||||
this.tree = node;
|
|
||||||
},
|
|
||||||
|
|
||||||
expandFolderNode(event: MouseEvent, node: { isLeaf: boolean } & VNode) {
|
|
||||||
const { isLeaf } = node;
|
|
||||||
|
|
||||||
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.tree.tree) {
|
|
||||||
// Get internal vc-tree
|
|
||||||
const internalTree = this.tree.tree;
|
|
||||||
|
|
||||||
// Call internal rc-tree expand function
|
|
||||||
// https://github.com/ant-design/ant-design/issues/12567
|
|
||||||
internalTree.onNodeExpand(event, node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setUncontrolledState(state: unknown) {
|
|
||||||
const newState = omit(
|
|
||||||
state,
|
|
||||||
Object.keys(getOptionProps(this)).map(p => `_${p}`),
|
|
||||||
);
|
|
||||||
if (Object.keys(newState).length) {
|
|
||||||
this.setState(newState);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleCheck(checkedObj: (string | number)[], eventObj: CheckEvent) {
|
|
||||||
this.$emit('update:checkedKeys', checkedObj);
|
|
||||||
this.$emit('check', checkedObj, eventObj);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.children = getSlot(this);
|
|
||||||
const { prefixCls: customizePrefixCls, ...props } = getOptionProps(this);
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('tree', customizePrefixCls);
|
|
||||||
const { _expandedKeys: expandedKeys, _selectedKeys: selectedKeys } = this.$data;
|
|
||||||
const { class: className, ...restAttrs } = this.$attrs;
|
|
||||||
const connectClassName = classNames(`${prefixCls}-directory`, className);
|
|
||||||
const treeProps = {
|
|
||||||
icon: getIcon,
|
|
||||||
...restAttrs,
|
|
||||||
...omit(props, ['onUpdate:selectedKeys', 'onUpdate:checkedKeys', 'onUpdate:expandedKeys']),
|
|
||||||
prefixCls,
|
|
||||||
expandedKeys,
|
|
||||||
selectedKeys,
|
|
||||||
switcherIcon: getComponent(this, 'switcherIcon'),
|
|
||||||
ref: this.setTreeRef,
|
|
||||||
class: connectClassName,
|
|
||||||
onSelect: this.hanldeSelect,
|
|
||||||
onClick: this.handleClick,
|
|
||||||
onDblclick: this.handleDoubleClick,
|
|
||||||
onExpand: this.handleExpand,
|
|
||||||
onCheck: this.handleCheck,
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Tree {...treeProps} v-slots={omit(this.$slots, ['default'])}>
|
|
||||||
{this.children}
|
|
||||||
</Tree>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,289 +0,0 @@
|
||||||
import type { VNode, PropType, CSSProperties } from 'vue';
|
|
||||||
import { defineComponent, inject } from 'vue';
|
|
||||||
import classNames from '../_util/classNames';
|
|
||||||
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
|
|
||||||
import FileOutlined from '@ant-design/icons-vue/FileOutlined';
|
|
||||||
import CaretDownFilled from '@ant-design/icons-vue/CaretDownFilled';
|
|
||||||
import MinusSquareOutlined from '@ant-design/icons-vue/MinusSquareOutlined';
|
|
||||||
import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined';
|
|
||||||
import VcTree, { TreeNode } from '../vc-tree';
|
|
||||||
import animation from '../_util/openAnimation';
|
|
||||||
import PropTypes from '../_util/vue-types';
|
|
||||||
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
|
|
||||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
|
||||||
import { cloneElement } from '../_util/vnode';
|
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
|
||||||
|
|
||||||
export interface TreeDataItem {
|
|
||||||
key?: string | number;
|
|
||||||
title?: string;
|
|
||||||
isLeaf?: boolean;
|
|
||||||
selectable?: boolean;
|
|
||||||
children?: TreeDataItem[];
|
|
||||||
disableCheckbox?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
class?: string;
|
|
||||||
style?: CSSProperties;
|
|
||||||
checkable?: boolean;
|
|
||||||
icon?: VNode;
|
|
||||||
|
|
||||||
slots?: Record<string, string>;
|
|
||||||
switcherIcon?: VNode;
|
|
||||||
// support custom field
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DefaultEvent {
|
|
||||||
nativeEvent: MouseEvent;
|
|
||||||
node: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CheckEvent extends DefaultEvent {
|
|
||||||
checked: boolean;
|
|
||||||
checkedNodes: Array<Record<string, any>>;
|
|
||||||
checkedNodesPositions: { node: Record<string, any>; pos: string | number }[];
|
|
||||||
event: string;
|
|
||||||
halfCheckedKeys: (string | number)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExpendEvent extends DefaultEvent {
|
|
||||||
expanded: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectEvent extends DefaultEvent {
|
|
||||||
event: string;
|
|
||||||
selected: boolean;
|
|
||||||
selectedNodes: Array<Record<string, any>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TreeDragEvent {
|
|
||||||
event: DragEvent;
|
|
||||||
expandedKeys: (string | number)[];
|
|
||||||
node: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DropEvent {
|
|
||||||
dragNode: Record<string, any>;
|
|
||||||
dragNodesKeys: (string | number)[];
|
|
||||||
dropPosition: number;
|
|
||||||
dropToGap: boolean;
|
|
||||||
event: DragEvent;
|
|
||||||
node: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TreeProps() {
|
|
||||||
return {
|
|
||||||
showLine: PropTypes.looseBool,
|
|
||||||
/** 是否支持多选 */
|
|
||||||
multiple: PropTypes.looseBool,
|
|
||||||
/** 是否自动展开父节点 */
|
|
||||||
autoExpandParent: PropTypes.looseBool,
|
|
||||||
/** checkable状态下节点选择完全受控(父子节点选中状态不再关联)*/
|
|
||||||
checkStrictly: PropTypes.looseBool,
|
|
||||||
/** 是否支持选中 */
|
|
||||||
checkable: PropTypes.looseBool,
|
|
||||||
/** 是否禁用树 */
|
|
||||||
disabled: PropTypes.looseBool,
|
|
||||||
/** 默认展开所有树节点 */
|
|
||||||
defaultExpandAll: PropTypes.looseBool,
|
|
||||||
/** 默认展开对应树节点 */
|
|
||||||
defaultExpandParent: PropTypes.looseBool,
|
|
||||||
/** 默认展开指定的树节点 */
|
|
||||||
defaultExpandedKeys: PropTypes.arrayOf(
|
|
||||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
||||||
),
|
|
||||||
/** (受控)展开指定的树节点 */
|
|
||||||
expandedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
||||||
/** (受控)选中复选框的树节点 */
|
|
||||||
checkedKeys: PropTypes.oneOfType([
|
|
||||||
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
||||||
PropTypes.shape({
|
|
||||||
checked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
||||||
halfChecked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
||||||
}).loose,
|
|
||||||
]),
|
|
||||||
/** 默认选中复选框的树节点 */
|
|
||||||
defaultCheckedKeys: PropTypes.arrayOf(
|
|
||||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
||||||
),
|
|
||||||
/** (受控)设置选中的树节点 */
|
|
||||||
selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
||||||
/** 默认选中的树节点 */
|
|
||||||
defaultSelectedKeys: PropTypes.arrayOf(
|
|
||||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
||||||
),
|
|
||||||
selectable: PropTypes.looseBool,
|
|
||||||
|
|
||||||
/** filter some AntTreeNodes as you need. it should return true */
|
|
||||||
filterAntTreeNode: PropTypes.func,
|
|
||||||
/** 异步加载数据 */
|
|
||||||
loadData: PropTypes.func,
|
|
||||||
loadedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
||||||
// onLoaded: (loadedKeys: string[], info: { event: 'load', node: AntTreeNode; }) => void,
|
|
||||||
/** 响应右键点击 */
|
|
||||||
// onRightClick: (options: AntTreeNodeMouseEvent) => void,
|
|
||||||
/** 设置节点可拖拽(IE>8)*/
|
|
||||||
draggable: PropTypes.looseBool,
|
|
||||||
// /** 开始拖拽时调用 */
|
|
||||||
// onDragStart: (options: AntTreeNodeMouseEvent) => void,
|
|
||||||
// /** dragenter 触发时调用 */
|
|
||||||
// onDragEnter: (options: AntTreeNodeMouseEvent) => void,
|
|
||||||
// /** dragover 触发时调用 */
|
|
||||||
// onDragOver: (options: AntTreeNodeMouseEvent) => void,
|
|
||||||
// /** dragleave 触发时调用 */
|
|
||||||
// onDragLeave: (options: AntTreeNodeMouseEvent) => void,
|
|
||||||
// /** drop 触发时调用 */
|
|
||||||
// onDrop: (options: AntTreeNodeMouseEvent) => void,
|
|
||||||
showIcon: PropTypes.looseBool,
|
|
||||||
icon: PropTypes.func,
|
|
||||||
switcherIcon: PropTypes.any,
|
|
||||||
prefixCls: PropTypes.string,
|
|
||||||
filterTreeNode: PropTypes.func,
|
|
||||||
openAnimation: PropTypes.any,
|
|
||||||
treeData: {
|
|
||||||
type: Array as PropType<TreeDataItem[]>,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @default{title,key,children}
|
|
||||||
* 替换treeNode中 title,key,children字段为treeData中对应的字段
|
|
||||||
*/
|
|
||||||
replaceFields: PropTypes.object,
|
|
||||||
blockNode: PropTypes.looseBool,
|
|
||||||
/** 展开/收起节点时触发 */
|
|
||||||
onExpand: PropTypes.func,
|
|
||||||
/** 点击复选框触发 */
|
|
||||||
onCheck: PropTypes.func,
|
|
||||||
/** 点击树节点触发 */
|
|
||||||
onSelect: PropTypes.func,
|
|
||||||
/** 单击树节点触发 */
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
/** 双击树节点触发 */
|
|
||||||
onDoubleclick: PropTypes.func,
|
|
||||||
onDblclick: PropTypes.func,
|
|
||||||
'onUpdate:selectedKeys': PropTypes.func,
|
|
||||||
'onUpdate:checkedKeys': PropTypes.func,
|
|
||||||
'onUpdate:expandedKeys': PropTypes.func,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export { TreeProps };
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ATree',
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: initDefaultProps(TreeProps(), {
|
|
||||||
checkable: false,
|
|
||||||
showIcon: false,
|
|
||||||
openAnimation: {
|
|
||||||
...animation,
|
|
||||||
appear: null,
|
|
||||||
},
|
|
||||||
blockNode: false,
|
|
||||||
}),
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
tree: null,
|
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
TreeNode,
|
|
||||||
methods: {
|
|
||||||
renderSwitcherIcon(prefixCls: string, switcherIcon: VNode, { isLeaf, loading, expanded }) {
|
|
||||||
const { showLine } = this.$props;
|
|
||||||
if (loading) {
|
|
||||||
return <LoadingOutlined class={`${prefixCls}-switcher-loading-icon`} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLeaf) {
|
|
||||||
return showLine ? <FileOutlined class={`${prefixCls}-switcher-line-icon`} /> : null;
|
|
||||||
}
|
|
||||||
const switcherCls = `${prefixCls}-switcher-icon`;
|
|
||||||
if (switcherIcon) {
|
|
||||||
return cloneElement(switcherIcon, {
|
|
||||||
class: switcherCls,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return showLine ? (
|
|
||||||
expanded ? (
|
|
||||||
<MinusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
|
|
||||||
) : (
|
|
||||||
<PlusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<CaretDownFilled class={switcherCls} />
|
|
||||||
);
|
|
||||||
},
|
|
||||||
updateTreeData(treeData: TreeDataItem[]) {
|
|
||||||
const { $slots } = this;
|
|
||||||
const defaultFields = { children: 'children', title: 'title', key: 'key' };
|
|
||||||
const replaceFields = { ...defaultFields, ...this.$props.replaceFields };
|
|
||||||
return treeData.map(item => {
|
|
||||||
const key = item[replaceFields.key];
|
|
||||||
const children = item[replaceFields.children];
|
|
||||||
const { slots = {}, class: cls, style, ...restProps } = item;
|
|
||||||
const treeNodeProps = {
|
|
||||||
...restProps,
|
|
||||||
icon: $slots[slots.icon] || restProps.icon,
|
|
||||||
switcherIcon: $slots[slots.switcherIcon] || restProps.switcherIcon,
|
|
||||||
title: $slots[slots.title] || $slots.title || restProps[replaceFields.title],
|
|
||||||
dataRef: item,
|
|
||||||
key,
|
|
||||||
class: cls,
|
|
||||||
style,
|
|
||||||
};
|
|
||||||
if (children) {
|
|
||||||
return { ...treeNodeProps, children: this.updateTreeData(children) };
|
|
||||||
}
|
|
||||||
return treeNodeProps;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setTreeRef(node: VNode) {
|
|
||||||
this.tree = node;
|
|
||||||
},
|
|
||||||
handleCheck(checkedObj: (number | string)[], eventObj: CheckEvent) {
|
|
||||||
this.$emit('update:checkedKeys', checkedObj);
|
|
||||||
this.$emit('check', checkedObj, eventObj);
|
|
||||||
},
|
|
||||||
handleExpand(expandedKeys: (number | string)[], eventObj: ExpendEvent) {
|
|
||||||
this.$emit('update:expandedKeys', expandedKeys);
|
|
||||||
this.$emit('expand', expandedKeys, eventObj);
|
|
||||||
},
|
|
||||||
handleSelect(selectedKeys: (number | string)[], eventObj: SelectEvent) {
|
|
||||||
this.$emit('update:selectedKeys', selectedKeys);
|
|
||||||
this.$emit('select', selectedKeys, eventObj);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const props = getOptionProps(this);
|
|
||||||
const { prefixCls: customizePrefixCls, showIcon, treeNodes, blockNode } = props;
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('tree', customizePrefixCls);
|
|
||||||
const switcherIcon = getComponent(this, 'switcherIcon');
|
|
||||||
const checkable = props.checkable;
|
|
||||||
let treeData = props.treeData || treeNodes;
|
|
||||||
if (treeData) {
|
|
||||||
treeData = this.updateTreeData(treeData);
|
|
||||||
}
|
|
||||||
const { class: className, ...restAttrs } = this.$attrs;
|
|
||||||
const vcTreeProps = {
|
|
||||||
...props,
|
|
||||||
prefixCls,
|
|
||||||
checkable: checkable ? <span class={`${prefixCls}-checkbox-inner`} /> : checkable,
|
|
||||||
children: getSlot(this),
|
|
||||||
switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps),
|
|
||||||
ref: this.setTreeRef,
|
|
||||||
...restAttrs,
|
|
||||||
class: classNames(className, {
|
|
||||||
[`${prefixCls}-icon-hide`]: !showIcon,
|
|
||||||
[`${prefixCls}-block-node`]: blockNode,
|
|
||||||
}),
|
|
||||||
onCheck: this.handleCheck,
|
|
||||||
onExpand: this.handleExpand,
|
|
||||||
onSelect: this.handleSelect,
|
|
||||||
} as Record<string, any>;
|
|
||||||
if (treeData) {
|
|
||||||
vcTreeProps.treeData = treeData;
|
|
||||||
}
|
|
||||||
return <VcTree {...vcTreeProps} />;
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,110 +0,0 @@
|
||||||
import type { VNode } from 'vue';
|
|
||||||
import { getNodeChildren, convertTreeToEntities } from '../vc-tree/src/util';
|
|
||||||
import { getSlot } from '../_util/props-util';
|
|
||||||
import type { TreeDataItem } from './Tree';
|
|
||||||
|
|
||||||
enum Record {
|
|
||||||
None,
|
|
||||||
Start,
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
type TreeKey = string | number;
|
|
||||||
|
|
||||||
// TODO: Move this logic into `rc-tree`
|
|
||||||
function traverseNodesKey(rootChildren: VNode[], callback?: Function) {
|
|
||||||
const nodeList = getNodeChildren(rootChildren) || [];
|
|
||||||
|
|
||||||
function processNode(node: VNode) {
|
|
||||||
const { key } = node;
|
|
||||||
const children = getSlot(node);
|
|
||||||
if (callback(key, node) !== false) {
|
|
||||||
traverseNodesKey(children, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeList.forEach(processNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFullKeyList(children: VNode[]) {
|
|
||||||
const { keyEntities } = convertTreeToEntities(children);
|
|
||||||
return [...keyEntities.keys()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 计算选中范围,只考虑expanded情况以优化性能 */
|
|
||||||
export function calcRangeKeys(
|
|
||||||
rootChildren: VNode[],
|
|
||||||
expandedKeys: TreeKey[],
|
|
||||||
startKey: TreeKey,
|
|
||||||
endKey: TreeKey,
|
|
||||||
) {
|
|
||||||
const keys = [];
|
|
||||||
let record = Record.None;
|
|
||||||
|
|
||||||
if (startKey && startKey === endKey) {
|
|
||||||
return [startKey];
|
|
||||||
}
|
|
||||||
if (!startKey || !endKey) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchKey(key: TreeKey) {
|
|
||||||
return key === startKey || key === endKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
traverseNodesKey(rootChildren, (key: TreeKey) => {
|
|
||||||
if (record === Record.End) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchKey(key)) {
|
|
||||||
// Match test
|
|
||||||
keys.push(key);
|
|
||||||
|
|
||||||
if (record === Record.None) {
|
|
||||||
record = Record.Start;
|
|
||||||
} else if (record === Record.Start) {
|
|
||||||
record = Record.End;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (record === Record.Start) {
|
|
||||||
// Append selection
|
|
||||||
keys.push(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expandedKeys.indexOf(key) === -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertDirectoryKeysToNodes(rootChildren: VNode[], keys: TreeKey[]) {
|
|
||||||
const restKeys = [...keys];
|
|
||||||
const nodes = [];
|
|
||||||
traverseNodesKey(rootChildren, (key: TreeKey, node: VNode) => {
|
|
||||||
const index = restKeys.indexOf(key);
|
|
||||||
if (index !== -1) {
|
|
||||||
nodes.push(node);
|
|
||||||
restKeys.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!restKeys.length;
|
|
||||||
});
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFullKeyListByTreeData(treeData: TreeDataItem[], replaceFields: any = {}) {
|
|
||||||
let keys = [];
|
|
||||||
const { key = 'key', children = 'children' } = replaceFields;
|
|
||||||
(treeData || []).forEach((item: TreeDataItem) => {
|
|
||||||
keys.push(item[key]);
|
|
||||||
if (item[children]) {
|
|
||||||
keys = [...keys, ...getFullKeyListByTreeData(item[children], replaceFields)];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return keys;
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ import type { FlattenNode } from './interface';
|
||||||
import type { TreeNodeRequiredProps } from './utils/treeUtil';
|
import type { TreeNodeRequiredProps } from './utils/treeUtil';
|
||||||
import { getTreeNodeProps } from './utils/treeUtil';
|
import { getTreeNodeProps } from './utils/treeUtil';
|
||||||
import { useInjectTreeContext } from './contextTypes';
|
import { useInjectTreeContext } from './contextTypes';
|
||||||
import { computed, getCurrentInstance, nextTick, PropType } from 'vue';
|
import { computed, nextTick, PropType } from 'vue';
|
||||||
import { defineComponent, onBeforeUnmount, onMounted, ref, Transition, watch } from 'vue';
|
import { defineComponent, onBeforeUnmount, onMounted, ref, Transition, watch } from 'vue';
|
||||||
import { treeNodeProps } from './props';
|
import { treeNodeProps } from './props';
|
||||||
import { collapseMotion } from '../_util/transition';
|
import { collapseMotion } from '../_util/transition';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ComputedRef, CSSProperties, DefineComponent, Ref, VNode } from 'vue';
|
import type { ComputedRef, CSSProperties, Ref, VNode } from 'vue';
|
||||||
import { TreeNodeProps } from './props';
|
import { TreeNodeProps } from './props';
|
||||||
export type { ScrollTo } from '../vc-virtual-list/List';
|
export type { ScrollTo } from '../vc-virtual-list/List';
|
||||||
|
|
||||||
|
@ -40,11 +40,7 @@ export type IconType = any;
|
||||||
|
|
||||||
export type Key = string | number;
|
export type Key = string | number;
|
||||||
|
|
||||||
export type NodeElement = VNode<DefineComponent<TreeNodeProps>> & {
|
export type NodeElement = VNode<TreeNodeProps>;
|
||||||
type: {
|
|
||||||
isTreeNode: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DragNodeEvent = {
|
export type DragNodeEvent = {
|
||||||
eventData: ComputedRef<EventDataNode>;
|
eventData: ComputedRef<EventDataNode>;
|
||||||
|
|
|
@ -42,7 +42,7 @@ export function getPosition(level: string | number, index: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTreeNode(node: NodeElement) {
|
export function isTreeNode(node: NodeElement) {
|
||||||
return node && node.type && node.type.isTreeNode;
|
return node && node.type && (node.type as any).isTreeNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record<Key, DataEntity>): Key[] {
|
export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record<Key, DataEntity>): Key[] {
|
||||||
|
@ -272,7 +272,6 @@ export function convertDataToTree(
|
||||||
const list = Array.isArray(treeData) ? treeData : [treeData];
|
const list = Array.isArray(treeData) ? treeData : [treeData];
|
||||||
return list.map(({ children, ...props }): NodeElement => {
|
return list.map(({ children, ...props }): NodeElement => {
|
||||||
const childrenNodes = convertDataToTree(children, processor);
|
const childrenNodes = convertDataToTree(children, processor);
|
||||||
|
|
||||||
return <TreeNode {...processProps(props)}>{childrenNodes}</TreeNode>;
|
return <TreeNode {...processProps(props)}>{childrenNodes}</TreeNode>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue