一、前言
首先祝各位朋友们新年快乐!龙年大吉!
在前端开发过程中,表单渲染是重要且繁琐的一环。为了提高开发效率并避免重复工作,我开发了一款基于vue3
的表单工具,并取名vue-form-craft(vue表单工艺)
。
是适用于 vue3项目
中后台表单的一种通用解决方案。
本文将介绍 vue-form-craft 的基本概念、使用方式及高级特性。
二、简介
vue-form-craft 主要由FormDesign(表单设计器) 和 SchemaForm(表单渲染器) 组成。
FormDesign
通过拖拽快速生成JsonSchema,SchemaForm
使用 JsonSchema 协议渲染表单
在线预览
文档
github源码
优势
- 轻量级: 可以通过npm依赖直接集成到你的vue3项目
- 易于使用:容易上手,可以通过表单设计器可视化拖拽的方式快速生成表单。
- 协议简单:遵循 JsonSchema 规范,因此相对容易理解和上手。
- 较强的配置能力:具有较强的配置能力,可以对表单联动、校验、布局以及数据处理等方面进行配置。
- 良好的性能体验:底层采用 element plus 的 Form 来实现表单的数据收集和管控,同时针对控件渲染层面进行优化处理,从而大幅提升性能,使得在使用过程中具有良好的性能体验。
- 内置组件丰富:内置组件非常丰富,包括基础组件、嵌套卡片类组件和动态增减 List 组件等,可以满足大多数场景的表单实现需求。
- 扩展性强:具有非常强的扩展性,支持自定义各种类型的表单控件,支持多种ui库,用户可以根据实际需要进行定制,非常灵活。
三、如何使用
最新版本v3.0.8 已经支持ts类型检查,推荐vue3+ts项目引入使用!
1、安装依赖
npm i vue-form-craft
2、全局注册
import { createApp } from 'vue'
import App from './App.vue'
import VueFormCraft from 'vue-form-craft'
const app = createApp(App)
app.use(VueFormCraft)
app.mount('#app')
3、使用
<template>
<schema-form :schema="schema" footer @onFinish="onFinish" />
</template>
<script setup lang="ts">
import type { schemaType , formValuesType } from 'vue-form-craft'
const schema: schemaType = {
labelWidth: 150,
labelAlign: 'right',
size: 'default',
items: [
{
label: '用户名',
component: 'Input',
props: {
placeholder: '请输入用户名'
},
name: 'username'
},
{
label: '密码',
component: 'Password',
props: {
placeholder: '请输入密码'
},
name: 'password'
}
]
}
const onFinish = (values: formValuesType) => {
alert(JSON.stringify(values))
}
</script>
4、通过表单设计器拖拖拽拽 快速生成JsonSchema
<template>
<form-design @onSave="(schema) => console.log(schema)" />
</template>
四、一分钟读懂JsonShema
首先,我们要理解,JSON Schema就是 表单的抽象 。
JSON的最外层是表单整体的配置,items里面是每个字段的配置。
items里是每个字段的抽象,label、name、component等是每个字段的通用配置。
component代表使用什么组件,props是传给该组件的props。大部分组件都是基于el二次封装,所以也支持该组件在el文档的所有props
{
"labelWidth": 150, //表单label宽度
"labelAlign": "right", //表单label对齐方式
"size": "default", //表单字段大小
"items": [ //表单所有字段的配置
{
"label": "用户名", //字段的label
"component": "input", //字段使用的组件
"props": { //传给该组件的props,支持该组件在element plus的所有props
"placeholder": "请输入用户名"
},
"name": "username" //唯一标识,也就是值key
},
{
"label": "密码",
"component": "password",
"props": {
"placeholder": "请输入密码"
},
"name": "password"
}
]
}
五、表单联动
要评价一个表单工具能力强不强,表单联动能力至关重要。 vue-form-craft 通过 模板引擎 动态生成JsonSchema,让表单联动变得非常容易。
1、模板表达式
模板表达式为字符串格式,以双花括号 {{ … }}为语法特征,对于简单的联动提供一种简洁的配置方式。
在JsonSchema中,被双花括号包裹的字符串一律会被解析为 js表达式并返回结果,且只能使用联动变量。这种联动方式能应对大部分联动场景😎
例如:控制字段禁用、隐藏、文案提示等交互。
JsonSchema 所有协议字段都支持模板表达式。
{
"labelWidth": 150,
"labelAlign": "right",
"size": "default",
"items": [
{
"label": "姓名",
"component": "Input",
"name": "name",
"props": {
"placeholder": "请输入姓名"
}
},
{
"label": "自我介绍",
"component": "TextArea",
"name": "desc",
"props": {
"placeholder": "{{ $values.name + '的自我介绍' }}",
"disabled":"{{ !$values.name }}"
}
}
]
}
Schema插值表达式 可以使用的联动变量:
变量名 | 类型 | 描述 |
---|---|---|
$val | any | 当前字段值 |
$values | Object | 整个表单的值 |
$select | Object | 当前字段如果是【选择类字段】,这个就是选中项对应的数据源 |
$selectData | Object | 【选择类字段】选中项数据源合集 |
$item | Object | 【自增组件】专用,单行的数据值 |
… | any | 由schemaContext传入的自定义变量 |
联动案例1
{
labelWidth: 150,
labelAlign: 'right',
size: 'default',
items: [
{
label: '评分',
component: 'Rate',
name: 'rate',
props: {
max: 5,
'allow-half': true
},
required: true
},
{
label: '差评原因',
component: 'Textarea',
name: 'reason',
props: {
placeholder: '请输入...',
autosize: {
minRows: 4,
maxRows: 999
}
},
hidden: '{{ !$values.rate || $values.rate>3 }}' //评分大于3分时隐藏,未评分时也要隐藏
}
]
}
联动案例2
{
labelWidth: 150,
labelAlign: 'right',
size: 'default',
items: [
{
label: '分类',
component: 'Radio',
props: {
mode: 'static',
options: [
{
name: '前端',
id: 1
},
{
name: '后端',
id: 2
},
{
name: '运维',
id: 3
},
{
name: '其他',
id: 4
}
],
labelKey: 'name',
valueKey: 'name',
optionType: 'button',
space: 0
},
name: 'category',
required: true
},
{
label: '文章',
component: 'Radio',
props: {
mode: 'remote',
placeholder: '请选择文章',
labelKey: 'title',
valueKey: 'id',
api: {
url: '/current/query/article',
method: 'GET',
params: {
filters: {
category: '{{$values.category}}'
}
},
dataPath: 'data'
},
optionType: 'circle',
autoSelectedFirst: true,
direction: 'vertical',
space: 0
},
name: 'article',
required: true,
hidden: '{{!$values.category}}'
}
]
}
2、字段监听
上面的 模板表达式 虽然足够灵活,但是不能做到表单值联动,所以给每个字段提供了一个change配置,可以监听字段变化去修改其他字段的值。
change是一个数组,可以同时联动多个字段。target为目标字段,value是修改的值,也支持插值表达式。
联动案例3
{
"labelWidth": 150,
"labelAlign": "right",
"size": "default",
"items": [
{
"label": "字段1",
"component": "Input",
"props": {
"placeholder": "请输入..."
},
"name": "item1",
"change": [
{
"target": "item2",
"value": "{{$val * 2}}"
},
{
"target": "item3",
"value": "{{$val + '元'}}"
}
]
},
{
"label": "字段2",
"component": "Input",
"props": {
"placeholder": "请输入..."
},
"name": "item2"
},
{
"label": "字段3",
"component": "Input",
"props": {
"placeholder": "请输入..."
},
"name": "item3"
}
]
}
联动案例4
一些场景需要根据已选值的数据源中取某个字段,再给其他字段作为值,这就可以用上 $select 了
{
"labelWidth": 150,
"labelAlign": "right",
"size": "default",
"items": [
{
"label": "选择商品",
"component": "Select",
"props": {
"mode": "static",
"options": [
{
"name": "商品1",
"id": "1",
"price": 25
},
{
"name": "商品2",
"id": "2",
"price": 65
},
{
"name": "商品3",
"id": "3",
"price": 100
}
],
"placeholder": "请选择...",
"labelKey": "name",
"valueKey": "id"
},
"name": "commodity",
"change": [
{
"target": "price",
"value": "{{$select.price}}"
}
]
},
{
"label": "价格",
"component": "InputNumber",
"name": "price",
"props": {
"min": 1,
"max": 9999,
"step": 1,
"unit": "元",
"disabled": true,
"controlsPosition": "right"
}
}
]
}
六、高级特性
1、表单校验
所有表单项都可以配置required:true
, 来给字段设置必填校验。
如果是input类字段,则可以配置 rules
设置更复杂的校验规则,参考el文档
type做了扩展,可以直接写正则表达式,就会根据其校验了
{
"labelWidth": 150,
"labelAlign": "right",
"size": "default",
"items": [
{
"label": "邮箱",
"component": "Input",
"props": {
"placeholder": "请输入邮箱"
},
"name": "email",
"required": true,
"rules": [
{
"type": "email",
"message": "邮箱格式不合法",
"trigger": [
"blur"
]
},
{
"type": "^\\S*$",
"message": "不能包含空格",
"trigger": [
"blur",
"change"
]
}
]
}
]
}
2、远程数据
下拉选择框、单选框等选择类字段,vue-form-craft
都进行了二次封装,可以直接配置接口参数,来自动获取远程数据。
{
label: '文章',
component: 'Radio',
props: {
mode: 'remote',
placeholder: '请选择文章',
labelKey: 'title',
valueKey: 'id',
api: {
url: '/current/query/article',
method: 'GET',
params: {},
dataPath: 'data'
},
optionType: 'circle',
autoSelectedFirst: true,
direction: 'vertical',
space: 0
},
name: 'article',
}
默认使用axios来请求,你也可以在main.js里给组件传入你项目里封装好的axios,然后表单所有组件都会用它来发ajax请求
import { createApp } from 'vue'
import App from './App.vue'
import VueFormCraft from 'vue-form-craft'
import { request } from '@/utils'
const app = createApp(App)
app.use(VueFormCraft, { request }) //传入你项目里的公共请求方法
app.mount('#app')
3、自增组件
收集一组格式一样的重复数据是表单经常遇到的场景,在 vue-form-craft 中可以轻松实现,
且支持多种展示格式
{
"labelWidth": 150,
"labelAlign": "right",
"size": "default",
"items": [
{
"label": "增添用户",
"component": "FormList",
"children": [
{
"label": "用户名",
"component": "Input",
"props": {
"placeholder": "请输入文本"
},
"name": "username",
},
{
"label": "密码",
"component": "Password",
"props": {
"placeholder": "请输入密码"
},
"name": "password"
},
{
"label": "设为管理员",
"component": "Switch",
"name": "vip",
"props": {
"inline-prompt": 0
}
}
],
"props": {
"mode": "table"
},
"designKey": "design-pMUa",
"name": "users",
}
]
}
4、深层数据绑定
在开发过程中,经常会遇到需要将前端数据转换为符合服务端数据结构的情况。
比如一张表单你收集到的可能是这样的数据:
而后端希望收到的是这样的数据
为了解决这个问题,name 字段扩展为魔法字段,既是唯一标识,也是数据路径,可以让你自由指定数据存储的层级。
比如name是【hostname】,数据就会保存为 { hostname: 'xxx' }
比如name是【flavor.cpu】,数据就会保存为 { flavor: { cpu:'xxx' } }
比如name是【flavor.memory】,数据就会保存为 { flavor: { memory:'xxx' } }
无论数据层级保存的多深,都能准确追踪,且能精准校验
5、组件自定义
vue-form-craft 提供了一些基础组件,例如 Input、Select 和 Radio 等,但有时候这些组件并不能完全符合我们的业务需求,此时可以考虑使用自定义组件(Custom)。
需要将你的组件注册为全局组件,并且能够接收v-model
{
"label": "自定义组件",
"component": "Custom",
"props": {
"componentName": "GridTable"
},
"designKey": "design-3J39",
"name": "form-iOOm"
}
6、支持多种组件库
可能你并不喜欢element ui的组件风格,或者你项目里用的是其他ui库。那么你也可以选择vue-form-craft ,因为它提供了ui库定制功能
全局配置customElements
可以用来定制所有内置组件,比如你想将内置组件替换成ant-design-vue
风格,示例如下:
import { createApp } from 'vue'
import App from './App.vue'
import VueFormCraft from 'vue-form-craft'
import { request } from '@/utils'
import { Switch, Input, Textarea, InputNumber } from 'ant-design-vue'
const app = createApp(App)
app.use(VueFormCraft, {
request,
customElements: {
Input: {
component: Input,
modelName: 'value'
},
Switch: {
component: Switch,
modelName: 'checked'
},
Textarea: {
component: Textarea,
modelName: 'value'
},
InputNumber: {
component: InputNumber,
modelName: 'value'
}
}
})
app.mount('#app')
可能不同组件库的参数会不一样,比如el都是直接使用v-model:modelValue
,而ant大部分都是v-model:value
,所以提供了modelName来指定v-model的名字
而其他参数不一样的问题,可以选择二次封装将组件的props都封装符合el参数格式的组件,再传给customElements
也可以通过attrs来自行配置每个字段的字段配置,和JsonSchema的items配置一样,配置成符合对应组件库参数的attr表单
Switch: {
component: Switch,
modelName: 'checked',
attrs: [
{ label: '标签', component: 'Input', name: 'label' },
{
label: '唯一标识',
component: 'Input',
name: 'name',
help: "既是唯一标识,也是数据路径。比如输入【props.name】,数据就会保存为 { props: { name:'xxx' } }"
},
{ label: '字段说明', component: 'Textarea', name: 'help' },
{
label: '占位提示',
component: 'Input',
name: 'props.placeholder',
designKey: 'form-ekRL'
},
{ label: '初始值', component: 'Input', name: 'initialValue' },
{ label: '是否必填', component: 'Switch', name: 'required' },
{ label: '是否只读', component: 'Switch', name: 'props.readonly' },
{ label: '是否禁用', component: 'Switch', name: 'props.disabled' },
{ label: '隐藏字段', component: 'Switch', name: 'hidden' },
{ label: '隐藏标签', component: 'Switch', name: 'hideLabel' }
]
}
七、写在最后
作为一款开箱即用的表单方案,vue-form-craft
目标是大幅提高中后台系统中的表单开发效率,让你可以快速创建各种类型的表单,并省略从头编写表单组件的繁琐步骤。我将一直坚持这个初衷,并不断推进协议配置方面的创新和提升,努力提供更加完善的表单开发体验。
后续开发的目标期望:
- 结合ts实现类型化支持
- 国际化翻译
- 结合
vue-form-craft
开发一套vue低代码平台
如果觉得 vue-form-craft 做的不错,或者本文对你有所帮助和启迪,可不可以顺手点个赞😁
如果项目对你有帮助,求个github star! 谢谢各位帅哥美女(❁´◡`❁)
github源码