vue3+elementuiPlus+sortablejs实现两个el-table上下左右互相拖拽


sortablejs操作文档可具体参照:Sortable.js中文网 (sortablejs.com)

一、html代码

<template>
  <el-dialog
    v-model="props.columnsBol"
    title="设置列"
    width="70%"
    @close="cameraClose"
  >
    <div class="tips">
      <el-icon color="#4290f7" :size="12">
        <BellFilled />
      </el-icon>
      <span class="tips-info">可以通过拖拽行直接修改行的位置与顺序;对列表进行勾选可控制列的显示与隐藏</span>
    </div>
    <div class="box-wrapper">
      <div class="single-table-container left-table">
        <div class="search-form-wrapper">
          <div class="title">
            左侧固定列
          </div>
        </div>
        <div ref="table_ref" class="single-table">
          <el-table
            ref="tables"
            :data="leftFixedColumn"
            class="draggable"
            row-key="prop"
            border
            fit
            highlight-current-row
            style="width: 100%"
            height="500px"
            @select="handleChageLeft"
            @select-all="handleChageLeft"
          >
            <el-table-column type="selection" width="50" />
            <el-table-column align="center" label="列" prop="label" width="150">
              <template #default="scope">
                <div style="display: flex; align-items: center">
                  <el-icon><Rank /></el-icon>
                  <span style="margin-left: 10px">{{ scope.row.label }}</span>
                </div>
              </template>
            </el-table-column>
            <el-table-column align="center" label="操作" >
              <template #default="scope">
                <el-button
                  link
                  type="primary"
                  @click="onMoveLeftOrRight(scope.row, 'right')"
                >
                  移动至【右侧滚动列】
                </el-button>
                <el-button
                  link
                  type="primary"
                  :disabled="scope.$index == 0"
                  @click="onMoveTopOrBottom(scope.row, scope.$index, 'left', 'top')"
                >
                  上移
                </el-button>
                <el-button
                  link
                  type="primary"
                  :disabled="scope.$index == leftFixedColumn.length - 1"
                  @click="onMoveTopOrBottom(scope.row, scope.$index, 'left', 'bottom')"
                >
                  下移
                </el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </div>
      <div class="single-table-container right-table">
        <div class="search-form-wrapper">
          <div class="title">
            右侧滚动列
          </div>
        </div>
        <div ref="table_refB" class="single-table">
          <el-table
            ref="tablesB"
            height="500px"
            :data="originalColumn"
            class="draggable"
            row-key="prop"
            border
            fit
            highlight-current-row
            style="width: 100%"
            @select="handleChageRight"
            @select-all="handleChageRight"
          >
            <el-table-column type="selection" width="50" />
            <el-table-column align="center" label="列" prop="label" width="150">
              <template #default="scope">
                <div style="display: flex; align-items: center">
                  <el-icon><Rank /></el-icon>
                  <span style="margin-left: 10px">{{ scope.row.label }}</span>
                </div>
              </template>
            </el-table-column>
            <el-table-column align="center" label="操作" >
              <template #default="scope">
                <el-button
                  link
                  type="primary"
                  @click="onMoveLeftOrRight(scope.row, 'left')"
                >
                  移动至【左侧固定列】
                </el-button>
                <el-button
                  link
                  type="primary"
                  :disabled="scope.$index == 0"
                  @click="onMoveTopOrBottom(scope.row, scope.$index, 'right', 'top')"
                >
                  上移
                </el-button>
                <el-button
                  link
                  type="primary"
                  :disabled="scope.$index == originalColumn.length - 1"
                  @click="onMoveTopOrBottom(scope.row, scope.$index, 'right', 'bottom')"
                >
                  下移
                </el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </div>
    </div>
    <template #footer>
      <span class="dialog-footer">
        <!-- <el-button @click="onReset()">重置</el-button> -->
        <el-button @click="cameraClose()">取消</el-button>
        <el-button type="primary" @click="onSubmit()" >确认</el-button>
      </span>
    </template>
  </el-dialog>
</template>

二、typeScript 代码


<script setup lang='ts'>
import _ from 'lodash';
import type { SortableEvent } from 'sortablejs';
import Sortable from 'sortablejs';
import { setColumnList, updateColumnList } from '~/api/system/dynamicColumn';
import { useColumnStore } from '~/store/modules/columns';

