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

格子表单/GRID-FORM已在Github 开源,如能帮到您麻烦给个星🤝

GRID-FORM 系列文章

新版本功能 🎉

不觉间,GRID-FORM 已经开源一年(2023年1月29日首次提交),初始版本功能较为简单,能用但很死板。后来陆续进行小版本迭代,增加诸如数据联动右键菜单等,可是作为常用且必要的嵌套(子表单)按钮功能一直没有实现。于是就有了今年的第一个0.1.1版本

  • 支持嵌套容器(子表单)
  • 支持自定义脚本交互
  • 新增 Element Plus 渲染器,完善 Vant4 渲染器
  • 新增组件:按钮、图片、静态表格

目前具备的模块与组件如下图(带边框为新增功能)所示:

运行时截图

表单渲染效果

从左到右分别是 NaiveUI、ElementPlus、Vant4对于同一表单的渲染效果

可视化设计器

子表单(嵌套)

所谓子表单,可以理解为大背包里面的小包,底下可以添加子字段,同时支持录入多条数据;常见应用于录入字段格式固定、条数不定的数据清单。

按照 GRID-FORM 的设计,初始表单为一个顶层容器,能够定义标签样式(如位置、对齐方式)、格子列数、尺寸大小等布局属性,还可以嵌套子容器(如上图中的外层容器子容器1子容器2),每个容器均能定义其布局属性,理论上支持无限嵌套 (递归渲染)。

嵌套类型

子表单能够设置如下类型:

类型 说明
仅布局 只作为布局上的分组,字段均为同级
单个 嵌入一个对象到父字段
多行 嵌入多个格式固定的对象(数组)到父字段

下面我用一个实际例子说明,比如要录入一则学生信息,字段包含:

字段名 说明
姓名、年龄、籍贯 仅布局三个同级基本信息
专业信息 单个数据:名称、学院、学年
教育经历 多行数据:开始日期、结束日期、学校

最终得到的表单数据:

javascript 复制代码
{
    姓名 : "张三",
    年龄 : 19,
    籍贯 : "广西",
    专业 : {
        名称 : "水利水电工程",
        学院 : "土木建筑工程学院",
        学年 : 4
    },
    教育经历 : [
        { 
            开始日期 : "2011.09",
            结束日期 : "2020.06",
            学校 : "XX市第一小学"
        },
        { 
            开始日期 : "2020.09",
            结束日期 : "2023.06",
            学校 : "XX市第一高级中学"
        }
    ]
}

效果演示

核心代码

html 复制代码
<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/编号 查找内容
javascript 复制代码
//找到编号为 name 的表单项(返回首个匹配值),并禁用
items.$("name").disabled = true
//找到编号为'name'、_text为'专业名称'的表单项(返回首个匹配值),并禁用
items.$({_uuid:"name", "_text":"专业名称"}).disabled = true

结语

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

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho5 小时前
【TypeScript】知识点梳理(三)
前端·typescript