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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,9 @@ import { getUsdcBalance, formatUsdcBalance, getUserInfo } from '@/api/user'
import { getUserWsUrl, BASE_URL } from '@/api/request' import { getUserWsUrl, BASE_URL } from '@/api/request'
import { jsonInBlacklist } from '@/api/jwt' import { jsonInBlacklist } from '@/api/jwt'
import { UserSdk, type BalanceData, type PositionData } from '../../sdk/userSocket' 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 { export interface UserInfo {
/** 用户 IDAPI 可能返回 id 或 ID */ /** 用户 IDAPI 可能返回 id 或 ID */
@ -82,6 +85,14 @@ export const useUserStore = defineStore('user', () => {
/** 钱包余额显示,如 "0.00",可从接口或 UserSocket 推送更新 */ /** 钱包余额显示,如 "0.00",可从接口或 UserSocket 推送更新 */
const balance = ref<string>('0.00') 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 let userSdkRef: UserSdk | null = null
const positionUpdateCallbacks: ((data: PositionData & Record<string, unknown>) => void)[] = [] const positionUpdateCallbacks: ((data: PositionData & Record<string, unknown>) => void)[] = []
@ -109,6 +120,7 @@ export const useUserStore = defineStore('user', () => {
}) })
sdk.onPositionUpdate((data) => { sdk.onPositionUpdate((data) => {
positionUpdateCallbacks.forEach((cb) => cb(data as PositionData & Record<string, unknown>)) positionUpdateCallbacks.forEach((cb) => cb(data as PositionData & Record<string, unknown>))
fetchPositionsValue()
}) })
sdk.onConnect(() => {}) sdk.onConnect(() => {})
sdk.onDisconnect(() => {}) 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 { return {
token, token,
user, user,
@ -238,12 +277,15 @@ export const useUserStore = defineStore('user', () => {
displayName, displayName,
avatarUrl, avatarUrl,
balance, balance,
positionsValue,
totalAssetValue,
setUser, setUser,
logout, logout,
clearLocalSession, clearLocalSession,
getAuthHeaders, getAuthHeaders,
fetchUsdcBalance, fetchUsdcBalance,
fetchUserInfo, fetchUserInfo,
fetchPositionsValue,
connectUserSocket, connectUserSocket,
disconnectUserSocket, disconnectUserSocket,
onPositionUpdate, onPositionUpdate,

View File

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

View File

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