element-ui table 实现表格嵌套多层表格
项目需求:要求点击行中的“其他”单元格中的字段展开相应的子表
1.点击“扩展属性”出现"扩展属性"子表,点击“国家/地区”出现国家/地区子表
2.在国家/地区子表中还有个“生命周期字段”,点击“生命周期”出现“生命周期”的子表

实现方案:使用table 中的type="expand" 同时把展开图标隐藏,给表格文字“扩展属性”、“国家/地区”添加点击事件,通过点击事件实现不同子表的切换

当时做的时候是用v-if来实现对各级子表的显示和隐藏但是出现了问题就是,当点击顺序是 扩展属性-->国家/地区-->生命周期时会出现生命周期子表没有展示出来,如图:
当时使用$nextTick()/$forceUpdate()都没有用,把v-if换成v-show就没有问题了。看来频繁切换的显示和隐藏就要使用v-show

js代码:


css:对type=expand属性的箭头展开样式隐藏

同时要让width=1 的那一列的边框去掉

这时当缩放浏览器时会出现表头错位的情况,需要再加一个样式

下面是完整代码,搜索条件和表格字段有点多,大家可以忽略。若有不对的地方,欢迎指出,谢谢!
<template>
<div>
<el-form size="mini" :model="searchData" class="searchBox">
<el-row :gutter="24">
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="4">
<el-form-item label="skuId" class="textArea">
<el-input
v-model="searchData.skuIds"
type="textarea"
autosize
placeholder="请输入"
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="4">
<el-form-item label="sku编码" class="textArea">
<el-input
v-model="searchData.skuCodes"
type="textarea"
autosize
placeholder="请输入"
></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="4">
<el-form-item label="sku名称(中文)">
<el-input v-model="searchData.skuNameCn" placeholder="请输入"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="4">
<el-form-item label="Sku名称(英文)">
<el-input v-model="searchData.skuNameEn" placeholder="请输入"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="4">
<el-form-item>
<el-button type="primary" @click="getTableData('init')">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-table
v-loading="loading"
row-key="skuId"
size="mini"
class="tableBox"
:data="tableList"
style="width:100%;"
border
:header-cell-style="{ background: '#fafafa' }"
:expand-row-keys="expands"
>
<el-table-column type="expand" width="1">
<template slot-scope="scopeOut">
<el-table
v-show="scopeOut.row.countryShow"
ref="countryArea"
:data="scopeOut.row.nations"
border
size="mini"
row-key="id"
:expand-row-keys="expandsInner"
:header-cell-style="{ background: '#fafafa' }"
>
<el-table-column type="expand" width="1">
<template slot-scope="scopeInner">
<el-table
v-show="scopeInner.row.lifeShow"
:data="scopeInner.row.lifecycles"
border
size="mini"
:header-cell-style="{ background: '#fafafa' }"
>
</el-table-column>
<el-table-column
prop="lifecycleNameCn"
label="生命周期中文名"
:show-overflow-tooltip="true"
>
</el-table-column>
<el-table-column
prop="lifecycleTime"
label="生命周期时间"
:show-overflow-tooltip="true"
>
</el-table-column>
<el-table-column prop="skuLifecycleStatus" label="生命周期状态">
<template slot-scope="scopes">
<el-tag v-if="scopes.row.skuLifecycleStatus === 0" type="danger">失效</el-tag>
<el-tag v-if="scopes.row.skuLifecycleStatus === 1" type="info">有效</el-tag>
</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
<el-table-column
prop="nationTwoAbbr"
label="国家地区二位码"
:show-overflow-tooltip="true"
>
</el-table-column>
<el-table-column
prop="nationNameCn"
label="国家地区中文名"
:show-overflow-tooltip="true"
>
</el-table-column>
<el-table-column label="其他" :show-overflow-tooltip="true">
<template slot-scope="scopeInner">
<span style="color:#09F;cursor: pointer;" @click="expandTable(scopeInner.row)"
>生命周期</span
>
</template>
</el-table-column>
</el-table>
<el-table
v-show="scopeOut.row.extenShow"
:data="scopeOut.row.extendAttributes"
row-key="id"
border
size="mini"
:header-cell-style="{ background: '#fafafa' }"
>
<el-table-column
prop="attrValueCn"
label="扩展属性值(中文)"
:show-overflow-tooltip="true"
>
</el-table-column>
<el-table-column
prop="attrValueEn"
label="扩展属性值(英文)"
:show-overflow-tooltip="true"
>
</el-table-column>
<el-table-column prop="valueDataType" label="数据类型">
<template slot-scope="scopes">
<el-tag v-if="scopes.row.valueDataType === 1" size="mini">字符串</el-tag>
<el-tag v-if="scopes.row.valueDataType === 2" type="info" size="mini"
>带单位实数等</el-tag
>
</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
<el-table-column
property="createTime"
label="创建时间"
:show-overflow-tooltip="true"
:render-header="renderHeader"
></el-table-column>
<el-table-column
property="updateTime"
label="更新时间"
:show-overflow-tooltip="true"
:render-header="renderHeader"
></el-table-column>
<el-table-column label="其他" :show-overflow-tooltip="true" width="140">
<template slot-scope="scopeOut">
<span style="color:#09F;cursor: pointer;" @click="expandExTable(scopeOut.row)"
>扩展属性</span
>
<span>|</span>
<span style="color:#09F;cursor: pointer;" @click="expandCounTable(scopeOut.row)"
>国家/地区</span
>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
:current-page="pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination>
</div>
</template>
<script>
import Crypto from '@/utils/crypto.js';
import diySelect from '@/components/Select/diySelect.vue';
export default {
components: {
diySelect,
},
data() {
return {
searchData: {},
tableList: [],
pageNum: 1,
pageSize: 10,
total: 0,
loading: false,
secretKey: '',
pickerOptions: {
shortcuts: [
{
text: '最近一周',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
},
},
{
text: '最近一个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
},
},
{
text: '最近三个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
},
},
{
text: '最近六个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 180);
picker.$emit('pick', [start, end]);
},
},
],
},
dataList: [],
expands: [],
expandsCoun: [],
lifeShow: false,
countryShow: false,
expandsInner: [], // 生命周期展开数组
counFlag: true,
colorCnsList: [],
colorEnsList: [],
colorLocalsList: [],
carrierNameCnsList: [],
carrierNameEnsList: [],
certifiedModelsList: [],
};
},
created() {
this.getTableData('init');
this.getSkuItem();
},
methods: {
renderHeader(h, data) {
return h('span', [
h(
'el-tooltip',
{
attrs: {
class: 'item',
effect: 'dark',
content: data.column.label,
placement: 'top',
},
},
[h('span', data.column.label)]
),
]);
},
expandTable(row) {
row.lifeShow = !row.lifeShow;
if (row.lifeShow) {
this.expandsInner.push(row.id);
} else {
this.expandsInner = this.expandsInner.filter(item => item !== row.id);
}
console.log(this.expandsInner, '生命周期');
},
expandCounTable(row) {
row.countryShow = !row.countryShow;
if (row.countryShow) {
this.expands.push(row.skuId);
} else {
this.expands = this.expands.filter(item => item !== row.skuId);
}
row.extenShow = false;
console.log(this.expands, '国家/地区');
},
expandExTable(row) {
row.extenShow = !row.extenShow;
if (row.extenShow) {
this.expands.push(row.skuId);
} else {
this.expands = this.expands.filter(item => item !== row.skuId);
}
row.countryShow = false;
console.log(this.expands, '扩展属性');
},
reset() {
this.searchData = {};
this.getTableData('init');
},
getTableData(init) {
this.expandsInner = [];
this.expands = [];
let creatObj = {};
if (this.searchData.createDate !== undefined && this.searchData.createDate !== null) {
creatObj = {
startCreateTime: this.searchData.createDate[0],
endCreateTime: this.searchData.createDate[1],
};
} else {
delete this.searchData.createDate;
creatObj = {};
}
let updateObj = {};
if (this.searchData.updateDate !== undefined && this.searchData.updateDate !== null) {
updateObj = {
startUpdateTime: this.searchData.updateDate[0],
endUpdateTime: this.searchData.updateDate[1],
};
} else {
delete this.searchData.updateDate;
updateObj = {};
}
const obj = {
skuIds: this.searchData.skuIds ? this.searchData.skuIds.split(/[(\r\n)\r\n]+/) : [],
skuCodes: this.searchData.skuCodes ? this.searchData.skuCodes.split(/[(\r\n)\r\n]+/) : [],
carrierIds: this.searchData.carrierIds
? this.searchData.carrierIds.split(/[(\r\n)\r\n]+/)
: [],
carrierCodes: this.searchData.carrierCodes
? this.searchData.carrierCodes.split(/[(\r\n)\r\n]+/)
: [],
normalizationIds: this.searchData.normalizationIds
? this.searchData.normalizationIds.split(/[(\r\n)\r\n]+/)
: [],
normalizationCodes: this.searchData.normalizationCodes
? this.searchData.normalizationCodes.split(/[(\r\n)\r\n]+/)
: [],
skuNameCn: this.searchData.skuNameCn,
skuNameEn: this.searchData.skuNameEn,
colorCns: this.searchData.colorCns,
colorEns: this.searchData.colorEns,
colorLocals: this.searchData.colorLocals,
carrierNameCns: this.searchData.carrierNameCns,
carrierNameEns: this.searchData.carrierNameEns,
certifiedModels: this.searchData.certifiedModels,
...updateObj,
...creatObj,
};
if (init) {
this.pageNum = 1;
this.pageSize = 10;
}
this.loading = true;
const param = {
pageNum: this.pageNum,
pageSize: this.pageSize,
...obj,
};
this.$api.productTree.skuList({ ...param }).then(res => {
if (res.data) {
this.tableList = res.data.content;
this.tableList.forEach(item => {
this.$set(item, 'countryShow', false);
this.$set(item, 'extenShow', false);
});
this.total = res.data.total;
}
this.loading = false;
});
},
handleCurrentChange(page) {
this.pageNum = page;
this.getTableData();
},
handleSizeChange(pageSize) {
this.pageSize = pageSize;
this.getTableData();
},
getSkuItem() {
this.$api.productTree.skuItem().then(res => {
if (res.data) {
this.colorCnsList = [];
this.colorEnsList = [];
this.colorLocalsList = [];
this.carrierNameCnsList = [];
this.carrierNameEnsList = [];
this.certifiedModelsList = [];
for (const key in res.data) {
const itemArr = [];
res.data[key].forEach(item => {
const obj = {
name: item,
};
itemArr.push(obj);
});
if (key === 'colorCns') {
this.colorCnsList = itemArr;
} else if (key === 'colorEns') {
this.colorEnsList = itemArr;
} else if (key === 'colorLocals') {
this.colorLocalsList = itemArr;
} else if (key === 'carrierNameCns') {
this.carrierNameCnsList = itemArr;
} else if (key === 'carrierNameEns') {
this.carrierNameEnsList = itemArr;
} else if (key === 'certifiedModels') {
this.certifiedModelsList = itemArr;
}
}
}
});
},
},
};
</script>
<style lang="scss" scoped>
.mainContent {
margin: 20px;
}
.form-content {
font-weight: 600;
}
.pagination {
float: right;
margin-top: 10px;
}
.import {
margin: 0 0 20px 0;
}
.el-dialog .el-dialog__body {
padding-top: 0 !important;
}
.el-form-item {
margin-bottom: 15px !important;
}
.el-select,
.el-cascader,
.el-date-editor.el-input {
width: 100%;
}
.searchBox >>> .el-form-item {
display: flex;
width: 100%;
height: 30px;
.el-form-item__label {
white-space: nowrap;
}
.el-range-editor--mini.el-input__inner {
height: 30px;
}
.el-form-item__content {
flex: 1;
.el-date-editor--daterange.el-input,
.el-date-editor--daterange.el-input__inner,
.el-date-editor--timerange.el-input,
.el-date-editor--timerange.el-input__inner {
width: 100% !important;
}
}
}
.tableBox >>> .el-table__expand-icon:after {
content: '';
}
.tableBox >>> .el-table__expand-icon > i {
display: none;
}
.tableBox >>> .el-table__expand-column {
border-right: none;
}
</style>
故不积跬步,无以至千里;不积小流,无以成江海。

浙公网安备 33010602011771号