|
|
@@ -7,7 +7,7 @@ import type { PropType, StyleValue } from 'vue';
|
|
|
|
|
|
import type { ApiConfig, FieldNames } from '#/components/form/types';
|
|
|
|
|
|
-import { computed, reactive, ref, unref, watch, watchEffect } from 'vue';
|
|
|
+import { computed, reactive, ref, toRaw, unref, watch, watchEffect } from 'vue';
|
|
|
|
|
|
import { VbenLoading, VbenScrollbar } from '@vben/common-ui';
|
|
|
import { EmptyIcon } from '@vben/icons';
|
|
|
@@ -65,16 +65,34 @@ const props = defineProps({
|
|
|
type: Boolean,
|
|
|
default: false,
|
|
|
},
|
|
|
+ checkedKeys: {
|
|
|
+ type: Array as PropType<Key[]>,
|
|
|
+ default: () => [],
|
|
|
+ },
|
|
|
+ checkStrictly: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
});
|
|
|
-
|
|
|
-const emit = defineEmits(['update:searchValue', 'change']);
|
|
|
+const emit = defineEmits([
|
|
|
+ 'update:searchValue',
|
|
|
+ 'change',
|
|
|
+ 'update:value',
|
|
|
+ 'check',
|
|
|
+]);
|
|
|
|
|
|
const state = reactive<{
|
|
|
autoExpandParent: boolean;
|
|
|
+ checkedKeys: (number | string)[];
|
|
|
+ checkStrictly: boolean;
|
|
|
expandedKeys: (number | string)[];
|
|
|
+ lastLeafKeys: (number | string)[];
|
|
|
}>({
|
|
|
expandedKeys: [],
|
|
|
autoExpandParent: true,
|
|
|
+ checkedKeys: [],
|
|
|
+ lastLeafKeys: [],
|
|
|
+ checkStrictly: props.checkStrictly,
|
|
|
});
|
|
|
|
|
|
const toolbarList = computed(() => {
|
|
|
@@ -91,15 +109,15 @@ const toolbarList = computed(() => {
|
|
|
|
|
|
return checkable
|
|
|
? [
|
|
|
+ ...defaultToolbarList,
|
|
|
{ label: '选择全部', value: ToolbarEnum.SELECT_ALL },
|
|
|
{
|
|
|
label: '取消选择',
|
|
|
value: ToolbarEnum.UN_SELECT_ALL,
|
|
|
divider: checkable,
|
|
|
},
|
|
|
- ...defaultToolbarList,
|
|
|
- { label: '层级关联', value: ToolbarEnum.CHECK_STRICTLY },
|
|
|
- { label: '层级独立', value: ToolbarEnum.CHECK_UN_STRICTLY },
|
|
|
+ // { label: '层级关联', value: ToolbarEnum.CHECK_STRICTLY },
|
|
|
+ // { label: '层级独立', value: ToolbarEnum.CHECK_UN_STRICTLY },
|
|
|
]
|
|
|
: defaultToolbarList;
|
|
|
});
|
|
|
@@ -124,7 +142,10 @@ const getFieldNames = computed((): Required<FieldNames> => {
|
|
|
};
|
|
|
});
|
|
|
|
|
|
-const { getAllKeys } = useTree(treeDataRef, getFieldNames);
|
|
|
+const { getAllKeys, getLeafNodeIds, getEnabledKeys } = useTree(
|
|
|
+ treeDataRef,
|
|
|
+ getFieldNames,
|
|
|
+);
|
|
|
const handleExpand = (keys: Key[]) => {
|
|
|
state.expandedKeys = keys;
|
|
|
state.autoExpandParent = false;
|
|
|
@@ -134,23 +155,20 @@ const handleSelect = (keys: Key[], e: any) => {
|
|
|
emit('change', keys, e);
|
|
|
};
|
|
|
|
|
|
-// const getParentKey = (
|
|
|
-// key: number | string,
|
|
|
-// tree: TreeProps['treeData'],
|
|
|
-// ): number | string | undefined => {
|
|
|
-// let parentKey;
|
|
|
-// tree &&
|
|
|
-// tree.forEach((node) => {
|
|
|
-// if (node.children) {
|
|
|
-// if (node.children.some((item) => item.key === key)) {
|
|
|
-// parentKey = node.key;
|
|
|
-// } else if (getParentKey(key, node.children)) {
|
|
|
-// parentKey = getParentKey(key, node.children);
|
|
|
-// }
|
|
|
-// }
|
|
|
-// });
|
|
|
-// return parentKey;
|
|
|
-// };
|
|
|
+const handleCheck = (keys: any) => {
|
|
|
+ let currentValue = [...keys];
|
|
|
+ if (searchState.startSearch) {
|
|
|
+ currentValue = currentValue.filter((item: number | string) => {
|
|
|
+ return state.lastLeafKeys.includes(item as never);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ state.checkedKeys = currentValue;
|
|
|
+
|
|
|
+ const rawVal = toRaw(state.checkedKeys);
|
|
|
+ emit('update:value', rawVal);
|
|
|
+ emit('check', rawVal);
|
|
|
+};
|
|
|
|
|
|
const contentStyle = computed<StyleValue>(() => {
|
|
|
let height = 45;
|
|
|
@@ -170,6 +188,7 @@ const fetch = async () => {
|
|
|
} else {
|
|
|
treeDataRef.value = props.api.result ? get(res, props.api.result) : [];
|
|
|
}
|
|
|
+ state.lastLeafKeys = getLeafNodeIds();
|
|
|
} catch (error) {
|
|
|
console.warn(error);
|
|
|
} finally {
|
|
|
@@ -181,6 +200,14 @@ watchEffect(() => {
|
|
|
props.immediate && fetch();
|
|
|
});
|
|
|
|
|
|
+watchEffect(() => {
|
|
|
+ state.checkStrictly = props.checkStrictly;
|
|
|
+});
|
|
|
+
|
|
|
+watchEffect(() => {
|
|
|
+ state.checkedKeys = props.checkedKeys;
|
|
|
+});
|
|
|
+
|
|
|
watch(
|
|
|
() => props.api.params,
|
|
|
() => {
|
|
|
@@ -254,15 +281,19 @@ const getNotFound = computed((): boolean => {
|
|
|
const expandAll = (expandAll: boolean) => {
|
|
|
state.expandedKeys = expandAll ? getAllKeys() : ([] as KeyType[]);
|
|
|
};
|
|
|
+
|
|
|
+const checkAll = (checkAll: boolean) => {
|
|
|
+ state.checkedKeys = checkAll ? getEnabledKeys() : ([] as KeyType[]);
|
|
|
+};
|
|
|
const handleMenuClick = async (e: any) => {
|
|
|
const { key } = e;
|
|
|
switch (key) {
|
|
|
case ToolbarEnum.CHECK_STRICTLY: {
|
|
|
- // emit('strictly-change', false);
|
|
|
+ state.checkStrictly = false;
|
|
|
break;
|
|
|
}
|
|
|
case ToolbarEnum.CHECK_UN_STRICTLY: {
|
|
|
- // emit('strictly-change', true);
|
|
|
+ state.checkStrictly = true;
|
|
|
break;
|
|
|
}
|
|
|
case ToolbarEnum.EXPAND_ALL: {
|
|
|
@@ -274,7 +305,7 @@ const handleMenuClick = async (e: any) => {
|
|
|
break;
|
|
|
}
|
|
|
case ToolbarEnum.SELECT_ALL: {
|
|
|
- // props.checkAll?.(true);
|
|
|
+ checkAll(true);
|
|
|
break;
|
|
|
}
|
|
|
case ToolbarEnum.UN_EXPAND_ALL: {
|
|
|
@@ -282,7 +313,7 @@ const handleMenuClick = async (e: any) => {
|
|
|
break;
|
|
|
}
|
|
|
case ToolbarEnum.UN_SELECT_ALL: {
|
|
|
- // props.checkAll?.(false);
|
|
|
+ checkAll(false);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
@@ -322,6 +353,7 @@ const handleMenuClick = async (e: any) => {
|
|
|
<DirectoryTree
|
|
|
:expanded-keys="state.expandedKeys"
|
|
|
:auto-expand-parent="state.autoExpandParent"
|
|
|
+ :checked-keys="state.checkedKeys"
|
|
|
:tree-data="getTreeData"
|
|
|
:field-names="{
|
|
|
title: fieldNames.label,
|
|
|
@@ -331,28 +363,33 @@ const handleMenuClick = async (e: any) => {
|
|
|
block-node
|
|
|
:show-icon="false"
|
|
|
class="px-2"
|
|
|
+ :checkable="checkable"
|
|
|
@expand="handleExpand"
|
|
|
@select="handleSelect"
|
|
|
+ @check="handleCheck"
|
|
|
+ :check-strictly="state.checkStrictly"
|
|
|
>
|
|
|
<template #title="treeDataItem">
|
|
|
- <span
|
|
|
- v-if="treeDataItem[fieldNames.label].indexOf(searchListData) > -1"
|
|
|
- >
|
|
|
- {{
|
|
|
- treeDataItem[fieldNames.label].substring(
|
|
|
- 0,
|
|
|
- treeDataItem[fieldNames.label].indexOf(searchListData),
|
|
|
- )
|
|
|
- }}
|
|
|
- <span style="color: #f50">{{ searchListData }}</span>
|
|
|
- {{
|
|
|
- treeDataItem[fieldNames.label].substring(
|
|
|
- treeDataItem[fieldNames.label].indexOf(searchListData) +
|
|
|
- searchListData.length,
|
|
|
- )
|
|
|
- }}
|
|
|
- </span>
|
|
|
- <span v-else>{{ treeDataItem[fieldNames.label] }}</span>
|
|
|
+ <slot name="title" v-bind="treeDataItem">
|
|
|
+ <span
|
|
|
+ v-if="treeDataItem[fieldNames.label].indexOf(searchListData) > -1"
|
|
|
+ >
|
|
|
+ {{
|
|
|
+ treeDataItem[fieldNames.label].substring(
|
|
|
+ 0,
|
|
|
+ treeDataItem[fieldNames.label].indexOf(searchListData),
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ <span style="color: #f50">{{ searchListData }}</span>
|
|
|
+ {{
|
|
|
+ treeDataItem[fieldNames.label].substring(
|
|
|
+ treeDataItem[fieldNames.label].indexOf(searchListData) +
|
|
|
+ searchListData.length,
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ <span v-else>{{ treeDataItem[fieldNames.label] }}</span>
|
|
|
+ </slot>
|
|
|
</template>
|
|
|
</DirectoryTree>
|
|
|
<div
|