vue实现antd-vue table表格 右上角设置列功能(支持列搜索列、上移、下移、置顶、置底)

根据现有需求考虑到表头很多,写了组件弹窗支持列搜索列、上移、下移、置顶、置底,上代码:

子组件:

<a-modal v-model:visible="visible" title="列设置" width="600px" @ok="handleOk" @cancel="handleCancel">
    <!-- 选项卡:全部列、显示列、隐藏列 -->
    <a-tabs v-model:activeKey="activeTab" @change="changeTabCallback">
      <a-tab-pane key="all" tab="全部列">
        <a-table
          :data-source="filteredData"
          :columns="columnsList"
          :pagination="false"
          :row-selection="{ selectedRowKeys: selectedColumns, onChange: colChange }"
        >
          <template #headerCell="{ column }">
            <template v-if="column.key === 'title'">
              <span style="color: #1890ff">名称 {{ selectedColumns.length }}/{{ filteredData.length }}</span>
            </template>
          </template>
          <template #customFilterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
            <div style="padding: 8px">
              <a-input
                ref="searchInput"
                :placeholder="`搜索${column.title}`"
                :value="selectedKeys[0]"
                style="width: 188px; margin-bottom: 8px; display: block"
                @change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
                @pressEnter="handleSearch(selectedKeys, confirm, column.dataIndex)"
              />
              <a-button
                type="primary"
                size="small"
                style="width: 90px; margin-right: 8px"
                @click="handleSearch(selectedKeys, confirm, column.dataIndex)"
              >
                <template #icon><SearchOutlined /></template>
                Search
              </a-button>
              <a-button size="small" style="width: 90px" @click="handleReset(clearFilters)"> Reset </a-button>
            </div>
          </template>
          <template #customFilterIcon="{ filtered }">
            <search-outlined :style="{ color: filtered ? '#1890ff' : '#1890ff' }" />
          </template>
          <template #bodyCell="{ record, column, index }">
            <span v-if="searchText && searchedColumn === column.dataIndex">
              <template v-for="(fragment, i) in record.title.toString().split(new RegExp(`(?<=${searchText})|(?=${searchText})`, 'i'))">
                <mark v-if="fragment.toLowerCase() === searchText.toLowerCase()" :key="i" class="highlight">
                  {{ fragment }}
                </mark>
                <template v-else>{{ fragment }}</template>
              </template>
            </span>
            <template v-if="column.dataIndex === 'action'">
              <a-space>
                <a @click="moveItem(record, index, 'up')">上移</a>
                <a-divider type="vertical" />
              </a-space>
              <a-space>
                <a @click="moveItem(record, index, 'down')">下移</a>
                <a-divider type="vertical" />
              </a-space>
              <a-space>
                <a @click="moveItem(record, index, 'top')">置顶</a>
                <a-divider type="vertical" />
              </a-space>
              <a-space>
                <a @click="moveItem(record, index, 'bottom')">置底</a>
                <a-divider type="vertical" />
              </a-space>
            </template>
          </template>
        </a-table>
      </a-tab-pane>

      <a-tab-pane key="visible" tab="显示列">
        <a-list>
          <a-list-item v-for="col in visibleColumns" :key="col.key">
            {{ col.title }}
          </a-list-item>
        </a-list>
      </a-tab-pane>

      <a-tab-pane key="hidden" tab="隐藏列">
        <a-list>
          <a-list-item v-for="col in hiddenColumns" :key="col.key">
            {{ col.title }}
          </a-list-item>
        </a-list>
      </a-tab-pane>
    </a-tabs>
  </a-modal>

  

