Skip to content

useVxeTable VxeTable 工具

vxe-table 是很好用的 vue 表格,但在使用时可能需要进一步封装,这里提供轻量的工具,方便使用。

未使用状态

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

<script lang="tsx" setup>
import { VxeGrid } from 'vxe-table'
import 'vxe-table/styles/all.scss'
import { reactive } from 'vue'
import { type VxeGridProps } from 'vxe-table'

interface RowVO {
  id: number
  [key: string]: string | number | boolean | any[]
}

const gridOptions = reactive<VxeGridProps<RowVO>>({
  border: true,
  loading: false,
  columnConfig: {
    resizable: true,
  },
  scrollY: {
    enabled: false,
    gt: 0,
  },
  columns: [
    { type: 'checkbox', width: 100, fixed: 'left' },
    { title: '列0', field: 'col0', width: 100 },
    { title: '列1', field: 'imgUrl', width: 100 },
    { title: '列2', field: 'col2', width: 300 },
    { title: '列3', field: 'col3', width: 300 },
    { title: '列4', field: 'col4', width: 300 },
    { title: '列5', field: 'col5', width: 300 },
    { title: '列6', field: 'col6', width: 200 },
    { title: '列7', field: 'col7', width: 200 },
    { title: '列8', field: 'col8', width: 200, fixed: 'right' },
  ],
  data: [],
})

// 模拟行数据
const loadData = (rowSize: number) => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList: RowVO[] = []
    for (let i = 0; i < rowSize; i++) {
      const item: RowVO = {
        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 < 10; 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)
    }

    gridOptions.data = dataList
    gridOptions.loading = false
  }, 350)
}

loadData(50)
</script>

<style>
/* 无关代码,为修复文档通用样式造成的影响 */
.vp-doc .vxe-grid table,
.vp-doc .vxe-grid td,
.vp-doc .vxe-grid th {
  margin: initial;
  border: initial;
  padding: initial;
  overflow-x: initial;
}

.vitepress-demo-preview__element-plus__container:has(.vxe-grid) {
  overflow: initial;
}
</style>

表头吸附、滚动条吸附(未开启虚拟滚动)

配置 sticky:true

<template>
  <vxe-grid ref="gridRef" v-bind="gridOptions" class="top-64"> </vxe-grid>
</template>

<script lang="tsx" setup>
import { VxeGrid } from 'vxe-table'
import 'vxe-table/styles/all.scss'
import { reactive, ref } from 'vue'
import { type VxeGridProps } from 'vxe-table'
import { useVxeTable } from '@x-anything/hooks'

interface RowVO {
  id: number
  [key: string]: string | number | boolean | any[]
}

const gridOptions = reactive<VxeGridProps<RowVO>>({
  border: true,
  loading: false,
  columnConfig: {
    resizable: true,
  },
  scrollY: {
    enabled: false,
    gt: 0,
  },
  columns: [
    { type: 'checkbox', width: 100, fixed: 'left' },
    { title: '列0', field: 'col0', width: 100 },
    { title: '列1', field: 'imgUrl', width: 100 },
    { title: '列2', field: 'col2', width: 300 },
    { title: '列3', field: 'col3', width: 300 },
    { title: '列4', field: 'col4', width: 300 },
    { title: '列5', field: 'col5', width: 300 },
    { title: '列6', field: 'col6', width: 200 },
    { title: '列7', field: 'col7', width: 200 },
    { title: '列8', field: 'col8', width: 200, fixed: 'right' },
  ],
  data: [],
})

// 模拟行数据
const loadData = (rowSize: number) => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList: RowVO[] = []
    for (let i = 0; i < rowSize; i++) {
      const item: RowVO = {
        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 < 10; 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)
    }

    gridOptions.data = dataList
    gridOptions.loading = false
  }, 350)
}

loadData(50)

const gridRef = ref()
useVxeTable(gridRef, {
  sticky: true,
})
</script>

<style>
.top-64 .vxe-table--header-wrapper {
  top: 64px !important;
}
</style>

表头吸附、滚动条吸附(开启虚拟滚动)

配置 virtualSticky:true

TIP

由于 vxe-table 会监测自身及父元素的高度变化来设置虚拟高度,如果直接设置表格或其父元素高度会导致滚动时虚拟高度异常,来回跳,闪现空白,所以使用 virtualSticky 时请提供两层祖先元素,useVxeTable 会将父父元素作为粘性定位的窗口,父元素作为粘性定位的元素,从而不改变表格自身,避免负面影响

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

