vue-form-craft,基于vue3的开箱即用表单方案

一、前言

首先祝各位朋友们新年快乐!龙年大吉!

在前端开发过程中,表单渲染是重要且繁琐的一环。为了提高开发效率并避免重复工作,我开发了一款基于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库,用户可以根据实际需要进行定制,非常灵活。

三、如何使用

1、安装依赖

js 复制代码
npm i vue-form-craft

2、全局注册

js 复制代码
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、使用

js 复制代码
<template>
  <schema-form :schema="schema" footer @onFinish="onFinish" />
</template>

<script setup>
const schema = {
  labelWidth: 150,
  labelAlign: 'right',
  size: 'default',
  items: [
    {
      label: '用户名',
      component: 'Input',
      props: {
        placeholder: '请输入用户名'
      },
      name: 'username'
    },
    {
      label: '密码',
      component: 'Password',
      props: {
        placeholder: '请输入密码'
      },
      name: 'password'
    }
  ]
}

const onFinish = (values) => {
  alert(JSON.stringify(values))
}
</script>

4、通过表单设计器拖拖拽拽 快速生成JsonSchema

js 复制代码
<template>
  <form-design @onSave="(schema) => console.log(schema)" />
</template>

四、一分钟读懂JsonShema

首先,我们要理解,JSON Schema就是 表单的抽象

JSON的最外层是表单整体的配置,items里面是每个字段的配置。

items里是每个字段的抽象,label、name、component等是每个字段的通用配置。

component代表使用什么组件,props是传给该组件的props。大部分组件都是基于el二次封装,所以也支持该组件在el文档的所有props

json 复制代码
{
  "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 所有协议字段都支持模板表达式。

json 复制代码
{
  "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

js 复制代码
{
  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

js 复制代码
{
  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

json 复制代码
{
  "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

json 复制代码
{
  "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做了扩展,可以直接写正则表达式,就会根据其校验了

json 复制代码
{
  "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都进行了二次封装,可以直接配置接口参数,来自动获取远程数据。

js 复制代码
{
      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请求

js 复制代码
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 中可以轻松实现, 且支持多种展示格式

js 复制代码
{
  "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

json 复制代码
{
      "label": "自定义组件",
      "component": "Custom",
      "props": {
        "componentName": "GridTable"
      },
      "designKey": "design-3J39",
      "name": "form-iOOm"
}

6、支持多种组件库

可能你并不喜欢element ui的组件风格,或者你项目里用的是其他ui库。那么你也可以选择vue-form-craft ,因为它提供了ui库定制功能

全局配置customElements可以用来定制所有内置组件,比如你想将内置组件替换成ant-design-vue风格,示例如下:

js 复制代码
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表单

js 复制代码
 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源码

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax