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