update at 2026-02-14 11:16:40

This commit is contained in:
douboer@gmail.com
2026-02-14 11:16:40 +08:00
parent 78896060b2
commit 3d9558508a
3 changed files with 67 additions and 3 deletions

View File

@@ -66,6 +66,15 @@ function getFileExtension(fileName) {
return lowerName.slice(lastDotIndex + 1);
}
/**
* 从路径提取文件名,兼容 Unix/Windows 路径分隔符。
*/
function getBaseNameFromPath(filePath) {
const normalized = String(filePath || '').replace(/\\/g, '/');
const segments = normalized.split('/');
return segments[segments.length - 1] || '';
}
const FALLBACK_THEME_COLORS = ['#9b6bc2', '#7e95f7', '#4cc9f0', '#f4a261'];
/**
@@ -859,7 +868,7 @@ Page({
*/
readAndApplyFile(filePath, fileName, onReadFailPrefix) {
const that = this;
const extension = getFileExtension(fileName);
const extension = getFileExtension(fileName) || getFileExtension(getBaseNameFromPath(filePath));
const isCsvFile = extension === 'csv';
const readOptions = {
filePath,
@@ -960,8 +969,12 @@ Page({
if (!picked) {
return;
}
const filePath = picked.path;
const fileName = picked.name || 'unknown.csv';
const filePath = picked.path || picked.tempFilePath || '';
const fileName =
picked.name ||
getBaseNameFromPath(filePath) ||
// 无法识别文件名时,默认按二进制读取,交由解析器做内容识别。
'upload.bin';
that.readAndApplyFile(filePath, fileName, '读取文件失败');
},
fail(err) {

View File

@@ -270,6 +270,34 @@ function parseXlsxBuffer(buffer) {
return toRawTable(rows);
}
/**
* 判断二进制是否为 Zip 容器xlsx魔数50 4B。
*/
function isZipMagic(bufferLike) {
if (!bufferLike || typeof bufferLike.byteLength !== 'number' || bufferLike.byteLength < 2) {
return false;
}
const bytes = new Uint8Array(bufferLike, 0, 2);
return bytes[0] === 0x50 && bytes[1] === 0x4b;
}
/**
* 判断二进制是否为 OLE 容器(老 xls魔数D0 CF 11 E0 A1 B1 1A E1。
*/
function isOleMagic(bufferLike) {
if (!bufferLike || typeof bufferLike.byteLength !== 'number' || bufferLike.byteLength < 8) {
return false;
}
const bytes = new Uint8Array(bufferLike, 0, 8);
const signature = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];
for (let i = 0; i < signature.length; i += 1) {
if (bytes[i] !== signature[i]) {
return false;
}
}
return true;
}
/**
* 按文件名后缀自动分流解析器。
*/
@@ -281,6 +309,13 @@ function parseTableByFileName(fileName, payload) {
if (lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls')) {
return parseXlsxBuffer(payload);
}
// 兜底:后缀不可用时,通过文件魔数自动识别 Excel。
if (payload && typeof payload === 'object' && typeof payload.byteLength === 'number') {
if (isZipMagic(payload) || isOleMagic(payload)) {
return parseXlsxBuffer(payload);
}
}
throw new Error('仅支持 .csv / .xlsx / .xls 文件');
}

View File

@@ -87,4 +87,20 @@ describe('miniapp utils sankey', () => {
expect(table.headers).toEqual(['人數']);
expect(table.rows).toEqual([['张三']]);
});
it('文件名后缀缺失时,仍可根据二进制魔数识别 xlsx', () => {
const sheet = XLSX.utils.aoa_to_sheet([
['source', 'target', 'value'],
['A', 'B', 1]
]);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, sheet, 'S1');
const buffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' });
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
const table = parseTableByFileName('upload.bin', arrayBuffer);
expect(table.headers).toEqual(['source', 'target', 'value']);
expect(table.rows).toEqual([['A', 'B', '1']]);
});
});