From 3cb1e1a0b0b884664a06c20281b666aeefa8605c Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 19 Jul 2026 15:49:52 +0330 Subject: [PATCH] fix bugs and added some feature for withdraw handling --- src/api/appBanners.ts | 111 ++++++++++++++---- src/pages/menuPages/banners/index.vue | 21 +++- src/pages/menuPages/captureSites/index.vue | 2 + src/pages/menuPages/configuration/index.vue | 10 +- .../menuPages/configuration/settingGroups.ts | 6 +- .../menuPages/configuration/settingTypes.ts | 18 ++- .../CustomerAccountingCodeModal.vue | 107 +++++++++++++++++ .../components/CustomerMobileCardList.vue | 3 + .../components/CustomerMobileDetailSheet.vue | 44 ++++++- .../customers/base/components/table.vue | 68 ++++++++++- .../customers/base/hooks/customerColumns.tsx | 27 ++++- src/pages/menuPages/customers/base/index.vue | 28 ++++- .../products/base/components/table.vue | 12 +- src/pages/menuPages/setting/index.vue | 3 +- src/router/routes/setting.ts | 7 +- src/utils/accountingCode.ts | 17 +++ src/utils/config.ts | 16 +++ 17 files changed, 442 insertions(+), 58 deletions(-) create mode 100644 src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue create mode 100644 src/utils/accountingCode.ts diff --git a/src/api/appBanners.ts b/src/api/appBanners.ts index 8d3d39f..f544f34 100644 --- a/src/api/appBanners.ts +++ b/src/api/appBanners.ts @@ -13,8 +13,23 @@ import { function pickConstRow(res: Const | Const[] | null | undefined): Const | null { if (!res) return null - if (Array.isArray(res)) return (res[0] as Const) ?? null - return res + if (Array.isArray(res)) { + const first = (res[0] as Const) ?? null + return isMeaningfulConstRow(first) ? first : null + } + return isMeaningfulConstRow(res) ? res : null +} + +/** API برای کلید ناموجود گاهی `{}` یا null برمی‌گرداند — نباید مهاجرت را قطع کند */ +function isMeaningfulConstRow(row: Const | null | undefined): row is Const { + if (!row || typeof row !== 'object') return false + const hasId = row.id != null || row.const_id != null + const raw = row.value + const hasValue = + typeof raw === 'string' + ? raw.trim() !== '' + : raw != null && !(typeof raw === 'object' && !Array.isArray(raw) && Object.keys(raw as object).length === 0) + return hasId || hasValue } function parseLegacySlider(value: string | null | undefined): IBenefits[] { @@ -33,24 +48,45 @@ export type AppBannersConstState = { data: AppBannersValue } +async function loadLegacySliderInto(merged: AppBannersValue): Promise { + try { + const sliderRes = await useAxios.get( + `/api/const/key/${LEGACY_SLIDER_BANNERS_CONST_KEY}` + ) + const sliderRow = pickConstRow(sliderRes) + if (!sliderRow) return + const slides = parseLegacySlider(sliderRow.value) + if (slides.length) merged.slider = slides + } catch { + // ignore + } +} + /** * یک API واحد برای همهٔ بنرها. - * اگر `app_banners` نبود، از کلیدهای قدیمی migrate می‌کند (فقط خواندن). + * اگر `app_banners` نبود یا اسلایدر خالی بود، از کلیدهای قدیمی migrate می‌کند (فقط خواندن). */ export async function loadAppBannersConst(): Promise { + let appRow: Const | null = null try { const res = await useAxios.get(`/api/const/key/${APP_BANNERS_CONST_KEY}`) - const row = pickConstRow(res) - if (row) { - return { - id: row.id ?? row.const_id ?? null, - data: parseAppBanners(row.value), - } - } + appRow = pickConstRow(res) } catch { // ادامه با مهاجرت از کلیدهای قدیمی } + if (appRow) { + const data = parseAppBanners(appRow.value) + // رکورد جدید خالی: اسلایدر را از header_banner پر کن + if (!data.slider.length) { + await loadLegacySliderInto(data) + } + return { + id: appRow.id ?? appRow.const_id ?? null, + data, + } + } + const merged = emptyAppBanners() try { @@ -76,23 +112,39 @@ export async function loadAppBannersConst(): Promise { // ignore } - try { - const sliderRes = await useAxios.get( - `/api/const/key/${LEGACY_SLIDER_BANNERS_CONST_KEY}` - ) - const sliderRow = pickConstRow(sliderRes) - if (sliderRow) { - const slides = parseLegacySlider(sliderRow.value) - if (slides.length) merged.slider = slides - } - } catch { - // ignore - } + await loadLegacySliderInto(merged) // id=null → اولین ذخیره، رکورد جدید با key=app_banners می‌سازد return { id: null, data: merged } } +async function syncLegacySliderConst(slides: IBenefits[]): Promise { + const value = JSON.stringify(slides ?? []) + const payload = { + key: LEGACY_SLIDER_BANNERS_CONST_KEY, + value, + title_fa: 'بنرهای اسلایدر', + is_active: true, + description: 'اسلایدر بالای صفحه اصلی اپ مشتری (سازگاری با اپ)', + } + + try { + const existingRes = await useAxios.get( + `/api/const/key/${LEGACY_SLIDER_BANNERS_CONST_KEY}` + ) + const existing = pickConstRow(existingRes) + const existingId = existing?.id ?? existing?.const_id ?? null + if (existingId) { + await useAxios.put(`/admin/const/${existingId}`, { id: existingId, ...payload }) + return + } + } catch { + // create below + } + + await useAxios.post(`/admin/const`, { id: 0, ...payload }) +} + export async function saveAppBannersConst( id: number | null, data: AppBannersValue @@ -106,11 +158,20 @@ export async function saveAppBannersConst( description: 'همهٔ بنرهای اپ: اسلایدر صفحه اصلی + تصاویر تم لایت/دارک', } + let nextId = id if (id) { await useAxios.put(`/admin/const/${id}`, { id, ...payload }) - return id + } else { + const created = await useAxios.post(`/admin/const`, { id: 0, ...payload }) + nextId = created?.id ?? created?.const_id ?? null } - const created = await useAxios.post(`/admin/const`, { id: 0, ...payload }) - return created?.id ?? created?.const_id ?? null + // اپ مشتری هنوز header_banner می‌خواند — اسلایدر را همگام نگه دار + try { + await syncLegacySliderConst(data.slider ?? []) + } catch (error) { + console.error('همگام‌سازی header_banner ناموفق بود:', error) + } + + return nextId } diff --git a/src/pages/menuPages/banners/index.vue b/src/pages/menuPages/banners/index.vue index 28827dc..c146c2b 100644 --- a/src/pages/menuPages/banners/index.vue +++ b/src/pages/menuPages/banners/index.vue @@ -2,7 +2,6 @@
([]) +const { state, saving, save, load } = useAppBannersAdmin() +/** اگر اسلایدری هست، پنل را باز نگه دار تا خالی به‌نظر نرسد */ +const sliderActiveKey = ref<(string | number)[]>(['slider']) const showSettingsProfileBanners = computed(() => hasAppUiConfig()) +watch( + () => state.slider.length, + (len) => { + if (len > 0 && !sliderActiveKey.value.includes('slider')) { + sliderActiveKey.value = ['slider'] + } + } +) + +onMounted(() => { + void load(true) +}) + const typeList = ref([ { lable: 'بدون رویداد', value: null }, { lable: 'باز شدن لینک', value: 'link' }, diff --git a/src/pages/menuPages/captureSites/index.vue b/src/pages/menuPages/captureSites/index.vue index 3d87883..39ef711 100644 --- a/src/pages/menuPages/captureSites/index.vue +++ b/src/pages/menuPages/captureSites/index.vue @@ -117,6 +117,7 @@ import { serializeReygiriSites, serializeTrackingSystemsEnabled, } from '@/utils/reygiriSites' +import { setTrackingSystemsEnabledFromSettings } from '@/utils/config' const sites = ref([]) const loading = ref(false) @@ -224,6 +225,7 @@ async function saveEnabled(checked: boolean) { const value = serializeTrackingSystemsEnabled(checked) await setConfiguration(buildSettingSavePayload(row, value)) enabledRow.value = { ...row, value } + setTrackingSystemsEnabledFromSettings(checked) message.success('وضعیت نمایش سایت‌ها ذخیره شد') } catch (error) { console.error(error) diff --git a/src/pages/menuPages/configuration/index.vue b/src/pages/menuPages/configuration/index.vue index c70e68f..3b40146 100644 --- a/src/pages/menuPages/configuration/index.vue +++ b/src/pages/menuPages/configuration/index.vue @@ -174,7 +174,7 @@ import SettingValueField from './SettingValueField.vue' import SettingCard from './SettingCard.vue' import { refreshAppModeMenu } from '@/router' import { useUserStore } from '@/store/userStore' -import { applyBannerEnabledFromRows, setBannerEnabledFromSettings, setStoreModeFromSettings } from '@/utils/config' +import { applyBannerEnabledFromRows, applyTrackingSystemsEnabledFromRows, setBannerEnabledFromSettings, setStoreModeFromSettings, setTrackingSystemsEnabledFromSettings } from '@/utils/config' import { EDITABLE_SETTING_GROUPS, SYSTEM_SETTING_GROUPS, @@ -424,7 +424,10 @@ async function initConfig() { baseData.value = rows.map(normalizeSettingRow) if (isSystemMode.value) { applyBannerEnabledFromRows(baseData.value) + applyTrackingSystemsEnabledFromRows(baseData.value) } else { + applyBannerEnabledFromRows(baseData.value) + applyTrackingSystemsEnabledFromRows(baseData.value) const storeRow = baseData.value.find((row) => row.key === 'is_store') if (storeRow) { setStoreModeFromSettings(isSwitchOn(storeRow.value)) @@ -535,9 +538,12 @@ async function saveSettingRow(row: Const) { setStoreModeFromSettings(isSwitchOn(value)) refreshMenuAfterStoreChange() } - if (row.key === 'banner_enabled' && isSystemMode.value) { + if (row.key === 'banner_enabled') { setBannerEnabledFromSettings(isSwitchOn(value)) } + if (row.key === 'tracking_systems_enabled') { + setTrackingSystemsEnabledFromSettings(isSwitchOn(value)) + } savedFlashIds.value.add(id) if (saveTimers.has(id)) clearTimeout(saveTimers.get(id)) saveTimers.set( diff --git a/src/pages/menuPages/configuration/settingGroups.ts b/src/pages/menuPages/configuration/settingGroups.ts index b9697d3..ee3fcab 100644 --- a/src/pages/menuPages/configuration/settingGroups.ts +++ b/src/pages/menuPages/configuration/settingGroups.ts @@ -12,7 +12,7 @@ export const EDITABLE_SETTING_GROUPS: SettingGroupDef[] = [ title: 'عمومی و برند', icon: 'vuesax-linear:home-2', match: (k) => - /^(name_fa|name_en|bace_url|server_url|logo_|title_pwa|title_fav|open_register|auto_active_customer|store_status|key$)/.test( + /^(name_fa|name_en|bace_url|server_url|logo_|title_pwa|title_fav|open_register|auto_active_customer|store_status|key$|tracking_systems_enabled|banner_enabled)$/.test( k ) }, @@ -119,7 +119,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [ title: 'عمومی و برند', icon: 'vuesax-linear:home-2', match: (k) => - /^(name_fa|name_en|app_url|admin_url|apk_download_link|banner_enabled|permision__enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|customer_direct_sms|has_page_earn_money|sms_panel)/.test( + /^(name_fa|name_en|app_url|admin_url|apk_download_link|permision__enabled|bace_url|server_url|logo_|title_pwa|title_fav|metal_type|main_metal|unit$|open_register|auto_active_customer|store_status|key$|is_store|group_sms|customer_direct_sms|has_page_earn_money|sms_panel)/.test( k ) }, @@ -176,7 +176,7 @@ export const SYSTEM_SETTING_GROUPS: SettingGroupDef[] = [ id: 'infra', title: 'زیرساخت', icon: 'vuesax-linear:cpu', - match: (k) => /^(wss_enabled|sse_path|profit_gold_api_enabled|tracking_systems|tracking_systems_enabled)$/.test(k) + match: (k) => /^(wss_enabled|sse_path|profit_gold_api_enabled)$/.test(k) }, { id: 'misc', diff --git a/src/pages/menuPages/configuration/settingTypes.ts b/src/pages/menuPages/configuration/settingTypes.ts index eceb22f..3f5037a 100644 --- a/src/pages/menuPages/configuration/settingTypes.ts +++ b/src/pages/menuPages/configuration/settingTypes.ts @@ -187,6 +187,8 @@ export const HIDDEN_LEGACY_SETTING_KEYS = new Set([ 'service_charge_percentage', 'support_messenger', 'support_messenger_id', + // JSON از صفحهٔ اختصاصی «سایت‌های ری‌گیری» مدیریت می‌شود؛ سوییچ نمایش در تنظیمات می‌ماند + 'tracking_systems', ]) /** فقط وقتی is_store فعال است در پنل دیده می‌شوند */ @@ -234,6 +236,7 @@ const KEY_TYPE_FALLBACK: Record = { sell_fee: 'price', fee_free: 'number', min_withdraw: 'price', + min_deposit: 'price', min_first_buy: 'price', minimum_sell: 'price', gift_for_referrer: 'number', @@ -514,7 +517,7 @@ const KEY_HELP_FALLBACK: Record = { name_fa: 'نام فارسی برند که در پیامک‌ها، عنوان صفحات و بخش‌های عمومی اپ به مشتری نشان داده می‌شود.', banner_enabled: - 'روشن: منوی «بنرها» در تنظیمات فعال می‌شود و می‌توانید اسلایدر بالای صفحهٔ اصلی اپ را مدیریت کنید. خاموش: بنرها در اپ نشان داده نمی‌شوند.', + 'روشن: منوی «بنرها» در ادمین و اسلایدر صفحه اصلی اپ مشتری نمایش داده می‌شود. خاموش: مخفی است.', permision__enabled: 'روشن: آیتم «مجوز ها» در منوی تنظیمات اپ خانگی (تم جدید) نمایش داده می‌شود. خاموش: مخفی است.', open_register: @@ -549,6 +552,8 @@ const KEY_HELP_FALLBACK: Record = { 'تعداد اولین سفارش‌های هر کاربر که از کارمزد معاف می‌شوند. صفر یعنی غیرفعال.', min_withdraw: 'حداقل مبلغ درخواست برداشت (ریال). درخواست‌های کمتر پذیرفته نمی‌شوند.', + min_deposit: + 'حداقل مبلغ واریز به کیف پول (ریال). مبالغ کمتر در اپ و درگاه پذیرفته نمی‌شوند.', min_first_buy: 'حداقل مبلغ اولین خرید هر کاربر (ریال).', minimum_sell: @@ -608,9 +613,9 @@ const KEY_HELP_FALLBACK: Record = { support_messengers: 'فقط مواردی که در این لیست ثبت شوند در منوی شناور پشتیبانی اپ مشتری نمایش داده می‌شوند. برای تماس تلفنی یا پیامک، نوع «تماس تلفنی» / «پیامک» را با شماره اضافه کنید.', tracking_systems: - 'لیست JSON سامانه‌های ردیابی/آنالیتیکس در اپ مشتری. هر مورد: type (gtag، gtm، clarity، custom)، id (کد اندازه‌گیری یا شناسه پروژه)، enabled (روشن/خاموش). فقط از صفحه دیپلوی قابل ویرایش است.', + 'لیست سایت‌های ری‌گیری (name/url) — از منوی «سایت‌های ری‌گیری» ویرایش کنید.', tracking_systems_enabled: - 'روشن: وقتی tracking_systems پر باشد، اسکریپت‌های ردیابی در اپ مشتری بارگذاری می‌شوند. خاموش: هیچ سامانه ردیابی نمایش داده نمی‌شود — حتی اگر JSON پر باشد.', + 'روشن: منوی «سایت‌های ری‌گیری» در ادمین و اپ مشتری نمایش داده می‌شود. خاموش: مخفی است.', support_messenger: 'پیام‌رسانی که آیکون کناری و دکمه شناور پشتیبانی در اپ مشتری (درباره ما، قوانین، آموزش و راهنمای واریز) به آن هدایت می‌کند.', support_messenger_id: @@ -771,6 +776,7 @@ export const KEY_LABEL_FALLBACK: Record = { fee_deduct_from_gold: 'اعمال مبلغ دقیق در معامله مشتری', fee_free: 'معافیت کارمزد سفارش اول', min_withdraw: 'حداقل مبلغ برداشت', + min_deposit: 'حداقل مبلغ واریز کیف پول', min_first_buy: 'حداقل اولین خرید', minimum_sell: 'حداقل مبلغ فروش (ریال)', referral_id: 'کد معرف اجباری', @@ -808,8 +814,8 @@ export const KEY_LABEL_FALLBACK: Record = { send_to_tahesab: 'ارسال خودکار به ته‌حساب (قدیمی)', send_to_pargar: 'ارسال خودکار به پرگار (قدیمی)', support_messengers: 'پیام‌رسان‌های پشتیبانی', - tracking_systems: 'سامانه‌های ردیابی', - tracking_systems_enabled: 'نمایش سامانه‌های ردیابی', + tracking_systems: 'سایت‌های ری‌گیری', + tracking_systems_enabled: 'نمایش سایت‌های ری‌گیری', permision__enabled: 'نمایش منوی مجوزها', support_messenger: 'پیام‌رسان پشتیبانی', support_messenger_id: 'شناسه پشتیبانی', @@ -891,7 +897,7 @@ export const SETTING_EDITABLE_KEYS = new Set([ 'invite_link_text', 'jibit_verification', 'main_product_price', - 'min_first_buy', 'min_withdraw', 'minimum_sell', + 'min_first_buy', 'min_withdraw', 'min_deposit', 'minimum_sell', 'montzar_mobile', 'montzar_open_buy', 'montzar_open_sell', 'montzar_password', 'montzar_price', 'montzar_product_id', 'open_register', diff --git a/src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue b/src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue new file mode 100644 index 0000000..a04cf63 --- /dev/null +++ b/src/pages/menuPages/customers/base/components/CustomerAccountingCodeModal.vue @@ -0,0 +1,107 @@ + + + diff --git a/src/pages/menuPages/customers/base/components/CustomerMobileCardList.vue b/src/pages/menuPages/customers/base/components/CustomerMobileCardList.vue index e2e4686..4b2816d 100644 --- a/src/pages/menuPages/customers/base/components/CustomerMobileCardList.vue +++ b/src/pages/menuPages/customers/base/components/CustomerMobileCardList.vue @@ -29,6 +29,7 @@ @toggleTrade="forward('toggleTrade', $event)" @confirmUser="forwardAndClose('confirmUser', $event)" @sendSms="forwardAndClose('sendSms', $event)" + @editAccounting="forwardAndClose('editAccounting', $event)" /> @@ -59,6 +60,7 @@ const emit = defineEmits<{ (e: 'toggleTrade', item: Customer): void (e: 'confirmUser', item: Customer): void (e: 'sendSms', item: Customer): void + (e: 'editAccounting', item: Customer): void }>() const { sheetVisible, selectedItem, openSheet, syncSelectedItem } = useMobileDetailSheet() @@ -72,6 +74,7 @@ type SheetAction = | 'toggleTrade' | 'confirmUser' | 'sendSms' + | 'editAccounting' function forward(action: SheetAction, item: Customer) { emit(action, item) diff --git a/src/pages/menuPages/customers/base/components/CustomerMobileDetailSheet.vue b/src/pages/menuPages/customers/base/components/CustomerMobileDetailSheet.vue index 3cbab5c..b81e206 100644 --- a/src/pages/menuPages/customers/base/components/CustomerMobileDetailSheet.vue +++ b/src/pages/menuPages/customers/base/components/CustomerMobileDetailSheet.vue @@ -37,6 +37,15 @@
+ + کد حسابداری + مشخصات @@ -84,6 +93,11 @@ + @@ -94,7 +108,7 @@