file.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { Notify, platformIsAIO, Toast } from '@/platform/index';
  2. import { useShowScanCode } from '@/composables/useShowScanCode';
  3. export interface DownloadFromUrlOptions {
  4. /** 保存时的文件名;不传则从响应头或 URL 路径推断 */
  5. filename?: string;
  6. /** 传给 fetch 的额外配置(如 credentials、headers) */
  7. fetchInit?: RequestInit;
  8. }
  9. function filenameFromContentDisposition(header: string | null): string | undefined {
  10. if (!header) return;
  11. const utf8 = /filename\*=UTF-8''([^;\s]+)/i.exec(header);
  12. if (utf8?.[1]) {
  13. try {
  14. return decodeURIComponent(utf8[1].replace(/['"]/g, ''));
  15. } catch {
  16. return utf8[1];
  17. }
  18. }
  19. const plain = /filename\s*=\s*("?)([^";\n]+)\1/i.exec(header);
  20. return plain?.[2];
  21. }
  22. function filenameFromUrl(url: string): string {
  23. try {
  24. const path = new URL(url, typeof location !== 'undefined' ? location.href : undefined).pathname;
  25. const seg = path.split('/').filter(Boolean).pop();
  26. return seg || 'download';
  27. } catch {
  28. return 'download';
  29. }
  30. }
  31. /**
  32. * 根据文件 URL 下载到本地(通过 fetch 取 Blob 后触发浏览器保存)。
  33. * 跨域资源需服务端允许 CORS,否则 fetch 会失败。
  34. */
  35. export function downloadFromUrl(url: string, options?: DownloadFromUrlOptions): Promise<void> {
  36. return (async () => {
  37. const res = await fetch(url, options?.fetchInit);
  38. if (!res.ok) throw new Error(`下载失败: ${res.status} ${res.statusText}`);
  39. const blob = await res.blob();
  40. const filename = options?.filename ?? filenameFromContentDisposition(res.headers.get('content-disposition')) ?? filenameFromUrl(url);
  41. const objectUrl = URL.createObjectURL(blob);
  42. try {
  43. const a = document.createElement('a');
  44. a.href = objectUrl;
  45. a.download = filename;
  46. a.rel = 'noopener';
  47. document.body.appendChild(a);
  48. a.click();
  49. a.remove();
  50. } finally {
  51. URL.revokeObjectURL(objectUrl);
  52. }
  53. })();
  54. }
  55. export async function printFromUrl(url: string, options?: { rollback?: boolean; title?: string }) {
  56. let closed = false;
  57. if (platformIsAIO()) {
  58. try {
  59. try {
  60. await Bridge.print({ url });
  61. } catch {
  62. window.AIO?.print?.(url);
  63. }
  64. } catch (e) {
  65. Notify.warning(`打印失败 (${e.message})`, { duration: 1500 });
  66. closed = true;
  67. }
  68. } else {
  69. try {
  70. const current = window.open(url, '_blank');
  71. closed = current.closed;
  72. current.location.href;
  73. } catch (e) {
  74. Notify.warning(`无法打开窗口 (${e.message})`, { duration: 1500 });
  75. closed = true;
  76. }
  77. }
  78. if (closed && options?.rollback) {
  79. Toast.close();
  80. await useShowScanCode().open({ url, title: options?.title });
  81. }
  82. }