什么? 封装el-table组件 实现动态表头 单元格编辑 行拖拽等 直接摸鱼时间翻倍

因公司项目需要,会用到大量表格作为数据展示,唯一不同的地方就是表格的展示,有的一级有的多级表头,重复写起来很麻烦,每生产一个表格,就得重复写一套或者 分页 查询 等套餐,或者对分页单独封装引用。还有需求上大同小异的变化,比如这个表格要输入框,那个表格要下拉框,索性干脆封装一下,自己用来方便 ,加班?加不了一点 !,封装完直接Copy,传入对应的接口等参数信息直接完事!支持的功能也都注释清楚了。

话不多说,直接上代码,当然自己写的可能不是很规范,比较随意,功能自己迭代中(有新需求就继续往上迭代优化),目前还存在一些问题和BUG,慢慢优化吧,也欢迎大佬指出问题。😂😂😂

2023/08/28  针对可编辑时渲染大量组件造成页面性能卡顿进行了优化,保持唯一性。
2023/11/17  新增拖拽等功能,代码优化,修复部分bug。

代码部分

主页面 index.vue 

<template>
  <div v-loading="Listloading">
    <div
      v-if="titleShow"
      style="padding: 20px 0px;overflow: hidden;display: flex;align-items: center;justify-content: center;position: relative;"
    >
      <div style="position: absolute;left: 0;">
        <slot name="ft"></slot>
      </div>
      <div style="font-size: 18px;font-weight: 900;position: absolute;">
        {{ title }}
      </div>
      <div style="position: absolute;right: 0;">
        <slot name="ht"></slot>
      </div>
    </div>
    <div class="hrm-table">
      <div
        style="width: 100%;height: 100%;position: relative;"
        v-loading="loading"
      >
        <el-table
          stripe
          border
          ref="table"
          :row-height="40"
          :resizable="true"
          :data="tableData"
          :sum-text="sumText"
          @row-click="rowClick"
          highlight-current-row
          @cell-click="cellClick"
          @sort-change="sortChange"
          :show-summary="showSummary"
          :height="TabularData.height"
          :summary-method="getSummaries"
          :span-method="arraySpanMethod"
          class="el-table-header--white"
          :cell-class-name="cellClassName"
          @header-dragend="handleHeaderDragend"
          @selection-change="handleSelectionChange"
          :header-cell-class-name="tableHeaderClassName"
        >
          <!-- 表头 -->
          <template v-show="loading && columnData.length">
            <!-- 勾选 -->
            <el-table-column
              type="selection"
              width="55"
              :selectable="selectEnable"
              v-if="selection"
            >
            </el-table-column>
            <Column
              v-for="(item, index) in columnData"
              :isEdit="isEdit"
              :columnId="columnId"
              :rowId="rowId"
              :customHtml="customHtml"
              :rowIdCode="rowIdCode"
              :key="index"
              :data="item"
              :indexNum="index"
              :fixedNum="fixedNumberColumns"
              :inputType="item.inputType ? item.inputType : inputType"
              :fieldName="fieldName"
              :selectOption="selectOption"
              @handleEdit="handleEdit"
              @removeClass="removeClass"
            >
              <!-- 自定义HTML -->
              <template
                slot="customContent"
                slot-scope="scope"
                v-if="customHtml"
              >
                <slot name="Content" v-bind="scope"></slot>
              </template>
            </Column>
            <!-- 操作列 -->
            <el-table-column
              v-if="isAction"
              :fixed="actionData.fixed"
              :label="actionData.title"
              :width="actionData.width"
            >
              <!-- 操作列 -->
              <template slot-scope="scope">
                <slot name="ActionButton" v-bind="scope"></slot>
              </template>
            </el-table-column>
            <!-- 额外自定义列 -->
            <el-table-column
              :resizable="false"
              fixed="right"
              width="40"
              v-if="customColumnShow"
            >
              <!-- 列头 -->
              <template
                slot="header"
                slot-scope="slot"
                style="position: relative;"
              >
                <slot name="columnHeader" v-bind="slot"></slot>
              </template>
              <!-- 内容 -->
              <template slot-scope="scope">
                <slot name="columnBody" v-bind="scope"></slot>
              </template>
            </el-table-column>
          </template>
        </el-table>
        <div
          v-show="editLoading"
          style="position: absolute;top: 50%; left: 50%;color: rgb(35, 98, 251);z-index: 999;"
        >
          加载中<i class="el-icon-loading"></i>
        </div>
      </div>
      <div class="p-contianer" v-if="paginationShow">
        <el-pagination
          :current-page="currentPage"
          :page-sizes="pageSizes"
          :page-size.sync="pageSize"
          :total="total"
          class="p-bar"
          layout="total, sizes, prev, pager, next, jumper"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </div>
  </div>
</template>

<script>
import Column from "./Column";
import { debounce } from "./debounce";
import { rowDrop } from "./sortable";
export default {
  components: {
    Column
  },
  provide() {
    const { scopedSlots } = this;
    return {
      scopedSlots
    };
  },
  props: {
    TabularData: {
      type: Object,
      default: () => {
        return {
          SpanMethod: null, // 合并方法
          customHtml: false, //自定义html
          customColumnShow: false, //自定义列
          isDrag: false, // 是否拖拽
          height: 600, //高度
          fieldName: false, //是否动态字段 fieldName + fieldExtend
          isShowDynamicHeaders: true, //是否开启动态表头
          FirstCall: true, // 是否首次调用
          headerHata: [], //固定表头数据
          HeaderDataApi: null, // 表头方法
          HeaderQuery: {}, // 表头参数
          DataApi: null, // 数据方法
          DataQuery: {}, // 数据参数
          EditApi: null, // 编辑方法
          EditQuery: {}, // 编辑参数
          rowIdCode: "id", // 数据唯一标识字段 默认取id
          DeleteApi: null, // 删除方法
          DeleteQuery: {}, // 删除参数
          selection: false, // 是否开启勾选
          titleShow: false, // 是否开启标题
          title: "", //标题
          updateNow: false, // 编辑时是否马上加载loading
          showSummary: false, //是否开启合计
          sumText: "合计", //合计文本
          getSummaries: null, // 自定义合计方法
          isEditFun: null, // 自定义编辑方法
          isEdit: false, // 是否开启编辑
          inputType: "input", // 可编辑组件类型input、textarea、select、number
          selectOption: [], //组件类型为下拉框的时候需要传入数据
          isAction: false, // 是否展示操作列
          actionData: {
            // 操作列配置
            title: "操作",
            width: 50,
            fixed: "right"
          },
          fixedNumberColumns: 0, // 固定列数
          frequencyTime: 1500, // 刷新频率
          paginationShow: true // 是否展示分页
        };
      }
    }
  },
  data() {
    return {
      SpanMethod: null, //合并方法
      customHtml: false, //自定义html
      customColumnShow: false, //自定义列
      isDrag: false, // 列表是否可以拖动
      loading: false,
      editLoading: false,
      Listloading: false,
      columnId: "", // 列ID
      rowId: "", //行ID
      total: 0,
      currentPage: 1,
      pageSize: 15,
      pageSizes: [15, 30, 60, 100],
      columnData: [],
      tableData: [],
      FirstCall: true,
      headFun: null,
      hearQuery: {},
      listFun: null,
      listQuery: {},
      EditFun: null,
      EditQuery: {},
      rowIdCode: "id", // 默认id唯一标识
      DeleteFun: null,
      DeleteQuery: {},
      selection: false,
      titleShow: false,
      title: "",
      updateNow: false, // 编辑时是否马上加载loading
      multipleSelection: [],
      showSummary: false, //是否开启合计
      sumText: "合计", //合计文本
      getSummaries: null, // 自定义合计方法
      isShowDynamicHeaders: true,
      headerHata: [],
      isEdit: false, // 是否可以编辑
      isEditFun: null, // 自定义编辑方法
      inputType: "input", // 可编辑组件类型input、textarea、select、number
      selectOption: [], //组件类型为下拉框的时候需要传入数据
      isAction: false, // 是否展示操作列
      actionData: {}, // 操作列配置
      empty: [
        {
          columnName: "暂无数据",
          fieldName: "NAN",
          id: "1"
        },
        {
          columnName: "暂无数据",
          fieldName: "NAN",
          id: "2"
        },
        {
          columnName: "暂无数据",
          fieldName: "NAN",
          id: "3"
        },
        {
          columnName: "暂无数据",
          fieldName: "NAN",
          id: "4"
        },
        {
          columnName: "暂无数据",
          fieldName: "NAN",
          id: "5"
        }
      ],
      fixedNumberColumns: 0, //固定列数
      frequencyTime: 900, // 刷新频率
      fieldName: false, //是否动态字段 fieldName + fieldExtend
      paginationShow: true, // 是否展示分页
      // 传递
      scopedSlots: {}
    };
  },
  computed: {},
  watch: {
    TabularData: {
      handler(val) {
        this.init();
        console.log("...更新数据");
      },
      immediate: true,
      deep: true
    }
  },
  updated() {}, // 生命周期 - 更新之后
  created() {}, // 生命周期 - 创建完成
  mounted() {
    console.log(this.TabularData, "初始化====>");
    this.getHead();
    // 在挂载后给特定函数防抖
    let than = this;
    let time = than.frequencyTime;
    this.updata = debounce(function() {
      this.getList("NO");
    }, time);
  }, // 生命周期 - 挂载完成
  beforeCreate() {}, // 生命周期 - 创建之前
  beforeMount() {}, // 生命周期 - 挂载之前
  beforeUpdate() {}, // 生命周期 - 更新之前
  updated() {}, // 生命周期 - 更新之后
  beforeDestroy() {}, // 生命周期 - 销毁之前
  destroyed() {}, // 生命周期 - 销毁完成
  activated() {}, // 如果页面有keep-alive缓存功能,这个函数会触发
  methods: {
    // 修改固定列高度 因为设置了滚动条高度 造成el-table表格错位
    fixedHeight() {
      const dom = this.$el.getElementsByClassName("el-table__fixed");
      if (dom && dom.length > 0) {
        for (let i = 0; i < dom.length; i++) {
          let fix = dom[i];
          let he = fix.style.height;
          if (he) {
            let px = he.replace("px", "");
            fix.style.height = px - 2 + "px";
            console.log(px, "px");
          }
        }
      }
    },
    // 合并
    arraySpanMethod({ row, column, rowIndex, columnIndex }) {
      // this.$emit("arraySpanMethod", row, column, rowIndex, columnIndex);
      // 判断是否存在自定义合并方法函数
      if (typeof this.SpanMethod === "function" && this.SpanMethod != null) {
        // 传递参数触发函数
        return this.SpanMethod(row, column, rowIndex, columnIndex);
      }
    },
    // 长度统计
    computeColumnWidth(column) {
      if (column.childes && column.childes.length > 0) {
        return column.childes.reduce(
          (prev, next) => prev + this.computeColumnWidth(next),
          0
        );
      }
      return column.realWidth || column.width || 150;
    },
    //重新渲染组件 解决fixed错位问题
    doLayoutFun() {
      this.$nextTick(() => {
        if (this.$refs.table) {
          this.$refs.table.doLayout();
        }
      });
    },
    // 合计方法
    // getSummaries(param) {
    //   const { columns, data } = param;
    //   const sums = [];
    //   columns.forEach((column, index) => {
    //     if (index === 0) {
    //       sums[index] = "总价";
    //       return;
    //     }
    //     const values = data.map(item => Number(item[column.property]));
    //     if (!values.every(value => isNaN(value))) {
    //       sums[index] = values.reduce((prev, curr) => {
    //         const value = Number(curr);
    //         if (!isNaN(value)) {
    //           return prev + curr;
    //         } else {
    //           return prev;
    //         }
    //       }, 0);
    //       sums[index] += " 元";
    //     } else {
    //       sums[index] = "N/A";
    //     }
    //   });
    //   return sums;
    // },
    // 是否禁用
    selectEnable(row) {
      return true;
    },
    // 当选择项发生变化时会触发该事件
    handleSelectionChange(selection) {
      this.multipleSelection = selection;
    },
    // 获取列表数据
    getList(NO) {
      if (!this.listFun) return (this.Listloading = false);
      // 如果不想加载lodong 传 NO
      // this.loading = NO == undefined ? true : false;
      // this.Listloading = NO == undefined ? true : false;
      this.Listloading = true;
      this.listFun({
        ...this.listQuery,
        pageSize: this.pageSize,
        pageNum: this.currentPage
      }).then(res => {
        if (res.code != 0) {
          this.lodingFun(false);
          this.tableData = [];
          this.total = 0;
          this.$message.error(res.msg);
          return;
        }
        // 存在分页的情况数据路径在res.data.list中
        if (res.data.list && Array.isArray(res.data.list)) {
          this.tableData = res.data.list;
          this.total = res.data.total;

          this.doLayoutFun();
          // 验证是否开启拖拽
          if (this.isDrag) {
            console.log("开启拖拽");
            rowDrop(this);
          }
        }
        this.lodingFun(false);
        // 更新固定列高度
        // this.fixedHeight();
      });
    },
    // 全局loding管理
    lodingFun(check) {
      this.loading = check;
      this.editLoading = check;
      this.Listloading = check;
    },
    // 获取表头
    async getHead(next) {
      // next 是否在获取表头后,立马更新列表 true/false
      // 判断是否开启动态表头
      if (this.isShowDynamicHeaders) {
        if (!this.headFun) return;
        // this.loading = true;
        this.Listloading = true;
        this.headFun({
          ...this.hearQuery
        }).then(res => {
          // this.loading = false;
          // 如果返回错误
          if (res.code != 0) {
            this.columnData = [];
            this.columnData = this.empty;
            this.lodingFun(false);
            return this.$message.error(res.msg);
          }
          // 如果空数据
          if (res.data.length == 0) {
            this.columnData = [];
            this.columnData = this.empty;
            this.lodingFun(false);
          } else {
            this.columnData = [];
            let arr = res.data;
            //  解决element ui el-table 固定列在多级表头时宽度无法动态变更
            arr.forEach(column => {
              var fixedWidth = 0;
              fixedWidth += this.computeColumnWidth(column);
              column.width = fixedWidth;
            });
            // 表头赋值
            this.columnData = arr;
            //
            this.$nextTick(() => {
              // FirstCall 是否需要首次加载 更新完表头 更新数据
              if (this.FirstCall || next) {
                this.getList();
              } else {
                this.lodingFun(false);
              }
            });
          }
        });
      } else {
        // 固定表头
        if (this.headerHata && this.headerHata.length > 0) {
          this.columnData = this.headerHata;
          // FirstCall 是否需要首次加载 更新完表头 更新数据
          if (this.FirstCall || next) {
            this.getList();
          }
        } else {
          this.columnData = this.empty;
        }
      }
    },
    // 初始化
    init() {
      // 取值
      const {
        isShowDynamicHeaders,
        headerHata,
        FirstCall,
        HeaderDataApi,
        HeaderQuery,
        DataApi,
        DataQuery,
        EditApi,
        EditQuery,
        DeleteApi,
        DeleteQuery,
        selection,
        titleShow,
        title,
        updateNow,
        showSummary,
        sumText,
        getSummaries,
        isEditFun,
        rowIdCode,
        isEdit,
        inputType,
        selectOption,
        isAction,
        actionData,
        fixedNumberColumns,
        fieldName,
        paginationShow,
        frequencyTime,
        isDrag,
        customColumnShow,
        customHtml,
        SpanMethod
      } = this.TabularData;
      //加载
      this.loading = true;
      // 是否开启动态表头
      this.isShowDynamicHeaders = isShowDynamicHeaders || false;
      // 固定表头
      this.headerHata = headerHata != undefined ? headerHata : [];
      // 是否首次调用查询列表
      this.FirstCall = FirstCall !== undefined ? FirstCall : true;
      // 表头数据接口方法
      this.headFun = HeaderDataApi || null;
      // 表头参数
      this.hearQuery = HeaderQuery || {};
      // 列表数据接口方法
      this.listFun = DataApi || null;
      // 列表参数
      this.listQuery = DataQuery || {};
      //  编辑接口方法
      this.EditFun = EditApi || null;
      // 编辑参数
      this.EditQuery = EditQuery || {};
      // 删除方法
      this.DeleteFun = DeleteApi || null;
      // 删除参数
      this.DeleteQuery = DeleteQuery || {};
      // 是否开启选择
      this.selection = selection || false;
      // 是否开启标题
      this.titleShow = titleShow || false;
      // 标题
      this.title = title || "";
      // 是否编辑立马加载loading
      this.updateNow = updateNow || false;
      // 是否开启合计
      this.showSummary = showSummary || false;
      // 合计抬头
      this.sumText = sumText || "合计";
      // 自定义合计方法
      this.getSummaries = getSummaries || null;
      // 自定义是否可编辑判断 返回 true  false
      this.isEditFun = isEditFun || null;
      // 数据唯一标识字段 用来可编辑时渲染组件 默认取id字段
      this.rowIdCode = rowIdCode || "id";
      // 是否可以编辑
      this.isEdit = isEdit || false;
      // 可编辑时 的组件类型 inputType input、textarea、select、number
      this.inputType = inputType || "input";
      // selectOption
      this.selectOption = selectOption || [];
      // 是否开启操作列
      this.isAction = isAction || false;
      // 操作项配置
      this.actionData = actionData || {};
      // 固定列数
      this.fixedNumberColumns = fixedNumberColumns || 0;
      // 是否动态字段
      this.fieldName = fieldName || false;
      // 是否分页
      this.paginationShow = paginationShow != undefined ? paginationShow : true;
      // 自定编辑后刷新频率
      this.frequencyTime = frequencyTime || 1500;
      // 列表是否可进行拖拽
      this.isDrag = isDrag || false;
      // 自定义列
      this.customColumnShow = customColumnShow || false;
      // 自定义html
      this.customHtml = customHtml || false;
      // 自定义合并
      this.SpanMethod = SpanMethod || null;
      console.log(this.TabularData, "=====初始化完成====");
      // 结束加载
      this.loading = false;
    },
    // 更改每页展示数量
    handleSizeChange(val) {
      this.pageSize = val;
      this.getList();
    },
    // 更改当前页数
    handleCurrentChange(val) {
      this.currentPage = val;
      this.getList();
    },
    // 表头单元格的 className 的回调方法,也可以使用字符串为所有表头单元格设置一个固定的 className。
    tableHeaderClassName({ row, column, rowIndex, columnIndex }) {
      // console.log(row, column, rowIndex, columnIndex,'======>tableHeaderClassName')
      let titleName = "";
      if (column.index == 1 && column.type == "default" && this.isEdit) {
        titleName = "table-header-edited"; //edited
        let dom = document.createElement("i");
        dom.className = "el-icon-edit";
        dom.title = "可编辑";
        this.$nextTick(() => {
          let col = document.getElementsByClassName("table-header-edited");
          for (let index = 0; index < col.length; index++) {
            let element = col[index];
            if (element.children.length < 2) {
              element.appendChild(dom);
            }
          }
        });
      }
      return titleName;
    },
    // 行点击
    rowClick(row, column, event) {},
    // 单元格点击
    cellClick(row, column, cell, event) {
      this.rowId = row[this.rowIdCode];
      this.columnId = column.id; // 避免大量渲染组件造成卡顿 只渲染局部
      this.$emit("cellClick", row, column, cell, event);
      // 是否开启编辑
      if (!this.isEdit) return;
      // 是否自定义编辑
      setTimeout(() => {
        if (typeof this.isEditFun === "function" && this.isEditFun != null) {
          // 传递参数触发函数
          let flx = this.isEditFun(row, column, cell, event);
          // 有传自定义方法
          if (!flx) return;
          this.cellClickClassName(cell);
        } else {
          // 没有传方法 默认全部开启可编辑
          this.cellClickClassName(cell);
        }
      });
    },
    // 点击时给列增加类名可编辑
    cellClickClassName(cell) {
      let than = this.$el;
      let COLUMNDOM = than.getElementsByClassName("current-cell").length;
      for (let i = 0; i < COLUMNDOM; i++) {
        than
          .getElementsByClassName("current-cell")
          [i].classList.remove("current-cell");
      }
      cell.classList.add("current-cell");
    },
    // 通过回调控制class
    cellClassName({ row, column, rowIndex, columnIndex }) {
      // console.log(row, column, rowIndex, columnIndex, "=====cellClassName====");
      if (this.detailAuth && column.property == "employeeName") {
        return "can-visit--underline";
      } else {
        return "";
      }
    },
    // 编辑
    handleEdit(scope) {
      this.getEdit(scope);
    },
    // 编辑数据
    getEdit(scope) {
      if (!this.EditFun) return;
      // this.loading = true;
      this.Listloading = this.updateNow;
      this.EditFun({
        ...scope.row,
        ...this.TabularData.EditQuery
      }).then(res => {
        // this.loading = false;
        if (res.code != 0) return this.$message.error(res.msg);
        // this.$message.success("操作成功!");
        // 编辑完数据 更新数据
        let than = this;
        // this.editLoading = true;
        this.updata(than);
      });
    },
    // 更新接口 防抖触发
    updata: debounce(function() {
      this.getList("NO");
    }, 1500),
    // 删除 类名
    removeClass() {
      let than = this.$el;
      let COLUMNDOM = than.getElementsByClassName("current-cell").length;
      for (let i = 0; i < COLUMNDOM; i++) {
        than
          .getElementsByClassName("current-cell")
          [i].classList.remove("current-cell");
      }
    },
    //字段排序
    sortChange(column, prop, order) {},
    // 列表点击
    handleRowClick(row, column, event) {},
    // 当拖动表头改变了列的宽度的时候会触发该事件
    handleHeaderDragend(newWidth, oldWidth, column, event) {}
  }
};
</script>
<style scoped lang="scss">
.hrm-table {
  /deep/ .el-table__cell {
    text-align: center;
  }

  // 输入框
  /deep/ .el-input {
    display: none;
  }
  /deep/ .current-cell .el-input {
    display: block;
  }
  /deep/ .current-cell .el-input + span {
    display: none;
  }
  // 多行文本
  /deep/ .el-textarea {
    display: none;
  }

  /deep/ .current-cell .el-textarea {
    display: block;
  }
  /deep/ .current-cell .el-textarea + span {
    display: none;
  }
  // 数字框
  /deep/.el-input-number {
    width: 100%;
  }
  /deep/ .el-input-number {
    display: none;
  }

  /deep/ .current-cell .el-input-number {
    display: block;
  }
  /deep/ .current-cell .el-input-number + span {
    display: none;
  }

  // 下拉框
  /deep/ .current-cell .el-select {
    display: block;
  }
  /deep/ .current-cell .el-select + span {
    display: none;
  }
  /deep/ .current-cell {
    padding: 0px;
  }
}
// 分页
.p-contianer {
  /deep/ .el-input {
    display: block !important;
  }
  /deep/ .el-pagination__jump {
    display: inline-flex !important;
  }
  /deep/ .p-bar {
    margin: 10px 10px 0 0 !important;
  }
}

// /deep/ .el-table {
//   overflow: visible !important;
// }

// /deep/ .el-table {
//   display: flex;
//   flex-direction: column;
// }
/* order默认值为0,只需将表格主体order设为1即可移到最后,合计就上移到最上方了 */
/deep/ .el-table__body-wrapper {
  order: 0;
}
/deep/ .el-table__fixed-body-wrapper {
  // top: 96px !important;
  // 解决固定列后 ,表头和表体上下重叠 导致边框被遮挡
  margin-top: 1px !important;
}
/deep/ .el-table__fixed-footer-wrapper {
  z-index: 0;
}
/deep/ .el-table__body-wrapper {
  z-index: 2;
  border-right: 1px solid #ebeef5 !important;
}
/deep/ .el-table__fixed-right {
  border-left: 1px solid #ebeef5 !important;
  z-index: 2;
}

/deep/ .el-table__fixed {
  z-index: 99;
}

// /deep/ .el-table__fixed-body-wrapper {
//   .el-table__cell {
//     border-top: 1px solid #ebeef5;
//   }
// }

// 可编辑图标
/deep/ .table-header-edited {
  position: relative;
}

/deep/ .table-header-edited > i {
  position: absolute;
  right: 4px;
  top: 4px;
  font-size: 14px;
}

// 滚动条
// /deep/.el-table__body-wrapper::-webkit-scrollbar {
//   width: 10px;
//   height: 10px;
//   /deep/.el-scrollbar__wrap::-webkit-scrollbar {
//     width: 10px;
//     height: 10px;
//   }
// }
</style>

动态表头 Column.vue

<template>
  <!-- 勾选 -->
  <!-- :sortable="data.sortable" -->
  <el-table-column
    v-if="data && Object.keys(data).length"
    :prop="data.fieldName"
    :label="data.columnName"
    :min-width="data.width || 150"
    show-overflow-tooltip
    :index="+data.isEdit"
    :fixed="fixedNum > indexNum"
    :align="data.align || 'center'"
    :formatter="fieldFormatter"
  >
    <template slot-scope="scope">
      <!-- 输入框 -->
      <el-input
        v-if="
          isEdit &&
            scope.column['index'] == 1 &&
            inputType == 'input' &&
            columnId == scope.column.id &&
            rowId == scope.row[rowIdCode]
        "
        ref="tableInput"
        v-model="scope.row[data.fieldName]"
        @blur="removeClass"
        @change="$emit('handleEdit', scope)"
      ></el-input>
      <!-- 多行文本 -->
      <el-input
        v-if="
          isEdit &&
            scope.column['index'] == 1 &&
            inputType == 'textarea' &&
            columnId == scope.column.id &&
            rowId == scope.row[rowIdCode]
        "
        type="textarea"
        :rows="1"
        v-model="scope.row[data.fieldName]"
        @blur="removeClass"
        @change="$emit('handleEdit', scope)"
      >
      </el-input>
      <!-- 下拉框 -->
      <el-select
        v-if="
          isEdit &&
            scope.column['index'] == 1 &&
            inputType == 'select' &&
            columnId == scope.column.id &&
            rowId == scope.row[rowIdCode]
        "
        v-model="scope.row[data.fieldName]"
        @blur="removeClass"
        @change="$emit('handleEdit', scope)"
      >
        <el-option
          v-for="item in selectOption"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        >
        </el-option>
      </el-select>
      <!-- 数字 -->
      <el-input-number
        v-if="
          isEdit &&
            scope.column['index'] == 1 &&
            inputType == 'number' &&
            columnId == scope.column.id &&
            rowId == scope.row[rowIdCode]
        "
        controls-position="right"
        v-model="scope.row[data.fieldName]"
        @blur="removeClass"
        @change="$emit('handleEdit', scope)"
      ></el-input-number>
      <!-- 是否自定义html -->
      <template v-if="customHtml">
        <span>
          <slot name="customContent" v-bind="scope"> </slot>
        </span>
      </template>
      <template v-else>
        <!-- 动态字段  展示用-->
        <span v-if="fieldName">{{
          scope.row[`${data.fieldName}${data.fieldExtend}`]
        }}</span>
        <!-- 动态字段  展示用-->
        <span v-else>{{ scope.row[data.fieldName] }}</span>
      </template>
    </template>
    <Column
      v-for="(item, index) in data.childes"
      :key="index"
      :data="item"
      :columnId="columnId"
      :rowId="rowId"
      :isEdit="isEdit"
      :customHtml="customHtml"
      :rowIdCode="rowIdCode"
      :inputType="
        item.inputType && item.inputType != '' ? item.inputType : inputType
      "
      :fieldName="fieldName"
      :selectOption="selectOption"
      :indexNum="indexNum"
      :fixedNum="fixedNum"
      @removeClass="removeClass"
      @handleEdit="handleEdit"
    >
      <!-- 是否自定义html -->
      <template slot="customContent" slot-scope="scope" v-if="customHtml">
        <span>
          <slot name="customContent" v-bind="scope"> </slot>
        </span>
      </template>
    </Column>
  </el-table-column>
