279 lines
7.6 KiB
Vue
Raw Normal View History

2026-03-09 14:09:14 +08:00
<!--
* vxe-table 通用表格组件 适用数据量大
* @props columns: 表格列配置
* @props tableData: 表格数据
* @props loading: 加载状态
* @props rowConfig: 行配置
* @props showOverflow: 是否显示内容溢出省略
* @props highlightCurrentRow: 是否高亮当前行
* @props scrollYConfig: 纵向滚动配置
* scroll-y 纵向滚动 设置虚拟滚动
-->
<template>
<div class="common-table-container">
<vxe-table :data="tableData" :loading="loading" border :checkbox-config="config.checkboxConfig"
2026-03-13 17:49:15 +08:00
:height="scrollYConfig.height" @checkbox-change="handleCheckboxChange"
:row-config="{ isHover: true, height: 30, isCurrent: true, keyField: keyField ? keyField : '_index' }"
2026-03-09 14:09:14 +08:00
:show-overflow="showOverflow" @current-change="handleRowClick" ref="tableRef" :size="size"
:current-row="currentRow" :scroll-y="scrollYConfig" :tooltip-config="{ appendToBody: true, zIndex: 3000 }"
:header-cell-class-name="enableColumnDrag ? 'el-table-header-cell' : ''" v-bind="$attrs">
<!-- 动态渲染列 -->
<template v-for="(column, i) in columns" :key="`${column.field}-${i}`">
<vxe-column type="checkbox" width="40" align="center" v-if="column.type == 'selection'" />
<vxe-column width="40" align="center" :title="column.title || '序号'" v-if="column.type == 'seq'">
<template #default="scope">
{{ scope.row._index }}
</template>
</vxe-column>
<vxe-column v-bind="column" v-if="column.field">
<!-- 自定义列模板 -->
<template v-if="column.slotName" #default="scope">
<slot :name="column.slotName" :row="scope.row" :$index="scope.$rowIndex"></slot>
</template>
</vxe-column>
</template>
2026-03-11 18:08:00 +08:00
<!-- 操作列 -->
<vxe-column v-if="$slots.action" :title="config.actionLabel || '操作'" :width="config.actionWidth"
:fixed="config.actionFixed || 'right'" :align="config.actionAlign || 'center'">
<template #default="scope">
<slot name="action" :row="scope.row" :column="scope.column" :$index="scope.$index" />
</template>
</vxe-column>
2026-03-09 14:09:14 +08:00
</vxe-table>
</div>
</template>
<script setup lang="ts">
import Sortable from 'sortablejs'; // 引入sortablejs
import type { VxeComponentSizeType, VxeColumnPropTypes } from 'vxe-table'
// 在子组件中定义插槽类型
defineSlots<{
// 具名插槽和动态插槽(不含 default)
[slotName: string]: (props: { row?: any; column?: any; $index?: number }) => any;
}>();
// 定义列配置类型
interface TableColumn {
field: string
title: string
width?: number | string
align?: string,
slotName?: string // 自定义插槽名称
[key: string]: any // 支持其他vxe-column属性
}
// 定义props类型
const props = defineProps({
// 表格数据
tableData: {
type: Array,
default: () => []
},
// 加载状态
loading: {
type: Boolean,
default: false
},
// 列配置
columns: {
type: Array as () => TableColumn[],
required: true
},
// 行配置
2026-03-13 17:49:15 +08:00
keyField: {
type: String,
default: 'id'
2026-03-09 14:09:14 +08:00
},
// 表格配置
config: {
type: Object,
default: () => ({}),
},
// 是否显示内容溢出省略
showOverflow: {
type: Boolean,
default: true
},
2026-03-11 18:08:00 +08:00
2026-03-09 14:09:14 +08:00
// 纵向滚动配置
scrollYConfig: {
type: Object,
default: () => ({
enabled: true, // 强制启用虚拟滚动
rSize: 30, // 行高(需与实际行高一致)
height: 600, // 固定表格高度(关键,单位px)
})
},
// 表格尺寸
size: {
type: String as () => VxeComponentSizeType,
default: 'medium'
},
// 拖拽列
enableColumnDrag: {
type: Boolean,
default: false
}
})
const emits = defineEmits(['current-change', "column-drag-end", "selection-change"])
const tableRef = ref<any>(null)
const currentRow = ref<any>(null)
// 处理行点击
const handleRowClick = (params: { row: any }) => {
currentRow.value = params.row
emits('current-change', params.row)
}
const handleCheckboxChange = ({ checked, records }: { checked: boolean, records: any[] }) => {
// console.log('选中的数据:', records);
// console.log('是否全选:', checked);
emits('selection-change', records)
};
// 处理行选择
const toggleRowSelection = (row: any, checked: boolean) => {
if (tableRef.value) {
tableRef.value.setCheckboxRow(row, checked)
}
}
// 获取所有选中行数据
const allSelection = () => {
return tableRef.value.getCheckboxRecords()
}
// 设置当前行
const setCurrentRow = (row: any) => {
if (tableRef.value) {
tableRef.value.setCurrentRow(row)
nextTick(() => {
tableRef.value.scrollToRow(row)
})
}
}
// 清除高亮行
const clearCurrentRow = () => {
if (tableRef.value) {
tableRef.value.clearCurrentRow()
}
}
// 初始化列拖拽功能
onMounted(() => {
if (props.enableColumnDrag && tableRef.value) {
initColumnDrag();
}
});
// 监听数据变化时重置当前行
watch(() => props.tableData, (newVal: any[]) => {
// 自定义索引
if (newVal) {
props.tableData.forEach((item: any, index: number) => {
item._index = index + 1
})
}
currentRow.value = null
})
// 初始化列拖拽
const initColumnDrag = () => {
// 获取表头单元格
const tableHeader = tableRef.value.$el.querySelector('.vxe-table--header thead tr');
if (!tableHeader) return;
// 初始化拖拽
// @ts-ignore
const sortable = new Sortable(tableHeader, {
animation: 150, // 动画时间
handle: '.el-table-header-cell', // 拖拽手柄
ghostClass: 'sortable-ghost', // 拖拽时的占位符样式
onEnd: (evt: any) => {
// console.log('evt==>', evt);
// 过滤掉固定列
const draggableColumns = props.columns.filter((col: any) => !col.fixed);
// 如果发生了位置变化
if (evt.oldIndex !== evt.newIndex && draggableColumns.length) {
// 复制原数组并调整顺序
const newColumns = [...draggableColumns];
const [movedItem] = newColumns.splice(evt.oldIndex, 1);
newColumns.splice(evt.newIndex, 0, movedItem);
// 更新原始columns数组中对应列的位置
const updatedColumns = [...props.columns];
// 先移除所有可拖拽列
const nonDraggableColumns = updatedColumns.filter(col => col.fixed);
// 合并固定列和新顺序的可拖拽列
const resultColumns = nonDraggableColumns.concat(newColumns);
// 触发事件,通知父组件列顺序已改变
emits('column-drag-end', {
oldIndex: evt.oldIndex,
newIndex: evt.newIndex,
columns: [...resultColumns]
});
}
}
});
};
// 暴露公共方法
defineExpose({
setCurrentRow,
clearCurrentRow,
toggleRowSelection,
allSelection,
tableRef,
})
</script>
<style scoped lang="scss">
.common-table-container {
height: 100%;
}
:deep(.vxe-header--row th) {
border-color: #1F6DD3;
background-color: #1F6DD3;
color: #fff;
}
:deep(.vxe-body--row td) {
border-color: #1F6DD3;
}
:deep(.vxe-table--row-hover) {
background-color: #1F6DD3;
}
:deep(.vxe-table--current-row) {
background-color: #1F6DD3;
}
:deep(.el-table-header-cell) {
cursor: move;
user-select: none;
}
// 表格边框
:deep(.vxe-table--render-default.border--full .vxe-body--column) {
background-image: linear-gradient(#96B8D9, #96B8D9), linear-gradient(#96B8D9, #96B8D9) !important;
}
// 表头边框
:deep(.vxe-table--render-default.border--full .vxe-header--column) {
background-image: linear-gradient(#1F6DD3, #1F6DD3), linear-gradient(#1F6DD3, #1F6DD3) !important;
}
</style>