1

官网地址: Vxe Table Vxe UI
github 链接以下
https://github.com/x-extends/vxe-table
https://gitee.com/xuliangzhan/vxe-table

vxe-table 是一个支持 vue2 和 vue3 的全功能表格,不仅支持常用表格功能,而且对大量数据的渲染性能也很好,功能也最强大的,不管是表格编辑、树表格,右键菜单、导入导出等都支持。
以前公司都是用内部封装的表格,后来业务越来越复杂,好多功能没法实现,性能也越来越差,几十条就开始卡了。直到看见 vxe-table ,文档非常详细,功能非常强大,配置式,还是自定义插槽,全部都能支持。对于我来说封装公司的业务组件太合适了。

vue 2 对应 vxe 3.x、vue3 对应 vxe 4.x

npm install vxe-table@4 vxe-pc-ui@4

我的项目是 vue3 版本,使用方式是

import VxeUI from 'vxe-pc-ui'
import 'vxe-pc-ui/lib/style.css'

import VxeUITable from 'vxe-table'
import 'vxe-table/lib/style.css'


createApp(App).use(VxeUI).use(VxeUITable).mount('#app')

基础功能

是否非常简单,只需要设置 columns 和 data 就可以渲染一个表格

{7B960215-64CC-4155-B9B8-1F21F557FEC3}.png

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

<script>
import Vue from 'vue'
export default {
  data () {
    const gridOptions = {
      columns: [
        { type: 'seq', width: 70 },
        { field: 'name', title: 'Name' },
        { field: 'sex', title: 'Sex' },
        { field: 'age', title: 'Age' }
      ],
      data: [
        { id: 10001, name: 'Test1', role: 'Develop', sex: 'Man', age: 28, address: 'test abc' },
        { id: 10002, name: 'Test2', role: 'Test', sex: 'Women', age: 22, address: 'Guangzhou' },
        { id: 10003, name: 'Test3', role: 'PM', sex: 'Man', age: 32, address: 'Shanghai' },
        { id: 10004, name: 'Test4', role: 'Designer', sex: 'Women', age: 24, address: 'Shanghai' }
      ]
    }
    return {
      gridOptions
    }
  }
}
</script>

再来个复杂一点的自定义插槽模板

{3AA36E62-7AE3-4B26-945C-DD9FD307E6A6}.png

<template>
  <div>
    <vxe-grid v-bind="gridOptions">
      <template #imgUrl_default="{ row }">
        <vxe-image :src="row.imgUrl" width="36" height="30"></vxe-image>
      </template>

      <template #name_default="{ row }">
        <vxe-button mode="text" @click="openDetail(row)">点击{{ row.name }}</vxe-button>
      </template>

      <template #sex_default="{ row }">
        <span>{{ formatSex(row) }}</span>
      </template>

      <template #address_default="{ row }">
        <vxe-image src="https://pic2.zhimg.com/50/v2-f7031359103859e1ed38559715ef5f3f_hd.gif" width="36" height="30"></vxe-image>
        <span>{{ row.address }}</span>
        <vxe-image src="https://n.sinaimg.cn/sinacn17/w120h120/20180314/89fc-fyscsmv5911424.gif" width="30" height="30"></vxe-image>
      </template>
    </vxe-grid>
  </div>
</template>

<script setup>
import { reactive } from 'vue'
import { VxeUI } from 'vxe-pc-ui'
const gridOptions = reactive({
  border: true,
  showOverflow: true,
  columns: [
    { type: 'seq', width: 70 },
    { field: 'imgUrl', title: 'imgUrl', slots: { default: 'imgUrl_default' } },
    { field: 'name', title: 'Name', slots: { default: 'name_default' } },
    {
      title: 'Group1',
      children: [
        { field: 'sex', title: 'Sex', slots: { default: 'sex_default' } },
        { field: 'num', title: 'Number' },
        { field: 'age', title: 'Age' }
      ]
    },
    { field: 'address', title: 'Address', width: 200, slots: { default: 'address_default' } }
  ],
  data: [
    { id: 10001, name: 'Test1', role: 'Develop', sex: '0', age: 28, num: 234, imgUrl: 'https://vxeui.com/resource/img/fj577.jpg', address: 'test abc' },
    { id: 10002, name: 'Test2', role: 'Test', sex: '1', age: 22, num: 34, imgUrl: 'https://vxeui.com/resource/img/fj581.jpeg', address: 'Guangzhou' },
    { id: 10003, name: 'Test3', role: 'PM', sex: '0', age: 32, num: 12, imgUrl: 'https://vxeui.com/resource/img/fj581.jpeg', address: 'Shanghai' }
  ]
})
const formatSex = (row) => {
  return row.sex === '1' ? '男' : '女'
}
const openDetail = (row) => {
  VxeUI.modal.message({
    status: 'success',
    content: `点击了${row.name}`
  })
}

