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