关于vxe-table的使用心得及扩展3【vxe-table二次封装组件应用】(非插件)


概要

1. 本节主要是再次对已封装的组件进行运用示范。
2. 补充了表格高度会根据窗口自适应填满的功能
3. 补充了鼠标拖拽范围选取功能,可根据拖拽范围进行粘贴剪贴板中的数据到表格中。支持ctrl+v,excel的数据或任意单文本
4. 补充了以鼠标选中单元格为起始点进行数据赋值的功能。支持ctrl+v

示例动图

单表格
在这里插入图片描述
双表格框选赋值互不影响
在这里插入图片描述

一、组件应用

<template>
  <div>
    <!-- 筛选表单 -->
    <div class="search-group">
      <vxe-input
        size="small"
        placeholder="搜索"></vxe-input>
    </div>
    <!-- 表格上方按钮组 -->
    <div class="list-button-area">
      <vxe-toolbar>
        <template #buttons>
          <vxe-button @click="Enable()">启用禁用标拖拽选取</vxe-button>
        </template>
      </vxe-toolbar>
    </div>
    <custom-vxe-table
      ref="xTable"
      :dynamicHeight="600"
      startActivatedPaste
      :checkboxConfig="{
        range: true
      }"
      showFooter
      :summaryColKeyList="[
        ['num1', 'rate'],
        ['age', 'rate'],
      ]"
      :sumConfig="[
        { columnIndex: 1, text: '合计(元)' },
        { columnIndex: 1, text: '平均值(元)', ruleName: 'avg' },
      ]"
      :table-data="tableData">
      <vxe-column
        type="checkbox"
        width="60"></vxe-column>
      <vxe-column
        type="seq"
        width="150"></vxe-column>
      <vxe-column
        field="name"
        title="name"
        :edit-render="{ name: 'VxeInput' }"></vxe-column>
      <vxe-column
        field="num1"
        title="num1"
        :edit-render="{ autofocus: '.vxe-input--inner' }">
        <template #edit="{ row }">
          <vxe-input
            v-model="row.num1"
            type="text"></vxe-input>
        </template>
      </vxe-column>
      <vxe-column
        field="age"
        :edit-render="{}">
        <template #edit="{ row }">
          <vxe-input
            v-model="row.num1"
            type="text"></vxe-input>
        </template>
      </vxe-column>
      <vxe-column
        field="rate"
        title="rate"></vxe-column>
    </custom-vxe-table>
    <el-divider>分割线</el-divider>
    <custom-vxe-table
      ref="xTable2"
      :dynamicHeight="600"
      showFooter
      :summaryColKeyList="[
        ['num1', 'rate'],
        ['age', 'rate'],
      ]"
      :sumConfig="[
        { columnIndex: 1, text: '合计(元)' },
        { columnIndex: 1, text: '平均值(元)', ruleName: 'avg' },
      ]"
      :table-data="tableData2">
      <vxe-column
        type="checkbox"
        width="60"></vxe-column>
      <vxe-column
        type="seq"
        width="150"></vxe-column>
      <vxe-column
        field="name"
        title="name"
        :edit-render="{ name: 'VxeInput' }"></vxe-column>
      <vxe-column
        field="num1"
        title="num1"
        :edit-render="{}">
        <template #edit="{ row }">
          <vxe-input
            v-model="row.num1"
            type="text"></vxe-input>
        </template>
      </vxe-column>
      <vxe-column
        field="age"
        title="age"></vxe-column>
      <vxe-column
        field="rate"
        title="rate"></vxe-column>
    </custom-vxe-table>
  </div>
</template>

