import { Options, FastKeyboard, CustomEditer, Column, Record } from "./type"; import Edit from "./edit"; import { v4 } from "uuid"; import { onBeforeUnmount, watch, onMounted, toValue } from "vue"; import NP from "number-precision"; import { getCopyPasteTextarea, readTextToClipboard, writeTextToClipboard, splitFillPrecision, deepCopy } from "./util"; export default class extends Edit { constructor(options: Options) { super(options); getCopyPasteTextarea(); const keypressCtrlC = () => this.keypressCtrlC(); const keypressCtrlV = () => this.keypressCtrlV(); onMounted(() => { document.addEventListener("keydown", keypressCtrlC); document.addEventListener("keydown", keypressCtrlV); }); onBeforeUnmount(() => { document.removeEventListener("keydown", keypressCtrlC); document.removeEventListener("keydown", keypressCtrlV); }); } private copyData: any[][] = []; protected cellSplit = "\t"; protected warpSplit = "\r"; protected enterSplit = "\n"; // 向下复制 copydownHandel() { // if (this.getMinStartY() !== this.getMaxEndY()) { // this.warningMsg(/*"不能对多重选择区域执行此操作"*/ this.getMessage('disabledMoreArea').value); // return; // } if (this.selectedState.rows && this.selectedState.cells) { this.warningMsg(/*"不能对多重选择区域执行此操作"*/ this.getMessage('disabledMoreArea').value); return; } let copyIndexRow = this.getMinStartY(); let copyIndexCol = this._getMinStartX(); const single = this.getMinStartY() === this.getMaxEndY() let lastRowIndex: number; if (single){ lastRowIndex = this.props?.keepLastRowEmpty ? this.dataSource.value?.length - 2 : this.dataSource.value?.length - 1; } else { lastRowIndex = this.getMaxEndY() } while (copyIndexRow < lastRowIndex) { copyIndexRow++; while (copyIndexCol <= this._getMaxEndX()) { const dataIndex = this.tileColumns?.value[copyIndexCol].dataIndex; const value = this.getValueByModel( dataIndex, this.dataSource?.value[this.getMinStartY()] ); this.setValueByModel( dataIndex, this.dataSource.value[copyIndexRow], value ); // @ts-ignore 手动清楚验证 this.clearValidate?.([dataIndex], copyIndexRow) copyIndexCol++; } copyIndexCol = this._getMinStartX(); } } // 向下递增 async addStepdownHandel(step: number = 1) { // if (this.getMinStartY() !== this.getMaxEndY()) { // this.warningMsg("不能对多行选择区域执行此操作"); // return; // } if (this.selectedState.rows && this.selectedState.cells) { this.warningMsg("不能对多重选择区域执行此操作"); return; } let copyIndexRow = this.getMinStartY(); let copyIndexCol = this._getMinStartX(); let count: any = {}; const single = this.getMinStartY() === this.getMaxEndY() let lastRowIndex: number; if (single){ lastRowIndex = this.props?.keepLastRowEmpty ? this.dataSource.value?.length - 2 : this.dataSource.value?.length - 1; } else { lastRowIndex = this.getMaxEndY() } while (copyIndexRow < lastRowIndex) { copyIndexRow++; while (copyIndexCol <= this._getMaxEndX()) { const dataIndex = this.tileColumns?.value[copyIndexCol].dataIndex; // 列key const precision = this.tileColumns?.value[copyIndexCol].precision; let value = this.getValueByModel( dataIndex, this.dataSource?.value[this.getMinStartY()] ); // 起始值 count[copyIndexCol] = NP.plus(count[copyIndexCol] || 0, step); if (Number(value) == value) { value = NP.plus(value, count[copyIndexCol]); } else if (/(\d+)$/g.exec(value)) { const exResult: any = /(\d+)$/g.exec(value); const execLength = exResult[0].length // 补齐前面的0(暂时不考虑小数) function fillExecLength(value: any, length: number){ const result: string[] = value.toString().split('') if (result.length < length) { for(let i = 0; i <= length - result.length; i++){ result.unshift('0') } } return result.join('') } const num = fillExecLength(NP.plus(exResult[0], count[copyIndexCol]), execLength); value = `${value.substring(0, exResult.index)}${num}`; } else { value = value + count[copyIndexCol]; } // 精度处理 if (precision) { const _precision = typeof precision === 'function' ? precision({ column: this.tileColumns?.value[copyIndexCol], record: this.dataSource.value[copyIndexRow], rowindex: copyIndexRow, }) : precision; value = splitFillPrecision(value, _precision); } this.setValueByModel( dataIndex, this.dataSource.value[copyIndexRow], value ); // @ts-ignore 手动清楚验证 this.clearValidate?.([dataIndex], copyIndexRow) copyIndexCol++; } copyIndexCol = this._getMinStartX(); } } // 复制 copyHandel() { if (this.selectedState.rows && this.selectedState.cells) { this.warningMsg(/*"不能对多重选择区域执行此操作"*/ this.getMessage('disabledMoreArea').value); return; } this.copyData = []; let copytext: string = ""; if (this.selectedState.cells || this.selectedState.rows) { copytext = this.getCopyDataAnyWhereSelected() as string; } else { copytext = this.getCopyData() as string; } writeTextToClipboard(copytext) } // 从剪辑版获取数据 async getClipboardData(): Promise { const clipboardData = await readTextToClipboard(); return (clipboardData && this.pasteToJson(clipboardData)) || []; } pasteToJson(text: string) { try { if (text.startsWith("[") && text.endsWith("]")) { const data = JSON.parse(text) ?? undefined; if (typeof data === "object") return data; throw ""; } else { throw ""; } } catch (e) { return text .replace(RegExp(`${this.enterSplit}`, "g"), "") .split(this.warpSplit) .map((row) => row.split(this.cellSplit)); } } // 粘贴 async pasteHandel() { if (this.checkHasReadonly(this.selectAreasData.value.records)) return; if (this.checkHasDisable(this.selectAreasData.value.records)) return; if (this.props.customEditer?.onEditBefore){ await this.props.customEditer?.onEditBefore?.(this.selectAreasData.value, this); } // 存在自由点选时, 不允许进行粘贴 if (this.selectedState.cells || this.selectedState.rows) { this.warningMsg(/*"不能对多重选择区域执行此操作"*/ this.getMessage('disabledMoreArea').value); return; } if(this.props.openUndo && this.emptyHistoryRecord()) this.addHistoryRecord(); this.copyData = await this.getClipboardData(); if (this.copyData.length === 0) return; let minRowindex = this.getMinStartY(); let maxRowindex = Math.max( this.getMaxEndY(), minRowindex + this.copyData.length - 1 ); let minColindex = this._getMinStartX(); let maxColindex = Math.max( this._getMaxEndX(), minColindex + this.copyData[0].length - 1 ); let copyDataRowIndex = 0; let copyDataColIndex = 0; const pasteCallbackData: {newValue?: any, oldValue?: any, column?: Column, record: Record, rowindex: number}[] = [] const pasteCells = () => { // 行不够,自动添加行 if (this.dataSource.value[minRowindex] === undefined) { this.insertRows?.(minRowindex, 'after', 1, false); } if (!this.copyData[copyDataRowIndex]) copyDataRowIndex = 0; copyDataColIndex = 0; const copyDataRow = this.copyData[copyDataRowIndex]; if (!copyDataRow) return; do { if (copyDataColIndex >= copyDataRow.length) copyDataColIndex = 0; const currentColumn = toValue(this.tileColumns)[minColindex]; const currentRecord = this.dataSource.value[minRowindex]; // 列存在时 if (currentColumn) { // 列只读、列禁用校验 if (!currentColumn.readonly && !currentColumn.disabled) { // disabled禁用校验 if (!this.props.customCell?.(currentRecord, currentColumn, minRowindex)?.disabled) { const copyDataCol = copyDataRow[copyDataColIndex]; const customPaste = currentColumn.customPaste; const dataIndex = currentColumn.dataIndex; const precision = currentColumn.precision; const valueType = currentColumn.valueType; const oldValue = this.getValueByModel(dataIndex, currentRecord) if (typeof customPaste === 'function'){ customPaste({ value: window.decodeURIComponent(copyDataCol), // 解码 rowindex: minRowindex, record: currentRecord, column: currentColumn, source: this.dataSource, }, this.selectAreasData.value); } else if (dataIndex) { let value = [undefined, null, ""].includes(copyDataCol) ? undefined : copyDataCol; if (value !== undefined && valueType === "number"){ value = isNaN(value) ? undefined : Number(value); } else if (value !== undefined && valueType === "boolean"){ const EUMNS: any = { true: true, false: false, "1": true, "0": false, } value = EUMNS[value]; } // 精度处理 if (precision) { const _precision = typeof precision === 'function' ? precision({ column: currentColumn, record: currentRecord, rowindex: minRowindex, }) : precision; value = splitFillPrecision(value, _precision); } this.setValueByModel( dataIndex, currentRecord, value ); } this.props.customEditer?.onChange?.({ newValue: this.getValueByModel(dataIndex, currentRecord), oldValue: oldValue, column: currentColumn, record: currentRecord, rowindex: minRowindex, }) } } } minColindex++; copyDataColIndex++; } while (minColindex <= maxColindex); minColindex = this._getMinStartX(); } const pasteRows = () => { if (!this.copyData[copyDataRowIndex]) copyDataRowIndex = 0; const innerItemData = this.copyData[copyDataRowIndex] if(Array.isArray(innerItemData)) { pasteCells(); } else if(innerItemData) { const readonlyColumns = this.tileColumns.value.filter(col => col.readonly) const disabledColumns = this.tileColumns.value.filter(col => col.disabled) // 粘贴前的旧数据 const oldData = deepCopy(this.dataSource.value[minRowindex]) // 将要粘贴的新数据 const newData = deepCopy(innerItemData) // 恢复只读的数据 readonlyColumns?.forEach(col => { const value = this.getValueByModel(col.dataIndex, oldData) this.setValueByModel(col.dataIndex, newData, value) }) // 恢复禁用的数据 disabledColumns?.forEach(col => { const value = this.getValueByModel(col.dataIndex, oldData) this.setValueByModel(col.dataIndex, newData, value) }) this.dataSource.value[minRowindex] = { ...newData, [this.props.rowKey]: v4(), }; this.tileColumns.value.forEach(col => { this.props.customEditer?.onChange?.({ newValue: this.getValueByModel(col.dataIndex, this.dataSource.value[minRowindex]), oldValue: this.getValueByModel(col.dataIndex, oldData), column: col, record: this.dataSource.value[minRowindex], rowindex: minRowindex, }) }) this.props.customRow?.(this.dataSource.value[minRowindex], minRowindex)?.onPasteAfter?.() } } do { // 行复制 if (this.selectedState.startX === -1) { pasteRows() } else { pasteCells() } minRowindex++; copyDataRowIndex++; } while (minRowindex <= maxRowindex); if(this.props.openUndo) this.addHistoryRecord(); } private _getMinStartX() { if (this.selectedState.startX === -1) return 0; return Math.min( this.selectedState.startX ?? 0, this.selectedState.endX ?? 0 ); } private _getMaxEndX() { if (this.selectedState.startX === -1) return this.tileColumns?.value?.length - 1; return Math.max( this.selectedState.startX ?? 0, this.selectedState.endX ?? 0 ); } /** * 获取某个单元格的复制数据 * @param rowindex * @param colindex * @returns */ private getCellValue(rowindex: number, colindex: number){ const record = this.dataSource?.value[rowindex]; const column = this.tileColumns?.value[colindex]; const { customCopy, dataIndex } = column; let value = ""; try{ if (typeof customCopy === 'function' && rowindex >= 0 && rowindex < this.dataSource.value.length) { value = customCopy({ value: this.getValueByModel(dataIndex, record), rowindex, record, source: this.dataSource, column }, this.selectAreasData.value) ?? ''; if (typeof value === 'number') { value = String(value); } else if (typeof value !== 'string') { value = window.encodeURIComponent(value); console.error('customCopy 返回值不是一个字符串. 请检查返回值.', value, column); } // 编码 if (typeof value === 'string' && (value.includes(this.cellSplit) || value.includes(this.warpSplit) || value.includes(this.enterSplit))){ value = window.encodeURIComponent(value); } } else if (!dataIndex || !record) { value = this.getCellElement({rowindex: rowindex, colindex: colindex})?.innerText ?? ''; } else { value = this.getValueByModel( dataIndex, record ) ?? ""; } return value; }catch(e){ console.error(e) return '' } } /** * 获取范围内单元格的复制数据 * @param config * @returns */ protected getValueByIndex(config: { startX: number, endX: number, startY: number, endY: number }){ const { startX, endX, startY, endY } = config; let copyIndexCol = startX; let copyIndexRow = startY; let copytext = ""; do { // 添加换行符 if (copytext !== "") { copytext += this.warpSplit; } do { // 添加制表符 if (copyIndexCol > startX) copytext += this.cellSplit; try{ copytext += this.getCellValue(copyIndexRow, copyIndexCol) ?? ''; } catch(e){ copytext += ""; } finally { copyIndexCol++; } } while (copyIndexCol <= endX); copyIndexCol = startX; copyIndexRow++; } while (copyIndexRow <= endY); return copytext; } // 获取复制数据 private getCopyData() { // 行复制 if (this.selectedState.startX === -1) { if (this.props.customEditer?.disabledCopySource){ return this.getValueByIndex({ startX: this._getMinStartX(), endX: this._getMaxEndX(), startY: this.getMinStartY(), endY: this.getMaxEndY() }) } else { const copyData = this.dataSource?.value.slice(this.getMinStartY(), this.getMaxEndY() + 1)?? []; return JSON.stringify(copyData); } } return this.getValueByIndex({ startX: this._getMinStartX(), endX: this._getMaxEndX(), startY: this.getMinStartY(), endY: this.getMaxEndY() }) } protected getCopyDataAnyWhereSelectedDisabledCopySource(){ let copyText = '' this.selectAreasData.value.rowindexs.forEach(rowindex => { copyText += this.getValueByIndex({ startX: 0, endX: this.tileColumns?.value?.length - 1, startY: rowindex, endY: rowindex }) copyText += this.warpSplit; }); return copyText } // 获取自由点选复制数据 private getCopyDataAnyWhereSelected() { if (this.selectedState.rows) { if (this.props.customEditer?.disabledCopySource){ return this.getCopyDataAnyWhereSelectedDisabledCopySource() } else { return JSON.stringify( this.selectedState.rows.map((rowindex) => { return this.dataSource.value[rowindex]; }) ); } } if (this.selectedState.cells) { // 自由点选单元格时,存在不同行、不同列时, 不允许复制 // 是否都在一行 或者 是否都在一列 const sameRow = new Set(this.selectedState.cells.map((v) => v[0])).size === 1; const sameCol = new Set(this.selectedState.cells.map((v) => v[1])).size === 1; if (!(sameRow || sameCol)) { this.warningMsg(/*"不能对多重选择区域执行此操作"*/ this.getMessage('disabledMoreArea').value); throw this.getMessage('disabledMoreArea').value; } // 同一行中 if (sameRow) { const colindexs = this.selectedState.cells.map((v) => v[1]); const rowindex = this.selectedState.cells[0][0]; let text: string = ""; let startX = Math.min(...colindexs); const endX = Math.max(...colindexs); while (startX <= endX) { if (colindexs.includes(startX)) { // TODO 含表头的复制 if (rowindex === -1) { if (text !== "") text += this.cellSplit; const th = this.getCellElement({colindex: startX}); text += th?.innerText; } else { if (text !== "") text += this.cellSplit; const dataIndex = this.tileColumns?.value[startX].dataIndex; const value = this.getValueByModel( dataIndex, this.dataSource?.value[rowindex] ) ?? ""; text += value; } } startX++; } return text; } // 同一列中 if (sameCol) { const rowindexs = this.selectedState.cells.map((v) => v[0]); const colindex = this.selectedState.cells[0][1]; const dataIndex = this.tileColumns?.value[colindex].dataIndex; let text: string = ""; let startY = Math.min(...rowindexs); const endY = Math.max(...rowindexs); while (startY <= endY) { if (rowindexs.includes(startY)) { // TODO 含表头的复制 if (startY === -1) { text += this.getCellElement({colindex: colindex})?.innerText ?? ''; } else { if (text !== "") text += this.warpSplit; const value = this.getValueByModel( dataIndex, this.dataSource?.value[startY] ) ?? ""; text += value; } } startY++; } return text; } } } // Ctrl+C private keypressCtrlC = () => { if (!this.currentView) return; if ((event?.ctrlKey || event?.metaKey) && event?.keyCode == 67) { // console.log(this.checkFastKeyboard(FastKeyboard.Ctrl_C)) if (!this.checkFastKeyboard(FastKeyboard.Ctrl_C)) return; this.copyHandel(); return false; } }; // Ctrl+V private keypressCtrlV = () => { if (!this.currentView) return; if ((event?.ctrlKey || event?.metaKey) && event?.keyCode == 86) { if (!this.checkFastKeyboard(FastKeyboard.Ctrl_V)) return; this.pasteHandel(); return false; } }; }