diff --git a/apps/player/src/i18n/en-US.ts b/apps/player/src/i18n/en-US.ts index b913f23..e58f996 100644 --- a/apps/player/src/i18n/en-US.ts +++ b/apps/player/src/i18n/en-US.ts @@ -124,6 +124,9 @@ export default { stats_stake: 'Total Stake', stats_return: 'Total Return', cashbacked: 'Cashbacked', + profit: 'Profit', + return_incl_stake: 'Return {amount} (incl. stake)', + stake_to_return: '{stake} × {odds} = {amount}', }, auth: { login: 'Login', diff --git a/apps/player/src/i18n/ms-MY.ts b/apps/player/src/i18n/ms-MY.ts index fac4d6b..ae32182 100644 --- a/apps/player/src/i18n/ms-MY.ts +++ b/apps/player/src/i18n/ms-MY.ts @@ -130,6 +130,9 @@ export default { stats_stake: 'Jumlah Taruhan', stats_return: 'Jumlah Pulangan', cashbacked: 'Rebat dibayar', + profit: 'Keuntungan', + return_incl_stake: 'Pulangan {amount} (termasuk jumlah)', + stake_to_return: '{stake} × {odds} = {amount}', }, auth: { login: 'Log Masuk', diff --git a/apps/player/src/i18n/zh-CN.ts b/apps/player/src/i18n/zh-CN.ts index 5c01846..1a8cbb2 100644 --- a/apps/player/src/i18n/zh-CN.ts +++ b/apps/player/src/i18n/zh-CN.ts @@ -124,6 +124,9 @@ export default { stats_stake: '总投注额', stats_return: '总回报', cashbacked: '已回水', + profit: '净赢', + return_incl_stake: '回报 {amount}(含本金)', + stake_to_return: '{stake} × {odds} = {amount}', }, auth: { login: '登录', diff --git a/apps/player/src/views/BetDetailView.vue b/apps/player/src/views/BetDetailView.vue index 1f65d1a..ff8fb52 100644 --- a/apps/player/src/views/BetDetailView.vue +++ b/apps/player/src/views/BetDetailView.vue @@ -3,7 +3,7 @@ import { ref, computed, onMounted } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import api from '../api'; -import { formatMoney } from '../utils/localeDisplay'; +import { formatMoney, parseAmount } from '../utils/localeDisplay'; import GoldSpinner from '../components/GoldSpinner.vue'; import { usePullToRefresh } from '../composables/usePullToRefresh'; import StatusWatermark from '../components/StatusWatermark.vue'; @@ -52,6 +52,8 @@ const statusKey = computed(() => { const statusLabel = computed(() => t(`history.status_${statusKey.value}`)); +const isSettled = computed(() => statusKey.value !== 'pending'); + const placedDateTime = computed(() => { if (!bet.value) return ''; const d = new Date(bet.value.placedAt); @@ -64,10 +66,53 @@ const returnAmount = computed(() => { if (!bet.value) return ''; if (statusKey.value === 'won') return formatMoney(bet.value.actualReturn, locale.value); if (statusKey.value === 'pending') return formatMoney(bet.value.potentialReturn, locale.value); - if (statusKey.value === 'lost') return formatMoney(-parseFloat(String(bet.value.stake ?? 0)), locale.value); + if (statusKey.value === 'lost') return formatMoney(-parseAmount(bet.value.stake), locale.value); return formatMoney(bet.value.actualReturn ?? bet.value.potentialReturn, locale.value); }); +const profitAmount = computed(() => { + if (!bet.value || statusKey.value !== 'won') return ''; + const profit = parseAmount(bet.value.actualReturn) - parseAmount(bet.value.stake); + return `+${formatMoney(profit, locale.value)}`; +}); + +const heroAmount = computed(() => { + if (statusKey.value === 'won') return profitAmount.value; + return returnAmount.value; +}); + +const heroTitle = computed(() => { + if (statusKey.value === 'won') return t('history.profit'); + if (statusKey.value === 'pending') return t('history.est_return'); + return statusLabel.value; +}); + +const formulaText = computed(() => { + if (!bet.value || !oddsText.value || statusKey.value === 'pending') return ''; + return t('history.stake_to_return', { + stake: stakeAmount.value, + odds: oddsText.value, + amount: returnAmount.value, + }); +}); + +const stakeAmount = computed(() => + bet.value ? formatMoney(bet.value.stake, locale.value) : '', +); + +const oddsText = computed(() => { + const o = bet.value?.totalOdds; + if (o == null || o === '' || o === 0) return ''; + const n = parseFloat(String(o)); + return Number.isFinite(n) ? n.toFixed(2) : String(o); +}); + +const betTypeLabel = computed(() => { + if (!bet.value) return ''; + if (bet.value.isParlay) return t('bet.parlay'); + return bet.value.betType || ''; +}); + function formatOdds(v: unknown): string { const n = parseFloat(String(v)); return isNaN(n) || n <= 0 ? '-' : n.toFixed(2); @@ -116,13 +161,20 @@ const matchTitle = computed(() => { return bet.value.matchTitle; }); -const myPick = computed(() => { +const pickMarket = computed(() => { if (!bet.value || bet.value.isParlay) return ''; const raw = bet.value.pickLabel ?? ''; - if (locale.value === 'zh-CN' || !raw) return raw; const ci = raw.indexOf(': '); - if (ci < 0) return raw; - return raw.slice(0, ci + 2) + translateSel(raw.slice(ci + 2)); + return ci >= 0 ? raw.slice(0, ci) : raw; +}); + +const pickSelection = computed(() => { + if (!bet.value || bet.value.isParlay) return ''; + const raw = bet.value.pickLabel ?? ''; + const ci = raw.indexOf(': '); + if (ci < 0) return ''; + const sel = raw.slice(ci + 2); + return locale.value === 'zh-CN' ? sel : translateSel(sel); }); const matchPhase = computed( @@ -130,20 +182,20 @@ const matchPhase = computed( ); const matchPhaseText = computed(() => matchPhaseLabel(t, matchPhase.value)); + +const showPhaseWatermark = computed( + () => !isSettled.value && matchPhase.value && matchPhase.value !== 'open', +);