import { type Ref, shallowRef, ref, computed, watch, toValue, type ComputedRef, toRaw, toRef } from 'vue'; import { Options, Column, DataSource, Props, Record, FastKeyboard, Menu, DEFAULT_MENU } from './type'; import { message } from 'ant-design-vue'; import { deepCopy, getValueByModel, setValueByModel } from "./util" import { v4 } from 'uuid' import zh from "./../lang/zh_CN.json" import en from "./../lang/en_US.json" import ru from "./../lang/ru_RU.json" export default class { table: Ref = shallowRef(); columns: Ref = ref([]); dataSource: Ref = ref([]); props: Props; emits: any; checkNoRepaeatRowKeyTimer: any; columnGroup: Ref<(Column & { colspan?: number, colindex: number })[][]> = ref([]); tileColumns: Ref<(Column)[]> = ref([]); // 子表行、空数据提示的colspan长度 allTileColumnsLength?: ComputedRef; filtersValue?: any = ref({}) constructor(options: Options){ Object.assign(this, options); //检测数据是否重复,会消耗性能,非必要场景,不建议开启检测 if (options.props.checkNoRepaeatRowKey || location.href.includes('checkNoRepaeatRowKey')){ watch(() => options.dataSource.value.length, (length: number) => { this.checkNoRepaeatRowKey(length); }) } this.columnGroup = this.getTheadGroup(); this.tileColumns = this.getTileColumns(); this.allTileColumnsLength = this.getAllTileColumnsLegth() // 筛选同步有问题, 需要优化 TODO /* if (this.columns.value.find(item => item.children?.length)) { this.columnGroup = this.getTheadGroup() this.tileColumns = this.getTileColumns() this.allTileColumnsLength = this.getAllTileColumnsLegth() } else { this.columnGroup = toRef(() => { const columns = this.columns.value.map((item: any, index) => { item.colindex = index; return item; }) return [columns] }); this.tileColumns = toRef(this.columns); this.allTileColumnsLength = this.getAllTileColumnsLegth(); } */ // console.log(this.tileColumns) } warningMsg(msg: string){ message.warning(msg) this.emits('warning', msg) } getValueByModel = getValueByModel setValueByModel = setValueByModel getMessage(str: string): ComputedRef{ return computed(() => { let data = this.props.messageConfig if (!data) { switch(this.props.lang){ case 'en_US' : data = en; break; case 'ru_RU' : data = ru; break; default : data = zh; } } return getValueByModel(str.split('.'), data!) ?? '' }) } checkHasReadonly(records: Record[]){ if (this.props.readonly === true) return true; if (Array.isArray(this.props.readonly)) return !!this.props.readonly.find(rowKey => !!records?.find?.((row : Record) => row[this.props.rowKey] === rowKey)) } checkHasDisable(records: Record[]){ if (this.props.disabled === true) return true; if (Array.isArray(this.props.disabled)) return !!this.props.disabled.find(rowKey => !!records?.find?.((row : Record) => row[this.props.rowKey] === rowKey)) } checkHasSelectThead(){ return this.selectedState?.selectedTh } checkNoRepaeatRowKey(length: number){ clearTimeout(this.checkNoRepaeatRowKeyTimer) this.checkNoRepaeatRowKeyTimer = null; this.checkNoRepaeatRowKeyTimer = setTimeout(() => { const keys = Array.from(new Set( toValue(this.props.dataSource)?.map(row => row[this.props.rowKey]))) if (keys.length !== length) { const sameKeys: string[] = [] const repeatData: { [key: string] : number[]} = {} toValue(this.props.dataSource)?.forEach((row, index) => { const currentKey = row[this.props.rowKey] if (keys[index] !== currentKey) { sameKeys.push(currentKey); keys.splice(index, 0, currentKey); // 记录重复的数据源 if (!repeatData[currentKey]) { repeatData[currentKey] = [ toValue(this.props.dataSource)?.findIndex(row => row[this.props.rowKey] === currentKey) ] } repeatData[currentKey].push(index) } }) console.error('表格数据重复的数据源key 及 索引index:', repeatData); throw `表格数据检测出重复的 ${this.props.rowKey} 数据 ${sameKeys.join('、')},为了渲染不会出错,key值必须唯一, 请检查数据.` } }, 0) } getCellElement({rowindex, colindex}: { rowindex?: number, colindex?: number, }): HTMLElement | undefined | null { if (rowindex === undefined && colindex === undefined) return undefined; if (rowindex !== undefined && colindex !== undefined) { if (rowindex < 0) { return this.table.value?.querySelector(`th[data-rowindex='${rowindex}'][data-colindex='${colindex}']`) } else { return this.table.value?.querySelector(`td[data-rowindex='${rowindex}'][data-colindex='${colindex}']`) } } // if (rowindex === undefined || rowindex < 0) { // return this.table.value?.querySelector(`th[data-colindex='${colindex}']`) // } if (colindex === undefined) { return this.table.value?.querySelector(`tr[data-rowindex='${rowindex}']`) } return undefined; } /** * 检测快捷键功能是否开启 * @param action FastKeyboard快捷功能标识 * @returns boolean */ checkFastKeyboard(action: FastKeyboard) { // 开放所有复制快捷操作 if ( action === FastKeyboard.Ctrl_C ) return true; if (this.props.fastKeyboard) { return this.props.fastKeyboard?.includes(action) } const hasMenu = () => { const menuList = toValue(this.menuList).filter((item: Menu) => item._disabled !== true).filter?.((item: Menu) => this.visableMenu?.(item) ?? true); switch (action) { // case FastKeyboard.Ctrl_C : return !!menuList?.find((item: Menu) => item.key === DEFAULT_MENU.COPY) case FastKeyboard.Ctrl_V : return !!menuList?.find((item: Menu) => item.key === DEFAULT_MENU.PAETS) case FastKeyboard.Ctrl_Z : return !!menuList?.find((item: Menu) => item.key === DEFAULT_MENU.UNDO) case FastKeyboard.Ctrl_Y : return !!menuList?.find((item: Menu) => item.key === DEFAULT_MENU.REDO) case FastKeyboard.Delete : return !!menuList?.find((item: Menu) => item.key === DEFAULT_MENU.DELETE_ROWS) default : return false; } } return !!hasMenu(); } getTileColumns() { return toRef(() => { const columns: Column[] = []; this.columns.value.forEach(item => { const loopdeep = (column: Column) => { if (column.children?.length) { for(const item of column.children) { loopdeep(item) } } else { columns.push(column) } } loopdeep(item) }) return columns; }); } getTheadGroup(){ return toRef(() => { const group: any[] = []; // 检测添加列唯一key function checkKey(column: Column){ if (column.children) { for(const col of column.children) { checkKey(col); } } if (column.sortKey === undefined) { column.sortKey = v4(); } } // 跨列结算 function computedColspan(column: Column) : number { let colspan = 0; if (column.children) { for(const col of column.children){ colspan += computedColspan(col) || 1; } } return colspan; } // 计算深度值 function getChildrenDeep(columns: Column[]){ const deeps:number[] = [] columns.forEach((item, index) => { let i = 1; function loopdeep(col: Column){ if (col.children) { i++; for(const item of col.children) { loopdeep(item) } } else { deeps[index] = i } } loopdeep(item) }) return Math.max(...deeps) } const maxRowNumber = getChildrenDeep(this.columns.value); // 跨行计算 const computedRowspan = (column: Column) : number => { let rowspan = maxRowNumber; function loopdeep(column: Column){ if (column.children) { rowspan--; for(const col of column.children){ loopdeep(col); } } } loopdeep(column) return rowspan; } // 列总长度计算(含跨列) function getColumnsLength() { let length = 0 const first = group[0]; for (const col of first) { length += (col.colspan || 1) } return length; } // 记录继续访问的对象和索引 let visted = new Map(this.columns.value.map((c, i) => [c, i])); // 行索引 let rowindex = 0; function deep (){ group[rowindex] = new Array(rowindex > 0 ? getColumnsLength() : 0).fill(undefined) // debugger const newVisted = new Map(); let offsetIndex = 0; for(const column of visted.keys()) { const index = visted.get(column) as number; let colspan = computedColspan(column); let rowspan = computedRowspan(column); checkKey(column); group[rowindex][index] = Object.assign({ rowspan: rowspan < 2 ? undefined : rowspan, colspan: colspan < 2 ? undefined : colspan, colindex: index + offsetIndex, }, column) if (column?.children){ column.children?.forEach((col: Column, i: number) => { newVisted.set(col, index + i + offsetIndex) }) offsetIndex += (colspan - 1) } } visted.clear(); visted = newVisted; if (visted.size > 0) { rowindex++ deep(); } } deep(); return group; }) } getAllTileColumnsLegth(){ return computed(() => toValue(this.tileColumns).length + (this.props.showRowIndex ? 1 : 0) + (this.props.rowSelection ? 1 : 0) + (this.props.expandedRowKeys ? 1 : 0)); } setFilteredValue(value: any, column: Column){ const index = this.tileColumns.value.findIndex(item => item.sortKey === column.sortKey) if ([undefined, null].includes(value) || !value?.length){ this.tileColumns.value[index].filteredValue = undefined; } else { this.tileColumns.value[index].filteredValue = value; } this.emitChange('filter'); } emitChange(action: 'sort' | 'paginate' | 'filter'){ // debugger const pagination = deepCopy(this.props.pagination); const filterColumns = this.tileColumns.value.filter(item => item.filteredValue); const filters: {[key: string]: any} = {}; filterColumns?.forEach((item: Column) => { if (item.dataIndex && typeof item.dataIndex === 'string') { filters[item.dataIndex] = deepCopy(item.filteredValue); } else if (Array.isArray(item.dataIndex)) { const key = item.dataIndex[0]; const data = filters[key] ?? {}; this.setValueByModel(item.dataIndex.slice(1), data, item.filteredValue) filters[key] = deepCopy(data) } }); const sorterColumn = this.tileColumns.value.find(item => item.sortOrder); const sorter = { column: sorterColumn, columnKey: sorterColumn?.key, field: sorterColumn?.dataIndex, order: sorterColumn?.sortOrder }; this.emits('change', pagination, filters, sorter, { action, currentDataSource: this.dataSource.value }) } }