<template>
<a-spin :spinning="loading">
<div class="sc-table" :style="{height}">
<div class="sc-table-header">
<div></div>
<div>
<!-- 字段显示隐藏 -->
<ColumnCheck v-if="showColumnCheck" :columns="columns" :showColumnCache="showColumnCache" :showColumns="showColumns" @change="onCheckBoxChange"></ColumnCheck>
</div>
</div>
<div class="sc-table-content" ref="tableContent">
<div class="sc-table-scroll" :style="{width: `${tableWidth}px`}">
<div class="sc-table-thead">
<table class="sc-table-level" border="1">
<TableColgroup :columns="columnsShow"></TableColgroup>
<TableHead :columns="columnsShow">
<template #checkbox>
<a-checkbox
v-if="tableData.length"
:indeterminate="indeterminate"
:checked="checkAll"
@change="onCheckAllChange">
</a-checkbox>
</template>
</TableHead>
</table>
</div>
<a-checkbox-group class="sc-table-body" v-model="selectedRowKeys" @change="selectionChange">
<table class="sc-table-level" border="1">
<TableColgroup :columns="columnsShow"></TableColgroup>
<TrComponent
v-model="selectedRowKeys"
:rowKey="rowKey"
:serialKey="serialKey"
:columns="columnsShow"
:data="tableData"
:checkStrictly="checkStrictly"
:theadNums="theadNums"
:checkDisabled="checkDisabled"
@updateCheckAllKeys="updateCheckAllKeys"
@updateCheckAllRows="updateCheckAllRows">
<!-- 自定义操作 -->
<template
v-for="c in scopedSlots"
:slot="c.scopedSlots && c.scopedSlots.customRender ? c.scopedSlots.customRender : ''"
slot-scope="{row, text}">
<slot
:name="c.scopedSlots.customRender"
:row="row"
:text="text"
></slot>
</template>
<!-- 一级层级扩展 -->
<template slot="expandedRowRender" slot-scope="{row}">
<slot name="expandedRowRender" :row="row"></slot>
</template>
</TrComponent>
</table>
</a-checkbox-group>
</div>
<!-- <div class="sc-table-fixed-right">
<table class="sc-table-level" border="1"> -->
<!-- <TableColgroup :columns="columnsShow"></TableColgroup>
<TrComponent :columns="columnsShow" :data="tableData">
<template
v-for="c in columnsShow"
:slot="c.scopedSlots && c.scopedSlots.customRender ? c.scopedSlots.customRender : ''"
slot-scope="{row, text}">
<slot
v-if="c.scopedSlots && c.scopedSlots.customRender"
:name="c.scopedSlots.customRender"
:row="row"
:text="text"
></slot>
</template>
</TrComponent> -->
<!-- </table>
</div> -->
</div>
<div class="sc-table-footer">
<div></div>
<a-pagination
v-if="isPagination && page.total"
v-model="page.current"
:total="page.total"
:pageSize.sync="page.pageSize"
:page-size-options="pagination.pageSizeOptions"
:show-size-changer="pagination.showSizeChanger"
:show-quick-jumper="pagination.showQuickJumper"
:show-total="pagination.showTotal"
@change="getTableList"
@showSizeChange="getTableList"
></a-pagination>
</div>
</div>
</a-spin>
</template>
<script>
import { httpAction } from '@ythApi/ythApiManage'
export default {
name: "",
components: {
ColumnCheck: () => import('./columnCheck'),
TrComponent: () => import('./TrComponent'),
TableColgroup: () => import('./tableColgroup'),
TableHead: () => import('./tableHead'),
},
props: {
// 当columns 存在checkbox=true时有效,当前选中项的 rowKey 数组
value: {
type: [Array],
},
// 表格行 key 的取值,可以是字符串或一个函数,当checkbox=true时为必要的属性
rowKey: {
type: [String, Function],
default: 'id',
},
// 表格序号key的取值
serialKey: {
type: [String, Function],
default: null,
},
// 表格列的配置
columns: {
type: Array,
required: true,
default: () => ([]),
},
// 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false。false关联,true不互相关联
checkStrictly: {
type: Boolean,
default: false,
},
// 在显示复选框的情况下,节点选择框是否禁用, 返回true 或 false;
checkDisabled: {
type: Function
},
// table 数据请求配置
tableServers: {
type: Object,
default: () => {
return { server: null, method: 'post' }
},
},
//查询参数
query: {
type: Object,
required: false,
default: () => {}
},
// 是否显示字段显隐项
showColumnCheck: {
type: Boolean,
default: true,
},
// 是否使用显示字段显隐项缓存
showColumnCache: {
ype: Boolean,
default: false,
},
// 初始化显影项
showColumns: {
type: Array,
required: false,
default: () => ([]),
},
// 是否分页查询
isPagination: {
type: Boolean,
required: false,
default: true,
},
// 分页参数
pagination: {
type: Object,
required: false,
default: function () {
return {
total: 0,
current: 1,
pageSize: 10,
pageSizeOptions: ['10', '20', '30'],
showQuickJumper: true,
showSizeChanger: true,
showTotal: (total, range) => `共${total}条`,
}
}
},
// 查询参数是否装换
isToLine:{
type: Boolean,
required: false,
default: true,
},
height: {
type: String,
},
},
data() {
return {
loading: false,
page: {
current: this.pagination.current || 1,
total: this.pagination.total || 0,
pageSize: this.pagination.pageSize || 10,
},
theadNums: 0,
tableWidth: 0,
scopedSlots: [],
tableData: [],
columnsShow: this.showColumnCheck ? [] : this.columns, // 列表配置
updateComponent: '',
checkAllKeys: [],
checkAllRows: [],
selectedRowKeys: this.value || [], // table checkbox 选中项的 rowKey 数组
selectedRows: [], // table checkbox 选中项的 rowKey 数组
}
},
computed: {
// 所有值
tableDataAll() {
let tableData = this.tableData
//递归获取子数据
function getAllData(dataArray) {
let result = []
dataArray.forEach(item => {
result.push(item)
if (item.children&&item.children.length>0) {
result = result.concat(getAllData(item.children))
}
})
return result
}
return getAllData(tableData)
},
// 设置 checkbox 是否全选状态
checkAll() {
return this.selectedRowKeys.length === this.checkAllKeys.length;
},
// 设置 checkbox indeterminate 状态,只负责样式控制
indeterminate() {
return this.selectedRowKeys.length > 0 && this.selectedRowKeys.length < this.checkAllKeys.length;
},
// 固定列
},
watch: {
tableServers: {
immediate: true,
deep: true,
handler: function(newVal,oldVal) {
if (JSON.stringify(newVal)!==JSON.stringify(oldVal)) this.getTableList(1)
}
},
selectedRowKeys: {
immediate: true,
deep: true,
handler: function(newVal,oldVal) {
if (JSON.stringify(newVal)!==JSON.stringify(oldVal)) {
this.$emit('input', newVal)
}
}
}
},
methods: {
// 暴露给父级组件使用,作为静态注入数据
setTableData(data) {
this.tableData = this.$listeners.recastData ? this.$listeners.recastData(data) : data
},
//获取页面数据
getTableData() {
return this.tableData
},
//列表显隐回调
onCheckBoxChange({columns, tableWidth, scopedSlots, theadNums}) {
this.theadNums = theadNums;
this.tableWidth = tableWidth + 8 + 2;
this.scopedSlots = scopedSlots;
this.columnsShow = columns
this.updateComponent = + new Date();
},
//刷新
reload () {
this.getTableList(1)
},
/**
* 获取当前table页数据
* @param current {number} 当前页
*/
getTableList(current) {
if (!this.tableServers.server) return;
if (!!current) this.page.current = current;
// 初始化
this.loading = true
this.tableData = []
const queryParams = this.getQueryParams()
const action = this.tableServers.action || httpAction
const method = this.tableServers.method || 'POST'
action( this.tableServers.server, queryParams, method).then((res) => {
let tableData = []
if (Array.isArray(res)) {
this.pagination = false
tableData = res
}
else if (Array.isArray(res.result)) {
this.pagination = false
tableData = res.result
}
else {
if (this.isPagination && (res.total || res.total == 0)) {
this.page.total = Array.isArray(res.total) ? res.total[0] : res.total;
}
tableData = res.result?.records || res.rows || []
}
this.setTableData(tableData)
// 接口数据加载成功,回调事件
this.$nextTick(()=>{
this.$emit("onLoadSuccess",this.tableData);
})
}).finally(() => {
this.loading = false
})
},
/**
* 获取查询参数
*/
getQueryParams() {
//基础参数
let data = {
...this.query
}
// 分页
if (this.isPagination) {
data.page = this.page.current
data.rows = this.page.pageSize
}
return data
},
/**
* 驼峰转换下划线
*/
toLine(name) {
return name.replace(/([A-Z])/g, '_$1').toLowerCase()
},
/**
* table checkbox 选中项发生改变时触发
*/
selectionChange(checkedValues) {
this.selectedRowKeys = checkedValues;
this.selectedRows = checkedValues.map(value => {
return this.tableDataAll.filter(row => row[this.rowKey] === value)[0]
})
this.$emit('selection-change', checkedValues, this.selectedRows)
},
/**
* 全选发生变化时回调函数
*/
onCheckAllChange(e) {
this.selectedRowKeys = e.target.checked ? this.checkAllKeys : [];
this.selectedRows = e.target.checked ? this.checkAllRows : [];
},
/**
* 更新当前选中checkbox全部keys数据
*/
updateCheckAllKeys(allKeys) {
this.checkAllKeys = allKeys
},
/**
* 更新当前选中checkbox全部rows数据
* */
updateCheckAllRows(allRows) {
this.checkAllRows = allRows
}
}
}
</script>
<style lang="less" >
.sc-table {
width: 100%;
height: 100%;
min-height: 300px;
display: flex;
flex-direction: column;
.sc-table-content {
width: 100%;
flex: 1;
overflow: auto;
background-color: #ffffff;
.sc-table-scroll {
min-width: 100%;
height: 100%;
display: flex;
flex-direction: column;
table {
width: 100%;
background-color: #ffffff;
border-style: solid;
border-color: #e8e8e8;
table-layout: fixed;
}
.sc-table-thead {
overflow: hidden scroll;
}
.sc-table-body {
width: 100%;
flex: 1;
overflow: hidden scroll;
}
}
}
}
.sc-table-header,
.sc-table-footer {
display: flex;
justify-content: space-between;
margin: 10px 0;
}
.sc-table-header {
.btn-icon {
border-radius: 50%;
border: 1px solid #d9d9d9;
background-color: #fff;
width: 32px;
height: 32px;
line-height: 32px;
text-align: center;
font-size: 15px;
cursor: pointer;
&:hover {
opacity: .9;
}
}
}
.sc-table-level {
.icon-expand {
margin-right: 8px;
cursor: pointer;
display: inline-block;
&::before {
content: '-';
width: 18px;
height: 18px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #e8e8e8;
border-radius: 2px;
background-color: #fff;
box-sizing: border-box;
transition: color .2s, border-color .2s;
}
&:hover::before {
color: #40a9ff;
border-color: #40a9ff;
}
}
tr[data-row-collapse="false"] {
.icon-expand::before {
content: '+';
}
}
tr.table-expanded-row {
> td {
padding: 0;
> * {
padding: 10px;
}
> a,
> span {
display: inline-block;
}
}
}
tr.table-expanded-row,
tr.table-expanded-row:hover {
background: #fbfbfb;
}
tr:hover {
background: rgba(24, 144, 255, 0.18);
}
// tr[data-highlight-1] {
// background: rgba(24, 144, 255, 0.08);
// }
// tr[data-highlight-2] {
// background: rgba(24, 144, 255, 0.06);
// }
// tbody > tr:nth-of-type(2n) {
// background: rgba(24, 144, 255, 0.03);
// }
tr {
transition: background 1s;
}
th {
background: #fafafa;
}
td {
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
td, th {
padding: 10px;
box-sizing: border-box;
}
}
</style>
该组件的构成为
<header><显隐组件></header>
<content>
<表格标题组件head>
<表格内容组件body>
</content>
<footer><分页组件></footer>
解决方案同时满足以下条件:
1.表头在y轴滚动时固定
2.x轴和y轴滚动条始终可见
3.复选框和序号列随x轴滚动而滚动