Polish boursecu UX: branding logos, home price stats, assets, and desktop footer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 10:32:10 +03:30
co-authored by Cursor
parent 033e31e7a9
commit 31caa8cca7
29 changed files with 384 additions and 197 deletions
+1 -1
View File
@@ -14,6 +14,7 @@ declare module 'vue' {
AInput: typeof import('ant-design-vue/es')['Input'] AInput: typeof import('ant-design-vue/es')['Input']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
AppCreditLink: typeof import('./src/components/AppCreditLink.vue')['default']
ARadio: typeof import('ant-design-vue/es')['Radio'] ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
@@ -69,6 +70,5 @@ declare module 'vue' {
TransactionSkeleton: typeof import('./src/components/TransactionSkeleton.vue')['default'] TransactionSkeleton: typeof import('./src/components/TransactionSkeleton.vue')['default']
VanActionSheet: typeof import('vant/es')['ActionSheet'] VanActionSheet: typeof import('vant/es')['ActionSheet']
VanDialog: typeof import('vant/es')['Dialog'] VanDialog: typeof import('vant/es')['Dialog']
VanField: typeof import('vant/es')['Field']
} }
} }
+120 -22
View File
@@ -27,10 +27,13 @@
</template> </template>
<div class="self-center splash_appear_fade_in"> <div class="self-center splash_appear_fade_in">
<img <img
:src="getProjectLoginLogo(config?.config?.avatar)" :src="getProjectSplashLogo(config?.config?.avatar)"
:class="hasCustomLoginLogoLayout() :class="isBoursecuProject()
? 'w-[80vw] max-w-[320px] h-auto object-contain' ? 'w-[min(72vw,280px)] h-auto object-contain'
: 'h-[20vh] object-contain'" : hasCustomLoginLogoLayout()
? 'w-[80vw] max-w-[320px] h-auto object-contain'
: 'h-[20vh] object-contain'"
alt=""
> >
</div> </div>
<div class="fixed w-full bottom-5 home_appear_fade_in_up"> <div class="fixed w-full bottom-5 home_appear_fade_in_up">
@@ -90,20 +93,16 @@
</div> </div>
</div> </div>
<div <div
v-if="!showSplash" v-if="!showSplash && !isDesktop"
class="app-credit-footer flex bg-white justify-center text-[9px]" class="app-credit-footer app-credit-footer--mobile"
> >
<a <AppCreditLink variant="mobile" />
href="https://montzar.ir" </div>
target="_blank" <div
rel="noopener noreferrer" v-if="!showSplash && showDesktopFloatingCredit"
class="flex items-center gap-1 text-inherit no-underline" class="app-credit-footer app-credit-footer--desktop-float"
> >
<div class="flex justify-center py-1 items-center"> <AppCreditLink variant="float" />
<div>توسعه و طراحی توسط</div>
<img src="/logo-montzar.png" alt="" class="h-3" />
</div>
</a>
</div> </div>
</template> </template>
@@ -118,19 +117,28 @@ import img_bottomGradiant from '@/views/login/assets/img/gr_bottom_white.svg'
import {DotLottieVue} from '@lottiefiles/dotlottie-vue' import {DotLottieVue} from '@lottiefiles/dotlottie-vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue' import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import {useConfigStore} from "@/store/configStore"; import {useConfigStore} from "@/store/configStore";
import {getProjectFontFamily, getProjectLoginLogo, getProjectSplashBackground, getProjectBrandRed, hasCustomLoginLogoLayout, isBoursecuProject, resetAppSurfaceBackground} from "@/utils/config"; import {getProjectFontFamily, getProjectSplashBackground, getProjectSplashLogo, getProjectBrandRed, hasCustomLoginLogoLayout, isBoursecuProject, resetAppSurfaceBackground} from "@/utils/config";
import {ConfigProvider as AConfigProvider} from 'ant-design-vue' import {ConfigProvider as AConfigProvider} from 'ant-design-vue'
import BasicIcon from "@/components/BasicPacks/BasicIcon.vue"; import BasicIcon from "@/components/BasicPacks/BasicIcon.vue";
import { useApplyDesktopLayoutClass } from '@/layouts/composables/useAppLayout' import { useApplyDesktopLayoutClass, useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import { import {
buildSupportMessengerUrl, buildSupportMessengerUrl,
loadSupportMessengerFromSettings, loadSupportMessengerFromSettings,
supportMessengerSettings, supportMessengerSettings,
} from '@/utils/supportMessenger' } from '@/utils/supportMessenger'
import {useDraggableFloatingButton} from '@/utils/useDraggableFloatingButton' import { useDraggableFloatingButton } from '@/utils/useDraggableFloatingButton'
import AppCreditLink from '@/components/AppCreditLink.vue'
import { useRoute } from 'vue-router'
useApplyDesktopLayoutClass() useApplyDesktopLayoutClass()
const isDesktop = useDesktopBreakpoint()
const route = useRoute()
const showDesktopFloatingCredit = computed(() => {
if (!isDesktop.value) return false
return route.name === 'login' || !!route.meta.hideMenu
})
const {offlineReady, needRefresh, updateServiceWorker} = useRegisterSW() const {offlineReady, needRefresh, updateServiceWorker} = useRegisterSW()
const config = useConfigStore() const config = useConfigStore()
const splashIndicator = computed(() => h(LoadingOutlined, { const splashIndicator = computed(() => h(LoadingOutlined, {
@@ -310,16 +318,106 @@ function update() {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 1000000;
box-sizing: content-box;
}
.app-credit-footer--mobile {
width: 100%; width: 100%;
max-width: 426px; max-width: 426px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
z-index: 1000000;
min-height: var(--app-credit-bar-inner); min-height: var(--app-credit-bar-inner);
padding-bottom: var(--app-safe-bottom); padding-bottom: var(--app-safe-bottom);
box-sizing: content-box;
background: #fff; background: #fff;
box-shadow: 0 -2px 4px -2px #0000001a; box-shadow: 0 -2px 4px -2px #0000001a;
display: flex;
justify-content: center;
font-size: 9px;
}
.app-credit-footer--desktop-float {
left: auto;
right: 20px;
bottom: 16px;
width: auto;
}
.app-credit-link {
display: inline-flex;
align-items: center;
text-decoration: none;
color: inherit;
transition: opacity 0.15s ease;
&:hover {
opacity: 0.85;
}
&__text {
font-weight: 500;
white-space: nowrap;
}
&__logo {
width: auto;
object-fit: contain;
}
&--mobile {
gap: 4px;
padding: 4px 0;
color: inherit;
.app-credit-link__logo {
height: 12px;
}
}
&--sidebar {
flex-direction: column;
gap: 6px;
padding: 4px;
color: #9ca3af;
font-size: 10px;
.app-credit-link__logo {
height: 15px;
opacity: 0.85;
}
}
&--float {
gap: 6px;
padding: 7px 14px;
font-size: 10px;
color: #6b7280;
background: #fff;
border-radius: 999px;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.06);
.app-credit-link__logo {
height: 13px;
}
}
}
html.project-boursecu {
.app-credit-link--sidebar {
color: rgba(255, 255, 255, 0.5);
.app-credit-link__logo {
filter: brightness(1.15);
opacity: 0.75;
}
}
.app-credit-link--float {
background: var(--App-brand-nav, #133050);
border-color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.65);
}
} }
.updateButton .van-dialog__confirm .van-button__text { .updateButton .van-dialog__confirm .van-button__text {
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+21
View File
@@ -0,0 +1,21 @@
<template>
<a
href="https://montzar.ir"
target="_blank"
rel="noopener noreferrer"
class="app-credit-link"
:class="`app-credit-link--${variant}`"
>
<span class="app-credit-link__text">توسعه و طراحی توسط</span>
<img src="/logo-montzar.png" alt="montzar" class="app-credit-link__logo" />
</a>
</template>
<script lang="ts" setup>
withDefaults(
defineProps<{
variant?: 'mobile' | 'sidebar' | 'float'
}>(),
{ variant: 'mobile' },
)
</script>
+11 -6
View File
@@ -1,13 +1,18 @@
import { ref } from 'vue'
import { getProducts } from '@/api/products' import { getProducts } from '@/api/products'
import type { Product } from '@/models/products' import type { Product } from '@/models/products'
/** Shared product catalog — dedupes getProducts() across wallet assets and chart offset. */ /** Shared product catalog — dedupes getProducts() across wallet assets and chart offset. */
let catalogCache: Product[] | null = null const catalogProducts = ref<Product[]>([])
let catalogPromise: Promise<Product[]> | null = null let catalogPromise: Promise<Product[]> | null = null
export function getProductsCatalogRef() {
return catalogProducts
}
export function getProductsCatalog(force = false): Promise<Product[]> { export function getProductsCatalog(force = false): Promise<Product[]> {
if (!force && catalogCache) { if (!force && catalogProducts.value.length > 0) {
return Promise.resolve(catalogCache) return Promise.resolve(catalogProducts.value)
} }
if (!force && catalogPromise) { if (!force && catalogPromise) {
@@ -16,8 +21,8 @@ export function getProductsCatalog(force = false): Promise<Product[]> {
catalogPromise = getProducts() catalogPromise = getProducts()
.then((products) => { .then((products) => {
catalogCache = products ?? [] catalogProducts.value = products ?? []
return catalogCache return catalogProducts.value
}) })
.finally(() => { .finally(() => {
catalogPromise = null catalogPromise = null
@@ -27,6 +32,6 @@ export function getProductsCatalog(force = false): Promise<Product[]> {
} }
export function clearProductsCatalog() { export function clearProductsCatalog() {
catalogCache = null catalogProducts.value = []
catalogPromise = null catalogPromise = null
} }
+7 -2
View File
@@ -34,20 +34,25 @@
@click="navigateTo(item.routeName)" @click="navigateTo(item.routeName)"
/> />
</nav> </nav>
<div class="desktop-sidebar__credit">
<AppCreditLink variant="sidebar" />
</div>
</aside> </aside>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import { useConfigStore } from '@/store/configStore' import { useConfigStore } from '@/store/configStore'
import { getProjectNavLogo, isBoursecuProject } from '@/utils/config' import { getProjectSplashLogo, isBoursecuProject } from '@/utils/config'
import { getPrimaryNavItems, SECONDARY_NAV } from '../config/navigation' import { getPrimaryNavItems, SECONDARY_NAV } from '../config/navigation'
import { useAppNavigation } from '../composables/useAppNavigation' import { useAppNavigation } from '../composables/useAppNavigation'
import DesktopNavItem from './DesktopNavItem.vue' import DesktopNavItem from './DesktopNavItem.vue'
import AppCreditLink from '@/components/AppCreditLink.vue'
const config = useConfigStore() const config = useConfigStore()
const isBoursecu = isBoursecuProject() const isBoursecu = isBoursecuProject()
const navLogo = computed(() => getProjectNavLogo(config.config?.avatar)) const navLogo = computed(() => getProjectSplashLogo(config.config?.avatar))
const primaryItems = getPrimaryNavItems() const primaryItems = getPrimaryNavItems()
const secondaryItems = SECONDARY_NAV const secondaryItems = SECONDARY_NAV
+9 -1
View File
@@ -72,7 +72,8 @@
.desktop-sidebar__logo { .desktop-sidebar__logo {
max-width: 200px; max-width: 200px;
max-height: 80px; max-height: 200px;
width: min(100%, 200px);
} }
} }
@@ -102,6 +103,13 @@
margin-top: auto; margin-top: auto;
} }
.desktop-sidebar__credit {
margin-top: 12px;
padding: 12px 8px 4px;
display: flex;
justify-content: center;
}
.desktop-sidebar__divider { .desktop-sidebar__divider {
height: 1px; height: 1px;
background: rgba(0, 0, 0, 0.08); background: rgba(0, 0, 0, 0.08);
+2 -2
View File
@@ -29,7 +29,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import { useConfigStore } from '@/store/configStore' import { useConfigStore } from '@/store/configStore'
import { getProjectNavLogo, isBoursecuProject } from '@/utils/config' import { getProjectMobileNavLogo, isBoursecuProject } from '@/utils/config'
import { SECONDARY_NAV } from '../config/navigation' import { SECONDARY_NAV } from '../config/navigation'
import { useAppNavigation } from '../composables/useAppNavigation' import { useAppNavigation } from '../composables/useAppNavigation'
import MobileBottomNav from './MobileBottomNav.vue' import MobileBottomNav from './MobileBottomNav.vue'
@@ -37,7 +37,7 @@ import MobileOverflowMenu from './MobileOverflowMenu.vue'
const config = useConfigStore() const config = useConfigStore()
const isBoursecu = isBoursecuProject() const isBoursecu = isBoursecuProject()
const navLogo = computed(() => getProjectNavLogo(config.config?.avatar)) const navLogo = computed(() => getProjectMobileNavLogo(config.config?.avatar))
const { const {
popVisible, popVisible,
+1 -3
View File
@@ -46,9 +46,7 @@
--app-credit-footer-offset: 0px; --app-credit-footer-offset: 0px;
} }
.app-credit-footer { /* Mobile fixed bar hidden on desktop — credit shown in sidebar / floating badge */
display: none !important;
}
/* Teleport to body escapes parent v-show — hide mobile dock chrome on desktop */ /* Teleport to body escapes parent v-show — hide mobile dock chrome on desktop */
.app-dock-add-bar, .app-dock-add-bar,
+22 -26
View File
@@ -11,6 +11,7 @@ import { useUserStore } from '@/store/userStore'
import { useWalletStore } from '@/store/walletStore' import { useWalletStore } from '@/store/walletStore'
import { useFundUserStore } from '@/store/fundStore' import { useFundUserStore } from '@/store/fundStore'
import VueLazyLoad from 'vue3-lazyload' import VueLazyLoad from 'vue3-lazyload'
import VueApexCharts from 'vue3-apexcharts'
import { getMe } from '@/api/me' import { getMe } from '@/api/me'
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router' import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { useAuthStore } from '@/store/useAuthStore' import { useAuthStore } from '@/store/useAuthStore'
@@ -24,6 +25,7 @@ app.use(VueLazyLoad, {
// options... // options...
}) })
app.use(router) app.use(router)
app.use(VueApexCharts)
const userStore = useUserStore() const userStore = useUserStore()
const walletStore = useWalletStore() const walletStore = useWalletStore()
@@ -121,20 +123,16 @@ router.beforeEach(async (to, from, next) => {
} }
// If not an Apple device and fingerprint exists // If not an Apple device and fingerprint exists
else if (!isApple && finger_print) { else if (!isApple && finger_print) {
// goToLogin(to, next)
fingerPosition = await authenticateWithFingerprint() fingerPosition = await authenticateWithFingerprint()
if (fingerPosition) { if (fingerPosition) {
// goToHome(to, next) // Redirect to home if fingerprint is authenticated return next()
} else {
goToLogin(to, next) // Redirect to login if authentication fails
} }
goToLogin(to, next)
} else { } else {
if (from.path === '/') { if (to.name === 'login') {
// goToHome(to, next) return goToHome(to, next)
}
if(to.name=="login"){
goToHome(to, next)
} }
return next() return next()
} }
@@ -166,26 +164,24 @@ router.beforeEach(async (to, from, next) => {
// If fingerprint authentication failed or was not done // If fingerprint authentication failed or was not done
else { else {
if (getToken() && !hasFingerPrint) { if (getToken() && !hasFingerPrint) {
next() authStore.setLoading(false)
} else { return next()
if (['login', '404','educate', 'aboutUs'].includes(to.name as string)) {
if (to.name === 'login'){
goToLogin(to, next)
return
} else {
return next()
}
// Allow access to login or 404
}
} }
/* if (to.name !== 'login') {
return next({ name: 'login' }) // Redirect to login if (['login', '404', 'educate', 'aboutUs'].includes(to.name as string)) {
} else { authStore.setLoading(false)
return next() // If already on the login page, don't redirect if (to.name === 'login' && !getToken()) {
}*/ return goToLogin(to, next)
/* return next({ name: 'login' })*/ }
return next()
}
authStore.setLoading(false)
return next({ name: 'login' })
} }
} }
authStore.setLoading(false)
}) })
app.mount('#app') app.mount('#app')
// if (currentRoute.value !== '/') { // if (currentRoute.value !== '/') {
+19 -1
View File
@@ -1,4 +1,6 @@
import boursecuLogo from '@/assets/img/boursemes_logofull.png' import boursecuLogo from '@/assets/img/boursemes_logofull.png'
import boursecuSplashLogo from '@/assets/img/boursemes-splash.png'
import boursecuLoginLogo from '@/assets/img/boursemes-login.png'
import bazretalaLoginLogo from '@/assets/img/bazretala-login-logo.png' import bazretalaLoginLogo from '@/assets/img/bazretala-login-logo.png'
import bazretalaNavLogo from '@/assets/img/bazretala-nav-logo.png' import bazretalaNavLogo from '@/assets/img/bazretala-nav-logo.png'
import bazretalaFundBg from '@/assets/img/bazretala-fund-bg.png' import bazretalaFundBg from '@/assets/img/bazretala-fund-bg.png'
@@ -46,7 +48,7 @@ export function resolveProjectColor(color?: string | null) {
export function getProjectLoginLogo(avatar?: string | null) { export function getProjectLoginLogo(avatar?: string | null) {
if (isBoursecuProject()) { if (isBoursecuProject()) {
return boursecuLogo return boursecuLoginLogo
} }
if (isBazretalaProject()) { if (isBazretalaProject()) {
return bazretalaLoginLogo return bazretalaLoginLogo
@@ -54,6 +56,14 @@ export function getProjectLoginLogo(avatar?: string | null) {
return avatar ?? getConfigApp('avatar') return avatar ?? getConfigApp('avatar')
} }
/** لوگوی صفحه لودینگ اولیه — جدا از لوگوی لاگین/ناوبری */
export function getProjectSplashLogo(avatar?: string | null) {
if (isBoursecuProject()) {
return boursecuSplashLogo
}
return getProjectLoginLogo(avatar)
}
export function getProjectNavLogo(avatar?: string | null) { export function getProjectNavLogo(avatar?: string | null) {
if (isBazretalaProject()) { if (isBazretalaProject()) {
return bazretalaNavLogo return bazretalaNavLogo
@@ -64,6 +74,14 @@ export function getProjectNavLogo(avatar?: string | null) {
return avatar ?? getConfigApp('avatar') return avatar ?? getConfigApp('avatar')
} }
/** لوگوی ناوبری موبایل — boursecu همان لوگوی لاگین (پس‌زمینه سفید) */
export function getProjectMobileNavLogo(avatar?: string | null) {
if (isBoursecuProject()) {
return boursecuLoginLogo
}
return getProjectNavLogo(avatar)
}
export const BAZRETALA_SUPPORT_PHONE = '09121784258' export const BAZRETALA_SUPPORT_PHONE = '09121784258'
export function getProjectSupportPhone() { export function getProjectSupportPhone() {
-29
View File
@@ -1,29 +0,0 @@
const PERSIAN_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] as const
export function toPersianDigits(value: string | number | null | undefined): string {
if (value === null || value === undefined) return ''
return String(value).replace(/\d/g, (digit) => PERSIAN_DIGITS[Number(digit)] ?? digit)
}
export function toEnglishDigits(value: string): string {
return value.replace(/[۰-۹]/g, (digit) => {
const index = PERSIAN_DIGITS.indexOf(digit as (typeof PERSIAN_DIGITS)[number])
return index >= 0 ? String(index) : digit
})
}
export function formatFixed(value: string | number | null | undefined, decimals = 2): string {
const num = Number(value)
if (!Number.isFinite(num)) return toPersianDigits(String(value ?? ''))
return toPersianDigits(num.toFixed(decimals))
}
export function formatPercent(
value: number,
decimals = 2,
options: { showSign?: boolean; suffix?: string } = {},
): string {
const { showSign = false, suffix = '%' } = options
const prefix = showSign && value > 0 ? '+' : value < 0 ? '-' : showSign ? '+' : ''
return `${prefix}${formatFixed(Math.abs(value), decimals)}${suffix}`
}
+36 -5
View File
@@ -1,5 +1,5 @@
import { toPersianDigits } from '@/utils/persianDigits'
import type { Product } from '@/models/products' import type { Product } from '@/models/products'
import { usePriceWithoutType } from '@/utils/usePrice'
import type { UserWalletLine } from '@/models/wallet' import type { UserWalletLine } from '@/models/wallet'
import type { ProductFile } from '@/utils/file' import type { ProductFile } from '@/utils/file'
import type { WalletInventoryCard, WalletMetalTotal } from '@/models/walletAssets' import type { WalletInventoryCard, WalletMetalTotal } from '@/models/walletAssets'
@@ -87,7 +87,7 @@ export function resolveMetalAssetsDisplayLabel(
case METAL_COPPER: case METAL_COPPER:
return 'دارایی مس' return 'دارایی مس'
case METAL_GOLD: case METAL_GOLD:
return 'دارایی طلایی' return 'دارایی شمش مس'
case METAL_SILVER: case METAL_SILVER:
return 'دارایی نقره' return 'دارایی نقره'
default: default:
@@ -103,16 +103,44 @@ export interface HomeAssetRow {
value: string value: string
metal?: WalletMetalTotal metal?: WalletMetalTotal
info: HomeAssetRowInfo info: HomeAssetRowInfo
/** boursecu — متن مودال توضیحات (info) */
modalText?: string
} }
function formatInventoryCardProductLabel(card: WalletInventoryCard): string { function formatInventoryCardProductLabel(card: WalletInventoryCard): string {
return `دارایی ${card.title}` return `دارایی ${card.title}`
} }
function formatBoursecuProductModalText(card: WalletInventoryCard): string {
return `${formatInventoryCardProductLabel(card)} = موجودی ${card.title}`
}
/** boursecu — متن مودال برای ردیف‌های metal_totals */
export function formatBoursecuMetalModalText(metal: WalletMetalTotal): string {
switch (metal.metal_id) {
case METAL_COPPER:
return 'مجموع دارایی مس = جمع کاتد و مفتول و شمش'
case METAL_GOLD:
return 'دارایی شمش مس = موجودی شمش مس'
case METAL_SILVER:
return 'دارایی نقره = موجودی نقره'
default:
return `${resolveMetalAssetsDisplayLabel(metal, true)} = موجودی ${metal.label}`
}
}
function findMetalTotalById(metalTotals: WalletMetalTotal[], metalId: number) { function findMetalTotalById(metalTotals: WalletMetalTotal[], metalId: number) {
return metalTotals.find((metal) => metal.metal_id === metalId) return metalTotals.find((metal) => metal.metal_id === metalId)
} }
function formatBoursecuMetalRowValue(metal: WalletMetalTotal): string {
const amount = usePriceWithoutType(Number(metal.total_weight ?? 0), false)
if (metal.metal_id === METAL_GOLD) {
return `${amount} عدد`
}
return `${amount}${metal.unit ?? ''}`
}
/** boursecu home — هر محصول / فلز / ریال از customer/wallet/assets جدا نمایش داده می‌شود */ /** boursecu home — هر محصول / فلز / ریال از customer/wallet/assets جدا نمایش داده می‌شود */
export function buildBoursecuHomeAssetRows( export function buildBoursecuHomeAssetRows(
summary: { summary: {
@@ -142,6 +170,7 @@ export function buildBoursecuHomeAssetRows(
value: formatInventoryCardValue(card), value: formatInventoryCardValue(card),
metal: findMetalTotalById(metalTotals, card.metal_id), metal: findMetalTotalById(metalTotals, card.metal_id),
info: 'metal', info: 'metal',
modalText: formatBoursecuProductModalText(card),
}) })
} }
@@ -153,9 +182,10 @@ export function buildBoursecuHomeAssetRows(
rows.push({ rows.push({
id: `metal-${metal.metal_id}`, id: `metal-${metal.metal_id}`,
label: resolveMetalAssetsDisplayLabel(metal, true), label: resolveMetalAssetsDisplayLabel(metal, true),
value: `${Number(metal.total_weight ?? 0)}${metal.unit ?? ''}`, value: formatBoursecuMetalRowValue(metal),
metal, metal,
info: 'metal', info: 'metal',
modalText: formatBoursecuMetalModalText(metal),
}) })
} }
@@ -166,6 +196,7 @@ export function buildBoursecuHomeAssetRows(
value: formatInventoryCardValue(card), value: formatInventoryCardValue(card),
metal: findMetalTotalById(metalTotals, card.metal_id), metal: findMetalTotalById(metalTotals, card.metal_id),
info: 'metal', info: 'metal',
modalText: formatBoursecuProductModalText(card),
}) })
} }
@@ -223,7 +254,7 @@ export function formatMetalAssetsInfoText(metal: WalletMetalTotal): string {
} }
function formatRialNumber(value: number): string { function formatRialNumber(value: number): string {
return Math.round(value).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') return usePriceWithoutType(Math.round(value))
} }
/** متن توضیح مودال دارایی ریالی */ /** متن توضیح مودال دارایی ریالی */
@@ -276,7 +307,7 @@ export function formatInventoryCardValue(card: {
}): string { }): string {
const quantity = Number(card.quantity ?? 0) const quantity = Number(card.quantity ?? 0)
const unit = card.unit ?? (card.display_type === 'count' ? 'عدد' : 'گرم') const unit = card.unit ?? (card.display_type === 'count' ? 'عدد' : 'گرم')
return `${quantity} ${unit}` return `${usePriceWithoutType(quantity, false)} ${unit}`
} }
export function findProductInventoryCard( export function findProductInventoryCard(
+18 -15
View File
@@ -1,24 +1,27 @@
export function usePrice(price: number, digit = true, unit = 'ریال') { const PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹'
price= price??0
let _price = price.toString() function formatGroupedPrice(price: string | number | null | undefined, digit: boolean) {
let value = price === null || price === undefined ? '' : String(price)
if (digit) { if (digit) {
_price = _price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') value = value.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
} }
return `${_price} ${unit}` return value.replace(/\d/g, (d) => PERSIAN_DIGITS[Number(d)] ?? d)
} }
export function usePriceWithoutType(price?: number, digit = true, unit = 'ریال') { export function usePrice(price: number, digit = true, unit = 'ریال') {
let _price = price?.toString() price = price ?? 0
if (digit) { const formatted = formatGroupedPrice(price, digit)
_price = _price?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') return `${formatted} ${unit}`
} }
return `${_price}`
export function usePriceWithoutType(price?: number, digit = true) {
return formatGroupedPrice(price ?? '', digit)
} }
export function useTowDecimal(num: string | number): string | number { export function useTowDecimal(num: string | number): string | number {
const temp = num.toString().match(/^-?\d+(?:\.\d{0,2})?/) const temp = num.toString().match(/^-?\d+(?:\.\d{0,2})?/)
if (temp && temp.length > 0) return temp[0] if (temp && temp.length > 0) return formatGroupedPrice(temp[0], false)
return Math.round(+num * 100) / 100 return formatGroupedPrice(Math.round(+num * 100) / 100, false)
} }
export function useFormat(num: string | number, unit = '') { export function useFormat(num: string | number, unit = '') {
@@ -76,6 +79,6 @@ export function getTransactionGoldAmount(item: TransactionPriceSource): number {
export function formatGoldAmount(amount?: number | null): string { export function formatGoldAmount(amount?: number | null): string {
const num = Number(amount) const num = Number(amount)
if (!Number.isFinite(num)) return '0' if (!Number.isFinite(num)) return '۰'
return String(Number(num.toFixed(3))) return formatGroupedPrice(Number(num.toFixed(3)), false)
} }
+3 -7
View File
@@ -5,8 +5,7 @@
> >
<div class="flex justify-between my-2 items-center"> <div class="flex justify-between my-2 items-center">
<div v-if="isBoursecuProject()" class="flex items-center gap-2"> <div v-if="isBoursecuProject()" class="flex items-center gap-2">
<img :src="projectLogo" alt="" class="h-8 w-auto object-contain" /> <img :src="projectLogo" alt="" class="h-10 w-auto max-w-[9rem] object-contain" />
<div class="color-transition font-bold text-primary">{{ appName }}</div>
</div> </div>
<div v-else class="flex items-center gap-2" @click.stop="router.push({ name: 'user' })"> <div v-else class="flex items-center gap-2" @click.stop="router.push({ name: 'user' })">
<div <div
@@ -46,16 +45,13 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useWindowScroll } from '@vueuse/core' import { useWindowScroll } from '@vueuse/core'
import { useUserStore } from '@/store/userStore' import { useUserStore } from '@/store/userStore'
import { useConfigStore } from '@/store/configStore'
import router from '@/router' import router from '@/router'
import Icon from '@/components/BasicPacks/BasicIcon.vue' import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { getProjectNavLogo, isBoursecuProject } from '@/utils/config' import { getProjectMobileNavLogo, isBoursecuProject } from '@/utils/config'
const { y } = useWindowScroll() const { y } = useWindowScroll()
const userStore = useUserStore() const userStore = useUserStore()
const config = useConfigStore() const projectLogo = computed(() => getProjectMobileNavLogo())
const projectLogo = computed(() => getProjectNavLogo())
const appName = computed(() => config.config?.name_fa || config.config?.name || 'بورس مس')
defineEmits<{ defineEmits<{
logout: [] logout: []
@@ -1,12 +1,12 @@
<template> <template>
<div :class="plain ? 'py-2' : 'bg-primary rounded-lg p-2 py-3 mt-2'"> <div :class="rootClass">
<div <div
v-if="!boursecuLayout && meta?.has_count_based_products" v-if="!boursecuLayout && meta?.has_count_based_products"
:class="rowClass" :class="rowClass"
class="flex justify-center w-full z-20" class="flex justify-center w-full z-20"
> >
محصولات: محصولات:
<div class="text-l mr-2 font-bold">{{ countBasedInventory }} عدد</div> <div class="text-l mr-2 font-bold">{{ usePriceWithoutType(countBasedInventory, false) }} عدد</div>
</div> </div>
<template v-if="boursecuAssetRows.length"> <template v-if="boursecuAssetRows.length">
@@ -94,7 +94,7 @@ const props = withDefaults(
) )
const emit = defineEmits<{ const emit = defineEmits<{
openGoldModal: [metal: WalletMetalTotal] openGoldModal: [metal: WalletMetalTotal, modalText?: string]
openRialModal: [] openRialModal: []
}>() }>()
@@ -103,6 +103,16 @@ const { assetsSummary } = useWalletAssets()
const rowClass = computed(() => (props.plain ? 'text-primary' : 'text-tertiary')) const rowClass = computed(() => (props.plain ? 'text-primary' : 'text-tertiary'))
const iconColor = computed(() => (props.plain ? 'primary' : 'tertiary')) const iconColor = computed(() => (props.plain ? 'primary' : 'tertiary'))
const rootClass = computed(() => {
if (props.boursecuLayout) {
return 'assets-section assets-section--boursecu rounded-lg px-3 py-3 mt-2'
}
if (props.plain) {
return 'py-2'
}
return 'bg-primary rounded-lg p-2 py-3 mt-2'
})
const meta = computed(() => assetsSummary.value?.meta) const meta = computed(() => assetsSummary.value?.meta)
const metalTotals = computed(() => assetsSummary.value?.metal_totals ?? []) const metalTotals = computed(() => assetsSummary.value?.metal_totals ?? [])
const primaryMetalTotal = computed(() => metalTotals.value[0]) const primaryMetalTotal = computed(() => metalTotals.value[0])
@@ -116,7 +126,7 @@ const countBasedInventory = computed(() =>
) )
function formatMetalTotal(metal: WalletMetalTotal) { function formatMetalTotal(metal: WalletMetalTotal) {
return `${resolveSummaryMetalWeight(metal, inventoryCards.value)}${metal.unit}` return `${usePriceWithoutType(resolveSummaryMetalWeight(metal, inventoryCards.value), false)}${metal.unit}`
} }
function handleRowInfo(row: HomeAssetRow) { function handleRowInfo(row: HomeAssetRow) {
@@ -126,7 +136,16 @@ function handleRowInfo(row: HomeAssetRow) {
} }
if (row.metal) { if (row.metal) {
emit('openGoldModal', row.metal) emit('openGoldModal', row.metal, row.modalText)
} }
} }
</script> </script>
<style scoped lang="less">
@boursecu-surface-border: #d4e3de;
.assets-section--boursecu {
background-color: #eff5f3;
border: 2px solid @boursecu-surface-border;
}
</style>
@@ -20,7 +20,7 @@
<Vue3Odometer <Vue3Odometer
:value="displayGoldVal" :value="displayGoldVal"
class="o-text !text-xl font-bold" class="o-text !text-xl font-bold"
style="font-family: var(--App-font-family); direction: ltr" style="font-family: iran-sans; direction: ltr"
/> />
ریال ریال
</div> </div>
+12 -15
View File
@@ -2,7 +2,7 @@
<div <div
class="w-full flex justify-evenly rounded-lg py-2 home_appear_fade_in" class="w-full flex justify-evenly rounded-lg py-2 home_appear_fade_in"
:class="[ :class="[
layout === 'boursecu' ? 'mt-8 pt-2' : 'mt-6', isBoursecuLayout ? 'mt-8 pt-2' : 'mt-6',
variant === 'mobile' ? 'pb-45' : 'mt-0 pb-0', variant === 'mobile' ? 'pb-45' : 'mt-0 pb-0',
]" ]"
> >
@@ -33,31 +33,28 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { usePriceWithoutType } from '../../../../utils/usePrice' import { usePriceWithoutType } from '../../../../utils/usePrice'
import type { ChartMetalType } from '../composables/usePriceChart' import { price } from '@/utils/price'
import { resolveMetalDayRangePrices } from '../composables/usePriceChart' import { getHomeLayoutId } from '../layouts/registry'
import { resolveMetalDayRangePrices, usePriceChart } from '../composables/usePriceChart'
const props = withDefaults( withDefaults(
defineProps<{ defineProps<{
price: any
layout?: 'default' | 'boursecu'
variant?: 'mobile' | 'desktop' variant?: 'mobile' | 'desktop'
selectedMetal?: ChartMetalType
maftolRatio?: number
}>(), }>(),
{ {
layout: 'default',
variant: 'mobile', variant: 'mobile',
selectedMetal: 'katod',
maftolRatio: 0,
} }
) )
const { selectedChartMetal, maftolChartRatio } = usePriceChart()
const isBoursecuLayout = computed(() => getHomeLayoutId() === 'boursecu')
const rangePrices = computed(() => const rangePrices = computed(() =>
resolveMetalDayRangePrices({ resolveMetalDayRangePrices({
price: props.price, price: price.value,
metal: props.selectedMetal, metal: selectedChartMetal.value,
isBoursecuLayout: props.layout === 'boursecu', isBoursecuLayout: isBoursecuLayout.value,
maftolRatio: props.maftolRatio, maftolRatio: maftolChartRatio.value,
}) })
) )
+15 -4
View File
@@ -5,7 +5,12 @@ import { useUserStore } from '@/store/userStore'
import { useWalletStore } from '@/store/walletStore' import { useWalletStore } from '@/store/walletStore'
import { useWalletAssets } from '@/composables/useWalletAssets' import { useWalletAssets } from '@/composables/useWalletAssets'
import type { WalletMetalTotal } from '@/models/walletAssets' import type { WalletMetalTotal } from '@/models/walletAssets'
import { formatMetalAssetsInfoText, formatRialAssetsInfoText, resolveAssetsRialValue } from '@/utils/productAssets' import {
formatBoursecuMetalModalText,
formatMetalAssetsInfoText,
formatRialAssetsInfoText,
resolveAssetsRialValue,
} from '@/utils/productAssets'
import { useConfigStore } from '@/store/configStore' import { useConfigStore } from '@/store/configStore'
import { getTradeBuyPrice, lastPrice, stopLastPricePolling } from '@/utils/price' import { getTradeBuyPrice, lastPrice, stopLastPricePolling } from '@/utils/price'
import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours' import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours'
@@ -36,7 +41,7 @@ export interface HomePageContext {
setChartMetal: (metal: ChartMetalType) => void setChartMetal: (metal: ChartMetalType) => void
isBoursecuHomeLayout: ReturnType<typeof computed<boolean>> isBoursecuHomeLayout: ReturnType<typeof computed<boolean>>
handleBannerClick: (banner: { typeClick?: string; modalText?: string; link?: string }) => void handleBannerClick: (banner: { typeClick?: string; modalText?: string; link?: string }) => void
openGoldModal: (metal: WalletMetalTotal) => void openGoldModal: (metal: WalletMetalTotal, modalText?: string) => void
openRialModal: () => void openRialModal: () => void
} }
@@ -142,9 +147,15 @@ export function useHomePage() {
window.location.reload() window.location.reload()
} }
function openGoldModal(metal: WalletMetalTotal) { function openGoldModal(metal: WalletMetalTotal, modalText?: string) {
const title =
modalText ??
(isBoursecuHomeLayout.value
? formatBoursecuMetalModalText(metal)
: formatMetalAssetsInfoText(metal))
Modal.confirm({ Modal.confirm({
title: formatMetalAssetsInfoText(metal), title,
okText: 'متوجه شدم', okText: 'متوجه شدم',
centered: true, centered: true,
cancelButtonProps: { style: { display: 'none' } }, cancelButtonProps: { style: { display: 'none' } },
@@ -21,8 +21,12 @@ export function isHomePageDataInitialized() {
export async function initHomePageData(force = false) { export async function initHomePageData(force = false) {
if (!force && initialized.value) { if (!force && initialized.value) {
// Resume live price updates when returning to home without refetching datasets
startLastPricePolling() startLastPricePolling()
if (getHomeLayoutId() === 'boursecu') {
void loadHomeMaftolOffset(false)
}
return return
} }
@@ -1,7 +1,7 @@
import { ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { getAllPrices } from '@/api/home' import { getAllPrices } from '@/api/home'
import type { Product } from '@/models/products' import type { Product } from '@/models/products'
import { getProductsCatalog } from '@/composables/useProductsCatalog' import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
import { getHomeLayoutId } from '../layouts/registry' import { getHomeLayoutId } from '../layouts/registry'
import { getProductBuyPrice, resolvePriceOffsetRatio } from '@/utils/productPrice' import { getProductBuyPrice, resolvePriceOffsetRatio } from '@/utils/productPrice'
import { isKatodMesGroup, isMaftolMesGroup } from '@/utils/productDealMethod' import { isKatodMesGroup, isMaftolMesGroup } from '@/utils/productDealMethod'
@@ -19,7 +19,27 @@ const rawChartPoints = ref<ChartPoint[]>([])
const chartSelection = ref('DAILY') const chartSelection = ref('DAILY')
const chartLoading = ref(false) const chartLoading = ref(false)
const selectedChartMetal = ref<ChartMetalType>('katod') const selectedChartMetal = ref<ChartMetalType>('katod')
const maftolChartRatio = ref(0) const catalogProducts = getProductsCatalogRef()
function resolveChartProducts(products: Product[]) {
return {
katod: findKatodProduct(products),
maftol: findMaftolProduct(products),
}
}
/** نسبت مفتول به کاتد — reactive به قیمت لحظه‌ای و کاتالوگ محصولات */
const maftolChartRatio = computed(() => {
if (getHomeLayoutId() !== 'boursecu') {
return 0
}
// lastPrice را track کن تا بعد از polling نسبت به‌روز شود
void lastPrice.value?.price_last
const { katod, maftol } = resolveChartProducts(catalogProducts.value)
return deriveMaftolChartRatio(maftol, katod)
})
let chartFetchPromise: Promise<void> | null = null let chartFetchPromise: Promise<void> | null = null
let chartInitialized = false let chartInitialized = false
@@ -69,19 +89,25 @@ export function resolveMetalDayRangePrices(options: {
} }
function deriveMaftolChartRatio(maftolProduct?: Product, katodProduct?: Product) { function deriveMaftolChartRatio(maftolProduct?: Product, katodProduct?: Product) {
const offsetRatio = resolvePriceOffsetRatio(maftolProduct?.price_offset_percent) const maftolOffset = resolvePriceOffsetRatio(maftolProduct?.price_offset_percent)
if (offsetRatio) { if (maftolOffset) {
return offsetRatio return maftolOffset
} }
if (!maftolProduct) { if (!maftolProduct) {
return 0 return 0
} }
const basePrice = getTradeBuyPrice(lastPrice.value) const liveBuy = getTradeBuyPrice(lastPrice.value)
const katodPrice = katodProduct ? getProductBuyPrice(katodProduct) : basePrice const katodLive = katodProduct ? getProductBuyPrice(katodProduct) : liveBuy
const maftolPrice = getProductBuyPrice(maftolProduct) const maftolLive = getProductBuyPrice(maftolProduct)
const referencePrice = katodPrice || basePrice
// وقتی فروشگاه بسته است قیمت لحظه‌ای صفر می‌شود — از قیمت ثابت کاتالوگ استفاده کن
const katodStatic = Number(katodProduct?.price ?? 0) + Number(katodProduct?.price_profit ?? 0)
const maftolStatic = Number(maftolProduct.price ?? 0) + Number(maftolProduct.price_profit ?? 0)
const referencePrice = katodLive || liveBuy || katodStatic
const maftolPrice = maftolLive || maftolStatic
if (!referencePrice || !maftolPrice || referencePrice === maftolPrice) { if (!referencePrice || !maftolPrice || referencePrice === maftolPrice) {
return 0 return 0
@@ -168,12 +194,11 @@ async function fetchPrices(force = false) {
async function loadMaftolOffset(force = false) { async function loadMaftolOffset(force = false) {
if (getHomeLayoutId() !== 'boursecu') { if (getHomeLayoutId() !== 'boursecu') {
maftolChartRatio.value = 0
syncChartSeries() syncChartSeries()
return return
} }
if (!force && maftolOffsetLoaded) { if (!force && maftolOffsetLoaded && catalogProducts.value.length > 0) {
return return
} }
@@ -183,15 +208,11 @@ async function loadMaftolOffset(force = false) {
maftolOffsetPromise = (async () => { maftolOffsetPromise = (async () => {
try { try {
const products = await getProductsCatalog(force) await getProductsCatalog(force)
const katodProduct = findKatodProduct(products)
const maftolProduct = findMaftolProduct(products)
maftolChartRatio.value = deriveMaftolChartRatio(maftolProduct, katodProduct)
syncChartSeries() syncChartSeries()
maftolOffsetLoaded = true maftolOffsetLoaded = true
} catch (error) { } catch (error) {
console.error('Error loading maftol chart ratio:', error) console.error('Error loading maftol chart ratio:', error)
maftolChartRatio.value = 0
syncChartSeries() syncChartSeries()
} finally { } finally {
maftolOffsetPromise = null maftolOffsetPromise = null
@@ -208,6 +229,10 @@ function updateChartData(timeline: string) {
function setChartMetal(metal: ChartMetalType) { function setChartMetal(metal: ChartMetalType) {
selectedChartMetal.value = metal selectedChartMetal.value = metal
if (metal === 'maftol' && getHomeLayoutId() === 'boursecu' && !catalogProducts.value.length) {
void loadMaftolOffset(true)
}
} }
export function usePriceChart() { export function usePriceChart() {
@@ -237,7 +262,6 @@ export function resetHomePriceChart() {
rawChartPoints.value = [] rawChartPoints.value = []
chartSelection.value = 'DAILY' chartSelection.value = 'DAILY'
selectedChartMetal.value = 'katod' selectedChartMetal.value = 'katod'
maftolChartRatio.value = 0
chartInitialized = false chartInitialized = false
chartFetchPromise = null chartFetchPromise = null
maftolOffsetLoaded = false maftolOffsetLoaded = false
@@ -8,7 +8,7 @@ import { buildBoursecuHomeAssetRows, resolveSummaryMetalWeight } from '@/utils/p
import { useHomePageContext } from '../../composables/useHomePage' import { useHomePageContext } from '../../composables/useHomePage'
const emit = defineEmits<{ const emit = defineEmits<{
openGoldModal: [metal: WalletMetalTotal] openGoldModal: [metal: WalletMetalTotal, modalText?: string]
openRialModal: [] openRialModal: []
}>() }>()
@@ -29,6 +29,7 @@ type MetricItem = {
label: string label: string
value: string value: string
metal?: WalletMetalTotal metal?: WalletMetalTotal
modalText?: string
showInfo?: boolean showInfo?: boolean
onInfo?: 'gold' | 'rial' onInfo?: 'gold' | 'rial'
} }
@@ -40,7 +41,7 @@ const metrics = computed<MetricItem[]>(() => {
items.push({ items.push({
id: 'products', id: 'products',
label: 'محصولات', label: 'محصولات',
value: `${countBasedInventory.value} عدد`, value: `${usePriceWithoutType(countBasedInventory.value, false)} عدد`,
}) })
} }
@@ -51,6 +52,7 @@ const metrics = computed<MetricItem[]>(() => {
label: row.label, label: row.label,
value: row.value, value: row.value,
metal: row.metal, metal: row.metal,
modalText: row.modalText,
showInfo: row.info !== 'none', showInfo: row.info !== 'none',
onInfo: row.info === 'rial' ? 'rial' : 'gold', onInfo: row.info === 'rial' ? 'rial' : 'gold',
}) })
@@ -93,7 +95,7 @@ const metrics = computed<MetricItem[]>(() => {
}) })
function formatMetalTotal(metal: WalletMetalTotal) { function formatMetalTotal(metal: WalletMetalTotal) {
return `${resolveSummaryMetalWeight(metal, inventoryCards.value)}${metal.unit}` return `${usePriceWithoutType(resolveSummaryMetalWeight(metal, inventoryCards.value), false)}${metal.unit}`
} }
function handleInfo(metric: MetricItem) { function handleInfo(metric: MetricItem) {
@@ -102,7 +104,7 @@ function handleInfo(metric: MetricItem) {
return return
} }
if (metric.metal) { if (metric.metal) {
emit('openGoldModal', metric.metal) emit('openGoldModal', metric.metal, metric.modalText)
} }
} }
</script> </script>
@@ -13,7 +13,7 @@
<Vue3Odometer <Vue3Odometer
:value="displayGoldVal" :value="displayGoldVal"
class="dhome-hero-price__odometer o-text" class="dhome-hero-price__odometer o-text"
style="font-family: var(--App-font-family)" style="font-family: iran-sans"
/> />
<span class="dhome-hero-price__unit">ریال</span> <span class="dhome-hero-price__unit">ریال</span>
</div> </div>
@@ -1,13 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, toValue } from 'vue'
import { useHomePageContext } from '../../composables/useHomePage'
import { price } from '@/utils/price'
import CurrentPrice from '../../components/CurrentPrice.vue' import CurrentPrice from '../../components/CurrentPrice.vue'
import PriceStats from '../../components/PriceStats.vue' import PriceStats from '../../components/PriceStats.vue'
import DesktopHomeCard from './DesktopHomeCard.vue' import DesktopHomeCard from './DesktopHomeCard.vue'
const ctx = useHomePageContext()
const statsLayout = computed(() => (toValue(ctx.isBoursecuHomeLayout) ? 'boursecu' : 'default'))
</script> </script>
<template> <template>
@@ -17,7 +11,7 @@ const statsLayout = computed(() => (toValue(ctx.isBoursecuHomeLayout) ? 'boursec
</DesktopHomeCard> </DesktopHomeCard>
<DesktopHomeCard title="بازه قیمت امروز"> <DesktopHomeCard title="بازه قیمت امروز">
<PriceStats variant="desktop" :price="price" :layout="statsLayout" /> <PriceStats variant="desktop" />
</DesktopHomeCard> </DesktopHomeCard>
</div> </div>
</template> </template>
@@ -52,5 +52,5 @@ function resolveSectionComponent(sectionId: HomeSectionId) {
</template> </template>
</div> </div>
<HomeSkeleton v-if="loading" class="w-full" /> <HomeSkeleton v-else class="w-full" />
</template> </template>
@@ -1,21 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject } from 'vue'
import PriceStats from '../../components/PriceStats.vue' import PriceStats from '../../components/PriceStats.vue'
import { price } from '@/utils/price'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
import { usePriceChart } from '../../composables/usePriceChart'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
const { maftolChartRatio } = usePriceChart()
const layout = computed(() => (ctx.isBoursecuHomeLayout.value ? 'boursecu' : 'default'))
const selectedMetal = computed(() => ctx.selectedChartMetal.value)
</script> </script>
<template> <template>
<PriceStats <PriceStats />
:price="price"
:layout="layout"
:selected-metal="selectedMetal"
:maftol-ratio="maftolChartRatio"
/>
</template> </template>
@@ -37,8 +37,8 @@ import pkg from '../../../../../package.json'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue' import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { useConfigStore } from '@/store/configStore' import { useConfigStore } from '@/store/configStore'
import { import {
getProjectLoginLogo,
getProjectLoginTaglineLines, getProjectLoginTaglineLines,
getProjectSplashLogo,
hasCustomLoginLogoLayout, hasCustomLoginLogoLayout,
isBoursecuProject, isBoursecuProject,
} from '@/utils/config' } from '@/utils/config'
@@ -48,7 +48,7 @@ const isBoursecu = isBoursecuProject()
const hasCustomLayout = hasCustomLoginLogoLayout() const hasCustomLayout = hasCustomLoginLogoLayout()
const version = pkg.version const version = pkg.version
const logo = computed(() => getProjectLoginLogo(config.config?.avatar)) const logo = computed(() => getProjectSplashLogo(config.config?.avatar))
const taglineLines = computed(() => getProjectLoginTaglineLines()) const taglineLines = computed(() => getProjectLoginTaglineLines())
const tagline = computed(() => { const tagline = computed(() => {
@@ -27,9 +27,9 @@
&--boursecu { &--boursecu {
.dauth-brand__logo { .dauth-brand__logo {
max-width: 300px; max-width: 280px;
max-height: 110px; max-height: 280px;
width: 100%; width: min(100%, 280px);
object-fit: contain; object-fit: contain;
} }
} }