* 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.
119 lines
2.9 KiB
Vue
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>
|