Files
boursecu_user/src/views/base/home/index.vue
T
2026-06-09 21:04:22 +03:30

313 lines
9.7 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="mt-10 p-5">
<div v-if="userStore.userInfo && !loading">
<!-- هشدار حداقل مبلغ -->
<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(limitedPrice.value as number ?? 0) }} ریال می باشد.
</div>
<!-- بخش اصلی -->
<div class="p-1 from-primary rounded-lg p-px to-transparent" :class="isGaroosi ? 'mt-0' : 'mt-5'">
<!-- دارایی‌ها (فقط گاروسی) -->
<div v-if="isGaroosi" class="flex justify-between px-2 mb-2">
<AssetItem label="دارایی طلایی:" :value="`${(userStore?.userInfo?.wallet?.count_gold ?? 0).toFixed(3)} گرم`"/>
<AssetItem label="دارایی ریالی:"
:value="`${usePriceWithoutType(userStore?.userInfo?.wallet?.count_cash ?? 0)} ریال`"/>
</div>
<!-- دکمه‌های اصلی -->
<div class="p-2 py-3 rounded-lg bg-tertiary">
<div class="flex justify-between">
<ActionButton
v-for="action in mainActions"
:key="action.name"
:action="action"
@click="action.handler"
/>
</div>
</div>
<!-- بخش کد معرف (گاروسی) -->
<ReferralSection v-if="isGaroosi"/>
<!-- بخش دارایی‌ها (سایر پروژه‌ها) -->
<AssetsSection v-else @openGoldModal="openGoldModal" @openRialModal="openRialModal"/>
<!-- پیام بسته بودن بازار -->
<MarketClosedMessage v-if="showClosedMessage" :message="closedMessage"/>
<!-- قیمت فعلی -->
<CurrentPrice/>
<!-- بنرها -->
<BannerSlider v-if="isGaroosi && banners?.length" :banners="banners" @bannerClick="handleBannerClick"/>
<!-- نمودار قیمت -->
<PriceChart @updateData="updateChartData" :price-chart-series="priceChartSeries"/>
<!-- آمار بالا و پایین -->
<PriceStats :price="price"/>
</div>
<!-- اسکلت لودینگ -->
<HomeSkeleton v-if="loading" class="w-full"/>
</div>
<!-- هدر -->
<AppHeader @logout="showLogoutConfirm = true"/>
<!-- دیالوگ خروج -->
<LogoutConfirmDialog v-model:show="showLogoutConfirm" @confirm="logout"/>
<!-- شیت موجودی -->
<BalanceSheet v-model:show="wholeBalanceVisible" :walletInfo="walletStore.walletInfo" :goldVal="goldVal"/>
<!-- مودال‌ها -->
<BasicModal v-model:visible="showTextBanner" :custom-footer="true" destroyOnClose>
<div class="max-h-[54vh] ql-editor" v-html="TextBanner"></div>
</BasicModal>
<ShareModal v-model:visible="showShare" :referralId="userStore.userInfo?.referral_id"/>
</div>
</template>
<script lang="tsx" setup>
import {computed, onMounted, onUnmounted, ref} from 'vue'
import router from '@/router'
import {useWindowScroll} from '@vueuse/core'
import {loading, removeToken} from '@/utils'
import {useUserStore} from '@/store/userStore'
import {useWalletStore} from '@/store/walletStore'
import { useWalletAssets } from '@/composables/useWalletAssets'
import {useConfigStore} from '@/store/configStore'
import {useFundUserStore} from '@/store/fundStore'
import {usePrice, usePriceWithoutType} from '../../../utils/usePrice'
import {getLastPrices, lastPrice, price, setIntervalGetAllPrice, stopPriceInterval} from '@/utils/price'
import {getConstByKey} from '@/api/const'
import {message, Modal} from 'ant-design-vue'
// Components
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import HomeSkeleton from '@/views/base/home/components/HomeSkeleton.vue'
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
import AssetItem from './components/AssetItem.vue'
import ActionButton from './components/ActionButton.vue'
import ReferralSection from './components/ReferralSection.vue'
import AssetsSection from './components/AssetsSection.vue'
import MarketClosedMessage from './components/MarketClosedMessage.vue'
import CurrentPrice from './components/CurrentPrice.vue'
import BannerSlider from './components/BannerSlider.vue'
import PriceChart from './components/PriceChart.vue'
import PriceStats from './components/PriceStats.vue'
import AppHeader from './components/AppHeader.vue'
import LogoutConfirmDialog from './components/LogoutConfirmDialog.vue'
import BalanceSheet from './components/BalanceSheet.vue'
import ShareModal from './components/ShareModal.vue'
// Composables
import {useBanners} from './composables/useBanners'
import {usePriceChart} from './composables/usePriceChart'
import {useUserFunds} from './composables/useUserFunds'
import {getReferralShareText, getReferralShareTitle} from '@/utils/config'
// Stores
const userStore = useUserStore()
const walletStore = useWalletStore()
const configStore = useConfigStore()
const fundStore = useFundUserStore()
// Config
const isGaroosi = computed(() => configStore.config.name === 'garoosi')
const closedMessage = computed(() => userStore.getValueByKey('closed_message'))
const showClosedMessage = computed(() =>
closedMessage.value &&
lastPrice?.value?.price_last === 0)
// State
const showLogoutConfirm = ref(false)
const wholeBalanceVisible = ref(false)
const showTextBanner = ref(false)
const showShare = ref(false)
const TextBanner = ref('')
const odometerKey = ref(0)
const {x, y} = useWindowScroll()
// Composables
const {banners, loadBanners} = useBanners()
const {updateChartData, priceChartSeries, fetchPrices} = usePriceChart()
const {funds, loadFunds} = useUserFunds()
// Computed
const limitedPrice = computed(() => walletStore.limitedPrice?.[0])
const goldVal = computed(() => {
return lastPrice.value?.price_last ?? 1
})
// Actions
const mainActions = computed(() => [
{
name: 'buySell',
icon: 'vuesax-linear:money-receive',
label: 'خرید و فروش',
handler: () => router.push({name: 'buyProduct'})
},
{
name: 'deposit',
icon: 'vuesax-linear:card-add',
label: isGaroosi.value ? 'واریز و برداشت' : 'واریز نقدی',
handler: () => router.push({name: 'wallet', query: {state: 'deposit'}})
},
{
name: 'balance',
icon: 'vuesax-linear:receipt-item',
label: 'موجودی',
handler: () => wholeBalanceVisible.value = true
},
{
name: 'assets',
icon: 'vuesax-linear:cup',
label: isGaroosi.value ? 'دعوت از دوستان' : 'دارایی ها',
handler: () => isGaroosi.value ? shareCode() : router.push({name: 'fund'})
}
])
// Methods
async function shareCode() {
const text = getReferralShareText({
userName: userStore.userInfo?.name,
projectName: configStore.config.name,
referralId: userStore.userInfo?.referral_id,
})
try {
await navigator.share({
title: getReferralShareTitle(configStore.config.name),
text,
})
} catch {
copyCode()
}
}
function copyCode() {
const code = userStore.userInfo?.referral_id
if (code) {
navigator.clipboard.writeText(code)
message.info('کد رونوشت شد')
}
}
async function logout() {
userStore.userInfo = undefined
walletStore.walletInfo = undefined
useWalletAssets().clearWalletAssets()
removeToken()
await router.push({name: 'login'})
window.location.reload()
}
function openGoldModal() {
const goldAmount = (walletStore.walletInfo?.gold ?? 0).toFixed(3)
const walletsAmount = (walletStore.walletInfo?.count_gold_wallets ?? 0).toFixed(3)
Modal.confirm({
title: `مجموع کل وزن طلای آبشده (${goldAmount}) + ارزش دارایی ها (${walletsAmount}) گرم`,
okText: 'متوجه شدم',
centered: true,
cancelButtonProps: {style: {display: 'none'}}
})
}
function openRialModal() {
const cashWallet = usePrice(walletStore?.walletInfo?.count_cash_wallet ?? 0)
const cashWallets = usePrice(walletStore?.walletInfo?.count_cash_wallets ?? 0)
Modal.confirm({
title: `مجموع کیف پول ریالی (${cashWallet}) + معادل ریالی محصولات (${cashWallets}) ریال`,
okText: 'متوجه شدم',
centered: true,
cancelButtonProps: {style: {display: 'none'}}
})
}
function handleBannerClick(banner) {
const handlers = {
share: () => showShare.value = true,
modal: () => {
if (banner.modalText) {
TextBanner.value = banner.modalText
showTextBanner.value = true
}
},
link: () => {
if (banner.link) {
if (banner.link.startsWith('http')) {
window.open(banner.link, '_blank')
} else {
router.push(banner.link)
}
}
}
}
handlers[banner.typeClick]?.()
}
// Lifecycle
onMounted(async () => {
try {
loading.value = true
await Promise.all([
loadFunds(),
getLastPrices(),
fetchPrices(),
setIntervalGetAllPrice(),
loadBanners()
])
// شروع آپدیت قیمت
const updateInterval = setInterval(() => {
odometerKey.value++
}, 1000)
onUnmounted(() => {
clearInterval(updateInterval)
stopPriceInterval()
})
} catch (error) {
console.error('Initialization error:', error)
} finally {
loading.value = false
}
})
</script>
<style lang="less">
.odometer {
font-size: 60px;
text-align: center;
width: 100%;
animation-duration: 3s;
}
label:focus-within {
box-shadow: none !important;
}
.banner-swiper {
--swiper-pagination-bottom: 0.75rem;
.swiper-pagination-bullet {
width: 0.5rem;
height: 0.5rem;
background: rgba(0, 0, 0, 0.35);
opacity: 1;
}
.swiper-pagination-bullet-active {
background: #fff;
}
}
</style>