</script>

渲染上万条数据,毫秒级

做个性能测试,渲染5万条在250毫秒以内

滚动丝滑流畅

<template>
  <div>
    <vxe-button @click="loadData(5000)">加载5k条</vxe-button>
    <vxe-button @click="loadData(10000)">加载1w条</vxe-button>
    <vxe-button @click="loadData(50000)">加载5w条</vxe-button>
    <vxe-grid v-bind="gridOptions">
      <template #action>
        <vxe-button mode="text" status="primary">编辑</vxe-button>
        <vxe-button mode="text" status="error">删除</vxe-button>
      </template>
    </vxe-grid>
  </div>
</template>

<script setup>
import { reactive, nextTick } from 'vue'
import { VxeUI } from 'vxe-table'
const flag1CellRender = reactive({
  name: 'VxeSwitch'
})
const imgUrlCellRender = reactive({
  name: 'VxeImage',
  props: {
    width: 36,
    height: 36
  }
})
const imgList1CellRender = reactive({
  name: 'VxeUpload',
  props: {
    mode: 'image',
    readonly: true,
    moreConfig: {
      maxCount: 2
    },
    imageStyle: {
      width: 40,
      height: 40
    }
  }
})
const gridOptions = reactive({
  border: true,
  showOverflow: true,
  showHeaderOverflow: true,
  showFooterOverflow: true,
  loading: false,
  height: 800,
  columnConfig: {
    resizable: true
  },
  scrollX: {
    enabled: true,
    gt: 0
  },
  scrollY: {
    enabled: true,
    gt: 0,
    mode: 'wheel'
  },
  columns: [
    { title: '列0', field: 'col0', width: 100, fixed: 'left' },
    { title: '列1', field: 'imgUrl', width: 80, fixed: 'left', cellRender: imgUrlCellRender },
    { title: '列2', field: 'col2', width: 90, fixed: 'left' },
    { 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 },
    { title: '列81', field: 'col81', width: 600 },
    { title: '列82', field: 'col82', width: 100 },
    { title: '列83', field: 'col83', width: 490 },
    { title: '列84', field: 'col84', width: 100 },
    { title: '列85', field: 'col85', width: 150 },
    { title: '列86', field: 'col86', width: 800 },
    { title: '列87', field: 'col87', width: 400 },
    { title: '列88', field: 'col88', width: 800 },
    { title: '列89', field: 'col89', width: 360 },
    { title: '列90', field: 'col90', width: 650 },
    { title: '列91', field: 'col91', width: 600 },
    { title: '列92', field: 'col92', width: 100 },
    { title: '列93', field: 'col93', width: 490 },
    { title: '列94', field: 'col94', width: 100 },
    { title: '列95', field: 'col95', width: 150 },
    { title: '列96', field: 'col96', width: 800 },
    { title: '列97', field: 'col97', width: 400 },
    { title: '列99', field: 'imgList1', width: 120, fixed: 'right', cellRender: imgList1CellRender },
    { title: '列100', field: 'flag1', width: 100, fixed: 'right', cellRender: flag1CellRender },
    { title: '操作', field: 'action', width: 120, fixed: 'right', slots: { default: 'action' } }
  ],
  data: []
})
// 模拟行数据
const loadData = (rowSize) => {
  const dataList = []
  for (let i = 0; i < rowSize; i++) {
    const item = {
      id: 10000 + i,
      imgUrl: i % 3 === 0 ? 'https://vxeui.com/resource/img/546.gif' : 'https://vxeui.com/resource/img/673.gif',
      imgList1: i % 4 === 0
        ? [
            { name: 'fj577.jpg', url: 'https://vxeui.com/resource/img/fj577.jpg' }
          ]
        : [
            { name: 'fj573.jpeg', url: 'https://vxeui.com/resource/img/fj573.jpeg' },
            { name: 'fj562.png', url: 'https://vxeui.com/resource/img/fj562.png' }
          ],
      flag1: i % 5 === 0
    }
    for (let j = 0; j < 120; j++) {
      item[`col${j}`] = `值_${i}_${j}`
    }
    dataList.push(item)
  }
  gridOptions.loading = true
  setTimeout(() => {
    const startTime = Date.now()
    gridOptions.data = dataList
    gridOptions.loading = false
    nextTick(() => {
      VxeUI.modal.message({
        content: `加载时间 ${Date.now() - startTime} 毫秒`,
        status: 'success'
      })
    })
  }, 100)
}
loadData(200)