<script lang="tsx" setup>
import { VxeGrid } from 'vxe-table'
import 'vxe-table/styles/all.scss'
import { computed, reactive, ref } from 'vue'
import { type VxeGridProps } from 'vxe-table'
import { useVxeTable } from '@x-anything/hooks'

interface RowVO {
  id: number
  [key: string]: string | number | boolean | any[]
}

const gridOptions = reactive<VxeGridProps<RowVO>>({
  height: computed(() => window.innerHeight) as any,
  border: true,
  loading: false,
  columnConfig: {
    resizable: true,
  },
  scrollY: {
    enabled: true,
    gt: 0,
  },
  columns: [
    { type: 'checkbox', width: 100, fixed: 'left' },
    { title: '列0', field: 'col0', width: 100 },
    { title: '列1', field: 'imgUrl', width: 100 },
    { title: '列2', field: 'col2', width: 300 },
    { title: '列3', field: 'col3', width: 300 },
    { title: '列4', field: 'col4', width: 300 },
    { title: '列5', field: 'col5', width: 300 },
    { title: '列6', field: 'col6', width: 200 },
    { title: '列7', field: 'col7', width: 200 },
    { title: '列8', field: 'col8', width: 200, fixed: 'right' },
  ],
  data: [],
})

// 模拟行数据
const loadData = (rowSize: number) => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList: RowVO[] = []
    for (let i = 0; i < rowSize; i++) {
      const item: RowVO = {
        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 < 10; 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)
    }

    gridOptions.data = dataList
    gridOptions.loading = false
  }, 350)
}

loadData(50)

const gridRef = ref()
useVxeTable(gridRef, {
  virtualSticky: true,
})
</script>

<style>
.top-64 .vxe-table--header-wrapper {
  top: 64px !important;
}
</style>

大量复杂列

目前 vxetable 在存在冻结列的情况下渲染速度较慢,在有大量列时容易出现白屏;并且为了提高性能,左右及中间滚动存在防抖处理,在滚动时会有延时错位的感觉。

<template>
  <x-button @click="handleOpenDialog">打开弹窗(为避免样式污染)</x-button>
  <Teleport to="body">
    <dialog id="largeClumnsdialog">
      <div>
        <x-button @click="loadData(5000)">加载5k条</x-button>
        <x-button @click="loadData(10000)">加载1w条</x-button>
        <x-button @click="loadData(30000)">加载3w条</x-button>

        <vxe-grid v-bind="gridOptions">
          <template #buttonSlot>
            <x-tooltip title="tooltip">
              <x-button>按钮</x-button>
            </x-tooltip>
          </template>
        </vxe-grid>
      </div>
      <x-button @click="handleCloseDialog">关闭</x-button>
    </dialog>
  </Teleport>
</template>

<script lang="tsx" setup>
import { VxeGrid } from 'vxe-table'
import 'vxe-table/styles/all.scss'
import { reactive } from 'vue'
import { type VxeGridProps } from 'vxe-table'
interface RowVO {
  id: number
  [key: string]: string | number | boolean | any[]
}

const gridOptions = reactive<VxeGridProps<RowVO>>({
  showOverflow: true,
  border: true,
  loading: false,
  height: 600,
  columnConfig: {
    resizable: true,
  },
  scrollY: {
    enabled: true,
    gt: 0,
  },
  columns: [
    { type: 'checkbox', width: 80, fixed: 'left' },
    {
      title: '列0',
      fixed: 'left',
      children: [
        { title: '列1', field: 'imgUrl', width: 80 },
        { title: '列2', field: 'col2', width: 80 },
      ],
    },
    { title: '列3', field: 'col3', width: 300 },
    { title: '列4', field: 'col4', width: 300 },
    { title: '列5', field: 'col5', width: 300 },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列7',
      fixed: 'right',
      children: [
        { title: '列9', field: 'col9', width: 80 },
        { title: '列10', field: 'col10', width: 80 },
      ],
    },
    { title: '列8', field: 'col8', width: 80, fixed: 'right' },
  ],
  data: [],
})

// 模拟行数据
const loadData = (rowSize: number) => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList: RowVO[] = []
    for (let i = 0; i < rowSize; i++) {
      const item: RowVO = {
        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 < 10; 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)
    }

    gridOptions.data = dataList
    gridOptions.loading = false
  }, 350)
}

