SFT/src/components/ProTable.vue

134 lines
3.7 KiB
Vue

<template>
<div class="pro-table">
<a-form layout="inline" class="search-form">
<slot name="search" :form="searchForm" />
<a-form-item>
<a-space>
<a-button type="primary" :loading="loading" @click="handleSearch"></a-button>
<a-button @click="handleReset"></a-button>
</a-space>
</a-form-item>
</a-form>
<div class="toolbar">
<slot name="actions" />
</div>
<a-table
:columns="columns"
:data-source="list"
:loading="loading"
:row-key="rowKey"
:pagination="pagination"
:row-selection="tableRowSelection"
:scroll="scroll"
@change="handleTableChange"
>
<template v-for="name in forwardSlotNames" #[name]="slotProps" :key="name">
<slot :name="name" v-bind="slotProps" />
</template>
</a-table>
</div>
</template>
<script setup>
import { reactive, ref, computed, onMounted, useSlots } from 'vue'
const props = defineProps({
columns: { type: Array, required: true },
fetchData: { type: Function, required: true },
rowKey: { type: String, default: 'id' },
initialSearch: { type: Object, default: () => ({}) },
pageSize: { type: Number, default: 10 },
// 兼容原有 Boolean 用法(checkbox 多选);新增字符串 'radio' 支持单选场景(如钱包账户列表)
rowSelection: { type: [Boolean, String], default: true },
selectedRowKeys: { type: Array, default: () => [] },
// 透传给内部 a-table 的 scroll 配置。列较多且有列声明了 fixed(如"操作"列固定在最右侧)时,
// 需要显式传 { x: 'max-content' } 之类的配置,否则 antd vue 会因存在 fixed 列而强制把
// table-layout 改成 fixed,导致未设置 width 的列被平均压缩宽度、表头文字挤在一起换行成竖排
// (仅有 fixed 列而不设 scroll.x 时的典型 bug 表现),详见 vc-table Table.js mergedTableLayout。
scroll: { type: Object, default: undefined }
})
const emit = defineEmits(['update:selectedRowKeys', 'search'])
const slots = useSlots()
const forwardSlotNames = computed(() =>
Object.keys(slots).filter((name) => name !== 'search' && name !== 'actions')
)
const searchForm = reactive({ ...props.initialSearch })
const list = ref([])
const loading = ref(false)
const pagination = reactive({
current: 1,
pageSize: props.pageSize,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total}`
})
const tableRowSelection = computed(() =>
props.rowSelection
? {
type: props.rowSelection === 'radio' ? 'radio' : 'checkbox',
selectedRowKeys: props.selectedRowKeys,
onChange: (keys) => emit('update:selectedRowKeys', keys)
}
: undefined
)
async function loadData() {
emit('update:selectedRowKeys', [])
loading.value = true
try {
const res = await props.fetchData({
...searchForm,
page: pagination.current,
pageSize: pagination.pageSize
})
list.value = res?.list || []
pagination.total = res?.total || 0
} finally {
loading.value = false
}
}
async function handleSearch() {
pagination.current = 1
await loadData()
emit('search')
}
function handleReset() {
Object.keys(searchForm).forEach((key) => {
searchForm[key] = props.initialSearch[key] ?? undefined
})
pagination.current = 1
loadData()
}
function handleTableChange(pag) {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
loadData()
}
onMounted(loadData)
function getRecordByKey(key) {
return list.value.find((item) => item[props.rowKey] === key)
}
defineExpose({ reload: loadData, searchForm, getRecordByKey })
</script>
<style scoped>
.search-form {
margin-bottom: 16px;
}
.toolbar {
margin-bottom: 12px;
}
</style>