<script>
import CustomVxeTable from '../components/custom-vxe-table'
// name > components > mixins > props > data > computed > watch > filter
export default {
  name: 'demo-1', //
  components: { CustomVxeTable },
  data() {
    return {
      tableData: [
        {
          id: 10001,
          name: 'Test1',
          nickname: 'T1',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 40,
          age: 28.2,
          rate: 22,
        },
        {
          id: 10002,
          name: 'Test2',
          nickname: 'T2',
          role: 'Designer',
          sex: '1',
          sex2: ['0', '1'],
          num1: 23,
          age: 22.1,
          rate: 34,
        },
        {
          id: 10003,
          name: 'Test3',
          nickname: 'T3',
          role: 'Test',
          sex: '0',
          sex2: ['1'],
          num1: 200,
          age: 32.1,
          rate: 18,
        },
        {
          id: 10004,
          name: 'Test4',
          nickname: 'T4',
          role: 'Designer',
          sex: '1',
          sex2: ['1'],
          num1: 30,
          age: 23.3,
          rate: 13,
        },
        {
          id: 10005,
          name: 'Test5',
          nickname: 'T5',
          role: 'Develop',
          sex: '0',
          sex2: ['1', '0'],
          num1: 20,
          age: 30.01,
          rate: 6,
        },
        {
          id: 10006,
          name: 'Test6',
          nickname: 'T6',
          role: 'Designer',
          sex: '1',
          sex2: ['0'],
          num1: 10,
          age: 21.03,
          rate: 33,
        },
        {
          id: 10007,
          name: 'Test7',
          nickname: 'T7',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 5,
          age: 29,
          rate: 4,
        },
        {
          id: 10008,
          name: 'Test8',
          nickname: 'T8',
          role: 'PM',
          sex: '1',
          sex2: ['0'],
          num1: 2,
          age: 35,
          rate: 55,
        },
        {
          id: 10009,
          name: 'Test9',
          nickname: 'T9',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 40,
          age: 28.2,
          rate: 22,
        },
        {
          id: 10010,
          name: 'Test10',
          nickname: 'T10',
          role: 'Designer',
          sex: '1',
          sex2: ['0', '1'],
          num1: 23,
          age: 22.1,
          rate: 34,
        },
        {
          id: 10011,
          name: 'Test11',
          nickname: 'T11',
          role: 'Test',
          sex: '0',
          sex2: ['1'],
          num1: 200,
          age: 32.1,
          rate: 18,
        },
        {
          id: 10012,
          name: 'Test12',
          nickname: 'T12',
          role: 'Designer',
          sex: '1',
          sex2: ['0', '1'],
          num1: 23,
          age: 22.1,
          rate: 34,
        },
        {
          id: 10013,
          name: 'Test13',
          nickname: 'T13',
          role: 'Test',
          sex: '0',
          sex2: ['1'],
          num1: 200,
          age: 32.1,
          rate: 18,
        },
        {
          id: 100014,
          name: 'Test14',
          nickname: 'T14',
          role: 'Designer',
          sex: '1',
          sex2: ['1'],
          num1: 30,
          age: 23.3,
          rate: 13,
        },
        {
          id: 100015,
          name: 'Test15',
          nickname: 'T15',
          role: 'Develop',
          sex: '0',
          sex2: ['1', '0'],
          num1: 20,
          age: 30.01,
          rate: 6,
        },
        {
          id: 100016,
          name: 'Test16',
          nickname: 'T16',
          role: 'Designer',
          sex: '1',
          sex2: ['0'],
          num1: 10,
          age: 21.03,
          rate: 33,
        },
        {
          id: 100017,
          name: 'Test17',
          nickname: 'T17',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 5,
          age: 29,
          rate: 4,
        },
        {
          id: 100018,
          name: 'Test18',
          nickname: 'T18',
          role: 'PM',
          sex: '1',
          sex2: ['0'],
          num1: 2,
          age: 35,
          rate: 55,
        },
      ],
      tableData2: [
        {
          id: 10001,
          name: 'Test1',
          nickname: 'T1',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 40,
          age: 28.2,
          rate: 22,
        },
        {
          id: 10002,
          name: 'Test2',
          nickname: 'T2',
          role: 'Designer',
          sex: '1',
          sex2: ['0', '1'],
          num1: 23,
          age: 22.1,
          rate: 34,
        },
        {
          id: 10003,
          name: 'Test3',
          nickname: 'T3',
          role: 'Test',
          sex: '0',
          sex2: ['1'],
          num1: 200,
          age: 32.1,
          rate: 18,
        },
        {
          id: 10004,
          name: 'Test4',
          nickname: 'T4',
          role: 'Designer',
          sex: '1',
          sex2: ['1'],
          num1: 30,
          age: 23.3,
          rate: 13,
        },
        {
          id: 10005,
          name: 'Test5',
          nickname: 'T5',
          role: 'Develop',
          sex: '0',
          sex2: ['1', '0'],
          num1: 20,
          age: 30.01,
          rate: 6,
        },
        {
          id: 10006,
          name: 'Test6',
          nickname: 'T6',
          role: 'Designer',
          sex: '1',
          sex2: ['0'],
          num1: 10,
          age: 21.03,
          rate: 33,
        },
        {
          id: 10007,
          name: 'Test7',
          nickname: 'T7',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 5,
          age: 29,
          rate: 4,
        },
        {
          id: 10008,
          name: 'Test8',
          nickname: 'T8',
          role: 'PM',
          sex: '1',
          sex2: ['0'],
          num1: 2,
          age: 35,
          rate: 55,
        },
        {
          id: 10009,
          name: 'Test9',
          nickname: 'T9',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 40,
          age: 28.2,
          rate: 22,
        },
        {
          id: 10010,
          name: 'Test10',
          nickname: 'T10',
          role: 'Designer',
          sex: '1',
          sex2: ['0', '1'],
          num1: 23,
          age: 22.1,
          rate: 34,
        },
        {
          id: 10011,
          name: 'Test11',
          nickname: 'T11',
          role: 'Test',
          sex: '0',
          sex2: ['1'],
          num1: 200,
          age: 32.1,
          rate: 18,
        },
        {
          id: 10012,
          name: 'Test12',
          nickname: 'T12',
          role: 'Designer',
          sex: '1',
          sex2: ['0', '1'],
          num1: 23,
          age: 22.1,
          rate: 34,
        },
        {
          id: 10013,
          name: 'Test13',
          nickname: 'T13',
          role: 'Test',
          sex: '0',
          sex2: ['1'],
          num1: 200,
          age: 32.1,
          rate: 18,
        },
        {
          id: 100014,
          name: 'Test14',
          nickname: 'T14',
          role: 'Designer',
          sex: '1',
          sex2: ['1'],
          num1: 30,
          age: 23.3,
          rate: 13,
        },
        {
          id: 100015,
          name: 'Test15',
          nickname: 'T15',
          role: 'Develop',
          sex: '0',
          sex2: ['1', '0'],
          num1: 20,
          age: 30.01,
          rate: 6,
        },
        {
          id: 100016,
          name: 'Test16',
          nickname: 'T16',
          role: 'Designer',
          sex: '1',
          sex2: ['0'],
          num1: 10,
          age: 21.03,
          rate: 33,
        },
        {
          id: 100017,
          name: 'Test17',
          nickname: 'T17',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 5,
          age: 29,
          rate: 4,
        },
        {
          id: 100018,
          name: 'Test18',
          nickname: 'T18',
          role: 'PM',
          sex: '1',
          sex2: ['0'],
          num1: 2,
          age: 35,
          rate: 55,
        },
      ],
    }
  },
  mounted() {
    // 注册单元格 鼠标拖拽范围选取功能
    this.$refs.xTable.selectCell({ dom: this.$refs.xTable })
    // this.$refs.xTable2.selectCell({ dom: this.$refs.xTable2 })
  },
  methods:{
    Enable(){
      this.$refs.xTable.selectCellToggle()
      // this.$refs.xTable2.selectCellToggle()
    }
  }
}
</script>

二、注册拖拽选取事件

1.注册单元格 鼠标拖拽范围选取功能

// dom: 框选识别的范围
selectCell({ dom: this.$refs.xTable })

2.以鼠标选中单元格为起始点进行数据赋值

组件上开启 startActivatedPaste 参数
上面双表格例子中,第一个表格开启了这个参数,第二个没有开启。所以第一个表格支持选中单元格直接粘贴数据


三、功能代码说明

1.表格组件代码更新

src\components\custom-vxe-table\index.vue

