import { watch, onMounted, onBeforeUnmount } from "vue" import { type Options } from './type'; import Auth from './auth'; import { deepCopy, splitFillPrecision } from "./util" export default class extends Auth { constructor(options: Options) { super(options); const restEdit = () => this.restEdit() onMounted(() => { document.body.addEventListener('mousedown', restEdit); }) onBeforeUnmount(() => { document.body.removeEventListener('mousedown', restEdit); }) }; currentTd: HTMLElement | null = null; input: HTMLElement | null = null; oldValue: any; findInputRequest: number | undefined; isEditer(rowindex: number, colindex: number) { return ( !this.checkHasReadonly(this.selectAreasData.value.records) && !this.checkHasDisable(this.selectAreasData.value.records) && this.editState.editX === colindex && this.editState.editY === rowindex ); } async clickEditHandel(rowindex: number, colindex: number) { if (colindex === undefined || rowindex === undefined) { this.resetSelectedState(); return; }; if (this.props.customEditer?.disabledSelectCell) return; // 在自由选择时,不进行任何操作 if (event?.ctrlKey || event?.metaKey) return // 在自动插入行时,拦截至关重要,否则会失焦 // if ( this.editState.editX === colindex && this.editState.editY === rowindex ) { // this.input?.focus?.(); // return; // } if (this.tileColumns.value[colindex].readonly) return; if (this.tileColumns.value[colindex].disabled) return; // disabled禁用校验 if (this.props.customCell?.(this.dataSource.value[rowindex], this.tileColumns.value[colindex], rowindex)?.disabled) { return; } // disabled禁用校验 if (this.props.customRow?.(this.dataSource.value[rowindex], rowindex)?.disabled) { return; } if (this.checkHasReadonly(this.selectAreasData.value.records)) return; if (this.checkHasDisable(this.selectAreasData.value.records)) return; this.findInput(rowindex, colindex) if (this.editState.editX !== colindex || this.editState.editY !== rowindex) { if (this.props.customEditer?.onEditBefore){ await this.props.customEditer?.onEditBefore?.(this.selectAreasData.value, this); } this.editState.editX = colindex; this.editState.editY = rowindex; this.editState.status = 'ready'; const stopWatcher = watch( () => [this.selectedState.startX, this.selectedState.startY], () => { removeListenKeydown() this.input?.blur?.() this.input = null stopWatcher() if (this.findInputRequest){ cancelAnimationFrame(this.findInputRequest) this.findInputRequest = undefined } }, { deep: true, }, ); } const keyboardInput = (e: any) => { // console.log(e) if ( e.metaKey || e.ctrlKey || [ // 8, // BackSpace 也是MAC的删除键, 所以不能拦截 9, // Tab 12, // Clear 13, // Enter 16, // Shift 17, // Ctrl 18, // Alt 19, // Pause 20, // Cape Lock 27, // Esc 32, // Spacebar 33, // Insert 37, // Left Arrow 38, // Up Arrow 39, // Right Arrow 40, // Dw Arrow 45, // Insert 91, // Command 93, // 右键菜单 144, // Num Lock/ 145, // Scr lk 192, // ~ 255, // Fn ].includes(e.keyCode) || // Page Up、Page Down、End、Home (e.keyCode >= 33 && e.keyCode <= 36) || // F1 - F12 (e.keyCode >= 112 && e.keyCode <= 123) || // 搜索、收藏、浏览器、静音、音量减、音量加 (e.keyCode >= 170 && e.keyCode <= 175) || // 停止、邮件 (e.keyCode >= 179 && e.keyCode <= 180) ) { // e.preventDefault(); return true; } if (this.editState.status === 'ready') { const dataIndex = this.tileColumns.value[this.editState.editX].dataIndex this.setValueByModel(dataIndex, this.dataSource.value[this.editState.editY], undefined) } removeListenKeydown(); this.editState.status = 'doing'; this.input?.focus?.(); }; function addListenKeydown () { removeListenKeydown() document.addEventListener('keydown', keyboardInput); // 兼容Ectorall 中文输入法, keydown无法触发, 壳子提供事件 document.addEventListener('keyDownData', keyboardInput); } function removeListenKeydown () { document.removeEventListener('keydown', keyboardInput); document.removeEventListener('keyDownData', keyboardInput); } addListenKeydown() } async dblclickEditHandel(rowindex: number, colindex: number) { if (colindex === undefined || rowindex === undefined) { this.resetSelectedState(); return; }; if (this.props.customEditer?.disabledSelectCell) return; if (!this.selectAreasData.value.records?.length) return; if (this.tileColumns.value[colindex].readonly) return; if (this.tileColumns.value[colindex].disabled) return; 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); } this.findInput(rowindex, colindex); setTimeout(() => { this.input?.focus?.(); this.editState.status = 'doing'; }, 0) } private findInput(rowindex: number, colindex: number){ let td: HTMLElement | undefined | null = this.getCellElement({rowindex: rowindex, colindex: colindex}); // if(event?.type === 'keydown') { // td = this.getCellElement({rowindex: rowindex, colindex: colindex}) // } else { // td = event?.currentTarget || (event?.target as any); // while (td?.tagName !== 'TD') { // td = td?.parentNode as HTMLElement; // } // } this.currentTd = td as HTMLElement; if (this.findInputRequest){ cancelAnimationFrame(this.findInputRequest) this.findInputRequest = undefined } const nextDraw = () => { this.getOldValue(rowindex, colindex) let input: HTMLElement | null = null; let findCount = 0; this.findInputRequest = requestAnimationFrame(() => { findCount++; input = td?.querySelector?.('input') || this.table.value?.querySelector('table+textarea') as HTMLElement; // 最多查找10次, 10次后取消查找 if (findCount > 10) { cancelAnimationFrame(this.findInputRequest!) } if (input) { // console.log("找到输入控件!") // console.log('findCount', findCount) cancelAnimationFrame(this.findInputRequest!) input?.focus?.(); if(input === this.input) return; this.input = input; const cancelDefaultUpDown = () => { // 上下键 if ([38, 40].includes(event.keyCode)) { event?.stopPropagation(); event?.preventDefault(); document.dispatchEvent(new KeyboardEvent('keydown', event)); return false; } } this.input.addEventListener('keydown', cancelDefaultUpDown) // if( // !input || // input.dataset?.inputtype === 'select' || // input.classList?.contains('ant-select-selection-search-input') || // input.classList?.contains('ant-input-number-input') // ){ this.onFocus(rowindex, colindex) const stopwatch = watch(this.selectedState, () => { const oldValue = this.oldValue; const column = this.tileColumns.value[colindex]; const record = this.dataSource.value[rowindex]; if (typeof column.precision === 'number' || typeof column.precision === 'function') { const precision = typeof column.precision === 'function' ? column.precision({ column: column, record: record, rowindex: rowindex, }) : column.precision; this.setValueByModel( column.dataIndex, this.dataSource.value[rowindex], splitFillPrecision(this.getValueByModel(column.dataIndex, record), precision) ); } if(this.getValueByModel(column.dataIndex, record) !== oldValue) { this.onChange(rowindex, colindex) } this.onBlur(rowindex, colindex) stopwatch() }, { deep: true }) // } else if(input){ // this.margeInputEvents(rowindex, colindex) // } if(input){ this.margeInputEvents(rowindex, colindex) } } else { // console.log("没有找到输入控件") } }) } nextDraw(); // Promise.resolve().then(nextDraw) // return setTimeout(nextDraw, 0); } private isInputNumber(input: HTMLElement){ return input?.classList?.contains('ant-input-number-input') || input?.getAttribute('type') === 'number' } private margeInputEvents(rowindex: number, colindex: number){ const focusHandel = () => this.onFocus(rowindex, colindex); const changeHandel = () => this.onChange(rowindex, colindex); const blurHandel = () => this.onBlur(rowindex, colindex); const keydownHandel = () => this.onKeydown(rowindex, colindex); const removeEvents = () => { this.input?.removeEventListener('keydown', keydownHandel) this.input?.removeEventListener('change', changeHandel) this.input?.removeEventListener('blur', blurHandel) } removeEvents() this.input?.addEventListener('keydown', keydownHandel) this.input?.addEventListener('change', changeHandel) this.input?.addEventListener('blur', blurHandel) const stopWatcherEditStatus = watch(() => this.editState.status, (status) => { if(status === 'doing') focusHandel() }) const stopWatcher = watch( () => [this.selectedState.startX, this.selectedState.startY], () => { removeEvents() stopWatcher() stopWatcherEditStatus() }, { deep: true, }, ); } private getOldValue(rowindex: number, colindex: number){ const record = this.dataSource.value[rowindex] const column = this.tileColumns.value[colindex] this.oldValue = deepCopy(this.getValueByModel(column.dataIndex, record)) } private restEdit() { let node: any = event?.target; while (node && node !== document.body) { // 防止下拉,proup等误触发 if ( node === this.currentTd || Array.from(node?.classList).includes('ant-dropdown') ) return false; node = node.parentElement; } this.editState.editX = this.editState.editY = undefined; this.editState.status = undefined; } private onChange(rowindex: number, colindex: number){ if(this.props.openUndo) this.addHistoryRecord(); this.tryValidateCell?.(rowindex, colindex, 'change'); if(typeof this.props.customEditer?.onChange !== 'function') return const column = this.tileColumns.value[colindex]; const record = this.dataSource.value[rowindex]; this.props.customEditer?.onChange({ oldValue: this.oldValue, newValue: this.getValueByModel(column.dataIndex, record), column: this.tileColumns.value[colindex], record: this.dataSource.value[rowindex], rowindex: rowindex, }) } private onFocus(rowindex: number, colindex: number){ if(typeof this.props.customEditer?.onFocus !== 'function') return const column = this.tileColumns.value[colindex]; const record = this.dataSource.value[rowindex]; this.props.customEditer?.onFocus({ value: this.getValueByModel(column.dataIndex, record), column: this.tileColumns.value[colindex], record: this.dataSource.value[rowindex], rowindex: rowindex, }) } private onBlur(rowindex: number, colindex: number){ this.tryValidateCell?.(rowindex, colindex, 'blur'); if(typeof this.props.customEditer?.onBlur !== 'function') return const column = this.tileColumns.value[colindex]; const record = this.dataSource.value[rowindex]; this.props.customEditer?.onBlur({ oldValue: this.oldValue, newValue: this.getValueByModel(column.dataIndex, record), column: this.tileColumns.value[colindex], record: this.dataSource.value[rowindex], rowindex: rowindex, }) } private onKeydown(rowindex: number, colindex: number) { if (this.isInputNumber(this.input) && this.editState.status === 'doing' && (event?.key === 'ArrowUp' || event?.key === 'ArrowDown')) { event?.stopPropagation(); event?.preventDefault(); this.triggerKeyEvent('keydown', event?.keyCode) return false } } private triggerKeyEvent(type: 'keydown' | 'keyup', keyCode: 40 | 38) { const event = new KeyboardEvent(type, { keyCode: keyCode, which: keyCode, key: String.fromCharCode(keyCode), }); document.dispatchEvent(event); } }