vue 表格组件 vxe-table 实现多行编辑,支持增删改查与校验

vxe-table 默认是单行编辑模式。通过"查看插槽 + 编辑插槽共用同一模板"的方式,可以伪实现多行同时编辑,并完整保留增删改查、数据校验等原生能力。多行校验通过 validConfig.msgMode: 'full' 设置,调用 fullValidate 即可一次性校验所有行。

vxe-table 如何实现多行同时编辑?

为每个可编辑列同时配置 editRender 与 slots.default,插槽内根据 row.isEdit 切换渲染输入组件或纯文本。点击"编辑"时设置 row.isEdit = true 并调用 setEditRow,即可让多行同时处于可编辑状态。

  • 核心思路:
    • editRender 保证列具备编辑能力与校验能力。
    • slots.default 接管渲染:row.isEdit 为 true 时渲染输入组件,否则渲染纯文本。
    • 每行独立维护 isEdit 与 isLoading 状态,互不干扰,因此可多行同时编辑。
  • 生效前提:
    • editConfig.mode 设为 'row',trigger 设为 'manual',由代码手动控制编辑状态。
    • 行主键 rowConfig.keyField 必须唯一,否则编辑状态可能错乱。
    • 每行的 isEdit、isLoading 需作为数据字段维护,确保响应式更新。
  • 预期结果:点击某行"编辑"后该行变为输入组件,其他已处于编辑状态的行不受影响;可同时编辑多行,逐行保存或统一保存。

vxe-table 如何实现多行同时校验?

设置 validConfig.msgMode: 'full',调用 grid.fullValidate() 或 grid.fullValidate(row),会一次性返回所有校验不通过的行与规则,而不是只提示第一个错误。

  • 生效前提:
    • editRules 中为需要校验的字段配置规则。
    • 校验前相关行应处于编辑状态或已被 setEditRow 激活,否则部分字段可能不参与校验。
    • fullValidate() 不传参数时校验全表;传入 row 时校验指定行。
  • 预期结果:返回 errMap 对象,键为行标识,值为该行的错误规则数组。校验不通过时,可将对应行重新置为编辑状态,方便用户定位修改。

代码

下面的配置实现了多行编辑、行内保存/取消/删除、批量新增/删除/保存,以及全量校验:

html 复制代码
<template>
  <div>
    <vxe-button status="primary" @click="addEvent">新增</vxe-button>
    <vxe-button status="error" @click="removeEvent">删除</vxe-button>
    <vxe-button status="success" @click="saveEvent">保存</vxe-button>

    <vxe-grid ref="gridRef" v-bind="gridOptions">
      <template #name_default="slotParams">
        <vxe-input v-if="slotParams.row.isEdit" v-model="slotParams.row.name" @change="changeEditEvent(slotParams)"></vxe-input>
        <span v-else>{{ slotParams.row.name }}</span>
      </template>

      <template #sex_default="slotParams">
        <vxe-select v-if="slotParams.row.isEdit" v-model="slotParams.row.sex" :options="sexList" @change="changeEditEvent(slotParams)"></vxe-select>
        <span v-else>{{ slotParams.row.sex }}</span>
      </template>

      <template #age_default="slotParams">
        <vxe-input v-if="slotParams.row.isEdit" v-model="slotParams.row.age" @change="changeEditEvent(slotParams)"></vxe-input>
        <span v-else>{{ slotParams.row.age }}</span>
      </template>

      <template #action="{ row }">
        <vxe-button v-if="row.isEdit" mode="text" status="success" :loading="row.isLoading" @click.stop="handleRowSave(row)">保存</vxe-button>
        <vxe-button v-else mode="text" status="primary" @click.stop="handleRowEdit(row)">编辑</vxe-button>
        <vxe-button v-if="row.isEdit" mode="text" @click.stop="cancelRowEdit(row)">取消</vxe-button>
        <vxe-button mode="text" status="error" @click.stop="handleRowRemove(row)">删除</vxe-button>
      </template>
    </vxe-grid>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { VxeUI } from 'vxe-pc-ui'
import XEUtils from 'xe-utils'

const gridRef = ref()

