自定义antd或element table 列设置组件(拖拽排序及控制是否展示)

需求

  • 展示出所有的字段
  • 显示当前展示的是哪些字段
  • 可以全选、取消全选
  • 可以拖拽排序,更改字段的展示顺序,在前面还是在后面
  • 可以保存配置,刷新不失效

难点

  • 如何进行拖拽排序,自己手写一个吗?
  • 如何得到拖拽后的顺序?(个人觉得这个比较难)

效果

在这里插入图片描述
在这里插入图片描述

实现

  • 可以设计传入可以展示的全部字段数组当前展示字段的数组保存配置的名称,这样就能解决控制字段是否展示的需求。
  • 如何实现拖拽排序,不建议自己写,有现成的组件,而且能够返回排序后的顺序👍。推荐一个vue.draggable.next 中文文档简单好用。
  • 保存配置,这里是使用localStorage。注意踩坑:JSON.stringify不会保留函数,意味着你的一些排序函数,customRender函数会丢失。 页面进来加载配置时需要进行重新赋值

组件源码

<template>
  <a-tooltip>
    <template #title>{{ compTitle }}</template>
    <setting-outlined
      style="color: var(--theme-color)"
      @click="handleShowDrawer"
    />
  </a-tooltip>
  <a-drawer
    v-model:visible="drawerVisible"
    :title="compTitle"
    placement="right"
  >
    <div>
      <a-checkbox
        v-model:checked="checkAll"
        :indeterminate="indeterminate"
        @change="onCheckAllChange"
      >
        全选
      </a-checkbox>
      <span style="float: right">已选择 {{ checkedList.length }} 个 </span>
    </div>
    <a-checkbox-group
      class="checkbox-group"
      v-model:value="checkedList"
      @change="handleColumnChange"
    >
      <draggable
        v-model="allColumns"
        :item-key="(item) => item.dataIndex"
        handle=".move"
        animation="300"
        @end="onDragColumnEnd"
      >
        <template #item="{ element }">
          <li class="move">
            <drag-outlined style="font-size: 16px; margin-right: 10px" />
            <a-checkbox :value="element.dataIndex">
              {{ element.title }}</a-checkbox
            >
          </li>
        </template>
      </draggable>
    </a-checkbox-group>
    <template #footer>
      <div class="drawer-footer">
        <a-button @click="drawerVisible = false">取消</a-button>
        <a-button
          style="margin-left: 10px"
          type="primary"
          @click="handleSaveSetting"
          >保存</a-button
        >
      </div>
    </template>
  </a-drawer>
</template>

<script setup>
import { ref, watch, watchEffect } from 'vue'
import { message } from 'ant-design-vue'
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue'
import draggable from 'vuedraggable'

const props = defineProps({
  allColumnList: {
    // 所有列数组
    type: Array,
    default: () => []
  },
  currentColumns: {
    // 当前列数组
    type: Array,
    default: () => []
  },
  settingName: {
    type: String,
    default: 'column_setting'
  }
})
const emits = defineEmits(['columnChanged', 'columnOrderChanged'])
const compTitle = ref('列设置')
// 抽屉展示相关
const drawerVisible = ref(false)
const handleShowDrawer = () => {
  drawerVisible.value = true
}
// 选择列相关
const allColumns = ref([])
const checkedList = ref([])
watchEffect(() => {
  allColumns.value = props.allColumnList.filter(
    (item) => item.dataIndex !== 'action'
  )
  checkedList.value = props.currentColumns
    .filter((item) => item.dataIndex !== 'action')
    .map((item) => item.dataIndex)
})
// 监听列顺序改变
const onDragColumnEnd = () => {
  // console.log('列顺序改变', allColumns.value)
  // 新顺序的所有列
  const newOrderAllColumns = [
    ...allColumns.value,
    props.allColumnList[props.allColumnList.length - 1]
  ]
  emits('columnOrderChanged', newOrderAllColumns)
  // 生成新顺序的选中列
  const newOrderSelectColumns = [
    ...allColumns.value.filter((item) =>
      checkedList.value.includes(item.dataIndex)
    ),
    props.allColumnList[props.allColumnList.length - 1]
  ]
  emits('columnChanged', newOrderSelectColumns)
}
const checkAll = ref(false)
const indeterminate = ref(true) // 不定态
// 全选逻辑
const onCheckAllChange = (e) => {
  if (e.target.checked) {
    checkedList.value = allColumns.value.map((item) => item.dataIndex)
    handleColumnChange(checkedList.value)
  } else {
    checkedList.value = []
    handleColumnChange([])
  }
  indeterminate.value = false
}
watch(
  () => checkedList.value,
  (val) => {
    indeterminate.value = !!val.length && val.length < allColumns.value.length
    checkAll.value = val.length === allColumns.value.length
  }
)
// 处理列改变逻辑
const handleColumnChange = (dataIndexList) => {
  // console.log('新的值', dataIndexList, props.allColumnList)
  const newColumn = [
    ...props.allColumnList.filter((item) =>
      dataIndexList.includes(item.dataIndex)
    ),
    props.allColumnList[props.allColumnList.length - 1]
  ]
  emits('columnChanged', newColumn)
}
// 处理保存设置逻辑
const handleSaveSetting = () => {
  const setting = [...checkedList.value, 'action']
  const newOrderAllColumns = [
    ...allColumns.value,
    props.allColumnList[props.allColumnList.length - 1]
  ]
  localStorage.setItem(props.settingName, JSON.stringify(setting))
  localStorage.setItem(
    `${props.settingName}Order`,
    JSON.stringify(newOrderAllColumns)
  )
  message.success('保存成功')
  drawerVisible.value = false
}

