From 77e4c3d0201a83cf41b73d7bd7a79255759575f4 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期六, 17 十月 2026 11:19:23 +0800
Subject: [PATCH] fix(mdm): 修正计量单位类型选项和API文档
---
src/views/mes/pro/qrexport/use-qr-export.ts | 119 +++++++++++++++++++++++++++++++++++++++++++++++------------
1 files changed, 95 insertions(+), 24 deletions(-)
diff --git a/src/views/mes/pro/qrexport/use-qr-export.ts b/src/views/mes/pro/qrexport/use-qr-export.ts
index fe78856..61275b2 100644
--- a/src/views/mes/pro/qrexport/use-qr-export.ts
+++ b/src/views/mes/pro/qrexport/use-qr-export.ts
@@ -34,6 +34,8 @@
export interface QrExportState {
bizType?: MesProQrExportApi.BizType;
+ /** 涓嬭浇澶辫触鍚庯紝鏈嶅姟绔枃浠朵粛鍙敤鏃跺厑璁哥洿鎺ラ噸璇� */
+ downloadRetryable: boolean;
/** 宸蹭笅杞藉瓧鑺� */
downloadLoaded: number;
/** 浜х墿鎬诲瓧鑺傦紙鍙栬嚜 Content-Length锛屽彇涓嶅埌鏃朵负 0 琛ㄧず鏈煡锛� */
@@ -94,6 +96,7 @@
function createInitialState(): QrExportState {
return {
downloadLoaded: 0,
+ downloadRetryable: false,
downloadTotal: 0,
errorMsg: '',
finished: 0,
@@ -253,15 +256,55 @@
}
}
-/** 涓嬭浇浜х墿锛氫紭鍏堟祦寮忓啓鍏ョ鐩橈紝閬垮厤鏁扮櫨 MB 鐨勫唴瀹规暣鍧楅┗鐣欏唴瀛� */
+/** 姣忔鍙姹� 2 MB锛岄伩鍏嶄唬鐞嗗鍗曚釜澶ф枃浠跺搷搴旇秴鏃躲�� */
+const DOWNLOAD_CHUNK_SIZE = 2 * 1024 * 1024;
+
+async function fetchDownloadChunk(
+ url: string,
+ token: string | undefined,
+ start: number,
+ end: number,
+ total: number,
+ signal: AbortSignal,
+): Promise<Uint8Array> {
+ for (let attempt = 0; attempt < 3; attempt++) {
+ try {
+ const response = await fetch(url, {
+ headers: {
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
+ Range: `bytes=${start}-${end}`,
+ },
+ signal,
+ });
+ if (response.status !== 206) {
+ throw new Error(`鍒嗘涓嬭浇澶辫触锛圚TTP ${response.status}锛塦);
+ }
+ if (response.headers.get('content-range') !== `bytes ${start}-${end}/${total}`) {
+ throw new Error('涓嬭浇鍝嶅簲鐨勫瓧鑺傝寖鍥翠笉姝g‘');
+ }
+ const bytes = new Uint8Array(await response.arrayBuffer());
+ if (bytes.byteLength !== end - start + 1) {
+ throw new Error('涓嬭浇鍐呭涓嶅畬鏁�');
+ }
+ return bytes;
+ } catch (error) {
+ if (signal.aborted || attempt === 2) throw error;
+ await new Promise((resolve) => setTimeout(resolve, 750 * (attempt + 1)));
+ }
+ }
+ throw new Error('鍒嗘涓嬭浇澶辫触');
+}
+
+/** 涓嬭浇浜х墿锛氫紭鍏堝啓鍏ョ鐩橈紝姣忔鐙珛璇锋眰骞跺彲閲嶈瘯銆� */
async function downloadProduct(task: MesProQrExportApi.ExportTask) {
state.phase = 'downloading';
+ state.downloadRetryable = false;
state.message = '姝e湪涓嬭浇鈥�';
state.downloadLoaded = 0;
state.downloadTotal = task.fileSize ?? 0;
const fileName = `${task.taskName ?? '浜岀淮鐮佸鍑�'}.zip`;
const url = `${apiURL}${getQrExportDownloadPath(task.id)}`;
- const token = useAccessStore().accessToken;
+ const token = useAccessStore().accessToken ?? undefined;
const controller = new AbortController();
let canceled = false;
@@ -272,33 +315,41 @@
};
try {
- const response = await fetch(url, {
- headers: token ? { Authorization: `Bearer ${token}` } : {},
- signal: controller.signal,
- });
- if (!response.ok || !response.body) {
- throw new Error(`涓嬭浇澶辫触锛圚TTP ${response.status}锛塦);
- }
- const contentLength = Number(response.headers.get('content-length') ?? 0);
- if (contentLength > 0) {
- state.downloadTotal = contentLength;
- }
- const reader = response.body.getReader();
- downloadAbort = () => {
- canceled = true;
- void reader.cancel();
- };
const useDisk = Boolean(saveHandle);
if (useDisk) {
writable = await saveHandle.createWritable();
}
- // 杈规敹杈瑰啓锛氳繘搴︽潵鑷湡瀹炲瓧鑺傛暟锛涜兘鍐欑洏灏卞彧鍗犱竴涓垎鐗囩殑鍐呭瓨
const chunks: Uint8Array[] = [];
let loaded = 0;
- for (;;) {
- const { done, value } = await reader.read();
- if (done) break;
- if (value) {
+ if (Number.isSafeInteger(task.fileSize) && (task.fileSize ?? 0) > 0) {
+ const total = task.fileSize!;
+ for (let start = 0; start < total; start += DOWNLOAD_CHUNK_SIZE) {
+ const end = Math.min(start + DOWNLOAD_CHUNK_SIZE, total) - 1;
+ const value = await fetchDownloadChunk(url, token, start, end, total, controller.signal);
+ if (useDisk) {
+ await writable.write(value);
+ } else {
+ chunks.push(value);
+ }
+ loaded += value.length;
+ state.downloadLoaded = loaded;
+ }
+ } else {
+ // 鍏煎鏈褰曟枃浠跺ぇ灏忕殑鏃т换鍔°��
+ const response = await fetch(url, {
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
+ signal: controller.signal,
+ });
+ if (!response.ok || !response.body) {
+ throw new Error(`涓嬭浇澶辫触锛圚TTP ${response.status}锛塦);
+ }
+ const contentLength = Number(response.headers.get('content-length') ?? 0);
+ if (contentLength > 0) state.downloadTotal = contentLength;
+ const reader = response.body.getReader();
+ for (;;) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ if (!value) continue;
if (useDisk) {
await writable.write(value);
} else {
@@ -309,7 +360,6 @@
}
}
if (canceled) {
- if (useDisk) await writable.abort();
return;
}
if (useDisk) {
@@ -322,10 +372,30 @@
state.message = `宸插鍑� ${state.total} 寮燻;
} catch (error: any) {
if (canceled) return;
+ state.downloadRetryable = true;
failWith(error);
} finally {
+ if (writable && state.phase !== 'done') {
+ await writable.abort().catch(() => {});
+ }
downloadAbort = null;
saveHandle = null;
+ }
+}
+
+/** 閲嶆柊涓嬭浇宸茬敓鎴愮殑 ZIP锛屾棤闇�鍐嶆鐢熸垚浜岀淮鐮併�� */
+async function retryDownload() {
+ if (!state.downloadRetryable || !state.taskId || state.phase !== 'failed') return;
+ try {
+ // 淇濆瓨鏂囦欢閫夋嫨鍣ㄩ渶瑕佺敤鎴风偣鍑昏Е鍙戯紱鍏堝彇寰楀彞鏌勶紝鍐嶆煡璇换鍔$姸鎬併��
+ saveHandle = await tryPickSaveFile(`${state.taskName}.zip`);
+ const task = await getQrExportTask(state.taskId);
+ if (!task.downloadable) {
+ throw new Error('瀵煎嚭鏂囦欢宸茶繃鏈燂紝璇烽噸鏂扮敓鎴�');
+ }
+ await downloadProduct(task);
+ } catch (error: any) {
+ failWith(error);
}
}
@@ -380,6 +450,7 @@
isActive,
start,
cancel,
+ retryDownload,
minimize,
expand,
close,
--
Gitblit v1.9.3