|
@@ -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
|
|
|
|
|
- v-if="multiple"
|
|
|
|
|
- @click.stop
|
|
|
|
|
- @update:model-value="
|
|
|
|
|
- (checked: boolean | 'indeterminate') =>
|
|
|
|
|
- checked === true ? checkAll() : unCheckAll()
|
|
|
|
|
- "
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <div class="flex items-center gap-1 item-all-checkbox">
|
|
|
|
|
+ <Checkbox
|
|
|
|
|
+ v-if="multiple"
|
|
|
|
|
+ :model-value="selectAllStatus"
|
|
|
|
|
+ :indeterminate="selectAllStatus === 'indeterminate'"
|
|
|
|
|
+ @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,
|