表格主组件
KTable.vue
HTML部分
<template>
<div
class="table-wrapper"
v-loading="loading"
element-loading-text="加载中..."
>
<vxe-table
:ref="tableRef"
:resizable="resizable"
:class="[
'my-table-scrollbar',
tableRef,
isNeedFirstLine ? 'hide-scroll-table' : '',
border ? 'show-warp-border' : '',
isShowColor ? 'is-show-color' : '',
whetherToMerge || border ? 'set-table-border' : '',
isShowTotal ? 'set-scroll-table' : '',
]"
:data="tableData"
:max-height="height ? height : minHeight"
:row-config="{ isHover: true }"
:sort-config="{ remote: true, trigger: 'cell', defaultSort: defaultSort }"
:tooltip-config="{ showAll: false }"
:border="border"
:row-id="rowId"
:show-footer="isShowTotal && tableData.length"
:footer-span-method="isNeedTotalMerge && footerColspanMethod"
:footer-method="footerMethod"
:tree-config="
dataStructure === '1'
? treeConfig1
: treeConfig2.transform
? treeConfig2
: false
"
:span-method="whetherToMerge && mergeRowMethod"
header-row-class-name="vxe-table__header-row"
:header-cell-style="headMerge"
:cell-style="defaultCellStyle"
:footer-cell-style="footerCellStyle"
@scroll="handleScroll"
@sort-change="val => sortChange(val, 'table')"
>
<vxe-table-column
align="left"
title="序号"
width="80"
:fixed="isFixed"
v-if="showSerial && tableConfig.length"
>
<template v-slot="{ rowIndex }">
<span>{{ rowIndex + (pageIndex - 1) * pageSize + 1 }}span>
template>
vxe-table-column>
<k-table-column
v-for="item in tableConfig"
:key="item.prop"
ref="KTableColumn"
:options="item"
:propsCode="propsCode"
:resizeSearch="resizeSearch"
:searchLoading="searchLoading"
:sortConfigObj="sortConfigObj"
:treeData="treeData"
:treeDataProps="treeDataProps"
@popoverOpen="popoverOpen"
@filter-change="filterColumnChange"
@sort-table-change="val => sortChange(val, 'custom')"
>k-table-column>
<slot>slot>
<template #empty>
<k-empty type="table">k-empty>
template>
vxe-table>
<k-pagination
v-if="tableData.length && isShowPagina"
v-bind="$attrs"
v-on="$listeners"
:total="total"
:page="page"
:limit="limit"
>k-pagination>
div>
template>
JS部分
<script>
import KTableColumn from '../KTableColumn/index'
import KEmpty from '../KEmpty/index'
import KPagination from '@/components/KPagination'
import { numToFixed } from '@/util/common'
export default {
name: 'KTable',
components: {
KTableColumn,
KEmpty,
KPagination,
},
props: {
propsCode: {
type: Object,
default: () => {
return {
prop: 'prop',
label: 'label',
children: 'children',
}
},
},
loading: {
type: Boolean,
default: false,
},
isShowSecondColor: {
type: Boolean,
default: false,
},
tableHeaderProp: {
type: String,
default: '',
},
tableConfig: {
type: Array,
default: () => [],
},
tableData: {
type: Array,
default: () => [],
},
totalRowData: {
type: Object,
default: () => {},
},
height: {
type: String || Number,
default: '',
},
isShowTotal: {
type: Boolean,
default: false,
},
isNeedTotalMerge: {
type: Boolean,
default: false,
},
defaultSort: {
type: Object,
default: () => {
return {
field: '',
order: '',
}
},
},
isNeedFirstLine: {
type: Boolean,
default: false,
},
tableClass: {
type: String,
default: '',
},
whetherToMerge: {
type: Boolean,
default: false,
},
isShowColor: {
type: Boolean,
default: true,
},
fields: {
type: Array,
default: () => [],
},
isNeedHeaderMerage: {
type: Boolean,
default: false,
},
isShowPagina: {
type: Boolean,
default: false,
},
minHeight: {
type: Number,
default: 400,
},
showSerial: {
type: Boolean,
default: false,
},
isFixed: {
type: String || Boolean,
default: false,
},
isShowScroll: {
type: Boolean,
default: false,
},
searchLoading: {
type: Boolean,
default: false,
},
dataStructure: {
type: String,
default: '2',
},
treeConfig1: {
type: Object,
default: () => {
return {
children: 'children',
iconOpen: 'iconfont icon-chevron-up',
iconClose: 'iconfont icon-chevron-down',
}
},
},
treeConfig2: {
type: Object,
default: () => {
return {
transform: false,
rowField: 'id',
parentField: 'parentId',
iconOpen: 'iconfont icon-chevron-up',
iconClose: 'iconfont icon-chevron-down',
}
},
},
resizeSearch: {
type: Boolean,
default: false,
},
treeData: {
type: Array,
default: () => [],
},
treeDataProps: {
type: Object,
default: () => {
return {
value: 'prop',
label: 'label',
children: 'children',
}
},
},
sortConfigObj: {
type: Object,
default: () => {
return {
code: '',
orderType: '',
}
},
},
rowId: {
type: String,
default: 'id',
},
resizable: {
type: Boolean,
default: false,
},
activeTabType: {
type: String,
default: '',
},
border: {
type: Boolean || String,
default: 'inner',
},
total: {
required: false,
type: Number,
},
page: {
type: Number,
default: 1,
},
limit: {
type: Number,
default: 20,
},
},
data() {
return {
scrollLeft: 0,
tableRef: '',
}
},
created() {
this.tableRef = `table-${this.guid2()}`
},
computed: {
pageIndex() {
return this.page
},
pageSize() {
return this.limit
},
},
methods: {
popoverOpen(val) {
this.$emit('popoverOpen', val)
},
sortTableChange(val) {
this.$emit('sort-table-change', val)
},
guid2() {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
}
return (
S4() +
S4() +
'-' +
S4() +
'-' +
S4() +
'-' +
S4() +
'-' +
S4() +
S4() +
S4()
)
},
handleScroll(ev) {
this.scrollLeft = ev.scrollLeft
},
tableSimulationFun(val) {
this.scrollLeft = val.scrollLeft
this.$refs[this.tableRef].scrollTo(val.scrollLeft, '')
},
sortChange(column, type) {
const { field, order } = column
this.$emit('sort-change', {
field,
order,
type,
})
if (type === 'custom') {
this.$nextTick(() => {
this.$refs[this.tableRef].clearSort()
})
}
},
seqMethod() {},
filterColumnChange(val) {
this.$emit('filter-change', val)
},
filterMethod({ option, row }) {
let k = null
for (k in row) {
if (row[k] === option.data) {
return true
}
}
return false
},
footerMethod({ columns }) {
const methodArr = []
columns.forEach((column, _columnIndex) => {
if (this.isNeedTotalMerge) {
if (_columnIndex === 0 || _columnIndex === 1) {
methodArr.push('合计')
} else if (
this.totalRowData[column.field] === '' ||
this.totalRowData[column.field] === null
) {
methodArr.push('-')
} else {
if (this.tableConfig && this.tableConfig.length) {
this.tableConfig.forEach(item => {
if (item[this.treeDataProps.value] === column.field) {
if (item.type === 'NUMBER' || item.type === 'Number' || item.type === 'number') {
const data = numToFixed(
this.totalRowData[column.field],
item.digit,
item.division,
item.empty
)
if (item.percentage) {
data.toString().indexOf('%') !== -1 ? methodArr.push(data) : methodArr.push(`${data}%`)
} else {
methodArr.push(data)
}
} else {
if (item.percentage) {
this.totalRowData[column.field].toString().indexOf('%') !== -1 ? methodArr.push(this.totalRowData[column.field]) : methodArr.push(`${this.totalRowData[column.field]}%`)
} else {
methodArr.push(this.totalRowData[column.field])
}
}
}
})
}
}
} else {
if (_columnIndex === 0) {
methodArr.push('合计')
} else if (
this.totalRowData[column.field] === '' ||
this.totalRowData[column.field] === null
) {
methodArr.push('-')
} else {
if (this.tableConfig && this.tableConfig.length) {
this.tableConfig.forEach(item => {
if (item[this.treeDataProps.value] === column.field) {
if (item.type === 'NUMBER' || item.type === 'Number' || item.type === 'number') {
const data = numToFixed(
this.totalRowData[column.field],
item.digit,
item.division,
item.empty
)
if (item.percentage) {
data.toString().indexOf('%') !== -1 ? methodArr.push(data) : methodArr.push(`${data}%`)
} else {
methodArr.push(data)
}
} else {
if (item.percentage) {
this.totalRowData[column.field].toString().indexOf('%') !== -1 ? methodArr.push(this.totalRowData[column.field]) : methodArr.push(`${this.totalRowData[column.field]}%`)
} else {
methodArr.push(this.totalRowData[column.field])
}
}
}
})
}
}
}
})
return [methodArr]
},
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
if (this.whetherToMerge && !this.fields.length)
return this.$message.warning('请输入需合并列的表头code')
const cellValue = row[column.property]
if (cellValue && this.fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow[column.property] === cellValue) {
return {
rowspan: 0,
colspan: 0,
}
} else {
let countRowspan = 1
while (nextRow && nextRow[column.property] === cellValue) {
nextRow = visibleData[++countRowspan + _rowIndex]
}
if (countRowspan > 1) {
return {
rowspan: countRowspan,
colspan: 1,
}
}
}
}
},
footerColspanMethod({ $rowIndex, _columnIndex }) {
if ($rowIndex === 0) {
if (_columnIndex === 0) {
return {
rowspan: 1,
colspan: 2,
}
} else if (_columnIndex === 1) {
return {
rowspan: 0,
colspan: 0,
}
}
}
},
headMerge({ column, $rowIndex, columnIndex }) {
if (this.isNeedHeaderMerage) {
if ($rowIndex === 0) {
if (columnIndex === 0) {
this.$nextTick(() => {
if (document.getElementsByClassName(column.id).length !== 0) {
document
.getElementsByClassName(column.id)[0]
.setAttribute('colSpan', '2')
return false
}
})
} else if (columnIndex === 1) {
return { display: 'none' }
}
}
}
return {
backgroundColor: '#F3F8FD',
fontSize: '14px',
color: '#333333',
lineHeight: '36px',
fontWeight: 700,
height: '36px',
fontFamily: 'MicrosoftYaHei-Bold, Microsoft-YaHei',
borderColor: '#E3E6EA',
padding: '0',
}
},
defaultCellStyle({ column }) {
const tableClass = this.tableClass
? `.${this.tableClass}.set-first-color`
: ''
if (tableClass) {
const setColor = document.querySelector(tableClass)
const prop = this.tableConfig.length
? this.tableConfig[0].prop
: this.tableHeaderProp
if (setColor && column.property === prop) {
return {
fontWeight: '700',
height: '36px',
padding: '0',
color: '#333333',
borderColor: '#E3E6EA',
backgroundColor: '#F3F8FD',
fontFamily: 'Microsoft-YaHei',
fontSize: '14px',
}
} else if (
setColor &&
this.isShowSecondColor &&
column.property === this.tableConfig[1].prop
) {
return {
fontWeight: '700',
height: '36px',
padding: '0',
color: '#333333',
borderColor: '#E3E6EA',
backgroundColor: '#F3F8FD',
fontFamily: 'Microsoft-YaHei',
fontSize: '14px',
}
}
}
return {
height: '36px',
padding: '0',
color: '#333333',
fontWeight: '400',
fontFamily: 'Microsoft-YaHei',
fontSize: '14px',
margin: '0',
borderColor: '#E3E6EA',
}
},
footerCellStyle() {
return {
height: '36px',
padding: '0',
fontFamily: 'Microsoft-YaHei',
color: '#333333',
fontSize: '14px',
margin: '0',
borderColor: '#E3E6EA',
}
},
},
mounted() {
if (this.isNeedFirstLine) {
let table = document.querySelector(
`.${this.tableClass} .vxe-table .vxe-table--main-wrapper`
)
let footer = document.querySelector(
`.${this.tableClass} .vxe-table--footer-wrapper`
)
let body = document.querySelector(
`.${this.tableClass} .vxe-table--body-wrapper`
)
table.removeChild(footer)
table.insertBefore(footer, body)
}
},
}
</script>
CSS部分
引入组件部分
KTableColumn.vue
HTML部分
<template>
<vxe-colgroup
v-if="options[propsCode.children]"
:key="options[propsCode.prop]"
:align="options.align || 'center'"
:field="options[propsCode.prop]"
:fixed="options.freeze ? 'left' : ''"
:min-width="options.minWidth || testHead(options[propsCode.label], 18)"
:sortable="options.sort || false"
:title="options[propsCode.label]"
show-header-overflow
show-overflow
>
<k-table-column
v-for="item in options[propsCode.children]"
:key="item[propsCode.prop]"
:options="item"
:propsCode="propsCode"
:resizeSearch="resizeSearch"
:searchLoading="searchLoading"
:sortConfigObj="sortConfigObj"
:accuracy="accuracy"
:treeData="treeData"
:treeDataProps="treeDataProps"
@popoverOpen="popoverOpen"
@filter-change="filterColumnChange"
@sort-table-change="val => sortChange(val, 'custom')"
/>
vxe-colgroup>
<vxe-column
v-else
:tree-node="options.treeNode"
:key="options[propsCode.prop]"
:align="options.align || 'left'"
:field="options[propsCode.prop]"
:fixed="options.freeze ? 'left' : ''"
:min-width="options.minWidth || testHead(options[propsCode.label], 18)"
:sortable="options.sort === '' || options.sort === undefined || options.sort === true"
:title="options[propsCode.label]"
show-overflow
show-footer-overflow
>
<template v-if="options.filter" #header>
<KTableFiltering
ref="KTableFiltering"
:defaultProps="treeDataProps"
:field="options[propsCode.prop]"
:loading="searchLoading"
:mainCode="treeDataProps.value"
:mainLabel="treeDataProps.label"
:resizeSearch="resizeSearch"
:sortConfigObj="sortConfigObj"
:title="options[propsCode.label]"
:treeCheckData="treeCheckData"
:treeData="treeData"
@popoverOpen="popoverOpen"
@filter-change="filterChangeFun"
@sort-table-change="sortTableChange"
>KTableFiltering>
template>
<template #default="{ row, rowIndex }">
{{ formatterTableData(row, rowIndex) }}
template>
vxe-column>
template>
JS部分
<script>
import KTableFiltering from '../KTableFiltering/index'
import { testHead } from '@/util'
import { numToFixed } from '@/util/common'
export default {
name: 'KTableColumn',
components: {
KTableFiltering,
},
props: {
accuracy: {
type: String,
default: '',
},
fixedVal: {
type: Boolean,
default: true,
},
propsCode: {
type: Object,
default: () => {
return {
prop: 'prop',
label: 'label',
children: 'children',
}
},
},
options: {
type: Object,
default: () => ({}),
},
treeData: {
type: Array,
default: () => [],
},
treeDataProps: {
type: Object,
default: () => {
return {
value: 'prop',
label: 'label',
children: 'children',
}
},
},
searchLoading: {
type: Boolean,
default: false,
},
resizeSearch: {
type: Boolean,
default: false,
},
sortConfigObj: {
type: Object,
default: () => {
return {
code: '',
orderType: '',
}
},
},
},
data() {
return {
sortActive: 0,
inputValue: '',
filterData: {
sortActive: 0,
inputValue: '',
treeCheckData: [],
},
defaultProps: {
children: 'children',
label: 'label',
},
treeCheckData: [9],
}
},
methods: {
testHead,
numToFixed,
filterChangeFun(val) {
this.$emit('filter-change', val)
},
popoverOpen(val) {
this.$emit('popoverOpen', val)
},
sortTableChange(val) {
this.$emit('sort-table-change', val)
},
formatterTableData(row, rowIndex) {
const rowData = row[this.options[this.propsCode.prop]]
if (rowData === null || rowData === '' || rowData === undefined) return '-'
if (this.options.type === 'NUMBER' || this.options.type === 'Number' || this.options.type === 'number') {
let data = ''
data = numToFixed(rowData, this.options.digit, this.options.division, this.options.empty)
if (this.options.percentage) {
if (data.toString().indexOf('%') !== -1) return data
data = data && data !== '-' ? `${data}%` : '-'
}
return data
}
if (this.options.percentage && rowData !== '-') {
if (rowData.toString().indexOf('%') !== -1) return rowData
return rowData + '%'
}
if (this.options.format) {
return this.options.format(row, rowIndex)
}
return rowData
},
}
</script>
CSS部分
KPagination.vue
分页组件HTML部分
<template>
<div
:class="{
hidden: hidden,
['k_pagination_position_' + position]: true,
'left-right': position === 'left-right',
'fixed-pagination': isShowScroll,
}"
class="k_pagination pageBox"
:style="{ width: isShowScroll ? scrollWidth : '' }"
>
<el-pagination
:class="{
'fixed-pagination-padding': isShowScroll,
'visibility-hidden': isVisibility,
}"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:pager-count="pagerCount"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
:background="isBackGround"
@current-change="handleCurrentChange"
/>
div>
template>
JS部分
<script>
import _ from 'lodash'
export default {
name: 'KPagination',
props: {
isShowScroll: {
type: Boolean,
default: false,
},
isVisibility: {
type: Boolean,
default: false,
},
total: {
required: true,
type: Number,
},
page: {
type: Number,
default: 1,
},
limit: {
type: Number,
default: 20,
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 40, 50, 100, 200, 300, 500]
},
},
pagerCount: {
type: Number,
default: document.body.clientWidth < 992 ? 5 : 7,
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper',
},
autoScroll: {
type: Boolean,
default: true,
},
hidden: {
type: Boolean,
default: false,
},
position: {
type: String,
default: 'left-right',
},
isBackGround: {
type: Boolean,
default: true,
},
},
data() {
return {
scrollWidth: 0,
scrollWidth1: 0,
}
},
computed: {
currentPage: {
get() {
return this.page
},
set(val) {
this.$emit('update:page', val)
},
},
pageSize: {
get() {
return this.limit
},
set(val) {
this.$emit('update:limit', val)
},
},
},
watch: {
total: {
handler() {
this.getWidthNext()
},
},
},
mounted() {
window.addEventListener('resize', _.debounce(this.getWidthNext, 150))
},
updated() {
this.getWidthNext()
},
beforeDestroy() {
window.removeEventListener('resize', this.getWidth)
},
methods: {
handleSizeChange(val) {
this.currentPage = 1
this.$emit('pagination', { current: 1, size: val })
},
handleCurrentChange(val) {
this.$emit('pagination', { current: val, size: this.pageSize })
},
getWidth() {
const vxeTable = document.getElementsByClassName('content-text')
let vxeScrollWidth = 0
Array.prototype.forEach.call(vxeTable, function (element) {
if (element && element.clientWidth !== 0) {
vxeScrollWidth = element.clientWidth
}
})
this.scrollWidth = vxeScrollWidth + 'px'
},
getWidthNext() {
this.$nextTick(() => {
this.$nextTick(() => {
this.$nextTick(() => {
this.getWidth()
})
})
})
},
},
}
</script>
CSS部分
公共方法部分参考其他文章