<template>
  <div class="table-box">
    <vxe-table
      ref="vxetablem"
      v-on="$listeners"
      v-bind="$attrs"
      :loading="loading"
      :loading-config="loadingOpts"
      :auto-resize="autoResize"
      :stripe="stripe"
      :align="align"
      :header-align="headerAlign"
      :merge-cells="mergeCells"
      :tree-config="treeOpts"
      :radio-config="radioOpts"
      :checkbox-config="checkboxOpts"
      :show-overflow="showOverflow"
      :show-header-overflow="showHeaderOverflow"
      :show-footer-overflow="showFooterOverflow"
      :keep-source="keepSource"
      :border="border"
      :height="height"
      :min-height="minHeight"
      :max-height="maxHeight"
      :data="tData"
      :show-footer="showFooter"
      :column-config="columnOpts"
      :resize-config="resizeOpts"
      :row-config="rowOpts"
      :menu-config="ctxMenuOpts"
      :edit-config="editOpts"
      :expand-config="expandConfig"
      :tooltip-config="tooltipOpts"
      :span-method="spanMethod"
      :row-class-name="rowClassName"
      :cell-class-name="cellClassName"
      :header-row-class-name="headerRowClassName"
      :header-cell-class-name="headerCellClassName"
      :footer-row-class-name="footerRowClassName"
      :footer-cell-class-name="footerCellClassName"
      :footer-data="footerData"
      :footer-align="footerAlign"
      :footer-span-method="footerSpanMethod"
      :footer-method="footerMethod"
      :cell-style="cellStyle"
      @header-cell-click="$emit('headerCellClick')"
      @header-cell-dblclick="$emit('headerCellDblclick')"
      @header-cell-menu="$emit('headerCellMenu')"
      @cell-click="$emit('cellClick')"
      @edit-activated="handleEditActivated"
      @cell-dblclick="$emit('cellDBLClick')"
      @cell-mouseenter="$emit('cellMouseenter')"
      @cell-mouseleave="$emit('cellMouseleave')"
      @cell-menu="$emit('cellMenu')"
      @radio-change="$emit('radioChange')"
      @checkbox-change="checkboxChange"
      @checkbox-all="checkboxAll"
      @scroll="$emit('scroll')">
      <slot></slot>
    </vxe-table>
  </div>
</template>

<script>
/* global XEUtils:false */
/* ==================== 说明开始 ==================== */

// 1. API详见 https://vxetable.cn/v3.8/#/table/api
// 2. 该表格属性若干,该版只重定义了部分常用属性,其它方法可直接在组件上按官网案例调用,其方法和属性穿透实现方式为:$listeners$attrs
// 3. 公用属性已提取到同级conf.js中,补充属性请按此规则配置并补充对应参数说明
`表格底部求和及平局值调用示例:
<custom-vxe-table
  showFooter
  :summaryColKeyList="[['classify','allSum'],['allSum','agreementMoney']]"
  :sumConfig="[{columnIndex: 1, text: '合计(元)' },{columnIndex: 1, text: '平均值(元)', ruleName: 'avg' }]"
  :table-data="data"
  @selection-change="handleSelectionChange">
    <vxe-column type="seq" width="50"></vxe-column>
    <vxe-column field="classify" title="classify"></vxe-column>
    <vxe-column field="allSum" title="allSum"></vxe-column>
    <vxe-column field="agreementMoney" title="agreementMoney"></vxe-column>
</custom-vxe-table>`

/* ==================== 说明结束 ==================== */

