ant-design下的表格自定义列控制以及拖拽排序

项目背景

只要有系统写,那几乎就逃脱不了表格的各种花哨操作。好几年前前出过一篇《 el-table表格自定义列控制与el-table表格拖拽》的文章,收到掘友的反馈还是挺多的,但是我觉得写的还是有点low。这不再重操旧业写系统的背景下,发现了更高明的框架和UI,特拿出来与需要的朋友分享。

技术以及框架

这是一个vue3的项目,UI组件主要是以antdesignvxe-table来构建的,至于为啥要引入vxe-table。无非就是提供了好的api,居然没有免费开放还想要我的钱。抱歉,那我只能宠幸其他(vxe-table)了。

实现思路分

想要实现表格自定义列控制以及拖拽排序,我们将功能拆分一下就是2个点,分为是列的自定义控制和列的拖拽。为了全局通用,我们封装成一个组件,我们将每个页面的表格列项作为参数传值。再配合与列操作相关的方法,进行组件通信传值。

实现列控制显示与否

Ant-design的表格组件中,我们的列是通过一个数组遍历来实现的,那么我们想要控制列的展示与后,我们只要在循环之前过滤一下我们需要的列数组就行,在这我们定义一个字段icChecked用来辨别这个列项是否需要被展示,需要的话我们赋值为true,不需要则赋值为false。在store中定义一个改变状态的方法,方便组件调用。最后来一个勾选框,勾选时赋值true,取消就赋值false。我们通过轻松拿捏。废话不多说,先上一部分核心代码。

在store中存放的列控制原始数据

javascript 复制代码
materialColumns: [
    {
      dataIndex: 'material_id',
      title: '物料编码',
      isChecked: true,
      sorter: (a, b) => a.material_id.localeCompare(b.material_id)
    },
    {
      dataIndex: 'material_name',
      title: '物料名称',
      isChecked: true,
      sorter: (a, b) => a.material_name.localeCompare(b.material_name)
    },
    {
      dataIndex: 'is_active',
      title: '使用状态',
      isChecked: false,
      sorter: (a, b) => {
        if (a.is_active === b.is_active) {
          return 0
        }
        return a.is_active ? -1 : 1
      }
    },
    {
      dataIndex: 'status',
      title: '单据状态',
      isChecked: true,
      sorter: (a, b) => a.status - b.status
    }
]

在表格展示前的数据过筛选

ini 复制代码
const tableColumns = computed(() => {
 return materialStore.materialColumns.filter(item => item.isChecked)
})

列控制组件传值与方法

ini 复制代码
 <ColumnsSet
    ref="columnsSetRef"
    :tableData="materialStore.materialColumns"
    @changeColumn="changeColumn"
    @changeRow="changeRow"
  />

勾选框方法传值,控制显示与否

dart 复制代码
const handleCheckboxChange = selection => {
  emit('changeColumn', selection.row.dataIndex, selection.checked)
}

实现列拖拽

实现列拖拽,我们可能最先想到的是利用sortable.js,在这我主要是用的通过vxe-table,它对sorttable.js进行了一个二次封装,还能在表格中使用。

vxe-table拖拽实现原理

DOM 拖拽事件(dragstart, dragover, drop) 根据对目标元素的拖拽,获取初始位置以及结束位置,拖拽完成时,对数据进行位置交换,从而实现位置的改变。核心代码:

ini 复制代码
const rowDragendEvent = async ({ newRow, oldRow, dragPos }) => {

  const tableDataValue = props.tableData
  let oldIndex = tableDataValue.findIndex(
    item => item.dataIndex === oldRow.dataIndex
  )
  let newIndex = tableDataValue.findIndex(
    item => item.dataIndex === newRow.dataIndex
  )
  // 向下移动
  if (oldIndex < newIndex && dragPos === 'top') {
    newIndex = newIndex - 1
  }
  // 向上移动
  if (oldIndex > newIndex && dragPos === 'bottom') {
    newIndex = newIndex + 1
  }
  // 增加逻辑判断,如果 oldIndex 和 newIndex 相等,则不进行数据交换
  if (oldIndex !== newIndex) {
    const oldItem = tableDataValue.splice(oldIndex, 1)[0]
    tableDataValue.splice(newIndex, 0, oldItem)
    emit('changeRow', tableDataValue)
  }
}

特别注意vxe-table插件有个bug,需要自己弥补一下:

当你在拖拽移动过程中,对元素进行了移动,但是没有移动到位就停止,此时页面上的操作显示数据还没发生移动。但是此时移动拖拽的方法rowDragendEvent已经触发,那么数据也会进行交换,那就呵呵哒了,原始数据位置已经发生改变,页面没变化,以后的移动数据都会不准确。

进行缺陷补救措施

