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/api/mdm/unit/index.ts                           |    2 
 src/views/mes/pro/qrexport/qr-export-progress.vue   |   10 +++
 src/views/basicData/mdm/unit/data.ts                |    6 +-
 src/api/mes/pro/bagCode/index.ts                    |    6 +-
 src/views/mes/pro/bagCode/modules/generate-form.vue |   18 +++---
 src/views/mes/pro/qrexport/use-qr-export.ts         |  119 +++++++++++++++++++++++++++++++--------
 6 files changed, 120 insertions(+), 41 deletions(-)

diff --git a/src/api/mdm/unit/index.ts b/src/api/mdm/unit/index.ts
index 084cdee..2135f01 100644
--- a/src/api/mdm/unit/index.ts
+++ b/src/api/mdm/unit/index.ts
@@ -8,7 +8,7 @@
     id?: number; // 鍗曚綅缂栧彿
     code: string; // 鍗曚綅缂栫爜
     name: string; // 鍗曚綅鍚嶇О
-    type?: string; // 绫诲瀷锛�48鍗冨厠/100鍗冨厠/鍚級
+    type?: string; // 绫诲瀷锛�48琚�/100琚�/鍚級
     primaryFlag?: boolean; // 鏄惁涓诲崟浣�
     primaryId?: number; // 涓诲崟浣嶇紪鍙�
     changeRate?: number; // 涓庝富鍗曚綅鎹㈢畻姣斾緥
diff --git a/src/api/mes/pro/bagCode/index.ts b/src/api/mes/pro/bagCode/index.ts
index 57f2d3b..7ea1122 100644
--- a/src/api/mes/pro/bagCode/index.ts
+++ b/src/api/mes/pro/bagCode/index.ts
@@ -41,17 +41,17 @@
     createTime?: string[];
   }
 
-  /** 鐢熸垚鏁版嵁鍖呰姹傦紙鍝佺涓昏閲忓崟浣嶇被鍨嬩负 N鍗冨厠 鏃舵寜鎵樼洏鏁版崲绠楄鏁帮紱涓� 鍚� 鏃舵棤鎵樼洏銆佺洿鎺ヨ緭鍏ヨ鐮佹暟锛� */
+  /** 鐢熸垚鏁版嵁鍖呰姹傦紙鍝佺涓昏閲忓崟浣嶇被鍨嬩负 N琚� 鏃舵寜鎵樼洏鏁版崲绠楄鏁帮紱涓� 鍚� 鏃舵棤鎵樼洏銆佺洿鎺ヨ緭鍏ヨ鐮佹暟锛� */
   export interface GenerateParams {
     itemId: number; // 鍝佺锛堢墿鏂欙級缂栧彿
-    palletCount?: number; // 鎵樼洏鏁帮紙48鍗冨厠/100鍗冨厠 鍝佺蹇呭~锛�
+    palletCount?: number; // 鎵樼洏鏁帮紙48琚�/100琚� 鍝佺蹇呭~锛�
     bagCount?: number; // 琚嬬爜鏁帮紙鍚ㄥ搧绉嶅繀濉級
     remark?: string; // 澶囨敞
   }
 
   export interface GenerateResult {
     uuid?: string; // 鐢熸垚缁� UUID
-    type?: string; // 鍝佺涓昏閲忓崟浣嶇被鍨嬶紙48鍗冨厠/100鍗冨厠/鍚級
+    type?: string; // 鍝佺涓昏閲忓崟浣嶇被鍨嬶紙48琚�/100琚�/鍚級
     perPallet?: number; // 姣忔墭琚嬫暟锛堝惃鍝佺涓� 0锛�
     totalCount?: number; // 鐢熸垚琚嬬爜鎬绘暟
     palletIds?: number[]; // 鑷姩鐢熸垚鐨勬墭鐩樼紪鍙峰垪琛紙鍚ㄥ搧绉嶄负绌猴級
diff --git a/src/views/basicData/mdm/unit/data.ts b/src/views/basicData/mdm/unit/data.ts
index aed8206..0712e11 100644
--- a/src/views/basicData/mdm/unit/data.ts
+++ b/src/views/basicData/mdm/unit/data.ts
@@ -8,8 +8,8 @@
 
 /** 璁¢噺鍗曚綅绫诲瀷閫夐」 */
 export const UNIT_TYPE_OPTIONS = [
-  { label: '48鍗冨厠', value: '48鍗冨厠' },
-  { label: '100鍗冨厠', value: '100鍗冨厠' },
+  { label: '48琚�', value: '48琚�' },
+  { label: '100琚�', value: '100琚�' },
   { label: '鍚�', value: '鍚�' },
 ];
 
@@ -152,4 +152,4 @@
       slots: { default: 'actions' },
     },
   ];
-}
\ No newline at end of file
+}
diff --git a/src/views/mes/pro/bagCode/modules/generate-form.vue b/src/views/mes/pro/bagCode/modules/generate-form.vue
index 46ba37e..a9bf722 100644
--- a/src/views/mes/pro/bagCode/modules/generate-form.vue
+++ b/src/views/mes/pro/bagCode/modules/generate-form.vue
@@ -18,9 +18,9 @@
 
   const emit = defineEmits(["success"]);
 
