diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue index 9ed9e3e9d..81d5e4eda 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue @@ -6,7 +6,7 @@ import type { ClassType, Recordable } from '@vben-core/typings'; import type { TreeProps } from './types'; -import { onMounted, ref, watchEffect } from 'vue'; +import { computed, onMounted, ref, watchEffect } from 'vue'; import { ChevronRight, IconifyIcon } from '@vben-core/icons'; import { cn, get } from '@vben-core/shared/utils'; @@ -192,6 +192,32 @@ function isNodeDisabled(item: FlattenedItem>) { return props.disabled || get(item.value, props.disabledField); } +// 计算全选/半选状态 +const selectAllStatus = computed<'indeterminate' | boolean>(() => { + if (!props.multiple) return false; + if (!modelValue.value || !Array.isArray(modelValue.value)) return false; + + const allValues = flattenData.value + .filter((item) => !get(item.value, props.disabledField)) + .map((item) => get(item.value, props.valueField)); + + const selectedCount = allValues.filter((v) => + (modelValue.value as (number | string)[]).includes(v), + ).length; + + if (selectedCount === 0) return false; + if (selectedCount === allValues.length) return true; + return 'indeterminate'; +}); + +function onSelectAllChange(checked: 'indeterminate' | boolean) { + if (checked === true) { + checkAll(); + } else { + unCheckAll(); + } +} + function onToggle(item: FlattenedItem>) { emits('expand', item); } @@ -316,14 +342,16 @@ defineExpose({ :class="{ 'rotate-90': expanded?.length > 0 }" class="text-foreground/80 hover:text-foreground size-4 cursor-pointer transition" /> - +
+ + {{ selectAllLabel }} +
@@ -371,8 +399,9 @@ defineExpose({ !isNodeDisabled(item) && onToggle(item); } " - class="tree-node focus:ring-grass8 my-0.5 flex items-center rounded p-1 outline-hidden focus:ring-2" + class="tree-node focus:ring-grass8 my-0.5 flex items-center rounded p-1 outline-hidden" > +
-
+