update at 2026-02-14 11:16:40
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 文件');
|
||||
}
|
||||
|
||||
|
||||
@@ -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']]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user