Vue2+ElementUI表格组件的封装

Vue2+ElementUI列表组件的封装

前言

本文要实现的表格组件功能包括是否有序号、是否可以勾选、是否需要多选、日期格式转换、字典值进行转换、金额进行千分符转换、是否需要小计、合计、是否需要设置列功能、是否不许拖动列宽、操作栏宽度、操作栏文本这是都可以通过父组件中进行设置,来满足表格的需求。

引言

在日常开发中,我们经常会遇到需要展示列表数据的场景。ElementUI 提供的 el-table 组件是一个功能强大的表格组件,可以满足大部分的需求。但是,在实际应用中,我们往往需要根据业务需求对 el-table 组件进行二次封装,以提高开发效率和代码的复用性。

本文将介绍 Vue2+ElementUI 列表组件的封装方法,包括:

  • 封装思路
  • 常见功能的实现
  • 代码示例
  • 注意点

封装思路 封装 el-table 组件的思路是将其作为一个独立的组件,对外暴露必要的属性和方法,并通过插槽机制来定制表格的内容。

具体来说,我们可以将 el-table 组件的封装分为以下几个步骤:

  1. 定义组件的 props,用于接收外部传入的数据和配置。
  2. 在组件内部,使用 el-table 组件来渲染表格。
  3. 通过插槽机制来定制表格的内容,例如表头、表尾、操作列等。
  4. 暴露一些方法,用于控制表格的操作,例如刷新、排序、筛选等。

创建组件模板并测试使用

创建组件,名为:V2yuTableComp,这是子组件

js 复制代码
<template>
  <div class="customer-table-container">
    <el-table
      ref="table"
      v-loading="loading"
      :row-key="rowKey"
      :data="list"
      stripe
      border
      :class="['autoWidth-table', !ifMultiple ? 'single-select-table' : '']"
      :span-method="spanMethod"
      @header-dragend="changeColumnWdith"
      @selection-change="handleSelectionChange"
    >
      <el-table-column
        v-if="ifCheck"
        type="selection"
        width="55"
        align="center"
      />
      <el-table-column
        v-if="ifIndex"
        type="index"
        label="序号"
        width="80"
        fixed="left"
        class-name="customer-table-index"
      >
        <template slot-scope="scope">
          <span>
            {{ setFirstColumn(scope.$index) }}
          </span>
        </template>
      </el-table-column>
      <template v-for="column in visibleColumns">
        <CustomerTableColumn
          :key="column.prop"
          :column="column"
          :list="list"
          :copyDict="copyDict"
          :ifDragCol="ifDragCol"
        >
          <template v-slot:[column.prop]="slotProps">
            <slot
              :name="column.prop"
              :prop="column.prop"
              :row="slotProps.scope.row"
              :index="slotProps.scope.$index"
              :originScope="slotProps.scope"
            ></slot>
          </template>
        </CustomerTableColumn>
      </template>
      <el-table-column
        v-if="operatorWidth > 0"
        label="操作"
        align="center"
        fixed="right"
        class-name="small-padding fixed-width"
        :width="operatorWidth"
      >
        <template slot="header">
          <span>{{ operatorLabel }}</span>
          <ColumnsSettings
            v-if="ifSetting"
            ref="ColumnsSettings"
            :columns="columns"
          ></ColumnsSettings>
        </template>
        <template slot-scope="scope">
          <slot
            v-if="scope.$index < page.pageSize"
            :row="scope.row"
            :index="scope.$index"
            :originScope="scope"
          ></slot>
        </template>
      </el-table-column>
    </el-table>
    <el-button
      v-if="!ifNotExport"
      type="warning"
      plain
      icon="el-icon-download"
      size="mini"
      class="export-button"
      @click="handleExport"
      >导出</el-button
    >
    <el-dialog
      :visible.sync="exportDialog"
      class="export-loading-dialog"
      width="200px"
    >
      <div class="export-loading">
        <div v-loading="exportLoading"></div>
        <div class="blue">文件下载中...</div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { add, bignumber } from "mathjs";
import { exportBlob } from "@/utils/common";
export default {
  data() {
    return {
      ifGetSummary: false, // 是否已获得过合计数据
      exportDialog: false,
      exportLoading: false,
    };
  },
  props: {
    // 字典,父组件固定传dict
    copyDict: {
      type: Object,
    },
    rowKey: {
      type: String,
      default: "id",
    },
    // 分页,用于序号相关
    page: {
      type: Object,
      default() {
        return {
          pageSize: 50,
          pageNum: 1,
        };
      },
    },
    exportPage: {
      type: Object,
      default() {
        return {
          pageSize: 100000,
          pageNum: 1,
        };
      },
    },
    // 其它查询条件,主要用于导出
    query: {
      type: Object,
      default: () => ({}),
    },
    // 是否至少含有一项查询条件才可以导出
    queryNotnull: {
      type: Boolean,
      default: false,
    },
    loading: {
      type: Boolean,
      default: false,
    },
    list: {
      type: Array,
      required: true,
    },
    columns: {
      type: Array,
      required: true,
    },
    // 操作栏宽度
    operatorWidth: {
      type: Number,
      required: true,
    },
    // 操作栏文本
    operatorLabel: {
      type: String,
      default: "操作",
    },
    // 是否不许拖动列宽
    ifDragCol: {
      type: Boolean,
      default: true,
    },
    // 是否需要选择
    ifCheck: {
      type: Boolean,
      default: false,
    },
    // 是否需要序号
    ifIndex: {
      type: Boolean,
      default: true,
    },
    // 是否需要多选
    ifMultiple: {
      type: Boolean,
      default: false,
    },
    // 是否需要设置列功能
    ifSetting: {
      type: Boolean,
      default: false,
    },
    menuId: {
      type: String,
    },
    // 是否需要小计功能,小计为前端根据当前表计算
    ifSubtotal: {
      type: Boolean,
      default: false,
    },
    subtotalLabel: {
      type: String,
      default: "小计",
    },
    // 是否需要合计功能,合计为后台根据库表计算
    ifSummary: {
      type: Boolean,
      default: false,
    },
    summaryLabel: {
      type: String,
      default: "合计",
    },
    // 是否不需要导出功能
    ifNotExport: {
      type: Boolean,
      default: false,
    },
    // 合计行或合计列方法
    spanMethod: {
      type: Function,
    },
    // 导出的回调接口
    exporter: {
      type: Function,
    },
  },
  computed: {
    visibleColumns() {
      return this.ifSetting
        ? this.columns.filter((column) => column.visible)
        : this.columns;
    },
  },
  watch: {
    loading(newVal, oldVal) {
      // loading由true变为false表示请求完毕,列表加载完成
      const subtotalObj = {};
      if (this.ifSubtotal && !newVal && oldVal) {
        this.columns.forEach((col) => {
          // 需要小计的列
          if (col.ifSubtotal) {
            const sum = this.list.reduce((accumulator, row) => {
              // return accumulator + row[col.prop];
              return add(
                bignumber(accumulator),
                bignumber(!row.ifExcept ? row[col.prop] : 0)
              );
            }, 0);
            subtotalObj[col.prop] = sum.toFixed(2);
          } else {
            subtotalObj[col.prop] = null;
          }
        });
        this.list.push(subtotalObj);
      }
      if (this.ifSummary && !newVal && oldVal) {
        let mockObj = {};
        if (!this.ifGetSummary) {
          // 模拟请求合计
          setTimeout(() => {
            mockObj = subtotalObj;
            this.list.push(mockObj);
            this.ifGetSummary = true;
            console.log(mockObj);
            console.log("模拟请求合计");
          }, 500);
        } else {
          mockObj = subtotalObj;
          this.list.push(mockObj);
        }
      }
    },
  },
  methods: {
    changeColumnWdith(newWidth, oldWidth, currentCol) {
      for (let i = 0; i < this.columns.length; i++) {
        if (this.columns[i].prop === currentCol.property) {
          this.$set(this.columns[i], "width", newWidth);
          break;
        }
      }
      const widthColumns = this.columns.map(({ prop, width }) => {
        return {
          prop,
          width,
        };
      });
      if (this.ifDragCol) {
        localStorage.setItem(
          this.menuId || this.$route.fullPath,
          JSON.stringify(widthColumns)
        );
      }
    },
    exportPageWithE() {
      return {
        ...this.exportPage,
        E: 1,
      };
    },
    handleExport() {
      if (this.queryNotnull) {
        let notEmpty = false;
        const keys = Object.keys(this.query);
        for (let i = 0; i < keys.length; i++) {
          const value = this.query[keys[i]];
          if (!Array.isArray(value) && !this.getIfEmpty(value)) {
            notEmpty = true;
            break;
          }
          if (Array.isArray(value) && value.length > 0) {
            notEmpty = true;
            break;
          }
        }
        if (!notEmpty) {
          this.$message.warning("至少选择一项查询条件");
          return;
        }
      }
      const exportParams = Object.assign(
        {
          expColumns: this.columns,
        },
        this.query
      );
      this.exportLoading = true;
      this.exportDialog = true;
      this.exporter(this.exportPageWithE(), exportParams)
        .then(async (res) => {
          const message = await exportBlob(res);
          this.$message.success(message);
          this.$emit("exportSuccess");
        })
        .catch((error) => {
          console.log(error);
          this.$message.error("导出失败");
          this.$emit("exportFail", error);
        })
        .finally(() => {
          this.exportLoading = false;
          this.exportDialog = false;
        });
    },
    handleSelectionChange(rows) {
      if (!this.ifMultiple && rows.length > 1) {
        this.$refs.table.toggleRowSelection(rows[rows.length - 2], false);
        return false;
      }
      if (!this.ifMultiple) {
        this.$emit("selection-change", rows[rows.length - 1]);
      } else {
        this.$emit("selection-change", rows);
      }
    },
    setFirstColumn(index) {
      if (this.ifSubtotal && this.ifSummary && index == this.list.length - 2) {
        return this.subtotalLabel;
      } else if (
        this.ifSubtotal &&
        !this.ifSummary &&
        index == this.list.length - 1
      ) {
        return this.subtotalLabel;
      } else if (this.ifSummary && index == this.list.length - 1) {
        return this.summaryLabel;
      } else {
        return (this.page.pageNum - 1) * this.page.pageSize + index + 1;
      }
    },
  },
  created() {
    this.ifSetting && this.getColumnSettings();
    if (this.ifDragCol) {
      let widthColumns = localStorage.getItem(
        this.menuId || this.$route.fullPath
      );
      if (widthColumns) {
        widthColumns = JSON.parse(widthColumns);
      } else {
        widthColumns = [];
      }
      widthColumns.forEach(({ prop, width }) => {
        this.columns.forEach((col) => {
          if (width && col.prop === prop && col.width != width) {
            this.$set(col, "width", width);
          }
        });
      });
    }
  },
};
</script>

<style scope lang="scss">
</style>

创建测试类并使用上面的组件,方便测试。注意:导入组件时注意路径。这是父组件。

js 复制代码
<template>
  <div class="container" style="min-height: 100%; padding-bottom: 100px;">
    <V2yuTableComp</V2yuTableComp>
  </div>
</template>

<script>
import V2yuTableComp from "../../components/table/V2yuTableComp";

export default {
  props: [],
  components: {
    V2yuTableComp
  },
  data() {
    return {}
  },
  watch: {},
  computed: {},
  beforeCreate() {
  },
  created() {
  },
  beforeMount() {
  },
  mounted() {
  },
  beforeUpdate() {
  },
  updated() {
  },
  destroyed() {
  },
  methods: {},
}
</script>

<style scoped>

.container {
}
</style>

父、子组件传值

父组件编写表格对象并传递给子组件。

js 复制代码
<V2yuTableComp :data="tableObj"></V2yuTableComp>


data() {
    return {
      // 这个对象可以从后端获取,下面是一些示例数据
      list:[],
        // 数据列 - 即el-table-column组件中的属性
        columns: [
          {prop: 'date', label: '日期', visible: true, ifDate:true},
          {prop: 'name', label: '名字',visible: true, },
          {prop: 'address', label: '地址',visible: true, },
          {prop: 'money', label: '车费',visible: true, ifMoney:true},
        ]
    }
  }

效果图如下:

总结

在开发过程中我们最常用的就是表单和表格,所以对表格组件的封装可以提高代码复用性和开发效率。下面是关于封装 Vue 2 和 Element UI 列表和表格组件的一些总结和最佳实践:

  1. 封装表格组件:

    • 创建一个可复用的表格组件,接受数据和列定义作为 props。
    • 使用 Element UI 的 <el-table><el-table-column> 组件来渲染表格。
    • 根据列定义动态生成表格的列。
    • 提供插槽以支持自定义表格内容,例如自定义表头或单元格内容。
    • 添加排序、筛选、分页等功能,可以使用 Element UI 提供的相应组件或自定义实现。

通过上述代码,在使用表格时大大提高了开发效率,在父组件中使用时可以通过配置来满足该功能的需求。

相关推荐
程序员白彬几秒前
为什么 npm run serve 正常,npm run build 就报错:digital envelope routines::unsupported
前端·npm·node.js
weixin_8368695203 分钟前
使用Random.next生成随机数
java·前端·python
小和尚敲木头16 分钟前
css 滚动词云
前端·css
xuchengxi-java33 分钟前
Vue3使用Vue Router4實現頁面切換
前端·javascript·vue.js
前端组件开发38 分钟前
基于Vue.js的电商前端模板:Vue-Dashboard-Template的设计与实现
前端·javascript·vue.js·小程序·前端框架·uni-app·html5
五点六六六1 小时前
深入解析Cookie机制:从操作实践到安全属性详解
前端·javascript·面试
知道了啊2 小时前
webpack源码深入--- webpack的编译主流程
前端·webpack
学习做游戏中2 小时前
layui在表格中嵌入上传按钮,并修改上传进度条
前端·javascript·layui
ZhouWei的博客2 小时前
Flutter Android打包aab包
android·前端
Chnsome2 小时前
antv/x6@2.x + vue3 开发环境配置
前端