示例:
组件代码:
<template>
<div id="table-box" :style="'height:' + height + ';'">
<table cellspacing="0" style="text-align: center;" cellpadding="0" width="100%" align="center">
<thead>
<tr>
<th v-if="isIndex" width="60">序号</th>
<th v-if="isCheck" width="60">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange" />
</th>
<th v-for="(header, index) in headers" :key="index" :width="header.width">{{ header.label }}</th>
</tr>
</thead>
<tbody v-if="tableData.length">
<template v-for="(order, index) in tableData">
<!-- 序号 -->
<!-- eslint-disable-next-line vue/require-v-for-key -->
<tr class="nodeRow">
<td id="isShowId">
<i :class="order.isShow ? 'el-icon-minus' : 'el-icon-plus'" @click="order.isShow = !order.isShow" />
</td>
<td :colspan="getColspan()">
<slot name="node" :row="order" :$index="index" />
</td>
</tr>
<!-- 每一行 -->
<!-- eslint-disable-next-line vue/no-lone-template -->
<template v-if="order.isShow">
<tr v-for="(row, ind) in order.list" :key="index + '-' + ind">
<td v-if="isIndex">{{ row.$index }}</td>
<td v-if="isCheck">
<el-checkbox v-model="row.checked" @change="handleCheckedCitiesChange" />
</td>
<td v-for="(item,x) in headers" :key="x">
<slot
v-if="item.slotName"
:name="item.slotName"
:row="row"
:$index="ind"
:parent="order"
:$parentIndex="index"
/>
<div v-else-if="item.isParentVal">{{ order[item.prop] }}</div>
<div v-else>{{ row[item.prop] }}</div>
</td>
</tr>
</template>
</template>
</tbody>
<tbody v-else>
<tr>
<td style="border: none;" :colspan="getColspan() + 1">暂无数据</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: {
// 表格高度
height: {
type: String,
default: 'auto'
},
// 是否显示序号
isIndex: {
default: true,
type: Boolean
},
// 是否显示多选
isCheck: {
default: true,
type: Boolean
},
// 表头数据
headers: {
type: Array,
default: () => {
return []
}
},
// 表格数据
data: {
type: Array,
default: () => {
return []
}
},
// 子集字段名称
children: {
type: String,
default: 'list'
}
},
data() {
return {
checkAll: false, // 全选框值
isIndeterminate: false, // 全选框状态
tableData: [] // 数据
}
},
watch: {
data: {
handler(val) {
this.init(val)
},
deep: true, // 深度监听
immediate: true // 初次监听即执行
}
},
mounted() {
},
methods: {
// 初始化数据
init(data) {
let i = 0
const arr = JSON.parse(JSON.stringify(data))
arr.forEach(row => {
const list = JSON.parse(JSON.stringify(row[this.children]))
row.list = list
delete row[this.children]
this.$set(row, 'isShow', true)
row.list.forEach(item => {
this.$set(item, 'checked', false)
i++
this.$set(item, '$index', i)
})
})
this.tableData = arr
},
// 多选框---全选
handleCheckAllChange(val) {
this.tableData.forEach(item => {
item.list.forEach(v => {
v.checked = val
})
})
this.isIndeterminate = false
this.$emit('changeChecked', this.getCheckedList())
},
// 多选框---单个选择
handleCheckedCitiesChange(value) {
let isAll = true
let isCheck = false
for (let index = 0; index < this.tableData.length; index++) {
const item = this.tableData[index]
for (let index = 0; index < item.list.length; index++) {
const v = item.list[index]
if (!v.checked) {
isAll = false
} else {
isCheck = true
}
}
}
this.checkAll = isAll
this.isIndeterminate = isCheck && !isAll
this.$emit('changeChecked', this.getCheckedList())
},
// 获取选中的数据
getCheckedList() {
const arr = []
this.tableData.forEach(item => {
// 深拷贝数据
const obj = JSON.parse(JSON.stringify(item))
delete obj.isShow
const newArr = []
obj.list.forEach(v => {
if (v.checked) {
delete v.checked
delete v.$index
newArr.push(v)
}
})
obj[this.children] = newArr
delete obj.list
if (newArr.length > 0) {
arr.push(obj)
}
})
return arr
},
// 获取合并单元格数量
getColspan() {
let i = 0
if (this.isIndex) i++
if (this.isCheck) i++
return this.headers.length + i - 1
}
}
}
</script>
<style scoped lang="scss">
#table-box {
overflow: auto;
border: 1px solid #d6d6d6;
table {
overflow: auto;
}
th,
td {
border: 1px solid #d6d6d6;
padding: 10px 10px;
}
th {
border-right: none;
border-top: none;
border-top: none;
background-color: #edf4fa !important;
position: sticky;
top: 0;
/* 首行永远固定在头部 */
z-index: 9;
white-space: nowrap;
}
td:first-child,
th:first-child {
position: sticky;
left: 0;
/* 首列永远固定在左侧 */
z-index: 9;
border-left: none;
background-color: #fff;
border-right: 1px solid #d6d6d6;
}
th:first-child {
z-index: 10;
/*表头的首列要在上面*/
}
// td th第二列
td:nth-child(2),
th:nth-child(2) {
border-left: none;
}
th>div {
width: 100%;
white-space: normal;
word-wrap: break-word;
word-break: break-all;
}
td {
border-right: none;
border-top: none;
}
.nodeRow {
background-color: #eaf3ff;
text-align: left;
border-top: #d6d6d6 1px solid;
border-bottom: #d6d6d6 1px solid;
td {
background-color: #eaf3ff;
}
}
#isShowId {
padding-left: 20px;
i {
border: 1px solid #d6d6d6;
cursor: pointer;
}
}
}
</style>
使用:
<nodeTable v-loading="loading" children="orderBarcodeVoList" height="500px" :headers="headers" :data="tableData" @changeChecked="changeChecked">
<!-- 展开节点行插槽 row本行数据 $index 本行下标 -->
<template #node="data">
{{ data.row.orderNo }}
<el-button class="btn-qbdy" type="text">全部打印</el-button>
</template>
<!-- 单元格插槽 row本行数据 $index 本行下标 parent父级数据 $parentIndex父级下标 -->
<template #subtotal="data">
{{ Number(data.parent.settlPrice)*Number(data.row.ticketCount) }}
</template>
<template #caozuo="data">
<el-button type="text">票重打</el-button>
</template>
</nodeTable>
headers数据参数:
headers: [{
prop: 'barcodeId',
label: '票号'
}, {
prop: 'ticketName',
label: '票种名称',
isParentVal: true // 是否从父级取值
}, {
prop: 'ticketFlag',
label: '出票方式',
slotName: 'ticketFlag'
}]