defineExpose({
  SettingOutlined,
  DragOutlined,
  compTitle,
  draggable,
  drawerVisible,
  allColumns,
  checkedList,
  checkAll,
  indeterminate,
  handleShowDrawer,
  onCheckAllChange,
  handleSaveSetting,
  handleColumnChange,
  onDragColumnEnd
})
</script>

<style lang="less" scoped>
.checkbox-group {
  display: flex;
  flex-direction: column;
}
.drawer-footer {
  display: flex;
  justify-content: flex-end;
}
.move {
  list-style: none;
  cursor: move;
}
:deep(.ant-checkbox-wrapper + .ant-checkbox-wrapper) {
  margin-left: 0;
}
</style>

组件使用

在这里插入图片描述
在这里插入图片描述
懂得都懂

### 推荐适用于 Ant Design Table 组件拖拽扩展实现方式 #### 使用 `react-resizable` 结合 Ant Design 表格组件 为了使 Ant Design 的表格具备可拖拽调整宽的功能,可以引入第三方库 `react-resizable` 来增强原生功能。通过这种方式不仅能够满足基本需求,还能进一步拓展至支持复杂场景如固定表头多级表头等情况。 ```javascript import React from &#39;react&#39;; import { Table, Column } from &#39;antd&#39;; import Resizable from &#39;react-resizable&#39;; class ResizeableTable extends React.Component { render() { const columns = [ { title: &#39;Name&#39;, dataIndex: &#39;name&#39;, width: 200, resizable: true, onResizeStop: this.handleColumnResize, }, // 更多定义... ]; return ( <Resizable> <Table {...this.props} columns={columns} /> </Resizable> ); } } ``` 此方案利用了 `react-resizable` 提供的高度灵活接口来捕获用户的交互行为并实时更新对应的实际宽度[^4]。 #### 处理特殊布局下的兼容性问题 对于存在固定是多层次结构头部设计的情形,则需额外注意因 DOM 架构差异所带来的挑战。具体来说,在这些情况下 `<thead>` 和 `<tbody>` 被放置于独立的 HTML `<table>` 元素内,因此要确保每次修改某一侧时另一侧也能同步反映变化。针对这一点,可以通过遍历文档对象模型查找所有关联节点,并统一应用新的尺寸参数;而对于特别之处比如左侧冻结区域,则可能还需要借助 CSS 定制样式来进行微调以达到最佳视觉效果[^3]。 #### 支持复合表头的全面解决方案 如果项目中有涉及更复杂的业务逻辑——即允许用户自定义组合多个字段形成单一展示单元(即所谓的“复合表头”),那么除了上述提到的技术手段外,还需考虑如何优雅地管理各子项之间的相对位置关系及其各自的大小比例分配等问题。此时建议参考官方示例其他成熟案例中的做法,结合实际应用场景做出适当优化[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值