</template>
<script>
export default {
  name: "Column",
  components: {},
  props: {
    customHtml: {
      type: Boolean,
      default: () => {
        return false;
      }
    },
    rowIdCode: {
      type: String,
      default: () => {
        return "";
      }
    },
    columnId: {
      type: String,
      default: () => {
        return "";
      }
    },
    rowId: {
      type: String,
      default: () => {
        return "";
      }
    },
    isEdit: {
      type: Boolean,
      default: () => {
        return false;
      }
    },
    loading: {
      type: Boolean,
      default: () => {
        return false;
      }
    },
    fieldName: {
      type: Boolean,
      default: () => {
        return false;
      }
    },
    selectOption: {
      type: Array,
      default: () => {
        return [];
      }
    },
    inputType: {
      type: String,
      default: () => {
        return "input";
      }
    },
    indexNum: {
      type: Number,
      default: () => {
        return 0;
      }
    },
    fixedNum: {
      type: Number,
      default: () => {
        return 0;
      }
    },
    data: {
      type: Object,
      default: () => {
        return {};
      }
    }
  },
  data() {
    return {};
  },
  computed: {},
  watch: {},
  created() {}, // 生命周期 - 创建完成
  mounted() {}, // 生命周期 - 挂载完成
  beforeCreate() {}, // 生命周期 - 创建之前
  beforeMount() {}, // 生命周期 - 挂载之前
  beforeUpdate() {}, // 生命周期 - 更新之前
  updated() {}, // 生命周期 - 更新之后
  beforeDestroy() {}, // 生命周期 - 销毁之前
  destroyed() {}, // 生命周期 - 销毁完成
  activated() {}, // 如果页面有keep-alive缓存功能,这个函数会触发
  methods: {
    isShow(scope) {
      return true;
    },
    fieldFormatter(row, column) {
      return row[column.property] || "--";
    },
    removeClass() {
      let than = document;
      let COLUMNDOM = than.getElementsByClassName("current-cell").length;
      for (let i = 0; i < COLUMNDOM; i++) {
        than
          .getElementsByClassName("current-cell")
          [i].classList.remove("current-cell");
      }
    },
    handleEdit(scope) {
      this.$emit("handleEdit", scope);
    }
  }
};
</script>
<style scoped lang="scss"></style>

