feat: 1、完善tree组件的全选状态不正确、全选没有label、item内容超长导致复选框对齐错乱、item内容超长没有tips无法看到完整内容的问题 (#7915)

Co-authored-by: PanFu <panfu@zhihuaai.com>
master^2
PanFu 2026-05-16 10:44:55 +08:00 committed by GitHub
parent 4d8d2de6ad
commit 42d82875ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 12 deletions

View File

@ -6,7 +6,7 @@ import type { ClassType, Recordable } from '@vben-core/typings';
import type { TreeProps } from './types'; 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 { ChevronRight, IconifyIcon } from '@vben-core/icons';
import { cn, get } from '@vben-core/shared/utils'; import { cn, get } from '@vben-core/shared/utils';
@ -192,6 +192,32 @@ function isNodeDisabled(item: FlattenedItem<Recordable<any>>) {
return props.disabled || get(item.value, props.disabledField); 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<Recordable<any>>) { function onToggle(item: FlattenedItem<Recordable<any>>) {
emits('expand', item); emits('expand', item);
} }
@ -316,14 +342,16 @@ defineExpose({
:class="{ 'rotate-90': expanded?.length > 0 }" :class="{ 'rotate-90': expanded?.length > 0 }"
class="text-foreground/80 hover:text-foreground size-4 cursor-pointer transition" class="text-foreground/80 hover:text-foreground size-4 cursor-pointer transition"
/> />
<Checkbox <div class="flex items-center gap-1 item-all-checkbox">
v-if="multiple" <Checkbox
@click.stop v-if="multiple"
@update:model-value=" :model-value="selectAllStatus"
(checked: boolean | 'indeterminate') => :indeterminate="selectAllStatus === 'indeterminate'"
checked === true ? checkAll() : unCheckAll() @click.stop
" @update:model-value="onSelectAllChange"
/> />
<span v-if="selectAllLabel">{{ selectAllLabel }}</span>
</div>
</div> </div>
</div> </div>
<TransitionGroup :name="transition ? 'fade' : ''"> <TransitionGroup :name="transition ? 'fade' : ''">
@ -371,8 +399,9 @@ defineExpose({
!isNodeDisabled(item) && onToggle(item); !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"
> >
<!-- class="hover:ring-2" 鼠标移动上去时2px的圆环边框 -->
<ChevronRight <ChevronRight
v-if=" v-if="
item.hasChildren && item.hasChildren &&
@ -389,7 +418,7 @@ defineExpose({
" "
/> />
<div v-else class="h-4 w-4"></div> <div v-else class="h-4 w-4"></div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1 item-checkbox">
<Checkbox <Checkbox
v-if="multiple" v-if="multiple"
:model-value="isSelected && !isNodeDisabled(item)" :model-value="isSelected && !isNodeDisabled(item)"
@ -407,7 +436,8 @@ defineExpose({
" "
/> />
<div <div
class="flex items-center gap-1" class="flex items-center gap-1 item-checkbox"
:title="get(item.value, labelField)"
@click=" @click="
(event: MouseEvent) => { (event: MouseEvent) => {
if (isNodeDisabled(item)) { if (isNodeDisabled(item)) {
@ -457,6 +487,20 @@ defineExpose({
border: 1px solid #666; border: 1px solid #666;
} }
.item-checkbox {
width: 100%;
overflow: hidden;
}
.item-all-checkbox {
width: 100%;
overflow: hidden;
.text-label {
margin-left: 8px;
}
}
/* 1. 声明过渡效果 */ /* 1. 声明过渡效果 */
.fade-move, .fade-move,
.fade-enter-active, .fade-enter-active,

View File

@ -31,6 +31,8 @@ export interface TreeProps {
labelField?: string; labelField?: string;
/** 是否多选 */ /** 是否多选 */
multiple?: boolean; multiple?: boolean;
/** 选择全部时的文字 */
selectAllLabel?: string;
/** 显示由iconField指定的图标 */ /** 显示由iconField指定的图标 */
showIcon?: boolean; showIcon?: boolean;
/** 启用展开收缩动画 */ /** 启用展开收缩动画 */