import XEUtils from 'xe-utils'
import GlobalConfig from './conf'
import { getTableHeight } from '@/utils/tablebodyheight'
import { validatenull } from '@/utils/validate'
import { selectionarea } from '@/mixins/selection-area'
export default {
  name: 'vxe-table-m',
  mixins: [selectionarea],
  props: {
    tableData: {
      type: Array,
      default: () => [],
    },
    // tableHeight、minHeight、maxHeight 三者之间的关系见 https://www.cnblogs.com/xldbk/p/12114686.html
    tableHeight: {
      type: [Number, String],
    },
    minHeight: { type: [Number, String], default: () => GlobalConfig.table.minHeight },
    maxHeight: {
      type: [Number, String],
      default: () => GlobalConfig.table.maxHeight,
    },
    // 最终高度需要再减去的高度,常用来减去已知的自定义元素高度,此数值需要是1920*1080分辨率下的像素值(只有不传入其它任何高度限制时生效)
    dynamicHeight: {
      type: [Number, String],
      default: 0,
    },
    // 保持原始值的状态,被某些功能所依赖,比如编辑状态、还原数据等(开启后影响性能,具体取决于数据量)
    keepSource: { type: Boolean, default: () => GlobalConfig.table.keepSource },
    // 是否自动监听父容器变化去更新响应式表格宽高
    autoResize: {
      type: Boolean,
      default: () => {
        return false
      },
    },
    // 响应式布局配置项
    resizeConfig: {
      type: Object,
    },
    // 是否带有斑马纹
    stripe: { type: Boolean, default: () => GlobalConfig.table.stripe },
    border: { type: [Boolean, String], default: () => GlobalConfig.table.border },
    loading: {
      type: Boolean,
      default: () => {
        return false
      },
    },
    loadingConfig: {
      type: Object,
    },
    // 所有的列对其方式
    align: { type: String, default: () => GlobalConfig.table.align },
    // 所有的表头列的对齐方式
    headerAlign: { type: String, default: () => GlobalConfig.table.headerAlign },
    // 所有的表尾列的对齐方式
    footerAlign: { type: String, default: () => GlobalConfig.table.footerAlign },
    // 给行附加 className
    rowClassName: {
      type: [String, Function],
      default: () => {
        return ''
      },
    },
    // 给单元格附加 className
    cellClassName: [String, Function],
    // 给表头的行附加 className
    headerRowClassName: [String, Function],
    // 给表头的单元格附加 className
    headerCellClassName: [String, Function],
    rowConfig: {
      type: Object,
    },
    // 列默认配置项
    columnConfig: {
      type: Object,
      default: () => GlobalConfig.table.columnConfig,
    },
    // 快捷菜单配置项(右键)
    menuConfig: {
      type: Object,
    },
    // 展开行配置项
    expandConfig: {
      type: Object,
    },
    // 编辑配置项
    editConfig: {
      type: Object,
    },
    // 合并指定单元格
    mergeCells: {
      type: Array,
    },
    tooltipConfig: {
      type: Object,
    },
    // 树形结构配置项
    treeConfig: [Boolean, Object],
    radioConfig: {
      type: Object,
    },
    checkboxConfig: {
      type: Object,
    },
    // 设置所有内容过长时显示为省略号
    showOverflow: { type: [Boolean, String], default: () => GlobalConfig.table.showOverflow },
    // 设置表头所有内容过长时显示为省略号
    showHeaderOverflow: {
      type: [Boolean, String],
      default: () => GlobalConfig.table.showHeaderOverflow,
    },
    // 设置表尾所有内容过长时显示为省略号
    showFooterOverflow: {
      type: [Boolean, String],
      default: () => GlobalConfig.table.showFooterOverflow,
    },
    // 是否显示表尾合计
    showFooter: {
      type: Boolean,
    },
    // 表尾数据
    footerData: {
      type: Array,
    },
    // 自定义合并行或列的方法
    spanMethod: Function,
    // 表尾合并行或列
    footerSpanMethod: Function,
    // 给表尾的行附加 className
    footerRowClassName: [String, Function],
    // 给表尾的单元格附加 className
    footerCellClassName: [String, Function],
    // 给单元格附加样式
    cellStyle: [Object, Function],
    // 需要计算合计的列的key, 二维数组, key可以是无序的 [['第一行key1', '第一行key2'],['第二行key1','第二行key2']], 如果只传一个数组,后续计算则复用
    summaryColKeyList: Array,
    /**
     * 合计描述及计算规则, 对应footerMethod方法的返回值(多行合计数组),即:
     * [{columnIndex: '第一行合计行描述列下标', text: '描述', ruleName: '计算规则(求和)'},{columnIndex: '第二行合计行描述列下标',text: '描述', ruleName: '计算规则(平均值)'}]
     * 计算规则参数(ruleName): add 求和, avg 平均值 , 默认值:add。结果保留2位小数
     */
    sumConfig: {
      type: Array,
      required: true,
      default: () => {
        return [{ columnIndex: 1, text: '合计(元)', ruleName: 'add' }]
      },
    },
    /**
     * 如果不想自动求和,比如直接使用后端请求回来的数据,则需要手动设置summaryData。此时将不启用sumConfig中的(ruleName)计算规则进行计算
     * 对象数组(对应summaryColKeyList中的key) [{第一行key1:'第一行key1的值', 第一行key2:'第一行key2的值'},...]
     */
    summaryData: Array,
    // 是否开启粘贴功能,已选择的单元格为起点
    startActivatedPaste:{
      type: Boolean,
    }
  },
  data() {
    return {
      height: '', // 表格高度
    }
  },
  watch: {
    tableData: {
      handler(newValue) {
        let _self = this
        if (!validatenull(newValue)) {
          _self.$nextTick(() => {
            // 官方的展开 只有再数据初始化时才生效且只执行一次,而在表格嵌套在tab中且懒加载数据时是不会再触发展开功能的。
            // 所以这里手动触发一下,但也只触发一次。expandInit: false 表示只触发一次
            if (!_self.treeOpts?.expandInit && _self.treeConfig?.expandAll) {
              _self.treeOpts.expandInit = true
              _self.$refs['vxetablem'].setAllTreeExpand(true)
            }
          })
        }
      },
      immediate: true,
      deep: true,
    },
  },
  computed: {
    tData() {
      if (!validatenull(this.tableData)) {
        this.autoHeight()
      }
      return this.tableData
    },
    columnOpts() {
      return Object.assign({}, GlobalConfig.table.columnConfig, this.columnConfig)
    },
    rowOpts() {
      return Object.assign({}, GlobalConfig.table.rowConfig, this.rowConfig)
    },
    resizeOpts() {
      return Object.assign({}, GlobalConfig.table.resizeConfig, this.resizeConfig)
    },
    radioOpts() {
      return Object.assign({}, GlobalConfig.table.radioConfig, this.radioConfig)
    },
    checkboxOpts() {
      return Object.assign({}, GlobalConfig.table.checkboxConfig, this.checkboxConfig)
    },
    tooltipOpts() {
      return Object.assign({}, GlobalConfig.table.tooltipConfig, this.tooltipConfig)
    },
    editOpts() {
      return Object.assign({}, GlobalConfig.table.editConfig, this.editConfig)
    },
    treeOpts() {
      return Object.assign({}, GlobalConfig.table.treeConfig, this.treeConfig)
    },
    ctxMenuOpts() {
      return Object.assign({}, GlobalConfig.table.menuConfig, this.menuConfig)
    },
    loadingOpts() {
      return Object.assign({}, GlobalConfig.table.loadingConfig, this.loadingConfig)
    },
  },
  mounted() {
    let _self = this
    _self.autoHeight()
    // 监听页面缩放, 重新计算表格高度
    window.onresize = XEUtils.debounce(() => {
      return (() => {
        // _self.autoHeight()
      })()
    }, 200)
  },
  methods: {
    // 表尾合计,
    footerMethod({ columns, data }) {
      let _self = this
      if (!_self.showFooter) return
      let sumRows = []
      if (!validatenull(_self.sumConfig)) {
        _self.sumConfig.forEach((conf, index) => {
          sumRows.push(
            columns.map((column, columnIndex) => {
              // 设置表尾合计的描述
              if (columnIndex == conf.columnIndex) {
                return conf.text
              }
              // 对符合要求的列进行计算
              if (!validatenull(_self.summaryColKeyList)) {
                let keyList = _self.summaryColKeyList[index]
                  ? _self.summaryColKeyList[index]
                  : _self.summaryColKeyList[0]
                if (!validatenull(keyList) && keyList?.includes(column.field)) {
                  if (!validatenull(_self.summaryData?.[index])) {
                    return _self.summaryData[index][column.field] || null
                  }
                  return _self.sumNum(data, column.field, conf.ruleName || 'add')
                }
              }
              return null
            })
          )
        })
      }
      return sumRows
    },
    // 根据sumConfig中配置的计算规则进行计算
    sumNum(list, field, rule) {
      let count = 0
      if (rule == 'add') {
        list.forEach((item) => {
          count += Number(item[field])
        })
        return Math.round(count * 100) / 100
      }
      if (rule == 'avg') {
        // 求平均...
        list.forEach((item) => {
          count += Number(item[field])
        })
        return Math.round((count / list.length) * 100) / 100
      }
    },
    // checkbox选中状态变换. 兼容el-table
    checkboxChange(params) {
      let selections = params.records || []
      let selectionIds = selections.map((item) => {
        return item.id
      })
      this.$emit('selection-change', selections, selectionIds, params)
    },
    // 计算table页面高度: 如果传入自定义高度,则使用自定义高度,否则动态计算高度
    autoHeight() {
      this.$nextTick(() => {
        if (this.maxHeight == null) {
          if (this.tableHeight) {
            this.height = this.tableHeight
          } else {
            this.height = getTableHeight({
              dynamicHeight: this.dynamicHeight,
              showPagination: this.tableData.length > 0,
            })
          }
        }
      })
    },
    // checkbox全选状态变换. 兼容el-table
    checkboxAll(params) {
      let selections = params.records || []
      this.$emit('select-all', selections, params)
      // 兼容el-table 触发逻辑,select-all会联动selection-change
      let selectionIds = selections.map((item) => {
        return item.id
      })
      this.$emit('selection-change', selections, selectionIds, params)
    },
  },
}
</script>