防抖JS页面 debounce.js

export function debounce(fn, delay) {
  // 如果没传延迟时间,就默认1500毫秒
  var delay = delay || 1500;
  // 记录上一次触发的定时器
  var timer = null;
  return function() {
    const th = this;
    var args = arguments;
    // 如果定时器还没结束,再次触发了这个函数
    // 就清除这个定时器,重新计时
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(function() {
      timer = null;
      // 定时器结束,正常执行函数
      fn.apply(th, args);
    }, delay);
  };
}

补充样式

// 头部为白色 不加粗的表风格
.el-table-header--white {
  th {
    border-right-width: 0;
    background-color: white !important;
    .cell {
      font-size: 13px;
      font-weight: normal;
      font-weight: bold;
    }
    &:hover {
      border-right-width: 1px;
    }
  }

  .el-table-column--selection .cell {
    padding-right: 14px !important;
  }
}

拖拽JS部分 sortable.js

import Sortable from "sortablejs";
export function rowDrop(than) {
  // 存在固定和不固定的情况
  const fixeDom =
    than.$el.getElementsByClassName("el-table__fixed-body-wrapper")[0] || false; // 固定
  const dom =
    than.$el.getElementsByClassName("el-table__body-wrapper")[0] || false; // 未固定
  // 存在固定行
  if (than.fixedNumberColumns > 0) {
    const el = fixeDom.querySelector("tbody");
    if (el) {
      Sortable.create(el, {
        sort: true,
        animation: 150,
        ghostClass: "sortable-ghost",
        // handle: ".table-row-drag",
        // filter: ".cell", // 过滤器,不需要进行拖动的元素
        // preventOnFilter: true, //  在触发过滤器`filter`的时候调用`event.preventDefault()`
        onEnd: evt => {
          // 下面将拖拽后的顺序进行修改
          console.log(evt, "evt");
          let index1 = evt.newIndex; // 新的下标
          let index2 = evt.oldIndex; // 原来的下标
          const obj = swapArrayObjects(than.tableData, index1, index2);
          than.tableData = [];
          than.$nextTick(() => {
            than.tableData = obj.data;
          });
          than.$emit("sortableEnd", obj);
        }
      });
    }
  } else {
    if (dom) {
      const el = dom.querySelector("tbody");
      if (el) {
        Sortable.create(el, {
          sort: true,
          animation: 150,
          ghostClass: "sortable-ghost",
          // handle: ".table-row-drag",
          // filter: ".cell", // 过滤器,不需要进行拖动的元素
          // preventOnFilter: true, //  在触发过滤器`filter`的时候调用`event.preventDefault()`
          onEnd: evt => {
            // 下面将拖拽后的顺序进行修改
            console.log(evt, "evt");
            let index1 = evt.newIndex; // 新的下标
            let index2 = evt.oldIndex; // 原来的下标
            swapArrayObjects(than.tableData, index1, index2);
            const obj = swapArrayObjects(than.tableData, index1, index2);
            than.tableData = [];
            than.$nextTick(() => {
              than.tableData = obj.data;
            });
            than.$emit("sortableEnd", obj);
          }
        });
      }
    }
  }
}