interface User {
  label: string;
  prop: string;
  fixed: string | boolean;
  checked: boolean;
  width: number;
  showOverflowTooltip?: boolean;
}

const props = defineProps({
  columnsBol: {
    type: Boolean,
    default: false,
  },
  clonColumnList: {
    type: Array,
  },
  // 表格id
  tableId: {
    type: Number,
    require: true,
  },
  columnCode: {
    type: String,
  },
  columnId: {
    type: Number,
  },
});
const emits = defineEmits<{ (e: 'update:columnsBol', value: boolean): void; (e: 'setColumnList', value: any): void; }>();

const cameraClose = () => {
  emits('update:columnsBol', false);
  emits('setColumnList', props.clonColumnList);
  tables.value!.clearSelection();
  tablesB.value!.clearSelection();
};

const leftFixedColumn = ref<[]>([]);
const leftSelectList = ref<[]>([]);
const originalColumn = ref<[]>([]);
const originalSelectList = ref<[]>([]);

// @ts-ignore
leftFixedColumn.value = _.filter(props.clonColumnList, (item) => item.fixed == 'left');
// @ts-ignore
originalColumn.value = _.filter(props.clonColumnList, (item) => !item.fixed);

// 监听移动的 表格数据 重新赋值
watch(
  () => props.clonColumnList,
  (val) => {
    onReset();
  },
  { deep: true },
);

// 获取el-table ref
const tables: any = ref<HTMLElement | null>(null);
// 获取t-table ref
const table_ref: any = ref<HTMLElement | null>(null);

// 获取el-tableB ref
const tablesB: any = ref<HTMLElement | null>(null);
// 获取t-tableB ref
const table_refB: any = ref<HTMLElement | null>(null);

// 行拖拽
const initSort = () => {
  nextTick(() => {
    const el = table_ref.value.querySelector('.el-table__body-wrapper tbody');
    Sortable.create(el, {
      group: 'shared', // 添加才能左右表格互相拖拽,添加以后才有onAdd、onRemove事件
      animation: 150, // 动画
      // disabled: false, // 拖拽不可用? false 启用(刚刚渲染表格的时候起作用,后面不起作用)
      // handle: '.move', // 指定拖拽目标,点击此目标才可拖拽元素(此例中设置操作按钮拖拽)
      // filter: '.disabled', // 指定不可拖动的类名(el-table中可通过row-class-name设置行的class)
      dragClass: 'dragClass', // 设置拖拽样式类名
      ghostClass: 'ghostClass', // 设置拖拽停靠样式类名
      chosenClass: 'chosenClass', // 设置选中样式类名
      onAdd: (event: SortableEvent) => {
        if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
          const curRow = originalColumn.value.splice(event.oldIndex, 1)[0];
          console.log('===curRow111', curRow);
          // @ts-ignore
          curRow.fixed = 'left';
          leftFixedColumn.value.splice(event.newIndex, 0, curRow);
        }
      },
      onUpdate: (event: SortableEvent) => {
        if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
          const curRow = leftFixedColumn.value.splice(event.oldIndex, 1)[0];
          leftFixedColumn.value.splice(event.newIndex, 0, curRow);
        }
      },
      onEnd: () => {
        toggleSelection();
      },
    });

    const elB = table_refB.value.querySelector('.el-table__body-wrapper tbody');
    Sortable.create(elB, {
      group: 'shared', //
      animation: 150, // 动画
      // disabled: false, // 拖拽不可用? false 启用(刚刚渲染表格的时候起作用,后面不起作用)
      // handle: '.move', // 指定拖拽目标,点击此目标才可拖拽元素(此例中设置操作按钮拖拽)
      // filter: '.disabled', // 指定不可拖动的类名(el-table中可通过row-class-name设置行的class)
      dragClass: 'dragClass', // 设置拖拽样式类名
      ghostClass: 'ghostClass', // 设置拖拽停靠样式类名
      chosenClass: 'chosenClass', // 设置选中样式类名
      onAdd: (event: SortableEvent) => {
        if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
          const curRow = leftFixedColumn.value.splice(event.oldIndex, 1)[0];
          // @ts-ignore
          curRow.fixed = false;
          originalColumn.value.splice(event.newIndex, 0, curRow);
        }
        // console.log('===leftFixedColumn.value', leftFixedColumn.value);
        // console.log('===originalColumn.value', originalColumn.value);
      },
      onUpdate: (event: SortableEvent) => {
        if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
          const curRow = originalColumn.value.splice(event.oldIndex, 1)[0];
          originalColumn.value.splice(event.newIndex, 0, curRow);
        }
      },
      onEnd: () => {
        toggleSelection();
      },
    });
  });
};

// 默认选中
const toggleSelection = () => {
  nextTick(() => {
    // @ts-ignore
    _.forEach(leftFixedColumn.value, (row) => {
      // @ts-ignore
      if (row && tables.value) {
        tables.value.toggleRowSelection(
          row,
          // @ts-ignore
          row.checked,
        );
      }
    });
    // @ts-ignore
    _.forEach(originalColumn.value, (row) => {
      // @ts-ignore
      if (row && tablesB.value) {
        tablesB.value.toggleRowSelection(
          row,
          // @ts-ignore
          row.checked,
        );
      }
    });
  });
};

// 左右移动
const onMoveLeftOrRight = (e, move) => {
  if (move === 'right') {
    e.fixed = false;
    // @ts-ignore
    originalColumn.value.unshift(e);
    // @ts-ignore
    leftFixedColumn.value = _.filter(leftFixedColumn.value, (item) => item.prop != e.prop);
  }
  if (move === 'left') {
    e.fixed = 'left';
    // @ts-ignore
    leftFixedColumn.value.unshift(e);
    // @ts-ignore
    originalColumn.value = _.filter(originalColumn.value, (item) => item.prop != e.prop);
  }
  toggleSelection();
};

// 上下移动
const onMoveTopOrBottom = (e, index, position, move) => {
  if (position === 'left') {
    if (move === 'top' && index == 0) {
      return;
    }
    if (move === 'bottom' && index == leftFixedColumn.value.length - 1) {
      return;
    }
    const curRow = leftFixedColumn.value.splice(index, 1)[0];
    leftFixedColumn.value.splice(move === 'top' ? index - 1 : index + 1, 0, curRow);
  }
  if (position === 'right') {
    if (move === 'top' && index == 0) {
      return;
    }
    if (move === 'bottom' && index == originalColumn.value.length - 1) {
      return;
    }
    const curRow = originalColumn.value.splice(index, 1)[0];
    originalColumn.value.splice(move === 'top' ? index - 1 : index + 1, 0, curRow);
  }
};

// 保存
const onSubmit = () => {
  const allList = _.concat(leftFixedColumn.value, originalColumn.value);
  emits('setColumnList', allList);
  emits('update:columnsBol', false);
  const code = ref<string>('');
  const id = ref<number>();
  const { tableId, columnCode, columnId } = props;
  // @ts-ignore
  const columnInfo = useColumnStore().getColumnCodeAndIdStore(props.tableId);
  code.value = columnCode || (columnInfo.code ? columnInfo.code : '');
  id.value = columnId || (columnInfo.id ? columnInfo.id : 0);
  // console.log('===columnInfo.value', columnInfo);
  if (code.value) {
    updateColumnList({
      tableId,
      code: code.value,
      id: id.value,
      tableColumns: JSON.stringify(allList),
    }).then((res) => {
      // @ts-ignore
      useColumnStore().setColumnStore(props.tableId, allList);
    });
  }
  else {
    setColumnList({
      tableId,
      tableColumns: JSON.stringify(allList),
    }).then((res) => {
      // console.log('==res新增', res);
      // @ts-ignore
      useColumnStore().setColumnStore(props.tableId, allList);
    });
  }
  tables.value!.clearSelection();
  tablesB.value!.clearSelection();
};

const onReset = () => {
  // @ts-ignore
  leftFixedColumn.value = _.filter(_.cloneDeep(props.clonColumnList), (item) => item.fixed == 'left');
  // @ts-ignore
  originalColumn.value = _.filter(_.cloneDeep(props.clonColumnList), (item) => !item.fixed);
  toggleSelection();
};

