elementPlus tabel实现复制粘贴功能

news/2025/10/11 18:18:10/文章来源:https://www.cnblogs.com/MrLie/p/19135718

58c984098a03a94c9169a191fa94e2df

 

<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(() => {
   return tableRef.value?.columns.filter(col => !col.hidden) || [];
});
const statusMessage = ref('');

// 监听全局快捷键,确保能捕获Ctrl+C和Ctrl+V
const setupGlobalKeyListeners = () => {
   const handleKeyDown = (event) => {
      // 处理复制 (Ctrl+C)
      if(event.ctrlKey && event.key === 'c') {
         event.preventDefault();
         event.stopPropagation();
         handleCopy();
      }

      // 处理粘贴 (Ctrl+V)
      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) => {
   console.log('鼠标按下事件 ', 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'));
   console.log('rows: ', rows);

   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('粘贴失败,尝试使用快捷键Ctrl+V', '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 ) => {
   statusMessage.value = msg;

   // 3秒后自动清除消息
   setTimeout(() => {
      statusMessage.value = '';
   }, 3000);
};

// 复制文本到剪贴板的 fallback 方法(兼容不支持Clipboard API的浏览器)
const fallbackCopyTextToClipboard = (text) => {
   const textArea = document.createElement('textarea');
   textArea.value = text;

   // 确保文本区域不在可视区域内
   textArea.style.position = 'fixed';
   textArea.style.top = '-999px';
   textArea.style.left = '-999px';

   document.body.appendChild(textArea);
   textArea.focus();
   textArea.select();

   try {
      const successful = document.execCommand('copy');
      const msg = successful ? '已复制数据' : '复制失败,请手动复制';
      showStatusMessage(msg, successful ? 'success' : 'error');
   }
   catch(err) {
      showStatusMessage('复制失败,请手动复制', 'error');
      console.error('Fallback: 无法复制文本: ', err);
   }

   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;
};

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

// 注册清理函数
onUnmounted(cleanup);

// 初始化
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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/934984.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025.10.11NOIP模拟

1.略 2.题目 贪心,流水线调度作业模型的 Johnson 不等式的运用,证明不会 1. 将所有 N 架飞船分成两个集合。集合\(S_1\)包含所有满足 \(U(i)≤V(i)\) 的飞船 \(i\)。集合 \(S_2\) ​包含所有满足 \(U(i)>V(i)\) 的…

颠覆传统RAG!Agentic RAG登场,AI代理如何“自我进化”解决复杂 query?

原文: https://mp.weixin.qq.com/s/2NPKctr45W7pS0vFz3XHeg 全文摘要本文介绍了大型语言模型(LLM)在自然语言理解和文本生成方面的革命性进展,但其依赖于静态训练数据限制了其对动态、实时查询的响应能力。为了解决…

在 Windows 下集成 Conda 与 VS Code 打造高效开发环境

在 Windows 下集成 Conda 与 VS Code 打造高效开发环境在 Windows 下集成 Conda 与 VS Code 打造高效开发环境 概述 在 Windows 系统上进行 Python 开发时,环境管理和工具集成是提升效率的关键。本文将详细介绍如何在…

洛谷题单指南-进阶数论-P2421 [NOI2002] 荒岛野人

原题链接:https://www.luogu.com.cn/problem/P2421 题意解读:一个环形坐标轴,n个点初始位于C1、C2...Cn,每个点每次逆时针移动P1、P2...Pn步,每个点分别最多只能移动L1、L2...Ln步,要求n个点能移动的点每次同时移…

2025粉末涂料厂家最新推荐榜:环保高效与色彩持久的行业佼佼

2025粉末涂料厂家最新推荐榜:环保高效与色彩持久的行业佼佼随着环保意识的不断增强和工业技术的不断进步,粉末涂料因其环保、高效及色彩持久等优点,逐渐成为涂装行业的主流选择。本文将为您推荐几家在粉末涂料领域表…

基于微信小工具高仿背单词消除游戏

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

python fast api websocket 连接事例

python fast api websocket 连接事例服务端事例:# -*- coding: utf-8 -*- import asyncio import traceback import json import uuid from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.re…

Idea摸鱼看小说插件(YsQy-Book)-免费使用

前言 之前用的idea看小说插件要么要收费,要么就不好用,这我那里受得了,所以就决定自己开发一个idea看小说插件。 idea搜索:YsQy-Book Github地址 https://github.com/ZHJJ03/YsQy-Book-plugin 用法 先去设置里选择…

贴牛皮纸铝卷生产商推荐/铝卷生产厂家/铝卷哪家好

在当今的工业生产与建筑领域中,贴牛皮纸铝卷以其独特的性能和广泛的应用而备受青睐。它不仅具有良好的防腐保温效果,还在装饰、包装等方面发挥着重要作用。今天,就为大家推荐一家值得信赖的贴牛皮纸铝卷生产商——济…

2025浇注型聚氨酯厂家口碑排行榜:品质与服务双优之选

2025浇注型聚氨酯厂家口碑排行榜:品质与服务双优之选随着工业技术的不断进步,浇注型聚氨酯作为一种高性能材料,在众多领域中得到了广泛应用。从机械制造到建筑施工,从汽车工业到电子电器,浇注型聚氨酯凭借其优异的…

查询top cpu占用排行

查询top cpu占用排行1、命令行ps aux --sort=-%cpu | head -n 10

RAFT 共识算法

Leader - Follower 消息同步以Kafka为例子 在 Kafka 中,Leader 节点确保所有 Follower 节点成功接收消息的机制,主要通过 ISR(In-Sync Replicas,同步副本列表) 和 acks 消息确认机制 实现,具体流程如下: 1. 核心…

2025氧化镁厂家最新推荐榜:高纯度与优质服务并重的行业先锋

2025氧化镁厂家最新推荐榜:高纯度与优质服务并重的行业先锋随着工业和科技的快速发展,氧化镁作为一种重要的化工原料,在多个领域发挥着关键作用。为了帮助筛选氧化镁品牌,特此发布权威推荐榜单,为采购决策提供专业…

【Vue】LangChain4j大模型对话-前端页面完成(vite+vue3+router)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【Vue】LangChain4j大模型对话-前端页面完成(vite+vue3+router)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

contenteditable 深度交互教程

contenteditable 交互式教程 - 百灵AI * { box-sizing: border-box; margin: 0; padding: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif } body { background: linear-gradient(135de…

【gradio】使用Gradio快速开发前端界面:基础知识

【gradio】使用Gradio快速开发前端界面:基础知识使用Gradio快速开发前端界面:基础知识前言一、什么是Gradio?二、安装Gradio三、快速入门:构建一个简单的文本处理界面四、Gradio 的核心组件五、核心组件的演示六、…

2025风机盘管厂家口碑推荐榜:高效节能与稳定性能的行业首选

2025风机盘管厂家口碑推荐榜:高效节能与稳定性能的行业首选随着建筑行业的快速发展,风机盘管作为中央空调系统中的重要组成部分,其市场需求日益增长。高效节能与稳定性能成为用户选择风机盘管时的重要考量因素。为了…

距离和

http://noip.ybtoj.com.cn/contest/1121/problem/3 1.3 距离和 图解1685. 有序数组中差绝对值之和 14962615. 等值距离和 17932602. 使数组元素全部相等的最少操作次数 19032968. 执行操作使频率分数最大 24441703. 得…

痞子衡嵌入式:在i.MXRT下测试启动特性时可改写OTP Shadow寄存器而不烧OTP

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是i.MXRT下测试启动特性时可改写OTP Shadow寄存器而不烧OTP。我们知道恩智浦 i.MXRT 系列除了 BOOT 相关引脚电平配置之外,主要通过片内 eFuse/OTP 存…