loadData(200)

// 不相关代码,传送到body下为避免样式污染造成的虚拟滚动错位
const handleOpenDialog = () => {
  const dialog = document.getElementById(
    'largeClumnsdialog'
  ) as HTMLDialogElement
  dialog.showModal()
}
const handleCloseDialog = () => {
  const dialog = document.getElementById(
    'largeClumnsdialog'
  ) as HTMLDialogElement
  dialog.close()
}
</script>

相比之下 v4.7.59 等低版本(不支持自适应行高)在相同的情况下基本不会白屏。(codesandbox 中可尝试修改两种版本比较)

打开 codesandbox(4.7.59 版本 vxetable)

如果开启了自适应行高性能会进一步下降

<template>
  <x-button @click="handleOpenDialog">打开弹窗(为避免样式污染)</x-button>
  <Teleport to="body">
    <dialog id="largeClumnsAutoRowHeightDialog">
      <div>
        <x-button @click="loadData(5000)">加载5k条</x-button>
        <x-button @click="loadData(10000)">加载1w条</x-button>
        <x-button @click="loadData(30000)">加载3w条</x-button>

        <vxe-grid v-if="show" v-bind="gridOptions">
          <template #buttonSlot>
            <x-tooltip title="tooltip">
              <x-button>按钮</x-button>
            </x-tooltip>
          </template>
        </vxe-grid>
      </div>
      <x-button @click="handleCloseDialog">关闭</x-button>
    </dialog>
  </Teleport>
</template>

<script lang="tsx" setup>
import { VxeGrid } from 'vxe-table'
import 'vxe-table/styles/all.scss'
import { reactive, ref } from 'vue'
import { type VxeGridProps } from 'vxe-table'
interface RowVO {
  id: number
  [key: string]: string | number | boolean | any[]
}

const gridOptions = reactive<VxeGridProps<RowVO>>({
  border: true,
  loading: false,
  height: 600,
  columnConfig: {
    resizable: true,
  },
  scrollY: {
    enabled: true,
    gt: 0,
  },
  columns: [
    { type: 'checkbox', width: 100, fixed: 'left' },
    {
      title: '列0',
      fixed: 'left',
      children: [
        { title: '列1', field: 'imgUrl', width: 100 },
        { title: '列2', field: 'col2', width: 200 },
      ],
    },
    { title: '列3', field: 'col3', width: 300 },
    { title: '列4', field: 'col4', width: 300 },
    { title: '列5', field: 'col5', width: 300 },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列7',
      fixed: 'right',
      children: [
        { title: '列9', field: 'col9', width: 80 },
        { title: '列10', field: 'col10', width: 80 },
      ],
    },
    { title: '列8', field: 'col8', width: 80, fixed: 'right' },
  ],
  data: [],
})

// 模拟行数据
const loadData = (rowSize: number) => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList: RowVO[] = []
    for (let i = 0; i < rowSize; i++) {
      const item: RowVO = {
        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 < 10; 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)
    }

    gridOptions.data = dataList
    gridOptions.loading = false
  }, 350)
}

loadData(200)

// 不相关代码,传送到body下为避免样式污染造成的虚拟滚动错位
const show = ref(false)
const handleOpenDialog = () => {
  const dialog = document.getElementById(
    'largeClumnsAutoRowHeightDialog'
  ) as HTMLDialogElement
  dialog.showModal()
  show.value = true
}
const handleCloseDialog = () => {
  const dialog = document.getElementById(
    'largeClumnsAutoRowHeightDialog'
  ) as HTMLDialogElement
  dialog.close()
}
</script>

采用原生 sticky 来固定左右列

在没有固定列的情况下性能会好很多,useVxeTable 提供原生粘性定位固定列,配置 colSticky:true ,如果有列宽拖动还需绑定 resizableChange

性能会比自带固定列好点,不会错位

