270 lines
7.1 KiB
Vue
Raw Normal View History

2025-10-10 17:35:28 +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">
2025-12-05 17:58:22 +08:00
<vxe-table :data="tableData" :loading="loading" border :checkbox-config="config.checkboxConfig"
:height="scrollYConfig.height" @checkbox-change="handleCheckboxChange" :row-config="rowConfig"
:show-overflow="showOverflow" @current-change="handleRowClick" ref="tableRef" :size="size"
2026-01-15 17:34:50 +08:00
:current-row="currentRow" :scroll-y="scrollYConfig" :tooltip-config="{ appendToBody: true, zIndex: 3000 }"
2025-10-10 17:35:28 +08:00
:header-cell-class-name="enableColumnDrag ? 'el-table-header-cell' : ''" v-bind="$attrs">
<!-- 动态渲染列 -->
<template v-for="(column, i) in columns" :key="`${column.field}-${i}`">
2025-11-18 18:12:34 +08:00
<vxe-column type="checkbox" width="40" align="center" v-if="column.type == 'selection'" />
2026-01-09 17:19:49 +08:00
<vxe-column width="40" align="center" :title="column.title || '序号'" v-if="column.type == 'seq'">
<template #default="scope">
{{ scope.row._index }}
</template>
</vxe-column>
2025-11-18 18:12:34 +08:00
<vxe-column v-bind="column" v-if="column.field">
2025-10-10 17:35:28 +08:00
<!-- 自定义列模板 -->
<template v-if="column.slotName" #default="scope">
2026-01-14 17:38:50 +08:00
<slot :name="column.slotName" :row="scope.row" :$index="scope.$rowIndex"></slot>
2025-10-10 17:35:28 +08:00
</template>
</vxe-column>
</template>
</vxe-table>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick, watch, onMounted } from 'vue'
// @ts-ignore
import Sortable from 'sortablejs'; // 引入sortablejs
2026-01-13 11:45:42 +08:00
import type { VxeComponentSizeType, VxeColumnPropTypes } from 'vxe-table'
2025-10-10 17:35:28 +08:00
// 定义列配置类型
interface TableColumn {
field: string
title: string
width?: number | string
2026-01-13 11:45:42 +08:00
align?: string,
2025-10-10 17:35:28 +08:00
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
},
// 行配置
rowConfig: {
type: Object,
2026-01-15 17:34:50 +08:00
default: () => ({
isHover: true, height: 30, isCurrent: true, keyField: '_index'
})
2025-10-10 17:35:28 +08:00
},
2025-11-18 18:12:34 +08:00
// 表格配置
config: {
type: Object,
default: () => ({}),
},
2025-10-10 17:35:28 +08:00
// 是否显示内容溢出省略
showOverflow: {
type: Boolean,
default: true
},
2026-01-15 17:34:50 +08:00
// 是否高亮当前行 已废弃
2025-10-10 17:35:28 +08:00
highlightCurrentRow: {
type: Boolean,
default: true
},
// 纵向滚动配置
scrollYConfig: {
type: Object,
2025-12-05 17:58:22 +08:00
default: () => ({
enabled: true, // 强制启用虚拟滚动
rSize: 30, // 行高(需与实际行高一致)
height: 600, // 固定表格高度(关键,单位px)
})
2025-10-10 17:35:28 +08:00
},
// 表格尺寸
size: {
2026-01-13 11:45:42 +08:00
type: String as () => VxeComponentSizeType,
2025-10-10 17:35:28 +08:00
default: 'medium'
},
// 拖拽列
enableColumnDrag: {
type: Boolean,
default: false
}
})
2025-11-18 18:12:34 +08:00
const emits = defineEmits(['current-change', "column-drag-end", "selection-change"])
2025-10-10 17:35:28 +08:00
const tableRef = ref<any>(null)
const currentRow = ref<any>(null)
// 处理行点击
const handleRowClick = (params: { row: any }) => {
currentRow.value = params.row
emits('current-change', params.row)
}
2025-11-18 18:12:34 +08:00
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()
}
2025-10-10 17:35:28 +08:00
// 设置当前行
const setCurrentRow = (row: any) => {
if (tableRef.value) {
tableRef.value.setCurrentRow(row)
nextTick(() => {
tableRef.value.scrollToRow(row)
})
}
}
2025-10-16 11:20:50 +08:00
// 清除高亮行
const clearCurrentRow = () => {
if (tableRef.value) {
tableRef.value.clearCurrentRow()
}
}
2025-10-10 17:35:28 +08:00
// 初始化列拖拽功能
onMounted(() => {
if (props.enableColumnDrag && tableRef.value) {
initColumnDrag();
}
});
// 监听数据变化时重置当前行
2026-01-09 17:19:49 +08:00
watch(() => props.tableData, (newVal) => {
// 自定义索引
2026-01-14 17:38:50 +08:00
if (newVal) {
props.tableData.forEach((item: any, index) => {
item._index = index + 1
})
}
2026-01-09 17:19:49 +08:00
2025-10-10 17:35:28 +08:00
currentRow.value = null
})
// 初始化列拖拽
const initColumnDrag = () => {
// 获取表头单元格
const tableHeader = tableRef.value.$el.querySelector('.vxe-table--header thead tr');
if (!tableHeader) return;
// 初始化拖拽
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 => !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,
2025-10-16 11:20:50 +08:00
clearCurrentRow,
2025-11-18 18:12:34 +08:00
toggleRowSelection,
allSelection,
2025-10-10 17:35:28 +08:00
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) {
2025-10-28 17:22:53 +08:00
background-image: linear-gradient(#96B8D9, #96B8D9), linear-gradient(#96B8D9, #96B8D9) !important;
2025-10-10 17:35:28 +08:00
}
// 表头边框
:deep(.vxe-table--render-default.border--full .vxe-header--column) {
2025-10-28 17:22:53 +08:00
background-image: linear-gradient(#1F6DD3, #1F6DD3), linear-gradient(#1F6DD3, #1F6DD3) !important;
2025-10-10 17:35:28 +08:00
}
</style>