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

简介: 基于 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 = 0
  for (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 += span
    if (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.contentColSpan
  const span = field.span || 1
  if (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>
  <GridForm
    ref="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。

相关文章
|
JavaScript 前端开发 小程序
Vue 3的高颜值UI组件库
Vue 3的高颜值UI组件库
1550 1
|
2月前
|
前端开发 JavaScript 安全
前端组件库——Naive UI知识点大全(二)
教程来源 https://hllft.cn/category/tech-trends.html Naive UI是专为Vue 3打造的高质量开源组件库,提供90+开箱即用组件。本文详解中后台核心组件:NButton(多态/状态/尺寸灵活)、NDataTable(虚拟滚动+固定列)、NForm(声明式验证)、Message/Dialog/Notification反馈体系,以及NGrid/NSpace布局方案,并深入解析其TypeScript驱动、零CSS变量的主题定制与暗黑模式支持。
|
4月前
|
人工智能 弹性计算 API
OpenClaw/Clawdbot限流终极解决方案:免费Nvidia API+阿里云百炼Coding Plan双模型部署即可
在OpenClaw(原Clawdbot/Moltbot)的使用过程中,限流是用户最头疼的问题之一——付费Coding Plan频繁触发调用上限,免费模型功能受限,严重影响AI Agent的连续运行。2026年,这个痛点终于有了完美解法:Nvidia推出的免费API支持多款主流开源模型,搭配阿里云百炼高性价比Coding Plan,形成“免费主力+稳定备用”的双模型架构,彻底告别限流困扰。
5815 3
|
分布式计算 JavaScript 前端开发
JS中数组22种常用API总结,slice、splice、map、reduce、shift、filter、indexOf......
JS中数组22种常用API总结,slice、splice、map、reduce、shift、filter、indexOf......
592 0
|
7月前
|
人工智能 程序员 开发者
用Qoder自动生成开源项目的说明书(wiki),新人爱看,老人爱用
程序员晚枫分享AI编程新利器Qoder:一键自动生成中文项目Wiki,解决文档维护难题。支持代码同步更新,降低学习成本,助力开源项目发展。12月第二周将在重庆阿里中心分享实战经验,欢迎交流!
2304 1
用Qoder自动生成开源项目的说明书(wiki),新人爱看,老人爱用
|
JavaScript 安全 测试技术
vue封装组件发布到Npm
【10月更文挑战第17天】
|
存储 JavaScript 前端开发
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
1142 6
|
资源调度 JavaScript API
【Vue2 / Vue3】 一个贼nb,贼强大的自定义打印插件
【Vue2 / Vue3】 一个贼nb,贼强大的自定义打印插件
13661 120
|
人工智能 自然语言处理 Java
对话即服务:Spring Boot整合MCP让你的CRUD系统秒变AI助手
本文介绍了如何通过Model Context Protocol (MCP) 协议将传统Spring Boot服务改造为支持AI交互的智能系统。MCP作为“万能适配器”,让AI以统一方式与多种服务和数据源交互,降低开发复杂度。文章以图书管理服务为例,详细说明了引入依赖、配置MCP服务器、改造服务方法(注解方式或函数Bean方式)及接口测试的全流程。最终实现用户通过自然语言查询数据库的功能,展示了MCP在简化AI集成、提升系统易用性方面的价值。未来,“对话即服务”有望成为主流开发范式。
9805 7
|
存储 Ubuntu Docker
Docker从入门到精通:Docker pull命令学习
了解Docker镜像下载方法!使用`docker pull`命令从[Docker Hub](https://hub.docker.com/)获取镜像。基本语法是`docker pull NAME[:TAG]`。例如,拉取Python最新镜像的命令是`docker pull python`或`docker pull python:latest`。可选参数包括`-a`(拉取所有标签)和`--quiet`(只显示进度条)。拉取后,用`docker images`检查镜像是否成功存储。开始你的容器化之旅吧!