修改:余额显示为持仓加可用余额

This commit is contained in:
马丁 2026-04-09 00:07:32 +08:00
parent fb74c20607
commit 2ceb735d48
11 changed files with 104 additions and 16 deletions

View File

@ -57,6 +57,7 @@ async function refreshUserData() {
await nextTick()
await userStore.fetchUserInfo()
await userStore.fetchUsdcBalance()
await userStore.fetchPositionsValue()
}
onMounted(() => {
@ -132,7 +133,7 @@ watch(
padding="4 12"
@click="$router.push('/wallet')"
>
<span class="balance-text">${{ userStore.balance }}</span>
<span class="balance-text">${{ userStore.totalAssetValue }}</span>
</v-btn>
<v-btn
icon

View File

@ -197,6 +197,7 @@ export interface MockPosition {
bet: string
toWin: string
value: string
currentValueNum?: number
valueChange?: string
valueChangePct?: string
valueChangeLoss?: boolean
@ -262,6 +263,7 @@ export const MOCK_WALLET_POSITIONS: MockPosition[] = [
bet: '$4.95',
toWin: '$6.87',
value: '$0.03',
currentValueNum: 0.03,
valueChange: '-$4.91',
valueChangePct: '99.31%',
valueChangeLoss: true,
@ -285,6 +287,7 @@ export const MOCK_WALLET_POSITIONS: MockPosition[] = [
bet: '$0.99',
toWin: '$3.54',
value: '$0.90',
currentValueNum: 0.90,
valueChange: '-$0.09',
valueChangePct: '8.9%',
valueChangeLoss: true,
@ -303,6 +306,7 @@ export const MOCK_WALLET_POSITIONS: MockPosition[] = [
bet: '$30',
toWin: '$36',
value: '$32',
currentValueNum: 32.00,
valueChange: '+$2',
valueChangePct: '6.67%',
valueChangeLoss: false,

View File

@ -142,6 +142,7 @@ export interface PositionDisplayItem {
bet: string
toWin: string
value: string
currentValueNum?: number
valueChange?: string
valueChangePct?: string
valueChangeLoss?: boolean
@ -220,18 +221,29 @@ export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplay
// AVG成本/份额NOWmarket.outcomePrices 中对应 outcome 的当前价
let avgNow = '—'
let currentValueNum = costUsd
let change = 0
let changePct = 0
if (size > 0 && costUsd > 0) {
const avgCents = Math.round((costUsd / size) * 100)
const avgVal = costUsd / size
const avgCents = Math.round(avgVal * 100)
const avgStr = `${avgCents}¢`
const prices = pos.market?.outcomePrices
let nowVal: number | null = null
if (prices?.length) {
const idx = getOutcomePriceIndex(pos.market?.outcomes, outcome)
const nowVal = typeof prices[idx] === 'string' ? parseFloat(prices[idx] as string) : Number(prices[idx])
nowVal = typeof prices[idx] === 'string' ? parseFloat(prices[idx] as string) : Number(prices[idx])
const nowCents = Number.isFinite(nowVal) ? Math.round(nowVal * 100) : null
avgNow = nowCents != null ? `${avgStr}${nowCents}¢` : avgStr
} else {
avgNow = avgStr
}
if (nowVal != null && Number.isFinite(nowVal)) {
currentValueNum = size * nowVal
}
change = currentValueNum - costUsd
changePct = costUsd > 0 ? (change / costUsd) * 100 : 0
}
const marketID = String(pos.marketID ?? pos.market?.ID ?? '')
@ -252,6 +264,10 @@ export function mapPositionToDisplayItem(pos: ClobPositionItem): PositionDisplay
bet,
toWin,
value,
currentValueNum,
valueChange: `${change >= 0 ? '+' : ''}$${Math.abs(change).toFixed(2)}`,
valueChangePct: `${changePct.toFixed(2)}%`,
valueChangeLoss: change < 0,
sellOutcome: outcome,
outcomeWord: outcome,
outcomeTag,

View File

@ -203,6 +203,7 @@
"weeksAgo": "{n}w ago"
},
"wallet": {
"availableAssets": "Available Assets",
"portfolio": "Portfolio",
"today": "Today",
"deposit": "Deposit",

View File

@ -203,6 +203,7 @@
"weeksAgo": "{n}週間前"
},
"wallet": {
"availableAssets": "利用可能資産",
"portfolio": "ポートフォリオ",
"today": "今日",
"deposit": "入金",

View File

@ -203,6 +203,7 @@
"weeksAgo": "{n}주 전"
},
"wallet": {
"availableAssets": "사용 가능 자산",
"portfolio": "포트폴리오",
"today": "오늘",
"deposit": "입금",

View File

@ -203,6 +203,7 @@
"weeksAgo": "{n}周前"
},
"wallet": {
"availableAssets": "可用资产",
"portfolio": "资产组合",
"today": "今日",
"deposit": "入金",

View File

@ -203,7 +203,8 @@
"weeksAgo": "{n}週前"
},
"wallet": {
"portfolio": "資產組合",
"availableAssets": "可用資產",
"portfolio": "資產組合",
"today": "今日",
"deposit": "入金",
"withdraw": "提現",

View File

@ -4,6 +4,9 @@ import { getUsdcBalance, formatUsdcBalance, getUserInfo } from '@/api/user'
import { getUserWsUrl, BASE_URL } from '@/api/request'
import { jsonInBlacklist } from '@/api/jwt'
import { UserSdk, type BalanceData, type PositionData } from '../../sdk/userSocket'
import { getPositionList, mapPositionToDisplayItem } from '@/api/position'
import { USE_MOCK_WALLET } from '@/config/mock'
import { MOCK_WALLET_POSITIONS } from '@/api/mockData'
export interface UserInfo {
/** 用户 IDAPI 可能返回 id 或 ID */
@ -82,6 +85,14 @@ export const useUserStore = defineStore('user', () => {
/** 钱包余额显示,如 "0.00",可从接口或 UserSocket 推送更新 */
const balance = ref<string>('0.00')
const positionsValue = ref<number>(0)
/** 总资产:余额 + 仓位当前价值 */
const totalAssetValue = computed(() => {
const bal = parseFloat(balance.value) || 0
return (bal + positionsValue.value).toFixed(2)
})
let userSdkRef: UserSdk | null = null
const positionUpdateCallbacks: ((data: PositionData & Record<string, unknown>) => void)[] = []
@ -109,6 +120,7 @@ export const useUserStore = defineStore('user', () => {
})
sdk.onPositionUpdate((data) => {
positionUpdateCallbacks.forEach((cb) => cb(data as PositionData & Record<string, unknown>))
fetchPositionsValue()
})
sdk.onConnect(() => {})
sdk.onDisconnect(() => {})
@ -231,6 +243,33 @@ export const useUserStore = defineStore('user', () => {
}
}
/** 获取仓位并更新仓位总价值 */
async function fetchPositionsValue() {
if (USE_MOCK_WALLET) {
positionsValue.value = MOCK_WALLET_POSITIONS.reduce((acc, p) => acc + (p.currentValueNum || 0), 0)
return
}
const headers = getAuthHeaders()
if (!headers) return
try {
const uid = user.value?.id ?? user.value?.ID
const userID = uid != null ? Number(uid) : undefined
if (!userID || !Number.isFinite(userID)) return
const res = await getPositionList(
{ page: 1, pageSize: 1000, userID },
{ headers },
)
if (res.code === 0 || res.code === 200) {
const list = res.data?.list ?? []
const posList = list.map(mapPositionToDisplayItem)
positionsValue.value = posList.reduce((acc, p) => acc + (p.currentValueNum || 0), 0)
}
} catch (e) {
console.error('[fetchPositionsValue] 请求失败:', e)
}
}
return {
token,
user,
@ -238,12 +277,15 @@ export const useUserStore = defineStore('user', () => {
displayName,
avatarUrl,
balance,
positionsValue,
totalAssetValue,
setUser,
logout,
clearLocalSession,
getAuthHeaders,
fetchUsdcBalance,
fetchUserInfo,
fetchPositionsValue,
connectUserSocket,
disconnectUserSocket,
onPositionUpdate,

View File

@ -46,7 +46,9 @@
{{ t('profile.walletDetail') }} &gt;
</button>
</div>
<div class="wallet-balance">${{ totalBalance }}</div>
<div class="wallet-balance">
${{ totalBalance }}
</div>
<div class="wallet-sub">
{{ t('profile.walletSub', { available: availableBalance, frozen: frozenBalance }) }}
</div>
@ -244,11 +246,11 @@ const userNameRaw = computed(() => {
})
/** 显示用:统一取 nickNameuserStore.displayName */
const displayName = computed(() => userStore.displayName || t('profile.defaultName'))
const userIdText = computed(() => {
const uid = rawUser.value.id ?? rawUser.value.ID
if (uid == null || uid === '') return '--'
return String(uid)
})
// const userIdText = computed(() => {
// const uid = rawUser.value.id ?? rawUser.value.ID
// if (uid == null || uid === '') return '--'
// return String(uid)
// })
const avatarImage = computed(() => userStore.avatarUrl || '')
const avatarText = computed(() => {
const first = displayName.value.trim().charAt(0)
@ -265,7 +267,7 @@ const hasVip = computed(() => {
})
})
const userTag = computed(() => (hasVip.value ? t('profile.vipTrader') : t('profile.trader')))
const totalBalance = computed(() => userStore.balance || '0.00')
const totalBalance = computed(() => userStore.totalAssetValue || '0.00')
const availableBalance = computed(() => {
const val = readStringFromUser(['availableBalance', 'available', 'walletAvailable'])
return val || totalBalance.value

View File

@ -1,12 +1,15 @@
<template>
<v-container class="wallet-container">
<div class="wallet-mobile-frame">
<div class="wallet-mobile-header">{{ t('wallet.walletTitle') }}</div>
<div class="wallet-mobile-header">{{ t('wallet.portfolio') }}</div>
<v-card class="wallet-card portfolio-card design-tu-asset" elevation="0" rounded="lg">
<div class="card-header">
<span class="card-title">{{ t('wallet.portfolio') }}</span>
</div>
<div class="card-value">${{ portfolioBalance }}</div>
<div class="card-value">
<div class="main-balance">${{ portfolioBalance }}</div>
<div class="available-balance">({{ t('wallet.availableAssets') }}: ${{ userStore.balance }})</div>
</div>
</v-card>
<div class="card-actions design-tu-actions">
<v-btn
@ -588,7 +591,8 @@ const { mobile } = useDisplay()
const userStore = useUserStore()
const { formatAuthError } = useAuthError()
const localeStore = useLocaleStore()
const portfolioBalance = computed(() => userStore.balance)
const portfolioBalance = computed(() => userStore.totalAssetValue)
const positionsValueText = computed(() => userStore.positionsValue.toFixed(2))
const profitLoss = ref('0.00')
const plRange = ref('ALL')
const plTimeRanges = computed(() => [
@ -1640,10 +1644,24 @@ async function submitAuthorize() {
}
.card-value {
display: flex;
flex-direction: column;
gap: 4px;
}
.main-balance {
font-size: 32px;
font-weight: 700;
color: #111827;
margin-bottom: 0;
color: inherit;
line-height: 1.1;
}
.available-balance {
font-size: 16px;
font-weight: 500;
opacity: 0.8;
color: inherit;
line-height: 1.2;
}
.card-value-row {