0

Is there a built-in tool in the Ant Vue Table component for making columns draggable? From my research, it seems there is no such feature available. I attempted to use vuedraggable, but encountered difficulties. Here is my table builder component. Do you have any suggestions or solutions?

<template>
  <div v-if="_columns">
    <div class="d-flex w-100 justify-content-end">
      <div class="d-flex align-items-center">
        <div v-if="filterColumn && _allColumns && _activeColumns"
             class="mb-3 mx-1 mt-3 w-100 d-flex justify-content-end">
          <div style="width: 200px">
            <CustomSelect
                :multiple="true"
                :select-data="_allColumns"
                v-model:value="_activeColumns"
                placeholder="ستون ها"
                @update:value="handleFilterColumnsSelectChange"
            />
          </div>
        </div>
        <div v-if="additionalButtons" class="w-100 d-flex justify-content-end">
          <a-space class="">
            <template v-for="button in additionalButtons">
              <a-button :class="'button button-' + button.color" @click.prevent="$emit(button.eventName)">
                <i :class="button.icon"></i>
                {{ button.label }}
              </a-button>
            </template>
          </a-space>
        </div>
      </div>
    </div>
    <a-config-provider>
      <template #renderEmpty>
        <EmptyDb :simple="true"/>
      </template>
      <a-table
          :scroll="{ x: 1500 }"
          :data-source="_dataSource"
          :columns="_columns"
          :pagination="false"
          size="small"
          :loading="loading"
          @change="tableChange"
          :show-sorter-tooltip="false"
          :row-selection="rowSelection ? { selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }: false"
      >
        <template
            #customFilterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }"
            v-if="enableColumnSearch"
        >
          <div style="padding: 8px">
            <a-input
                v-model:value="searchInput"
                :placeholder="` جستجوی ${column.title}`"
                style="width: 188px; margin-bottom: 8px; display: block"
                @pressEnter="handleColumnSearch(column)"
            />
            <a-space>
              <a-button
                  type="primary"
                  size="small"
                  style="width: 90px; margin-right: 8px"
                  @click="() => handleColumnSearch(column)"
              >
                <template #icon>
                  <i class="bi bi-search"></i>
                </template>
                جستجو
              </a-button>
              <a-button size="small" style="width: 90px" @click="() => searchInput = ''">
                پاک کردن
              </a-button>
            </a-space>
          </div>
        </template>
        <template #customFilterIcon="{ filtered }">
          <SearchOutlined/>
        </template>
        <template #bodyCell="{ column, text, record }">
          <template v-if="column.dataIndex === 'actions'">
            <td class="actions-cell d-flex justify-content-center text-center align-items-center">
              <a-button size="small" @click="$emit('deleteClicked', record)" v-if="actions.deleteAction"
                        class="mx-1 button button-danger">
                {{ buttonDeleteText ? buttonDeleteText : 'حذف' }}
              </a-button>
              <a-button size="small" @click="$emit('editClicked', record)" v-if="actions.editAction"
                        class="mx-1 button button-warning">
                {{ buttonEditText ? buttonEditText : 'ویرایش' }}
              </a-button>
              <a-button size="small" @click="$emit('viewClicked', record)" v-if="actions.viewAction"
                        class="mx-1 button button-primary">
                {{ buttonViewText ? buttonViewText : 'نمایش' }}
              </a-button>
            </td>
            <td v-if="customActionButtons && customActionButtons.length > 0"
                class="actions-cell d-flex justify-content-center text-center align-items-center">
              <template v-for="button in customActionButtons">
                <a-button class="ms-1" size="small" style="cursor: pointer"
                          @click.prevent="$emit(button.eventName, record, column)"
                          :class="'button button-' + button.color">
                  {{ button.label }}
                </a-button>
              </template>
            </td>
          </template>
          <template v-else-if="column.key in columnRenders">
            {{ columnRenders[column.key](record) }}
          </template>
          <template v-else>
            <td class="text-center d-flex justify-content-center align-items-center">
              {{ text }}
            </td>
          </template>
        </template>
      </a-table>
    </a-config-provider>
    <a-pagination
        v-if="pagination"
        :total="pagination*10"
        v-model:current="currentPage"
        @change="$emit('pagination', currentPage)"
        class="d-flex justify-content-center mt-3"
        :show-size-changer="false"
    ></a-pagination>
  </div>
</template>

<script lang="ts">
import {defineComponent, type PropType, ref, reactive, computed, onMounted, watch, UnwrapRef} from "vue";
import type {TableColumnsType} from 'ant-design-vue';
import CustomSelect from "@/components/custom/global/CustomSelect.vue";
import EmptyDb from "@/components/custom/EmptyDb.vue";
import {SearchOutlined} from "@ant-design/icons-vue";
import draggable from "vuedraggable";


interface Actions {
  deleteAction: boolean,
  editAction: boolean,
  viewAction: boolean
}

interface ColumnRenders {
  [key: string]: (record: any) => string
}