function swapArrayObjects(arr, index1, index2) {
  // 创建一个新的数组对象,将原数组对象的元素添加到新数组中
  let newArr = [...arr];
  // 找到要交换的两个对象在原数组中的索引
  let obj1 = newArr[index1]; // 新的下标
  let obj2 = newArr[index2]; // 原来的下标
  // 删除原来的位置的本体
  newArr.splice(index2, 1);
  // 在新下标插入
  newArr.splice(index1, 0, obj2);
  return {
    row: obj2,
    data: newArr
  };
}

使用方法

HTML

    <ElTable
      :TabularData="TabularData"
      @cellClick="cellClick"
      @sortableEnd="sortableEnd"
      ref="ElTable"
    >
    <!-- 表格头部左插槽 -->
    <!-- <template slot="ft"></template> -->
    <!-- 表格头部右插槽 -->
    <!-- <template slot="ht"></template> -->
    <!-- 操作列插槽 -->
    <!-- <template slot="ActionButton" slot-scope="scope"></template> -->
    <!-- 自定义追加列插槽 -->
    <!-- 头部更多操作 -->
    <!-- <template slot="columnHeader" slot-scope="scope"></template> -->
    <!-- 内容 -->
    <!-- <template slot="columnBody" slot-scope="scope"></template> -->
    <!-- 自定义HTML -->
         <!-- <template slot="Content" slot-scope="scope"></template> -->
    </ElTable>

