fix some bugs from zarin
This commit is contained in:
+19
-3
@@ -54,8 +54,14 @@ export interface CustomerSyncStatus {
|
||||
panel_total: number
|
||||
panel_registered: number
|
||||
panel_only: number
|
||||
zarin_total: number
|
||||
zarin_only_count: number
|
||||
zarin_total: number | null
|
||||
zarin_only_count: number | null
|
||||
/** اگر دریافت لیست شکست بخورد */
|
||||
zarin_fetch_error?: string | null
|
||||
zarin_http_status?: number | null
|
||||
/** نام گروه فیلتر (مثلاً مشتری نقدی) */
|
||||
zarin_group_filter?: string | null
|
||||
include_zarin?: boolean
|
||||
panel_only_list: {
|
||||
data: PanelOnlyCustomer[]
|
||||
current_page: number
|
||||
@@ -75,7 +81,17 @@ export const getZarinCustomerSyncStatus = async (params?: {
|
||||
page?: number
|
||||
zarin_page?: number
|
||||
include_zarin?: number | boolean
|
||||
}) => await useAxios.get<CustomerSyncStatus>('/admin/zarin/customer-sync/status', { params })
|
||||
}) => {
|
||||
// روی بعضی سرورها query به PHP نمیرسد — لیست زرین از مسیر جدا
|
||||
if (params?.include_zarin) {
|
||||
const { include_zarin: _ignored, ...rest } = params
|
||||
return await useAxios.get<CustomerSyncStatus>(
|
||||
'/admin/zarin/customer-sync/status-with-zarin',
|
||||
Object.keys(rest).length ? { params: rest } : undefined,
|
||||
)
|
||||
}
|
||||
return await useAxios.get<CustomerSyncStatus>('/admin/zarin/customer-sync/status', { params })
|
||||
}
|
||||
|
||||
export interface SyncPanelResult {
|
||||
ok: boolean
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
<Icon icon="vuesax-linear:arrow-left" :size="16" />
|
||||
<Icon icon="vuesax-linear:arrow-right" :size="16" />
|
||||
</div>
|
||||
<div class="cs-stat cs-stat--zarin">
|
||||
<div class="cs-stat cs-stat--zarin cs-stat--clickable" role="button" tabindex="0" @click="onTabChange('zarin')" @keydown.enter="onTabChange('zarin')">
|
||||
<div class="cs-stat__num">
|
||||
<template v-if="status?.zarin_total != null">{{ status.zarin_total }}</template>
|
||||
<a-spin v-else-if="loadingZarin" size="small" />
|
||||
<span v-else class="cs-stat__hint">تب دوم</span>
|
||||
<span v-else class="cs-stat__hint">کلیک کنید</span>
|
||||
</div>
|
||||
<div class="cs-stat__label">مشتریان زرین</div>
|
||||
</div>
|
||||
@@ -38,11 +38,11 @@
|
||||
</div>
|
||||
<div class="cs-stat__label">پنل — بدون زرین</div>
|
||||
</div>
|
||||
<div class="cs-stat cs-stat--sky">
|
||||
<div class="cs-stat cs-stat--sky cs-stat--clickable" role="button" tabindex="0" @click="onTabChange('zarin')" @keydown.enter="onTabChange('zarin')">
|
||||
<div class="cs-stat__num">
|
||||
<template v-if="status?.zarin_only_count != null">{{ status.zarin_only_count }}</template>
|
||||
<a-spin v-else-if="loadingZarin" size="small" />
|
||||
<span v-else class="cs-stat__hint">تب دوم</span>
|
||||
<span v-else class="cs-stat__hint">کلیک کنید</span>
|
||||
</div>
|
||||
<div class="cs-stat__label">زرین — بدون پنل</div>
|
||||
</div>
|
||||
@@ -77,6 +77,7 @@
|
||||
<Icon icon="vuesax-linear:arrow-right" :size="14" />
|
||||
زرین ← پنل
|
||||
<span v-if="status?.zarin_only_count" class="cs-tab__badge cs-tab__badge--sky">{{ status.zarin_only_count }}</span>
|
||||
<span v-else-if="!zarinFetched && !loadingZarin" class="cs-tab__hint">بارگذاری</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -193,6 +194,10 @@
|
||||
<span class="cs-card__head-title">مشتریان زرین بدون حساب پنل</span>
|
||||
</div>
|
||||
<div class="cs-card__head-actions">
|
||||
<a-button size="small" :loading="loadingZarin" @click="reloadZarin">
|
||||
<template #icon><Icon icon="vuesax-linear:refresh" :size="13" /></template>
|
||||
بروزرسانی از زرین
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -214,6 +219,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="status?.zarin_group_filter && status.include_zarin" class="cs-group-filter">
|
||||
گروه: <b>{{ status.zarin_group_filter }}</b>
|
||||
</div>
|
||||
|
||||
<div v-if="zarinLoadFailed" class="cs-fetch-error">
|
||||
<Icon icon="vuesax-linear:danger" :size="16" />
|
||||
<div>
|
||||
<div class="cs-fetch-error__title">دریافت لیست از زرین ممکن نشد</div>
|
||||
<div class="cs-fetch-error__msg">اتصال را بررسی کنید و «بروزرسانی از زرین» را دوباره بزنید.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="syncingZarin || zarinSyncResult" class="cs-result">
|
||||
<template v-if="syncingZarin">
|
||||
<a-spin size="small" />
|
||||
@@ -233,6 +250,12 @@
|
||||
</div>
|
||||
|
||||
<div v-if="loadingZarin" class="cs-loading"><a-spin /></div>
|
||||
<template v-else-if="zarinLoadFailed">
|
||||
<div class="cs-empty cs-empty--error">
|
||||
<div class="cs-empty__title">لیست مشتریان فعلاً در دسترس نیست</div>
|
||||
<div class="cs-empty__sub">دکمه «بروزرسانی از زرین» را دوباره بزنید.</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="zarinOnlyList.length">
|
||||
<!-- موبایل -->
|
||||
<div class="cs-mobile-list">
|
||||
@@ -300,7 +323,10 @@
|
||||
</template>
|
||||
<div v-else class="cs-empty">
|
||||
<div class="cs-empty__icon"><Icon icon="vuesax-linear:tick-circle" :size="28" /></div>
|
||||
<div class="cs-empty__title">همه مشتریان زرین در پنل ثبت شدهاند</div>
|
||||
<div class="cs-empty__title">
|
||||
<template v-if="(status?.zarin_total ?? 0) > 0">همه مشتریان زرین در پنل ثبت شدهاند</template>
|
||||
<template v-else>هنوز لیست زرین بارگذاری نشده یا خالی است</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -329,6 +355,7 @@ const importStore = useCustomerImportStore()
|
||||
// ─── state ───
|
||||
const loadingPanel = ref(true) // شروع با true تا اولین load کامل بشه
|
||||
const loadingZarin = ref(false)
|
||||
const zarinLoadFailed = ref(false)
|
||||
const syncingPanel = ref(false)
|
||||
const syncingZarin = ref(false)
|
||||
const activeTab = ref<'panel' | 'zarin'>('panel')
|
||||
@@ -404,21 +431,51 @@ async function load(panelPage = 1) {
|
||||
}
|
||||
|
||||
// ─── load zarin customers (کند — فقط وقتی تب زرین باز میشه) ───
|
||||
async function loadZarin() {
|
||||
if (zarinFetched.value) return
|
||||
async function loadZarin(force = false) {
|
||||
if (zarinFetched.value && !force) return
|
||||
loadingZarin.value = true
|
||||
zarinLoadFailed.value = false
|
||||
try {
|
||||
const res = await getZarinCustomerSyncStatus({ include_zarin: 1 } as any)
|
||||
const res = await getZarinCustomerSyncStatus({ include_zarin: 1 })
|
||||
if (status.value) {
|
||||
status.value.zarin_total = res.zarin_total ?? 0
|
||||
status.value.zarin_total = res.zarin_total ?? 0
|
||||
status.value.zarin_only_count = res.zarin_only_count ?? 0
|
||||
status.value.zarin_group_filter = res.zarin_group_filter ?? null
|
||||
status.value.include_zarin = res.include_zarin ?? false
|
||||
status.value.zarin_fetch_error = null
|
||||
} else {
|
||||
status.value = res
|
||||
status.value = { ...res, zarin_fetch_error: null }
|
||||
}
|
||||
zarinOnlyList.value = res.zarin_only_list?.data ?? []
|
||||
zarinFetched.value = true
|
||||
} catch (e) { console.error(e) }
|
||||
finally { loadingZarin.value = false }
|
||||
if (res.zarin_fetch_error || res.include_zarin === false) {
|
||||
zarinLoadFailed.value = true
|
||||
if (status.value) {
|
||||
status.value.zarin_total = 0
|
||||
status.value.zarin_only_count = 0
|
||||
}
|
||||
message.error('دریافت لیست مشتریان از حسابداری ناموفق بود')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
zarinLoadFailed.value = true
|
||||
zarinFetched.value = true
|
||||
if (status.value) {
|
||||
status.value.zarin_total = 0
|
||||
status.value.zarin_only_count = 0
|
||||
}
|
||||
message.error('دریافت لیست مشتریان از حسابداری ناموفق بود')
|
||||
} finally {
|
||||
loadingZarin.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function reloadZarin() {
|
||||
zarinFetched.value = false
|
||||
zarinLoadFailed.value = false
|
||||
selectedZarin.clear()
|
||||
zarinPage.value = 1
|
||||
loadZarin(true)
|
||||
}
|
||||
|
||||
function onTabChange(tab: 'panel' | 'zarin') {
|
||||
@@ -609,6 +666,11 @@ onMounted(() => {
|
||||
&--zarin { background: rgba(14,165,233,0.25); }
|
||||
&--amber { background: rgba(251,191,36,0.2); }
|
||||
&--sky { background: rgba(14,165,233,0.2); }
|
||||
&--clickable {
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
&:hover { background: rgba(14,165,233,0.4); }
|
||||
}
|
||||
|
||||
&__num {
|
||||
font-size: 20px;
|
||||
@@ -702,6 +764,15 @@ onMounted(() => {
|
||||
&--amber { background: rgba(245,158,11,0.15); color: #d97706; }
|
||||
&--sky { background: rgba(14,165,233,0.15); color: #0ea5e9; }
|
||||
}
|
||||
|
||||
&__hint {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 1px 6px;
|
||||
border-radius: 99px;
|
||||
background: rgba(14,165,233,0.12);
|
||||
color: #0ea5e9;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── کارت ─── */
|
||||
@@ -786,6 +857,33 @@ onMounted(() => {
|
||||
|
||||
.cs-loading { display: flex; justify-content: center; padding: 48px; }
|
||||
|
||||
.cs-group-filter {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
color: var(--admin-text-secondary, #64748b);
|
||||
border-bottom: 1px solid var(--admin-border);
|
||||
background: var(--admin-bg-muted, transparent);
|
||||
|
||||
b { color: var(--admin-text); }
|
||||
&__warn { color: #dc2626; font-weight: 600; }
|
||||
}
|
||||
|
||||
.cs-fetch-error {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(239, 68, 68, 0.25);
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
color: #dc2626;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
|
||||
&__title { font-weight: 700; font-size: 13px; margin-bottom: 2px; }
|
||||
&__msg { word-break: break-word; color: var(--admin-text); }
|
||||
&__meta { margin-top: 4px; opacity: 0.8; font-family: ui-monospace, monospace; }
|
||||
}
|
||||
|
||||
/* ─── موبایل ─── */
|
||||
.cs-mobile-list {
|
||||
display: block;
|
||||
@@ -852,6 +950,8 @@ onMounted(() => {
|
||||
|
||||
&__icon { width: 52px; height: 52px; border-radius: 50%; background: rgba(52,211,153,0.12); color: #059669; display: flex; align-items: center; justify-content: center; }
|
||||
&__title { font-size: 14px; font-weight: 700; color: var(--admin-text); }
|
||||
&__sub { font-size: 12px; color: var(--admin-text-muted, #64748b); text-align: center; max-width: 360px; }
|
||||
&--error &__title { color: #dc2626; }
|
||||
}
|
||||
|
||||
.cs-pagination {
|
||||
|
||||
@@ -179,6 +179,7 @@ export function supportMessengerIdPlaceholder(messenger?: string): string {
|
||||
export const HIDDEN_LEGACY_SETTING_KEYS = new Set([
|
||||
'main_product_price',
|
||||
'montzar_price',
|
||||
'fee_single',
|
||||
'etehadie_api_url',
|
||||
'goldir_price',
|
||||
'price_primary_source',
|
||||
@@ -566,11 +567,11 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
||||
show_product_details:
|
||||
'روشن: بعد از انتخاب محصول در صفحه خرید/فروش، متن توضیح محصول نمایش داده میشود. خاموش: توضیح نشان داده نمیشود.',
|
||||
fee_single:
|
||||
'اختلاف پایه (ریال) بین قیمت خرید و فروش روی مظنه. معمولاً نصف مجموع اختلاف قیمت خرید و فروش است.',
|
||||
'منسوخ — دیگر استفاده نمیشود. اختلاف فقط از buy_fee / sell_fee (خالی = صفر).',
|
||||
buy_fee:
|
||||
'مبلغ اضافه (ریال) روی قیمت پایه برای سمت خرید. اگر خالی بگذارید، از «اختلاف پایه» استفاده میشود.',
|
||||
'مبلغ اضافه (ریال) روی قیمت پایه برای سمت خرید. خالی یا صفر = بدون اختلاف.',
|
||||
sell_fee:
|
||||
'مبلغ کسر (ریال) از قیمت پایه برای سمت فروش. اگر خالی بگذارید، از «اختلاف پایه» استفاده میشود.',
|
||||
'مبلغ کسر (ریال) از قیمت پایه برای سمت فروش. خالی یا صفر = بدون اختلاف.',
|
||||
fee_service_charge_percentage:
|
||||
'درصد کارمزد هر معامله (اعشاری؛ مثلاً ۰٫۰۰۵). روی همهٔ سفارشها اعمال میشود و با اختلاف قیمت محصول یا buy_fee/sell_fee قاطی نمیشود. محصول مقداری: اگر مبلغ ریالی قفل باشد کارمزد از فلز، اگر وزن قفل باشد از ریال. محصول تعدادی: کارمزد فقط از ریال.',
|
||||
fee_deduct_from_gold:
|
||||
@@ -776,7 +777,7 @@ const KEY_HELP_FALLBACK: Record<string, string> = {
|
||||
zarin_gorohe_kar:
|
||||
'کد gorooheKar هنگام ساخت/جستجوی کار در زرین. اگر چند پنل یک زرین دارند، برای هر پنل گروه کار متفاوت بگذارید.',
|
||||
zarin_name_g:
|
||||
'نام گروه حساب مشتریان در زرین (nameG). اگر چند پنل یک توکن دارند، برای هر پنل نام گروه متفاوت بگذارید. خالی = نام برند.',
|
||||
'نام گروه حساب مشتریان در زرین (nameG). برای اپ خانگی معمولاً «مشتری نقدی (خریدار)» (گروه ۶). گروه همکار استفاده نشود چون مظنه فرق دارد. فقط از تنظیمات/دیپلوی تغییر میکند.',
|
||||
zarin_uid_prefix:
|
||||
'پیشوند عددی uid برای جلوگیری از تداخل بین چند پنل روی یک زرین. فقط عدد (مثلاً ۱۱).',
|
||||
montify_name:
|
||||
@@ -918,7 +919,6 @@ export const SETTING_LOCKED_KEYS = new Set([
|
||||
'chart_id', 'chart_use_local_data',
|
||||
'external_order_api_enabled',
|
||||
'fee_deduct_from_gold',
|
||||
'fee_single',
|
||||
'referral_bonus_amount',
|
||||
'group_sms',
|
||||
'customer_direct_sms',
|
||||
|
||||
Reference in New Issue
Block a user