格子表单/GRID-FORM已在Github 开源,如能帮到您麻烦给个星🤝
GRID-FORM 系列文章
- 基于 VUE3 可视化低代码表单设计器
- 嵌套表单与自定义脚本交互
新版本功能 🎉
不觉间,GRID-FORM
已经开源一年(2023年1月29日首次提交),初始版本功能较为简单,能用但很死板。后来陆续进行小版本迭代,增加诸如数据联动
、右键菜单
等,可是作为常用且必要的嵌套(子表单)
、按钮
功能一直没有实现。于是就有了今年的第一个0.1.1版本:
- 支持
嵌套容器(子表单)
- 支持自定义
脚本交互
- 新增 Element Plus 渲染器,完善 Vant4 渲染器
- 新增组件:按钮、图片、静态表格
目前具备的模块与组件如下图(带边框为新增功能)所示:
运行时截图
表单渲染效果
从左到右分别是 NaiveUI、ElementPlus、Vant4对于同一表单的渲染效果
可视化设计器
子表单(嵌套)
所谓子表单,可以理解为大背包里面的小包,底下可以添加子字段,同时支持录入多条数据;常见应用于录入字段格式固定、条数不定的数据清单。
按照 GRID-FORM 的设计,初始表单为一个顶层容器,能够定义标签样式(如位置、对齐方式)、格子列数、尺寸大小等布局属性,还可以嵌套子容器(如上图中的外层容器
、子容器1
、子容器2
),每个容器均能定义其布局属性,理论上支持无限嵌套(递归渲染)。
嵌套类型
子表单能够设置如下类型:
类型 | 说明 |
---|---|
仅布局 | 只作为布局上的分组,字段均为同级 |
单个 | 嵌入一个对象到父字段 |
多行 | 嵌入多个格式固定的对象(数组)到父字段 |
下面我用一个实际例子说明,比如要录入一则学生信息,字段包含:
字段名 | 说明 |
---|---|
姓名、年龄、籍贯 | 仅布局 三个同级基本信息 |
专业信息 | 单个 数据:名称、学院、学年 |
教育经历 | 多行 数据:开始日期、结束日期、学校 |
最终得到的表单数据:
{姓名 : "张三",年龄 : 19,籍贯 : "广西",专业 : {名称 : "水利水电工程",学院 : "土木建筑工程学院",学年 : 4},教育经历 : [{ 开始日期 : "2011.09",结束日期 : "2020.06",学校 : "XX市第一小学"},{ 开始日期 : "2020.09",结束日期 : "2023.06",学校 : "XX市第一高级中学"}]
}
效果演示
核心代码
<template><template v-if="isMultiple"><table class="gf-render-table"><tr v-for="(rowData, rowIndex) in formData" :class="{striped:rowIndex%2==1}"><td width="40" class="c"><n-popconfirm :negative-text="null" @positive-click="formData.splice(rowIndex, 1)"><template #trigger><n-button size="small" type="primary" tertiary circle>{{rowIndex+1}}</n-button></template>删除第{{rowIndex+1}}行数据?</n-popconfirm></td><td><n-grid :x-gap="gridGap" :y-gap="gridGap" :cols="form.grid" :style="{width: form.width, margin:'0px auto' }"><template v-for="(item, index) in form.items" :key="index"><n-form-item-gi v-if="item._hide!=true" :span="item._col" :show-feedback="false" :show-label="!(item._hideLabel === true || !form.labelShow)":label-placement="form.labelPlacement" :label-align="form.labelAlign" :label-width="form.labelWidth"><template #label>{{item._text}}<span v-if="item._required" style="color: red;"> *</span></template><component v-if="item._container && item.items" :is="buildComponent(item, renders, false)"><render-container :gridGap="gridGap" :renders="renders" :form="item" :formData="childForm(item)" :labelPlacement="item.labelPlacement" :labelAlign="item.labelAlign" /></component><component v-else-if="item._widget=='DATE'" v-model:formatted-value="rowData[item._uuid]" :is="buildComponent(item, renders, false)" /><component v-else v-model:value="rowData[item._uuid]" :is="buildComponent(item, renders, false)" /></n-form-item-gi></template></n-grid></td></tr></table><div style="margin-top: 10px; text-align: center;"><n-button size="small" :disabled="!canAdd" circle @click.stop="onAddRow">+</n-button></div></template><template v-else><n-grid :x-gap="gridGap" :y-gap="gridGap" :cols="form.grid" :style="{width: form.width, margin:'0px auto' }"><template v-for="(item, index) in form.items" :key="index"><n-form-item-gi v-if="item._hide!=true" :span="item._col" :show-feedback="false" :show-label="!(item._hideLabel === true || !form.labelShow)":label-placement="form.labelPlacement" :label-align="form.labelAlign" :label-width="form.labelWidth"><template #label>{{item._text}}<span v-if="item._required" style="color: red;"> *</span></template><component v-if="item._container && item.items" :is="buildComponent(item, renders, false)"><render-container :gridGap="gridGap" :renders="renders" :form="item" :formData="childForm(item)" :labelPlacement="item.labelPlacement" :labelAlign="item.labelAlign" /></component><component v-else-if="item._widget=='DATE'" v-model:formatted-value="formData[item._uuid]" :is="buildComponent(item, renders, false)" /><component v-else v-model:value="formData[item._uuid]" :is="buildComponent(item, renders, false)" /></n-form-item-gi></template></n-grid></template>
</template>
<script setup>import { ref, computed } from 'vue'import { ContainerProps, ContainerMixin } from '@grid-form/common/render.mixin'import { buildComponent } from '@grid-form/common'const props = defineProps(ContainerProps)const { isMultiple, canAdd, childForm, onAddRow } = ContainerMixin(props)
</script>
脚本交互
- 增加支持交互(单击、双击)的组件:按钮、图片
- 优化运行时函数:表单项数组对象增加
$
(致敬 JQuery😄)方法,便于快速按ID/编号
查找内容
//找到编号为 name 的表单项(返回首个匹配值),并禁用
items.$("name").disabled = true
//找到编号为'name'、_text为'专业名称'的表单项(返回首个匹配值),并禁用
items.$({_uuid:"name", "_text":"专业名称"}).disabled = true
结语
因个人能力有限,此工具在设计、实现上存在诸多不足,仅作学习交流🙂。