diff --git a/src/App.vue b/src/App.vue index b1540fc..54a0541 100644 --- a/src/App.vue +++ b/src/App.vue @@ -104,6 +104,7 @@ + @@ -129,6 +130,8 @@ import EnamadBadge from '@/components/EnamadBadge.vue' import { usePwaInstall } from '@/composables/usePwaInstall' import PwaInstallGuideModal from '@/components/pwa/PwaInstallGuideModal.vue' import GoldDayCalculatorModal from '@/components/GoldDayCalculatorModal.vue' +import GoldLoading from '@/components/GoldLoading.vue' +import { colleagueOrderWaiting, resumeColleagueOrderIfPending } from '@/composables/useColleagueOrderWait' import { useUserStore } from '@/store/userStore' import { useSettingsStore } from '@/store/settingsStore' import { connectChatIfExists, disconnectChat, isSettingFlagOn, unreadSupportCount, useChatService } from '@/utils/chat' @@ -272,6 +275,14 @@ watch( { immediate: true }, ) +watch( + () => getToken(), + (token) => { + if (token) void resumeColleagueOrderIfPending() + }, + { immediate: true }, +) + watch(latestIncomingNotification, (payload) => { if (!payload) return if (payload.at === lastSupportToastAt) return diff --git a/src/api/order.ts b/src/api/order.ts index ced8c0b..3c3913e 100644 --- a/src/api/order.ts +++ b/src/api/order.ts @@ -7,11 +7,22 @@ import {useUserStore} from "@/store/userStore"; const userStore = useUserStore() -export const createOrder = async (obj: Order): Promise => { - return await useAxios.post(`/customer${!userStore.isSingle ? '' : '/single'}/order`, obj) +function customerOrderPrefix() { + return `/customer${!userStore.isSingle ? '' : '/single'}` } +export const createOrder = async (obj: Order): Promise => { + return await useAxios.post(`${customerOrderPrefix()}/order`, obj) +} + +export const getPendingColleagueOrder = async (): Promise => { + return await useAxios.get(`${customerOrderPrefix()}/order/pending`) +} + +export const getColleagueOrderStatus = async (id: number): Promise => { + return await useAxios.get(`${customerOrderPrefix()}/order/${id}/status`) +} export const getOrders = async (tbl: Ref>): Promise> => { - return await useAxios.post>(`/customer${!userStore.isSingle ? '' : '/single'}/order/table`, tbl) + return await useAxios.post>(`${customerOrderPrefix()}/order/table`, tbl) } diff --git a/src/composables/useColleagueOrderWait.ts b/src/composables/useColleagueOrderWait.ts new file mode 100644 index 0000000..6a5e715 --- /dev/null +++ b/src/composables/useColleagueOrderWait.ts @@ -0,0 +1,176 @@ +import { ref } from 'vue' +import { message } from 'ant-design-vue' +import router from '@/router' +import { getColleagueOrderStatus, getPendingColleagueOrder } from '@/api/order' +import { useUserStore } from '@/store/userStore' +import { useWalletStore } from '@/store/walletStore' +import { useFundUserStore } from '@/store/fundStore' +import { getToken } from '@/utils' + +export const colleagueOrderWaiting = ref(false) + +const CLIENT_POLL_MS = 5000 +const STREAM_EVENT = 'colleague-order' + +type WaitResult = 'success' | 'failed' | 'none' + +let inflight: Promise | null = null + +export function emitColleagueOrderFromStream(payload: unknown) { + window.dispatchEvent(new CustomEvent(STREAM_EVENT, { detail: payload })) +} + +export function isColleagueOrderPending(res: unknown): boolean { + if (!res || typeof res !== 'object') return false + const row = res as Record + if (row.pending === true) return true + if (Array.isArray(row) && row[0]?.pending === true) return true + const sts = Number( + row[0]?.sts ?? row.sts ?? row[1]?.order?.sts ?? row.order?.sts ?? NaN + ) + return sts === 4 +} + +export function colleagueOrderIdFromResponse(res: unknown): number | null { + if (!res || typeof res !== 'object') return null + const row = res as Record + const id = Number( + row[0]?.order_id ?? row.order_id ?? row[1]?.order?.id ?? row.order?.id ?? NaN + ) + return id > 0 ? id : null +} + +async function finishSuccess() { + try { + await useUserStore().getMyInfo() + } catch { + /* ignore */ + } + try { + await useWalletStore().getMyWallet() + } catch { + /* ignore */ + } + try { + await useFundUserStore().getMyFund() + } catch { + /* ignore */ + } + try { + const { resetTransactionsPageData } = await import( + '@/views/base/transactions/composables/useTransactionsPageData' + ) + resetTransactionsPageData() + } catch { + /* optional */ + } + message.success('سفارش شما با موفقیت انجام شد') + if (router.currentRoute.value.name !== 'transactions') { + await router.push({ name: 'transactions' }) + } +} + +function stsFromPayload(payload: Record | null | undefined): number | null { + if (!payload) return null + const sts = Number(payload.sts ?? payload.order?.sts) + return Number.isFinite(sts) ? sts : null +} + +async function readStatus(orderId: number | null): Promise { + try { + if (orderId && orderId > 0) { + const row = (await getColleagueOrderStatus(orderId)) as Record + return stsFromPayload(row) + } + const row = (await getPendingColleagueOrder()) as Record + if (row?.pending === false && row?.sts == null) { + return null + } + return stsFromPayload(row) + } catch { + return null + } +} + +export async function waitForColleagueOrder(orderId?: number | null): Promise { + if (inflight) return inflight + + inflight = (async () => { + colleagueOrderWaiting.value = true + let settled: WaitResult | null = null + + const finish = (result: WaitResult) => { + if (settled) return + settled = result + } + + const onStream = (event: Event) => { + const detail = (event as CustomEvent).detail as Record | undefined + if (orderId && Number(detail?.order_id) > 0 && Number(detail.order_id) !== orderId) { + return + } + const sts = stsFromPayload(detail) + if (sts === 2) finish('success') + if (sts === 3) finish('failed') + } + + window.addEventListener(STREAM_EVENT, onStream) + + try { + while (!settled) { + const sts = await readStatus(orderId ?? null) + if (sts === 2) { + finish('success') + break + } + if (sts === 3) { + finish('failed') + break + } + await new Promise((resolve) => { + const timer = window.setTimeout(resolve, CLIENT_POLL_MS) + const once = () => { + window.clearTimeout(timer) + resolve() + } + window.addEventListener(STREAM_EVENT, once, { once: true }) + }) + } + } finally { + window.removeEventListener(STREAM_EVENT, onStream) + colleagueOrderWaiting.value = false + } + + if (settled === 'success') { + await finishSuccess() + return 'success' + } + + message.error('سفارش توسط همکار رد شد') + return 'failed' + })().finally(() => { + inflight = null + }) + + return inflight +} + +export async function afterCreateOrder(res: unknown): Promise { + if (isColleagueOrderPending(res)) { + return waitForColleagueOrder(colleagueOrderIdFromResponse(res)) + } + await finishSuccess() + return 'success' +} + +export async function resumeColleagueOrderIfPending(): Promise { + if (!getToken()) return 'none' + try { + const row = (await getPendingColleagueOrder()) as Record + if (!row?.pending) return 'none' + const id = Number(row.order_id ?? row.order?.id ?? 0) + return waitForColleagueOrder(id > 0 ? id : null) + } catch { + return 'none' + } +} diff --git a/src/utils/priceSocket.ts b/src/utils/priceSocket.ts index 5f9e5a2..24f0c22 100644 --- a/src/utils/priceSocket.ts +++ b/src/utils/priceSocket.ts @@ -560,6 +560,16 @@ function bindStreamEvents(source: AuthPriceStream) { } }) + source.addEventListener('colleague_order', (event) => { + try { + void import('@/composables/useColleagueOrderWait').then(({ emitColleagueOrderFromStream }) => { + emitColleagueOrderFromStream(JSON.parse(event.data)) + }) + } catch (error) { + console.warn('Colleague order SSE parse failed', error) + } + }) + source.addEventListener('ping', () => { touchStreamActivity() }) diff --git a/src/views/base/gold/index.vue b/src/views/base/gold/index.vue index 1df9163..0cf25e1 100644 --- a/src/views/base/gold/index.vue +++ b/src/views/base/gold/index.vue @@ -251,6 +251,7 @@ import BasicButton from '@/components/BasicPacks/BasicButton.vue' import router from '@/router' import { useWalletStore } from '@/store/walletStore' import { createOrder } from '@/api/order' +import { afterCreateOrder } from '@/composables/useColleagueOrderWait' import { message } from 'ant-design-vue' import type { Price } from '@/models/home' import { getLastPrice } from '@/api/home' @@ -790,7 +791,7 @@ async function ok() { loading.value = true await nextTick() infoVisible.value = true - await createOrder({ + const res = await createOrder({ direction: isBuy.value ? 1 : 2, price: Math.round(unitPrice.value), amount: normalizeOrderGoldAmount(orderGoldAmount.value), @@ -798,17 +799,16 @@ async function ok() { target_cash: calcTradeTargetCash(form.value.goldCash, inputLock.value), abshode_id: props.ProductItem?.id, }) - await walletStore.getMyWallet() - await loadWalletAssets(true) - await router.push({ name: 'transactions' }) + const result = await afterCreateOrder(res) + if (result !== 'success') { + return + } form.value = { goldValue: undefined, goldCash: undefined, percentWallet: 0, } inputLock.value = null - message.success('سفارش شما با موفقیت انجام شد') - await fundStore.getMyFund() } catch (e) { // console.log(e) // await initPrice() diff --git a/src/views/base/products/components/DealProduct.vue b/src/views/base/products/components/DealProduct.vue index 0b37da4..0293a52 100644 --- a/src/views/base/products/components/DealProduct.vue +++ b/src/views/base/products/components/DealProduct.vue @@ -252,6 +252,7 @@ import BasicButton from '@/components/BasicPacks/BasicButton.vue' import router from '@/router' import { useWalletStore } from '@/store/walletStore' import { createOrder } from '@/api/order' +import { afterCreateOrder } from '@/composables/useColleagueOrderWait' import { FormInstance, message } from 'ant-design-vue' import type { Price } from '@/models/home' import Icon from '@/components/BasicPacks/BasicIcon.vue' @@ -734,9 +735,10 @@ async function ok() { window.location.href = returnUrl return // Stop execution here } - // If no gateway, continue with normal flow - await walletStore.getMyWallet() - await router.push({ name: 'transactions' }) + const result = await afterCreateOrder(data) + if (result !== 'success') { + return + } form.value = { goldValue: undefined, goldCash: undefined, @@ -744,9 +746,6 @@ async function ok() { method: 'cash', gateway: false } - // refreshTable() - message.success('سفارش شما با موفقیت انجام شد') - await loadWalletAssets(true) } catch (e) { // console.log(e) // await initPrice()