forked from wangziqi/gongxue-base
Server: - Add DingTalk attendance import service with SSE progress streaming - Add IntegrationConfig entity & module for multi-tenant DingTalk setup - Add ExpenseType entity & ExpenseTypesModule - Add SeedModule for DB initialization - Add UserDingMapping entity for DingTalk user linkage - Attendance service: import flow with dedup & student auto-mapping - Rooms service: time-range overlap queries - Sync controller/service: DingTalk integration wiring - Permission guard: refactor to pure re-export - Campus scope middleware: tenant-aware filtering Admin UI: - Attendance page: import UI with progress & result summary - All pages: tableStyle/tablePagination standardization - Login page: responsive styling - Sensitive data: useViewSensitive hook for masked viewing - Vite config: path aliases, build optimization - Test infra: vitest config, test utilities Docs: PRD DingTalk batch 1 & 2 design docs
32 lines
969 B
TypeScript
32 lines
969 B
TypeScript
/**
|
|
* Download a file from the API as a blob and trigger a browser download.
|
|
*
|
|
* @param endpoint - API path (e.g. '/rooms/template')
|
|
* @param filename - Suggested filename for the download
|
|
*/
|
|
export async function downloadBlob(endpoint: string, filename: string): Promise<void> {
|
|
const baseURL = import.meta.env.PROD
|
|
? '/api'
|
|
: `http://localhost:${import.meta.env.VITE_API_PORT || 3002}/api`;
|
|
|
|
const token = localStorage.getItem('token');
|
|
const res = await fetch(`${baseURL}${endpoint}`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
throw new Error(text || `下载失败 (HTTP ${res.status})`);
|
|
}
|
|
|
|
const blob = await res.blob();
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|