This commit is contained in:
2026-07-21 10:57:54 +03:30
parent a60f1fbc68
commit bc642e91ed
10 changed files with 159 additions and 33 deletions
+11 -3
View File
@@ -1,4 +1,12 @@
import useAxios from '@/utils/useAxios' import useAxios from '@/utils/useAxios'
import type {Physicalreceive} from "@/models/physicalreceive"; import type { Physicalreceive } from '@/models/physicalreceive'
export const physicalReceiveApi = async (obj:Object) => await useAxios.post(`/customer/wallet/withdraw_physical`, obj) import type { TableEngineInput, TableEngineOutput } from '@/models/table'
export const getPhysicalReceiveList = async (obj?:Object) => await useAxios.post<Physicalreceive>(`/customer/wallet/withdraw_physical/table`, obj)
export const physicalReceiveApi = async (obj: Object) =>
await useAxios.post(`/customer/wallet/withdraw_physical`, obj)
export const getPhysicalReceiveList = async (obj?: TableEngineInput) =>
await useAxios.post<TableEngineOutput<Physicalreceive>>(
`/customer/wallet/withdraw_physical/table`,
obj,
)
@@ -30,13 +30,14 @@
<div <div
class="flex items-center gap-1" class="flex items-center gap-1"
:class="variant === 'desktop' ? 'dpr-cart-row__input' : 'pr-cart-row__input'" :class="variant === 'desktop' ? 'dpr-cart-row__input' : 'pr-cart-row__input'"
@focusin="selectCountInput"
> >
<BasicInput <BasicInput
v-model:value="item.count" v-model:value="item.count"
:addon-before="quantityUnit" :addon-before="quantityUnit"
:addon-bg-color="config.config?.color" :addon-bg-color="config.config?.color"
addon-text-color="white" addon-text-color="white"
type="text" type="number"
:inputmode="isCountBased ? 'numeric' : 'decimal'" :inputmode="isCountBased ? 'numeric' : 'decimal'"
@update:value="onCountInput" @update:value="onCountInput"
/> />
@@ -91,6 +92,13 @@ const displayGold = computed(() => {
const config = useConfigStore() const config = useConfigStore()
function selectCountInput(e: FocusEvent) {
const el = e.target as HTMLInputElement | null
if (!el || el.tagName !== 'INPUT' || typeof el.select !== 'function') return
// تا کاربر با تایپ عدد جدید، مقدار قبلی را جایگزین کند
requestAnimationFrame(() => el.select())
}
function onCountInput(raw: string | number) { function onCountInput(raw: string | number) {
countError.value = '' countError.value = ''
const normalized = persianToEnglishDigits(String(raw ?? '').trim()) const normalized = persianToEnglishDigits(String(raw ?? '').trim())
@@ -1,6 +1,8 @@
<template> <template>
<MobileRequestListView v-show="!isDesktop" /> <div class="physical-receive-list-root">
<DesktopRequestListView v-show="isDesktop" /> <MobileRequestListView v-show="!isDesktop" />
<DesktopRequestListView v-show="isDesktop" />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -12,3 +14,20 @@ import MobileRequestListView from '../mobile/MobileRequestListView.vue'
const { isDesktop } = usePhysicalReceiveShellContext() const { isDesktop } = usePhysicalReceiveShellContext()
usePhysicalReceiveList() usePhysicalReceiveList()
</script> </script>
<style scoped>
.physical-receive-list-root {
flex: 1 1 0;
min-height: 0;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.physical-receive-list-root > * {
flex: 1 1 0;
min-height: 0;
height: 100%;
}
</style>
+6
View File
@@ -44,6 +44,12 @@ const { isDesktop } = usePhysicalReceiveShell()
overflow: hidden; overflow: hidden;
} }
.physical-receive-page-root__content > :deep(.physical-receive-list-root),
.physical-receive-page-root__content > :deep(.physical-new-request) {
flex: 1 1 0;
min-height: 0;
}
.physical-receive-page-root--desktop { .physical-receive-page-root--desktop {
height: auto; height: auto;
min-height: 0; min-height: 0;
@@ -1,9 +1,11 @@
<template> <template>
<div> <div class="physical-receive-list">
<div <div class="physical-receive-list__scroll mpage-content px-2">
class="flex flex-col gap-4 mpage-content px-2 pb-[calc(var(--app-bottom-chrome-height)+var(--app-add-card-height)+1rem)]" <div
> class="flex flex-col gap-4 pb-[calc(var(--app-add-card-height,88px)+1.25rem)]"
<RequestItem v-for="item in requestItems" :key="item.id" :item="item" /> >
<RequestItem v-for="item in requestItems" :key="item.id" :item="item" />
</div>
</div> </div>
<Teleport to="body"> <Teleport to="body">
<div class="add-request app-dock-add-bar" @click="openAdd()"> <div class="add-request app-dock-add-bar" @click="openAdd()">
@@ -21,3 +23,21 @@ import { usePhysicalReceiveListContext } from '../composables/usePhysicalReceive
const { requestItems, openAdd } = usePhysicalReceiveListContext() const { requestItems, openAdd } = usePhysicalReceiveListContext()
</script> </script>
<style scoped>
.physical-receive-list {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
overflow: hidden;
}
.physical-receive-list__scroll {
flex: 1 1 0;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: contain;
}
</style>
@@ -44,7 +44,16 @@
<div ref="listRef" :class="loading ? 'min-h-[50vh]' : ''" class="mt-5"> <div ref="listRef" :class="loading ? 'min-h-[50vh]' : ''" class="mt-5">
<list-anim v-if="!loading"> <list-anim v-if="!loading">
<Item v-for="item in items" :key="item.id" :item="item" /> <template v-if="activeTab.fetchMode === 'physical'">
<RequestItem
v-for="item in physicalItems"
:key="item.id"
:item="item"
/>
</template>
<template v-else>
<Item v-for="item in items" :key="item.id" :item="item" />
</template>
<div :ref="bindMobileObserver" class="w-full opacity-0 relative"> <div :ref="bindMobileObserver" class="w-full opacity-0 relative">
در حال بارگذاری در حال بارگذاری
</div> </div>
@@ -55,7 +64,7 @@
<TransactionSkeleton v-for="i in 8" :key="i" class="mt-1" /> <TransactionSkeleton v-for="i in 8" :key="i" class="mt-1" />
</template> </template>
<div v-if="!loading && items.length === 0" class="dtxn-empty"> <div v-if="!loading && listEmpty" class="dtxn-empty">
<img :src="emptyImage" alt="" class="dtxn-empty__image" /> <img :src="emptyImage" alt="" class="dtxn-empty__image" />
<p class="dtxn-empty__text">{{ emptyText }}</p> <p class="dtxn-empty__text">{{ emptyText }}</p>
</div> </div>
@@ -72,6 +81,7 @@ import ListAnim from '@/components/ListAnim.vue'
import TransactionSkeleton from '@/components/TransactionSkeleton.vue' import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
import emptyImage from '@/assets/img/cash_empty.svg' import emptyImage from '@/assets/img/cash_empty.svg'
import Item from './components/Item.vue' import Item from './components/Item.vue'
import RequestItem from '@/views/base/physicalReceive/components/RequestItem.vue'
import BoursecuTransactionFilters from './components/BoursecuTransactionFilters.vue' import BoursecuTransactionFilters from './components/BoursecuTransactionFilters.vue'
import { BOURSECU_TRANSACTION_TABS } from './config/boursecuTabs' import { BOURSECU_TRANSACTION_TABS } from './config/boursecuTabs'
import { useTransactionsPageContext } from './composables/useTransactionsPage' import { useTransactionsPageContext } from './composables/useTransactionsPage'
@@ -80,10 +90,12 @@ const listRef = ref<HTMLElement | null>(null)
const { const {
items, items,
physicalItems,
activeTabId, activeTabId,
tradeSide, tradeSide,
activeTab, activeTab,
emptyText, emptyText,
listEmpty,
setActiveTab, setActiveTab,
setTradeSide, setTradeSide,
bindMobileObserver, bindMobileObserver,
@@ -27,6 +27,7 @@ import {
transactionDateFromFilter, transactionDateFromFilter,
transactionDateToFilter, transactionDateToFilter,
transactionItems, transactionItems,
physicalReceiveItems,
transactionPagination, transactionPagination,
transactionStatusFilter, transactionStatusFilter,
transactionProductFilter, transactionProductFilter,
@@ -37,6 +38,7 @@ import {
export interface TransactionsPageContext { export interface TransactionsPageContext {
pagination: typeof transactionPagination pagination: typeof transactionPagination
items: typeof transactionItems items: typeof transactionItems
physicalItems: typeof physicalReceiveItems
tableOutput: typeof transactionTableOutput tableOutput: typeof transactionTableOutput
showFilter: ReturnType<typeof ref<boolean>> showFilter: ReturnType<typeof ref<boolean>>
dateFilter: typeof transactionDateFilter dateFilter: typeof transactionDateFilter
@@ -53,6 +55,7 @@ export interface TransactionsPageContext {
activeTab: ReturnType<typeof computed<BoursecuTransactionTab>> activeTab: ReturnType<typeof computed<BoursecuTransactionTab>>
statusOptions: ReturnType<typeof computed<ReturnType<typeof getBoursecuStatusOptionsForTab>>> statusOptions: ReturnType<typeof computed<ReturnType<typeof getBoursecuStatusOptionsForTab>>>
emptyText: ReturnType<typeof computed<string>> emptyText: ReturnType<typeof computed<string>>
listEmpty: ReturnType<typeof computed<boolean>>
resetPagination: typeof resetTransactionsPagination resetPagination: typeof resetTransactionsPagination
initTable: () => Promise<void> initTable: () => Promise<void>
reloadTable: typeof reloadTransactionsTable reloadTable: typeof reloadTransactionsTable
@@ -111,6 +114,13 @@ export function useTransactionsPageProvider() {
return activeTab.value.emptyText return activeTab.value.emptyText
}) })
const listEmpty = computed(() => {
if (activeTab.value.fetchMode === 'physical') {
return physicalReceiveItems.value.length === 0
}
return transactionItems.value.length === 0
})
async function initTable() { async function initTable() {
await reloadTransactionsTable() await reloadTransactionsTable()
} }
@@ -207,6 +217,7 @@ export function useTransactionsPageProvider() {
const context: TransactionsPageContext = { const context: TransactionsPageContext = {
pagination: transactionPagination, pagination: transactionPagination,
items: transactionItems, items: transactionItems,
physicalItems: physicalReceiveItems,
tableOutput: transactionTableOutput, tableOutput: transactionTableOutput,
showFilter, showFilter,
dateFilter: transactionDateFilter, dateFilter: transactionDateFilter,
@@ -223,6 +234,7 @@ export function useTransactionsPageProvider() {
activeTab, activeTab,
statusOptions, statusOptions,
emptyText, emptyText,
listEmpty,
resetPagination: resetTransactionsPagination, resetPagination: resetTransactionsPagination,
initTable, initTable,
reloadTable: reloadTransactionsTable, reloadTable: reloadTransactionsTable,
@@ -1,7 +1,9 @@
import { ref } from 'vue' import { ref } from 'vue'
import { getTransactionTable, getTransactionTable2 } from '@/api/transaction' import { getTransactionTable, getTransactionTable2 } from '@/api/transaction'
import { getPhysicalReceiveList } from '@/api/physicalreceive'
import type { TableEngineInput, TableEngineOutput } from '@/models/table' import type { TableEngineInput, TableEngineOutput } from '@/models/table'
import type { Transaction } from '@/models/transaction' import type { Transaction } from '@/models/transaction'
import type { Physicalreceive } from '@/models/physicalreceive'
import { loading, loadingMore } from '@/utils' import { loading, loadingMore } from '@/utils'
import { import {
BOURSECU_TRANSACTION_TABS, BOURSECU_TRANSACTION_TABS,
@@ -17,7 +19,10 @@ let activeFetchId = 0
export const transactionsBoursecuMode = ref(true) export const transactionsBoursecuMode = ref(true)
export const transactionPagination = ref<TableEngineInput>({ filters: [], limit: 7, page: 0 }) export const transactionPagination = ref<TableEngineInput>({ filters: [], limit: 7, page: 0 })
export const transactionItems = ref<Transaction[]>([]) export const transactionItems = ref<Transaction[]>([])
export const transactionTableOutput = ref<TableEngineOutput<Transaction> | null>(null) export const physicalReceiveItems = ref<Physicalreceive[]>([])
export const transactionTableOutput = ref<TableEngineOutput<Transaction | Physicalreceive> | null>(
null,
)
export const transactionDateFilter = ref<string>() export const transactionDateFilter = ref<string>()
export const transactionDateFromFilter = ref<string>() export const transactionDateFromFilter = ref<string>()
export const transactionDateToFilter = ref<string>() export const transactionDateToFilter = ref<string>()
@@ -46,12 +51,19 @@ export function resetTransactionsPagination() {
function resolveTransactionFetcher() { function resolveTransactionFetcher() {
const activeTab = getActiveBoursecuTab() const activeTab = getActiveBoursecuTab()
return activeTab.fetchMode === 'deposit' ? getTransactionTable2 : getTransactionTable if (activeTab.fetchMode === 'deposit') {
return getTransactionTable2
}
if (activeTab.fetchMode === 'physical') {
return getPhysicalReceiveList
}
return getTransactionTable
} }
async function fetchTransactionsPage(append: boolean): Promise<boolean> { async function fetchTransactionsPage(append: boolean): Promise<boolean> {
const fetchId = ++activeFetchId const fetchId = ++activeFetchId
const isFirstPage = transactionPagination.value.page === 0 && !append const isFirstPage = transactionPagination.value.page === 0 && !append
const isPhysical = getActiveBoursecuTab().fetchMode === 'physical'
if (isFirstPage) { if (isFirstPage) {
loading.value = true loading.value = true
@@ -69,10 +81,22 @@ async function fetchTransactionsPage(append: boolean): Promise<boolean> {
transactionTableOutput.value = output transactionTableOutput.value = output
const rows = output.rows ?? [] const rows = output.rows ?? []
if (isFirstPage) { if (isPhysical) {
transactionItems.value = [...rows] const physicalRows = rows as Physicalreceive[]
if (isFirstPage) {
physicalReceiveItems.value = [...physicalRows]
transactionItems.value = []
} else {
physicalReceiveItems.value.push(...physicalRows)
}
} else { } else {
transactionItems.value.push(...rows) const txRows = rows as Transaction[]
if (isFirstPage) {
transactionItems.value = [...txRows]
physicalReceiveItems.value = []
} else {
transactionItems.value.push(...txRows)
}
} }
return true return true
@@ -93,6 +117,7 @@ async function fetchTransactionsPage(append: boolean): Promise<boolean> {
export async function reloadTransactionsTable() { export async function reloadTransactionsTable() {
transactionPagination.value.page = 0 transactionPagination.value.page = 0
transactionItems.value = [] transactionItems.value = []
physicalReceiveItems.value = []
transactionTableOutput.value = null transactionTableOutput.value = null
resetTransactionsPagination() resetTransactionsPagination()
await fetchTransactionsPage(false) await fetchTransactionsPage(false)
@@ -128,6 +153,7 @@ export function resetTransactionsPageData() {
transactionsBoursecuMode.value = true transactionsBoursecuMode.value = true
transactionPagination.value = { filters: [], limit: 7, page: 0 } transactionPagination.value = { filters: [], limit: 7, page: 0 }
transactionItems.value = [] transactionItems.value = []
physicalReceiveItems.value = []
transactionTableOutput.value = null transactionTableOutput.value = null
transactionDateFilter.value = undefined transactionDateFilter.value = undefined
transactionDateFromFilter.value = undefined transactionDateFromFilter.value = undefined
@@ -8,7 +8,7 @@ export type BoursecuTransactionTabId =
export type BoursecuTradeSide = 'buy' | 'sell' export type BoursecuTradeSide = 'buy' | 'sell'
export type BoursecuTransactionFetchMode = 'transaction' | 'deposit' export type BoursecuTransactionFetchMode = 'transaction' | 'deposit' | 'physical'
export type BoursecuTradeStatusFilter = 'completed' | 'pending' | 'cancelled' export type BoursecuTradeStatusFilter = 'completed' | 'pending' | 'cancelled'
export type BoursecuFulfillmentStatusFilter = 'delivered' | 'registered' | 'cancelled' export type BoursecuFulfillmentStatusFilter = 'delivered' | 'registered' | 'cancelled'
@@ -38,10 +38,11 @@ export const BOURSECU_TRADE_STATUS_OPTIONS: BoursecuTransactionStatusOption[] =
{ value: 'cancelled', label: 'لغو شده', sts: 3 }, { value: 'cancelled', label: 'لغو شده', sts: 3 },
] ]
/** وضعیت‌های سند برداشت فیزیکی (Withdraw::$STS) */
export const BOURSECU_FULFILLMENT_STATUS_OPTIONS: BoursecuTransactionStatusOption[] = [ export const BOURSECU_FULFILLMENT_STATUS_OPTIONS: BoursecuTransactionStatusOption[] = [
{ value: 'delivered', label: 'تحویل شده', sts: 2 }, { value: 'registered', label: 'در انتظار ارسال', sts: 50 },
{ value: 'registered', label: 'ثبت درخواست', sts: 1 }, { value: 'delivered', label: 'ارسال شده', sts: 51 },
{ value: 'cancelled', label: 'لغو شده', sts: 3 }, { value: 'cancelled', label: 'لغو شده', sts: 52 },
] ]
/** @deprecated Use getBoursecuStatusOptionsForTab instead */ /** @deprecated Use getBoursecuStatusOptionsForTab instead */
@@ -74,8 +75,8 @@ export const BOURSECU_TRANSACTION_TABS: BoursecuTransactionTab[] = [
id: 'physical', id: 'physical',
label: 'تحویل فیزیکی', label: 'تحویل فیزیکی',
emptyText: 'درخواست تحویل فیزیکی برای شما ثبت نشده است', emptyText: 'درخواست تحویل فیزیکی برای شما ثبت نشده است',
fetchMode: 'physical',
showAdvancedFilters: true, showAdvancedFilters: true,
typeFilters: [9],
}, },
] ]
@@ -97,11 +98,12 @@ export function getBoursecuStatusOptionsForTab(
} }
export function boursecuTabSupportsProductFilter(tab: BoursecuTransactionTab): boolean { export function boursecuTabSupportsProductFilter(tab: BoursecuTransactionTab): boolean {
return tab.id === 'trades' || tab.id === 'physical' // فیلتر محصول روی جدول سند برداشت فیزیکی (hasMany Transaction) قابل‌اعتماد نیست
return tab.id === 'trades'
} }
function getBoursecuStatusFilterField(tab: BoursecuTransactionTab): string { function getBoursecuStatusFilterField(tab: BoursecuTransactionTab): string {
if (tab.fetchMode === 'deposit') { if (tab.fetchMode === 'deposit' || tab.fetchMode === 'physical') {
return 'sts' return 'sts'
} }
@@ -129,7 +131,7 @@ export function buildBoursecuTransactionPagination(
): TableEngineInput { ): TableEngineInput {
const filters: TableEngineInput['filters'] = [] const filters: TableEngineInput['filters'] = []
if (tab.fetchMode !== 'deposit' && tab.typeFilters?.length) { if (tab.fetchMode !== 'deposit' && tab.fetchMode !== 'physical' && tab.typeFilters?.length) {
const typeValue = const typeValue =
tab.showTradeToggle && tab.typeFilters.includes(TRADE_TYPE_BY_SIDE[tradeSide]) tab.showTradeToggle && tab.typeFilters.includes(TRADE_TYPE_BY_SIDE[tradeSide])
? TRADE_TYPE_BY_SIDE[tradeSide] ? TRADE_TYPE_BY_SIDE[tradeSide]
@@ -42,12 +42,22 @@
<div ref="listRef" :class="loading ? 'min-h-[40vh]' : ''" class="mt-5"> <div ref="listRef" :class="loading ? 'min-h-[40vh]' : ''" class="mt-5">
<div v-if="!loading" class="dtxn-list"> <div v-if="!loading" class="dtxn-list">
<list-anim> <list-anim>
<Item <template v-if="activeTab.fetchMode === 'physical'">
v-for="item in items" <RequestItem
:key="item.id" v-for="item in physicalItems"
:item="item" :key="item.id"
variant="desktop" :item="item"
/> variant="desktop"
/>
</template>
<template v-else>
<Item
v-for="item in items"
:key="item.id"
:item="item"
variant="desktop"
/>
</template>
</list-anim> </list-anim>
<div :ref="bindDesktopObserver" class="w-full opacity-0 relative"> <div :ref="bindDesktopObserver" class="w-full opacity-0 relative">
در حال بارگذاری در حال بارگذاری
@@ -59,7 +69,7 @@
<TransactionSkeleton v-for="i in 8" :key="i" class="mt-1" /> <TransactionSkeleton v-for="i in 8" :key="i" class="mt-1" />
</template> </template>
<div v-if="!loading && items.length === 0" class="dtxn-empty"> <div v-if="!loading && listEmpty" class="dtxn-empty">
<img :src="emptyImage" alt="" class="dtxn-empty__image" /> <img :src="emptyImage" alt="" class="dtxn-empty__image" />
<p class="dtxn-empty__text">{{ emptyText }}</p> <p class="dtxn-empty__text">{{ emptyText }}</p>
</div> </div>
@@ -76,6 +86,7 @@ import ListAnim from '@/components/ListAnim.vue'
import TransactionSkeleton from '@/components/TransactionSkeleton.vue' import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
import emptyImage from '@/assets/img/cash_empty.svg' import emptyImage from '@/assets/img/cash_empty.svg'
import Item from '../components/Item.vue' import Item from '../components/Item.vue'
import RequestItem from '@/views/base/physicalReceive/components/RequestItem.vue'
import BoursecuTransactionFilters from '../components/BoursecuTransactionFilters.vue' import BoursecuTransactionFilters from '../components/BoursecuTransactionFilters.vue'
import { BOURSECU_TRANSACTION_TABS } from '../config/boursecuTabs' import { BOURSECU_TRANSACTION_TABS } from '../config/boursecuTabs'
import { useTransactionsPageContext } from '../composables/useTransactionsPage' import { useTransactionsPageContext } from '../composables/useTransactionsPage'
@@ -84,10 +95,12 @@ const listRef = ref<HTMLElement | null>(null)
const { const {
items, items,
physicalItems,
activeTabId, activeTabId,
tradeSide, tradeSide,
activeTab, activeTab,
emptyText, emptyText,
listEmpty,
setActiveTab, setActiveTab,
setTradeSide, setTradeSide,
bindDesktopObserver, bindDesktopObserver,