vxe-table 虚拟滚动下自定义行高:支持每行独立高度与自适应

在数据量巨大的表格中,虚拟滚动是保证性能的关键技术。但启用虚拟滚动后,表格默认所有行高一致(由 rowConfig.height 或默认值决定)。然而在实际业务中,常常需要某些行展示更多内容(如多行文本、图片、自定义组件),导致行高需要独立调整。vxe-table 提供了 setRowHeight, setRowHeightConf 与 getRowHeight 方法,允许你在虚拟滚动模式下动态设置每一行的具体高度,同时未设置的行仍可保持默认高度,实现真正的"自适应行高 + 自定义行高"的混合模式。

核心配置

核心 show-overflow=false,要使用动态行高功能,需满足以下基础配置:

配置项 要求 说明
rowConfig.keyField 必须 指定行数据的唯一标识字段(如 id),用于精确匹配行。
virtualYConfig.enabled true 启用纵向虚拟滚动。
virtualYConfig.gt 建议 0 超过该阈值启用虚拟滚动,设为 0 表示始终启用。

注意:虚拟滚动模式下,行高需要预先计算以正确渲染滚动条。使用 setRowHeight 后,表格内部会重新计算虚拟滚动布局,因此不会破坏虚拟滚动的性能优势。

行高的 API

方法 签名 说明
setRowHeight (rowId: string number, height: number) => void 设置指定主键行的行高(单位 px)。
setRowHeightConf (heightConf: Record<string, number>) => void 批量设置指定主键行的行高(单位 px)。
getRowHeight (rowId: string number) => number 获取指定主键行的当前行高。

代码

示例模拟生成了大量数据(包含多行文本内容、图片列),通过按钮动态将指定行的行高调整为 300px、400px、500px 等,演示了虚拟滚动下独立行高的效果。

html 复制代码
<template>
  <div>
    <vxe-button status="primary" @click="handleRowHeight(10000, 500)">设置10004=500px</vxe-button>
    <vxe-button status="primary" @click="handleRowHeight(10004, 400)">设置10004=400px</vxe-button>
    <vxe-button status="primary" @click="handleRowHeight(10095, 300)">设置10095=300px</vxe-button>

    <vxe-grid ref="gridRef" v-bind="gridOptions"></vxe-grid>
  </div>
</template>

<script setup>
import { ref, reactive, nextTick } from 'vue'
import { VxeUI } from 'vxe-table'

const gridRef = ref()

const imgUrlCellRender = reactive({
  name: 'VxeImage',
  props: {
    width: 36,
    height: 36
  }
})

