fix bug of main-page for guest

This commit is contained in:
2026-10-17 14:12:18 +03:30
parent 9487490981
commit 5e181fdae9
7 changed files with 39 additions and 30 deletions
@@ -35,3 +35,5 @@
- 2026-10-05 | ?????? ?? ??? ????? | boutUs/components/AboutUsContentPanel.vue | ??? ?????? ???? ??? ???? ?? ????? ???? ?? ??? ?? - 2026-10-05 | ?????? ?? ??? ????? | boutUs/components/AboutUsContentPanel.vue | ??? ?????? ???? ??? ???? ?? ????? ???? ?? ??? ??
- 2026-10-06 | بیلد آموزش ENOENT 1.png | EducateContentPanel.vue + assets/img/1.png,2.png,4.png + banner1/2 | فایل‌ها در گیت بودند و از دیسک پاک شده بودند؛ git restore - 2026-10-06 | بیلد آموزش ENOENT 1.png | EducateContentPanel.vue + assets/img/1.png,2.png,4.png + banner1/2 | فایل‌ها در گیت بودند و از دیسک پاک شده بودند؛ git restore
- 2026-10-17 | باز/بسته خرید فروش محصول | `utils/productTradeOpen.ts` + `gold/index.vue` | `open_buy`/`open_sell`؛ بدون ProductItem هر دو باز - 2026-10-17 | باز/بسته خرید فروش محصول | `utils/productTradeOpen.ts` + `gold/index.vue` | `open_buy`/`open_sell`؛ بدون ProductItem هر دو باز
- 2026-10-17 | نمودار مهمان ۴۰۱ | `usePriceChart.ts` + `api/home.ts` + سرور `routes/api.php` `POST /api/price/all` | مهمان product/list نمی‌زند؛ تاریخچه نمودار از `/api/price/all` (همان priceData)
- 2026-10-17 | قیمت زنده مهمان | `bootstrap.ts` + `priceSocket.ts` + سرور `/api/price/stream` بدون auth | SSE بدون توکن؛ polling پشتیبان product/list فقط با توکن
+4 -1
View File
@@ -8,4 +8,7 @@ export const getPrice = async () => await useAxios.get<Price>(`/api/price/up`)
export const getLastPrice = async () => await useAxios.get<Price>(`/api/price/last`) export const getLastPrice = async () => await useAxios.get<Price>(`/api/price/last`)
export const getAllPrices = async () => await useAxios.post('/customer/prices/all') export const getAllPrices = async () => await useAxios.post('/customer/prices/all')
/** نمودار مهمان — همان بدنهٔ prices/all، بدون توکن مشتری */
export const getPublicChartPrices = async () => await useAxios.post('/api/price/all')
+1 -3
View File
@@ -117,9 +117,7 @@ export async function bootstrapApp() {
await withTimeout(settingsStore.fetchSettings(), 45000, 'Settings loading timeout') await withTimeout(settingsStore.fetchSettings(), 45000, 'Settings loading timeout')
if (getToken()) { initPriceRealtimeChannel((key) => settingsStore.getSettingByKey(key))
initPriceRealtimeChannel((key) => settingsStore.getSettingByKey(key))
}
if (getToken()) { if (getToken()) {
await withTimeout(userStore.getMyInfo(), 45000, 'User session loading timeout') await withTimeout(userStore.getMyInfo(), 45000, 'User session loading timeout')
+20 -21
View File
@@ -38,7 +38,7 @@ const STREAM_CONNECTING = 0
const STREAM_OPEN = 1 const STREAM_OPEN = 1
const STREAM_CLOSED = 2 const STREAM_CLOSED = 2
/** EventSource نمی‌تواند Authorization بفرستد؛ استریم همچنان auth:customer می‌خواهد. */ /** fetch + ReadableStream — با توکن اختلاف قیمت ویژه؛ بدون توکن همان مظنهٔ عمومی (مهمان). */
class AuthPriceStream extends EventTarget { class AuthPriceStream extends EventTarget {
readyState = STREAM_CONNECTING readyState = STREAM_CONNECTING
onopen: (() => void) | null = null onopen: (() => void) | null = null
@@ -46,7 +46,7 @@ class AuthPriceStream extends EventTarget {
onmessage: (() => void) | null = null onmessage: (() => void) | null = null
private abort = new AbortController() private abort = new AbortController()
constructor(url: string, token: string) { constructor(url: string, token?: string) {
super() super()
void this.start(url, token) void this.start(url, token)
} }
@@ -56,13 +56,18 @@ class AuthPriceStream extends EventTarget {
this.abort.abort() this.abort.abort()
} }
private async start(url: string, token: string) { private async start(url: string, token?: string) {
try { try {
const headers: Record<string, string> = {
Accept: 'text/event-stream',
}
const bearer = String(token ?? '').trim()
if (bearer) {
headers.Authorization = `Bearer ${bearer}`
}
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers,
Accept: 'text/event-stream',
Authorization: `Bearer ${token}`,
},
cache: 'no-store', cache: 'no-store',
signal: this.abort.signal, signal: this.abort.signal,
}) })
@@ -191,10 +196,6 @@ function resolveStreamUrl(): string | null {
return null return null
} }
if (!findStoredAuthToken()) {
return null
}
const base = String(getConfigApp('node_server') || window.location.origin).replace(/\/$/, '') const base = String(getConfigApp('node_server') || window.location.origin).replace(/\/$/, '')
return `${base}${resolveStreamPath()}` return `${base}${resolveStreamPath()}`
} }
@@ -301,10 +302,12 @@ async function runFallbackPoll() {
'@/views/base/products/composables/useProductsPageData' '@/views/base/products/composables/useProductsPageData'
) )
const [priceResult] = await Promise.allSettled([ const pollTasks: Promise<unknown>[] = [getLastPrices()]
getLastPrices(), if (findStoredAuthToken()) {
refreshProductsPageData(), pollTasks.push(refreshProductsPageData())
]) }
const [priceResult] = await Promise.allSettled(pollTasks)
const priceOk = const priceOk =
priceResult.status === 'fulfilled' priceResult.status === 'fulfilled'
@@ -643,7 +646,7 @@ function connect() {
const url = resolveStreamUrl() const url = resolveStreamUrl()
const token = findStoredAuthToken() const token = findStoredAuthToken()
if (!url || !token) { if (!url) {
priceSocketConnected.value = false priceSocketConnected.value = false
priceStreamLifecycle.value = 'closed' priceStreamLifecycle.value = 'closed'
clearConnectingLifecycle() clearConnectingLifecycle()
@@ -661,7 +664,7 @@ function connect() {
markConnectingLifecycle() markConnectingLifecycle()
try { try {
eventSource = new AuthPriceStream(url, token) eventSource = new AuthPriceStream(url, token ?? undefined)
} catch (error) { } catch (error) {
console.warn('Price SSE connect failed', error) console.warn('Price SSE connect failed', error)
handleStreamLost() handleStreamLost()
@@ -824,10 +827,6 @@ export function isPriceWebSocketEnabled(settingGetter: (key: string) => unknown)
return false return false
} }
if (!findStoredAuthToken()) {
return false
}
return resolveStreamUrl() !== null return resolveStreamUrl() !== null
} }
@@ -1,6 +1,7 @@
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { getAllPrices } from '@/api/home' import { getAllPrices, getPublicChartPrices } from '@/api/home'
import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog' import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
import { getToken } from '@/utils'
import { getTradeBuyPrice, getTradeSellPrice } from '@/utils/price' import { getTradeBuyPrice, getTradeSellPrice } from '@/utils/price'
export type ChartMetalType = 'katod' | 'maftol' | 'shamsh' export type ChartMetalType = 'katod' | 'maftol' | 'shamsh'
@@ -132,7 +133,7 @@ async function fetchPrices(force = false) {
chartFetchPromise = (async () => { chartFetchPromise = (async () => {
try { try {
chartLoading.value = true chartLoading.value = true
const response = await getAllPrices() const response = await (getToken() ? getAllPrices() : getPublicChartPrices())
const data = resolvePricesMap(response) const data = resolvePricesMap(response)
cachedPrices.value = data cachedPrices.value = data
applyTimelineFromCache() applyTimelineFromCache()
@@ -149,6 +150,14 @@ async function fetchPrices(force = false) {
} }
async function loadMetalChartOffsets(force = false) { async function loadMetalChartOffsets(force = false) {
if (!getToken()) {
if (!metalOffsetLoaded) {
syncChartSeries()
metalOffsetLoaded = true
}
return
}
if (!force && metalOffsetLoaded && catalogProducts.value.length > 0) { if (!force && metalOffsetLoaded && catalogProducts.value.length > 0) {
return return
} }
+1 -1
View File
@@ -221,7 +221,7 @@
</div> </div>
<div <div
v-if="variant === 'desktop' && !isRegister" v-if="isRegister && variant === 'desktop'"
class="dauth-switch" class="dauth-switch"
> >
<span>حساب کاربری ندارید؟</span> <span>حساب کاربری ندارید؟</span>
-2
View File
@@ -8,9 +8,7 @@
<p class="dauth-card__subtitle">برای ادامه، رمز ورود خود را انتخاب کنید.</p> <p class="dauth-card__subtitle">برای ادامه، رمز ورود خود را انتخاب کنید.</p>
</header> </header>
<header v-else class="dauth-card__header"> <header v-else class="dauth-card__header">
<p class="dauth-card__eyebrow">حساب جدید</p>
<h2 class="dauth-card__title">ثبت نام</h2> <h2 class="dauth-card__title">ثبت نام</h2>
<p class="dauth-card__subtitle">اطلاعات خود را برای ایجاد حساب وارد کنید</p>
</header> </header>
<Login <Login