Pattern d’intégration dans NFZ Studio
Dans NFZ Studio, FormBuilder doit rester un composant UI. La persistance doit être portée par un store métier ou un service Feathers/NFZ.
Architecture recommandée
txt
app/pages/console/forms/[id].vue
↓
app/stores/dynamicFormsStore.ts
↓
app/composables/useDynamicFormsRepository.ts
↓
service NFZ dynamic-forms
↓
MongoDBModèle métier conseillé
ts
// app/types/dynamic-form.ts
import type { FormKitSchemaDefinition } from '@formkit/core'
export type DynamicFormStatus = 'draft' | 'published' | 'archived'
export type DynamicFormDocument = {
_id?: string
name: string
slug: string
description?: string
schemaVersion: 1
schema: FormKitSchemaDefinition[]
settings: Record<string, unknown>
status: DynamicFormStatus
createdAt?: string
updatedAt?: string
}Repository côté client
Dans un projet NFZ, remplace les $fetch par ton accès Feathers centralisé, par exemple useAdminFeathers() ou un store métier.
ts
// app/composables/useDynamicFormsRepository.ts
import type { FormKitSchemaDefinition } from '@formkit/core'
export type DynamicFormDocument = {
_id?: string
name: string
slug: string
schemaVersion: 1
schema: FormKitSchemaDefinition[]
settings: Record<string, unknown>
status: 'draft' | 'published' | 'archived'
}
export function useDynamicFormsRepository() {
async function get(id: string) {
return await $fetch<DynamicFormDocument>(`/api/dynamic-forms/${id}`)
}
async function save(id: string, payload: DynamicFormDocument) {
return await $fetch<DynamicFormDocument>(`/api/dynamic-forms/${id}`, {
method: 'PATCH',
body: payload,
})
}
return {
get,
save,
}
}Page NFZ Studio type
vue
<!-- app/pages/console/forms/[id].vue -->
<script setup lang="ts">
import type { FormKitSchemaDefinition } from '@formkit/core'
type FormBuilderValues = Record<string, unknown>
type DynamicFormDocument = {
_id?: string
name: string
slug: string
schemaVersion: 1
schema: FormKitSchemaDefinition[]
values?: FormBuilderValues
settings: Record<string, unknown>
status: 'draft' | 'published' | 'archived'
}
const route = useRoute()
const repository = useDynamicFormsRepository()
const id = computed(() => String(route.params.id))
const builderId = computed(() => `nfz-studio-form-${id.value}`)
const pending = ref(false)
const saving = ref(false)
const form = ref<DynamicFormDocument>({
name: 'Nouveau formulaire',
slug: 'new-form',
schemaVersion: 1,
schema: [],
values: {},
settings: {},
status: 'draft',
})
onMounted(async () => {
pending.value = true
try {
form.value = await repository.get(id.value)
}
finally {
pending.value = false
}
})
async function handleSave(payload: {
schema: FormKitSchemaDefinition[]
values: FormBuilderValues
settings: Record<string, unknown>
}) {
saving.value = true
try {
form.value = await repository.save(id.value, {
...form.value,
schema: payload.schema,
values: payload.values,
settings: payload.settings,
})
}
finally {
saving.value = false
}
}
</script>
<template>
<QPage padding>
<QInnerLoading :showing="pending" />
<FormBuilder
v-if="!pending"
:builder-id="builderId"
v-model:schema="form.schema"
v-model:values="form.values"
:settings="form.settings"
:title="form.name"
:subtitle="`Statut : ${form.status}`"
:disabled="saving"
autosave
height="calc(100vh - 48px)"
@save="handleSave"
>
<template #toolbar-after>
<QBadge :color="form.status === 'published' ? 'positive' : 'warning'" outline>
{{ form.status }}
</QBadge>
</template>
</FormBuilder>
</QPage>
</template>Service NFZ conseillé
Nom de service possible :
txt
dynamic-formsChamps minimum :
txt
name
slug
schemaVersion
schema
settings
status
createdAt
updatedAtPour une application plus avancée, ajoute :
txt
ownerId
workspaceId
publishedAt
version
tags
permissionsBonne pratique sécurité
Le builder est un outil d’administration. Dans NFZ Studio, protège la route avec :
txt
middleware route Nuxt
store session Pinia
RBAC côté service Feathers/NFZ
validation Zod côté backendLe contrôle d’accès ne doit jamais reposer uniquement sur le masquage du composant dans l’interface.