From 75d403cf442acb7ee7219b556494b37f0274d29a Mon Sep 17 00:00:00 2001 From: "douboer@gmail.com" Date: Fri, 13 Feb 2026 15:06:04 +0800 Subject: [PATCH] update at 2026-02-13 15:06:04 --- progress.txt | 1 + src/App.vue | 72 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/progress.txt b/progress.txt index a0d68f4..2d4b4d5 100644 --- a/progress.txt +++ b/progress.txt @@ -16,6 +16,7 @@ 8. 小程序端已完成视觉骨架(非完整业务)。 9. 已实现本地持久化:用户上传文件、映射配置与预览选项会写入 localStorage,刷新后自动恢复。 10. 已新增“汇聚对齐”配置(Between/Middle/Top/Bottom),可控制 target 侧对齐,且 gap 作为源侧基准。 +11. 已优化“无配置初始化映射”:优先按表头别名自动匹配,缺失时按第二行首个数字列兜底。 二、当前状态(In Progress) 1. 无进行中的代码重构任务。 diff --git a/src/App.vue b/src/App.vue index dbebda7..4df0daa 100644 --- a/src/App.vue +++ b/src/App.vue @@ -393,9 +393,9 @@ const uploadedFileSnapshot = ref(null); const isRestoringWorkspace = ref(false); const mapping = reactive({ - sourceDataColumn: 2, - sourceDescriptionColumns: [0], - targetDescriptionColumns: [2], + sourceDataColumn: null, + sourceDescriptionColumns: [], + targetDescriptionColumns: [], delimiter: '-' }); @@ -935,26 +935,46 @@ function findHeaderIndex(headers: string[], aliases: string[]): number { } /** - * 上传文件后按列名设置默认映射: - * - 源数据-数据列:value - * - 源数据-描述列:source - * - 目标数据-描述列:target - * 若未找到对应列名,回退到安全列索引。 + * 判断单元格文本是否可作为数值列候选(支持千分位)。 */ -function setDefaultMappingByHeaders(headers: string[]): void { - const safeSize = Math.max(headers.length, 1); - const safeColumn = (index: number): number => Math.min(index, safeSize - 1); - const sourceDataByName = findHeaderIndex(headers, ['value']); - const sourceDescByName = findHeaderIndex(headers, ['source']); - const targetDescByName = findHeaderIndex(headers, ['target']); +function isNumericCell(text: string): boolean { + const normalized = text.replace(/,/g, '').trim(); + if (!normalized) { + return false; + } + return !Number.isNaN(Number(normalized)); +} - mapping.sourceDataColumn = sourceDataByName >= 0 ? sourceDataByName : safeColumn(2); - mapping.sourceDescriptionColumns = [sourceDescByName >= 0 ? sourceDescByName : safeColumn(0)].filter( - (item, index, list) => list.indexOf(item) === index - ); - mapping.targetDescriptionColumns = [targetDescByName >= 0 ? targetDescByName : safeColumn(2)].filter( - (item, index, list) => list.indexOf(item) === index - ); +/** + * 从“第二行内容”(RawTable 第 1 条数据行)中找第一个数字列。 + * 找不到时返回 -1。 + */ +function findNumericColumnFromSecondRow(table: RawTable): number { + const secondRow = table.rows[0] ?? []; + return secondRow.findIndex((cell) => isNumericCell(cell ?? '')); +} + +/** + * 在“用户没有配置”的情况下,根据产品规则设置默认映射: + * 1) 源数据-数据列: + * - 优先表头 data/value/数据/值 + * - 否则取第二行内容中的第一个数字列 + * 2) 源数据-描述列: + * - 取表头 source/源,找不到则不选 + * 3) 目标数据-描述列: + * - 取表头 target/目标,找不到则不选 + */ +function setDefaultMappingByTable(table: RawTable): void { + const headers = table.headers; + const sourceDataByName = findHeaderIndex(headers, ['data', 'value', '数据', '值']); + const sourceDataBySecondRow = findNumericColumnFromSecondRow(table); + const sourceDescByName = findHeaderIndex(headers, ['source', '源']); + const targetDescByName = findHeaderIndex(headers, ['target', '目标']); + + mapping.sourceDataColumn = + sourceDataByName >= 0 ? sourceDataByName : sourceDataBySecondRow >= 0 ? sourceDataBySecondRow : null; + mapping.sourceDescriptionColumns = sourceDescByName >= 0 ? [sourceDescByName] : []; + mapping.targetDescriptionColumns = targetDescByName >= 0 ? [targetDescByName] : []; } /** @@ -1200,7 +1220,11 @@ async function restoreWorkspaceFromStorage(): Promise<{ const parsed = parseTableByFileName(workspace.uploadedFile.name, fileBuffer); rawTable.value = parsed; uploadedFileSnapshot.value = workspace.uploadedFile; - applyMappingConfig(sanitizePersistedMapping(workspace.mapping, parsed.headers.length)); + if (workspace.mapping) { + applyMappingConfig(sanitizePersistedMapping(workspace.mapping, parsed.headers.length)); + } else { + setDefaultMappingByTable(parsed); + } uploadMessage.value = `已恢复: ${workspace.uploadedFile.name}(${parsed.rows.length} 行)`; parseError.value = ''; return { restoredUploadedFile: true }; @@ -1223,7 +1247,7 @@ async function loadDataFile(file: File): Promise { const fileBuffer = await file.arrayBuffer(); const parsed = parseTableByFileName(file.name, fileBuffer); rawTable.value = parsed; - setDefaultMappingByHeaders(parsed.headers); + setDefaultMappingByTable(parsed); uploadedFileSnapshot.value = createUploadedFileSnapshot(file.name, file.type, fileBuffer); uploadMessage.value = `已加载: ${file.name}(${parsed.rows.length} 行)`; persistWorkspace(); @@ -1269,7 +1293,7 @@ async function loadDefaultExampleFile(): Promise { const buffer = await response.arrayBuffer(); const parsed = parseXlsxBuffer(buffer); rawTable.value = parsed; - setDefaultMappingByHeaders(parsed.headers); + setDefaultMappingByTable(parsed); uploadedFileSnapshot.value = null; uploadMessage.value = `已加载: example0.xlsx(${parsed.rows.length} 行)`; parseError.value = '';