293 lines
6.9 KiB
Vue
293 lines
6.9 KiB
Vue
<template>
|
||
<div class="search-table-container">
|
||
<a-card class="general-card" :title="title">
|
||
<!-- 搜索表单 -->
|
||
<SearchForm
|
||
:model-value="formModel"
|
||
:form-items="formItems"
|
||
:show-buttons="showSearchButtons"
|
||
:search-button-text="searchButtonText"
|
||
:reset-button-text="resetButtonText"
|
||
@update:model-value="handleFormModelUpdate"
|
||
@search="handleSearch"
|
||
@reset="handleReset"
|
||
>
|
||
<template #form-items>
|
||
<slot name="form-items" />
|
||
</template>
|
||
</SearchForm>
|
||
|
||
<a-divider style="margin-top: 0" />
|
||
|
||
<!-- 数据表格:工具栏始终展示,表格主体随状态切换 -->
|
||
<DataTable
|
||
:data="data"
|
||
:columns="columns"
|
||
:loading="loading"
|
||
:pagination="pagination"
|
||
:bordered="bordered"
|
||
:row-selection="rowSelection"
|
||
:scroll="responsiveScroll"
|
||
:show-toolbar="showToolbar"
|
||
:show-download="showDownload"
|
||
:show-refresh="showRefresh"
|
||
:show-density="showDensity"
|
||
:show-column-setting="showColumnSetting"
|
||
:download-button-text="downloadButtonText"
|
||
:refresh-tooltip-text="refreshTooltipText"
|
||
:density-tooltip-text="densityTooltipText"
|
||
:column-setting-tooltip-text="columnSettingTooltipText"
|
||
:status="stateStatus"
|
||
:loading-text="loadingText"
|
||
:empty-text="emptyText"
|
||
:error-text="errorText"
|
||
:partial-text="partialText"
|
||
@page-change="handlePageChange"
|
||
@page-size-change="handlePageSizeChange"
|
||
@selection-change="handleSelectionChange"
|
||
@row-click="handleRowClick"
|
||
@refresh="handleRefresh"
|
||
@download="handleDownload"
|
||
@density-change="handleDensityChange"
|
||
@column-change="handleColumnChange"
|
||
@retry="handleRefresh"
|
||
>
|
||
<template #toolbar-left>
|
||
<slot name="toolbar-left" />
|
||
</template>
|
||
<template #toolbar-right>
|
||
<slot name="toolbar-right" />
|
||
</template>
|
||
<!-- 动态插槽透传 -->
|
||
<template v-for="col in slotColumns" :key="col.dataIndex" #[String(col.slotName)]="slotProps">
|
||
<slot :name="col.slotName" v-bind="slotProps" />
|
||
</template>
|
||
</DataTable>
|
||
</a-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { computed, PropType } from 'vue'
|
||
import SearchForm from '../search-form/index.vue'
|
||
import type { FormItem } from '../search-form/types'
|
||
import DataTable from '../data-table/index.vue'
|
||
import type { TableColumnData, TableRowSelection } from '@arco-design/web-vue/es/table/interface'
|
||
|
||
type SizeProps = 'mini' | 'small' | 'medium' | 'large'
|
||
|
||
const props = defineProps({
|
||
// 表单相关
|
||
formModel: {
|
||
type: Object as PropType<Record<string, any>>,
|
||
required: true,
|
||
},
|
||
formItems: {
|
||
type: Array as PropType<FormItem[]>,
|
||
default: () => [],
|
||
},
|
||
showSearchButtons: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
searchButtonText: {
|
||
type: String,
|
||
default: '查询',
|
||
},
|
||
resetButtonText: {
|
||
type: String,
|
||
default: '重置',
|
||
},
|
||
// 表格相关
|
||
data: {
|
||
type: Array as PropType<any[]>,
|
||
default: () => [],
|
||
},
|
||
columns: {
|
||
type: Array as PropType<TableColumnData[]>,
|
||
required: true,
|
||
},
|
||
loading: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
status: {
|
||
type: String as PropType<'loading' | 'empty' | 'error' | 'success' | 'partial' | 'unauthorized' | undefined>,
|
||
default: undefined,
|
||
},
|
||
loadingText: {
|
||
type: String,
|
||
default: '加载中',
|
||
},
|
||
emptyText: {
|
||
type: String,
|
||
default: '暂无数据',
|
||
},
|
||
errorText: {
|
||
type: String,
|
||
default: '数据暂时不可用,请稍后重试。',
|
||
},
|
||
partialText: {
|
||
type: String,
|
||
default: '部分数据加载失败,当前页面展示可用数据。',
|
||
},
|
||
pagination: {
|
||
type: Object as PropType<{
|
||
current: number
|
||
pageSize: number
|
||
total?: number
|
||
}>,
|
||
default: () => ({
|
||
current: 1,
|
||
pageSize: 20,
|
||
}),
|
||
},
|
||
bordered: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
rowSelection: {
|
||
type: Object as PropType<TableRowSelection | undefined>,
|
||
default: undefined,
|
||
},
|
||
scroll: {
|
||
type: Object as PropType<{ x?: number | string; y?: number | string } | undefined>,
|
||
default: undefined,
|
||
},
|
||
// 工具栏相关
|
||
showToolbar: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
showDownload: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
showRefresh: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
showDensity: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
showColumnSetting: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
// 文本配置
|
||
title: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
downloadButtonText: {
|
||
type: String,
|
||
default: '下载',
|
||
},
|
||
refreshTooltipText: {
|
||
type: String,
|
||
default: '刷新',
|
||
},
|
||
densityTooltipText: {
|
||
type: String,
|
||
default: '密度',
|
||
},
|
||
columnSettingTooltipText: {
|
||
type: String,
|
||
default: '列设置',
|
||
},
|
||
})
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'update:formModel', value: Record<string, any>): void
|
||
(e: 'search'): void
|
||
(e: 'reset'): void
|
||
(e: 'page-change', current: number): void
|
||
(e: 'page-size-change', pageSize: number): void
|
||
(e: 'selection-change', rowKeys: (string | number)[]): void
|
||
(e: 'row-click', record: any, ev: Event): void
|
||
(e: 'refresh'): void
|
||
(e: 'download'): void
|
||
(e: 'density-change', size: SizeProps): void
|
||
(e: 'column-change', columns: TableColumnData[]): void
|
||
}>()
|
||
|
||
// 计算需要插槽的列(动态插槽透传)
|
||
const slotColumns = computed(() => {
|
||
return props.columns.filter((col) => col.slotName)
|
||
})
|
||
|
||
const stateStatus = computed(() => {
|
||
if (props.status) return props.status
|
||
if (props.loading) return 'loading'
|
||
if (!props.data.length) return 'empty'
|
||
return 'success'
|
||
})
|
||
|
||
const responsiveScroll = computed(() => {
|
||
return props.scroll || { x: 'max-content' }
|
||
})
|
||
|
||
const handleFormModelUpdate = (value: Record<string, any>) => {
|
||
emit('update:formModel', value)
|
||
}
|
||
|
||
const handleSearch = () => {
|
||
emit('search')
|
||
}
|
||
|
||
const handleReset = () => {
|
||
emit('reset')
|
||
}
|
||
|
||
const handlePageChange = (current: number) => {
|
||
emit('page-change', current)
|
||
}
|
||
|
||
const handlePageSizeChange = (pageSize: number) => {
|
||
emit('page-size-change', pageSize)
|
||
}
|
||
|
||
const handleSelectionChange = (rowKeys: (string | number)[]) => {
|
||
emit('selection-change', rowKeys)
|
||
}
|
||
|
||
const handleRowClick = (record: any, ev: Event) => {
|
||
emit('row-click', record, ev)
|
||
}
|
||
|
||
const handleRefresh = () => {
|
||
emit('refresh')
|
||
}
|
||
|
||
const handleDownload = () => {
|
||
emit('download')
|
||
}
|
||
|
||
const handleDensityChange = (size: SizeProps) => {
|
||
emit('density-change', size)
|
||
}
|
||
|
||
const handleColumnChange = (columns: TableColumnData[]) => {
|
||
emit('column-change', columns)
|
||
}
|
||
</script>
|
||
|
||
<script lang="ts">
|
||
export default {
|
||
name: 'SearchTable',
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.search-table-container {
|
||
padding: 0 20px 20px 20px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.search-table-container {
|
||
padding: 0 12px 12px;
|
||
}
|
||
}
|
||
</style>
|