1405/04/27
-
This commit is contained in:
@@ -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