Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
commit
c0476613d7
@ -44,6 +44,7 @@ import { FileUpload, ImageUpload } from '#/components/upload';
|
|||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
|
componentProps: Recordable<any> = {},
|
||||||
) => {
|
) => {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
@ -74,7 +75,13 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
return () =>
|
return () =>
|
||||||
h(
|
h(
|
||||||
component,
|
component,
|
||||||
{ ...props, ...attrs, placeholder: placeholder.value, ref: innerRef },
|
{
|
||||||
|
...componentProps,
|
||||||
|
placeholder: placeholder.value,
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
ref: innerRef,
|
||||||
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -118,38 +125,20 @@ async function initComponentAdapter() {
|
|||||||
// 如果你的组件体积比较大,可以使用异步加载
|
// 如果你的组件体积比较大,可以使用异步加载
|
||||||
// Button: () =>
|
// Button: () =>
|
||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
ApiSelect: (props, { attrs, slots }) => {
|
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||||
return h(
|
component: Select,
|
||||||
ApiComponent,
|
loadingSlot: 'suffixIcon',
|
||||||
{
|
visibleEvent: 'onDropdownVisibleChange',
|
||||||
placeholder: $t('ui.placeholder.select'),
|
modelPropName: 'value',
|
||||||
...props,
|
}),
|
||||||
...attrs,
|
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||||
component: Select,
|
component: TreeSelect,
|
||||||
loadingSlot: 'suffixIcon',
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
visibleEvent: 'onDropdownVisibleChange',
|
loadingSlot: 'suffixIcon',
|
||||||
modelPropName: 'value',
|
modelPropName: 'value',
|
||||||
},
|
optionsPropName: 'treeData',
|
||||||
slots,
|
visibleEvent: 'onVisibleChange',
|
||||||
);
|
}),
|
||||||
},
|
|
||||||
ApiTreeSelect: (props, { attrs, slots }) => {
|
|
||||||
return h(
|
|
||||||
ApiComponent,
|
|
||||||
{
|
|
||||||
placeholder: $t('ui.placeholder.select'),
|
|
||||||
...props,
|
|
||||||
...attrs,
|
|
||||||
component: TreeSelect,
|
|
||||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
|
||||||
loadingSlot: 'suffixIcon',
|
|
||||||
modelPropName: 'value',
|
|
||||||
optionsPropName: 'treeData',
|
|
||||||
visibleEvent: 'onVisibleChange',
|
|
||||||
},
|
|
||||||
slots,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
CheckboxGroup,
|
CheckboxGroup,
|
||||||
@ -159,19 +148,11 @@ async function initComponentAdapter() {
|
|||||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||||
},
|
},
|
||||||
Divider,
|
Divider,
|
||||||
IconPicker: (props, { attrs, slots }) => {
|
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
|
||||||
return h(
|
iconSlot: 'addonAfter',
|
||||||
IconPicker,
|
inputComponent: Input,
|
||||||
{
|
modelValueProp: 'value',
|
||||||
iconSlot: 'addonAfter',
|
}),
|
||||||
inputComponent: Input,
|
|
||||||
modelValueProp: 'value',
|
|
||||||
...props,
|
|
||||||
...attrs,
|
|
||||||
},
|
|
||||||
slots,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
Input: withDefaultPlaceholder(Input, 'input'),
|
Input: withDefaultPlaceholder(Input, 'input'),
|
||||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||||
|
@ -4,7 +4,9 @@ import type { FlattenedItem } from 'radix-vue';
|
|||||||
|
|
||||||
import type { ClassType, Recordable } from '@vben-core/typings';
|
import type { ClassType, Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
import { onMounted, ref, watch, watchEffect } from 'vue';
|
import type { TreeProps } from './types';
|
||||||
|
|
||||||
|
import { 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';
|
||||||
@ -14,46 +16,13 @@ import { TreeItem, TreeRoot } from 'radix-vue';
|
|||||||
|
|
||||||
import { Checkbox } from '../checkbox';
|
import { Checkbox } from '../checkbox';
|
||||||
|
|
||||||
interface TreeProps {
|
|
||||||
/** 单选时允许取消已有选项 */
|
|
||||||
allowClear?: boolean;
|
|
||||||
/** 显示边框 */
|
|
||||||
bordered?: boolean;
|
|
||||||
/** 取消父子关联选择 */
|
|
||||||
checkStrictly?: boolean;
|
|
||||||
/** 子级字段名 */
|
|
||||||
childrenField?: string;
|
|
||||||
/** 默认展开的键 */
|
|
||||||
defaultExpandedKeys?: Array<number | string>;
|
|
||||||
/** 默认展开的级别(优先级高于defaultExpandedKeys) */
|
|
||||||
defaultExpandedLevel?: number;
|
|
||||||
/** 默认值 */
|
|
||||||
defaultValue?: Arrayable<number | string>;
|
|
||||||
/** 禁用 */
|
|
||||||
disabled?: boolean;
|
|
||||||
/** 自定义节点类名 */
|
|
||||||
getNodeClass?: (item: FlattenedItem<Recordable<any>>) => string;
|
|
||||||
iconField?: string;
|
|
||||||
/** label字段 */
|
|
||||||
labelField?: string;
|
|
||||||
/** 当前值 */
|
|
||||||
modelValue?: Arrayable<number | string>;
|
|
||||||
/** 是否多选 */
|
|
||||||
multiple?: boolean;
|
|
||||||
/** 显示由iconField指定的图标 */
|
|
||||||
showIcon?: boolean;
|
|
||||||
/** 启用展开收缩动画 */
|
|
||||||
transition?: boolean;
|
|
||||||
/** 树数据 */
|
|
||||||
treeData: Recordable<any>[];
|
|
||||||
/** 值字段 */
|
|
||||||
valueField?: string;
|
|
||||||
}
|
|
||||||
const props = withDefaults(defineProps<TreeProps>(), {
|
const props = withDefaults(defineProps<TreeProps>(), {
|
||||||
allowClear: false,
|
allowClear: false,
|
||||||
|
autoCheckParent: true,
|
||||||
bordered: false,
|
bordered: false,
|
||||||
checkStrictly: false,
|
checkStrictly: false,
|
||||||
defaultExpandedKeys: () => [],
|
defaultExpandedKeys: () => [],
|
||||||
|
defaultExpandedLevel: 0,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
expanded: () => [],
|
expanded: () => [],
|
||||||
iconField: 'icon',
|
iconField: 'icon',
|
||||||
@ -61,7 +30,7 @@ const props = withDefaults(defineProps<TreeProps>(), {
|
|||||||
modelValue: () => [],
|
modelValue: () => [],
|
||||||
multiple: false,
|
multiple: false,
|
||||||
showIcon: true,
|
showIcon: true,
|
||||||
transition: false,
|
transition: true,
|
||||||
valueField: 'value',
|
valueField: 'value',
|
||||||
childrenField: 'children',
|
childrenField: 'children',
|
||||||
});
|
});
|
||||||
@ -72,28 +41,36 @@ const emits = defineEmits<{
|
|||||||
'update:modelValue': [value: Arrayable<Recordable<any>>];
|
'update:modelValue': [value: Arrayable<Recordable<any>>];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
interface InnerFlattenItem<T = Recordable<any>> {
|
interface InnerFlattenItem<T = Recordable<any>, P = number | string> {
|
||||||
hasChildren: boolean;
|
hasChildren: boolean;
|
||||||
level: number;
|
level: number;
|
||||||
|
parents: P[];
|
||||||
value: T;
|
value: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
function flatten<T = Recordable<any>>(
|
function flatten<T = Recordable<any>, P = number | string>(
|
||||||
items: T[],
|
items: T[],
|
||||||
childrenField: string = 'children',
|
childrenField: string = 'children',
|
||||||
level = 0,
|
level = 0,
|
||||||
): InnerFlattenItem<T>[] {
|
parents: P[] = [],
|
||||||
const result: InnerFlattenItem<T>[] = [];
|
): InnerFlattenItem<T, P>[] {
|
||||||
|
const result: InnerFlattenItem<T, P>[] = [];
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
const children = get(item, childrenField) as Array<T>;
|
const children = get(item, childrenField) as Array<T>;
|
||||||
const val = {
|
const val = {
|
||||||
hasChildren: Array.isArray(children) && children.length > 0,
|
hasChildren: Array.isArray(children) && children.length > 0,
|
||||||
level,
|
level,
|
||||||
|
parents: [...parents],
|
||||||
value: item,
|
value: item,
|
||||||
};
|
};
|
||||||
result.push(val);
|
result.push(val);
|
||||||
if (val.hasChildren)
|
if (val.hasChildren)
|
||||||
result.push(...flatten(children, childrenField, level + 1));
|
result.push(
|
||||||
|
...flatten(children, childrenField, level + 1, [
|
||||||
|
...parents,
|
||||||
|
get(item, props.valueField),
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -133,14 +110,6 @@ function updateTreeValue() {
|
|||||||
: getItemByValue(val);
|
: getItemByValue(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
|
||||||
modelValue,
|
|
||||||
() => {
|
|
||||||
updateTreeValue();
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
function updateModelValue(val: Arrayable<Recordable<any>>) {
|
function updateModelValue(val: Arrayable<Recordable<any>>) {
|
||||||
modelValue.value = Array.isArray(val)
|
modelValue.value = Array.isArray(val)
|
||||||
? val.map((v) => get(v, props.valueField))
|
? val.map((v) => get(v, props.valueField))
|
||||||
@ -186,7 +155,33 @@ function collapseAll() {
|
|||||||
function onToggle(item: FlattenedItem<Recordable<any>>) {
|
function onToggle(item: FlattenedItem<Recordable<any>>) {
|
||||||
emits('expand', item);
|
emits('expand', item);
|
||||||
}
|
}
|
||||||
function onSelect(item: FlattenedItem<Recordable<any>>) {
|
function onSelect(item: FlattenedItem<Recordable<any>>, isSelected: boolean) {
|
||||||
|
if (
|
||||||
|
!props.checkStrictly &&
|
||||||
|
props.multiple &&
|
||||||
|
props.autoCheckParent &&
|
||||||
|
isSelected
|
||||||
|
) {
|
||||||
|
flattenData.value
|
||||||
|
.find((i) => {
|
||||||
|
return (
|
||||||
|
get(i.value, props.valueField) === get(item.value, props.valueField)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
?.parents?.forEach((p) => {
|
||||||
|
if (Array.isArray(modelValue.value) && !modelValue.value.includes(p)) {
|
||||||
|
modelValue.value.push(p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (Array.isArray(modelValue.value)) {
|
||||||
|
const index = modelValue.value.indexOf(get(item.value, props.valueField));
|
||||||
|
if (index !== -1) {
|
||||||
|
modelValue.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateTreeValue();
|
||||||
emits('select', item);
|
emits('select', item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,78 +219,130 @@ defineExpose({
|
|||||||
<div class="w-full" v-if="$slots.header">
|
<div class="w-full" v-if="$slots.header">
|
||||||
<slot name="header"> </slot>
|
<slot name="header"> </slot>
|
||||||
</div>
|
</div>
|
||||||
<TreeItem
|
<TransitionGroup
|
||||||
v-for="item in flattenItems"
|
:name="transition ? 'fade' : ''"
|
||||||
v-slot="{
|
mode="out-in"
|
||||||
isExpanded,
|
class="container"
|
||||||
isSelected,
|
|
||||||
isIndeterminate,
|
|
||||||
handleSelect,
|
|
||||||
handleToggle,
|
|
||||||
}"
|
|
||||||
:key="item._id"
|
|
||||||
:style="{ 'padding-left': `${item.level - 0.5}rem` }"
|
|
||||||
:class="
|
|
||||||
cn('cursor-pointer', getNodeClass?.(item), {
|
|
||||||
'data-[selected]:bg-accent': !multiple,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
v-bind="item.bind"
|
|
||||||
@select="
|
|
||||||
(event) => {
|
|
||||||
if (event.detail.originalEvent.type === 'click') {
|
|
||||||
// event.preventDefault();
|
|
||||||
}
|
|
||||||
onSelect(item);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@toggle="
|
|
||||||
(event) => {
|
|
||||||
if (event.detail.originalEvent.type === 'click') {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
onToggle(item);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
class="tree-node focus:ring-grass8 my-0.5 flex items-center rounded px-2 py-1 outline-none focus:ring-2"
|
|
||||||
>
|
>
|
||||||
<ChevronRight
|
<TreeItem
|
||||||
v-if="item.hasChildren"
|
v-for="item in flattenItems"
|
||||||
class="size-4 cursor-pointer transition"
|
v-slot="{
|
||||||
:class="{ 'rotate-90': isExpanded }"
|
isExpanded,
|
||||||
@click.stop="handleToggle"
|
isSelected,
|
||||||
/>
|
isIndeterminate,
|
||||||
<div v-else class="h-4 w-4">
|
handleSelect,
|
||||||
<!-- <IconifyIcon v-if="item.value.icon" :icon="item.value.icon" /> -->
|
handleToggle,
|
||||||
</div>
|
}"
|
||||||
<Checkbox
|
:key="item._id"
|
||||||
v-if="multiple"
|
:style="{ 'padding-left': `${item.level - 0.5}rem` }"
|
||||||
:checked="isSelected"
|
:class="
|
||||||
:indeterminate="isIndeterminate"
|
cn('cursor-pointer', getNodeClass?.(item), {
|
||||||
@click.stop="handleSelect"
|
'data-[selected]:bg-accent': !multiple,
|
||||||
/>
|
})
|
||||||
<div
|
"
|
||||||
class="flex items-center gap-1 pl-2"
|
v-bind="item.bind"
|
||||||
@click="
|
@select="
|
||||||
($event) => {
|
(event) => {
|
||||||
$event.stopPropagation();
|
if (event.detail.originalEvent.type === 'click') {
|
||||||
$event.preventDefault();
|
// event.preventDefault();
|
||||||
handleSelect();
|
}
|
||||||
|
onSelect(item, event.detail.isSelected);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@toggle="
|
||||||
|
(event) => {
|
||||||
|
if (event.detail.originalEvent.type === 'click') {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
onToggle(item);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
class="tree-node focus:ring-grass8 my-0.5 flex items-center rounded px-2 py-1 outline-none focus:ring-2"
|
||||||
>
|
>
|
||||||
<slot name="node" v-bind="item">
|
<ChevronRight
|
||||||
<IconifyIcon
|
v-if="item.hasChildren"
|
||||||
class="size-4"
|
class="size-4 cursor-pointer transition"
|
||||||
v-if="showIcon && get(item.value, iconField)"
|
:class="{ 'rotate-90': isExpanded }"
|
||||||
:icon="get(item.value, iconField)"
|
@click.stop="
|
||||||
/>
|
() => {
|
||||||
{{ get(item.value, labelField) }}
|
handleToggle();
|
||||||
</slot>
|
onToggle(item);
|
||||||
</div>
|
}
|
||||||
</TreeItem>
|
"
|
||||||
|
/>
|
||||||
|
<div v-else class="h-4 w-4">
|
||||||
|
<!-- <IconifyIcon v-if="item.value.icon" :icon="item.value.icon" /> -->
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
v-if="multiple"
|
||||||
|
:checked="isSelected"
|
||||||
|
:indeterminate="isIndeterminate"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
handleSelect();
|
||||||
|
// onSelect(item, !isSelected);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-1 pl-2"
|
||||||
|
@click="
|
||||||
|
(_event) => {
|
||||||
|
// $event.stopPropagation();
|
||||||
|
// $event.preventDefault();
|
||||||
|
handleSelect();
|
||||||
|
// onSelect(item, !isSelected);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot name="node" v-bind="item">
|
||||||
|
<IconifyIcon
|
||||||
|
class="size-4"
|
||||||
|
v-if="showIcon && get(item.value, iconField)"
|
||||||
|
:icon="get(item.value, iconField)"
|
||||||
|
/>
|
||||||
|
{{ get(item.value, labelField) }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</TreeItem>
|
||||||
|
</TransitionGroup>
|
||||||
<div class="w-full" v-if="$slots.footer">
|
<div class="w-full" v-if="$slots.footer">
|
||||||
<slot name="footer"> </slot>
|
<slot name="footer"> </slot>
|
||||||
</div>
|
</div>
|
||||||
</TreeRoot>
|
</TreeRoot>
|
||||||
</template>
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
border: 1px solid #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. 声明过渡效果 */
|
||||||
|
.fade-move,
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 声明进入和离开的状态 */
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleY(0.01) translate(30px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 确保离开的项目被移除出了布局流
|
||||||
|
以便正确地计算移动时的动画效果。 */
|
||||||
|
.fade-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
42
packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts
Normal file
42
packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import type { Arrayable } from '@vueuse/core';
|
||||||
|
import type { FlattenedItem } from 'radix-vue';
|
||||||
|
|
||||||
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
|
export interface TreeProps {
|
||||||
|
/** 单选时允许取消已有选项 */
|
||||||
|
allowClear?: boolean;
|
||||||
|
/** 非关联选择时,自动选中上级节点 */
|
||||||
|
autoCheckParent?: boolean;
|
||||||
|
/** 显示边框 */
|
||||||
|
bordered?: boolean;
|
||||||
|
/** 取消父子关联选择 */
|
||||||
|
checkStrictly?: boolean;
|
||||||
|
/** 子级字段名 */
|
||||||
|
childrenField?: string;
|
||||||
|
/** 默认展开的键 */
|
||||||
|
defaultExpandedKeys?: Array<number | string>;
|
||||||
|
/** 默认展开的级别(优先级高于defaultExpandedKeys) */
|
||||||
|
defaultExpandedLevel?: number;
|
||||||
|
/** 默认值 */
|
||||||
|
defaultValue?: Arrayable<number | string>;
|
||||||
|
/** 禁用 */
|
||||||
|
disabled?: boolean;
|
||||||
|
/** 自定义节点类名 */
|
||||||
|
getNodeClass?: (item: FlattenedItem<Recordable<any>>) => string;
|
||||||
|
iconField?: string;
|
||||||
|
/** label字段 */
|
||||||
|
labelField?: string;
|
||||||
|
/** 当前值 */
|
||||||
|
modelValue?: Arrayable<number | string>;
|
||||||
|
/** 是否多选 */
|
||||||
|
multiple?: boolean;
|
||||||
|
/** 显示由iconField指定的图标 */
|
||||||
|
showIcon?: boolean;
|
||||||
|
/** 启用展开收缩动画 */
|
||||||
|
transition?: boolean;
|
||||||
|
/** 树数据 */
|
||||||
|
treeData: Recordable<any>[];
|
||||||
|
/** 值字段 */
|
||||||
|
valueField?: string;
|
||||||
|
}
|
@ -84,7 +84,7 @@ const emit = defineEmits<{
|
|||||||
const modelValue = defineModel({ default: '' });
|
const modelValue = defineModel({ default: '' });
|
||||||
|
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
|
const innerParams = ref({});
|
||||||
const refOptions = ref<OptionsItem[]>([]);
|
const refOptions = ref<OptionsItem[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
// 首次是否加载过了
|
// 首次是否加载过了
|
||||||
@ -175,8 +175,15 @@ async function handleFetchForVisible(visible: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const params = computed(() => {
|
||||||
|
return {
|
||||||
|
...props.params,
|
||||||
|
...unref(innerParams),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.params,
|
params,
|
||||||
(value, oldValue) => {
|
(value, oldValue) => {
|
||||||
if (isEqual(value, oldValue)) {
|
if (isEqual(value, oldValue)) {
|
||||||
return;
|
return;
|
||||||
@ -189,12 +196,22 @@ watch(
|
|||||||
function emitChange() {
|
function emitChange() {
|
||||||
emit('optionsChange', unref(getOptions));
|
emit('optionsChange', unref(getOptions));
|
||||||
}
|
}
|
||||||
|
const componentRef = ref();
|
||||||
|
defineExpose({
|
||||||
|
/** 获取被包装的组件实例 */
|
||||||
|
getComponentRef: <T = any,>() => componentRef.value as T,
|
||||||
|
/** 更新Api参数 */
|
||||||
|
updateParam(newParams: Record<string, any>) {
|
||||||
|
innerParams.value = newParams;
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<component
|
<component
|
||||||
:is="component"
|
:is="component"
|
||||||
v-bind="bindProps"
|
v-bind="bindProps"
|
||||||
:placeholder="$attrs.placeholder"
|
:placeholder="$attrs.placeholder"
|
||||||
|
ref="componentRef"
|
||||||
>
|
>
|
||||||
<template v-for="item in Object.keys($slots)" #[item]="data">
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
||||||
<slot :name="item" v-bind="data || {}"></slot>
|
<slot :name="item" v-bind="data || {}"></slot>
|
||||||
|
@ -41,6 +41,7 @@ import {
|
|||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
|
componentProps: Recordable<any> = {},
|
||||||
) => {
|
) => {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
@ -63,7 +64,11 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return () =>
|
return () =>
|
||||||
h(component, { ...props, ...attrs, placeholder, ref: innerRef }, slots);
|
h(
|
||||||
|
component,
|
||||||
|
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
|
||||||
|
slots,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -103,38 +108,20 @@ async function initComponentAdapter() {
|
|||||||
// Button: () =>
|
// Button: () =>
|
||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
|
|
||||||
ApiSelect: (props, { attrs, slots }) => {
|
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||||
return h(
|
component: Select,
|
||||||
ApiComponent,
|
loadingSlot: 'suffixIcon',
|
||||||
{
|
modelPropName: 'value',
|
||||||
placeholder: $t('ui.placeholder.select'),
|
visibleEvent: 'onVisibleChange',
|
||||||
...props,
|
}),
|
||||||
...attrs,
|
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||||
component: Select,
|
component: TreeSelect,
|
||||||
loadingSlot: 'suffixIcon',
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
modelPropName: 'value',
|
loadingSlot: 'suffixIcon',
|
||||||
visibleEvent: 'onVisibleChange',
|
modelPropName: 'value',
|
||||||
},
|
optionsPropName: 'treeData',
|
||||||
slots,
|
visibleEvent: 'onVisibleChange',
|
||||||
);
|
}),
|
||||||
},
|
|
||||||
ApiTreeSelect: (props, { attrs, slots }) => {
|
|
||||||
return h(
|
|
||||||
ApiComponent,
|
|
||||||
{
|
|
||||||
placeholder: $t('ui.placeholder.select'),
|
|
||||||
...props,
|
|
||||||
...attrs,
|
|
||||||
component: TreeSelect,
|
|
||||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
|
||||||
loadingSlot: 'suffixIcon',
|
|
||||||
modelPropName: 'value',
|
|
||||||
optionsPropName: 'treeData',
|
|
||||||
visibleEvent: 'onVisibleChange',
|
|
||||||
},
|
|
||||||
slots,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
CheckboxGroup,
|
CheckboxGroup,
|
||||||
@ -144,19 +131,11 @@ async function initComponentAdapter() {
|
|||||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||||
},
|
},
|
||||||
Divider,
|
Divider,
|
||||||
IconPicker: (props, { attrs, slots }) => {
|
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
|
||||||
return h(
|
iconSlot: 'addonAfter',
|
||||||
IconPicker,
|
inputComponent: Input,
|
||||||
{
|
modelValueProp: 'value',
|
||||||
iconSlot: 'addonAfter',
|
}),
|
||||||
inputComponent: Input,
|
|
||||||
modelValueProp: 'value',
|
|
||||||
...props,
|
|
||||||
...attrs,
|
|
||||||
},
|
|
||||||
slots,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
Input: withDefaultPlaceholder(Input, 'input'),
|
Input: withDefaultPlaceholder(Input, 'input'),
|
||||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||||
|
@ -98,7 +98,7 @@ function getNodeClass(node: Recordable<any>) {
|
|||||||
<Drawer :title="getDrawerTitle">
|
<Drawer :title="getDrawerTitle">
|
||||||
<Form>
|
<Form>
|
||||||
<template #permissions="slotProps">
|
<template #permissions="slotProps">
|
||||||
<Spin :spinning="loadingPermissions">
|
<Spin :spinning="loadingPermissions" wrapper-class-name="w-full">
|
||||||
<VbenTree
|
<VbenTree
|
||||||
:tree-data="permissions"
|
:tree-data="permissions"
|
||||||
multiple
|
multiple
|
||||||
|
Loading…
Reference in New Issue
Block a user