Show percent progress bar during long Excel imports.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-03 17:45:08 +03:30
co-authored by Cursor
parent c629729a9a
commit 07bdc4a3b6
2 changed files with 88 additions and 8 deletions
+31 -6
View File
@@ -25,6 +25,8 @@ const mapping = reactive<Record<string, string>>({});
const submitting = ref(false);
const phase = ref<'idle' | 'preview' | 'import'>('idle');
const progressLabel = ref('');
/** درصد ۰–۱۰۰؛ در مرحلهٔ خواندن فایل null */
const progressPct = ref<number | null>(null);
const requiredKeys = computed(
() => preview.value?.fields.filter((f) => f.required).map((f) => f.key) ?? [],
@@ -41,6 +43,9 @@ const busyMessage = computed(() => {
}
return 'در حال خواندن و بررسی فایل اکسل…';
});
const busyProgress = computed(() =>
phase.value === 'import' ? progressPct.value : null,
);
function pick() {
if (props.disabled || submitting.value) return;
@@ -53,6 +58,7 @@ function reset() {
submitting.value = false;
phase.value = 'idle';
progressLabel.value = '';
progressPct.value = null;
for (const key of Object.keys(mapping)) delete mapping[key];
}
@@ -112,9 +118,21 @@ async function postImportChunk(opts: {
return data;
}
function updateImportProgress(processed: number, total: number) {
if (total <= 0) {
progressPct.value = null;
progressLabel.value = 'در حال ورود اطلاعات از اکسل…';
return;
}
const pct = Math.max(0, Math.min(100, Math.round((processed / total) * 100)));
progressPct.value = pct;
progressLabel.value = `ورود فایل: ${processed.toLocaleString('fa-IR')} از ${total.toLocaleString('fa-IR')} ردیف`;
}
async function runChunkedImport(file: File, map?: Record<string, string>) {
phase.value = 'import';
progressLabel.value = 'در حال ورود اطلاعات از اکسل…';
progressLabel.value = 'در حال آماده‌سازی و خواندن فایل…';
progressPct.value = 0;
let acc: ImportContactsResult = { created: 0, skipped: 0, errors: [] };
let sessionId: string | undefined;
@@ -134,9 +152,7 @@ async function runChunkedImport(file: File, map?: Record<string, string>) {
const total = part.total ?? 0;
const processed = part.processed ?? 0;
if (total > 0) {
progressLabel.value = `ورود چندمرحله‌ای: ${processed.toLocaleString('fa-IR')} از ${total.toLocaleString('fa-IR')} ردیف`;
}
updateImportProgress(processed, total);
// خطای سراسری (مثلاً سقف ردیف) — ادامه نده
if (
@@ -145,7 +161,10 @@ async function runChunkedImport(file: File, map?: Record<string, string>) {
break;
}
if (!part.hasMore || !part.sessionId) break;
if (!part.hasMore || !part.sessionId) {
if (total > 0) updateImportProgress(total, total);
break;
}
sessionId = part.sessionId;
offset = part.nextOffset ?? processed;
}
@@ -228,6 +247,7 @@ async function confirmMapping() {
phase.value = 'idle';
submitting.value = false;
progressLabel.value = '';
progressPct.value = null;
emit('settled');
}
}
@@ -247,7 +267,12 @@ async function confirmMapping() {
/>
</span>
<UiBusyOverlay :open="busyOpen" :message="busyMessage" nest />
<UiBusyOverlay
:open="busyOpen"
:message="busyMessage"
:progress="busyProgress"
nest
/>
<UiModal :open="!!preview" title="تطبیق ستون‌های اکسل" @close="closeMapper">
<p class="lead">