文章目录
前言
根据官网文档预设些自定义参数,二次封装了vxe-table。注意这不是vxe-grid哦!
1.添加了官方不支持的表格合计计算参数
2.使表格 全选复选框勾选变化时,能直接触发复选框的变化事件。即使联动逻辑和el-table保持相同
3.补充了表格高度动态计算参数,dynamicHeight,如果实在不会用 就留言要例子把,请先看我写的参数注释!
一、在components中创建vxe-table\index.vue文件
<template>
<div class="table-box">
<vxe-table
ref="vxetableRef"
:scroll-x="{enabled: true, gt: 0}"
:scroll-y="{enabled: true, gt: 0}"
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"
: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')"
@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 GlobalConfig from './conf'
import { accAdd } from '@/util/util'
import { getPageIndexHeight } from '@/util/tools'
import { validatenull } from '@/util/validate'
export default {
name: 'vxe-table-m',
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 },
// 设置表头所有内容过长时显示为省略号; 要开启横向虚拟滚动 此项必须为true
showHeaderOverflow: {
type: [Boolean, String],
default: () => GlobalConfig.table.showHeaderOverflow,
},
// 设置表尾所有内容过长时显示为省略号; 要开启横向虚拟滚动 此项必须为true
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,
},
data() {
return {
height: '', // 表格高度
}
},
watch: {
tableData: {
handler(newValue) {
let _self = this
if (!validatenull(newValue)) {
_self.autoHeight()
_self.$nextTick(()=>{
_self.$refs["vxetableRef"].reloadData(_self.tableData)
// 官方的展开 只有再数据初始化时才生效且只执行一次,而在表格嵌套在tab中且懒加载数据时是不会再触发展开功能的。
// 所以这里手动触发一下,但也只触发一次。expandInit: false 表示只触发一次
_self.$nextTick(()=>{
setTimeout(()=>{
if(!_self.treeOpts?.expandInit &&_self.treeConfig?.expandAll){
_self.treeOpts.expandInit = true
_self.$refs["vxetableRef"].setAllTreeExpand(true)
}
},0)
})
})
}else{
_self.autoHeight()
_self.$nextTick(()=>{
_self.$refs["vxetableRef"].reloadData([])
})
}
},
immediate: 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.exportMethodForVxeTable()
// 动态计算高度
_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 = accAdd(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 = getPageIndexHeight(this.dynamicHeight, 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)
},
// 抛出vxe-*组件自带的指定方法,以便提供给refs.xxx方式的调用 如果你无需重写官方自带方法,都不建议再写emit或者prop。请保持方法名的唯一性。
// ps: 通过ref获取vxetable的方法进行循环注册会造成页面假死。可能是vxe自带的方法是在太多了,挂再this上会很影响性能。
exportMethodForVxeTable(){
const names = ['loadData','reloadData','getRowNode','getTableData','getData','remove','setAllTreeExpand','insertNextAt','getScroll','scrollToRow','scrollTo','setCurrentRow','isCheckedByRadioRow','cell-click']
for (let i = 0; i < names.length; i++) {
const key = names[i];
if(this.$refs.vxetableRef[key]){
this[key] = this.$refs.vxetableRef[key]
}
}
}
},
}
</script>
二、同级创建配置文件conf.js
export default {
table: {
fit: true,
align: 'left',
headerAlign: 'left',
footerAlign: 'left',
showHeader: true,
animat: true,
delayHover: 250,
autoResize: true,
minHeight: null,
maxHeight: null,
keepSource: false,
showOverflow: true,
showHeaderOverflow: true,
showFooterOverflow: true,
// size: null,
// zIndex: null,
stripe: true,
border: true,
// round: false,
// emptyText: '暂无数据',
// emptyRender: {
// name: ''
// },
loadingConfig: { icon: 'el-icon-loading', text: '加载中...' },
rowConfig: {
// keyField: '_X_ROW_KEY', // 行数据的唯一主键字段名
// 是否需要为每一行的 VNode 设置 key 属性
boolean: false,
// 自定义行数据唯一主键的字段名(默认自动生成)
// 当鼠标点击行时,是否要高亮当前行
isCurrent: false,
// 当鼠标移到行时,是否要高亮当前行
isHover: false,
},
columnConfig: {
resizable: true,
},
resizeConfig: {
refreshDelay: 250,
},
radioConfig: {
// trigger: 'default'
strict: true,
},
checkboxConfig: {
// trigger: 'default',
strict: true,
},
tooltipConfig: {
// 所有单元格开启工具提示 boolean
showAll: false,
// tooltip 的主题颜色string dark,light
theme: 'dark',
// 鼠标是否可进入到工具提示中boolean
enterable: false,
// 鼠标移入后延时多少才显示工具提示number
enterDelay: 500,
// 鼠标移出后延时多少才隐藏工具提示number
leaveDelay: 300,
},
validConfig: {
showMessage: true,
autoClear: true,
autoPos: true,
message: 'inline',
msgMode: 'single',
},
menuConfig: {
enabled: false, // 是否启用
},
customConfig: {
allowVisible: true,
allowResizable: true,
allowFixed: true,
allowSort: true,
showFooter: true,
placement: 'topRight',
// storage: false,
// checkMethod () {},
modalOptions: {
showZoom: true,
mask: true,
lockView: true,
resize: true,
escClosable: true,
},
},
sortConfig: {
// remote: false,
// trigger: 'default',
// orders: ['asc', 'desc', null],
// sortMethod: null,
showIcon: true,
iconLayout: 'vertical',
},
filterConfig: {
// remote: false,
// filterMethod: null,
showIcon: true,
},
treeConfig: {
rowField: 'id',
parentField: 'parentId',
childrenField: 'children',
hasChildField: 'hasChild',
indent: 20,
showIcon: true,
expandInit: false
},
expandConfig: {
// trigger: 'default',
showIcon: true,
},
editConfig: {
// mode: 'cell',
showIcon: true,
showAsterisk: true,
},
importConfig: {
_typeMaps: {
csv: 1,
html: 1,
xml: 1,
txt: 1,
},
modes: ['insert', 'covering'],
},
exportConfig: {
_typeMaps: {
csv: 1,
html: 1,
xml: 1,
txt: 1,
},
modes: ['current', 'selected'],
},
printConfig: {
modes: ['current', 'selected'],
},
mouseConfig: {
extension: true,
},
keyboardConfig: {
isEsc: true,
},
scrollX: {
// enabled: false,
gt: 60,
// oSize: 0
},
scrollY: {
// enabled: false,
gt: 100,
// oSize: 0
},
},
export: {
types: {},
},
pager: {
pageSizePlacement: 'top',
// size: null,
// autoHidden: false,
// perfect: true,
// pageSize: 10,
// pagerCount: 7,
// pageSizes: [10, 15, 20, 50, 100],
// layouts: ['PrevJump', 'PrevPage', 'Jump', 'PageCount', 'NextPage', 'NextJump', 'Sizes', 'Total']
},
}
三. 目录结构
仅供参考(示例):
四. 注册调用, 可使用其它方式注册
注册(示例):
// components/components.js
import Vue from 'vue'
import CustomVxeTable from '@/components/vxe-table'
Vue.component('CustomVxeTable', CustomVxeTable)
// main.js
import './components/components'
调用(示例):
<custom-vxe-table
border
:max-height="475"
show-footer
:merge-footer-items="[{ row: 0, col: 1, rowspan: 1, colspan: 3 }]"
:summaryColKeyList="[['num1', 'age'], ['rate']]"
:sumConfig="[{columnIndex: 1, text: '合计(元)' },{columnIndex: 1, text: '平均值(元)', ruleName: 'avg' }]"
:table-data="tableData"
@selection-change="handleSelectionChange">
<template v-slot:column>
<vxe-column
type="seq"
title="序号"
width="60"></vxe-column>
<vxe-colgroup title="统计信息">
<vxe-column
field="name"
title="Name"
:edit-render="{}">
<template #edit="{ row }">
<vxe-input
v-model="row.name"
type="text"></vxe-input>
</template>
</vxe-column>
<vxe-column
field="age"
title="Age"
:edit-render="{ autofocus: '.vxe-input--inner' }">
<template #edit="{ row }">
<vxe-input
v-model="row.age"
type="text"
:min="1"
:max="120"
@change="updateFooterEvent"></vxe-input>
</template>
</vxe-column>
<vxe-column
field="num1"
title="Num"
:edit-render="{ autofocus: '.vxe-input--inner' }">
<template #edit="{ row }">
<vxe-input
v-model="row.num1"
type="text"
@input="updateFooterEvent"></vxe-input>
</template>
</vxe-column>
<vxe-column
field="rate"
title="Rate"
:edit-render="{}">
<template #edit="{ row }">
<vxe-input
v-model="row.rate"
type="text"
@input="updateFooterEvent"></vxe-input>
</template>
</vxe-column>
</vxe-colgroup>
</template>
</custom-vxe-table>
表格测试数据
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,
},
],
五. 链接指引
💡 关于vxe-table的使用心得及扩展【表格虚拟滚动】(非插件)
💡💡 关于vxe-table的使用心得及扩展3【vxe-table二次封装组件应用】(非插件)