const sexList = ref([
  { label: '男', value: 'Man' },
  { label: '女', value: 'Women' }
])

const gridOptions = reactive({
  border: true,
  showOverflow: true,
  keepSource: true,
  rowConfig: {
    keyField: 'id'
  },
  editConfig: {
    trigger: 'manual',
    mode: 'row',
    showStatus: true
  },
  validConfig: {
    msgMode: 'full'
  },
  editRules: {
    name: [
      { required: true, message: '必须填写' }
    ],
    sex: [
      { required: true, message: '必须填写' }
    ]
  },
  columns: [
    { type: 'checkbox', width: 60, align: 'center' },
    { type: 'seq', width: 70 },
    { field: 'name', title: 'Name', editRender: { name: 'VxeInput' }, slots: { default: 'name_default' } },
    { field: 'sex', title: 'Sex', editRender: { name: 'VxeSelect', options: sexList.value }, slots: { default: 'sex_default' } },
    { field: 'age', title: 'Age', editRender: { name: 'VxeInput' }, slots: { default: 'age_default' } },
    { field: 'action', title: '操作', width: 160, slots: { default: 'action' } }
  ],
  data: []
})

const loadList = () => {
  // 模拟后端接口
  gridOptions.loading = true
  setTimeout(() => {
    gridOptions.data = [
      { id: 10001, name: 'Test1', role: 'Develop', sex: 'Man', age: 28, address: 'test abc', isEdit: false, isLoading: false },
      { id: 10002, name: 'Test2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou', isEdit: false, isLoading: false },
      { id: 10003, name: 'Test3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai', isEdit: false, isLoading: false },
      { id: 10004, name: 'Test4', role: 'Designer', sex: 'Women', age: 24, address: 'Shanghai', isEdit: false, isLoading: false }
    ]
    gridOptions.loading = false
  }, 200)
}

const handleRowEdit = (row) => {
  const $grid = gridRef.value
  if (!$grid) {
    return
  }
  row.isEdit = true
  // 激活不自动聚焦
  $grid.setEditRow(row)
  // 激活并自动聚焦第一个可编辑列
  // $grid.setEditRow(row, true)
}

const cancelRowEdit = async (row) => {
  const $grid = gridRef.value
  if (!$grid) {
    return
  }
  row.isEdit = false
  $grid.clearEdit(row)
  $grid.clearValidate(row)
}

const handleRowRemove = async (row) => {
  const $grid = gridRef.value
  if (!$grid) {
    return
  }
  $grid.remove(row)
}

const handleRowSave = async (row) => {
  const $grid = gridRef.value
  if (!$grid) {
    return
  }
  const errMap = await $grid.fullValidate(row)
  if (errMap) {
    // 将校验不通过行显示编辑状态
    XEUtils.each(errMap, errRules => {
      errRules.forEach(errRule => {
        handleRowEdit(errRule.row)
      })
    })
    VxeUI.modal.message({
      status: 'error',
      content: '校验不通过!'
    })
    return
  }
  row.isLoading = true
  // 模拟后端接口
  setTimeout(() => {
    VxeUI.modal.message({
      status: 'success',
      content: '保存成功'
    })
    row.isLoading = false
    cancelRowEdit(row)
  }, 300)
}

const addEvent = async () => {
  const $grid = gridRef.value
  if (!$grid) {
    return
  }
  const record = {
    isEdit: false,
    isLoading: false
  }
  const { row: newRow } = await $grid.insert(record)
  handleRowEdit(newRow)
}

const removeEvent = async () => {
  const $grid = gridRef.value
  if (!$grid) {
    return
  }
  const selectRecords = $grid.getCheckboxRecords()
  if (selectRecords.length > 0) {
    const type = await VxeUI.modal.confirm({
      title: '系统提示',
      content: '请您确认是否删除?'
    })
    if (type === 'confirm') {
      $grid.removeCheckboxRow()
      VxeUI.modal.message({
        content: '已删除选中',
        status: 'success'
      })
    }
  } else {
    VxeUI.modal.message({
      content: '未选择数据',
      status: 'info'
    })
  }
}

