From c2a355f2de19bd9c2b091d84f11d09fcba4adaa0 Mon Sep 17 00:00:00 2001 From: MojtabaSoumi Date: Sat, 18 Jul 2026 11:30:40 +0330 Subject: [PATCH] 1405/04/27 - --- components.d.ts | 1 + src/App.vue | 64 +--- src/assets/messengers/call.svg | 10 + src/assets/messengers/sms.svg | 7 + src/components/SupportContactBar.vue | 89 +++++ src/components/SupportMessengerBrandIcon.vue | 4 + src/composables/useSupportMessenger.ts | 250 ++++++++++++ src/store/settingsStore.ts | 25 +- src/utils/depositGuideText.ts | 202 ++++++++++ src/utils/supportMessenger.ts | 359 ++++++++++++++---- src/views/base/about/aboutContent.ts | 60 +++ .../about/components/AboutSectionsContent.vue | 58 ++- .../about/components/AboutSupportActions.vue | 29 +- .../base/about/composables/useAboutPage.ts | 27 +- .../about/desktop/styles/desktop-about.less | 17 +- .../rules/components/RulesContentPanel.vue | 40 +- .../rules/components/RulesSupportActions.vue | 25 +- .../base/rules/composables/useRulesPage.ts | 27 +- .../rules/desktop/styles/desktop-rules.less | 17 +- .../base/rules/permissions/permissions.vue | 12 +- src/views/base/rules/rulesContent.ts | 60 +++ src/views/base/wallet/index.vue | 31 +- 22 files changed, 1185 insertions(+), 229 deletions(-) create mode 100644 src/assets/messengers/call.svg create mode 100644 src/assets/messengers/sms.svg create mode 100644 src/components/SupportContactBar.vue create mode 100644 src/composables/useSupportMessenger.ts create mode 100644 src/utils/depositGuideText.ts diff --git a/components.d.ts b/components.d.ts index e8d8e5d..1d0c2fb 100644 --- a/components.d.ts +++ b/components.d.ts @@ -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'] diff --git a/src/App.vue b/src/App.vue index d99d54f..762eb09 100644 --- a/src/App.vue +++ b/src/App.vue @@ -67,8 +67,8 @@ @@ -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(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 () => { diff --git a/src/assets/messengers/call.svg b/src/assets/messengers/call.svg new file mode 100644 index 0000000..0ebc7ab --- /dev/null +++ b/src/assets/messengers/call.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/assets/messengers/sms.svg b/src/assets/messengers/sms.svg new file mode 100644 index 0000000..9902c71 --- /dev/null +++ b/src/assets/messengers/sms.svg @@ -0,0 +1,7 @@ + + + + diff --git a/src/components/SupportContactBar.vue b/src/components/SupportContactBar.vue new file mode 100644 index 0000000..d03ef8c --- /dev/null +++ b/src/components/SupportContactBar.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/components/SupportMessengerBrandIcon.vue b/src/components/SupportMessengerBrandIcon.vue index 5878d33..07366e6 100644 --- a/src/components/SupportMessengerBrandIcon.vue +++ b/src/components/SupportMessengerBrandIcon.vue @@ -21,8 +21,10 @@ + + diff --git a/src/views/base/about/components/AboutSupportActions.vue b/src/views/base/about/components/AboutSupportActions.vue index 32ea3ad..fe176e0 100644 --- a/src/views/base/about/components/AboutSupportActions.vue +++ b/src/views/base/about/components/AboutSupportActions.vue @@ -1,23 +1,18 @@ diff --git a/src/views/base/about/composables/useAboutPage.ts b/src/views/base/about/composables/useAboutPage.ts index 6775722..67e6a63 100644 --- a/src/views/base/about/composables/useAboutPage.ts +++ b/src/views/base/about/composables/useAboutPage.ts @@ -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 aboutSections: ComputedRef - supportHref: ComputedRef } export const ABOUT_PAGE_CONTEXT_KEY: InjectionKey = 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 } diff --git a/src/views/base/about/desktop/styles/desktop-about.less b/src/views/base/about/desktop/styles/desktop-about.less index 8f940a0..acdad30 100644 --- a/src/views/base/about/desktop/styles/desktop-about.less +++ b/src/views/base/about/desktop/styles/desktop-about.less @@ -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) { diff --git a/src/views/base/rules/components/RulesContentPanel.vue b/src/views/base/rules/components/RulesContentPanel.vue index 30ef2fd..3449daf 100644 --- a/src/views/base/rules/components/RulesContentPanel.vue +++ b/src/views/base/rules/components/RulesContentPanel.vue @@ -11,13 +11,27 @@ :key="`block-${blockIndex}`" :class="variant === 'desktop' ? 'drules-content__block' : 'rules-block'" > -

- {{ rule }} -

+ v-html="block.html" + /> + @@ -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; +} diff --git a/src/views/base/rules/components/RulesSupportActions.vue b/src/views/base/rules/components/RulesSupportActions.vue index 446f690..d56f8fa 100644 --- a/src/views/base/rules/components/RulesSupportActions.vue +++ b/src/views/base/rules/components/RulesSupportActions.vue @@ -1,19 +1,18 @@ diff --git a/src/views/base/rules/composables/useRulesPage.ts b/src/views/base/rules/composables/useRulesPage.ts index 9f1db15..eba03eb 100644 --- a/src/views/base/rules/composables/useRulesPage.ts +++ b/src/views/base/rules/composables/useRulesPage.ts @@ -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 rulesTitle: ComputedRef rulesBlocks: ComputedRef - supportHref: ComputedRef } export const RULES_PAGE_CONTEXT_KEY: InjectionKey = 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 diff --git a/src/views/base/rules/desktop/styles/desktop-rules.less b/src/views/base/rules/desktop/styles/desktop-rules.less index dd035e2..2a2ae34 100644 --- a/src/views/base/rules/desktop/styles/desktop-rules.less +++ b/src/views/base/rules/desktop/styles/desktop-rules.less @@ -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) { diff --git a/src/views/base/rules/permissions/permissions.vue b/src/views/base/rules/permissions/permissions.vue index 490a508..d2ed870 100644 --- a/src/views/base/rules/permissions/permissions.vue +++ b/src/views/base/rules/permissions/permissions.vue @@ -16,14 +16,9 @@
جواز کسب و کار
- + @@ -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' diff --git a/src/views/base/rules/rulesContent.ts b/src/views/base/rules/rulesContent.ts index c5466a2..6677c53 100644 --- a/src/views/base/rules/rulesContent.ts +++ b/src/views/base/rules/rulesContent.ts @@ -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) +} diff --git a/src/views/base/wallet/index.vue b/src/views/base/wallet/index.vue index 4d7f973..c5bf413 100644 --- a/src/views/base/wallet/index.vue +++ b/src/views/base/wallet/index.vue @@ -65,12 +65,14 @@
مشتریان گرامی
- جهت شارژ حساب کاربری خود، کلمه "شماره حساب" را برای پشتیبانی ارسال نمایید. + {{ supportMessengerText }}
- - - پیام به پشتیبانی - + + پیام به پشتیبانی