311 lines
8.4 KiB
Vue
311 lines
8.4 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="rowConfig"
|
||
:show-overflow="showOverflow" @current-change="handleRowClick" ref="tableRef" :size="size"
|
||
:class="{ 'drag-table': enableRowDrag }" :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>
|
||
</vxe-table>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, nextTick, watch, onMounted } from 'vue'
|
||
import Sortable from 'sortablejs'; // 引入sortablejs
|
||
import type { VxeComponentSizeType, VxeColumnPropTypes } from 'vxe-table'
|
||
|
||
// 定义列配置类型
|
||
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
|
||
},
|
||
// 行配置
|
||
rowConfig: {
|
||
type: Object,
|
||
default: () => ({
|
||
isHover: true, height: 30, isCurrent: true, keyField: '_index'
|
||
})
|
||
},
|
||
// 表格配置
|
||
config: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
},
|
||
// 是否显示内容溢出省略
|
||
showOverflow: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 是否高亮当前行 已废弃
|
||
highlightCurrentRow: {
|
||
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
|
||
},
|
||
// 拖拽行
|
||
enableRowDrag: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
})
|
||
|
||
const emits = defineEmits(['current-change', "column-drag-end", "row-drag-end", "selection-change"])
|
||
|
||
const tableRef = ref<any>(null)
|
||
const currentRow = ref<any>(null)
|
||
// 处理行点击
|
||
const handleRowClick = (params: { row: any, $rowIndex, column, $columnIndex, cell, $event }) => {
|
||
currentRow.value = params.row
|
||
emits('current-change', params.row, params.$rowIndex, params.column, params.$columnIndex, params.cell, params.$event)
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
if (props.enableRowDrag && tableRef.value) {
|
||
initRowDrag();
|
||
}
|
||
});
|
||
|
||
// 监听数据变化时重置当前行
|
||
watch(() => props.tableData, (newVal) => {
|
||
// 自定义索引
|
||
if (newVal) {
|
||
props.tableData.forEach((item: any, index) => {
|
||
item._index = index + 1
|
||
})
|
||
}
|
||
|
||
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]
|
||
});
|
||
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// 初始化行拖拽
|
||
const initRowDrag = () => {
|
||
|
||
const tableBody = tableRef.value.$el.querySelector('.vxe-table--body-wrapper tbody');
|
||
const sortable = new Sortable(tableBody, {
|
||
animation: 150, // 拖拽动画时长
|
||
ghostClass: 'sortable-ghost', // 拖拽占位符样式
|
||
chosenClass: 'sortable-chosen', // 选中行样式
|
||
// 拖拽结束回调
|
||
onEnd: (evt: any) => {
|
||
const { oldIndex, newIndex } = evt
|
||
if (oldIndex === newIndex) return // 未改变位置则不处理
|
||
|
||
// 调整数组顺序
|
||
const moveRow = props.tableData.splice(oldIndex, 1)[0]
|
||
props.tableData.splice(newIndex, 0, moveRow)
|
||
|
||
// 重新更新排序值
|
||
props.tableData.forEach((item: any, index) => {
|
||
item.sort = index + 1
|
||
})
|
||
// console.log("🚀 ~ initRowDrag ~ props.tableData:", props.tableData)
|
||
emits('row-drag-end', {
|
||
oldIndex,
|
||
newIndex,
|
||
list: props.tableData
|
||
})
|
||
}
|
||
})
|
||
};
|
||
|
||
|
||
|
||
// 暴露公共方法
|
||
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> |