712 lines
20 KiB
Vue
712 lines
20 KiB
Vue
<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>
|
||
|
||
|