<template>
  <x-button @click="handleOpenDialog">打开弹窗(为避免样式污染)</x-button>
  <Teleport to="body">
    <dialog id="largeClumnsStickyDialog">
      <div>
        <x-button @click="loadData(5000)">加载5k条</x-button>
        <x-button @click="loadData(10000)">加载1w条</x-button>
        <x-button @click="loadData(30000)">加载3w条</x-button>

        <vxe-grid
          ref="gridRef"
          v-bind="gridOptions"
          @resizable-change="resizableChange"
        >
          <template #buttonSlot>
            <x-tooltip title="tooltip">
              <x-button>按钮</x-button>
            </x-tooltip>
          </template>
        </vxe-grid>
      </div>
      <x-button @click="handleCloseDialog">关闭</x-button>
    </dialog>
  </Teleport>
</template>

<script lang="tsx" setup>
import { VxeGrid } from 'vxe-table'
import 'vxe-table/styles/all.scss'
import { reactive, ref, toRef } from 'vue'
import { type VxeGridProps } from 'vxe-table'
import { useVxeTable } from '@x-anything/hooks'

interface RowVO {
  id: number
  [key: string]: string | number | boolean | any[]
}

const gridOptions = reactive<VxeGridProps<RowVO>>({
  showOverflow: true,
  border: true,
  loading: false,
  height: 600,
  columnConfig: {
    resizable: true,
  },
  scrollY: {
    enabled: false,
    gt: 0,
  },
  columns: [
    { type: 'checkbox', width: 80, fixed: 'left' },
    {
      title: '列0',
      fixed: 'left',
      children: [
        { title: '列1', field: 'imgUrl', width: 80 },
        { title: '列2', field: 'col2', width: 80 },
      ],
    },
    { title: '列3', field: 'col3', width: 300 },
    { title: '列4', field: 'col4', width: 300 },
    { title: '列5', field: 'col5', width: 300 },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列7',
      fixed: 'right',
      children: [
        { title: '列9', field: 'col9', width: 80 },
        { title: '列10', field: 'col10', width: 80 },
      ],
    },
    { title: '列8', field: 'col8', width: 80, fixed: 'right' },
  ],
  data: [],
})

const gridRef = ref()
const { resizableChange } = useVxeTable(gridRef, {
  colSticky: true,
})

// 模拟行数据
const loadData = (rowSize: number) => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList: RowVO[] = []
    for (let i = 0; i < rowSize; i++) {
      const item: RowVO = {
        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 < 10; 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)
    }

    gridOptions.data = dataList
    gridOptions.loading = false
  }, 350)
}

loadData(100)

// 不相关代码,传送到body下为避免样式污染造成的虚拟滚动错位
const handleOpenDialog = () => {
  const dialog = document.getElementById(
    'largeClumnsStickyDialog'
  ) as HTMLDialogElement
  dialog.showModal()
}
const handleCloseDialog = () => {
  const dialog = document.getElementById(
    'largeClumnsStickyDialog'
  ) as HTMLDialogElement
  dialog.close()
}
</script>

采用原生 sticky 来固定左右列(开启虚拟滚动)

目前 4.8.x 版本白屏问题比较严重,待官方优化

如果开启虚拟滚动还需绑定 cellStyle

<template>
  <x-button @click="handleOpenDialog">打开弹窗(为避免样式污染)</x-button>
  <Teleport to="body">
    <dialog id="largeClumnsStickyVirtualDialog">
      <div>
        <x-button @click="loadData(5000)">加载5k条</x-button>
        <x-button @click="loadData(10000)">加载1w条</x-button>
        <x-button @click="loadData(30000)">加载3w条</x-button>

        <vxe-grid
          ref="gridRef"
          v-bind="gridOptions"
          @resizable-change="resizableChange"
        >
          <template #buttonSlot>
            <x-tooltip title="tooltip">
              <x-button>按钮</x-button>
            </x-tooltip>
          </template>
        </vxe-grid>
      </div>
      <x-button @click="handleCloseDialog">关闭</x-button>
    </dialog>
  </Teleport>
</template>

<script lang="tsx" setup>
import { VxeGrid } from 'vxe-table'
import 'vxe-table/styles/all.scss'
import { reactive, ref, toRef } from 'vue'
import { type VxeGridProps } from 'vxe-table'
import { useVxeTable } from '@x-anything/hooks'

interface RowVO {
  id: number
  [key: string]: string | number | boolean | any[]
}

