282 lines
7.8 KiB
Vue
282 lines
7.8 KiB
Vue
<!--
|
||
* 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> |