const handleChageRight = (selection) => {
  const data = _.map(selection, 'prop');
  _.forEach(originalColumn.value, (item, index) => {
    // @ts-ignore
    if (_.includes(data, item.prop)) {
      // @ts-ignore
      originalColumn.value[index].checked = true;
    }
    else {
      // @ts-ignore
      originalColumn.value[index].checked = false;
    }
  });
};

const handleChageLeft = (selection) => {
  const data = _.map(selection, 'prop');
  _.forEach(leftFixedColumn.value, (item, index) => {
    // @ts-ignore
    if (_.includes(data, item.prop)) {
      // @ts-ignore
      leftFixedColumn.value[index].checked = true;
    }
    else {
      // @ts-ignore
      leftFixedColumn.value[index].checked = false;
    }
  });
};

defineExpose({
  initSort,
  toggleSelection,
});
</script>

三、style 代码

<style lang="scss" scoped>
.tips {
  line-height: 35px;
  border: 1px solid #4290f7;
  border-radius: 10px;
  padding: 0 20px;
  margin-bottom: 10px;
  background: #B4D5FF;
  color: #333;
  &-info {
    margin-left: 5px;
  }
}
.box-wrapper {
  font-size: 14px;
  display: flex;
  justify-content: space-between;
  .left-table {
    width: 51%;
    padding: 0;
  }
  .right-table {
    width: 48%;
    padding: 0;
    .el-input {
      width: 100%;
    }
    .el-form-item {
      margin: 0;
    }
    .el-radio-group {
      :deep(.el-radio) {
        margin-right: 10px;
        .el-radio__label {
          font-size: 12px;
          padding-left: 10px;
        }
      }
    }
  }
}
//单页表格
.single-table-container {
  width: 100%;
  height: 100%;
  padding: 10px;
  overflow: auto;
  .search-form-wrapper {
    height: 40px;
    display: flex;
    align-items: center;
    border: 1px solid #e6eaef;
    border-bottom: none;
    padding: 0 10px;
    .title {
      font-size: 14px;
      font-weight: 700;
      color: #303133;
    }
  }
  .single-table {
    height: calc(100% - 80px);
    .inner_table {
      padding: 10px;
      // background: rgba(25, 137, 254, 0.1);
      margin-top: -4px;
      margin-bottom: -4px;
    }
  }
  .table-pagination {
    text-align: right;
    height: 40px;
    background: #fff;
    border: 1px solid #e6eaef;
    border-top: unset;
    .el-pagination {
      padding: 6px 10px;
    }
  }
}
</style>

四、核心实现(***添加group才能左右拖拽***

// 行拖拽
const initSort = () => {
  nextTick(() => {
    const el = table_ref.value.querySelector('.el-table__body-wrapper tbody');
    Sortable.create(el, {
      group: 'shared', // 添加才能左右表格互相拖拽,添加以后才有onAdd、onRemove事件
      animation: 150, // 动画
      // disabled: false, // 拖拽不可用? false 启用(刚刚渲染表格的时候起作用,后面不起作用)
      // handle: '.move', // 指定拖拽目标,点击此目标才可拖拽元素(此例中设置操作按钮拖拽)
      // filter: '.disabled', // 指定不可拖动的类名(el-table中可通过row-class-name设置行的class)
      dragClass: 'dragClass', // 设置拖拽样式类名
      ghostClass: 'ghostClass', // 设置拖拽停靠样式类名
      chosenClass: 'chosenClass', // 设置选中样式类名
      onAdd: (event: SortableEvent) => {
        if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
          const curRow = originalColumn.value.splice(event.oldIndex, 1)[0];
          console.log('===curRow111', curRow);
          // @ts-ignore
          curRow.fixed = 'left';
          leftFixedColumn.value.splice(event.newIndex, 0, curRow);
        }
      },
      onUpdate: (event: SortableEvent) => {
        if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
          const curRow = leftFixedColumn.value.splice(event.oldIndex, 1)[0];
          leftFixedColumn.value.splice(event.newIndex, 0, curRow);
        }
      },
      onEnd: () => {
        toggleSelection();
      },
    });

    const elB = table_refB.value.querySelector('.el-table__body-wrapper tbody');
    Sortable.create(elB, {
      group: 'shared', //
      animation: 150, // 动画
      // disabled: false, // 拖拽不可用? false 启用(刚刚渲染表格的时候起作用,后面不起作用)
      // handle: '.move', // 指定拖拽目标,点击此目标才可拖拽元素(此例中设置操作按钮拖拽)
      // filter: '.disabled', // 指定不可拖动的类名(el-table中可通过row-class-name设置行的class)
      dragClass: 'dragClass', // 设置拖拽样式类名
      ghostClass: 'ghostClass', // 设置拖拽停靠样式类名
      chosenClass: 'chosenClass', // 设置选中样式类名
      onAdd: (event: SortableEvent) => {
        if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
          const curRow = leftFixedColumn.value.splice(event.oldIndex, 1)[0];
          // @ts-ignore
          curRow.fixed = false;
          originalColumn.value.splice(event.newIndex, 0, curRow);
        }
        // console.log('===leftFixedColumn.value', leftFixedColumn.value);
        // console.log('===originalColumn.value', originalColumn.value);
      },
      onUpdate: (event: SortableEvent) => {
        if (event && event.oldIndex !== undefined && event.newIndex !== undefined) {
          const curRow = originalColumn.value.splice(event.oldIndex, 1)[0];
          originalColumn.value.splice(event.newIndex, 0, curRow);
        }
      },
      onEnd: () => {
        toggleSelection();
      },
    });
  });
};