const gridOptions = reactive({
  border: true,
  loading: false,
  showOverflow: false,
  height: 800,
  rowConfig: {
    keyField: 'id'
  },
  columnConfig: {
    resizable: true
  },
  virtualXConfig: {
    enabled: true,
    gt: 0
  },
  virtualYConfig: {
    enabled: true,
    gt: 0
  },
  columns: [
    { type: 'checkbox', width: 60 },
    { title: 'ID', field: 'id', width: 100 },
    { title: '列0', field: 'col0', width: 100 },
    { title: '列1', field: 'imgUrl', width: 80, cellRender: imgUrlCellRender },
    { title: '列2', field: 'col2', width: 160 },
    { title: '列3', field: 'col3', width: 200 },
    { title: '列4', field: 'col4', width: 140 },
    { title: '列5', field: 'col5', width: 300 },
    { title: '列6', field: 'col6', width: 160 },
    { title: '列7', field: 'col7', width: 120 },
    { title: '列8', field: 'col8', width: 400 },
    { title: '列9', field: 'col9', width: 160 },
    { title: '列10', field: 'col10', width: 160 },
    { title: '列11', field: 'col11', width: 180 },
    { title: '列12', field: 'col12', width: 160 },
    { title: '列13', field: 'col13', width: 80 },
    { title: '列14', field: 'col14', width: 120 },
    { title: '列15', field: 'col15', width: 360 },
    { title: '列16', field: 'col16', width: 150 },
    { title: '列17', field: 'col17', width: 380 },
    { title: '列18', field: 'col18', width: 100 },
    { title: '列19', field: 'col19', width: 290 },
    { title: '列20', field: 'col20', width: 80 },
    { title: '列21', field: 'col21', width: 100 },
    { title: '列22', field: 'col22', width: 120 },
    { title: '列23', field: 'col23', width: 270 },
    { title: '列24', field: 'col24', width: 330 },
    { title: '列25', field: 'col25', width: 460 },
    { title: '列26', field: 'col26', width: 280 },
    { title: '列27', field: 'col27', width: 220 },
    { title: '列28', field: 'col28', width: 120 },
    { title: '列29', field: 'col29', width: 180 },
    { title: '列30', field: 'col30', width: 500 },
    { title: '列31', field: 'col31', width: 600 },
    { title: '列32', field: 'col32', width: 100 },
    { title: '列33', field: 'col33', width: 490 },
    { title: '列34', field: 'col34', width: 100 },
    { title: '列35', field: 'col35', width: 150 },
    { title: '列36', field: 'col36', width: 800 },
    { title: '列37', field: 'col37', width: 400 },
    { title: '列38', field: 'col38', width: 800 },
    { title: '列39', field: 'col39', width: 360 },
    { title: '列40', field: 'col40', width: 420 },
    { title: '列41', field: 'col41', width: 100 },
    { title: '列42', field: 'col42', width: 120 },
    { title: '列43', field: 'col43', width: 280 },
    { title: '列44', field: 'col44', width: 170 },
    { title: '列45', field: 'col45', width: 370 },
    { title: '列46', field: 'col46', width: 420 },
    { title: '列47', field: 'col47', width: 170 },
    { title: '列48', field: 'col48', width: 400 },
    { title: '列49', field: 'col49', width: 220 },
    { title: '列50', field: 'col50', width: 170 },
    { title: '列51', field: 'col51', width: 160 },
    { title: '列52', field: 'col52', width: 500 },
    { title: '列53', field: 'col53', width: 280 },
    { title: '列54', field: 'col54', width: 170 },
    { title: '列55', field: 'col55', width: 370 },
    { title: '列56', field: 'col56', width: 120 },
    { title: '列57', field: 'col57', width: 170 },
    { title: '列58', field: 'col58', width: 400 },
    { title: '列59', field: 'col59', width: 220 },
    { title: '列60', field: 'col60', width: 650 },
    { title: '列61', field: 'col61', width: 600 },
    { title: '列62', field: 'col62', width: 100 },
    { title: '列63', field: 'col63', width: 490 },
    { title: '列64', field: 'col64', width: 100 },
    { title: '列65', field: 'col65', width: 150 },
    { title: '列66', field: 'col66', width: 800 },
    { title: '列67', field: 'col67', width: 400 },
    { title: '列68', field: 'col68', width: 800 },
    { title: '列69', field: 'col69', width: 360 },
    { title: '列70', field: 'col70', width: 650 },
    { title: '列71', field: 'col71', width: 600 },
    { title: '列72', field: 'col72', width: 100 },
    { title: '列73', field: 'col73', width: 490 },
    { title: '列74', field: 'col74', width: 100 },
    { title: '列75', field: 'col75', width: 150 },
    { title: '列76', field: 'col76', width: 800 },
    { title: '列77', field: 'col77', width: 400 },
    { title: '列78', field: 'col78', width: 800 },
    { title: '列79', field: 'col79', width: 360 },
    { title: '列80', field: 'col80', width: 650 }
  ],
  data: []
})

// 模拟行数据
const loadList = () => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList = []
    for (let i = 0; i < rowSize.value; i++) {
      const item = {
        id: 10000 + i,
        imgUrl:
          i % 3 === 0
            ? 'https://vxeui.com/resource/img/546.gif'
            : 'https://vxeui.com/resource/img/673.gif'
      }
      for (let j = 0; j < 100; j++) {
        if (i % 9 === 0) {
          item[`col${j}`] =
            `值_${i}_${j} 内容9内容9 内容9内容9内容9 内容9内容9内容9内容9 内容9内容9内容9内容9 内容9内容9内容9 内容9内容9`
        } else if (i % 8 === 0) {
          item[`col${j}`] = `值_${i}_${j} 内容8内容8内容8内容8`
        } else if (i % 7 === 0) {
          item[`col${j}`] = `值_${i}_${j} 内容7内容7`
        } else if (i % 6 === 0) {
          item[`col${j}`] = `值_${i}_${j} 内容6内容6内容6内容6内容6内容6内容6内容6`
        } else if (i % 5 === 0) {
          item[`col${j}`] = `值_${i}_${j} 内容5内容5内容5内容5内容5`
        } else if (i % 4 === 0) {
          item[`col${j}`] =
            `值_${i}_${j} 内容4内容4内容4内容4内容4内容4内容4内容4内容4内容4内容4内容4`
        } else {
          item[`col${j}`] = `值_${i}_${j}`
        }
      }
      dataList.push(item)
    }
    const startTime = Date.now()
    gridOptions.data = dataList
    gridOptions.loading = false
    nextTick(() => {
      VxeUI.modal.message({
        content: `加载时间 ${Date.now() - startTime} 毫秒`,
        status: 'success'
      })
    })
  }, 350)
}

