import { closeToast, showLoadingToast, showToast } from 'vant' import { detectEnvironment } from './environment' import { getAttachmentDisplayName } from './fileType' import { mobileUtil } from './mobileUtil' import { resolveResourceUrl } from './resourceUrl' export interface AttachmentDownloadSource { url?: string f_downloadpath?: string real_name?: string name?: string } /** * 解析附件下载地址: * - /resource 开头:保持相对路径,由 nginx 转发,不拼接 origin * - http/https 开头:直接使用 * - 其他情况:走 resolveResourceUrl 正常处理 */ function getDownloadUrl(file: AttachmentDownloadSource): string { const raw = file.url || file.f_downloadpath || '' if (!raw) return '' if (raw.startsWith('/resource/') || /^https?:\/\//i.test(raw)) return raw return resolveResourceUrl(raw) } /** * 浏览器环境:fetch 文件内容后用隐藏 标签触发保存 * WebView 环境此方法无效(WebView 没有下载管理器) */ function triggerBlobDownload(blob: Blob, filename: string) { const blobUrl = URL.createObjectURL(blob) const link = document.createElement('a') link.href = blobUrl link.download = filename link.style.display = 'none' document.body.appendChild(link) link.click() document.body.removeChild(link) setTimeout(() => URL.revokeObjectURL(blobUrl), 1000) } /** * 用其他应用打开文件(方式二:传 url+name,Flutter 端自动下载后调起系统分享面板) * 非 http/https 路径由 Flutter 端拼接 AppConfig.webUrl */ export function openFileWithApp(url: string, name: string): void { mobileUtil.execute({ funcName: 'openFileWithApp', param: { url, name }, callbackFunc: (result: any) => { console.log('>>>> openFileWithApp: result: ', JSON.stringify(result)) }, }) } /** * 下载附件,自动区分 Flutter WebView 和普通浏览器: * - Flutter WebView:通过 mobileUtil 调用原生 downloadFile channel * - 普通浏览器:fetch + blob 触发浏览器保存对话框 */ export async function downloadAttachment(file: AttachmentDownloadSource): Promise { const url = getDownloadUrl(file) const filename = getAttachmentDisplayName(file) if (!url) { showToast('附件地址无效') return } // Flutter WebView 环境:交给原生层下载 if (detectEnvironment().isApp) { const toast = showLoadingToast({ message: '下载中...', forbidClick: true, duration: 0 }) try { await new Promise((resolve, reject) => { mobileUtil.execute({ funcName: 'downloadFile', param: { url, name: filename }, callbackFunc: (result: any) => { result?.status === 'error' ? reject(new Error(result.msg || '下载失败')) : resolve() }, }) }) showToast('下载成功') } catch (e: any) { showToast(e?.message || '下载失败') } finally { closeToast() } return } // 普通浏览器环境:fetch + blob const toast = showLoadingToast({ message: '下载中...', forbidClick: true, duration: 0 }) try { const response = await fetch(url, { credentials: 'include' }) if (!response.ok) throw new Error(`下载失败(${response.status})`) const blob = await response.blob() triggerBlobDownload(blob, filename) showToast('下载成功') } catch { window.open(url, '_blank') showToast('已打开文件,可在浏览器中保存到本地') } finally { closeToast() } }