Files
nl-admin-view/playground/src/views/system/user/modules/form.vue
dream-weave 9f5b1cd9fb fix(@vben-core/form-ui)!: group field slot component props (#8215)
* refactor(@vben-core/form-ui)!: group field slot component props

* fix(@vben-core/form-ui): stabilize grouped field slot bindings

Resolve function-based common component props with field context.

Preserve internal model and event bindings when custom field slots spread componentProps.

Add regression coverage for array contexts, disabled precedence, and stale binding isolation.

* feat(@vben/vite-config): warn about field slot migration

Print the temporary field-slot migration warning when the development server starts.

Inject the same message into the browser console and keep the plugin serve-only.

* docs(@vben/docs): document field slot migration

Place the breaking field-slot notice at the start of both form documents.

Explain old and new bindings, development warnings, and planned notice removal.

* feat(@vben/playground): extend custom form example

Add a switchable input and select field to demonstrate schema component updates.

Update the composite phone field to emit new tuple values so validation observes changes.
2026-07-31 18:13:49 +08:00

119 lines
2.9 KiB
Vue

<script lang="ts" setup>
import type { DataNode } from 'antdv-next/dist/tree';
import type { Recordable } from '@vben/types';
import type { SystemUserApi } from '#/api/system/user';
import { computed, nextTick, ref } from 'vue';
import { Tree, useVbenDrawer } from '@vben/common-ui';
import { Spin } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';
import { getMenuList } from '#/api/system/menu';
import { createUser, updateUser } from '#/api/system/user';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emits = defineEmits(['success']);
const formData = ref<SystemUserApi.SystemUser>();
const [Form, formApi] = useVbenForm({
schema: useFormSchema(),
showDefaultActions: false,
});
const permissions = ref<DataNode[]>([]);
const loadingPermissions = ref(false);
const id = ref();
const [Drawer, drawerApi] = useVbenDrawer({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) return;
const values = await formApi.getValues();
drawerApi.lock();
(id.value ? updateUser(id.value, values) : createUser(values))
.then(() => {
emits('success');
drawerApi.close();
})
.catch(() => {
drawerApi.unlock();
});
},
async onOpenChange(isOpen) {
if (isOpen) {
const data = drawerApi.getData<SystemUserApi.SystemUser>();
formApi.reset();
if (data) {
formData.value = data;
id.value = data.id;
} else {
id.value = undefined;
}
if (permissions.value.length === 0) {
await loadPermissions();
}
// Wait for Vue to flush DOM updates (form fields mounted)
await nextTick();
if (data) {
formApi.setValues(data);
}
}
},
});
async function loadPermissions() {
loadingPermissions.value = true;
try {
const res = await getMenuList();
permissions.value = res as unknown as DataNode[];
} finally {
loadingPermissions.value = false;
}
}
const getDrawerTitle = computed(() => {
return formData.value?.id
? $t('common.edit', $t('system.user.name'))
: $t('common.create', $t('system.user.name'));
});
function getNodeClass(node: Recordable<any>) {
const classes: string[] = [];
if (node.value?.type === 'button') {
classes.push('inline-flex');
}
return classes.join(' ');
}
</script>
<template>
<Drawer :title="getDrawerTitle">
<Form>
<template #permissions="slotProps">
<Spin :spinning="loadingPermissions" :classes="{ root: 'w-full' }">
<Tree
:tree-data="permissions"
multiple
bordered
:default-expanded-level="2"
:get-node-class="getNodeClass"
v-bind="slotProps.componentProps"
value-field="id"
label-field="name"
/>
</Spin>
</template>
</Form>
</Drawer>
</template>