<style lang="scss" scoped>
.table-box {
  background-color: #fff;
}
</style>

补充了表格动态计算代码
import { getTableHeight } from ‘@/utils/tablebodyheight’
补充了一个混入函数
import { selectionarea } from ‘@/mixins/selection-area’

2. tablebodyheight.js

src\utils\tablebodyheight.js

import { validatenull } from '@/utils/validate'
/**
 * 动态计算表格高度
 * 表格最终高度 = 屏幕可视区域高度 - tabs表头的高度(现在支持两层tabs嵌套) - 搜索条件(search-group) - 表格上方按钮(list-button-area) - 底部的分页(pagination-container) - 及以上提及dom所使用的上下外边距
 * @param {*} dynamicHeight 分页页面内容中如果有已知高度的元素,将1920*1080下的像素值高度传入,自动计算其他分辨率下的高度,并减掉,返回的是净高度。
 * @param {*} showPagination 页面中是否显示了分页组件
 */
export function getTableHeight({ selectorName = 'body', dynamicHeight, showPagination }) {
  let defaultDom = document.createElement('div')

  // 默认10行数据高度(0.21875rem=42px), 假设每行高度在1920*1080分辨率下为42px
  let tableRowheight = pxToRem(0.21875) * 10
  // 页面高度
  let contentDom = document.querySelector(selectorName)
  let mainH = getComputedStyle(contentDom || defaultDom)?.height?.replace('px', '') || 0
  console.log('%c [ mainH ]-16', 'font-size:13px; background:#a31039; color:#e7547d;', mainH)

  // 最外层tabs
  let outerLayerTabs = contentDom.querySelector('.el-tabs .el-tabs__header')
  let outerLayerTabsHeaderH = 0
  let outerLayerTabsHeaderMarginBottom = 0
  if (!validatenull(outerLayerTabs)) {
    let tabsComputed = getComputedStyle(outerLayerTabs) || defaultDom
    if (tabsComputed?.height == 'auto' || validatenull(tabsComputed?.height)) {
      outerLayerTabsHeaderH = 0
    } else {
      outerLayerTabsHeaderH = tabsComputed?.height?.replace('px', '')
    }
    outerLayerTabsHeaderMarginBottom = tabsComputed?.marginBottom?.replace('px', '') || 0
  }

  // tab中嵌套tabs的情况(第二层tabs)
  //   let childTabsContentDom = contentDom.querySelector('.child-tabs')
  //   if (childTabsContentDom && childTabsContentDom.querySelector('.el-tabs__content')) {
  //     let tabPaneDom = childTabsContentDom.querySelector('.el-tabs__content').childNodes
  //     for (let t = 0; t < tabPaneDom.length; t++) {
  //       const item = tabPaneDom[t]
  //       if (item.hasAttribute && !item.hasAttribute('aria-hidden')) {
  //         contentDom = item
  //         break
  //       }
  //     }
  //   }

  // 表格内容区域 底部内边距
  let tableBox = contentDom.querySelector('.table-box')
  let tableBoxPaddingBottom = getComputedStyle(tableBox || defaultDom)?.paddingBottom?.replace('px', '') || 0
  // 分页
  let paginationDom = contentDom.querySelector('.pagination-container')
  let paginationH = getComputedStyle(paginationDom || defaultDom)?.height
  // auto作用为需要分页功能但是隐藏状态
  if (paginationDom?.style?.display == 'none' || paginationH == 'auto') {
    paginationH = showPagination ? remToPx(55 / 192) : 0 // 55为分页默认高度
  } else {
    paginationH = paginationH.replace('px', '')
  }
  // 模拟分页组件底部边距,15px
  let pageBottomMarginBottom = remToPx(15 / 192)
  let height =
    Number(mainH) -
    Number(outerLayerTabsHeaderH) -
    Number(outerLayerTabsHeaderMarginBottom) -
    Number(tableBoxPaddingBottom) -
    Number(paginationH) -
    Number(pageBottomMarginBottom)

  // let dynamicHeightToRem_192 = pxToRem(dynamicHeight, 192)
  // let dynamicHeightToPX_rule = remToPx(dynamicHeightToRem_192) || 0
  // height = dynamicHeight ? height - dynamicHeightToPX_rule : height
  height = dynamicHeight ? height - dynamicHeight : height
  height = height > tableRowheight ? height : tableRowheight
  return height
}

// px转rem, rem = px / 转换基数
export function pxToRem(px = 0, rootV) {
  let defaultDom = document.createElement('div')
  // 获取屏幕px和rem转换基数
  let rootValue =
    getComputedStyle(document.getElementsByTagName('html')[0] || defaultDom).fontSize?.replace('px', '') || 192
  return Number(px) / (rootV ? rootV : rootValue)
}
// rem转px, px = rem * 转换基数
export function remToPx(rem = 0, rootV) {
  let defaultDom = document.createElement('div')
  // 获取屏幕px和rem转换基数
  let rootValue =
    getComputedStyle(document.getElementsByTagName('html')[0] || defaultDom).fontSize?.replace('px', '') || 192
  return Number(rem) * (rootV ? rootV : rootValue)
}