-  // 鎵�閫夊搧绉嶄富璁¢噺鍗曚綅绫诲瀷锛�48鍗冨厠/100鍗冨厠 -> 姣忔墭 N 琚嬶紱鍚� -> 鏃犳墭鐩樼洿鎺ヨ緭琚嬬爜鏁�
+  // 鎵�閫夊搧绉嶄富璁¢噺鍗曚綅绫诲瀷锛�48琚�/100琚� -> 姣忔墭 N 琚嬶紱鍚� -> 鏃犳墭鐩樼洿鎺ヨ緭琚嬬爜鏁�
   const unitType = ref("");
-  const kgPerPallet = ref<number | null>(null);
+  const bagsPerPallet = ref<number | null>(null);
   const isTon = ref(false);
   const unitTypeError = ref(false);
   const palletCountVal = ref<number | null>(null);
@@ -29,7 +29,7 @@
   /** 鎹㈢畻/閰嶇疆鎻愮ず鏂囨 */
   const tipText = computed(() => {
     if (unitTypeError.value) {
-      return "璇ュ搧绉嶄富璁¢噺鍗曚綅鏈厤缃被鍨嬶紙浠呮敮鎸� 48鍗冨厠/100鍗冨厠/鍚級锛岃鍏堝墠寰�銆岃閲忓崟浣嶃�嶇淮鎶ゅ悗閲嶈瘯";
+      return "璇ュ搧绉嶄富璁¢噺鍗曚綅鏈厤缃被鍨嬶紙浠呮敮鎸� 48琚�/100琚�/鍚級锛岃鍏堝墠寰�銆岃閲忓崟浣嶃�嶇淮鎶ゅ悗閲嶈瘯";
     }
     if (!unitType.value) {
       return "";
@@ -39,7 +39,7 @@
         bagCountVal.value ? `锛屽皢鐢熸垚 ${bagCountVal.value} 涓鐮乣 : ""
       }`;
     }
-    const per = kgPerPallet.value ?? 0;
+    const per = bagsPerPallet.value ?? 0;
     const count = palletCountVal.value ?? 0;
     return `璁¢噺鍗曚綅绫诲瀷涓恒��${unitType.value}銆嶏細姣忔墭 ${per} 琚�${
       count > 0
@@ -61,7 +61,7 @@
         rules: "selectRequired",
       },
     ];
-    if (kgPerPallet.value) {
+    if (bagsPerPallet.value) {
       schema.push({
         fieldName: "palletCount",
         label: "鎵樼洏鏁�",
@@ -110,7 +110,7 @@
   /** 閫夋嫨鍝佺鍚庤鍙栦富璁¢噺鍗曚綅绫诲瀷锛屽姩鎬佸垏鎹㈡墭鐩樻暟/琚嬬爜鏁拌緭鍏� */
   async function handleItemChange(item?: MdmItemApi.Item) {
     unitType.value = "";
-    kgPerPallet.value = null;
+    bagsPerPallet.value = null;
     isTon.value = false;
     unitTypeError.value = false;
     palletCountVal.value = null;
@@ -123,9 +123,9 @@
         if (type === "鍚�") {
           isTon.value = true;
         } else {
-          const matcher = /^(\d{1,5})鍗冨厠$/.exec(type);
+          const matcher = /^(\d{1,5})琚�$/.exec(type);
           if (matcher) {
-            kgPerPallet.value = Number(matcher[1]);
+            bagsPerPallet.value = Number(matcher[1]);
           } else {
             unitTypeError.value = true;
           }
@@ -190,7 +190,7 @@
       modalApi.lock();
       try {
         unitType.value = "";
-        kgPerPallet.value = null;
+        bagsPerPallet.value = null;
         isTon.value = false;
         unitTypeError.value = false;
         palletCountVal.value = null;
diff --git a/src/views/mes/pro/qrexport/qr-export-progress.vue b/src/views/mes/pro/qrexport/qr-export-progress.vue
index 3d9281d..5edbe3f 100644
--- a/src/views/mes/pro/qrexport/qr-export-progress.vue
+++ b/src/views/mes/pro/qrexport/qr-export-progress.vue
@@ -7,7 +7,7 @@
 
   defineOptions({ name: 'QrExportProgress' });
 
-  const { state, cancel, minimize, expand, close } = useQrExport();
+  const { state, cancel, retryDownload, minimize, expand, close } = useQrExport();
 
   /** 闈㈡澘鏄惁灞曠ず锛坕dle 琛ㄧず娌℃湁浠讳綍浠诲姟锛� */
   const visible = computed(() => state.phase !== 'idle');
@@ -143,6 +143,14 @@
         {{ title }}
       </span>
       <div class="flex items-center gap-1">
+        <Button
+          v-if="state.phase === 'failed' && state.downloadRetryable"
+          type="link"
+          size="small"
+          @click="retryDownload"
+        >
+          閲嶈瘯涓嬭浇
+        </Button>
         <!-- 杩涜涓彁渚涖�岄殣钘忋�嶏紝浠诲姟杞叆鍚庡彴缁х画锛屼笉浼氳涓柇 -->
         <Button
           v-if="state.phase !== 'done' && state.phase !== 'failed' && state.phase !== 'canceled'"
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