JxlTable 表格
满足开发者采用配置的方式,快速搭建表格数据的需求。
基于 ElementUI 的 el-table 、el-table-column、el-pagination、el-button、el-row 和 el-col 标签封装。
基于自封组件的 jxl-tip 标签封装。
<template>
<div class="el-table-wrapper">
<div class="el-table-body">
<el-table
v-loading="loading"
row-key="id"
:data="dataPage"
:border="border"
:highlight-current-row="getRowType() === 'radio'"
:class="isAutoLayout ? 'auto-layout': ''"
v-bind="$attrs"
v-on="$listeners"
@current-change="currentChange"
@expand-change="expandChange"
@selection-change="selectionChange"
>
<!-- 表格下方追加内容 -->
<template slot="append">
<slot name="table-append" />
</template>
<!-- 表格没有数据时显示的内容 -->
<template slot="empty">
<slot name="table-empty" />
</template>
<!-- 表格扩展行,显示额外的数据 -->
<template v-if="$scopedSlots['row-expand']">
<el-table-column type="expand">
<template v-slot="{row, $index}">
<slot name="row-expand" :row="row" :index="$index + 1" />
</template>
</el-table-column>
</template>
<!-- 选择列 -->
<el-table-column v-if="getRowType() === 'checkbox'" type="selection" width="80" />
<template v-for="(item, key) in columns">
<!-- 隐藏列 -->
<template v-if="isHidden(item)" />
<!-- 正常列 -->
<el-table-column
v-else
:key="key"
:fixed="item.fixed"
:prop="item.prop"
:min-width="getMinWidth(item)"
:width="isActions(item) ? getActionWidth(item) : getWidth(item)"
:align="getAlign(item)"
:header-align="getHeaderAlign(item)"
v-bind="insertAttrs(item)"
>
<template slot="header">
<!-- 表头名称 -->
<span>{{ item.label }}</span>
<!--条目提示符-->
<jxl-tip v-if="item.tip" :options="item.tip">
<template v-if="_get(item, 'tip.prompt.content.slotName')" v-slot:content>
<slot v-if="$scopedSlots['tipContent']" name="tipContent" :row="item" />
</template>
</jxl-tip>
</template>
<template v-slot="{row, $index}">
<!-- 插槽渲染 -->
<slot v-if="item.slot === true && $scopedSlots[item.prop]" :name="item.prop" :row="row" :index="$index + 1" />
<slot v-else-if="_typeof(item.slotName) === String && $scopedSlots[item.slotName]" :name="item.slotName" :row="row" :index="$index + 1" />
<!-- 方法渲染 -->
<template v-else-if="typeof item.render === 'function'">{{ item.render(row, $index + 1) }}</template>
<!-- 序号渲染 -->
<template v-else-if="item.type === 'index' || item.prop === 'index'">{{ $index + 1 }}</template>
<!-- 操作按钮 -->
<template v-else-if="isActions(item)">
<div :class="item.overlayClassName">
<template v-for="(btn, key1) in item.actions">
<el-button
v-if="buttonVisible(btn, row)"
:key="key1"
:disabled="buttonDisabled(btn, row)"
:loading="btn.loading"
v-bind="btn.attrs"
v-on="eventsBindHandle(row, btn.events)"
@click="buttonClick(btn, row)"
>{{ buttonText(btn, row) }}</el-button>
</template>
</div>
</template>
<!-- 普通文本 -->
<template v-else>
{{ friendly(_get(row, item.prop), isVoid(item.void) ? '-' : item.void) }}
</template>
</template>
</el-table-column>
</template>
</el-table>
</div>
<el-row v-if="$scopedSlots['table-tip'] || paginationVisible()" type="flex" justify="end" class="el-table-pagination">
<el-col :xs="24" :sm="24" :lg="8" :xl="8">
<div v-if="$scopedSlots['table-tip']" class="el-table-tip">
<slot name="table-tip" />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="16" :xl="16">
<el-pagination
v-if="paginationVisible()"
:class="pagination.overlayClassName"
:layout="pagination.layout"
:background="pagination.background"
:total="pagination.total"
:current-page="pagination.pageNum"
:page-size="pagination.pageSize"
:page-sizes="pagination.pageSizeOptions"
@current-change="pageNumChange"
@size-change="pageSizeChange"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import { _get, _typeof } from '@/components/libs/util'
import JxlTip from '@/components/libs/Tip/JxlTip'
import mixin from '@/components/libs/mixin'
export default {
name: 'JxlTable',
components: { JxlTip },
mixins: [mixin],
props: {
rowSelection: {
type: Object,
default: null
},
rowKey: {
type: [String, Function],
default: 'key',
required: true
},
data: {
type: Array,
required: true
},
columns: {
type: [Object, Array],
required: true
},
pagination: {
type: [Object, Boolean],
default: () => {
return {
total: 0, // 总条数
pageNum: 1, // 当前页码数
pageSize: 10, // 每页显示条数
pageSizeOptions: [10, 20, 30, 50], // 指定每页可以显示多少条
layout: 'total, prev, pager, next, sizes, jumper',
background: true,
overlayClassName: ''
}
}
},
loading: {
type: Boolean,
default: false
},
border: {
type: Boolean,
default: false
},
align: {
type: String,
default: 'left'
},
headerAlign: {
type: String,
default: 'left'
},
// 自动布局,平均列宽,当数据为空时
autoLayout: {
type: Boolean,
default: true
}
},
data() {
return {
dataPage: [], // 每页数据,当data的数据大于pageSize时需要用到
isAutoLayout: false // 是否自动布局
}
},
watch: {
data: {
handler: function() {
this.dataTotalChange() // 数据总量发生改变
},
deep: true
}
},
created() {
this.dataTotalChange() // 数据总量发生改变
},
methods: {
/**
* 列显示条件发生改变
*/
columnsChange() {
Object.keys(this.columns).map(key => {
const OBJ = this.columns[key]
if (this._typeof(OBJ.condition) !== Function) return
OBJ.hidden = OBJ.condition()
})
},
/**
* 页码页数发生改变
* @param pageNum 当前页
*/
pageNumChange(pageNum) {
if (this._typeof(this.pagination) !== Object) return
this.pagination.pageNum = pageNum
this.pagingWayHandle() // 分页方式处理
if (this._typeof(this.pagination['onChange']) !== Function) return
this.pagination['onChange'](pageNum, this.pagination['pageSize'])
},
/**
* 页码大小发生改变
* @param pageSize 每页条数
*/
pageSizeChange(pageSize) {
if (this._typeof(this.pagination) !== Object) return
this.pagination['pageSize'] = pageSize
this.pagingWayHandle() // 分页方式处理
if (this._typeof(this.pagination['onPageSizeChange']) !== Function) return
this.pagination['onPageSizeChange'](this.pagination['pageNum'], pageSize)
},
/**
* 分页器可见
* @returns {boolean}
*/
paginationVisible() {
if (this._typeof(this.pagination) !== Object) return false
if (this._typeof(this.pagination['total']) !== Number) return false
return this.pagination['total'] > 0
},
/**
* 获取行类型,普通、单选、多选
* @returns {string|*}
*/
getRowType() {
if (this._typeof(this.rowSelection) !== Object) return ''
if (this._typeof(this.rowSelection.type) !== String) return ''
return this.rowSelection.type
},
/**
* 数据总量发生改变
*/
dataTotalChange() {
if (this._typeof(this.pagination) !== Object) {
this.dataPage = this.data // 不分页则直接全部显示
return false
}
if (this._typeof(this.pagination['total']) === Number) {
if (this.pagination['total'] < this.data.length) {
this.pagination['total'] = this.data.length
}
} else {
this.pagination['total'] = this.data.length
}
this.pagingWayHandle() // 分页方式处理
this.getAutoLayout() // 获取布局
},
/**
* 分页方式处理
*/
pagingWayHandle() {
/**
* 数据条数比规定的页码大小要大,说明需要手动支持分页功能
*/
if (this.data.length > this.pagination['pageSize']) {
this.pagingHandle() // 静态数据分页处理
} else {
this.dataPage = this.data // 动态数据分页处理
}
},
/**
* 静态数据分页处理
*/
pagingHandle() {
const firstIndex = (this.pagination['pageNum'] - 1) * this.pagination['pageSize']
const lastIndex = firstIndex + this.pagination['pageSize']
this.dataPage = this.data.slice(firstIndex, lastIndex)
},
/**
* 选择行发生改变
*/
selectionChange(rows) {
const keys = []
rows.map(item => {
let key
if (this._typeof(this.rowKey) === Function) {
key = this.rowKey(item)
} else if (this._typeof(this.rowKey) === String) {
key = item[this.rowKey]
}
keys.push(key)
})
if (this.rowSelection.selectedRows) {
this.rowSelection.selectedRows = rows
}
if (this.rowSelection.selectedRowKeys) {
this.rowSelection.selectedRowKeys = keys
}
if (this._typeof(this.rowSelection.onSelectedRowChange) === Function) {
this.rowSelection.onSelectedRowChange(keys, rows)
}
},
/**
* 单选行改变
* @param currentRow 当前行
* @param oldCurrentRow 上一行
*/
currentChange(currentRow, oldCurrentRow) {
if (this.getRowType() !== 'radio') return false
this.selectionChange([currentRow]) // 选择行发生改变
},
/**
* 行展开改变
* @param row
* @param expandedRows 所有展开的行
*/
expandChange(row, expandedRows) {
if (this._typeof(_get(this.rowSelection, 'onExpandChange')) !== Function) return
if (this._typeof(expandedRows) === Array) this.rowSelection.onExpandChange(row, expandedRows.includes(row), expandedRows)
if (this._typeof(expandedRows) === Boolean) this.rowSelection.onExpandChange(row, expandedRows)
},
/**
* 事件绑定处理
* @param item
* @param events
*/
eventsBindHandle(item, events) {
if (this._typeof(events) !== Object) return undefined
const newEvents = {}
Object.keys(events).map(key => {
newEvents[key] = (value, value2) => { events[key](item, value, value2) }
})
return newEvents
},
/**
* 按钮可见
* @param item 被点击的按钮
* @param row 本行的数据
*/
buttonVisible(item, row) {
return this.isVisible(item, row)
},
/**
* 按钮文本
* @param item 被点击的按钮
* @param row 本行的数据
*/
buttonText(item, row) {
if (this._typeof(item.text) === Function) return item.text(row)
return item.text
},
/**
* 按钮隐藏
* @param item 被点击的按钮
* @param row 本行的数据
*/
buttonDisabled(item, row) {
return this.isDisabled(item, row)
},
/**
* 按钮点击
* @param btn 被点击的按钮
* @param row 本行的数据
*/
buttonClick(btn, row) {
if (this._typeof(btn.onClick) !== Function) return void (0)
btn.onClick(row)
},
/**
* 是否是操作列
* @param item
*/
isActions(item) {
return this._typein(item.actions, [Array, Object])
},
/**
* 获取操作列宽度
*/
getActionWidth(item) {
if (!item.autoWidth || !this.isActions(item)) return this.getWidth(item)
let width = 0
let number = 0
Object.keys(item.actions).map(key => {
if (!this.isHidden(item.actions[key])) {
number += 1
width += item.actions[key].width
}
})
return width + (item.cellPadding * 2) + item.marginRight + item.spaceBetween * (number - 1)
},
/**
* 获取列的宽度
* @param item
*/
getWidth(item) {
if (this.isVoid(item.width)) return ''
return item.width
},
/**
* 获取列的最小宽度
* @param item
*/
getMinWidth(item) {
if (this.isVoid(item.minWidth)) return 120
return item.minWidth
},
/**
* 获取列的对齐方式
* @param item
*/
getAlign(item) {
if (this._typeof(item.align) === String) return item.align
if (this._typeof(this.align) === String) return this.align
return 'left'
},
/**
* 获取表头列的对齐方式
* @param item
*/
getHeaderAlign(item) {
if (this._typeof(item.headerAlign) === String) return item.headerAlign
if (this._typeof(this.headerAlign) === String) return this.headerAlign
return 'left'
},
/**
* 获取布局
* @returns {boolean}
*/
getAutoLayout() {
if (!this.autoLayout) {
this.isAutoLayout = false
} else if (this._typeof(this.pagination) === Boolean) {
this.isAutoLayout = this.data.length === 0
} else if (this._typeof(this.pagination) === Object) {
this.isAutoLayout = this.pagination['total'] === 0
}
},
/**
* 插入属性
* @param item
*/
insertAttrs(item) {
const attrs = _typeof(item.attrs) === Object ? item.attrs : {}
if ('show-overflow-tooltip' in attrs || 'showOverflowTooltip' in attrs) return attrs
if (this.isActions(item)) return Object.assign(attrs, { 'show-overflow-tooltip': false })
return Object.assign(attrs, { 'show-overflow-tooltip': true })
}
}
}
</script>
<style lang="less" scoped>
.el-table-wrapper {
.el-table-body {
padding: 0;
/deep/ .el-table {
&.auto-layout colgroup {
display: none;
}
&.auto-layout .el-table__cell.is-hidden > * {
visibility: visible;
}
.header-label-icon {
margin-left: 5px;
}
th {
background-color: #f5f8fa;
color: #252631;
font-weight: 400;
}
}
}
.el-table-pagination {
margin-top: 20px;
.el-table-tip {
padding: 0 0 20px 20px;
color: #778ca2;
font-size: 14px;
}
/**分页样式*/
/deep/ .el-pagination {
padding: 0 20px 20px 0;
text-align: right;
font-weight: 500;
position: relative;
white-space: normal;
.el-pager {
li {
padding: 0 0;
height: 28px;
line-height: 28px;
}
li:not(.disabled).active {
background-color: #409EFF;
color: #FFF;
}
li:hover {
border: 1px solid #297aff;
color: #ffffff;
}
&:not(.disabled).active {
border: none;
}
&:not(.disabled).active:hover {
border: none;
}
}
&.is-background {
.btn-next,
.btn-prev,
.el-pager li {
background-color: #ffffff;
min-width: 28px;
border: 1px solid #ececec;
color: #a6b6c6;
&:hover {
border: 1px solid #297aff;
color: #297aff;
}
}
.btn-next.disabled,
.btn-next:disabled,
.btn-prev.disabled,
.btn-prev:disabled,
.el-pager li.disabled {
background-color: #f7f7f7;
min-width: 28px;
border: 1px solid #e3e3e3;
color: #a6b6c6;
}
.btn-next:not(.disabled).active {
border: 1px solid #297aff;
color: #ffffff;
}
.btn-prev:not(.disabled).active {
border: 1px solid #297aff;
color: #ffffff;
}
.btn-next:not(.disabled):hover,
.btn-prev:not(.disabled):hover {
border: 1px solid #297aff;
color: #297aff;
}
.btn-next:disabled:hover,
.btn-prev:disabled:hover {
background-color: #f7f7f7;
border: 1px solid #e3e3e3;
color: #a6b6c6;
}
.btn-next {
margin-right: 9px;
}
}
.el-pagination__total {
color: #b4b9c6;
display: inline-block !important;
}
.el-pagination__sizes .el-input__inner {
color: #b4b9c6;
}
.el-pagination__jump {
color: #b4b9c6;
margin-left: 0;
}
.el-select .el-input {
width: 82px;
}
}
}
}
</style>