You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_web/src/components/TableSelectDialog/TableSelectDialog.vue

270 lines
7.5 KiB
Vue

<template>
<Dialog :title="title" v-model="dialogVisible" :appendToBody="true" width="1080">
<ContentWrap>
<!-- 新增查询表单区域 -->
<div v-if="$slots.header" class="search-container">
<slot name="header"></slot>
</div>
<el-table
ref="tableRef"
v-loading="loading"
:data="list"
:row-key="resolveRowKey"
:show-overflow-tooltip="true"
:stripe="true"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
>
<el-table-column
type="selection"
width="55"
reserve-selection
:selectable="isSelectionColumnSelectable"
/>
<el-table-column
v-for="column in columns"
:key="column.prop || column.slotName"
:label="column.label"
:prop="column.slotName ? undefined : column.prop"
:width="column.width"
:min-width="column.minWidth"
:align="column.align || 'center'"
:fixed="column.fixed"
>
<template v-if="column.slotName" #default="scope">
<slot :name="column.slotName" :row="scope.row"></slot>
</template>
</el-table-column>
</el-table>
<Pagination
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<template #footer>
<el-button type="primary" @click="submitSelection">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import type { ElTable } from 'element-plus'
defineOptions({ name: 'TableSelectDialog' })
type TableColumn = {
label: string
prop?: string
slotName?: string
width?: string | number
minWidth?: string | number
align?: 'left' | 'center' | 'right'
fixed?: string | boolean
}
const props = withDefaults(
defineProps<{
title: string
columns: TableColumn[]
fetchApi: (params: Record<string, any>) => Promise<{ list: any[]; total: number }>
selectionType?: 'single' | 'multiple'
rowKey?: string
pageSize?: number
initialRows?: any[]
queryParams?: Record<string, any> // 新增
selectable?: (row: any, index: number) => boolean
/** ✅ 新增 */
defaultSelectedKeys?: (string | number)[]
}>(),
{
selectionType: 'multiple',
rowKey: 'id',
pageSize: 10,
initialRows: () => [],
queryParams: () => ({}), // 默认值
defaultSelectedKeys: () => []
}
)
const emit = defineEmits<{
(e: 'confirm', value: { ids: (number | string)[]; rows: any[] }): void
}>()
const dialogVisible = ref(false)
const loading = ref(false)
const list = ref<any[]>([])
const total = ref(0)
const tableRef = ref<InstanceType<typeof ElTable>>()
const queryParams = reactive({
pageNo: 1,
pageSize: props.pageSize
})
const selectedMap = ref(new Map<number | string, any>())
const syncingSelection = ref(false)
const isSingleSelect = computed(() => props.selectionType === 'single')
const resolveRowKey = (row: Record<string, any>) => row[props.rowKey]
const isRowSelectable = (row: any, index = list.value.findIndex((item) => resolveRowKey(item) === resolveRowKey(row))) => {
return props.selectable ? props.selectable(row, index) : true
}
const isSelectionColumnSelectable = (row: any, index: number) => isRowSelectable(row, index)
const removeUnselectableSelections = () => {
list.value.forEach((row, index) => {
if (!isRowSelectable(row, index)) {
selectedMap.value.delete(resolveRowKey(row))
}
})
}
const refreshSelectionOnTable = async () => {
await nextTick()
const table = tableRef.value
if (!table) return
syncingSelection.value = true
try {
table.clearSelection()
const selectedIds = selectedMap.value
list.value.forEach((row, index) => {
const rowId = resolveRowKey(row)
if (selectedIds.has(rowId) && isRowSelectable(row, index)) {
table.toggleRowSelection(row, true)
}
})
} finally {
syncingSelection.value = false
}
}
/**
* ✅ 新增:根据 defaultSelectedKeys 初始化选中状态
*/
const initDefaultSelection = () => {
if (!props.defaultSelectedKeys.length) return
props.defaultSelectedKeys.forEach((key) => {
selectedMap.value.set(key, { [props.rowKey]: key })
})
}
const getList = async () => {
loading.value = true
try {
const params = getQueryParams()
const data = await props.fetchApi(params)
list.value = data.list || []
total.value = data.total || 0
removeUnselectableSelections()
await refreshSelectionOnTable()
} finally {
loading.value = false
}
}
const handleSelectionChange = (rows: any[]) => {
if (syncingSelection.value) return
const selectableRows = rows.filter((row) => isRowSelectable(row))
if (isSingleSelect.value) {
selectedMap.value.clear()
const latestRow = selectableRows.length ? selectableRows[selectableRows.length - 1] : undefined
if (latestRow) {
selectedMap.value.set(resolveRowKey(latestRow), latestRow)
}
void refreshSelectionOnTable()
return
}
const pageIdSet = new Set(list.value.map((item) => resolveRowKey(item)))
pageIdSet.forEach((id) => {
selectedMap.value.delete(id)
})
selectableRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
}
const handleRowClick = (row: any) => {
if (!isSingleSelect.value) return
if (!isRowSelectable(row)) return
const table = tableRef.value
if (!table) return
syncingSelection.value = true
try {
table.clearSelection()
table.toggleRowSelection(row, true)
} finally {
syncingSelection.value = false
}
selectedMap.value.clear()
selectedMap.value.set(resolveRowKey(row), row)
}
const open = async (rows?: any[]) => {
/* selectedMap.value.clear()
const initialRows = rows || props.initialRows
const nextRows = isSingleSelect.value ? initialRows.slice(0, 1) : initialRows
nextRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true
await getList()*/
selectedMap.value.clear()
// ✅ 优先使用 defaultSelectedKeys
if (props.defaultSelectedKeys.length) {
initDefaultSelection()
}
// ✅ 兼容原来的 initialRows / rows
const initialRows = rows || props.initialRows
const nextRows = isSingleSelect.value ? initialRows.slice(0, 1) : initialRows
nextRows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true
await getList()
}
const submitSelection = () => {
const rows = Array.from(selectedMap.value.values())
const ids = rows.map((row) => resolveRowKey(row))
emit('confirm', { ids, rows })
dialogVisible.value = false
}
// 暴露给父组件的方法
const reload =async () => {
loading.value = true
// 这里是你组件内部原有的加载数据逻辑
await getList()
}
// 3. 合并参数:将父组件传入的 queryParams 与内部参数合并
const getQueryParams = () => {
return {
pageNo: queryParams.pageNo || 1,
pageSize: queryParams.pageSize || 10,
...props.queryParams // 父组件传入的查询条件
}
}
// 4. 监听父组件参数变化
watch(() => props.queryParams, async () => {
await getList()
}, { deep: true })
defineExpose({ open, reload })
</script>
<style scoped>
.search-container {
margin-bottom: 10px;
padding: 0 0px;
}
</style>