export default defineComponent({
  components: {CustomSelect, EmptyDb, SearchOutlined, draggable},
  props: {
    dataSource: {
      required: true
    },
    columns: {
      required: true
    },
    scrollX: {
      required: false,
      type: Number
    },
    actions: {
      required: false,
      type: Object as PropType<Actions>,
      default: {
        deleteAction: false,
        editAction: false,
        viewAction: false
      }
    },
    pagination: {
      type: Number,
      required: false
    },
    loading: {
      type: Boolean,
      required: false
    },
    buttonEditText: {
      type: Text,
      required: false
    },
    buttonViewText: {
      type: Text,
      required: false
    },
    buttonDeleteText: {
      type: Text,
      required: false
    },
    rowKey: {
      type: Boolean,
      required: false
    },
    selectedRows: {
      type: [],
      required: false
    },
    rowSelection: {
      type: Boolean,
      required: false
    },
    filterColumn: {
      type: Boolean,
      default: false,
      required: false
    },
    customActionButtons: {
      type: Array as PropType<{ label: string, eventName: string, color: string }[]>,
      required: false
    },
    columnRenders: {
      type: Object as PropType<ColumnRenders>,
      required: false,
      default: () => ({})
    },
    actionCellWidth: {
      type: Number,
      required: false,
      default: 300
    },
    enableColumnSearch: {
      type: Boolean,
      required: false,
      default: false
    },
    additionalButtons: {
      type: Array as PropType<{ label: string, eventName: string, color?: string, icon?: string }[]>,
      required: false,
    }
  },
  setup(props, context) {
    type id = string | number;
    const _allColumns = ref<any[] | null>(null);
    const currentPage = ref<number | null>(1);
    const _dataSource = ref(null);
    const _columns = ref<TableColumnsType>(null);
    const _columnsCopy = ref<TableColumnsType>(null);
    const _activeColumns = ref<string[]>(null);
    const editableData: UnwrapRef<Record<string, never>> = reactive({});
    const searchInput = ref('');
    const state = reactive<{
      selectedRowKeys: id[];
      loading: boolean;
    }>({
      selectedRowKeys: [], // Check here to configure the default column
      loading: false,
    });

    // handle editable rows
    // const edit = (key: string) => {
    //   editableData[key] = cloneDeep(_dataSource.value.filter(el => el.key === key)[0]);
    // }
    // const save = (key: string) => {
    //   Object.assign(_dataSource.value.filter(item => key === item.key)[0], editableData[key]);
    //   delete editableData[key];
    // };
    // const cancel = (key: string) => {
    //   delete editableData[key];
    // };

    const handleColumnSearch = (searchedColumn) => {
      context.emit('columnSearch', searchedColumn, searchInput.value);
    }

    const onSelectChange = (selectedRowKeys: id[]) => {
      state.selectedRowKeys = selectedRowKeys;
    };
    const hasSelected = computed(() => state.selectedRowKeys.length > 0);

    const tableChange = (pagination, filters, sorter) => {
      context.emit('handleTableChange', pagination, filters, sorter);
    }

    watch(() => props.dataSource, () => {
      _dataSource.value = props.dataSource.map((el, index) => {
        return {
          index: index + 1,
          ...el
        }
      });
    });

    onMounted(() => {
      _dataSource.value = props.dataSource.map((el, index) => {
        return {
          index: index + 1,
          ...el
        }
      });

      let temp = props.columns.map(el => {
        if (el.dataIndex === 'actions') {
          return {
            ...el,
            width: props.actionCellWidth,
            fixed: 'right',
            key: `column_${el.key}`
          }
        }
        return {
          ...el,
          width: 200,
        }
      });

      _columns.value = [{
        title: 'ردیف',
        dataIndex: 'index',
        key: 'index',
        width: 70,
      }, ...temp];

      _columnsCopy.value = _columns.value;

      _allColumns.value = _columns.value.map(el => {
        return {
          label: el.title,
          value: el.key,
        }
      });

      _activeColumns.value = _columns.value.map(el => el.key);
    });

    const handleFilterColumnsSelectChange = (values) => {
      _columns.value = _columnsCopy.value.map(el => {
        if (values.includes(el.key)) return el;
      }).filter(el => el !== undefined);
    }

    return {
      currentPage,
      state,
      hasSelected,
      onSelectChange,
      tableChange,
      _columns,
      _allColumns,
      _activeColumns,
      handleFilterColumnsSelectChange,
      _dataSource,
      searchInput,
      handleColumnSearch,

      // edit rows
      // edit,
      // save,
      // cancel,

      // editableData
    }
  }
})
</script>

<style lang="scss">
.cell-min-width {
  text-align: center;
}

.ant-table-cell {
  text-align: center !important;
}

:where(.css-dev-only-do-not-override-mv22ks).ant-table-wrapper .ant-table-thead > tr > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before, :where(.css-dev-only-do-not-override-mv22ks).ant-table-wrapper .ant-table-thead > tr > td:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
  background-color: #909090;
}

.ant-table-thead th:last-child,
.ant-table-tbody td:last-child {
  position: sticky !important;
  left: -10px;
  z-index: 1;
}

.ant-table-thead th:last-child {
  box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px !important;
}

.ant-select-selection-overflow {
  display: none !important;
}


.ant-table-thead {
  .ant-table-cell {
    font-size: 17px !important;
  }
}

</style>

I tried using the vuedraggable component, but unfortunately, it didn't seem to work as expected.

0

Browse other questions tagged or ask your own question.