格子表单/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
结语
因个人能力有限,此工具在设计、实现上存在诸多不足,仅作学习交流🙂。