账单导出
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import { getToken } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 导出文件下载(双端)
|
||||
* 后端导出接口成功返回 xlsx 文件流,业务失败时仍以文件流形式返回 JSON
|
||||
* (blob.type 为 application/json),两端都需探测并解析 msg 提示
|
||||
*/
|
||||
|
||||
/** 从 Content-Disposition 解析文件名(优先 RFC 5987 filename*=utf-8''...) */
|
||||
export function parseExportFilename(disposition?: string | null): string {
|
||||
if (!disposition) return ''
|
||||
const star = disposition.match(/filename\*=utf-8''([^;]+)/i)
|
||||
if (star?.[1]) {
|
||||
try {
|
||||
return decodeURIComponent(star[1])
|
||||
} catch {
|
||||
// 编码异常时退化为普通 filename
|
||||
}
|
||||
}
|
||||
const plain = disposition.match(/filename="?([^";]+)"?/i)
|
||||
return plain?.[1] ?? ''
|
||||
}
|
||||
|
||||
/** 解析 JSON 文本中的业务错误信息 */
|
||||
function parseJsonError(text: string): Error {
|
||||
try {
|
||||
const json = JSON.parse(text)
|
||||
return new Error(json.msg || '导出失败,请稍后重试')
|
||||
} catch {
|
||||
return new Error('导出失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
/** H5:fetch 带鉴权拉取 blob → <a download> 触发保存 */
|
||||
async function downloadFileH5(url: string, fallbackName: string): Promise<void> {
|
||||
const token = getToken()
|
||||
const res = await fetch(url, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`下载失败(${res.status})`)
|
||||
}
|
||||
const blob = await res.blob()
|
||||
|
||||
// 业务失败:返回的是 JSON blob
|
||||
if (blob.type.includes('application/json')) {
|
||||
throw parseJsonError(await blob.text())
|
||||
}
|
||||
|
||||
const filename =
|
||||
parseExportFilename(res.headers.get('Content-Disposition')) || fallbackName
|
||||
const objectUrl = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = objectUrl
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
}
|
||||
|
||||
/** 读取小程序本地文件内容(utf8) */
|
||||
function readTempFile(filePath: string, length?: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.getFileSystemManager().readFile({
|
||||
filePath,
|
||||
encoding: 'utf8',
|
||||
...(length !== undefined ? { position: 0, length } : {}),
|
||||
success: r => resolve(r.data as string),
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** 小程序:downloadFile 下载后 openDocument 打开;业务失败时下载"成功"但内容是 JSON */
|
||||
async function downloadFileWeapp(url: string): Promise<void> {
|
||||
const token = getToken()
|
||||
const { tempFilePath } = await Taro.downloadFile({
|
||||
url,
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
})
|
||||
|
||||
// 探测前 200 字节:以 '{' 开头即为 JSON 错误响应
|
||||
const head = await readTempFile(tempFilePath, 200)
|
||||
if (head.trimStart().startsWith('{')) {
|
||||
throw parseJsonError(await readTempFile(tempFilePath))
|
||||
}
|
||||
|
||||
await Taro.openDocument({
|
||||
filePath: tempFilePath,
|
||||
fileType: 'xlsx',
|
||||
showMenu: true,
|
||||
fail: () => {
|
||||
Taro.showToast({ title: '文件已下载,打开失败', icon: 'none' })
|
||||
},
|
||||
} as Parameters<typeof Taro.openDocument>[0])
|
||||
}
|
||||
|
||||
/**
|
||||
* 带鉴权下载导出文件
|
||||
* - H5:保存到浏览器下载目录,返回保存的文件名
|
||||
* - 小程序:直接调起系统文档预览(showMenu 支持转发/保存)
|
||||
*/
|
||||
export async function downloadExportFile(url: string, fallbackName: string): Promise<void> {
|
||||
if (process.env.TARO_ENV === 'h5') {
|
||||
return downloadFileH5(url, fallbackName)
|
||||
}
|
||||
return downloadFileWeapp(url)
|
||||
}
|
||||
Reference in New Issue
Block a user