2026-03-19 18:00:24 +08:00

282 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* 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"
:height="scrollYConfig.height" @checkbox-change="handleCheckboxChange"
:row-config="{ isHover: true, height: 30, isCurrent: true, keyField: keyField ? keyField : '_index' }"
: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.headerSlot" #header="scope">
<slot :name="column.headerSlot" :column="scope.column" :$index="scope.$index" />
</template>
<!-- 自定义列模板 -->
<template v-if="column.slotName" #default="scope">
<slot :name="column.slotName" :row="scope.row" :$index="scope.$rowIndex"></slot>
</template>
</vxe-column>
</template>
<!-- 操作列 -->
<vxe-column v-if="$slots.action" :title="config.actionLabel || '操作'" :width="config.actionWidth"
:fixed="config.actionFixed || ''" :align="config.actionAlign || 'center'">
<template #default="scope">
<slot name="action" :row="scope.row" :column="scope.column" :$index="scope.$index" />
</template>
</vxe-column>
</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
},
// 行配置
keyField: {
type: String,
default: 'id'
},
// 表格配置
config: {
type: Object,
default: () => ({}),
},
// 是否显示内容溢出省略
showOverflow: {
type: Boolean,
default: true
},
// 纵向滚动配置
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, row, records }: { checked: boolean, row: any, records: any[] }) => {
// console.log('选中的数据:', records);
// console.log('是否全选:', checked);
emits('selection-change', checked, row, 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>