JS

    // 开启拖拽需要安装
    // npm install sortablejs --save
    import ElTable from "@/components/ElTableColumn/index";
    export default {
        components: {
            ElTable
        },
        data(){
            return{
                TabularData:{
                    customHtml: false, // 是否开启自定义html
                    customColumnShow:false,// 自定义追加列
                    isDrag:false,// 是否开启行拖拽
                    title: "", //标题
                    height:600,//高度
                    FirstCall: true, // 是否首次调用
                    // height: document.documentElement.clientHeight - 230, //高度
                    fieldName: false, //是否动态字段 fieldName + fieldExtend
                    isShowDynamicHeaders: false, //是否开启动态表头
                    headerHata: [], //固定表头数据
                    HeaderDataApi: null, // 表头方法
                    HeaderQuery: {}, // 表头参数
                    DataApi: null, // 数据方法
                    DataQuery: {}, // 数据参数
                    EditApi: null, // 编辑方法
                    EditQuery: {}, // 编辑参数
                    rowIdCode: "id", // 数据唯一标识字段 默认取id ,可编辑时需要配置对应字段
                    DeleteApi: null, // 删除方法
                    DeleteQuery: {}, // 删除参数
                    selection: false, // 是否开启勾选
                    titleShow: false, // 是否开启标题
                    updateNow: false, // 编辑时是否马上加载loading 需要提供前置编辑方法
                    showSummary: false, //是否开启合计
                    sumText: "合计", //合计文本
                    getSummaries: null, // 自定义合计方法
                    isEditFun: null, // 自定义编辑方法
                    isEdit: false, // 是否开启编辑
                    inputType: "input", // 可编辑组件类型input、textarea、select、number
                    selectOption: [], //组件类型为下拉框的时候需要传入数据
                    isAction: false, // 是否展示操作列
                    actionData: {
                        // 操作列配置
                        title: "操作",
                        width: 150,
                        fixed: "right"
                    },
                    fixedNumberColumns: 0, // 固定列数
                    frequencyTime: 500, // 刷新频率 防抖
                    paginationShow: true // 是否展示分页
                };
            }
        },
        methods:{
            // 行点击
            cellClick(row, column, cell, event) {
              this.TabularData.isEditFun = this.isEditFun;
            },
            // 自定义是否可编辑 方法
            isEditFun(row, column, cell, event) {
              if (column.index && column.index == "1") return true;
                return false;
            },
            // 行拖拽
            sortableEnd(data){
              console.log(data);
            }
        }
    }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值