LSSE-front/src/components/Common/Layout/AntQueryTable.vue

517 lines
16 KiB
Vue

<template>
<div class="ant-query-table" :style="{ height }">
<slot name="qt-top-query" :queryParams="queryParams" :tableData="tableData"></slot>
<div v-if="queryConfig !== false" class="qt-query">
<a-form-model layout="inline" :model="queryTemp">
<a-form-model-item v-for="item in queryConfig.items" :key="item.prop" :label="item.label">
<slot :name="`query-${item.prop}`" :prop="item.prop" :queryParams="queryTemp">
<component
:is="item.component || 'a-input'"
v-model="queryTemp[item.prop]"
v-bind="item.options"
v-on="item.listeners"
/>
</slot>
</a-form-model-item>
<a-form-model-item>
<a-space :size="16">
<a-button type="primary" @click="handleQueryBtnClick">查询</a-button>
<a-button @click="handleResetBtnClick">重置</a-button>
<slot name="querybar-action"></slot>
</a-space>
</a-form-model-item>
</a-form-model>
</div>
<slot name="qt-query-toolbar" :queryParams="queryParams" :tableData="tableData"></slot>
<div v-if="showTool" class="qt-toolbar">
<slot name="toolbar">
<a-space :size="16">
<slot
name="toolbar-left"
:tableData="tableData"
:selectedRows="selectedRows"
:selectedRowKeys="selectedRowKeys"
:queryParams="queryParams"
:queryParamsWithoutPage="queryTemp"
/>
</a-space>
<a-space :size="16">
<slot name="toolbar-right" />
</a-space>
</slot>
</div>
<div ref="qt-table" class="qt-table" v-resize="handleTableResize">
<a-table
ref="table"
:columns="tableColumns"
:loading="tableLoading"
:data-source="tableData"
:pagination="false"
v-bind="tableConf"
:scroll="tableScrollConfig"
:style="{ height: '100%' }"
:rowSelection="rowSelection"
>
<template v-for="(_, name) in tabletitleSlots" :slot="name">
<slot :name="name" />
</template>
<template v-for="(config, name) in titleRender" :slot="name">
<div :key="name" :style="{ display: 'flex', alignItems: 'center' }">
<span>{{ config.title }}</span>
<a-popover title="注释">
<template slot="content">
{{ config.explain }}
</template>
<a-icon type="exclamation-circle" style="width: 14px; height: 14px; margin-left: 5px; cursor: pointer" />
</a-popover>
</div>
</template>
<template v-for="(_, name) in tablecellSlots" :slot="name" slot-scope="text, record, index">
<slot :name="tablecellSlotPrefix + name" v-bind="{ text, record, index }" :data-index="name" />
</template>
</a-table>
<div v-if="tableScrollY" class="qt-table-border__bottom"></div>
</div>
<div v-if="isPageMode" class="qt-page">
<a-pagination v-bind="pageConf" @change="handlePageChange" @showSizeChange="handlePageChange"></a-pagination>
</div>
</div>
</template>
<script>
import moment from 'moment'
import cloneDeep from 'lodash.clonedeep'
import {
defaultUserColumns,
tabletitleSlotPrefix,
tablecellSlotPrefix,
defaultTableCellWidth,
defaultTableScrollSize,
defaultTableConfig,
actionSlotName,
defaultPageSize,
defaultPageConfig,
} from './defaultSettings'
export default {
props: {
height: { type: [String, Boolean], default: 'auto' },
queryConfig: { type: [Object, Boolean], default: () => ({}) },
tableConfig: { type: Object, default: () => ({}) },
pageConfig: { type: [Object, Boolean], default: () => ({}) },
showTool: { type: Boolean, default: true },
},
data() {
return {
queryTemp: {},
tableLoading: false,
tableData: [],
selectedRows: [],
selectedRowKeys: [],
widthMap: {
date: 168,
},
titleRender: {},
tableScrollX: false,
tableScrollY: false,
tabletitleSlotPrefix,
tablecellSlotPrefix,
actionSlotName,
total: 0,
pageParams: { pageNum: 1, pageSize: defaultPageSize },
}
},
computed: {
isPageMode() {
return this.pageConfig !== false
},
queryParams() {
if (this.isPageMode) {
return { ...this.queryTemp, ...this.pageParams }
}
return this.queryTemp
},
tableColumns() {
const { columns, userColumns = false } = this.tableConfig
const originColumns = cloneDeep(columns)
// 暂存序号列
let serialCol = null
const serialColIndex = originColumns.findIndex((col) => col.dataIndex === 'serial')
if (serialColIndex > -1) {
serialCol = originColumns[serialColIndex]
originColumns.splice(serialColIndex, 1)
}
// 暂存操作列
let actionCol = null
const actionColIndex = originColumns.findIndex((col) => col.dataIndex === actionSlotName)
if (actionColIndex > -1) {
actionCol = originColumns[actionColIndex]
originColumns.splice(actionColIndex, 1)
}
// 先处理其他列
if (userColumns) {
originColumns.push(...defaultUserColumns)
}
this.handleColumns(originColumns)
// 补充序号列到第一列
if (serialCol) {
originColumns.unshift({
title: '序号',
align: 'center',
width: 80,
...serialCol,
customRender: this.renderSerial.bind(this, serialCol.followPage === true),
fixed: 'left',
})
}
// 补充操作列到最后一列
if (actionCol) {
originColumns.push({
title: '操作',
width: 150,
align: 'center',
...actionCol,
slots: { title: 'action-title' },
scopedSlots: { customRender: 'action' },
fixed: 'right',
})
}
return originColumns
},
tableMaxWidth() {
return this.calculateWidthByColumns(this.tableColumns)
},
tableScrollConfig() {
return {
...(this.tableScrollX === false ? {} : { x: '100%' }),
...(this.tableScrollY === false ? {} : { y: this.tableScrollY }),
}
},
tableConf() {
return {
...defaultTableConfig,
...this.tableConfig.table,
}
},
defaultRowSelection() {
return {
selectedRowKeys: this.selectedRowKeys,
onChange: this.handleSelectChange.bind(this),
fixed: true,
getCheckboxProps: (record) => ({
props: { disabled: record.selectable === false },
style: { display: record.selectable === false ? 'none' : '' },
}),
}
},
rowSelection() {
if (!this.tableConfig.select) return null
if (this.tableConfig.select instanceof Object) {
return {
...this.tableConfig.select,
...this.defaultRowSelection,
}
}
return this.defaultRowSelection
},
tabletitleSlots() {
const tabletitleSlots = {}
for (const slotName in this.$scopedSlots) {
if (!slotName.startsWith(this.tabletitleSlotPrefix)) continue
const slotFunc = this.$scopedSlots[slotName]
tabletitleSlots[slotName] = slotFunc
}
return tabletitleSlots
},
tablecellSlots() {
const tablecellSlots = {}
for (const slotName in this.$scopedSlots) {
if (!slotName.startsWith(this.tablecellSlotPrefix)) continue
const translotName = slotName.replace(this.tablecellSlotPrefix, '')
const slotFunc = this.$scopedSlots[slotName]
tablecellSlots[translotName] = slotFunc
}
return tablecellSlots
},
pageConf() {
if (!this.isPageMode) return false
return {
...defaultPageConfig,
current: this.pageParams.pageNum,
pageSize: this.pageParams.pageSize,
total: this.total,
onChange: this.handlePageChange.bind(this),
onShowSizeChange: this.handlePageChange.bind(this),
}
},
},
mounted() {
if (this.queryConfig && this.queryConfig.initQueryParams) {
this.initQueryTemp()
}
const immediate = this.tableConfig.immediate
if (immediate !== false) this.queryTable()
},
methods: {
initQueryTemp() {
const { initQueryParams = {} } = this.queryConfig
this.queryTemp = cloneDeep(initQueryParams)
},
handleTableResize(target) {
if (!target) return
this.tableScrollX = target.offsetWidth < this.tableMaxWidth
const tableBody = target.querySelector('.ant-table-scroll>.ant-table-body>table>.ant-table-tbody') || {
offsetHeight: 0,
}
const innerHeight =
this.getTableHeadDom(target, this.tableScrollY).offsetHeight +
tableBody.offsetHeight +
(this.tableScrollX ? defaultTableScrollSize : 0)
if (target.offsetHeight > innerHeight) {
this.tableScrollY = false
} else {
this.tableScrollY = true
this.$nextTick(() => {
this.tableScrollY = target.offsetHeight - this.getTableHeadDom(target, true).offsetHeight
})
}
},
getTableHeadDom(target, tableScrollY) {
let tableHead = null
if (tableScrollY === false) {
tableHead = target.querySelector('.ant-table-scroll>.ant-table-body>table>.ant-table-thead')
} else {
tableHead = target.querySelector('.ant-table-scroll>.ant-table-header>table')
}
return tableHead || { offsetHeight: 0 }
},
handleColumns(originColumns) {
for (let i = 0; i < originColumns.length; i++) {
const {
title,
titleConfig,
dataIndex,
style,
width,
minWidth,
align,
type,
format,
dictName,
digits,
unit = '',
} = originColumns[i]
if (!title) {
originColumns[i].slots = { title: tabletitleSlotPrefix + dataIndex }
}
if (!title && titleConfig) {
this.$set(this.titleRender, tabletitleSlotPrefix + dataIndex, titleConfig)
originColumns[i].align = align || 'right'
}
originColumns[i].scopedSlots = { customRender: dataIndex }
if (!width) {
originColumns[i].width = this.widthMap[type] || defaultTableCellWidth
}
if (width === 'auto' && this.tableScrollX !== false) {
originColumns[i].width = minWidth || defaultTableCellWidth
}
if (type === 'number') {
originColumns[i].align = align || 'right'
originColumns[i].customRender = (text) => {
if (isNaN(text)) {
return text || ''
} else {
let num = text
if (typeof digits === 'number') {
num = Number(text).toLocaleString('en', { minimumFractionDigits: digits })
}
return unit ? `${num} ${unit}` : num
}
}
}
if (type === 'date') {
originColumns[i].align = align || 'center'
originColumns[i].customRender = (text) => (text && format ? moment(text).format(format) : text)
}
if (originColumns[i].children) {
this.handleColumns(originColumns[i].children)
continue
}
}
},
calculateWidthByColumns(columns) {
let baseWidth = 0
if (this.rowSelection) {
baseWidth = this.rowSelection.columnWidth || 60
}
return columns.reduce((cur, col) => {
if (col.children && col.children.length > 0) {
return cur + this.calculateWidthByColumns(col.children)
}
return cur + (col.width === 'auto' ? col.minWidth || defaultTableCellWidth : col.width)
}, baseWidth)
},
async queryTable() {
this.tableLoading = true
try {
let res = []
if (this.tableConfig.query && typeof this.tableConfig.query === 'function') {
res = await this.tableConfig.query(this.queryParams)
}
// console.log('----res----', res)
if (!this.isPageMode) {
// ---- 不分页 ----
this.tableData = res?.data || []
this.tableLoading = false
this.$nextTick(() => {
this.handleTableResize(this.$refs['qt-table'])
})
return
}
// ---- 分页模式 ----
this.tableData = res.data || []
this.total = res.totalCount || 0
if (this.tableData.length === 0 && this.total > 0) {
this.pageParams.pageNum = Math.ceil(this.total / this.pageParams.pageSize)
this.queryTable()
} else {
this.tableLoading = false
this.$nextTick(() => {
this.handleTableResize(this.$refs['qt-table'])
})
}
} catch {
this.tableLoading = false
this.tableData = []
this.total = 0
this.$nextTick(() => {
this.handleTableResize(this.$refs['qt-table'])
})
}
this.handleSelectChange(
this.selectedRowKeys.filter((key) => this.tableData.some((d) => d[this.tableConf.rowKey] === key)),
this.selectedRows.filter((r) =>
this.tableData.some((d) => d[this.tableConf.rowKey] === r[this.tableConf.rowKey])
)
)
},
handleQueryBtnClick() {
this.pageParams.pageNum = 1
this.commitAction('query')
},
handleResetBtnClick() {
this.initQueryTemp()
this.pageParams.pageNum = 1
this.commitAction('query')
},
handleSelectChange(selectedRowKeys, selectedRows) {
this.selectedRowKeys = selectedRowKeys
this.selectedRows = selectedRows
},
renderSerial(followPage, text, record, index) {
if (text) return text
const { pageNum, pageSize } = this.pageParams
const base = followPage ? pageSize * (pageNum - 1) + 1 : 1
return base + index
},
handlePageChange(page, size) {
if (this.pageParams.pageSize === size) {
this.pageParams.pageNum = page
} else {
this.pageParams.pageNum = 1
this.pageParams.pageSize = size
}
this.queryTable()
},
commitAction(action) {
switch (action) {
case 'query':
this.queryTable()
break
case 'reload':
this.queryTemp = {}
this.pageParams.pageNum = 1
this.pageParams.pageSize = defaultPageSize
this.queryTable()
break
case 'gofirst':
this.pageParams.pageNum = 1
this.queryTable()
}
},
getQueryParams(withPage = false) {
if (withPage) {
return cloneDeep(this.queryParams)
} else {
return cloneDeep(this.queryTemp)
}
},
setQueryParamsValue(key, value) {
this.$set(this.queryTemp, key, value)
},
},
}
</script>
<style lang="less">
.ant-query-table {
display: flex;
flex-direction: column;
}
.qt-query {
.ant-form-item {
margin-bottom: 16px;
}
.ant-input-affix-wrapper .ant-input {
width: 190px;
}
.ant-select {
width: 190px;
}
.ant-select-dropdown {
max-height: 246px !important;
top: 28px !important;
}
.ant-select-dropdown-menu {
max-height: 246px;
}
}
.qt-toolbar {
margin-bottom: 16px;
display: flex;
justify-content: space-between;
}
.qt-table {
min-height: 122px;
flex: 1;
overflow-y: auto;
.ant-table-row-indent {
padding-left: 0 !important;
}
.ant-table-thead .ant-table-row-cell-break-word {
text-align: center !important;
font-weight: bold;
}
.qt-table-border__bottom {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
background-color: #e8e8e8;
}
}
.qt-table::-webkit-scrollbar {
display: none;
}
.qt-page {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
</style>