封装一个 el-table 组件代码

102 阅读3分钟
  1. index.vue
  2. TableTemplate.vue
  3. xTableRender.vue

-- 1.index--

//index.vue

<template>
  <div
    class="x-table"
    :style="minTableHeight ? `height: calc(100% - ${minTableHeight}px)` : null"
  >
    <el-table
      ref="xTable"
      v-bind="$attrs"
      v-on="$listeners"
      :data="tableData"
      :max-height="tableMaxHeight ? tableMaxHeight : tableHeight"
      :min-height="`200px`"
      @selection-change="selectionChange"
      :style="{ '--line': cellLineNumber }"
    >
      <!-- 多选列 -->
      <template v-if="tableConfig.selection" align="left">
        <el-table-column
          type="selection"
          :width="tableConfig.selectionWidth || 40"
          :fixed="tableConfig.selectionFixed || null"
          :reserve-selection="tableConfig.reserveSelection || false"
          :selectable="
            tableConfig.selectable ||
            function () {
              return true;
            }
          "
        ></el-table-column>
      </template>
      <!-- 序号列 -->
      <template v-if="tableConfig.index">
        <el-table-column
          type="index"
          :width="tableConfig.indexWidth || 50"
          :label="tableConfig.indexLabel || '序号'"
          :fixed="tableConfig.indexFixed || null"
          :sortable="tableConfig.sortable ? tableConfig.sortable : false"
        ></el-table-column>
      </template>
      <template v-for="item in tableColumnConfig">
        <!-- 动态插槽 -->
        <el-table-column
          v-if="item.scope && !item.noShow"
          :key="item.scope"
          :label="item.label || ''"
          :width="item.width || ''"
          :min-width="item.minWidth || ''"
          :align="item.align || 'left'"
          :header-align="item.headerAlign || 'left'"
        >
          <template slot-scope="scope">
            <slot
              :name="item.scope"
              :data="{ scope: scope.row, index: scope.$index }"
            />
          </template>
        </el-table-column>

        <!-- 循环列 -->
        <TableTemplate
          v-else-if="!item.scope && !item.noShow"
          :key="item.label"
          :table-colum-config="item"
          v-bind="$attrs"
          v-on="$listeners"
        />
      </template>
      <!-- 提供一个尾插槽插槽  -->
      <slot />
    </el-table>
  </div>
</template>

<script>
import TableTemplate from "./TableTemplate.vue";
export default {
  name: "XTable",
  props: {
    tableData: {
      type: Array,
      default: () => [],
    },
    tableConfig: {
      type: Object,
      default: () => {},
    },
    tableColumnConfig: {
      type: Array,
      require: true,
      default: () => [],
    },
    minusPart: {
      type: Number,
      default: () => 0,
    },
    minTableHeight: {
      type: Number,
      default: () => 0,
    },
    cellLineNumber: {
      type: Number,
      default: () => 2,
    },
    tableMaxHeight: {
      type: Number,
      default: () => 0,
    },
  },
  data() {
    return {
      tableHeight: 0,
    };
  },
  mounted() {
    this.getTableHeight();
    window.addEventListener("resize", this.getTableHeight);
  },
  beforeMount() {
    window.removeEventListener("resize", this.getTableHeight);
  },
  methods: {
    getTableHeight() {
      this.$nextTick(() => {
        this.tableHeight = window.innerHeight - this.minusPart;
      });
    },

    selectionChange(val) {
      this.$emit("selectionChange", val);
    },
  },
  components: { TableTemplate },
};
</script>

<style lang="scss">
.x-table {
  .cell {
    padding: 0 10px;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box; //作为弹性伸缩盒子模型显示。
    -webkit-box-orient: vertical; //设置伸缩盒子的子元素排列方式--从上到下垂直排列
    -webkit-line-clamp: var(--line); //显示的行
  }
  th {
    background: rgb(240, 242, 245);
  }
  .x-table-template {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: flex-start;
    .x-table-input {
      input {
        text-align: center;
      }
    }
  }
  .el-link {
    font-size: 13px;
  }
  .is-disabled {
    color: #c0c4cc !important;
  }
}
</style>