const handleRowHeight = (id, height) => {
  const $grid = gridRef.value
  if ($grid) {
    $grid.setRowHeight(id, height)
  }
}

loadList()
</script>

自适应行高与自定义行高的协同机制

  • 默认行高:虚拟滚动模式下,表格会使用一个全局默认行高(可通过 rowConfig.height 设置,默认约 40px)。未调用 setRowHeight 的行均采用此高度。
  • 手动自定义:通过 setRowHeight(id, height) 为特定行指定高度后,该行将使用你设定的值,且虚拟滚动会精确调整滚动条和渲染区域。
  • "自适应"的含义:表格本身不会自动根据内容计算行高。但你可以结合业务逻辑实现自适应效果,例如:
    • 在数据加载后,遍历每一行,根据其内容长度、图片数量等动态计算所需高度,然后批量调用 setRowHeight。
    • 使用 cellRender 自定义渲染器,在渲染完成后通过 DOM 获取实际高度,再调用 setRowHeight 调整(注意性能)。

小提示:如果希望表格完全自动撑开所有行的高度(类似非虚拟滚动模式),则需要关闭虚拟滚动。虚拟滚动的核心价值在于固定行高下的极致性能,动态行高会带来一定额外计算开销,因此请按需使用。

注意事项与性能建议

注意点 说明
必须设置 keyField 没有唯一主键,setRowHeight 无法定位具体行。
调用时机 必须在表格数据渲染完成(nextTick)后调用,否则行可能尚未生成 DOM。
频繁调用 避免在滚动或高频事件中频繁调用 setRowHeight,会导致虚拟滚动重新计算布局,影响性能。建议批量设置。
行高过大 如果某行高度超过可视区域,依然可以正常滚动,但会增加滚动时的渲染开销。
与 showOverflow 的关系 若 showOverflow: true,内容会被截断并显示省略号,不会撑高行;如需内容自动换行撑高行,请设置 showOverflow: false,并在列上使用 min-width 或 word-break 样式。
获取行高 使用 getRowHeight(id) 可读取当前行高,包括默认值和已设置的值。
动态列宽 如果列宽变化导致内容换行数量变化,行高可能需重新计算,此时应重新调用 setRowHeight。
与分组/树形结构 对于树形表格或分组表,setRowHeight 同样有效,但需注意主键的唯一性。
  • vxe-table 在虚拟滚动模式下,通过 rowConfig.keyField + setRowHeight 方法,完美支持混合行高场景:
    • 大部分行使用统一的默认行高,保证虚拟滚动的流畅性。
    • 少数特殊行(如展示图片、多行文本、详情信息)可独立设置更高或更低的高度。
    • 配合业务逻辑,可以实现基于内容的"自适应"行高。

这种方法既保留了虚拟滚动处理大数据量的能力,又提供了灵活的行高定制能力,非常适合企业级复杂表格需求。

vxetable.cn

相关推荐
如果超人不会飞3 小时前
TinyVue 组件库实战指南:从安装到上手一篇就够了
vue.js
开飞机的舒克_3 小时前
vue3+router动态权限路由
前端·vue.js
dy17174 小时前
二维码打印
前端·javascript·vue.js
智商不够_熬夜来凑4 小时前
【Radio & Checkbox】
前端·javascript·vue.js
贺今宵5 小时前
Vue 3 + Capacitor 使用jeep-sqlite,web端使用本地sqlite数据库
前端·数据库·vue.js·sqlite·web
梦幻通灵7 小时前
Vue3 Element日期控件置灰明天之后日期
前端·javascript·vue.js
晓13138 小时前
【Cocos Creator 2.x】篇——第五章 游戏常用关键技术
前端·javascript·vue.js·游戏引擎
W_LuYi1858 小时前
Tauri + Rust + Vue 3 打造极速轻量桌面应用
java·开发语言·vue.js·rust
qq4356947019 小时前
Vue03
javascript·vue.js