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。

相关文章
|
3天前
|
Web App开发 人工智能 网络安全
OpenClaw阿里云及本地部署喂饭级教程:+AI4SE领域深度协作、搭建 AI 学习助手指南
用OpenClaw辅助学习时,很多人会陷入“高产出但低价值”的困境:AI能快速整合信息生成结构化内容,却缺乏领域深度与独到见解,长期使用还会出现内容同质化问题。核心原因在于角色定位偏差——将AI视为“执行写手”而非“领域专家”。
202 1
|
1月前
|
人工智能 自然语言处理 数据可视化
Java与AI的深度融合:JBoltAI赋能基础AI能力探索
本文介绍JBoltAI框架如何赋能Java开发者快速构建AI应用,涵盖数据管理、可视化、OCR识别、Text2SQL/JSON、流式对话及多模态交互等核心能力,并支持20+大模型无缝接入,推动Java迈向AIGS新时代。(239字)
139 2
|
2月前
|
人工智能 搜索推荐 持续交付
阿里云GPU服务器租用价格表2026年最新:L20/A10/V100/T4/P100/P4 GPU卡支持
阿里云2026年最新GPU服务器(现称EGS弹性GPU服务)支持L20、A10、V100等多款GPU卡,覆盖AI推理、图形渲染、科学计算等场景。提供按量、包月、包年多种计费,gn8is(L20)等实例月付低至6919元起,支持1小时起租与机密计算。(239字)
624 9
|
2月前
|
数据库
向量数据库实战:从“看起来能用”到“真的能用”,中间隔着一堆坑
本文揭示向量数据库实战的七大关键陷阱:选型前需明确业务本质(模糊匹配 or 精确查询?);embedding 比数据库本身更重要,决定语义“世界观”;文档切分是核心工程,非辅助步骤;建库成功≠可用,TopK 准确率会随数据演进失效;“相似但不可用”是常态,必须引入 rerank;需建立可追溯的bad case排查路径;向量库是长期系统,非一次性组件。核心结论:难在“用对”,不在“用上”。
|
2月前
|
人工智能 图形学 异构计算
阿里云GPU服务器NVIDIA L20 GPU卡收费价格,GPU计算型gn8is实例规格族2026年最新整理
阿里云GPU服务器gn8is实例搭载NVIDIA L20卡,单卡48GB显存,支持FP8加速,专为30B–70B大模型推理与图形处理优化。2026年最新月付价:6919元起(8核64G+1卡),最高55354元(128核1024G+8卡)。
235 1
|
27天前
|
网络协议 安全 Linux
Rocky Linux 9:Samba服务安装配置全攻略
本文手把手教你用Rocky Linux 9从零搭建Samba文件共享服务:详解防火墙/SELinux配置、smb.conf核心参数(global与共享段)、目录权限与SELinux上下文设置、Samba用户创建及Win/Linux双端访问,新手也能轻松实现跨系统无缝传文件!
|
1月前
|
人工智能 自然语言处理 搜索推荐
智能体来了:从 0 到 1 搭建个人 AI 助手
个人 AI 助手正在从“聊天工具”升级为“数字助手系统”。它不仅能回答问题,还可以帮你整理信息、生成内容、管理任务甚至辅助决策。本文从 0 到 1 介绍个人 AI 助手的核心能力、搭建思路与实用步骤,帮助读者打造真正能提升效率的个人 AI 助手。
287 0
|
4天前
|
人工智能 并行计算 算法
video-subtitle-remover(VSR)--开源AI去字幕方案深度解析
VSR(video-subtitle-remover)是一款开源AI视频去字幕工具,支持本地运行,无需上传数据。它融合STTN、LaMa、ProPainter三大前沿修复模型,可智能检测并擦除硬字幕/水印,保持原分辨率与画质。兼容CUDA/DirectML,适配NVIDIA/AMD/Intel显卡,兼顾隐私性、可控性与高性能。
163 6
video-subtitle-remover(VSR)--开源AI去字幕方案深度解析
|
23小时前
|
SQL 人工智能 自然语言处理
我用DataClaw打造了一个7X24小时的数据助理
阿里云DMS DataClaw是7×24小时AI数据助理,支持自然语言提工单、智能巡检、多任务编排、SQL风险预审等9项硬功能,原生集成DMS安全体系,覆盖MySQL/Oracle等60+数据源。现在可免费试用,快来体验吧。
64 9
|
3天前
|
人工智能 网络安全 开发工具
让OpenClaw价值翻倍:阿里云/本地部署与10个官方skill 解锁 AI Agent 效率上限
大多数人使用OpenClaw时,仅停留在“指令-输出”的基础层面,却忽略了其背后可深度挖掘的效率杠杆。OpenClaw的核心瓶颈与Claude Code一致——上下文窗口是有限的“白板”,如何让这块白板发挥最大价值,决定了AI开发的效率上限。
149 11