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

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

GRID-FORM 系列文章

新版本功能 🎉

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

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

目前具备的模块与组件如下图(带边框为新增功能)所示:
格子表单GRID-FORM | 嵌套子表单与自定义脚本交互

运行时截图

表单渲染效果

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

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

可视化设计器

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

子表单(嵌套)

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

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

嵌套类型

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

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

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

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

最终得到的表单数据:

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

效果演示

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

核心代码

<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

结语

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

原文链接:https://juejin.cn/post/7329100659876839436 作者:集成显卡

(0)
上一篇 2024年1月29日 上午11:12
下一篇 2024年1月29日 下午4:05

相关推荐

发表回复

登录后才能评论