149 lines
3.7 KiB
Vue
149 lines
3.7 KiB
Vue
<template>
|
|
<div class="table-container">
|
|
<el-table
|
|
ref="tableRef"
|
|
height="100%"
|
|
style="width: 100%"
|
|
:data="tableData"
|
|
:row-key="rowKey"
|
|
:tree-props="treeProps"
|
|
:border="border"
|
|
:highlight-current-row="highlightCurrentRow"
|
|
v-bind="$attrs"
|
|
@row-click="handleRowClick"
|
|
@row-contextmenu="handleRowContextmenu"
|
|
>
|
|
<!-- 动态表头列 -->
|
|
<template v-for="(column, index) in columns" :key="index">
|
|
<el-table-column
|
|
v-if="column.type === 'index'"
|
|
type="index"
|
|
:label="column.label"
|
|
:width="column.width || 60"
|
|
:align="column.align || 'center'"
|
|
/>
|
|
<el-table-column
|
|
v-else-if="!column.slotName"
|
|
:prop="column.prop"
|
|
:label="column.label"
|
|
:width="column.width"
|
|
:align="column.align || 'left'"
|
|
:sortable="column.sortable"
|
|
/>
|
|
|
|
<!-- 具名插槽列 -->
|
|
<el-table-column
|
|
v-else
|
|
:label="column.label"
|
|
:width="column.width"
|
|
:align="column.align || 'left'"
|
|
>
|
|
<template #default="{ row }">
|
|
<slot :name="column.slotName" :row="row" />
|
|
</template>
|
|
</el-table-column>
|
|
</template>
|
|
</el-table>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch } from 'vue'
|
|
import type { TableInstance } from 'element-plus'
|
|
|
|
// 定义表格列配置接口
|
|
interface TableColumn {
|
|
prop?: string
|
|
type?: string
|
|
label: string
|
|
width?: string | number
|
|
align?: 'left' | 'center' | 'right'
|
|
slotName?: string // 具名插槽名称
|
|
sortable?: boolean // 是否可排序
|
|
}
|
|
|
|
// 定义表格组件属性接口
|
|
interface Props {
|
|
tableData: any[]
|
|
rowKey?: string
|
|
treeProps?: {
|
|
children: string
|
|
}
|
|
columns?: TableColumn[]
|
|
border?: boolean // 是否显示边框
|
|
highlightCurrentRow?: boolean // 是否高亮当前行
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
border: true,
|
|
highlightCurrentRow: true,
|
|
})
|
|
|
|
// 定义组件抛出的事件
|
|
const emit = defineEmits<{
|
|
(e: 'row-click', row: any): void // 保留原有行点击事件
|
|
(e: 'row-contextmenu', row: any, column: any, event: MouseEvent): void
|
|
}>()
|
|
|
|
const tableRef = ref<TableInstance>()
|
|
|
|
// 维护当前选中行状态,用于二次点击取消选中
|
|
const selectedRow = ref<any>(null)
|
|
|
|
/**
|
|
* 处理行点击事件(支持二次点击取消选中)
|
|
*/
|
|
const handleRowClick = (row: any) => {
|
|
// 增加空值保护,避免传递 null 给父组件
|
|
if (!row) return
|
|
|
|
// 判断是否是二次点击同一行
|
|
if (selectedRow.value && selectedRow.value === row) {
|
|
// 二次点击:取消选中
|
|
selectedRow.value = null
|
|
tableRef.value?.setCurrentRow() // 不传参即清除选中 [^14^]
|
|
} else {
|
|
// 首次点击或点击不同行:选中该行
|
|
selectedRow.value = row
|
|
tableRef.value?.setCurrentRow(row)
|
|
}
|
|
|
|
emit('row-click', selectedRow.value)
|
|
}
|
|
|
|
/**
|
|
* 处理行右键菜单事件
|
|
*/
|
|
const handleRowContextmenu = (row: any, column: any, event: MouseEvent) => {
|
|
console.log('row', row)
|
|
emit('row-contextmenu', row, column, event)
|
|
}
|
|
|
|
// 监听 tableData 变化,数据更新时取消选中
|
|
watch(
|
|
() => props.tableData,
|
|
() => {
|
|
// 数据更新时,清除选中状态
|
|
selectedRow.value = null
|
|
tableRef.value?.setCurrentRow() // 不传参即清除选中 [^14^]
|
|
},
|
|
{ deep: true }, // 深度监听,防止数组元素变化未触发
|
|
)
|
|
|
|
// 暴露清除当前选中行的方法
|
|
defineExpose({
|
|
clearCurrentRow: () => {
|
|
selectedRow.value = null
|
|
tableRef.value?.setCurrentRow()
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.table-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
// overflow: hidden;
|
|
}
|
|
</style>
|