当前位置: 首页 > news >正文

GridForm:基于 Vue 3 + Ant Design Vue 的表格样式通用表单组件

GridForm:基于 Vue 3 + Ant Design Vue 的表格样式通用表单组件

仓库地址:https://gitee.com/wanghenan/grid-from


背景

在企业级后台系统开发中,表单是使用频率最高的 UI 元素之一。传统的 a-form + a-row/col 栅格布局虽然灵活,但在需要精确对齐多列字段、实现单元格合并等复杂布局时,往往需要大量手动调整,代码也不够直观。

参考 FineUI 等企业级 UI 框架的表格样式表单设计,笔者基于 Vue 3 + Ant Design Vue 4 封装了一个通用表格样式表单组件 GridForm,以 <table> 为底层结构,通过配置式 fields 数组驱动渲染,一行代码完成复杂表单布局。


效果预览

单列布局

单列布局

两列布局 + span 横向合并

两列布局

三列布局 + 多种控件类型

三列布局

rowSpan 纵向合并 + noLabel 无标签字段

纵向合并

校验错误提示

校验错误

后端错误注入(setErrors)

后端错误注入


技术栈

技术 版本 说明
Vue 3 ^3.5 Composition API + <script setup>
Ant Design Vue ^4.x UI 组件库
Vite ^5.x 构建工具
JavaScript ES2020+ 无 TypeScript 依赖

核心设计

布局模型

组件内部使用"逻辑列"概念:

物理总列数 = columns * 2
每个字段默认占 2 个物理列(1 标签列 + 1 内容列)

通过 border-collapse: separate; border-spacing: 0 模式,既保持单元格紧贴,又支持 box-shadow 焦点高亮效果。

边框采用"每个单元格只绘制右边框和下边框,table 负责上/左边框"的方案,避免相邻单元格边框叠加导致颜色深浅不一。

布局属性

属性 说明
span 横向合并:字段占几个逻辑列宽
rowSpan 纵向合并:内容列跨几行
noLabel 不渲染标签列,内容自动补满

layoutRows 计算逻辑

这是组件的核心计算属性,负责将 fields 数组按行分组:

const layoutRows = computed(() => {const rows = []// 占用表:记录被 rowSpan 占用的行列位置const occupied = new Map()let currentRow = [], currentColPos = 0, rowIndex = 0for (const field of props.fields) {const span = field.span || 1// 跳过被 rowSpan 占用的列位置while (isOccupied(rowIndex, currentColPos)) currentColPos++// 放不下则换行if (currentColPos + span > props.columns && currentRow.length > 0) {rows.push(currentRow)currentRow = []rowIndex++currentColPos = 0}// 标记后续行占用if (field.rowSpan > 1) markOccupied(rowIndex, currentColPos, field.rowSpan)currentRow.push(field)currentColPos += spanif (currentColPos >= props.columns) {rows.push(currentRow)currentRow = []rowIndex++currentColPos = 0}}if (currentRow.length > 0) rows.push(currentRow)return rows
})

内容列 colspan 计算

function calcContentColSpan(field) {if (field.contentColSpan) return field.contentColSpanconst span = field.span || 1if (field.noLabel) return span * 2        // 无标签:吞掉标签列return span === 1 ? 1 : span * 2 - 1     // 多列合并:跨越中间的标签列
}

快速上手

安装依赖

npm install ant-design-vue@4

注册组件

// main.js
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
createApp(App).use(Antd).mount('#app')

GridForm.vue 复制到项目中即可,无需额外配置。

示例一:单列布局

<template><GridFormref="formRef"title="个人简介":columns="1":fields="fields"v-model="formData"label-width="90px"/><a-button type="primary" @click="formRef.validate()">提交</a-button>
</template><script setup>
import { ref } from 'vue'
import GridForm from '@/components/GridForm.vue'const formRef = ref(null)
const fields = [{ name: 'nickname', label: '昵称', required: true },{ name: 'age',      label: '年龄', type: 'number', min: 1, max: 120 },{ name: 'email',    label: '邮箱', required: true,validator: v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? null : '邮箱格式不正确' },{ name: 'bio',      label: '简介', type: 'textarea', rows: 3 },
]
let formData = ref({})
</script>

示例二:两列布局 + span 横向合并

<template><GridForm title="联系我们" :columns="2" :fields="fields" v-model="formData" />
</template><script setup>
import { ref } from 'vue'
import GridForm from '@/components/GridForm.vue'const fields = [{ name: 'firstName', label: '姓', required: true, span: 1 },{ name: 'lastName',  label: '名', required: true, span: 1 },// span=2:横跨两列,占满整行{ name: 'email',   label: '电子邮箱', required: true, span: 2 },{ name: 'subject', label: '主题',     required: true, span: 2 },{ name: 'message', label: '消息正文', type: 'textarea', rows: 5, span: 2 },
]
let formData = ref({})
</script>

示例三:rowSpan 纵向合并 + noLabel 无标签

