1 Commits
Author SHA1 Message Date
MojtabaSoumi 12382656e8 1405/04/25
-
2026-07-16 11:17:18 +03:30
42 changed files with 639 additions and 112 deletions
BIN
View File
Binary file not shown.
+18 -12
View File
@@ -3,14 +3,7 @@
<transition mode="out-in" name="fade"> <transition mode="out-in" name="fade">
<AConfigProvider <AConfigProvider
v-if="!showSplash" v-if="!showSplash"
:theme="{ :theme="antDesignTheme"
token: {
colorPrimary: colors.primary,
colorLink: colors.primary,
colorLinkActive: colors.primary,
colorLinkHover: colors['primary-rgba']
}
}"
direction="rtl" direction="rtl"
> >
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
@@ -68,10 +61,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { colors } from './themeConfig'
import pkg from '../package.json'
import { getDisplayAppVersion } from '@/config/config-cache' import { getDisplayAppVersion } from '@/config/config-cache'
import { computed, defineAsyncComponent, h, onMounted, ref } from 'vue' import { computed, defineAsyncComponent, h, onMounted, ref } from 'vue'
import pkg from '../package.json'
import img_topGradiant from '@/views/login/assets/img/gr_top_white.svg' import img_topGradiant from '@/views/login/assets/img/gr_top_white.svg'
import img_pattern from '@/views/login/assets/img/pattern.svg' import img_pattern from '@/views/login/assets/img/pattern.svg'
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue'
@@ -83,6 +75,7 @@ import {
} from '@/utils/supportMessenger' } from '@/utils/supportMessenger'
import { useDraggableFloatingButton } from '@/utils/useDraggableFloatingButton' import { useDraggableFloatingButton } from '@/utils/useDraggableFloatingButton'
import { useApplyDesktopLayoutClass } from '@/layouts/composables/useAppLayout' import { useApplyDesktopLayoutClass } from '@/layouts/composables/useAppLayout'
import { getProjectBranding } from '@/utils/project'
import img_bottomGradiant from '@/views/login/assets/img/gr_bottom_white.svg' import img_bottomGradiant from '@/views/login/assets/img/gr_bottom_white.svg'
import logoFull from '@/assets/img/logo_full_.png' import logoFull from '@/assets/img/logo_full_.png'
@@ -100,6 +93,19 @@ const indicator = h(LoadingOutlined, {
const displayVersion = getDisplayAppVersion(pkg.version) const displayVersion = getDisplayAppVersion(pkg.version)
const showSplash = ref(true) const showSplash = ref(true)
const antDesignTheme = computed(() => {
const primary = getProjectBranding().accentColor
return {
token: {
colorPrimary: primary,
colorLink: primary,
colorLinkActive: primary,
colorLinkHover: primary,
},
}
})
useApplyDesktopLayoutClass() useApplyDesktopLayoutClass()
const supportLink = computed(() => const supportLink = computed(() =>
@@ -141,7 +147,7 @@ const supportAccentColor = computed(() => {
case 'soroush': case 'soroush':
return '#2563eb' return '#2563eb'
default: default:
return colors.primary || '#334155' return getProjectBranding().accentColor
} }
}) })
@@ -181,7 +187,7 @@ onMounted(async () => {
<style lang="less"> <style lang="less">
.updateButton .van-dialog__confirm .van-button__text { .updateButton .van-dialog__confirm .van-button__text {
color: @primary-color !important; color: @theme-primary !important;
font-weight: 600; font-weight: 600;
font-size: 15px; font-size: 15px;
} }
+8 -3
View File
@@ -13,6 +13,7 @@ import {
setHasFingerPrint setHasFingerPrint
} from '@/app/authBoot' } from '@/app/authBoot'
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset' import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
import { getProjectDefaultRoute, isGhatreProject } from '@/utils/project'
export function setupRouterGuards() { export function setupRouterGuards() {
const userStore = useUserStore() const userStore = useUserStore()
@@ -44,13 +45,17 @@ export function setupRouterGuards() {
} }
function goToHome(to: RouteLocationNormalized, next: NavigationGuardNext) { function goToHome(to: RouteLocationNormalized, next: NavigationGuardNext) {
if (to.name !== 'buyProduct') { const defaultRoute = getProjectDefaultRoute()
return next({ name: 'buyProduct' }) if (to.name !== defaultRoute) {
return next({ name: defaultRoute })
} }
return next() return next()
} }
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
if (to.name === 'home' && isGhatreProject()) {
return next({ name: 'buyProduct' })
}
authStore.setLoading(true) authStore.setLoading(true)
clearFingerTimeout() clearFingerTimeout()
setFingerTimeOut(to) setFingerTimeOut(to)
@@ -88,7 +93,7 @@ export function setupRouterGuards() {
if (getFingerPosition()) { if (getFingerPosition()) {
if (getToken()) { if (getToken()) {
if (to.name === 'login') { if (to.name === 'login') {
return next({ name: from.name || 'buyProduct' }) return next({ name: from.name || getProjectDefaultRoute() })
} }
return next() return next()
} }
+17 -5
View File
@@ -61,6 +61,8 @@ body {
:root { :root {
color-scheme: only light; color-scheme: only light;
--app-primary-color: #a72b77;
--project-hero-gradient: linear-gradient(145deg, #a72b77 0%, #711d4d 40%, #a72b77 100%);
--app-safe-top: env(safe-area-inset-top, 0px); --app-safe-top: env(safe-area-inset-top, 0px);
--app-safe-bottom: env(safe-area-inset-bottom, 0px); --app-safe-bottom: env(safe-area-inset-bottom, 0px);
--app-credit-bar-inner: 0px; --app-credit-bar-inner: 0px;
@@ -78,6 +80,16 @@ body {
--app-z-modal: 5500; --app-z-modal: 5500;
} }
:root[data-project='ghatre'] {
--app-primary-color: #521620;
--project-hero-gradient: linear-gradient(145deg, #521620 0%, #7a2a1e 40%, #521620 100%);
}
:root[data-project='kashkool'] {
--app-primary-color: #a72b77;
--project-hero-gradient: linear-gradient(145deg, #a72b77 0%, #711d4d 40%, #a72b77 100%);
}
.ant-form-item { .ant-form-item {
margin-bottom: 10px !important; margin-bottom: 10px !important;
@@ -164,7 +176,7 @@ body {
background-color: #fff; background-color: #fff;
//display: none; //display: none;
outline: none; outline: none;
border: 1px solid #a72b77 !important; border: 1px solid var(--app-primary-color, #a72b77) !important;
} }
} }
@@ -362,18 +374,18 @@ body {
} }
.ant-radio-group-solid .ant-radio-group-solid
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{ .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{
color: #a72b77 !important; color: var(--app-primary-color, #a72b77) !important;
} }
.ant-input-group-addon{ .ant-input-group-addon{
background-color: #a72b77 !important; background-color: var(--app-primary-color, #a72b77) !important;
color: #e0be7f !important; color: #e0be7f !important;
} }
.ant-slider-track{ .ant-slider-track{
background-color: #a72b77 !important; background-color: var(--app-primary-color, #a72b77) !important;
} }
.ant-slider-handle { .ant-slider-handle {
background-color: #a72b77 !important; background-color: var(--app-primary-color, #a72b77) !important;
} }
+1
View File
@@ -3,6 +3,7 @@
@primary-color: #a72b77; @primary-color: #a72b77;
@secondary-color: #a72b77; @secondary-color: #a72b77;
@App-primary-color: #a72b77; @App-primary-color: #a72b77;
@theme-primary: var(--app-primary-color, #a72b77);
@prefixCls-font-family: 'iran-sans'; @prefixCls-font-family: 'iran-sans';
@prefixCls-background-color: #fff; @prefixCls-background-color: #fff;
+40
View File
@@ -0,0 +1,40 @@
import { computed } from 'vue'
import {
getProjectBranding,
getProjectId,
isBoursecuProject,
isGhatreProject,
isKashkoolProject,
type ProjectBranding,
type ProjectId,
} from '@/utils/project'
export function useProject() {
const projectId = computed<ProjectId>(() => getProjectId())
const branding = computed<ProjectBranding>(() => getProjectBranding())
const isGhatre = computed(() => isGhatreProject())
const isKashkool = computed(() => isKashkoolProject())
const isBoursecu = computed(() => isBoursecuProject())
const projectNameFa = computed(() => branding.value.nameFa)
const heroGradient = computed(() => branding.value.heroGradient)
const accentColor = computed(() => branding.value.accentColor)
const fundLabel = computed(() => branding.value.fundLabel)
const showHomeRoute = computed(() => branding.value.showHomeRoute)
const defaultRoute = computed(() => branding.value.defaultRoute)
return {
projectId,
branding,
isGhatre,
isKashkool,
isBoursecu,
projectNameFa,
heroGradient,
accentColor,
fundLabel,
showHomeRoute,
defaultRoute,
}
}
+22 -3
View File
@@ -15,14 +15,33 @@ export function getRawAppConfig() {
return appConfig ?? window.__APP_CONFIG__ ?? null return appConfig ?? window.__APP_CONFIG__ ?? null
} }
function isPlainObject(value: unknown): value is Record<string, any> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
export function getTenantConfig() { export function getTenantConfig() {
const rawConfig = getRawAppConfig() const rawConfig = getRawAppConfig()
if (!rawConfig) return {}
if (rawConfig?.user && typeof rawConfig.user === 'object') { const user = rawConfig.user
return rawConfig.user
if (isPlainObject(user)) {
return {
...user,
name: rawConfig.name ?? user.name,
name_fa: rawConfig.name_fa ?? user.name_fa,
}
} }
if (rawConfig && Object.keys(rawConfig).length > 0) { if (Array.isArray(user) && isPlainObject(user[0])) {
return {
...user[0],
name: rawConfig.name ?? user[0].name,
name_fa: rawConfig.name_fa ?? user[0].name_fa,
}
}
if (Object.keys(rawConfig).length > 0) {
return rawConfig return rawConfig
} }
+24 -1
View File
@@ -1,14 +1,37 @@
import { getConfig } from '@/config/app-config' import { getConfig } from '@/config/app-config'
import { isPwaManifestReady } from '@/modules/pwa/manifest' import { isPwaManifestReady } from '@/modules/pwa/manifest'
import { getProjectBranding } from '@/utils/project'
export function applyAppConfig() { export function applyAppConfig() {
if (isPwaManifestReady()) return applyProjectTheme()
if (isPwaManifestReady()) {
setTitle()
return
}
setTitle() setTitle()
setFavicon() setFavicon()
setAppleTouchIconFallback() setAppleTouchIconFallback()
} }
function applyProjectTheme() {
const branding = getProjectBranding()
const root = document.documentElement
const primary = branding.accentColor
root.style.setProperty('--project-accent', primary)
root.style.setProperty('--project-hero-gradient', branding.heroGradient)
root.style.setProperty('--app-primary-color', primary)
root.style.setProperty('--primary-color', primary)
root.dataset.project = branding.id
const secondaryColor = getConfig<string>('secondary_color')
if (secondaryColor) {
root.style.setProperty('--secondary-color', secondaryColor)
}
}
function setTitle() { function setTitle() {
document.title = getConfig('name_fa', 'در حال بارگذاری...') document.title = getConfig('name_fa', 'در حال بارگذاری...')
} }
+10 -5
View File
@@ -92,11 +92,16 @@ export async function fetchFreshAppConfig(rounds = CONFIG_RETRY_ROUNDS): Promise
} }
export async function loadAppConfig(): Promise<AppConfig> { export async function loadAppConfig(): Promise<AppConfig> {
const cachedConfig = readCachedAppConfig() try {
return await fetchFreshAppConfig()
} catch (error) {
const cachedConfig = readCachedAppConfig()
if (cachedConfig) { if (cachedConfig) {
return cachedConfig console.warn('Using cached app config because fresh fetch failed', error)
return cachedConfig
}
throw error
} }
return await fetchFreshAppConfig()
} }
+19 -3
View File
@@ -1,6 +1,7 @@
import type { AppNavItem } from '../types' import type { AppNavItem } from '../types'
import { getProjectBranding, isGhatreProject } from '@/utils/project'
/** تب‌های پایین موبایل — کاربر لاگین‌شده */ /** تب‌های پایین موبایل — کاربر لاگین‌شده (کشکول) */
export const AUTH_PRIMARY_NAV: AppNavItem[] = [ export const AUTH_PRIMARY_NAV: AppNavItem[] = [
{ id: 'home', routeName: 'home', label: 'صفحه اصلی', icon: 'vuesax-linear:home' }, { id: 'home', routeName: 'home', label: 'صفحه اصلی', icon: 'vuesax-linear:home' },
{ id: 'transactions', routeName: 'transactions', label: 'گزارش', icon: 'vuesax-linear:receipt-item' }, { id: 'transactions', routeName: 'transactions', label: 'گزارش', icon: 'vuesax-linear:receipt-item' },
@@ -8,6 +9,14 @@ export const AUTH_PRIMARY_NAV: AppNavItem[] = [
{ id: 'cart', routeName: 'cart', label: 'سبد خرید', icon: 'vuesax-linear:shopping-cart' }, { id: 'cart', routeName: 'cart', label: 'سبد خرید', icon: 'vuesax-linear:shopping-cart' },
] ]
/** تب‌های پایین موبایل — کاربر لاگین‌شده (قطره) */
export const GHATRE_AUTH_PRIMARY_NAV: AppNavItem[] = [
{ id: 'buyProduct', routeName: 'buyProduct', label: 'فروشگاه', icon: 'vuesax-linear:coin' },
{ id: 'transactions', routeName: 'transactions', label: 'گزارش', icon: 'vuesax-linear:receipt-item' },
{ id: 'wallet', routeName: 'wallet', label: 'کیف پول', icon: 'vuesax-linear:wallet-1' },
{ id: 'cart', routeName: 'cart', label: 'سبد خرید', icon: 'vuesax-linear:shopping-cart' },
]
/** تب‌های پایین موبایل — مهمان */ /** تب‌های پایین موبایل — مهمان */
export const GUEST_PRIMARY_NAV: AppNavItem[] = [ export const GUEST_PRIMARY_NAV: AppNavItem[] = [
{ id: 'buyProduct', routeName: 'buyProduct', label: 'فروشگاه', icon: 'vuesax-linear:coin' }, { id: 'buyProduct', routeName: 'buyProduct', label: 'فروشگاه', icon: 'vuesax-linear:coin' },
@@ -31,9 +40,16 @@ export const AUTH_SECONDARY_NAV: AppNavItem[] = [
] ]
export function getPrimaryNavItems(isAuthenticated: boolean): AppNavItem[] { export function getPrimaryNavItems(isAuthenticated: boolean): AppNavItem[] {
return isAuthenticated ? AUTH_PRIMARY_NAV : GUEST_PRIMARY_NAV if (!isAuthenticated) return GUEST_PRIMARY_NAV
return isGhatreProject() ? GHATRE_AUTH_PRIMARY_NAV : AUTH_PRIMARY_NAV
} }
export function getSecondaryNavItems(isAuthenticated: boolean): AppNavItem[] { export function getSecondaryNavItems(isAuthenticated: boolean): AppNavItem[] {
return isAuthenticated ? AUTH_SECONDARY_NAV : [] if (!isAuthenticated) return []
const fundLabel = getProjectBranding().fundLabel
return AUTH_SECONDARY_NAV.map((item) =>
item.id === 'fund' ? { ...item, label: fundLabel } : item
)
} }
+1 -1
View File
@@ -48,7 +48,7 @@ defineEmits<{
height: 18px; height: 18px;
padding: 0 4px; padding: 0 4px;
border-radius: 999px; border-radius: 999px;
background: #a72b77; background: var(--app-primary-color, #a72b77);
color: #fff; color: #fff;
font-size: 10px; font-size: 10px;
font-weight: 700; font-weight: 700;
+3 -1
View File
@@ -1,7 +1,7 @@
<template> <template>
<aside class="desktop-sidebar"> <aside class="desktop-sidebar">
<div class="desktop-sidebar__brand"> <div class="desktop-sidebar__brand">
<img :src="navLogo" alt="کشکول گلد" class="desktop-sidebar__logo" /> <img :src="navLogo" :alt="projectNameFa" class="desktop-sidebar__logo" />
</div> </div>
<nav class="desktop-sidebar__section" aria-label="منوی اصلی"> <nav class="desktop-sidebar__section" aria-label="منوی اصلی">
@@ -45,7 +45,9 @@ import { useCartStore } from '@/store/cartStore'
import { getPrimaryNavItems, getSecondaryNavItems } from '../config/navigation' import { getPrimaryNavItems, getSecondaryNavItems } from '../config/navigation'
import { useAppNavigation } from '../composables/useAppNavigation' import { useAppNavigation } from '../composables/useAppNavigation'
import DesktopNavItem from './DesktopNavItem.vue' import DesktopNavItem from './DesktopNavItem.vue'
import { useProject } from '@/composables/useProject'
const { projectNameFa } = useProject()
const cartStore = useCartStore() const cartStore = useCartStore()
const authenticated = computed(() => isToken()) const authenticated = computed(() => isToken())
const navLogo = logoFull const navLogo = logoFull
+3 -3
View File
@@ -110,15 +110,15 @@
&:hover { &:hover {
background: rgba(167, 43, 119, 0.06); background: rgba(167, 43, 119, 0.06);
color: @primary-color; color: @theme-primary;
} }
&--active { &--active {
background: rgba(167, 43, 119, 0.1); background: rgba(167, 43, 119, 0.1);
color: @primary-color; color: @theme-primary;
.desktop-nav-item__icon { .desktop-nav-item__icon {
color: @primary-color; color: @theme-primary;
} }
} }
} }
@@ -85,7 +85,7 @@
&__price { &__price {
font-size: 15px; font-size: 15px;
font-weight: 700; font-weight: 700;
color: @App-primary-color; color: @theme-primary;
} }
} }
@@ -114,7 +114,7 @@
&__total { &__total {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
color: @App-primary-color; color: @theme-primary;
} }
} }
@@ -153,7 +153,7 @@
.dproduct-info__price { .dproduct-info__price {
font-size: 22px; font-size: 22px;
font-weight: 700; font-weight: 700;
color: @App-primary-color; color: @theme-primary;
} }
.dproduct-info__desc { .dproduct-info__desc {
+3 -3
View File
@@ -43,7 +43,7 @@
&.text-primary .tab__icon, &.text-primary .tab__icon,
&.text-primary .tab__icon .ant-badge { &.text-primary .tab__icon .ant-badge {
color: @primary-color; color: @theme-primary;
} }
&.text-gray-400 .tab__icon, &.text-gray-400 .tab__icon,
@@ -68,11 +68,11 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: #a72b77; background-color: @theme-primary;
.circle_into { .circle_into {
overflow: hidden; overflow: hidden;
background-color: @primary-color; background-color: @theme-primary;
border-radius: 50%; border-radius: 50%;
width: 56px; width: 56px;
height: 56px; height: 56px;
+2 -2
View File
@@ -31,7 +31,7 @@
padding: 0.75rem; padding: 0.75rem;
padding-top: calc(0.75rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); padding-top: calc(0.75rem + var(--app-safe-top, env(safe-area-inset-top, 0px)));
background: #fff; background: #fff;
color: @primary-color; color: @theme-primary;
font-size: 1rem; font-size: 1rem;
font-weight: 700; font-weight: 700;
line-height: 1.5; line-height: 1.5;
@@ -67,7 +67,7 @@
z-index: 1999; z-index: 1999;
padding: 0.75rem; padding: 0.75rem;
background: #fff; background: #fff;
color: @primary-color; color: @theme-primary;
font-weight: 700; font-weight: 700;
} }
+2 -7
View File
@@ -1,4 +1,5 @@
import {createRouter, createWebHistory} from 'vue-router' import {createRouter, createWebHistory} from 'vue-router'
import { getProjectDefaultRoute } from '@/utils/project'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@@ -8,16 +9,10 @@ const router = createRouter({
name: 'login', name: 'login',
component: () => import('../views/login/index.vue') component: () => import('../views/login/index.vue')
}, },
/* {
path: '/',
name: 'null',
redirect: {name: 'home'},
// component: () => import('../views/login/index.vue')
},*/
{ {
path: `/`, path: `/`,
name: 'main', name: 'main',
redirect: {name: 'buyProduct'}, redirect: () => ({ name: getProjectDefaultRoute() }),
component: () => import('../views/base/index.vue'), component: () => import('../views/base/index.vue'),
children: [ children: [
{ {
+17 -6
View File
@@ -1,8 +1,19 @@
export function getConfigApp(key: string) { import { getTenantConfig } from '@/config/app-config'
const config = window.__APP_CONFIG__ ? window.__APP_CONFIG__.user : {}
if (config) { export function getConfigApp(key: string) {
return config[key] const config = getTenantConfig()
} const value = config?.[key]
return null
return value ?? null
} }
export {
getProjectId,
getProjectBranding,
getProjectDefaultRoute,
isGhatreProject,
isKashkoolProject,
isBoursecuProject,
} from '@/utils/project'
export type { ProjectId, ProjectBranding } from '@/utils/project'
+25
View File
@@ -0,0 +1,25 @@
function normalizeGroupType(value: unknown): string {
return String(value ?? '').trim().toLowerCase()
}
export function isKatodMesGroup(groupType: unknown): boolean {
const type = normalizeGroupType(groupType)
return type.includes('کاتد') || type.includes('katod')
}
export function isMaftolMesGroup(groupType: unknown): boolean {
const type = normalizeGroupType(groupType)
return type.includes('مفتول') || type.includes('maftol')
}
export function isShamshMesGroup(groupType: unknown, title?: unknown): boolean {
const type = normalizeGroupType(groupType)
const name = normalizeGroupType(title)
return (
type.includes('شمش') ||
type.includes('shamsh') ||
name.includes('شمش') ||
name.includes('shamsh')
)
}
+155
View File
@@ -0,0 +1,155 @@
import { getRawAppConfig } from '@/config/app-config'
import type { AppConfig } from '@/config/types'
export type ProjectId = 'ghatre' | 'kashkool' | 'boursecu'
export interface ProjectBranding {
id: ProjectId
nameFa: string
heroGradient: string
accentColor: string
borderAccent: string
fundLabel: string
fundHeroBg: string
idCardLabel: string
buyPriceLabel: string
defaultRoute: string
showHomeRoute: boolean
referralDomain: string
}
const PROJECT_BRANDING: Record<ProjectId, ProjectBranding> = {
ghatre: {
id: 'ghatre',
nameFa: 'قطره',
heroGradient: 'linear-gradient(145deg, #521620 0%, #7a2a1e 40%, #521620 100%)',
accentColor: '#521620',
borderAccent: '#521620',
fundLabel: 'قلک',
fundHeroBg: '#EFCED9',
idCardLabel: 'شناسه شما در قطره',
buyPriceLabel: 'قیمت خرید از قطره',
defaultRoute: 'buyProduct',
showHomeRoute: false,
referralDomain: 'ghatre.gold',
},
kashkool: {
id: 'kashkool',
nameFa: 'کشکول',
heroGradient: 'linear-gradient(145deg, #a72b77 0%, #711d4d 40%, #a72b77 100%)',
accentColor: '#a72b77',
borderAccent: '#a72b77',
fundLabel: 'دارایی ها',
fundHeroBg: '',
idCardLabel: 'شناسه شما در کشکول',
buyPriceLabel: 'قیمت خرید از کشکول',
defaultRoute: 'home',
showHomeRoute: true,
referralDomain: 'kh.kashkolgold.ir',
},
boursecu: {
id: 'boursecu',
nameFa: 'بورسکو',
heroGradient: 'linear-gradient(145deg, #1e2229 0%, #374151 40%, #1e2229 100%)',
accentColor: '#1e2229',
borderAccent: '#1e2229',
fundLabel: 'دارایی ها',
fundHeroBg: '',
idCardLabel: 'شناسه شما',
buyPriceLabel: 'قیمت خرید',
defaultRoute: 'home',
showHomeRoute: true,
referralDomain: '',
},
}
function normalizeProjectName(value: unknown): string {
return String(value ?? '')
.trim()
.toLowerCase()
.replace(/\s+/g, '')
}
export function resolveProjectNameFromConfig(raw: AppConfig | null | undefined): string {
if (!raw) return ''
const candidates: unknown[] = [
raw.name,
typeof raw.user === 'object' && raw.user !== null && !Array.isArray(raw.user)
? (raw.user as Record<string, unknown>).name
: undefined,
Array.isArray(raw.user) && raw.user[0] && typeof raw.user[0] === 'object'
? (raw.user[0] as Record<string, unknown>).name
: undefined,
]
for (const value of candidates) {
const normalized = normalizeProjectName(value)
if (normalized) return normalized
}
return ''
}
function getProjectNameRaw(): string {
return resolveProjectNameFromConfig(getRawAppConfig())
}
function matchesProjectName(name: string, aliases: string[]): boolean {
return aliases.some((alias) => name.includes(normalizeProjectName(alias)))
}
export function getProjectId(): ProjectId {
const name = getProjectNameRaw()
if (name === 'ghatreh') {
return 'ghatre'
}
if (name === 'kashkolgold') {
return 'kashkool'
}
if (matchesProjectName(name, ['boursecu', 'bourse', 'بورسکو', 'بورس'])) {
return 'boursecu'
}
if (matchesProjectName(name, ['ghatre', 'qatre', 'قطره'])) {
return 'ghatre'
}
if (matchesProjectName(name, ['kashkool', 'kashkol', 'کشکول'])) {
return 'kashkool'
}
return 'kashkool'
}
export function isGhatreProject(): boolean {
return getProjectId() === 'ghatre'
}
export function isKashkoolProject(): boolean {
return getProjectId() === 'kashkool'
}
export function isBoursecuProject(): boolean {
return getProjectId() === 'boursecu'
}
export function getProjectBranding(): ProjectBranding {
const id = getProjectId()
const defaults = PROJECT_BRANDING[id]
return {
...defaults,
nameFa: defaults.nameFa,
accentColor: defaults.accentColor,
borderAccent: defaults.borderAccent,
heroGradient: defaults.heroGradient,
}
}
export function getProjectDefaultRoute(): string {
return getProjectBranding().defaultRoute
}
@@ -95,7 +95,7 @@
height: 56px; height: 56px;
border-radius: 14px; border-radius: 14px;
background: rgba(9, 61, 81, 0.08); background: rgba(9, 61, 81, 0.08);
color: @App-primary-color; color: @theme-primary;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -125,7 +125,7 @@
border: 1px solid rgba(9, 61, 81, 0.15); border: 1px solid rgba(9, 61, 81, 0.15);
border-radius: 12px; border-radius: 12px;
background: rgba(9, 61, 81, 0.03); background: rgba(9, 61, 81, 0.03);
color: @App-primary-color; color: @theme-primary;
} }
&__divider { &__divider {
@@ -6,6 +6,7 @@ import { useWalletAssets } from '@/composables/useWalletAssets'
import type { WalletMetalTotal } from '@/models/walletAssets' import type { WalletMetalTotal } from '@/models/walletAssets'
import { fundPageInventoryCards, formatMetalTotal as buildMetalTotalLabel } from '@/utils/productAssets' import { fundPageInventoryCards, formatMetalTotal as buildMetalTotalLabel } from '@/utils/productAssets'
import { getLastPrices } from '@/utils/price' import { getLastPrices } from '@/utils/price'
import { getProjectBranding, isGhatreProject } from '@/utils/project'
import img from '@/assets/img/user_ly1.svg' import img from '@/assets/img/user_ly1.svg'
import empty from '@/assets/img/cash_empty.svg' import empty from '@/assets/img/cash_empty.svg'
@@ -20,6 +21,8 @@ export interface FundPageContext {
fundHeaderBackground: string fundHeaderBackground: string
fundEmptyImage: string fundEmptyImage: string
fundEmptyText: string fundEmptyText: string
fundLabel: string
fundBalanceLabel: string
formatMetalTotal: (metal: WalletMetalTotal) => string formatMetalTotal: (metal: WalletMetalTotal) => string
openFundInfo: (productId: number) => void openFundInfo: (productId: number) => void
goBack: () => void goBack: () => void
@@ -70,6 +73,8 @@ export function useFundPage(_isDesktop: Ref<boolean>) {
} }
}) })
const branding = getProjectBranding()
const context: FundPageContext = { const context: FundPageContext = {
meta, meta,
inventoryCards, inventoryCards,
@@ -80,7 +85,9 @@ export function useFundPage(_isDesktop: Ref<boolean>) {
countBasedInventory, countBasedInventory,
fundHeaderBackground: img, fundHeaderBackground: img,
fundEmptyImage: empty, fundEmptyImage: empty,
fundEmptyText: 'یک گنج جدید بساز!', fundEmptyText: isGhatreProject() ? 'یک قلک جدید بساز!' : 'یک گنج جدید بساز!',
fundLabel: branding.fundLabel,
fundBalanceLabel: isGhatreProject() ? `موجودی ${branding.fundLabel} ها` : `موجودی ${branding.fundLabel}`,
formatMetalTotal, formatMetalTotal,
openFundInfo, openFundInfo,
goBack, goBack,
@@ -7,16 +7,16 @@
بازگشت بازگشت
</button> </button>
<div class="dfund-topbar__meta"> <div class="dfund-topbar__meta">
<p class="dfund-topbar__eyebrow">دارایی</p> <p class="dfund-topbar__eyebrow">{{ fundLabel }}</p>
<h1 class="dfund-topbar__title">دارایی‌ها</h1> <h1 class="dfund-topbar__title">{{ fundLabel }}</h1>
<p class="dfund-topbar__subtitle">نمای کلی موجودی محصولات و کیف پول</p> <p class="dfund-topbar__subtitle">نمای کلی موجودی محصولات و کیف پول</p>
</div> </div>
</header> </header>
<section class="dfund-summary dfund-summary--ghatreh"> <section class="dfund-summary" :class="isGhatre ? 'dfund-summary--ghatreh' : ''">
<img :src="fundHeaderBackground" alt="" class="dfund-summary__bg" /> <img :src="fundHeaderBackground" alt="" class="dfund-summary__bg" />
<div class="dfund-summary__content"> <div class="dfund-summary__content">
<h2 class="dfund-summary__title">موجودی دارایی‌ها</h2> <h2 class="dfund-summary__title">{{ fundBalanceLabel }}</h2>
<div v-if="meta?.has_count_based_products" class="dfund-summary__row"> <div v-if="meta?.has_count_based_products" class="dfund-summary__row">
<span>محصولات</span> <span>محصولات</span>
@@ -93,6 +93,9 @@ import { loading } from '@/utils'
import { usePriceWithoutType } from '@/utils/usePrice' import { usePriceWithoutType } from '@/utils/usePrice'
import Item from '../components/Item.vue' import Item from '../components/Item.vue'
import { useFundPageContext } from '../composables/useFundPage' import { useFundPageContext } from '../composables/useFundPage'
import { useProject } from '@/composables/useProject'
const { isGhatre } = useProject()
const { const {
meta, meta,
@@ -105,6 +108,8 @@ const {
fundHeaderBackground, fundHeaderBackground,
fundEmptyImage, fundEmptyImage,
fundEmptyText, fundEmptyText,
fundLabel,
fundBalanceLabel,
formatMetalTotal, formatMetalTotal,
openFundInfo, openFundInfo,
goBack, goBack,
@@ -73,7 +73,7 @@
overflow: hidden; overflow: hidden;
border-radius: 16px; border-radius: 16px;
margin-bottom: 24px; margin-bottom: 24px;
background: @App-primary-color; background: @theme-primary;
color: #fff; color: #fff;
&--bazretala { &--bazretala {
@@ -339,7 +339,7 @@
.dfund-info__balance-value { .dfund-info__balance-value {
font-size: 28px; font-size: 28px;
font-weight: 700; font-weight: 700;
color: @App-primary-color; color: @theme-primary;
} }
.dfund-info__balance-unit { .dfund-info__balance-unit {
@@ -495,8 +495,8 @@
cursor: pointer; cursor: pointer;
&--active { &--active {
background: @App-primary-color; background: @theme-primary;
border-color: @App-primary-color; border-color: @theme-primary;
color: #fff; color: #fff;
} }
} }
+14 -3
View File
@@ -6,17 +6,21 @@
<div class="cursor-pointer" @click="goBack()"> <div class="cursor-pointer" @click="goBack()">
<BasicIcon icon="vuesax-linear:arrow-right-1" /> <BasicIcon icon="vuesax-linear:arrow-right-1" />
</div> </div>
<div class="mr-2">دارایی ها</div> <div class="mr-2">{{ fundLabel }}</div>
</div> </div>
<div class="fund-mobile-root__hero text-tertiary bg-primary"> <div
class="fund-mobile-root__hero text-tertiary"
:class="isGhatre ? 'fund-mobile-root__hero--ghatre' : 'bg-primary'"
:style="isGhatre ? { backgroundColor: fundHeroBg } : undefined"
>
<img <img
:src="fundHeaderBackground" :src="fundHeaderBackground"
alt="" alt=""
class="fund-mobile-root__hero-bg" class="fund-mobile-root__hero-bg"
/> />
<div class="fund-mobile-root__hero-content flex flex-col"> <div class="fund-mobile-root__hero-content flex flex-col">
<div class="font-bold mr-5 z-20">موجودی دارایی ها</div> <div class="font-bold mr-5 z-20">{{ fundBalanceLabel }}</div>
<div <div
v-if="meta?.has_count_based_products" v-if="meta?.has_count_based_products"
@@ -91,10 +95,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue' import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { computed } from 'vue'
import { loading } from '@/utils' import { loading } from '@/utils'
import { usePriceWithoutType } from '@/utils/usePrice' import { usePriceWithoutType } from '@/utils/usePrice'
import Item from '../components/Item.vue' import Item from '../components/Item.vue'
import { useFundPageContext } from '../composables/useFundPage' import { useFundPageContext } from '../composables/useFundPage'
import { useProject } from '@/composables/useProject'
const { isGhatre, branding } = useProject()
const fundHeroBg = computed(() => branding.value.fundHeroBg)
const { const {
meta, meta,
@@ -107,6 +116,8 @@ const {
fundHeaderBackground, fundHeaderBackground,
fundEmptyImage, fundEmptyImage,
fundEmptyText, fundEmptyText,
fundLabel,
fundBalanceLabel,
formatMetalTotal, formatMetalTotal,
openFundInfo, openFundInfo,
goBack, goBack,
@@ -47,7 +47,7 @@
height: 56px; height: 56px;
border-radius: 14px; border-radius: 14px;
background: rgba(167, 43, 119, 0.08); background: rgba(167, 43, 119, 0.08);
color: @App-primary-color; color: @theme-primary;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -126,7 +126,7 @@
} }
.basic-icon { .basic-icon {
color: @App-primary-color; color: @theme-primary;
flex-shrink: 0; flex-shrink: 0;
margin-top: 2px; margin-top: 2px;
} }
@@ -0,0 +1,94 @@
import { computed, inject, provide, type InjectionKey } from 'vue'
import router from '@/router'
import { useSettingsStore } from '@/store/settingsStore'
import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours'
import { isKashkoolProject } from '@/utils/project'
import { getHomeLayoutId } from '../layouts/registry'
import { useBanners } from './useBanners'
import { usePriceChart } from './usePriceChart'
import { initHomePageData } from './useHomePageData'
import type { HomeActionItem } from '../layouts/types'
export const HOME_PAGE_CONTEXT_KEY: InjectionKey<ReturnType<typeof createHomePageContext>> =
Symbol('homePageContext')
function createHomePageContext() {
const settingsStore = useSettingsStore()
const { banners } = useBanners()
const chart = usePriceChart()
const isBoursecuHomeLayout = computed(() => getHomeLayoutId() === 'boursecu')
const isGaroosi = computed(() => isKashkoolProject())
const bannerEnabled = computed(() => settingsStore.isSettingTruthy('banner_enabled'))
const showClosedMessage = computed(() => isStoreClosed())
const closedMessage = computed(() => getStoreClosedMessage())
const mainActions = computed<HomeActionItem[]>(() => [
{
name: 'buySell',
icon: 'vuesax-linear:money-receive',
label: 'خرید و فروش',
handler: () => router.push({ name: 'gold' }),
},
{
name: 'deposit',
icon: 'vuesax-linear:card-add',
label: 'واریز نقدی',
handler: () => router.push({ name: 'wallet', query: { state: 'deposit' } }),
},
{
name: 'balance',
icon: 'vuesax-linear:receipt-item',
label: 'موجودی',
handler: () => {},
},
{
name: 'fund',
icon: 'vuesax-linear:archive-1',
label: 'دارایی ها',
handler: () => router.push({ name: 'fund' }),
},
{
name: 'earn',
icon: 'vuesax-linear:dollar-circle',
label: 'کسب درآمد',
handler: () => router.push({ name: 'earnMoney' }),
},
])
function openGoldModal() {}
function openRialModal() {}
function handleBannerClick() {}
void initHomePageData()
return {
isBoursecuHomeLayout,
isGaroosi,
bannerEnabled,
banners,
showClosedMessage,
closedMessage,
mainActions,
priceChartSeries: chart.priceChartSeries,
chartSelection: chart.chartSelection,
selectedChartMetal: chart.selectedChartMetal,
updateChartData: chart.updateChartData,
setChartMetal: chart.setChartMetal,
openGoldModal,
openRialModal,
handleBannerClick,
}
}
export function useHomePageProvider() {
const context = createHomePageContext()
provide(HOME_PAGE_CONTEXT_KEY, context)
return context
}
export function useHomePageContext() {
const existing = inject(HOME_PAGE_CONTEXT_KEY, null)
if (existing) return existing
return useHomePageProvider()
}
@@ -0,0 +1,7 @@
export async function loadHomeFunds(_force = false) {
return Promise.resolve()
}
export function resetHomeFunds() {
// no-op — fund list is loaded via wallet assets on the home page
}
@@ -8,7 +8,7 @@
<template v-else-if="userStore.userInfo"> <template v-else-if="userStore.userInfo">
<section class="dhome-hero-row"> <section class="dhome-hero-row">
<div class="dhome-surface dhome-hero-price"> <div class="dhome-surface dhome-hero-price">
<span class="dhome-label">قیمت خرید از کشکول گلد</span> <span class="dhome-label">{{ branding.buyPriceLabel }}</span>
<div class="dhome-hero-price__value-row"> <div class="dhome-hero-price__value-row">
<span <span
v-if="percentPriceChanged() !== 0" v-if="percentPriceChanged() !== 0"
@@ -174,8 +174,9 @@ import DesktopHomeTopbar from '@/views/base/home/components/DesktopHomeTopbar.vu
import HomeSkeleton from '@/views/base/home/components/HomeSkeleton.vue' import HomeSkeleton from '@/views/base/home/components/HomeSkeleton.vue'
import bannerImg from '@/assets/img/banner/2.png' import bannerImg from '@/assets/img/banner/2.png'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { useProject } from '@/composables/useProject'
const emit = defineEmits<{ logout: []; openBalance: [] }>() const { branding, projectNameFa } = useProject()
const userStore = useUserStore() const userStore = useUserStore()
const walletStore = useWalletStore() const walletStore = useWalletStore()
const { assetsSummary, loadWalletAssets } = useWalletAssets() const { assetsSummary, loadWalletAssets } = useWalletAssets()
@@ -263,7 +264,7 @@ function shareReferral() {
const text = `⭐️الان طلا بخر؛ آخر ماه سودشو ببر! const text = `⭐️الان طلا بخر؛ آخر ماه سودشو ببر!
این دعوت نامه از طرف "${userStore.userInfo?.name}" برای شما ارسال شده. این دعوت نامه از طرف "${userStore.userInfo?.name}" برای شما ارسال شده.
kh.kashkolgold.ir/login?id=${userStore.userInfo?.referral_id}` kh.kashkolgold.ir/login?id=${userStore.userInfo?.referral_id}`
navigator.share({ title: 'کشکول گلد', text }) navigator.share({ title: projectNameFa.value, text })
} }
onMounted(async () => { onMounted(async () => {
@@ -8,10 +8,12 @@ import { computed } from 'vue'
import { getHomeLayoutId } from '../layouts/registry' import { getHomeLayoutId } from '../layouts/registry'
import BoursecuDesktopHome from './layouts/BoursecuDesktopHome.vue' import BoursecuDesktopHome from './layouts/BoursecuDesktopHome.vue'
import DefaultDesktopHome from './layouts/DefaultDesktopHome.vue' import DefaultDesktopHome from './layouts/DefaultDesktopHome.vue'
import { useHomePageProvider } from '../composables/useHomePage'
defineEmits<{ defineEmits<{
logout: [] logout: []
}>() }>()
useHomePageProvider()
const layoutId = computed(() => getHomeLayoutId()) const layoutId = computed(() => getHomeLayoutId())
</script> </script>
@@ -420,7 +420,7 @@
height: 36px; height: 36px;
border-radius: 10px; border-radius: 10px;
background: #f3f4f6; background: #f3f4f6;
color: @App-primary-color; color: @theme-primary;
flex-shrink: 0; flex-shrink: 0;
transition: background 0.15s ease; transition: background 0.15s ease;
overflow: visible; overflow: visible;
@@ -498,7 +498,7 @@
height: 40px; height: 40px;
border-radius: 12px; border-radius: 12px;
background: #ffffff; background: #ffffff;
color: @App-primary-color; color: @theme-primary;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05); box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
} }
@@ -628,7 +628,7 @@
transition: background 0.15s ease, color 0.15s ease; transition: background 0.15s ease, color 0.15s ease;
&--active { &--active {
background: @App-primary-color; background: @theme-primary;
color: #fff; color: #fff;
font-weight: 600; font-weight: 600;
} }
+24 -14
View File
@@ -1,11 +1,11 @@
<template> <template>
<div class="home-page-root"> <div class="home-page-root">
<GhatrehHomeDesktopView <GhatrehHomeDesktopView
v-show="isDesktop" v-if="isDesktop && !isGhatre"
@logout="showLogoutConfirm = true" @logout="showLogoutConfirm = true"
@open-balance="openWholeBalanceVisible()" @open-balance="openWholeBalanceVisible()"
/> />
<div v-show="!isDesktop" class="home-mobile-root pb-[calc(var(--app-bottom-chrome-height)+1rem)]"> <div v-show="!isDesktop || isGhatre" class="home-mobile-root pb-[calc(var(--app-bottom-chrome-height)+1rem)]">
<div class="app-header-scroll-sentinel" aria-hidden="true" /> <div class="app-header-scroll-sentinel" aria-hidden="true" />
<div <div
v-if="!loading" v-if="!loading"
@@ -13,7 +13,7 @@
> >
<div <div
class="px-5 py-5 shadow-lg rounded-2xl text-white relative overflow-hidden shadow-lg" class="px-5 py-5 shadow-lg rounded-2xl text-white relative overflow-hidden shadow-lg"
style=" background: linear-gradient(145deg, #a72b77 0%, #711d4d 40%, #a72b77 100%); " :style="{ background: heroGradient }"
> >
<div <div
class="absolute inset-0 opacity-20" class="absolute inset-0 opacity-20"
@@ -24,13 +24,13 @@
<div class="relative z-10"> <div class="relative z-10">
<div class="flex font-bold text-lg items-center gap-2 mb-3"> <div class="flex font-bold text-lg items-center gap-2 mb-3">
<img src='@/assets/img/logo.png' alt='' class='h-8 drop-shadow-md' /> <img src='@/assets/img/logo.png' alt='' class='h-8 drop-shadow-md' />
<div class="tracking-wide">کشکول گلد </div> <div class="tracking-wide">{{ projectNameFa }}</div>
</div> </div>
<div class="flex justify-between items-end mb-3"> <div class="flex justify-between items-end mb-3">
<div class="text-sm" /> <div class="text-sm" />
<div> <div>
<div class="text-xs opacity-75" style="justify-self: end;">شناسه شما در کشکول گلد</div> <div class="text-xs opacity-75" style="justify-self: end;">{{ branding.idCardLabel }}</div>
<div class="flex"> <div class="flex">
<div v-if="userStore?.userInfo?.virtual_card_number" class="ml-2 " @click.stop="copyCode()"> <div v-if="userStore?.userInfo?.virtual_card_number" class="ml-2 " @click.stop="copyCode()">
<BasicIcon <BasicIcon
@@ -75,7 +75,7 @@
> >
<div <div
class="rounded-full w-[42px] h-[42px] bg-primary flex items-center justify-center" class="rounded-full w-[42px] h-[42px] bg-primary flex items-center justify-center"
style="border: solid 1px #a72b77" :style="actionBorderStyle"
> >
<Icon class="text-tertiary" icon="vuesax-linear:money-send" /> <Icon class="text-tertiary" icon="vuesax-linear:money-send" />
@@ -89,7 +89,7 @@
> >
<div <div
class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center" class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center"
style="border: solid 1px #a72b77" :style="actionBorderStyle"
> >
<Icon class="text-white" icon="vuesax-linear:money-receive" /> <Icon class="text-white" icon="vuesax-linear:money-receive" />
@@ -103,7 +103,7 @@
> >
<div <div
class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center" class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center"
style="border: solid 1px #a72b77" :style="actionBorderStyle"
> >
<Icon class="text-white" icon="vuesax-linear:card-add" /> <Icon class="text-white" icon="vuesax-linear:card-add" />
@@ -117,7 +117,7 @@
> >
<div <div
class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center " class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center "
style="border: solid 1px #a72b77" :style="actionBorderStyle"
> >
<Icon class="text-white" icon="vuesax-linear:receipt-item" /> <Icon class="text-white" icon="vuesax-linear:receipt-item" />
</div> </div>
@@ -130,7 +130,7 @@
> >
<div <div
class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center" class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center"
style=" border: solid 1px #a72b77" :style="actionBorderStyle"
> >
<Icon class="text-white font-bold" icon="vuesax-linear:archive-1" /> <Icon class="text-white font-bold" icon="vuesax-linear:archive-1" />
</div> </div>
@@ -143,7 +143,7 @@
> >
<div <div
class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center" class="rounded-full w-[50px] h-[50px] bg-primary flex items-center justify-center"
style=" border: solid 1px #a72b77" :style="actionBorderStyle"
> >
<Icon class="text-white font-bold" icon="vuesax-linear:dollar-circle" /> <Icon class="text-white font-bold" icon="vuesax-linear:dollar-circle" />
@@ -245,7 +245,7 @@
> >
<div class="w-full items-center flex flex-col"> <div class="w-full items-center flex flex-col">
<div class="text-gray-600">قیمت خرید از کشکول گلد</div> <div class="text-gray-600">{{ branding.buyPriceLabel }}</div>
<div v-if="price" class="flex gap-1 mt-3 text-xs items-center"> <div v-if="price" class="flex gap-1 mt-3 text-xs items-center">
<!-- <div id="odometer" class="font-bold text-2xl odometer animated fadeIn"> <!-- <div id="odometer" class="font-bold text-2xl odometer animated fadeIn">
&lt;!&ndash; {{ usePriceWithoutType(useCounterAnim(price.price_last) ?? 0) }}&ndash;&gt; &lt;!&ndash; {{ usePriceWithoutType(useCounterAnim(price.price_last) ?? 0) }}&ndash;&gt;
@@ -437,7 +437,7 @@
<div class="text-center"> <div class="text-center">
<div class="mt-2"> <div class="mt-2">
<div>🔥یه فرصت طلایی واقعی جلوتِ !🔥</div> <div>🔥یه فرصت طلایی واقعی جلوتِ !🔥</div>
<div>دوستاتو همین الان به کشکول گلد دعوت کن!</div> <div>دوستاتو همین الان به {{ projectNameFa }} دعوت کن!</div>
<div>اونا اولین خریدشونو کاملاً بدون کارمزد انجام می‌دن،۱۰٪ از کارمزد ۱۰ معامله‌ی اولشون هم، مستقیم میاد تو حساب تو!</div> <div>اونا اولین خریدشونو کاملاً بدون کارمزد انجام می‌دن،۱۰٪ از کارمزد ۱۰ معامله‌ی اولشون هم، مستقیم میاد تو حساب تو!</div>
<div>زود باش، این فرصت همیشه باز نیست! ⏳✨</div> <div>زود باش، این فرصت همیشه باز نیست! ⏳✨</div>
</div> </div>
@@ -471,6 +471,7 @@ import Vue3Odometer from 'vue3-odometer'
import 'odometer/themes/odometer-theme-default.css' import 'odometer/themes/odometer-theme-default.css'
import HomeSkeleton from '@/views/base/home/components/HomeSkeleton.vue' import HomeSkeleton from '@/views/base/home/components/HomeSkeleton.vue'
import GhatrehHomeDesktopView from '@/views/base/home/desktop/GhatrehHomeDesktopView.vue' import GhatrehHomeDesktopView from '@/views/base/home/desktop/GhatrehHomeDesktopView.vue'
import { useProject } from '@/composables/useProject'
import { import {
getLastPrices, getLastPrices,
getTradeBuyPrice, getTradeBuyPrice,
@@ -498,6 +499,10 @@ import AppHeader from '@/views/base/home/components/AppHeader.vue'
const userStore = useUserStore() const userStore = useUserStore()
const walletStore = useWalletStore() const walletStore = useWalletStore()
const isDesktop = useDesktopBreakpoint() const isDesktop = useDesktopBreakpoint()
const { branding, projectNameFa, heroGradient, isGhatre, accentColor } = useProject()
const actionBorderStyle = computed(() => ({
border: `solid 1px ${accentColor.value}`,
}))
const chartLoading = ref<boolean>(false) const chartLoading = ref<boolean>(false)
const openModal = ref<boolean>(false) const openModal = ref<boolean>(false)
const wholeBalanceVisible = ref<boolean>(false) const wholeBalanceVisible = ref<boolean>(false)
@@ -708,7 +713,7 @@ const priceChartOptions = ref({
show: false show: false
} }
}, },
colors: ['#a72b77'], colors: [accentColor.value],
fill: { fill: {
type: 'gradient', type: 'gradient',
gradient: { gradient: {
@@ -720,6 +725,11 @@ const priceChartOptions = ref({
} }
}) })
watch(accentColor, (color) => {
priceChartOptions.value.colors = [color]
}, { immediate: true })
const toPersianNumber = (num) => { const toPersianNumber = (num) => {
return num.toString().replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[d]) return num.toString().replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[d])
} }
@@ -0,0 +1,11 @@
<template>
<HomeDesktopView @logout="$emit('logout')" />
</template>
<script lang="ts" setup>
import HomeDesktopView from '../desktop/HomeDesktopView.vue'
defineEmits<{
logout: []
}>()
</script>
@@ -0,0 +1,12 @@
import type { HomeLayoutConfig } from '../types'
export const boursecuHomeLayoutConfig: HomeLayoutConfig = {
id: 'boursecu',
wrapperClass: 'dhome--boursecu',
sections: [
{ id: 'assets', visible: true },
{ id: 'currentPrice', visible: true },
{ id: 'priceChart', visible: true },
{ id: 'boursecuQuickActions', visible: true },
],
}
@@ -0,0 +1,40 @@
import type { HomeQuickActionItem } from '../types'
export const boursecuQuickActions: HomeQuickActionItem[] = [
{
id: 'buy',
label: 'خرید',
icon: 'vuesax-linear:money-receive',
route: { name: 'gold', query: { state: 'buy' } },
},
{
id: 'sell',
label: 'فروش',
icon: 'vuesax-linear:money-send',
route: { name: 'gold', query: { state: 'sell' } },
},
{
id: 'wallet',
label: 'کیف پول',
icon: 'vuesax-linear:wallet-1',
route: { name: 'wallet' },
},
{
id: 'transactions',
label: 'گزارش',
icon: 'vuesax-linear:receipt-item',
route: { name: 'transactions' },
},
{
id: 'fund',
label: 'دارایی ها',
icon: 'vuesax-linear:archive-1',
route: { name: 'fund' },
},
{
id: 'shop',
label: 'فروشگاه',
icon: 'vuesax-linear:coin',
route: { name: 'buyProduct' },
},
]
@@ -0,0 +1,12 @@
import type { HomeLayoutConfig } from '../types'
export const defaultHomeLayoutConfig: HomeLayoutConfig = {
id: 'default',
sections: [
{ id: 'currentPrice', visible: true },
{ id: 'assets', visible: true },
{ id: 'mainActions', visible: true },
{ id: 'priceChart', visible: true },
{ id: 'banners', visible: true },
],
}
@@ -87,8 +87,8 @@
transition: border-color 0.15s ease, color 0.15s ease; transition: border-color 0.15s ease, color 0.15s ease;
&:hover { &:hover {
border-color: @App-primary-color; border-color: @theme-primary;
color: @App-primary-color; color: @theme-primary;
} }
} }
} }
@@ -251,7 +251,7 @@
margin-top: 4px; margin-top: 4px;
border: none; border: none;
border-radius: 10px; border-radius: 10px;
background: @App-primary-color; background: @theme-primary;
color: #fff; color: #fff;
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
@@ -46,7 +46,7 @@
height: 56px; height: 56px;
border-radius: 14px; border-radius: 14px;
background: rgba(167, 43, 119, 0.08); background: rgba(167, 43, 119, 0.08);
color: @App-primary-color; color: @theme-primary;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -69,7 +69,7 @@
border: 1px solid rgba(167, 43, 119, 0.15); border: 1px solid rgba(167, 43, 119, 0.15);
border-radius: 12px; border-radius: 12px;
background: rgba(167, 43, 119, 0.03); background: rgba(167, 43, 119, 0.03);
color: @App-primary-color; color: @theme-primary;
&__title { &__title {
font-size: 16px; font-size: 16px;
@@ -44,8 +44,8 @@
} }
&--active { &--active {
background: @App-primary-color; background: @theme-primary;
border-color: @App-primary-color; border-color: @theme-primary;
color: #fff; color: #fff;
} }
} }
@@ -93,7 +93,7 @@
height: 64px; height: 64px;
border-radius: 16px; border-radius: 16px;
background: linear-gradient(135deg, #f0f4ff 0%, #e8eeff 100%); background: linear-gradient(135deg, #f0f4ff 0%, #e8eeff 100%);
color: @App-primary-color; color: @theme-primary;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -174,8 +174,8 @@
&--primary { &--primary {
width: 100%; width: 100%;
background: @App-primary-color; background: @theme-primary;
border-color: @App-primary-color; border-color: @theme-primary;
color: #fff; color: #fff;
&:hover { &:hover {
@@ -75,7 +75,7 @@
&--active { &--active {
background: #fff; background: #fff;
color: @App-primary-color; color: @theme-primary;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08); box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
} }
} }
@@ -182,8 +182,8 @@
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease; transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
&:hover { &:hover {
border-color: @App-primary-color; border-color: @theme-primary;
color: @App-primary-color; color: @theme-primary;
background: #fff; background: #fff;
} }
} }
@@ -255,13 +255,13 @@
transition: border-color 0.15s ease, color 0.15s ease; transition: border-color 0.15s ease, color 0.15s ease;
&:hover { &:hover {
border-color: @App-primary-color; border-color: @theme-primary;
color: @App-primary-color; color: @theme-primary;
} }
&--primary { &--primary {
background: @App-primary-color; background: @theme-primary;
border-color: @App-primary-color; border-color: @theme-primary;
color: #fff; color: #fff;
padding: 10px 16px; padding: 10px 16px;
+1 -1
View File
@@ -11,7 +11,7 @@ export default defineConfig({
], ],
theme: { theme: {
colors: { colors: {
"primary": "#a72b77", "primary": "var(--app-primary-color, #a72b77)",
"secondary": "#ffffff", "secondary": "#ffffff",
"tertiary": "#e0be7f" "tertiary": "#e0be7f"
} }