1405/04/27
-
This commit is contained in:
Vendored
+1
@@ -64,6 +64,7 @@ declare module 'vue' {
|
||||
Size: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/size.vue')['default']
|
||||
StartEnd: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/start-end.vue')['default']
|
||||
Suffix: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/suffix.vue')['default']
|
||||
SupportContactBar: typeof import('./src/components/SupportContactBar.vue')['default']
|
||||
SupportMessengerBrandIcon: typeof import('./src/components/SupportMessengerBrandIcon.vue')['default']
|
||||
SupportMessengerPicker: typeof import('./src/components/SupportMessengerPicker.vue')['default']
|
||||
Switchable: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/switchable.vue')['default']
|
||||
|
||||
+20
-44
@@ -67,8 +67,8 @@
|
||||
<SupportMessengerPicker
|
||||
v-model:open="supportPickerOpen"
|
||||
:entries="supportMessengerEntries"
|
||||
:anchor-el="supportButtonEl"
|
||||
@select="openSupportMessengerEntry"
|
||||
:anchor-el="pickerAnchorEl"
|
||||
@select="openMessengerEntry"
|
||||
/>
|
||||
|
||||
<VersionUpdateModal />
|
||||
@@ -84,13 +84,8 @@ import img_topGradiant from '@/views/login/assets/img/gr_top_white.svg'
|
||||
import img_pattern from '@/views/login/assets/img/pattern.svg'
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import {
|
||||
loadSupportMessengerFromSettings,
|
||||
openSupportMessengerEntry,
|
||||
resolveSupportMessengerEntries,
|
||||
supportMessengerPickerOpen,
|
||||
supportMessengerSettings,
|
||||
} from '@/utils/supportMessenger'
|
||||
import { useSupportMessenger } from '@/composables/useSupportMessenger'
|
||||
import { loadSupportMessengerFromSettings } from '@/utils/supportMessenger'
|
||||
import SupportMessengerPicker from '@/components/SupportMessengerPicker.vue'
|
||||
import { useDraggableFloatingButton } from '@/utils/useDraggableFloatingButton'
|
||||
import { useApplyDesktopLayoutClass } from '@/layouts/composables/useAppLayout'
|
||||
@@ -113,42 +108,27 @@ const showSplash = ref(true)
|
||||
|
||||
useApplyDesktopLayoutClass()
|
||||
|
||||
const {
|
||||
supportPickerOpen,
|
||||
supportPickerAnchor,
|
||||
supportMessengerEntries,
|
||||
messengerMeta,
|
||||
hasMessengerLink: showSupportButton,
|
||||
openMessengerEntry,
|
||||
openSupportMessenger,
|
||||
} = useSupportMessenger()
|
||||
|
||||
const supportButtonEl = ref<HTMLElement | null>(null)
|
||||
const supportPickerOpen = supportMessengerPickerOpen
|
||||
const pickerAnchorEl = computed(() => supportPickerAnchor.value ?? supportButtonEl.value)
|
||||
|
||||
const supportMessengerEntries = computed(() =>
|
||||
resolveSupportMessengerEntries(supportMessengerSettings.value),
|
||||
)
|
||||
|
||||
const showSupportButton = computed(() => supportMessengerEntries.value.length > 0)
|
||||
|
||||
const primaryEntry = computed(() => supportMessengerEntries.value[0] ?? null)
|
||||
|
||||
const supportIcon = computed(() => primaryEntry.value?.icon ?? 'vuesax-linear:messages-2')
|
||||
const supportIcon = computed(() => messengerMeta.value.icon)
|
||||
|
||||
const supportAccentColor = computed(() =>
|
||||
(primaryEntry.value?.accent ?? colors.primary) || '#334155',
|
||||
(messengerMeta.value.accent !== '#334155'
|
||||
? messengerMeta.value.accent
|
||||
: colors.primary) || '#334155',
|
||||
)
|
||||
|
||||
let supportPickerOpenedAt = 0
|
||||
|
||||
function openSupportLink() {
|
||||
const entries = supportMessengerEntries.value
|
||||
if (entries.length === 0) return
|
||||
if (entries.length === 1) {
|
||||
void openSupportMessengerEntry(entries[0])
|
||||
return
|
||||
}
|
||||
const now = Date.now()
|
||||
if (supportPickerOpen.value) {
|
||||
if (now - supportPickerOpenedAt < 450) return
|
||||
supportPickerOpen.value = false
|
||||
return
|
||||
}
|
||||
supportPickerOpen.value = true
|
||||
supportPickerOpenedAt = now
|
||||
}
|
||||
|
||||
const {
|
||||
dragging: supportDragging,
|
||||
fabStyle: supportButtonStyle,
|
||||
@@ -163,15 +143,11 @@ const {
|
||||
defaultRightOffset: 16,
|
||||
})
|
||||
|
||||
function openSupportLinkHandler() {
|
||||
openSupportLink()
|
||||
}
|
||||
|
||||
function handleSupportPointerUp(event: PointerEvent) {
|
||||
const clicked = supportPointerUp(event)
|
||||
if (!clicked) return
|
||||
event.stopPropagation()
|
||||
openSupportLinkHandler()
|
||||
void openSupportMessenger(supportButtonEl.value)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<circle cx="128" cy="128" r="124" fill="#12B76A"/>
|
||||
<!-- Bootstrap Icons telephone-fill — گوشی کامل -->
|
||||
<g transform="translate(48 48) scale(10)">
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M1.885.511a1.745 1.745 0 0 1 2.612.163L6.29 2.98c.329.423.445.967.315 1.49l-.547 2.19a.678.678 0 0 0 .178.643l2.457 2.457a.678.678 0 0 0 .644.178l2.189-.547a1.745 1.745 0 0 1 1.49.315l2.306 1.794c.829.645.905 1.87.163 2.611l-1.034 1.034c-.74.74-1.846 1.065-2.877.702a18.634 18.634 0 0 1-7.01-4.42 18.634 18.634 0 0 1-4.42-7.009c-.362-1.03-.038-2.137.703-2.877L1.885.511z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 680 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256" fill="none">
|
||||
<circle cx="128" cy="128" r="128" fill="#3B82F6"/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M78 76h100c11 0 20 9 20 20v64c0 11-9 20-20 20h-52l-30 26v-26H78c-11 0-20-9-20-20V96c0-11 9-20 20-20zm16 40h68v14H94v-14zm0 28h48v14H94v-14z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 337 B |
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div :class="variant === 'desktop' ? desktopRootClass : mobileRootClass">
|
||||
<button
|
||||
ref="triggerEl"
|
||||
type="button"
|
||||
class="support-contact-bar__btn"
|
||||
:disabled="!canContactSupport"
|
||||
@click="onSupportClick"
|
||||
>
|
||||
<BasicIcon icon="vuesax-linear:messages-2" size="20" color="#fff" />
|
||||
<span>ارتباط با پشتیبانی</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useSupportMessenger } from '@/composables/useSupportMessenger'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
variant?: 'mobile' | 'desktop'
|
||||
mobileRootClass?: string
|
||||
desktopRootClass?: string
|
||||
}>(),
|
||||
{
|
||||
variant: 'mobile',
|
||||
mobileRootClass: 'wrap-fix bottom-0 px-10 pb-3rem flex bg-white',
|
||||
desktopRootClass: 'dpage-support',
|
||||
},
|
||||
)
|
||||
|
||||
const {
|
||||
supportPhoneLink,
|
||||
hasMessengerLink,
|
||||
hasSupportPhoneLink,
|
||||
openSupportMessenger,
|
||||
} = useSupportMessenger()
|
||||
|
||||
const triggerEl = ref<HTMLElement | null>(null)
|
||||
|
||||
const canContactSupport = computed(() => hasMessengerLink.value || hasSupportPhoneLink.value)
|
||||
|
||||
async function onSupportClick() {
|
||||
// همان رفتار دکمه شناور پشتیبانی — بدون پیشنویس واریز
|
||||
if (hasMessengerLink.value) {
|
||||
await openSupportMessenger(triggerEl.value)
|
||||
return
|
||||
}
|
||||
|
||||
if (hasSupportPhoneLink.value && supportPhoneLink.value) {
|
||||
window.location.href = supportPhoneLink.value
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.support-contact-bar__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
min-height: 2.75rem;
|
||||
padding: 0.65rem 1rem;
|
||||
border: 0;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--App-primary, var(--ant-color-primary, #7f1d1d));
|
||||
color: #fff;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.dpage-support {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -21,8 +21,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import baleLogo from '@/assets/messengers/bale.png'
|
||||
import callLogo from '@/assets/messengers/call.svg'
|
||||
import eitaaLogo from '@/assets/messengers/eitaa.png'
|
||||
import rubikaLogo from '@/assets/messengers/rubika.png'
|
||||
import smsLogo from '@/assets/messengers/sms.svg'
|
||||
import soroushLogo from '@/assets/messengers/soroush.svg'
|
||||
import telegramLogo from '@/assets/messengers/telegram.svg'
|
||||
import whatsappLogo from '@/assets/messengers/whatsapp.svg'
|
||||
@@ -34,6 +36,8 @@ const LOGOS: Record<string, string> = {
|
||||
rubika: rubikaLogo,
|
||||
eitaa: eitaaLogo,
|
||||
soroush: soroushLogo,
|
||||
call: callLogo,
|
||||
sms: smsLogo,
|
||||
}
|
||||
|
||||
const props = defineProps<{ messenger: string }>()
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
buildDepositSmsUrl,
|
||||
buildSupportPhoneUrl,
|
||||
loadSupportMessengerFromSettings,
|
||||
messengerNeedsClipboardCopy,
|
||||
openSupportMessengerEntry,
|
||||
openSupportMessengerWithMessage,
|
||||
resolveDepositGuideText,
|
||||
resolveDepositSupportMessage,
|
||||
resolveSupportMessengerEntries,
|
||||
resolveSupportMessengerLink,
|
||||
resolveSupportMessengerMeta,
|
||||
supportMessengerPickerAnchor,
|
||||
supportMessengerPickerOpen,
|
||||
supportMessengerSettings,
|
||||
type ResolvedSupportMessengerEntry,
|
||||
} from '@/utils/supportMessenger'
|
||||
import {
|
||||
copyDepositFieldValue,
|
||||
displayDepositCardNumber,
|
||||
displayDepositSheba,
|
||||
getDepositGuideVarsFromSettings,
|
||||
getDepositSettingLabel,
|
||||
hasDepositFieldValue,
|
||||
} from '@/utils/depositGuideText'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
|
||||
/** plain: منوی شناور / درباره ما — بدون پیشمتن | deposit: مودال واریز — با deposit_support_message_text */
|
||||
type SupportPickerIntent = 'plain' | 'deposit'
|
||||
|
||||
export function useSupportMessenger() {
|
||||
const settingsStore = useSettingsStore()
|
||||
const supportPickerOpen = supportMessengerPickerOpen
|
||||
const supportPickerIntent = ref<SupportPickerIntent>('plain')
|
||||
|
||||
const legacySupportLink = computed(() => '')
|
||||
|
||||
const depositSupportMessage = computed(() => resolveDepositSupportMessage())
|
||||
|
||||
/** لینکهای تمیز بدون پیشمتن — متن فقط موقع باز کردن (قصد deposit) اضافه میشود */
|
||||
const supportMessengerEntries = computed(() =>
|
||||
resolveSupportMessengerEntries(supportMessengerSettings.value),
|
||||
)
|
||||
|
||||
const primaryMessengerEntry = computed(() => supportMessengerEntries.value[0] ?? null)
|
||||
|
||||
const messengerType = computed(
|
||||
() => (primaryMessengerEntry.value?.messenger ?? supportMessengerSettings.value.messenger ?? '').trim().toLowerCase(),
|
||||
)
|
||||
|
||||
const messengerMeta = computed(() =>
|
||||
primaryMessengerEntry.value
|
||||
? {
|
||||
label: primaryMessengerEntry.value.label,
|
||||
icon: primaryMessengerEntry.value.icon,
|
||||
accent: primaryMessengerEntry.value.accent,
|
||||
}
|
||||
: resolveSupportMessengerMeta(messengerType.value),
|
||||
)
|
||||
|
||||
const messengerLink = computed(() =>
|
||||
primaryMessengerEntry.value?.url
|
||||
|| resolveSupportMessengerLink(
|
||||
supportMessengerSettings.value.messenger,
|
||||
supportMessengerSettings.value.messengerId,
|
||||
legacySupportLink.value,
|
||||
),
|
||||
)
|
||||
|
||||
async function openMessengerEntry(entry: ResolvedSupportMessengerEntry, text?: string) {
|
||||
// فقط وقتی از مودال واریز آمدهایم پیشنویس میگذاریم؛ منوی شناور همیشه خالی
|
||||
const messageText =
|
||||
text !== undefined
|
||||
? text.trim()
|
||||
: supportPickerIntent.value === 'deposit'
|
||||
? depositSupportMessage.value.trim()
|
||||
: ''
|
||||
const opened = await openSupportMessengerEntry(entry, messageText, legacySupportLink.value)
|
||||
if (!opened) return false
|
||||
|
||||
if (messageText && messengerNeedsClipboardCopy(entry.messenger)) {
|
||||
message.success('متن پیام کپی شد — در پیامرسان paste کنید')
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
let supportPickerOpenedAt = 0
|
||||
|
||||
async function openSupportPicker(
|
||||
intent: SupportPickerIntent,
|
||||
anchorEl?: HTMLElement | null,
|
||||
) {
|
||||
supportPickerIntent.value = intent
|
||||
if (anchorEl !== undefined) {
|
||||
supportMessengerPickerAnchor.value = anchorEl
|
||||
}
|
||||
|
||||
const draft =
|
||||
intent === 'deposit' ? depositSupportMessage.value.trim() : ''
|
||||
const entries = supportMessengerEntries.value
|
||||
|
||||
if (entries.length === 0) {
|
||||
return openSupportMessengerWithMessage(
|
||||
supportMessengerSettings.value.messenger,
|
||||
supportMessengerSettings.value.messengerId,
|
||||
draft,
|
||||
legacySupportLink.value,
|
||||
)
|
||||
}
|
||||
|
||||
if (entries.length === 1) {
|
||||
return openMessengerEntry(entries[0], draft)
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
if (supportPickerOpen.value) {
|
||||
if (now - supportPickerOpenedAt < 450) return true
|
||||
supportPickerOpen.value = false
|
||||
return true
|
||||
}
|
||||
|
||||
supportPickerOpen.value = true
|
||||
supportPickerOpenedAt = now
|
||||
return true
|
||||
}
|
||||
|
||||
/** منوی شناور / صفحات درباره ما — بدون پیشنویس پیام */
|
||||
async function openSupportMessenger(anchorEl?: HTMLElement | null) {
|
||||
return openSupportPicker('plain', anchorEl)
|
||||
}
|
||||
|
||||
/** مودال راهنمای واریز — با متن deposit_support_message_text */
|
||||
async function openDepositSupportMessenger(anchorEl?: HTMLElement | null) {
|
||||
return openSupportPicker('deposit', anchorEl)
|
||||
}
|
||||
|
||||
function handleSupportMessengerClick(event?: Event) {
|
||||
const target = event?.currentTarget
|
||||
const anchor = target instanceof HTMLElement ? target : null
|
||||
void openSupportMessenger(anchor)
|
||||
}
|
||||
|
||||
const depositSmsLink = computed(() =>
|
||||
buildDepositSmsUrl(
|
||||
supportMessengerSettings.value.depositSmsSupportNumber,
|
||||
depositSupportMessage.value,
|
||||
),
|
||||
)
|
||||
|
||||
const supportPhoneLink = computed(() => {
|
||||
const fromSettings = buildSupportPhoneUrl(
|
||||
supportMessengerSettings.value.depositSmsSupportNumber,
|
||||
)
|
||||
if (fromSettings) return fromSettings
|
||||
|
||||
const legacy = legacySupportLink.value
|
||||
if (legacy.startsWith('tel:') && legacy.length > 4) return legacy
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
const supportMessengerText = computed(() => resolveDepositGuideText())
|
||||
|
||||
const depositCardNumberRaw = computed(() => {
|
||||
void settingsStore.settings
|
||||
const fromMessenger = supportMessengerSettings.value.depositCardNumber?.trim()
|
||||
if (fromMessenger) return fromMessenger
|
||||
return String(getDepositGuideVarsFromSettings().cardNumber ?? '').trim()
|
||||
})
|
||||
|
||||
const depositShebaRaw = computed(() => {
|
||||
void settingsStore.settings
|
||||
const fromMessenger = supportMessengerSettings.value.depositSheba?.trim()
|
||||
if (fromMessenger) return fromMessenger
|
||||
return String(getDepositGuideVarsFromSettings().sheba ?? '').trim()
|
||||
})
|
||||
|
||||
const depositCardNumber = computed(() => displayDepositCardNumber(depositCardNumberRaw.value))
|
||||
const depositSheba = computed(() => displayDepositSheba(depositShebaRaw.value))
|
||||
|
||||
const depositCardLabel = computed(() => {
|
||||
void settingsStore.settings
|
||||
return getDepositSettingLabel('deposit_card_number', 'شماره کارت واریز')
|
||||
})
|
||||
|
||||
const depositShebaLabel = computed(() => {
|
||||
void settingsStore.settings
|
||||
return getDepositSettingLabel('deposit_sheba', 'شبا واریز')
|
||||
})
|
||||
|
||||
const hasDepositCardValue = computed(() => hasDepositFieldValue(depositCardNumberRaw.value))
|
||||
const hasDepositShebaValue = computed(() => hasDepositFieldValue(depositShebaRaw.value))
|
||||
|
||||
async function copyDepositCardNumber() {
|
||||
await copyDepositFieldValue(
|
||||
depositCardNumberRaw.value,
|
||||
'card',
|
||||
'شماره کارت ثبت نشده است',
|
||||
)
|
||||
}
|
||||
|
||||
async function copyDepositShebaNumber() {
|
||||
await copyDepositFieldValue(
|
||||
depositShebaRaw.value,
|
||||
'sheba',
|
||||
'شماره شبا ثبت نشده است',
|
||||
)
|
||||
}
|
||||
|
||||
const hasMessengerLink = computed(() => supportMessengerEntries.value.length > 0 || !!messengerLink.value)
|
||||
const hasMultipleMessengers = computed(() => supportMessengerEntries.value.length > 1)
|
||||
const hasSupportPhoneLink = computed(() => !!supportPhoneLink.value)
|
||||
const hasSupportActions = computed(() => hasSupportPhoneLink.value || hasMessengerLink.value)
|
||||
const opensInNewTab = computed(() => /^https?:\/\//i.test(messengerLink.value))
|
||||
|
||||
return {
|
||||
supportPickerOpen,
|
||||
supportPickerAnchor: supportMessengerPickerAnchor,
|
||||
supportMessengerEntries,
|
||||
messengerType,
|
||||
messengerMeta,
|
||||
messengerLink,
|
||||
supportPhoneLink,
|
||||
depositSmsLink,
|
||||
supportMessengerText,
|
||||
depositSupportMessage,
|
||||
depositCardNumber,
|
||||
depositSheba,
|
||||
depositCardNumberRaw,
|
||||
depositShebaRaw,
|
||||
depositCardLabel,
|
||||
depositShebaLabel,
|
||||
hasDepositCardValue,
|
||||
hasDepositShebaValue,
|
||||
copyDepositCardNumber,
|
||||
copyDepositShebaNumber,
|
||||
openSupportMessenger,
|
||||
openDepositSupportMessenger,
|
||||
openMessengerEntry,
|
||||
handleSupportMessengerClick,
|
||||
hasMessengerLink,
|
||||
hasMultipleMessengers,
|
||||
hasSupportPhoneLink,
|
||||
hasSupportActions,
|
||||
opensInNewTab,
|
||||
loadSupportMessengerFromSettings,
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,28 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
return item?.value ?? null
|
||||
}
|
||||
|
||||
function mergeSettings(items?: AppSetting[] | null) {
|
||||
if (!items?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const merged = [...settings.value]
|
||||
items.forEach((item) => {
|
||||
if (!item?.key) {
|
||||
return
|
||||
}
|
||||
|
||||
const index = merged.findIndex((setting) => setting.key === item.key)
|
||||
if (index >= 0) {
|
||||
merged[index] = { ...merged[index], ...item }
|
||||
} else {
|
||||
merged.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
settings.value = merged
|
||||
}
|
||||
|
||||
async function fetchSettings(force = false) {
|
||||
if (!force && fetchPromise) {
|
||||
return fetchPromise
|
||||
@@ -31,7 +53,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
try {
|
||||
const data = await getSettings()
|
||||
if (Array.isArray(data)) {
|
||||
settings.value = data
|
||||
mergeSettings(data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
@@ -49,6 +71,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
settings,
|
||||
loaded,
|
||||
fetchSettings,
|
||||
mergeSettings,
|
||||
getSettingByKey,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
|
||||
export interface DepositGuideVars {
|
||||
accountHolder?: string | null
|
||||
cardNumber?: string | null
|
||||
sheba?: string | null
|
||||
/** نام مشتری واردشده — برای پیام پشتیبانی واریز */
|
||||
customerName?: string | null
|
||||
}
|
||||
|
||||
export const DEFAULT_DEPOSIT_GUIDE_TEXT =
|
||||
'جهت شارژ حساب کاربری خود، کلمه «شماره حساب» را برای پشتیبانی ارسال نمایید.'
|
||||
|
||||
export const DEFAULT_DEPOSIT_SUPPORT_MESSAGE_TEXT =
|
||||
'باسلام، %نام هستم. کدپیگیری و مبلغ واریزی من:'
|
||||
|
||||
/** الگوی متغیر نام — ٪نام / %نام / % نام / %نام% */
|
||||
const NAME_PLACEHOLDER_RE = /[%٪]\s*نام(?:\s*[%٪])?/gu
|
||||
const CARD_PLACEHOLDER_RE = /[%٪]\s*کارت(?:\s*[%٪])?/gu
|
||||
const SHEBA_PLACEHOLDER_RE = /[%٪]\s*شبا(?:\s*[%٪])?/gu
|
||||
|
||||
export function getCustomerNameFromUser(): string {
|
||||
try {
|
||||
const user = useUserStore().userInfo
|
||||
const fullName = [user?.fname, user?.lname].filter(Boolean).join(' ').trim()
|
||||
return fullName
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/** نام نمایشی در پیام پشتیبانی: اول مشتری، بعد نام صاحب حساب تنظیمات */
|
||||
function resolveSupportMessageName(vars: DepositGuideVars): string {
|
||||
const customerName = String(vars.customerName ?? '').trim()
|
||||
if (customerName) return customerName
|
||||
return String(vars.accountHolder ?? '').trim()
|
||||
}
|
||||
|
||||
/** فقط %نام — برای پیام SMS/پیامرسان پشتیبانی (نه کارت/شبا) */
|
||||
export function renderDepositSupportMessageText(
|
||||
template: string,
|
||||
vars: DepositGuideVars,
|
||||
): string {
|
||||
const name = resolveSupportMessageName(vars)
|
||||
return template.replace(NAME_PLACEHOLDER_RE, name)
|
||||
}
|
||||
|
||||
/** جایگزینی متغیرها در متن راهنمای واریز — ٪نام / %نام، ٪کارت / %کارت، ٪شبا / %شبا */
|
||||
export function renderDepositGuideText(
|
||||
template: string,
|
||||
vars: DepositGuideVars,
|
||||
): string {
|
||||
const accountHolder = String(vars.accountHolder ?? '').trim()
|
||||
const cardNumber = String(vars.cardNumber ?? '').trim()
|
||||
const sheba = String(vars.sheba ?? '').trim()
|
||||
|
||||
return template
|
||||
.replace(SHEBA_PLACEHOLDER_RE, sheba)
|
||||
.replace(CARD_PLACEHOLDER_RE, cardNumber)
|
||||
.replace(NAME_PLACEHOLDER_RE, accountHolder)
|
||||
}
|
||||
|
||||
export function getDepositGuideVarsFromSettings(): DepositGuideVars {
|
||||
const customerName = getCustomerNameFromUser()
|
||||
try {
|
||||
const store = useSettingsStore()
|
||||
return {
|
||||
accountHolder: store.getSettingByKey('deposit_account_holder') as string | null,
|
||||
cardNumber: store.getSettingByKey('deposit_card_number') as string | null,
|
||||
sheba: store.getSettingByKey('deposit_sheba') as string | null,
|
||||
customerName,
|
||||
}
|
||||
} catch {
|
||||
return { customerName }
|
||||
}
|
||||
}
|
||||
|
||||
export function getDepositGuideTemplate(): string {
|
||||
try {
|
||||
const fromSettings = String(useSettingsStore().getSettingByKey('support_messenger_text') ?? '').trim()
|
||||
if (fromSettings) return fromSettings
|
||||
} catch {
|
||||
// settings store may not be ready yet
|
||||
}
|
||||
return DEFAULT_DEPOSIT_GUIDE_TEXT
|
||||
}
|
||||
|
||||
export function getDepositSupportMessageTemplate(): string {
|
||||
try {
|
||||
const fromSettings = String(
|
||||
useSettingsStore().getSettingByKey('deposit_support_message_text') ?? '',
|
||||
).trim()
|
||||
if (fromSettings) return fromSettings
|
||||
} catch {
|
||||
// settings store may not be ready yet
|
||||
}
|
||||
return DEFAULT_DEPOSIT_SUPPORT_MESSAGE_TEXT
|
||||
}
|
||||
|
||||
export function getRenderedDepositGuideText(vars?: DepositGuideVars): string {
|
||||
return renderDepositGuideText(getDepositGuideTemplate(), vars ?? getDepositGuideVarsFromSettings())
|
||||
}
|
||||
|
||||
export function getRenderedDepositSupportMessage(vars?: DepositGuideVars): string {
|
||||
return renderDepositSupportMessageText(
|
||||
getDepositSupportMessageTemplate(),
|
||||
vars ?? getDepositGuideVarsFromSettings(),
|
||||
)
|
||||
}
|
||||
|
||||
export function formatDepositCardNumber(value?: string | null) {
|
||||
const digits = String(value ?? '').replace(/\D/g, '')
|
||||
if (!digits) return ''
|
||||
|
||||
return digits.replace(/(\d{4})(?=\d)/g, '$1 ').trim()
|
||||
}
|
||||
|
||||
export function formatDepositSheba(value?: string | null) {
|
||||
const normalized = String(value ?? '').replace(/\s/g, '').toUpperCase()
|
||||
if (!normalized) return ''
|
||||
|
||||
const digits = normalized.replace(/^IR/i, '').replace(/\D/g, '')
|
||||
if (!digits) return normalized
|
||||
|
||||
const full = `IR${digits}`
|
||||
return full.replace(/(.{4})/g, '$1 ').trim()
|
||||
}
|
||||
|
||||
export function hasDepositFieldValue(value?: string | null) {
|
||||
return Boolean(String(value ?? '').trim())
|
||||
}
|
||||
|
||||
export function displayDepositCardNumber(value?: string | null) {
|
||||
return formatDepositCardNumber(value) || '**** **** **** ****'
|
||||
}
|
||||
|
||||
export function displayDepositSheba(value?: string | null) {
|
||||
return formatDepositSheba(value) || 'IR** **** **** **** **** **** **'
|
||||
}
|
||||
|
||||
export function getDepositSettingLabel(key: string, fallback: string) {
|
||||
try {
|
||||
const item = useSettingsStore().settings.find(
|
||||
(setting) => String(setting?.key ?? '') === key,
|
||||
)
|
||||
return item?.title_fa?.trim() || fallback
|
||||
} catch {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDepositCopyValue(value: string, kind: 'card' | 'sheba') {
|
||||
const normalized = value.replace(/\s/g, '')
|
||||
if (!normalized) return ''
|
||||
|
||||
if (kind === 'sheba') {
|
||||
const upper = normalized.toUpperCase()
|
||||
if (upper.startsWith('IR')) return upper
|
||||
const digits = upper.replace(/\D/g, '')
|
||||
return digits ? `IR${digits}` : upper
|
||||
}
|
||||
|
||||
return normalized.replace(/\D/g, '')
|
||||
}
|
||||
|
||||
export async function copyDepositFieldValue(
|
||||
value: string | null | undefined,
|
||||
kind: 'card' | 'sheba',
|
||||
emptyMessage: string,
|
||||
) {
|
||||
const raw = String(value ?? '').trim()
|
||||
const normalized = normalizeDepositCopyValue(raw, kind)
|
||||
|
||||
if (!normalized) {
|
||||
message.warning(emptyMessage)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(normalized)
|
||||
} else if (typeof document !== 'undefined') {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = normalized
|
||||
textarea.setAttribute('readonly', 'true')
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.left = '-9999px'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
} else {
|
||||
throw new Error('clipboard unavailable')
|
||||
}
|
||||
|
||||
message.info('کپی شد')
|
||||
} catch {
|
||||
message.error('کپی انجام نشد')
|
||||
}
|
||||
}
|
||||
+278
-81
@@ -1,5 +1,17 @@
|
||||
import { ref } from 'vue'
|
||||
import { getSettings } from '@/api/configuration'
|
||||
import type { DepositGuideVars } from '@/utils/depositGuideText'
|
||||
import {
|
||||
DEFAULT_DEPOSIT_GUIDE_TEXT,
|
||||
getCustomerNameFromUser,
|
||||
getDepositGuideVarsFromSettings,
|
||||
getDepositSupportMessageTemplate,
|
||||
getRenderedDepositGuideText,
|
||||
getRenderedDepositSupportMessage,
|
||||
renderDepositGuideText,
|
||||
renderDepositSupportMessageText,
|
||||
} from '@/utils/depositGuideText'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
|
||||
export type SupportMessengerType =
|
||||
| 'bale'
|
||||
@@ -8,23 +20,24 @@ export type SupportMessengerType =
|
||||
| 'rubika'
|
||||
| 'eitaa'
|
||||
| 'soroush'
|
||||
|
||||
export interface SupportMessengerEntry {
|
||||
messenger: string
|
||||
id: string
|
||||
}
|
||||
| 'call'
|
||||
| 'sms'
|
||||
|
||||
export interface SupportMessengerSettings {
|
||||
messengers?: SupportMessengerEntry[]
|
||||
messenger?: SupportMessengerType | string
|
||||
messengerId?: string
|
||||
messengerText?: string
|
||||
depositSupportMessageText?: string
|
||||
depositAccountHolder?: string
|
||||
depositCardNumber?: string
|
||||
depositSheba?: string
|
||||
depositSmsSupportNumber?: string
|
||||
}
|
||||
|
||||
export interface SupportMessengerMeta {
|
||||
label: string
|
||||
icon: string
|
||||
accent: string
|
||||
export interface SupportMessengerEntry {
|
||||
messenger: string
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface ResolvedSupportMessengerEntry extends SupportMessengerEntry {
|
||||
@@ -34,9 +47,6 @@ export interface ResolvedSupportMessengerEntry extends SupportMessengerEntry {
|
||||
url: string
|
||||
}
|
||||
|
||||
export const supportMessengerSettings = ref<SupportMessengerSettings>({})
|
||||
export const supportMessengerPickerOpen = ref(false)
|
||||
|
||||
const VALID_SUPPORT_MESSENGERS = new Set([
|
||||
'bale',
|
||||
'telegram',
|
||||
@@ -44,27 +54,16 @@ const VALID_SUPPORT_MESSENGERS = new Set([
|
||||
'rubika',
|
||||
'eitaa',
|
||||
'soroush',
|
||||
'call',
|
||||
'sms',
|
||||
])
|
||||
|
||||
export const SUPPORT_MESSENGER_META: Record<string, SupportMessengerMeta> = {
|
||||
bale: { label: 'بله', icon: 'vuesax-linear:messages-2', accent: '#16a34a' },
|
||||
telegram: { label: 'تلگرام', icon: 'vuesax-linear:send-2', accent: '#0ea5e9' },
|
||||
whatsapp: { label: 'واتساپ', icon: 'vuesax-linear:whatsapp', accent: '#22c55e' },
|
||||
rubika: { label: 'روبیکا', icon: 'vuesax-linear:messages-2', accent: '#7c3aed' },
|
||||
eitaa: { label: 'ایتا', icon: 'vuesax-linear:messages-2', accent: '#f59e0b' },
|
||||
soroush: { label: 'سروش', icon: 'vuesax-linear:messages-2', accent: '#2563eb' },
|
||||
}
|
||||
const PHONE_NATIVE_MESSENGERS = new Set(['call', 'sms'])
|
||||
|
||||
function getSupportSettingValue(
|
||||
settings: Array<{ key?: string; value?: unknown }>,
|
||||
key: string,
|
||||
) {
|
||||
const setting = settings.find((item) => item?.key === key)
|
||||
if (!setting) return ''
|
||||
const value = setting.value
|
||||
if (value === undefined || value === null) return ''
|
||||
return String(value).trim()
|
||||
}
|
||||
export const supportMessengerSettings = ref<SupportMessengerSettings>({})
|
||||
export const supportMessengerPickerOpen = ref(false)
|
||||
/** لنگر منوی انتخاب پیامرسان — FAB یا دکمه داخل مودال شارژ */
|
||||
export const supportMessengerPickerAnchor = ref<HTMLElement | null>(null)
|
||||
|
||||
export function parseSupportMessengersJson(raw?: string | null): SupportMessengerEntry[] {
|
||||
const value = (raw ?? '').trim()
|
||||
@@ -101,54 +100,13 @@ export function resolveSupportMessengersFromSettings(
|
||||
return [{ messenger, id: legacyId }]
|
||||
}
|
||||
|
||||
export function resolveSupportMessengerMeta(messenger?: string): SupportMessengerMeta {
|
||||
const type = (messenger ?? '').trim().toLowerCase()
|
||||
return SUPPORT_MESSENGER_META[type] ?? {
|
||||
label: 'پشتیبانی',
|
||||
icon: 'vuesax-linear:headphone',
|
||||
accent: '#334155',
|
||||
}
|
||||
}
|
||||
|
||||
export function buildSupportMessengerUrl(
|
||||
messenger?: string,
|
||||
messengerId?: string,
|
||||
) {
|
||||
const id = (messengerId ?? '').trim()
|
||||
if (!id) return ''
|
||||
|
||||
if (/^https?:\/\//i.test(id)) return id
|
||||
|
||||
const type = (messenger ?? '').trim().toLowerCase()
|
||||
const username = id.replace(/^@/, '')
|
||||
|
||||
if (!type) return ''
|
||||
|
||||
switch (type) {
|
||||
case 'bale':
|
||||
if (id.includes('ble.ir')) {
|
||||
return id.startsWith('http') ? id : `https://${id.replace(/^\/\//, '')}`
|
||||
}
|
||||
return `https://ble.ir/${username}`
|
||||
case 'telegram':
|
||||
return `https://t.me/${username}`
|
||||
case 'whatsapp':
|
||||
return `https://wa.me/${username.replace(/\D/g, '')}`
|
||||
case 'rubika':
|
||||
return `https://rubika.ir/${username}`
|
||||
case 'eitaa':
|
||||
return `https://eitaa.com/${username}`
|
||||
case 'soroush':
|
||||
return `https://splus.ir/splus/${username}`
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/** فقط موارد موجود در `support_messengers` — تماس/پیامک خودکار اضافه نمیشوند */
|
||||
export function resolveSupportMessengerEntries(
|
||||
settings?: SupportMessengerSettings,
|
||||
messageText?: string,
|
||||
): ResolvedSupportMessengerEntry[] {
|
||||
const resolved = settings ?? supportMessengerSettings.value
|
||||
const text = (messageText ?? '').trim()
|
||||
const source = resolved.messengers?.length
|
||||
? resolved.messengers
|
||||
: resolveLegacySingleMessenger(resolved)
|
||||
@@ -156,9 +114,13 @@ export function resolveSupportMessengerEntries(
|
||||
return source
|
||||
.map((entry) => {
|
||||
const meta = resolveSupportMessengerMeta(entry.messenger)
|
||||
const url = buildSupportMessengerUrl(entry.messenger, entry.id)
|
||||
const url = buildSupportMessengerUrl(entry.messenger, entry.id, text)
|
||||
if (!url) return null
|
||||
return { ...entry, ...meta, url }
|
||||
return {
|
||||
...entry,
|
||||
...meta,
|
||||
url,
|
||||
}
|
||||
})
|
||||
.filter((entry): entry is ResolvedSupportMessengerEntry => entry !== null)
|
||||
}
|
||||
@@ -171,25 +133,260 @@ function resolveLegacySingleMessenger(settings: SupportMessengerSettings): Suppo
|
||||
return [{ messenger, id }]
|
||||
}
|
||||
|
||||
export async function openSupportMessengerEntry(entry: ResolvedSupportMessengerEntry | SupportMessengerEntry) {
|
||||
const url = 'url' in entry && entry.url
|
||||
? entry.url
|
||||
: buildSupportMessengerUrl(entry.messenger, entry.id)
|
||||
/** @deprecated از depositGuideText استفاده کنید */
|
||||
export const DEFAULT_SUPPORT_MESSENGER_TEXT = DEFAULT_DEPOSIT_GUIDE_TEXT
|
||||
|
||||
export interface SupportMessengerMeta {
|
||||
label: string
|
||||
icon: string
|
||||
accent: string
|
||||
}
|
||||
|
||||
export const SUPPORT_MESSENGER_META: Record<string, SupportMessengerMeta> = {
|
||||
bale: { label: 'بله', icon: 'vuesax-linear:messages-2', accent: '#16a34a' },
|
||||
telegram: { label: 'تلگرام', icon: 'vuesax-linear:send-2', accent: '#0ea5e9' },
|
||||
whatsapp: { label: 'واتساپ', icon: 'vuesax-linear:whatsapp', accent: '#22c55e' },
|
||||
rubika: { label: 'روبیکا', icon: 'vuesax-linear:messages-2', accent: '#7c3aed' },
|
||||
eitaa: { label: 'ایتا', icon: 'vuesax-linear:messages-2', accent: '#f59e0b' },
|
||||
soroush: { label: 'سروش', icon: 'vuesax-linear:messages-2', accent: '#2563eb' },
|
||||
call: { label: 'تماس', icon: 'vuesax-linear:call-calling', accent: '#10b981' },
|
||||
sms: { label: 'پیامک', icon: 'vuesax-linear:sms', accent: '#3b82f6' },
|
||||
}
|
||||
|
||||
export const DEFAULT_SUPPORT_MESSENGER_META: SupportMessengerMeta = {
|
||||
label: 'پشتیبانی',
|
||||
icon: 'vuesax-linear:headphone',
|
||||
accent: '#334155',
|
||||
}
|
||||
|
||||
export function resolveSupportMessengerMeta(messenger?: string): SupportMessengerMeta {
|
||||
const type = (messenger ?? '').trim().toLowerCase()
|
||||
return SUPPORT_MESSENGER_META[type] ?? DEFAULT_SUPPORT_MESSENGER_META
|
||||
}
|
||||
|
||||
export function resolveSupportMessengerLink(
|
||||
messenger?: string,
|
||||
messengerId?: string,
|
||||
fallbackLink = '',
|
||||
messageText?: string,
|
||||
) {
|
||||
const fromSettings = buildSupportMessengerUrl(messenger, messengerId, messageText)
|
||||
return fromSettings || fallbackLink
|
||||
}
|
||||
|
||||
export function getDepositGuideVarsFromSupportSettings(
|
||||
settings: SupportMessengerSettings,
|
||||
): DepositGuideVars {
|
||||
return {
|
||||
accountHolder: settings.depositAccountHolder,
|
||||
cardNumber: settings.depositCardNumber,
|
||||
sheba: settings.depositSheba,
|
||||
customerName: getCustomerNameFromUser(),
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDepositGuideText(settings?: SupportMessengerSettings): string {
|
||||
try {
|
||||
const fromStore = getRenderedDepositGuideText()
|
||||
if (fromStore.trim()) return fromStore
|
||||
} catch {
|
||||
// settings store may not be ready
|
||||
}
|
||||
|
||||
const resolved = settings ?? supportMessengerSettings.value
|
||||
const template = resolved.messengerText?.trim() || DEFAULT_DEPOSIT_GUIDE_TEXT
|
||||
const vars = resolved.depositAccountHolder
|
||||
? getDepositGuideVarsFromSupportSettings(resolved)
|
||||
: getDepositGuideVarsFromSettings()
|
||||
|
||||
return renderDepositGuideText(template, vars)
|
||||
}
|
||||
|
||||
export function resolveDepositSupportMessage(settings?: SupportMessengerSettings): string {
|
||||
try {
|
||||
const fromStore = getRenderedDepositSupportMessage()
|
||||
if (fromStore.trim()) return fromStore
|
||||
} catch {
|
||||
// settings store may not be ready
|
||||
}
|
||||
|
||||
const resolved = settings ?? supportMessengerSettings.value
|
||||
const rawTemplate = resolved.depositSupportMessageText?.trim() || ''
|
||||
const template = rawTemplate || getDepositSupportMessageTemplate()
|
||||
const vars = resolved.depositAccountHolder
|
||||
? getDepositGuideVarsFromSupportSettings(resolved)
|
||||
: getDepositGuideVarsFromSettings()
|
||||
|
||||
return renderDepositSupportMessageText(template, vars)
|
||||
}
|
||||
|
||||
/** پیامرسانهایی که پارامتر text در URL دارند */
|
||||
export function messengerSupportsUrlText(messenger?: string): boolean {
|
||||
const type = (messenger ?? '').trim().toLowerCase()
|
||||
return type === 'telegram' || type === 'whatsapp' || type === 'sms'
|
||||
}
|
||||
|
||||
/** تماس/پیام بومی — نیازی به کپی متن در کلیپبورد نیست */
|
||||
export function isPhoneNativeSupportMessenger(messenger?: string): boolean {
|
||||
const type = (messenger ?? '').trim().toLowerCase()
|
||||
return PHONE_NATIVE_MESSENGERS.has(type)
|
||||
}
|
||||
|
||||
export function messengerNeedsClipboardCopy(messenger?: string): boolean {
|
||||
if (isPhoneNativeSupportMessenger(messenger) || messengerSupportsUrlText(messenger)) {
|
||||
return false
|
||||
}
|
||||
return Boolean((messenger ?? '').trim())
|
||||
}
|
||||
|
||||
export async function openSupportMessengerWithMessage(
|
||||
messenger?: string,
|
||||
messengerId?: string,
|
||||
messageText?: string,
|
||||
fallbackLink = '',
|
||||
): Promise<boolean> {
|
||||
const text = (messageText ?? '').trim()
|
||||
const url =
|
||||
buildSupportMessengerUrl(messenger, messengerId, text) ||
|
||||
(fallbackLink.trim() || '')
|
||||
|
||||
return openSupportMessengerUrl(url, messenger, text)
|
||||
}
|
||||
|
||||
export async function openSupportMessengerEntry(
|
||||
entry: SupportMessengerEntry | ResolvedSupportMessengerEntry,
|
||||
messageText?: string,
|
||||
fallbackLink = '',
|
||||
): Promise<boolean> {
|
||||
const text = (messageText ?? '').trim()
|
||||
// همیشه URL را با متن فعلی بساز — url کششدهٔ entry ممکن است text قدیمی داشته باشد
|
||||
const url =
|
||||
buildSupportMessengerUrl(entry.messenger, entry.id, text) ||
|
||||
('url' in entry && !text ? entry.url : '') ||
|
||||
fallbackLink.trim()
|
||||
|
||||
return openSupportMessengerUrl(url, entry.messenger, text)
|
||||
}
|
||||
|
||||
async function openSupportMessengerUrl(
|
||||
url: string,
|
||||
messenger?: string,
|
||||
messageText?: string,
|
||||
): Promise<boolean> {
|
||||
if (!url) return false
|
||||
|
||||
if (messageText && messengerNeedsClipboardCopy(messenger) && navigator.clipboard?.writeText) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(messageText)
|
||||
} catch {
|
||||
// clipboard optional
|
||||
}
|
||||
}
|
||||
|
||||
if (url.startsWith('tel:') || url.startsWith('sms:')) {
|
||||
window.location.href = url
|
||||
return true
|
||||
}
|
||||
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
return true
|
||||
}
|
||||
|
||||
export async function loadSupportMessengerFromSettings() {
|
||||
function getSupportSettingValue(
|
||||
settings: Array<{ key?: string; is_active?: boolean | number; value?: unknown }>,
|
||||
key: string
|
||||
) {
|
||||
const setting = settings.find((item) => item?.key === key)
|
||||
if (!setting) return ''
|
||||
|
||||
const value = setting.value
|
||||
if (value === undefined || value === null) return ''
|
||||
|
||||
return String(value).trim()
|
||||
}
|
||||
|
||||
export function buildSupportMessengerUrl(
|
||||
messenger?: string,
|
||||
messengerId?: string,
|
||||
messageText?: string,
|
||||
) {
|
||||
const id = (messengerId ?? '').trim()
|
||||
if (!id) return ''
|
||||
|
||||
if (/^https?:\/\//i.test(id)) return id
|
||||
|
||||
const type = (messenger ?? '').trim().toLowerCase()
|
||||
const username = id.replace(/^@/, '')
|
||||
const text = (messageText ?? '').trim()
|
||||
const textQuery = text ? `?text=${encodeURIComponent(text)}` : ''
|
||||
|
||||
if (!type) return ''
|
||||
|
||||
switch (type) {
|
||||
case 'bale':
|
||||
if (id.includes('ble.ir')) {
|
||||
return id.startsWith('http') ? id : `https://${id.replace(/^\/\//, '')}`
|
||||
}
|
||||
return `https://ble.ir/${username}`
|
||||
case 'telegram':
|
||||
return `https://t.me/${username}${textQuery}`
|
||||
case 'whatsapp':
|
||||
return `https://wa.me/${username.replace(/\D/g, '')}${textQuery}`
|
||||
case 'rubika':
|
||||
return `https://rubika.ir/${username}`
|
||||
case 'eitaa':
|
||||
return `https://eitaa.com/${username}`
|
||||
case 'soroush':
|
||||
return `https://splus.ir/splus/${username}`
|
||||
case 'call':
|
||||
return buildSupportPhoneUrl(id)
|
||||
case 'sms':
|
||||
return buildDepositSmsUrl(id, text)
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function buildDepositSmsUrl(phoneNumber?: string, messageText?: string) {
|
||||
const digits = (phoneNumber ?? '').replace(/\D/g, '')
|
||||
if (!digits) return ''
|
||||
|
||||
const body = (messageText ?? '').trim()
|
||||
// بدون پیشنویس مگر صریحاً متن داده شود (مودال واریز)
|
||||
if (!body) return `sms:${digits}`
|
||||
return `sms:${digits}?body=${encodeURIComponent(body)}`
|
||||
}
|
||||
|
||||
export function buildSupportPhoneUrl(phoneNumber?: string) {
|
||||
const digits = (phoneNumber ?? '').replace(/\D/g, '')
|
||||
if (!digits) return ''
|
||||
return `tel:${digits}`
|
||||
}
|
||||
|
||||
export async function loadSupportMessengerFromSettings(forceSettingsRefresh = false) {
|
||||
try {
|
||||
const settingsStore = useSettingsStore()
|
||||
if (forceSettingsRefresh) {
|
||||
await settingsStore.fetchSettings(true)
|
||||
} else if (!settingsStore.loaded) {
|
||||
await settingsStore.fetchSettings()
|
||||
}
|
||||
|
||||
const settings = await getSettings()
|
||||
if (!settings || !Array.isArray(settings)) return
|
||||
|
||||
settingsStore.mergeSettings(settings)
|
||||
|
||||
supportMessengerSettings.value = {
|
||||
messengers: resolveSupportMessengersFromSettings(settings),
|
||||
messenger: getSupportSettingValue(settings, 'support_messenger'),
|
||||
messengerId: getSupportSettingValue(settings, 'support_messenger_id'),
|
||||
messengerText: getSupportSettingValue(settings, 'support_messenger_text'),
|
||||
depositSupportMessageText: getSupportSettingValue(settings, 'deposit_support_message_text'),
|
||||
depositAccountHolder: getSupportSettingValue(settings, 'deposit_account_holder'),
|
||||
depositCardNumber: getSupportSettingValue(settings, 'deposit_card_number'),
|
||||
depositSheba: getSupportSettingValue(settings, 'deposit_sheba'),
|
||||
depositSmsSupportNumber: getSupportSettingValue(settings, 'deposit_sms_support_number'),
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
||||
@@ -2,8 +2,12 @@ export interface AboutSection {
|
||||
title?: string
|
||||
paragraphs?: string[]
|
||||
items?: string[]
|
||||
/** متن HTML از Setting — در صورت وجود، اولویت با این است */
|
||||
html?: string
|
||||
}
|
||||
|
||||
const ABOUT_SETTING_KEYS = ['text_about'] as const
|
||||
|
||||
export function getAboutSections(projectName: string): AboutSection[] {
|
||||
return [
|
||||
{
|
||||
@@ -11,3 +15,59 @@ export function getAboutSections(projectName: string): AboutSection[] {
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function looksLikeHtml(value: string) {
|
||||
return /<\/?[a-z][\s\S]*>/i.test(value)
|
||||
}
|
||||
|
||||
function parseAboutSettingValue(raw: string): AboutSection[] | null {
|
||||
const value = raw.trim()
|
||||
if (!value) return null
|
||||
|
||||
try {
|
||||
const decoded = JSON.parse(value)
|
||||
if (Array.isArray(decoded) && decoded.length > 0) {
|
||||
return decoded as AboutSection[]
|
||||
}
|
||||
if (decoded && typeof decoded === 'object' && typeof decoded.html === 'string') {
|
||||
const html = decoded.html.trim()
|
||||
if (html) return [{ html }]
|
||||
}
|
||||
if (decoded && typeof decoded === 'object' && typeof decoded.text === 'string') {
|
||||
return parseAboutSettingValue(decoded.text)
|
||||
}
|
||||
} catch {
|
||||
// plain text / html
|
||||
}
|
||||
|
||||
if (looksLikeHtml(value)) {
|
||||
return [{ html: value }]
|
||||
}
|
||||
|
||||
const paragraphs = value
|
||||
.split(/\r?\n+/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
return [{ paragraphs: paragraphs.length > 0 ? paragraphs : [value] }]
|
||||
}
|
||||
|
||||
export function getAboutSettingText(
|
||||
getSettingByKey: (key: string) => unknown,
|
||||
): string {
|
||||
for (const key of ABOUT_SETTING_KEYS) {
|
||||
const value = String(getSettingByKey(key) ?? '').trim()
|
||||
if (value) return value
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** اولویت با Setting؛ در صورت خالی بودن، متن پیشفرض */
|
||||
export function resolveAboutSections(
|
||||
projectName: string,
|
||||
settingsText?: string | null,
|
||||
): AboutSection[] {
|
||||
const fromSettings = parseAboutSettingValue(String(settingsText ?? ''))
|
||||
if (fromSettings?.length) return fromSettings
|
||||
return getAboutSections(projectName)
|
||||
}
|
||||
|
||||
@@ -8,13 +8,41 @@
|
||||
v-if="sectionIndex > 0"
|
||||
:class="variant === 'desktop' ? 'dabout-content__divider' : 'w-full h-1px my-3 bg-tertiary'"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-for="(paragraph, paragraphIndex) in section.paragraphs ?? []"
|
||||
:key="`p-${sectionIndex}-${paragraphIndex}`"
|
||||
:class="[variant === 'desktop' ? 'dabout-content__paragraph' : '', paragraphIndex > 0 ? 'mt-3' : '']"
|
||||
>
|
||||
{{ paragraph }}
|
||||
</div>
|
||||
v-if="section.html"
|
||||
class="about-sections-html"
|
||||
:class="variant === 'desktop' ? 'dabout-content__paragraph' : ''"
|
||||
v-html="section.html"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="section.title"
|
||||
:class="variant === 'desktop' ? 'dabout-content__title' : 'font-bold text-sm mb-2'"
|
||||
>
|
||||
{{ section.title }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(paragraph, paragraphIndex) in section.paragraphs ?? []"
|
||||
:key="`p-${sectionIndex}-${paragraphIndex}`"
|
||||
:class="[variant === 'desktop' ? 'dabout-content__paragraph' : '', paragraphIndex > 0 ? 'mt-3' : '']"
|
||||
>
|
||||
{{ paragraph }}
|
||||
</div>
|
||||
<ul
|
||||
v-if="section.items?.length"
|
||||
:class="variant === 'desktop' ? 'dabout-content__list' : 'mt-2 pr-4 list-disc'"
|
||||
>
|
||||
<li
|
||||
v-for="(item, itemIndex) in section.items"
|
||||
:key="`i-${sectionIndex}-${itemIndex}`"
|
||||
:class="variant === 'desktop' ? 'dabout-content__list-item' : 'mt-2'"
|
||||
>
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,3 +54,21 @@ withDefaults(defineProps<{ variant?: 'mobile' | 'desktop' }>(), { variant: 'mobi
|
||||
|
||||
const { aboutSections } = useAboutPageContext()
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.about-sections-html {
|
||||
:deep(p + p) {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
:deep(ul) {
|
||||
margin: 0.5rem 0 0;
|
||||
padding-right: 1.1rem;
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
:deep(li + li) {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
<template>
|
||||
<div :class="variant === 'desktop' ? 'dabout-support' : 'wrap-fix bottom-0 px-10 py-6 flex gap-2 bg-white items-center'">
|
||||
<a class="w-full" :href="supportHref" target="_blank" rel="noopener noreferrer">
|
||||
<BasicButton class="w-full" type="primary">تماس با پشتیبانی</BasicButton>
|
||||
</a>
|
||||
<a :href="supportHref" target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
src="@/assets/img/rubika.webp"
|
||||
alt=""
|
||||
:class="variant === 'desktop' ? 'dabout-support__bale' : 'w-12'"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<SupportContactBar
|
||||
:variant="variant"
|
||||
mobile-root-class="wrap-fix mobile-info-page__support flex gap-2"
|
||||
desktop-root-class="dabout-support"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import { useAboutPageContext } from '../composables/useAboutPage'
|
||||
import SupportContactBar from '@/components/SupportContactBar.vue'
|
||||
|
||||
withDefaults(defineProps<{ variant?: 'mobile' | 'desktop' }>(), { variant: 'mobile' })
|
||||
|
||||
const { supportHref } = useAboutPageContext()
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
variant?: 'mobile' | 'desktop'
|
||||
}>(),
|
||||
{ variant: 'mobile' },
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
import { computed, inject, provide, type InjectionKey, type ComputedRef } from 'vue'
|
||||
import { computed, inject, onMounted, provide, type InjectionKey, type ComputedRef } from 'vue'
|
||||
import { getConfig } from '@/config/app-config'
|
||||
import { getAboutSections, type AboutSection } from '../aboutContent'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import {
|
||||
getAboutSettingText,
|
||||
resolveAboutSections,
|
||||
type AboutSection,
|
||||
} from '../aboutContent'
|
||||
|
||||
export interface AboutPageContext {
|
||||
projectName: ComputedRef<string>
|
||||
aboutSections: ComputedRef<AboutSection[]>
|
||||
supportHref: ComputedRef<string>
|
||||
}
|
||||
|
||||
export const ABOUT_PAGE_CONTEXT_KEY: InjectionKey<AboutPageContext> = Symbol('aboutPageContext')
|
||||
|
||||
const SUPPORT_LINK = 'https://web.rubika.ir/#c=u0I8F1U0a939b0df5ba9f73618d33d7c'
|
||||
|
||||
export function useAboutPage() {
|
||||
const settingsStore = useSettingsStore()
|
||||
const projectName = computed(() => getConfig('name_fa', 'کشکول گلد'))
|
||||
const aboutSections = computed(() => getAboutSections(projectName.value))
|
||||
const supportHref = computed(() => SUPPORT_LINK)
|
||||
|
||||
const context: AboutPageContext = { projectName, aboutSections, supportHref }
|
||||
const aboutSections = computed(() => {
|
||||
void settingsStore.settings
|
||||
const fromSettings = getAboutSettingText((key) => settingsStore.getSettingByKey(key))
|
||||
return resolveAboutSections(projectName.value, fromSettings)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void settingsStore.fetchSettings()
|
||||
})
|
||||
|
||||
const context: AboutPageContext = { projectName, aboutSections }
|
||||
provide(ABOUT_PAGE_CONTEXT_KEY, context)
|
||||
return context
|
||||
}
|
||||
|
||||
@@ -205,22 +205,7 @@
|
||||
}
|
||||
|
||||
.dabout-support {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
> a:first-child {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__bale {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1023.98px) {
|
||||
|
||||
@@ -11,13 +11,27 @@
|
||||
:key="`block-${blockIndex}`"
|
||||
:class="variant === 'desktop' ? 'drules-content__block' : 'rules-block'"
|
||||
>
|
||||
<p
|
||||
v-for="(rule, index) in block.items"
|
||||
:key="`rule-${blockIndex}-${index}`"
|
||||
<div
|
||||
v-if="block.html"
|
||||
class="rules-html"
|
||||
:class="variant === 'desktop' ? 'drules-content__paragraph' : 'rules-paragraph'"
|
||||
>
|
||||
{{ rule }}
|
||||
</p>
|
||||
v-html="block.html"
|
||||
/>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="block.title"
|
||||
:class="variant === 'desktop' ? 'drules-content__title' : 'font-bold text-sm text-primary'"
|
||||
>
|
||||
{{ block.title }}
|
||||
</div>
|
||||
<p
|
||||
v-for="(rule, index) in block.items"
|
||||
:key="`rule-${blockIndex}-${index}`"
|
||||
:class="variant === 'desktop' ? 'drules-content__paragraph' : 'rules-paragraph'"
|
||||
>
|
||||
{{ rule }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -40,4 +54,18 @@ const { rulesTitle, rulesBlocks } = useRulesPageContext()
|
||||
text-align: right;
|
||||
line-height: 1.9;
|
||||
}
|
||||
|
||||
.rules-html :deep(p + p) {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.rules-html :deep(ul) {
|
||||
margin: 0.5rem 0 0;
|
||||
padding-right: 1.1rem;
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.rules-html :deep(li + li) {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<template>
|
||||
<div :class="variant === 'desktop' ? 'drules-support' : 'wrap-fix bottom-0 px-10 py-6 flex gap-2 bg-white items-center'">
|
||||
<a class="w-full" :href="supportHref" target="_blank" rel="noopener noreferrer">
|
||||
<BasicButton class="w-full" type="primary">تماس با پشتیبانی</BasicButton>
|
||||
</a>
|
||||
<a :href="supportHref" target="_blank" rel="noopener noreferrer">
|
||||
<img src="@/assets/img/rubika.webp" alt="" :class="variant === 'desktop' ? 'drules-support__bale' : 'w-12'" />
|
||||
</a>
|
||||
</div>
|
||||
<SupportContactBar
|
||||
:variant="variant"
|
||||
mobile-root-class="wrap-fix mobile-info-page__support flex gap-2"
|
||||
desktop-root-class="drules-support"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import { useRulesPageContext } from '../composables/useRulesPage'
|
||||
import SupportContactBar from '@/components/SupportContactBar.vue'
|
||||
|
||||
withDefaults(defineProps<{ variant?: 'mobile' | 'desktop' }>(), { variant: 'mobile' })
|
||||
|
||||
const { supportHref } = useRulesPageContext()
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
variant?: 'mobile' | 'desktop'
|
||||
}>(),
|
||||
{ variant: 'mobile' },
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
import { computed, inject, provide, type ComputedRef, type InjectionKey } from 'vue'
|
||||
import { computed, inject, onMounted, provide, type ComputedRef, type InjectionKey } from 'vue'
|
||||
import { getConfig } from '@/config/app-config'
|
||||
import { getRulesBlocks, getRulesTitle, type RulesBlock } from '../rulesContent'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import {
|
||||
getRulesSettingText,
|
||||
getRulesTitle,
|
||||
resolveRulesBlocks,
|
||||
type RulesBlock,
|
||||
} from '../rulesContent'
|
||||
|
||||
export interface RulesPageContext {
|
||||
nameProject: ComputedRef<string>
|
||||
rulesTitle: ComputedRef<string>
|
||||
rulesBlocks: ComputedRef<RulesBlock[]>
|
||||
supportHref: ComputedRef<string>
|
||||
}
|
||||
|
||||
export const RULES_PAGE_CONTEXT_KEY: InjectionKey<RulesPageContext> = Symbol('rulesPageContext')
|
||||
|
||||
const SUPPORT_LINK = 'https://web.rubika.ir/#c=u0I8F1U0a939b0df5ba9f73618d33d7c'
|
||||
|
||||
export function useRulesPage() {
|
||||
const settingsStore = useSettingsStore()
|
||||
const nameProject = computed(() => getConfig('name_fa', 'کشکول گلد'))
|
||||
const rulesTitle = computed(() => getRulesTitle(nameProject.value, false))
|
||||
const rulesBlocks = computed(() => getRulesBlocks(nameProject.value, false))
|
||||
const supportHref = computed(() => SUPPORT_LINK)
|
||||
|
||||
const rulesBlocks = computed(() => {
|
||||
void settingsStore.settings
|
||||
const fromSettings = getRulesSettingText((key) => settingsStore.getSettingByKey(key))
|
||||
return resolveRulesBlocks(nameProject.value, fromSettings)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void settingsStore.fetchSettings()
|
||||
})
|
||||
|
||||
const context: RulesPageContext = {
|
||||
nameProject,
|
||||
rulesTitle,
|
||||
rulesBlocks,
|
||||
supportHref,
|
||||
}
|
||||
provide(RULES_PAGE_CONTEXT_KEY, context)
|
||||
return context
|
||||
|
||||
@@ -127,22 +127,7 @@
|
||||
}
|
||||
|
||||
.drules-support {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
> a:first-child {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__bale {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1023.98px) {
|
||||
|
||||
@@ -16,14 +16,9 @@
|
||||
<div class=" font-bold text-sm text-primary text-center">جواز کسب و کار</div>
|
||||
<!-- <img :src="permission1" alt="" class="w-full">-->
|
||||
<div class="mt-5 text-xs"></div>
|
||||
<div class="wrap-fix bottom-0 px-2 py-6 flex gap-2 bg-white">
|
||||
<a class="w-full" href="tel:09128081392">
|
||||
<BasicButton class="w-full" type="primary">تماس با پشتیبانی</BasicButton>
|
||||
</a>
|
||||
<a href="https://t.me/kashkol">
|
||||
<img src="@/assets/img/telegram.png" alt="" class="h-10 w-10"/>
|
||||
</a>
|
||||
</div>
|
||||
<SupportContactBar
|
||||
mobile-root-class="wrap-fix bottom-0 px-2 py-6 flex gap-2 bg-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +34,7 @@ import { onMounted, reactive, ref } from 'vue'
|
||||
import img from '@/assets/img/user_ly1.svg'
|
||||
// import gr_bottom from './assets/img/gr_bottom_white.svg'
|
||||
import { loading } from '@/utils'
|
||||
import SupportContactBar from '@/components/SupportContactBar.vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
|
||||
import { lengthValidation, required } from '@/utils/inputRules'
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
export interface RulesBlock {
|
||||
title?: string
|
||||
items: string[]
|
||||
/** متن HTML از Setting — در صورت وجود، اولویت با این است */
|
||||
html?: string
|
||||
}
|
||||
|
||||
const RULES_SETTING_KEYS = ['text_rule'] as const
|
||||
|
||||
function resolveProjectName(nameProject?: string | null) {
|
||||
return (nameProject ?? '').trim() || 'کشکول گلد'
|
||||
}
|
||||
@@ -47,3 +51,59 @@ export function getRulesTitle(nameProject: string, _isBoursecu = false) {
|
||||
export function getRulesBlocks(nameProject: string, _isBoursecu = false): RulesBlock[] {
|
||||
return [{ items: getDefaultRulesItems(nameProject) }]
|
||||
}
|
||||
|
||||
function looksLikeHtml(value: string) {
|
||||
return /<\/?[a-z][\s\S]*>/i.test(value)
|
||||
}
|
||||
|
||||
function parseRulesSettingValue(raw: string): RulesBlock[] | null {
|
||||
const value = raw.trim()
|
||||
if (!value) return null
|
||||
|
||||
try {
|
||||
const decoded = JSON.parse(value)
|
||||
if (Array.isArray(decoded) && decoded.length > 0) {
|
||||
return decoded as RulesBlock[]
|
||||
}
|
||||
if (decoded && typeof decoded === 'object' && typeof decoded.html === 'string') {
|
||||
const html = decoded.html.trim()
|
||||
if (html) return [{ items: [], html }]
|
||||
}
|
||||
if (decoded && typeof decoded === 'object' && typeof decoded.text === 'string') {
|
||||
return parseRulesSettingValue(decoded.text)
|
||||
}
|
||||
} catch {
|
||||
// plain text / html
|
||||
}
|
||||
|
||||
if (looksLikeHtml(value)) {
|
||||
return [{ items: [], html: value }]
|
||||
}
|
||||
|
||||
const items = value
|
||||
.split(/\r?\n+/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
return [{ items: items.length > 0 ? items : [value] }]
|
||||
}
|
||||
|
||||
export function getRulesSettingText(
|
||||
getSettingByKey: (key: string) => unknown,
|
||||
): string {
|
||||
for (const key of RULES_SETTING_KEYS) {
|
||||
const value = String(getSettingByKey(key) ?? '').trim()
|
||||
if (value) return value
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** اولویت با Setting؛ در صورت خالی بودن، متن پیشفرض */
|
||||
export function resolveRulesBlocks(
|
||||
nameProject: string,
|
||||
settingsText?: string | null,
|
||||
): RulesBlock[] {
|
||||
const fromSettings = parseRulesSettingValue(String(settingsText ?? ''))
|
||||
if (fromSettings?.length) return fromSettings
|
||||
return getRulesBlocks(nameProject, false)
|
||||
}
|
||||
|
||||
@@ -65,12 +65,14 @@
|
||||
<div class="text-center">
|
||||
<div class="font-bold text-red-600">مشتریان گرامی</div>
|
||||
<div class="mt-2">
|
||||
<span>جهت شارژ حساب کاربری خود، کلمه "شماره حساب" را برای پشتیبانی ارسال نمایید.</span>
|
||||
<span>{{ supportMessengerText }}</span>
|
||||
</div>
|
||||
<BasicButton class="mt-4">
|
||||
<a class="w-full" href="https://web.rubika.ir/#c=u0I8F1U0a939b0df5ba9f73618d33d7c">
|
||||
پیام به پشتیبانی
|
||||
</a>
|
||||
<BasicButton
|
||||
v-if="hasMessengerLink"
|
||||
class="mt-4"
|
||||
@click="onDepositSupportClick"
|
||||
>
|
||||
پیام به پشتیبانی
|
||||
</BasicButton>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -90,11 +92,14 @@ import OtpInput from 'vue3-otp-input'
|
||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||
import ResponsiveViewPair from '@/layouts/components/ResponsiveViewPair.vue'
|
||||
import { loading } from '@/utils'
|
||||
import { useSupportMessenger } from '@/composables/useSupportMessenger'
|
||||
import { loadSupportMessengerFromSettings } from '@/utils/supportMessenger'
|
||||
import { useWalletPage } from './composables/useWalletPage'
|
||||
import DesktopWalletView from './desktop/DesktopWalletView.vue'
|
||||
import MobileWalletView from './mobile/MobileWalletView.vue'
|
||||
import SkeletonLoading from './components/SkeletonLoading.vue'
|
||||
import WalletAddCardForm from './components/WalletAddCardForm.vue'
|
||||
import { watch } from 'vue'
|
||||
|
||||
const isDesktop = useDesktopBreakpoint()
|
||||
|
||||
@@ -107,6 +112,22 @@ const {
|
||||
handleOnComplete,
|
||||
addCard,
|
||||
} = useWalletPage()
|
||||
|
||||
const {
|
||||
hasMessengerLink,
|
||||
supportMessengerText,
|
||||
openDepositSupportMessenger,
|
||||
} = useSupportMessenger()
|
||||
|
||||
watch(chargeAccountModal, (visible) => {
|
||||
if (visible) {
|
||||
void loadSupportMessengerFromSettings(true)
|
||||
}
|
||||
})
|
||||
|
||||
function onDepositSupportClick() {
|
||||
void openDepositSupportMessenger()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user