效果如下:
elemetui自定义表格实现可拖拽列、显示隐藏列、数据汇总
需要注意的点:
①props接受的值是只读的,所以需要定义一个变量来存从组件获取的值
②如果你的页面有多个表格时,需要给组件定义一个id或者ref,确保const wrapperTr = document.querySelector('#dragTable .el-popper .dragCol');确保能够获取到正确的元素结构
③如果不想使用组件原有的方法,比如汇总方法,可以自己加一个summaryMethod接收新的方法
④拖拽显示隐藏的列才能看到效果
以下是组件的代码:
<template>
<div>
<el-popover placement="bottom-start" :width="600" trigger="hover">
<transition name="fade">
<div>
<div>选择需要显示的列</div>
<div class="dragCol">
<el-checkbox v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
<el-checkbox v-for="(item, index) in checkboxColumn" :key="index" @change="handleCheckedBoxChange(item)" v-model="item.status">{{item.label}}</el-checkbox>
</div>
</div>
</transition>
<template #reference>
<el-button icon="Setting">显示列</el-button>
</template>
</el-popover>
<div style="height: 200px;"> </div>
<el-table id="dragTable" :data="roleList" :show-summary="showSummary" :summary-method="getSummaries" row-key="id" width="100%">
<template v-for="(col,index) in tableColumn" :key="index">
<!-- 设置每一列的宽度,根据列的宽度和列的长度计算 -->
<!-- :width="index === 0 ? flexColumnWidth(col.prop, roleList) : '300'" -->
<el-table-column :width="setColumnWidth ? flexColumnWidth(col.prop, roleList) : ''" :label="col.label" v-if="tableColumn[index].status" :prop="tableColumn[index].prop" align="center" />
</template>
</el-table>
</div>
</template>
<script setup>
import { defineProps, ref, onMounted } from 'vue';
import Sortable from 'sortablejs';
const props = defineProps({
roleList: Array,
Col: Array,
showSummary: Boolean,
setColumnWidth:Boolean
// summaryMethod: Function
});
//由于props获取的值是只读的,因此只能定义新的变量来修改
let checkboxColumn=ref(JSON.parse(JSON.stringify(props.Col)));
let tableColumn=ref(JSON.parse(JSON.stringify(props.Col)));
let checkAll = ref(true);
function handleCheckAllChange() {
checkboxColumn.value.forEach(item => {
item.status = checkAll.value;
});
tableColumn.value.forEach(i => {
i.status=checkAll.value;
});
}
function handleCheckedBoxChange(item) {
const allTrue = checkboxColumn.value.every(item => item.status === true);
// 修改显示列表的状态
tableColumn.value.forEach(i => {
if(i.label==item.label){
i.status=item.status
}
});
if (allTrue) {
checkAll.value = true
} else {
checkAll.value = false
}
}
function columnDrop() {
// const wrapperTr = document.querySelector('.el-table__header-wrapper tr')
const wrapperTr = document.querySelector('.el-popper .dragCol')
let sortableInstance = Sortable.create(wrapperTr, {
animation: 180,
delay: 0,
onEnd: evt => {
let oldItem =tableColumn.value[evt.oldIndex-1]
tableColumn.value.splice(evt.oldIndex-1,1)
tableColumn.value.splice(evt.newIndex-1,0,oldItem)
}
})
}
// 自适应表格列宽
const flexColumnWidth = (str, tableData, flag = 'max') => {
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
// flag为可选值,可不传该参数,传参时可选'max'或'equal',默认为'max'
// flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。
str = str + ''
let columnContent = ''
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
return
}
if (!str || !str.length || str.length === 0 || str === undefined) {
return
}
if (flag === 'equal') {
// 获取该列中第一个不为空的数据(内容)
for (let i = 0; i < tableData.length; i++) {
if (tableData[i][str].length > 0) {
// console.log('该列数据[0]:', tableData[0][str])
columnContent = tableData[i][str]
break
}
}
} else {
// 获取该列中最长的数据(内容)
let index = 0
for (let i = 0; i < tableData.length; i++) {
// 数据为空跳出循环
// if (tableData[i][str] === null) {
// return
// }
const now_temp = tableData[i][str] + ''
const max_temp = tableData[index][str] + ''
if (now_temp.length > max_temp.length) {
index = i
}
}
columnContent = tableData[index][str]
}
// console.log('该列数据[i]:', columnContent)
// 以下分配的单位长度可根据实际需求进行调整
let flexWidth = 0
columnContent = columnContent + ''
for (const char of columnContent) {
if ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z')) {
// 如果是英文字符,为字符分配8个单位宽度
flexWidth += 12
} else if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符,为字符分配15个单位宽度
flexWidth += 15
} else {
// 其他种类字符,为字符分配15个单位宽度
flexWidth += 12
}
}
if (flexWidth < 80) {
// 设置最小宽度
flexWidth = 80
}
// if (flexWidth > 250) {
// // 设置最大宽度
// flexWidth = 250
// }
return flexWidth + 'px'
}
// 自定义汇总方法
const getSummaries = (param) => {
const { columns, data } = param
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '总数'
return
}
// console.log(column.property);
// 将所有元素的值转换成数字类型
const values = data.map((item) => Number(item[column.property]))
// console.log(values.some((val) => Number.isNaN(val)));
// every 只要有一个是数字,就会返回true
// 判断数组中的数据是否是数字类型 some 只要存在一个是NaN那么就返回false
if (!values.some((value) => Number.isNaN(value))) {
sums[index] = ` ${values.reduce((prev, curr) => {
const value = Number(curr)
if (!Number.isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)}`
} else {
sums[index] = '/'
}
})
return sums
}
onMounted(() => {
columnDrop();
});
</script>
<style scoped>
/* 可能需要的样式 */
</style>
使用方法如下:
<draggable-table :Col="Col" :roleList="roleList" :setColumnWidth="true" :showSummary="true" />
数据如下:
const roleList = ref([
{partName: 1,
batchNo:2,
invoiceNo: 3,
orderNo:4,
boxNo: 5,
caseNo: 6,}
]);
const dropCol = ref([
{
label: '零件名称',
prop: 'partName',
status:true
},
{
label: '批次号',
prop: 'batchNo',
status:true
},
{
label: '发票号',
prop: 'invoiceNo',
status:true
},
{
label: '订单号',
prop: 'orderNo',
status:true
},
{
label: '箱号',
prop: 'boxNo',
status:true
},
{
label: '小箱号',
prop: 'caseNo',
status:true
}
])
这是我在原有的基础上进行的组件封装,具体的代码思路可以看我前面发的
更新:新增插槽操作列
由于之前的表格只是单纯展示内容就没有考虑做操作列,现在新增了一个插槽操作列
完整代码如下:
<template>
<div>
<el-popover placement="bottom-start" :width="600" trigger="hover">
<transition name="fade">
<div>
<div>选择需要显示的列</div>
<div class="dragCol">
<el-checkbox v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
<el-checkbox v-for="(item, index) in checkboxColumn" :key="index" @change="handleCheckedBoxChange(item)" v-model="item.status">{{item.label}}</el-checkbox>
</div>
</div>
</transition>
<template #reference>
<el-button icon="Setting">显示列</el-button>
</template>
</el-popover>
<div style="height: 200px;"> </div>
<el-table id="dragTable" :data="roleList" :show-summary="showSummary" :summary-method="getSummaries" row-key="id" width="100%">
<template v-for="(col,index) in tableColumn" :key="index">
<el-table-column :width="operationWidth " :label="col.label" :prop="col.prop" align="center" v-if="col.label === '操作'&&col.status" >
<template #default="scope">
<!-- 将原有操作按钮放入插槽中 -->
<!-- scope.row 就是当前行的数据对象,在插槽中就可以使用 row 这个属性来访问当前行的数据。 -->
<slot name="operation" :row="scope.row"></slot>
</template>
</el-table-column>
<!-- 设置每一列的宽度,根据列的宽度和列的长度计算 -->
<!-- :width="index === 0 ? flexColumnWidth(col.prop, roleList) : '300'" -->
<el-table-column :width="setColumnWidth ? flexColumnWidth(col.prop, roleList) : ''" :label="col.label" v-if="tableColumn[index].status&&tableColumn[index].label!='操作'" :prop="tableColumn[index].prop" align="center" />
</template>
</el-table>
</div>
</template>
<script setup>
import { defineProps, ref, onMounted } from 'vue';
import Sortable from 'sortablejs';
const props = defineProps({
roleList: Array,
Col: Array,
showSummary: Boolean,
setColumnWidth:Boolean,
// summaryMethod: Function
// 是否显示操作列
showOperation: {
type: Boolean,
default: true
},
// 操作列的宽度
operationWidth:{
type:Number,
default:200
}
});
//由于props获取的值是只读的,因此只能定义新的变量来修改
let Col=props.Col;
let newCol = props.showOperation ? [...Col, { label: '操作', prop: 'operation', status: true }] : Col;
let checkboxColumn=ref(JSON.parse(JSON.stringify(newCol)));
let tableColumn=ref(JSON.parse(JSON.stringify(newCol)));
console.log(tableColumn.value)
debugger
let checkAll = ref(true);
function handleCheckAllChange() {
checkboxColumn.value.forEach(item => {
item.status = checkAll.value;
});
tableColumn.value.forEach(i => {
i.status=checkAll.value;
});
}
function handleCheckedBoxChange(item) {
const allTrue = checkboxColumn.value.every(item => item.status === true);
// 修改显示列表的状态
tableColumn.value.forEach(i => {
if(i.label==item.label){
i.status=item.status
}
});
if (allTrue) {
checkAll.value = true
} else {
checkAll.value = false
}
}
function columnDrop() {
// const wrapperTr = document.querySelector('.el-table__header-wrapper tr')
const wrapperTr = document.querySelector('.el-popper .dragCol')
let sortableInstance = Sortable.create(wrapperTr, {
animation: 180,
delay: 0,
onEnd: evt => {
let oldItem =tableColumn.value[evt.oldIndex-1]
tableColumn.value.splice(evt.oldIndex-1,1)
tableColumn.value.splice(evt.newIndex-1,0,oldItem)
}
})
}
// 自适应表格列宽
const flexColumnWidth = (str, tableData, flag = 'max') => {
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
// flag为可选值,可不传该参数,传参时可选'max'或'equal',默认为'max'
// flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。
str = str + ''
let columnContent = ''
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
return
}
if (!str || !str.length || str.length === 0 || str === undefined) {
return
}
if (flag === 'equal') {
// 获取该列中第一个不为空的数据(内容)
for (let i = 0; i < tableData.length; i++) {
if (tableData[i][str].length > 0) {
// console.log('该列数据[0]:', tableData[0][str])
columnContent = tableData[i][str]
break
}
}
} else {
// 获取该列中最长的数据(内容)
let index = 0
for (let i = 0; i < tableData.length; i++) {
// 数据为空跳出循环
// if (tableData[i][str] === null) {
// return
// }
const now_temp = tableData[i][str] + ''
const max_temp = tableData[index][str] + ''
if (now_temp.length > max_temp.length) {
index = i
}
}
columnContent = tableData[index][str]
}
// console.log('该列数据[i]:', columnContent)
// 以下分配的单位长度可根据实际需求进行调整
let flexWidth = 0
columnContent = columnContent + ''
for (const char of columnContent) {
if ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z')) {
// 如果是英文字符,为字符分配8个单位宽度
flexWidth += 12
} else if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符,为字符分配15个单位宽度
flexWidth += 15
} else {
// 其他种类字符,为字符分配15个单位宽度
flexWidth += 12
}
}
if (flexWidth < 80) {
// 设置最小宽度
flexWidth = 80
}
// if (flexWidth > 250) {
// // 设置最大宽度
// flexWidth = 250
// }
return flexWidth + 'px'
}
// 自定义汇总方法
const getSummaries = (param) => {
const { columns, data } = param
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '总数'
return
}
// console.log(column.property);
// 将所有元素的值转换成数字类型
const values = data.map((item) => Number(item[column.property]))
// console.log(values.some((val) => Number.isNaN(val)));
// every 只要有一个是数字,就会返回true
// 判断数组中的数据是否是数字类型 some 只要存在一个是NaN那么就返回false
if (!values.some((value) => Number.isNaN(value))) {
sums[index] = ` ${values.reduce((prev, curr) => {
const value = Number(curr)
if (!Number.isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)}`
} else {
sums[index] = '/'
}
})
return sums
}
onMounted(() => {
columnDrop();
});
</script>
<style scoped>
/* 可能需要的样式 */
</style>
在页面中使用的话只需要填充添加showOperation变量就好了,也可以使用operationWidth自定义宽度,默认200
<template>
<div>
<DraggableTable :Col="dropCol" :roleList="roleList" :setColumnWidth="true" :showSummary="true" :showOperation="true" >
<!-- 定义了一个名为 row 的变量来接收子组件传递过来的当前行的数据 -->
<template #operation="{ row }">
<!-- 这里放入你想要显示的操作按钮 -->
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" >修改</el-button>
</template>
</DraggableTable>
</div>
</template>
<script setup>
import { ref } from 'vue'
import DraggableTable from './dragComponent.vue'
const roleList = ref([
{ partName: 1, batchNo: 2, invoiceNo: 3, orderNo: 4, boxNo: 5, caseNo: 6 }
]);
const dropCol = ref([
{ label: '零件名称', prop: 'partName', status: true },
{ label: '批次号', prop: 'batchNo', status: true },
{ label: '发票号', prop: 'invoiceNo', status: true },
{ label: '订单号', prop: 'orderNo', status: true },
{ label: '箱号', prop: 'boxNo', status: true },
{ label: '小箱号', prop: 'caseNo', status: true }
]);
</script>
如果你只是需要直接在自己页面改造的话,可以看这篇