refactor(admin): 用已安装的 file-saver/fast-deep-equal 替代手写下载与 JSON 比较
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { lazy, Suspense, useEffect, useMemo, useState } from 'react';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { XCard, registerCatalog, type XAgentCommand_v0_9 } from '@ant-design/x-card';
|
||||
import { Button, Spin, Tag, Tooltip, Typography } from 'antd';
|
||||
import { DownloadOutlined } from '@ant-design/icons';
|
||||
@@ -232,12 +233,7 @@ const ChartPreview: React.FC<ChartPreviewProps> = ({ chart }) => {
|
||||
pixelRatio: 2,
|
||||
backgroundColor: '#fff',
|
||||
});
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${chart.title || '图表'}.png`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
saveAs(url, `${chart.title || '图表'}.png`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
importErrorReportUrl,
|
||||
previewImportStep,
|
||||
} from '../../api/imports';
|
||||
import { saveAs } from 'file-saver';
|
||||
import {
|
||||
STEP_FIELDS,
|
||||
type ImportPreviewResult,
|
||||
@@ -101,12 +102,7 @@ async function downloadErrorReport(runId: string, stepKey?: ImportStepKey): Prom
|
||||
});
|
||||
if (!response.ok) throw new Error('错误报告下载失败');
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = `导入错误报告-${runId.slice(0, 8)}.csv`;
|
||||
anchor.click();
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
saveAs(blob, `导入错误报告-${runId.slice(0, 8)}.csv`);
|
||||
}
|
||||
|
||||
export const ImportWizardModal: React.FC<ImportWizardModalProps> = ({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { App } from 'antd';
|
||||
import type { FormInstance } from 'antd';
|
||||
import equal from 'fast-deep-equal';
|
||||
|
||||
/**
|
||||
* 弹窗「未保存内容」保护:关闭弹窗时若表单值已被修改,先确认再关闭,
|
||||
@@ -15,16 +16,16 @@ import type { FormInstance } from 'antd';
|
||||
*/
|
||||
export function useDirtyGuard(form: FormInstance) {
|
||||
const { modal } = App.useApp();
|
||||
const pristineRef = useRef<string>('');
|
||||
const pristineRef = useRef<unknown>(null);
|
||||
|
||||
/** 记录当前表单值为「未修改」基准;打开弹窗/回填后调用 */
|
||||
const snapshot = useCallback(() => {
|
||||
pristineRef.current = JSON.stringify(form.getFieldsValue());
|
||||
pristineRef.current = form.getFieldsValue();
|
||||
}, [form]);
|
||||
|
||||
/** 表单是否有未保存修改(与 snapshot 时对比) */
|
||||
const isDirty = useCallback(() => {
|
||||
return JSON.stringify(form.getFieldsValue()) !== pristineRef.current;
|
||||
return !equal(form.getFieldsValue(), pristineRef.current);
|
||||
}, [form]);
|
||||
|
||||
const confirmClose = useCallback(
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
type DingTalkSyncStatus,
|
||||
type HistoryScheduleOption,
|
||||
} from './AttendanceAdmin.helpers';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
export const AdminAttendanceArchive: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
||||
const { modal } = App.useApp();
|
||||
@@ -349,14 +350,7 @@ export const AdminAttendanceArchive: React.FC<{ canEdit: boolean }> = ({ canEdit
|
||||
if (!response.ok) throw new Error('导出失败');
|
||||
return response.blob();
|
||||
})
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = `学生考勤-${dayjs().format('YYYYMMDD')}.xlsx`;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.then((blob) => saveAs(blob, `学生考勤-${dayjs().format('YYYYMMDD')}.xlsx`))
|
||||
.catch(() => message.error('导出失败'));
|
||||
}, [buildParams]);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import { QueryEmpty } from '../../components/QueryState';
|
||||
import { useSubmitShortcut } from '../../hooks/useSubmitShortcut';
|
||||
import { message } from '../../ui/app-message';
|
||||
import { buildTeacherCandidateOptions, type TeacherCandidateUser } from './teacher-candidate';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
export interface ClassStudent {
|
||||
id: number;
|
||||
@@ -312,12 +313,7 @@ export const ClassStudentsTab: React.FC<{
|
||||
});
|
||||
if (!res.ok) throw new Error('导出失败');
|
||||
const blob = await res.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `班级花名册-${detail?.name || id}.xlsx`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
saveAs(blob, `班级花名册-${detail?.name || id}.xlsx`);
|
||||
message.success('花名册导出成功');
|
||||
} catch (error) {
|
||||
console.error('花名册导出失败', error);
|
||||
|
||||
@@ -36,6 +36,7 @@ import { usePermission } from '../../hooks/usePermission';
|
||||
import { useUserStore } from '../../store/user/userStore';
|
||||
import { useVisibleRefetch } from '../../hooks/usePageVisible';
|
||||
import { useDirtyGuard } from '../../hooks/useDirtyGuard';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
const statusMap: Record<string, { text: string; color: string }> = {
|
||||
available: { text: '可用', color: 'green' },
|
||||
@@ -221,14 +222,7 @@ const ClassroomsPage: React.FC = () => {
|
||||
const token = useUserStore.getState().token;
|
||||
fetch(`${baseURL}/classrooms/template`, { headers: { Authorization: `Bearer ${token}` } })
|
||||
.then((res) => res.blob())
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = '教室导入模板.xlsx';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
})
|
||||
.then((blob) => saveAs(blob, '教室导入模板.xlsx'))
|
||||
.catch(() => message.error('下载失败'));
|
||||
};
|
||||
|
||||
@@ -486,12 +480,8 @@ const ClassroomsPage: React.FC = () => {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
.then((r) => r.blob())
|
||||
.then((b) => {
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(b);
|
||||
a.download = '教室使用报表.xlsx';
|
||||
a.click();
|
||||
});
|
||||
.then((b) => saveAs(b, '教室使用报表.xlsx'))
|
||||
.catch(() => message.error('导出失败'));
|
||||
}}
|
||||
>
|
||||
导出报表
|
||||
|
||||
Reference in New Issue
Block a user