refactor: tree
parent
5f189f534e
commit
94aa0df8d7
|
@ -1,33 +1,21 @@
|
|||
import type { ExtractPropTypes, PropType, VNode } from 'vue';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import omit from 'omit.js';
|
||||
import { ExtractPropTypes, nextTick, onUpdated, PropType, ref, watch } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
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 { treeProps } from './Tree';
|
||||
import { AntdTreeNodeAttribute, treeProps } from './Tree';
|
||||
import Tree, { TreeProps } from './Tree';
|
||||
import {
|
||||
calcRangeKeys,
|
||||
getFullKeyList,
|
||||
convertDirectoryKeysToNodes,
|
||||
getFullKeyListByTreeData,
|
||||
} from './util';
|
||||
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
|
||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { convertDataToEntities, convertTreeToData } from '../vc-tree/utils/treeUtil';
|
||||
import { DataNode, EventDataNode, Key } from '../vc-tree/interface';
|
||||
import { conductExpandParent } from '../vc-tree/util';
|
||||
import { calcRangeKeys, convertDirectoryKeysToNodes } from './utils/dictUtil';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import { filterEmpty } from '../_util/props-util';
|
||||
|
||||
export type ExpandAction = false | 'click' | 'doubleClick' | 'dblclick';
|
||||
|
||||
function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) {
|
||||
const { isLeaf, expanded } = props;
|
||||
if (isLeaf) {
|
||||
return <FileOutlined />;
|
||||
}
|
||||
return expanded ? <FolderOpenOutlined /> : <FolderOutlined />;
|
||||
}
|
||||
export type ExpandAction = false | 'click' | 'doubleclick' | 'dblclick';
|
||||
|
||||
const directoryTreeProps = {
|
||||
...treeProps(),
|
||||
|
@ -36,6 +24,14 @@ const directoryTreeProps = {
|
|||
|
||||
export type DirectoryTreeProps = Partial<ExtractPropTypes<typeof directoryTreeProps>>;
|
||||
|
||||
function getIcon(props: AntdTreeNodeAttribute) {
|
||||
const { isLeaf, expanded } = props;
|
||||
if (isLeaf) {
|
||||
return <FileOutlined />;
|
||||
}
|
||||
return expanded ? <FolderOpenOutlined /> : <FolderOutlined />;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ADirectoryTree',
|
||||
inheritAttrs: false,
|
||||
|
@ -43,9 +39,234 @@ export default defineComponent({
|
|||
showIcon: true,
|
||||
expandAction: 'click',
|
||||
}),
|
||||
setup() {
|
||||
slots: ['icon', 'title', 'switcherIcon'],
|
||||
emits: [
|
||||
'update:selectedKeys',
|
||||
'update:checkedKeys',
|
||||
'update:expandedKeys',
|
||||
'expand',
|
||||
'select',
|
||||
'check',
|
||||
'doubleclick',
|
||||
'dblclick',
|
||||
'click',
|
||||
],
|
||||
setup(props, { attrs, slots, emit }) {
|
||||
// convertTreeToData 兼容 a-tree-node 历史写法,未来a-tree-node移除后,删除相关代码,不要再render中调用 treeData,否则死循环
|
||||
const treeData = ref<DataNode[]>(
|
||||
props.treeData || convertTreeToData(filterEmpty(slots.default?.())),
|
||||
);
|
||||
watch(
|
||||
() => props.treeData,
|
||||
() => {
|
||||
treeData.value = props.treeData;
|
||||
},
|
||||
);
|
||||
onUpdated(() => {
|
||||
nextTick(() => {
|
||||
if (props.treeData === undefined && slots.default) {
|
||||
treeData.value = convertTreeToData(filterEmpty(slots.default?.()));
|
||||
}
|
||||
});
|
||||
});
|
||||
// Shift click usage
|
||||
const lastSelectedKey = ref<Key>();
|
||||
|
||||
const cachedSelectedKeys = ref<Key[]>();
|
||||
|
||||
const treeRef = ref();
|
||||
|
||||
const getInitExpandedKeys = () => {
|
||||
const { keyEntities } = convertDataToEntities(treeData.value);
|
||||
|
||||
let initExpandedKeys: any;
|
||||
|
||||
// Expanded keys
|
||||
if (props.defaultExpandAll) {
|
||||
initExpandedKeys = Object.keys(keyEntities);
|
||||
} else if (props.defaultExpandParent) {
|
||||
initExpandedKeys = conductExpandParent(
|
||||
props.expandedKeys || props.defaultExpandedKeys,
|
||||
keyEntities,
|
||||
);
|
||||
} else {
|
||||
initExpandedKeys = props.expandedKeys || props.defaultExpandedKeys;
|
||||
}
|
||||
return initExpandedKeys;
|
||||
};
|
||||
|
||||
const selectedKeys = ref(props.selectedKeys || props.defaultSelectedKeys || []);
|
||||
|
||||
const expandedKeys = ref<Key[]>(getInitExpandedKeys());
|
||||
|
||||
watch(
|
||||
() => props.selectedKeys,
|
||||
() => {
|
||||
if (props.selectedKeys !== undefined) {
|
||||
selectedKeys.value = props.selectedKeys;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.expandedKeys,
|
||||
() => {
|
||||
if (props.expandedKeys !== undefined) {
|
||||
expandedKeys.value = props.expandedKeys;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const expandFolderNode = (event: MouseEvent, node: any) => {
|
||||
const { isLeaf } = node;
|
||||
|
||||
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
// Call internal rc-tree expand function
|
||||
// https://github.com/ant-design/ant-design/issues/12567
|
||||
treeRef.value!.onNodeExpand(event as any, node);
|
||||
};
|
||||
const onDebounceExpand = debounce(expandFolderNode, 200, {
|
||||
leading: true,
|
||||
});
|
||||
const onExpand = (
|
||||
keys: Key[],
|
||||
info: {
|
||||
node: EventDataNode;
|
||||
expanded: boolean;
|
||||
nativeEvent: MouseEvent;
|
||||
},
|
||||
) => {
|
||||
if (props.expandedKeys === undefined) {
|
||||
expandedKeys.value = keys;
|
||||
}
|
||||
// Call origin function
|
||||
emit('update:expandedKeys', keys);
|
||||
emit('expand', keys, info);
|
||||
};
|
||||
|
||||
const onClick = (event: MouseEvent, node: EventDataNode) => {
|
||||
const { expandAction } = props;
|
||||
|
||||
// Expand the tree
|
||||
if (expandAction === 'click') {
|
||||
onDebounceExpand(event, node);
|
||||
}
|
||||
emit('click', event, node);
|
||||
};
|
||||
|
||||
const onDoubleClick = (event: MouseEvent, node: EventDataNode) => {
|
||||
const { expandAction } = props;
|
||||
// Expand the tree
|
||||
if (expandAction === 'dblclick' || expandAction === 'doubleclick') {
|
||||
onDebounceExpand(event, node);
|
||||
}
|
||||
|
||||
emit('doubleclick', event, node);
|
||||
emit('dblclick', event, node);
|
||||
};
|
||||
|
||||
const onSelect = (
|
||||
keys: Key[],
|
||||
event: {
|
||||
event: 'select';
|
||||
selected: boolean;
|
||||
node: any;
|
||||
selectedNodes: DataNode[];
|
||||
nativeEvent: MouseEvent;
|
||||
},
|
||||
) => {
|
||||
const { multiple } = props;
|
||||
const { node, nativeEvent } = event;
|
||||
const { key = '' } = node;
|
||||
|
||||
// const newState: DirectoryTreeState = {};
|
||||
|
||||
// We need wrap this event since some value is not same
|
||||
const newEvent: any = {
|
||||
...event,
|
||||
selected: true, // Directory selected always true
|
||||
};
|
||||
|
||||
// Windows / Mac single pick
|
||||
const ctrlPick: boolean = nativeEvent.ctrlKey || nativeEvent.metaKey;
|
||||
const shiftPick: boolean = nativeEvent.shiftKey;
|
||||
|
||||
// Generate new selected keys
|
||||
let newSelectedKeys: Key[];
|
||||
if (multiple && ctrlPick) {
|
||||
// Control click
|
||||
newSelectedKeys = keys;
|
||||
lastSelectedKey.value = key;
|
||||
cachedSelectedKeys.value = newSelectedKeys;
|
||||
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData.value, newSelectedKeys);
|
||||
} else if (multiple && shiftPick) {
|
||||
// Shift click
|
||||
newSelectedKeys = Array.from(
|
||||
new Set([
|
||||
...(cachedSelectedKeys.value || []),
|
||||
...calcRangeKeys({
|
||||
treeData: treeData.value,
|
||||
expandedKeys: expandedKeys.value,
|
||||
startKey: key,
|
||||
endKey: lastSelectedKey.value,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData.value, newSelectedKeys);
|
||||
} else {
|
||||
// Single click
|
||||
newSelectedKeys = [key];
|
||||
lastSelectedKey.value = key;
|
||||
cachedSelectedKeys.value = newSelectedKeys;
|
||||
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData.value, newSelectedKeys);
|
||||
}
|
||||
|
||||
emit('update:selectedKeys', newSelectedKeys);
|
||||
emit('select', newSelectedKeys, newEvent);
|
||||
if (props.selectedKeys === undefined) {
|
||||
selectedKeys.value = newSelectedKeys;
|
||||
}
|
||||
};
|
||||
|
||||
const onCheck: TreeProps['onCheck'] = (checkedObjOrKeys, eventObj) => {
|
||||
emit('update:checkedKeys', checkedObjOrKeys);
|
||||
emit('check', checkedObjOrKeys, eventObj);
|
||||
};
|
||||
|
||||
const { prefixCls, direction } = useConfigInject('tree', props);
|
||||
|
||||
return () => {
|
||||
return null;
|
||||
const connectClassName = classNames(
|
||||
`${prefixCls.value}-directory`,
|
||||
{
|
||||
[`${prefixCls.value}-directory-rtl`]: direction.value === 'rtl',
|
||||
},
|
||||
attrs.class,
|
||||
);
|
||||
const { icon = slots.icon, ...otherProps } = props;
|
||||
return (
|
||||
<Tree
|
||||
{...attrs}
|
||||
icon={icon || getIcon}
|
||||
ref={treeRef}
|
||||
blockNode
|
||||
{...otherProps}
|
||||
prefixCls={prefixCls.value}
|
||||
class={connectClassName}
|
||||
expandedKeys={expandedKeys.value}
|
||||
selectedKeys={selectedKeys.value}
|
||||
onSelect={onSelect}
|
||||
onClick={onClick}
|
||||
onDblclick={onDoubleClick}
|
||||
onExpand={onExpand}
|
||||
onCheck={onCheck}
|
||||
v-slots={slots}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -150,13 +150,18 @@ export default defineComponent({
|
|||
'expand',
|
||||
'select',
|
||||
'check',
|
||||
'doubleclick',
|
||||
'dblclick',
|
||||
],
|
||||
TreeNode,
|
||||
setup(props, { attrs, expose, emit, slots }) {
|
||||
const { prefixCls, direction, virtual } = useConfigInject('tree', props);
|
||||
const tree = ref();
|
||||
const treeRef = ref();
|
||||
expose({
|
||||
tree,
|
||||
treeRef,
|
||||
onNodeExpand: (...args) => {
|
||||
treeRef.value?.onNodeExpand(...args);
|
||||
},
|
||||
});
|
||||
|
||||
const handleCheck: TreeProps['onCheck'] = (checkedObjOrKeys, eventObj) => {
|
||||
|
@ -197,7 +202,7 @@ export default defineComponent({
|
|||
itemHeight={20}
|
||||
virtual={virtual.value}
|
||||
{...newProps}
|
||||
ref={tree}
|
||||
ref={treeRef}
|
||||
prefixCls={prefixCls.value}
|
||||
class={classNames(
|
||||
{
|
||||
|
|
|
@ -55,7 +55,7 @@ export default defineComponent({
|
|||
allowDrop: () => true,
|
||||
}),
|
||||
|
||||
setup(props, { attrs, slots }) {
|
||||
setup(props, { attrs, slots, expose }) {
|
||||
const destroyed = ref(false);
|
||||
let delayedDragEnterLogic: Record<Key, number> = {};
|
||||
const indent = ref();
|
||||
|
@ -543,9 +543,9 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const onNodeDoubleClick: NodeMouseEventHandler = (e, treeNode) => {
|
||||
const { onDblClick } = props;
|
||||
if (onDblClick) {
|
||||
onDblClick(e, treeNode);
|
||||
const { onDblclick } = props;
|
||||
if (onDblclick) {
|
||||
onDblclick(e, treeNode);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -953,7 +953,9 @@ export default defineComponent({
|
|||
onKeyDown(event);
|
||||
}
|
||||
};
|
||||
|
||||
expose({
|
||||
onNodeExpand,
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('dragend', onWindowDragEnd);
|
||||
destroyed.value = true;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { ComputedRef, CSSProperties, Ref, VNode } from 'vue';
|
||||
import type { ComputedRef, CSSProperties, DefineComponent, Ref, VNode } from 'vue';
|
||||
import { TreeNodeProps } from './props';
|
||||
export type { ScrollTo } from '../vc-virtual-list/List';
|
||||
|
||||
export interface DataNode {
|
||||
|
@ -39,7 +40,7 @@ export type IconType = any;
|
|||
|
||||
export type Key = string | number;
|
||||
|
||||
export type NodeElement = VNode & {
|
||||
export type NodeElement = VNode<DefineComponent<TreeNodeProps>> & {
|
||||
type: {
|
||||
isTreeNode: boolean;
|
||||
};
|
||||
|
|
|
@ -149,7 +149,7 @@ export const treeProps = () => ({
|
|||
onKeyDown: { type: Function as PropType<EventHandlerNonNull> },
|
||||
onContextmenu: { type: Function as PropType<EventHandlerNonNull> },
|
||||
onClick: { type: Function as PropType<NodeMouseEventHandler> },
|
||||
onDblClick: { type: Function as PropType<NodeMouseEventHandler> },
|
||||
onDblclick: { type: Function as PropType<NodeMouseEventHandler> },
|
||||
onScroll: { type: Function as PropType<EventHandlerNonNull> },
|
||||
onExpand: {
|
||||
type: Function as PropType<
|
||||
|
|
|
@ -64,7 +64,7 @@ export function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames)
|
|||
* Convert `children` of Tree into `treeData` structure.
|
||||
*/
|
||||
export function convertTreeToData(rootNodes: VNodeChild): DataNode[] {
|
||||
function dig(node: VNodeChild): DataNode[] {
|
||||
function dig(node: VNodeChild = []): DataNode[] {
|
||||
const treeNodes = node as NodeElement[];
|
||||
return treeNodes
|
||||
.map(treeNode => {
|
||||
|
@ -75,8 +75,8 @@ export function convertTreeToData(rootNodes: VNodeChild): DataNode[] {
|
|||
}
|
||||
|
||||
const key = treeNode.key as string | number;
|
||||
const { children, ...rest } = treeNode.props;
|
||||
|
||||
const { ...rest } = treeNode.props;
|
||||
const children = (treeNode.children as any)?.default?.();
|
||||
const dataNode: DataNode = {
|
||||
...rest,
|
||||
key,
|
||||
|
|
|
@ -1,45 +1,30 @@
|
|||
<template>
|
||||
<a-tree
|
||||
<a-directory-tree
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
:load-data="onLoadData"
|
||||
:tree-data="treeData"
|
||||
/>
|
||||
multiple
|
||||
@test="onTest"
|
||||
>
|
||||
<a-tree-node key="0-0" title="parent 0">
|
||||
<a-tree-node key="0-0-0" title="leaf 0-0" is-leaf />
|
||||
<a-tree-node key="0-0-1" title="leaf 0-1" is-leaf />
|
||||
</a-tree-node>
|
||||
<a-tree-node key="0-1" title="parent 1">
|
||||
<a-tree-node key="0-1-0" title="leaf 1-0" is-leaf />
|
||||
<a-tree-node key="0-1-1" title="leaf 1-1" is-leaf />
|
||||
</a-tree-node>
|
||||
</a-directory-tree>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
const expandedKeys = ref<string[]>(['0-0', '0-1']);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const treeData = ref<TreeDataItem[]>([
|
||||
{ title: 'Expand to load', key: '0' },
|
||||
{ title: 'Expand to load', key: '1' },
|
||||
{ title: 'Tree Node', key: '2', isLeaf: true },
|
||||
]);
|
||||
const onLoadData = (treeNode: any) => {
|
||||
return new Promise((resolve: (value?: unknown) => void) => {
|
||||
if (treeNode.dataRef.children) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
treeNode.dataRef.children = [
|
||||
{ title: 'Child Node', key: `${treeNode.eventKey}-0` },
|
||||
{ title: 'Child Node', key: `${treeNode.eventKey}-1` },
|
||||
];
|
||||
treeData.value = [...treeData.value];
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
return {
|
||||
expandedKeys,
|
||||
selectedKeys,
|
||||
treeData,
|
||||
onLoadData,
|
||||
onTest: () => {},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue