Element + Vue 封装的table组件,可动态生成列,可通过npm进行安装

 已更新至npm,实现多级表头、筛选、排序、展开行,详情请看bo-tablehttps://www.npmjs.com/package/bo-table

 实现效果:

 只需要简单的引用,给组件传递列表数据、表头数据就好了(我这里列写的比较多,所以看起来代码行数多 QaQ):


 可通过传入数据来动态生成列,使用方便

 目前完成的功能:

  • 序号、选中行
  • 行(单击、双击)事件
  • 合计(可默认计算当前页合计【相加】,也可以通过父组件传入对应合计值进来)
  • 固定列、固定头
  • 行展示数据使用slot插入,prop为slot-name
  • 列表排序
  • 多级表头
  • 展开行
  • 筛选

目前缺失、常见的功能:

  • 表格合并

全局引用表格组件以及表格列,因为存在多级表头,设计到递归,所以子组件也要全局注册,方便子组件引用自身,递归表格列使用了 vue-fragment 无渲染组件替代<template></template>不能嵌套的问题。

// vue无渲染标签
import Fragment from "vue-fragment";
Vue.use(Fragment.Plugin);

// 自定义表格组件
import bTable from "@/components/bTable";
Vue.component("bTable", bTable);

// 自定义表格组件--表格列
import ChildColumn from "@/components/bTable/ChildColumn";
Vue.component("ChildColumn", ChildColumn);

// 分页组件
import Pagination from "@/components/Pagination";
Vue.component("pagination", Pagination);

主体index.vue代码:

<template>
  <div class="table">
    <div class="b-table-handle" v-if="showHandle">
      <div class="right">
        <slot name="handle"></slot>
      </div>
      <div class="left">
        <el-popover placement="bottom" width="320" trigger="click">
          <div class="check-box">
            <el-checkbox
              :indeterminate="isIndeterminate"
              v-model="checkAll"
              @change="handleCheckAllChange"
              >全选</el-checkbox
            >
            <div style="margin: 15px 0;"></div>
            <el-checkbox-group
              class="check-flex"
              v-model="checkboxVal"
              @change="handleCheckedChange"
            >
              <el-checkbox
                v-for="(itemCheck, index) in showItems.filter(i => i.prop)"
                :key="itemCheck.prop + '_' + index"
                :label="itemCheck.prop"
              >
                {{ itemCheck.label }}
              </el-checkbox>
            </el-checkbox-group>
          </div>
          <el-button slot="reference" size="mini" icon="el-icon-setting" type="primary"
            >自定义列表</el-button
          >
        </el-popover>
      </div>
    </div>
    <el-table
      id="iTable"
      :class="tableClass"
      v-loading="loading"
      element-loading-text="加载中..."
      :data="data"
      :stripe="options.stripe"
      :border="options.border"
      :highlight-current-row="options.highlightCurrentRow"
      :header-row-style="options.headerRowStyle"
      :lazy="options.lazy"
      :height="options.height"
      :max-height="options.max_height || $max_height"
      :load="loadGetData"
      :show-summary="showsummary"
      ref="mutipleTable"
      style="width:100%;"
      @row-click="clickRow"
      @row-dblclick="dblclickRow"
      @row-contextmenu="contextmenu"
      @header-click="headClick"
      @header-contextmenu="headcontextmenu"
      @select="select"
      @select-all="selectAll"
      @current-change="rowChange"
      @selection-change="handleSelectionChange"
      @sort-change="handleChangeSort"
      :default-sort="defaultSort"
      :summary-method="getSummaries"
      :key="tableKey"
    >
      <!--region 数据列-->
      <el-table-column width="1" class-name="btable-hide-col"></el-table-column>
      <fragment v-for="(column, index) in columns" :key="index">
        <child-column
          :index="index"
          :column="column"
          :showPage="showPage"
          :page="page"
          :sortMethod="sortMethod"
          @cell-db-click="cellDbClick"
        >
          <!-- <template slot-scope="data">
            <slot name="column" :data="data" />
          </template> -->
          <!-- 注意:此处遍历值不一样,如果是vue2中的话customSlots可以替换为$scopedSlots,而且下面setup中的取值也不需要了 -->
          <!-- vue2:$scopedSlots -->
          <!-- vue3:
            setup() {
              const { proxy } = getCurrentInstance()
              const customSlots = reactive({
                ...proxy.$slots
              })

              return {
              customSlots
              }
            } -->

          <!-- #[slot]="scope"  可以理解为 v-slot:slot = 'scope'   v-slot:'插槽名称' = '传过来的值' -->
          <template v-for="slot in Object.keys($scopedSlots)" #[slot]="scope">
            <!-- 以之前的名字命名插槽,同时把数据原样绑定 -->
            <slot :name="slot" v-bind="scope" />
          </template>
        </child-column>
      </fragment>
      <!--endregion-->
    </el-table>

    <!-- 分页 -->
    <pagination
      v-if="showPage"
      :total="page.total"
      :page.sync="page.page"
      :limit.sync="page.perpage"
      :disabled="loading"
      @pagination="pagination"
    />
  </div>
