vue2+elementUi实现自定义表格框选复制粘贴

一、介绍:

本文介绍了一个基于ElementUI实现的表格框选复制功能,支持单元格和行两种选择模式。主要功能包括:1)通过点击或拖动框选单元格;2)支持全选、复制选中内容到剪贴板;3)可切换单元格/行选择模式;4)复制内容可直接粘贴到Excel/WPS等办公软件。该方案采用Vue.js框架,结合ElementUI组件库,实现了高效的大数据量表格操作功能,并提供了用户友好的交互反馈。

由于数据量增大,需要考虑性能问题。但Element UI的表格是虚拟滚动吗?不,Element UI的表格默认不提供虚拟滚动,所以大量数据可能会影响性能。但用户要求10000条,所以只能尽量优化。

在现有代码中,表格是通过el-table组件渲染的,对于大量数据,Element UI建议使用虚拟滚动,但虚拟滚动需要额外配置。然而,用户没有指定使用虚拟滚动,所以先直接增加数据量。

另外,需要增加列数。现有列有:选择列、日期、姓名、地址、年龄、职业、状态。可以再添加一些列,比如电话、邮箱、部门等。

目前已经实现的功能有:

1.框选多行多列单元格进行复制粘贴到excel

2.切换按行模式时,通过勾选复选框实现按照行复制粘贴到excel

3.横向滚动时可以框选复制

4.当表格数据较大时候,固定了表头,通过elementUI中table的height属性控制

5.目前随机生成10000条数据,当数据量超大时,需要使用虚拟滚动进行优化,要不然表格一定会卡顿

二、效果:

1.框选中+复制

2.粘贴wps

3.完整代码

