change login-page design
@@ -26,3 +26,10 @@
|
||||
- 2026-09-09 | نام منوی کاربر | `navigation.ts` + `MobileBottomNav.vue` | «تنظیمات» → «امکانات بیشتر» (دسکتاپ+موبایل)
|
||||
- 2026-09-12 | دکمه داراییها خانه | `boursecuQuickActions` + `useHomePage.openAssetsSheet` | قبلاً `fund` میرفت و با خاموش بودن قلک گارد بیصدا برمیگرداند؛ حالا شیت BalanceSheet
|
||||
- 2026-09-09 | ماشین حساب | `useUserPage.ts` + `GoldDayCalculatorModal.vue` | «قیمت روز طلا» → «ماشین حساب»؛ برچسب فلز/واحد از settingsStore (metalTypeLabel + tradeWeightUnit) نه هاردکد طلا/گرم
|
||||
- 2026-10-05 | ????? ????? ??????? | desktop/components/DesktopAuthShell.vue + desktop/styles/desktop-auth.less | ????? stretch? ??? ???????? bleed? tagline ? form-links ?? margin-top:auto ???????? support ??? ???? ?? __top
|
||||
- 2026-10-05 | نمای کلی پروژه | `boursecu/BOURSECU.md` | چهار ریپو: user:3100، admin:3600، server:8000، landing:3200؛ فلز مس از settings
|
||||
- 2026-10-05 | ???? ????? ????? | DesktopAuthShell.vue + desktop-auth.less | status ???? form-column__foot ??? ???????? body/top ?? margin-top:auto ???????? tagline ??????? foot
|
||||
- 2026-10-05 | ??? ????? ?????? | home/components/AppHeader.vue + guestHome.ts | ???? ????? ???? ?????login? ????????? ????? ?????/??/???? ?? guest ????
|
||||
- 2026-10-05 | FAB ???????? ????? ?????? | DesktopAuthShell.vue + useDraggableFloatingButton | ??????: ????? ???? ????????? ??????: ???? inline ??? ????
|
||||
- 2026-10-05 | ?????? ?? ??? ????? | boutUs/components/AboutUsContentPanel.vue | ??? ?????? ???? ??? ???? ?? ????? ???? ?? ??? ??
|
||||
- 2026-10-06 | بیلد آموزش ENOENT 1.png | EducateContentPanel.vue + assets/img/1.png,2.png,4.png + banner1/2 | فایلها در گیت بودند و از دیسک پاک شده بودند؛ git restore
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
</AConfigProvider>
|
||||
<div
|
||||
v-if="showSupportButton && !showSplash && route.name !== 'support' && !isTvBoardRoute"
|
||||
v-if="showSupportButton && !showSplash && route.name !== 'support' && route.name !== 'login' && !isTvBoardRoute"
|
||||
id="support-button"
|
||||
ref="supportButtonEl"
|
||||
class="fixed z-[5100] cursor-grab select-none rounded-full"
|
||||
@@ -71,7 +71,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!showSplash && !isDesktop && !isTvBoardRoute"
|
||||
v-if="!showSplash && !isDesktop && !isTvBoardRoute && !isLoginRoute"
|
||||
class="app-credit-footer app-credit-footer--mobile"
|
||||
:class="{ 'app-credit-footer--login': showLoginCreditExtras }"
|
||||
>
|
||||
@@ -157,7 +157,8 @@ const showDesktopFloatingCredit = computed(() => {
|
||||
if (!isDesktop.value) return false
|
||||
// پشتیبانی خودش سایدبار دارد؛ float روی دکمه ارسال میافتد
|
||||
if (route.name === 'support') return false
|
||||
return route.name === 'login' || !!route.meta.hideMenu
|
||||
if (route.name === 'login') return false
|
||||
return !!route.meta.hideMenu
|
||||
})
|
||||
const isLoginRoute = computed(() => route.name === 'login')
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { initPriceRealtimeChannel } from '@/utils/priceSocket'
|
||||
import { initMobileScrollRestoration } from '@/layouts/composables/useMobileScrollReset'
|
||||
import { initMobileInputKeyboardScroll } from '@/utils/mobileInputKeyboardScroll'
|
||||
import { isServerUnavailableError } from '@/utils/serverUnavailable'
|
||||
import { isGuestHomeQuery } from '@/utils/guestHome'
|
||||
|
||||
type BootWindow = Window & {
|
||||
__BOOT_TIMEOUT__?: number
|
||||
@@ -132,7 +133,15 @@ export async function bootstrapApp() {
|
||||
|
||||
await router.isReady()
|
||||
|
||||
if (!getToken() && router.currentRoute.value.name !== 'login' && router.currentRoute.value.name !== 'tvMonitor' && router.currentRoute.value.name !== 'aboutSoftware') {
|
||||
const bootRoute = router.currentRoute.value
|
||||
const guestHomeBoot = bootRoute.name === 'home' && isGuestHomeQuery(bootRoute.query)
|
||||
if (
|
||||
!getToken()
|
||||
&& bootRoute.name !== 'login'
|
||||
&& bootRoute.name !== 'tvMonitor'
|
||||
&& bootRoute.name !== 'aboutSoftware'
|
||||
&& !guestHomeBoot
|
||||
) {
|
||||
await router.replace({ name: 'login' })
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,16 @@ import type { NavigationGuardNext } from 'vue-router'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
|
||||
import { getFingerPosition, setFingerPosition } from '@/app/authBoot'
|
||||
import { isGuestHomeQuery } from '@/utils/guestHome'
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
|
||||
const PUBLIC_ROUTE_NAMES = ['login', '404', 'educate', 'aboutUs', 'aboutSoftware', 'tvMonitor'] as const
|
||||
|
||||
function isPublicRoute(name: string | symbol | null | undefined) {
|
||||
return PUBLIC_ROUTE_NAMES.includes(name as (typeof PUBLIC_ROUTE_NAMES)[number])
|
||||
function isPublicRoute(to: RouteLocationNormalized) {
|
||||
if (to.name === 'home' && isGuestHomeQuery(to.query)) {
|
||||
return true
|
||||
}
|
||||
return PUBLIC_ROUTE_NAMES.includes(to.name as (typeof PUBLIC_ROUTE_NAMES)[number])
|
||||
}
|
||||
|
||||
function goToLogin(next: NavigationGuardNext) {
|
||||
@@ -71,7 +76,7 @@ export function setupRouterGuards() {
|
||||
const authenticated = await resolveSession()
|
||||
|
||||
if (!authenticated) {
|
||||
if (isPublicRoute(to.name)) {
|
||||
if (isPublicRoute(to)) {
|
||||
return next()
|
||||
}
|
||||
return goToLogin(next)
|
||||
@@ -192,7 +197,11 @@ export async function ensureAuthenticatedRoute() {
|
||||
|
||||
if (!token) {
|
||||
userStore.userInfo = undefined
|
||||
if (router.currentRoute.value.name !== 'login') {
|
||||
const current = router.currentRoute.value
|
||||
if (current.name === 'home' && isGuestHomeQuery(current.query)) {
|
||||
return false
|
||||
}
|
||||
if (current.name !== 'login') {
|
||||
await router.replace({ name: 'login' })
|
||||
}
|
||||
return false
|
||||
|
||||
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 830 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 912 KiB |
|
Before Width: | Height: | Size: 929 KiB |
|
Before Width: | Height: | Size: 2.4 MiB |
@@ -1,5 +1,6 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { isGuestHomeActive } from '@/utils/guestHome'
|
||||
|
||||
export function useAppNavigation() {
|
||||
const router = useRouter()
|
||||
@@ -7,7 +8,7 @@ export function useAppNavigation() {
|
||||
|
||||
const popVisible = ref(false)
|
||||
const currentRoute = computed(() => route.name)
|
||||
const hideMenu = computed(() => !!route.meta.hideMenu)
|
||||
const hideMenu = computed(() => !!route.meta.hideMenu || isGuestHomeActive(route))
|
||||
/** فقط ناو پایین موبایل — سایدبار دسکتاپ را مخفی نمیکند */
|
||||
const hideBottomNav = computed(
|
||||
() => !!route.meta.hideBottomNav || !!route.meta.hideMenu,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { getToken } from '@/utils'
|
||||
import router from '@/router'
|
||||
import type { LocationQuery, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
export const GUEST_HOME_QUERY = { guest: '1' } as const
|
||||
|
||||
export function isGuestHomeQuery(query: LocationQuery | Record<string, unknown>) {
|
||||
const value = query.guest
|
||||
return value === '1' || value === 1
|
||||
}
|
||||
|
||||
export function isGuestHomeActive(route?: RouteLocationNormalizedLoaded) {
|
||||
const current = route ?? router.currentRoute.value
|
||||
return current.name === 'home' && isGuestHomeQuery(current.query) && !getToken()
|
||||
}
|
||||
|
||||
export function guestHomeRoute() {
|
||||
return { name: 'home' as const, query: { ...GUEST_HOME_QUERY } }
|
||||
}
|
||||
@@ -17,6 +17,10 @@ type DraggableFloatingOptions = {
|
||||
/** فاصله از پایینِ نوار منو + montzar (px) — مثل تصویر */
|
||||
defaultBottomGap?: number
|
||||
defaultRightOffset?: number
|
||||
/** عرض کل پنجره بهجای فریم موبایل ۴۲۶ */
|
||||
fullViewport?: boolean
|
||||
/** نوار پایین اپ را در موقعیت پیشفرض لحاظ نکن */
|
||||
ignoreBottomChrome?: boolean
|
||||
}
|
||||
|
||||
function readBottomChromeHeight(): number {
|
||||
@@ -49,6 +53,8 @@ export function useDraggableFloatingButton(
|
||||
const height = options.height ?? 44
|
||||
const defaultBottomGap = options.defaultBottomGap ?? 12
|
||||
const defaultRightOffset = options.defaultRightOffset ?? 16
|
||||
const fullViewport = options.fullViewport === true
|
||||
const ignoreBottomChrome = options.ignoreBottomChrome === true
|
||||
|
||||
const pos = ref<FabPosition>({ x: 0, y: 0 })
|
||||
const dragging = ref(false)
|
||||
@@ -68,6 +74,7 @@ export function useDraggableFloatingButton(
|
||||
}))
|
||||
|
||||
function getViewportWidth() {
|
||||
if (fullViewport) return window.innerWidth
|
||||
return Math.min(window.innerWidth, 426)
|
||||
}
|
||||
|
||||
@@ -78,11 +85,16 @@ export function useDraggableFloatingButton(
|
||||
return Math.max(0, (vw - contentWidth) / 2)
|
||||
}
|
||||
|
||||
function getBottomChrome() {
|
||||
if (ignoreBottomChrome) return 0
|
||||
return readBottomChromeHeight()
|
||||
}
|
||||
|
||||
function getDefaultPosition(): FabPosition {
|
||||
const vh = window.innerHeight
|
||||
const offsetX = getHorizontalOffset()
|
||||
const contentWidth = getViewportWidth()
|
||||
const bottomChrome = readBottomChromeHeight()
|
||||
const bottomChrome = getBottomChrome()
|
||||
|
||||
return {
|
||||
x: offsetX + contentWidth - width - defaultRightOffset,
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<h2 :class="variant === 'desktop' ? 'daboutus-content__title' : 'text-base font-bold mb-3'">درباره ما</h2>
|
||||
|
||||
<div :class="variant === 'desktop' ? 'daboutus-content__paragraph' : 'mt-3'">
|
||||
گروسی گالری بستری امن و آنلاین برای خرید و فروش طلای آبشده فراهم کرده تا کاربران بتوانند سرمایههای خرد خود را به راحتی پسانداز و مدیریت کنند.
|
||||
بورس مس بستری امن و آنلاین برای خرید و فروش مس فراهم کرده تا کاربران بتوانند معاملات کاتد، مفتول و سیم کابل را ساده، سریع و قابل اعتماد انجام دهند.
|
||||
</div>
|
||||
<div :class="variant === 'desktop' ? 'daboutus-content__paragraph' : 'mt-3'">
|
||||
هدف ما ایجاد تجربهای سریع، شفاف و کاربرپسند در معاملات طلای آبشده است.
|
||||
هدف ما ایجاد تجربهای شفاف و کاربرپسند در معاملات مس، با پشتوانه تولیدکنندگان واقعی است.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="!isGuestHome"
|
||||
type="button"
|
||||
class="mhome-header-btn"
|
||||
@click="router.push({ name: 'user' })"
|
||||
@@ -55,6 +56,16 @@
|
||||
<Icon icon="vuesax-linear:user" size="18" />
|
||||
</button>
|
||||
<button
|
||||
v-if="isGuestHome"
|
||||
type="button"
|
||||
class="mhome-header-btn mhome-header-btn--logout"
|
||||
@click="router.push({ name: 'login' })"
|
||||
>
|
||||
<Icon icon="vuesax-linear:login" size="18" />
|
||||
<span>ورود</span>
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="mhome-header-btn mhome-header-btn--logout"
|
||||
@click="$emit('logout')"
|
||||
@@ -82,6 +93,7 @@ import {
|
||||
initPwaInstallPromptListener,
|
||||
} from '@/modules/pwa/pwaInstall'
|
||||
import { unreadSupportCount, isUserSupportChatEnabled } from '@/utils/chat'
|
||||
import { isGuestHomeActive } from '@/utils/guestHome'
|
||||
|
||||
const headerRef = ref<HTMLElement | null>(null)
|
||||
const isHeaderScrolled = ref(false)
|
||||
@@ -89,13 +101,15 @@ const canNativeInstall = ref(false)
|
||||
const route = useRoute()
|
||||
|
||||
const projectLogo = computed(() => getProjectMobileNavLogo())
|
||||
const chatEnabled = computed(() => isUserSupportChatEnabled())
|
||||
const isGuestHome = computed(() => isGuestHomeActive(route))
|
||||
const chatEnabled = computed(() => !isGuestHome.value && isUserSupportChatEnabled())
|
||||
const chatUnreadCount = computed(() => unreadSupportCount.value)
|
||||
const {
|
||||
enabled: notificationEnabled,
|
||||
enabled: notificationEnabledRaw,
|
||||
unreadCount,
|
||||
clearUnreadLocally,
|
||||
} = useAppNotificationBadge()
|
||||
const notificationEnabled = computed(() => !isGuestHome.value && notificationEnabledRaw.value)
|
||||
const { tryInstallOrOpenGuide, showInstallEntry } = usePwaInstall()
|
||||
|
||||
function refreshCanInstall() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { computed, h, inject, onUnmounted, provide, ref, type InjectionKey } from 'vue'
|
||||
import router from '@/router'
|
||||
import { getToken, removeToken } from '@/utils'
|
||||
import { isGuestHomeActive } from '@/utils/guestHome'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
@@ -266,6 +267,11 @@ export function useHomePageContext() {
|
||||
}
|
||||
|
||||
async function ensureHomeUserAndData() {
|
||||
if (isGuestHomeActive()) {
|
||||
await initHomePageData(false, { guest: true })
|
||||
return
|
||||
}
|
||||
|
||||
const authed = await ensureAuthenticatedRoute()
|
||||
if (!authed) {
|
||||
return
|
||||
|
||||
@@ -20,7 +20,9 @@ export function isHomePageDataInitialized() {
|
||||
return initialized.value
|
||||
}
|
||||
|
||||
export async function initHomePageData(force = false) {
|
||||
export async function initHomePageData(force = false, options?: { guest?: boolean }) {
|
||||
const guest = options?.guest === true
|
||||
|
||||
if (!force && initialized.value) {
|
||||
startLastPricePolling()
|
||||
void loadHomeMaftolOffset(false)
|
||||
@@ -36,14 +38,18 @@ export async function initHomePageData(force = false) {
|
||||
try {
|
||||
const { loadWalletAssets } = useWalletAssets()
|
||||
|
||||
await Promise.all([
|
||||
loadHomeFunds(force),
|
||||
loadWalletAssets(force),
|
||||
const sharedTasks = [
|
||||
getLastPrices(),
|
||||
fetchHomeChartPrices(force),
|
||||
startLastPricePolling(),
|
||||
loadHomeBanners(force),
|
||||
])
|
||||
]
|
||||
|
||||
await Promise.all(
|
||||
guest
|
||||
? sharedTasks
|
||||
: [...sharedTasks, loadHomeFunds(force), loadWalletAssets(force)],
|
||||
)
|
||||
|
||||
await loadHomeMaftolOffset(force)
|
||||
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
<template>
|
||||
<header class="dhome-topbar">
|
||||
<div>
|
||||
<p class="dhome-topbar__eyebrow">نمای کلی</p>
|
||||
<h1 class="dhome-topbar__title">{{ userStore.userInfo?.name ?? 'کاربر' }}</h1>
|
||||
<p class="dhome-topbar__eyebrow">{{ guestMode ? 'بازار مس' : 'نمای کلی' }}</p>
|
||||
<h1 class="dhome-topbar__title">{{ guestMode ? 'قیمت لحظهای مس' : (userStore.userInfo?.name ?? 'کاربر') }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="dhome-topbar__actions">
|
||||
<button type="button" class="dhome-btn-ghost" @click="router.push({ name: 'user' })">
|
||||
<button
|
||||
v-if="guestMode"
|
||||
type="button"
|
||||
class="dhome-btn-ghost"
|
||||
@click="router.push({ name: 'login' })"
|
||||
>
|
||||
<BasicIcon icon="vuesax-linear:login" size="16" />
|
||||
<span>ورود</span>
|
||||
</button>
|
||||
<button v-if="!guestMode" type="button" class="dhome-btn-ghost" @click="router.push({ name: 'user' })">
|
||||
<BasicIcon icon="vuesax-linear:user" size="16" />
|
||||
<span>حساب کاربری</span>
|
||||
</button>
|
||||
@@ -21,7 +30,7 @@
|
||||
<span>{{ canNativeInstall ? 'نصب وب اپلیکیشن' : 'آموزش نصب' }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="notificationEnabled"
|
||||
v-if="!guestMode && notificationEnabled"
|
||||
type="button"
|
||||
class="dhome-btn-ghost dhome-btn-ghost--notify"
|
||||
aria-label="اعلان"
|
||||
@@ -36,7 +45,7 @@
|
||||
<span>اعلان</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="chatEnabled"
|
||||
v-if="!guestMode && chatEnabled"
|
||||
type="button"
|
||||
class="dhome-btn-ghost dhome-btn-ghost--notify"
|
||||
aria-label="چت پشتیبانی"
|
||||
@@ -54,7 +63,7 @@
|
||||
<BasicIcon icon="vuesax-linear:refresh-2" size="16" />
|
||||
</button>
|
||||
<button
|
||||
v-if="showLogout"
|
||||
v-if="!guestMode && showLogout"
|
||||
type="button"
|
||||
class="dhome-btn-ghost"
|
||||
@click="$emit('logout')"
|
||||
@@ -84,8 +93,9 @@ import router from '@/router'
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
showLogout?: boolean
|
||||
guestMode?: boolean
|
||||
}>(),
|
||||
{ showLogout: true },
|
||||
{ showLogout: true, guestMode: false },
|
||||
)
|
||||
|
||||
defineEmits<{
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { getToken } from '@/utils'
|
||||
import { isGuestHomeActive } from '@/utils/guestHome'
|
||||
import { useHomePageContext } from '../../composables/useHomePage'
|
||||
import HomeSkeleton from '../../components/HomeSkeleton.vue'
|
||||
import DesktopHomeShell from '../components/DesktopHomeShell.vue'
|
||||
@@ -22,18 +23,20 @@ const userStore = useUserStore()
|
||||
const { userInfo } = storeToRefs(userStore)
|
||||
const ctx = useHomePageContext()
|
||||
|
||||
const showContent = computed(() => Boolean(userInfo.value))
|
||||
const isGuestHome = computed(() => isGuestHomeActive())
|
||||
const showContent = computed(() => Boolean(userInfo.value) || isGuestHome.value)
|
||||
const showSkeleton = computed(() => Boolean(getToken()) && !userInfo.value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DesktopHomeShell>
|
||||
<DesktopHomeTopbar @logout="$emit('logout')" />
|
||||
<DesktopHomeTopbar :guest-mode="isGuestHome" @logout="$emit('logout')" />
|
||||
|
||||
<HomeSkeleton v-if="showSkeleton" />
|
||||
|
||||
<template v-if="showContent">
|
||||
<DesktopAssetsMetrics
|
||||
v-if="!isGuestHome"
|
||||
@open-gold-modal="ctx.openGoldModal"
|
||||
@open-rial-modal="ctx.openRialModal"
|
||||
/>
|
||||
@@ -47,7 +50,7 @@ const showSkeleton = computed(() => Boolean(getToken()) && !userInfo.value)
|
||||
|
||||
<section class="dhome-main-grid">
|
||||
<DesktopChartSection />
|
||||
<DesktopQuickActionsPanel />
|
||||
<DesktopQuickActionsPanel v-if="!isGuestHome" />
|
||||
</section>
|
||||
</template>
|
||||
</DesktopHomeShell>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { getToken } from '@/utils'
|
||||
import { isGuestHomeActive } from '@/utils/guestHome'
|
||||
import HomeSkeleton from '../components/HomeSkeleton.vue'
|
||||
import { getHomeLayoutConfig } from './registry'
|
||||
import { HOME_SECTION_COMPONENTS } from './sections'
|
||||
@@ -16,8 +17,15 @@ const userStore = useUserStore()
|
||||
const { userInfo } = storeToRefs(userStore)
|
||||
const layoutConfig = computed(() => props.layoutConfig ?? getHomeLayoutConfig())
|
||||
|
||||
const isGuestHome = computed(() => isGuestHomeActive())
|
||||
|
||||
const outerSections = computed(() =>
|
||||
(layoutConfig.value.outerSections ?? []).filter((section) => section.visible !== false)
|
||||
(layoutConfig.value.outerSections ?? [])
|
||||
.filter((section) => section.visible !== false)
|
||||
.filter((section) => {
|
||||
if (!isGuestHome.value) return true
|
||||
return section.id !== 'assets' && section.id !== 'boursecuQuickActions'
|
||||
}),
|
||||
)
|
||||
|
||||
const innerSections = computed(() =>
|
||||
@@ -29,7 +37,7 @@ const tailSections = computed(() =>
|
||||
)
|
||||
|
||||
/** محتوا با userInfo — دادههای API در پسزمینه لود میشوند */
|
||||
const showContent = computed(() => Boolean(userInfo.value))
|
||||
const showContent = computed(() => Boolean(userInfo.value) || isGuestHome.value)
|
||||
const showSkeleton = computed(() => Boolean(getToken()) && !userInfo.value)
|
||||
|
||||
function resolveSectionComponent(sectionId: HomeSectionId) {
|
||||
|
||||
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -81,13 +81,26 @@
|
||||
<!-- <transition name="fade" mode="out-in">-->
|
||||
<a-form-item
|
||||
v-if="!stepTwoVerify"
|
||||
:rules="[required('شماره تلفن'), mobileValidation('شماره تلفن')]"
|
||||
:rules="[required(mobileFieldLabel), mobileValidation(mobileFieldLabel)]"
|
||||
:validate-trigger="['change', 'blur']"
|
||||
class="home_appear_fade_in_down"
|
||||
label="شماره تلفن"
|
||||
:label="mobileFieldLabel"
|
||||
name="mobile"
|
||||
>
|
||||
<a-input
|
||||
v-if="variant === 'desktop'"
|
||||
v-model:value="userFormState.mobile"
|
||||
size="large"
|
||||
inputmode="numeric"
|
||||
type="tel"
|
||||
placeholder="شماره موبایل"
|
||||
>
|
||||
<template #prefix>
|
||||
<BasicIcon icon="vuesax-linear:mobile" size="18" />
|
||||
</template>
|
||||
</a-input>
|
||||
<BasicInput
|
||||
v-else
|
||||
v-model:value="userFormState.mobile"
|
||||
place-holder="شماره تلفن"
|
||||
size="large"
|
||||
@@ -164,7 +177,7 @@
|
||||
<div v-if="variant === 'desktop'" class="dauth-actions">
|
||||
<BasicButton
|
||||
:disabled="loading"
|
||||
class="w-full dauth-actions__submit"
|
||||
class="w-full dauth-actions__submit dauth-btn-stack"
|
||||
html-type="submit"
|
||||
type="primary"
|
||||
>
|
||||
@@ -172,36 +185,43 @@
|
||||
<span v-else>{{ !isSMS ? 'ورود' : stepTwoVerify ? 'ورود' : 'ارسال کد' }}</span>
|
||||
</BasicButton>
|
||||
|
||||
<BasicButton
|
||||
v-if="getToken() && userStore.userInfo?.finger_print"
|
||||
<button
|
||||
v-if="!isTWA()"
|
||||
type="button"
|
||||
class="dauth-btn-stack"
|
||||
:disabled="loading"
|
||||
class="w-full"
|
||||
@click="enterFingerPrint"
|
||||
@click="installWebApp"
|
||||
>
|
||||
ورود با اثر انگشت
|
||||
</BasicButton>
|
||||
|
||||
<button type="button" class="dauth-link-btn dauth-link-btn--primary" @click="goToAboutSoftware">
|
||||
درباره نرمافزار
|
||||
<span>نصب وب اپلیکیشن</span>
|
||||
</button>
|
||||
|
||||
<div v-if="!isTWA()" class="mt-1 flex flex-col gap-2">
|
||||
<a
|
||||
v-if="!!setting.getSettingByKey('apk_download_link')"
|
||||
class="dauth-link-btn dauth-link-btn--primary"
|
||||
:href="setting.getSettingByKey('apk_download_link')"
|
||||
>
|
||||
دانلود اپ اندروید
|
||||
</a>
|
||||
<button type="button" class="dauth-link-btn dauth-link-btn--primary w-full" @click="installWebApp">
|
||||
نصب نسخه وب اپ
|
||||
</button>
|
||||
</div>
|
||||
<!-- <a
|
||||
v-if="!isTWA() && apkDownloadHref"
|
||||
class="dauth-btn-stack dauth-btn-stack--link"
|
||||
:href="apkDownloadHref"
|
||||
>
|
||||
<span>دانلود اپ اندروید</span>
|
||||
<BasicIcon icon="vuesax-linear:android" size="20" />
|
||||
</a>-->
|
||||
|
||||
<button
|
||||
v-else-if="!isTWA()"
|
||||
type="button"
|
||||
class="dauth-btn-stack"
|
||||
@click="onApkDownloadClick"
|
||||
>
|
||||
<span>نصب وب اپلیکیشن</span>
|
||||
<BasicIcon icon="vuesax-linear:android" size="20" />
|
||||
</button>
|
||||
|
||||
<button type="button" class="dauth-btn-stack" :disabled="loading" @click="goToGuestHome">
|
||||
<span>قیمت لحظهای مس</span>
|
||||
<!-- <BasicIcon icon="vuesax-linear:global" size="20" />-->
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isRegister && variant === 'desktop'"
|
||||
v-if="variant === 'desktop' && !isRegister"
|
||||
class="dauth-switch"
|
||||
>
|
||||
<span>حساب کاربری ندارید؟</span>
|
||||
@@ -242,12 +262,9 @@
|
||||
دانلود اپ اندروید
|
||||
</a>
|
||||
<BasicButton type="primary" class="w-full py-2" html-type="button" @click="installWebApp">
|
||||
<div>نصب نسخه وب اپ</div>
|
||||
<div>نصب وب اپلیکیشن</div>
|
||||
</BasicButton>
|
||||
</div>
|
||||
<BasicButton class="mt-2 w-full" @click="goToAboutSoftware">
|
||||
<div>درباره نرمافزار</div>
|
||||
</BasicButton>
|
||||
<div
|
||||
v-if="isRegister"
|
||||
class="cursor-pointer flex gap-1 justify-center mt-5 text-xs "
|
||||
@@ -309,8 +326,9 @@ import { usePwaInstall } from '@/composables/usePwaInstall'
|
||||
import {getProducts} from "@/api/products";
|
||||
import {getSettings} from "@/api/configuration";
|
||||
import { LOGIN_PAGE_CONTEXT_KEY } from '../composables/useLoginPage'
|
||||
import { guestHomeRoute } from '@/utils/guestHome'
|
||||
|
||||
withDefaults(
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
variant?: 'mobile' | 'desktop'
|
||||
}>(),
|
||||
@@ -323,6 +341,17 @@ defineEmits<{
|
||||
|
||||
const loginPageContext = inject(LOGIN_PAGE_CONTEXT_KEY, null)
|
||||
const isRegister = computed(() => unref(loginPageContext?.loginForm?.isRegister) ?? false)
|
||||
const mobileFieldLabel = computed(() => (props.variant === 'desktop' ? 'شماره موبایل' : 'شماره تلفن'))
|
||||
const apkDownloadHref = computed(() => {
|
||||
const link = setting.getSettingByKey('apk_download_link')
|
||||
return link ? String(link) : ''
|
||||
})
|
||||
|
||||
function onApkDownloadClick() {
|
||||
if (!apkDownloadHref.value) {
|
||||
message.info('لینک دانلود اندروید در تنظیمات تعریف نشده است.')
|
||||
}
|
||||
}
|
||||
|
||||
const config=useConfigStore()
|
||||
const { time, onFinish, reset } = useCountdownTimer('00:02:00', 'mm:ss')
|
||||
@@ -465,8 +494,8 @@ async function goToAboutUs(){
|
||||
await router.push('/aboutUs')
|
||||
}
|
||||
|
||||
async function goToAboutSoftware() {
|
||||
await router.push({ name: 'aboutSoftware' })
|
||||
async function goToGuestHome() {
|
||||
await router.push(guestHomeRoute())
|
||||
}
|
||||
|
||||
const { tryInstallOrOpenGuide: installWebApp } = usePwaInstall()
|
||||
|
||||
@@ -100,8 +100,13 @@
|
||||
<a-spin v-if="loading" size="small" />
|
||||
<span v-else>ثبت نام</span>
|
||||
</BasicButton>
|
||||
<button type="button" class="dauth-link-btn dauth-link-btn--primary" @click="goToAboutSoftware">
|
||||
درباره نرمافزار
|
||||
<button
|
||||
v-if="!isTWA()"
|
||||
type="button"
|
||||
class="dauth-btn-stack"
|
||||
@click="installWebApp"
|
||||
>
|
||||
<span>نصب وب اپلیکیشن</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -124,8 +129,14 @@
|
||||
<div v-else>ثبت نام</div>
|
||||
</div>
|
||||
</BasicButton>
|
||||
<BasicButton class="mt-2 w-full" @click="goToAboutSoftware">
|
||||
<div>درباره نرمافزار</div>
|
||||
<BasicButton
|
||||
v-if="!isTWA()"
|
||||
class="mt-2 w-full"
|
||||
type="primary"
|
||||
html-type="button"
|
||||
@click="installWebApp"
|
||||
>
|
||||
<div>نصب وب اپلیکیشن</div>
|
||||
</BasicButton>
|
||||
<div
|
||||
class="cursor-pointer flex gap-1 justify-center mt-5 text-xs"
|
||||
@@ -221,8 +232,9 @@ import { registerCustomer } from '@/api/login'
|
||||
import router from '@/router'
|
||||
import {useConfigStore} from "@/store/configStore";
|
||||
import {useSettingsStore} from '@/store/settingsStore'
|
||||
import {getProjectLoginLogo, getProjectLoginTaglineLines, hasCustomLoginLogoLayout} from "@/utils/config";
|
||||
import { getProjectLoginLogo, getProjectLoginTaglineLines, hasCustomLoginLogoLayout, isTWA } from "@/utils/config";
|
||||
import { normalizeAuthFormFields } from '@/utils/persianDigits'
|
||||
import { usePwaInstall } from '@/composables/usePwaInstall'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
@@ -311,9 +323,7 @@ async function goToAboutUs(){
|
||||
await router.push('/aboutUs')
|
||||
}
|
||||
|
||||
async function goToAboutSoftware() {
|
||||
await router.push({ name: 'aboutSoftware' })
|
||||
}
|
||||
const { tryInstallOrOpenGuide: installWebApp } = usePwaInstall()
|
||||
|
||||
const goToEducate = () => {
|
||||
router.push({ name: "educate" })
|
||||
|
||||
@@ -13,7 +13,7 @@ const page = useLoginPageContext()
|
||||
<header v-if="!page.enterType" class="dauth-card__header">
|
||||
<p class="dauth-card__eyebrow">حساب کاربری</p>
|
||||
<h2 class="dauth-card__title">ورود</h2>
|
||||
<p class="dauth-card__subtitle">برای ادامه، روش ورود خود را انتخاب کنید</p>
|
||||
<p class="dauth-card__subtitle">برای ادامه، رمز ورود خود را انتخاب کنید.</p>
|
||||
</header>
|
||||
<header v-else class="dauth-card__header">
|
||||
<p class="dauth-card__eyebrow">حساب جدید</p>
|
||||
|
||||
@@ -1,66 +1,167 @@
|
||||
<template>
|
||||
<div class="dauth dauth--boursecu">
|
||||
<div class="dauth__layout">
|
||||
<main class="dauth-form-panel">
|
||||
<slot />
|
||||
</main>
|
||||
<div class="dauth-page-logo">
|
||||
<img :src="pageLogo" alt="بورس مس" class="dauth-page-logo__img">
|
||||
</div>
|
||||
|
||||
<aside class="dauth-brand dauth-brand--boursecu">
|
||||
<div class="dauth-brand__pattern" />
|
||||
<div class="dauth-brand__content">
|
||||
<img
|
||||
:src="logo"
|
||||
alt=""
|
||||
class="dauth-brand__logo"
|
||||
>
|
||||
<h1 class="dauth-brand__title">{{ brandTitle }}</h1>
|
||||
<p v-if="tagline" class="dauth-brand__tagline">{{ tagline }}</p>
|
||||
<ul class="dauth-brand__features">
|
||||
<li v-for="feature in features" :key="feature" class="dauth-brand__feature">
|
||||
<span class="dauth-brand__feature-icon">
|
||||
<BasicIcon icon="vuesax-linear:tick-circle" size="16" />
|
||||
<div class="dauth__layout">
|
||||
<div class="dauth-visual-col">
|
||||
<img
|
||||
:src="copperProducts"
|
||||
alt=""
|
||||
class="dauth-visual-col__products"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
|
||||
<section class="dauth-center-col" aria-label="معرفی بورس مس">
|
||||
<div class="dauth-center-col__body">
|
||||
<h1 class="dauth-center-col__headline">
|
||||
<span class="dauth-center-col__headline-line">به دنیای معاملات مس</span>
|
||||
<span class="dauth-center-col__headline-accent">خوش آمدید</span>
|
||||
</h1>
|
||||
<p class="dauth-center-col__lead">
|
||||
بورس مس مسیر خرید و فروش آنلاین مس را ساده، سریع و قابل اعتماد کرده است؛ با پشتوانه تولیدکنندگان واقعی.
|
||||
</p>
|
||||
<ul class="dauth-center-col__features">
|
||||
<li v-for="item in featureItems" :key="item.title" class="dauth-center-col__feature">
|
||||
<span class="dauth-center-col__feature-icon" aria-hidden="true">
|
||||
<BasicIcon :icon="item.icon" size="22" />
|
||||
</span>
|
||||
{{ feature }}
|
||||
<div class="dauth-center-col__feature-text">
|
||||
<p class="dauth-center-col__feature-title">{{ item.title }}</p>
|
||||
<p class="dauth-center-col__feature-desc">{{ item.desc }}</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dauth-brand__footer">نسخه {{ version }}</div>
|
||||
</aside>
|
||||
<p class="dauth-center-col__tagline">
|
||||
<span class="dauth-center-col__tagline-line" aria-hidden="true" />
|
||||
<span>مس، آیندهای مطمئن و...</span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<main class="dauth-form-panel">
|
||||
<div class="dauth-form-column">
|
||||
<div class="dauth-form-column__top">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="dauth-form-column__foot">
|
||||
<nav class="dauth-form-links" aria-label="پیوندهای صفحه ورود">
|
||||
<span class="dauth-form-links__text">معاملات آتی</span>
|
||||
<span class="dauth-form-links__sep" aria-hidden="true">|</span>
|
||||
<span class="dauth-form-links__text">دسترسی به بازار</span>
|
||||
<span class="dauth-form-links__sep" aria-hidden="true">|</span>
|
||||
<button type="button" class="dauth-form-links__link dauth-form-links__link--accent" @click="goAboutUs">
|
||||
درباره مس
|
||||
</button>
|
||||
<span class="dauth-form-links__sep" aria-hidden="true">|</span>
|
||||
<span class="dauth-form-links__text">پشتیبانی</span>
|
||||
</nav>
|
||||
<div class="dauth-page-footer__status">
|
||||
<span class="dauth-page-footer__status-icon" aria-hidden="true">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M4.5 16.2L9 12.2L11.6 14.6L17.2 7.8"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M14.2 7.8H17.2V10.8"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>پلتفرم معاملات مس، در دسترس است</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="dauth-form-support dauth-form-support--fab"
|
||||
:class="{ 'dauth-form-support--fab-dragging': supportDragging }"
|
||||
:style="supportFabStyle"
|
||||
title="پشتیبانی"
|
||||
@pointerdown="onSupportPointerDown"
|
||||
@pointermove="onSupportPointerMove"
|
||||
@pointerup="onSupportFabPointerUp"
|
||||
@pointercancel="onSupportPointerCancel"
|
||||
>
|
||||
<BasicIcon icon="vuesax-linear:headphone" size="16" />
|
||||
<span>پشتیبانی</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import pkg from '../../../../../package.json'
|
||||
import { getDisplayAppVersion } from '@/config/config-cache'
|
||||
import router from '@/router'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useConfigStore } from '@/store/configStore'
|
||||
import {
|
||||
getProjectDesktopLoginBrandLogo,
|
||||
getProjectLoginTaglineLines,
|
||||
} from '@/utils/config'
|
||||
import { getProjectLoginLogo } from '@/utils/config'
|
||||
import { useDraggableFloatingButton } from '@/utils/useDraggableFloatingButton'
|
||||
import copperProducts from '../../assets/img/login-copper-hero.jpg'
|
||||
|
||||
const config = useConfigStore()
|
||||
const version = getDisplayAppVersion(pkg.version)
|
||||
const pageLogo = getProjectLoginLogo()
|
||||
|
||||
const logo = computed(() => getProjectDesktopLoginBrandLogo(config.config?.avatar))
|
||||
|
||||
const taglineLines = computed(() => getProjectLoginTaglineLines())
|
||||
const tagline = computed(() => {
|
||||
if (taglineLines.value?.length) {
|
||||
return taglineLines.value.join(' ')
|
||||
}
|
||||
return 'پلتفرم امن و سریع برای مدیریت دارایی و معاملات شما'
|
||||
const {
|
||||
dragging: supportDragging,
|
||||
fabStyle: supportFabStyle,
|
||||
handlePointerDown: onSupportPointerDown,
|
||||
handlePointerMove: onSupportPointerMove,
|
||||
handlePointerUp: supportPointerUp,
|
||||
handlePointerCancel: onSupportPointerCancel,
|
||||
} = useDraggableFloatingButton('boursecu_login_support_fab_v2', {
|
||||
width: 108,
|
||||
height: 40,
|
||||
defaultBottomGap: 28,
|
||||
defaultRightOffset: 24,
|
||||
fullViewport: true,
|
||||
ignoreBottomChrome: true,
|
||||
})
|
||||
|
||||
const brandTitle = computed(() => config.config?.name_fa ?? 'خوش آمدید')
|
||||
|
||||
const features = [
|
||||
'قیمت لحظهای و نمودار بازار',
|
||||
'مدیریت دارایی و کیف پول',
|
||||
'معاملات سریع و امن',
|
||||
const featureItems = [
|
||||
{
|
||||
icon: 'vuesax-linear:box',
|
||||
title: 'خرید و فروش کاتد، مفتول و سیم کابل',
|
||||
desc: 'با پشتوانه تولیدکننده واقعی',
|
||||
},
|
||||
{
|
||||
icon: 'vuesax-linear:refresh-circle',
|
||||
title: 'قابلیت تبدیل محصول',
|
||||
desc: 'از کاتد و مفتول به محصول نهایی (سیم و کابل)',
|
||||
},
|
||||
{
|
||||
icon: 'vuesax-linear:truck-tick',
|
||||
title: 'قابلیت تحویل فیزیکی',
|
||||
desc: 'بهصورت مطمئن و برنامهریزیشده',
|
||||
},
|
||||
{
|
||||
icon: 'vuesax-linear:shield-tick',
|
||||
title: 'قابلیت خرید اعتباری',
|
||||
desc: 'با شرایط منعطف و مطمئن',
|
||||
},
|
||||
]
|
||||
|
||||
function onSupportClick() {
|
||||
void router.push({ name: 'support' })
|
||||
}
|
||||
|
||||
function onSupportFabPointerUp(event: PointerEvent) {
|
||||
const clicked = supportPointerUp(event)
|
||||
if (!clicked) return
|
||||
onSupportClick()
|
||||
}
|
||||
|
||||
function goAboutUs() {
|
||||
void router.push({ name: 'aboutUs' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" src="../styles/desktop-auth.less"></style>
|
||||
|
||||
@@ -44,10 +44,6 @@ function goToEducate() {
|
||||
function goToAboutUs() {
|
||||
router.push('/aboutUs')
|
||||
}
|
||||
|
||||
function goToAboutSoftware() {
|
||||
router.push({ name: 'aboutSoftware' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -166,23 +162,23 @@ function goToAboutSoftware() {
|
||||
>
|
||||
ورود با اثر انگشت
|
||||
</BasicButton>
|
||||
<button type="button" class="dauth-link-btn" @click="goToAboutSoftware">
|
||||
درباره نرمافزار
|
||||
|
||||
<button
|
||||
v-if="!isTWA()"
|
||||
type="button"
|
||||
class="dauth-btn-stack"
|
||||
@click="installWebApp"
|
||||
>
|
||||
<span>نصب وب اپلیکیشن</span>
|
||||
</button>
|
||||
|
||||
<div v-if="!isTWA()" class="dauth-actions-row flex flex-col gap-2">
|
||||
<a
|
||||
v-if="!!setting.getSettingByKey('apk_download_link')"
|
||||
class="dauth-link-btn dauth-link-btn--primary"
|
||||
:href="setting.getSettingByKey('apk_download_link')"
|
||||
>
|
||||
دانلود اپ اندروید
|
||||
</a>
|
||||
<BasicButton html-type="button" type="primary" class="w-full" @click.prevent="installWebApp">
|
||||
نصب نسخه وب اپ
|
||||
</BasicButton>
|
||||
</div>
|
||||
|
||||
<a
|
||||
v-if="!isTWA() && !!setting.getSettingByKey('apk_download_link')"
|
||||
class="dauth-link-btn dauth-link-btn--primary"
|
||||
:href="setting.getSettingByKey('apk_download_link')"
|
||||
>
|
||||
دانلود اپ اندروید
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div v-if="isRegister" class="dauth-switch">
|
||||
|
||||
@@ -9,6 +9,8 @@ import { useConfigStore } from '@/store/configStore'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { useLoginPageContext } from '../../composables/useLoginPage'
|
||||
import router from '@/router'
|
||||
import { isTWA } from '@/utils/config'
|
||||
import { usePwaInstall } from '@/composables/usePwaInstall'
|
||||
|
||||
const emit = defineEmits<{
|
||||
register: [val: boolean]
|
||||
@@ -16,6 +18,7 @@ const emit = defineEmits<{
|
||||
|
||||
const config = useConfigStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
const { tryInstallOrOpenGuide: installWebApp } = usePwaInstall()
|
||||
const referralIdRules = computed(() =>
|
||||
settingsStore.isReferralRequired ? [required('کد معرف')] : [],
|
||||
)
|
||||
@@ -40,10 +43,6 @@ function goToAboutUs() {
|
||||
router.push('/aboutUs')
|
||||
}
|
||||
|
||||
function goToAboutSoftware() {
|
||||
router.push({ name: 'aboutSoftware' })
|
||||
}
|
||||
|
||||
function handleSuccessDismiss() {
|
||||
dismissSuccess()
|
||||
emit('register', false)
|
||||
@@ -119,8 +118,13 @@ function handleSuccessDismiss() {
|
||||
<a-spin v-if="loading" size="small" />
|
||||
<span v-else>ثبت نام</span>
|
||||
</BasicButton>
|
||||
<button type="button" class="dauth-link-btn" @click="goToAboutSoftware">
|
||||
درباره نرمافزار
|
||||
<button
|
||||
v-if="!isTWA()"
|
||||
type="button"
|
||||
class="dauth-btn-stack"
|
||||
@click="installWebApp"
|
||||
>
|
||||
<span>نصب وب اپلیکیشن</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
.dauth {
|
||||
min-height: 100vh;
|
||||
/* Force exact viewport sizing to avoid page scrolling */
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
box-sizing: border-box;
|
||||
background: #f6f7f9;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
@@ -489,6 +495,895 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Bourse Mes login mockup (۳ ناحیه: تصویر | متن | کارت) ─── */
|
||||
@boursecu-navy: #0b192e;
|
||||
@boursecu-navy-deep: #0a1628;
|
||||
@boursecu-icon-bg: #152a45;
|
||||
@boursecu-copper: #d4843f;
|
||||
@boursecu-copper-soft: #e8a04a;
|
||||
@boursecu-btn: #1a3554;
|
||||
|
||||
.dauth--boursecu {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: @boursecu-navy;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.dauth-page-logo {
|
||||
position: absolute;
|
||||
top: clamp(20px, 3.2vh, 36px);
|
||||
left: clamp(28px, 3vw, 52px);
|
||||
z-index: 6;
|
||||
|
||||
&__img {
|
||||
display: block;
|
||||
width: clamp(132px, 12.5vw, 172px);
|
||||
height: auto;
|
||||
max-height: clamp(112px, 16vh, 152px);
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth__layout {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
direction: ltr;
|
||||
/* تصویر چپ | متن وسط | کارت راست — موکاپ */
|
||||
grid-template-columns: minmax(220px, 0.82fr) minmax(340px, 1.4fr) minmax(380px, 440px);
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
gap: clamp(8px, 1.4vw, 24px);
|
||||
align-items: stretch;
|
||||
padding:
|
||||
clamp(88px, 11vh, 108px)
|
||||
clamp(28px, 3.2vw, 52px)
|
||||
clamp(88px, 11vh, 112px)
|
||||
0;
|
||||
}
|
||||
|
||||
/* تصویر: پایینچپ، بزرگ، bleed به لبه چپ/پایین */
|
||||
.dauth-visual-col {
|
||||
direction: ltr;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
|
||||
&__products {
|
||||
display: block;
|
||||
width: clamp(460px, 50vw, 740px);
|
||||
max-width: none;
|
||||
min-width: 380px;
|
||||
max-height: min(66vh, 580px);
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
object-position: left bottom;
|
||||
margin: 0;
|
||||
margin-inline-start: -10%;
|
||||
margin-bottom: -28px;
|
||||
transform: translateX(-8%);
|
||||
mix-blend-mode: lighten;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* متن: بالا همتراز کارت؛ شعار پایین همتراز لینکها */
|
||||
.dauth-center-col {
|
||||
direction: rtl;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-self: stretch;
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
padding-top: 0;
|
||||
padding-inline: clamp(4px, 1.2vw, 20px) clamp(4px, 1vw, 12px);
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 1 auto;
|
||||
min-height: 0;
|
||||
/* با tagline که margin-top:auto دارد → محتوا وسطچین عمودی مثل موکاپ */
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
&__headline {
|
||||
margin: 0 0 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__headline-line {
|
||||
font-size: clamp(24px, 2.3vw, 32px);
|
||||
color: #ffffff;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
&__headline-accent {
|
||||
font-size: clamp(32px, 3.2vw, 44px);
|
||||
color: @boursecu-copper-soft;
|
||||
letter-spacing: -0.02em;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
&__lead {
|
||||
margin: 0 0 22px;
|
||||
max-width: 520px;
|
||||
font-size: 13px;
|
||||
line-height: 1.9;
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
}
|
||||
|
||||
&__features {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&__feature {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__feature-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
background: @boursecu-icon-bg;
|
||||
border: 1px solid rgba(232, 160, 74, 0.4);
|
||||
color: @boursecu-copper-soft;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__feature-title {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
&__feature-desc {
|
||||
margin: 2px 0 0;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
color: rgba(255, 255, 255, 0.48);
|
||||
}
|
||||
|
||||
&__tagline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
margin-top: auto;
|
||||
/* همتراز لینکها؛ استاتوس زیر لینکهاست */
|
||||
margin-bottom: calc(10px + 36px);
|
||||
margin-inline-start: clamp(20px, 3.5vw, 48px);
|
||||
padding-top: 0;
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.45;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
&__tagline-line {
|
||||
width: 40px;
|
||||
height: 2px;
|
||||
background: @boursecu-copper-soft;
|
||||
border-radius: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* کارت+پشتیبانی وسط؛ فوتر (لینک+استاتوس) پایین راست — همتراز شعار */
|
||||
.dauth-form-panel {
|
||||
direction: rtl;
|
||||
background: transparent;
|
||||
padding: 0 !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start; /* RTL: لبه راست ستون */
|
||||
align-self: stretch;
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dauth-form-column {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.dauth-form-column__top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
flex: 0 0 auto;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.dauth-form-column__foot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start; /* RTL: راستچین زیر کارت */
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
margin-top: auto;
|
||||
margin-bottom: 8px;
|
||||
margin-inline-start: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.dauth-form-support {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 7px 16px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.45);
|
||||
background: rgba(12, 28, 48, 0.94);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(10, 22, 40, 0.75);
|
||||
border-color: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
&--fab {
|
||||
position: fixed;
|
||||
z-index: 5200;
|
||||
display: inline-flex;
|
||||
margin: 0;
|
||||
min-height: 40px;
|
||||
padding: 8px 14px;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
&--fab-dragging {
|
||||
cursor: grabbing;
|
||||
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.36);
|
||||
opacity: 0.96;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-form-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
|
||||
&__link {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
cursor: pointer;
|
||||
transition: color 0.15s ease;
|
||||
|
||||
&:hover,
|
||||
&--accent {
|
||||
color: @boursecu-copper-soft;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&__sep {
|
||||
color: rgba(255, 255, 255, 0.28);
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-card {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
border-radius: 14px;
|
||||
padding: 24px 26px 20px;
|
||||
box-shadow: none !important;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dauth-card__eyebrow {
|
||||
color: #9ca3af;
|
||||
font-size: 11px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.dauth-card__title {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.dauth-card__subtitle {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.dauth-toggle {
|
||||
background: #eceff3;
|
||||
border-radius: 12px;
|
||||
padding: 5px;
|
||||
gap: 6px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__btn {
|
||||
border-radius: 10px;
|
||||
padding: 10px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #4b5563;
|
||||
|
||||
&--active {
|
||||
background: @boursecu-btn;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 4px 12px rgba(15, 35, 60, 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-form {
|
||||
:deep(.ant-form-item-label > label) {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
border-radius: 12px;
|
||||
min-height: 46px;
|
||||
border-color: #d1d5db;
|
||||
padding-inline: 14px;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&-focused {
|
||||
border-color: @boursecu-btn;
|
||||
box-shadow: 0 0 0 2px rgba(26, 53, 84, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input-affix-wrapper .ant-input-prefix) {
|
||||
color: #9ca3af;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-actions {
|
||||
gap: 9px;
|
||||
margin-top: 2px;
|
||||
|
||||
&__submit,
|
||||
:deep(.noname-base-button.dauth-actions__submit) {
|
||||
min-height: 44px;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
background: @boursecu-btn !important;
|
||||
border-color: @boursecu-btn !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-btn-stack {
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid @boursecu-btn;
|
||||
background: @boursecu-btn;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
box-sizing: border-box;
|
||||
padding: 0 14px;
|
||||
transition: opacity 0.15s ease, background 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled):not(.dauth-btn-stack--disabled) {
|
||||
opacity: 0.94;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:disabled,
|
||||
&--disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&--link {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-link-btn--primary {
|
||||
background: @boursecu-btn;
|
||||
border-color: @boursecu-btn;
|
||||
min-height: 44px;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dauth-switch {
|
||||
margin-top: 14px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
|
||||
&__link {
|
||||
color: @boursecu-copper;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-page-footer__status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px 6px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(148, 180, 220, 0.28);
|
||||
background: rgba(12, 28, 48, 0.92);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.dauth-page-footer__status-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 999px;
|
||||
background: rgba(8, 18, 32, 0.85);
|
||||
color: @boursecu-copper-soft;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* دسکتاپ متوسط: جمعوجورتر بدون کوچککردن زیاد عکس */
|
||||
@media (max-width: 1360px) and (min-width: 1101px) {
|
||||
.dauth--boursecu {
|
||||
.dauth__layout {
|
||||
grid-template-columns: minmax(220px, 0.8fr) minmax(300px, 1.3fr) minmax(360px, 420px);
|
||||
gap: clamp(6px, 1vw, 16px);
|
||||
padding-bottom: clamp(72px, 9vh, 96px);
|
||||
}
|
||||
|
||||
.dauth-visual-col__products {
|
||||
width: clamp(420px, 46vw, 640px);
|
||||
min-width: 360px;
|
||||
max-height: min(60vh, 520px);
|
||||
margin-inline-start: -8%;
|
||||
transform: translateX(-6%);
|
||||
}
|
||||
|
||||
.dauth-form-column__foot {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dauth-center-col {
|
||||
&__headline-line,
|
||||
&__headline-accent {
|
||||
font-size: clamp(24px, 2.2vw, 30px);
|
||||
}
|
||||
|
||||
&__lead {
|
||||
font-size: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__features {
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ارتفاع کوتاه دسکتاپ */
|
||||
@media (max-height: 780px) and (min-width: 768px) {
|
||||
.dauth--boursecu {
|
||||
.dauth__layout {
|
||||
padding-top: clamp(72px, 9vh, 92px);
|
||||
padding-bottom: clamp(28px, 4vh, 44px);
|
||||
}
|
||||
|
||||
.dauth-visual-col__products {
|
||||
max-height: min(48vh, 360px);
|
||||
margin-bottom: -16px;
|
||||
}
|
||||
|
||||
.dauth-center-col {
|
||||
&__lead {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
&__features {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__feature-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── تبلت عریض (≈768–1100): دو ستون — برند چپ، فرم راست ─── */
|
||||
@media (max-width: 1100px) and (min-width: 768px) {
|
||||
.dauth--boursecu {
|
||||
.dauth-page-logo {
|
||||
top: 20px;
|
||||
left: 24px;
|
||||
|
||||
&__img {
|
||||
width: clamp(110px, 14vw, 140px);
|
||||
max-height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth__layout {
|
||||
direction: ltr;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(340px, 420px);
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
grid-template-areas:
|
||||
'brand form'
|
||||
'visual form';
|
||||
align-items: stretch;
|
||||
gap: 16px 28px;
|
||||
padding: 96px 28px 20px 0;
|
||||
min-height: calc(100vh - 64px);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.dauth-center-col {
|
||||
grid-area: brand;
|
||||
order: unset;
|
||||
min-height: auto;
|
||||
height: auto;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 8px 12px 0 20px;
|
||||
justify-content: flex-start;
|
||||
|
||||
&__headline-line,
|
||||
&__headline-accent {
|
||||
font-size: clamp(24px, 3vw, 30px);
|
||||
}
|
||||
|
||||
&__lead {
|
||||
font-size: 13px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
&__features {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
&__tagline {
|
||||
margin-top: 20px;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-visual-col {
|
||||
grid-area: visual;
|
||||
order: unset;
|
||||
min-height: auto;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 0;
|
||||
|
||||
&__products {
|
||||
width: clamp(360px, 44vw, 520px);
|
||||
min-width: 320px;
|
||||
max-height: min(46vh, 380px);
|
||||
margin-inline-start: -4%;
|
||||
margin-bottom: -8px;
|
||||
transform: translateX(-4%);
|
||||
mix-blend-mode: lighten;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-form-panel {
|
||||
grid-area: form;
|
||||
order: unset;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
padding-inline-end: 8px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.dauth-form-column {
|
||||
max-width: 400px;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.dauth-center-col__body {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.dauth-form-column__top {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.dauth-form-column__foot {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.dauth-card {
|
||||
box-shadow: none !important;
|
||||
padding: 22px 22px 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── موبایل (<768): لوگو + کارت + پشتیبانی، وسطچین عمودی ─── */
|
||||
@media (max-width: 767px) {
|
||||
.dauth--boursecu {
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 12px 0;
|
||||
|
||||
.dauth-page-logo {
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: auto;
|
||||
transform: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 16px 12px;
|
||||
z-index: 5;
|
||||
flex-shrink: 0;
|
||||
|
||||
&__img {
|
||||
width: 104px;
|
||||
max-height: 96px;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
direction: rtl;
|
||||
flex: 0 1 auto;
|
||||
gap: 12px;
|
||||
padding: 0 16px;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dauth-form-panel {
|
||||
order: 1;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dauth-form-column {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.dauth-form-column__top {
|
||||
gap: 12px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.dauth-form-column__foot {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0;
|
||||
margin-inline-start: 0;
|
||||
padding-top: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dauth-page-footer__status {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dauth-card {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
border-radius: 14px;
|
||||
padding: 20px 16px 16px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.dauth-card__header {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.dauth-card__title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.dauth-card__subtitle {
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dauth-toggle {
|
||||
margin-bottom: 14px;
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
|
||||
&__btn {
|
||||
padding: 10px 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-form {
|
||||
&.ant-form-vertical :deep(.ant-form-item) {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-actions {
|
||||
gap: 8px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.dauth-btn-stack,
|
||||
.dauth-actions__submit,
|
||||
:deep(.noname-base-button.dauth-actions__submit) {
|
||||
min-height: 44px;
|
||||
border-radius: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dauth-form-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: 6px 8px;
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
flex-wrap: wrap;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dauth-center-col {
|
||||
display: flex;
|
||||
order: 2;
|
||||
min-height: auto;
|
||||
height: auto;
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
padding: 4px 0 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__body {
|
||||
display: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&__tagline {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
margin: 8px 0 0;
|
||||
padding-top: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-visual-col {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dauth-switch {
|
||||
margin-top: 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.dauth--boursecu {
|
||||
.dauth-page-logo {
|
||||
&__img {
|
||||
width: 88px;
|
||||
max-height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.dauth-form-links {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px 10px;
|
||||
justify-items: center;
|
||||
|
||||
&__sep {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-form--desktop {
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 18px;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div class="login-page-root">
|
||||
<!-- Desktop: premium split-panel -->
|
||||
<DesktopAuthShell v-show="isDesktop">
|
||||
<DesktopAuthShell>
|
||||
<div class="dauth-card">
|
||||
<header v-if="!loginPage.enterType" class="dauth-card__header">
|
||||
<p class="dauth-card__eyebrow">حساب کاربری</p>
|
||||
<h2 class="dauth-card__title">ورود</h2>
|
||||
<p class="dauth-card__subtitle">برای ادامه، روش ورود خود را انتخاب کنید</p>
|
||||
<p class="dauth-card__subtitle">برای ادامه، رمز ورود خود را انتخاب کنید.</p>
|
||||
</header>
|
||||
<header v-else class="dauth-card__header">
|
||||
<p class="dauth-card__eyebrow">حساب جدید</p>
|
||||
@@ -14,53 +13,101 @@
|
||||
<p class="dauth-card__subtitle">اطلاعات خود را برای ایجاد حساب وارد کنید</p>
|
||||
</header>
|
||||
|
||||
<Login v-if="!loginPage.enterType" variant="desktop" @register="loginPage.setEnterType" />
|
||||
<Register v-else variant="desktop" @register="loginPage.setEnterType" />
|
||||
<Login
|
||||
v-if="!loginPage.enterType"
|
||||
variant="desktop"
|
||||
@register="loginPage.setEnterType"
|
||||
/>
|
||||
<Register
|
||||
v-else
|
||||
variant="desktop"
|
||||
@register="loginPage.setEnterType"
|
||||
/>
|
||||
</div>
|
||||
</DesktopAuthShell>
|
||||
|
||||
<!-- Mobile: original layout -->
|
||||
<div v-show="!isDesktop" class="login-page flex flex-col items-center">
|
||||
<div class="login-page__content z-1000 w-full p-5">
|
||||
<Login v-if="!loginPage.enterType" @register="loginPage.setEnterType" />
|
||||
<Register v-else @register="loginPage.setEnterType" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { useLoginPage } from './composables/useLoginPage'
|
||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||
import Login from './components/Login.vue'
|
||||
import Register from './components/Register.vue'
|
||||
import DesktopAuthShell from './desktop/components/DesktopAuthShell.vue'
|
||||
|
||||
const isDesktop = useDesktopBreakpoint()
|
||||
const loginPage = useLoginPage()
|
||||
|
||||
onMounted(() => {
|
||||
document.documentElement.classList.add('login-page-active')
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.documentElement.classList.remove('login-page-active')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-page-root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-page {
|
||||
height: 100%;
|
||||
max-height: calc(100dvh - var(--app-credit-footer-offset, 28px));
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.login-page__content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
/* موبایل لاگین: تمامعرض — بدون فریم ۴۲۶ و حاشیه سفید دوطرف */
|
||||
@media screen and (max-width: 767.98px) {
|
||||
html.login-page-active {
|
||||
body {
|
||||
display: block !important;
|
||||
justify-content: unset !important;
|
||||
background: #0b192e !important;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
background: #0b192e !important;
|
||||
}
|
||||
|
||||
.desktopConfig,
|
||||
.app-main,
|
||||
.login-page-root,
|
||||
.dauth,
|
||||
.dauth--boursecu {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
margin: 0 !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.desktopConfig,
|
||||
.app-main,
|
||||
.login-page-root {
|
||||
min-height: 100dvh !important;
|
||||
height: auto !important;
|
||||
max-height: none !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
background: #0b192e !important;
|
||||
}
|
||||
|
||||
.dauth--boursecu {
|
||||
flex: 1 1 auto;
|
||||
min-height: 100dvh !important;
|
||||
}
|
||||
|
||||
.app-credit-footer--mobile,
|
||||
.app-credit-footer--login {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
<template>
|
||||
<div class="login-page flex items-center justify-center">
|
||||
<div class="z-1000 w-full p-5">
|
||||
<Login v-if="!page.enterType" key="login" @register="page.setEnterType" />
|
||||
<Register v-else key="register" @register="page.setEnterType" />
|
||||
<DesktopAuthShell>
|
||||
<div class="dauth-card">
|
||||
<header v-if="!page.enterType" class="dauth-card__header">
|
||||
<p class="dauth-card__eyebrow">حساب کاربری</p>
|
||||
<h2 class="dauth-card__title">ورود</h2>
|
||||
<p class="dauth-card__subtitle">برای ادامه، رمز ورود خود را انتخاب کنید.</p>
|
||||
</header>
|
||||
<header v-else class="dauth-card__header">
|
||||
<p class="dauth-card__eyebrow">حساب جدید</p>
|
||||
<h2 class="dauth-card__title">ثبت نام</h2>
|
||||
<p class="dauth-card__subtitle">اطلاعات خود را برای ایجاد حساب وارد کنید</p>
|
||||
</header>
|
||||
|
||||
<Login v-if="!page.enterType" variant="desktop" @register="page.setEnterType" />
|
||||
<Register v-else variant="desktop" @register="page.setEnterType" />
|
||||
</div>
|
||||
</div>
|
||||
</DesktopAuthShell>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Login from '../components/Login.vue'
|
||||
import Register from '../components/Register.vue'
|
||||
import DesktopAuthShell from '../desktop/components/DesktopAuthShell.vue'
|
||||
import { useLoginPageContext } from '../composables/useLoginPage'
|
||||
|
||||
const page = useLoginPageContext()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-page {
|
||||
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>
|
||||
|
||||