// 数字金额转大写
export const numToCny = (money) => {
  // 汉字的数字
  let cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
  // 基本单位
  let cnIntRadice = ['', '拾', '佰', '仟']
  // 对应整数部分扩展单位
  let cnIntUnits = ['', '万', '亿', '兆']
  // 对应小数部分单位
  let cnDecUnits = ['角', '分', '毫', '厘']
  // 整数金额时后面跟的字符
  let cnInteger = '整'
  // 整型完以后的单位
  let cnIntLast = '元'
  // 最大处理的数字
  let maxNum = 999999999999999.9999
  // 金额整数部分
  let integerNum
  // 负数
  let negative = '负'
  // 负数
  let negativeFlag = false
  // 金额小数部分
  let decimalNum
  // 输出的中文金额字符串
  let chineseStr = ''
  // 分离金额后用的数组,预定义
  let parts
  if (money == 0) {
    chineseStr = cnNums[0] + cnIntLast + cnInteger
    return chineseStr
  }
  if (money < 0) {
    money = 0 - money
    negativeFlag = true
  }
  if (money == '' || money == null || money == undefined) {
    return ''
  }
  money = parseFloat(money)
  if (money >= maxNum) {
    // 超出最大处理数字
    return ''
  }
  // 转换为字符串
  money = money.toString()
  if (money.indexOf('.') == -1) {
    integerNum = money
    decimalNum = ''
  } else {
    parts = money.split('.')
    integerNum = parts[0]
    decimalNum = parts[1].substr(0, 4)
  }
  // 获取整型部分转换
  if (parseInt(integerNum, 10) > 0) {
    let zeroCount = 0
    let IntLen = integerNum.length
    for (let i = 0; i < IntLen; i++) {
      let n = integerNum.substr(i, 1)
      let p = IntLen - i - 1
      let q = p / 4
      let m = p % 4
      if (n == '0') {
        zeroCount++
      } else {
        if (zeroCount > 0) {
          chineseStr += cnNums[0]
        }
        // 归零
        zeroCount = 0
        chineseStr += cnNums[parseInt(n)] + cnIntRadice[m]
      }
      if (m == 0 && zeroCount < 4) {
        chineseStr += cnIntUnits[q]
      }
    }
    chineseStr += cnIntLast
  }
  // 小数部分
  if (decimalNum != '') {
    let decLen = decimalNum.length
    for (let j = 0; j < decLen; j++) {
      let x = decimalNum.substr(j, 1)
      if (x != '0') {
        chineseStr += cnNums[Number(x)] + cnDecUnits[j]
      }
    }
  }
  if (chineseStr == '') {
    chineseStr += cnNums[0] + cnIntLast + cnInteger
  } else if (decimalNum == '') {
    chineseStr += cnInteger
  }
  if (negativeFlag) {
    return negative + chineseStr
  }
  return chineseStr
}

3. selection-area.js

src\mixins\selection-area.js

默认情况下,鼠标范围框选是不启用的。如果要默认启用
直接在文件中删除该代码

// 设置默认禁用状态
 _self.selection.disable()
