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">
|
2026-03-19 18:00:24 +08:00
|
|
|
|
<template v-if="column.headerSlot" #header="scope">
|
|
|
|
|
|
<slot :name="column.headerSlot" :column="scope.column" :$index="scope.$index" />
|
|
|
|
|
|
</template>
|
2026-03-09 14:09:14 +08:00
|
|
|
|
<!-- 自定义列模板 -->
|
|
|
|
|
|
<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"
|
2026-03-19 18:00:24 +08:00
|
|
|
|
:fixed="config.actionFixed || ''" :align="config.actionAlign || 'center'">
|
2026-03-11 18:08:00 +08:00
|
|
|
|
<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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-16 18:01:58 +08:00
|
|
|
|
const handleCheckboxChange = ({ checked, row, records }: { checked: boolean, row: any, records: any[] }) => {
|
2026-03-09 14:09:14 +08:00
|
|
|
|
// console.log('选中的数据:', records);
|
|
|
|
|
|
// console.log('是否全选:', checked);
|
2026-03-16 18:01:58 +08:00
|
|
|
|
emits('selection-change', checked, row, records)
|
2026-03-09 14:09:14 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理行选择
|
|
|
|
|
|
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>
|