|
@@ -1,23 +1,16 @@
|
|
|
<script lang="tsx">
|
|
|
import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types';
|
|
|
|
|
|
- import {
|
|
|
- defineComponent,
|
|
|
- reactive,
|
|
|
- computed,
|
|
|
- unref,
|
|
|
- ref,
|
|
|
- watchEffect,
|
|
|
- onMounted,
|
|
|
- toRaw,
|
|
|
- } from 'vue';
|
|
|
+ import { defineComponent, reactive, computed, unref, ref, watchEffect, toRaw } from 'vue';
|
|
|
import { Tree } from 'ant-design-vue';
|
|
|
import { TreeIcon } from './TreeIcon';
|
|
|
+ import TreeHeader from './TreeHeader.vue';
|
|
|
// import { DownOutlined } from '@ant-design/icons-vue';
|
|
|
|
|
|
import { omit, get } from 'lodash-es';
|
|
|
import { isBoolean, isFunction } from '/@/utils/is';
|
|
|
import { extendSlots } from '/@/utils/helper/tsxHelper';
|
|
|
+ import { filter } from '/@/utils/helper/treeHelper';
|
|
|
|
|
|
import { useTree } from './useTree';
|
|
|
import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu';
|
|
@@ -30,18 +23,25 @@
|
|
|
expandedKeys: Keys;
|
|
|
selectedKeys: Keys;
|
|
|
checkedKeys: CheckKeys;
|
|
|
+ checkStrictly: boolean;
|
|
|
}
|
|
|
export default defineComponent({
|
|
|
name: 'BasicTree',
|
|
|
props: basicProps,
|
|
|
- emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'get'],
|
|
|
+ emits: ['update:expandedKeys', 'update:selectedKeys', 'update:value', 'change'],
|
|
|
setup(props, { attrs, slots, emit }) {
|
|
|
const state = reactive<State>({
|
|
|
+ checkStrictly: props.checkStrictly,
|
|
|
expandedKeys: props.expandedKeys || [],
|
|
|
selectedKeys: props.selectedKeys || [],
|
|
|
checkedKeys: props.checkedKeys || [],
|
|
|
});
|
|
|
|
|
|
+ const searchState = reactive({
|
|
|
+ startSearch: false,
|
|
|
+ searchData: [] as TreeItem[],
|
|
|
+ });
|
|
|
+
|
|
|
const treeDataRef = ref<TreeItem[]>([]);
|
|
|
|
|
|
const [createContextMenu] = useContextMenu();
|
|
@@ -77,6 +77,7 @@
|
|
|
expandedKeys: state.expandedKeys,
|
|
|
selectedKeys: state.selectedKeys,
|
|
|
checkedKeys: state.checkedKeys,
|
|
|
+ checkStrictly: state.checkStrictly,
|
|
|
replaceFields: unref(getReplaceFields),
|
|
|
'onUpdate:expandedKeys': (v: Keys) => {
|
|
|
state.expandedKeys = v;
|
|
@@ -88,21 +89,27 @@
|
|
|
},
|
|
|
onCheck: (v: CheckKeys) => {
|
|
|
state.checkedKeys = v;
|
|
|
+ emit('change', v);
|
|
|
emit('update:value', v);
|
|
|
},
|
|
|
onRightClick: handleRightClick,
|
|
|
};
|
|
|
- propsData = omit(propsData, 'treeData');
|
|
|
+ propsData = omit(propsData, 'treeData', 'class');
|
|
|
return propsData;
|
|
|
});
|
|
|
|
|
|
- const getTreeData = computed((): TreeItem[] => unref(treeDataRef));
|
|
|
-
|
|
|
- const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey } = useTree(
|
|
|
- treeDataRef,
|
|
|
- getReplaceFields
|
|
|
+ const getTreeData = computed((): TreeItem[] =>
|
|
|
+ searchState.startSearch ? searchState.searchData : unref(treeDataRef)
|
|
|
);
|
|
|
|
|
|
+ const {
|
|
|
+ deleteNodeByKey,
|
|
|
+ insertNodeByKey,
|
|
|
+ filterByLevel,
|
|
|
+ updateNodeByKey,
|
|
|
+ getAllKeys,
|
|
|
+ } = useTree(treeDataRef, getReplaceFields);
|
|
|
+
|
|
|
function getIcon(params: Recordable, icon?: string) {
|
|
|
if (!icon) {
|
|
|
if (props.renderIcon && isFunction(props.renderIcon)) {
|
|
@@ -112,60 +119,6 @@
|
|
|
return icon;
|
|
|
}
|
|
|
|
|
|
- function renderAction(node: TreeItem) {
|
|
|
- const { actionList } = props;
|
|
|
- if (!actionList || actionList.length === 0) return;
|
|
|
- return actionList.map((item, index) => {
|
|
|
- if (isFunction(item.show)) {
|
|
|
- return item.show?.(node);
|
|
|
- }
|
|
|
-
|
|
|
- if (isBoolean(item.show)) {
|
|
|
- return item.show;
|
|
|
- }
|
|
|
-
|
|
|
- return (
|
|
|
- <span key={index} class={`${prefixCls}__action`}>
|
|
|
- {item.render(node)}
|
|
|
- </span>
|
|
|
- );
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) {
|
|
|
- if (!data) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- return data.map((item) => {
|
|
|
- const { title: titleField, key: keyField, children: childrenField } = unref(
|
|
|
- getReplaceFields
|
|
|
- );
|
|
|
-
|
|
|
- const propsData = omit(item, 'title');
|
|
|
- const icon = getIcon({ ...item, level }, item.icon);
|
|
|
- return (
|
|
|
- <Tree.TreeNode {...propsData} node={toRaw(item)} key={get(item, keyField)}>
|
|
|
- {{
|
|
|
- title: () => (
|
|
|
- <span class={`${prefixCls}-title`}>
|
|
|
- {icon && <TreeIcon icon={icon} />}
|
|
|
- <span
|
|
|
- class={`${prefixCls}__content`}
|
|
|
- // style={unref(getContentStyle)}
|
|
|
- >
|
|
|
- {get(item, titleField)}
|
|
|
- </span>
|
|
|
- <span class={`${prefixCls}__actions`}> {renderAction({ ...item, level })}</span>
|
|
|
- </span>
|
|
|
- ),
|
|
|
- default: () =>
|
|
|
- renderTreeNode({ data: get(item, childrenField) || [], level: level + 1 }),
|
|
|
- }}
|
|
|
- </Tree.TreeNode>
|
|
|
- );
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
async function handleRightClick({ event, node }: any) {
|
|
|
const { rightMenuList: menuList = [], beforeRightClick } = props;
|
|
|
let rightMenuList: ContextMenuItem[] = [];
|
|
@@ -205,6 +158,32 @@
|
|
|
return state.checkedKeys;
|
|
|
}
|
|
|
|
|
|
+ function checkAll(checkAll: boolean) {
|
|
|
+ state.checkedKeys = checkAll ? getAllKeys() : ([] as Keys);
|
|
|
+ }
|
|
|
+
|
|
|
+ function expandAll(expandAll: boolean) {
|
|
|
+ state.expandedKeys = expandAll ? getAllKeys() : ([] as Keys);
|
|
|
+ }
|
|
|
+
|
|
|
+ function onStrictlyChange(strictly: boolean) {
|
|
|
+ state.checkStrictly = strictly;
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleSearch(searchValue: string) {
|
|
|
+ if (!searchValue) {
|
|
|
+ searchState.startSearch = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ searchState.startSearch = true;
|
|
|
+
|
|
|
+ searchState.searchData = filter(unref(treeDataRef), (node) => {
|
|
|
+ const { title } = node;
|
|
|
+ return title?.includes(searchValue) ?? false;
|
|
|
+ // || key?.includes(searchValue);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
watchEffect(() => {
|
|
|
treeDataRef.value = props.treeData as TreeItem[];
|
|
|
state.expandedKeys = props.expandedKeys;
|
|
@@ -212,6 +191,16 @@
|
|
|
state.checkedKeys = props.checkedKeys;
|
|
|
});
|
|
|
|
|
|
+ watchEffect(() => {
|
|
|
+ if (props.value) {
|
|
|
+ state.checkedKeys = props.value;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ watchEffect(() => {
|
|
|
+ state.checkStrictly = props.checkStrictly;
|
|
|
+ });
|
|
|
+
|
|
|
const instance: TreeActionType = {
|
|
|
setExpandedKeys,
|
|
|
getExpandedKeys,
|
|
@@ -222,6 +211,8 @@
|
|
|
insertNodeByKey,
|
|
|
deleteNodeByKey,
|
|
|
updateNodeByKey,
|
|
|
+ checkAll,
|
|
|
+ expandAll,
|
|
|
filterByLevel: (level: number) => {
|
|
|
state.expandedKeys = filterByLevel(level);
|
|
|
},
|
|
@@ -229,19 +220,83 @@
|
|
|
|
|
|
useExpose<TreeActionType>(instance);
|
|
|
|
|
|
- onMounted(() => {
|
|
|
- emit('get', instance);
|
|
|
- });
|
|
|
+ function renderAction(node: TreeItem) {
|
|
|
+ const { actionList } = props;
|
|
|
+ if (!actionList || actionList.length === 0) return;
|
|
|
+ return actionList.map((item, index) => {
|
|
|
+ if (isFunction(item.show)) {
|
|
|
+ return item.show?.(node);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isBoolean(item.show)) {
|
|
|
+ return item.show;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <span key={index} class={`${prefixCls}__action`}>
|
|
|
+ {item.render(node)}
|
|
|
+ </span>
|
|
|
+ );
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderTreeNode({ data, level }: { data: TreeItem[] | undefined; level: number }) {
|
|
|
+ if (!data) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return data.map((item) => {
|
|
|
+ const { title: titleField, key: keyField, children: childrenField } = unref(
|
|
|
+ getReplaceFields
|
|
|
+ );
|
|
|
|
|
|
+ const propsData = omit(item, 'title');
|
|
|
+ const icon = getIcon({ ...item, level }, item.icon);
|
|
|
+ return (
|
|
|
+ <Tree.TreeNode {...propsData} node={toRaw(item)} key={get(item, keyField)}>
|
|
|
+ {{
|
|
|
+ title: () => (
|
|
|
+ <span class={`${prefixCls}-title pl-2`}>
|
|
|
+ {icon && <TreeIcon icon={icon} />}
|
|
|
+ <span
|
|
|
+ class={`${prefixCls}__content`}
|
|
|
+ // style={unref(getContentStyle)}
|
|
|
+ >
|
|
|
+ {get(item, titleField)}
|
|
|
+ </span>
|
|
|
+ <span class={`${prefixCls}__actions`}> {renderAction({ ...item, level })}</span>
|
|
|
+ </span>
|
|
|
+ ),
|
|
|
+ default: () =>
|
|
|
+ renderTreeNode({ data: get(item, childrenField) || [], level: level + 1 }),
|
|
|
+ }}
|
|
|
+ </Tree.TreeNode>
|
|
|
+ );
|
|
|
+ });
|
|
|
+ }
|
|
|
return () => {
|
|
|
+ const { title, helpMessage, toolbar, search } = props;
|
|
|
return (
|
|
|
- <Tree {...unref(getBindValues)} showIcon={false} class={[prefixCls]}>
|
|
|
- {{
|
|
|
- // switcherIcon: () => <DownOutlined />,
|
|
|
- default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),
|
|
|
- ...extendSlots(slots),
|
|
|
- }}
|
|
|
- </Tree>
|
|
|
+ <div class={[prefixCls, 'h-full bg-white']}>
|
|
|
+ {(title || toolbar || search) && (
|
|
|
+ <TreeHeader
|
|
|
+ checkAll={checkAll}
|
|
|
+ expandAll={expandAll}
|
|
|
+ title={title}
|
|
|
+ search={search}
|
|
|
+ toolbar={toolbar}
|
|
|
+ helpMessage={helpMessage}
|
|
|
+ onStrictlyChange={onStrictlyChange}
|
|
|
+ onSearch={handleSearch}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ <Tree {...unref(getBindValues)} showIcon={false}>
|
|
|
+ {{
|
|
|
+ // switcherIcon: () => <DownOutlined />,
|
|
|
+ default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),
|
|
|
+ ...extendSlots(slots),
|
|
|
+ }}
|
|
|
+ </Tree>
|
|
|
+ </div>
|
|
|
);
|
|
|
};
|
|
|
},
|
|
@@ -251,8 +306,6 @@
|
|
|
@prefix-cls: ~'@{namespace}-basic-tree';
|
|
|
|
|
|
.@{prefix-cls} {
|
|
|
- position: relative;
|
|
|
-
|
|
|
.ant-tree-node-content-wrapper {
|
|
|
position: relative;
|
|
|
|
|
@@ -278,14 +331,14 @@
|
|
|
}
|
|
|
|
|
|
&__content {
|
|
|
- display: inline-block;
|
|
|
+ // display: inline-block;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
&__actions {
|
|
|
position: absolute;
|
|
|
top: 2px;
|
|
|
- right: 2px;
|
|
|
+ right: 3px;
|
|
|
display: flex;
|
|
|
}
|
|
|
|