Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12382656e8 | ||
|
|
51e3c3f0cb | ||
|
|
e4d92831c2 | ||
|
|
3561e8ae07 | ||
|
|
ce8029919c |
Binary file not shown.
+16
-2
@@ -326,7 +326,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getVersionUrls() {
|
function getVersionUrls() {
|
||||||
return ['https://nerkh.gold/get_version', 'https://tablo.talasevin.ir/get_version', 'https://abshode-service.montgit.ir/server/get_version']
|
var protocol = getProtocol()
|
||||||
|
|
||||||
|
return [
|
||||||
|
'https://nerkh.gold/get_version',
|
||||||
|
'https://tablo.talasevin.ir/get_version',
|
||||||
|
'https://abshode-service.montgit.ir/server/get_version',
|
||||||
|
protocol + '//212.23.201.173:8381/get_version',
|
||||||
|
protocol + '//91.212.174.137:4040/server/get_version'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfigUrls() {
|
function getConfigUrls() {
|
||||||
@@ -453,6 +461,9 @@
|
|||||||
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url)
|
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url)
|
||||||
return version
|
return version
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (localStorage.getItem(VERSION_SUCCESS_URL_KEY) === url) {
|
||||||
|
localStorage.removeItem(VERSION_SUCCESS_URL_KEY)
|
||||||
|
}
|
||||||
console.warn('Version failed:', url, error)
|
console.warn('Version failed:', url, error)
|
||||||
return undefined
|
return undefined
|
||||||
} finally {
|
} finally {
|
||||||
@@ -496,6 +507,9 @@
|
|||||||
url: url
|
url: url
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (localStorage.getItem(CONFIG_SUCCESS_URL_KEY) === url) {
|
||||||
|
localStorage.removeItem(CONFIG_SUCCESS_URL_KEY)
|
||||||
|
}
|
||||||
console.warn('Config failed:', url, 'round:', round, error)
|
console.warn('Config failed:', url, 'round:', round, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -626,7 +640,7 @@
|
|||||||
renderRecovering('در حال دریافت تنظیمات جدید برنامه...')
|
renderRecovering('در حال دریافت تنظیمات جدید برنامه...')
|
||||||
|
|
||||||
var version = await loadRemoteVersion(5)
|
var version = await loadRemoteVersion(5)
|
||||||
var freshConfigResult = await fetchFreshConfig(2)
|
var freshConfigResult = await fetchFreshConfig(5)
|
||||||
|
|
||||||
clearReadableCookies()
|
clearReadableCookies()
|
||||||
// clearLocalStorageExceptToken()
|
// clearLocalStorageExceptToken()
|
||||||
|
|||||||
+21
-29
@@ -1,30 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="desktopConfig w-full right-0 left-0">
|
<div class="desktopConfig w-full right-0 left-0">
|
||||||
<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: primaryColor,
|
|
||||||
colorLink: primaryColor,
|
|
||||||
colorLinkActive: primaryColor,
|
|
||||||
colorLinkHover: primaryColor
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
>
|
>
|
||||||
<router-view v-slot="{ Component, route }">
|
<router-view v-slot="{ Component, route }">
|
||||||
<transition :name="route.meta.transition || 'fade'">
|
<transition :name="route.meta.transition || 'fade'">
|
||||||
<!--
|
<component :is="Component" :key="route.fullPath" class="app-main" />
|
||||||
Key only the top-level matched route (e.g. main vs login), not fullPath.
|
|
||||||
Using fullPath remounted the whole AppLayoutShell (sidebar/bottom nav)
|
|
||||||
on every page change and reloaded the menu logo each time.
|
|
||||||
-->
|
|
||||||
<component
|
|
||||||
:is="Component"
|
|
||||||
:key="route.matched[0]?.name ?? route.name"
|
|
||||||
class="app-main"
|
|
||||||
/>
|
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
</AConfigProvider>
|
</AConfigProvider>
|
||||||
@@ -77,11 +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 { getAppPrimaryColor } from '@/config/brand-theme'
|
|
||||||
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'
|
||||||
@@ -93,12 +75,10 @@ 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 { getAppLogoFullUrl } from '@/config/brand-assets'
|
|
||||||
|
|
||||||
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'
|
||||||
const logoFull = getAppLogoFullUrl()
|
|
||||||
|
|
||||||
const VersionUpdateModal = defineAsyncComponent(
|
const VersionUpdateModal = defineAsyncComponent(
|
||||||
() => import('@/modules/version/VersionUpdateModal.vue')
|
() => import('@/modules/version/VersionUpdateModal.vue')
|
||||||
@@ -112,7 +92,19 @@ const indicator = h(LoadingOutlined, {
|
|||||||
})
|
})
|
||||||
const displayVersion = getDisplayAppVersion(pkg.version)
|
const displayVersion = getDisplayAppVersion(pkg.version)
|
||||||
const showSplash = ref(true)
|
const showSplash = ref(true)
|
||||||
const primaryColor = getAppPrimaryColor(colors.primary || '#000000')
|
|
||||||
|
const antDesignTheme = computed(() => {
|
||||||
|
const primary = getProjectBranding().accentColor
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: {
|
||||||
|
colorPrimary: primary,
|
||||||
|
colorLink: primary,
|
||||||
|
colorLinkActive: primary,
|
||||||
|
colorLinkHover: primary,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
useApplyDesktopLayoutClass()
|
useApplyDesktopLayoutClass()
|
||||||
|
|
||||||
@@ -155,7 +147,7 @@ const supportAccentColor = computed(() => {
|
|||||||
case 'soroush':
|
case 'soroush':
|
||||||
return '#2563eb'
|
return '#2563eb'
|
||||||
default:
|
default:
|
||||||
return primaryColor || '#334155'
|
return getProjectBranding().accentColor
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -195,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: var(--app-primary-color, #a72b77) !important;
|
color: @theme-primary !important;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-70
@@ -2,7 +2,6 @@ import { computed, watch } from 'vue'
|
|||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { authenticateWithFingerprint, getToken, isIphone, removeToken } from '@/utils'
|
import { authenticateWithFingerprint, getToken, isIphone, removeToken } from '@/utils'
|
||||||
import { useUserStore } from '@/store/userStore'
|
import { useUserStore } from '@/store/userStore'
|
||||||
import { useWalletStore } from '@/store/walletStore'
|
|
||||||
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
|
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
import {
|
import {
|
||||||
@@ -14,48 +13,10 @@ import {
|
|||||||
setHasFingerPrint
|
setHasFingerPrint
|
||||||
} from '@/app/authBoot'
|
} from '@/app/authBoot'
|
||||||
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
|
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
|
||||||
import {
|
import { getProjectDefaultRoute, isGhatreProject } from '@/utils/project'
|
||||||
getDefaultLandingRoute,
|
|
||||||
hasAbshodeTrading,
|
|
||||||
hasHomePage,
|
|
||||||
hasProductShop,
|
|
||||||
} from '@/config/project-profile'
|
|
||||||
|
|
||||||
const GUEST_PUBLIC_ROUTES = ['login', '404', 'about', 'cart'] as const
|
|
||||||
|
|
||||||
function getGuestPublicRoutes(): string[] {
|
|
||||||
const routes: string[] = [...GUEST_PUBLIC_ROUTES]
|
|
||||||
if (hasProductShop()) {
|
|
||||||
routes.push('buyProduct', 'productDetail')
|
|
||||||
}
|
|
||||||
return routes
|
|
||||||
}
|
|
||||||
|
|
||||||
function enforceProjectRouteAccess(
|
|
||||||
to: RouteLocationNormalized,
|
|
||||||
next: NavigationGuardNext
|
|
||||||
): boolean {
|
|
||||||
if (!hasHomePage() && to.name === 'home') {
|
|
||||||
next({ name: getDefaultLandingRoute() })
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasAbshodeTrading() && to.name === 'gold') {
|
|
||||||
next({ name: getDefaultLandingRoute() })
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasProductShop() && (to.name === 'buyProduct' || to.name === 'productDetail')) {
|
|
||||||
next({ name: getDefaultLandingRoute() })
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setupRouterGuards() {
|
export function setupRouterGuards() {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const walletStore = useWalletStore()
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const isApple: boolean = isIphone()
|
const isApple: boolean = isIphone()
|
||||||
@@ -68,20 +29,13 @@ export function setupRouterGuards() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks: Promise<unknown>[] = [
|
|
||||||
walletStore.getLimitedPrice(),
|
|
||||||
walletStore.getLimitedBuyGold(),
|
|
||||||
]
|
|
||||||
|
|
||||||
if (!userStore.userInfo) {
|
if (!userStore.userInfo) {
|
||||||
tasks.push(userStore.getMyInfo())
|
await userStore.getMyInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(tasks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) {
|
function goToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||||
if (getGuestPublicRoutes().includes(to.name as string)) {
|
if (to.name === 'buyProduct' || to.name === 'productDetail' || to.name === 'about' || to.name === 'cart') {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
if (to.name !== 'login') {
|
if (to.name !== 'login') {
|
||||||
@@ -90,23 +44,22 @@ export function setupRouterGuards() {
|
|||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToLanding(to: RouteLocationNormalized, next: NavigationGuardNext) {
|
function goToHome(to: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||||
const landing = getDefaultLandingRoute()
|
const defaultRoute = getProjectDefaultRoute()
|
||||||
if (to.name !== landing) {
|
if (to.name !== defaultRoute) {
|
||||||
return next({ name: landing })
|
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)
|
||||||
|
|
||||||
if (enforceProjectRouteAccess(to, next)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFirst) {
|
if (isFirst) {
|
||||||
isFirst = false
|
isFirst = false
|
||||||
|
|
||||||
@@ -126,15 +79,12 @@ export function setupRouterGuards() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (to.name == 'login') {
|
if (to.name == 'login') {
|
||||||
return goToLanding(to, next)
|
goToHome(to, next)
|
||||||
}
|
|
||||||
if (enforceProjectRouteAccess(to, next)) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (getGuestPublicRoutes().includes(to.name as string)) {
|
if (['login', '404', 'buyProduct', 'productDetail', 'about', 'cart'].includes(to.name as string)) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
goToLogin(to, next)
|
goToLogin(to, next)
|
||||||
@@ -143,24 +93,18 @@ export function setupRouterGuards() {
|
|||||||
if (getFingerPosition()) {
|
if (getFingerPosition()) {
|
||||||
if (getToken()) {
|
if (getToken()) {
|
||||||
if (to.name === 'login') {
|
if (to.name === 'login') {
|
||||||
return next({ name: (from.name as string) || getDefaultLandingRoute() })
|
return next({ name: from.name || getProjectDefaultRoute() })
|
||||||
}
|
|
||||||
if (enforceProjectRouteAccess(to, next)) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getGuestPublicRoutes().includes(to.name as string)) {
|
if (['login', '404', 'buyProduct', 'productDetail', 'about', 'cart'].includes(to.name as string)) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
return next({ name: 'login' })
|
return next({ name: 'login' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getToken() && !getHasFingerPrint()) {
|
if (getToken() && !getHasFingerPrint()) {
|
||||||
if (enforceProjectRouteAccess(to, next)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
goToLogin(to, next)
|
goToLogin(to, next)
|
||||||
|
|||||||
+14
-33
@@ -62,11 +62,7 @@ body {
|
|||||||
:root {
|
:root {
|
||||||
color-scheme: only light;
|
color-scheme: only light;
|
||||||
--app-primary-color: #a72b77;
|
--app-primary-color: #a72b77;
|
||||||
--app-secondary-color: #a72b77;
|
--project-hero-gradient: linear-gradient(145deg, #a72b77 0%, #711d4d 40%, #a72b77 100%);
|
||||||
--app-tertiary-color: #e0be7f;
|
|
||||||
--app-background-color: #ffffff;
|
|
||||||
--app-primary-dark: #711d4d;
|
|
||||||
--app-primary-light: #c45f9a;
|
|
||||||
--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;
|
||||||
@@ -84,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;
|
||||||
|
|
||||||
@@ -372,45 +378,20 @@ body {
|
|||||||
}
|
}
|
||||||
.ant-input-group-addon{
|
.ant-input-group-addon{
|
||||||
background-color: var(--app-primary-color, #a72b77) !important;
|
background-color: var(--app-primary-color, #a72b77) !important;
|
||||||
color: var(--app-tertiary-color, #e0be7f) !important;
|
color: #e0be7f !important;
|
||||||
}
|
}
|
||||||
.ant-slider-track{
|
.ant-slider-track{
|
||||||
background-color: var(--app-primary-color, #a72b77) !important;
|
background-color: var(--app-primary-color, #a72b77) !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
.ant-radio-checked .ant-radio-inner,.ant-radio-group-solid .ant-radio-button-wrapper-checked {
|
|
||||||
border-color: var(--app-primary-color)!important;
|
|
||||||
background-color: var(--app-primary-color)!important
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover:before {
|
|
||||||
background-color: var(--app-primary-color)!important
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-slider-handle {
|
.ant-slider-handle {
|
||||||
background-color: var(--app-primary-color, #a72b77) !important;
|
background-color: var(--app-primary-color, #a72b77) !important;
|
||||||
border-color: var(--app-tertiary-color, #e0be7f) !important;
|
|
||||||
}
|
|
||||||
.ant-btn-primary{
|
|
||||||
background-color: var(--app-primary-color, #a72b77) !important;
|
|
||||||
color: var(--app-tertiary-color, #ffffff) !important;
|
|
||||||
border-color: var(--app-primary-color, #a72b77) !important;;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-drawer-body{
|
.ant-drawer-body{
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
}
|
}
|
||||||
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{
|
.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
|
||||||
.ant-spin{
|
|
||||||
color: var(--app-tertiary-color, #000000) !important;
|
|
||||||
}
|
|
||||||
.ant-spin-dot-item{
|
|
||||||
background-color: var(--app-tertiary-color, #a72b77) !important;
|
|
||||||
|
|
||||||
}
|
|
||||||
.ant-btn:hover{
|
|
||||||
color: var(--app-primary-color, #a72b77) !important;
|
|
||||||
border-color: var(--app-primary-color, #a72b77) !important;;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
@prefixCls: 'noname';
|
@prefixCls: 'noname';
|
||||||
|
|
||||||
/* مقادیر ثابت برای Ant Design Less (fade و توابع رنگ). تم واقعی در runtime با --app-primary-color ست میشود. */
|
|
||||||
@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;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<AButton :disabled="props.disabledB" :type="props.type" :size="props.size" :class="[prefixCls, _class]" :loading="props.loading">
|
<AButton :disabled="props.disabledB" :type="props.type" :size="props.size" :class="[prefixCls, _class]" :loading="props.loading">
|
||||||
<!-- py-1 px-2-->
|
<!-- py-1 px-2-->
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-1 justify-center flex-row-reverse gap-2 px-2"
|
class="flex items-center gap-1 justify-center flex-row-reverse gap-2 py-1 px-2"
|
||||||
:class="[{ 'flex-row-reverse': suffixIcon }]"
|
:class="[{ 'flex-row-reverse': suffixIcon }]"
|
||||||
>
|
>
|
||||||
<slot v-if="$slots.default"></slot>
|
<slot v-if="$slots.default"></slot>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
import icons from '@/components/BasicPacks/tools/icons/icons.json'
|
import icons from '@/components/BasicPacks/tools/icons/icons.json'
|
||||||
import {usePrefix} from "@/utils/usePrefix";
|
import {usePrefix} from "@/utils/usePrefix";
|
||||||
import {computed, onMounted, ref, withDefaults, defineProps} from "vue";
|
import {computed, onMounted, ref, withDefaults, defineProps} from "vue";
|
||||||
import { getAppPrimaryColor } from '@/config/brand-theme'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
icon: string
|
icon: string
|
||||||
@@ -29,11 +28,6 @@ const tempIcon = ref<string>()
|
|||||||
const refIcon = ref<HTMLDivElement>()
|
const refIcon = ref<HTMLDivElement>()
|
||||||
tempIcon.value = icons[`${props.icon}`] as string
|
tempIcon.value = icons[`${props.icon}`] as string
|
||||||
const color = computed(() => {
|
const color = computed(() => {
|
||||||
if (!props.color) return props.color
|
|
||||||
const raw = String(props.color).trim()
|
|
||||||
if (raw.includes('--app-primary-color') || raw === 'primary') {
|
|
||||||
return getAppPrimaryColor()
|
|
||||||
}
|
|
||||||
return props.color
|
return props.color
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import {usePrefix} from "@/utils/usePrefix";
|
|||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import {usePrice} from "@/components/BasicPacks/BasicPriceInput/src/utils/usePrice";
|
import {usePrice} from "@/components/BasicPacks/BasicPriceInput/src/utils/usePrice";
|
||||||
import {persianToEnglishDigits, shouldNormalizeInputDigits} from '@/utils/persianDigits';
|
import {persianToEnglishDigits, shouldNormalizeInputDigits} from '@/utils/persianDigits';
|
||||||
import { getAppPrimaryColor } from '@/config/brand-theme'
|
|
||||||
|
|
||||||
//Props
|
//Props
|
||||||
interface Prop {
|
interface Prop {
|
||||||
@@ -56,22 +55,12 @@ interface Prop {
|
|||||||
placeHolder?: String
|
placeHolder?: String
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveCssColor(value?: String | string | null) {
|
const addonBgColor = computed(() => props.addonBgColor)
|
||||||
if (!value) return value
|
const addonTextColor = computed(() => props.addonTextColor)
|
||||||
const raw = String(value).trim()
|
|
||||||
if (raw.includes('--app-primary-color') || raw === 'primary') {
|
|
||||||
return getAppPrimaryColor()
|
|
||||||
}
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Prop>(), {
|
const props = withDefaults(defineProps<Prop>(), {
|
||||||
isPassword: false
|
isPassword: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const addonBgColorResolved = computed(() => resolveCssColor(props.addonBgColor) || getAppPrimaryColor())
|
|
||||||
const addonTextColorResolved = computed(() => resolveCssColor(props.addonTextColor) || 'white')
|
|
||||||
|
|
||||||
const emits = defineEmits(['update:value', 'onchange'])
|
const emits = defineEmits(['update:value', 'onchange'])
|
||||||
|
|
||||||
const localValue = computed({
|
const localValue = computed({
|
||||||
@@ -129,8 +118,8 @@ const {prefixCls} = usePrefix('input')
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-group-addon {
|
.ant-input-group-addon {
|
||||||
background-color: v-bind(addonBgColorResolved);
|
background-color: v-bind(addonBgColor);
|
||||||
color: v-bind(addonTextColorResolved);
|
color: v-bind(addonTextColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,17 +1 @@
|
|||||||
export function usePrice(price: number, digit = true, unit = 'ریال') {
|
export { usePrice, usePriceWithoutType, useTowDecimal, useFormat } from '@/utils/usePrice'
|
||||||
let _price = price.toString()
|
|
||||||
if (digit) {
|
|
||||||
_price = _price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
||||||
}
|
|
||||||
return `${_price} ${unit}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTowDecimal(num: string | number): string | number {
|
|
||||||
const temp = num.toString().match(/^-?\d+(?:\.\d{0,2})?/)
|
|
||||||
if (temp && temp.length > 0) return temp[0]
|
|
||||||
return Math.round(+num * 100) / 100
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFormat(num: string | number, unit = '') {
|
|
||||||
return usePrice(Math.abs(+useTowDecimal(num)), true, unit)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div>
|
<div>
|
||||||
{{ props.title }}
|
{{ props.title }}
|
||||||
@@ -105,7 +105,7 @@ const handleChange = (e: any) => {
|
|||||||
//padding: 5px;
|
//padding: 5px;
|
||||||
/* .ant-select {
|
/* .ant-select {
|
||||||
.ant-select-selector {
|
.ant-select-selector {
|
||||||
border-color: var(--app-primary-color, #a72b77);
|
border-color: @primary-color;
|
||||||
border-radius: @border-radius-base;
|
border-radius: @border-radius-base;
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,45 @@
|
|||||||
import { getConfig } from '@/config/app-config'
|
import { getConfig } from '@/config/app-config'
|
||||||
import { getAppIconUrl, resolveConfigAssetUrl } from '@/config/brand-assets'
|
|
||||||
import { applyBrandTheme } from '@/config/brand-theme'
|
|
||||||
import { isPwaManifestReady } from '@/modules/pwa/manifest'
|
import { isPwaManifestReady } from '@/modules/pwa/manifest'
|
||||||
|
import { getProjectBranding } from '@/utils/project'
|
||||||
|
|
||||||
export function applyAppConfig() {
|
export function applyAppConfig() {
|
||||||
applyBrandTheme()
|
applyProjectTheme()
|
||||||
|
|
||||||
if (isPwaManifestReady()) return
|
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', 'در حال بارگذاری...')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLogoUrl() {
|
||||||
|
return getConfig<string>('avatar') || '/favicon.png'
|
||||||
|
}
|
||||||
|
|
||||||
function setFavicon() {
|
function setFavicon() {
|
||||||
let faviconLink =
|
let faviconLink =
|
||||||
document.querySelector<HTMLLinkElement>("link[rel='icon']") ||
|
document.querySelector<HTMLLinkElement>("link[rel='icon']") ||
|
||||||
@@ -28,14 +51,14 @@ function setFavicon() {
|
|||||||
document.head.appendChild(faviconLink)
|
document.head.appendChild(faviconLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
const logoUrl = getAppIconUrl()
|
const logoUrl = getLogoUrl()
|
||||||
|
|
||||||
faviconLink.type = 'image/png'
|
faviconLink.type = 'image/png'
|
||||||
faviconLink.href = logoUrl
|
faviconLink.href = logoUrl.startsWith('http') ? logoUrl : resolveRemoteLogo(logoUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAppleTouchIconFallback() {
|
function setAppleTouchIconFallback() {
|
||||||
const logoUrl = getAppIconUrl()
|
const logoUrl = getLogoUrl()
|
||||||
|
|
||||||
if (!logoUrl) return
|
if (!logoUrl) return
|
||||||
|
|
||||||
@@ -57,7 +80,7 @@ function setAppleTouchIconFallback() {
|
|||||||
document.head.appendChild(link)
|
document.head.appendChild(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
link.href = logoUrl
|
link.href = logoUrl.startsWith('http') ? logoUrl : resolveRemoteLogo(logoUrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
let appleTitle = document.querySelector<HTMLMetaElement>("meta[name='apple-mobile-web-app-title']")
|
let appleTitle = document.querySelector<HTMLMetaElement>("meta[name='apple-mobile-web-app-title']")
|
||||||
@@ -71,4 +94,13 @@ function setAppleTouchIconFallback() {
|
|||||||
appleTitle.content = getConfig('name_fa', 'اپ مشتری')
|
appleTitle.content = getConfig('name_fa', 'اپ مشتری')
|
||||||
}
|
}
|
||||||
|
|
||||||
export { resolveConfigAssetUrl }
|
function resolveRemoteLogo(path: string) {
|
||||||
|
if (/^(https?:\/\/|data:|blob:)/i.test(path)) return path
|
||||||
|
|
||||||
|
const lastSuccess = localStorage.getItem('CONFIG_Success_URL')
|
||||||
|
const base = lastSuccess
|
||||||
|
? lastSuccess.replace(/\/get_user\/?$/i, '').replace(/\/+$/, '')
|
||||||
|
: 'https://nerkh.gold'
|
||||||
|
|
||||||
|
return `${base}/${path.replace(/^\/+/, '')}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import { getConfig } from '@/config/app-config'
|
|
||||||
|
|
||||||
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
|
|
||||||
|
|
||||||
const DEFAULT_ICON = '/favicon.png'
|
|
||||||
const DEFAULT_LOGO = '/logo.png'
|
|
||||||
const DEFAULT_LOGO_FULL = '/logo_full_.png'
|
|
||||||
|
|
||||||
export function resolveConfigAssetUrl(path?: string | null): string {
|
|
||||||
if (!path) return ''
|
|
||||||
|
|
||||||
if (/^(https?:\/\/|data:|blob:)/i.test(path)) return path
|
|
||||||
|
|
||||||
const lastSuccess = localStorage.getItem(CONFIG_SUCCESS_URL_KEY)
|
|
||||||
const base = lastSuccess
|
|
||||||
? lastSuccess.replace(/\/get_user\/?$/i, '').replace(/\/+$/, '')
|
|
||||||
: 'https://nerkh.gold'
|
|
||||||
|
|
||||||
return `${base}/${path.replace(/^\/+/, '')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickConfigAsset(...keys: string[]): string {
|
|
||||||
for (const key of keys) {
|
|
||||||
const value = getConfig<string>(key)
|
|
||||||
if (value) return resolveConfigAssetUrl(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/** آیکون کوچک اپ — معمولاً فیلد avatar در get_user */
|
|
||||||
export function getAppIconUrl(fallback = DEFAULT_ICON): string {
|
|
||||||
return pickConfigAsset('avatar', 'icon') || fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
/** لوگوی فشرده — برای هدر و FAB */
|
|
||||||
export function getAppLogoUrl(fallback = DEFAULT_LOGO): string {
|
|
||||||
return pickConfigAsset('logo', 'avatar') || fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
/** لوگوی کامل — برای اسپلش، لاگین و سایدبار */
|
|
||||||
export function getAppLogoFullUrl(fallback = DEFAULT_LOGO_FULL): string {
|
|
||||||
return pickConfigAsset('logo_full', 'logo', 'avatar') || fallback
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import { getConfig } from '@/config/app-config'
|
|
||||||
import { colors } from '@/themeConfig'
|
|
||||||
|
|
||||||
export const DEFAULT_PRIMARY_COLOR = '#a72b77'
|
|
||||||
export const DEFAULT_SECONDARY_COLOR = '#a72b77'
|
|
||||||
export const DEFAULT_TERTIARY_COLOR = '#e0be7f'
|
|
||||||
export const DEFAULT_BACKGROUND_COLOR = '#ffffff'
|
|
||||||
|
|
||||||
function normalizeHex(value: unknown): string | null {
|
|
||||||
if (typeof value !== 'string') return null
|
|
||||||
|
|
||||||
const trimmed = value.trim()
|
|
||||||
if (!trimmed) return null
|
|
||||||
|
|
||||||
const withHash = trimmed.startsWith('#') ? trimmed : `#${trimmed}`
|
|
||||||
|
|
||||||
if (!/^#[0-9a-fA-F]{6}$/.test(withHash) && !/^#[0-9a-fA-F]{3}$/.test(withHash)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (withHash.length === 4) {
|
|
||||||
const [, r, g, b] = withHash
|
|
||||||
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
return withHash.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
function lightenHex(hex: string, amount = 0.22): string {
|
|
||||||
const normalized = normalizeHex(hex)
|
|
||||||
if (!normalized) return hex
|
|
||||||
|
|
||||||
const num = parseInt(normalized.slice(1), 16)
|
|
||||||
const r = (num >> 16) & 255
|
|
||||||
const g = (num >> 8) & 255
|
|
||||||
const b = num & 255
|
|
||||||
|
|
||||||
const mix = (channel: number) => Math.round(channel + (255 - channel) * amount)
|
|
||||||
|
|
||||||
return `#${[mix(r), mix(g), mix(b)]
|
|
||||||
.map((channel) => channel.toString(16).padStart(2, '0'))
|
|
||||||
.join('')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function darkenHex(hex: string, amount = 0.22): string {
|
|
||||||
const normalized = normalizeHex(hex)
|
|
||||||
if (!normalized) return hex
|
|
||||||
|
|
||||||
const num = parseInt(normalized.slice(1), 16)
|
|
||||||
const r = Math.round(((num >> 16) & 255) * (1 - amount))
|
|
||||||
const g = Math.round(((num >> 8) & 255) * (1 - amount))
|
|
||||||
const b = Math.round((num & 255) * (1 - amount))
|
|
||||||
|
|
||||||
return `#${[r, g, b].map((channel) => channel.toString(16).padStart(2, '0')).join('')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAppPrimaryColor(fallback = DEFAULT_PRIMARY_COLOR): string {
|
|
||||||
return (
|
|
||||||
normalizeHex(getConfig('color')) ||
|
|
||||||
normalizeHex(getConfig('primary_color')) ||
|
|
||||||
normalizeHex(getConfig('primary')) ||
|
|
||||||
fallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAppSecondaryColor(fallback = DEFAULT_SECONDARY_COLOR): string {
|
|
||||||
return (
|
|
||||||
normalizeHex(getConfig('secondary_color')) ||
|
|
||||||
normalizeHex(getConfig('secondary')) ||
|
|
||||||
getAppPrimaryColor(fallback)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAppTertiaryColor(fallback = DEFAULT_TERTIARY_COLOR): string {
|
|
||||||
return (
|
|
||||||
normalizeHex(getConfig('tertiary_color')) ||
|
|
||||||
normalizeHex(getConfig('tertiary')) ||
|
|
||||||
fallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAppBackgroundColor(fallback = DEFAULT_BACKGROUND_COLOR): string {
|
|
||||||
return (
|
|
||||||
normalizeHex(getConfig('background_color')) ||
|
|
||||||
normalizeHex(getConfig('background')) ||
|
|
||||||
fallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAppPrimaryDark(fallback = '#711d4d'): string {
|
|
||||||
return darkenHex(getAppPrimaryColor(), 0.22) || fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAppPrimaryLight(fallback = '#c45f9a'): string {
|
|
||||||
return lightenHex(getAppPrimaryColor(), 0.22) || fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCssVar(name: string, value: string) {
|
|
||||||
document.documentElement.style.setProperty(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyBrandTheme() {
|
|
||||||
const primary = getAppPrimaryColor()
|
|
||||||
const secondary = getAppSecondaryColor()
|
|
||||||
const tertiary = getAppTertiaryColor()
|
|
||||||
const background = getAppBackgroundColor()
|
|
||||||
const primaryDark = getAppPrimaryDark()
|
|
||||||
const primaryLight = getAppPrimaryLight()
|
|
||||||
|
|
||||||
setCssVar('--app-primary-color', primary)
|
|
||||||
setCssVar('--app-secondary-color', secondary)
|
|
||||||
setCssVar('--app-tertiary-color', tertiary)
|
|
||||||
setCssVar('--app-background-color', background)
|
|
||||||
setCssVar('--app-primary-dark', primaryDark)
|
|
||||||
setCssVar('--app-primary-light', primaryLight)
|
|
||||||
|
|
||||||
colors.primary = primary
|
|
||||||
colors['primary-rgba'] = primary
|
|
||||||
colors.secondary = secondary
|
|
||||||
colors['secondary-rgba'] = secondary
|
|
||||||
colors.tertiary = tertiary
|
|
||||||
colors['tertiary-rgba'] = tertiary
|
|
||||||
|
|
||||||
const themeMeta = document.querySelector<HTMLMetaElement>('meta[name="theme-color"]')
|
|
||||||
if (themeMeta) {
|
|
||||||
themeMeta.content = primary
|
|
||||||
} else {
|
|
||||||
const meta = document.createElement('meta')
|
|
||||||
meta.name = 'theme-color'
|
|
||||||
meta.content = primary
|
|
||||||
document.head.appendChild(meta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+31
-16
@@ -2,6 +2,7 @@ import type { AppConfig } from '@/config/types'
|
|||||||
import { readCachedAppConfig, writeCachedAppConfig } from '@/config/config-cache'
|
import { readCachedAppConfig, writeCachedAppConfig } from '@/config/config-cache'
|
||||||
|
|
||||||
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
|
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
|
||||||
|
export const CONFIG_RETRY_ROUNDS = 5
|
||||||
|
|
||||||
function getProtocol() {
|
function getProtocol() {
|
||||||
return window.location.protocol === 'https:' ? 'https:' : 'http:'
|
return window.location.protocol === 'https:' ? 'https:' : 'http:'
|
||||||
@@ -29,6 +30,12 @@ export function sortConfigUrls(urls: string[]) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearStaleConfigSuccessUrl(url: string) {
|
||||||
|
if (localStorage.getItem(CONFIG_SUCCESS_URL_KEY) === url) {
|
||||||
|
localStorage.removeItem(CONFIG_SUCCESS_URL_KEY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchConfig(url: string, timeout = 3000): Promise<AppConfig> {
|
async function fetchConfig(url: string, timeout = 3000): Promise<AppConfig> {
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const timeoutId = window.setTimeout(() => controller.abort(), timeout)
|
const timeoutId = window.setTimeout(() => controller.abort(), timeout)
|
||||||
@@ -59,22 +66,25 @@ async function fetchConfig(url: string, timeout = 3000): Promise<AppConfig> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchFreshAppConfig(): Promise<AppConfig> {
|
export async function fetchFreshAppConfig(rounds = CONFIG_RETRY_ROUNDS): Promise<AppConfig> {
|
||||||
const urls = sortConfigUrls(getConfigUrls())
|
const urls = sortConfigUrls(getConfigUrls())
|
||||||
|
|
||||||
for (let index = 0; index < urls.length; index++) {
|
for (let round = 1; round <= rounds; round++) {
|
||||||
const url = urls[index]
|
for (let index = 0; index < urls.length; index++) {
|
||||||
|
const url = urls[index]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const timeout = index === urls.length - 1 ? 5000 : 2500
|
const timeout = index === urls.length - 1 ? 7000 : 4000
|
||||||
const config = await fetchConfig(url, timeout)
|
const config = await fetchConfig(url, timeout)
|
||||||
|
|
||||||
localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url)
|
localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url)
|
||||||
writeCachedAppConfig(config)
|
writeCachedAppConfig(config)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Config failed from ${url}`, error)
|
clearStaleConfigSuccessUrl(url)
|
||||||
|
console.warn(`Config failed from ${url}. Round: ${round}`, error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,11 +92,16 @@ export async function fetchFreshAppConfig(): Promise<AppConfig> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,24 @@ type VersionResponse =
|
|||||||
}
|
}
|
||||||
|
|
||||||
const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL'
|
const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL'
|
||||||
const VERSION_RETRY_ROUNDS = 5
|
export const VERSION_RETRY_ROUNDS = 5
|
||||||
|
|
||||||
let versionFetchPromise: Promise<string | null> | null = null
|
let versionFetchPromise: Promise<string | null> | null = null
|
||||||
let cachedVersionResult: string | null | undefined
|
let cachedVersionResult: string | null | undefined
|
||||||
|
|
||||||
|
function getProtocol() {
|
||||||
|
return window.location.protocol === 'https:' ? 'https:' : 'http:'
|
||||||
|
}
|
||||||
|
|
||||||
function getVersionUrls() {
|
function getVersionUrls() {
|
||||||
|
const protocol = getProtocol()
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'https://nerkh.gold/get_version',
|
'https://nerkh.gold/get_version',
|
||||||
'https://tablo.talasevin.ir/get_version',
|
'https://tablo.talasevin.ir/get_version',
|
||||||
'https://abshode-service.montgit.ir/server/get_version'
|
'https://abshode-service.montgit.ir/server/get_version',
|
||||||
|
`${protocol}//212.23.201.173:8381/get_version`,
|
||||||
|
`${protocol}//91.212.174.137:4040/server/get_version`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +41,12 @@ function sortVersionUrls(urls: string[]) {
|
|||||||
return [lastSuccessUrl, ...others]
|
return [lastSuccessUrl, ...others]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearStaleVersionSuccessUrl(url: string) {
|
||||||
|
if (localStorage.getItem(VERSION_SUCCESS_URL_KEY) === url) {
|
||||||
|
localStorage.removeItem(VERSION_SUCCESS_URL_KEY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function readUserVersion(user: unknown) {
|
function readUserVersion(user: unknown) {
|
||||||
if (!user || typeof user !== 'object' || Array.isArray(user)) {
|
if (!user || typeof user !== 'object' || Array.isArray(user)) {
|
||||||
return ''
|
return ''
|
||||||
@@ -82,7 +96,6 @@ async function fetchVersion(url: string, timeout = 3500): Promise<string | null>
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// Non-2xx/timeout should be treated as "request failed" and we may try the next URL.
|
|
||||||
throw new Error(`Version request failed with status ${response.status}`)
|
throw new Error(`Version request failed with status ${response.status}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,10 +107,8 @@ async function fetchVersion(url: string, timeout = 3500): Promise<string | null>
|
|||||||
|
|
||||||
const version = normalizeVersionResponse(data)
|
const version = normalizeVersionResponse(data)
|
||||||
|
|
||||||
// IMPORTANT: if the request returned 200/ok, we stop here even if we couldn't extract a version.
|
|
||||||
// This prevents unnecessary looping when the endpoint is "reachable" but returns an empty payload.
|
|
||||||
if (!version) {
|
if (!version) {
|
||||||
return null
|
throw new Error('Version response is empty')
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url)
|
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url)
|
||||||
@@ -118,10 +129,11 @@ async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise<str
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const version = await fetchVersion(url, timeout)
|
const version = await fetchVersion(url, timeout)
|
||||||
// fetchVersion only throws on real request failures (non-ok / timeout).
|
if (version) {
|
||||||
// If it returns null, we got HTTP 200 but empty version => stop immediately.
|
return version
|
||||||
return version
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
clearStaleVersionSuccessUrl(url)
|
||||||
console.warn(`Version failed from ${url}. Round: ${round}`, error)
|
console.warn(`Version failed from ${url}. Round: ${round}`, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
import { getConfig } from '@/config/app-config'
|
|
||||||
|
|
||||||
export type ProjectProfileId = 'ghatre' | 'kashkool' | 'boursecu' | 'default'
|
|
||||||
|
|
||||||
export interface ProjectFeatures {
|
|
||||||
id: ProjectProfileId
|
|
||||||
hasHomePage: boolean
|
|
||||||
hasAbshodeTrading: boolean
|
|
||||||
hasProductShop: boolean
|
|
||||||
landingRoute: 'home' | 'buyProduct'
|
|
||||||
}
|
|
||||||
|
|
||||||
const PROFILE_FEATURES: Record<ProjectProfileId, ProjectFeatures> = {
|
|
||||||
ghatre: {
|
|
||||||
id: 'ghatre',
|
|
||||||
hasHomePage: false,
|
|
||||||
hasAbshodeTrading: false,
|
|
||||||
hasProductShop: true,
|
|
||||||
landingRoute: 'buyProduct',
|
|
||||||
},
|
|
||||||
kashkool: {
|
|
||||||
id: 'kashkool',
|
|
||||||
hasHomePage: true,
|
|
||||||
hasAbshodeTrading: true,
|
|
||||||
hasProductShop: true,
|
|
||||||
landingRoute: 'home',
|
|
||||||
},
|
|
||||||
boursecu: {
|
|
||||||
id: 'boursecu',
|
|
||||||
hasHomePage: true,
|
|
||||||
hasAbshodeTrading: true,
|
|
||||||
hasProductShop: true,
|
|
||||||
landingRoute: 'home',
|
|
||||||
},
|
|
||||||
default: {
|
|
||||||
id: 'default',
|
|
||||||
hasHomePage: true,
|
|
||||||
hasAbshodeTrading: true,
|
|
||||||
hasProductShop: true,
|
|
||||||
landingRoute: 'home',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeSlug(value: unknown): string {
|
|
||||||
return String(value ?? '').toLowerCase().trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesAny(haystack: string, needles: string[]): boolean {
|
|
||||||
return needles.some((needle) => haystack.includes(needle))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveProjectProfileId(): ProjectProfileId {
|
|
||||||
const name = normalizeSlug(getConfig('name'))
|
|
||||||
const nameFa = String(getConfig('name_fa') ?? '')
|
|
||||||
const mode = normalizeSlug(getConfig('mode'))
|
|
||||||
|
|
||||||
if (
|
|
||||||
matchesAny(name, ['ghatre', 'ghatreh', 'qatre']) ||
|
|
||||||
nameFa.includes('قطره')
|
|
||||||
) {
|
|
||||||
return 'ghatre'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
matchesAny(name, ['boursecu', 'bourse', 'bours']) ||
|
|
||||||
nameFa.includes('بورس')
|
|
||||||
) {
|
|
||||||
return 'boursecu'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
matchesAny(name, ['kashkool', 'kashkol', 'kashkoolgold']) ||
|
|
||||||
nameFa.includes('کشکول')
|
|
||||||
) {
|
|
||||||
return 'kashkool'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchesAny(mode, ['ghatre', 'ghatreh', 'qatre'])) return 'ghatre'
|
|
||||||
if (matchesAny(mode, ['boursecu', 'bourse'])) return 'boursecu'
|
|
||||||
if (matchesAny(mode, ['kashkool', 'kashkol'])) return 'kashkool'
|
|
||||||
|
|
||||||
return 'default'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getProjectProfile(): ProjectFeatures {
|
|
||||||
return PROFILE_FEATURES[resolveProjectProfileId()]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getProjectProfileId(): ProjectProfileId {
|
|
||||||
return resolveProjectProfileId()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getProjectDisplayName(fallback = 'اپ مشتری'): string {
|
|
||||||
return getConfig('name_fa') || getConfig('name') || fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDefaultLandingRoute(): 'home' | 'buyProduct' {
|
|
||||||
return getProjectProfile().landingRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasHomePage(): boolean {
|
|
||||||
return getProjectProfile().hasHomePage
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasAbshodeTrading(): boolean {
|
|
||||||
return getProjectProfile().hasAbshodeTrading
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasProductShop(): boolean {
|
|
||||||
return getProjectProfile().hasProductShop
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isGhatreProject(): boolean {
|
|
||||||
return resolveProjectProfileId() === 'ghatre'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isKashkoolProject(): boolean {
|
|
||||||
return resolveProjectProfileId() === 'kashkool'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isBoursecuProject(): boolean {
|
|
||||||
return resolveProjectProfileId() === 'boursecu'
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
import type { AppNavItem } from '../types'
|
import type { AppNavItem } from '../types'
|
||||||
import {
|
import { getProjectBranding, isGhatreProject } from '@/utils/project'
|
||||||
hasAbshodeTrading,
|
|
||||||
hasHomePage,
|
|
||||||
hasProductShop,
|
|
||||||
isKashkoolProject,
|
|
||||||
} from '@/config/project-profile'
|
|
||||||
|
|
||||||
/** تبهای پایین موبایل — کاربر لاگینشده (کشکول و مشابه) */
|
/** تبهای پایین موبایل — کاربر لاگینشده (کشکول) */
|
||||||
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' },
|
||||||
@@ -14,7 +9,7 @@ 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[] = [
|
export const GHATRE_AUTH_PRIMARY_NAV: AppNavItem[] = [
|
||||||
{ id: 'buyProduct', routeName: 'buyProduct', label: 'فروشگاه', icon: 'vuesax-linear:coin' },
|
{ id: 'buyProduct', routeName: 'buyProduct', label: 'فروشگاه', icon: 'vuesax-linear:coin' },
|
||||||
{ id: 'transactions', routeName: 'transactions', label: 'گزارش', icon: 'vuesax-linear:receipt-item' },
|
{ id: 'transactions', routeName: 'transactions', label: 'گزارش', icon: 'vuesax-linear:receipt-item' },
|
||||||
@@ -44,55 +39,17 @@ export const AUTH_SECONDARY_NAV: AppNavItem[] = [
|
|||||||
{ id: 'rules', routeName: 'rules', label: 'قوانین و مقررات', icon: 'vuesax-linear:task' },
|
{ id: 'rules', routeName: 'rules', label: 'قوانین و مقررات', icon: 'vuesax-linear:task' },
|
||||||
]
|
]
|
||||||
|
|
||||||
/** منوی تمامصفحه کشکول — فقط این آیتمها */
|
|
||||||
export const KASHKOOL_OVERFLOW_NAV: AppNavItem[] = [
|
|
||||||
{ id: 'user', routeName: 'user', label: 'حساب کاربری', icon: 'vuesax-linear:user' },
|
|
||||||
{ id: 'physicalReceive', routeName: 'physicalReceive', label: 'دریافت طلای فیزیکی', icon: 'vuesax-linear:direct-inbox' },
|
|
||||||
{ id: 'notifyTrigger', routeName: 'notifyTrigger', label: 'هشدار قیمت', icon: 'vuesax-linear:bill' },
|
|
||||||
{ id: 'buyProduct', routeName: 'buyProduct', label: 'خرید محصول', icon: 'vuesax-linear:coin' },
|
|
||||||
{ id: 'giftCard', routeName: 'giftCard', label: 'ثبت کارت هدیه', icon: 'vuesax-linear:gift' },
|
|
||||||
{ id: 'rules', routeName: 'rules', label: 'قوانین و مقررات', icon: 'vuesax-linear:task' },
|
|
||||||
]
|
|
||||||
|
|
||||||
function filterSecondaryNavItems(items: AppNavItem[]): AppNavItem[] {
|
|
||||||
return items.filter((item) => {
|
|
||||||
if (item.routeName === 'buyProduct' && !hasProductShop()) return false
|
|
||||||
if (item.routeName === 'notifyTrigger' && !hasAbshodeTrading()) return false
|
|
||||||
if (item.routeName === 'earnMoney' && !hasAbshodeTrading()) return false
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPrimaryNavItems(isAuthenticated: boolean): AppNavItem[] {
|
export function getPrimaryNavItems(isAuthenticated: boolean): AppNavItem[] {
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) return GUEST_PRIMARY_NAV
|
||||||
return GUEST_PRIMARY_NAV.filter(
|
return isGhatreProject() ? GHATRE_AUTH_PRIMARY_NAV : AUTH_PRIMARY_NAV
|
||||||
(item) => item.routeName !== 'buyProduct' || hasProductShop()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasHomePage()) {
|
|
||||||
return GHATRE_AUTH_PRIMARY_NAV
|
|
||||||
}
|
|
||||||
|
|
||||||
return AUTH_PRIMARY_NAV
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSecondaryNavItems(isAuthenticated: boolean): AppNavItem[] {
|
export function getSecondaryNavItems(isAuthenticated: boolean): AppNavItem[] {
|
||||||
if (!isAuthenticated) return []
|
if (!isAuthenticated) return []
|
||||||
if (isKashkoolProject()) return KASHKOOL_OVERFLOW_NAV
|
|
||||||
return filterSecondaryNavItems(AUTH_SECONDARY_NAV)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getOverflowMenuItems(isAuthenticated: boolean): AppNavItem[] {
|
const fundLabel = getProjectBranding().fundLabel
|
||||||
if (!isAuthenticated) {
|
|
||||||
return AUTH_SECONDARY_NAV.filter((item) =>
|
|
||||||
['about', 'rules'].includes(item.routeName)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isKashkoolProject()) {
|
return AUTH_SECONDARY_NAV.map((item) =>
|
||||||
return KASHKOOL_OVERFLOW_NAV
|
item.id === 'fund' ? { ...item, label: fundLabel } : item
|
||||||
}
|
)
|
||||||
|
|
||||||
return filterSecondaryNavItems(AUTH_SECONDARY_NAV)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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="projectName" 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="منوی اصلی">
|
||||||
@@ -39,18 +39,18 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import logoFull from '@/assets/img/logo_full_.png'
|
||||||
import { isToken } from '@/utils'
|
import { isToken } from '@/utils'
|
||||||
import { useCartStore } from '@/store/cartStore'
|
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 { getProjectDisplayName } from '@/config/project-profile'
|
|
||||||
import { getAppLogoFullUrl } from '@/config/brand-assets'
|
|
||||||
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 = getAppLogoFullUrl()
|
const navLogo = logoFull
|
||||||
const projectName = getProjectDisplayName('کشکول گلد')
|
|
||||||
const primaryItems = computed(() => getPrimaryNavItems(authenticated.value))
|
const primaryItems = computed(() => getPrimaryNavItems(authenticated.value))
|
||||||
const secondaryItems = computed(() => getSecondaryNavItems(authenticated.value))
|
const secondaryItems = computed(() => getSecondaryNavItems(authenticated.value))
|
||||||
const cartCount = computed(() => cartStore.itemCount || 0)
|
const cartCount = computed(() => cartStore.itemCount || 0)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.desktop-app-layout {
|
.desktop-app-layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -110,15 +110,15 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(167, 43, 119, 0.06);
|
background: rgba(167, 43, 119, 0.06);
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
background: rgba(167, 43, 119, 0.1);
|
background: rgba(167, 43, 119, 0.1);
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
|
|
||||||
.desktop-nav-item__icon {
|
.desktop-nav-item__icon {
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Shared desktop layouts for inner pages */
|
/* Shared desktop layouts for inner pages */
|
||||||
|
|
||||||
.dinner {
|
.dinner {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
&__price {
|
&__price {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
&__total {
|
&__total {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dproduct-info__desc {
|
.dproduct-info__desc {
|
||||||
|
|||||||
@@ -16,12 +16,7 @@
|
|||||||
<div class="main_in">
|
<div class="main_in">
|
||||||
<div class="text-white circle_in">
|
<div class="text-white circle_in">
|
||||||
<div class="circle_into" @click="$emit('toggle-overflow')">
|
<div class="circle_into" @click="$emit('toggle-overflow')">
|
||||||
<img
|
<img class="p-2" :src="navLogo" alt="" />
|
||||||
class="p-2"
|
|
||||||
:class="{ 'nav-logo--white': isKashkool }"
|
|
||||||
:src="navLogo"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,9 +45,8 @@ import { computed } from 'vue'
|
|||||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
import { isToken } from '@/utils'
|
import { isToken } from '@/utils'
|
||||||
import { useCartStore } from '@/store/cartStore'
|
import { useCartStore } from '@/store/cartStore'
|
||||||
import { getAppIconUrl } from '@/config/brand-assets'
|
|
||||||
import { isKashkoolProject } from '@/config/project-profile'
|
|
||||||
import { getPrimaryNavItems } from '../config/navigation'
|
import { getPrimaryNavItems } from '../config/navigation'
|
||||||
|
import logo from '@/assets/img/logo.png'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
navItemClass: (routeName: string) => string
|
navItemClass: (routeName: string) => string
|
||||||
@@ -64,8 +58,7 @@ defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const cartStore = useCartStore()
|
const cartStore = useCartStore()
|
||||||
const navLogo = getAppIconUrl()
|
const navLogo = logo
|
||||||
const isKashkool = isKashkoolProject()
|
|
||||||
const cartCount = computed(() => cartStore.itemCount || 0)
|
const cartCount = computed(() => cartStore.itemCount || 0)
|
||||||
|
|
||||||
const primaryItems = computed(() => getPrimaryNavItems(isToken()))
|
const primaryItems = computed(() => getPrimaryNavItems(isToken()))
|
||||||
@@ -87,8 +80,4 @@ const rightTabs = computed(() => primaryItems.value.slice(2))
|
|||||||
.tab__icon :deep(.ant-badge) {
|
.tab__icon :deep(.ant-badge) {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-logo--white {
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -17,15 +17,85 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center gap-2">
|
<div class="flex flex-col items-center gap-2">
|
||||||
<BasicButton
|
<BasicButton
|
||||||
v-for="item in menuItems"
|
|
||||||
:key="item.id"
|
|
||||||
icon-size="16"
|
icon-size="16"
|
||||||
class="!font-bold w-15rem"
|
class="!font-bold w-15rem"
|
||||||
@click="go(item.routeName)"
|
@click="go('user')"
|
||||||
>
|
>
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex gap-2 items-center">
|
||||||
<BasicIcon :icon="item.icon" size="16" class="absolute right-2" />
|
<BasicIcon icon="vuesax-linear:user" size="16" class="absolute right-2" />
|
||||||
<div>{{ item.label }}</div>
|
<div>حساب کاربری</div>
|
||||||
|
</div>
|
||||||
|
</BasicButton>
|
||||||
|
<BasicButton
|
||||||
|
v-if="authenticated"
|
||||||
|
icon-size="16"
|
||||||
|
class="!font-bold w-15rem"
|
||||||
|
@click="go('physicalReceive')"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<BasicIcon icon="vuesax-linear:direct-inbox" size="16" class="absolute right-2" />
|
||||||
|
<div>دریافت طلای فیزیکی</div>
|
||||||
|
</div>
|
||||||
|
</BasicButton>
|
||||||
|
<BasicButton
|
||||||
|
v-if="authenticated"
|
||||||
|
icon-size="16"
|
||||||
|
class="!font-bold w-15rem"
|
||||||
|
@click="go('notifyTrigger')"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<BasicIcon icon="vuesax-linear:bill" size="16" class="absolute right-2" />
|
||||||
|
<div>هشدار قیمت</div>
|
||||||
|
</div>
|
||||||
|
</BasicButton>
|
||||||
|
<BasicButton
|
||||||
|
v-if="authenticated"
|
||||||
|
icon-size="16"
|
||||||
|
class="!font-bold w-15rem"
|
||||||
|
@click="go('buyProduct')"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<BasicIcon icon="vuesax-linear:coin" size="16" class="absolute right-2" />
|
||||||
|
<div>خرید محصول</div>
|
||||||
|
</div>
|
||||||
|
</BasicButton>
|
||||||
|
<!-- <BasicButton-->
|
||||||
|
<!-- v-if="authenticated"-->
|
||||||
|
<!-- icon-size="16"-->
|
||||||
|
<!-- class="!font-bold w-15rem"-->
|
||||||
|
<!-- @click="go('list')"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <div class="flex gap-2 items-center">-->
|
||||||
|
<!-- <BasicIcon icon="vuesax-linear:receipt-item" size="16" class="absolute right-2" />-->
|
||||||
|
<!-- <div>لیست دریافتی ها</div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </BasicButton>-->
|
||||||
|
<BasicButton
|
||||||
|
v-if="authenticated"
|
||||||
|
icon-size="16"
|
||||||
|
class="!font-bold w-15rem"
|
||||||
|
@click="go('giftCard')"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<BasicIcon icon="vuesax-linear:gift" size="16" class="absolute right-2" />
|
||||||
|
<div>ثبت کارت هدیه</div>
|
||||||
|
</div>
|
||||||
|
</BasicButton>
|
||||||
|
<BasicButton icon-size="16" class="!font-bold w-15rem" @click="go('about')">
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<BasicIcon icon="vuesax-linear:info-circle" size="16" class="absolute right-2" />
|
||||||
|
<div>درباره ما</div>
|
||||||
|
</div>
|
||||||
|
</BasicButton>
|
||||||
|
<BasicButton
|
||||||
|
v-if="authenticated"
|
||||||
|
icon-size="16"
|
||||||
|
class="!font-bold w-15rem"
|
||||||
|
@click="go('rules')"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<BasicIcon icon="vuesax-linear:task" size="16" class="absolute right-2" />
|
||||||
|
<div>قوانین و مقررات</div>
|
||||||
</div>
|
</div>
|
||||||
</BasicButton>
|
</BasicButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +109,6 @@ import { computed } from 'vue'
|
|||||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
import { isToken } from '@/utils'
|
import { isToken } from '@/utils'
|
||||||
import { getOverflowMenuItems } from '../config/navigation'
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
@@ -50,7 +119,7 @@ const emit = defineEmits<{
|
|||||||
navigate: [routeName: string]
|
navigate: [routeName: string]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const menuItems = computed(() => getOverflowMenuItems(isToken()))
|
const authenticated = computed(() => isToken())
|
||||||
|
|
||||||
function go(routeName: string) {
|
function go(routeName: string) {
|
||||||
emit('navigate', routeName)
|
emit('navigate', routeName)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.bottom-appbar {
|
.bottom-appbar {
|
||||||
z-index: var(--app-z-nav, 5000);
|
z-index: var(--app-z-nav, 5000);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -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: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
background-color: @theme-primary;
|
||||||
|
|
||||||
.circle_into {
|
.circle_into {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--app-primary-color, #a72b77);
|
background-color: @theme-primary;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Shared mobile page chrome (inner pages — not home .app-header) */
|
/* Shared mobile page chrome (inner pages — not home .app-header) */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--mpage-header-bar: 2.75rem;
|
--mpage-header-bar: 2.75rem;
|
||||||
@@ -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: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface Order {
|
|||||||
sts_str?: string
|
sts_str?: string
|
||||||
payment_sts?: number
|
payment_sts?: number
|
||||||
payment_amount?: number
|
payment_amount?: number
|
||||||
|
target_cash?: number
|
||||||
service_charge_price?: number
|
service_charge_price?: number
|
||||||
total_fee?: number
|
total_fee?: number
|
||||||
total_price?: number
|
total_price?: number
|
||||||
|
|||||||
+2
-8
@@ -1,5 +1,5 @@
|
|||||||
import {createRouter, createWebHistory} from 'vue-router'
|
import {createRouter, createWebHistory} from 'vue-router'
|
||||||
import { getDefaultLandingRoute } from '@/config/project-profile'
|
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),
|
||||||
@@ -9,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: getDefaultLandingRoute() }),
|
redirect: () => ({ name: getProjectDefaultRoute() }),
|
||||||
component: () => import('../views/base/index.vue'),
|
component: () => import('../views/base/index.vue'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import { type ComputedRef, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import {defineStore} from "pinia";
|
import {defineStore} from "pinia";
|
||||||
import {getMe} from "@/api/me";
|
|
||||||
import type {User} from "@/models/login";
|
|
||||||
import type {Wallet} from "@/models/wallet";
|
import type {Wallet} from "@/models/wallet";
|
||||||
import {getWallet} from "@/api/wallet";
|
import {getWallet} from "@/api/wallet";
|
||||||
import type {Const} from "@/models/const";
|
|
||||||
import {getConstByKey} from "@/api/const";
|
|
||||||
export const useWalletStore = defineStore('wallet', () => {
|
export const useWalletStore = defineStore('wallet', () => {
|
||||||
const walletInfo = ref<Wallet | undefined>(undefined)
|
const walletInfo = ref<Wallet | undefined>(undefined)
|
||||||
const limitedPrice = ref<Const[]>()
|
|
||||||
const limitedBuyGold = ref<Const[]>()
|
|
||||||
|
|
||||||
async function getMyWallet() {
|
async function getMyWallet() {
|
||||||
try {
|
try {
|
||||||
@@ -19,40 +13,6 @@ export const useWalletStore = defineStore('wallet', () => {
|
|||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getLimitedPrice(){
|
|
||||||
try {
|
|
||||||
limitedPrice.value = await getConstByKey('limited_price')
|
|
||||||
return limitedPrice.value
|
|
||||||
}catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getLimitedBuyGold(){
|
return { walletInfo, getMyWallet }
|
||||||
try {
|
|
||||||
limitedBuyGold.value = await getConstByKey('first_customer_wallet_value')
|
|
||||||
return limitedBuyGold.value
|
|
||||||
}catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* const permissions: ComputedRef<string[]> = computed(() => {
|
|
||||||
const permissions: string[] = get(userInfo.value, 'permission', [])
|
|
||||||
const excludes: string[] = get(userInfo.value, 'exclude', [])
|
|
||||||
return permissions.filter((item) => !excludes.includes(item))
|
|
||||||
})*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
function canAccess(permissionKey: string) {
|
|
||||||
if (isAdmin.value) return true
|
|
||||||
return permissions.value.includes(permissionKey)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
const isAdmin = computed(() => {
|
|
||||||
return userInfo.value?.is_admin
|
|
||||||
})*/
|
|
||||||
|
|
||||||
return { walletInfo, getMyWallet, getLimitedPrice, getLimitedBuyGold, limitedPrice, limitedBuyGold/* permissions, canAccess, isAdmin*/ }
|
|
||||||
})
|
})
|
||||||
|
|||||||
+11
-28
@@ -1,36 +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 {
|
export {
|
||||||
getProjectDisplayName,
|
getProjectId,
|
||||||
getDefaultLandingRoute,
|
getProjectBranding,
|
||||||
hasHomePage,
|
getProjectDefaultRoute,
|
||||||
hasAbshodeTrading,
|
|
||||||
hasProductShop,
|
|
||||||
isGhatreProject,
|
isGhatreProject,
|
||||||
isKashkoolProject,
|
isKashkoolProject,
|
||||||
isBoursecuProject,
|
isBoursecuProject,
|
||||||
getProjectProfile,
|
} from '@/utils/project'
|
||||||
getProjectProfileId,
|
|
||||||
} from '@/config/project-profile'
|
|
||||||
|
|
||||||
export {
|
export type { ProjectId, ProjectBranding } from '@/utils/project'
|
||||||
getAppIconUrl,
|
|
||||||
getAppLogoUrl,
|
|
||||||
getAppLogoFullUrl,
|
|
||||||
resolveConfigAssetUrl,
|
|
||||||
} from '@/config/brand-assets'
|
|
||||||
|
|
||||||
export {
|
|
||||||
getAppPrimaryColor,
|
|
||||||
getAppSecondaryColor,
|
|
||||||
getAppTertiaryColor,
|
|
||||||
getAppBackgroundColor,
|
|
||||||
applyBrandTheme,
|
|
||||||
} from '@/config/brand-theme'
|
|
||||||
|
|||||||
@@ -39,11 +39,13 @@ export function getProductDisplayImage(product?: {
|
|||||||
return resolveProductImageSrc(product)
|
return resolveProductImageSrc(product)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { formatGoldAmount } from '@/utils/usePrice'
|
||||||
|
|
||||||
export function formatMetalTotal(
|
export function formatMetalTotal(
|
||||||
metal: WalletMetalTotal,
|
metal: WalletMetalTotal,
|
||||||
inventoryCards: WalletInventoryCard[]
|
inventoryCards: WalletInventoryCard[]
|
||||||
): string {
|
): string {
|
||||||
return `${resolveSummaryMetalWeight(metal, inventoryCards)}${metal.unit}`
|
return `${formatGoldAmount(resolveSummaryMetalWeight(metal, inventoryCards))}${metal.unit}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveAssetsRialValue(
|
export function resolveAssetsRialValue(
|
||||||
|
|||||||
@@ -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')
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { getSettings } from '@/api/configuration'
|
||||||
|
|
||||||
|
export const serviceFeePercentage = ref<number>(0.01)
|
||||||
|
|
||||||
|
/** true = مبالغ رند: فروش → ریال دقیق (کارمزد از طلا)؛ خرید → وزن دقیق (کارمزد از ریال) */
|
||||||
|
export const feeDeductFromGold = ref(false)
|
||||||
|
|
||||||
|
/** آخرین فیلدی که کاربر مستقیم ویرایش کرده — مبنای بهروزرسانی هنگام تغییر قیمت */
|
||||||
|
export type TradeInputLock = 'gram' | 'rial' | null
|
||||||
|
|
||||||
|
/** حداکثر اعشار ذخیره/ارسال به سرور (مطابق orders.amount) */
|
||||||
|
export const TRADE_GRAM_STORAGE_DECIMALS = 10
|
||||||
|
|
||||||
|
/** حداکثر اعشار نمایش به کاربر در اپ */
|
||||||
|
export const TRADE_GRAM_DISPLAY_DECIMALS = 8
|
||||||
|
|
||||||
|
export function normalizeOrderGoldAmount(grams?: number | null): number {
|
||||||
|
const value = Number(grams ?? 0)
|
||||||
|
if (!Number.isFinite(value) || value === 0) return 0
|
||||||
|
return parseFloat(value.toFixed(TRADE_GRAM_STORAGE_DECIMALS))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTradeGramDisplay(grams?: number | null): number {
|
||||||
|
const value = Number(grams ?? 0)
|
||||||
|
if (!Number.isFinite(value)) return 0
|
||||||
|
return parseFloat(value.toFixed(TRADE_GRAM_DISPLAY_DECIMALS))
|
||||||
|
}
|
||||||
|
|
||||||
|
function feeRate() {
|
||||||
|
return Number(serviceFeePercentage.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadServiceFeeFromSettings() {
|
||||||
|
try {
|
||||||
|
const settings = await getSettings()
|
||||||
|
if (settings && Array.isArray(settings)) {
|
||||||
|
const feeSetting = settings.find(
|
||||||
|
(s) => s?.key === 'fee_service_charge_percentage' && s?.is_active === true
|
||||||
|
)
|
||||||
|
if (feeSetting?.value !== undefined && feeSetting?.value !== null) {
|
||||||
|
serviceFeePercentage.value = Number(feeSetting.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const feeModeSetting = settings.find(
|
||||||
|
(s) => s?.key === 'fee_deduct_from_gold' && s?.is_active === true
|
||||||
|
)
|
||||||
|
if (feeModeSetting?.value !== undefined && feeModeSetting?.value !== null) {
|
||||||
|
const raw = String(feeModeSetting.value).trim().toLowerCase()
|
||||||
|
feeDeductFromGold.value = raw === 'true' || raw === '1'
|
||||||
|
} else {
|
||||||
|
feeDeductFromGold.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calcFeeInGrams(goldValue?: number | null) {
|
||||||
|
return parseFloat(((goldValue ?? 0) * Number(serviceFeePercentage.value)).toFixed(4))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** گرم معادل برای دریافت دقیق مبلغ ریالی در فروش */
|
||||||
|
export function calcGoldAmountFromTargetRial(rialAmount: number, unitPriceRial: number) {
|
||||||
|
if (!rialAmount || !unitPriceRial) return 0
|
||||||
|
return normalizeOrderGoldAmount(rialAmount / unitPriceRial)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** وقتی کاربر گرم را قفل کرده — ریال نمایشی/قابل پرداخت از همان گرم */
|
||||||
|
export function calcRialFromLockedGram(displayGram: number, unitPriceRial: number, isBuy: boolean) {
|
||||||
|
const grams = Number(displayGram)
|
||||||
|
const unit = Number(unitPriceRial)
|
||||||
|
if (!grams || !unit) return 0
|
||||||
|
const fee = feeRate()
|
||||||
|
|
||||||
|
if (isBuy) {
|
||||||
|
if (feeDeductFromGold.value) {
|
||||||
|
return Math.round(grams * unit * (1 + fee))
|
||||||
|
}
|
||||||
|
return Math.round(grams * unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feeDeductFromGold.value) {
|
||||||
|
return Math.round(grams * unit)
|
||||||
|
}
|
||||||
|
return Math.round(grams * unit * (1 - fee))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** وقتی کاربر ریال را قفل کرده — گرم نمایشی از همان مبلغ */
|
||||||
|
export function calcGramFromLockedRial(rialAmount: number, unitPriceRial: number, isBuy: boolean) {
|
||||||
|
const rial = Number(rialAmount)
|
||||||
|
const unit = Number(unitPriceRial)
|
||||||
|
if (!rial || !unit) return 0
|
||||||
|
const fee = feeRate()
|
||||||
|
|
||||||
|
if (isBuy) {
|
||||||
|
if (feeDeductFromGold.value) {
|
||||||
|
return rial / (unit * (1 + fee))
|
||||||
|
}
|
||||||
|
return (rial / unit) * (1 - fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feeDeductFromGold.value) {
|
||||||
|
return rial / unit
|
||||||
|
}
|
||||||
|
return rial / (unit * (1 - fee))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** مبلغ ریالی قفلشده توسط کاربر — برای ارسال به سرور */
|
||||||
|
export function calcTradeTargetCash(
|
||||||
|
goldCash?: number | null,
|
||||||
|
inputLock?: TradeInputLock,
|
||||||
|
): number | undefined {
|
||||||
|
if (inputLock !== 'rial') return undefined
|
||||||
|
const cash = Number(goldCash ?? 0)
|
||||||
|
if (cash <= 0) return undefined
|
||||||
|
return Math.round(cash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** مبلغ نهایی قابل پرداخت (خرید) یا دریافت (فروش) به ریال */
|
||||||
|
export function calcPayablePriceRial(
|
||||||
|
finalPriceRial: number,
|
||||||
|
feeInRial: number,
|
||||||
|
isBuy: boolean,
|
||||||
|
targetRialCash?: number | null
|
||||||
|
) {
|
||||||
|
if (targetRialCash != null && Number(targetRialCash) > 0) {
|
||||||
|
return Number(targetRialCash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feeDeductFromGold.value) {
|
||||||
|
if (isBuy) return finalPriceRial + feeInRial
|
||||||
|
return finalPriceRial
|
||||||
|
}
|
||||||
|
return isBuy ? finalPriceRial : Math.max(0, finalPriceRial - feeInRial)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** وزن ارسالی به سرور */
|
||||||
|
export function calcOrderGoldAmount(
|
||||||
|
goldValue?: number | null,
|
||||||
|
goldCash?: number | null,
|
||||||
|
unitPriceRial?: number,
|
||||||
|
isBuy?: boolean,
|
||||||
|
inputLock?: TradeInputLock
|
||||||
|
) {
|
||||||
|
const gold = Number(goldValue ?? 0)
|
||||||
|
const cash = Number(goldCash ?? 0)
|
||||||
|
const unit = Number(unitPriceRial ?? 0)
|
||||||
|
|
||||||
|
if (isBuy && inputLock === 'rial' && cash > 0 && unit > 0) {
|
||||||
|
if (feeDeductFromGold.value) {
|
||||||
|
const fee = feeRate()
|
||||||
|
return normalizeOrderGoldAmount(cash / (unit * (1 + fee)))
|
||||||
|
}
|
||||||
|
return normalizeOrderGoldAmount(cash / unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isBuy && feeDeductFromGold.value) {
|
||||||
|
if (inputLock === 'rial' && cash > 0 && unit > 0) {
|
||||||
|
return calcGoldAmountFromTargetRial(cash, unit)
|
||||||
|
}
|
||||||
|
if (inputLock !== 'rial') {
|
||||||
|
return normalizeOrderGoldAmount(gold)
|
||||||
|
}
|
||||||
|
const targetCash = calcSellTargetCash(goldCash, goldValue, unitPriceRial, inputLock)
|
||||||
|
if (targetCash != null && unit > 0) {
|
||||||
|
return calcGoldAmountFromTargetRial(targetCash, unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeOrderGoldAmount(gold)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** مبلغ ریالی دقیق دریافتی در فروش (فقط وقتی کاربر با ریال وارد کرده) */
|
||||||
|
export function calcSellTargetCash(
|
||||||
|
goldCash?: number | null,
|
||||||
|
goldValue?: number | null,
|
||||||
|
unitPriceRial?: number,
|
||||||
|
inputLock?: TradeInputLock
|
||||||
|
) {
|
||||||
|
if (!feeDeductFromGold.value) return undefined
|
||||||
|
if (inputLock === 'gram') return undefined
|
||||||
|
|
||||||
|
const cash = Number(goldCash ?? 0)
|
||||||
|
if (cash <= 0) return undefined
|
||||||
|
if (inputLock === 'rial') return Math.round(cash)
|
||||||
|
|
||||||
|
const gold = Number(goldValue ?? 0)
|
||||||
|
const unit = Number(unitPriceRial ?? 0)
|
||||||
|
if (gold > 0 && unit > 0) {
|
||||||
|
const derived = Math.round(gold * unit)
|
||||||
|
if (Math.abs(derived - Math.round(cash)) <= 1) return undefined
|
||||||
|
}
|
||||||
|
return Math.round(cash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** کل طلای کسرشونده از کیف در فروش (شامل کارمزد وقتی fee_deduct_from_gold روشن است) */
|
||||||
|
export function calcSellGoldDebit(goldValue?: number | null, feeInGrams?: number | null) {
|
||||||
|
const gold = Number(goldValue ?? 0)
|
||||||
|
const fee = Number(feeInGrams ?? 0)
|
||||||
|
if (feeDeductFromGold.value) {
|
||||||
|
return parseFloat((gold + fee).toFixed(4))
|
||||||
|
}
|
||||||
|
return gold
|
||||||
|
}
|
||||||
+88
-14
@@ -1,24 +1,46 @@
|
|||||||
export function usePrice(price: number, digit = true, unit = 'ریال') {
|
function formatGroupedPrice(price: string | number | null | undefined, digit: boolean) {
|
||||||
price= price??0
|
let value = price === null || price === undefined ? '' : String(price)
|
||||||
let _price = price.toString()
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePriceWithoutType(price?: number, digit = true, unit = 'ریال') {
|
/** مبلغ ریالی — بدون اعشار، گرد به بالا */
|
||||||
let _price = price?.toString()
|
export function ceilRial(price?: number | string | null): number {
|
||||||
if (digit) {
|
const num = Number(price ?? 0)
|
||||||
_price = _price?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
if (!Number.isFinite(num)) {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
return `${_price}`
|
|
||||||
|
return Math.ceil(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatNumber(value: string | number | null | undefined, grouped = false): string {
|
||||||
|
return formatGroupedPrice(value, grouped)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePrice(price: number, digit = true, unit = 'ریال') {
|
||||||
|
const formatted = formatGroupedPrice(ceilRial(price), digit)
|
||||||
|
return `${formatted} ${unit}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePriceWithoutType(price?: number, digit = true) {
|
||||||
|
return formatGroupedPrice(ceilRial(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 formatSignedPercentChange(value: number): string {
|
||||||
|
if (!value) return '0%'
|
||||||
|
|
||||||
|
const formatted = useTowDecimal(Math.abs(value))
|
||||||
|
const prefix = value > 0 ? '+' : '-'
|
||||||
|
return `${prefix}${formatted}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFormat(num: string | number, unit = '') {
|
export function useFormat(num: string | number, unit = '') {
|
||||||
@@ -28,11 +50,26 @@ export function useFormat(num: string | number, unit = '') {
|
|||||||
type TransactionPriceSource = {
|
type TransactionPriceSource = {
|
||||||
cash?: number | null
|
cash?: number | null
|
||||||
gold?: number | null
|
gold?: number | null
|
||||||
|
type?: number | null
|
||||||
order?: {
|
order?: {
|
||||||
price?: number | null
|
price?: number | null
|
||||||
total_price?: number | null
|
total_price?: number | null
|
||||||
amount?: number | null
|
amount?: number | null
|
||||||
|
sts?: number | null
|
||||||
|
sts_str?: string | number | null
|
||||||
|
payment_sts?: number | null
|
||||||
} | null
|
} | null
|
||||||
|
withdraw?: {
|
||||||
|
sts?: number | null
|
||||||
|
sts_str?: string | number | null
|
||||||
|
} | null
|
||||||
|
order_id?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const ORDER_STATUS_LABELS: Record<number, string> = {
|
||||||
|
1: 'باز',
|
||||||
|
2: 'موفق',
|
||||||
|
3: 'ناموفق',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTransactionTradePrice(item: TransactionPriceSource): number {
|
export function getTransactionTradePrice(item: TransactionPriceSource): number {
|
||||||
@@ -57,6 +94,38 @@ export function getTransactionTradePrice(item: TransactionPriceSource): number {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveTransactionStatusLabel(item?: TransactionPriceSource | null): string {
|
||||||
|
if (!item) return ''
|
||||||
|
|
||||||
|
const raw = item.order?.sts_str ?? item.withdraw?.sts_str
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
const trimmed = raw.trim()
|
||||||
|
if (trimmed && trimmed !== 'بدون مقدار') {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof raw === 'number' || (typeof raw === 'string' && /^\d+$/.test(String(raw).trim()))) {
|
||||||
|
const mapped = ORDER_STATUS_LABELS[Number(raw)]
|
||||||
|
if (mapped) return mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
const sts = item.order?.sts ?? item.withdraw?.sts
|
||||||
|
if (sts != null && ORDER_STATUS_LABELS[Number(sts)]) {
|
||||||
|
return ORDER_STATUS_LABELS[Number(sts)]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.order_id && Number(item.order?.payment_sts) === 4) {
|
||||||
|
return 'موفق'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.order_id && (item.type === 1 || item.type === 2 || item.type === 10)) {
|
||||||
|
return 'موفق'
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
export function getTransactionGoldAmount(item: TransactionPriceSource): number {
|
export function getTransactionGoldAmount(item: TransactionPriceSource): number {
|
||||||
const gold = Number(item.gold) || 0
|
const gold = Number(item.gold) || 0
|
||||||
if (Math.abs(gold) > 0) {
|
if (Math.abs(gold) > 0) {
|
||||||
@@ -74,8 +143,13 @@ export function getTransactionGoldAmount(item: TransactionPriceSource): number {
|
|||||||
return gold
|
return gold
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatGoldAmount(amount?: number | null): string {
|
export function formatGoldAmount(amount?: number | null, decimals = 8): string {
|
||||||
const num = Number(amount)
|
const num = Number(amount)
|
||||||
if (!Number.isFinite(num)) return '0'
|
if (!Number.isFinite(num)) return '0'
|
||||||
return String(Number(num.toFixed(3)))
|
return formatGroupedPrice(Number(num.toFixed(decimals)), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** وزن فلز (گرم) — بدون گرد کردن به بالا */
|
||||||
|
export function formatWeightAmount(amount?: number | null): string {
|
||||||
|
return formatGoldAmount(amount)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop about page */
|
/* Premium desktop about page */
|
||||||
|
|
||||||
.dabout {
|
.dabout {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -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: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__divider {
|
&__divider {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dinner cart-page">
|
<div class="dinner cart-page">
|
||||||
<div class="dinner__container">
|
<div class="dinner__container">
|
||||||
<DesktopPageTopbar title="سبد خرید" show-back>
|
<DesktopPageTopbar title="سبد خرید" show-back>
|
||||||
@@ -174,7 +174,7 @@ const {
|
|||||||
.dcart-summary__discount-link {
|
.dcart-summary__discount-link {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @App-primary-color;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dinner earn-money-page">
|
<div class="dinner earn-money-page">
|
||||||
<div class="dinner__container">
|
<div class="dinner__container">
|
||||||
<DesktopPageTopbar title="کسب درآمد" show-back />
|
<DesktopPageTopbar title="کسب درآمد" show-back />
|
||||||
@@ -94,7 +94,7 @@ const { data, userStore, isTransaction, setIsTransaction, copyCode, shareCode }
|
|||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @App-primary-color;
|
||||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
|
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="bg-white w-full py-3 px-3 text-primary font-bold drop-shadow flex">
|
<div class="bg-white w-full py-3 px-3 text-primary font-bold drop-shadow flex">
|
||||||
<div @click="router.back()">
|
<div @click="router.back()">
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<div class="mr-2">کسب درآمد</div>
|
<div class="mr-2">کسب درآمد</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="p-3 border-solid border-1 rounded-[8px] border-primary">
|
<div class="p-3 border-solid border-1 rounded-[8px] border-[#a72b77]">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="bg-[#DBEAFE] h-10 w-10 rounded-100% flex justify-center items-center">
|
<div class="bg-[#DBEAFE] h-10 w-10 rounded-100% flex justify-center items-center">
|
||||||
<img :src="coin" />
|
<img :src="coin" />
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop fund page */
|
/* Premium desktop fund page */
|
||||||
|
|
||||||
.dfund {
|
.dfund {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
background: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dfund-info__balance-unit {
|
.dfund-info__balance-unit {
|
||||||
@@ -495,8 +495,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
background: var(--app-primary-color, #a72b77);
|
background: @theme-primary;
|
||||||
border-color: var(--app-primary-color, #a72b77);
|
border-color: @theme-primary;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop gift card page */
|
/* Premium desktop gift card page */
|
||||||
|
|
||||||
.dgift {
|
.dgift {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -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: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.basic-icon {
|
.basic-icon {
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|||||||
+161
-108
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-35rem overflow-y-auto px-5">
|
<div class="h-35rem overflow-y-auto px-5">
|
||||||
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
|
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
|
||||||
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
|
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
|
||||||
@@ -24,14 +24,9 @@
|
|||||||
<img :src="img1" alt="" class="h-[88px] absolute">
|
<img :src="img1" alt="" class="h-[88px] absolute">
|
||||||
<div class="w-full items-center flex flex-col">
|
<div class="w-full items-center flex flex-col">
|
||||||
<div class="text-gray-700 font-bold">{{ !isBuy ? 'قیمت فروش' : 'قیمت خرید' }}</div>
|
<div class="text-gray-700 font-bold">{{ !isBuy ? 'قیمت فروش' : 'قیمت خرید' }}</div>
|
||||||
<div 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 class="gold-live-price">
|
<div :class="!isBuy ? 'text-red-700' : 'text-green-700'" class="font-bold text-2xl color-transition">
|
||||||
<Vue3Odometer
|
{{ usePriceWithoutType(goldPriceValue) }}
|
||||||
:key="isBuy ? 'buy' : 'sell'"
|
|
||||||
:value="displayedGoldPrice"
|
|
||||||
:class="[!isBuy ? 'text-red-700' : 'text-green-700', 'o-text !text-2xl font-bold color-transition']"
|
|
||||||
style="font-family: iran-sans; direction: ltr"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
ریال
|
ریال
|
||||||
</div>
|
</div>
|
||||||
@@ -40,25 +35,20 @@
|
|||||||
<div v-else class="w-full h-[88px]">
|
<div v-else class="w-full h-[88px]">
|
||||||
<GoldSkeleton class="w-full" />
|
<GoldSkeleton class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="limitedPrice !== undefined && userStore?.userInfo.trade_active !== 1"
|
|
||||||
class="text-red text-xs flex gap-1 items-center justify-center my-2">
|
|
||||||
<Icon icon="vuesax-linear:info-circle" size="16" />
|
|
||||||
حداقل مبلغ اولیه برای ثبت سفارش {{ usePriceWithoutType(limitedPrice.value as number ?? 0) }} ریال می باشد.
|
|
||||||
</div>
|
|
||||||
</a-spin>
|
</a-spin>
|
||||||
<a-form :model="form" class="!mt-5" name="basic" @finish="okConfirm()">
|
<a-form :model="form" class="!mt-5" name="basic" @finish="okConfirm()">
|
||||||
<div class="border-gray-200 border-1 rounded-xl p-5 h-58 relative">
|
<div class="border-gray-200 border-1 rounded-xl p-5 h-58 relative">
|
||||||
<bg_main/>
|
<bg_main/>
|
||||||
<div class="z-100 relative">
|
<div class="z-100 relative">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<BasicInput v-model:value="form.goldCash" addon-before="ریال" addon-bg-color="var(--app-primary-color)"
|
<BasicInput v-model:value="form.goldCash" addon-before="ریال" addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="ارزش کل"
|
place-holder="ارزش کل"
|
||||||
size="large"
|
size="large"
|
||||||
type="price" @onchange="calculateGold" />
|
type="price" @onchange="calculateGold" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :rules="[justNumber()]">
|
<a-form-item :rules="[justNumber()]">
|
||||||
<BasicInput v-model:value="form.goldValue" addon-before="گرم" addon-bg-color="var(--app-primary-color)"
|
<BasicInput v-model:value="form.goldValue" addon-before="گرم" addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="مقدار طلا"
|
place-holder="مقدار طلا"
|
||||||
size="large" type="text" inputmode="decimal" @onchange="calculateCash" />
|
size="large" type="text" inputmode="decimal" @onchange="calculateCash" />
|
||||||
@@ -66,7 +56,7 @@
|
|||||||
<!-- {{walletStore?.walletInfo?.gold}}-->
|
<!-- {{walletStore?.walletInfo?.gold}}-->
|
||||||
<Transition mode="out-in" name="fade">
|
<Transition mode="out-in" name="fade">
|
||||||
<div v-if="!isBuy" class="font-bold text-xs">
|
<div v-if="!isBuy" class="font-bold text-xs">
|
||||||
<div> {{ `موجودی کیف طلا: ${walletStore?.walletInfo?.gold ?? 0} گرم` }}</div>
|
<div> {{ `موجودی کیف طلا: ${formatGoldAmount(walletStore?.walletInfo?.gold ?? 0)} گرم` }}</div>
|
||||||
<!-- <div v-if="walletStore?.walletInfo?.gold_block !== 0">
|
<!-- <div v-if="walletStore?.walletInfo?.gold_block !== 0">
|
||||||
{{ `موجودی قابل برداشت طلا: ${walletStore?.walletInfo?.gold_block ?? 0} گرم` }}
|
{{ `موجودی قابل برداشت طلا: ${walletStore?.walletInfo?.gold_block ?? 0} گرم` }}
|
||||||
</div>-->
|
</div>-->
|
||||||
@@ -88,13 +78,6 @@
|
|||||||
@change="percentChange" />
|
@change="percentChange" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</Transition>
|
</Transition>
|
||||||
<Transition mode="out-in" name="fade">
|
|
||||||
<div v-if="priceErrorVisible" class="flex gap-1 items-center text-xs mt-[-15px] text-blue-600">
|
|
||||||
<BasicIcon icon="vuesax-linear:info-circle" />
|
|
||||||
ارزش کل وارد شده بر حسب گرم طلا محاسبه خواهد شد و قیمت وارد شده معادل {{ usePrice(parseInt(finalPrice.toFixed(0))) }} می
|
|
||||||
باشد!
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<GoldLoading v-model:show="infoVisible" />
|
<GoldLoading v-model:show="infoVisible" />
|
||||||
@@ -111,10 +94,11 @@
|
|||||||
<div v-else>{{ !isBuy ? 'فروش' : 'خرید' }}</div>
|
<div v-else>{{ !isBuy ? 'فروش' : 'خرید' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</BasicButton>
|
</BasicButton>
|
||||||
<div v-if="!isBuy && form.goldValue > walletStore?.walletInfo?.gold"
|
<div v-if="!isBuy && sellGoldDebit > (walletStore?.walletInfo?.gold ?? 0)"
|
||||||
class="text-red-700 text-xs font-bold mt-2">
|
class="text-red-700 text-xs font-bold mt-2">
|
||||||
مقدار گرم طلای وارد شده جهت فروش ({{ form.goldValue }}گرم) بیشتر از مقدار موجودی شما
|
مقدار گرم طلای وارد شده جهت فروش ({{ form.goldValue }}گرم) بیشتر از مقدار موجودی شما
|
||||||
({{ walletStore?.walletInfo?.gold }}گرم) می باشد.
|
({{ walletStore?.walletInfo?.gold }}گرم) می باشد.
|
||||||
|
<span v-if="feeDeductFromGold"> (با احتساب {{ feeInGrams }} گرم کارمزد، مجموعاً {{ sellGoldDebit }} گرم کسر میشود)</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div>{{form?.goldValue? true: false}}</div>-->
|
<!-- <div>{{form?.goldValue? true: false}}</div>-->
|
||||||
<div v-if="isBuy? ((form?.goldValue || form.goldValue === 0)? form?.goldCash < 500000: false) : false"
|
<div v-if="isBuy? ((form?.goldValue || form.goldValue === 0)? form?.goldCash < 500000: false) : false"
|
||||||
@@ -128,7 +112,7 @@
|
|||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div>کارمزد:</div>
|
<div>کارمزد:</div>
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex gap-2 items-center">
|
||||||
<div class="">{{ parseFloat(((form.goldValue ?? 0) * 0.01).toFixed(4)) }}</div>
|
<div class="">{{ feeInGrams }}</div>
|
||||||
<!-- <div class="font-bold text-2xl">{{ 0 }}</div>-->
|
<!-- <div class="font-bold text-2xl">{{ 0 }}</div>-->
|
||||||
<div>
|
<div>
|
||||||
گرم
|
گرم
|
||||||
@@ -154,25 +138,39 @@
|
|||||||
<div class="flex justify-between ">
|
<div class="flex justify-between ">
|
||||||
<div>وزن:</div>
|
<div>وزن:</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="font-bold">{{ form.goldValue }}</div>
|
<div class="font-bold">{{ displayTradeGoldAmount }}</div>
|
||||||
<div class="text-xs mr-1">گرم</div>
|
<div class="text-xs mr-1">گرم</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between mt-3">
|
<div class="flex justify-between mt-3">
|
||||||
<div>مظنه:</div>
|
<div>مظنه:</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="font-bold">{{ usePriceWithoutType(isBuy ? price.buy ?? 0 : price.sell ?? 0) }}</div>
|
<div class="font-bold">{{ usePriceWithoutType(Math.round(goldPriceValue)) }}</div>
|
||||||
<div class="text-xs mr-1">ریال</div>
|
<div class="text-xs mr-1">ریال</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between mt-3">
|
<div class="flex justify-between mt-3">
|
||||||
<div>کارمزد:</div>
|
<div>کارمزد:</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="font-bold text-xl">{{ parseFloat(((form.goldValue ?? 0) * 0.01).toFixed(4)) }}</div>
|
<div class="font-bold text-xl">{{ feeInGrams }}</div>
|
||||||
<div class="text-xs mr-1">گرم</div>
|
<div class="text-xs mr-1">گرم</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div >(معادل {{ equivalentPrice }} ریال)</div>
|
<div >(معادل {{ equivalentPrice }} ریال)</div>
|
||||||
|
<div v-if="isBuy && feeDeductFromGold" class="flex justify-between mt-3">
|
||||||
|
<div>طلا دریافتی:</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="font-bold">{{ displayTradeGoldAmount }}</div>
|
||||||
|
<div class="text-xs mr-1">گرم (دقیق — کارمزد از ریال)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!isBuy && feeDeductFromGold" class="flex justify-between mt-3">
|
||||||
|
<div>کسر از کیف طلا:</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="font-bold">{{ sellGoldDebit }}</div>
|
||||||
|
<div class="text-xs mr-1">گرم</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="flex justify-between mt-3">
|
<div class="flex justify-between mt-3">
|
||||||
<div>{{ isBuy ? 'قیمت قابل پرداخت:' : 'مبلغ دریافتی:' }}</div>
|
<div>{{ isBuy ? 'قیمت قابل پرداخت:' : 'مبلغ دریافتی:' }}</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -203,10 +201,10 @@
|
|||||||
|
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import img1 from '@/assets/img/home_ly2.svg'
|
import img1 from '@/assets/img/home_ly2.svg'
|
||||||
import { usePrice, usePriceWithoutType } from '@/utils/usePrice'
|
import { usePrice, usePriceWithoutType, formatGoldAmount } from '@/utils/usePrice'
|
||||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||||
import type { Gold } from '@/models/gold'
|
import type { Gold } from '@/models/gold'
|
||||||
import { isIphone, loading, loadingMore, toFixedDownThreeChar } from '@/utils'
|
import { isIphone, loading, loadingMore } from '@/utils'
|
||||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { useWalletStore } from '@/store/walletStore'
|
import { useWalletStore } from '@/store/walletStore'
|
||||||
@@ -228,12 +226,27 @@ import type { Transaction } from '@/models/transaction'
|
|||||||
import type { TableEngineInput, TableOutput } from '@/models/table'
|
import type { TableEngineInput, TableOutput } from '@/models/table'
|
||||||
import { useInfiniteScroll } from '@vueuse/core'
|
import { useInfiniteScroll } from '@vueuse/core'
|
||||||
import { getTransactionTable } from '@/api/transaction'
|
import { getTransactionTable } from '@/api/transaction'
|
||||||
import { getLastPrices, lastPrice, setIntervalGetAllPrice, stopPriceInterval } from '@/utils/price'
|
import { getLastPrices, getTradeBuyPrice, getTradeSellPrice, lastPrice, setIntervalGetAllPrice, stopPriceInterval } from '@/utils/price'
|
||||||
|
import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours'
|
||||||
import type { Product } from '@/models/products'
|
import type { Product } from '@/models/products'
|
||||||
import { useFundUserStore } from '@/store/fundStore'
|
import { useFundUserStore } from '@/store/fundStore'
|
||||||
|
import {
|
||||||
|
calcFeeInGrams,
|
||||||
|
calcGramFromLockedRial,
|
||||||
|
calcOrderGoldAmount,
|
||||||
|
calcPayablePriceRial,
|
||||||
|
calcRialFromLockedGram,
|
||||||
|
calcSellGoldDebit,
|
||||||
|
calcSellTargetCash,
|
||||||
|
calcTradeTargetCash,
|
||||||
|
feeDeductFromGold,
|
||||||
|
formatTradeGramDisplay,
|
||||||
|
loadServiceFeeFromSettings,
|
||||||
|
normalizeOrderGoldAmount,
|
||||||
|
serviceFeePercentage,
|
||||||
|
type TradeInputLock,
|
||||||
|
} from '@/utils/serviceFee'
|
||||||
import Bg_main from '@/components/bg_main.vue'
|
import Bg_main from '@/components/bg_main.vue'
|
||||||
import Vue3Odometer from 'vue3-odometer'
|
|
||||||
import 'odometer/themes/odometer-theme-default.css'
|
|
||||||
const infoVisible = ref(false)
|
const infoVisible = ref(false)
|
||||||
const walletStore = useWalletStore()
|
const walletStore = useWalletStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@@ -242,7 +255,6 @@ const confirmAction = ref<boolean>(false)
|
|||||||
const noCompleteVisible = ref<boolean>(false)
|
const noCompleteVisible = ref<boolean>(false)
|
||||||
const buyItems = ref<Transaction[]>([])
|
const buyItems = ref<Transaction[]>([])
|
||||||
const sellItems = ref<Transaction[]>([])
|
const sellItems = ref<Transaction[]>([])
|
||||||
const priceErrorVisible = ref<boolean>(false)
|
|
||||||
const buyTableOutput = ref<TableOutput<Transaction> | null>(null)
|
const buyTableOutput = ref<TableOutput<Transaction> | null>(null)
|
||||||
const sellTableOutput = ref<TableOutput<Transaction> | null>(null)
|
const sellTableOutput = ref<TableOutput<Transaction> | null>(null)
|
||||||
const el = ref()
|
const el = ref()
|
||||||
@@ -253,6 +265,7 @@ interface Prop {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Prop>(), {})
|
const props = withDefaults(defineProps<Prop>(), {})
|
||||||
const isBuy = ref<boolean>(true)
|
const isBuy = ref<boolean>(true)
|
||||||
|
const inputLock = ref<TradeInputLock>(null)
|
||||||
|
|
||||||
const buyPagination = ref<TableEngineInput>({
|
const buyPagination = ref<TableEngineInput>({
|
||||||
filters: [{
|
filters: [{
|
||||||
@@ -273,20 +286,52 @@ const sellPagination = ref<TableEngineInput>({
|
|||||||
}], limit: 6, page: 0
|
}], limit: 6, page: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const finalPrice = computed(() => (form.value?.goldValue * (isBuy.value ? price.value?.buy ?? 0 : price.value?.sell ?? 0)))
|
const unitPrice = computed(() =>
|
||||||
// const payablePrice = computed(() => finalPrice.value + finalPrice.value * 0.007)
|
isBuy.value ? getTradeBuyPrice(lastPrice.value) : getTradeSellPrice(lastPrice.value)
|
||||||
const payablePrice = computed(() => finalPrice.value)
|
)
|
||||||
const equivalentPrice = computed(() => usePriceWithoutType(((isBuy ? price.value?.buy ?? 0 : price.value?.sell ?? 0) * (parseFloat(((form.value?.goldValue ?? 0) * 0.01).toFixed(4))) ).toFixed(0)) )
|
const orderGoldAmount = computed(() =>
|
||||||
|
calcOrderGoldAmount(
|
||||||
|
form.value?.goldValue,
|
||||||
|
form.value?.goldCash,
|
||||||
|
unitPrice.value,
|
||||||
|
isBuy.value,
|
||||||
|
inputLock.value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const feeInGrams = computed(() => calcFeeInGrams(orderGoldAmount.value))
|
||||||
|
const finalPrice = computed(() => orderGoldAmount.value * unitPrice.value)
|
||||||
|
const feeInRial = computed(() => unitPrice.value * feeInGrams.value)
|
||||||
|
const equivalentPrice = computed(() => usePriceWithoutType(feeInRial.value.toFixed(0)))
|
||||||
|
const payablePrice = computed(() =>
|
||||||
|
calcPayablePriceRial(
|
||||||
|
finalPrice.value,
|
||||||
|
feeInRial.value,
|
||||||
|
isBuy.value,
|
||||||
|
inputLock.value === 'rial' ? form.value?.goldCash : undefined
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const sellGoldDebit = computed(() => calcSellGoldDebit(orderGoldAmount.value, feeInGrams.value))
|
||||||
|
|
||||||
|
const displayTradeGoldAmount = computed(() =>
|
||||||
|
formatTradeGramDisplay(orderGoldAmount.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
const priceUnavailable = computed(() => {
|
||||||
|
const unit = unitPrice.value
|
||||||
|
return !unit || Number.isNaN(unit) || unit <= 0
|
||||||
|
})
|
||||||
|
|
||||||
const disableTransaction = computed(() =>
|
const disableTransaction = computed(() =>
|
||||||
loading.value ||
|
loading.value ||
|
||||||
|
isStoreClosed() ||
|
||||||
form.value?.goldValue === undefined ||
|
form.value?.goldValue === undefined ||
|
||||||
form.value?.goldValue === '' ||
|
form.value?.goldValue === '' ||
|
||||||
form.value?.goldCash === '' ||
|
form.value?.goldCash === '' ||
|
||||||
form.value?.goldCash === undefined ||
|
form.value?.goldCash === undefined ||
|
||||||
(isBuy.value ? (form.value?.goldCash ?? 0) < 500000 : (form.value?.goldValue ?? 0) < 0.001) ||
|
(isBuy.value ? (form.value?.goldCash ?? 0) < 500000 : (form.value?.goldValue ?? 0) < 0.001) ||
|
||||||
(!isBuy.value && form.value.goldValue > walletStore?.walletInfo?.gold) ||
|
(!isBuy.value && sellGoldDebit.value > (walletStore?.walletInfo?.gold ?? 0)) ||
|
||||||
price.value?.buy === undefined)
|
(isBuy.value && payablePrice.value > (walletStore?.walletInfo?.cash ?? 0)) ||
|
||||||
|
priceUnavailable.value)
|
||||||
|
|
||||||
function refreshTable() {
|
function refreshTable() {
|
||||||
sellItems.value = []
|
sellItems.value = []
|
||||||
@@ -388,6 +433,7 @@ onMounted(async () => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
await initPrice()
|
await initPrice()
|
||||||
|
await loadServiceFeeFromSettings()
|
||||||
/* await initSellTable()
|
/* await initSellTable()
|
||||||
await initBuyTable()*/
|
await initBuyTable()*/
|
||||||
})
|
})
|
||||||
@@ -482,20 +528,18 @@ onUnmounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function percentChange(val: number) {
|
function percentChange(val: number) {
|
||||||
if (val !== 0)
|
if (val !== 0) {
|
||||||
if (isBuy.value) {
|
if (isBuy.value) {
|
||||||
form.value.goldCash = unitCashCalculator.value * val
|
form.value.goldCash = unitCashCalculator.value * val
|
||||||
calculateGold(form.value.goldCash)
|
calculateGold(form.value.goldCash)
|
||||||
calculateCash(form.value.goldValue)
|
|
||||||
} else {
|
} else {
|
||||||
// console.log((unitGoldCalculator.value * val).toFixed(2))
|
form.value.goldValue = formatTradeGramDisplay(parseFloat(String(unitGoldCalculator.value * val)))
|
||||||
// form.value.goldValue = parseFloat((unitGoldCalculator.value * val).toFixed(2))
|
|
||||||
form.value.goldValue = toFixedDownThreeChar(parseFloat(unitGoldCalculator.value * val))
|
|
||||||
calculateCash(form.value.goldValue)
|
calculateCash(form.value.goldValue)
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
form.value.goldValue = undefined
|
form.value.goldValue = undefined
|
||||||
form.value.goldCash = undefined
|
form.value.goldCash = undefined
|
||||||
|
inputLock.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,55 +552,61 @@ const unitCashCalculator = computed(() => {
|
|||||||
return (walletStore?.walletInfo?.cash ?? 0) / 100
|
return (walletStore?.walletInfo?.cash ?? 0) / 100
|
||||||
})
|
})
|
||||||
|
|
||||||
const limitedPrice = computed(() => {
|
function syncFormWithLatestPrice() {
|
||||||
if (walletStore.limitedPrice)
|
const unit = unitPrice.value
|
||||||
return walletStore?.limitedPrice[0]
|
if (!unit || Number.isNaN(unit) || !inputLock.value) return
|
||||||
})
|
|
||||||
|
|
||||||
function calculateCash(val: number) {
|
if (inputLock.value === 'gram') {
|
||||||
priceErrorVisible.value = false
|
const goldValue = form.value.goldValue
|
||||||
// console.log(val)
|
if (goldValue !== undefined && goldValue !== null && goldValue !== '' && !isNaN(Number(goldValue))) {
|
||||||
if (price.value) {
|
calculateCash(Number(goldValue))
|
||||||
if (isBuy.value) {
|
|
||||||
if (val) form.value.goldCash = parseFloat((val * price?.value?.buy).toFixed(0))
|
|
||||||
else form.value.goldCash = undefined
|
|
||||||
} else {
|
|
||||||
if (val) form.value.goldCash = parseFloat((val * price?.value?.sell).toFixed(0))
|
|
||||||
else form.value.goldCash = undefined
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const goldCash = form.value.goldCash
|
||||||
|
if (goldCash !== undefined && goldCash !== null && goldCash !== '' && !isNaN(Number(goldCash))) {
|
||||||
|
calculateGold(Number(goldCash))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [unitPrice.value, isBuy.value],
|
||||||
|
() => {
|
||||||
|
syncFormWithLatestPrice()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch([feeDeductFromGold, () => serviceFeePercentage.value], () => {
|
||||||
|
syncFormWithLatestPrice()
|
||||||
|
})
|
||||||
|
|
||||||
|
function calculateCash(val: number) {
|
||||||
|
inputLock.value = 'gram'
|
||||||
|
const unit = unitPrice.value
|
||||||
|
if (!unit || Number.isNaN(unit)) return
|
||||||
|
|
||||||
|
if (val === undefined || val === null || Number.isNaN(val)) {
|
||||||
|
form.value.goldCash = undefined
|
||||||
|
inputLock.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
form.value.goldCash = calcRialFromLockedGram(val, unit, isBuy.value)
|
||||||
|
}
|
||||||
|
|
||||||
function calculateGold(val: number) {
|
function calculateGold(val: number) {
|
||||||
// console.log(val)
|
inputLock.value = 'rial'
|
||||||
// form.value.goldCash = usePriceWithoutType(form.value.goldCash)
|
const unit = unitPrice.value
|
||||||
if (price.value) {
|
if (!unit || Number.isNaN(unit)) return
|
||||||
if (isBuy.value) {
|
|
||||||
if (val) {
|
if (val) {
|
||||||
form.value.goldValue = toFixedDownThreeChar(parseFloat(val / price?.value?.buy))
|
form.value.goldValue = formatTradeGramDisplay(calcGramFromLockedRial(val, unit, isBuy.value))
|
||||||
if (parseInt(finalPrice.value) > 0) priceErrorVisible.value = true
|
} else if (val.toString() === '') {
|
||||||
if (parseInt(finalPrice.value) === 0) priceErrorVisible.value = false
|
form.value.goldValue = undefined
|
||||||
} else if (val.toString() === '') {
|
inputLock.value = null
|
||||||
form.value.goldValue = undefined
|
} else {
|
||||||
priceErrorVisible.value = false
|
form.value.goldValue = 0
|
||||||
} else {
|
|
||||||
form.value.goldValue = 0
|
|
||||||
priceErrorVisible.value = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (val) {
|
|
||||||
form.value.goldValue = toFixedDownThreeChar(parseFloat((val / price?.value?.sell)))
|
|
||||||
if (parseInt(finalPrice.value) > 0) priceErrorVisible.value = true
|
|
||||||
if (parseInt(finalPrice.value) === 0) priceErrorVisible.value = false
|
|
||||||
} else if (val.toString() === '') {
|
|
||||||
// console.log('sasasasasasa')
|
|
||||||
form.value.goldValue = undefined
|
|
||||||
priceErrorVisible.value = false
|
|
||||||
} else {
|
|
||||||
form.value.goldValue = 0
|
|
||||||
priceErrorVisible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,7 +628,7 @@ function setIsBuy(val: boolean) {
|
|||||||
form.value.goldValue = undefined
|
form.value.goldValue = undefined
|
||||||
form.value.goldCash = undefined
|
form.value.goldCash = undefined
|
||||||
form.value.percentWallet = 0
|
form.value.percentWallet = 0
|
||||||
priceErrorVisible.value = false
|
inputLock.value = null
|
||||||
// console.log(bganim.value.classList)
|
// console.log(bganim.value.classList)
|
||||||
// bganim?.value?.classList.add(val?'right':'left')
|
// bganim?.value?.classList.add(val?'right':'left')
|
||||||
// bganim?.value?.style.transform = `translateX(${val ? '0' : '-100'});`
|
// bganim?.value?.style.transform = `translateX(${val ? '0' : '-100'});`
|
||||||
@@ -594,10 +644,7 @@ const formatter = (value: number) => {
|
|||||||
|
|
||||||
const price = computed((): Price => <Price>lastPrice.value ?? 0)
|
const price = computed((): Price => <Price>lastPrice.value ?? 0)
|
||||||
|
|
||||||
const displayedGoldPrice = computed(() => {
|
const goldPriceValue = computed(() => unitPrice.value)
|
||||||
if (!lastPrice.value) return 0
|
|
||||||
return isBuy.value ? (lastPrice.value.buy ?? 0) : (lastPrice.value.sell ?? 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
async function initPrice() {
|
async function initPrice() {
|
||||||
try {
|
try {
|
||||||
@@ -613,17 +660,32 @@ async function initPrice() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function okConfirm() {
|
function okConfirm() {
|
||||||
|
if (isStoreClosed()) {
|
||||||
|
message.warning(getStoreClosedMessage())
|
||||||
|
return
|
||||||
|
}
|
||||||
confirmAction.value = true
|
confirmAction.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ok() {
|
async function ok() {
|
||||||
|
if (isStoreClosed()) {
|
||||||
|
message.warning(getStoreClosedMessage())
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
infoVisible.value = true
|
infoVisible.value = true
|
||||||
await createOrder({
|
await createOrder({
|
||||||
direction: isBuy.value ? 1 : 2,
|
direction: isBuy.value ? 1 : 2,
|
||||||
price: isBuy.value ? price.value?.buy : price.value?.sell,
|
price: Math.round(unitPrice.value),
|
||||||
amount: form.value.goldValue ?? 0
|
amount: normalizeOrderGoldAmount(orderGoldAmount.value),
|
||||||
|
target_cash: calcTradeTargetCash(form.value.goldCash, inputLock.value)
|
||||||
|
?? calcSellTargetCash(
|
||||||
|
form.value.goldCash,
|
||||||
|
form.value.goldValue,
|
||||||
|
unitPrice.value,
|
||||||
|
inputLock.value
|
||||||
|
),
|
||||||
})
|
})
|
||||||
await walletStore.getMyWallet()
|
await walletStore.getMyWallet()
|
||||||
await router.push({ name: 'transactions' })
|
await router.push({ name: 'transactions' })
|
||||||
@@ -632,6 +694,7 @@ async function ok() {
|
|||||||
goldCash: undefined,
|
goldCash: undefined,
|
||||||
percentWallet: undefined
|
percentWallet: undefined
|
||||||
}
|
}
|
||||||
|
inputLock.value = null
|
||||||
// refreshTable()
|
// refreshTable()
|
||||||
message.success('سفارش شما با موفقیت انجام شد')
|
message.success('سفارش شما با موفقیت انجام شد')
|
||||||
await fundStore.getMyFund()
|
await fundStore.getMyFund()
|
||||||
@@ -647,7 +710,7 @@ async function ok() {
|
|||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.completeButtonStyle .van-dialog__confirm .van-button__content {
|
.completeButtonStyle .van-dialog__confirm .van-button__content {
|
||||||
background-color: var(--app-primary-color);
|
background-color: #a72b77;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,14 +743,4 @@ async function ok() {
|
|||||||
right: 100%;
|
right: 100%;
|
||||||
transform: translate(100%, 0);
|
transform: translate(100%, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gold-live-price {
|
|
||||||
:deep(.odometer) {
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.odometer-digit) {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="props.ProductItem.wallet" class="bg-gradient-to-l from-primary rounded-lg p-px to-transparent ">
|
<div v-if="props.ProductItem.wallet" class="bg-gradient-to-l from-primary rounded-lg p-px to-transparent ">
|
||||||
<div class="flex flex-col gap-7 rounded-lg p-4 w-56 bg-white ">
|
<div class="flex flex-col gap-7 rounded-lg p-4 w-56 bg-white ">
|
||||||
<div class="flex justify-between items-center font-medium text-sm">
|
<div class="flex justify-between items-center font-medium text-sm">
|
||||||
<div class="text-primary">موجودی {{ props.ProductItem.title }}</div>
|
<div class="text-primary">موجودی {{ props.ProductItem.title }}</div>
|
||||||
<BasicIcon v-if="props.ProductItem.group_type=='گوی'" stroke-width="1" size="50" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:grammerly"/>
|
<BasicIcon v-if="props.ProductItem.group_type=='گوی'" stroke-width="1" size="50" color="#a72b77" class="rotate-310" icon="vuesax-linear:grammerly"/>
|
||||||
<BasicIcon v-else-if="props.ProductItem.group_type=='سکه'" stroke-width="1" size="50" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:coin"/>
|
<BasicIcon v-else-if="props.ProductItem.group_type=='سکه'" stroke-width="1" size="50" color="#a72b77" class="rotate-310" icon="vuesax-linear:coin"/>
|
||||||
<BasicIcon v-else-if="props.ProductItem.group_type=='شمش'" stroke-width="1" size="50" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:slider-vertical"/>
|
<BasicIcon v-else-if="props.ProductItem.group_type=='شمش'" stroke-width="1" size="50" color="#a72b77" class="rotate-310" icon="vuesax-linear:slider-vertical"/>
|
||||||
<BasicIcon v-else-if="props.ProductItem.group_type=='مکعب'" stroke-width="1" size="50" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:keyboard"/>
|
<BasicIcon v-else-if="props.ProductItem.group_type=='مکعب'" stroke-width="1" size="50" color="#a72b77" class="rotate-310" icon="vuesax-linear:keyboard"/>
|
||||||
<BasicIcon v-else-if="props.ProductItem.group_type=='آبشده'" stroke-width="1" size="50" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:slider-vertical"/>
|
<BasicIcon v-else-if="props.ProductItem.group_type=='آبشده'" stroke-width="1" size="50" color="#a72b77" class="rotate-310" icon="vuesax-linear:slider-vertical"/>
|
||||||
<BasicIcon v-else stroke-width="1" size="50" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:slider-vertical"/>
|
<BasicIcon v-else stroke-width="1" size="50" color="#a72b77" class="rotate-310" icon="vuesax-linear:slider-vertical"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between px-2 items-center ">
|
<div class="flex justify-between px-2 items-center ">
|
||||||
<BasicIcon v-if="props.ProductItem.group_type=='گوی'" stroke-width="1" size="26" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:grammerly"/>
|
<BasicIcon v-if="props.ProductItem.group_type=='گوی'" stroke-width="1" size="26" color="#a72b77" class="rotate-310" icon="vuesax-linear:grammerly"/>
|
||||||
<BasicIcon v-else-if="props.ProductItem.group_type=='سکه'" stroke-width="1" size="26" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:coin"/>
|
<BasicIcon v-else-if="props.ProductItem.group_type=='سکه'" stroke-width="1" size="26" color="#a72b77" class="rotate-310" icon="vuesax-linear:coin"/>
|
||||||
<BasicIcon v-else-if="props.ProductItem.group_type=='شمش'" stroke-width="1" size="26" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:slider-vertical"/>
|
<BasicIcon v-else-if="props.ProductItem.group_type=='شمش'" stroke-width="1" size="26" color="#a72b77" class="rotate-310" icon="vuesax-linear:slider-vertical"/>
|
||||||
<BasicIcon v-else-if="props.ProductItem.group_type=='مکعب'" stroke-width="1" size="26" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:keyboard"/>
|
<BasicIcon v-else-if="props.ProductItem.group_type=='مکعب'" stroke-width="1" size="26" color="#a72b77" class="rotate-310" icon="vuesax-linear:keyboard"/>
|
||||||
<BasicIcon v-else stroke-width="1" size="26" color="var(--app-primary-color)" class="rotate-310" icon="vuesax-linear:slider-vertical"/> <div class="flex justify-between gap-2 items-center">
|
<BasicIcon v-else stroke-width="1" size="26" color="#a72b77" class="rotate-310" icon="vuesax-linear:slider-vertical"/> <div class="flex justify-between gap-2 items-center">
|
||||||
<span v-if="props.ProductItem.group_type=='آبشده'" class="font-bold text-secondary">{{userWallet.walletInfo?.gold??0}}</span>
|
<span v-if="props.ProductItem.group_type=='آبشده'" class="font-bold text-secondary">{{userWallet.walletInfo?.gold??0}}</span>
|
||||||
<span v-else class="font-bold text-secondary">{{props?.ProductItem.wallet?.gold??0}}</span>
|
<span v-else class="font-bold text-secondary">{{props?.ProductItem.wallet?.gold??0}}</span>
|
||||||
<span class="text-gray-400">{{props?.ProductItem?.unit}}</span>
|
<span class="text-gray-400">{{props?.ProductItem?.unit}}</span>
|
||||||
|
|||||||
@@ -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">قیمت خرید از {{ projectName }}</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"
|
||||||
@@ -160,7 +160,7 @@ 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 { resolveAssetsRialValue } from '@/utils/productAssets'
|
import { resolveAssetsRialValue } from '@/utils/productAssets'
|
||||||
import { usePrice, usePriceWithoutType, useTowDecimal } from '@/utils/usePrice'
|
import { usePrice, usePriceWithoutType, useTowDecimal, formatGoldAmount } from '@/utils/usePrice'
|
||||||
import {
|
import {
|
||||||
getLastPrices,
|
getLastPrices,
|
||||||
getTradeBuyPrice,
|
getTradeBuyPrice,
|
||||||
@@ -174,15 +174,12 @@ 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 { getProjectDisplayName, hasAbshodeTrading } from '@/config/project-profile'
|
import { useProject } from '@/composables/useProject'
|
||||||
import { getAppPrimaryColor } from '@/config/brand-theme'
|
|
||||||
|
|
||||||
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()
|
||||||
const projectName = getProjectDisplayName('کشکول گلد')
|
|
||||||
const primaryColor = getAppPrimaryColor()
|
|
||||||
|
|
||||||
const goldVal = ref<number | undefined>()
|
const goldVal = ref<number | undefined>()
|
||||||
const selection = ref<'DAILY' | 'WEEKLY' | 'MONTHLY'>('DAILY')
|
const selection = ref<'DAILY' | 'WEEKLY' | 'MONTHLY'>('DAILY')
|
||||||
@@ -193,7 +190,7 @@ const priceChartOptions = ref({
|
|||||||
dataLabels: { enabled: false },
|
dataLabels: { enabled: false },
|
||||||
yaxis: { show: false },
|
yaxis: { show: false },
|
||||||
xaxis: { type: 'datetime', labels: { show: false }, axisBorder: { show: false } },
|
xaxis: { type: 'datetime', labels: { show: false }, axisBorder: { show: false } },
|
||||||
colors: [primaryColor],
|
colors: ['#a72b77'],
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient',
|
type: 'gradient',
|
||||||
gradient: { shadeIntensity: 1, opacityFrom: 0.7, opacityTo: 0.9, stops: [0, 100] },
|
gradient: { shadeIntensity: 1, opacityFrom: 0.7, opacityTo: 0.9, stops: [0, 100] },
|
||||||
@@ -214,40 +211,23 @@ const balanceLabel = computed(() => {
|
|||||||
}
|
}
|
||||||
const metal = primaryMetalTotal.value
|
const metal = primaryMetalTotal.value
|
||||||
if (metal && Number(metal.total_weight) > 0) {
|
if (metal && Number(metal.total_weight) > 0) {
|
||||||
return `${Number(metal.total_weight).toFixed(3)} ${metal.unit}`
|
return `${formatGoldAmount(metal.total_weight)} ${metal.unit}`
|
||||||
}
|
}
|
||||||
const fallback = walletStore.walletInfo?.gold_sum ?? userStore.userInfo?.wallet?.count_gold ?? 0
|
const fallback = walletStore.walletInfo?.gold_sum ?? userStore.userInfo?.wallet?.count_gold ?? 0
|
||||||
return `${Number(fallback).toFixed(3)} گرم`
|
return `${formatGoldAmount(fallback)} گرم`
|
||||||
})
|
})
|
||||||
|
|
||||||
const totalRialDisplay = computed(() =>
|
const totalRialDisplay = computed(() =>
|
||||||
resolveAssetsRialValue(assetsSummary.value, getTradeBuyPrice(price.value) || Number(goldVal.value ?? 0))
|
resolveAssetsRialValue(assetsSummary.value, getTradeBuyPrice(price.value) || Number(goldVal.value ?? 0))
|
||||||
)
|
)
|
||||||
|
|
||||||
const mainActions = computed(() => {
|
const mainActions = [
|
||||||
const actions = [
|
{ 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: 'deposit', icon: 'vuesax-linear:card-add', label: 'واریز نقدی', handler: () => router.push({ name: 'wallet', query: { state: 'deposit' } }) },
|
||||||
{ name: 'balance', icon: 'vuesax-linear:receipt-item', label: 'موجودی', handler: () => emitBalance() },
|
{ name: 'balance', icon: 'vuesax-linear:receipt-item', label: 'موجودی', handler: () => emitBalance() },
|
||||||
{ name: 'fund', icon: 'vuesax-linear:archive-1', label: 'دارایی ها', handler: () => router.push({ name: 'fund' }) },
|
{ 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' }) },
|
||||||
|
]
|
||||||
if (hasAbshodeTrading()) {
|
|
||||||
actions.unshift({
|
|
||||||
name: 'buySell',
|
|
||||||
icon: 'vuesax-linear:money-receive',
|
|
||||||
label: 'خرید و فروش',
|
|
||||||
handler: () => router.push({ name: 'gold' }),
|
|
||||||
})
|
|
||||||
actions.push({
|
|
||||||
name: 'earn',
|
|
||||||
icon: 'vuesax-linear:dollar-circle',
|
|
||||||
label: 'کسب درآمد',
|
|
||||||
handler: () => router.push({ name: 'earnMoney' }),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions
|
|
||||||
})
|
|
||||||
|
|
||||||
function emitBalance() {
|
function emitBalance() {
|
||||||
emit('openBalance')
|
emit('openBalance')
|
||||||
@@ -284,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>
|
||||||
|
|||||||
@@ -1,36 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject } from 'vue'
|
import { inject } from 'vue'
|
||||||
import { useUserStore } from '@/store/userStore'
|
|
||||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
|
||||||
import { isBoursecuProject } from '@/utils/config'
|
import { isBoursecuProject } from '@/utils/config'
|
||||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
|
||||||
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
|
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
|
||||||
|
|
||||||
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
|
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
|
||||||
const userStore = useUserStore()
|
|
||||||
const isBoursecu = isBoursecuProject()
|
const isBoursecu = isBoursecuProject()
|
||||||
|
|
||||||
const showLimitedPrice = computed(
|
|
||||||
() =>
|
|
||||||
ctx.limitedPrice.value !== undefined &&
|
|
||||||
userStore.userInfo?.trade_active !== 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
const limitedPriceText = computed(() =>
|
|
||||||
usePriceWithoutType(Number(ctx.limitedPrice.value?.value ?? 0)),
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="showLimitedPrice || ctx.showClosedMessage.value" class="dhome-alerts">
|
<div v-if="!isBoursecu && ctx.showClosedMessage.value" class="dhome-alerts">
|
||||||
<div v-if="showLimitedPrice" class="dhome-alert dhome-alert--warning">
|
|
||||||
<BasicIcon icon="vuesax-linear:info-circle" size="14" />
|
|
||||||
<span>
|
|
||||||
حداقل مبلغ اولیه برای ثبت سفارش {{ limitedPriceText }} ریال میباشد.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-if="!isBoursecu && ctx.showClosedMessage.value"
|
|
||||||
class="dhome-alert dhome-alert--info"
|
class="dhome-alert dhome-alert--info"
|
||||||
v-html="ctx.closedMessage.value"
|
v-html="ctx.closedMessage.value"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop home — SaaS dashboard design tokens */
|
/* Premium desktop home — SaaS dashboard design tokens */
|
||||||
|
|
||||||
.dhome {
|
.dhome {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -420,7 +420,7 @@
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
color: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
background: @theme-primary;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,19 @@
|
|||||||
<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"
|
||||||
class="bg-white w-full md:w-auto h-fit px-5 m-[-1px]"
|
class="bg-white w-full md:w-auto h-fit px-5 m-[-1px]"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
v-if="limitedPrice !== undefined && userStore?.userInfo.trade_active !== 1"
|
|
||||||
class="text-red text-xs flex gap-1 items-center justify-center mb-2"
|
|
||||||
>
|
|
||||||
<Icon icon="vuesax-linear:info-circle" size="16" />
|
|
||||||
حداقل مبلغ اولیه برای ثبت سفارش
|
|
||||||
{{ usePriceWithoutType(parseInt(limitedPrice.value) ?? 0) }} ریال می باشد.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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, ${primaryColor} 0%, ${primaryDark} 40%, ${primaryColor} 100%)` }"
|
:style="{ background: heroGradient }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="absolute inset-0 opacity-20"
|
class="absolute inset-0 opacity-20"
|
||||||
@@ -32,19 +23,14 @@
|
|||||||
|
|
||||||
<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
|
<img src='@/assets/img/logo.png' alt='' class='h-8 drop-shadow-md' />
|
||||||
:src="appIconUrl"
|
<div class="tracking-wide">{{ projectNameFa }}</div>
|
||||||
alt=""
|
|
||||||
class="h-8 drop-shadow-md"
|
|
||||||
:class="{ 'home-brand-logo--white': isKashkool }"
|
|
||||||
/>
|
|
||||||
<div class="tracking-wide">{{ projectName }}</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;">شناسه شما در {{ projectName }}</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
|
||||||
@@ -89,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 var(--app-primary-color, #a72b77)"
|
:style="actionBorderStyle"
|
||||||
|
|
||||||
>
|
>
|
||||||
<Icon class="text-tertiary" icon="vuesax-linear:money-send" />
|
<Icon class="text-tertiary" icon="vuesax-linear:money-send" />
|
||||||
@@ -97,14 +83,13 @@
|
|||||||
<div class="text-primary">فروش</div>
|
<div class="text-primary">فروش</div>
|
||||||
</div>-->
|
</div>-->
|
||||||
<div
|
<div
|
||||||
v-if="showAbshodeTrading"
|
|
||||||
class="text-center w-full flex-flex-col items-center home_appear_fade_in_up"
|
class="text-center w-full flex-flex-col items-center home_appear_fade_in_up"
|
||||||
style="text-align: -webkit-center"
|
style="text-align: -webkit-center"
|
||||||
@click="router.push({ name: 'gold' })"
|
@click="router.push({ name: 'gold' })"
|
||||||
>
|
>
|
||||||
<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 var(--app-primary-color, #a72b77)"
|
:style="actionBorderStyle"
|
||||||
|
|
||||||
>
|
>
|
||||||
<Icon class="text-white" icon="vuesax-linear:money-receive" />
|
<Icon class="text-white" icon="vuesax-linear:money-receive" />
|
||||||
@@ -118,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 var(--app-primary-color, #a72b77)"
|
:style="actionBorderStyle"
|
||||||
|
|
||||||
>
|
>
|
||||||
<Icon class="text-white" icon="vuesax-linear:card-add" />
|
<Icon class="text-white" icon="vuesax-linear:card-add" />
|
||||||
@@ -132,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 var(--app-primary-color, #a72b77)"
|
:style="actionBorderStyle"
|
||||||
>
|
>
|
||||||
<Icon class="text-white" icon="vuesax-linear:receipt-item" />
|
<Icon class="text-white" icon="vuesax-linear:receipt-item" />
|
||||||
</div>
|
</div>
|
||||||
@@ -145,21 +130,20 @@
|
|||||||
>
|
>
|
||||||
<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 var(--app-primary-color, #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>
|
||||||
<div class="text-primary">دارایی ها</div>
|
<div class="text-primary">دارایی ها</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="showAbshodeTrading"
|
|
||||||
class="text-center w-full flex-flex-col items-center home_appear_fade_in_down"
|
class="text-center w-full flex-flex-col items-center home_appear_fade_in_down"
|
||||||
style="text-align: -webkit-center"
|
style="text-align: -webkit-center"
|
||||||
@click="router.push({ name: 'earnMoney' })"
|
@click="router.push({ name: 'earnMoney' })"
|
||||||
>
|
>
|
||||||
<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 var(--app-primary-color, #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" />
|
||||||
@@ -261,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">قیمت خرید از {{ projectName }}</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">
|
||||||
<!– {{ usePriceWithoutType(useCounterAnim(price.price_last) ?? 0) }}–>
|
<!– {{ usePriceWithoutType(useCounterAnim(price.price_last) ?? 0) }}–>
|
||||||
@@ -453,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>
|
||||||
@@ -473,7 +457,7 @@ import { Swiper, SwiperSlide } from 'swiper/vue';
|
|||||||
import 'swiper/swiper-bundle.css';
|
import 'swiper/swiper-bundle.css';
|
||||||
import img2 from '@/assets/img/banner/2.png'
|
import img2 from '@/assets/img/banner/2.png'
|
||||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||||
import { usePrice, usePriceWithoutType, useTowDecimal } from '../../../utils/usePrice'
|
import { usePrice, usePriceWithoutType, useTowDecimal, formatGoldAmount } from '../../../utils/usePrice'
|
||||||
import {computed, createVNode, onActivated, onMounted, onUnmounted, ref, watch} from 'vue'
|
import {computed, createVNode, onActivated, onMounted, onUnmounted, ref, watch} from 'vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
@@ -487,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,
|
||||||
@@ -505,9 +490,6 @@ import type { PInventory, Product } from '@/models/products'
|
|||||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||||
import { resolveAssetsRialValue } from '@/utils/productAssets'
|
import { resolveAssetsRialValue } from '@/utils/productAssets'
|
||||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||||
import { getProjectDisplayName, hasAbshodeTrading, isKashkoolProject } from '@/config/project-profile'
|
|
||||||
import { getAppIconUrl } from '@/config/brand-assets'
|
|
||||||
import { getAppPrimaryColor, getAppPrimaryDark } from '@/config/brand-theme'
|
|
||||||
import { message, Modal } from 'ant-design-vue'
|
import { message, Modal } from 'ant-design-vue'
|
||||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||||
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
|
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
|
||||||
@@ -517,12 +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 projectName = getProjectDisplayName('کشکول گلد')
|
const { branding, projectNameFa, heroGradient, isGhatre, accentColor } = useProject()
|
||||||
const showAbshodeTrading = hasAbshodeTrading()
|
const actionBorderStyle = computed(() => ({
|
||||||
const isKashkool = isKashkoolProject()
|
border: `solid 1px ${accentColor.value}`,
|
||||||
const appIconUrl = getAppIconUrl()
|
}))
|
||||||
const primaryColor = getAppPrimaryColor()
|
|
||||||
const primaryDark = getAppPrimaryDark()
|
|
||||||
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)
|
||||||
@@ -551,14 +531,14 @@ const balanceLabel = computed(() => {
|
|||||||
|
|
||||||
const metal = primaryMetalTotal.value
|
const metal = primaryMetalTotal.value
|
||||||
if (metal && Number(metal.total_weight) > 0) {
|
if (metal && Number(metal.total_weight) > 0) {
|
||||||
return `${Number(metal.total_weight).toFixed(3)} ${metal.unit}`
|
return `${formatGoldAmount(metal.total_weight)} ${metal.unit}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallback =
|
const fallback =
|
||||||
walletStore.walletInfo?.gold_sum ??
|
walletStore.walletInfo?.gold_sum ??
|
||||||
userStore.userInfo?.wallet?.count_gold ??
|
userStore.userInfo?.wallet?.count_gold ??
|
||||||
0
|
0
|
||||||
return `${Number(fallback).toFixed(3)} گرم`
|
return `${formatGoldAmount(fallback)} گرم`
|
||||||
})
|
})
|
||||||
|
|
||||||
const totalRialDisplay = computed(() =>
|
const totalRialDisplay = computed(() =>
|
||||||
@@ -733,7 +713,7 @@ const priceChartOptions = ref({
|
|||||||
show: false
|
show: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors: [primaryColor],
|
colors: [accentColor.value],
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient',
|
type: 'gradient',
|
||||||
gradient: {
|
gradient: {
|
||||||
@@ -745,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])
|
||||||
}
|
}
|
||||||
@@ -768,10 +753,6 @@ const updateData = (timeline) => {
|
|||||||
fetchPrices()
|
fetchPrices()
|
||||||
}
|
}
|
||||||
|
|
||||||
const limitedPrice = computed(() => {
|
|
||||||
if (walletStore.limitedPrice) return walletStore?.limitedPrice[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
const test_series = [
|
const test_series = [
|
||||||
{
|
{
|
||||||
name: 'candle',
|
name: 'candle',
|
||||||
@@ -994,8 +975,4 @@ apexchart {
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-brand-logo--white {
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="">
|
<div class="">
|
||||||
<!-- <div class="bg-primary h-30 mt-[-3rem] z-100000000000"></div>-->
|
<!-- <div class="bg-primary h-30 mt-[-3rem] z-100000000000"></div>-->
|
||||||
<div class="fixed h-[60vh] bg-primary w-screen">
|
<div class="fixed h-[60vh] bg-[#a72b77] w-screen">
|
||||||
<div class="flex w-full justify-around mt-18">
|
<div class="flex w-full justify-around mt-18">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-[#E5D7CE] text-xs ">موجودی کیف طلا</div>
|
<div class="text-[#E5D7CE] text-xs ">موجودی کیف طلا</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-35rem overflow-y-auto">
|
<div class="h-35rem overflow-y-auto">
|
||||||
<!-- <div class="bg-primary h-30 mt-[-3rem] z-100000000000"></div>-->
|
<!-- <div class="bg-primary h-30 mt-[-3rem] z-100000000000"></div>-->
|
||||||
<div class="h-[60vh] bg-primary wrap-fix">
|
<div class="h-[60vh] bg-primary wrap-fix">
|
||||||
@@ -620,7 +620,7 @@ const priceChartOptions = ref({
|
|||||||
show: false
|
show: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors: ['var(--app-primary-color)'],
|
colors: ['#a72b77'],
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient',
|
type: 'gradient',
|
||||||
gradient: {
|
gradient: {
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
export type HomeLayoutId = 'default' | 'boursecu'
|
export type HomeLayoutId = 'default' | 'boursecu'
|
||||||
|
|
||||||
export type HomeSectionId =
|
export type HomeSectionId =
|
||||||
| 'limitedPriceAlert'
|
|
||||||
| 'garoosiAssets'
|
| 'garoosiAssets'
|
||||||
| 'mainActions'
|
| 'mainActions'
|
||||||
| 'boursecuQuickActions'
|
| 'boursecuQuickActions'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="bg-gray-100 m-2 rounded-lg w-18 h-18 flex justify-center p-1 shrink-0">
|
<div class="bg-gray-100 m-2 rounded-lg w-18 h-18 flex justify-center p-1 shrink-0">
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<BasicInput
|
<BasicInput
|
||||||
v-model:value="item.count"
|
v-model:value="item.count"
|
||||||
:addon-before="item.product.unit"
|
:addon-before="item.product.unit"
|
||||||
addon-bg-color="var(--app-primary-color)"
|
addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
type="text"
|
type="text"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-35rem overflow-y-auto">
|
<div class="h-35rem overflow-y-auto">
|
||||||
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
|
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
|
||||||
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
|
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
|
||||||
@@ -42,11 +42,6 @@
|
|||||||
<div v-else class="w-full h-[88px]">
|
<div v-else class="w-full h-[88px]">
|
||||||
<GoldSkeleton class="w-full" />
|
<GoldSkeleton class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="limitedPrice !== undefined && userStore?.userInfo.trade_active !== 1"
|
|
||||||
class="text-red text-xs flex gap-1 items-center justify-center my-2">
|
|
||||||
<Icon icon="vuesax-linear:info-circle" size="16" />
|
|
||||||
حداقل مبلغ اولیه برای ثبت سفارش {{ usePriceWithoutType(limitedPrice.value as number ?? 0) }} ریال می باشد.
|
|
||||||
</div>
|
|
||||||
<!-- </a-spin>-->
|
<!-- </a-spin>-->
|
||||||
<a-form ref="refInput" :model="form" class="!mt-5" name="basic" @finish="okConfirm()">
|
<a-form ref="refInput" :model="form" class="!mt-5" name="basic" @finish="okConfirm()">
|
||||||
<div class="border-gray-200 border-1 rounded-xl p-5 relative">
|
<div class="border-gray-200 border-1 rounded-xl p-5 relative">
|
||||||
@@ -65,7 +60,7 @@
|
|||||||
|
|
||||||
<a-form-item v-if="isBuy" name="goldValue"
|
<a-form-item v-if="isBuy" name="goldValue"
|
||||||
:rules="[justNumber2(props?.ProductItem?.price , form.method , props?.ProductItem?.amount, true)]">
|
:rules="[justNumber2(props?.ProductItem?.price , form.method , props?.ProductItem?.amount, true)]">
|
||||||
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="var(--app-primary-color)"
|
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="تعداد"
|
place-holder="تعداد"
|
||||||
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
||||||
@@ -73,7 +68,7 @@
|
|||||||
|
|
||||||
<a-form-item v-else name="goldValue"
|
<a-form-item v-else name="goldValue"
|
||||||
:rules="[justNumber3(props?.ProductItem?.sellPrice, productNum ,false)]">
|
:rules="[justNumber3(props?.ProductItem?.sellPrice, productNum ,false)]">
|
||||||
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="var(--app-primary-color)"
|
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="تعداد"
|
place-holder="تعداد"
|
||||||
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
||||||
@@ -505,11 +500,6 @@ const unitCashCalculator = computed(() => {
|
|||||||
return (walletStore?.walletInfo?.cash ?? 0) / 100
|
return (walletStore?.walletInfo?.cash ?? 0) / 100
|
||||||
})
|
})
|
||||||
|
|
||||||
const limitedPrice = computed(() => {
|
|
||||||
if (walletStore.limitedPrice)
|
|
||||||
return walletStore?.limitedPrice[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
function calculateCash(val: number) {
|
function calculateCash(val: number) {
|
||||||
priceErrorVisible.value = false
|
priceErrorVisible.value = false
|
||||||
|
|
||||||
@@ -694,7 +684,7 @@ watch(form.value.goldValue, () => {
|
|||||||
background: blue;
|
background: blue;
|
||||||
}*/
|
}*/
|
||||||
.completeButtonStyle .van-dialog__confirm .van-button__content {
|
.completeButtonStyle .van-dialog__confirm .van-button__content {
|
||||||
background: var(--app-primary-color);
|
background: #a72b77;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="w-full flex justify-between" v-for="item in props.item.transaction">
|
<div class="w-full flex justify-between" v-for="item in props.item.transaction">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<div class="w-7 h-7 flex justify-center rounded-lg bg-gray-100 ">
|
<div class="w-7 h-7 flex justify-center rounded-lg bg-gray-100 ">
|
||||||
<img width="30" class="p-1" :src="item.product.group_type=='گوی'?goyImg:mokaabImg">
|
<ProductImage :product="item.product" img-class="p-1 max-h-full max-w-full object-contain" />
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">{{ item?.title }}</div>
|
<div class="text-xs">{{ item?.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,8 +38,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {Physicalreceive} from "@/models/physicalreceive";
|
import type {Physicalreceive} from "@/models/physicalreceive";
|
||||||
import {toJalali} from "../../../../utils/useDateTime";
|
import {toJalali} from "../../../../utils/useDateTime";
|
||||||
import goyImg from "@/assets/img/goyImg.svg"
|
import ProductImage from "@/components/ProductImage.vue";
|
||||||
import mokaabImg from "@/assets/img/mokaabImg.svg"
|
|
||||||
|
|
||||||
interface Prop {
|
interface Prop {
|
||||||
item: Physicalreceive
|
item: Physicalreceive
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop price trigger page */
|
/* Premium desktop price trigger page */
|
||||||
|
|
||||||
.dptr {
|
.dptr {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -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: var(--app-primary-color, #a72b77);
|
border-color: @theme-primary;
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: var(--app-primary-color, #a72b77);
|
background: @theme-primary;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-35rem overflow-y-auto">
|
<div class="h-35rem overflow-y-auto">
|
||||||
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
|
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
|
||||||
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
|
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
|
||||||
@@ -38,11 +38,6 @@
|
|||||||
<div v-else class="w-full h-[88px]">
|
<div v-else class="w-full h-[88px]">
|
||||||
<GoldSkeleton class="w-full" />
|
<GoldSkeleton class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="limitedPrice !== undefined && userStore?.userInfo.trade_active !== 1"
|
|
||||||
class="text-red text-xs flex gap-1 items-center justify-center my-2">
|
|
||||||
<Icon icon="vuesax-linear:info-circle" size="16" />
|
|
||||||
حداقل مبلغ اولیه برای ثبت سفارش {{ usePriceWithoutType(limitedPrice.value as number ?? 0) }} ریال می باشد.
|
|
||||||
</div>
|
|
||||||
<!-- </a-spin>-->
|
<!-- </a-spin>-->
|
||||||
<a-form ref="refInput" :model="form" class="!mt-5" name="basic" @finish="okConfirm()" @finishFailed="(errorInfo) => { console.log('Form submit failed:', errorInfo); console.log('Error fields details:', JSON.stringify(errorInfo.errorFields, null, 2)); if (errorInfo.errorFields && errorInfo.errorFields.length > 0) { const firstError = errorInfo.errorFields[0]; console.log('First error field:', firstError.name, 'errors:', firstError.errors); message.error(firstError.errors?.[0] || 'لطفا تمام فیلدها را به درستی پر کنید') } }">
|
<a-form ref="refInput" :model="form" class="!mt-5" name="basic" @finish="okConfirm()" @finishFailed="(errorInfo) => { console.log('Form submit failed:', errorInfo); console.log('Error fields details:', JSON.stringify(errorInfo.errorFields, null, 2)); if (errorInfo.errorFields && errorInfo.errorFields.length > 0) { const firstError = errorInfo.errorFields[0]; console.log('First error field:', firstError.name, 'errors:', firstError.errors); message.error(firstError.errors?.[0] || 'لطفا تمام فیلدها را به درستی پر کنید') } }">
|
||||||
<div class="border-gray-200 border-1 rounded-xl p-5 relative">
|
<div class="border-gray-200 border-1 rounded-xl p-5 relative">
|
||||||
@@ -61,14 +56,14 @@
|
|||||||
|
|
||||||
<a-form-item v-if="isBuy" name="goldValue"
|
<a-form-item v-if="isBuy" name="goldValue"
|
||||||
:rules="[justNumber4(props?.ProductItem?.price , form.method , props?.ProductItem?.amount, true, form.gateway)]">
|
:rules="[justNumber4(props?.ProductItem?.price , form.method , props?.ProductItem?.amount, true, form.gateway)]">
|
||||||
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="var(--app-primary-color)"
|
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="تعداد"
|
place-holder="تعداد"
|
||||||
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else name="goldValue"
|
<a-form-item v-else name="goldValue"
|
||||||
:rules="[justNumber3(props?.ProductItem?.sellPrice, productNum ,false)]">
|
:rules="[justNumber3(props?.ProductItem?.sellPrice, productNum ,false)]">
|
||||||
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="var(--app-primary-color)"
|
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="تعداد"
|
place-holder="تعداد"
|
||||||
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
||||||
@@ -552,11 +547,6 @@ const unitCashCalculator = computed(() => {
|
|||||||
return (walletStore?.walletInfo?.cash ?? 0) / 100
|
return (walletStore?.walletInfo?.cash ?? 0) / 100
|
||||||
})
|
})
|
||||||
|
|
||||||
const limitedPrice = computed(() => {
|
|
||||||
if (walletStore.limitedPrice)
|
|
||||||
return walletStore?.limitedPrice[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
function calculateCash(val: number) {
|
function calculateCash(val: number) {
|
||||||
priceErrorVisible.value = false
|
priceErrorVisible.value = false
|
||||||
console.log("val",val,props?.ProductItem)
|
console.log("val",val,props?.ProductItem)
|
||||||
@@ -773,7 +763,7 @@ async function ok() {
|
|||||||
background: blue;
|
background: blue;
|
||||||
}*/
|
}*/
|
||||||
.completeButtonStyle .van-dialog__confirm .van-button__content {
|
.completeButtonStyle .van-dialog__confirm .van-button__content {
|
||||||
background: var(--app-primary-color);
|
background: #a72b77;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-35rem overflow-y-auto">
|
<div class="h-35rem overflow-y-auto">
|
||||||
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
|
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
|
||||||
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
|
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
|
||||||
@@ -61,14 +61,14 @@
|
|||||||
|
|
||||||
<a-form-item v-if="isBuy" name="goldValue"
|
<a-form-item v-if="isBuy" name="goldValue"
|
||||||
:rules="[justNumber2(props?.ProductItem?.price , form.method , props?.ProductItem?.amount, true, form.gateway)]">
|
:rules="[justNumber2(props?.ProductItem?.price , form.method , props?.ProductItem?.amount, true, form.gateway)]">
|
||||||
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="var(--app-primary-color)"
|
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="تعداد"
|
place-holder="تعداد"
|
||||||
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else name="goldValue"
|
<a-form-item v-else name="goldValue"
|
||||||
:rules="[justNumber3(props?.ProductItem?.sellPrice, productNum ,false)]">
|
:rules="[justNumber3(props?.ProductItem?.sellPrice, productNum ,false)]">
|
||||||
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="var(--app-primary-color)"
|
<BasicInput v-model:value="form.goldValue" addon-before="تعداد" addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="تعداد"
|
place-holder="تعداد"
|
||||||
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
size="large" type="number" inputmode="number" @onchange="calculateCash" />
|
||||||
@@ -749,7 +749,7 @@ async function ok() {
|
|||||||
background: blue;
|
background: blue;
|
||||||
}*/
|
}*/
|
||||||
.completeButtonStyle .van-dialog__confirm .van-button__content {
|
.completeButtonStyle .van-dialog__confirm .van-button__content {
|
||||||
background: var(--app-primary-color);
|
background: #a72b77;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop rules page */
|
/* Premium desktop rules page */
|
||||||
|
|
||||||
.drules {
|
.drules {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -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: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop transactions page */
|
/* Premium desktop transactions page */
|
||||||
|
|
||||||
.dtxn {
|
.dtxn {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -44,8 +44,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
background: var(--app-primary-color, #a72b77);
|
background: @theme-primary;
|
||||||
border-color: var(--app-primary-color, #a72b77);
|
border-color: @theme-primary;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop user account page */
|
/* Premium desktop user account page */
|
||||||
|
|
||||||
.duser {
|
.duser {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -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: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,8 +174,8 @@
|
|||||||
|
|
||||||
&--primary {
|
&--primary {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--app-primary-color, #a72b77);
|
background: @theme-primary;
|
||||||
border-color: var(--app-primary-color, #a72b77);
|
border-color: @theme-primary;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dwallet">
|
<div class="dwallet">
|
||||||
<div class="dwallet__container">
|
<div class="dwallet__container">
|
||||||
<DesktopPageTopbar
|
<DesktopPageTopbar
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<BasicInput
|
<BasicInput
|
||||||
v-model:value="form.value"
|
v-model:value="form.value"
|
||||||
addon-before="ریال"
|
addon-before="ریال"
|
||||||
addon-bg-color="var(--app-primary-color)"
|
addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="مبلغ"
|
place-holder="مبلغ"
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Premium desktop wallet page */
|
/* Premium desktop wallet page */
|
||||||
|
|
||||||
.dwallet {
|
.dwallet {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
border-color: @theme-primary;
|
||||||
color: var(--app-primary-color, #a72b77);
|
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: var(--app-primary-color, #a72b77);
|
border-color: @theme-primary;
|
||||||
color: var(--app-primary-color, #a72b77);
|
color: @theme-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--primary {
|
&--primary {
|
||||||
background: var(--app-primary-color, #a72b77);
|
background: @theme-primary;
|
||||||
border-color: var(--app-primary-color, #a72b77);
|
border-color: @theme-primary;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute inset-0 bg-cover bg-center opacity-60" :style="{ backgroundImage: `url(${img})` }"></div>
|
<div class="absolute inset-0 bg-cover bg-center opacity-60" :style="{ backgroundImage: `url(${img})` }"></div>
|
||||||
<div class="mpage-header wrap-fix z-[1999]">کیف پول</div>
|
<div class="mpage-header wrap-fix z-[1999]">کیف پول</div>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<BasicInput
|
<BasicInput
|
||||||
v-model:value="form.value"
|
v-model:value="form.value"
|
||||||
addon-before="ریال"
|
addon-before="ریال"
|
||||||
addon-bg-color="var(--app-primary-color)"
|
addon-bg-color="#a72b77"
|
||||||
addon-text-color="white"
|
addon-text-color="white"
|
||||||
place-holder="مبلغ"
|
place-holder="مبلغ"
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full flex flex-col">
|
<div class="w-full flex flex-col">
|
||||||
<img :src="logo" alt="" class="z-100 rounded-full aspect-square self-center w-42 " STYLE="margin-bottom: 60px">
|
<img :src="logo" alt="" class="z-100 rounded-full aspect-square self-center w-42 " STYLE="margin-bottom: 60px">
|
||||||
|
|
||||||
@@ -151,11 +151,9 @@ import { loginCustomer, verifyToken } from '@/api/login'
|
|||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { useUserStore } from '@/store/userStore'
|
import { useUserStore } from '@/store/userStore'
|
||||||
import { useWalletStore } from '@/store/walletStore'
|
import { useWalletStore } from '@/store/walletStore'
|
||||||
import { getAppLogoFullUrl } from '@/config/brand-assets'
|
import logo from '@/assets/img/logo_full_.png'
|
||||||
import { fingerPosition, setTruth } from '@/main'
|
import { fingerPosition, setTruth } from '@/main'
|
||||||
|
|
||||||
const logo = getAppLogoFullUrl()
|
|
||||||
|
|
||||||
const { time, onFinish, reset } = useCountdownTimer('00:02:00', 'mm:ss')
|
const { time, onFinish, reset } = useCountdownTimer('00:02:00', 'mm:ss')
|
||||||
const enableSendAgainCode = ref<boolean>(false)
|
const enableSendAgainCode = ref<boolean>(false)
|
||||||
|
|
||||||
@@ -192,13 +190,6 @@ function setIsSMS(val: boolean) {
|
|||||||
if (!val) stepTwoVerify.value = false
|
if (!val) stepTwoVerify.value = false
|
||||||
userFormState.token = ''
|
userFormState.token = ''
|
||||||
userFormState.password = ''
|
userFormState.password = ''
|
||||||
// console.log(bganim.value.classList)
|
|
||||||
// bganim?.value?.classList.add(val?'right':'left')
|
|
||||||
// bganim?.value?.style.transform = `translateX(${val ? '0' : '-100'});`
|
|
||||||
// bganim?.value?.style.animationDirection = val? 'normal':'reverse'
|
|
||||||
// val? bganim?.value?.classList?.add("move") : bganim?.value?.classList?.remove("move")
|
|
||||||
// const el = bganim.value?.
|
|
||||||
// el.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function enterFingerPrint() {
|
async function enterFingerPrint() {
|
||||||
@@ -437,7 +428,7 @@ const getcode=()=>{
|
|||||||
</div>
|
</div>
|
||||||
</BasicButton>
|
</BasicButton>
|
||||||
<div class="flex gap-1 justify-center mt-5 text-xs" @click="$emit('register',true)">اگر حساب کاربری ندارید
|
<div class="flex gap-1 justify-center mt-5 text-xs" @click="$emit('register',true)">اگر حساب کاربری ندارید
|
||||||
<div class="font-bold text-primary">از این قسمت</div>
|
<div class="font-bold text-[#a72b77]">از این قسمت</div>
|
||||||
ثبت نام کنید.
|
ثبت نام کنید.
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -125,9 +125,7 @@ import registerDelayImage from '@/assets/img/registerDelay.svg'
|
|||||||
import DialogVantOneButton from '@/components/DialogVantOneButton.vue'
|
import DialogVantOneButton from '@/components/DialogVantOneButton.vue'
|
||||||
import { registerCustomer } from '@/api/login'
|
import { registerCustomer } from '@/api/login'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { getAppLogoFullUrl } from '@/config/brand-assets'
|
import logo from "@/assets/img/logo_full_.png";
|
||||||
|
|
||||||
const logo = getAppLogoFullUrl()
|
|
||||||
|
|
||||||
|
|
||||||
// const stepTwoVerify = ref<boolean>(false)
|
// const stepTwoVerify = ref<boolean>(false)
|
||||||
|
|||||||
+2
-2
@@ -12,8 +12,8 @@ export default defineConfig({
|
|||||||
theme: {
|
theme: {
|
||||||
colors: {
|
colors: {
|
||||||
"primary": "var(--app-primary-color, #a72b77)",
|
"primary": "var(--app-primary-color, #a72b77)",
|
||||||
"secondary": "var(--app-secondary-color, #ffffff)",
|
"secondary": "#ffffff",
|
||||||
"tertiary": "var(--app-tertiary-color, #e0be7f)"
|
"tertiary": "#e0be7f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
presets: [
|
presets: [
|
||||||
|
|||||||
Reference in New Issue
Block a user