fix(ui): repair PWA install, credit footer, and boursecu copy

Make install guide and BasicButton clicks reliable, align montzar credit bar, remove calculator quick action, and use مس in about-software default text.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-03 13:53:10 -04:00
co-authored by Cursor
parent 9356a2ab5e
commit 5920866a95
9 changed files with 85 additions and 28 deletions
+20 -6
View File
@@ -73,17 +73,17 @@
<div
v-if="!showSplash && !isDesktop && !isTvBoardRoute"
class="app-credit-footer app-credit-footer--mobile"
:class="{ 'app-credit-footer--login': isLoginRoute }"
:class="{ 'app-credit-footer--login': showLoginCreditExtras }"
>
<EnamadBadge v-if="isLoginRoute" size="sm" />
<EnamadBadge v-if="showLoginCreditExtras" size="sm" />
<AppCreditLink variant="mobile" />
</div>
<div
v-if="!showSplash && showDesktopFloatingCredit && !isTvBoardRoute"
class="app-credit-footer app-credit-footer--desktop-float"
:class="{ 'app-credit-footer--login': isLoginRoute }"
:class="{ 'app-credit-footer--login': showLoginCreditExtras }"
>
<EnamadBadge v-if="isLoginRoute" size="sm" />
<EnamadBadge v-if="showLoginCreditExtras" size="sm" />
<AppCreditLink variant="float" />
</div>
@@ -297,6 +297,12 @@ watch(
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const showLoginCreditExtras = computed(() => {
if (!isLoginRoute.value) return false
const href = String(settingsStore.getSettingByKey('enamad_url') ?? '').trim()
const src = String(settingsStore.getSettingByKey('enamad_logo') ?? '').trim()
return Boolean(href && src)
})
const { latestIncomingNotification } = useChatService()
let lastConnectedChatSessionKey = ''
let lastSupportToastAt = 0
@@ -424,13 +430,14 @@ watch(latestIncomingNotification, (payload) => {
min-height: var(--app-credit-bar-inner);
padding: 4px 0 var(--app-safe-bottom);
background: #fff;
box-shadow: 0 -2px 4px -2px #0000001a;
box-shadow: 0 3.75px 16px 0 rgba(0, 0, 0, 0.43);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
font-size: 9px;
color: #000;
}
.app-credit-footer--login.app-credit-footer--mobile {
@@ -461,6 +468,7 @@ watch(latestIncomingNotification, (payload) => {
&:hover {
opacity: 0.85;
color: inherit;
}
&__text {
@@ -470,13 +478,19 @@ watch(latestIncomingNotification, (payload) => {
&__logo {
width: auto;
height: 12px;
object-fit: contain;
display: block;
}
&--mobile {
gap: 4px;
padding: 4px 0;
color: inherit;
color: #000;
.app-credit-link__text {
color: #000;
}
.app-credit-link__logo {
height: 12px;
+6 -1
View File
@@ -7,11 +7,16 @@
:class="`app-credit-link--${variant}`"
>
<span class="app-credit-link__text">توسعه و طراحی توسط</span>
<img src="/logo-montzar.png" alt="montzar" class="app-credit-link__logo" />
<img :src="logoSrc" alt="montzar" class="app-credit-link__logo" />
</a>
</template>
<script lang="ts" setup>
const logoSrc = `${import.meta.env.BASE_URL || '/'}logo-montzar.png`.replace(
/([^:]\/)\/+/g,
'$1',
)
withDefaults(
defineProps<{
variant?: 'mobile' | 'sidebar' | 'float' | 'tv'
+20 -3
View File
@@ -1,5 +1,13 @@
<template>
<AButton :disabled="props.disabledB" :type="props.type" :size="props.size" :class="[prefixCls, _class]" :loading="props.loading">
<AButton
:disabled="props.disabledB"
:type="props.type"
:size="props.size"
:class="[prefixCls, _class]"
:loading="props.loading"
:html-type="props.htmlType"
@click="onClick"
>
<!-- py-1 px-2-->
<div
class="flex items-center gap-1 justify-center flex-row-reverse gap-2 py-1 px-2"
@@ -28,7 +36,7 @@ interface Props {
size?: string
loading?: boolean
disabledB?:boolean
htmlType?: 'button' | 'submit' | 'reset'
// prefixIcon?: string
}
@@ -51,8 +59,17 @@ const { prefixCls } = usePrefix('base-button')
const props = withDefaults(defineProps<Props>(), {
iconSize: baseButtonIconSize,
// iconLeft: false,
iconClass: ''
iconClass: '',
htmlType: 'submit',
})
const emit = defineEmits<{
click: [event: MouseEvent]
}>()
function onClick(event: MouseEvent) {
emit('click', event)
}
</script>
<style lang="less">
+14 -7
View File
@@ -20,11 +20,17 @@ const emit = defineEmits<{
close: []
}>()
const activePlatform = ref<PwaPlatform>(props.platform ?? getDefaultPwaPlatform())
function normalizePlatform(value?: string | null): PwaPlatform {
return value === 'ios' || value === 'android' ? value : getDefaultPwaPlatform()
}
const activePlatform = ref<PwaPlatform>(normalizePlatform(props.platform))
const stepIndex = ref(0)
const guide = computed(() => PWA_INSTALL_GUIDE[activePlatform.value])
const currentStep = computed(() => guide.value.steps[stepIndex.value])
const guide = computed(
() => PWA_INSTALL_GUIDE[normalizePlatform(activePlatform.value)] ?? PWA_INSTALL_GUIDE.android,
)
const currentStep = computed(() => guide.value.steps[stepIndex.value] ?? guide.value.steps[0])
const totalSteps = computed(() => guide.value.steps.length)
const isFirstStep = computed(() => stepIndex.value === 0)
const isLastStep = computed(() => stepIndex.value === totalSteps.value - 1)
@@ -34,7 +40,7 @@ watch(
() => props.open,
(visible) => {
if (!visible) return
activePlatform.value = props.platform ?? getDefaultPwaPlatform()
activePlatform.value = normalizePlatform(props.platform)
stepIndex.value = 0
},
)
@@ -42,7 +48,7 @@ watch(
watch(
() => props.platform,
(value) => {
if (value) activePlatform.value = value
if (value) activePlatform.value = normalizePlatform(value)
},
)
@@ -52,8 +58,9 @@ function close() {
}
function selectPlatform(next: PwaPlatform) {
if (activePlatform.value === next) return
activePlatform.value = next
const platform = normalizePlatform(next)
if (activePlatform.value === platform) return
activePlatform.value = platform
stepIndex.value = 0
}
+14 -2
View File
@@ -1,4 +1,5 @@
import { computed, onMounted, ref } from 'vue'
import { message } from 'ant-design-vue'
import type { PwaPlatform } from '@/modules/pwa/pwaInstallGuideSteps'
import {
getDefaultPwaPlatform,
@@ -77,7 +78,10 @@ export function usePwaInstall() {
// فقط وقتی واقعاً داخل PWA/TWA هستیم راهنما را نشان نده
if (isStandalonePwa() || isTWA()) return
consumeInstallBanner()
platform.value = nextPlatform ?? getDefaultPwaPlatform()
const next = nextPlatform === 'ios' || nextPlatform === 'android'
? nextPlatform
: getDefaultPwaPlatform()
platform.value = next
guideOpen.value = true
}
@@ -108,16 +112,24 @@ export function usePwaInstall() {
/** نصب مستقیم مرورگر؛ اگر ممکن نبود مدال آموزش. */
async function tryInstallOrOpenGuide(nextPlatform?: PwaPlatform) {
ensureListener()
refreshOfferInstall()
if (isStandalonePwa() || isTWA()) return false
if (isStandalonePwa() || isTWA()) {
message.info('نسخه وب‌اپ از قبل نصب شده است')
offerInstall.value = false
return false
}
const installed = await triggerNativePwaInstall()
if (installed) {
offerInstall.value = false
consumeInstallBanner()
message.success('نصب وب‌اپ آغاز شد')
return true
}
// Chrome روی HTTP/IP یا Safari معمولاً prompt بومی ندارد — راهنما را باز کن
openGuide(nextPlatform)
return false
}
+9
View File
@@ -96,6 +96,11 @@ export function initPwaInstallPromptListener() {
if (installListenerInitialized || typeof window === 'undefined') return
installListenerInitialized = true
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
deferredInstallPrompt = e as BeforeInstallPromptEvent
})
window.addEventListener(PWA_MANIFEST_READY_EVENT, (event) => {
const installEvent = (event as CustomEvent<{ installEvent?: Event | null }>).detail?.installEvent
@@ -103,6 +108,10 @@ export function initPwaInstallPromptListener() {
deferredInstallPrompt = installEvent as BeforeInstallPromptEvent
}
})
window.addEventListener('appinstalled', () => {
deferredInstallPrompt = null
})
}
export async function triggerNativePwaInstall() {
@@ -1,6 +1,6 @@
export function getDefaultAboutSoftwareText(projectName: string) {
const name = projectName.trim() || 'این نرم‌افزار'
return `${name} بستری امن و آنلاین برای خرید و فروش طلا فراهم کرده تا کاربران بتوانند سرمایه‌های خود را به‌سادگی مدیریت کنند.\n\nهدف ما ارائه تجربه‌ای سریع، شفاف و کاربرپسند در معاملات طلا است.`
return `${name} بستری امن و آنلاین برای خرید و فروش مس فراهم کرده تا کاربران بتوانند سرمایه‌های خود را به‌سادگی مدیریت کنند.\n\nهدف ما ارائه تجربه‌ای سریع، شفاف و کاربرپسند در معاملات مس است.`
}
function looksLikeHtml(value: string) {
@@ -1,5 +1,4 @@
import { getBoursecuActionIconSrc } from '@/utils/config'
import { openGoldDayCalculator } from '@/composables/useGoldDayCalculator'
import type { HomeQuickActionItem } from '../types'
export const boursecuQuickActions: HomeQuickActionItem[] = [
@@ -45,10 +44,4 @@ export const boursecuQuickActions: HomeQuickActionItem[] = [
iconSrc: getBoursecuActionIconSrc('convert'),
route: { name: 'buyProduct' },
},
{
id: 'calculator',
label: 'محاسبه‌گر',
icon: 'vuesax-linear:chart-2',
handler: () => openGoldDayCalculator(),
},
]
+1 -1
View File
@@ -241,7 +241,7 @@
>
دانلود اپ اندروید
</a>
<BasicButton type="primary" class="w-full py-2" html-type="button" @click.prevent="installWebApp">
<BasicButton type="primary" class="w-full py-2" html-type="button" @click="installWebApp">
<div>نصب نسخه وب اپ</div>
</BasicButton>
</div>