<script>
export default {
  props: {
    visible: Boolean,
    columns: Array // 所有列配置
  },
  emits: ['update:visible', 'change'],
  data() {
    return {
      searchedColumn: '',
      searchText: '', // 搜索文本
      activeTab: 'all', // 当前选项卡(all/visible/hidden)
      selectedColumns: [], // 当前选中的列(存储 col.key)
      selectedRowKeys: [],
      filteredData: [],
      columnsList: [
        {
          title: '名称',
          dataIndex: 'title',
          key: 'title',
          width: 260,
          minWidth: 260,
          customFilterDropdown: true,
          onFilter: (value, record) => record.title.toString().toLowerCase().includes(value.toLowerCase()),
          onFilterDropdownOpenChange: visible => {
            if (visible) {
              setTimeout(() => {
                searchInput.value.focus();
              }, 100);
            }
          }
        },
        {
          title: '操作',
          dataIndex: 'action'
        }
      ]
    };
  },
  computed: {
    // 显示的列(当前选中的列)
    visibleColumns() {
      return this.columns.filter(col => this.selectedColumns.includes(col.key));
    },
    // 隐藏的列
    hiddenColumns() {
      return this.columns.filter(col => !this.selectedColumns.includes(col.key));
    }
  },
  watch: {
    // 监听初始化
    visible() {
      if (this.visible) {
        this.searchText = '';
        this.filteredData = [...this.columns];
        this.selectedColumns = this.columns
          .filter(col => col.checked) // 初始选中的列(如果 col 有 checked 属性)
          .map(col => col.key);
      }
    }
  },
  methods: {
    changeTabCallback(key) {
      if (key == 'visible') {
      }
    },
    colChange(val, key) {
      this.selectedColumns = val;
      this.filteredData = this.updateCheckedStatus(this.filteredData, val);
    },
    updateCheckedStatus(data, targetKeys) {
      return data.map(column => ({
        ...column, // 保留原有属性
        checked: targetKeys.includes(column.key) // 如果 key 在 targetKeys 中,则 checked: true,否则 false
      }));
    },
    handleOk() {
      this.selectedColumns = this.filteredData
        .filter(item => item.checked) // 筛选 checked: true 的项
        .map(item => item.key); // 提取 key
      this.$emit('change', this.selectedColumns, this.filteredData);
      this.$emit('update:visible', false);
    },
    handleCancel() {
      this.$emit('update:visible', false);
    },
    moveItem(record, index, type) {
      const arr = this.filteredData;
      if (type === 'up' && index > 0) {
        [arr[index - 1], arr[index]] = [arr[index], arr[index - 1]];
      }
      if (type === 'down' && index < arr.length - 1) {
        [arr[index + 1], arr[index]] = [arr[index], arr[index + 1]];
      }
      if (type === 'top' && index > 0) {
        const item = arr.splice(index, 1)[0];
        arr.unshift(item);
      }
      if (type === 'bottom' && index < arr.length - 1) {
        const item = arr.splice(index, 1)[0];
        arr.push(item);
      }
      // 带有fixed: right属性始终在最下面
      arr.sort((a, b) => {
        const aHasFixed = a.fixed === 'right';
        const bHasFixed = b.fixed === 'right';
        if (aHasFixed && !bHasFixed) return 1; // a 带 fixed,b 不带,a 排后面
        if (!aHasFixed && bHasFixed) return -1; // a 不带 fixed,b 带,a 排前面
        return 0; // 保持原有顺序
      });
    },
    handleSearch(selectedKeys, confirm, dataIndex) {
      confirm();
      this.searchText = selectedKeys[0];
      this.searchedColumn = dataIndex;
    },
    handleReset(clearFilters) {
      clearFilters({
        confirm: true
      });
      this.searchText = '';
    }
  }
};
</script>

父组件:

<a-tooltip placement="top">
              <template #title>
                <span>列设置</span>
              </template>
              <SettingOutlined @click="showColumnSettings" />
            </a-tooltip>


    <columnSettings v-model:visible="columnSettingsVisible" :columns="columns" @change="handleColumnsChange" />



export default {
  data() {
return: {
columnSettingsVisible: false,
 // 表格列配置
      columns: [
        {
          key: 'index',
          width: 48,
          checked: true,
          hideInSetting: true,
          customRender: ({index}) => this.$refs.table.tableIndex + index
        },
        {
          title: '订单号',
          dataIndex: 'orderMovementXid',
          key: 'orderMovementXid',
          checked: true,
          resizable: true,
          width: 160,
          minWidth: 100
        },
        {
          title: '创建日期',
          dataIndex: 'orderCreationDate',
          key: 'orderCreationDate',
          checked: true,
          resizable: true,
          width: 160,
          minWidth: 100
        },
        {
          title: '金额',
          dataIndex: 'contractAmountInc',
          key: 'contractAmountInc',
          checked: true,
          resizable: true,
          width: 160,
          minWidth: 100
        },
        {
          title: '名称',
          dataIndex: 'templateName',
          key: 'templateName',
          checked: true,
          resizable: true,
          width: 160,
          minWidth: 100
        },
        {
          title: '明细',
          dataIndex: 'templateDetailCode',
          key: 'templateDetailCode',
          checked: true,
          resizable: true,
          width: 160,
          minWidth: 100
        },
        {
          title: '状态',
          dataIndex: 'chargingStatus',
          key: 'chargingStatus',
          checked: true,
          resizable: true,
          width: 160,
          minWidth: 100
        },
        {
          title: '原因',
          dataIndex: 'chargingFailReason',
          key: 'chargingFailReason',
          checked: true,
          resizable: true,
          width: 160,
          minWidth: 100
        },
        {
          title: '操作',
          key: 'action',
          checked: true,
          width: 120,
          align: 'center',
          fixed: 'right'
        }
      ],
}
},
  methods: {
     showColumnSettings() {
      this.columnSettingsVisible = true;
    },
     handleColumnsChange(selectedKeys, filteredData) {
      // 更新列的显示状态
      this.columns = filteredData;
    },
},
 computed: {
    displayedColumns() {
      return this.columns.filter(col => col.checked);
    }
  }
}

展示效果

image

 

posted @ 2025-09-08 10:01  孙三水ya  阅读(29)  评论(0)    收藏  举报