From 39820c783cf4160e1ee7cff168bbaf8d4dce45ff Mon Sep 17 00:00:00 2001 From: zouawen <846027729@qq.com> Date: Thu, 11 Sep 2025 16:41:41 +0800 Subject: [PATCH] =?UTF-8?q?fix:=201=E3=80=81VbenTree=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E5=85=A8=E9=80=89=E3=80=81=E5=B1=95=E5=BC=80?= =?UTF-8?q?=E6=8A=98=E5=8F=A0=E5=8A=9F=E8=83=BD=EF=BC=9B=202=E3=80=81?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=BD=93=E7=82=B9=E5=87=BB=E5=AD=90=E8=8A=82?= =?UTF-8?q?=E7=82=B9label=E6=96=87=E5=AD=97=E5=8C=BA=E5=9F=9F=EF=BC=8C?= =?UTF-8?q?=E8=80=8C=E9=9D=9Echeckbox=E6=97=B6=EF=BC=8C=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=88=B6=E7=BB=84=E4=BB=B6=E4=B8=8D=E8=83=BD=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9B=203=E3=80=81=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=AD=90=E8=8A=82=E7=82=B9=E9=80=89=E4=B8=AD=E6=97=B6=E5=85=B3?= =?UTF-8?q?=E8=81=94=E7=88=B6=E8=8A=82=E7=82=B9=E9=80=89=E4=B8=AD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=9A=E5=88=A0=E9=99=A4VbenTree=E4=B8=ADprocessPar?= =?UTF-8?q?entSelection=E6=96=B9=E6=B3=95=EF=BC=8C=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=9C=A8onSelect=E4=B8=AD=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=8E=9F?= =?UTF-8?q?=E5=9B=A0=EF=BC=9AprocessParentSelection=E5=9C=A8=E6=AF=8F?= =?UTF-8?q?=E6=AC=A1=E6=A8=A1=E5=9E=8B=E5=80=BC=E6=9B=B4=E6=96=B0=E6=97=B6?= =?UTF-8?q?=E9=83=BD=E4=BC=9A=E8=A2=AB=E8=B0=83=E7=94=A8=EF=BC=8C=E4=B8=94?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E5=A4=8D=E6=9D=82=E5=BA=A6=E4=B8=BAO(n^2)?= =?UTF-8?q?=EF=BC=8ConSelect=E5=8F=AA=E5=9C=A8=E4=BA=A4=E4=BA=92=E6=97=B6?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=EF=BC=8C=E5=A4=8D=E6=9D=82=E5=BA=A6=E4=B8=BA?= =?UTF-8?q?O(n)=EF=BC=9B=204=E3=80=81=E6=96=B0=E5=A2=9E=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E5=B1=82tree=E7=BB=84=E4=BB=B6=EF=BC=8C=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=97=A0=E6=95=B0=E6=8D=AE=E6=97=B6=E6=98=BE=E7=A4=BA=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=EF=BC=88=E6=98=BE=E7=A4=BA=E5=9B=BE=E6=A0=87Inbox?= =?UTF-8?q?=E5=92=8C=E5=9B=BD=E9=99=85=E5=8C=96comom.noData=E6=96=87?= =?UTF-8?q?=E6=9C=AC=EF=BC=89=EF=BC=9B=205=E3=80=81=E4=B8=BA=E9=98=B2?= =?UTF-8?q?=E6=AD=A2=E7=88=B6=E7=BB=84=E4=BB=B6=E4=BC=A0=E5=80=BC=E5=AD=90?= =?UTF-8?q?=E7=BB=84=E4=BB=B6boolean=E7=B1=BB=E5=9E=8B=E9=BB=98=E8=AE=A4fa?= =?UTF-8?q?lse=E9=97=AE=E9=A2=98=EF=BC=8C=E6=96=B0=E5=A2=9EtreePropsDefaul?= =?UTF-8?q?ts=E6=96=B9=E6=B3=95=EF=BC=8C=E4=B8=BATreeProps=E8=B5=8B?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=80=BC=EF=BC=8CTree=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=92=8CVbenTree=E7=BB=84=E4=BB=B6=E7=BB=9F=E4=B8=80=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=EF=BC=9B=206=E3=80=81=E4=BC=98=E5=8C=96VbenTree?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=95=B4=E4=BD=93=E6=A0=B7=E5=BC=8F=EF=BC=88?= =?UTF-8?q?=E4=BC=98=E5=8C=96padding=E3=80=81margin=E3=80=81gap=E5=80=BC?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96type=E4=B8=BAbutton=E6=97=B6outline?= =?UTF-8?q?=E5=B7=A6=E5=8F=B3=E7=A9=BA=E7=99=BD=E5=8C=BA=E5=9F=9F=E4=B8=8D?= =?UTF-8?q?=E5=AF=B9=E7=A7=B0=E9=97=AE=E9=A2=98=EF=BC=89=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=86=85=E9=83=A8header=E3=80=81footer=E6=8F=92?= =?UTF-8?q?=E6=A7=BD=E6=A0=B7=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@core/base/icons/src/lucide.ts | 1 + .../ui-kit/shadcn-ui/src/ui/tree/index.ts | 2 + .../ui-kit/shadcn-ui/src/ui/tree/tree.vue | 249 ++++++++++-------- .../ui-kit/shadcn-ui/src/ui/tree/types.ts | 21 ++ .../effects/common-ui/src/components/index.ts | 2 +- .../common-ui/src/components/tree/index.ts | 1 + .../common-ui/src/components/tree/tree.vue | 25 ++ .../src/views/system/role/modules/form.vue | 9 +- 8 files changed, 198 insertions(+), 112 deletions(-) create mode 100644 packages/effects/common-ui/src/components/tree/index.ts create mode 100644 packages/effects/common-ui/src/components/tree/tree.vue diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts index 70e6a426..a167aea0 100644 --- a/packages/@core/base/icons/src/lucide.ts +++ b/packages/@core/base/icons/src/lucide.ts @@ -32,6 +32,7 @@ export { Grip, GripVertical, Menu as IconDefault, + Inbox, Info, InspectionPanel, Languages, diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/index.ts b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/index.ts index 1abd4c50..45162b8a 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/index.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/index.ts @@ -1,2 +1,4 @@ export { default as VbenTree } from './tree.vue'; +export type { TreeProps } from './types'; +export { treePropsDefaults } from './types'; export type { FlattenedItem } from 'radix-vue'; 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 3e497c51..5f50d2df 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 @@ -14,25 +14,9 @@ import { cn, get } from '@vben-core/shared/utils'; import { TreeItem, TreeRoot } from 'radix-vue'; import { Checkbox } from '../checkbox'; +import { treePropsDefaults } from './types'; -const props = withDefaults(defineProps(), { - allowClear: false, - autoCheckParent: true, - bordered: false, - checkStrictly: false, - defaultExpandedKeys: () => [], - defaultExpandedLevel: 0, - disabled: false, - disabledField: 'disabled', - expanded: () => [], - iconField: 'icon', - labelField: 'label', - multiple: false, - showIcon: true, - transition: true, - valueField: 'value', - childrenField: 'children', -}); +const props = withDefaults(defineProps(), treePropsDefaults()); const emits = defineEmits<{ expand: [value: FlattenedItem>]; @@ -41,7 +25,9 @@ const emits = defineEmits<{ interface InnerFlattenItem, P = number | string> { hasChildren: boolean; + id: P; level: number; + parentId: null | P; parents: P[]; value: T; } @@ -50,24 +36,25 @@ function flatten, P = number | string>( items: T[], childrenField: string = 'children', level = 0, + parentId: null | P = null, parents: P[] = [], ): InnerFlattenItem[] { const result: InnerFlattenItem[] = []; items.forEach((item) => { const children = get(item, childrenField) as Array; - const val = { + const id = get(item, props.valueField) as P; + const val: InnerFlattenItem = { hasChildren: Array.isArray(children) && children.length > 0, + id, level, + parentId, parents: [...parents], value: item, }; result.push(val); if (val.hasChildren) result.push( - ...flatten(children, childrenField, level + 1, [ - ...parents, - get(item, props.valueField), - ]), + ...flatten(children, childrenField, level + 1, id, [...parents, id]), ); }); return result; @@ -103,15 +90,10 @@ function updateTreeValue() { treeValue.value = undefined; } else { if (Array.isArray(val)) { - let filteredValues = val.filter((v) => { + const filteredValues = val.filter((v) => { const item = getItemByValue(v); return item && !get(item, props.disabledField); }); - - if (!props.checkStrictly && props.autoCheckParent) { - filteredValues = processParentSelection(filteredValues); - } - treeValue.value = filteredValues.map((v) => getItemByValue(v)); if (filteredValues.length !== val.length) { @@ -128,35 +110,7 @@ function updateTreeValue() { } } } -function processParentSelection( - selectedValues: Array, -): Array { - if (props.checkStrictly) return selectedValues; - const result = [...selectedValues]; - - for (let i = result.length - 1; i >= 0; i--) { - const currentValue = result[i]; - if (currentValue === undefined) continue; - const currentItem = getItemByValue(currentValue); - - if (!currentItem) continue; - - const children = get(currentItem, props.childrenField); - if (Array.isArray(children) && children.length > 0) { - const hasSelectedChildren = children.some((child) => { - const childValue = get(child, props.valueField); - return result.includes(childValue); - }); - - if (!hasSelectedChildren) { - result.splice(i, 1); - } - } - } - - return result; -} function updateModelValue(val: Arrayable>) { if (Array.isArray(val)) { const filteredVal = val.filter((v) => !get(v, props.disabledField)); @@ -204,6 +158,22 @@ function collapseAll() { expanded.value = []; } +function checkAll() { + if (props.multiple) { + modelValue.value = flattenData.value.map((item) => + get(item.value, props.valueField), + ); + updateTreeValue(); + } +} + +function unCheckAll() { + if (props.multiple) { + modelValue.value = []; + updateTreeValue(); + } +} + function isNodeDisabled(item: FlattenedItem>) { return props.disabled || get(item.value, props.disabledField); } @@ -229,8 +199,45 @@ function onSelect(item: FlattenedItem>, isSelected: boolean) { ); }) ?.parents?.forEach((p) => { - if (Array.isArray(modelValue.value) && !modelValue.value.includes(p)) { - modelValue.value.push(p); + if (Array.isArray(modelValue.value) && !modelValue.value.includes(p)) { + modelValue.value.push(p); + } + }); + } + if ( + !props.checkStrictly && + props.multiple && + props.autoCheckParent && + !isSelected + ) { + flattenData.value + .find((i) => { + return ( + get(i.value, props.valueField) === get(item.value, props.valueField) + ); + }) + ?.parents?.reverse() + .forEach((p) => { + const children = flattenData.value.filter((i) => { + return ( + i.parents.length > 0 && + i.parents.includes(p) && + i.id !== item._id && + i.parentId === p + ); + }); + if (Array.isArray(modelValue.value)) { + const hasSelectedChild = children.some((child) => + (modelValue.value as unknown[]).includes( + get(child.value, props.valueField), + ), + ); + if (!hasSelectedChild) { + const index = modelValue.value.indexOf(p); + if (index !== -1) { + modelValue.value.splice(index, 1); + } + } } }); } @@ -243,6 +250,8 @@ defineExpose({ collapseNodes, expandAll, expandNodes, + checkAll, + unCheckAll, expandToLevel, getItemByValue, }); @@ -263,15 +272,41 @@ defineExpose({ v-slot="{ flattenItems }" :class=" cn( - 'text-blackA11 container select-none list-none rounded-lg p-2 text-sm font-medium', + 'text-blackA11 container select-none list-none rounded-lg text-sm font-medium', $attrs.class as unknown as ClassType, bordered ? 'border' : '', ) " > -
+
+
+
+ + +
+
-
- -
-
+
+ -
+
- - - {{ get(item.value, labelField) }} - + " + > + + + {{ get(item.value, labelField) }} + +
+
-
+
diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts index 97b09139..72dc19c4 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts @@ -40,3 +40,24 @@ export interface TreeProps { /** 值字段 */ valueField?: string; } + +export function treePropsDefaults() { + return { + allowClear: false, + autoCheckParent: true, + bordered: false, + checkStrictly: false, + defaultExpandedKeys: () => [], + defaultExpandedLevel: 0, + disabled: false, + disabledField: 'disabled', + expanded: () => [], + iconField: 'icon', + labelField: 'label', + multiple: false, + showIcon: true, + transition: true, + valueField: 'value', + childrenField: 'children', + }; +} diff --git a/packages/effects/common-ui/src/components/index.ts b/packages/effects/common-ui/src/components/index.ts index 543fcf3c..5914d44c 100644 --- a/packages/effects/common-ui/src/components/index.ts +++ b/packages/effects/common-ui/src/components/index.ts @@ -9,6 +9,7 @@ export * from './loading'; export * from './page'; export * from './resize'; export * from './tippy'; +export * from './tree'; export * from '@vben-core/form-ui'; export * from '@vben-core/popup-ui'; @@ -27,7 +28,6 @@ export { VbenPinInput, VbenSelect, VbenSpinner, - VbenTree, } from '@vben-core/shadcn-ui'; export type { FlattenedItem } from '@vben-core/shadcn-ui'; diff --git a/packages/effects/common-ui/src/components/tree/index.ts b/packages/effects/common-ui/src/components/tree/index.ts new file mode 100644 index 00000000..ce3bc5c6 --- /dev/null +++ b/packages/effects/common-ui/src/components/tree/index.ts @@ -0,0 +1 @@ +export { default as Tree } from './tree.vue'; diff --git a/packages/effects/common-ui/src/components/tree/tree.vue b/packages/effects/common-ui/src/components/tree/tree.vue new file mode 100644 index 00000000..1f2fcc17 --- /dev/null +++ b/packages/effects/common-ui/src/components/tree/tree.vue @@ -0,0 +1,25 @@ + + + diff --git a/playground/src/views/system/role/modules/form.vue b/playground/src/views/system/role/modules/form.vue index e3c1d02d..511fb04b 100644 --- a/playground/src/views/system/role/modules/form.vue +++ b/playground/src/views/system/role/modules/form.vue @@ -7,7 +7,7 @@ import type { SystemRoleApi } from '#/api/system/role'; import { computed, nextTick, ref } from 'vue'; -import { useVbenDrawer, VbenTree } from '@vben/common-ui'; +import { Tree, useVbenDrawer } from '@vben/common-ui'; import { IconifyIcon } from '@vben/icons'; import { Spin } from 'ant-design-vue'; @@ -92,9 +92,6 @@ function getNodeClass(node: Recordable) { const classes: string[] = []; if (node.value?.type === 'button') { classes.push('inline-flex'); - if (node.index % 3 >= 1) { - classes.push('!pl-0'); - } } return classes.join(' '); @@ -105,7 +102,7 @@ function getNodeClass(node: Recordable) {
- +