This commit is contained in:
linfso 2025-07-07 10:09:48 +08:00
parent 543afc5729
commit 5e90851927
3 changed files with 3906 additions and 0 deletions

View File

@ -0,0 +1,711 @@
<template>
<div class="xd-table full-block flex-layout" :id="tableId" >
<div class="relative overflow-x-auto">
<el-table
class="xd-tb-wrapper"
:data="data"
tabindex="0"
stripe
border
:key="refreshKeyId"
ref="tableRef"
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
@cell-click="handleCellClick">
<el-table-column prop="date" label="日期" width="180"></el-table-column>
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="age" label="年龄" width="100"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
<el-table-column prop="email" label="邮箱"></el-table-column>
</el-table>
<!-- 选择范围指示器 -->
<div id="selectionRange" class="selection-range" style="display: none;"></div>
</div>
<div class="mt-6 p-4 border border-gray-200 rounded-lg">
<h3 class="font-medium text-gray-700 mb-2">选中内容预览:</h3>
<pre id="selectionPreview" class="p-3 bg-gray-50 rounded overflow-x-auto text-sm min-h-16">
框选单元格后,内容将显示在这里...
</pre>
</div>
</div>
</template>
<script>
import { listSysCustomizeTpl, delSysCustomizeTpl, addSysCustomizeTpl, updateSysCustomizeTpl } from "@/api/system/sysCustomizeTpl";
import { genJsSeqNo } from "@/utils/custom";
import clip from '@/utils/clipboard'
import Sortable from 'sortablejs'
import draggable from 'vuedraggable'
import origTable from './table.jsx'
import VirtualScroll from "el-table-virtual-scroll";
import { VirtualColumn } from "el-table-virtual-scroll";
const components = { origTable, draggable,VirtualScroll }
export default {
name: 'XdTable',
components,
props: {
id: {
type: String,
},
showSearch: {
type: Boolean,
default: true,
},
showStatInfo: {
type: Boolean,
default: false,
},
canHideColumns: {
type: Boolean,
default: true,
},
storageName: {
type: String,
required: false
},
queryParams:{
type: Object,
default: null,
},
total:{
type: Number,
default: 0,
},
showRightButton:{
type: Boolean,
default: true,
},
showPagination:{
type: Boolean,
default: true,
},
showOperateButton:{
type: Boolean,
default: true,
},
showCustomizeTpl:{
type: Boolean,
default: true,
},
data:{
type: Array
},
},
data() {
return {
refreshKeyId:Math.random().toString(36).substring(3,10),
tableId:this.id?this.id:"XdTable"+Math.random().toString(36).substring(3,10),
isSelecting: false,
startCell: null,
endCell: null,
selectedCells: [],
ctrlKeyPressed: false,
shiftKeyPressed: false,
// 表格个性化记忆配置
isNeedDbSaveTpl:false,
fullUrl:null,
pageId:null,
saveStorageName:this.storageName,
storeTplList:[],
storageList: [],
openTbShowTplDlg:false,
curentTbShowTpl:null,
form:{
id: null,
tplCode: null,
tplName: null,
tplType: null,
tagId: null,
blDefault: null,
tplData: null,
},
rules: {
tplName: [
{ required: true, message: "模版名称必填", trigger: "blur" }
],
}
}
},
watch: {
},
mounted() {
// 初始化表格单元格选择功能
this.initCellSelection();
// 监听键盘状态
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('keyup', this.handleKeyUp);
},
beforeDestroy() {
// 清理事件监听
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
const tableEl = this.$refs.tableRef.$el;
tableEl.removeEventListener('mousedown', this.handleTableMouseDown);
document.removeEventListener('mousemove', this.handleDocumentMouseMove);
document.removeEventListener('mouseup', this.handleDocumentMouseUp);
},
methods: {
// 初始化单元格选择功能
initCellSelection() {
const tableEl = this.$refs.tableRef.$el;
const selectionRange = document.getElementById('selectionRange');
// 鼠标按下事件
tableEl.addEventListener('mousedown', this.handleTableMouseDown);
// 鼠标移动事件
document.addEventListener('mousemove', this.handleDocumentMouseMove);
// 鼠标释放事件
document.addEventListener('mouseup', this.handleDocumentMouseUp);
},
// 处理表格鼠标按下
handleTableMouseDown(event) {
// 只处理左键点击
if (event.button !== 0) return;
// 获取鼠标按下的单元格
const cell = event.target.closest('td');
if (!cell || cell.classList.contains('el-table__expanded-cell')) return;
// 获取单元格位置信息
const cellInfo = this.getCellInfo(cell);
if (!cellInfo) return;
// 阻止默认行为,防止文本选中
event.preventDefault();
// 记录起始单元格
this.startCell = cellInfo;
// 如果按下Ctrl键,启用多选模式
if (this.ctrlKeyPressed) {
this.toggleCellSelection(cellInfo);
} else if (!this.shiftKeyPressed) {
// 清除现有选择(除非按下Shift键)
this.clearSelection();
this.selectCell(cellInfo);
}
// 开始选择
this.isSelecting = true;
},
// 处理文档鼠标移动
handleDocumentMouseMove(event) {
if (!this.isSelecting) return;
// 获取鼠标当前所在的单元格
const cell = event.target.closest('td');
if (!cell || cell.classList.contains('el-table__expanded-cell')) return;
// 获取单元格位置信息
const cellInfo = this.getCellInfo(cell);
if (!cellInfo) return;
// 记录结束单元格
this.endCell = cellInfo;
// 更新选择范围
this.updateSelectionRange();
},
// 处理文档鼠标释放
handleDocumentMouseUp() {
if (!this.isSelecting) return;
// 结束选择
this.isSelecting = false;
// 如果有结束单元格,更新选择范围
if (this.endCell) {
this.updateSelectionRange(true);
}
// 隐藏选择范围指示器
const selectionRange = document.getElementById('selectionRange');
selectionRange.style.display = 'none';
// 更新预览
this.updateSelectionPreview();
},
// 获取单元格信息
getCellInfo(cell) {
if (!cell) return null;
// 获取行索引(排除表头)
const row = Array.from(cell.closest('tbody').children).indexOf(cell.parentElement);
if (row === -1) return null;
// 获取列索引
const column = Array.from(cell.parentElement.children).indexOf(cell);
if (column === -1) return null;
// 获取单元格内容
const content = cell.textContent.trim();
// 获取对应的属性名
const table = this.$refs.tableRef;
const columnProperty = table.columns[column].property;
return { row, column, content, columnProperty, element: cell };
},
// 更新选择范围
updateSelectionRange(finalize = false) {
if (!this.startCell || !this.endCell) return;
// 计算选择范围
const startRow = Math.min(this.startCell.row, this.endCell.row);
const endRow = Math.max(this.startCell.row, this.endCell.row);
const startColumn = Math.min(this.startCell.column, this.endCell.column);
const endColumn = Math.max(this.startCell.column, this.endCell.column);
if (finalize) {
// 最终确定选择范围
this.clearSelection();
// 选择范围内的所有单元格
for (let row = startRow; row <= endRow; row++) {
for (let col = startColumn; col <= endColumn; col++) {
const cell = this.getCellByPosition(row, col);
if (cell) {
const cellInfo = this.getCellInfo(cell);
this.selectCell(cellInfo);
}
}
}
} else {
// 实时显示选择范围指示器
const startCell = this.getCellByPosition(startRow, startColumn);
const endCell = this.getCellByPosition(endRow, endColumn);
if (startCell && endCell) {
const selectionRange = document.getElementById('selectionRange');
const startRect = startCell.getBoundingClientRect();
const endRect = endCell.getBoundingClientRect();
const tableRect = this.$refs.tableRef.$el.getBoundingClientRect();
selectionRange.style.left = `${startRect.left - tableRect.left}px`;
selectionRange.style.top = `${startRect.top - tableRect.top}px`;
selectionRange.style.width = `${endRect.right - startRect.left}px`;
selectionRange.style.height = `${endRect.bottom - startRect.top}px`;
selectionRange.style.display = 'block';
}
}
},
// 根据行列位置获取单元格元素
getCellByPosition(rowIndex, columnIndex) {
const table = this.$refs.tableRef;
const rows = table.$el.querySelectorAll('tbody tr');
if (rowIndex >= 0 && rowIndex < rows.length) {
const row = rows[rowIndex];
const cells = row.querySelectorAll('td');
if (columnIndex >= 0 && columnIndex < cells.length) {
return cells[columnIndex];
}
}
return null;
},
// 选择单元格
selectCell(cellInfo) {
if (!cellInfo || this.isCellSelected(cellInfo)) return;
// 添加到选中列表
this.selectedCells.push(cellInfo);
// 添加选中样式
if (cellInfo.element) {
cellInfo.element.classList.add('selected-cell');
}
},
// 取消选择单元格
deselectCell(cellInfo) {
if (!cellInfo) return;
// 从选中列表中移除
this.selectedCells = this.selectedCells.filter(cell =>
cell.row !== cellInfo.row || cell.column !== cellInfo.column
);
// 移除选中样式
if (cellInfo.element) {
cellInfo.element.classList.remove('selected-cell');
}
},
// 切换单元格选择状态
toggleCellSelection(cellInfo) {
if (this.isCellSelected(cellInfo)) {
this.deselectCell(cellInfo);
} else {
this.selectCell(cellInfo);
}
},
// 检查单元格是否已选中
isCellSelected(cellInfo) {
if (!cellInfo) return false;
return this.selectedCells.some(cell =>
cell.row === cellInfo.row && cell.column === cellInfo.column
);
},
// 清除所有选择
clearSelection() {
// 移除所有选中样式
this.selectedCells.forEach(cell => {
if (cell.element) {
cell.element.classList.remove('selected-cell');
}
});
// 清空选中列表
this.selectedCells = [];
// 重置起始和结束单元格
this.startCell = null;
this.endCell = null;
// 更新预览
this.updateSelectionPreview();
},
// 全选所有单元格
selectAll() {
this.clearSelection();
const table = this.$refs.tableRef;
const rows = table.$el.querySelectorAll('tbody tr');
rows.forEach((row, rowIndex) => {
const cells = row.querySelectorAll('td');
cells.forEach((cell, columnIndex) => {
const cellInfo = this.getCellInfo(cell);
if (cellInfo) {
this.selectCell(cellInfo);
}
});
});
this.updateSelectionPreview();
},
// 复制选中内容到剪贴板
copySelection() {
if (this.selectedCells.length === 0) {
this.$message.warning('没有选中的单元格');
return;
}
// 按行和列排序选中的单元格
const sortedCells = [...this.selectedCells].sort((a, b) => {
if (a.row !== b.row) {
return a.row - b.row;
}
return a.column - b.column;
});
// 生成表格数据
let copyText = '';
let currentRow = sortedCells[0].row;
sortedCells.forEach(cell => {
if (cell.row !== currentRow) {
copyText += '\n';
currentRow = cell.row;
} else if (cell.column > 0) {
copyText += '\t';
}
copyText += cell.content;
});
// 复制到剪贴板
navigator.clipboard.writeText(copyText).then(() => {
this.$message.success('已复制到剪贴板');
}).catch(err => {
this.$message.error('复制失败,请手动复制');
console.error('复制失败:', err);
});
},
// 更新选择内容预览
updateSelectionPreview() {
const previewElement = document.getElementById('selectionPreview');
if (this.selectedCells.length === 0) {
previewElement.textContent = '框选单元格后,内容将显示在这里...';
return;
}
// 按行和列排序选中的单元格
const sortedCells = [...this.selectedCells].sort((a, b) => {
if (a.row !== b.row) {
return a.row - b.row;
}
return a.column - b.column;
});
// 生成预览文本
let previewText = '';
let currentRow = sortedCells[0].row;
sortedCells.forEach(cell => {
if (cell.row !== currentRow) {
previewText += '\n';
currentRow = cell.row;
} else if (cell.column > 0) {
previewText += ' | ';
}
previewText += cell.content;
});
previewElement.textContent = previewText;
},
// 处理键盘按下
handleKeyDown(event) {
if (event.key === 'Control' || event.key === 'Meta') {
this.ctrlKeyPressed = true;
}
if (event.key === 'Shift') {
this.shiftKeyPressed = true;
}
},
// 处理键盘释放
handleKeyUp(event) {
if (event.key === 'Control' || event.key === 'Meta') {
this.ctrlKeyPressed = false;
}
if (event.key === 'Shift') {
this.shiftKeyPressed = false;
}
},
// 处理单元格鼠标进入
handleCellMouseEnter(row, column, cell, event) {
cell.classList.add('bg-gray-50');
},
// 处理单元格鼠标离开
handleCellMouseLeave(row, column, cell, event) {
if (!this.isCellSelected(this.getCellInfo(cell))) {
cell.classList.remove('bg-gray-50');
}
},
// 处理单元格点击
handleCellClick(row, column, cell, event) {
// 如果不是左键点击或正在选择,不处理
if (event.button !== 0 || this.isSelecting) return;
const cellInfo = this.getCellInfo(cell);
if (!cellInfo) return;
// 处理点击选择
if (this.ctrlKeyPressed) {
// Ctrl+点击:切换选择状态
this.toggleCellSelection(cellInfo);
} else if (this.shiftKeyPressed && this.startCell) {
// Shift+点击:从起点扩展选择
this.endCell = cellInfo;
this.updateSelectionRange(true);
} else {
// 普通点击:清除现有选择,选择当前单元格
this.clearSelection();
this.selectCell(cellInfo);
}
this.updateSelectionPreview();
}
}
}
</script>
<style lang="scss" scoped>
.table-cloumn-setting-popper{
.setting-row-content{
max-height: 600px;
overflow-y: auto;
//overflow-y: hidden;
.setting-row{
height: 30px;
line-height: 30px;
.el-icon-s-operation{
cursor: move;
font-size: 16px;
margin-right: 8px;
}
.label{
margin-right: 8px;
}
.btn{
padding: 4px!important;
}
}
}
}
.xd-table{
width:100%;
height:100%;
position: relative;
.el-icon-setting{
position: absolute;
right: 7px;
top: 5px;
cursor: pointer;
}
.table-operate-btn-content{
// width: calc(100% - 40px);
width: 100%;
// min-height: 28px; // 给一个最低的高度,防止没有插槽,右侧icon被吞
min-height: 40px; // 给一个最低的高度,防止没有插槽,右侧icon被吞
.operate-btn-content {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 28px;
padding-bottom: 6px;
}
}
.parent:has(.child){
background-color: #eee;
}
}
.pagination-container{
padding: 0px 20px !important;
}
.menuDiv {
position: absolute;
background-color: #fff;
width: 100px;
/*height: 106px;*/
}
:deep .seled{
background-color: #51dd397d !important;
color: black;
font-weight: 600;
}
:deep .rowSeled{
background-color: #ffa5a55e !important;
color: black;
font-weight: 600;
}
.xd-tb-wrapper :deep .el-table__body tr:hover > td {
background-color: #ffa5a55e;
}
.xd-tb-wrapper{
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}
.drag-ghost { background-color: #e7e7e77d; }
.drag-chosen { background-color: #e7e7e77d; }
// 拖拽
.dragClass {
background: rgba($color: #41c21a, $alpha: 0.5) !important;
}
// 停靠
.ghostClass {
background: rgba($color: #6cacf5, $alpha: 0.5) !important;
}
// 选择
.chosenClass:hover > td {
background: rgba($color: #f56c6c, $alpha: 0.5) !important;
}
.el-table__body tr.current-row > td {
background-color: #78f709 !important;
}
.el-table--enable-row-hover .el-table__body tr:hover > td {
background-color: #f6d604 !important;
cursor:pointer;
}
.full-block{
height:100%;
width:100%;
overflow:hidden;
}
.flex-layout{
display:flex;
flex-direction:column;
}
.flex-auto{
flex:1 1 auto
}
.flex-none{
flex:none
}
.el-table {
width: 100%;
display: flex;
flex-direction: column;
.el-table__body-wrapper {
flex: 1;
}
}
.content-auto {
content-visibility: auto;
}
.selected-cell {
background-color: rgba(64, 158, 255, 0.2) !important;
border: 1px solid #409EFF !important;
}
.selection-range {
position: absolute;
background-color: rgba(64, 158, 255, 0.1);
border: 1px dashed #409EFF;
pointer-events: none;
z-index: 10;
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff