517 lines
16 KiB
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>
|