Split large activity Excel prepare from chunk import and clarify errors.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,6 +8,8 @@ import { UiBusyOverlay, UiButton, UiModal, UiSelect } from '@/components/ui';
|
||||
const props = defineProps<{
|
||||
previewPath: string;
|
||||
importPath: string;
|
||||
/** اگر باشد: ابتدا فقط فایل خوانده میشود، بعد تکهتکه وارد میشود (برای فایلهای خیلی بزرگ) */
|
||||
preparePath?: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
|
||||
@@ -129,6 +131,22 @@ function updateImportProgress(processed: number, total: number) {
|
||||
progressLabel.value = `ورود فایل: ${processed.toLocaleString('fa-IR')} از ${total.toLocaleString('fa-IR')} ردیف`;
|
||||
}
|
||||
|
||||
async function postPrepare(file: File, map?: Record<string, string>) {
|
||||
if (!props.preparePath) {
|
||||
throw new Error('preparePath تنظیم نشده');
|
||||
}
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
if (map && Object.values(map).some(Boolean)) {
|
||||
form.append('mapping', JSON.stringify(map));
|
||||
}
|
||||
const { data } = await api.post<ImportContactsResult>(props.preparePath, form, {
|
||||
headers: { 'Content-Type': undefined },
|
||||
timeout: 10 * 60 * 1000,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
async function runChunkedImport(file: File, map?: Record<string, string>) {
|
||||
phase.value = 'import';
|
||||
progressLabel.value = 'در حال آمادهسازی و خواندن فایل…';
|
||||
@@ -140,6 +158,23 @@ async function runChunkedImport(file: File, map?: Record<string, string>) {
|
||||
let guard = 0;
|
||||
const maxChunks = 2000;
|
||||
|
||||
if (props.preparePath) {
|
||||
const prep = await postPrepare(file, map);
|
||||
acc = mergeImportResults(acc, prep);
|
||||
if (
|
||||
prep.errors?.some((e) => e.row === 1 && /بیش از .* ردیف|خواندن فایل/.test(e.message))
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
if (!prep.sessionId) {
|
||||
if (!prep.total) return acc;
|
||||
throw new Error(prep.errors[0]?.message || 'آمادهسازی فایل ناموفق بود');
|
||||
}
|
||||
sessionId = prep.sessionId;
|
||||
updateImportProgress(0, prep.total ?? 0);
|
||||
if (!prep.hasMore && (prep.total ?? 0) === 0) return acc;
|
||||
}
|
||||
|
||||
while (guard < maxChunks) {
|
||||
guard += 1;
|
||||
const part = await postImportChunk({
|
||||
@@ -183,11 +218,30 @@ async function runChunkedImport(file: File, map?: Record<string, string>) {
|
||||
}
|
||||
|
||||
function apiMessage(e: unknown, fallback: string) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const msg = (e.response?.data as { message?: string | string[] })?.message;
|
||||
return Array.isArray(msg) ? msg.join('، ') : (msg ?? fallback);
|
||||
if (!axios.isAxiosError(e)) {
|
||||
return e instanceof Error && e.message ? e.message : fallback;
|
||||
}
|
||||
return fallback;
|
||||
if (e.code === 'ECONNABORTED' || /timeout/i.test(e.message)) {
|
||||
return 'مهلت درخواست تمام شد — فایل خیلی بزرگ است یا سرور دیر جواب داد. دوباره تلاش کنید.';
|
||||
}
|
||||
if (!e.response) {
|
||||
return 'ارتباط با سرور برقرار نشد. اتصال را بررسی کنید و دوباره تلاش کنید.';
|
||||
}
|
||||
const status = e.response.status;
|
||||
const data = e.response.data as { message?: string | string[] } | string | undefined;
|
||||
let msg: string | undefined;
|
||||
if (typeof data === 'string' && data.trim() && !data.trim().startsWith('<')) {
|
||||
msg = data.trim().slice(0, 280);
|
||||
} else if (data && typeof data === 'object') {
|
||||
const m = data.message;
|
||||
msg = Array.isArray(m) ? m.join('، ') : m;
|
||||
}
|
||||
if (msg) return msg;
|
||||
if (status === 413) return 'حجم فایل بیش از حد مجاز سرور است.';
|
||||
if (status === 502 || status === 504) {
|
||||
return 'سرور میانی قطع شد (معمولاً بهخاطر حجم/زمان فایل). چند دقیقه بعد دوباره تلاش کنید.';
|
||||
}
|
||||
return `${fallback} (کد ${status})`;
|
||||
}
|
||||
|
||||
async function onFile(ev: Event) {
|
||||
|
||||
Reference in New Issue
Block a user