下面是一个完整的示例代码,基于Vue2,antd和sortablejs实现两个表格之间的数据拖拽功能。 ```html <template> <div> <h2>表格1</h2> <a-table :columns="columns" :dataSource="list1" rowKey="id"> <template #name="{text, record}"> <span v-text="text" v-sortable="&#39;table1&#39;"></span> </template> </a-table> <h2>表格2</h2> <a-table :columns="columns" :dataSource="list2" rowKey="id"> <template #name="{text, record}"> <span v-text="text" v-sortable="&#39;table2&#39;"></span> </template> </a-table> </div> </template> <script> import Sortable from &#39;sortablejs&#39; import { Table } from &#39;ant-design-vue&#39; export default { components: { &#39;a-table&#39;: Table, }, data() { return { columns: [ { title: &#39;姓名&#39;, dataIndex: &#39;name&#39; }, { title: &#39;年龄&#39;, dataIndex: &#39;age&#39; }, ], list1: [ { id: 1, name: &#39;张三&#39;, age: 18 }, { id: 2, name: &#39;李四&#39;, age: 20 }, { id: 3, name: &#39;王五&#39;, age: 22 }, ], list2: [], } }, mounted() { Sortable.create(this.$el.querySelector(&#39;.ant-table-tbody&#39;), { group: { name: &#39;table1&#39;, put: [&#39;table2&#39;], }, animation: 150, fallbackOnBody: true, swapThreshold: 0.65, onEnd: this.onEnd, }) Sortable.create(this.$el.querySelectorAll(&#39;.ant-table-tbody&#39;)[1], { group: { name: &#39;table2&#39;, put: [&#39;table1&#39;], }, animation: 150, fallbackOnBody: true, swapThreshold: 0.65, onEnd: this.onEnd, }) }, methods: { onEnd(evt) { const { oldIndex, newIndex, item } = evt if (oldIndex !== newIndex) { const table = evt.to.dataset.table if (table === &#39;table1&#39;) { // 从表格1拖拽表格2 this.list2.splice(newIndex, 0, this.list1.splice(oldIndex, 1)[0]) } else { // 从表格2拖拽表格1 this.list1.splice(newIndex, 0, this.list2.splice(oldIndex, 1)[0]) } } }, }, } </script> ``` 在此示例中,我们使用了Ant Design Vue中的`<a-table>`组件来渲染表格,并在模板中使用了Vue的`v-sortable`指令来绑定Sortable实例。在mounted钩子函数中,我们为每个表格的tbody元素创建了Sortable实例,并将它们分组为“table1”和“table2”。然后,我们在onEnd方法中处理拖拽完成事件,并根据拖拽的方向和目标表格移动数据。 需要注意的是,在模板中,我们使用了`<span>`元素包装了表格中的文本数据,来实现拖拽时只能拖拽文本数据而不是整个行。同时,在Sortable实例的options中,我们设置了`fallbackOnBody`为true,这样拖拽元素在拖拽结束时会回到原来的位置,而不是消失。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值