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);
|
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'];
|
const FALLBACK_THEME_COLORS = ['#9b6bc2', '#7e95f7', '#4cc9f0', '#f4a261'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -859,7 +868,7 @@ Page({
|
|||||||
*/
|
*/
|
||||||
readAndApplyFile(filePath, fileName, onReadFailPrefix) {
|
readAndApplyFile(filePath, fileName, onReadFailPrefix) {
|
||||||
const that = this;
|
const that = this;
|
||||||
const extension = getFileExtension(fileName);
|
const extension = getFileExtension(fileName) || getFileExtension(getBaseNameFromPath(filePath));
|
||||||
const isCsvFile = extension === 'csv';
|
const isCsvFile = extension === 'csv';
|
||||||
const readOptions = {
|
const readOptions = {
|
||||||
filePath,
|
filePath,
|
||||||
@@ -960,8 +969,12 @@ Page({
|
|||||||
if (!picked) {
|
if (!picked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const filePath = picked.path;
|
const filePath = picked.path || picked.tempFilePath || '';
|
||||||
const fileName = picked.name || 'unknown.csv';
|
const fileName =
|
||||||
|
picked.name ||
|
||||||
|
getBaseNameFromPath(filePath) ||
|
||||||
|
// 无法识别文件名时,默认按二进制读取,交由解析器做内容识别。
|
||||||
|
'upload.bin';
|
||||||
that.readAndApplyFile(filePath, fileName, '读取文件失败');
|
that.readAndApplyFile(filePath, fileName, '读取文件失败');
|
||||||
},
|
},
|
||||||
fail(err) {
|
fail(err) {
|
||||||
|
|||||||
@@ -270,6 +270,34 @@ function parseXlsxBuffer(buffer) {
|
|||||||
return toRawTable(rows);
|
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')) {
|
if (lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls')) {
|
||||||
return parseXlsxBuffer(payload);
|
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 文件');
|
throw new Error('仅支持 .csv / .xlsx / .xls 文件');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,4 +87,20 @@ describe('miniapp utils sankey', () => {
|
|||||||
expect(table.headers).toEqual(['人數']);
|
expect(table.headers).toEqual(['人數']);
|
||||||
expect(table.rows).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