封装elemetui自定义表格实现可拖拽列、显示隐藏列、数据汇总、自适应宽度组件

效果如下:

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>

如果你只是需要直接在自己页面改造的话,可以看这篇

elemetui自定义表格实现可拖拽列、显示隐藏列、数据汇总、自适应宽度 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值