elementPlus tabel实现复制粘贴功能

elementPlus表格复制粘贴效果

elementPlus tabel实现复制粘贴功能

<template>
  <div class="table-container">
    <h3>类Excel表格复制粘贴示例 (修复版)</h3>
    <el-table 
      ref="tableRef"
      :data="tableData"
      border
      style="width: 100%"
      @mousedown="handleTableMouseDown"
      @mousemove="handleTableMouseMove"
      @mouseup="handleTableMouseUp"
      @mouseleave="handleTableMouseLeave"
    >
      <el-table-column prop="id" label="ID" width="100" />
      <el-table-column prop="name" label="姓名" width="120" />
      <el-table-column prop="age" label="年龄" width="100" />
      <el-table-column prop="gender" label="性别" width="100" />
      <el-table-column prop="email" label="邮箱" />
      <el-table-column prop="test1" label="测试1">
        <template #default="{ row }">
          <el-input v-model="row.test1" />
        </template>
      </el-table-column>
      <el-table-column prop="test2" label="测试2">
        <template #default="{ row }">
          <el-input v-model="row.test2" />
        </template>
      </el-table-column>
    </el-table>

    <div v-if="selectionRange" class="selection-info">
      选中区域: {{ selectionRange.start.row + 1 }}行{{ getColumnLetter(selectionRange.start.col) }}列
      至 {{ selectionRange.end.row + 1 }}行{{ getColumnLetter(selectionRange.end.col) }}列
    </div>

    <div v-if="statusMessage" class="status-message">
      {{ statusMessage }}
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, computed, nextTick, onUnmounted } from 'vue';

// 表格数据
const tableData = reactive([
  { id: 1, name: '张三', age: 28, gender: '男', email: 'zhangsan@example.com' },
  { id: 2, name: '李四', age: 32, gender: '男', email: 'lisi@example.com' },
  { id: 3, name: '王五', age: 45, gender: '女', email: 'wangwu@example.com' },
  { id: 4, name: '赵六', age: 22, gender: '男', email: 'zhaoliu@example.com' },
  { id: 5, name: '钱七', age: 36, gender: '女', email: 'qianqi@example.com' },
]);

const tableRef = ref(null);
const selectionRange = ref(null);
const isSelecting = ref(false);
const columns = computed(() => tableRef.value?.columns.filter(col => !col.hidden) || []);
const statusMessage = ref('');

// 监听全局快捷键
const setupGlobalKeyListeners = () => {
  const handleKeyDown = (event) => {
    if(event.ctrlKey && event.key === 'c') {
      event.preventDefault();
      event.stopPropagation();
      handleCopy();
    }
    if(event.ctrlKey && event.key === 'v') {
      event.preventDefault();
      event.stopPropagation();
      handlePaste();
    }
  };
  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
};

const removeKeyListener = setupGlobalKeyListeners();

// 表格鼠标事件
const handleTableMouseDown = (event) => {
  if(event.button !== 0) return;
  event.preventDefault();
  const cellInfo = getCellInfoFromEvent(event);
  if(!cellInfo) return;
  selectionRange.value = { start: { ...cellInfo }, end: { ...cellInfo } };
  isSelecting.value = true;
  highlightSelection();
};

const handleTableMouseMove = (event) => {
  if(!isSelecting.value) return;
  const cellInfo = getCellInfoFromEvent(event);
  if(!cellInfo) return;
  selectionRange.value.end = { ...cellInfo };
  highlightSelection();
};

const handleTableMouseUp = () => isSelecting.value = false;
const handleTableMouseLeave = () => isSelecting.value = false;

// 获取单元格信息
const getCellInfoFromEvent = (event) => {
  const cellEl = event.target.closest('.el-table__cell');
  if(!cellEl) return null;
  const rowEl = cellEl.closest('.el-table__row');
  if(!rowEl) return null;
  const rows = Array.from(document.querySelectorAll('.el-table__row'));
  const rowIndex = rows.indexOf(rowEl);
  const cells = Array.from(rowEl.querySelectorAll('.el-table__cell'));
  const colIndex = cells.indexOf(cellEl);
  if(rowIndex === -1 || colIndex === -1) return null;
  return { row: rowIndex, col: colIndex };
};

// 高亮选中区域
const highlightSelection = () => {
  document.querySelectorAll('.el-table__cell').forEach(cell => {
    cell.style.backgroundColor = '';
    cell.style.border = '';
  });
  if(!selectionRange.value) return;
  const { start, end } = selectionRange.value;
  const minRow = Math.min(start.row, end.row);
  const maxRow = Math.max(start.row, end.row);
  const minCol = Math.min(start.col, end.col);
  const maxCol = Math.max(start.col, end.col);
  const rows = document.querySelectorAll('.el-table__row');
  for(let i = minRow; i <= maxRow; i++) {
    const row = rows[i];
    if(!row) continue;
    const cells = row.querySelectorAll('.el-table__cell');
    for(let j = minCol; j <= maxCol; j++) {
      const cell = cells[j];
      if(cell) {
        cell.style.backgroundColor = 'rgba(144, 202, 249, 0.5)';
        cell.style.border = '1px solid #409EFF';
      }
    }
  }
};