</script>

单元格中上传附件 、上传图片

{75A7760E-F825-4C01-8299-AC61C26AAF33}.png

点击图片,全屏预览
{827ACDD2-8550-490A-B4AA-B7EE075B18D5}.png

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

<script setup>
import { reactive } from 'vue'
const fileList1CellRender = reactive({
  name: 'VxeUpload',
  props: {
    readonly: true,
    progressText: '{percent}%',
    moreConfig: {
      maxCount: 1,
      layout: 'horizontal'
    }
  }
})
const fileList2CellRender = reactive({
  name: 'VxeUpload',
  props: {
    multiple: true,
    showButtonText: false,
    progressText: '{percent}%',
    moreConfig: {
      maxCount: 1,
      layout: 'horizontal'
    }
  }
})
const imgList1CellRender = reactive({
  name: 'VxeUpload',
  props: {
    mode: 'image',
    readonly: true,
    progressText: '{percent}%',
    moreConfig: {
      maxCount: 1
    },
    imageStyle: {
      width: 40,
      height: 40
    }
  }
})
const imgList2CellRender = reactive({
  name: 'VxeUpload',
  props: {
    mode: 'image',
    multiple: true,
    showButtonText: false,
    progressText: '{percent}%',
    moreConfig: {
      maxCount: 1
    },
    imageStyle: {
      width: 40,
      height: 40
    }
  }
})
const gridOptions = reactive({
  border: true,
  showOverflow: true,
  columns: [
    { type: 'seq', width: 70 },
    { field: 'name', title: 'Name', minWidth: 180 },
    { field: 'fileList1', title: '附件列表', width: 240, cellRender: fileList1CellRender },
    { field: 'fileList2', title: '上传附件', width: 300, cellRender: fileList2CellRender },
    { field: 'imgList1', title: '图片列表', width: 160, cellRender: imgList1CellRender },
    { field: 'imgList2', title: '上传图片', width: 210, cellRender: imgList2CellRender }
  ],
  data: [
    {
      id: 10001,
      name: 'Test1',
      imgList1: [],
      imgList2: [],
      fileList1: [
        { name: 'fj562.png', url: 'https://vxeui.com/resource/img/fj562.png' }
      ],
      fileList2: [
        { name: 'fj562.png', url: 'https://vxeui.com/resource/img/fj562.png' }
      ]
    },
    {
      id: 10002,
      name: 'Test2',
      imgList1: [
        { name: 'fj562.png', url: 'https://vxeui.com/resource/img/fj562.png' },
        { name: 'fj573.jpeg', url: 'https://vxeui.com/resource/img/fj573.jpeg' }
      ],
      imgList2: [
        { name: 'fj562.png', url: 'https://vxeui.com/resource/img/fj562.png' },
        { name: 'fj573.jpeg', url: 'https://vxeui.com/resource/img/fj573.jpeg' }
      ],
      fileList1: [],
      fileList2: []
    },
    {
      id: 10003,
      name: 'Test3',
      imgList1: [
        { name: 'fj577.jpg', url: 'https://vxeui.com/resource/img/fj577.jpg' }
      ],
      imgList2: [
        { name: 'fj577.jpg', url: 'https://vxeui.com/resource/img/fj577.jpg' }
      ],
      fileList1: [
        { name: 'fj562.png', url: 'https://vxeui.com/resource/img/fj562.png' },
        { name: 'fj573.jpeg', url: 'https://vxeui.com/resource/img/fj573.jpeg' },
        { name: 'fj187.jpg', url: 'https://vxeui.com/resource/img/fj187.jpg' }
      ],
      fileList2: [
        { name: 'fj562.png', url: 'https://vxeui.com/resource/img/fj562.png' },
        { name: 'fj573.jpeg', url: 'https://vxeui.com/resource/img/fj573.jpeg' },
        { name: 'fj187.jpg', url: 'https://vxeui.com/resource/img/fj187.jpg' }
      ]
    }
  ]
})

</script>

单元格中多行文本溢出

{95EC8F2D-17CA-4727-8CCE-69E70C373998}.png

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

<script setup>
import { reactive } from 'vue'
const addressCellRender = reactive({
  name: 'VxeTextEllipsis',
  props: {
    lineClamp: 3
  }
})
const gridOptions = reactive({
  border: true,
  height: 500,
  columns: [
    { type: 'seq', width: 70 },
    { field: 'name', title: 'Name' },
    { field: 'sex', title: 'Sex', width: 100 },
    { field: 'address', title: '多行文本溢出省略', width: 400, cellRender: addressCellRender }
  ],
  data: [
    { id: 10001, name: 'Test1', role: 'Develop', sex: 'Man', age: 28, address: '这是一个多行的文本溢出省略组件,用于实现多行文本溢出省略,这将非常有用,如果没有超出,则显示全部文本,如超出指定行数之后,文字会被会自动截断,并且会出现省略,后面文字会被隐藏将不会被显示出来。' },
    { id: 10002, name: 'Test2', role: 'Test', sex: 'Women', age: 22, address: '这是一个多行的文本溢出省略组件' },
    { id: 10003, name: 'Test3', role: 'PM', sex: 'Man', age: 32, address: '这是一个多行的文本溢出省略组件,用于实现多行文本溢出省略' },
    { id: 10004, name: 'Test4', role: 'Designer', sex: 'Women', age: 24, address: '这是一个多行的文本溢出省略组件,用于实现多行文本溢出省略,这将非常有用,如果没有超出,则显示全部文本,如超出指定行数之后,文字会被会自动截断,并且会出现省略,后面文字会被隐藏将不会被显示出来。' },
    { id: 10005, name: 'Test5', role: 'Designer', sex: 'Women', age: 42, address: '这是一个多行的文本溢出省略组件' },
    { id: 10006, name: 'Test6', role: 'PM', sex: 'Women', age: 36, address: '这是一个多行的文本溢出省略组件' },
    { id: 10007, name: 'Test7', role: 'Test', sex: 'Women', age: 39, address: '这是一个多行的文本溢出省略组件,用于实现多行文本溢出省略,这将非常有用,如果没有超出,则显示全部文本,如超出指定行数之后,文字会被会自动截断,并且会出现省略,后面文字会被隐藏将不会被显示出来。' },
    { id: 10008, name: 'Test8', role: 'Designer', sex: 'Women', age: 56, address: '这是一个多行的文本溢出省略组件' }
  ]
})

</script>

单元格渲染高性能图表

{C310E9A4-F0C0-46C6-8E45-9833A1E10A86}.png

{6F449F62-8378-4D55-BE1B-AEC2A5F1A9C2}.png

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

<script setup>
import { reactive } from 'vue'
const gridOptions = reactive({
  border: true,
  showOverflow: true,
  height: 500,
  columnConfig: {
    resizable: true
  },
  columns: [
    { type: 'seq', width: 70 },
    { field: 'name', title: 'Name' },
    {
      field: 'num10',
      title: '柱状图',
      width: 200,
      cellRender: {
        name: 'bar',
        props: {
          bar: {
            max: 100
          },
          label: {
            formatter: '{value}%'
          }
        }
      }
    },
    {
      field: 'num11',
      title: '柱状图 - 显示值',
      width: 200,
      cellRender: {
        name: 'bar',
        props: {
          label: {
            formatter: '{value}'
          }
        }
      }
    },
    {
      field: 'num12',
      title: '柱状图 - 最大值',
      width: 200,
      cellRender: {
        name: 'bar',
        props: {
          bar: {
            max: 140
          },
          colors: ['#FFDB5C', '#91C7AE', '#D48265'],
          tooltip: {
            formatter: '值:{value}%'
          },
          label: {
            formatter: '{value}%'
          }
        }
      }
    }
  ],
  data: [
    { id: 101, name: 'test1', num10: [60], num11: [60, 111], num12: [60, 134, 76] },
    { id: 102, name: 'test2', num10: [85], num11: [33, 25], num12: [42, 73, 22] },
    { id: 103, name: 'test3', num10: [45], num11: [60, 104], num12: [6, 64, 44] },
    { id: 104, name: 'test4', num10: [88], num11: [76, 99], num12: [41, 81, 77] },
    { id: 105, name: 'test5', num10: [72], num11: [27, 157], num12: [48, 101, 76] },
    { id: 106, name: 'test6', num10: [50], num11: [8, 111], num12: [60, 5, 19] },
    { id: 107, name: 'test7', num10: [16], num11: [60, 6], num12: [9, 57, 34] },
    { id: 108, name: 'test8', num10: [24], num11: [23, 68], num12: [35, 111, 80] },
    { id: 109, name: 'test9', num10: [100], num11: [14, 66], num12: [27, 34, 98] },
    { id: 1010, name: 'test10', num10: [98], num11: [44, 98], num12: [29, 107, 127] }
  ]
})

</script>

右键弹窗图表

{76D1B6E4-14A7-4FA1-A9C9-9FA9DCCF5E51}.png

{B8799CDE-12D9-4C0A-8527-73BB67F25888}.png


激动个球
41 声望0 粉丝