已更新至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>