<template><GridForm title="商品信息" :columns="2" :fields="fields" v-model="formData" />
</template><script setup>
import { ref } from 'vue'
import GridForm from '@/components/GridForm.vue'/*** 布局示意:* ┌────────┬──────────┬────────┬──────────────┐* │ 商品名 │ [input]  │ 备注   │              │* ├────────┼──────────┤        │ [textarea]   │* │ 价格   │ [number] │ row    │ rowSpan=3    │* ├────────┼──────────┤ Span=3 │              │* │ 品牌   │ [input]  │        │              │* ├────────┴──────────┴────────┴──────────────┤* │ [无标签,noLabel=true,span=2,跨满整行]   │* └───────────────────────────────────────────┘*/
const fields = [{ name: 'goodsName', label: '商品名', required: true },{ name: 'remark',    label: '备注',   type: 'textarea', rows: 6, rowSpan: 3 },{ name: 'price',     label: '价格',   type: 'number' },{ name: 'brand',     label: '品牌' },{ name: 'desc', noLabel: true, type: 'textarea', rows: 2, span: 2,placeholder: '请输入商品详细描述' },
]
let formData = ref({})
</script>

示例四:后端校验错误注入

// 模拟后端返回字段级错误
formRef.value.setErrors({username: '用户名已被占用',email: '该邮箱已注册',
})

支持的控件类型

type 控件
input(默认) 单行文本
textarea 多行文本
number 数字输入框
select 下拉选择框
date 日期选择器
date-range 日期范围选择器
radio 单选按钮组
checkbox 多选框组
switch 开关
text 纯文本展示

暴露的方法

方法 说明
validate() 触发校验,返回 Boolean
clearValidate() 清除所有错误
resetFields() 重置为默认值
setErrors(map) 注入外部错误
formData 当前表单数据(Ref)

焦点高亮实现细节

组件去掉了 Ant Design Vue 控件自身的边框和阴影,统一由 <td> 单元格管理视觉状态:

/* 焦点态:蓝色边框 + 光晕 */
.grid-form-content.is-focused {border-color: #1677ff;box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.2);position: relative;z-index: 1;
}/* 校验错误:浅红底色 */
.grid-form-content.is-error {background: #fff2f0;
}

通过在 <td> 上监听 @focusin/@focusout 事件跟踪焦点字段:

const focusedField = ref(null)
// <td @focusin="focusedField = field.name" @focusout="focusedField = null">

总结

GridForm 通过 <table> 底层结构 + fields 配置数组,实现了:

  • ✅ 任意列数布局(columns 属性)
  • ✅ 横向合并(span)
  • ✅ 纵向合并(rowSpan)
  • ✅ 无标签字段(noLabel)
  • ✅ 10 种内置控件类型
  • ✅ 内置校验 + 后端错误注入
  • ✅ 焦点单元格高亮
  • ✅ 插槽自定义渲染

适合在企业级后台系统中需要密集信息录入的场景,与传统栅格表单相比布局更加精确,视觉上更接近 Excel 风格的信息填报界面。

开源地址: https://gitee.com/wanghenan/grid-from

欢迎 Star ⭐ 和提 Issue。

http://www.jsqmd.com/news/438925/

相关文章:

  • 【3 月小记】Part 2: 对拍 - L
  • 2026年上海专业蠕动泵厂家排名,口碑不错的蠕动泵生产企业推荐 - 工业品网
  • 上海智推时代 GEO 合作通道:2026 官方正规联系方式与对接流程 - 速递信息
  • 工业级数据增强:超越传统方法,探索合成数据生成的技术前沿
  • 超越序列:注意力机制的思想演化与工程实现
  • 2026年滑架式污泥料仓选购指南,全国靠谱供应商推荐与费用解读 - myqiye
  • 2026年考研数学辅导靠谱机构推荐,颜语堂全程陪伴助力上岸 - 工业推荐榜
  • 超越传统嵌入:Nomic Atlas嵌入API及其在现代AI系统中的革命性应用
  • 基于 Python+flask框架的老年人健康冠心病防治知识科普网站_vmrw72ad_
  • 2026 年,人生仓库集团的服务究竟有多独特?
  • MetaGPT记忆系统深度剖析:从短期缓存到终身学习的三大记忆架构实战
  • 2026四款AI,效率直接拉满了
  • 基于C#实现的多线程文件上传下载工具
  • 直流电压电流采集检测方案:STM32 的实战之旅
  • 教育平台ueditor怎样配置本地Word文档编辑功能?
  • 基于Python+flask的二手书估价回收平台_r7iyy6nh
  • 脱发用哪种洗发水效果好?8大脱发成分测评:关键看这个 - 速递信息
  • 金融OA系统集成ueditor实现Word本地编辑的步骤?
  • 热门防脱洗发水成分大起底!看看红榜都有哪些成分 - 速递信息
  • 基于Python+flask的毕业论文开题评审管理系统_a58ik09e
  • iptables服务详解
  • win7可以使用Litemonitor监控GPU使用率
  • 2026企业数字化新引擎:北京高端小程序定制服务商全景解读 - 品牌2026
  • Python基于flask的游戏投诉私聊玩家交流信息平台_9923tjjt
  • 2026年云南地区靠谱的护坡锚固高举钻机,推荐型号多少钱 - 工业设备
  • Bcrypt 简介与加密和验证示例【加密知多少系列_】
  • 分析2026年热处理大型厂家,选哪家能满足你的需求 - 工业品牌热点
  • Python基于flask的玉米病虫害远程咨询系统的设计与实现_bydat7w3_
  • 2026年GEO优化服务好用吗,推荐几家靠谱企业 - 工业设备
  • 2026年口碑好的热处理专业供应商排名,惠州企业全梳理 - 工业品网