javascript 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>Element UI表格框选复制功能</title>
		<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
		<style>
			* {
				margin: 0;
				padding: 0;
				box-sizing: border-box;
				font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
			}

			body {
				background-color: #f5f7fa;
				padding: 20px;
				color: #333;
			}

			.container {
				max-width: 1200px;
				margin: 0 auto;
				background: white;
				border-radius: 8px;
				box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
				overflow: hidden;
			}

			header {
				background: #2c3e50;
				color: white;
				padding: 20px;
				text-align: center;
			}

			h1 {
				font-size: 24px;
				margin-bottom: 10px;
			}

			.subtitle {
				font-size: 14px;
				opacity: 0.8;
			}

			.content {
				padding: 20px;
			}

			.controls {
				display: flex;
				gap: 10px;
				margin-bottom: 20px;
				flex-wrap: wrap;
			}

			.el-button {
				transition: all 0.3s;
			}

			.el-button:hover {
				transform: translateY(-2px);
				box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
			}

			.table-container {
				position: relative;
				margin-bottom: 20px;
				border: 1px solid #ebeef5;
				border-radius: 4px;
				overflow: hidden;
			}

			.status-bar {
				display: flex;
				justify-content: space-between;
				padding: 10px;
				background-color: #f5f7fa;
				border-radius: 4px;
				font-size: 14px;
				color: #606266;
			}

			.instructions {
				background-color: #ecf5ff;
				padding: 15px;
				border-radius: 4px;
				margin-top: 20px;
				border-left: 5px solid #409eff;
			}

			.instructions h3 {
				color: #2c3e50;
				margin-bottom: 10px;
			}

			.instructions ul {
				padding-left: 20px;
			}

			.instructions li {
				margin-bottom: 8px;
				line-height: 1.5;
			}

			.highlight {
				background-color: #fff9c4;
				padding: 2px 5px;
				border-radius: 3px;
				font-weight: 500;
			}

			.selection-info {
				display: flex;
				gap: 15px;
			}

			.selecting-rect {
				position: absolute;
				border: 2px solid #409eff;
				background-color: rgba(64, 158, 255, 0.1);
				pointer-events: none;
				z-index: 100;
			}

			.selected-cell {
				background-color: rgba(64, 158, 255, 0.2) !important;
				border: 1px solid #409eff !important;
			}

			.el-table .current-row td {
				background-color: #f0f9ff !important;
			}

			.el-table .el-table__body tr:hover>td {
				background-color: #f5f7fa !important;
			}

			.copy-status {
				position: fixed;
				top: 20px;
				right: 20px;
				background: #67c23a;
				color: white;
				padding: 10px 15px;
				border-radius: 4px;
				box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
				z-index: 2000;
				opacity: 0;
				transition: opacity 0.3s;
			}

			.copy-status.show {
				opacity: 1;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<div class="container">

				<div class="content">
					<div class="controls">
						<el-button type="primary" @click="selectAll">全选</el-button>
						<el-button type="success" @click="copySelected">复制选中内容</el-button>
						<el-button type="warning" @click="clearSelection">清除选择</el-button>
						<el-button type="info" @click="toggleSelectionMode">
							{{ selectionMode === 'cell' ? '切换至行选择模式' : '切换至单元格选择模式' }}
						</el-button>
					</div>

					<div class="table-container" id="table-container">
						<el-table ref="multipleTable" :data="tableData" border style="width: 100%;height: 700px;overflow-y: auto;"
							 height="700"
							:row-class-name="tableRowClassName" @cell-click="handleCellClick"
							@row-click="handleRowClick" @selection-change="handleSelectionChange"
							@mousedown.native="handleMouseDown" @mousemove.native="handleMouseMove"
							@mouseup.native="handleMouseUp">
							<el-table-column type="selection" width="55"></el-table-column>
							<el-table-column prop="date" label="日期" width="150"></el-table-column>
							<el-table-column prop="name" label="姓名" width="120"></el-table-column>
							<el-table-column prop="address" label="地址"></el-table-column>
							<el-table-column prop="age" label="年龄" width="80"></el-table-column>
							<el-table-column prop="job" label="职业" width="120"></el-table-column>
							<el-table-column prop="status" label="状态" width="100"></el-table-column>
							<el-table-column prop="phone" label="电话" width="150"></el-table-column>
							<el-table-column prop="email" label="邮箱" width="200"></el-table-column>
							<el-table-column prop="department" label="部门" width="150"></el-table-column>
							<el-table-column prop="salary" label="薪资" width="120"></el-table-column>
							<el-table-column prop="education" label="学历" width="100"></el-table-column>
							<el-table-column prop="experience" label="工作经验" width="120"></el-table-column>
							<el-table-column prop="city" label="城市" width="120"></el-table-column>
							<el-table-column prop="country" label="国家" width="120"></el-table-column>
							<el-table-column prop="company" label="公司" width="200"></el-table-column>
							<el-table-column prop="position" label="职位" width="150"></el-table-column>
							<el-table-column prop="project" label="项目" width="200"></el-table-column>
							<el-table-column prop="skill" label="技能" width="150"></el-table-column>
						</el-table>

						<!-- 选择框 -->
						<div v-if="selecting" class="selecting-rect" :style="rectStyle"></div>
					</div>

					<div class="status-bar">
						<div>总记录数: {{ tableData.length }}</div>
						<div class="selection-info">
							<span>选中: {{ selectedCount }} 个单元格</span>
							<span>选择模式: {{ selectionMode === 'cell' ? '单元格' : '行' }}</span>
						</div>
						<div>最后操作: {{ lastOperation }}</div>
					</div>

					<!-- <div class="instructions">
                    <h3>使用说明</h3>
                    <ul>
                        <li><span class="highlight">点选</span>: 点击单元格可选择单个单元格</li>
                        <li><span class="highlight">框选</span>: 按住鼠标左键拖动可以选择多个单元格</li>
                        <li><span class="highlight">行选择</span>: 点击行可以选择整行,或使用左侧复选框</li>
                        <li><span class="highlight">全选</span>: 点击"全选"按钮选择所有行</li>
                        <li><span class="highlight">复制</span>: 选择内容后点击"复制选中内容"按钮或按Ctrl+C</li>
                        <li><span class="highlight">模式切换</span>: 可以切换单元格选择模式或行选择模式</li>
                        <li><span class="highlight">粘贴到Excel/WPS</span>: 复制后可直接粘贴到Excel或WPS表格中</li>
                    </ul>
                </div> -->
				</div>
			</div>

			<!-- 复制状态提示 -->
			<div class="copy-status" :class="{show: showCopyStatus}">
				已复制 {{ selectedCount }} 个单元格到剪贴板
			</div>
		</div>

		<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
		<script src="https://unpkg.com/element-ui/lib/index.js"></script>
		<script>
			new Vue({
				el: '#app',
				data() {
					return {
						// tableData: [
						//     { date: '2023-10-01', name: '张三', address: '北京市海淀区', age: 30, job: '工程师', status: '在职' },
						//     { date: '2023-10-02', name: '李四', address: '上海市浦东新区', age: 28, job: '设计师', status: '在职' },
						//     { date: '2023-10-03', name: '王五', address: '广州市天河区', age: 35, job: '产品经理', status: '离职' },
						//     { date: '2023-10-04', name: '赵六', address: '深圳市南山区', age: 32, job: '开发工程师', status: '在职' },
						//     { date: '2023-10-05', name: '钱七', address: '杭州市西湖区', age: 29, job: 'UI设计师', status: '休假' },
						//     { date: '2023-10-06', name: '孙八', address: '南京市鼓楼区', age: 31, job: '测试工程师', status: '在职' },
						//     { date: '2023-10-07', name: '周九', address: '武汉市江汉区', age: 27, job: '前端开发', status: '在职' },
						//     { date: '2023-10-08', name: '吴十', address: '成都市锦江区', age: 33, job: '后端开发', status: '休假' },
						//     { date: '2023-10-09', name: '郑十一', address: '西安市雁塔区', age: 26, job: '运维工程师', status: '在职' },
						//     { date: '2023-10-10', name: '王十二', address: '苏州市工业园区', age: 34, job: '架构师', status: '在职' }
						// ],
						tableData: this.generateTableData(10000), //模拟数据
						selectedCount: 0,
						lastOperation: '暂无',
						selectionMode: 'cell', // 'cell' 或 'row'
						selecting: false,
						startX: 0,
						startY: 0,
						endX: 0,
						endY: 0,
						selectedCells: [],
						selectedRows: [],
						showCopyStatus: false,
						isDragging: false, // 标记是否正在拖拽框选
						tableRect: null // 存储表格位置信息
					};
				},
				computed: {
					rectStyle() {
						if (!this.selecting) return {};

						const left = Math.min(this.startX, this.endX);
						const top = Math.min(this.startY, this.endY);
						const width = Math.abs(this.endX - this.startX);
						const height = Math.abs(this.endY - this.startY);

						return {
							left: left + 'px',
							top: top + 'px',
							width: width + 'px',
							height: height + 'px'
						};
					}
				},
				mounted() {
					// 初始化表格
					this.$nextTick(() => {
						this.updateSelectedCount();
						this.updateTableRect();

						// 监听窗口大小变化,更新表格位置信息
						window.addEventListener('resize', this.updateTableRect);
					});

					// 添加键盘事件监听
					document.addEventListener('keydown', this.handleKeyDown);
				},
				beforeDestroy() {
					window.removeEventListener('resize', this.updateTableRect);
					// 移除键盘事件监听
					document.removeEventListener('keydown', this.handleKeyDown);
				},
				methods: {
					// 生成表格数据
					generateTableData(count) {
						const data = [];
						const departments = ['技术部', '市场部', '销售部', '财务部', '人力资源部', '研发部', '产品部', '运营部'];
						const statuses = ['进行中', '已完成', '待审核', '已取消', '待开始'];
						const jobs = ['工程师', '设计师', '产品经理', '开发工程师', 'UI设计师', '测试工程师', '前端开发', '后端开发', '运维工程师', '架构师'];
						const cities = ['北京', '上海', '广州', '深圳', '杭州', '南京', '武汉', '成都', '西安', '苏州'];
						const countries = ['中国', '美国', '英国', '日本', '德国', '法国', '澳大利亚', '加拿大'];
						const companies = ['腾讯科技', '阿里巴巴', '百度', '华为', '小米', '字节跳动', '美团', '滴滴'];
						const positions = ['初级工程师', '中级工程师', '高级工程师', '技术专家', '架构师', '项目经理', '部门经理'];
						const projects = ['项目A', '项目B', '项目C', '项目D', '项目E', '项目F', '项目G'];
						const skills = ['Java', 'Python', 'JavaScript', 'C++', 'Go', 'React', 'Vue', 'Angular'];
						const educations = ['本科', '硕士', '博士', '大专', '高中'];

						for (let i = 0; i < count; i++) {
							data.push({
								date: '2023-' + this.padZero(Math.floor(Math.random() * 12) + 1) + '-' + this
									.padZero(Math.floor(Math.random() * 28) + 1),
								name: '用户' + (i + 1),
								address: cities[Math.floor(Math.random() * cities.length)] + '市某区某街道' + (Math
									.floor(Math.random() * 100) + 1) + '号',
								age: Math.floor(Math.random() * 40) + 20,
								job: jobs[Math.floor(Math.random() * jobs.length)],
								status: statuses[Math.floor(Math.random() * statuses.length)],
								phone: '1' + Math.floor(Math.random() * 10) + this.padZero(Math.floor(Math
									.random() * 1000000000), 9),
								email: 'user' + (i + 1) + '@example.com',
								department: departments[Math.floor(Math.random() * departments.length)],
								salary: (Math.floor(Math.random() * 50000) + 10000).toLocaleString(),
								education: educations[Math.floor(Math.random() * educations.length)],
								experience: Math.floor(Math.random() * 20) + '年',
								city: cities[Math.floor(Math.random() * cities.length)],
								country: countries[Math.floor(Math.random() * countries.length)],
								company: companies[Math.floor(Math.random() * companies.length)],
								position: positions[Math.floor(Math.random() * positions.length)],
								project: projects[Math.floor(Math.random() * projects.length)],
								skill: skills[Math.floor(Math.random() * skills.length)]
							});
						}
						return data;
					},
					
					// 数字补零
					padZero(num, length = 2) {
						return num.toString().padStart(length, '0');
					},

					// 处理键盘事件
					handleKeyDown(event) {
						// 检查是否Ctrl+C
						if (event.ctrlKey && event.key === 'c') {
							event.preventDefault(); // 防止默认行为
							this.copySelected(); // 调用复制方法
						}
					},

					// 更新表格位置信息
					updateTableRect() {
						if (this.$refs.multipleTable && this.$refs.multipleTable.$el) {
							this.tableRect = this.$refs.multipleTable.$el.getBoundingClientRect();
						}
					},

					// 处理单元格点击
					handleCellClick(row, column, cell, event) {
						// 如果正在框选,不触发单元格点击事件
						if (this.isDragging) {
							this.isDragging = false;
							return;
						}

						if (this.selectionMode === 'cell') {
							this.toggleCellSelection(cell);
							this.lastOperation = '点选了单元格: ' + column.label;
						}
					},

					// 处理行点击
					handleRowClick(row, event, column) {
						// 如果正在框选,不触发行点击事件
						if (this.isDragging) {
							this.isDragging = false;
							return;
						}

						if (this.selectionMode === 'row') {
							this.toggleRowSelection(row);
							this.lastOperation = '点击了行: ' + row.name;
						}
					},

					// 处理选择变化
					handleSelectionChange(selection) {
						this.selectedRows = selection;
						this.updateSelectedCount();
					},

					// 处理鼠标按下
					handleMouseDown(event) {
						if (this.selectionMode !== 'cell') return;

						// 更新表格位置信息
						this.updateTableRect();

						this.selecting = true;
						this.isDragging = false;

						// 计算相对于表格的坐标
						this.startX = event.clientX - this.tableRect.left;
						this.startY = event.clientY - this.tableRect.top;
						this.endX = this.startX;
						this.endY = this.startY;

						// 清除之前的选择(如果不按Ctrl键)
						if (!event.ctrlKey) {
							this.clearSelection();
						}

						// 阻止默认行为,避免选中文本
						event.preventDefault();
					},

					// 处理鼠标移动
					handleMouseMove(event) {
						if (!this.selecting) return;

						// 标记为拖拽状态
						this.isDragging = true;

						// 计算相对于表格的坐标
						this.endX = event.clientX - this.tableRect.left;
						this.endY = event.clientY - this.tableRect.top;

						// 限制选择框在表格范围内
						this.endX = Math.max(0, Math.min(this.endX, this.tableRect.width));
						this.endY = Math.max(0, Math.min(this.endY, this.tableRect.height));
					},

					// 处理鼠标释放
					handleMouseUp() {
						if (!this.selecting) return;

						this.selecting = false;

						// 只有在拖拽距离足够大时才认为是框选
						const dragDistance = Math.sqrt(
							Math.pow(this.endX - this.startX, 2) +
							Math.pow(this.endY - this.startY, 2)
						);

						if (dragDistance > 5) {
							this.selectCellsInRect();
							this.lastOperation = '框选了多个单元格';
						}
					},

					// 选择矩形区域内的单元格
					selectCellsInRect() {
						const tableBody = this.$refs.multipleTable.$el.querySelector('.el-table__body tbody');
						if (!tableBody) return;

						const minX = Math.min(this.startX, this.endX);
						const maxX = Math.max(this.startX, this.endX);
						const minY = Math.min(this.startY, this.endY);
						const maxY = Math.max(this.startY, this.endY);

						// 获取所有行
						const rows = tableBody.querySelectorAll('tr');

						// 获取表头信息
						const tableHeader = this.$refs.multipleTable.$el.querySelector('.el-table__header thead');
						const headers = tableHeader.querySelectorAll('th');
						const headerWidths = Array.from(headers).map(th => th.offsetWidth);

						// 遍历每一行
						rows.forEach((row, rowIndex) => {
							const rowRect = row.getBoundingClientRect();
							const rowTop = rowRect.top - this.tableRect.top;
							const rowBottom = rowTop + rowRect.height;

							// 检查行是否在选择矩形内
							if (rowBottom > minY && rowTop < maxY) {
								// 获取行中的所有单元格
								const cells = row.querySelectorAll('td');

								// 遍历每个单元格
								cells.forEach((cell, cellIndex) => {
									// 跳过选择列(第一列)
									if (cellIndex === 0) return;

									const cellRect = cell.getBoundingClientRect();
									const cellLeft = cellRect.left - this.tableRect.left;
									const cellRight = cellLeft + cellRect.width;
									const cellTop = cellRect.top - this.tableRect.top;
									const cellBottom = cellTop + cellRect.height;

									// 检查单元格是否在选择矩形内
									if (cellRight > minX && cellLeft < maxX &&
										cellBottom > minY && cellTop < maxY) {
										this.toggleCellSelection(cell, true);
									}
								});
							}
						});

						this.updateSelectedCount();
					},

					// 切换单元格选择状态
					toggleCellSelection(cell, forceSelect = false) {
						if (cell.classList.contains('selected-cell')) {
							if (!forceSelect) {
								cell.classList.remove('selected-cell');
								this.removeCellFromSelection(cell);
							}
						} else {
							cell.classList.add('selected-cell');
							this.addCellToSelection(cell);
						}
					},

					// 添加单元格到选择集合
					addCellToSelection(cell) {
						const row = cell.parentNode;
						const rowIndex = Array.from(row.parentNode.children).indexOf(row);
						const cellIndex = Array.from(row.children).indexOf(cell);

						if (rowIndex >= 0 && rowIndex < this.tableData.length) {
							const key = `${rowIndex}-${cellIndex}`;
							if (!this.selectedCells.includes(key)) {
								this.selectedCells.push(key);
							}
						}
					},

					// 从选择集合移除单元格
					removeCellFromSelection(cell) {
						const row = cell.parentNode;
						const rowIndex = Array.from(row.parentNode.children).indexOf(row);
						const cellIndex = Array.from(row.children).indexOf(cell);

						const key = `${rowIndex}-${cellIndex}`;
						this.selectedCells = this.selectedCells.filter(k => k !== key);
					},

					// 切换行选择状态
					toggleRowSelection(row) {
						this.$refs.multipleTable.toggleRowSelection(row);
					},

					// 全选
					selectAll() {
						this.$refs.multipleTable.toggleAllSelection();
						this.lastOperation = '选择了所有行';
					},

					// 清除选择
					clearSelection() {
						this.$refs.multipleTable.clearSelection();

						// 清除单元格选择
						const table = this.$refs.multipleTable.$el;
						const cells = table.querySelectorAll('.selected-cell');
						cells.forEach(cell => {
							cell.classList.remove('selected-cell');
						});

						this.selectedCells = [];
						this.updateSelectedCount();
						this.lastOperation = '清除了所有选择';
					},

					// 复制选中内容
					copySelected() {
						let textToCopy = '';

						if (this.selectionMode === 'row' && this.selectedRows.length > 0) {
							// 复制选中的行
							const columns = this.$refs.multipleTable.columns;
							// 跳过选择列
							const dataColumns = columns.filter((col, index) => index > 0);

							// 表头
							textToCopy = dataColumns.map(col => col.label).join('\t') + '\n';

							// 数据行
							textToCopy += this.selectedRows.map(row => {
								return dataColumns.map(col => {
									return col.property ? row[col.property] || '' : '';
								}).join('\t');
							}).join('\n');
						} else if (this.selectedCells.length > 0) {
							// 复制选中的单元格
							const tableBody = this.$refs.multipleTable.$el.querySelector('.el-table__body tbody');
							if (!tableBody) return;

							// 获取表头信息
							const tableHeader = this.$refs.multipleTable.$el.querySelector('.el-table__header thead');
							const headers = tableHeader.querySelectorAll('th');
							const headerLabels = Array.from(headers).map(th => th.textContent.trim()).slice(1); // 跳过选择列

							// 将选中的单元格按行和列分组
							const selectedMap = {};
							this.selectedCells.forEach(key => {
								const [rowIndex, cellIndex] = key.split('-').map(Number);
								if (!selectedMap[rowIndex]) selectedMap[rowIndex] = [];
								// 跳过选择列,调整列索引
								selectedMap[rowIndex].push(cellIndex - 1);
							});

							// 确定最小和最大行索引
							const rowIndexes = Object.keys(selectedMap).map(Number).sort((a, b) => a - b);
							if (rowIndexes.length === 0) return;

							const minRow = Math.min(...rowIndexes);
							const maxRow = Math.max(...rowIndexes);

							// 确定最小和最大列索引
							let minCol = Infinity,
								maxCol = -Infinity;
							Object.values(selectedMap).forEach(colIndexes => {
								minCol = Math.min(minCol, ...colIndexes);
								maxCol = Math.max(maxCol, ...colIndexes);
							});

							// 生成TSV格式文本
							for (let rowIndex = minRow; rowIndex <= maxRow; rowIndex++) {
								const rowData = [];

								for (let colIndex = minCol; colIndex <= maxCol; colIndex++) {
									if (selectedMap[rowIndex] && selectedMap[rowIndex].includes(colIndex)) {
										// 获取单元格数据
										const row = tableBody.children[rowIndex];
										if (row) {
											// 跳过选择列,所以+1
											const cell = row.children[colIndex + 1];
											if (cell) {
												rowData.push(cell.textContent.trim());
											} else {
												rowData.push('');
											}
										} else {
											rowData.push('');
										}
									} else {
										rowData.push('');
									}
								}

								textToCopy += rowData.join('\t') + '\n';
							}
						}

						if (textToCopy) {
							// 使用Clipboard API复制文本
							navigator.clipboard.writeText(textToCopy).then(() => {
								this.showCopyStatus = true;
								setTimeout(() => {
									this.showCopyStatus = false;
								}, 2000);
								this.lastOperation = '复制了选中内容';
							}).catch(err => {
								// 如果Clipboard API不可用,使用传统方法
								this.fallbackCopyTextToClipboard(textToCopy);
							});
						} else {
							this.$message.warning('没有选中任何内容');
						}
					},

					// 传统复制方法
					fallbackCopyTextToClipboard(text) {
						const textArea = document.createElement('textarea');
						textArea.value = text;
						textArea.style.position = 'fixed';
						textArea.style.opacity = 0;
						document.body.appendChild(textArea);
						textArea.focus();
						textArea.select();

						try {
							const successful = document.execCommand('copy');
							if (successful) {
								this.showCopyStatus = true;
								setTimeout(() => {
									this.showCopyStatus = false;
								}, 2000);
								this.lastOperation = '复制了选中内容';
							} else {
								this.$message.error('复制失败');
							}
						} catch (err) {
							this.$message.error('复制失败: ' + err);
						}

						document.body.removeChild(textArea);
					},

					// 切换选择模式
					toggleSelectionMode() {
						this.selectionMode = this.selectionMode === 'cell' ? 'row' : 'cell';
						this.clearSelection();
						this.lastOperation = `切换到${this.selectionMode === 'cell' ? '单元格' : '行'}选择模式`;
					},

					// 更新选中计数
					updateSelectedCount() {
						if (this.selectionMode === 'row') {
							this.selectedCount = this.selectedRows.length;
						} else {
							this.selectedCount = this.selectedCells.length;
						}
					},

					// 表格行类名
					tableRowClassName({
						row,
						rowIndex
					}) {
						if (this.selectedRows.includes(row)) {
							return 'selected-row';
						}
						return '';
					}
				}
			});
		</script>
	</body>
</html>
相关推荐
JarvanMo3 小时前
Flutter 中的 ClipPath | Flutter 每日组件
前端
chéng ௹3 小时前
Vue3+Ts+Element Plus 权限菜单控制节点
前端·javascript·vue.js·typescript
星光一影3 小时前
HIS系统天花板,十大核心模块,门诊/住院/医保全流程打通,医院数字化转型首选
java·spring boot·后端·sql·elementui·html·scss
FIN66684 小时前
昂瑞微:以射频“芯”火 点亮科技强国之路
前端·人工智能·科技·前端框架·智能
携欢4 小时前
PortSwigger靶场之Exploiting server-side parameter pollution in a REST URL通关秘籍
前端·javascript·安全
鹏多多4 小时前
今天你就是VS Code之神!15个隐藏技巧让代码效率翻倍
前端·程序员·visual studio code
linksinke4 小时前
html案例:制作一个图片水印生成器,防止复印件被滥用
开发语言·前端·程序人生·html
寒月霜华4 小时前
JavaWeb-html、css-网页正文制作
前端·css·html
执沐4 小时前
HTML实现流星雨
前端·html