--2.TableTemplate.vue--

// 为了实现多级表头而做的子组件的再封装
<template>
  <el-table-column
    :label="tableColumConfig.label || ''"
    :width="tableColumConfig.width || ''"
    :min-width="tableColumConfig.minWidth || ''"
    :prop="tableColumConfig.prop"
    :align="tableColumConfig.align || 'left'"
    :header-align="tableColumConfig.headerAlign || 'left'"
    :fixed="tableColumConfig.fixed || null"
    :sortable="tableColumConfig.sortable || false"
    :render-header="
      tableColumConfig.renderHeader ? tableColumConfig.renderHeader : null
    "
  >
    <template slot-scope="scope">
      <!-- 循环可能存在的template数组 -->
      <div
        class="x-table-template"
        v-if="
          tableColumConfig.template &&
          Array.isArray(tableColumConfig.template) &&
          !tableColumConfig.children
        "
        :style="tableColumConfig.alignStyle || ''"
      >
        <div
          v-for="(i, _index) in renderItem(
            tableColumConfig.template,
            scope.row
          )"
          :key="_index"
        >
          <!-- 按钮项 -->
          <template v-if="i.type === 'button'">
            <el-button
              :type="i.assemblyType || 'primary'"
              :disabled="
                i.disabled
                  ? i.disabled({
                      row: scope.row,
                      route: $route,
                      exData: exData,
                    })
                  : false
              "
              @click="
                emitFunc(i.method || 'defaultMethod', {
                  row: scope.row,
                  index: scope.$index,
                  additionalParams: i.addParams || '',
                })
              "
            >
              {{ i.text }}
            </el-button>
          </template>
          <!-- icon项 -->
          <template v-else-if="i.type === 'icon'">
            <el-link
              :type="i.assemblyType || 'primary'"
              :disabled="
                i.disabled
                  ? i.disabled({
                      row: scope.row,
                      route: $route,
                      exData: exData,
                    })
                  : false
              "
              @click="
                emitFunc(i.method || 'defaultMethod', {
                  row: scope.row,
                  index: scope.$index,
                  additionalParams: i.addParams || '',
                })
              "
            >
              <!-- icon的样式 具体看 https://element.eleme.cn/#/zh-CN/component/icon -->
              <i :class="i.iconClass"></i>
            </el-link>
          </template>
          <!-- input输入框 -->
          <template v-else-if="i.type === 'input'">
            <el-input
              class="x-table-input"
              v-model="scope.row[`${tableColumConfig.prop}`]"
              :type="i.assemblyType || 'text'"
              :placeholder="i.placeholder || '请输入'"
              :disabled="
                i.disabled
                  ? i.disabled({
                      row: scope.row,
                      route: $route,
                      exData: exData,
                    })
                  : false
              "
              @blur="
                emitFunc(i.method || 'defaultMethod', {
                  row: scope.row,
                  index: scope.$index,
                  additionalParams: i.addParams || '',
                })
              "
            ></el-input>
          </template>
          <!-- 文字方面的 -->
          <template v-else-if="i.type === 'link'">
            <el-link
              :type="i.assemblyType || 'primary'"
              :disabled="
                i.disabled
                  ? i.disabled({
                      row: scope.row,
                      route: $route,
                      exData: exData,
                    })
                  : false
              "
              @click="
                emitFunc(i.method || 'defaultMethod', {
                  row: scope.row,
                  index: scope.$index,
                  additionalParams: i.addParams || '',
                })
              "
            >
              {{ i.text }}
            </el-link>
          </template>
          <!-- 非超链接 -->
          <template v-else-if="i.type === 'span'">
            <span
              @click="
                emitFunc(i.method || 'defaultMethod', {
                  row: scope.row,
                  index: scope.$index,
                  additionalParams: i.addParams || '',
                })
              "
              :style="i.spanStyle ? i.spanStyle(scope.row) : ''"
            >
              {{ i.span(scope.row) }}
            </span>
          </template>
          <template v-else-if="i.type === 'render'">
            <XTableRender
              v-bind="$attrs"
              v-on="$listeners"
              :sc="scope"
              :row="scope.row"
              :render="i.render(scope.row, that)"
              :rederStyle="i.classStyle ? i.classStyle(scope.row) : ''"
              @sss="sss"
            ></XTableRender>
          </template>
          <template v-else-if="i.type === 'renderLink'">
            <el-link
              v-for="(_x, _xindex) in i.renderLink(scope.row)"
              :key="_xindex"
              @click="
                emitFunc(i.method || 'defaultMethod', {
                  row: scope.row,
                  index: scope.$index,
                  additionalParams: i.addParams || '',
                  linkIndex: _xindex,
                })
              "
              :disabled="
                i.disabled
                  ? i.disabled({
                      row: scope.row,
                      route: $route,
                      exData: exData,
                    })
                  : false
              "
              :type="i.assemblyType ? i.assemblyType() : 'primary'"
              :style="i.linkStyle"
            >
              {{ _x }}
            </el-link>
          </template>
          <!-- 自定义值 -->
          <el-link
            @click="
              emitFunc(i.method || 'defaultMethod', {
                row: scope.row,
                index: scope.$index,
                additionalParams: i.addParams || '',
              })
            "
            v-if="i.supplement"
            :type="i.suppleType ? i.suppleType(scope.row) : ' '"
            :disabled="
              i.disabled
                ? i.disabled({
                    row: scope.row,
                    route: $route,
                    exData: exData,
                  })
                : false
            "
            >{{ i.supplement(scope.row) }}
          </el-link>
        </div>
      </div>

      <template v-else>
        {{ scope.row[`${tableColumConfig.prop}`] }}
      </template>
    </template>
    <!-- 可能存在的子集 -->
    <template v-if="tableColumConfig.children">
      <xtable-template
        v-for="(i, __index) in tableColumConfig.children"
        :key="__index"
        :tableColumConfig="i"
        v-bind="$attrs"
        v-on="$listeners"
      ></xtable-template>
    </template>
  </el-table-column>
