feat: filename-aware attachment download endpoint (Content-Disposition), preview download uses it (no popup flash)

This commit is contained in:
root
2026-10-17 11:07:00 +08:00
parent 6d686c1d69
commit a0aa77dfda
15 changed files with 120 additions and 99 deletions
+10 -22
View File
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { Download, Loader2 } from 'lucide-react';
import { Button, Dialog, useToast } from '@/components/ui';
import { readFull } from '@/lib/att-cache';
import { attachmentDownload } from '@/lib/api';
import type { Attachment } from '@/lib/types';
export type AttKind = 'image' | 'pdf' | 'text' | 'audio' | 'video' | 'docx' | 'xlsx' | 'other';
@@ -23,14 +24,6 @@ function b64ToUint8(dataB64: string): Uint8Array<ArrayBuffer> {
return Uint8Array.from(atob(dataB64), c => c.charCodeAt(0)) as Uint8Array<ArrayBuffer>;
}
function forceDownload(fileName: string, mime: string | undefined | null, dataB64: string) {
const url = URL.createObjectURL(new Blob([b64ToUint8(dataB64)], { type: mime || 'application/octet-stream' }));
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
setTimeout(() => URL.revokeObjectURL(url), 60_000);
}
const KIND_LABEL: Record<AttKind, string> = {
image: '图片', pdf: 'PDF 文档', text: '文本文件', audio: '音频', video: '视频',
@@ -109,22 +102,17 @@ export function AttachmentPreview({ att, onClose }: { att: Attachment | null; on
footer={att ? (
<>
<Button variant="secondary" className="mr-auto" onClick={() => {
// 远程附件:同步新标签打开(用户手势内触发,不会被拦截;右键即可另存)
if (att.remote_url) {
const a = document.createElement('a');
a.href = att.remote_url;
a.target = '_blank';
a.rel = 'noopener';
a.click();
toast('success', '已在新的标签页打开,右键图片可「另存为」');
return;
}
// 本地附件:base64 下载,失败给出提示
// 统一走服务器下载端点:Content-Disposition 带原始文件名,浏览器直接落盘,不闪窗
void (async () => {
try {
toast('success', '已开始下载');
const r = await readFull(att);
forceDownload(att.file_name, att.mime ?? undefined, r.data_b64);
const { blob, file_name } = await attachmentDownload(att.id);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file_name;
a.click();
setTimeout(() => URL.revokeObjectURL(url), 60_000);
toast('success', '已下载:' + file_name);
} catch (e) {
toast('error', '下载失败:' + (e as Error).message);
}