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:
@@ -0,0 +1,291 @@
|
||||
import { computed, inject, onMounted, provide, ref, type InjectionKey, type Ref } from 'vue'
|
||||
import OtpInput from 'vue3-otp-input'
|
||||
import { formatCashWalletBalanceText } from '@/utils/productDealMethod'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import router from '@/router'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import type { Transaction } from '@/models/transaction'
|
||||
import { createCard, withdraw, confirmCode } from '@/api/wallet'
|
||||
import { useInfiniteScroll } from '@vueuse/core'
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { Card } from '@/models/wallet'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useConfigStore } from '@/store/configStore'
|
||||
import { isBazretalaProject } from '@/utils/config'
|
||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||
import {
|
||||
buildSupportMessengerUrl,
|
||||
DEFAULT_SUPPORT_MESSENGER_TEXT,
|
||||
supportMessengerSettings,
|
||||
} from '@/utils/supportMessenger'
|
||||
import {
|
||||
initWalletPageData,
|
||||
loadMoreWalletDeposits,
|
||||
loadMoreWalletWithdrawals,
|
||||
refreshWalletCards,
|
||||
walletCardItems,
|
||||
walletDepositItems,
|
||||
walletWithdrawItems,
|
||||
} from './useWalletPageData'
|
||||
|
||||
export interface WalletPageContext {
|
||||
isDeposit: Ref<boolean>
|
||||
walletStore: ReturnType<typeof useWalletStore>
|
||||
config: ReturnType<typeof useConfigStore>
|
||||
showAddCard: Ref<boolean>
|
||||
chargeAccountModal: Ref<boolean>
|
||||
showConfirmCode: Ref<boolean>
|
||||
withdrawInfModal: Ref<boolean>
|
||||
infoVisible: Ref<boolean>
|
||||
refOtpInput: Ref<InstanceType<typeof OtpInput> | null>
|
||||
form: Ref<Transaction>
|
||||
cardForm: Ref<Card>
|
||||
disableButton: ReturnType<typeof computed<boolean>>
|
||||
cardItems: typeof walletCardItems
|
||||
depositItems: typeof walletDepositItems
|
||||
withdrawItems: typeof walletWithdrawItems
|
||||
loading: typeof loading
|
||||
loadingMore: typeof loadingMore
|
||||
mobileDepositScrollEl: Ref<HTMLElement | null>
|
||||
desktopDepositScrollEl: Ref<HTMLElement | null>
|
||||
supportMessengerText: ReturnType<typeof computed<string>>
|
||||
supportMessengerLink: ReturnType<typeof computed<string | undefined>>
|
||||
isBazretalaProject: typeof isBazretalaProject
|
||||
usePriceWithoutType: typeof usePriceWithoutType
|
||||
formatCashWalletBalanceText: typeof formatCashWalletBalanceText
|
||||
setIsDeposit: (val: boolean) => void
|
||||
useStyle: () => string
|
||||
formatter: (value: number) => string
|
||||
percentChange: (val: number) => void
|
||||
ok: () => Promise<void>
|
||||
addCard: () => Promise<void>
|
||||
getCards: () => Promise<void>
|
||||
handleOnComplete: (value: string) => Promise<void>
|
||||
}
|
||||
|
||||
export const WALLET_PAGE_CONTEXT_KEY: InjectionKey<WalletPageContext> = Symbol('walletPageContext')
|
||||
|
||||
export function useWalletPage() {
|
||||
const isDeposit = ref(true)
|
||||
const walletStore = useWalletStore()
|
||||
const userStore = useUserStore()
|
||||
const config = useConfigStore()
|
||||
const isDesktop = useDesktopBreakpoint()
|
||||
const showAddCard = ref(false)
|
||||
const chargeAccountModal = ref(false)
|
||||
const showConfirmCode = ref(false)
|
||||
const withdrawInfModal = ref(false)
|
||||
const infoVisible = ref(false)
|
||||
const refOtpInput = ref<InstanceType<typeof OtpInput> | null>(null)
|
||||
const mobileDepositScrollEl = ref<HTMLElement | null>(null)
|
||||
const desktopDepositScrollEl = ref<HTMLElement | null>(null)
|
||||
|
||||
const activeDepositScrollEl = computed(() =>
|
||||
isDesktop.value ? desktopDepositScrollEl.value : mobileDepositScrollEl.value,
|
||||
)
|
||||
|
||||
const supportMessengerText = computed(
|
||||
() => supportMessengerSettings.value.messengerText || DEFAULT_SUPPORT_MESSENGER_TEXT,
|
||||
)
|
||||
|
||||
const supportMessengerLink = computed(() =>
|
||||
buildSupportMessengerUrl(
|
||||
supportMessengerSettings.value.messenger,
|
||||
supportMessengerSettings.value.messengerId,
|
||||
),
|
||||
)
|
||||
|
||||
const form = ref<Transaction>({
|
||||
value: undefined,
|
||||
percentWallet: 0,
|
||||
card_id: undefined,
|
||||
})
|
||||
|
||||
const cardForm = ref<Card>({
|
||||
birth_date: 0,
|
||||
account_number: '',
|
||||
bank_name: '',
|
||||
cart_number: '',
|
||||
shaba_number: '',
|
||||
})
|
||||
|
||||
const disableButton = computed(() => {
|
||||
if (!loading.value) {
|
||||
if (walletStore?.walletInfo?.cash && !isDeposit.value) {
|
||||
if (walletCardItems.value.length > 0) {
|
||||
return (
|
||||
walletStore?.walletInfo?.cash <= 0
|
||||
|| walletStore?.walletInfo?.cash < parseInt(form.value?.value)
|
||||
|| isNaN(parseInt(form.value?.value))
|
||||
|| parseInt(form.value?.value) < 1000000
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (isDeposit.value) {
|
||||
return isNaN(parseInt(form.value?.value)) || parseInt(form.value?.value) < 1000000
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
const unitCalculator = computed(() => (walletStore?.walletInfo?.cash ?? 0) / 100)
|
||||
|
||||
function resetData() {
|
||||
form.value.value = undefined
|
||||
form.value.percentWallet = 0
|
||||
}
|
||||
|
||||
async function addCard() {
|
||||
try {
|
||||
loading.value = true
|
||||
await createCard(cardForm.value)
|
||||
await getCards()
|
||||
showAddCard.value = false
|
||||
cardForm.value = {
|
||||
birth_date: 0,
|
||||
account_number: '',
|
||||
bank_name: '',
|
||||
cart_number: '',
|
||||
shaba_number: '',
|
||||
}
|
||||
message.success('کارت وارد شده با موفقیت افزوده شد و پس از تایید نمایش داده خواهد شد.')
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function getCards() {
|
||||
await refreshWalletCards()
|
||||
form.value.card_id = walletCardItems.value.length ? walletCardItems.value[0].id : undefined
|
||||
}
|
||||
|
||||
function setIsDeposit(val: boolean) {
|
||||
isDeposit.value = val
|
||||
form.value.value = undefined
|
||||
form.value.percentWallet = 0
|
||||
}
|
||||
|
||||
function useStyle() {
|
||||
if (isDeposit.value) return 'right: 2%;'
|
||||
return ' right: 98%; transform: translate(100%, 0);'
|
||||
}
|
||||
|
||||
const formatter = (value: number) => `${value}%`
|
||||
|
||||
function percentChange(val: number) {
|
||||
if (val !== 0) form.value.value = parseInt((unitCalculator.value * val).toFixed())
|
||||
else form.value.value = undefined
|
||||
}
|
||||
|
||||
async function ok() {
|
||||
try {
|
||||
if (form.value.value !== undefined) {
|
||||
loading.value = true
|
||||
if (!isDeposit.value) {
|
||||
await confirmCode({ value: form.value.value })
|
||||
showConfirmCode.value = true
|
||||
}
|
||||
if (isDeposit.value) {
|
||||
chargeAccountModal.value = true
|
||||
}
|
||||
} else {
|
||||
message.error('ابتدا مبلغ را وارد کنید')
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOnComplete(value: string) {
|
||||
try {
|
||||
infoVisible.value = true
|
||||
showConfirmCode.value = false
|
||||
await withdraw({ ...form.value, otp: value })
|
||||
await walletStore.getMyWallet()
|
||||
await message.success('عملیات با موفقیت انجام شد')
|
||||
withdrawInfModal.value = true
|
||||
resetData()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
infoVisible.value = false
|
||||
refOtpInput.value?.clearInput()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (router.currentRoute.value.query.state === 'deposit') {
|
||||
isDeposit.value = true
|
||||
}
|
||||
cardForm.value.birth_date = userStore.userInfo?.birth_date
|
||||
await initWalletPageData()
|
||||
form.value.card_id = walletCardItems.value.length ? walletCardItems.value[0].id : undefined
|
||||
})
|
||||
|
||||
if (!userStore.isSingle) {
|
||||
useInfiniteScroll(
|
||||
activeDepositScrollEl,
|
||||
async () => {
|
||||
if (isDeposit.value) {
|
||||
await loadMoreWalletDeposits()
|
||||
} else {
|
||||
await loadMoreWalletWithdrawals()
|
||||
}
|
||||
},
|
||||
{ distance: 12 },
|
||||
)
|
||||
}
|
||||
|
||||
const context: WalletPageContext = {
|
||||
isDeposit,
|
||||
walletStore,
|
||||
config,
|
||||
showAddCard,
|
||||
chargeAccountModal,
|
||||
showConfirmCode,
|
||||
withdrawInfModal,
|
||||
infoVisible,
|
||||
refOtpInput,
|
||||
form,
|
||||
cardForm,
|
||||
disableButton,
|
||||
cardItems: walletCardItems,
|
||||
depositItems: walletDepositItems,
|
||||
withdrawItems: walletWithdrawItems,
|
||||
loading,
|
||||
loadingMore,
|
||||
mobileDepositScrollEl,
|
||||
desktopDepositScrollEl,
|
||||
supportMessengerText,
|
||||
supportMessengerLink,
|
||||
isBazretalaProject,
|
||||
usePriceWithoutType,
|
||||
formatCashWalletBalanceText,
|
||||
setIsDeposit,
|
||||
useStyle,
|
||||
formatter,
|
||||
percentChange,
|
||||
ok,
|
||||
addCard,
|
||||
getCards,
|
||||
handleOnComplete,
|
||||
}
|
||||
|
||||
provide(WALLET_PAGE_CONTEXT_KEY, context)
|
||||
return context
|
||||
}
|
||||
|
||||
export function useWalletPageContext() {
|
||||
const context = inject(WALLET_PAGE_CONTEXT_KEY)
|
||||
if (!context) {
|
||||
throw new Error('useWalletPageContext must be used within a page that calls useWalletPage()')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import { ref } from 'vue'
|
||||
import { loading, loadingMore } from '@/utils'
|
||||
import type { Transaction } from '@/models/transaction'
|
||||
import { getActivatedCards } from '@/api/wallet'
|
||||
import { getTransactionTable, getTransactionTable2 } from '@/api/transaction'
|
||||
import type { TableEngineInput, TableOutput } from '@/models/table'
|
||||
import type { Card } from '@/models/wallet'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import { runOnceAsync, resetOnceAsyncByPrefix } from '@/composables/useOnceAsync'
|
||||
import { loadSupportMessengerFromSettings } from '@/utils/supportMessenger'
|
||||
|
||||
/**
|
||||
* Shared wallet page datasets — mobile and desktop views read this module state only.
|
||||
* Resize / breakpoint changes must not trigger new requests.
|
||||
*/
|
||||
let initPromise: Promise<void> | null = null
|
||||
const initialized = ref(false)
|
||||
|
||||
export const walletCardItems = ref<Card[]>([])
|
||||
export const walletDepositItems = ref<Transaction[]>([])
|
||||
export const walletWithdrawItems = ref<Transaction[]>([])
|
||||
export const walletDepositTableOutput = ref<TableOutput<Transaction> | null>(null)
|
||||
export const walletWithdrawTableOutput = ref<TableOutput<Transaction> | null>(null)
|
||||
|
||||
export const walletDepositPagination = ref<TableEngineInput>({
|
||||
filters: [],
|
||||
limit: 6,
|
||||
page: 0,
|
||||
})
|
||||
|
||||
export const walletWithdrawPagination = ref<TableEngineInput>({
|
||||
filters: [
|
||||
{
|
||||
field: 'type',
|
||||
cr: 'and',
|
||||
type: 'number',
|
||||
op: 'eq',
|
||||
value: 4,
|
||||
},
|
||||
],
|
||||
limit: 6,
|
||||
page: 0,
|
||||
})
|
||||
|
||||
export function isWalletPageDataInitialized() {
|
||||
return initialized.value
|
||||
}
|
||||
|
||||
async function fetchDepositPage(append: boolean) {
|
||||
const isFirstPage = walletDepositPagination.value.page === 0 && !append
|
||||
|
||||
if (isFirstPage) {
|
||||
loading.value = true
|
||||
} else {
|
||||
loadingMore.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
walletDepositTableOutput.value = await getTransactionTable2(walletDepositPagination.value)
|
||||
const rows = walletDepositTableOutput.value.rows
|
||||
|
||||
if (isFirstPage) {
|
||||
walletDepositItems.value = [...rows]
|
||||
} else {
|
||||
walletDepositItems.value.push(...rows)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Wallet deposit table error:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
loadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchWithdrawPage(append: boolean) {
|
||||
const isFirstPage = walletWithdrawPagination.value.page === 0 && !append
|
||||
|
||||
if (isFirstPage) {
|
||||
loading.value = true
|
||||
} else {
|
||||
loadingMore.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
walletWithdrawTableOutput.value = await getTransactionTable(walletWithdrawPagination.value)
|
||||
const rows = walletWithdrawTableOutput.value.rows
|
||||
|
||||
if (isFirstPage) {
|
||||
walletWithdrawItems.value = [...rows]
|
||||
} else {
|
||||
walletWithdrawItems.value.push(...rows)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Wallet withdraw table error:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
loadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshWalletCards() {
|
||||
try {
|
||||
walletCardItems.value = (await getActivatedCards()) ?? []
|
||||
} catch (error) {
|
||||
console.error('Wallet cards refresh error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadMoreWalletDeposits() {
|
||||
if (
|
||||
!walletDepositTableOutput.value
|
||||
|| (walletDepositPagination.value.page + 1) * walletDepositPagination.value.limit
|
||||
>= walletDepositTableOutput.value.query.total
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
walletDepositPagination.value.page++
|
||||
await fetchDepositPage(true)
|
||||
}
|
||||
|
||||
export async function loadMoreWalletWithdrawals() {
|
||||
if (
|
||||
!walletWithdrawTableOutput.value
|
||||
|| (walletWithdrawPagination.value.page + 1) * walletWithdrawPagination.value.limit
|
||||
>= walletWithdrawTableOutput.value.query.total
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
walletWithdrawPagination.value.page++
|
||||
await fetchWithdrawPage(true)
|
||||
}
|
||||
|
||||
export async function initWalletPageData(force = false) {
|
||||
if (!force && initialized.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && initPromise) {
|
||||
return initPromise
|
||||
}
|
||||
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
if (force) {
|
||||
resetWalletPageData()
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
const walletStore = useWalletStore()
|
||||
|
||||
walletDepositPagination.value.page = 0
|
||||
walletWithdrawPagination.value.page = 0
|
||||
|
||||
await Promise.all([
|
||||
runOnceAsync('wallet:getMyWallet', () => walletStore.getMyWallet(), force),
|
||||
runOnceAsync('wallet:cards', () => refreshWalletCards(), force),
|
||||
runOnceAsync('wallet:deposit:init', () => fetchDepositPage(false), force),
|
||||
runOnceAsync('wallet:withdraw:init', () => fetchWithdrawPage(false), force),
|
||||
])
|
||||
|
||||
loadSupportMessengerFromSettings()
|
||||
initialized.value = true
|
||||
} catch (error) {
|
||||
console.error('Wallet page initialization error:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
initPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return initPromise
|
||||
}
|
||||
|
||||
export function resetWalletPageData() {
|
||||
initialized.value = false
|
||||
initPromise = null
|
||||
resetOnceAsyncByPrefix('wallet:')
|
||||
walletCardItems.value = []
|
||||
walletDepositItems.value = []
|
||||
walletWithdrawItems.value = []
|
||||
walletDepositTableOutput.value = null
|
||||
walletWithdrawTableOutput.value = null
|
||||
walletDepositPagination.value.page = 0
|
||||
walletWithdrawPagination.value.page = 0
|
||||
}
|
||||
Reference in New Issue
Block a user