241 lines
6.8 KiB
Vue
241 lines
6.8 KiB
Vue
<template>
|
||||
|
|
<div class="pw-bulk">
|
|||
|
|
<div class="pw-bulk__head">
|
|||
|
|
<div class="pw-bulk__icon" aria-hidden="true">
|
|||
|
|
<Icon icon="vuesax-linear:refresh-circle" :size="20" color="var(--admin-primary, #10913A)" />
|
|||
|
|
</div>
|
|||
|
|
<div class="pw-bulk__copy min-w-0 flex-1">
|
|||
|
|
<p class="pw-bulk__title">همگامسازی ماندهٔ همهٔ مشتریان</p>
|
|||
|
|
<p class="pw-bulk__hint">
|
|||
|
|
موجودی کیف همهٔ مشتریان دارای کد پرگار را با ماندهٔ پرگار یکی میکند — تاریخچهٔ اسناد روی کیف
|
|||
|
|
replay نمیشود
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<a-button
|
|||
|
|
type="primary"
|
|||
|
|
block
|
|||
|
|
class="pw-bulk__btn"
|
|||
|
|
:loading="running"
|
|||
|
|
:disabled="running"
|
|||
|
|
@click="confirmAndStart"
|
|||
|
|
>
|
|||
|
|
همگامسازی مانده از پرگار برای همه
|
|||
|
|
</a-button>
|
|||
|
|
|
|||
|
|
<div v-if="running || resultVisible" class="pw-bulk__progress">
|
|||
|
|
<template v-if="running">
|
|||
|
|
<div class="pw-bulk__progress-row">
|
|||
|
|
<a-spin size="small" />
|
|||
|
|
<span>
|
|||
|
|
در حال همگامسازی…
|
|||
|
|
<template v-if="total > 0">{{ processed }} / {{ total }}</template>
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="pw-bulk__track">
|
|||
|
|
<div class="pw-bulk__track-fill" :style="{ width: percent + '%' }" />
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
<template v-else-if="resultVisible">
|
|||
|
|
<div class="pw-bulk__progress-row">
|
|||
|
|
<span class="pw-bulk__ok">✓ {{ success }} موفق</span>
|
|||
|
|
<span v-if="changed" class="pw-bulk__changed">{{ changed }} تغییر موجودی</span>
|
|||
|
|
<span v-if="failed" class="pw-bulk__fail">✗ {{ failed }} ناموفق</span>
|
|||
|
|
<a-button v-if="errors.length" type="link" size="small" @click="showErrors = !showErrors">
|
|||
|
|
{{ showErrors ? 'پنهان' : 'جزئیات' }}
|
|||
|
|
</a-button>
|
|||
|
|
<a-button type="link" size="small" @click="dismissResult">بستن</a-button>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="showErrors && errors.length" class="pw-bulk__errors">
|
|||
|
|
<div v-for="e in errors" :key="e.id">
|
|||
|
|
<b>{{ e.name }}</b>
|
|||
|
|
<template v-if="e.mobile"> ({{ e.mobile }})</template>
|
|||
|
|
— {{ e.error }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { computed, createVNode, ref } from 'vue'
|
|||
|
|
import { Modal, message } from 'ant-design-vue'
|
|||
|
|
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
|||
|
|
import Icon from '@core/components/BaseIcon/src/Icon.vue'
|
|||
|
|
import {
|
|||
|
|
syncAllWalletsFromPargarChunk,
|
|||
|
|
type PargarBulkWalletSyncChunk,
|
|||
|
|
} from '@/api/pargar'
|
|||
|
|
|
|||
|
|
const CHUNK_LIMIT = 20
|
|||
|
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|||
|
|
|
|||
|
|
const running = ref(false)
|
|||
|
|
const resultVisible = ref(false)
|
|||
|
|
const showErrors = ref(false)
|
|||
|
|
|
|||
|
|
const total = ref(0)
|
|||
|
|
const processed = ref(0)
|
|||
|
|
const success = ref(0)
|
|||
|
|
const changed = ref(0)
|
|||
|
|
const failed = ref(0)
|
|||
|
|
const errors = ref<PargarBulkWalletSyncChunk['errors']>([])
|
|||
|
|
|
|||
|
|
const percent = computed(() =>
|
|||
|
|
total.value > 0 ? Math.min(100, Math.round((processed.value / total.value) * 100)) : 0,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
function dismissResult() {
|
|||
|
|
resultVisible.value = false
|
|||
|
|
showErrors.value = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function confirmAndStart() {
|
|||
|
|
if (running.value) return
|
|||
|
|
Modal.confirm({
|
|||
|
|
title: 'همگامسازی ماندهٔ همهٔ مشتریان؟',
|
|||
|
|
icon: createVNode(ExclamationCircleOutlined),
|
|||
|
|
content:
|
|||
|
|
'ماندهٔ کیف همهٔ مشتریان دارای کد پرگار با پرگار همتراز میشود. این کار ممکن است چند دقیقه طول بکشد.',
|
|||
|
|
okText: 'شروع همگامسازی',
|
|||
|
|
cancelText: 'انصراف',
|
|||
|
|
centered: true,
|
|||
|
|
onOk: () => start(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function start() {
|
|||
|
|
running.value = true
|
|||
|
|
resultVisible.value = false
|
|||
|
|
showErrors.value = false
|
|||
|
|
total.value = 0
|
|||
|
|
processed.value = 0
|
|||
|
|
success.value = 0
|
|||
|
|
changed.value = 0
|
|||
|
|
failed.value = 0
|
|||
|
|
errors.value = []
|
|||
|
|
|
|||
|
|
let offset = 0
|
|||
|
|
let done = false
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
while (!done) {
|
|||
|
|
const res = await syncAllWalletsFromPargarChunk({
|
|||
|
|
offset,
|
|||
|
|
limit: CHUNK_LIMIT,
|
|||
|
|
force: false,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
total.value = res.total
|
|||
|
|
success.value += res.success
|
|||
|
|
changed.value += res.changed
|
|||
|
|
failed.value += res.failed
|
|||
|
|
if (res.errors?.length) {
|
|||
|
|
errors.value = [...errors.value, ...res.errors]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
processed.value = Math.min(res.next_offset, res.total)
|
|||
|
|
offset = res.next_offset
|
|||
|
|
done = res.done || res.processed === 0 || res.total === 0
|
|||
|
|
|
|||
|
|
if (!done) await sleep(150)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
resultVisible.value = true
|
|||
|
|
if (failed.value === 0) {
|
|||
|
|
message.success(
|
|||
|
|
total.value === 0
|
|||
|
|
? 'مشتری دارای کد پرگار یافت نشد'
|
|||
|
|
: `همگامسازی ${success.value} مشتری انجام شد`,
|
|||
|
|
)
|
|||
|
|
} else if (success.value > 0) {
|
|||
|
|
message.warning(`${success.value} موفق — ${failed.value} ناموفق`)
|
|||
|
|
} else {
|
|||
|
|
message.error('همگامسازی ناموفق بود')
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(e)
|
|||
|
|
message.error('خطا در ارتباط با سرور')
|
|||
|
|
resultVisible.value = processed.value > 0 || success.value > 0 || failed.value > 0
|
|||
|
|
} finally {
|
|||
|
|
running.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.pw-bulk {
|
|||
|
|
@apply rounded-xl border border-base p-3;
|
|||
|
|
background: var(--admin-surface-muted, var(--admin-bg-muted, #f8fafc));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__head {
|
|||
|
|
@apply mb-3 flex items-start gap-2.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__icon {
|
|||
|
|
@apply flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gray-50;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__title {
|
|||
|
|
@apply m-0 text-sm font-700;
|
|||
|
|
color: var(--admin-text, inherit);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__hint {
|
|||
|
|
@apply m-0 mt-0.5 text-xs leading-5;
|
|||
|
|
color: var(--admin-text-secondary, #6b7280);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__btn {
|
|||
|
|
@apply font-600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__progress {
|
|||
|
|
@apply mt-3 rounded-lg border border-base p-2.5 text-xs;
|
|||
|
|
background: var(--admin-bg-surface, #fff);
|
|||
|
|
color: var(--admin-text, inherit);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__progress-row {
|
|||
|
|
@apply flex flex-wrap items-center gap-2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__track {
|
|||
|
|
@apply mt-2 h-1.5 overflow-hidden rounded-full;
|
|||
|
|
background: var(--admin-border, #e5e7eb);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__track-fill {
|
|||
|
|
@apply h-full rounded-full transition-all duration-300;
|
|||
|
|
background: var(--admin-primary, #10913A);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__ok {
|
|||
|
|
@apply rounded-full px-2.5 py-0.5 font-600;
|
|||
|
|
background: rgba(52, 211, 153, 0.15);
|
|||
|
|
color: #059669;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__changed {
|
|||
|
|
@apply rounded-full px-2.5 py-0.5 font-600;
|
|||
|
|
background: rgba(14, 165, 233, 0.12);
|
|||
|
|
color: #0284c7;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__fail {
|
|||
|
|
@apply rounded-full px-2.5 py-0.5 font-600;
|
|||
|
|
background: rgba(239, 68, 68, 0.12);
|
|||
|
|
color: #dc2626;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.pw-bulk__errors {
|
|||
|
|
@apply mt-2 max-h-28 overflow-y-auto rounded-lg p-2 text-xs leading-6;
|
|||
|
|
background: rgba(239, 68, 68, 0.08);
|
|||
|
|
border: 1px solid rgba(239, 68, 68, 0.2);
|
|||
|
|
color: #dc2626;
|
|||
|
|
}
|
|||
|
|
</style>
|