134 lines
3.7 KiB
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>
|