Split desktop and mobile layouts with shared page data so APIs fetch once per context.
Add layout shells, desktop views, module-level page caches, and UI fixes for auth, home, wallet, transactions, address, and related pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,37 +1,74 @@
|
||||
import { ref } from 'vue'
|
||||
import { getConstByKey } from '@/api/const'
|
||||
|
||||
export function useBanners() {
|
||||
const banners = ref([])
|
||||
const loading = ref(false)
|
||||
/** Module-level banner state — shared by mobile and desktop home layouts. */
|
||||
const banners = ref<unknown[]>([])
|
||||
const bannersLoading = ref(false)
|
||||
let bannersPromise: Promise<void> | null = null
|
||||
let bannersLoaded = false
|
||||
|
||||
const loadBanners = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await getConstByKey('header_banner')
|
||||
if (!response) return
|
||||
async function fetchBanners(force = false) {
|
||||
if (!force && bannersLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
let list = []
|
||||
if (typeof response === 'string') {
|
||||
list = JSON.parse(response)
|
||||
} else if (typeof response === 'object' && !Array.isArray(response) && 'value' in response) {
|
||||
list = typeof response.value === 'string' ? JSON.parse(response.value) : response.value
|
||||
} else if (Array.isArray(response) && response.length > 0) {
|
||||
const first = response[0]
|
||||
if (first.value) {
|
||||
list = typeof first.value === 'string' ? JSON.parse(first.value) : first.value
|
||||
} else if (first.icon) {
|
||||
list = response
|
||||
}
|
||||
}
|
||||
if (!force && bannersPromise) {
|
||||
return bannersPromise
|
||||
}
|
||||
|
||||
banners.value = Array.isArray(list) ? list.filter(b => b.show === true) : []
|
||||
} catch (error) {
|
||||
console.error('Error loading banners:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
bannersPromise = (async () => {
|
||||
try {
|
||||
bannersLoading.value = true
|
||||
const response = await getConstByKey('header_banner')
|
||||
if (!response) {
|
||||
banners.value = []
|
||||
return
|
||||
}
|
||||
|
||||
let list: unknown[] = []
|
||||
if (typeof response === 'string') {
|
||||
list = JSON.parse(response)
|
||||
} else if (typeof response === 'object' && !Array.isArray(response) && 'value' in response) {
|
||||
const value = (response as { value: unknown }).value
|
||||
list = typeof value === 'string' ? JSON.parse(value) : (value as unknown[])
|
||||
} else if (Array.isArray(response) && response.length > 0) {
|
||||
const first = response[0] as { value?: unknown; icon?: unknown }
|
||||
if (first.value) {
|
||||
list = typeof first.value === 'string' ? JSON.parse(first.value) : (first.value as unknown[])
|
||||
} else if (first.icon) {
|
||||
list = response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { banners, loading, loadBanners }
|
||||
}
|
||||
banners.value = Array.isArray(list)
|
||||
? list.filter((banner) => (banner as { show?: boolean }).show === true)
|
||||
: []
|
||||
bannersLoaded = true
|
||||
} catch (error) {
|
||||
console.error('Error loading banners:', error)
|
||||
} finally {
|
||||
bannersLoading.value = false
|
||||
bannersPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return bannersPromise
|
||||
}
|
||||
|
||||
export function useBanners() {
|
||||
return {
|
||||
banners,
|
||||
loading: bannersLoading,
|
||||
loadBanners: fetchBanners,
|
||||
}
|
||||
}
|
||||
|
||||
export function loadHomeBanners(force = false) {
|
||||
return fetchBanners(force)
|
||||
}
|
||||
|
||||
export function resetHomeBanners() {
|
||||
banners.value = []
|
||||
bannersLoaded = false
|
||||
bannersPromise = null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
import { computed, inject, onMounted, onUnmounted, provide, ref, type InjectionKey } from 'vue'
|
||||
import router from '@/router'
|
||||
import { loading, removeToken } from '@/utils'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import type { WalletMetalTotal } from '@/models/walletAssets'
|
||||
import { formatMetalAssetsInfoText, formatRialAssetsInfoText } from '@/utils/productAssets'
|
||||
import { useConfigStore } from '@/store/configStore'
|
||||
import { lastPrice, stopLastPricePolling } from '@/utils/price'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { getReferralShareText, getReferralShareTitle } from '@/utils/config'
|
||||
import { useBanners } from './useBanners'
|
||||
import { usePriceChart, type ChartMetalType } from './usePriceChart'
|
||||
import type { HomeActionItem } from '../layouts/types'
|
||||
import { getHomeLayoutId } from '../layouts/registry'
|
||||
import { initHomePageData, resetHomePageData } from './useHomePageData'
|
||||
import { resetWalletPageData } from '../../wallet/composables/useWalletPageData'
|
||||
import { resetAddressPageData } from '../../address/composables/useAddressPageData'
|
||||
import { resetTransactionsPageData } from '../../transactions/composables/useTransactionsPageData'
|
||||
import { resetProductsPageData } from '../../products/composables/useProductsPageData'
|
||||
|
||||
export interface HomePageContext {
|
||||
loading: typeof loading
|
||||
isGaroosi: ReturnType<typeof computed<boolean>>
|
||||
limitedPrice: ReturnType<typeof computed>
|
||||
showClosedMessage: ReturnType<typeof computed<boolean>>
|
||||
closedMessage: ReturnType<typeof computed>
|
||||
mainActions: ReturnType<typeof computed<HomeActionItem[]>>
|
||||
banners: ReturnType<typeof useBanners>['banners']
|
||||
priceChartSeries: ReturnType<typeof usePriceChart>['priceChartSeries']
|
||||
chartSelection: ReturnType<typeof usePriceChart>['chartSelection']
|
||||
updateChartData: ReturnType<typeof usePriceChart>['updateChartData']
|
||||
selectedChartMetal: ReturnType<typeof usePriceChart>['selectedChartMetal']
|
||||
setChartMetal: (metal: ChartMetalType) => void
|
||||
isBoursecuHomeLayout: ReturnType<typeof computed<boolean>>
|
||||
handleBannerClick: (banner: { typeClick?: string; modalText?: string; link?: string }) => void
|
||||
openGoldModal: (metal: WalletMetalTotal) => void
|
||||
openRialModal: () => void
|
||||
}
|
||||
|
||||
export const HOME_PAGE_CONTEXT_KEY: InjectionKey<HomePageContext> = Symbol('homePageContext')
|
||||
|
||||
export function useHomePage() {
|
||||
const userStore = useUserStore()
|
||||
const walletStore = useWalletStore()
|
||||
const configStore = useConfigStore()
|
||||
const { assetsSummary, clearWalletAssets } = useWalletAssets()
|
||||
|
||||
const showLogoutConfirm = ref(false)
|
||||
const wholeBalanceVisible = ref(false)
|
||||
const showTextBanner = ref(false)
|
||||
const showShare = ref(false)
|
||||
const TextBanner = ref('')
|
||||
|
||||
const { banners } = useBanners()
|
||||
const {
|
||||
updateChartData,
|
||||
priceChartSeries,
|
||||
chartSelection,
|
||||
selectedChartMetal,
|
||||
setChartMetal,
|
||||
} = usePriceChart()
|
||||
const isBoursecuHomeLayout = computed(() => getHomeLayoutId() === 'boursecu')
|
||||
|
||||
const isGaroosi = computed(() => configStore.config?.name === 'garoosi')
|
||||
const closedMessage = computed(() => userStore.getValueByKey('closed_message'))
|
||||
const showClosedMessage = computed(
|
||||
() => Boolean(closedMessage.value && lastPrice?.value?.price_last === 0)
|
||||
)
|
||||
const limitedPrice = computed(() => walletStore.limitedPrice?.[0])
|
||||
const goldVal = computed(() => lastPrice.value?.price_last ?? 1)
|
||||
|
||||
const mainActions = computed<HomeActionItem[]>(() => [
|
||||
{
|
||||
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' })),
|
||||
},
|
||||
])
|
||||
|
||||
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
|
||||
clearWalletAssets()
|
||||
resetHomePageData()
|
||||
resetWalletPageData()
|
||||
resetAddressPageData()
|
||||
resetTransactionsPageData()
|
||||
resetProductsPageData()
|
||||
removeToken()
|
||||
await router.push({ name: 'login' })
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
function openGoldModal(metal: WalletMetalTotal) {
|
||||
Modal.confirm({
|
||||
title: formatMetalAssetsInfoText(metal),
|
||||
okText: 'متوجه شدم',
|
||||
centered: true,
|
||||
cancelButtonProps: { style: { display: 'none' } },
|
||||
})
|
||||
}
|
||||
|
||||
function openRialModal() {
|
||||
const summary = assetsSummary.value
|
||||
Modal.confirm({
|
||||
title: formatRialAssetsInfoText(
|
||||
summary?.cash_wallet ?? 0,
|
||||
summary?.assets_rial_value ?? 0
|
||||
),
|
||||
okText: 'متوجه شدم',
|
||||
centered: true,
|
||||
cancelButtonProps: { style: { display: 'none' } },
|
||||
})
|
||||
}
|
||||
|
||||
function handleBannerClick(banner: { typeClick?: string; modalText?: string; link?: string }) {
|
||||
const handlers: Record<string, () => void> = {
|
||||
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 ?? '']?.()
|
||||
}
|
||||
|
||||
const context: HomePageContext = {
|
||||
loading,
|
||||
isGaroosi,
|
||||
limitedPrice,
|
||||
showClosedMessage,
|
||||
closedMessage,
|
||||
mainActions,
|
||||
banners,
|
||||
priceChartSeries,
|
||||
chartSelection,
|
||||
updateChartData,
|
||||
selectedChartMetal,
|
||||
setChartMetal,
|
||||
isBoursecuHomeLayout,
|
||||
handleBannerClick,
|
||||
openGoldModal,
|
||||
openRialModal,
|
||||
}
|
||||
|
||||
provide(HOME_PAGE_CONTEXT_KEY, context)
|
||||
|
||||
onUnmounted(() => {
|
||||
stopLastPricePolling()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// Single shared fetch for mobile + desktop — safe to call on remount (deduped in useHomePageData)
|
||||
void initHomePageData()
|
||||
})
|
||||
|
||||
return {
|
||||
userStore,
|
||||
walletStore,
|
||||
showLogoutConfirm,
|
||||
wholeBalanceVisible,
|
||||
showTextBanner,
|
||||
showShare,
|
||||
TextBanner,
|
||||
goldVal,
|
||||
logout,
|
||||
}
|
||||
}
|
||||
|
||||
export function useHomePageContext() {
|
||||
const context = inject(HOME_PAGE_CONTEXT_KEY)
|
||||
if (!context) {
|
||||
throw new Error('useHomePageContext must be used within useHomePage')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { ref } from 'vue'
|
||||
import { loading } from '@/utils'
|
||||
import { getLastPrices, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import { clearProductsCatalog } from '@/composables/useProductsCatalog'
|
||||
import { getHomeLayoutId } from '../layouts/registry'
|
||||
import { fetchHomeChartPrices, loadHomeMaftolOffset, resetHomePriceChart } from './usePriceChart'
|
||||
import { loadHomeBanners, resetHomeBanners } from './useBanners'
|
||||
import { loadHomeFunds, resetHomeFunds } from './useUserFunds'
|
||||
|
||||
/**
|
||||
* Single entry point for all homepage API calls.
|
||||
* Mobile and desktop layouts must not fetch independently — they consume shared module state.
|
||||
*/
|
||||
let initPromise: Promise<void> | null = null
|
||||
const initialized = ref(false)
|
||||
|
||||
export function isHomePageDataInitialized() {
|
||||
return initialized.value
|
||||
}
|
||||
|
||||
export async function initHomePageData(force = false) {
|
||||
if (!force && initialized.value) {
|
||||
// Resume live price updates when returning to home without refetching datasets
|
||||
startLastPricePolling()
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && initPromise) {
|
||||
return initPromise
|
||||
}
|
||||
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { loadWalletAssets } = useWalletAssets()
|
||||
|
||||
await Promise.all([
|
||||
loadHomeFunds(force),
|
||||
loadWalletAssets(force),
|
||||
getLastPrices(),
|
||||
fetchHomeChartPrices(force),
|
||||
startLastPricePolling(),
|
||||
loadHomeBanners(force),
|
||||
])
|
||||
|
||||
if (getHomeLayoutId() === 'boursecu') {
|
||||
await loadHomeMaftolOffset(force)
|
||||
}
|
||||
|
||||
initialized.value = true
|
||||
} catch (error) {
|
||||
console.error('Home page initialization error:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
initPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return initPromise
|
||||
}
|
||||
|
||||
/** Clears homepage caches — call on logout or when a full refresh is required. */
|
||||
export function resetHomePageData() {
|
||||
initialized.value = false
|
||||
initPromise = null
|
||||
stopLastPricePolling()
|
||||
resetHomeBanners()
|
||||
resetHomeFunds()
|
||||
resetHomePriceChart()
|
||||
clearProductsCatalog()
|
||||
}
|
||||
|
||||
export function useHomePageInitState() {
|
||||
return { initialized }
|
||||
}
|
||||
@@ -1,33 +1,225 @@
|
||||
import { ref } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { getAllPrices } from '@/api/home'
|
||||
import type { Product } from '@/models/products'
|
||||
import { getProductsCatalog } from '@/composables/useProductsCatalog'
|
||||
import { getHomeLayoutId } from '../layouts/registry'
|
||||
import { getProductBuyPrice, resolvePriceOffsetRatio } from '@/utils/productPrice'
|
||||
import { isKatodMesGroup, isMaftolMesGroup } from '@/utils/productDealMethod'
|
||||
import { getTradeBuyPrice, lastPrice } from '@/utils/price'
|
||||
|
||||
export type ChartMetalType = 'katod' | 'maftol'
|
||||
|
||||
type ChartPoint = [number, number]
|
||||
|
||||
/** Module-level chart state — one dataset shared by mobile and desktop PriceChart instances. */
|
||||
const priceChartSeries = ref<{ name: string; data: ChartPoint[] }[]>([
|
||||
{ name: 'قیمت', data: [] },
|
||||
])
|
||||
const rawChartPoints = ref<ChartPoint[]>([])
|
||||
const chartSelection = ref('DAILY')
|
||||
const chartLoading = ref(false)
|
||||
const selectedChartMetal = ref<ChartMetalType>('katod')
|
||||
const maftolChartRatio = ref(0)
|
||||
|
||||
let chartFetchPromise: Promise<void> | null = null
|
||||
let chartInitialized = false
|
||||
let maftolOffsetPromise: Promise<void> | null = null
|
||||
let maftolOffsetLoaded = false
|
||||
|
||||
function findKatodProduct(products: Product[]) {
|
||||
return (
|
||||
products.find((product) => isKatodMesGroup(product.group_type))
|
||||
?? products.find((product) => (product.title ?? '').includes('کاتد'))
|
||||
)
|
||||
}
|
||||
|
||||
function findMaftolProduct(products: Product[]) {
|
||||
return (
|
||||
products.find((product) => isMaftolMesGroup(product.group_type))
|
||||
?? products.find((product) => (product.title ?? '').includes('مفتول'))
|
||||
)
|
||||
}
|
||||
|
||||
function applyChartRatio(basePrice: number, ratio: number) {
|
||||
if (!ratio) {
|
||||
return basePrice
|
||||
}
|
||||
|
||||
return Math.round(basePrice + basePrice * ratio)
|
||||
}
|
||||
|
||||
function deriveMaftolChartRatio(maftolProduct?: Product, katodProduct?: Product) {
|
||||
const offsetRatio = resolvePriceOffsetRatio(maftolProduct?.price_offset_percent)
|
||||
if (offsetRatio) {
|
||||
return offsetRatio
|
||||
}
|
||||
|
||||
if (!maftolProduct) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const basePrice = getTradeBuyPrice(lastPrice.value)
|
||||
const katodPrice = katodProduct ? getProductBuyPrice(katodProduct) : basePrice
|
||||
const maftolPrice = getProductBuyPrice(maftolProduct)
|
||||
const referencePrice = katodPrice || basePrice
|
||||
|
||||
if (!referencePrice || !maftolPrice || referencePrice === maftolPrice) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return (maftolPrice - referencePrice) / referencePrice
|
||||
}
|
||||
|
||||
function normalizeChartTimestamp(timestamp: number) {
|
||||
return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp
|
||||
}
|
||||
|
||||
function resolvePricesMap(response: unknown): Record<string, Array<{ timestamp: number; value: number }>> | null {
|
||||
if (!response || typeof response !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
const body = response as Record<string, unknown>
|
||||
const nested = body.data
|
||||
|
||||
if (nested && typeof nested === 'object' && 'prices' in nested) {
|
||||
return (nested as { prices: Record<string, Array<{ timestamp: number; value: number }>> }).prices
|
||||
}
|
||||
|
||||
if ('prices' in body) {
|
||||
return body.prices as Record<string, Array<{ timestamp: number; value: number }>>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function buildChartPoints(items: Array<{ timestamp: number; value: number }>): ChartPoint[] {
|
||||
return items.map((item) => [
|
||||
normalizeChartTimestamp(item.timestamp),
|
||||
item.value - 1,
|
||||
])
|
||||
}
|
||||
|
||||
function syncChartSeries() {
|
||||
const data =
|
||||
getHomeLayoutId() === 'boursecu' && selectedChartMetal.value === 'maftol'
|
||||
? rawChartPoints.value.map(
|
||||
([timestamp, value]) =>
|
||||
[timestamp, applyChartRatio(value, maftolChartRatio.value)] as ChartPoint
|
||||
)
|
||||
: rawChartPoints.value
|
||||
|
||||
priceChartSeries.value[0].data = data
|
||||
}
|
||||
|
||||
watch([selectedChartMetal, maftolChartRatio], () => {
|
||||
syncChartSeries()
|
||||
})
|
||||
|
||||
async function fetchPrices(force = false) {
|
||||
if (!force && chartInitialized && rawChartPoints.value.length > 0) {
|
||||
syncChartSeries()
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && chartFetchPromise) {
|
||||
return chartFetchPromise
|
||||
}
|
||||
|
||||
chartFetchPromise = (async () => {
|
||||
try {
|
||||
chartLoading.value = true
|
||||
const response = await getAllPrices()
|
||||
const data = resolvePricesMap(response)
|
||||
const selectedData = data?.[chartSelection.value] ?? []
|
||||
rawChartPoints.value = buildChartPoints(selectedData)
|
||||
syncChartSeries()
|
||||
chartInitialized = true
|
||||
} catch (error) {
|
||||
console.error('Error fetching prices:', error)
|
||||
} finally {
|
||||
chartLoading.value = false
|
||||
chartFetchPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return chartFetchPromise
|
||||
}
|
||||
|
||||
async function loadMaftolOffset(force = false) {
|
||||
if (getHomeLayoutId() !== 'boursecu') {
|
||||
maftolChartRatio.value = 0
|
||||
syncChartSeries()
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && maftolOffsetLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && maftolOffsetPromise) {
|
||||
return maftolOffsetPromise
|
||||
}
|
||||
|
||||
maftolOffsetPromise = (async () => {
|
||||
try {
|
||||
const products = await getProductsCatalog(force)
|
||||
const katodProduct = findKatodProduct(products)
|
||||
const maftolProduct = findMaftolProduct(products)
|
||||
maftolChartRatio.value = deriveMaftolChartRatio(maftolProduct, katodProduct)
|
||||
syncChartSeries()
|
||||
maftolOffsetLoaded = true
|
||||
} catch (error) {
|
||||
console.error('Error loading maftol chart ratio:', error)
|
||||
maftolChartRatio.value = 0
|
||||
syncChartSeries()
|
||||
} finally {
|
||||
maftolOffsetPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return maftolOffsetPromise
|
||||
}
|
||||
|
||||
function updateChartData(timeline: string) {
|
||||
chartSelection.value = timeline
|
||||
void fetchPrices(true)
|
||||
}
|
||||
|
||||
function setChartMetal(metal: ChartMetalType) {
|
||||
selectedChartMetal.value = metal
|
||||
}
|
||||
|
||||
export function usePriceChart() {
|
||||
const priceChartSeries = ref([{ name: 'قیمت', data: [] }])
|
||||
const selection = ref('DAILY')
|
||||
const chartLoading = ref(false)
|
||||
return {
|
||||
priceChartSeries,
|
||||
chartSelection,
|
||||
chartLoading,
|
||||
selectedChartMetal,
|
||||
maftolChartRatio,
|
||||
updateChartData,
|
||||
fetchPrices,
|
||||
loadMaftolOffset,
|
||||
setChartMetal,
|
||||
}
|
||||
}
|
||||
|
||||
const toPersianNumber = (num: number) => {
|
||||
return num.toString().replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[d])
|
||||
}
|
||||
export function fetchHomeChartPrices(force = false) {
|
||||
return fetchPrices(force)
|
||||
}
|
||||
|
||||
const fetchPrices = async () => {
|
||||
try {
|
||||
chartLoading.value = true
|
||||
const response = await getAllPrices()
|
||||
const data = response.data?.prices
|
||||
const selectedData = data[selection.value] || []
|
||||
priceChartSeries.value[0].data = selectedData.map((item: any) => [item.timestamp, item.value - 1])
|
||||
} catch (error) {
|
||||
console.error('Error fetching prices:', error)
|
||||
} finally {
|
||||
chartLoading.value = false
|
||||
}
|
||||
}
|
||||
export function loadHomeMaftolOffset(force = false) {
|
||||
return loadMaftolOffset(force)
|
||||
}
|
||||
|
||||
const updateChartData = (timeline: string) => {
|
||||
selection.value = timeline
|
||||
fetchPrices()
|
||||
}
|
||||
|
||||
return { priceChartSeries, chartLoading, updateChartData, fetchPrices }
|
||||
}
|
||||
export function resetHomePriceChart() {
|
||||
priceChartSeries.value = [{ name: 'قیمت', data: [] }]
|
||||
rawChartPoints.value = []
|
||||
chartSelection.value = 'DAILY'
|
||||
selectedChartMetal.value = 'katod'
|
||||
maftolChartRatio.value = 0
|
||||
chartInitialized = false
|
||||
chartFetchPromise = null
|
||||
maftolOffsetLoaded = false
|
||||
maftolOffsetPromise = null
|
||||
}
|
||||
|
||||
@@ -1,22 +1,54 @@
|
||||
import { ref } from 'vue'
|
||||
import { useFundUserStore } from '@/store/fundStore'
|
||||
|
||||
export function useUserFunds() {
|
||||
const funds = ref([])
|
||||
const loading = ref(false)
|
||||
const fundStore = useFundUserStore()
|
||||
/** Module-level fund list — shared across home layout variants. */
|
||||
const funds = ref<unknown[]>([])
|
||||
const fundsLoading = ref(false)
|
||||
let fundsPromise: Promise<void> | null = null
|
||||
let fundsLoaded = false
|
||||
|
||||
const loadFunds = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const result = await fundStore.getMyFund()
|
||||
funds.value = result?.rows || []
|
||||
} catch (error) {
|
||||
console.error('Error loading funds:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
async function fetchFunds(force = false) {
|
||||
if (!force && fundsLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && fundsPromise) {
|
||||
return fundsPromise
|
||||
}
|
||||
|
||||
const fundStore = useFundUserStore()
|
||||
|
||||
fundsPromise = (async () => {
|
||||
try {
|
||||
fundsLoading.value = true
|
||||
const result = await fundStore.getMyFund()
|
||||
funds.value = result?.rows || []
|
||||
fundsLoaded = true
|
||||
} catch (error) {
|
||||
console.error('Error loading funds:', error)
|
||||
} finally {
|
||||
fundsLoading.value = false
|
||||
fundsPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return { funds, loading, loadFunds }
|
||||
}
|
||||
return fundsPromise
|
||||
}
|
||||
|
||||
export function useUserFunds() {
|
||||
return {
|
||||
funds,
|
||||
loading: fundsLoading,
|
||||
loadFunds: fetchFunds,
|
||||
}
|
||||
}
|
||||
|
||||
export function loadHomeFunds(force = false) {
|
||||
return fetchFunds(force)
|
||||
}
|
||||
|
||||
export function resetHomeFunds() {
|
||||
funds.value = []
|
||||
fundsLoaded = false
|
||||
fundsPromise = null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user