// ImportXlsxModal.jsx
|
import { useEffect, useState } from 'react';
|
import { Box, CircularProgress, Stack, Typography } from '@mui/material';
|
import Alert from '@mui/material/Alert';
|
import Dialog from '@mui/material/Dialog';
|
import DialogActions from '@mui/material/DialogActions';
|
import DialogContent from '@mui/material/DialogContent';
|
import DialogTitle from '@mui/material/DialogTitle';
|
import MuiLink from '@mui/material/Link';
|
import {
|
Button,
|
FileField,
|
FileInput,
|
Form,
|
Toolbar,
|
useRefresh,
|
useTranslate,
|
} from 'react-admin';
|
import DialogCloseButton from './DialogCloseButton';
|
import { useExcelParse } from './useExcelParse';
|
|
const ImportXlsxModal = ({
|
open,
|
onClose,
|
importTemp,
|
useImport,
|
params,
|
title = '',
|
onceBatch = 10,
|
}) => {
|
const refresh = useRefresh();
|
const t = useTranslate();
|
|
const { processBatch } = useImport();
|
const { importer, parseExcel, reset } = useExcelParse({
|
batchSize: onceBatch,
|
processBatch,
|
params,
|
});
|
|
const [file, setFile] = useState(null);
|
|
useEffect(() => {
|
if (importer.state === 'complete') {
|
refresh();
|
}
|
}, [importer.state, refresh]);
|
|
const handleFileChange = (val) => {
|
const f = Array.isArray(val) ? val[0]?.rawFile ?? val[0] : val?.rawFile ?? val;
|
setFile(f || null);
|
};
|
|
const startImport = async () => {
|
if (!file) return;
|
parseExcel(file);
|
};
|
|
const handleClose = () => {
|
reset();
|
setFile(null);
|
onClose();
|
};
|
|
const handleReset = (e) => {
|
e.preventDefault();
|
reset();
|
};
|
|
const nameMatch = importTemp?.split('/').pop() || 'import_template.xlsx';
|
|
return (
|
<Dialog open={open} maxWidth="md" fullWidth onClick={(e) => e.stopPropagation()}>
|
<DialogCloseButton onClose={handleClose} />
|
<DialogTitle>{t('common.action.import.title')} {title}</DialogTitle>
|
<DialogContent>
|
<Form>
|
<Stack spacing={2}>
|
<Alert
|
severity="info"
|
action={
|
importTemp ? (
|
<Button
|
component="a"
|
label="common.action.import.download"
|
color="info"
|
href={importTemp}
|
download={nameMatch}
|
/>
|
) : null
|
}
|
sx={{
|
alignItems: 'center',
|
'& .MuiAlert-action': { p: 0, mr: 0 },
|
}}
|
>
|
{t('common.action.import.msg', { _: '这是一个可以用作模板的示例 Excel 文件' })}
|
</Alert>
|
|
{importer.state === 'running' && (
|
<Stack gap={2}>
|
<Alert
|
severity="info"
|
action={
|
<Box sx={{ display: 'flex', alignItems: 'center', p: 0 }}>
|
<CircularProgress size={20} />
|
</Box>
|
}
|
sx={{ alignItems: 'center', '& .MuiAlert-action': { p: 0, mr: 0 } }}
|
>
|
{t('common.action.import.tips')}
|
</Alert>
|
<Typography variant="body2">
|
Imported <strong>{importer.importCount}</strong> /{' '}
|
<strong>{importer.rowCount}</strong> rows, with{' '}
|
<strong>{importer.errorCount}</strong> errors.
|
{importer.remainingTime !== null && (
|
<>
|
{' '}ETA: <strong>{millisecondsToTime(importer.remainingTime)}</strong>.{' '}
|
<MuiLink href="#" onClick={handleReset} color="error">
|
{t('common.action.import.stop')}
|
</MuiLink>
|
</>
|
)}
|
</Typography>
|
</Stack>
|
)}
|
|
{importer.state === 'error' && (
|
<Alert severity="error">{t('common.action.import.err')}</Alert>
|
)}
|
|
{importer.state === 'complete' && (
|
<>
|
<Alert severity="success">
|
{t('common.action.import.result', {
|
success: importer.importCount,
|
error: importer.errorCount,
|
})}
|
</Alert>
|
{importer.errorMsg && <Alert severity="error">{importer.errorMsg}</Alert>}
|
</>
|
)}
|
|
{importer.state === 'idle' && (
|
<>
|
<FileInput
|
source="xlsx"
|
label="Excel File (.xlsx / .xls)"
|
accept={{
|
'application/vnd.ms-excel': ['.xls'],
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
|
}}
|
onChange={handleFileChange}
|
>
|
<FileField source="src" title="title" />
|
</FileInput>
|
</>
|
)}
|
</Stack>
|
</Form>
|
</DialogContent>
|
<DialogActions sx={{ p: 0, justifyContent: 'flex-start' }}>
|
<Toolbar sx={{ width: '100%' }}>
|
{importer.state === 'idle' ? (
|
<Button
|
label="common.action.import.title"
|
variant="contained"
|
onClick={startImport}
|
disabled={!file}
|
/>
|
) : (
|
<Button
|
label="ra.action.close"
|
onClick={handleClose}
|
disabled={importer.state === 'running'}
|
/>
|
)}
|
</Toolbar>
|
</DialogActions>
|
</Dialog>
|
);
|
};
|
|
function millisecondsToTime(ms) {
|
const seconds = Math.floor((ms / 1000) % 60);
|
const minutes = Math.floor((ms / (60 * 1000)) % 60);
|
return `${minutes}m ${seconds}s`;
|
}
|
|
export default ImportXlsxModal;
|