const gridOptions = reactive<VxeGridProps<RowVO>>({
  showOverflow: true,
  border: true,
  loading: false,
  height: 600,
  columnConfig: {
    resizable: true,
  },
  scrollY: {
    enabled: true,
    gt: 0,
  },
  columns: [
    { type: 'checkbox', width: 80, fixed: 'left' },
    {
      title: '列0',
      fixed: 'left',
      children: [
        { title: '列1', field: 'imgUrl', width: 80 },
        { title: '列2', field: 'col2', width: 80 },
      ],
    },
    { title: '列3', field: 'col3', width: 300 },
    { title: '列4', field: 'col4', width: 300 },
    { title: '列5', field: 'col5', width: 300 },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列7',
      fixed: 'right',
      children: [
        { title: '列9', field: 'col9', width: 80 },
        { title: '列10', field: 'col10', width: 80 },
      ],
    },
    { title: '列8', field: 'col8', width: 80, fixed: 'right' },
  ],
  data: [],
})

const gridRef = ref()
const { resizableChange, cellStyle } = useVxeTable(gridRef, {
  colSticky: true,
})
gridOptions.cellStyle = cellStyle

// 模拟行数据
const loadData = (rowSize: number) => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList: RowVO[] = []
    for (let i = 0; i < rowSize; i++) {
      const item: RowVO = {
        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 < 10; 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)
    }

    gridOptions.data = dataList
    gridOptions.loading = false
  }, 350)
}

loadData(100)

// 不相关代码,传送到body下为避免样式污染造成的虚拟滚动错位
const handleOpenDialog = () => {
  const dialog = document.getElementById(
    'largeClumnsStickyVirtualDialog'
  ) as HTMLDialogElement
  dialog.showModal()
}
const handleCloseDialog = () => {
  const dialog = document.getElementById(
    'largeClumnsStickyVirtualDialog'
  ) as HTMLDialogElement
  dialog.close()
}
</script>

完整功能

<template>
  <div>
    <x-button @click="loadData(5000)">加载5k条</x-button>
    <x-button @click="loadData(10000)">加载1w条</x-button>
    <x-button @click="loadData(30000)">加载3w条</x-button>

    <div>
      <div>
        <vxe-grid
          class="top-64"
          ref="gridRef"
          v-bind="gridOptions"
          @resizable-change="resizableChange"
        >
          <template #buttonSlot>
            <x-tooltip title="tooltip">
              <x-button>按钮</x-button>
            </x-tooltip>
          </template>
        </vxe-grid>
      </div>
    </div>
  </div>
</template>

<script lang="tsx" setup>
import { VxeGrid } from 'vxe-table'
import 'vxe-table/styles/all.scss'
import { reactive, ref, toRef, computed } from 'vue'
import { type VxeGridProps } from 'vxe-table'
import { useVxeTable } from '@x-anything/hooks'
interface RowVO {
  id: number
  [key: string]: string | number | boolean | any[]
}

const gridOptions = reactive<VxeGridProps<RowVO>>({
  border: true,
  loading: false,
  height: computed(() => window.innerHeight) as any,
  columnConfig: {
    resizable: true,
  },
  scrollY: {
    enabled: true,
    gt: 0,
  },
  columns: [
    { type: 'checkbox', width: 80, fixed: 'left' },
    {
      title: '列0',
      fixed: 'left',
      children: [
        { title: '列1', field: 'imgUrl', width: 80 },
        { title: '列2', field: 'col2', width: 80 },
      ],
    },
    { title: '列3', field: 'col3', width: 300 },
    { title: '列4', field: 'col4', width: 300 },
    { title: '列5', field: 'col5', width: 300 },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列6',
      field: 'col6',
      width: 200,
      slots: {
        default: 'buttonSlot',
      },
    },
    {
      title: '列7',
      fixed: 'right',
      children: [
        { title: '列9', field: 'col9', width: 80 },
        { title: '列10', field: 'col10', width: 80 },
      ],
    },
    { title: '列8', field: 'col8', width: 80, fixed: 'right' },
  ],
  data: [],
})

const gridRef = ref()
const { resizableChange, cellStyle } = useVxeTable(gridRef, {
  virtualSticky: true,
  colSticky: true,
})
gridOptions.cellStyle = cellStyle

// 模拟行数据
const loadData = (rowSize: number) => {
  gridOptions.loading = true
  setTimeout(() => {
    const dataList: RowVO[] = []
    for (let i = 0; i < rowSize; i++) {
      const item: RowVO = {
        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 < 10; 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)
    }

    gridOptions.data = dataList
    gridOptions.loading = false
  }, 350)
}

loadData(200)
</script>