好用的低代码可视化表单设计器 FcDesigner 是一款基于 Vue 的开源工具(设计器项目名 form-create-designer),通过数据驱动表单渲染。可以拖拽生成表单规则,再由配套渲染器 form-create 加载 JSON 并渲染。面向政务、OA、ERP、电商、流程管理里要做动态表单的开发者和业务配置同学。
审批单画完要入库、刷新后还得回到同一张画布、OA 里审批详情页还要用同一份 JSON 渲染------这些都是保存和回显。FcDesigner 把设计结果收成两段数据:规则 rule 和配置 options。内置保存按钮用 config.showSaveBtn: true 打开,点击触发 save 事件,handler 收到 {rule, options},两段都是 JSON 字符串。嵌进 OA 用自己的工具栏时,调用 getRule / getOptions,再用 formCreate.toJson 转成字符串后 POST。回显写在 onMounted 里,调用 setOptions、setRule。转换 JSON 必须用 formCreate.toJson / formCreate.parseJson,不要用 JSON.stringify / JSON.parse。

截图是设计器顶部工具栏:导入、导出旁边有绿色的「保存」,红箭头指着它,后面是「预览」「清空」。这是把 config.showSaveBtn 设为 true 之后才会出现的内置保存按钮。点「保存」会触发 save 事件。点「预览」只是按当前画布渲染出表单,不会自动发保存请求。
FcDesigner 保存表单为什么必须用 toJson 和 parseJson?
规则里不只是普通对象,还可能带设计器自己的结构。用浏览器自带的 JSON.stringify / JSON.parse 转出来的字符串,回显时格式会对不上。保存用 formCreate.toJson,读回来用 formCreate.parseJson。本课的 formCreate 从 @form-create/designer 引入,和设计器、渲染器是同一套。
设计器实例上和保存、回显直接相关的方法(调用前要等组件初始化完成,写在 onMounted 里):
ts
// 设置 / 读取规则
type SetRule = (rule: string | Rule[]) => void
type GetRule = () => Rule[]
type GetJson = () => string
// 设置 / 读取配置;setOption 别名 setOptions,getOption 别名 getOptions
type SetOption = (opt: Options) => void
type GetOption = () => Options
type GetOptionsJson = () => string
getJson() / getOptionsJson() 已经是 JSON 字符串。本课保存示例按官方写法:先 getRule() / getOptions(),再 formCreate.toJson。回显不要把接口返回的字符串直接 JSON.parse,先 formCreate.parseJson,再 setRule / setOptions。
接口字段名官方示例里有两套,对接时按你们的后端映射即可:
- 保存 POST
/api/saveForm:body 是{ rules, options },值是 JSON 字符串(@save参数本身叫rule/options,POST 时规则字段写成了rules) - 回显 GET
/api/getForm:{ ruleJson, optionsJson } - 渲染 GET
/api/getFormRules:同样是{ ruleJson, optionsJson },还可以带一份初始formData
怎么显示内置保存按钮 showSaveBtn?审批单怎么 POST 保存?
基本示例:打开 showSaveBtn
场景: 审批单配置页。业务同学拖完字段,希望在设计器顶部看到保存按钮,先不用自己画工具栏。
按下面做:
- 页面里放
fc-designer,把config传给:config。 config里写showSaveBtn: true。- 打开这个页面,看顶部工具栏,不要只看画布中间。
- 点「预览」确认当前表单能渲染;点绿色「保存」才会走到下一节的
@save。
vue
<template>
<fc-designer ref="designer" :config="config"/>
</template>
<script setup>
const config = {
showSaveBtn: true
}
</script>
预览后:工具栏出现绿色「保存」。此时还没有监听 @save,点保存不会发接口,但按钮已经在了。「预览」打开的是当前画布对应的表单,和保存是两件事。
再一个示例:@save 把 rule/options POST 到 /api/saveForm
场景: 录入页的表单模板要入库。业务同学点顶部「保存」,把当前规则和配置交给后端。
按下面做:
- 保留上一节的
showSaveBtn: true。 - 在
fc-designer上写@save="handleSave"。 handleSave解构{rule, options},两段都是 JSON 字符串,不要再JSON.stringify。axios.post('/api/saveForm', { rules: rule, options: options })。注意 body 里规则字段是rules。- 点顶部「保存」发请求;只点「预览」看不到这次 POST。
vue
<template>
<fc-designer ref="designer" @save="handleSave" :config="config"/>
</template>
<script setup>
import axios from 'axios'
const config = {
showSaveBtn: true
}
function handleSave ({rule, options}) {
axios.post('/api/saveForm', {
rules: rule, //JSON字符串
options: options //JSON字符串
}).then(response => {
console.log('表单保存成功', response.data)
}).catch(error => {
console.error('表单保存失败', error)
})
}
</script>
预览后:点绿色「保存」,发出 POST /api/saveForm,body 里 rules、options 是两段 JSON 字符串。成功会在控制台看到「表单保存成功」。把 /api/saveForm 换成你们的模板保存地址,字段名按上表映射。
嵌进 OA 时怎么用自己的保存按钮 toJson?
基本示例:自己的按钮 + toJson(getRule()) / toJson(getOptions())
场景: 设计器嵌进 OA。页面顶部已经有业务自己的工具栏,不能靠设计器内置保存按钮,要点 OA 里那颗「保存」。
按下面做:
fc-designer可以不传showSaveBtn(不打开内置按钮)。- 在旁边(或 OA 工具栏)放自己的按钮,例如 Element Plus 的
el-button。 - 点击时从
designer取getRule()、getOptions(),用formCreate.toJson转成 JSON 字符串。 - 仍然 POST
/api/saveForm,body 字段还是rules、options。 - 看表单长什么样,点设计器顶部「预览」;真正入库走你自己的按钮。
vue
<template>
<fc-designer ref="designer"/>
<el-button @click="save">保存</el-button>
</template>
<script setup>
import {ref} from 'vue'
import axios from 'axios'
import {formCreate} from '@form-create/designer'
const designer = ref(null)
function save() {
axios.post('/api/saveForm', {
rules: formCreate.toJson(designer.value.getRule()), //JSON字符串
options: formCreate.toJson(designer.value.getOptions()), //JSON字符串
}).then(response => {
console.log('保存成功')
})
}
</script>
预览后:点你自己的「保存」,发出的 POST 和上一节同一条 /api/saveForm,两段也是 JSON 字符串。差别是数据从实例方法取,再 toJson,而不是等 @save 把字符串递过来。getRule() 返回的是规则数组,getOptions() 返回配置对象,不要对它们 JSON.stringify。按钮要点在组件已经渲染出来之后,初始化完成前不要调这些方法。
刷新后怎么 setRule 回显?审批详情页怎么 parseJson?
基本示例:刷新回显 setRule + setOptions + parseJson
场景: 审批单配置页刷新或下次再打开,画布要回到上次保存的样子,业务同学接着改,而不是空白设计器。
按下面做:
ref="designer",script setup里const designer = ref(null)。- 在
onMounted里拉接口。必须等设计器初始化完成才能调实例方法。 - GET
/api/getForm,从data里取ruleJson、optionsJson。 - 先
formCreate.parseJson,再setOptions、setRule。顺序按保存页示例:配置先于规则。 - 回显后点「预览」,应看到上次保存的表单,而不是空表。
vue
<template>
<fc-designer ref="designer" @save="handleSave" :config="config"/>
</template>
<script setup>
import {ref, onMounted} from 'vue'
import axios from 'axios'
import {formCreate} from '@form-create/designer'
const designer = ref(null)
const config = {
showSaveBtn: true
}
function handleSave ({rule, options}) {
axios.post('/api/saveForm', {
rules: rule, //JSON字符串
options: options //JSON字符串
})
}
onMounted(async () => {
try {
const { data } = await axios.get('/api/getForm')
const { ruleJson, optionsJson } = data
designer.value.setOptions(formCreate.parseJson(optionsJson))
designer.value.setRule(formCreate.parseJson(ruleJson))
} catch (error) {
console.error('加载表单数据失败', error)
}
})
</script>
预览后:组件挂载结束就会去拉 /api/getForm,画布还原成 ruleJson / optionsJson 里的表单。点「预览」看到的就是这份规则渲染出来的结果。接口如果沿用保存时的 rules / options 字段,读取时改成你们实际的键名即可,不要假定一定叫 ruleJson。onMounted 的回调必须是 async,才能 await axios.get。
再一个示例:运行时 form-create 加载同一份 JSON
场景: 审批详情页。填表人看不到设计器,只要把已经保存的规则渲染成可填的表单,提交的是字段值,不是再存一份设计 JSON。
运行时用 <form-create>。同一份 formCreate 从 @form-create/designer 引入;应用启动时挂载一次即可:
js
import {formCreate} from '@form-create/designer'
app.use(formCreate)
按下面做:
- 页面放
<form-create>,:rule、:option绑解析后的对象,v-model绑formData。 v-if="rule.length",规则还是空数组时先不渲染。onMounted里 GET/api/getFormRules,用formCreate.parseJson赋给rule、option。- 如果接口还带了初始值,赋给
formData。formData的 key 必须和规则里的字段field一致。 - 打开详情页就能看到表单;提交走
@submit,POST 的是填写值,不是rules/options。
vue
<template>
<div id="app">
<form-create
v-if="rule.length"
v-model="formData"
v-model:api="api"
@submit="handleSubmit"
:rule="rule"
:option="option"
></form-create>
</div>
</template>
<script setup>
import {ref, onMounted} from 'vue'
import axios from 'axios'
import {formCreate} from '@form-create/designer'
const rule = ref([])
const option = ref({})
const api = ref(null)
const formData = ref({})
function handleSubmit(formData) {
axios.post('/api/submitFormData', formData)
.then(response => {
console.log('提交成功:', response.data)
})
.catch(error => {
console.error('提交失败:', error)
})
}
onMounted(() => {
axios.get('/api/getFormRules')
.then(response => {
const {ruleJson, optionsJson, formData: initFormData} = response.data
rule.value = formCreate.parseJson(ruleJson)
option.value = formCreate.parseJson(optionsJson)
formData.value = initFormData || {}
})
.catch(error => {
console.error('加载表单规则失败:', error)
})
})
</script>
预览后:详情页渲染的是保存过的那份规则。提交发出 POST /api/submitFormData,body 是当前 formData。设计器里保存的是模板(rules / options),详情页提交的是填写结果,两套接口不要混。没有 initFormData 时,formData 先空对象,等用户填;编辑已有记录时,key 必须对上 field。
保存和回显常见问题
Q: 为什么不能用 JSON.stringify / JSON.parse?
A: 必须用 formCreate.toJson 代替 JSON.stringify,用 formCreate.parseJson 代替 JSON.parse,才能保证规则格式正确。@save 拿到的 rule / options 已经是 JSON 字符串,POST 时原样放入 rules / options 即可。自己取实例数据时:formCreate.toJson(designer.value.getRule())、formCreate.toJson(designer.value.getOptions())。回显和运行时渲染都先 parseJson,再 setRule / setOptions 或赋给 :rule / :option。
Q: @save 的 rule 和 POST 的 rules、GET 的 ruleJson 是什么关系?
A: save 事件参数是 {rule, options},两段 JSON 字符串。官方保存接口 body 写成 { rules: rule, options: options },规则多了一个 s。回显 GET /api/getForm 示例取的是 { ruleJson, optionsJson };运行时 GET /api/getFormRules 同样是这两个键,还可以带 formData。后端可以统一命名,前端按实际字段做映射,不要三种名字混在一次请求里。
Q: setRule、getRule 什么时候能调?内置保存和自己的按钮怎么选?
A: 必须等 fc-designer 初始化完成,写在 onMounted 里,通过 designer.value 调。内置按钮:config.showSaveBtn: true,点「保存」走 @save,参数已经是字符串。嵌进 OA、工具栏是自己的:不依赖 showSaveBtn,按钮里 toJson(getRule()) / toJson(getOptions())。两种保存都可以 POST /api/saveForm。getJson() / getOptionsJson() 也能拿到 JSON 字符串;本课示例按官方用 toJson 包一层 getRule / getOptions。
Q: 设计器回显和审批详情页渲染有什么不同?
A: 设计器回显是给画布:setOptions(formCreate.parseJson(optionsJson))、setRule(formCreate.parseJson(ruleJson)),之后还能继续拖改,点「预览」看效果。详情页不挂设计器,用 <form-create v-if="rule.length" v-model="formData" v-model:api="api" :rule="rule" :option="option">,rule / option 同样来自 parseJson。formData 的 key 必须和字段 field 一致。保存的是模板 JSON,提交的是填写值,不要把 formData 存进 rules。