Browse Source

fix(订单表单): 修复下载文件中文名乱码问题

yz 2 weeks ago
parent
commit
511faa0fd8
1 changed files with 68 additions and 7 deletions
  1. 68 7
      src/components/order-form/material-detail-mixin.js

+ 68 - 7
src/components/order-form/material-detail-mixin.js

@@ -37,6 +37,73 @@ import {
   NUMBER_TYPES
 } from './number-format-utils'
 
+/**
+ * 将 Content-Disposition 中的 filename 进行容错解码,修复常见中文文件名乱码
+ * @param {string|undefined|null} disposition
+ * @param {string} fallback
+ * @returns {string}
+ */
+function resolveDownloadFilename(disposition, fallback) {
+  if (!disposition) return fallback
+  try {
+    const starMatch = /filename\*=([^']*)''([^;\n]+)/i.exec(disposition)
+    if (starMatch && starMatch[2]) {
+      return decodeURIComponent(starMatch[2])
+    }
+
+    const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
+    if (plainMatch && plainMatch[1]) {
+      const raw = String(plainMatch[1])
+      const urlDecoded = tryDecodeURIComponent(raw)
+      return maybeDecodeLatin1ToUtf8(urlDecoded)
+    }
+  } catch (e) {
+    // ignore
+  }
+  return fallback
+}
+
+/**
+ * @param {string} value
+ * @returns {string}
+ */
+function tryDecodeURIComponent(value) {
+  try {
+    return decodeURIComponent(value.replace(/\+/g, '%20'))
+  } catch (e) {
+    return value
+  }
+}
+
+/**
+ * 常见场景:后端直接输出 UTF-8 字节到 header,浏览器按 latin1 解码,导致文件名变成“销售...”
+ * @param {string} value
+ * @returns {string}
+ */
+function maybeDecodeLatin1ToUtf8(value) {
+  if (!value) return value
+
+  const chars = Array.from(value)
+  const isLatin1 = chars.every(ch => ch.charCodeAt(0) <= 0xFF)
+  if (!isLatin1) return value
+
+  try {
+    if (typeof TextDecoder === 'function') {
+      const bytes = Uint8Array.from(chars.map(ch => ch.charCodeAt(0)))
+      return new TextDecoder('utf-8').decode(bytes)
+    }
+  } catch (e) {
+    // ignore
+  }
+
+  try {
+    // 兼容没有 TextDecoder 的环境
+    return decodeURIComponent(escape(value))
+  } catch (e) {
+    return value
+  }
+}
+
 
 
 // 使用本地 PaginationConfig 作为分页数据结构描述
@@ -608,13 +675,7 @@ export default {
         const res = await downloadSalesOrderTemplate()
 
         const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition']
-        let filename = '销售订单模板.xlsx'
-        if (disposition) {
-          const utf8Match = /filename\*=UTF-8''([^;\n]+)/i.exec(disposition)
-          const plainMatch = /filename="?([^;\n"]+)"?/i.exec(disposition)
-          const raw = utf8Match ? decodeURIComponent(utf8Match[1]) : (plainMatch ? plainMatch[1] : '')
-          if (raw) filename = raw
-        }
+        const filename = resolveDownloadFilename(disposition, '销售订单模板.xlsx')
 
         const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
         const url = window.URL.createObjectURL(blob)