const saveEvent = async () => {
  const $grid = gridRef.value
  if (!$grid) {
    return
  }
  const errMap = await $grid.fullValidate()
  if (errMap) {
    // 将校验不通过行显示编辑状态
    XEUtils.each(errMap, errRules => {
      errRules.forEach(errRule => {
        handleRowEdit(errRule.row)
      })
    })
    VxeUI.modal.message({
      status: 'error',
      content: '校验不通过!'
    })
    return
  }
  const { insertRecords, updateRecords, removeRecords } = $grid.getRecordset()
  if (insertRecords.length > 0 || updateRecords.length > 0 || removeRecords.length > 0) {
    console.log(`新增:${insertRecords.length} 行,修改:${updateRecords.length} 行,删除:${removeRecords.length} 行`)
    loadList()
    VxeUI.modal.message({
      content: '保存成功',
      status: 'success'
    })
  } else {
    VxeUI.modal.message({
      content: '数据未改动',
      status: 'warning'
    })
  }
}

const changeEditEvent = (slotParams) => {
  const $grid = gridRef.value
  if ($grid) {
    $grid.updateStatus(slotParams)
  }
}

loadList()
</script>
  • 预期结果:
    • 点击某行"编辑",该行切换为输入组件,其他行可继续编辑,支持多行同时处于编辑状态。
    • 行内"保存"仅校验当前行;顶部"保存"校验全表,失败时自动将错误行置为编辑状态并提示。
    • "新增"插入一行并立即进入编辑;"删除"支持行内删除与勾选批量删除。

关键配置参数

配置项 类型 默认值 说明
editConfig.mode String 'cell' 设为 'row' 表示行编辑模式
editConfig.trigger String 'click' 设为 'manual' 由代码控制编辑状态
editConfig.showStatus Boolean false 是否显示编辑状态样式
validConfig.msgMode String 'single' 设为 'full' 返回全部错误,支持多行校验
rowConfig.keyField String --- 行主键字段,必须唯一
keepSource Boolean false 保留原始数据,getRecordset() 依赖此配置
row.isEdit Boolean false 自定义字段,控制当前行是否处于编辑态
row.isLoading Boolean false 自定义字段,控制行内保存按钮的加载状态

常用方法

方法 说明
$grid.setEditRow(row) 将指定行置为编辑状态;第二参数传 true 时自动聚焦第一个可编辑列
$grid.clearEdit(row) 清除指定行的编辑状态
$grid.clearValidate(row) 清除指定行的校验提示
$grid.fullValidate(row?) 校验全表或指定行,返回 errMap;msgMode: 'full' 时返回全部错误
$grid.insert(record) 插入一行,返回 { row }
$grid.remove(row) 删除指定行
$grid.removeCheckboxRow() 删除复选框选中的行
$grid.getCheckboxRecords() 获取复选框选中的行
$grid.getRecordset() 获取变更集:{ insertRecords, updateRecords, removeRecords }
$grid.updateStatus(slotParams) 更新行状态,标记数据已修改

通过以上就能直接实现多行可编辑表格

https://vxetable.cn

相关推荐
u0111026752 小时前
Vue 页面离开后还在轮询?统一清理计时器、监听器与观察器
前端·javascript·vue.js
ss2732 小时前
基于springboot+vue+mysql「校园二手教材循环交易」小程序,三端全跑通
vue.js·spring boot·mysql
跨境数据猎手3 小时前
跨境电商系统三大 Web 漏洞的防护:SQL 注入、XSS、CSRF
前端·sql·xss
冒险的豆子4 小时前
Proxy & Vue 响应式(合并精简版,适合面试)
前端·javascript·vue.js
Java后端的Ai之路4 小时前
前端技术Vue-18-视图中的渲染
前端·javascript·vue.js
程序员海军10 小时前
用了两年多 GPT,我最近开始换模型了,还折腾了个求职避坑 Agent
前端·人工智能·后端
zttbee11 小时前
echarts图表tooltip色块失效
前端·javascript·echarts
excel12 小时前
IndexNow:让搜索引擎更快发现网站更新的主动通知协议
前端·后端·架构
子兮曰13 小时前
CSS 复现 23 种设计风格:从拟物到液态玻璃的成本阶梯与兜底写法【实战】
前端·css·视觉设计