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组件库
1374 1
|
运维 Cloud Native 前端开发
如何解决 503 Service Temporarily Unavailable?
如何解决 503 Service Temporarily Unavailable?
2943 0
|
7月前
|
人工智能 API 开发者
Dify x AiOnly平台:手把手教你调用GPT-5从零构建AI工作流!
本文介绍如何通过Dify与AiOnly平台,快速构建基于GPT-5等顶尖大模型的AI应用。涵盖环境部署、模型接入、工作流编排及实战案例,助力开发者低门槛打造专属聊天机器人,轻松实现AI应用落地。(238字)
|
分布式计算 JavaScript 前端开发
JS中数组22种常用API总结,slice、splice、map、reduce、shift、filter、indexOf......
JS中数组22种常用API总结,slice、splice、map、reduce、shift、filter、indexOf......
559 0
|
7月前
|
SQL 监控 关系型数据库
SQL优化技巧:让MySQL查询快人一步
本文深入解析了MySQL查询优化的核心技巧,涵盖索引设计、查询重写、分页优化、批量操作、数据类型优化及性能监控等方面,帮助开发者显著提升数据库性能,解决慢查询问题,适用于高并发与大数据场景。
|
资源调度 JavaScript API
【Vue2 / Vue3】 一个贼nb,贼强大的自定义打印插件
【Vue2 / Vue3】 一个贼nb,贼强大的自定义打印插件
12467 120
|
12月前
|
存储 JavaScript 前端开发
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
1004 6
|
Web App开发 JavaScript 前端开发
2024年纯前端VUE在线编辑微软Office/金山WPS的Word/Excel文档
现在,随着数字化进程渗透到到各行各业,数据安全已经成为了数字化革命中的重要组成部分,而在线Office成在OA、ERP、文档系统中得到了广泛的应用,为我国的信息化事业也做出了巨大贡献。随着操作系统、浏览器及Office软件的不断升级和更新换代,加上国家对信息化、数字化系统要求的不断提升,一些厂家的WebOffice控件产品不断被淘汰出局,而现存的几个产品也存在以下几个问题:
1628 93
2024年纯前端VUE在线编辑微软Office/金山WPS的Word/Excel文档
|
并行计算 Java API
Java List集合取交集的八种不同实现方式
Java List集合取交集的八种不同实现方式
|
存储 网络协议 算法
UDP & TCP 超详解
本文详细介绍了UDP与TCP协议的相关知识。首先阐述了UDP协议结构,包括其报文格式、各字段含义及其CRC校验和机制。接着深入探讨了TCP协议,涵盖其协议结构、确认应答机制、超时重传策略、三次握手与四次挥手过程,以及滑动窗口、流量控制和拥塞控制等关键技术。最后分析了TCP在异常情况下的处理机制,如进程崩溃、主机关机、掉电和网线断开等情况。
804 6