// 复制/粘贴逻辑
const handleCopy = () => {
  if(!selectionRange.value) {
    showStatusMessage('请先选择要复制的单元格区域', 'warning');
    return;
  }
  const { start, end } = selectionRange.value;
  const minRow = Math.min(start.row, end.row);
  const maxRow = Math.max(start.row, end.row);
  const minCol = Math.min(start.col, end.col);
  const maxCol = Math.max(start.col, end.col);
  let copyText = '';
  for(let i = minRow; i <= maxRow; i++) {
    const rowData = [];
    for(let j = minCol; j <= maxCol; j++) {
      const prop = columns.value[j]?.property;
      rowData.push(prop ? (tableData[i][prop] || '') : '');
    }
    copyText += rowData.join('\t') + '\n';
  }
  if(navigator.clipboard) {
    navigator.clipboard.writeText(copyText).then(() => {
      showStatusMessage(`已复制 ${maxRow - minRow + 1}行${maxCol - minCol + 1}列数据`, 'success');
    }).catch(() => fallbackCopyTextToClipboard(copyText));
  } else {
    fallbackCopyTextToClipboard(copyText);
  }
};

const handlePaste = () => {
  if(!selectionRange.value) {
    showStatusMessage('请先选择粘贴目标区域的起始单元格', 'warning');
    return;
  }
  if(navigator.clipboard) {
    navigator.clipboard.readText().then(text => {
      processPasteData(text);
    }).catch(err => {
      showStatusMessage('粘贴失败', 'error');
      console.error(err);
    });
  } else {
    showStatusMessage('浏览器不支持剪贴板API', 'error');
  }
};

const processPasteData = (text) => {
  if(!text) { showStatusMessage('剪贴板为空', 'warning'); return; }
  const rows = text.split('\n').filter(row => row.trim() !== '');
  const startRow = selectionRange.value.start.row;
  const startCol = selectionRange.value.start.col;
  rows.forEach((row, rowIdx) => {
    const cellValues = row.split('\t');
    cellValues.forEach((value, colIdx) => {
      const targetRowIdx = startRow + rowIdx;
      const targetColIdx = startCol + colIdx;
      if(targetRowIdx < tableData.length && targetColIdx < columns.value.length && columns.value[targetColIdx]?.property !== 'id') {
        tableData[targetRowIdx][columns.value[targetColIdx].property] = value;
      }
    });
  });
  showStatusMessage(`已粘贴 ${rows.length} 行数据`, 'success');
};

// 工具函数
const showStatusMessage = (msg, type = 'success') => {
  statusMessage.value = msg;
  setTimeout(() => statusMessage.value = '', 3000);
};

const fallbackCopyTextToClipboard = (text) => {
  const textArea = document.createElement('textarea');
  textArea.value = text;
  textArea.style.position = 'fixed';
  textArea.style.top = '-999px';
  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();
  try {
    const successful = document.execCommand('copy');
    showStatusMessage(successful ? '已复制' : '复制失败', successful ? 'success' : 'error');
  } catch(err) {
    showStatusMessage('复制失败', 'error');
  }
  document.body.removeChild(textArea);
};

const getColumnLetter = (index) => {
  let letter = '';
  let remaining = index;
  while(true) {
    const code = remaining % 26;
    letter = String.fromCharCode(65 + code) + letter;
    remaining = Math.floor(remaining / 26) - 1;
    if(remaining < 0) break;
  }
  return letter;
};

// 组件卸载清理
onUnmounted(() => {
  if(removeKeyListener) removeKeyListener();
});

nextTick(() => console.log('表格组件初始化完成'));
</script>

<style scoped>
.table-container {
  max-width: 1200px;
  margin: 20px auto;
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

h3 {
  margin-bottom: 20px;
  color: #333;
}

.selection-info {
  margin-top: 10px;
  color: #666;
  font-size: 14px;
  padding: 5px 10px;
  background-color: #f5f7fa;
  border-radius: 4px;
}

.status-message {
  margin-top: 10px;
  padding: 5px 10px;
  border-radius: 4px;
  color: #fff;
}

.status-message.success { background-color: #52c41a; }
.status-message.warning { background-color: #faad14; }
.status-message.error { background-color: #f5222d; }

::v-deep .el-table, ::v-deep .el-table__cell {
  user-select: none !important;
  -webkit-user-select: none !important;
}

::v-deep .el-table__cell:focus-within {
  outline: none !important;
}
</style>
posted @ 2025-10-11 18:14  Uncle_MrLee  阅读(182)  评论(0)    收藏  举报