/* global XEUtils:false */
/* global VXETable:false */
import SelectionArea from '@simonwep/selection-js'
import XEUtils from 'xe-utils'
import { Message, MessageBox } from 'element-ui'
import { validatenull } from '@/utils/validate'
// 表格拖拽范围选择
export const selectionarea = {
  data() {
    return {
      selection: null,
      selectedCell: [],
      selectCellStatus: true,
    }
  },
  methods: {
    // 启用禁用标拖拽选取-范围赋值
    selectCellToggle() {
      let _self = this
      const $grid = _self.$refs.vxetablem
      if (_self.selectCellStatus) {
        _self.selection.enable()
        _self.listenerPasteAreaCell()
      } else {
        document.removeEventListener('paste', this.listenerPasteAreaFunc)
        _self.selection.disable()
        _self.clearCellActiveClass($grid)
      }
      _self.selectCellStatus = !_self.selectCellStatus
    },
    // 单元格鼠标拖拽选取
    selectCell({ dom }) {
      let _self = this
      let firstChild = dom?.$el?.firstChild
      if (firstChild) {
        let tid = firstChild.classList.values().find((item) => {
          return item.indexOf('tid_') > -1
        })
        if (validatenull(tid)) return
        let cont = '.vxe-table.' + tid
        let body = cont + ' .vxe-table--body'
        let bodyTd = body + ' td'
        _self.selection = new SelectionArea({
          // document object - if you want to use it within an embed document (or iframe).
          // document: dom,
          class: 'selection-area',
          container: cont, //'.vxe-table--body', // 查询选择器或 dom-node 为 selection-area 元素设置容器。
          selectables: [bodyTd], // 查询可以选择的元素的选择器。
          startareas: [body], // 查询元素的选择器,从中可以开始选择。
          boundaries: [body], // 查询将用作所选内容边界的元素的选择器。
          // px, how many pixels the point should move before starting the selection (combined distance).
          // Or specifiy the threshold for each axis by passing an object like {x: <number>, y: <number>}.
          startThreshold: 20, // 移动多少像素px开始进行选择 {x: <number>, y: <number>}.
          // Enable / disable touch support
          allowTouch: true, // 触摸支持
          // On which point an element should be selected.
          // Available modes are cover (cover the entire element), center (touch the center) or
          // the default mode is touch (just touching it).
          intersect: 'touch', // 选中的时机:整个覆盖cover,移动到中心center, 触摸到touch
          // Specifies what should be done if already selected elements get selected again.
          // invert: Invert selection for elements which were already selected
          // keep: Make stored elements (by keepSelection()) 'fix'
          // drop: Remove stored elements after they have been touched
          overlap: 'invert',
          // Configuration in case a selectable gets just clicked
          singleTap: {
            // Enable single-click selection (Also disables range-selection via shift + ctrl).
            allow: true,
            // 'native' (element was mouse-event target) or 'touch' (element visually touched).
            intersect: 'native',
          },
          scrolling: {
            // On scrollable areas the number on px per frame is devided by this amount.
            // Default is 10 to provide a enjoyable scroll experience.
            speedDivider: 10,
            // Browsers handle mouse-wheel events differently, this number will be used as
            // numerator to calculate the mount of px while scrolling manually: manualScrollSpeed / scrollSpeedDivider.
            manualSpeed: 750,
          },
          startScrollMargins: { x: 110, y: 110 },
        })
        _self.selection
          .on('beforestart', (params) => {
            let { event, store } = params
            console.log('%c [ params ]-132', 'font-size:13px; background:#1ed0fe; color:#62ffff;', params)
            event.preventDefault() // 拖拽时屏蔽选中文字
            event.stopPropagation() // 阻止事件穿透,防止激活输入框
            // return event.target.tagName !== "TD"; // 只识别TD标签
            //   return !event.path.some(item => {
            //     // item is in this case an element affected by the event-bubbeling.
            //     // To exclude elements with class "blocked" you could do the following (#73):
            //     return item.classList.contains('blocked');
            //     // If the areas you're using contains input elements you might want to prevent
            //     // any out-going selections from these elements (#72):
            //     return event.target.tagName !== 'INPUT';
            // });
          })
          .on('start', (params) => {
            let { event } = params
            event.stopPropagation() // 阻止事件穿透,防止激活输入框
            if (event.button != 2 && !event.ctrlKey && !event.metaKey) {
              // const $grid = _self.$refs.vxetablem
              // console.log('%c [ $grid ]-98', 'font-size:13px; background:#1858df; color:#5c9cff;', $grid)
              _self.clearCellActiveClass(dom)
              console.log('[ 清除 ] >')
            }
          })
          .on(
            'move',
            ({
              store: {
                changed: { added, removed },
              },
              event,
            }) => {
              // let h = _self.selection.h
              if (event?.button === 0) {
                addClass(added)
                removeClass(removed)
              }
            }
          )
          .on('stop', ({ event }) => {
            event.stopPropagation() // 阻止事件穿透,防止激活输入框
            event.preventDefault() // 拖拽时屏蔽选中文字
            if (event?.button === 0) {
              // _self.selectedCell = store?.selected?.filter((item) => {
              //   return Array(...item.classList).includes('td-mouse-active')
              // })
              _self.selection.keepSelection()
            }
          })
      }
      // 设置默认禁用状态
      _self.selection.disable()
      // 设置单元格选中样式
      function addClass(els) {
        // console.log('%c [ els ]-453', 'font-size:13px; background:#326519; color:#76a95d;', els)
        return els.map((v) => {
          if (!v.classList.contains('td-mouse-active')) {
            v.classList.add('td-mouse-active')
          }
        })
      }
      // 移除单元格选中样式
      function removeClass(els) {
        // console.log('%c [ els ]-462', 'font-size:13px; background:#a4963f; color:#e8da83;', els)
        return els.map((v) => {
          if (v.classList.contains('td-mouse-active')) {
            v.classList.remove('td-mouse-active')
          }
        })
      }
    },

    // 单元格被激活编辑时会触发该事件
    handleEditActivated(params) {
      this.$emit('edit-activated', params)
      if (this.startActivatedPaste) {
        this.ActivatCell = params
        console.log('%c [ 激活的单元格 ]-163', 'font-size:13px; background:#8e669c; color:#d2aae0;', this.ActivatCell)
        // ps 必须独立方法,便于移除监听
        document.addEventListener('paste', this.listenerPasteStartFunc, false)
      }
    },
    listenerPasteStartFunc(event) {
      let _self = this
      _self.pasteForCopeStartCell()
    },
    // 单元格被退出编辑时会触发该事件
    handleEditClosed(params) {
      document.removeEventListener('paste', this.listenerPasteStartFunc)
    },
    // 监听ctrl+v事件2
    listenerPasteAreaCell() {
      // ps 必须独立方法,便于移除监听
      document.addEventListener('paste', this.listenerPasteAreaFunc)
    },
    listenerPasteAreaFunc(event) {
      let _self = this
      const $grid = _self.$refs.vxetablem
      // 防止默认行为
      event.preventDefault()
      _self.pasteForCope($grid)
    },
    // 根据鼠标框选范围粘贴数据
    pasteForCope($grid) {
      let _self = this
      // 根据框选范围开始赋值,横纵依次类推
      let selectedCell = _self.selection.getSelection()
      if (!validatenull(selectedCell)) {
        // console.log('%c [ selected ]-298', 'font-size:13px; background:#2bb077; color:#6ff4bb;', _self.selectedCell)
        // 获取剪贴板数据进行赋值。赋值规则为 从左到右 从上到下。如果剪贴板中只有一行一列的数据,则对所有已框选的单元格使用相同值进行赋值,同excel操作
        // 数据量大的话还有效率优化空间, 建议:把框选的数据组织成和剪贴板一样的结构,遍历剪贴版数据进行赋值
        _self.getClipboardData(({ data }) => {
          if (!validatenull(data)) {
            let { fullData } = $grid?.getTableData()
            let fData = XEUtils.toTreeArray(fullData)
            let cpRowIndex = -1
            let cpColIndex = 0
            let cpRow = []
            let _selectRowIndex = ''
            selectedCell.forEach((td, index) => {
              const column = $grid.getColumnNode(td)
              const field = column?.item?.field || ''
              if (!validatenull(field)) {
                const rowNode = $grid.getRowNode(td.parentNode)
                let selectRowIndex = $grid.getVTRowIndex(rowNode.item)
                if (selectRowIndex !== _selectRowIndex) {
                  _selectRowIndex = selectRowIndex
                  cpRowIndex++
                  cpColIndex = 0
                  cpRow = data?.[cpRowIndex] || []
                }
                let dataRow = fData[selectRowIndex]
                if (!validatenull(cpRow[cpColIndex])) {
                  dataRow[field] = cpRow[cpColIndex]
                  cpColIndex++
                } else {
                  return
                }
              }
            })
            _self.clearCellActiveClass($grid)
          }
        })
      } else {
        _self.$message.warning('请框选要粘贴的数据区域!')
      }
    },
    // 以鼠标选中单元格做起始点粘贴数据
    pasteForCopeStartCell() {
      let _self = this
      const { $columnIndex, rowIndex, $table } = _self.ActivatCell
      const columns = $table.getColumns()
      // 取消单元格编辑状态 全局
      $table.clearEdit()
      // 以选中单元格开始赋值,横纵依次类推
      let startColumnIndex = $columnIndex
      let startRowIndex = rowIndex
      // 获取剪贴板数据进行赋值。赋值规则为 从左到右 从上到下。
      _self.getClipboardData(({ data, sourceData, type }) => {
        let contentText = []
        if (type === 'text/html') {
          contentText = [[sourceData.documentElement.textContent]]
        } else if (type === 'text/plain') {
          contentText = [[sourceData]]
        }
        let _data = data.length > 0 ? data : contentText
        if (!validatenull(_data)) {
          let { fullData } = $table?.getTableData()
          let fData = XEUtils.toTreeArray(fullData)
          _data.forEach((cpRow) => {
            for (let i = 0; i < cpRow.length; i++) {
              if (validatenull(columns[startColumnIndex]) || validatenull(fData[startRowIndex])) {
                break
              }
              let field = columns[startColumnIndex]?.field ?? ''
              fData[startRowIndex][field] = cpRow[i] ?? ''
              startColumnIndex += 1
            }
            startColumnIndex = $columnIndex
            startRowIndex += 1
          })
        }
      })
    },
    // 清除选中单元格样式
    clearCellActiveClass($grid) {
      if ($grid) {
        this.selection.clearSelection()
        let tbody = $grid?.$el?.querySelector('.vxe-table--body-wrapper.body--wrapper')
        let trs = tbody?.getElementsByTagName('tr')
        for (var i = 0; i < trs.length; i++) {
          let tr = trs[i]
          let tds = tr.getElementsByTagName('td')
          for (var j = 0; j < tds.length; j++) {
            let td = tds[j]
            if (td.classList.contains('td-mouse-active')) {
              td.classList.remove('td-mouse-active')
            }
          }
        }
      }
    },
    /**
     * 获取剪贴版数据
     * @returns { data: Array }
     */
    getClipboardData(callback) {
      if (XEUtils.isFunction(callback)) {
        this.insertForClipboard('', '', callback)
      }
    },

    decidePromiseState(promise) {
      const PROMISE_STATE = {
        PENDING: 'pending',
        FULFILLED: 'fulfilled', // 成功
        REJECTED: 'rejected', // 失败
      }
      const t = {}
      return Promise.race([promise, t])
        .then((v) => (v === t ? PROMISE_STATE.PENDING : PROMISE_STATE.FULFILLED))
        .catch(() => PROMISE_STATE.REJECTED)
    },
    // 剪切板内容 insertForClipboard(右键菜单实例,类型)
    // type:从选中行向下插入行insert, 从选中单元格开始赋值pasteForCope, 如果传空则返回剪贴板数据
    insertForClipboard(params, type, callback) {
      let _self = this
      const enterStr = '\r\n'
      const spaceStr = '\t'
      let clipboardRead = window.navigator.permissions.query({
        name: 'clipboard-read',
      })
      if (!clipboardRead) return
      clipboardRead.then((res) => {
        if (res.state == 'denied') {
          Message.error('不支持获取剪切板内容')
          return
        }
        navigator.clipboard
          .read()
          .then(async (data) => {
            let ps_html = 'rejected'
            await _self.decidePromiseState(data[0].getType('text/html')).then((state) => {
              ps_html = state
              if (state === 'fulfilled') {
                data[0].getType('text/html').then((res) => {
                  let reader = new FileReader()
                  //以下这两种方式都可以解析出来,因为Blob对象的数据可以按文本或二进制的格式进行读取
                  //reader.readAsBinaryString(blob);
                  reader.readAsText(res, 'utf8')
                  reader.onload = function () {
                    let fileTxt = this.result //这个就是解析出来的数据
                    let $doc = new DOMParser().parseFromString(fileTxt, 'text/html')
                    const $trs = Array.from($doc.querySelectorAll('table tr'))
                    if (!validatenull($trs)) {
                      // 解析剪贴板数据,生成行数据
                      let rowsInfo = []
                      $trs.forEach((tr) => {
                        let trData = []
                        if (!validatenull(tr.children)) {
                          let childrens = tr.children
                          for (let l = 0; l < childrens.length; l++) {
                            const td = childrens[l]
                            trData.push(td.textContent)
                          }
                          rowsInfo.push(trData)
                        }
                      })
                      if (!validatenull(rowsInfo)) {
                        // 插入
                        if (type == 'insert') {
                          _self.setRowData(params, rowsInfo)
                        }
                        // 粘贴
                        // if (type == 'pasteForCope') {
                        //   pasteForCope(params, rowsInfo)
                        // }
                        if (XEUtils.isFunction(callback)) {
                          callback({ data: rowsInfo, sourceData: $doc, type: 'text/html' })
                          console.log('[ html ] >')
                        }
                      }
                    } else {
                      callback({ data: [], sourceData: $doc, type: 'text/html' })
                      // const bodyChild_div = Array.from($doc.querySelectorAll('body div'))
                      // const bodyChild_p = Array.from($doc.querySelectorAll('p'))
                      // const bodyChild_sp = Array.from($doc.querySelectorAll('span'))
                      // if (!validatenull(bodyChild_div) || !validatenull(bodyChild_p) || !validatenull(bodyChild_sp)) {
                      //   Message.error('剪贴板数据不是表格(excel)格式!')
                      // } else {
                      //   Message.error('剪切板内容为空或无法识别!')
                      //   return
                      // }
                    }
                  }
                })
              }
            })
            if (ps_html == 'rejected') {
              _self.decidePromiseState(data[0].getType('text/plain')).then((state) => {
                if (state === 'fulfilled') {
                  data[0].getType('text/plain').then((res) => {
                    let reader = new FileReader()
                    //以下这两种方式都可以解析出来,因为Blob对象的数据可以按文本或二进制的格式进行读取
                    //reader.readAsBinaryString(blob);
                    reader.readAsText(res, 'utf8')
                    reader.onload = function () {
                      let fileTxt = this.result //这个就是解析出来的数据
                      let txtRows = fileTxt.split(enterStr)
                      let rowsInfo = []
                      txtRows.forEach((item) => {
                        let row = item.split(spaceStr)
                        rowsInfo.push(row)
                      })
                      if (!validatenull(rowsInfo)) {
                        // 插入
                        if (type == 'insert') {
                          _self.setRowData(params, rowsInfo)
                        }
                        // 粘贴
                        // if (type == 'pasteForCope') {
                        //   pasteForCope(params, rowsInfo)
                        // }
                        if (XEUtils.isFunction(callback)) {
                          callback({ data: rowsInfo, sourceData: fileTxt, type: 'text/plain' })
                          console.log('[ plain ] >')
                        }
                      }
                    }
                  })
                }
              })
            }
          })
          .catch((error) => {
            Message.error('获取剪切板内容失败!')
            console.error('Failed to read text from clipboard: ', error)
          })
      })
    },
  },
  // deactivated() {
  //   document.removeEventListener('paste', this.listenerPasteStartFunc)
  //   document.removeEventListener('paste', this.listenerPasteAreaFunc)
  // },
  // beforeDestory() {
  //   this.selection?.destroy()
  // },
}

4. 单元格选中样式及拖拽范围样式

src\styles\vxe-table-reset.scss

// 鼠标滑动范围框
.selection-area {
  background: rgba(50, 117, 252, 0.11);
  border: 1px solid rgba(46, 115, 252, 0.5);
  border-radius: 0.1em;
  user-select: none;
  pointer-events: none;
}
// 单元格选中样式
.td-mouse-active {
  background-color: rgba(64, 158, 255, 0.2);
  border: 1px solid #409eff;
  user-select: none;
  z-index: 1;
  & + .td-mouse-active {
    border-left: none;
  }
}

创建vxe-table-reset.scss文件后在main.js中引用
import “@/styles/vxe-table-reset.scss”;

插件版本说明

"vxe-table": "^3.8.11",
"xe-utils": "^3.5.12",
"element-ui": "^2.15.10",
"@simonwep/selection-js": "^2.1.2",

完结散花~

链接指引

💡 关于vxe-table的使用心得及扩展【表格虚拟滚动】(非插件)
💡💡 关于vxe-table的使用心得及扩展2【table表格二次封装】(非插件)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值