|
|
@@ -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)
|