vue2 + vxe-table + xe-clipboard实现列表区域选中和复制粘贴
1.前言
全选方法:
selectAll() {const tableData = this.getTablexGrid().getTableData().visibleData;const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;if (tableData.length === 0 || tableColumn.length === 0) return;this.selectionStart = { rowIndex: 0, cellIndex: 0 };this.selectionEnd = { rowIndex: tableData.length - 1, cellIndex: tableColumn.length - 1 };this.updateSelectionHighlight();this.updateSelectionInfo();this.$message.success(`已全选 ${tableData.length} 行 ${tableColumn.length} 列`);
}
2.实现效果
3.完整代码
<template><div class="excel-table-container"><!-- 工具栏 --><div class="toolbar"><div class="toolbar-left"><button class="toolbar-btn" @click="selectAll"><i class="icon-select-all"></i> 全选</button><button class="toolbar-btn" @click="copySelected"><i class="icon-copy"></i> 复制</button><button class="toolbar-btn" @click="pasteData"><i class="icon-paste"></i> 粘贴</button><button class="toolbar-btn" @click="clearSelected"><i class="icon-delete"></i> 清除</button><button class="toolbar-btn" @click="fillDown"><i class="icon-fill-down"></i> 向下填充</button></div><div class="toolbar-right"><span class="selection-info">已选中: {{ selectedRows }}行 {{ selectedColumns }}列</span></div></div><!-- 表格主体 --><vxe-tableref="xGrid"borderauto-resizesync-resizeshow-overflow="title"size="small"height="700":data="tableData":row-config="{ isHover: true, height: 35 }":scroll-x="{ enabled: true, gt: 0, mode: 'wheel'}":scroll-y="{ enabled: true, gt: 0, mode: 'wheel'}"@cell-click="tableCellClick"@keydown="tableKeydown"><vxe-column field="date" title="日期" width="150" fixed="left"></vxe-column><vxe-column field="name" title="姓名" width="120"></vxe-column><vxe-column field="address" title="地址" width="200"></vxe-column><vxe-column field="age" title="年龄" width="80"></vxe-column><vxe-column field="job" title="职业" width="120"></vxe-column><vxe-column field="status" title="状态" width="100"></vxe-column><vxe-column field="phone" title="电话" width="150"></vxe-column><vxe-column field="email" title="邮箱" width="200"></vxe-column><vxe-column field="department" title="部门" width="150"></vxe-column><vxe-column field="salary" title="薪资" width="120"></vxe-column><vxe-column field="education" title="学历" width="100"></vxe-column><vxe-column field="experience" title="工作经验" width="120"></vxe-column><vxe-column field="city" title="城市" width="120"></vxe-column><vxe-column field="country" title="国家" width="120"></vxe-column><vxe-column field="company" title="公司" width="200"></vxe-column><vxe-column field="position" title="职位" width="150"></vxe-column><vxe-column field="project" title="项目" width="200"></vxe-column><vxe-column field="skill" title="技能" width="150" fixed="right"></vxe-column></vxe-table><!-- 操作提示 --><div class="operation-tips"><div class="tip-item"><span class="key">Ctrl+A</span> 全选表格</div><div class="tip-item"><span class="key">单击行号</span> 选择整行</div><div class="tip-item"><span class="key">Ctrl+C</span> 复制选中区域</div><div class="tip-item"><span class="key">Ctrl+V</span> 粘贴内容</div></div></div>
</template><script>
import XEClipboard from 'xe-clipboard';export default {name: 'ExcelStyleTable',data() {return {tableData: this.generateTableData(50),isSelecting: false,selectionStart: { rowIndex: -1, cellIndex: -1 },selectionEnd: { rowIndex: -1, cellIndex: -1 },lastActive: null,selectedRows: 0,selectedColumns: 0,eventSource: null, // 'main' 或 'fixed'scrollDebounce: null // 滚动防抖};},mounted() {this.addListener();this.addScrollListener();},beforeDestroy() {this.removeScrollListener();},methods: {// 生成表格数据generateTableData(count) {const data = [];const departments = ['技术部', '市场部', '销售部', '财务部', '人力资源部'];const statuses = ['进行中', '已完成', '待审核', '已取消'];const jobs = ['工程师', '设计师', '产品经理', '开发工程师', 'UI设计师'];const cities = ['北京', '上海', '广州', '深圳', '杭州'];const companies = ['腾讯科技', '阿里巴巴', '百度', '华为', '小米'];const positions = ['初级工程师', '中级工程师', '高级工程师', '技术专家'];const projects = ['项目A', '项目B', '项目C', '项目D'];const skills = ['Java', 'Python', 'JavaScript', 'C++', 'Go'];for (let i = 0; i < count; i++) {data.push({id: i + 1,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: Math.random() > 0.5 ? '本科' : '硕士',experience: `${Math.floor(Math.random() * 20)}年`,city: cities[Math.floor(Math.random() * cities.length)],country: Math.random() > 0.5 ? '中国' : '美国',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');},// 获取表格实例getTablexGrid() {return this.$refs.xGrid;},// 添加事件监听addListener() {this.$nextTick(() => {// 主表格区域事件绑定const mainTbody = this.getTablexGrid().$el.querySelector(".vxe-table--main-wrapper table tbody");if (mainTbody) {mainTbody.addEventListener("mousedown", (e) => this.tbodymousedown(e, 'main'));mainTbody.addEventListener("mouseup", (e) => this.tbodymouseup(e, 'main'));mainTbody.addEventListener("mousemove", (e) => this.tbodymousemove(e, 'main'));}// 左侧固定列区域事件绑定const fixedLeftTbody = this.getTablexGrid().$el.querySelector(".vxe-table--fixed-left-wrapper table tbody");if (fixedLeftTbody) {fixedLeftTbody.addEventListener("mousedown", (e) => this.tbodymousedown(e, 'fixed'));fixedLeftTbody.addEventListener("mouseup", (e) => this.tbodymouseup(e, 'fixed'));fixedLeftTbody.addEventListener("mousemove", (e) => this.tbodymousemove(e, 'fixed'));}// 右侧固定列区域事件绑定const fixedRightTbody = this.getTablexGrid().$el.querySelector(".vxe-table--fixed-right-wrapper table tbody");if (fixedRightTbody) {fixedRightTbody.addEventListener("mousedown", (e) => this.tbodymousedown(e, 'fixed'));fixedRightTbody.addEventListener("mouseup", (e) => this.tbodymouseup(e, 'fixed'));fixedRightTbody.addEventListener("mousemove", (e) => this.tbodymousemove(e, 'fixed'));}// 添加行号区域点击事件this.addRowNumberClickListener();});},// 添加行号区域点击事件addRowNumberClickListener() {this.$nextTick(() => {// 主表格行号区域const mainRowNumber = this.getTablexGrid().$el.querySelector(".vxe-table--main-wrapper .vxe-table--fixed-left-wrapper");if (mainRowNumber) {mainRowNumber.addEventListener("click", this.handleRowNumberClick);}// 固定列行号区域const fixedRowNumber = this.getTablexGrid().$el.querySelector(".vxe-table--fixed-left-wrapper");if (fixedRowNumber) {fixedRowNumber.addEventListener("click", this.handleRowNumberClick);}});},// 行号区域点击事件handleRowNumberClick(event) {let target = event.target;// 向上查找行元素while (target && !target.classList.contains('vxe-body--row')) {target = target.parentElement;}if (target && target.classList.contains('vxe-body--row')) {const rowId = target.getAttribute('rowid');const visibleData = this.getTablexGrid().getTableData().visibleData;const rowIndex = visibleData.findIndex(row => row._X_ROW_KEY === rowId);if (rowIndex >= 0) {this.selectRow(rowIndex);event.preventDefault();event.stopPropagation();}}},// 选择整行selectRow(rowIndex) {const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;const columnCount = tableColumn.length;this.selectionStart = { rowIndex, cellIndex: 0 };this.selectionEnd = { rowIndex, cellIndex: columnCount - 1 };this.updateSelectionHighlight();this.updateSelectionInfo();// 滚动到选中行this.scrollToRow(rowIndex);},// 滚动到指定行scrollToRow(rowIndex) {const table = this.getTablexGrid().$el.querySelector(".vxe-table--body-wrapper");if (table) {const rowHeight = 35;const visibleRows = Math.floor(table.clientHeight / rowHeight);const scrollTop = Math.max(0, rowIndex * rowHeight - (visibleRows / 2) * rowHeight);table.scrollTop = scrollTop;}},// 添加滚动监听addScrollListener() {this.$nextTick(() => {const scrollBody = this.getTablexGrid().$el.querySelector('.vxe-table--body-wrapper');if (scrollBody) {scrollBody.addEventListener('scroll', this.handleScroll);}const fixedLeftScroll = this.getTablexGrid().$el.querySelector('.vxe-table--fixed-left-wrapper .vxe-table--body-wrapper');if (fixedLeftScroll) {fixedLeftScroll.addEventListener('scroll', this.handleScroll);}const fixedRightScroll = this.getTablexGrid().$el.querySelector('.vxe-table--fixed-right-wrapper .vxe-table--body-wrapper');if (fixedRightScroll) {fixedRightScroll.addEventListener('scroll', this.handleScroll);}});},// 移除滚动监听removeScrollListener() {const scrollBody = this.getTablexGrid().$el.querySelector('.vxe-table--body-wrapper');if (scrollBody) {scrollBody.removeEventListener('scroll', this.handleScroll);}const fixedLeftScroll = this.getTablexGrid().$el.querySelector('.vxe-table--fixed-left-wrapper .vxe-table--body-wrapper');if (fixedLeftScroll) {fixedLeftScroll.removeEventListener('scroll', this.handleScroll);}const fixedRightScroll = this.getTablexGrid().$el.querySelector('.vxe-table--fixed-right-wrapper .vxe-table--body-wrapper');if (fixedRightScroll) {fixedRightScroll.removeEventListener('scroll', this.handleScroll);}},// 滚动事件处理handleScroll() {// 使用防抖避免频繁更新if (this.scrollDebounce) clearTimeout(this.scrollDebounce);this.scrollDebounce = setTimeout(() => {this.updateSelectionHighlight();}, 50);},// 鼠标按下事件tbodymousedown(event, source) {if (event.button === 0) {this.eventSource = source;this.selectionStart = this.getCellPosition(event.target);this.isSelecting = true;this.updateSelectionHighlight();}},// 鼠标移动事件tbodymousemove(event, source) {if (event.button === 0 && this.isSelecting) {this.eventSource = source;this.selectionEnd = this.getCellPosition(event.target);this.updateSelectionHighlight();// 自动滚动let x = event.clientX;let y = event.clientY;let table = this.getTablexGrid().$el.querySelector(".vxe-table--body-wrapper table");if (table) {let tableRect = table.parentElement.getBoundingClientRect();// 向右滚动if (x > tableRect.right - 20) {table.parentElement.scrollLeft += 20;}// 向左滚动else if (x < tableRect.left + 20) {table.parentElement.scrollLeft -= 20;}// 向下滚动if (y > tableRect.bottom - 20) {table.parentElement.scrollTop += 20;}// 向上滚动else if (y < tableRect.top + 20) {table.parentElement.scrollTop -= 20;}}}},// 鼠标释放事件tbodymouseup(event, source) {if (event.button === 0) {this.eventSource = source;this.isSelecting = false;this.updateSelectionInfo();}},// 获取单元格位置getCellPosition(cell) {try {while (cell && cell.tagName !== 'TD') {cell = cell.parentElement;}if (!cell) return { rowIndex: -1, cellIndex: -1 };const visibleColumn = this.getTablexGrid().getTableColumn().visibleColumn;const colId = cell.getAttribute("colid");const cellIndex = visibleColumn.findIndex(col => col.id === colId);const visibleData = this.getTablexGrid().getTableData().visibleData;const rowId = cell.parentElement.getAttribute("rowid");const rowIndex = visibleData.findIndex(row => row._X_ROW_KEY === rowId);return { rowIndex, cellIndex };} catch (e) {return { rowIndex: -1, cellIndex: -1 };}},// 更新选择区域高亮updateSelectionHighlight() {// 清除之前的高亮this.clearSelectionHighlight();const startRow = Math.min(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const endRow = Math.max(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const startCol = Math.min(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);const endCol = Math.max(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);const visibleColumn = this.getTablexGrid().getTableColumn().visibleColumn;const visibleData = this.getTablexGrid().getTableData().visibleData;if (startRow < 0 || endRow < 0 || startCol < 0 || endCol < 0) return;// 高亮主表格区域const mainTbody = this.getTablexGrid().$el.querySelector(".vxe-table--main-wrapper table tbody");if (mainTbody) {for (let rowIdx = startRow; rowIdx <= endRow; rowIdx++) {const row = mainTbody.querySelector(`tr[rowid="${visibleData[rowIdx]._X_ROW_KEY}"]`);if (row) {for (let colIdx = startCol; colIdx <= endCol; colIdx++) {const cell = row.querySelector(`td[colid="${visibleColumn[colIdx].id}"]`);if (cell) {cell.classList.add('selected-cell');}}}}}// 高亮左侧固定列区域const fixedLeftTbody = this.getTablexGrid().$el.querySelector(".vxe-table--fixed-left-wrapper table tbody");if (fixedLeftTbody) {for (let rowIdx = startRow; rowIdx <= endRow; rowIdx++) {const row = fixedLeftTbody.querySelector(`tr[rowid="${visibleData[rowIdx]._X_ROW_KEY}"]`);if (row) {for (let colIdx = 0; colIdx < visibleColumn.length; colIdx++) {if (colIdx >= startCol && colIdx <= endCol && visibleColumn[colIdx].fixed === 'left') {const cell = row.querySelector(`td[colid="${visibleColumn[colIdx].id}"]`);if (cell) {cell.classList.add('selected-cell');}}}}}}// 高亮右侧固定列区域const fixedRightTbody = this.getTablexGrid().$el.querySelector(".vxe-table--fixed-right-wrapper table tbody");if (fixedRightTbody) {for (let rowIdx = startRow; rowIdx <= endRow; rowIdx++) {const row = fixedRightTbody.querySelector(`tr[rowid="${visibleData[rowIdx]._X_ROW_KEY}"]`);if (row) {for (let colIdx = 0; colIdx < visibleColumn.length; colIdx++) {if (colIdx >= startCol && colIdx <= endCol && visibleColumn[colIdx].fixed === 'right') {const cell = row.querySelector(`td[colid="${visibleColumn[colIdx].id}"]`);if (cell) {cell.classList.add('selected-cell');}}}}}}},// 清除选择区域高亮clearSelectionHighlight() {const cells = document.querySelectorAll('.selected-cell');cells.forEach(cell => {cell.classList.remove('selected-cell');});},// 更新选中区域信息updateSelectionInfo() {if (this.selectionStart.rowIndex < 0 || this.selectionStart.cellIndex < 0) {this.selectedRows = 0;this.selectedColumns = 0;return;}const startRow = Math.min(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const endRow = Math.max(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const startCol = Math.min(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);const endCol = Math.max(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);this.selectedRows = endRow - startRow + 1;this.selectedColumns = endCol - startCol + 1;},// 表格单元格点击事件tableCellClick(e) {const { row, column } = e;if (!this.isSelecting) {this.clearSelectionHighlight();this.selectionStart = this.getCellPosition(e.$event.target);this.selectionEnd = this.selectionStart;this.updateSelectionHighlight();this.updateSelectionInfo();this.lastActive = { rowid: row._X_ROW_KEY, colid: column.id };}},// 表格键盘事件tableKeydown({ $event }) {const key = $event.key.toLowerCase();const ctrl = $event.ctrlKey || $event.metaKey;if (ctrl && key === 'a') {// Ctrl+A 全选this.selectAll();$event.preventDefault();} else if (ctrl && key === 'c') {this.copySelected();$event.preventDefault();} else if (ctrl && key === 'v') {this.pasteData();$event.preventDefault();} else if (ctrl && key === 'd') {this.fillDown();$event.preventDefault();} else if (key === 'delete') {this.clearSelected();$event.preventDefault();} else if (ctrl && key === 'x') {this.cutSelected();$event.preventDefault();} else if (ctrl && key === 'z') {this.incrementValues();$event.preventDefault();} else if (['arrowright', 'arrowleft', 'arrowup', 'arrowdown'].includes(key)) {this.handleArrowKeys(key);$event.preventDefault();}},// 全选表格selectAll() {const tableData = this.getTablexGrid().getTableData().visibleData;const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;if (tableData.length === 0 || tableColumn.length === 0) return;this.selectionStart = { rowIndex: 0, cellIndex: 0 };this.selectionEnd = { rowIndex: tableData.length - 1, cellIndex: tableColumn.length - 1 };this.updateSelectionHighlight();this.updateSelectionInfo();this.$message.success(`已全选 ${tableData.length} 行 ${tableColumn.length} 列`);},// 处理方向键handleArrowKeys(key) {const tableData = this.getTablexGrid().getTableData().visibleData;const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;let startRowIndex = this.selectionStart.rowIndex;let endRowIndex = this.selectionEnd.rowIndex;let startColumnIndex = this.selectionStart.cellIndex;let endColumnIndex = this.selectionEnd.cellIndex;const maxColumnIndex = tableColumn.length - 1;const maxRowIndex = tableData.length - 1;switch (key) {case 'arrowright':if (endColumnIndex < maxColumnIndex) {endColumnIndex++;startColumnIndex = endColumnIndex;startRowIndex = endRowIndex;}break;case 'arrowleft':if (startColumnIndex > 0) {startColumnIndex--;endColumnIndex = startColumnIndex;endRowIndex = startRowIndex;}break;case 'arrowup':if (startRowIndex > 0) {startRowIndex--;endRowIndex = startRowIndex;endColumnIndex = startColumnIndex;}break;case 'arrowdown':if (endRowIndex < maxRowIndex) {endRowIndex++;startRowIndex = endRowIndex;startColumnIndex = endColumnIndex;}break;}this.selectionStart = { rowIndex: startRowIndex, cellIndex: startColumnIndex };this.selectionEnd = { rowIndex: endRowIndex, cellIndex: endColumnIndex };this.updateSelectionHighlight();this.updateSelectionInfo();this.tableScrollMove(key.replace('arrow', ''));},// 控制滚动条tableScrollMove(direction) {const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;const startColumnIndex = this.selectionStart.cellIndex;const endColumnIndex = this.selectionEnd.cellIndex;let column;if (direction === 'right') {column = tableColumn[endColumnIndex];} else if (direction === 'left') {column = tableColumn[startColumnIndex];} else {return;}// 根据事件来源区域选择tbodylet tbody;if (this.eventSource === 'fixed') {if (column.fixed === 'left') {tbody = this.getTablexGrid().$el.querySelector(".vxe-table--fixed-left-wrapper table tbody");} else if (column.fixed === 'right') {tbody = this.getTablexGrid().$el.querySelector(".vxe-table--fixed-right-wrapper table tbody");}}if (!tbody) {tbody = this.getTablexGrid().$el.querySelector(".vxe-table--main-wrapper table tbody");}if (!tbody) return;const td = tbody.querySelector(`td[colid="${column.id}"]`);if (!td) return;const tdRect = td.getBoundingClientRect();const table = this.getTablexGrid().$el.querySelector(".vxe-table--body-wrapper table");if (!table) return;const tableRect = table.parentElement.getBoundingClientRect();let fixedWidth = 0;const flexDiv = this.getTablexGrid().$el.querySelector(".vxe-table--fixed-left-wrapper");if (flexDiv) fixedWidth = flexDiv.offsetWidth;if (direction === 'right' && tdRect.right > tableRect.right) {table.parentElement.scrollLeft += (tdRect.right - tableRect.right);} else if (direction === 'left' && tdRect.left < tableRect.left + fixedWidth) {table.parentElement.scrollLeft += (tdRect.left - tableRect.left - fixedWidth);}},// 复制选中区域copySelected() {const tableData = this.getTablexGrid().getTableData().visibleData;const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;const startRow = Math.min(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const endRow = Math.max(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const startCol = Math.min(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);const endCol = Math.max(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);const data = [];for (let i = startRow; i <= endRow; i++) {const row = [];for (let j = startCol; j <= endCol; j++) {row.push(tableData[i][tableColumn[j].field]);}data.push(row);}const text = data.map(row => row.join("\t")).join("\r\n");this.copyValue(text);this.$message.success(`已复制 ${endRow - startRow + 1} 行 ${endCol - startCol + 1} 列数据`);},// 粘贴数据pasteData() {navigator.clipboard.readText().then(text => {if (text) {this.pasteToTable(text);this.$message.success("粘贴成功");}}).catch(err => {console.error("粘贴失败:", err);this.$message.error("粘贴失败,请检查剪贴板内容");});},// 粘贴到表格pasteToTable(text) {const cleanedText = text.replace(/^\r\n+|\r\n+$/g, '');const rows = cleanedText.split(/\r\n+/);const pasteData = rows.map(row => row.split("\t"));const tableData = this.getTablexGrid().getTableData().visibleData;const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;const startRow = this.selectionStart.rowIndex;const startCol = this.selectionStart.cellIndex;for (let i = 0; i < pasteData.length; i++) {const rowData = pasteData[i];if (startRow + i >= tableData.length) break;const row = tableData[startRow + i];for (let j = 0; j < rowData.length; j++) {if (startCol + j >= tableColumn.length) break;const column = tableColumn[startCol + j];row[column.field] = rowData[j];}}// 更新高亮显示this.updateSelectionHighlight();},// 清除选中区域clearSelected() {const tableData = this.getTablexGrid().getTableData().visibleData;const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;const startRow = Math.min(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const endRow = Math.max(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const startCol = Math.min(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);const endCol = Math.max(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);for (let i = startRow; i <= endRow; i++) {for (let j = startCol; j <= endCol; j++) {const row = tableData[i];const column = tableColumn[j];row[column.field] = "";}}this.$message.success("已清除选中区域内容");},// 向下填充fillDown() {const tableData = this.getTablexGrid().getTableData().visibleData;const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;const startRow = Math.min(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const endRow = Math.max(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const startCol = Math.min(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);const endCol = Math.max(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);for (let j = startCol; j <= endCol; j++) {const firstValue = tableData[startRow][tableColumn[j].field];for (let i = startRow + 1; i <= endRow; i++) {tableData[i][tableColumn[j].field] = firstValue;}}this.$message.success("向下填充完成");},// 序列填充incrementValues() {const tableData = this.getTablexGrid().getTableData().visibleData;const tableColumn = this.getTablexGrid().getTableColumn().visibleColumn;const startRow = Math.min(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const endRow = Math.max(this.selectionStart.rowIndex, this.selectionEnd.rowIndex);const startCol = Math.min(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);const endCol = Math.max(this.selectionStart.cellIndex, this.selectionEnd.cellIndex);for (let j = startCol; j <= endCol; j++) {const firstValue = tableData[startRow][tableColumn[j].field];if (!isNaN(firstValue)) {// 数字序列for (let i = startRow + 1; i <= endRow; i++) {tableData[i][tableColumn[j].field] = parseFloat(firstValue) + (i - startRow);}} else {// 文本序列const match = firstValue.match(/(.*?)(\d+)$/);if (match) {const prefix = match[1];const startNum = parseInt(match[2]);for (let i = startRow + 1; i <= endRow; i++) {tableData[i][tableColumn[j].field] = prefix + (startNum + i - startRow);}}}}this.$message.success("序列填充完成");},// 剪切选中区域cutSelected() {this.copySelected();this.clearSelected();},// 复制值到剪贴板copyValue(value) {if (XEClipboard.copy(value)) {this.$message.success('已复制到剪贴板!');} else {this.$message.error('复制失败');}}}
}
</script><style scoped>
.excel-table-container {width: 100%;height: 100%;display: flex;flex-direction: column;background: #fff;border-radius: 8px;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);overflow: hidden;font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}.toolbar {padding: 12px 16px;background: linear-gradient(to bottom, #f8f9fa, #e9ecef);border-bottom: 1px solid #dee2e6;display: flex;justify-content: space-between;align-items: center;
}.toolbar-left {display: flex;gap: 8px;
}.toolbar-btn {padding: 8px 16px;background: #fff;border: 1px solid #ced4da;border-radius: 4px;cursor: pointer;display: flex;align-items: center;transition: all 0.3s;font-size: 14px;color: #495057;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}.toolbar-btn:hover {background: #e9ecef;border-color: #adb5bd;transform: translateY(-1px);box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}.toolbar-btn i {margin-right: 6px;font-size: 16px;
}.icon-select-all::before { content: "📋"; }
.icon-copy::before { content: "📋"; }
.icon-paste::before { content: "📄"; }
.icon-delete::before { content: "🗑️"; }
.icon-fill-down::before { content: "⤵️"; }.selection-info {font-size: 14px;color: #495057;background: #e9ecef;padding: 6px 12px;border-radius: 4px;font-weight: 500;
}.vxe-table {flex: 1;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;background: #fff;
}.operation-tips {display: flex;justify-content: center;gap: 20px;padding: 12px;background: #f8f9fa;border-top: 1px solid #dee2e6;font-size: 14px;color: #6c757d;
}.tip-item {display: flex;align-items: center;
}.key {display: inline-block;min-width: 24px;padding: 2px 6px;background: #e9ecef;border: 1px solid #ced4da;border-radius: 3px;margin-right: 6px;text-align: center;font-weight: bold;color: #495057;
}
</style><style>
/* 全局样式 - 用于高亮选中单元格 */
.selected-cell {background-color: rgba(33, 150, 243, 0.2) !important;position: relative;
}.selected-cell::after {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;border: 2px solid #2196F3;pointer-events: none;z-index: 1;
}.vxe-table--fixed-left-wrapper .selected-cell::after,
.vxe-table--fixed-right-wrapper .selected-cell::after {z-index: 10;
}/* 行号区域样式 */
.vxe-table--fixed-left-wrapper .vxe-body--row {cursor: pointer;position: relative;
}.vxe-table--fixed-left-wrapper .vxe-body--row::after {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: rgba(33, 150, 243, 0.05);opacity: 0;transition: opacity 0.2s;pointer-events: none;
}.vxe-table--fixed-left-wrapper .vxe-body--row:hover::after {opacity: 1;
}.vxe-table--fixed-left-wrapper .vxe-body--row.selected-row::after {background: rgba(33, 150, 243, 0.15);opacity: 1;
}
</style>