问题
- 直接复制后面代码,数据全为模拟,可直接运行,样式自己调整即可
- 不需要内部分页时,隐藏分页组件,
getRowTableData
函数返回返回 return children
- 项目需求
- 列表中存在多个不同分类,分类下存在相同格式的数据
- 利用
<el-table-column type="expand" width="35" :resizable="false" fixed="right">
的 type=“expand” 实现表格嵌套,放入表格,同级的 el-table-column
的宽度设置与嵌套表格宽度一样,外部 el-table-column
的prop必须设置正确,用于自定义排序功能,但是宽度超出时,内部表格会有一个小滚动条,需要hidden掉,配置 table_column--has-expand
类修改样式
- 每个分类共用表头排序功能(但是不对分类排序)
- 外部表格使用
sortable="custom"
,在表格中配置 @sort-change="handleSortChange"
,查看 handleSortChange 函数实现功能
- 单行展开和全部展开功能
- 展开函数
isExpandRow
、onExpandRow
、isExpandAll
、onExpandRows
4个函数分别用于控制 单行是否可展开、单行展开或收起、全局是否可展开、全局展开或收起
- 浮动操作效果,并自定义分类 expand 展开文案和样式
- 这里其实有点细节,
type="expand" width="35"
的宽度如果设置小于35左右,行数的宽度会不准确,存在浮动情况下造成错位,所以外部表格设置 <el-table-column label="操作" width="65" :resizable="false" fixed="right">
、<el-table-column type="expand" width="35" :resizable="false" fixed="right">
,外部一个65,一个35,嵌套表格则设置 <el-table-column label="操作" width="100" fixed="right">"
,宽度为外部 操作列和expand列 宽度的总和,并在外部表格配置一个合并行函数 objectSpanMethod
,将外部表格的 操作列和expand列 合并,隐藏 expand 图标
- 这里存在部分我的项目样式,已经尽量清除
- 未展开效果如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/372dd5d876834bb697f8ced39c520ec7.png)
- 展开并且排序效果:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/ee3e3ff8ca334928b673f4b65524f016.png)
- 全部展开效果:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/97b9cbda125f4fa790cd1557b6dcf67f.png)
实现
export function isType(obj, type) {
return Object.prototype.toString.call(obj) === '[object ' + type + ']'
}
<template>
<div class="page-content" v-loding="loading">
<!-- 按钮 -->
<div class="button_right">
<el-button type="primary" @click="onExpandRows(isExpandAll ? true : false)">{{ isExpandAll ? '全部展开' : '全部收起' }}</el-button>
</div>
<!-- 列表 -->
<div class="page-table">
<!-- :tree-props 防止识别为树形数据 -->
<el-table
:data="tableData"
ref="ref_multipleSelection"
class="table_column--has-expand"
height="100%"
row-key="parentId"
row-class-name="table_row-title"
:span-method="objectSpanMethod"
@sort-change="handleSortChange"
@expand-change="handleExpandChange"
:tree-props="{ children: 'children-1' }"
>
<el-table-column sortable="custom" prop="prop1" label="表头1" :resizable="false" min-width="300">
<template v-slot="scope">{{ scope.row.typeName }}</template>
</el-table-column>
<el-table-column sortable="custom" prop="prop2" label="表头2" :resizable="false" min-width="300" />
<el-table-column sortable="custom" prop="prop3" label="表头3" :resizable="false" min-width="300" />
<el-table-column sortable="custom" prop="prop4" label="表头4" :resizable="false" min-width="300">
<!-- 该数据存在与页面子表相同数据-所以手动置空 -->
<template v-slot="scope"></template>
</el-table-column>
<el-table-column sortable="custom" prop="prop5" label="表头5" :resizable="false" min-width="300" />
<el-table-column sortable="custom" prop="prop6" label="表头6" :resizable="false" min-width="300" />
<el-table-column label="操作" width="65" :resizable="false" fixed="right">
<template v-slot="scope">
<el-button type="text" class="expand_custom" @click="onExpandRow(scope.$index, scope.row)">
{{ isExpandRow(scope.row) ? '展开' : '收起' }}
<i :class="['el-icon-arrow-right', { 'arrow-right_rotate': !isExpandRow(scope.row) }]"></i>
</el-button>
</template>
</el-table-column>
<!-- width 给0会设置默认值大概45左右,给值小于35会造成位置错位,利用合并行做隐藏 -->
<el-table-column type="expand" width="35" :resizable="false" fixed="right">
<template slot-scope="{ row }">
<el-table :data="row.showTable" style="width: 100%" :show-header="false">
<el-table-column sortable="custom" prop="prop1" label="表头1" :resizable="false" min-width="300" />
<el-table-column sortable="custom" prop="prop2" label="表头2" :resizable="false" min-width="300" />
<el-table-column sortable="custom" prop="prop3" label="表头3" :resizable="false" min-width="300" />
<el-table-column sortable="custom" prop="prop4" label="表头4" :resizable="false" min-width="300" />
<el-table-column sortable="custom" prop="prop5" label="表头5" :resizable="false" min-width="300" />
<el-table-column sortable="custom" prop="prop6" label="表头6" :resizable="false" min-width="300" />
<!-- 该操作的width 由 type="expand" width="35" 与-->
<el-table-column label="操作" width="100" fixed="right">
<template v-slot="scope">
<el-button type="text" size="mini" @click="onDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<!-- 如果不需要分页,全部展示(消耗可能有点大),隐藏分页组件 -->
<div class="table_row_pagination">
<el-pagination
@size-change="
(val) => {
row.pageSize = val
getRowTableData(row)
}
"
@current-change="
(val) => {
row.pageNo = val
getRowTableData(row)
}
"
:current-page="row.pageNo"
:page-size="row.pageSize"
:page-sizes="[10, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="row.children.length"
/>
</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import { isType } from '@/utils'
export default {
props: {
lazy: {
default: false,
},
},
data() {
return {
tableData: [],
total: 0,
loading: false,
expandedRows: [],
}
},
computed: {
isExpandAll() {
const hasExpandRows = this.tableData.filter((row) => row.children?.length > 0)?.length || 0
if (hasExpandRows === 0) return true
return hasExpandRows !== this.expandedRows.length
},
},
created() {
if (!this.lazy) return
for (let fun of this.$options.activated || []) {
fun.call(this)
}
},
activated() {
this.getTableData()
},
methods: {
async onSearch() {
await this.getTableData()
},
handleSortChange({ column, prop, order }) {
if (!order) {
this.tableData.forEach((element) => {
element.showTable = this.getRowTableData(element)
})
return
}
const changeType = (v) => {
if (isType(v, 'Null') || isType(v, 'Undefined') || isType(v, 'Object') || isType(v, 'Array')) return ''
if (isType(v, 'Number') || !isType(v, 'String')) return v.toString()
return v
}
const isChangeLocation = (a, b, isString) => {
a = changeType(a)
b = changeType(b)
if (order === 'ascending') {
if (isString) return a.localeCompare(b)
return a < b ? -1 : 1
} else if (order === 'descending') {
if (isString) return b.localeCompare(a)
return a > b ? -1 : 1
}
}
for (let props of this.tableData) {
props.showTable.sort((a, b) => {
return isChangeLocation(a[prop], b[prop], true)
})
}
return false
},
onExpandRows(isExpand) {
this.tableData.forEach((row) => {
this.$refs['ref_multipleSelection']?.toggleRowExpansion(row, isExpand)
})
},
isExpandRow(row) {
return this.expandedRows.findIndex((item) => item.parentId === row.parentId) === -1
},
onExpandRow(index, row) {
this.$refs['ref_multipleSelection']?.toggleRowExpansion(row, this.isExpandRow(row))
},
handleExpandChange(row, expandedRows) {
this.expandedRows = expandedRows
},
objectSpanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0 || columnIndex === 6) {
return {
rowspan: 1,
colspan: 2,
}
} else if (columnIndex === 1 || columnIndex === 7) {
return {
rowspan: 0,
colspan: 0,
}
}
},
getRowTableData(row) {
const { children, pageNo, pageSize } = row
row.showTable = (children || []).slice((pageNo - 1) * pageSize, pageNo * pageSize)
return row.showTable
},
onDetail(row) {
console.log(onDetail, 'row', row)
},
async getTableData() {
const _interface = () =>
new Promise((resolve) => {
const result = {
code: 200,
data: Array.from({ length: 6 }).map((v, i) => {
v = {}
v.parentId = '父节点id' + i
v.typeName = `类型${i + 1}`
v.children = Array.from({ length: 21 }).map((val, index) => {
val = {}
val.id = index + '测试数据'
val.prop1 = `表头1-${index + 1}内容`
val.prop2 = `表头2-${index + 1}内容`
val.prop3 = `表头3-${index + 1}内容`
val.prop4 = `表头4-${index + 1}内容`
val.prop5 = `表头5-${index + 1}内容`
val.prop6 = `表头6-${index + 1}内容`
return val
})
return v
}),
}
resolve(result)
})
this.loading = true
const { data, code } = await _interface().finally(() => (this.loading = false))
if (code != 200) return
console.log(data, code)
this.$refs['ref_multipleSelection']?.clearSort()
this.tableData = (data || []).map((v) => {
v.pageNo = 1
v.pageSize = 10
v.showTable = this.getRowTableData(v)
return v
})
},
},
}
</script>
<style lang="scss" scoped>
.page-content {
background: #fff;
padding: 16px;
border-radius: 0;
width: 100%;
height: 500px;
.page-table {
height: calc(100% - 46px);
}
.button_right {
text-align: right;
margin-bottom: 16px;
}
.expand_custom {
color: #1f2b44;
font-weight: 400;
.el-icon-arrow-right {
transition: transform 0.3s;
}
.arrow-right_rotate {
transform: rotate(90deg);
}
}
.table_row_pagination {
background: #fff;
margin-left: auto;
display: flex;
justify-content: flex-end;
padding: 6px;
padding-right: 100px;
}
::v-deep {
.table_column--has-expand {
.el-table__body .el-table__expanded-cell {
padding: 0;
.el-table__body-wrapper {
overflow-x: hidden;
}
.el-table__fixed {
&::before {
content: '';
visibility: hidden;
}
}
}
}
.table_row-title {
& > td {
background-color: #edf4ff;
}
& > td:nth-of-type(1) {
font-weight: bold;
& > div {
color: #1f2b44;
}
}
}
}
}
</style>