</template>

<script>
import XTableRender from "./XTableRender.vue";
export default {
  name: "xtable-template", //必须存在
  props: {
    tableColumConfig: {
      type: Object,
      default: () => {},
    },
  },
  computed: {
    that() {
      return this;
    },
    exData() {
      return window.localStorage.getItem("exData") || "";
    },
    // 是否渲染模板内的template
    renderItem() {
      return function (templateList, row) {
        if (!Array.isArray(templateList)) {
          return [];
        }
        return templateList.filter((i) => {
          return i.showItem
            ? i.showItem({
                row: row,
                route: this.$route,
                exData: this.exData,
              })
            : true;
        });
      };
    },
  },
  methods: {
    emitFunc(method, row) {
      this.$emit(method, row);
    },
  },
  components: { XTableReder },
};
</script>

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

--3.XTableRender--

<script>
export default {
  functional: true,
  props: {
    row: {
      type: Object,
      required: true,
    },
    render: {
      type: Function,
      required: true,
    },
    sc: {
      type: Object,
      required: true,
    },
    rederStyle: {
      type: String,
      require: true,
    },
  },
  render: (h, ctx) => {
    const arr = [];
    const params = {
      row: ctx.props.row,
      index: ctx.props.sc.$index,
    };
    const VNode = ctx.props.render(h, params);
    arr.push(VNode);
    return h("div", { class: ctx.props.rederStyle }, arr);
  },
};
</script>

使用示例:

