修改:入金面板做成组件调用

This commit is contained in:
马丁 2026-07-04 20:12:45 +08:00
parent a42eeeeaed
commit 49ccbec0da
4 changed files with 394 additions and 144 deletions

View File

@ -0,0 +1,298 @@
<template>
<div class="history-mobile-list">
<div v-if="loading" class="empty-cell empty-cell--loading">
<v-progress-circular indeterminate size="36" width="2" />
<span>{{ t('common.loading') }}</span>
</div>
<div v-else-if="displayList.length === 0" class="empty-cell">
{{ t('common.noData') }}
</div>
<div
v-for="h in displayList"
:key="h.id"
class="history-mobile-card design-history-card"
:class="[
{ 'wallet-row--clickable': true },
{ 'wallet-row--nav': historyRowCanOpenDetail(h) },
]"
:role="historyRowCanOpenDetail(h) ? 'button' : undefined"
:tabindex="historyRowCanOpenDetail(h) ? 0 : undefined"
@click="onHistoryRowClick(h)"
@keydown.enter.prevent="onHistoryRowClick(h)"
@keydown.space.prevent="onHistoryRowClick(h)"
>
<div class="design-funding-left">
<div class="history-mobile-icon funding-icon-shell">
<div class="funding-icon-disc">
<v-icon size="20" class="funding-icon-usd">mdi-currency-usd</v-icon>
</div>
</div>
<div class="history-mobile-main">
<MarqueeTitle
:text="getFundingTitle(h, t)"
title-class="history-mobile-title"
fast
/>
<div class="history-mobile-activity">{{ h.timeAgo || '—' }}</div>
</div>
</div>
<span
:class="[
'history-mobile-pl',
isWithdrawalHistory(h) || h.profitLossNegative ? 'pl-loss' : 'pl-gain',
]"
>
{{ h.profitLoss ?? h.value }}
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import MarqueeTitle from '@/components/MarqueeTitle.vue'
import { useUserStore } from '@/stores/user'
import {
getHistoryRecordListClient,
getHistoryRecordList,
type HistoryDisplayItem,
} from '@/api/historyRecord'
import { MOCK_WALLET_HISTORY } from '@/api/mockData'
import { USE_MOCK_WALLET } from '@/config/mock'
import {
getFundingTitle,
isFundingHistory,
isWithdrawalHistory,
} from '@/utils/walletFundingHistory'
const props = defineProps<{
page: number
itemsPerPage: number
search: string
active: boolean
}>()
const emit = defineEmits<{
'update:total': [total: number]
}>()
const { t } = useI18n()
const userStore = useUserStore()
const mockHistory = ref<HistoryDisplayItem[]>(
USE_MOCK_WALLET ? [...MOCK_WALLET_HISTORY] : [],
)
const depositHistoryList = ref<HistoryDisplayItem[]>([])
const depositHistoryTotal = ref(0)
const loading = ref(false)
function matchSearch(text: string): boolean {
const q = props.search.trim().toLowerCase()
return !q || text.toLowerCase().includes(q)
}
function historyRowCanOpenDetail(_h: HistoryDisplayItem): boolean {
return false
}
function onHistoryRowClick(h: HistoryDisplayItem) {
if (isFundingHistory(h)) return
}
const filteredList = computed(() => {
const list = USE_MOCK_WALLET ? mockHistory.value : depositHistoryList.value
return list.filter(
(h) => matchSearch(h.market) && (USE_MOCK_WALLET ? isFundingHistory(h) : true),
)
})
const displayList = computed(() => {
if (USE_MOCK_WALLET) {
const start = (props.page - 1) * props.itemsPerPage
return filteredList.value.slice(start, start + props.itemsPerPage)
}
return filteredList.value
})
const listTotal = computed(() =>
USE_MOCK_WALLET ? filteredList.value.length : depositHistoryTotal.value,
)
watch(
listTotal,
(total) => {
emit('update:total', total)
},
{ immediate: true },
)
async function loadDepositHistoryOrders() {
if (USE_MOCK_WALLET) return
const headers = userStore.getAuthHeaders()
if (!headers) {
depositHistoryList.value = []
depositHistoryTotal.value = 0
return
}
const uid = userStore.user?.id ?? userStore.user?.ID
const userID = uid != null ? Number(uid) : undefined
if (!userID || !Number.isFinite(userID)) {
depositHistoryList.value = []
depositHistoryTotal.value = 0
return
}
loading.value = true
try {
const res = await getHistoryRecordListClient(
{
page: props.page,
pageSize: props.itemsPerPage,
userId: userID,
isFunding: true,
},
{ headers },
)
if (res.code === 0 || res.code === 200) {
const { list, total } = getHistoryRecordList(res.data)
depositHistoryList.value = list
depositHistoryTotal.value = total
} else {
depositHistoryList.value = []
depositHistoryTotal.value = 0
}
} catch {
depositHistoryList.value = []
depositHistoryTotal.value = 0
} finally {
loading.value = false
}
}
watch(
() => [props.active, props.page, props.itemsPerPage] as const,
([active]) => {
if (active && !USE_MOCK_WALLET) loadDepositHistoryOrders()
},
{ immediate: true },
)
defineExpose({ reload: loadDepositHistoryOrders })
</script>
<style scoped>
.history-mobile-list {
padding: 0;
}
.history-mobile-list .empty-cell {
padding: 48px 16px;
text-align: center;
color: #6b7280;
font-size: 14px;
}
.history-mobile-card {
border-bottom: 1px solid #f3f4f6;
padding: 14px 16px;
cursor: pointer;
transition: background-color 0.15s ease;
}
.history-mobile-card:last-child {
border-bottom: none;
}
.history-mobile-card:hover {
background-color: #fafafa;
}
.design-funding-left {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
flex: 1;
}
.history-mobile-icon {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: #fff;
font-weight: 700;
font-size: 18px;
}
.funding-icon-disc {
width: 28px;
height: 28px;
border-radius: 50%;
background: #2b66ff;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
flex-shrink: 0;
}
.funding-icon-usd {
color: #ffffff !important;
}
.history-mobile-main {
flex: 1;
min-width: 0;
}
.history-mobile-main :deep(.history-mobile-title) {
font-size: 14px;
font-weight: 500;
color: #111827;
line-height: 1.35;
margin-bottom: 4px;
}
.history-mobile-activity {
font-size: 13px;
color: #6b7280;
}
.history-mobile-pl {
font-size: 14px;
font-weight: 600;
}
.pl-gain,
.pl-loss {
color: #111827;
}
.wallet-row--nav {
cursor: pointer;
}
.wallet-row--nav:focus-visible {
outline: 2px solid rgb(var(--v-theme-primary));
outline-offset: 2px;
}
.empty-cell {
text-align: center;
color: #6b7280;
padding: 48px 16px !important;
font-size: 14px;
}
.empty-cell--loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<v-btn
color="default"
variant="outlined"
class="action-btn design-tu-action"
@click="dialogOpen = true"
>
{{ t('wallet.deposit') }}
</v-btn>
<Teleport to="body">
<DepositDialog v-model="dialogOpen" />
</Teleport>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import DepositDialog from '@/components/DepositDialog.vue'
const { t } = useI18n()
const dialogOpen = ref(false)
function open() {
dialogOpen.value = true
}
defineExpose({ open })
</script>
<style scoped>
.design-tu-action {
flex: 1;
height: 72px;
border-radius: 12px;
border: 1px solid #e5e7eb;
background: #fff;
color: #111827;
font-size: 20px;
font-weight: 600;
}
.design-tu-action :deep(.v-btn__content) {
font-size: 20px;
}
.action-btn {
text-transform: none;
}
</style>

