格子表单GRID-FORM | 嵌套子表单与自定义脚本交互

本文介绍了GRID-FORM项目,一个开源的VUE3可视化低代码表单设计器,着重讲述了新版本的0.1.1更新,包括嵌套子表单支持、自定义脚本交互增强以及不同渲染器的引入。作者分享了如何使用和实例,并承认工具仍有改进空间,主要用于学习交流。
摘要由CSDN通过智能技术生成

格子表单/GRID-FORM已在Github 开源,如能帮到您麻烦给个星🤝
点此查看👉在线文档及演示 👈

GRID-FORM 系列文章

新版本功能 🎉

不觉间,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>

脚本交互

  1. 增加支持交互(单击、双击)的组件:按钮、图片
  2. 优化运行时函数:表单项数组对象增加$(致敬 JQuery😄)方法,便于快速按 ID/编号 查找内容
//找到编号为 name 的表单项(返回首个匹配值),并禁用
items.$("name").disabled = true
//找到编号为'name'、_text为'专业名称'的表单项(返回首个匹配值),并禁用
items.$({_uuid:"name", "_text":"专业名称"}).disabled = true

结语

因个人能力有限,此工具在设计、实现上存在诸多不足,仅作学习交流🙂。

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

集成显卡

码字不易,需要您的鼓励😄

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值