const XTableColumnConfig = [
  {
    props: "zw", // el-table-column 的 props
    width: "200", // 该列的宽度
    minWidth: "120", // 该列的最小宽度 按理说 width 和 min-width 是互斥的
    label: "列名", // 列名
    align: "left", // [left,right,center]  列内文本对齐方式 如果在XTableConfig 设置了全局的 config ,该属性不会生效
    headerAlign: "left", //[left,right,center] 表头的对齐方式 如果在XTableConfig 设置了全局的 config ,该属性不会生效
    fixed: true, //[true,false] 是否开启fixed 就是是否浮动,与 el-table-column 属性一样
    sortable: false, //[true,false] 是否启用排序功能,与 el-table-column 属性一样
    renderHeader: null, //一个render 函数,是否使用render 函数来渲染表头
    noShow: false, //[true,false] 该列是否显示
    alignStyle:'',//行内文本或者组件的排序方式 默认是 justify-content: flex-start;  gap:10px ; flex-wrap: wrap;
    template:[  //这里的 template 是一个数组,其中的内容是可循环的
    // 几乎是定死没有变化的情况下的元素且它的列表深度只有一层
        {
            type:'button', //[button、icon、input、link]  该组件的类型
            assemblyType:'default', //[primary,warning,error,default] 该组件的状态
            disabled:false, //[false,true] 是否禁用
            method:'event' //String 事件名可以自己定义书写 组件点击或者失去焦点的时候触发的事件,可以在 <XTable> 上获得响应
            text:'hh',//String 一般情况下是内填充的文本
        },
    // 以下介绍不是定死,会存在变化的元素
    {
        type:"span", //文本元素 可根据某一个值变化
        spanStyle:(row)=>'',//该文本的 CSS 样式, row 是行对象
        methods:'spanClick',// 事件名可自己定义书写 此事件是文本被点击后触发的事件
        span:(row)=>'我是文本',// row 可获得行对象,通过返回值可写入该行内,动态改变或者获取更深层次的
    },
    {
        supplement:(row)=>'我是自定义的占位链接',// 该组件没有 type 可自定义可变化的 link
        disabled:(row)=>false,//是否禁用
        method:'supplementClick',// 链接点击后触发的事件
        suppleType:(row)=>'primary' //该链接的状态
    },
    ]
  },
//   该对象介绍自定义插槽
{
    label:'我是自定义插槽',//插槽的列名
    slot:'mySlot',//插槽的自定义 name 名
    noShow:false,//自定义插槽是否可见
}
];

// 配置对象 如无特殊需求,该对象可以是一个空对象 如不传给 XTable 会报错
const tableConfig = {
    selection:true,//是否开始多选列
    reserveSelection:true,//开启的多选列是否是采用唯一值(换页也唯一)
    selectable:selectable,//该行是否可选,返回false 不可选
    selectionFixed:true,//序号列是否粘性居左
    selectionWidth:40,//多选列的宽度
    index:true,//是否开启序号列
    indexLabel:'序号',//序号列的列名
    indexWidth:40,//序号列的宽度
    sortable:true,//序号列是否开启排序
}

//!请注意 XTable 的必传参数 tableData  tableConfig  tableColumnConfig

//如何使用 XTable 以下是一个示例
<XTable
          class="mb-2"
          border
          :key="tableKey"
          :table-data="tableData"
          :tableConfig="tableConfig"
          :table-column-config="XTableColumnConfig"
          @toDetailed="toDetailed"
        >
          <template slot="mySlot" slot-scope="{ data: data }">
            <div style="width: 100%; height: 100%" class="flex flex-center">
              <el-dropdown
                class="avatar-container"
                trigger="click"
                placement="bottom"
                @command="handleAction"
              >
                <el-link class="action-icon" icon="el-icon-setting" />
                <el-dropdown-menu>
                  <el-dropdown-item
                    icon="el-icon-turn-off"
                    :command="['bind', data.scope]"
                    >绑定</el-dropdown-item
                  >
                  <el-dropdown-item
                    icon="el-icon-edit"
                    :command="['edit', data.scope]"
                    >修改</el-dropdown-item
                  >
                  <el-dropdown-item
                    icon="el-icon-delete"
                    :command="['deleteOne', data.scope]"
                    >删除</el-dropdown-item
                  >
                </el-dropdown-menu>
              </el-dropdown>
            </div>
          </template>
        </XTable>


data(){
    return {
        tableKey: 2233,
        tableData:[]
    }
},
methods:{
     selectable(row) {
      const find = this.tableData.find((e) => e.dtcCode === row.dtcCode);
      if (find) {
        return false;
      }
      return true;
    },
}