feat(ops): v4.0.7 HQ 活动图快链与勾选导出

HQ 指定一张活动图为勾选主合伙人合成 PNG/zip;城市合伙人页增加快链与单下/导出。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-01 15:12:10 +08:00
parent eeee55e215
commit cdc4b82931
21 changed files with 1041 additions and 28 deletions
+48
View File
@@ -58,6 +58,54 @@ export async function request<T>(path: string, options: RequestInit = {}): Promi
return json.data as T;
}
function parseDownloadFileName(header: string | null, fallback: string): string {
if (!header) return fallback;
const star = /filename\*=(?:UTF-8''|utf-8'')([^;]+)/i.exec(header);
if (star?.[1]) {
try {
return decodeURIComponent(star[1].trim());
} catch {
/* ignore */
}
}
const quoted = /filename="([^"]+)"/i.exec(header);
if (quoted?.[1]) return quoted[1];
const plain = /filename=([^;]+)/i.exec(header);
return plain?.[1]?.trim() || fallback;
}
/** Binary download (PNG / zip). JSON error bodies are surfaced as Error. */
export async function requestDownload(path: string, options: RequestInit = {}, fallbackName: string) {
const headers: Record<string, string> = {
'X-Client-App': CLIENT_APP,
...(options.headers as Record<string, string>),
};
if (options.body && !headers['Content-Type']) headers['Content-Type'] = 'application/json';
const token = getToken();
if (token) headers.Authorization = `Bearer ${token}`;
const res = await fetch(`${apiBase}${path}`, { ...options, headers });
const contentType = res.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
const json = (await res.json()) as { code?: number; message?: string };
if (json.code === 401) {
clearAuth();
window.location.href = '/login';
throw new Error('未登录');
}
throw new Error(json.message || '下载失败');
}
if (!res.ok) throw new Error(`下载失败(HTTP ${res.status}`);
const blob = await res.blob();
const fileName = parseDownloadFileName(res.headers.get('content-disposition'), fallbackName);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
}
export type Paginated<T> = {
items: T[];
total: number;