1405/04/10-fix bug iphone show

This commit is contained in:
2026-07-01 12:49:43 +03:30
parent a133f6e11d
commit 40c8d21a0d
20 changed files with 315 additions and 71 deletions
+12
View File
@@ -112,6 +112,7 @@ import {
import { useDraggableFloatingButton } from '@/utils/useDraggableFloatingButton'
import AppCreditLink from '@/components/AppCreditLink.vue'
import { useRoute } from 'vue-router'
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
const VersionUpdateModal = defineAsyncComponent(
() => import('@/modules/version/VersionUpdateModal.vue')
@@ -251,6 +252,17 @@ watch(showSplash, (visible) => {
}
})
watch(
() => route.name,
(toName, fromName) => {
if (isDesktop.value) return
if (fromName === 'login' || toName === 'home') {
recoverMobileViewportAfterAuth()
}
},
{ flush: 'post' },
)
</script>
<style lang="less">
+2
View File
@@ -11,6 +11,7 @@ import { useUserStore } from '@/store/userStore'
import { getToken } from '@/utils'
import '@/utils/useDateTime'
import { initPriceRealtimeChannel } from '@/utils/priceSocket'
import { initMobileScrollRestoration } from '@/layouts/composables/useMobileScrollReset'
type BootWindow = Window & {
__BOOT_TIMEOUT__?: number
@@ -122,6 +123,7 @@ export async function bootstrapApp() {
}
setupRouterGuards()
initMobileScrollRestoration()
app.mount('#app')
+8 -1
View File
@@ -7,7 +7,7 @@ import { useFundUserStore } from '@/store/fundStore'
import type { NavigationGuardNext } from 'vue-router'
import { useAuthStore } from '@/store/useAuthStore'
import { getConfigApp } from '@/utils/config'
import { getFingerPosition, setFingerPosition } from '@/app/authBoot'
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
const PUBLIC_ROUTE_NAMES = ['login', '404', 'educate', 'aboutUs'] as const
@@ -119,6 +119,13 @@ export function setupRouterGuards() {
}
})
router.afterEach((to, from) => {
if (document.documentElement.classList.contains('app-layout--desktop')) return
if (from.name === 'login') {
recoverMobileViewportAfterAuth()
}
})
watch(currentRoute, async () => (userStore.userInfo ? '' : await start()))
}
@@ -0,0 +1,125 @@
import { nextTick } from 'vue'
import router from '@/router'
let recoveryTimers: ReturnType<typeof setTimeout>[] = []
function clearRecoveryTimers() {
recoveryTimers.forEach(clearTimeout)
recoveryTimers = []
}
function blurFocusedField() {
const active = document.activeElement
if (active instanceof HTMLElement && active !== document.body) {
active.blur()
}
}
function clearBodyScrollLock() {
document.body.classList.remove('van-overflow-hidden')
document.body.style.removeProperty('position')
document.body.style.removeProperty('top')
document.body.style.removeProperty('left')
document.body.style.removeProperty('right')
document.body.style.removeProperty('width')
document.body.style.removeProperty('height')
document.body.style.removeProperty('min-height')
document.body.style.removeProperty('overflow')
document.body.style.removeProperty('transform')
document.documentElement.style.removeProperty('overflow')
document.documentElement.style.removeProperty('height')
document.documentElement.style.removeProperty('transform')
const appRoot = document.getElementById('app')
if (appRoot) {
appRoot.style.removeProperty('position')
appRoot.style.removeProperty('top')
appRoot.style.removeProperty('height')
appRoot.style.removeProperty('min-height')
appRoot.style.removeProperty('overflow')
appRoot.style.removeProperty('transform')
}
}
function resetAllScrollPositions() {
window.scrollTo(0, 0)
document.documentElement.scrollTop = 0
document.body.scrollTop = 0
document
.querySelectorAll('.main-with-bottom-chrome-inner, .login-page, .login-page-root')
.forEach((node) => {
if (node instanceof HTMLElement) {
node.scrollTop = 0
}
})
}
function kickViewportReflow() {
window.dispatchEvent(new Event('resize'))
if (window.visualViewport) {
window.scrollTo(0, 0)
}
}
function runViewportRecovery() {
clearBodyScrollLock()
resetAllScrollPositions()
kickViewportReflow()
}
function scheduleViewportRecovery() {
clearRecoveryTimers()
runViewportRecovery()
nextTick(() => {
requestAnimationFrame(() => {
runViewportRecovery()
for (const delay of [50, 150, 300, 500]) {
recoveryTimers.push(window.setTimeout(runViewportRecovery, delay))
}
})
})
}
/** Reset scroll offsets after navigation — fixes iOS safe-area drift. */
export function resetMobileAppScroll() {
runViewportRecovery()
nextTick(() => {
requestAnimationFrame(runViewportRecovery)
})
}
export async function dismissMobileKeyboard() {
blurFocusedField()
await new Promise((resolve) => window.setTimeout(resolve, 120))
}
export async function prepareMobileViewportForAppShell() {
blurFocusedField()
resetAllScrollPositions()
await dismissMobileKeyboard()
}
/** Full recovery after auth — runs after home shell is mounted. */
export function recoverMobileViewportAfterAuth() {
scheduleViewportRecovery()
}
export const resetMobileAppScrollAfterLogin = recoverMobileViewportAfterAuth
export async function completeLoginNavigation() {
await prepareMobileViewportForAppShell()
await router.push({ name: 'home' })
await router.isReady()
recoverMobileViewportAfterAuth()
}
export function initMobileScrollRestoration() {
if (typeof history !== 'undefined' && 'scrollRestoration' in history) {
history.scrollRestoration = 'manual'
}
}
+21 -5
View File
@@ -1,9 +1,9 @@
<template>
<div>
<div class="mobile-app-layout">
<div :class="['main-with-bottom-chrome', { 'main-with-bottom-chrome--no-nav': hideMenu }]">
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'">
<component :is="Component" class="main-with-bottom-chrome-inner" />
<router-view v-slot="{ Component, route: activeRoute }">
<transition :name="activeRoute.meta.transition || 'fade'">
<component :is="Component" :key="activeRoute.fullPath" class="main-with-bottom-chrome-inner" />
</transition>
</router-view>
</div>
@@ -19,13 +19,16 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { computed, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useConfigStore } from '@/store/configStore'
import { getProjectMobileNavLogo, isBoursecuProject } from '@/utils/config'
import { useAppNavigation } from '../composables/useAppNavigation'
import { recoverMobileViewportAfterAuth, resetMobileAppScroll } from '../composables/useMobileScrollReset'
import MobileBottomNav from './MobileBottomNav.vue'
const config = useConfigStore()
const route = useRoute()
const isBoursecu = isBoursecuProject()
const navLogo = computed(() => getProjectMobileNavLogo(config.config?.avatar))
@@ -34,6 +37,19 @@ const {
navItemClass,
navigateTo,
} = useAppNavigation()
watch(
() => route.fullPath,
() => {
resetMobileAppScroll()
},
{ flush: 'post' },
)
onMounted(() => {
resetMobileAppScroll()
recoverMobileViewportAfterAuth()
})
</script>
<style lang="less" src="./mobile-layout.less"></style>
+14 -2
View File
@@ -131,8 +131,16 @@
opacity: 0;
}
.mobile-app-layout {
height: 100%;
max-height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.main-with-bottom-chrome {
height: calc(100vh - var(--app-bottom-chrome-height, 108px));
height: calc(100dvh - var(--app-bottom-chrome-height, 108px));
overflow: hidden;
display: flex;
flex-direction: column;
@@ -148,7 +156,11 @@
overscroll-behavior-y: contain;
}
.main-with-bottom-chrome-inner--contained {
overflow: hidden;
}
.main-with-bottom-chrome--no-nav,
.main-with-bottom-chrome--no-nav .main-with-bottom-chrome-inner {
height: calc(100vh - var(--app-credit-footer-offset, 28px));
height: calc(100dvh - var(--app-credit-footer-offset, 28px));
}
+27
View File
@@ -5,6 +5,33 @@
*/
html:not(.app-layout--desktop) {
html,
body,
#app {
min-height: 0;
height: 100dvh;
max-height: 100dvh;
overflow: hidden;
}
body {
overscroll-behavior: none;
}
.desktopConfig,
.app-main {
min-height: 0;
height: 100dvh;
max-height: 100dvh;
overflow: hidden;
}
.app-main > * {
height: 100%;
max-height: 100%;
overflow: hidden;
}
/* Teleport to body — dock bar must span viewport, not shrink to content */
.app-dock-add-bar,
.wallet-card-slider {
+40 -7
View File
@@ -107,17 +107,54 @@
overflow: hidden;
}
.mobile-info-page__header {
z-index: 1999;
}
/* Decorative page background — fixed to viewport, never scrolls on iOS */
.mobile-page-bg,
.mobile-info-page__bg {
pointer-events: none;
position: absolute;
inset: 0;
overflow: hidden;
position: fixed;
z-index: 0;
top: 0;
left: 0;
right: 0;
bottom: var(--app-bottom-chrome-height, 108px);
overflow: hidden;
max-width: 100vw;
box-sizing: border-box;
img {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: auto;
}
}
.mobile-page-bg--no-nav,
.mobile-info-page__bg {
bottom: var(--app-credit-footer-offset, 28px);
}
.mobile-page-bg--cover {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
html:not(.app-layout--desktop) {
@media screen and (min-width: 426px) and (max-width: 767.98px) {
.mobile-page-bg,
.mobile-info-page__bg {
width: 426px;
max-width: 426px;
left: 50%;
right: auto;
transform: translateX(-50%);
}
}
}
@@ -152,10 +189,6 @@
z-index: 1998;
}
.mobile-info-page__header {
z-index: 1999;
}
.mobile-content-box {
display: flex;
flex-direction: column;
@@ -1,13 +1,13 @@
<template>
<div>
<MobilePageHeader title="آدرس ها" show-back />
<div>
<img :src="img" alt="" class="mt-2 w-full absolute bottom-0" />
<div class="relative">
<div class="mobile-page-bg" aria-hidden="true">
<img :src="img" alt="" />
</div>
<MobilePageHeader title="آدرس ها" show-back />
<div
:class="loading ? 'h-screen' : ''"
class="px-3 mpage-content pb-2 overflow-y-scroll mpage-scroll"
class="relative z-1 px-3 mpage-content pb-2 overflow-y-scroll mpage-scroll"
>
<div
v-if="isBazretalaProject()"
+18 -1
View File
@@ -40,7 +40,8 @@
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { computed, nextTick, onActivated, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useUserStore } from '@/store/userStore'
import router from '@/router'
import Icon from '@/components/BasicPacks/BasicIcon.vue'
@@ -48,6 +49,7 @@ import { getProjectMobileNavLogo, isBoursecuProject } from '@/utils/config'
const headerRef = ref<HTMLElement | null>(null)
const isHeaderScrolled = ref(false)
const route = useRoute()
const userStore = useUserStore()
const projectLogo = computed(() => getProjectMobileNavLogo())
@@ -158,6 +160,21 @@ onMounted(async () => {
requestAnimationFrame(() => setupScrollTracking())
})
onActivated(() => {
isHeaderScrolled.value = false
setupScrollTracking()
})
watch(
() => route.fullPath,
() => {
if (route.name === 'home') {
isHeaderScrolled.value = false
nextTick(() => setupScrollTracking())
}
},
)
onUnmounted(() => {
teardownScrollTracking()
})
+6
View File
@@ -25,6 +25,8 @@ import BalanceSheet from './components/BalanceSheet.vue'
import ShareModal from './components/ShareModal.vue'
import HomePageShell from './shell/HomePageShell.vue'
import { useHomePage } from './composables/useHomePage'
import { onMounted } from 'vue'
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
const {
userStore,
@@ -37,6 +39,10 @@ const {
goldVal,
logout,
} = useHomePage()
onMounted(() => {
recoverMobileViewportAfterAuth()
})
</script>
<style scoped>
+2 -9
View File
@@ -1,5 +1,5 @@
<template>
<div class="products-page-root">
<div class="products-page-root main-with-bottom-chrome-inner--contained">
<ResponsiveViewPair>
<template #mobile>
<MobileProductsView />
@@ -104,16 +104,9 @@ const {
<style scoped>
.products-page-root {
min-height: 100%;
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
</style>
<style lang="less">
.products-page-root.main-with-bottom-chrome-inner {
overflow: hidden;
}
</style>
@@ -1,6 +1,6 @@
<template>
<div class="products-mobile-root">
<div class="products-mobile-root__bg" aria-hidden="true">
<div class="mobile-page-bg" aria-hidden="true">
<img :src="img" alt="" />
</div>
<MobilePageHeader title="خرید و فروش" />
@@ -86,20 +86,6 @@ const storeClosedMessage = computed(() => getStoreClosedMessage())
overflow: hidden;
}
.products-mobile-root__bg {
pointer-events: none;
position: absolute;
inset: 0;
overflow: hidden;
z-index: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.products-mobile-root__chrome {
position: relative;
z-index: 1;
@@ -1,6 +1,10 @@
<template>
<div>
<div class="absolute inset-0 bg-cover bg-center opacity-60 pointer-events-none"></div>
<div class="relative">
<div
class="mobile-page-bg mobile-page-bg--cover opacity-60"
aria-hidden="true"
:style="{ backgroundImage: `url(${img})` }"
></div>
<MobilePageHeader title="تراکنش ها">
<template #trailing>
<BasicIcon icon="vuesax-linear:filter" @click="showFilter = true" />
@@ -8,7 +12,7 @@
</MobilePageHeader>
<!-- <div ref="el" :class="loading ? 'h-screen' : ''" class="px-3 mpage-content pb-10">-->
<div ref="el" class="transactions-page-body px-3 pt-16 pb-24">
<div ref="el" class="transactions-page-body relative z-1 px-3 mpage-content pb-24">
<list-anim v-if="!loading">
<Item v-for="item in items" :key="item.id" :item="item" />
<div :ref="bindMobileObserver" v-show="!loading" class="w-full opacity-0 relative">
@@ -52,6 +56,7 @@ import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker/src/BaseDateP
import ListAnim from '@/components/ListAnim.vue'
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
import { DotLottieVue } from '@lottiefiles/dotlottie-vue'
import img from '@/assets/img/user_ly1.svg'
import { useTransactionsPageContext } from './composables/useTransactionsPage'
const el = ref<HTMLElement | null>(null)
+1 -15
View File
@@ -1,6 +1,6 @@
<template>
<div class="mobile-user-view">
<div class="mobile-user-view__bg" aria-hidden="true">
<div class="mobile-page-bg" aria-hidden="true">
<img :src="img" alt="" />
</div>
<MobilePageHeader title="اطلاعات حساب کاربری" show-back />
@@ -114,20 +114,6 @@ const {
overflow: hidden;
}
.mobile-user-view__bg {
pointer-events: none;
position: absolute;
inset: 0;
overflow: hidden;
z-index: 0;
img {
position: absolute;
bottom: 0;
width: 100%;
}
}
.mobile-user-view__scroll {
position: relative;
z-index: 1;
@@ -1,6 +1,10 @@
<template>
<div>
<div class="absolute inset-0 bg-cover bg-center opacity-60" :style="{ backgroundImage: `url(${img})` }"></div>
<div class="relative">
<div
class="mobile-page-bg mobile-page-bg--cover opacity-60"
aria-hidden="true"
:style="{ backgroundImage: `url(${img})` }"
></div>
<MobilePageHeader title="کیف پول" />
<div class="mpage-subheader wrap-fix flex justify-evenly">
<div class="flex justify-evenly w-full px-2 h-11 items-center bg-gray-50 relative rounded">
+2 -1
View File
@@ -377,6 +377,7 @@ import { useWalletStore } from '@/store/walletStore'
import { useSettingsStore } from '@/store/settingsStore'
import { syncPriceRealtimeChannel } from '@/utils/priceSocket'
import { setTruth } from '@/app/authBoot'
import { completeLoginNavigation } from '@/layouts/composables/useMobileScrollReset'
import {useConfigStore} from "@/store/configStore";
import {
getConfigApp,
@@ -488,7 +489,7 @@ async function login(accessToken: string | undefined) {
/* if (userStore.userInfo?.type === (URLUserType() === 'customer' ? 1 : 2)) {*/
await walletStore.getMyWallet()
setTruth()
await router.push({ name: 'home' })
await completeLoginNavigation()
/* } else {
removeToken()
userStore.removeInformation()
+3 -2
View File
@@ -16,6 +16,7 @@ import { useUserStore } from '@/store/userStore'
import { useWalletStore } from '@/store/walletStore'
import { useSettingsStore } from '@/store/settingsStore'
import { setTruth } from '@/app/authBoot'
import { completeLoginNavigation } from '@/layouts/composables/useMobileScrollReset'
export function useLoginForm() {
const { time, onFinish, reset } = useCountdownTimer('00:02:00', 'mm:ss')
@@ -55,7 +56,7 @@ export function useLoginForm() {
const fingerPrint = await authenticateWithFingerprint()
if (fingerPrint) {
setTruth()
await router.push({ name: 'home' })
await completeLoginNavigation()
} else {
message.error('خطا در اعتبارسنجی')
}
@@ -75,7 +76,7 @@ export function useLoginForm() {
await userStore.getMyInfo()
await walletStore.getMyWallet()
setTruth()
await router.push({ name: 'home' })
await completeLoginNavigation()
}
function getcode() {
+9 -2
View File
@@ -78,11 +78,18 @@ const isGaroosi = computed(() => config.config?.name === 'garoosi')
<style scoped>
.login-page-root {
width: 100%;
min-height: 100vh;
height: 100%;
max-height: 100dvh;
overflow: hidden;
}
.login-page {
min-height: calc(100vh - var(--app-credit-footer-offset, 28px));
height: 100%;
max-height: calc(100dvh - var(--app-credit-footer-offset, 28px));
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
.login-page__content {
+5 -1
View File
@@ -32,6 +32,10 @@ const isBoursecu = isBoursecuProject()
<style scoped>
.login-page {
min-height: calc(100vh - var(--app-credit-footer-offset, 28px));
min-height: calc(100dvh - var(--app-credit-footer-offset, 28px));
max-height: calc(100dvh - var(--app-credit-footer-offset, 28px));
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
}
</style>