git地址:项目git地址(https://github.com/hgg111/vue2AntdTablePage.git)
实现效果:
代码:
RemberTableHeader.vue组件: 记忆表头组件。将用户选择展示的表头数组存储在 localStorage 中,进入页面时读取,实现记忆表头功能。
<template>
<div style="position: absolute;right: -5px;width: 30px;background-color: #f5f7fd;">
<a-popover placement="bottomRight" :arrow="true">
<a><a-icon type="setting"></a-icon></a>
<template slot="title">
表头显示字段
</template>
<template slot="content">
<div style="max-height: 200px;overflow-y: auto;">
<div>
<a-checkbox value="all" :checked="checkedAll" @change="allChange">全选</a-checkbox>
</div>
<a-checkbox-group v-model="tableHeader" style="display:flex;flex-direction: column;align-items: flex-start;"
@change="tableHeaderChange">
<a-checkbox v-for="item in tableHeaderOptions" :key="item" :value="item">{{
item
}}</a-checkbox>
</a-checkbox-group>
</div>
</template>
</a-popover>
</div>
</template>
<script>
export default {
props: {
// 表头
allColumns: {
type: Array,
default: ()=>[],
},
// 表名(唯一)
tableName: {
type: String,
default: ''
},
// 是否有报告操作列
hasReportAction: {
type: Boolean,
default: false
},
// 是否有基本操作列
hasAction: {
type: Boolean,
default: true
},
// 是否有序号列
hasXuhao: {
type: Boolean,
default: true
},
},
data() {
return {
// 全选框的状态
checkedAll: true,
// 所有可选择的表头
tableHeaderOptions: [],
// 选择的表头
tableHeader: [],
// 操作列内容
actionContent: [],
// 报告操作列内容
reportActionContent: [],
// 序号列内容
xuhaoContent: [],
};
},
watch:{
allColumns: {
handler(newVal) {
if(newVal.length != 0){
Object.assign(this.allColumns,newVal)
this.init()
}
},
deep: true,
immediate: true
},
},
mounted() {
if(this.allColumns.length != 0){
this.init()
}
},
methods: {
init() {
this.tableHeaderOptions = []
// 获取除了序号列和操作列的其他列
this.allColumns.forEach(item => {
if (item.title && item.title != '序号' && item.title != '报告操作' && item.title != '授权操作') {
this.tableHeaderOptions.push(item.title)
}
if (item.key == 'xuHao') {
this.xuhaoContent = item
}
if (item.key == 'reportAction' || item.key == 'authorization') {
this.reportActionContent = item
}
if (item.key == 'action') {
this.actionContent = item
}
})
this.tableHeader = this.tableHeaderOptions
// 从localStorage中获取当前表格之前存储的表头
if (localStorage.getItem(this.tableName) != null) {
this.tableHeader = localStorage.getItem(this.tableName).split(',')
this.tableHeaderChange()
}
this.tableHeaderChange()
},
// 点击全选框
allChange(value) {
this.checkedAll = value.target.checked
if (this.checkedAll == false) {
this.tableHeader = []
} else {
this.tableHeader = this.tableHeaderOptions
}
this.tableHeaderChange()
},
// 表头选择改变
tableHeaderChange() {
// 向 localStorage 中存储当前选择的表头
localStorage.setItem(this.tableName,this.tableHeader)
var length = 2
if(this.hasReportAction){
length = 3
}else if(!this.hasAction){
length = 1
}
if(!this.hasXuhao){
length -= 1
}
// 判断是否全选,设置选择框的选中状态
if (this.tableHeader.length < this.allColumns.length - length) {
this.checkedAll = false
} else {
this.checkedAll = true
}
var header = this.allColumns.filter(item => this.tableHeader.indexOf(item.title) > -1)
// 向表头中添加序号列和操作列
if(this.hasXuhao){
header.unshift(this.xuhaoContent)
}
if(this.hasReportAction){
header.push(this.reportActionContent)
}
if(this.hasAction){
header.push(this.actionContent)
}
this.$emit('columnChange',header)
},
},
};
</script>
<style>
.ant-checkbox-group :first-child {
margin-left: 8px;
}
</style>
记忆表头功能效果:
TablePage.vue:表格页面组件。页面包括树结构数据展示,树搜索,表格展示,表格筛选功能,并且实现响应式布局。
<template>
<div id="page">
<a-layout>
<!-- 树搜索 -->
<a-layout-sider v-if='treeDisplay'>
<a-input-search v-if="hasTreeSearch" style="margin-bottom: 8px" placeholder="请输入名称"
@search="onSearch" />
<a-tree expand-action="doubleclick" :tree-data="treeData" :expandedKeys="expandedKeys" defaultExpandAll
@select="select" @expand="onExpand" show-icon>
<template slot="title" slot-scope="{title}">
<span>{{ title }}</span>
</template>
</a-tree>
</a-layout-sider>
<a-layout-content :class="{ 'tableContent': !treeDisplay }">
<div classs="layout">
<div class="header">
<a-row>
<slot name="topOptionButton"></slot>
</a-row>
<a-row style="display: flex;justify-content: flex-end;align-items: center;margin-top: 5px;">
<slot name="filter"></slot>
</a-row>
</div>
<div style="position: relative;">
<div class="setHeaderIcon">
<rember-table-header :allColumns="allColumns" :tableName="tableName" :hasReportAction="hasReportAction" :hasAction="hasAction"
@columnChange="columnChange"></rember-table-header>
</div>
<!-- 表格列表 -->
<a-table id="tableDom" :columns="columns" :scroll="{ x: nowTableWidth,y: tableHeight}" :data-source="tableData"
:row-key="(record) => record.id ? record.id : record.taskId"
:row-selection="{ selectedRowKeys: tableSelectedRowKeys, onChange: onSelectChange, }"
:pagination="{ defaultPageSize: requestParams.pageSize, current: requestParams.pageNo, total: tableTotal, showTotal: (total) => `共 ${Math.ceil(total / requestParams.pageSize)} 页,共 ${total} 条数据`, }"
@change="pageCurrentChange">
<div slot="actionTitle">
<div style="display: flex;">
<span>基本操作</span>
</div>
</div>
<span class="indexColor resolveResult" slot="xuHao" slot-scope="record, id, index">
{{ (index + 1) + (requestParams.pageNo - 1) * requestParams.pageSize }}
</span>
<a slot="name" slot-scope="text">{{ text }}</a>
<div slot="action" slot-scope="text, record">
<a-space>
<slot name="tableOpreation" :record="record"></slot>
</a-space>
</div>
</a-table>
</div>
</div>
</a-layout-content>
</a-layout>
</div>
</template>
<script>
import RemberTableHeader from '@/components/RemberTableHeader.vue'
export default {
props: {
// 表头
allColumns: {
type: Array,
default: ()=>[],
},
// 树数据
treeData: {
type: Array,
default: ()=>[],
},
// 表数据总数
tableTotal: {
type: Number,
default: 0,
},
// 表数据
tableData: {
type: Array,
default: ()=>[],
},
// 树结构是否展示
treeDisplay: {
type: Boolean,
default: true
},
// 表选择项
selectedRowKeys: {
type: Array,
default: ()=>[],
},
// 是否有其他操作项
hasReportAction: {
type: Boolean,
default: false
},
// 是否有树搜索框
hasTreeSearch: {
type: Boolean,
default: true
},
// 表名(用来存储,唯一)
tableName: {
type: String,
default: ''
},
// 表格宽度
tableWidth : {
type: Number,
default: 3200
},
// 表格高度
tableHeight: {
type: String,
default: 'calc(100vh - 420px)'
},
// 是否有基本操作项
hasAction: {
type: Boolean,
default: true
},
},
data() {
return {
selectedStatusKeys: [],
requestParams: {
pageNo: 1,
pageSize: 10
},
columns: this.allColumns,
expandedKeys: [],
searchValue: '',
tableSelectedRowKeys: this.selectedRowKeys,
nowTableWidth: 0,
allTableWidth: 0
};
},
watch: {
selectedRowKeys: {
handler(newVal) {
this.tableSelectedRowKeys = newVal
},
deep: true,
immediate: true
},
tableTotal: {
handler() {
this.requestParams.pageNo = 1
},
deep: true,
immediate: true
},
allTableWidth(){
this.getTableWidth()
}
},
mounted() {
this.expandedKeys.push('0')
this.getTableData();
this.getTreeData();
// 延迟,等dom加载完毕,获取当前表格宽度
setTimeout(() => {
var tableDom = document.getElementById('tableDom')
this.allTableWidth = tableDom.clientWidth - 20
}, 100)
// 响应式,分辨率改变时重新获取当前表格宽度
window.addEventListener('resize',() => {
var tableDom1 = document.getElementById('tableDom')
if(tableDom1){
this.allTableWidth = tableDom1.clientWidth - 20
this.getTableWidth()
}
})
},
components: {
RemberTableHeader
},
methods: {
// 调用父组件函数获取表格数据
getTableData(val) {
if (val == 1) {
this.requestParams.pageNo = 1;
}
this.$emit('getTableData', this.requestParams)
},
// 表格页码切换
pageCurrentChange({ current }) {
this.requestParams.pageNo = current;
this.getTableData();
},
// 表格选中项改变
onSelectChange(selectedRowKeys) {
this.tableSelectedRowKeys = selectedRowKeys;
this.$emit('getSelectedRowKeys', this.tableSelectedRowKeys)
this.selectedStatusKeys = [];
this.tableData.forEach(dt => {
if (dt.status == 0) {
const filt = this.tableSelectedRowKeys.filter(ft => dt.id == ft)
if (filt && filt.length != 0) {
this.selectedStatusKeys.push(dt.id);
}
}
})
},
// 点击选择树结构
select(selectId) {
this.$emit('select', selectId)
if (selectId.length === 0) {
return;
}
this.selectedKeys = selectId;
this.tableSelectedRowKeys = [];
if (selectId[0] == 'root') {
this.selectedKeys = []
return;
}
this.requestParams.pageNo = 1
this.getTableData()
this.total = 0
},
onExpand(expandedKeys) {
this.expandedKeys = expandedKeys;
this.autoExpandParent = false;
},
// 调用父组件函数获取树数据
getTreeData(params) {
this.searchValue = params;
this.$emit('getTreeData', this.searchValue)
},
// 树搜索
onSearch(value) {
this.getTreeData(value)
},
// 展示列改变
columnChange(columns) {
this.columns = columns
this.getTableWidth()
},
// 获取当前表格宽度
getTableWidth() {
var allColumnsWidth = 0
// 获取当前所有列宽度之和,如果当前列没有设宽度则默认200
this.columns.forEach(item => {
if(item.width){
allColumnsWidth += item.width
}else{
allColumnsWidth += 200
}
})
// 如果总列宽大于表格dom宽度,则取总列宽;如果总列宽小于表格dom宽度,则取表格dom宽度
if (allColumnsWidth > this.allTableWidth) {
this.nowTableWidth = allColumnsWidth
} else {
this.nowTableWidth = this.allTableWidth
}
},
},
};
</script>
<style lang="scss" scoped>
#page {
padding: 20px;
}
.header {
padding-bottom: 10px;
}
.tableContent {
margin: 0;
}
.layout {
background: #fff !important;
}
.ant-layout-sider {
background: #fff;
padding-right: 5px;
border-right: 1px solid #eee;
}
.ant-layout-content {
background: transparent;
padding: 10px;
}
.ant-table-tbody {
background: #fff !important;
padding: 10px;
}
.outBoxStyle {
background: blue !important;
}
.text {
width: 100%;
line-height: 15px;
outLine: none;
}
/deep/.ant-table-fixed-header .ant-table-scroll .ant-table-header {
background-color: #f5f7fd;
}
/deep/.ant-select-selection--multiple .ant-select-selection__rendered {
height: 40px;
}
/deep/.ant-modal-body {
max-height: 370px;
overflow-y: auto;
}
/deep/.ant-table table {
background-color: #f5f7fd;
}
/deep/.ant-transfer-customize-list .ant-transfer-list {
width: 45%;
}
/deep/.ant-tree.ant-tree-block-node li span.ant-tree-checkbox+.ant-tree-node-content-wrapper {
overflow: hidden;
text-overflow: ellipsis;
}
/deep/.ant-table-fixed-header .ant-table-body-inner {
background-color: #ffffff;
}
/deep/.ant-select-auto-complete.ant-select .ant-select-selection__rendered {
line-height: 30px;
}
/deep/ .ant-table-thead>tr>th,
/deep/ .ant-table-tbody>tr>td {
text-align: center;
}
/deep/.ant-table {
height: calc(100vh - 300px);
}
/deep/.layout-dashboard .ant-tree {
height: calc(100vh - 200px);
overflow: scroll !important;
}
.setHeaderIcon{
position: absolute;
z-index: 999;
right: 10px;
top: 17px;
}
</style>
实例 tableTest.vue
<template>
<div id = 'page'>
<table-page
:allColumns="columns"
:treeData="treeData"
:tableTotal="tableTotal"
:tableData="tableData"
:treeDisplay="true"
:selectedRowKeys="selectedRowKeys"
tableName="testTable"
:tableWidth="1700"
tableHeight="calc(100vh - 350px)"
:hasTreeSearch="true"
:hasReportAction="false"
:hasAction="true"
@getTableData="getTableData"
@getTreeData="getTreeData"
@getSelectedRowKeys="getSelectedRowKeys"
@select="select"
>
<!-- 顶部按钮 -->
<template v-slot:topOptionButton>
<a-button type="primary" style="margin-right: 10px">新增</a-button>
</template>
<!-- 顶部筛选 -->
<template slot="filter">
<a-input placeholder="请输入名称" style="width: 120px;margin-right: 10px;"></a-input>
<a-input placeholder="请输入年龄" style="width: 120px;"></a-input>
</template>
<!-- 表格操作 -->
<template v-slot:tableOpreation="itemProps">
<a @click="editRecord(itemProps.record)">编辑</a>
<a-popconfirm title="确定删除?" ok-text="是" cancel-text="否" @confirm="() => deleteRecord(itemProps.record.id)">
<a class="deleText">删除</a>
</a-popconfirm>
</template>
</table-page>
</div>
</template>
<script>
import TablePage from '@/components/TablePage.vue'
const columns = [
{
title: "序号",
scopedSlots: { customRender: "xuHao" },
key:"xuHao",
width: 80,
fixed: 'left',
},
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '电话',
dataIndex: 'phone',
key: 'phone',
},
{
title: '学历',
dataIndex: 'education',
key: 'education',
},
{
title: '职业',
dataIndex: 'occupation',
key: 'occupation',
},
{
title: '职业1',
dataIndex: 'occupation1',
key: 'occupation1',
},
{
title: '职业2',
dataIndex: 'occupation2',
key: 'occupation2',
},
{
title: '职业3',
dataIndex: 'occupation3',
key: 'occupation3',
},
{
slots: { title: 'actionTitle'},
key: 'action',
scopedSlots: { customRender: 'action' },
width: 100,
fixed: 'right',
},
]
export default{
data(){
return {
tableData: [],
tableTotal: 0,
treeData: [],
columns,
selectedRowKeys: [],
pageParams: {
pageNo: 1,
pageSize: 10
}
}
},
components:{
TablePage
},
methods:{
// 获取表格数据
getTableData(params){
if(params){
this.pageParams.pageNo = params.pageNo
}
this.tableData = [
{
id: 1,
name: '一',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 2,
name: '二',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 3,
name: '三',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 4,
name: '一',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 5,
name: '二',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 6,
name: '三',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 7,
name: '一',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 8,
name: '二',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 9,
name: '三',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 10,
name: '一',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 11,
name: '二',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
},
{
id: 12,
name: '三',
age: 12,
phone: '12238746545',
education: '本科',
occupation: '学生'
}
]
this.tableTotal = this.tableData.length
},
// 获取树数据
getTreeData(params){
if(params){
console.log("树搜索:",params)
}
this.treeData = [
{
title: 'parent 1',
key: '0-0',
children: [
{
title: 'parent 1-0',
key: '0-0-0',
disabled: true,
children: [
{
title: 'leaf1',
key: '0-0-0-0',
disableCheckbox: true,
},
{
title: 'leaf2',
key: '0-0-0-1',
},
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [
{
title: 'leaf3',
key: '0-0-1-0',
disableCheckbox: true,
},
{
title: 'leaf4',
key: '0-0-1-1',
},
],
},
],
},
]
},
// 表格选中项
getSelectedRowKeys(selectedRowKeys){
console.log("表格选中: ",selectedRowKeys)
},
// 编辑操作
editRecord(record){
console.log("编辑的表数据:",record)
},
// 删除操作
deleteRecord(id){
console.log("删除id为:",id,"的数据")
},
// 选择的树节点
select(key){
console.log("选择的树节点key:",key[0])
}
}
}
</script>
<style>
body{
background-color: rgb(207, 212, 212,0.42);
}
#page{
width: 100%;
height: 100%;
}
.ant-layout-sider{
padding: 10px;
}
.deleText{
color: red;
}
</style>