</template>
<script>
// import { getCurrentInstance, reactive } from "vue";
export default {
  props: {
    data: {
      type: Array,
      default: () => []
    },
    // 数据列表
    columns: {
      type: Array,
      default: () => []
    }, // 需要展示的列 === prop:列数据对应的属性,label:列名,align:对齐方式,width:列宽
    options: {
      type: Object,
      default: function() {
        return {
          stripe: true, // 是否为斑马纹 table
          highlightCurrentRow: false, // 是否要高亮当前行
          border: true, //是否有纵向边框
          lazy: false, //是否需要懒加载
          max_height: "",
          headerRowStyle: {
            backgroundColor: "#f8f8f8"
          }
        };
      }
    }, // table 表格的控制参数
    tableClass: {
      type: String,
      default: "hxTable"
    },
    // 是否展示分页
    showPage: {
      type: Boolean,
      default: true
    },

    // 分页数据
    page: {
      type: Object,
      default: () => ({
        total: 0,
        page: 1,
        perpage: 20
      })
    },
    // 表格加载
    loading: {
      type: Boolean,
      default: false
    },
    // 默认排序
    defaultSort: {
      type: Object,
      default: () => ({})
    },
    // 自定义排序方法
    sortMethod: {
      type: Function,
      default: () => {}
    },
    // 显示合计
    showsummary: {
      type: Boolean,
      default: false
    },
    // 显示合计,价格精度-保留几位小数
    precision: {
      type: Number,
      default: () => 2
    },
    // 手动传入总计
    count_sum: {
      type: Object,
      default: () => ({})
    },
    // 是否显示表格上方显示区域
    showHandle: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      // 多行选中
      multipleSelection: [],
      // 每次切换列显示隐藏,更新key
      tableKey: 0,
      // 自定义列绑定值
      checkboxVal: [],
      // checkout 全部的绑定值
      defaultFormThead: [],
      // 是否全选
      checkAll: true,
      // 全选按钮的展示状态
      isIndeterminate: false,
      // 需要展示的列
      showItems: [],
      // slots数据
      customSlots: [],
      // 拷贝的columns数据
      copyColumn: []
    };
  },
  // mounted() {
  //   // setTimeout(() => {
  //   //   console.log(this.$refs.aaaa.$slots);
  //   // }, 3000);
  //   const { proxy } = getCurrentInstance();
  //   const customSlots = reactive({
  //     ...proxy.$slots
  //   });
  //   this.customSlots = customSlots;
  //   console.log(customSlots);
  // },

  methods: {
    /**
     * 列表懒加载,必须先开启懒加载
     * */
    loadGetData(row, treeNode, resolve) {
      //懒加载事件数据
      let data = {
        row: row,
        treeNode: treeNode,
        resolve: resolve
      };
      this.$emit("loadGetData", data);
    },
    /**
     * 修改表格prop
     */
    handleProp(row, prop) {
      if (prop.indexOf(".") !== -1) {
        let props = prop.split(".");
        if (row[props[0]]) {
          return row[props[0]][props[1]] || row[props[0]][props[1]] === 0
            ? row[props[0]][props[1]]
            : "";
        } else {
          return "";
        }
      } else {
        return row[prop] || row[prop] === 0 ? row[prop] : "";
      }
    },
    /**
     * 多行选中
     * */
    handleSelectionChange(val) {
      // 多行选中
      this.multipleSelection = val;
      this.$emit("handleSelectionChange", val);
    },
    /**
     * 禁用当前行选中
     * 调用父组件的自定义方法获取返回值,返回false 代表禁用
     * */
    checkSelectable(row) {
      let status = true;
      this.$emit("checkSelectable", row, val => {
        status = val;
      });
      return status;
    },
    /**
     * 当前组件表格点击复制
     * */
    copy(row, columns) {
      if (!columns.nodbclick) {
        let text = this.handleProp(row, columns.prop);
        if (text) {
          navigator.clipboard.writeText(text).then(() => {
            this.$message("复制成功");
            this.$emit("cell-db-click");
          });
        }
      }
    },
    // 子组件传出的点击复制成功
    cellDbClick() {
      this.$emit("cell-db-click");
    },
    /**
     * 单击行事件
     * */
    clickRow(row, column, event) {
      let data = {
        row: row,
        column: column,
        event: event
      };
      this.$emit("clickRow", data);
    },
    /**
     * 双击行事件
     * */
    dblclickRow(row, column, event) {
      let data = {
        row: row,
        column: column,
        event: event
      };
      this.$emit("dblclickRow", data);
    },
    /**
     * 右键行事件-没去掉页面默认的
     * */
    contextmenu(row, column, event) {
      let data = {
        row: row,
        column: column,
        event: event
      };
      this.$emit("contextmenu", data);
    },
    /**
     * 头部列点击事件
     * */
    headClick(column, event) {
      let data = {
        column: column,
        event: event
      };
      this.$emit("headClick", data);
    },
    /**
     * 头部列右键点击事件
     * */
    headcontextmenu(column, event) {
      let data = {
        column: column,
        event: event
      };
      this.$emit("headcontextmenu", data);
    },
    /**
     * 当前行发生改变时的事件
     * */
    rowChange(currentRow, oldCurrentRow) {
      let data = {
        currentRow: currentRow,
        oldCurrentRow: oldCurrentRow
      };
      this.$emit("rowChange", data);
    },
    /**
     * 用户手动勾选复选框触发
     * */
    select(sel, row) {
      let data = {
        sel: sel,
        row: row
      };
      this.$emit("select", data);
    },
    /**
     * 用户点击全选触发
     * */
    selectAll(sel) {
      let data = {
        sel: sel
      };
      this.$emit("selectAll", data);
    },
    /**
     * 表格合计
     */
    getSummaries(param) {
      const { columns, data } = param;
      const cols = JSON.parse(JSON.stringify(this.columns));
      const constArr = cols.map(i => {
        if (i.hj === "num" && i.prop) {
          return i.prop;
        }
        return "";
      });
      const mentArr = cols.map(i => {
        if (i.hj === "price" && i.prop) {
          return i.prop;
        }
        return "";
      });
      const sums = [];
      columns.forEach((column, index) => {
        let is_count = Object.keys(this.count_sum).length > 0;
        if (index === 0) {
          sums[index] = is_count ? "总计" : "合计";
          return;
        }
        if (is_count && this.count_sum[column.property] !== undefined) {
          sums[index] = this.count_sum[column.property];
        } else if (
          mentArr.indexOf(column.property) !== -1 ||
          constArr.indexOf(column.property) !== -1
        ) {
          // column.property  当前列的绑定值  scope.row.xxx
          const values = data.map(item => Number(item[column.property]));
          // 判断当前的值不为NaN
          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);
            if (mentArr.indexOf(column.property) !== -1) {
              sums[index] = sums[index].toFixed(this.precision);
            }
          } else {
            sums[index] = "N/A";
          }
        }
      });
      return sums;
    },

    // 表格排序
    handleChangeSort(row, column) {
      // console.log(row, column);
      this.$emit("change-sort", row, column);
    },

    // 表格排序
    sortHandle(row, row1, index, prop) {
      if (this.sortMethod(row, row1, index, prop)) {
        return this.sortMethod(row, row1, index, prop);
      } else {
        if (!Number(row[prop]) || Number(row[prop]) === 0) {
          return -1;
        }
        if (Number(row[prop]) < Number(row1[prop])) {
          return -1;
        } else {
          return 1;
        }
      }
    },

    // 分页
    pagination(val) {
      let { limit, page } = val;
      // 根据当前总条数、当前每页展示条数来判断当前页码是否超标;
      // 是的话就将当前总条数、当前每页展示条数的最大页码赋值给组件;
      // 例:total: 99   pagesize: 10  page: 10;
      // 此时切换pagesize为30,如果继续按照page:10去计算,那就是10*30 = 300;
      // 但是数据一共就99条,因此要更新页码为:Math.ceil(99 / 30) = 4;3*30 < 99 < 4*30,就ok了。
      // ,唯一美中不足的是,由于pagesize和page的改变都会触发分页组件的Pagination方法,所以会导致调用两次接口,但数据是一样的
      // 目前还没想到要怎么处理,难不成要在<Pagination/>里边另写一个触发更改分页的方法?
      let max_page = Math.ceil(this.page.total / limit);
      if (max_page < page) {
        page = max_page;
        this.$emit("pagination", { limit, page });
      } else {
        this.$emit("pagination", { limit, page });
      }
    },

    // 操作自定义列
    handleDefaultHead(data) {
      this.defaultFormThead = data.filter(i => i.prop).map(i => i.prop);
      this.checkboxVal = data.filter(i => i.prop).map(i => i.prop);
    },
    // 全选
    handleCheckAllChange(val) {
      console.log(val);
      this.checkboxVal = val ? this.defaultFormThead : [];
      this.isIndeterminate = false;
      this.handleHead();
    },
    // 判断是否是全选
    handleCheckedChange(value) {
      this.handleHead();
      let checkedCount = value.length;
      this.checkAll = checkedCount === this.defaultFormThead.length;
      this.isIndeterminate = checkedCount > 0 && checkedCount < this.defaultFormThead.length;
    },
    // 表格头部变化
    handleHead() {
      this.$emit("handle-column", this.handleShowColumn(this.copyColumn));
    },
    // 操作选择隐藏/显示列
    handleShowColumn(columns) {
      let arr = [];
      columns = JSON.parse(JSON.stringify(columns));
      columns.forEach(i => {
        if (i.child) {
          let child = this.handleShowColumn(i.child);
          if (child.length) {
            i.child = child;
            arr.push(i);
          }
        } else {
          if (!i.prop || this.checkboxVal.indexOf(i.prop) !== -1) {
            // console.log(i);
            arr.push(i);
          }
        }
      });
      return arr;
    },
    // 操作传入列数据
    handleItems(val) {
      let arr = [];
      val.forEach(item => {
        if (item.child) {
          arr.push(...this.handleItems(item.child));
        } else {
          arr.push(item);
        }
      });

      return arr;
    }
  },
  watch: {
    columns: {
      handler(val) {
        // 表格宽度计算公式:每个字宽度默认为14.5,(元)宽度默认30 + padding左右各20 = 50
        if (!this.showItems.length) {
          this.showItems = this.handleItems(val);
          this.handleDefaultHead(this.showItems);
        }
        if (!this.copyColumn.length) {
          this.copyColumn = JSON.parse(JSON.stringify(val));
        }
        this.tableKey++;
      },
      deep: true,
      immediate: true
    }
  }
};
</script>
<style lang="scss">
.el-icon-question {
  color: #cc9756;
}
.atooltip.el-tooltip__popper[x-placement^="top"] .popper__arrow {
  border-top-color: #cc9756;
}
.atooltip.el-tooltip__popper[x-placement^="top"] .popper__arrow:after {
  border-top-color: #fff;
}
.atooltip {
  border: 1px solid #cc9756 !important;
  color: #cc9756;
}

