element-plus实现列表拖拽切换位置、顺序(支持搜索)

发布时间 2023-04-21 18:55:08作者: rachelch

1.组件实现

<template>
  <el-popover
    placement="bottom"
    popper-class="interBarControl-setPopover"
    :width="200"
    :visible="visible"
    trigger="click"
    @click.stop=""
  >
    <template #reference>
      <el-button size="small" type="primary" @click.stop="visible = true"
        >数据项</el-button
      >
    </template>
    <div class="popover-list" @click.stop="">
      <div class="search-box">
        <el-input
          placeholder="搜索"
          size="small"
          v-model="inputValue"
          @input="querySearch"
          clearable
        >
          <i class="el-icon-search el-input__icon" slot="prefix"></i>
        </el-input>
      </div>
      <div class="switch-box">
        <el-tree
          :allow-drop="allowDrop"
          :allow-drag="allowDrag"
          :data="dragListColumn"
          draggable
          node-key="id"
        >
          <template #default="{ node, data }">
            <div
              class="flex-row justify-content-between align-item-center drag-item"
            >
              <span> {{ node.label }}</span>
              <el-switch
                size="small"
                v-model="data.visible"
                :key="data.prop"
              ></el-switch>
            </div>
          </template>
        </el-tree>
      </div>
      <div class="btn-group flex-row justify-content-between">
        <el-button size="small" type="primary" @click="_resetColumn"
          >重置</el-button
        >
        <div>
          <el-button size="small" type="primary" @click="_cancel"
            >取消</el-button
          >
          <el-button size="small" type="success" @click="_saveColumn"
            >保存</el-button
          >
        </div>
      </div>
    </div>
  </el-popover>
</template>

<script lang="ts" setup>
import * as _ from 'lodash';
import {
  ref,
  defineProps,
  defineEmits,
  computed,
  watch,
  onMounted,
  onUnmounted,
  readonly,
} from 'vue';
import type Node from 'element-plus/es/components/tree/src/model/node';
import type { DragEvents } from 'element-plus/es/components/tree/src/model/useDragNode';
import type {
  AllowDropType,
  NodeDropType,
} from 'element-plus/es/components/tree/src/tree.type';
const $emit = defineEmits(['column-change']);
const props = defineProps({
  columns: {
    type: Array,
    default: () => [],
  },
});

const visible = ref(false);
const inputValue = ref('');
const dragListColumn = ref([]);
const originalColumn = ref([]);
const lastColumn = ref({});

// 列表操作输入
const querySearch = queryString => {
  let tableColumn = props.columns;
  let results = queryString
    ? tableColumn.filter(createFilter(queryString))
    : tableColumn;
  dragListColumn.value = _.cloneDeep([...results]);
};

const createFilter = queryString => {
  return tableColumn => {
    return tableColumn.label.indexOf(queryString) === 0;
  };
};

const allowDrop = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
  if (draggingNode.data.level === dropNode.data.level) {
    if (draggingNode.data.parentId === dropNode.data.parentId) {
      return type === 'prev' || type === 'next';
    } else {
      return false;
    }
  } else {
    // 不同级进行处理
    return false;
  }
};
const allowDrag = (draggingNode: Node) => {
  return true;
};

// 重置
const _resetColumn = () => {
  dragListColumn.value = _.cloneDeep(
    originalColumn.value.filter(item => item.label !== '操作')
  );
};

// 取消
const _cancel = () => {
  closePopover();
};

// 保存
const _saveColumn = () => {
  closePopover();
  $emit('column-change', dragListColumn.value.concat(lastColumn.value));
};

watch(
  () => props.columns,
  value => {
    if (value) {
      dragListColumn.value = _.cloneDeep(
        props.columns.filter(item => item.label !== '操作')
      );
      lastColumn.value = props.columns.filter(item => item.label == '操作');
    }
  }
);

const closePopover = () => {
  visible.value = false;
};

onMounted(() => {
  originalColumn.value = readonly(_.cloneDeep(props.columns)); // 不可修改
  dragListColumn.value = _.cloneDeep(
    props.columns.filter(item => item.label !== '操作')
  );
  lastColumn.value = props.columns.filter(item => item.label == '操作');
  window.addEventListener('click', closePopover);
});

onUnmounted(() => {
  window.removeEventListener('click', closePopover);
});
</script>

<style lang="scss" scoped>
.popover-list {
  margin: -12px;
  padding: 8px 0 0;
  :deep(.el-switch--small) {
    width: 24px !important;
    height: 14px !important;
    .el-switch__core {
      width: 24px !important;
      height: 14px !important;
      .el-switch__action {
        width: 12px;
        height: 12px;
        margin-top: -1px;
        margin-left: -1px;
      }
    }
    &.is-checked {
      .el-switch__action {
        width: 12px;
        height: 12px;
        margin-top: -1px;
        margin-left: -12px;
      }
    }
  }
  :deep(.el-tree-node__content) {
    height: 30px;
    &:hover {
      color: var(--el-color-primary);
      background-color: var(--theme-select-dropdown-item-hover);
    }
  }
}
.search-box {
  padding: 0 8px;
}
.switch-box {
  margin-top: 4px;
  max-height: 189px;
  overflow-y: auto;
}
.btn-group {
  padding: 12px;
  .el-button + .el-button {
    margin-left: 8px;
  }
}
.drag-item {
  width: 100%;
  font-size: var(--el-font-size-base);
  padding: 0 12px 0 0;
  position: relative;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--el-text-color-regular);
  height: 30px;
  line-height: 30px;
  box-sizing: border-box;
  margin-left: -12px;
  cursor: pointer;
  &:hover {
    color: var(--el-color-primary);
    background-color: var(--theme-select-dropdown-item-hover);
  }
}
.interBarControl-setPopover {
  .el-checkbox__label {
    width: 142px;
  }
  .el-checkbox {
    margin-bottom: 20px;
  }
  .el-checkbox + .el-checkbox {
    margin-left: 0;
  }
}
</style>

2.组件调用

      <r-table-data-item
        :columns="tableColumn"
        @column-change="handleColumnChange"
      ></r-table-data-item>

3.组件数据

const tableColumn = ref([
  {
    prop: 'date',
    label: '日期',
    width: 200,
    visible: true,
    headerSlot: 'dateHeader',
    render: ({ column }) =>
      h('span', proxy.$moment(column.date).format('yyyy-MM-DD')),
  },
  { prop: 'name', label: '姓名', width: 200, visible: true },
  { prop: 'num', label: '数量', width: 100, sortable: 'custom', visible: true },
  {
    prop: 'type',
    label: '类型',
    width: 100,
    visible: true,
    headerSlot: 'typeHeader',
  },
  {
    prop: 'address',
    label: '地址',
    width: 130,
    visible: true,
    headerRender: () =>
      h(ElInput, {
        ...ElInput.$el,
        ...ElInput.$attrs,
        size: 'small',
        placeholder: '请输入内容',
        modelValue: search.value,
        'onUpdate:modelValue': (value: any) => {
          return (search.value = value);
        },
      }),
  },
  {
    width: '180',
    label: '操作',
    visible: true,
    buttons: [
      {
        name: '编辑',
        type: 'primary',
        size: 'small',
        command: 'edit',
      },
      {
        name: '删除',
        type: 'primary',
        size: 'small',
        command: 'delete',
      },
      {
        name: '审核',
        type: 'primary',
        size: 'small',
        command: 'check',
      },
      {
        name: '保存',
        type: 'primary',
        size: 'small',
        command: 'save',
      },
      {
        name: '设置',
        type: 'primary',
        size: 'small',
        command: 'set',
      },
    ],
  },
]);
// 数据项
const handleColumnChange = val => {
  tableColumn.value = val;
};

4.组件效果