ini 复制代码
 // 向下移动
  if (oldIndex < newIndex && dragPos === 'top') {
    newIndex = newIndex - 1
  }
  // 向上移动
  if (oldIndex > newIndex && dragPos === 'bottom') {
    newIndex = newIndex + 1
  }

在上面的代码中我们增加了2个注释,从注释中我们可以看到其作用,那就是使用其第三参数,判断它是往上移动还是往下移动。在元素移动过程中,刚接触到上面时,我们不对数据进行交换处理,只有数据撤离移动到目标位置的底部,我们才进行数据交换。这样就完美避坑了。

如何保持每个人专属的配置

在这里我们根据项目情况选择pinia的持久化存储。那么这里就得使用到其对应的插件pinia-plugin-persistedstate

安装

css 复制代码
npm i pinia-plugin-persistedstate

引入到pina实例中

javascript 复制代码
import { createPinia } from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate)

export default pinia;

使用

css 复制代码
 persist: [{ pick: ['materialColumns'], storage: localStorage }],

在使用过程中,添加一个persist数组。里面可以包含多个对象,其中对象的pick对应将要进行持久化的数据,storage为要进行持久化处理的方式(sessionStorage,localStorage)。

控制台数据

列控制以及拖拽组件源码

ini 复制代码
<script setup>
import { ref, reactive } from 'vue'
const emit = defineEmits(['changeColumn', 'changeRow'])
const open = ref(false)
const title = '自定义显示列项'
const props = defineProps({
  tableData: {
    type: Array, // 数据类型为数组
    default: () => [] // 默认值为空数组
  }
})
const rowConfig = reactive({
  drag: true
})
const handleCheckboxChange = selection => {
  emit('changeColumn', selection.row.dataIndex, selection.checked)
}
const rowDragendEvent = async ({ newRow, oldRow, dragPos }) => {

  const tableDataValue = props.tableData
  let oldIndex = tableDataValue.findIndex(
    item => item.dataIndex === oldRow.dataIndex
  )
  let newIndex = tableDataValue.findIndex(
    item => item.dataIndex === newRow.dataIndex
  )
  // 向下移动
  if (oldIndex < newIndex && dragPos === 'top') {
    newIndex = newIndex - 1
  }
  // 向上移动
  if (oldIndex > newIndex && dragPos === 'bottom') {
    newIndex = newIndex + 1
  }
  // 增加逻辑判断,如果 oldIndex 和 newIndex 相等,则不进行数据交换
  if (oldIndex !== newIndex) {
    const oldItem = tableDataValue.splice(oldIndex, 1)[0]
    tableDataValue.splice(newIndex, 0, oldItem)
    emit('changeRow', tableDataValue)
  }
}

const showModal = () => {
  open.value = true
}
const handleOk = async () => {}
// 重置表单数据和校验状态的方法
defineExpose({
  showModal
})
</script>
<template>
  <a-modal
    v-model:open="open"
    :title="title"
    @ok="handleOk"
    :maskClosable="false"
    :footer="null"
  >
    <vxe-table
      border
      ref="tableRef"
      :row-config="rowConfig"
      max-height="460px"
      size="small"
      :checkbox-config="{ checkField: 'isChecked', showHeader: false }"
      @checkbox-change="handleCheckboxChange"
      @row-dragend="rowDragendEvent"
      :data="props.tableData"
    >
      <vxe-column
        type="checkbox"
        title="显示"
        align="center"
        width="60"
      ></vxe-column>
      <vxe-column field="title" title="列明"></vxe-column>
      <vxe-column
        field=""
        title="拖动排序"
        drag-sort
        align="center"
      ></vxe-column>
    </vxe-table>
  </a-modal>
</template>

总结

温故而知新,通过不同的技术栈,以及框架,重新实现了一遍列控制和拖拽排序。

相关推荐
汪洪墩9 分钟前
使用Mars3d加载热力图的时候,出现阴影碎片
开发语言·前端·javascript·vue.js·cesium
木木黄木木9 分钟前
使用Three.js创建炫酷的3D玻璃质感动态效果
开发语言·javascript·3d
weixin_ab12 分钟前
Uniapp 中根据不同离开页面方式处理 `onHide` 的方法
javascript·uni-app
云之遥_12 分钟前
Cornerstone3D 2.x升级调研
前端·医学影像·cornerstone3d
幸福摩天轮16 分钟前
大O表示法
前端
半碗水16 分钟前
修·洞(JS)
javascript·css
游九尘19 分钟前
vue2自定义指令directive用法: dom中关键字文字高亮
前端·vue
moning25 分钟前
realStartActivity 是由哪里触发的?
前端
德莱厄斯36 分钟前
简单聊聊小程序、uniapp及其生态圈
前端·微信小程序·uni-app
tianchang39 分钟前
从输入 URL 到页面渲染:浏览器做了什么?
前端·面试