View File

@ -0,0 +1,26 @@
import type { HistoryDisplayItem } from '@/api/historyRecord'
export function isWithdrawalHistory(h: HistoryDisplayItem): boolean {
const text = `${h.activity} ${h.activityDetail ?? ''} ${h.market}`.toLowerCase()
return text.includes('withdraw') || text.includes('提现')
}
export function isFundingHistory(h: HistoryDisplayItem): boolean {
const text = `${h.activity} ${h.activityDetail ?? ''} ${h.market}`.toLowerCase()
return (
text.includes('deposit') ||
text.includes('充值') ||
text.includes('withdraw') ||
text.includes('提现')
)
}
export function getFundingTitle(
h: HistoryDisplayItem,
t: (key: string) => string,
): string {
if (h.market?.trim()) return h.market
return isWithdrawalHistory(h)
? t('wallet.btcWithdrawHistoryLabel')
: t('wallet.usdtDepositHistoryLabel')
}

View File

@ -12,14 +12,7 @@
</div>
</v-card>
<div class="card-actions design-tu-actions">
<v-btn
color="default"
variant="outlined"
class="action-btn design-tu-action"
@click="depositDialogOpen = true"
>
{{ t('wallet.deposit') }}
</v-btn>
<WalletDepositModule />
<v-btn
color="default"
variant="outlined"
@ -304,7 +297,7 @@
</div>
<div class="history-mobile-main">
<MarqueeTitle
:text="getFundingTitle(h)"
:text="getFundingTitle(h, t)"
title-class="history-mobile-title"
fast
/>
@ -324,53 +317,13 @@
</div>
</template>
<template v-else-if="activeTab === 'depositHistory'">
<div class="history-mobile-list">
<div v-if="depositHistoryLoading" class="empty-cell empty-cell--loading">
<v-progress-circular indeterminate size="36" width="2" />
<span>{{ t('common.loading') }}</span>
</div>
<div v-else-if="filteredDepositHistory.length === 0" class="empty-cell">
{{ t('common.noData') }}
</div>
<div
v-for="h in paginatedDepositHistory"
:key="h.id"
class="history-mobile-card design-history-card"
:class="[
{ 'wallet-row--clickable': true },
{ 'wallet-row--nav': historyRowCanOpenDetail(h) },
]"
:role="historyRowCanOpenDetail(h) ? 'button' : undefined"
:tabindex="historyRowCanOpenDetail(h) ? 0 : undefined"
@click="onHistoryRowClick(h)"
@keydown.enter.prevent="onHistoryRowClick(h)"
@keydown.space.prevent="onHistoryRowClick(h)"
>
<div class="design-funding-left">
<div class="history-mobile-icon funding-icon-shell">
<div class="funding-icon-disc">
<v-icon size="20" class="funding-icon-usd">mdi-currency-usd</v-icon>
</div>
</div>
<div class="history-mobile-main">
<MarqueeTitle
:text="getFundingTitle(h)"
title-class="history-mobile-title"
fast
<WalletDepositHistoryPanel
:page="page"
:items-per-page="itemsPerPage"
:search="search"
:active="activeTab === 'depositHistory'"
@update:total="depositHistoryTotal = $event"
/>
<div class="history-mobile-activity">{{ h.timeAgo || '—' }}</div>
</div>
</div>
<span
:class="[
'history-mobile-pl',
isWithdrawalHistory(h) || h.profitLossNegative ? 'pl-loss' : 'pl-gain',
]"
>
{{ h.profitLoss ?? h.value }}
</span>
</div>
</div>
</template>
<template v-else-if="activeTab === 'withdrawals'">
<div class="withdrawals-mobile-list">
@ -457,7 +410,6 @@
</div>
</div>
<DepositDialog v-model="depositDialogOpen" />
<WithdrawDialog
v-model="withdrawDialogOpen"
:balance="portfolioBalance"
@ -591,7 +543,8 @@ import { useDisplay } from 'vuetify'
const { t } = useI18n()
import { createChart, AreaSeries, LineType, LastPriceAnimationMode } from 'lightweight-charts'
import { toLwcData } from '../composables/useLightweightChart'
import DepositDialog from '../components/DepositDialog.vue'
import WalletDepositModule from '../components/WalletDepositModule.vue'
import WalletDepositHistoryPanel from '../components/WalletDepositHistoryPanel.vue'
import WithdrawDialog from '../components/WithdrawDialog.vue'
import MarqueeTitle from '../components/MarqueeTitle.vue'
import { useUserStore } from '../stores/user'
@ -621,6 +574,11 @@ import {
import { USE_MOCK_WALLET } from '../config/mock'
import { CrossChainUSDTAuth } from '../../sdk/approve'
import { useToastStore } from '../stores/toast'
import {
getFundingTitle,
isFundingHistory,
isWithdrawalHistory,
} from '../utils/walletFundingHistory'
/** 远程 iconUrl 加载失败时改显示 MDI 矢量图标,与全局 Vuetify 图标体系一致 */
const walletRemoteIconFailed = ref<Set<string>>(new Set())
@ -707,8 +665,8 @@ async function onClaimSettlement() {
claimLoading.value = false
}
}
const depositDialogOpen = ref(false)
const withdrawDialogOpen = ref(false)
const depositHistoryTotal = ref(0)
const authorizeDialogOpen = ref(false)
const sellDialogOpen = ref(false)
const sellPositionItem = ref<Position | null>(null)
@ -889,28 +847,6 @@ function getOrderDisplayTitle(ord: OpenOrder): string {
return ord.outcome?.trim() ? `Market · ${ord.outcome}` : 'Untitled Market'
}
function isWithdrawalHistory(h: HistoryItem): boolean {
const text = `${h.activity} ${h.activityDetail ?? ''} ${h.market}`.toLowerCase()
return text.includes('withdraw') || text.includes('提现')
}
function isFundingHistory(h: HistoryItem): boolean {
const text = `${h.activity} ${h.activityDetail ?? ''} ${h.market}`.toLowerCase()
return (
text.includes('deposit') ||
text.includes('充值') ||
text.includes('withdraw') ||
text.includes('提现')
)
}
function getFundingTitle(h: HistoryItem): string {
if (h.market?.trim()) return h.market
return isWithdrawalHistory(h)
? t('wallet.btcWithdrawHistoryLabel')
: t('wallet.usdtDepositHistoryLabel')
}
function getHistoryTagLabel(h: HistoryItem): string {
if (!h.recordType) {
return h.profitLossNegative ? t('wallet.historySideBuy') : t('wallet.historySideSell')
@ -1076,10 +1012,6 @@ const historyList = ref<HistoryItem[]>([])
const historyTotal = ref(0)
const historyLoading = ref(false)
const depositHistoryList = ref<HistoryItem[]>([])
const depositHistoryTotal = ref(0)
const depositHistoryLoading = ref(false)
/** 历史记录来自 GET /hr/getHistoryRecordListClient需鉴权按当前用户分页 */
async function loadHistoryOrders() {
if (USE_MOCK_WALLET) return
@ -1123,48 +1055,6 @@ async function loadHistoryOrders() {
}
}
async function loadDepositHistoryOrders() {
if (USE_MOCK_WALLET) return
const headers = userStore.getAuthHeaders()
if (!headers) {
depositHistoryList.value = []
depositHistoryTotal.value = 0
return
}
const uid = userStore.user?.id ?? userStore.user?.ID
const userID = uid != null ? Number(uid) : undefined
if (!userID || !Number.isFinite(userID)) {
depositHistoryList.value = []
depositHistoryTotal.value = 0
return
}
depositHistoryLoading.value = true
try {
const res = await getHistoryRecordListClient(
{
page: page.value,
pageSize: itemsPerPage.value,
userId: userID,
isFunding: true,
},
{ headers },
)
if (res.code === 0 || res.code === 200) {
const { list, total } = getHistoryRecordList(res.data)
depositHistoryList.value = list
depositHistoryTotal.value = total
} else {
depositHistoryList.value = []
depositHistoryTotal.value = 0
}
} catch {
depositHistoryList.value = []
depositHistoryTotal.value = 0
} finally {
depositHistoryLoading.value = false
}
}
async function loadWithdrawals() {
const headers = userStore.getAuthHeaders()
if (!headers) {
@ -1307,11 +1197,6 @@ const filteredHistory = computed(() => {
const list = USE_MOCK_WALLET ? history.value : historyList.value
return list.filter((h) => matchSearch(h.market) && (USE_MOCK_WALLET ? !isFundingHistory(h) : true))
})
const filteredDepositHistory = computed(() => {
const list = USE_MOCK_WALLET ? history.value : depositHistoryList.value
return list.filter((h) => matchSearch(h.market) && (USE_MOCK_WALLET ? isFundingHistory(h) : true))
})
const page = ref(1)
const itemsPerPage = ref(10)
const pageSizeOptions = [5, 10, 25, 50]
@ -1332,11 +1217,6 @@ const paginatedHistory = computed(() => {
if (USE_MOCK_WALLET) return paginate(filteredHistory.value)
return filteredHistory.value
})
const paginatedDepositHistory = computed(() => {
if (USE_MOCK_WALLET) return paginate(filteredDepositHistory.value)
return filteredDepositHistory.value
})
const totalPagesPositions = computed(() => {
const total = USE_MOCK_WALLET ? filteredPositions.value.length : positionTotal.value
return Math.max(1, Math.ceil(total / itemsPerPage.value))
@ -1349,10 +1229,9 @@ const totalPagesHistory = computed(() => {
const total = USE_MOCK_WALLET ? filteredHistory.value.length : historyTotal.value
return Math.max(1, Math.ceil(total / itemsPerPage.value))
})
const totalPagesDepositHistory = computed(() => {
const total = USE_MOCK_WALLET ? filteredDepositHistory.value.length : depositHistoryTotal.value
return Math.max(1, Math.ceil(total / itemsPerPage.value))
})
const totalPagesDepositHistory = computed(() =>
Math.max(1, Math.ceil(depositHistoryTotal.value / itemsPerPage.value)),
)
const totalPagesWithdrawals = computed(() =>
Math.max(1, Math.ceil(withdrawalsTotal.value / itemsPerPage.value)),
)
@ -1363,7 +1242,7 @@ const currentListTotal = computed(() => {
if (activeTab.value === 'orders')
return USE_MOCK_WALLET ? filteredOpenOrders.value.length : openOrderTotal.value
if (activeTab.value === 'withdrawals') return withdrawalsTotal.value
if (activeTab.value === 'depositHistory') return USE_MOCK_WALLET ? filteredDepositHistory.value.length : depositHistoryTotal.value
if (activeTab.value === 'depositHistory') return depositHistoryTotal.value
return USE_MOCK_WALLET ? filteredHistory.value.length : historyTotal.value
})
const currentTotalPages = computed(() => {
@ -1385,14 +1264,12 @@ watch(activeTab, (tab) => {
if (tab === 'positions' && !USE_MOCK_WALLET) loadPositionList()
if (tab === 'orders' && !USE_MOCK_WALLET) loadOpenOrders()
if (tab === 'history' && !USE_MOCK_WALLET) loadHistoryOrders()
if (tab === 'depositHistory' && !USE_MOCK_WALLET) loadDepositHistoryOrders()
if (tab === 'withdrawals') loadWithdrawals()
})
watch([page, itemsPerPage], () => {
if (activeTab.value === 'positions' && !USE_MOCK_WALLET) loadPositionList()
if (activeTab.value === 'orders' && !USE_MOCK_WALLET) loadOpenOrders()
if (activeTab.value === 'history' && !USE_MOCK_WALLET) loadHistoryOrders()
if (activeTab.value === 'depositHistory' && !USE_MOCK_WALLET) loadDepositHistoryOrders()
if (activeTab.value === 'withdrawals') loadWithdrawals()
})
watch(withdrawStatusFilter, () => {