.table {
  margin-top: 20px;
  .el-table {
    margin-top: 0;
  }
  .b-table-handle {
    border: 1px solid #ebeef5;
    border-bottom: none;
    padding: 10px;
    background: #f5f7fa;
    display: flex;
    align-items: center;
    justify-content: space-between;
    .right {
      flex: 1;
    }
    .left {
      // width: 160px;
      padding-left: 50px;
    }
  }
}

.check-box {
  max-height: 350px;
  overflow-y: auto;

  .check-flex {
    display: none;
    display: flex;
    flex-wrap: wrap;

    .el-checkbox {
      width: 50%;
      margin-right: 0;
      display: flex;
      align-items: center;
      box-sizing: border-box;

      &:nth-child(2n) {
        padding-left: 10px;
      }

      .el-checkbox__label {
        flex: 1;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
    }
  }
}
</style>

表格子组件:表格列 ChildColumn.vue

<template>
  <fragment v-if="!column.child">
    <fragment v-if="column.type == 'selection'">
      <!--复选框(START)-->
      <el-table-column
        type="selection"
        :width="column.width ? column.width : 55"
        :align="column.align ? column.align : 'center'"
        :key="column.prop + '_' + index"
        :selectable="checkSelectable"
      >
      </el-table-column>
      <!--复选框(END)-->
    </fragment>
    <fragment v-else-if="column.type == 'index'">
      <!--序号(START)-->
      <el-table-column
        :label="column.label ? column.label : '序号'"
        :min-width="column.minWidth ? column.minWidth : 60"
        :align="column.align ? column.align : 'left'"
        :sortable="column.sort || false"
        :sort-method="(a, b) => sortHandle(a, b, index, column.prop)"
        :key="column.prop + '_' + index"
        fixed="left"
      >
        <template slot-scope="scope">
          <slot name="index" :scope="scope" v-if="showPage">{{
            scope.$index + 1 + page.perpage * (page.page - 1)
          }}</slot>
          <slot name="index" :scope="scope" v-else>{{ scope.$index + 1 }}</slot>
        </template>
      </el-table-column>
    </fragment>
    <fragment v-else>
      <!-- 默认渲染列-渲染每一列的汉字 -->
      <el-table-column
        :prop="column.prop"
        :label="column.label"
        :align="column.align"
        :header-align="column.head_align ? column.head_align : 'center'"
        :width="column.width"
        :fixed="column.fixed"
        :min-width="column.minWidth"
        :show-overflow-tooltip="true"
        :key="column.prop + '_' + index"
        :sortable="column.sort || false"
        :sort-method="(a, b) => sortHandle(a, b, index, column.prop)"
      >
        <template slot-scope="scope" slot="header">
          <template v-if="column.head_slot">
            <slot :name="column.head_slot" v-bind="scope"></slot>
          </template>
          <template v-else>
            {{ column.label }}
            <el-tooltip
              effect="light"
              placement="top"
              :scope="scope"
              v-if="column.header"
              popper-class="atooltip"
            >
              <div slot="content" v-for="(item, index) in column.header.split(';')" :key="index">
                {{ item }}
              </div>
              <i class="el-icon-question"></i>
            </el-tooltip>
          </template>
        </template>
        <template slot-scope="scope">
          <!-- 双击复制 -->
          <!-- 如不需要双击复制,在外部column添加 nodbclick = true -->
          <slot :name="column.prop" :scope="scope">
            <span @dblclick="copy(scope.row, column)"
              >{{ handleProp(scope.row, column.prop) }}
            </span>
          </slot>
        </template>
      </el-table-column>
    </fragment>
  </fragment>
  <fragment v-else>
    <el-table-column :prop="column.prop" :label="column.label" :align="column.align">
      // 不知道为何element动态渲染表格,会把每次循环的第一列渲染到最后一列去,研究了好久,放弃了
      // 最后选择在每次循环开始前,添加一列width=1px的空列出来,让它去渲染到最后一列,不影响循环,调整一下样式
      <el-table-column width="1" class-name="btable-hide-col"></el-table-column>
      <child-column
        v-for="(item, i) in column.child"
        :key="i"
        :index="i"
        :column="item"
        :showPage="showPage"
        :page="page"
        :sortMethod="sortMethod"
        @cell-db-click="cellDbClick"
      >
        <!-- <template slot-scope="data">
          <slot name="column" :data="data" />
        </template> -->
        <!-- 注意:如果是vue2中的话customSlots可以替换为$scopedSlots,而且下面setup中的取值也不需要了 -->
        <template v-for="slot in Object.keys($scopedSlots)" #[slot]="scope">
          <!-- 以之前的名字命名插槽,同时把数据原样绑定 -->
          <slot :name="slot" v-bind="scope" />
        </template>
      </child-column>
    </el-table-column>
  </fragment>
</template>
<script>
export default {
  props: {
    child: {
      type: Array,
      default: () => []
    },
    index: {
      type: Number,
      default: 0
    },
    // 自定义排序方法
    sortMethod: {
      type: Function,
      default: () => {}
    },
    showPage: {
      type: Boolean,
      default: true
    },
    // 分页数据
    page: {
      type: Object,
      default: () => ({
        total: 0,
        page: 1,
        perpage: 20
      })
    },
    column: {
      type: Object,
      required: true
    },
    children: {
      //child识别字段,用于识别多级表头字段
      type: String,
      required: false,
      default: "child"
    }
  },
  methods: {
    // 表格排序
    sortHandle(row, row1, index, prop) {
      if (this.sortMethod(row, row1, index, prop)) {
        return this.sortMethod(row, row1, index, prop);
      } else {
        if (!Number(row[prop]) || Number(row[prop]) === 0) {
          return -1;
        }
        if (Number(row[prop]) < Number(row1[prop])) {
          return -1;
        } else {
          return 1;
        }
      }
    },
    /**
     * 当前组件表格点击复制
     * */
    copy(row, columns) {
      if (!columns.nodbclick) {
        let text = this.handleProp(row, columns.prop);
        if (text) {
          navigator.clipboard.writeText(text).then(() => {
            this.$message("复制成功");
            console.log();
            this.$emit("cell-db-click");
          });
        }
      }
    },
    // 子组件传出的点击复制成功
    cellDbClick() {
      this.$emit("cell-db-click");
    },
    /**
     * 修改表格prop
     */
    handleProp(row, prop) {
      if (prop && prop.indexOf(".") !== -1) {
        console.log(row);
        let props = prop.split(".");
        if (row[props[0]]) {
          return row[props[0]][props[1]] || row[props[0]][props[1]] === 0
            ? row[props[0]][props[1]]
            : "";
        } else {
          return "";
        }
      } else {
        return row[prop] || row[prop] === 0 ? row[prop] : "";
      }
    }
  },
  render(h) {
    // 本来想使用render来渲染页面的,但是涉及到的方法较多,还是算了
    // 个人觉得render函数要比直接写模板代码看起来简洁,比较容易理解
    function interite(arr) {
      return arr.map(item => {
        if (item.child) {
          return h("el-table-column", { attrs: { label: item.label } }, interite(item.child));
        } else {
          let name = "column_";
          if (item.prop) {
            name += item.prop;
          } else if (item.type) {
            name += item.type;
          }
          console.log(item);
          return h("slot", { attrs: { name } });
        }
      });
    }
    let children = interite(this.child);
    let el = h("template", {}, children);
    return el;
  }
};
</script>
<style lang="scss">
// 不知道为何element动态渲染表格,会把每次循环的第一列渲染到最后一列去,研究了好久,放弃了
// 最后选择在每次循环开始前,添加一列width=1px的空列出来,让它去渲染到最后一列,不影响循环,调整一下样式
.el-table .btable-hide-col {
  border-right: none !important;
  width: 0px;
  // display: none;
}
</style>

分页组件:

<template>
  <div :class="{ hidden: hidden }" class="pagination-container">
    <el-pagination
      :background="background"
      :current-page.sync="currentPage"
      :page-size.sync="pageSize"
      :layout="layout"
      :page-sizes="pageSizes"
      :total="total"
      v-bind="$attrs"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>

<script>
import { scrollTo } from "@/utils/scroll-to";

export default {
  name: "Pagination",
  props: {
    total: {
      required: true,
      type: Number
    },
    page: {
      type: Number,
      default: 1
    },
    limit: {
      type: Number,
      default: 20
    },
    pageSizes: {
      type: Array,
      default() {
        return [10, 20, 30, 50, 100];
      }
    },
    layout: {
      type: String,
      default: "total, sizes, prev, pager, next, jumper"
    },
    background: {
      type: Boolean,
      default: true
    },
    autoScroll: {
      type: Boolean,
      default: true
    },
    hidden: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    currentPage: {
      get() {
        return this.page;
      },
      set(val) {
        this.$emit("update:page", val);
      }
    },
    pageSize: {
      get() {
        return this.limit;
      },
      set(val) {
        this.$emit("update:limit", val);
      }
    }
  },
  methods: {
    handleSizeChange(val) {
      this.$nextTick(() => {
        this.$emit("pagination", { page: this.currentPage, limit: val });
        if (this.autoScroll) {
          scrollTo(0, 800);
        }
      });
    },
    handleCurrentChange(val) {
      this.$nextTick(() => {
        this.$emit("pagination", { page: val, limit: this.pageSize });
        if (this.autoScroll) {
          scrollTo(0, 800);
        }
      });
    }
  }
};
</script>

<style lang="scss" scoped>
.pagination-container {
  padding: 0px 16px;
  text-align: right;
  display: flex;
  flex-direction: inherit;
  justify-content: flex-end;
  align-items: center;
  display: block;
  width: 100%;
  overflow: auto;
}
.pagination-container.hidden {
  display: none;
}
</style>

 引用组件:

  • 可以通过操作 headList 实现动态展示列的效果
  • 通过slot自定义表格内容展示(默认show-overflow-tooltip = true)
<template>
  <!-- 表格数据 -->
  <b-table
    ref="btable"
    :data="listData"
    :columns="defaultHead"
    :loading="loading"
    :showsummary="true"
    :showPage="true"
    :page="{ total: total, page: searchForm.page, perpage: searchForm.perpage }"
    @pagination="pagination"
    :count_sum="count_sum"
  >
  </b-table>
</template>

<script>
export default {
  data() {
    return {
      searchForm: {
        shop_id: "",
        product_type: "",
        page: 1,
        perpage: 20
      },
      // 订单列表数据
      listData: [],
      // 表格头部数据---全部的
      defaultHead: [
        {
          type: "index"
        },
        {
          prop: "shop_name",
          label: "门店",
          align: "center",
          minWidth: "120",
          fixed: "left"
        },
        {
          prop: "product_type_name",
          label: "物料分类",
          align: "center",
          minWidth: "120"
        },
        {
          prop: "cost_price",
          label: "成本金额(元)",
          align: "center",
          minWidth: "150"
        },
        {
          prop: "sale_price",
          label: "售价金额(元)",
          align: "center",
          minWidth: "150"
        },
        {
          prop: "gross_profit",
          label: "毛利(元)",
          align: "center",
          minWidth: "150"
        },
        {
          prop: "gross_profit_rate",
          label: "毛利率",
          align: "center",
          minWidth: "150"
        },
        {
          prop: "day",
          label: "日期",
          align: "center",
          minWidth: "170"
        }
      ],
      // 订单列表总数
      total: 0,
      // 列表加载状态
      loading: false,
      // 合计数据
      count_sum: {}
    };
  },
};
</script>

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会做饭的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值