基于el-table的多级表头表格组件封装
StandardTable.vue 表格组件
<template>
<div class="tableWrap">
<el-table
ref="table"
element-loading-text="Loading"
v-loading="loading"
:data="tableData"
border
:highlight-current-row="radioSelect"
tooltip-effect="dark"
:stripe="stripe"
@selection-change="handleSelectChange"
@sort-change="handleSortChange"
@row-click="rowClick"
:span-method="spanMethod"
:header-cell-style="headerCellStyle"
:row-key="rowKey"
:tree-props="treeProps"
style="width: 100%"
:height="height"
:summary-method="getSummaries"
:show-summary="showSummary"
>
<template slot="empty">
<span>{{ emptyTxt }}</span>
</template>
<el-table-column
v-if="selectionShow"
key="selection"
type="selection"
align="center"
:selectable="selectableFn"
:resizable="resizable"
></el-table-column>
<el-table-column
v-if="radioSelect"
align="center"
width="50px"
label=""
fixed="left"
>
<template slot-scope="scope">
<el-radio
v-model="radio"
:label="scope.row.id"
:disabled="scope.row.disabled"
@change="handleRadioChange(scope.row)"
>{{ "" }}</el-radio
>
</template>
</el-table-column>
<el-table-column
v-if="indexShow"
key="index"
type="index"
align="center"
:label="indexShowStr || '序号'"
fixed="left"
width="50px"
:resizable="resizable"
>
<template slot-scope="scope">
<span>{{ (current - 1) * pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<table-column
v-for="(item, index) in tableLabel || []"
:column="item"
:min-width="minWidth"
:resizable="resizable"
:key="index"
></table-column>
<el-table-column
v-if="tableOption.label"
:width="tableOption.width"
:label="tableOption.label"
:fixed="tableOption.fixed ? tableOption.fixed : false"
align="center"
class-name="small-padding fixed-width"
:resizable="resizable"
>
<template slot-scope="scope">
<el-button
v-for="(item, index) in tableOption.options"
:key="index"
:type="item.type"
:icon="item.icon"
@click="item.clickFun(scope.row, scope.$index)"
size="mini"
v-if="item.showFn ? item.showFn(scope.row) : true"
:disabled="item.disabledFn ? item.disabledFn(scope.row) : false"
>
<span v-if="item.render">
{{ renderToHtml(item, scope.row) }}
<slot :name="item.renderName"></slot>
</span>
<span v-else>{{ item.label }}</span>
</el-button>
</template>
</el-table-column>
</el-table>
<el-row type="flex" justify="end">
<el-pagination
v-if="!noPage"
@size-change="handlePageSizeChange"
@current-change="handleCurrentChange"
:current-page="current"
:page-sizes="pageSizes"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</el-row>
</div>
</template>
<script>
import { accAdd } from "@/utils/commonUtils.js";
import TableColumn from "@/components/table/table-column.vue";
export default {
name: "StandardTable",
props: {
pageSize: {
type: Number,
default: 50
},
current: {
type: Number,
default: 1
},
total: {
type: Number,
default: 0
},
pageSizes: {
type: Array,
default: () => {
return [10, 20, 50, 100, 200, 500];
}
},
stripe: {
type: Boolean,
default: false
},
noPage: {
type: Boolean,
default: false
},
isRowDrop: {
type: Boolean,
default: false
},
indexShow: {
type: Boolean,
default: false
},
selectionShow: {
type: Boolean,
default: false
},
radioSelect: {
type: Boolean,
default: false
},
showSummary: {
type: Boolean,
default: false
},
tableData: {
type: Array,
default: () => {
return [];
}
},
tableLabel: {
type: Array,
default: () => {
return [];
}
},
totalParams: {
type: Array,
default: () => {
return [];
}
},
tableOption: {
type: Object,
default: () => {
return {};
}
},
loading: {
type: Boolean,
default: false
},
height: "",
spanMethod: {},
headerCellStyle: {},
dropDataType: {
type: Number,
default: 0
},
indexShowStr: {
type: String,
default: "序号"
},
emptyTxt: {
type: String,
default: "暂无数据"
},
//禁止拖动表头
resizable: {
type: Boolean,
default: false
},
treeProps: {
//树状表格子级参数
type: Object,
default: () => {
return {};
}
},
rowKey: {
type: String,
default: "id"
},
// 需要传的其他参数
params: {
type: Object,
default: () => {}
}
},
data() {
return {
radio: "",
minWidth: "100px" //不生效,elementUI当前版本不支持
};
},
components: {
TableColumn
},
mounted() {
if (this.isRowDrop) {
this.$emit("rowDrop");
}
},
methods: {
rowClick(row, column, event) {
this.$emit("rowClick", row, column, event);
},
toggleRowSelection(row, selectedStatus) {
if (row) {
this.$refs.table.toggleRowSelection(row, selectedStatus);
} else {
this.$refs.table.clearSelection();
}
},
renderToHtml(col, row) {
if (typeof col.render === "function") {
this.$slots[col.renderName] = [col.render(row)];
return;
}
return;
},
//行拖拽
// rowDrop() {
// const tbody = document.querySelectorAll('.el-table__body-wrapper tbody')
// const _this = this
// Sortable.create(tbody, {
// onEnd({newIndex, oldIndex}) {
// const currRow = _this.tableData.splice(oldIndex, 1)[0]
// _this.tableData.splice(newIndex, 0, currRow)
// },
// })
// },
handleCurrentChange(current) {
this.$emit("handleCurrentChange", current);
},
handlePageSizeChange(pageSize) {
this.$emit("handlePageSizeChange", pageSize);
},
handleSelectChange(value) {
this.$emit("handleSelectChange", value);
},
handleRadioChange(value) {
this.$emit("handleRadioChange", value);
},
handleRadioChange2(value) {
console.log(value);
},
handleButton(method, value) {
this.$emit(method, value);
},
handleSortChange(column, prop, order) {
this.$emit("handleSortChange", column, prop, order);
},
getSummaries(params) {
const { columns, data } = params;
let sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "合计";
return;
}
if (this.totalParams.indexOf(column.property) != -1) {
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);
let res = 0;
if (!isNaN(value)) {
res = accAdd(prev, curr);
} else {
res = prev;
}
// if(res!=NaN&&res!='undefined'){
return parseFloat(res.toFixed(2));
// }
}, 0);
} else {
sums[index] = "-";
}
}
});
return sums;
},
// 该行是否禁用
selectableFn(row, index) {
if (row.disabled) {
return false;
} else {
return true;
}
},
doLayout() {
if (this.$refs.table) {
this.$refs.table.doLayout();
}
},
handleBlurChange(e, type) {
this.$emit("handleBlurChange");
}
},
watch: {
tableData: {
immediate: true,
deep: true,
handler() {
setTimeout(() => {
this.$nextTick(() => {
// 重新渲染表格,解决固定列错位的问题
if (this.$refs.table) {
this.$refs.table.doLayout();
}
});
}, 50);
}
}
}
};
</script>
<style lang="scss" scoped>
a {
text-decoration: underline;
}
/deep/ .el-table__header th,
/deep/ .el-table thead.is-group th {
background-color: #fff8f2;
}
.el-table .cell > span,
.el-table .cell a {
// max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
/*禁止换行显示*/
white-space: nowrap;
color: #4d4d4d;
}
.el-table /deep/ .cell {
padding-left: 6px;
padding-right: 6px;
color: #4d4d4d;
}
// 表格折叠样式兼容 start
/deep/ .el-table__expand-icon {
vertical-align: middle;
}
/deep/ .el-table__expand-icon + span {
width: calc(100% - 23px);
display: inline-block;
vertical-align: middle;
}
/deep/ .el-table__placeholder + span {
width: calc(100% - 36px);
display: inline-block;
vertical-align: middle;
}
// 表格折叠样式兼容 end
.el-pagination {
margin-top: 12px;
padding-bottom: 12px;
}
.el-table /deep/ .el-button--primary {
padding: 5px 8px;
height: 30px;
background: linear-gradient(180deg, #f79999 0%, #c70019 60%);
border-radius: 6px;
border: 1px solid #c70019;
color: #fff !important;
font-size: 14px;
&:hover {
color: #ffe300 !important;
background: linear-gradient(180deg, #f79999 0%, #c70019 60%);
border: 1px solid #c70019;
}
&:focus {
color: #fff;
background: linear-gradient(180deg, #c50018 0%, #960000 60%);
border: 1px solid #c8001a;
}
}
.el-button.is-disabled,
.el-button.is-disabled:hover,
.el-button.is-disabled:focus {
color: #979797 !important;
cursor: not-allowed;
background-image: none;
background: linear-gradient(180deg, #dddddd 0%, #ffffff 100%) !important;
border: 1px solid #e5e5e5;
}
.input-wrap {
display: flex;
align-items: center;
label {
width: auto;
}
div {
min-width: 50%;
flex: 1;
}
}
.editLable {
label {
float: left;
width: 55%;
text-align: left;
}
.el-input {
float: right;
width: 44%;
}
}
/deep/ .el-table__footer-wrapper .cell {
font-weight: bold;
}
.fontW {
font-weight: bold;
}
</style>
列组件
<template>
<el-table-column
:width="column.width ? column.width : ''"
:min-width="minWidth"
:align="column.align"
:label="column.label"
:fixed="column.fixed"
:prop="column.param"
:sortable="column.sortable ? 'custom' : false"
:resizable="resizable"
>
<!-- 多级表头 -->
<template v-if="column.children">
<table-column
v-for="(sunitem, i) in column.children"
:column="sunitem"
:min-width="minWidth"
:resizable="resizable"
:key="i"
></table-column>
</template>
<template slot="header" slot-scope="scope">
<!-- 自定义表头 -->
<div v-if="column.headerRender">
{{ renderHeaderToHtml(item, scope.row) }}
<slot :name="column.headerRenderName"></slot>
</div>
<div v-else>{{ column.label }}</div>
</template>
<template slot-scope="scope">
<!-- 枚举值,动态入参 -->
<span
v-if="column.dicType && column.dicParam"
:title="dic[column.dicType][scope.row[column.param]]"
>
{{ dic[column.dicType][column.dicParam(scope.row)] || "-" }}
</span>
<!-- 枚举值 -->
<span
v-else-if="column.dicType"
:title="dic[column.dicType][scope.row[column.param]]"
>
{{ dic[column.dicType][scope.row[column.param]] || "-" }}
</span>
<!-- 自定义渲染 -->
<span v-else-if="column.render">
{{ renderToHtml(item, scope.row) }}
<slot :name="column.renderName"></slot>
</span>
<!-- 可进行点击操作 -->
<span v-else-if="column.handler" @click="column.handler(scope.row)">
{{ scope.row[column.param] }}
</span>
<!-- creatTime、createTime 时间格式处理 -->
<span
v-else-if="column.param == 'creatTime' || column.param == 'createTime'"
:title="filterDate(scope.row[column.param])"
>
{{ filterDate(scope.row[column.param]) }}
</span>
<!-- 可编辑列,label为枚举值 -->
<div class="input-box editLable" v-else-if="column.edit">
<label v-if="dic[column.labelType]">{{
dic[column.labelType][scope.row[column.labelType]]
}}</label>
<el-form-item :prop="column.prop" :ref="column.ref">
<el-input
size="small"
v-model="scope.row[column.param]"
@blur="handleBlurChange($event, scope.row[column.labelType])"
></el-input>
</el-form-item>
</div>
<!-- 不可编辑列,label为枚举值 -->
<div class="input-box editLable" v-else-if="column.showLabel">
<label v-if="dic[column.labelType]">{{
dic[column.labelType][scope.row[column.labelType]]
}}</label>
<span>{{ scope.row[column.param] }}</span>
</div>
<!-- 其他文本类型渲染 -->
<span
v-else
:title="scope.row[column.param]"
:class="scope.row.fontBold || column.fontBold ? 'fontW' : ''"
>{{
scope.row[column.param] ||
scope.row[column.param] === 0 ||
scope.row[column.param] == "0"
? scope.row[column.param]
: "-"
}}</span
>
</template>
</el-table-column>
</template>
<script>
import TableColumn from "@/components/table/table-column.vue";
import dic from "@/utils/dic.js";
import moment from "moment";
export default {
name: "table-column",
components: { TableColumn },
props: {
// column 列的参数如下:
// param 取值字段名称
// dicType 枚举字段名称
// dicParam 枚举参数 例:let dic={ name:{name1:"名称一",name2:"名称二"}} name为dicType ,name1、name2 为 dicParam
// fontBold 文字是否加粗
// directShow 暂未使用
// renderProject 自定义项目名称render方式
// renderName 插槽名称
// link 添加跳转链接
// prop 绑定的prop变量,与el-form-item相关
// ref 绑定的ref变量,与el-form-item相关
// showLabel 是否显示el-form-item的label
// labelType el-form-item的枚举取值变量
column: {
type: Object,
default: () => {}
},
//列的最小宽度
minWidth: {
type: String,
default: "100px"
},
//禁止拖动表头
resizable: {
type: Boolean,
default: false
}
},
data() {
return {
dic: dic || {}
};
},
methods: {
renderToHtml(col, row) {
if (typeof col.render === "function") {
this.$slots[col.renderName] = [col.render(row)];
return;
}
return;
},
renderHeaderToHtml(col, row) {
if (typeof col.headerRender === "function") {
this.$slots[col.headerRenderName] = [col.headerRender(row)];
return;
}
return;
},
// 格式化时间
filterDate(date) {
if (date) {
return moment(date).format("YYYY-MM-DD");
} else {
return "";
}
}
}
};
</script>
<style lang="scss" scoped></style>
表格的使用
<template>
<div class="about">
<h1>表格</h1>
<StandardTable
ref="table"
style="margin-top: 15px"
:table-data="tableData"
:table-label="tableLabel"
:table-option="tableOption"
:total="total"
:pageSize="size"
:current="current"
>
</StandardTable>
</div>
</template>
<script>
import StandardTable from "@/components/table/StandardTable2.vue";
export default {
name: "tableList",
components: {
StandardTable
},
data() {
return {
form: {},
tableData: [
{
projectName: "名称",
projectName1: "二级名称1",
projectName2: "二级名称2",
implementDept: "项目单位名称",
controlLevelId: "层次1"
},
{
projectName: "名称2",
projectName1: "二级名称1",
projectName2: "二级名称2",
implementDept: "项目单位名称1",
controlLevelId: "层次1"
},
{
projectName: "名称3",
projectName1: "二级名称1",
projectName2: "二级名称2",
implementDept: "项目单位名称2",
controlLevelId: "层次1"
},
{
projectName: "名称4",
projectName1: "二级名称1",
projectName2: "二级名称2",
implementDept: "项目单位名称3",
controlLevelId: "层次1"
},
{
projectName: "名称5",
projectName1: "二级名称1",
projectName2: "二级名称2",
implementDept: "项目单位名称4",
controlLevelId: "层次1"
},
{
projectName: "名称6",
projectName1: "二级名称1",
projectName2: "二级名称2",
implementDept: "项目单位名称5",
controlLevelId: "层次1"
}
],
tableLabel: [
{
label: "项目名称",
param: "projectName",
align: "center",
fixed: "left"
},
{
label: "总名称",
param: "implementDept",
align: "center",
children: [
{
label: "二级表头1",
param: "projectName1",
align: "center",
children: [
{
label: "三级表头1",
param: "projectName2",
align: "center"
},
{
label: "三级表头2",
param: "projectName2",
align: "center",
children: [
{
label: "三级表头1",
param: "projectName2",
align: "center"
},
{
label: "三级表头2",
param: "projectName2",
align: "center"
}
]
}
]
},
{
label: "二级表头2",
param: "projectName2",
align: "center"
}
]
},
{
label: "项目管控级次",
param: "controlLevelId",
align: "center"
}
],
// 功能列表操作按钮
tableOption: {
label: "操作",
width: "130px",
options: [
{
label: "编辑",
type: "primary",
clickFun: this.handleEdit
},
{
label: "删除",
type: "primary",
clickFun: this.handleDelete
}
]
},
tableHeight: "auto",
current: 1,
size: 100000,
total: 0,
loading: false,
selectRows: []
};
},
methods: {
handlePageSizeChange(size) {
this.size = size;
let params = { ...this.form, current: this.current, size: size };
// this.loadData(params)
},
handleCurrentChange(current) {
this.current = current;
let params = { ...this.form, current: current, size: this.size };
// this.loadData(params)
},
handleSelectTableChange(rows) {
this.selectRows = rows;
},
handleEdit() {},
handleDelete() {}
}
};
</script>
效果如下: