一、前言
首先祝各位朋友们新年快乐!龙年大吉!
在前端开发过程中,表单渲染是重要且繁琐的一环。为了提高开发效率并避免重复工作,我开发了一款基于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源码