新增:国际化

This commit is contained in:
ivan 2026-02-25 21:08:23 +08:00
parent 359059bff6
commit e41d6b5b0d
19 changed files with 1143 additions and 252 deletions

71
package-lock.json generated
View File

@ -15,6 +15,7 @@
"pinia": "^3.0.4", "pinia": "^3.0.4",
"siwe": "^3.0.0", "siwe": "^3.0.0",
"vue": "^3.5.27", "vue": "^3.5.27",
"vue-i18n": "^11.2.8",
"vue-router": "^5.0.1", "vue-router": "^5.0.1",
"vuetify": "^4.0.0-beta.0", "vuetify": "^4.0.0-beta.0",
"ws": "^8.19.0" "ws": "^8.19.0"
@ -1396,6 +1397,50 @@
"url": "https://github.com/sponsors/nzakas" "url": "https://github.com/sponsors/nzakas"
} }
}, },
"node_modules/@intlify/core-base": {
"version": "11.2.8",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.2.8.tgz",
"integrity": "sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "11.2.8",
"@intlify/shared": "11.2.8"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "11.2.8",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.2.8.tgz",
"integrity": "sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "11.2.8",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "11.2.8",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.2.8.tgz",
"integrity": "sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -8719,6 +8764,32 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/vue-i18n": {
"version": "11.2.8",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.2.8.tgz",
"integrity": "sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "11.2.8",
"@intlify/shared": "11.2.8",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-i18n/node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-5.0.2.tgz", "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-5.0.2.tgz",

View File

@ -25,6 +25,7 @@
"pinia": "^3.0.4", "pinia": "^3.0.4",
"siwe": "^3.0.0", "siwe": "^3.0.0",
"vue": "^3.5.27", "vue": "^3.5.27",
"vue-i18n": "^11.2.8",
"vue-router": "^5.0.1", "vue-router": "^5.0.1",
"vuetify": "^4.0.0-beta.0", "vuetify": "^4.0.0-beta.0",
"ws": "^8.19.0" "ws": "^8.19.0"

View File

@ -1,10 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, watch, nextTick } from 'vue' import { computed, onMounted, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useUserStore } from './stores/user' import { useUserStore } from './stores/user'
import { useLocaleStore } from './stores/locale'
import Toast from './components/Toast.vue' import Toast from './components/Toast.vue'
const route = useRoute() const route = useRoute()
const { t } = useI18n()
const localeStore = useLocaleStore()
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
@ -41,20 +45,36 @@ watch(
icon icon
variant="text" variant="text"
class="back-btn" class="back-btn"
aria-label="返回" :aria-label="t('common.back')"
@click="$router.back()" @click="$router.back()"
> >
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<v-app-bar-title v-if="currentRoute === '/'">PolyMarket</v-app-bar-title> <v-app-bar-title v-if="currentRoute === '/'">PolyMarket</v-app-bar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-menu location="bottom" :close-on-content-click="true" class="locale-menu">
<template #activator="{ props }">
<v-btn v-bind="props" icon variant="text" class="locale-btn" :aria-label="t('common.more')">
<v-icon size="20">mdi-translate</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item
v-for="opt in localeStore.localeOptions"
:key="opt.value"
:title="opt.label"
:active="localeStore.currentLocale === opt.value"
@click="localeStore.setLocale(opt.value)"
/>
</v-list>
</v-menu>
<v-btn <v-btn
v-if="!userStore.isLoggedIn" v-if="!userStore.isLoggedIn"
text text
to="/login" to="/login"
:class="{ active: currentRoute === '/login' }" :class="{ active: currentRoute === '/login' }"
> >
Login {{ t('common.login') }}
</v-btn> </v-btn>
<template v-else> <template v-else>
<v-btn <v-btn
@ -78,10 +98,10 @@ watch(
</template> </template>
<v-list density="compact"> <v-list density="compact">
<v-list-item <v-list-item
:title="userStore.user?.nickName || userStore.user?.userName || 'User'" :title="userStore.user?.nickName || userStore.user?.userName || t('common.user')"
disabled disabled
/> />
<v-list-item title="退出登录" @click="userStore.logout()" /> <v-list-item :title="t('common.logout')" @click="userStore.logout()" />
</v-list> </v-list>
</v-menu> </v-menu>
</template> </template>

View File

@ -9,9 +9,9 @@
> >
<v-card rounded="lg"> <v-card rounded="lg">
<div class="deposit-header"> <div class="deposit-header">
<h2 class="deposit-title">Deposit</h2> <h2 class="deposit-title">{{ t('deposit.title') }}</h2>
<p class="deposit-balance">Polymarket Balance: ${{ balance }}</p> <p class="deposit-balance">{{ t('deposit.polymarketBalance') }} ${{ balance }}</p>
<v-btn icon variant="text" class="close-btn" aria-label="关闭" @click="close"> <v-btn icon variant="text" class="close-btn" :aria-label="t('deposit.close')" @click="close">
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
</v-btn> </v-btn>
</div> </div>
@ -29,8 +29,8 @@
<v-icon size="28">mdi-lightning-bolt</v-icon> <v-icon size="28">mdi-lightning-bolt</v-icon>
</div> </div>
<div class="method-info"> <div class="method-info">
<div class="method-name">Transfer Crypto</div> <div class="method-name">{{ t('deposit.transferCrypto') }}</div>
<div class="method-desc">No limit Instant</div> <div class="method-desc">{{ t('deposit.noLimit') }} {{ t('deposit.instant') }}</div>
</div> </div>
<v-icon class="method-arrow">mdi-chevron-right</v-icon> <v-icon class="method-arrow">mdi-chevron-right</v-icon>
</v-card> </v-card>
@ -44,8 +44,8 @@
<v-icon size="28">mdi-link-variant</v-icon> <v-icon size="28">mdi-link-variant</v-icon>
</div> </div>
<div class="method-info"> <div class="method-info">
<div class="method-name">Connect Exchange</div> <div class="method-name">{{ t('deposit.connectExchange') }}</div>
<div class="method-desc">No limit 2 min</div> <div class="method-desc">{{ t('deposit.noLimit') }} {{ t('deposit.twoMin') }}</div>
</div> </div>
<v-icon class="method-arrow">mdi-chevron-right</v-icon> <v-icon class="method-arrow">mdi-chevron-right</v-icon>
</v-card> </v-card>
@ -58,26 +58,26 @@
<v-btn icon variant="text" size="small" @click="step = 'method'"> <v-btn icon variant="text" size="small" @click="step = 'method'">
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<span class="step-title">Transfer Crypto</span> <span class="step-title">{{ t('deposit.transferCrypto') }}</span>
</div> </div>
<v-select <v-select
v-model="selectedNetwork" v-model="selectedNetwork"
:items="networks" :items="networks"
item-title="label" item-title="label"
item-value="id" item-value="id"
label="Network" :label="t('deposit.network')"
variant="outlined" variant="outlined"
density="comfortable" density="comfortable"
hide-details hide-details
class="network-select" class="network-select"
/> />
<p class="support-tip">Supported: USDC, ETH</p> <p class="support-tip">{{ t('deposit.supportedTip') }}</p>
<div class="address-box"> <div class="address-box">
<label class="address-label">Deposit address</label> <label class="address-label">{{ t('deposit.depositAddress') }}</label>
<div class="address-row"> <div class="address-row">
<code class="address-code">{{ depositAddressShort }}</code> <code class="address-code">{{ depositAddressShort }}</code>
<v-btn size="small" variant="tonal" @click="copyAddress"> <v-btn size="small" variant="tonal" @click="copyAddress">
{{ copied ? 'Copied' : 'Copy' }} {{ copied ? t('deposit.copied') : t('deposit.copy') }}
</v-btn> </v-btn>
</div> </div>
<div class="qr-wrap"> <div class="qr-wrap">
@ -92,12 +92,11 @@
<v-btn icon variant="text" size="small" @click="step = 'method'"> <v-btn icon variant="text" size="small" @click="step = 'method'">
<v-icon>mdi-arrow-left</v-icon> <v-icon>mdi-arrow-left</v-icon>
</v-btn> </v-btn>
<span class="step-title">Connect Exchange</span> <span class="step-title">{{ t('deposit.connectExchange') }}</span>
</div> </div>
<template v-if="!exchangeConnected"> <template v-if="!exchangeConnected">
<p class="connect-desc"> <p class="connect-desc">
Connect your wallet to deposit. Send USDC or ETH to your deposit address after {{ t('deposit.connectDesc') }}
connecting.
</p> </p>
<div class="wallet-buttons"> <div class="wallet-buttons">
<v-btn <v-btn
@ -109,29 +108,29 @@
@click="connectMetaMask" @click="connectMetaMask"
> >
<v-icon start>mdi-wallet</v-icon> <v-icon start>mdi-wallet</v-icon>
MetaMask {{ t('deposit.metaMask') }}
</v-btn> </v-btn>
<v-btn class="wallet-btn" variant="outlined" rounded="lg" block disabled> <v-btn class="wallet-btn" variant="outlined" rounded="lg" block disabled>
<v-icon start>mdi-wallet</v-icon> <v-icon start>mdi-wallet</v-icon>
Coinbase Wallet (Coming soon) {{ t('deposit.coinbaseComingSoon') }}
</v-btn> </v-btn>
<v-btn class="wallet-btn" variant="outlined" rounded="lg" block disabled> <v-btn class="wallet-btn" variant="outlined" rounded="lg" block disabled>
<v-icon start>mdi-wallet</v-icon> <v-icon start>mdi-wallet</v-icon>
WalletConnect (Coming soon) {{ t('deposit.walletConnectComingSoon') }}
</v-btn> </v-btn>
</div> </div>
</template> </template>
<template v-else> <template v-else>
<p class="connected-tip"> <p class="connected-tip">
<v-icon color="success" size="18">mdi-check-circle</v-icon> <v-icon color="success" size="18">mdi-check-circle</v-icon>
Connected. Send USDC or ETH to the address below. {{ t('deposit.connectedTip') }}
</p> </p>
<div class="address-box"> <div class="address-box">
<label class="address-label">Deposit address</label> <label class="address-label">{{ t('deposit.depositAddress') }}</label>
<div class="address-row"> <div class="address-row">
<code class="address-code">{{ depositAddressShort }}</code> <code class="address-code">{{ depositAddressShort }}</code>
<v-btn size="small" variant="tonal" @click="copyAddress"> <v-btn size="small" variant="tonal" @click="copyAddress">
{{ copied ? 'Copied' : 'Copy' }} {{ copied ? t('deposit.copied') : t('deposit.copy') }}
</v-btn> </v-btn>
</div> </div>
<div class="qr-wrap"> <div class="qr-wrap">
@ -147,7 +146,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
modelValue: boolean modelValue: boolean
@ -204,7 +205,7 @@ async function copyAddress() {
async function connectMetaMask() { async function connectMetaMask() {
if (!window.ethereum) { if (!window.ethereum) {
alert('Please install MetaMask or another Web3 wallet.') alert(t('deposit.installMetaMask'))
return return
} }
connecting.value = true connecting.value = true

View File

@ -36,7 +36,7 @@
</svg> </svg>
<div class="semi-progress-inner"> <div class="semi-progress-inner">
<span class="chance-value">{{ chanceValue }}%</span> <span class="chance-value">{{ chanceValue }}%</span>
<span class="chance-label">chance</span> <span class="chance-label">{{ t('common.chance') }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -164,9 +164,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import type { EventCardOutcome } from '../api/event' import type { EventCardOutcome } from '../api/event'
const { t } = useI18n()
const router = useRouter() const router = useRouter()
const emit = defineEmits<{ const emit = defineEmits<{
openTrade: [ openTrade: [

View File

@ -2,11 +2,11 @@
<v-card class="order-book" elevation="0"> <v-card class="order-book" elevation="0">
<!-- Header --> <!-- Header -->
<div class="order-book-header"> <div class="order-book-header">
<h3 class="order-book-title">Order Book</h3> <h3 class="order-book-title">{{ t('trade.orderBook') }}</h3>
<div class="order-book-vol"> <div class="order-book-vol">
<span v-if="connected" class="live-badge"> <span v-if="connected" class="live-badge">
<span class="live-dot"></span> <span class="live-dot"></span>
Live {{ t('activity.live') }}
</span> </span>
<span v-else-if="loading" class="loading-badge">连接中...</span> <span v-else-if="loading" class="loading-badge">连接中...</span>
<span v-else class="vol-text">$4.4k Vol.</span> <span v-else class="vol-text">$4.4k Vol.</span>
@ -17,8 +17,8 @@
<!-- Trade Tabs --> <!-- Trade Tabs -->
<div class="trade-tabs-container"> <div class="trade-tabs-container">
<v-tabs v-model="activeTrade" class="trade-tabs" density="comfortable"> <v-tabs v-model="activeTrade" class="trade-tabs" density="comfortable">
<v-tab value="up" class="trade-tab">Trade Up</v-tab> <v-tab value="up" class="trade-tab">{{ t('trade.buyLabel', { label: props.yesLabel }) }}</v-tab>
<v-tab value="down" class="trade-tab">Trade Down</v-tab> <v-tab value="down" class="trade-tab">{{ t('trade.buyLabel', { label: props.noLabel }) }}</v-tab>
</v-tabs> </v-tabs>
</div> </div>
@ -80,8 +80,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import HorizontalProgressBar from './HorizontalProgressBar.vue' import HorizontalProgressBar from './HorizontalProgressBar.vue'
const { t } = useI18n()
export interface OrderBookRow { export interface OrderBookRow {
price: number price: number
shares: number shares: number
@ -95,6 +98,10 @@ const props = withDefaults(
spread?: number spread?: number
loading?: boolean loading?: boolean
connected?: boolean connected?: boolean
/** 市场 Yes 选项文案,来自 market.outcomes[0] */
yesLabel?: string
/** 市场 No 选项文案,来自 market.outcomes[1] */
noLabel?: string
}>(), }>(),
{ {
asks: () => [], asks: () => [],
@ -103,6 +110,8 @@ const props = withDefaults(
spread: undefined, spread: undefined,
loading: false, loading: false,
connected: false, connected: false,
yesLabel: 'Yes',
noLabel: 'No',
}, },
) )

View File

@ -4,29 +4,29 @@
<!-- Header --> <!-- Header -->
<div class="header"> <div class="header">
<v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact"> <v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact">
<v-tab value="buy" class="minimal-tab">Buy</v-tab> <v-tab value="buy" class="minimal-tab">{{ t('trade.buy') }}</v-tab>
<v-tab value="sell" class="minimal-tab">Sell</v-tab> <v-tab value="sell" class="minimal-tab">{{ t('trade.sell') }}</v-tab>
</v-tabs> </v-tabs>
<v-menu> <v-menu>
<template v-slot:activator="{ props, isActive }"> <template v-slot:activator="{ props, isActive }">
<v-btn v-bind="props" class="limit-btn" text end> <v-btn v-bind="props" class="limit-btn" text end>
{{ limitType }} {{ limitTypeDisplay }}
<v-icon right>{{ isActive ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon> <v-icon right>{{ isActive ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
</v-btn> </v-btn>
</template> </template>
<v-list> <v-list>
<v-list-item @click="limitType = 'Market'"> <v-list-item @click="limitType = 'Market'">
<v-list-item-title>Market</v-list-item-title> <v-list-item-title>{{ t('trade.market') }}</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item @click="limitType = 'Limit'"> <v-list-item @click="limitType = 'Limit'">
<v-list-item-title>Limit</v-list-item-title> <v-list-item-title>{{ t('trade.limit') }}</v-list-item-title>
</v-list-item> </v-list-item>
<v-divider></v-divider> <v-divider></v-divider>
<v-list-item @click="openMergeDialog"> <v-list-item @click="openMergeDialog">
<v-list-item-title>Merge</v-list-item-title> <v-list-item-title>{{ t('trade.merge') }}</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item @click="openSplitDialog"> <v-list-item @click="openSplitDialog">
<v-list-item-title>Split</v-list-item-title> <v-list-item-title>{{ t('trade.split') }}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
@ -61,8 +61,8 @@
<div class="input-group"> <div class="input-group">
<div class="amount-header"> <div class="amount-header">
<div> <div>
<span class="label amount-label">Amount</span> <span class="label amount-label">{{ t('trade.amount') }}</span>
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span> <span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
</div> </div>
<div class="amount-value">${{ amount.toFixed(2) }}</div> <div class="amount-value">${{ amount.toFixed(2) }}</div>
</div> </div>
@ -70,7 +70,7 @@
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn> <v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn> <v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn> <v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn> <v-btn class="amount-btn" @click="setMaxAmount">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
</template> </template>
@ -78,7 +78,7 @@
<template v-if="activeTab === 'sell'"> <template v-if="activeTab === 'sell'">
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -96,7 +96,7 @@
<div class="shares-buttons"> <div class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
</template> </template>
@ -106,11 +106,11 @@
<!-- Buy模式 --> <!-- Buy模式 -->
<template v-if="activeTab === 'buy'"> <template v-if="activeTab === 'buy'">
<div class="total-row"> <div class="total-row">
<span class="label">Total</span> <span class="label">{{ t('trade.total') }}</span>
<span class="total-value">${{ totalPrice }}</span> <span class="total-value">${{ totalPrice }}</span>
</div> </div>
<div class="total-row"> <div class="total-row">
<span class="label">To win</span> <span class="label">{{ t('trade.toWin') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ toWinValue }} {{ toWinValue }}
@ -120,7 +120,7 @@
<!-- Sell模式 --> <!-- Sell模式 -->
<template v-else> <template v-else>
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span> <span class="label">{{ t('trade.youllReceive') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ totalPrice }} {{ totalPrice }}
@ -128,7 +128,7 @@
</div> </div>
<div class="total-row avg-price-row"> <div class="total-row avg-price-row">
<span class="label"> <span class="label">
Avg. Price {{ avgPriceCents }}¢ {{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon> <v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
</span> </span>
</div> </div>
@ -136,13 +136,13 @@
</div> </div>
<p v-if="orderError" class="order-error">{{ orderError }}</p> <p v-if="orderError" class="order-error">{{ orderError }}</p>
<!-- Action Button: Buy 余额足够显示 Buy Yes/No不足显示 DepositSell 只显示 Sell Yes/No --> <!-- Action Button: Buy 余额足够显示 Buy Yes/No不足显示 {{ t('trade.deposit') }}Sell 只显示 Sell Yes/No -->
<v-btn <v-btn
v-if="activeTab === 'buy' && showDepositForBuy" v-if="activeTab === 'buy' && showDepositForBuy"
class="deposit-btn" class="deposit-btn"
@click="deposit" @click="deposit"
> >
Deposit {{ t('trade.deposit') }}
</v-btn> </v-btn>
<v-btn <v-btn
v-else v-else
@ -182,8 +182,8 @@
<div class="input-group"> <div class="input-group">
<div class="amount-header"> <div class="amount-header">
<div> <div>
<span class="label amount-label">Amount</span> <span class="label amount-label">{{ t('trade.amount') }}</span>
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span> <span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
</div> </div>
<div class="amount-value">${{ amount.toFixed(2) }}</div> <div class="amount-value">${{ amount.toFixed(2) }}</div>
</div> </div>
@ -193,27 +193,27 @@
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn> <v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn> <v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn> <v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn> <v-btn class="amount-btn" @click="setMaxAmount">{{ t('trade.max') }}</v-btn>
</div> </div>
<!-- To win份数 × 1U --> <!-- To win份数 × 1U -->
<div v-if="amount > 0" class="total-row amount-to-win-row"> <div v-if="amount > 0" class="total-row amount-to-win-row">
<span class="label">To win</span> <span class="label">{{ t('trade.toWin') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ toWinValue }} {{ toWinValue }}
</span> </span>
</div> </div>
</div> </div>
<!-- Buy 余额不足时显示 Deposit --> <!-- Buy 余额不足时显示 {{ t('trade.deposit') }} -->
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn> <v-btn class="deposit-btn" @click="deposit">{{ t('trade.deposit') }}</v-btn>
</template> </template>
<!-- Sell: Shares + To receive + Avg. Price只显示 Sell Yes/No Deposit --> <!-- Sell: Shares + To receive + Avg. Price只显示 Sell Yes/No {{ t('trade.deposit') }} -->
<template v-else> <template v-else>
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -231,12 +231,12 @@
<div class="shares-buttons"> <div class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
<div class="total-section"> <div class="total-section">
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span> <span class="label">{{ t('trade.youllReceive') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ totalPrice }} {{ totalPrice }}
@ -244,7 +244,7 @@
</div> </div>
<div class="total-row avg-price-row"> <div class="total-row avg-price-row">
<span class="label"> <span class="label">
Avg. Price {{ avgPriceCents }}¢ {{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon> <v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
</span> </span>
</div> </div>
@ -287,7 +287,7 @@
<!-- Limit Price --> <!-- Limit Price -->
<div class="input-group limit-price-group"> <div class="input-group limit-price-group">
<div class="limit-price-header"> <div class="limit-price-header">
<span class="label">Limit Price</span> <span class="label">{{ t('trade.limitPrice') }}</span>
<div class="price-input"> <div class="price-input">
<v-btn class="adjust-btn" icon @click="decreasePrice"> <v-btn class="adjust-btn" icon @click="decreasePrice">
<v-icon>mdi-minus</v-icon> <v-icon>mdi-minus</v-icon>
@ -316,7 +316,7 @@
<!-- Shares --> <!-- Shares -->
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -343,7 +343,7 @@
<div v-else class="shares-buttons"> <div v-else class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
<div v-if="activeTab === 'buy'" class="matching-info"> <div v-if="activeTab === 'buy'" class="matching-info">
<v-icon size="14">mdi-information</v-icon> <v-icon size="14">mdi-information</v-icon>
@ -354,7 +354,7 @@
<!-- Set Expiration --> <!-- Set Expiration -->
<div class="input-group expiration-group"> <div class="input-group expiration-group">
<div class="expiration-header"> <div class="expiration-header">
<span class="label">Set Expiration</span> <span class="label">{{ t('trade.setExpiration') }}</span>
<v-switch v-model="expirationEnabled" class="expiration-switch" hide-details></v-switch> <v-switch v-model="expirationEnabled" class="expiration-switch" hide-details></v-switch>
</div> </div>
<!-- Expiration Time Dropdown --> <!-- Expiration Time Dropdown -->
@ -362,6 +362,8 @@
v-if="expirationEnabled" v-if="expirationEnabled"
v-model="expirationTime" v-model="expirationTime"
:items="expirationOptions" :items="expirationOptions"
item-title="title"
item-value="value"
class="expiration-select" class="expiration-select"
hide-details hide-details
density="compact" density="compact"
@ -373,11 +375,11 @@
<!-- Buy模式 --> <!-- Buy模式 -->
<template v-if="activeTab === 'buy'"> <template v-if="activeTab === 'buy'">
<div class="total-row"> <div class="total-row">
<span class="label">Total</span> <span class="label">{{ t('trade.total') }}</span>
<span class="total-value">${{ totalPrice }}</span> <span class="total-value">${{ totalPrice }}</span>
</div> </div>
<div class="total-row"> <div class="total-row">
<span class="label">To win</span> <span class="label">{{ t('trade.toWin') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ toWinValue }} {{ toWinValue }}
@ -387,7 +389,7 @@
<!-- Sell模式 --> <!-- Sell模式 -->
<template v-else> <template v-else>
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span> <span class="label">{{ t('trade.youllReceive') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ totalPrice }} {{ totalPrice }}
@ -414,29 +416,29 @@
<div class="trade-component trade-sheet-inner"> <div class="trade-component trade-sheet-inner">
<div class="header"> <div class="header">
<v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact"> <v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact">
<v-tab value="buy" class="minimal-tab">Buy</v-tab> <v-tab value="buy" class="minimal-tab">{{ t('trade.buy') }}</v-tab>
<v-tab value="sell" class="minimal-tab">Sell</v-tab> <v-tab value="sell" class="minimal-tab">{{ t('trade.sell') }}</v-tab>
</v-tabs> </v-tabs>
<v-menu class="limit-dropdown hide-in-mobile-sheet"> <v-menu class="limit-dropdown hide-in-mobile-sheet">
<template v-slot:activator="{ props: limitProps, isActive }"> <template v-slot:activator="{ props: limitProps, isActive }">
<v-btn v-bind="limitProps" class="limit-btn" text end> <v-btn v-bind="limitProps" class="limit-btn" text end>
{{ limitType }} {{ limitTypeDisplay }}
<v-icon right>{{ isActive ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon> <v-icon right>{{ isActive ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
</v-btn> </v-btn>
</template> </template>
<v-list> <v-list>
<v-list-item @click="limitType = 'Market'" <v-list-item @click="limitType = 'Market'"
><v-list-item-title>Market</v-list-item-title></v-list-item ><v-list-item-title>{{ t('trade.market') }}</v-list-item-title></v-list-item
> >
<v-list-item @click="limitType = 'Limit'" <v-list-item @click="limitType = 'Limit'"
><v-list-item-title>Limit</v-list-item-title></v-list-item ><v-list-item-title>{{ t('trade.limit') }}</v-list-item-title></v-list-item
> >
<v-divider></v-divider> <v-divider></v-divider>
<v-list-item @click="openMergeDialog" <v-list-item @click="openMergeDialog"
><v-list-item-title>Merge</v-list-item-title></v-list-item ><v-list-item-title>{{ t('trade.merge') }}</v-list-item-title></v-list-item
> >
<v-list-item @click="openSplitDialog" <v-list-item @click="openSplitDialog"
><v-list-item-title>Split</v-list-item-title></v-list-item ><v-list-item-title>{{ t('trade.split') }}</v-list-item-title></v-list-item
> >
</v-list> </v-list>
</v-menu> </v-menu>
@ -464,8 +466,8 @@
<div class="input-group"> <div class="input-group">
<div class="amount-header"> <div class="amount-header">
<div> <div>
<span class="label amount-label">Amount</span> <span class="label amount-label">{{ t('trade.amount') }}</span>
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span> <span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
</div> </div>
<div class="amount-value">${{ amount.toFixed(2) }}</div> <div class="amount-value">${{ amount.toFixed(2) }}</div>
</div> </div>
@ -473,7 +475,7 @@
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn> <v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn> <v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn> <v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn> <v-btn class="amount-btn" @click="setMaxAmount">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
</template> </template>
@ -481,7 +483,7 @@
<template v-if="activeTab === 'sell'"> <template v-if="activeTab === 'sell'">
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -499,17 +501,17 @@
<div class="shares-buttons"> <div class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
</template> </template>
<div class="total-section"> <div class="total-section">
<template v-if="activeTab === 'buy'"> <template v-if="activeTab === 'buy'">
<div class="total-row"> <div class="total-row">
<span class="label">Total</span><span class="total-value">${{ totalPrice }}</span> <span class="label">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
</div> </div>
<div class="total-row"> <div class="total-row">
<span class="label">To win</span <span class="label">{{ t('trade.toWin') }}</span
><span class="to-win-value" ><span class="to-win-value"
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span ><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
> >
@ -517,7 +519,7 @@
</template> </template>
<template v-else> <template v-else>
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span <span class="label">{{ t('trade.youllReceive') }}</span
><span class="to-win-value" ><span class="to-win-value"
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ ><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
totalPrice totalPrice
@ -526,7 +528,7 @@
</div> </div>
<div class="total-row avg-price-row"> <div class="total-row avg-price-row">
<span class="label"> <span class="label">
Avg. Price {{ avgPriceCents }}¢ {{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon> <v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
</span> </span>
</div> </div>
@ -538,7 +540,7 @@
class="deposit-btn" class="deposit-btn"
@click="deposit" @click="deposit"
> >
Deposit {{ t('trade.deposit') }}
</v-btn> </v-btn>
<v-btn <v-btn
v-else v-else
@ -571,8 +573,8 @@
<div class="input-group"> <div class="input-group">
<div class="amount-header"> <div class="amount-header">
<div> <div>
<span class="label amount-label">Amount</span <span class="label amount-label">{{ t('trade.amount') }}</span
><span class="balance-label">Balance ${{ balance.toFixed(2) }}</span> ><span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
</div> </div>
<div class="amount-value">${{ amount.toFixed(2) }}</div> <div class="amount-value">${{ amount.toFixed(2) }}</div>
</div> </div>
@ -580,23 +582,23 @@
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn> <v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn> <v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn> <v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn> <v-btn class="amount-btn" @click="setMaxAmount">{{ t('trade.max') }}</v-btn>
</div> </div>
<div v-if="amount > 0" class="total-row amount-to-win-row"> <div v-if="amount > 0" class="total-row amount-to-win-row">
<span class="label">To win</span> <span class="label">{{ t('trade.toWin') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ toWinValue }} {{ toWinValue }}
</span> </span>
</div> </div>
</div> </div>
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn> <v-btn class="deposit-btn" @click="deposit">{{ t('trade.deposit') }}</v-btn>
</template> </template>
<!-- Sell: Shares + To receive + Avg. Price --> <!-- Sell: Shares + To receive + Avg. Price -->
<template v-else> <template v-else>
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -614,12 +616,12 @@
<div class="shares-buttons"> <div class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
<div class="total-section"> <div class="total-section">
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span> <span class="label">{{ t('trade.youllReceive') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ totalPrice }} {{ totalPrice }}
@ -627,7 +629,7 @@
</div> </div>
<div class="total-row avg-price-row"> <div class="total-row avg-price-row">
<span class="label"> <span class="label">
Avg. Price {{ avgPriceCents }}¢ {{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon> <v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
</span> </span>
</div> </div>
@ -662,7 +664,7 @@
</div> </div>
<div class="input-group limit-price-group"> <div class="input-group limit-price-group">
<div class="limit-price-header"> <div class="limit-price-header">
<span class="label">Limit Price</span> <span class="label">{{ t('trade.limitPrice') }}</span>
<div class="price-input"> <div class="price-input">
<v-btn class="adjust-btn" icon @click="decreasePrice" <v-btn class="adjust-btn" icon @click="decreasePrice"
><v-icon>mdi-minus</v-icon></v-btn ><v-icon>mdi-minus</v-icon></v-btn
@ -689,7 +691,7 @@
</div> </div>
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -713,12 +715,12 @@
<div v-else class="shares-buttons"> <div v-else class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
<div class="input-group expiration-group"> <div class="input-group expiration-group">
<div class="expiration-header"> <div class="expiration-header">
<span class="label">Set expiration</span> <span class="label">{{ t('trade.setExpiration') }}</span>
<v-switch <v-switch
v-model="expirationEnabled" v-model="expirationEnabled"
class="expiration-switch" class="expiration-switch"
@ -730,6 +732,8 @@
v-if="expirationEnabled" v-if="expirationEnabled"
v-model="expirationTime" v-model="expirationTime"
:items="expirationOptions" :items="expirationOptions"
item-title="title"
item-value="value"
class="expiration-select" class="expiration-select"
hide-details hide-details
density="compact" density="compact"
@ -738,10 +742,10 @@
<div class="total-section"> <div class="total-section">
<template v-if="activeTab === 'buy'"> <template v-if="activeTab === 'buy'">
<div class="total-row"> <div class="total-row">
<span class="label">Total</span><span class="total-value">${{ totalPrice }}</span> <span class="label">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
</div> </div>
<div class="total-row"> <div class="total-row">
<span class="label">To win</span <span class="label">{{ t('trade.toWin') }}</span
><span class="to-win-value" ><span class="to-win-value"
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span ><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
> >
@ -749,7 +753,7 @@
</template> </template>
<template v-else> <template v-else>
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span <span class="label">{{ t('trade.youllReceive') }}</span
><span class="to-win-value" ><span class="to-win-value"
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ totalPrice }}</span ><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ totalPrice }}</span
> >
@ -781,7 +785,7 @@
block block
@click="openSheet('yes')" @click="openSheet('yes')"
> >
Buy {{ yesLabel }} {{ yesPriceCents }}¢ {{ t('trade.buyLabel', { label: yesLabel }) }} {{ yesPriceCents }}¢
</v-btn> </v-btn>
<v-btn <v-btn
class="mobile-bar-btn mobile-bar-no" class="mobile-bar-btn mobile-bar-no"
@ -791,7 +795,7 @@
block block
@click="openSheet('no')" @click="openSheet('no')"
> >
Buy {{ noLabel }} {{ noPriceCents }}¢ {{ t('trade.buyLabel', { label: noLabel }) }} {{ noPriceCents }}¢
</v-btn> </v-btn>
</div> </div>
@ -800,8 +804,8 @@
<div class="trade-component trade-sheet-inner"> <div class="trade-component trade-sheet-inner">
<div class="header"> <div class="header">
<v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact"> <v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact">
<v-tab value="buy" class="minimal-tab">Buy</v-tab> <v-tab value="buy" class="minimal-tab">{{ t('trade.buy') }}</v-tab>
<v-tab value="sell" class="minimal-tab">Sell</v-tab> <v-tab value="sell" class="minimal-tab">{{ t('trade.sell') }}</v-tab>
</v-tabs> </v-tabs>
<v-menu class="limit-dropdown hide-in-mobile-sheet"> <v-menu class="limit-dropdown hide-in-mobile-sheet">
<template v-slot:activator="{ props: limitProps, isActive }"> <template v-slot:activator="{ props: limitProps, isActive }">
@ -819,10 +823,10 @@
> >
<v-divider></v-divider> <v-divider></v-divider>
<v-list-item @click="openMergeDialog" <v-list-item @click="openMergeDialog"
><v-list-item-title>Merge</v-list-item-title></v-list-item ><v-list-item-title>{{ t('trade.merge') }}</v-list-item-title></v-list-item
> >
<v-list-item @click="openSplitDialog" <v-list-item @click="openSplitDialog"
><v-list-item-title>Split</v-list-item-title></v-list-item ><v-list-item-title>{{ t('trade.split') }}</v-list-item-title></v-list-item
> >
</v-list> </v-list>
</v-menu> </v-menu>
@ -850,8 +854,8 @@
<div class="input-group"> <div class="input-group">
<div class="amount-header"> <div class="amount-header">
<div> <div>
<span class="label amount-label">Amount</span> <span class="label amount-label">{{ t('trade.amount') }}</span>
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span> <span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
</div> </div>
<div class="amount-value">${{ amount.toFixed(2) }}</div> <div class="amount-value">${{ amount.toFixed(2) }}</div>
</div> </div>
@ -859,7 +863,7 @@
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn> <v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn> <v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn> <v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn> <v-btn class="amount-btn" @click="setMaxAmount">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
</template> </template>
@ -867,7 +871,7 @@
<template v-if="activeTab === 'sell'"> <template v-if="activeTab === 'sell'">
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -885,7 +889,7 @@
<div class="shares-buttons"> <div class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
</template> </template>
@ -896,7 +900,7 @@
><span class="total-value">${{ totalPrice }}</span> ><span class="total-value">${{ totalPrice }}</span>
</div> </div>
<div class="total-row"> <div class="total-row">
<span class="label">To win</span <span class="label">{{ t('trade.toWin') }}</span
><span class="to-win-value" ><span class="to-win-value"
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span ><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
> >
@ -904,7 +908,7 @@
</template> </template>
<template v-else> <template v-else>
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span <span class="label">{{ t('trade.youllReceive') }}</span
><span class="to-win-value" ><span class="to-win-value"
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ ><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
totalPrice totalPrice
@ -913,7 +917,7 @@
</div> </div>
<div class="total-row avg-price-row"> <div class="total-row avg-price-row">
<span class="label"> <span class="label">
Avg. Price {{ avgPriceCents }}¢ {{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon> <v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
</span> </span>
</div> </div>
@ -925,7 +929,7 @@
class="deposit-btn" class="deposit-btn"
@click="deposit" @click="deposit"
> >
Deposit {{ t('trade.deposit') }}
</v-btn> </v-btn>
<v-btn <v-btn
v-else v-else
@ -958,8 +962,8 @@
<div class="input-group"> <div class="input-group">
<div class="amount-header"> <div class="amount-header">
<div> <div>
<span class="label amount-label">Amount</span <span class="label amount-label">{{ t('trade.amount') }}</span
><span class="balance-label">Balance ${{ balance.toFixed(2) }}</span> ><span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
</div> </div>
<div class="amount-value">${{ amount.toFixed(2) }}</div> <div class="amount-value">${{ amount.toFixed(2) }}</div>
</div> </div>
@ -967,23 +971,23 @@
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn> <v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn> <v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn> <v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn> <v-btn class="amount-btn" @click="setMaxAmount">{{ t('trade.max') }}</v-btn>
</div> </div>
<div v-if="amount > 0" class="total-row amount-to-win-row"> <div v-if="amount > 0" class="total-row amount-to-win-row">
<span class="label">To win</span> <span class="label">{{ t('trade.toWin') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ toWinValue }} {{ toWinValue }}
</span> </span>
</div> </div>
</div> </div>
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn> <v-btn class="deposit-btn" @click="deposit">{{ t('trade.deposit') }}</v-btn>
</template> </template>
<!-- Sell: Shares + To receive + Avg. Price --> <!-- Sell: Shares + To receive + Avg. Price -->
<template v-else> <template v-else>
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -1001,12 +1005,12 @@
<div class="shares-buttons"> <div class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
<div class="total-section"> <div class="total-section">
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span> <span class="label">{{ t('trade.youllReceive') }}</span>
<span class="to-win-value"> <span class="to-win-value">
<v-icon size="16" color="green">mdi-currency-usd</v-icon> <v-icon size="16" color="green">mdi-currency-usd</v-icon>
{{ totalPrice }} {{ totalPrice }}
@ -1014,7 +1018,7 @@
</div> </div>
<div class="total-row avg-price-row"> <div class="total-row avg-price-row">
<span class="label"> <span class="label">
Avg. Price {{ avgPriceCents }}¢ {{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
<v-icon size="14" class="info-icon">mdi-information-outline</v-icon> <v-icon size="14" class="info-icon">mdi-information-outline</v-icon>
</span> </span>
</div> </div>
@ -1049,7 +1053,7 @@
</div> </div>
<div class="input-group limit-price-group"> <div class="input-group limit-price-group">
<div class="limit-price-header"> <div class="limit-price-header">
<span class="label">Limit Price</span> <span class="label">{{ t('trade.limitPrice') }}</span>
<div class="price-input"> <div class="price-input">
<v-btn class="adjust-btn" icon @click="decreasePrice" <v-btn class="adjust-btn" icon @click="decreasePrice"
><v-icon>mdi-minus</v-icon></v-btn ><v-icon>mdi-minus</v-icon></v-btn
@ -1075,7 +1079,7 @@
</div> </div>
<div class="input-group shares-group"> <div class="input-group shares-group">
<div class="shares-header"> <div class="shares-header">
<span class="label">Shares</span> <span class="label">{{ t('trade.shares') }}</span>
<div class="shares-input"> <div class="shares-input">
<v-text-field <v-text-field
:model-value="shares" :model-value="shares"
@ -1099,12 +1103,12 @@
<div v-else class="shares-buttons"> <div v-else class="shares-buttons">
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn> <v-btn class="share-btn" @click="setSharesPercentage(100)">{{ t('trade.max') }}</v-btn>
</div> </div>
</div> </div>
<div class="input-group expiration-group"> <div class="input-group expiration-group">
<div class="expiration-header"> <div class="expiration-header">
<span class="label">Set expiration</span> <span class="label">{{ t('trade.setExpiration') }}</span>
<v-switch <v-switch
v-model="expirationEnabled" v-model="expirationEnabled"
class="expiration-switch" class="expiration-switch"
@ -1116,6 +1120,8 @@
v-if="expirationEnabled" v-if="expirationEnabled"
v-model="expirationTime" v-model="expirationTime"
:items="expirationOptions" :items="expirationOptions"
item-title="title"
item-value="value"
class="expiration-select" class="expiration-select"
hide-details hide-details
density="compact" density="compact"
@ -1124,10 +1130,10 @@
<div class="total-section"> <div class="total-section">
<template v-if="activeTab === 'buy'"> <template v-if="activeTab === 'buy'">
<div class="total-row"> <div class="total-row">
<span class="label">Total</span><span class="total-value">${{ totalPrice }}</span> <span class="label">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
</div> </div>
<div class="total-row"> <div class="total-row">
<span class="label">To win</span <span class="label">{{ t('trade.toWin') }}</span
><span class="to-win-value" ><span class="to-win-value"
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span ><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ toWinValue }}</span
> >
@ -1135,7 +1141,7 @@
</template> </template>
<template v-else> <template v-else>
<div class="total-row"> <div class="total-row">
<span class="label">You'll receive</span <span class="label">{{ t('trade.youllReceive') }}</span
><span class="to-win-value" ><span class="to-win-value"
><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{ ><v-icon size="16" color="green">mdi-currency-usd</v-icon> {{
totalPrice totalPrice
@ -1284,12 +1290,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue' import { ref, computed, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useUserStore } from '../stores/user' import { useUserStore } from '../stores/user'
import { pmMarketMerge, pmMarketSplit, pmOrderPlace } from '../api/market' import { pmMarketMerge, pmMarketSplit, pmOrderPlace } from '../api/market'
import { OrderType, Side } from '../api/constants' import { OrderType, Side } from '../api/constants'
const { mobile } = useDisplay() const { mobile } = useDisplay()
const { t } = useI18n()
const userStore = useUserStore() const userStore = useUserStore()
/** 限价单允许的 135 个价格档位01 区间规则19/1090/1009900/99109990/99919999 */ /** 限价单允许的 135 个价格档位01 区间规则19/1090/1009900/99109990/99919999 */
@ -1443,7 +1451,10 @@ const selectedOption = ref<'yes' | 'no'>(props.initialOption ?? 'no')
const limitPrice = ref(0.82) // const limitPrice = ref(0.82) //
const shares = ref(20) // const shares = ref(20) //
const expirationTime = ref('5m') // const expirationTime = ref('5m') //
const expirationOptions = ref(['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d']) // const EXPIRATION_VALUES = ['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d'] as const
const expirationOptions = computed(() =>
EXPIRATION_VALUES.map((v) => ({ title: t(`trade.expiration.${v}`), value: v })),
)
// Market mode state // Market mode state
const isMarketMode = computed(() => limitType.value === 'Market') const isMarketMode = computed(() => limitType.value === 'Market')
@ -1503,11 +1514,13 @@ const toWinValue = computed(() => {
return (shares.value * 1).toFixed(2) return (shares.value * 1).toFixed(2)
}) })
const limitTypeDisplay = computed(() =>
limitType.value === 'Market' ? t('trade.market') : t('trade.limit'),
)
const actionButtonText = computed(() => { const actionButtonText = computed(() => {
const label = selectedOption.value === 'yes' ? yesLabel.value : noLabel.value const label = selectedOption.value === 'yes' ? yesLabel.value : noLabel.value
const tab = activeTab.value const tab = activeTab.value
const tabCapitalized = tab.charAt(0).toUpperCase() + tab.slice(1) return tab === 'buy' ? t('trade.buyLabel', { label }) : t('trade.sellLabel', { label })
return `${tabCapitalized} ${label}`
}) })
function applyInitialOption(option: 'yes' | 'no') { function applyInitialOption(option: 'yes' | 'no') {
@ -1669,9 +1682,9 @@ const canAffordBuy = computed(() => {
return bal >= cost return bal >= cost
}) })
/** Buy 模式且余额不足时显示 Deposit否则显示 Buy Yes/No */
const showDepositForBuy = computed(() => !canAffordBuy.value) const showDepositForBuy = computed(() => !canAffordBuy.value)
/** Buy 模式且余额不足时显示 Deposit否则显示 Buy Yes/No */
const deposit = () => { const deposit = () => {
console.log('Depositing amount:', amount.value) console.log('Depositing amount:', amount.value)
// API // API
@ -1711,12 +1724,12 @@ async function submitOrder() {
emit('submit', payload) emit('submit', payload)
if (!tokenId) { if (!tokenId) {
orderError.value = '请先选择市场(需包含 clobTokenIds' orderError.value = t('trade.pleaseSelectMarket')
return return
} }
const headers = userStore.getAuthHeaders() const headers = userStore.getAuthHeaders()
if (!headers) { if (!headers) {
orderError.value = '请先登录' orderError.value = t('trade.pleaseLogin')
return return
} }
const uid = userStore?.user?.ID ?? 0 const uid = userStore?.user?.ID ?? 0
@ -1729,7 +1742,7 @@ async function submitOrder() {
: 0 : 0
if (!Number.isFinite(userIdNum) || userIdNum <= 0) { if (!Number.isFinite(userIdNum) || userIdNum <= 0) {
console.warn('[submitOrder] 用户信息异常: user=', userStore.user, 'uid=', uid) console.warn('[submitOrder] 用户信息异常: user=', userStore.user, 'uid=', uid)
orderError.value = '用户信息异常' orderError.value = t('trade.userError')
return return
} }
@ -1767,10 +1780,10 @@ async function submitOrder() {
userStore.fetchUsdcBalance() userStore.fetchUsdcBalance()
emit('orderSuccess') emit('orderSuccess')
} else { } else {
orderError.value = res.msg || '下单失败' orderError.value = res.msg || t('trade.orderFailed')
} }
} catch (e) { } catch (e) {
orderError.value = e instanceof Error ? e.message : 'Request failed' orderError.value = e instanceof Error ? e.message : t('error.requestFailed')
} finally { } finally {
orderLoading.value = false orderLoading.value = false
} }

View File

@ -9,9 +9,9 @@
> >
<v-card rounded="lg"> <v-card rounded="lg">
<div class="withdraw-header"> <div class="withdraw-header">
<h2 class="withdraw-title">Withdraw</h2> <h2 class="withdraw-title">{{ t('withdraw.title') }}</h2>
<p class="withdraw-balance">Polymarket Balance: ${{ balance }}</p> <p class="withdraw-balance">{{ t('withdraw.polymarketBalance') }} ${{ balance }}</p>
<v-btn icon variant="text" class="close-btn" aria-label="关闭" @click="close"> <v-btn icon variant="text" class="close-btn" :aria-label="t('withdraw.close')" @click="close">
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
</v-btn> </v-btn>
</div> </div>
@ -21,16 +21,16 @@
type="number" type="number"
min="0" min="0"
step="0.01" step="0.01"
label="Amount (USD)" :label="t('withdraw.amountUsd')"
variant="outlined" variant="outlined"
density="comfortable" density="comfortable"
hide-details hide-details
class="amount-field" class="amount-field"
placeholder="0.00" :placeholder="t('withdraw.amountPlaceholder')"
@keypress="allowDecimal" @keypress="allowDecimal"
> >
<template #append-inner> <template #append-inner>
<v-btn variant="text" size="small" class="max-btn" @click="setMax">Max</v-btn> <v-btn variant="text" size="small" class="max-btn" @click="setMax">{{ t('withdraw.max') }}</v-btn>
</template> </template>
</v-text-field> </v-text-field>
@ -39,7 +39,7 @@
:items="networks" :items="networks"
item-title="label" item-title="label"
item-value="id" item-value="id"
label="Network" :label="t('withdraw.network')"
variant="outlined" variant="outlined"
density="comfortable" density="comfortable"
hide-details hide-details
@ -47,10 +47,10 @@
/> />
<div class="destination-section"> <div class="destination-section">
<label class="section-label">Withdraw to</label> <label class="section-label">{{ t('withdraw.withdrawTo') }}</label>
<v-radio-group v-model="destinationType" hide-details class="destination-radio"> <v-radio-group v-model="destinationType" hide-details class="destination-radio">
<v-radio value="wallet" label="Connected wallet" /> <v-radio value="wallet" :label="t('withdraw.connectedWallet')" />
<v-radio value="address" label="Custom address" /> <v-radio value="address" :label="t('withdraw.customAddress')" />
</v-radio-group> </v-radio-group>
<template v-if="destinationType === 'wallet'"> <template v-if="destinationType === 'wallet'">
<v-btn <v-btn
@ -63,7 +63,7 @@
@click="connectWallet" @click="connectWallet"
> >
<v-icon start>mdi-wallet</v-icon> <v-icon start>mdi-wallet</v-icon>
Connect Wallet {{ t('withdraw.connectWallet') }}
</v-btn> </v-btn>
<div v-else class="connected-address"> <div v-else class="connected-address">
<v-icon color="success" size="18">mdi-check-circle</v-icon> <v-icon color="success" size="18">mdi-check-circle</v-icon>
@ -73,11 +73,11 @@
<v-text-field <v-text-field
v-else v-else
v-model="customAddress" v-model="customAddress"
label="Wallet address" :label="t('withdraw.walletAddress')"
variant="outlined" variant="outlined"
density="comfortable" density="comfortable"
hide-details hide-details
placeholder="0x..." :placeholder="t('withdraw.addressPlaceholder')"
class="address-field" class="address-field"
/> />
</div> </div>
@ -95,7 +95,7 @@
:disabled="!canSubmit" :disabled="!canSubmit"
@click="submitWithdraw" @click="submitWithdraw"
> >
Withdraw {{ t('withdraw.title') }}
</v-btn> </v-btn>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -104,7 +104,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
modelValue: boolean modelValue: boolean
@ -134,8 +136,8 @@ const amountNum = computed(() => parseFloat(amount.value) || 0)
const amountError = computed(() => { const amountError = computed(() => {
if (!amount.value) return '' if (!amount.value) return ''
if (amountNum.value <= 0) return 'Amount must be greater than 0' if (amountNum.value <= 0) return t('withdraw.amountMustBePositive')
if (amountNum.value > balanceNum.value) return 'Insufficient balance' if (amountNum.value > balanceNum.value) return t('withdraw.insufficientBalance')
return '' return ''
}) })
@ -173,7 +175,7 @@ function allowDecimal(e: KeyboardEvent) {
async function connectWallet() { async function connectWallet() {
if (!window.ethereum) { if (!window.ethereum) {
alert('Please install MetaMask or another Web3 wallet.') alert(t('deposit.installMetaMask'))
return return
} }
connecting.value = true connecting.value = true

172
src/locales/en.json Normal file
View File

@ -0,0 +1,172 @@
{
"common": {
"login": "Login",
"logout": "Log out",
"balance": "Balance",
"back": "Back",
"search": "Search",
"filter": "Filter",
"collapse": "Collapse",
"delete": "Delete",
"clear": "Clear",
"loading": "Loading...",
"more": "More",
"user": "User",
"chance": "chance"
},
"toast": {
"orderSuccess": "Order placed successfully"
},
"trade": {
"buy": "Buy",
"sell": "Sell",
"orderBook": "Order Book",
"buyLabel": "Buy {label}",
"sellLabel": "Sell {label}",
"merge": "Merge",
"split": "Split",
"market": "Market",
"limit": "Limit",
"deposit": "Deposit",
"amount": "Amount",
"shares": "Shares",
"limitPrice": "Limit Price",
"setExpiration": "Set expiration",
"total": "Total",
"toWin": "To win",
"youllReceive": "You'll receive",
"avgPrice": "Avg. Price",
"max": "Max",
"balanceLabel": "Balance",
"pleaseLogin": "Please log in first",
"pleaseSelectMarket": "Please select a market (with clobTokenIds)",
"userError": "User info error",
"orderFailed": "Order failed",
"expiration": {
"5m": "5m",
"15m": "15m",
"30m": "30m",
"1h": "1h",
"2h": "2h",
"4h": "4h",
"8h": "8h",
"12h": "12h",
"1d": "1d",
"2d": "2d",
"3d": "3d"
}
},
"home": {
"searchHistory": "Search history",
"searchPlaceholder": "Search",
"loadMore": "Load more"
},
"error": {
"requestFailed": "Request failed",
"loadFailed": "Load failed",
"invalidId": "Invalid ID or slug"
},
"activity": {
"comments": "Comments",
"topHolders": "Top Holders",
"activity": "Activity",
"noCommentsYet": "No comments yet.",
"topHoldersPlaceholder": "Top holders will appear here.",
"minAmount": "Min amount",
"any": "Any",
"live": "Live",
"bought": "bought",
"sold": "sold",
"at": "at",
"viewTransaction": "View transaction",
"justNow": "just now",
"minutesAgo": "{n}m ago",
"hoursAgo": "{n}h ago",
"daysAgo": "{n}d ago",
"weeksAgo": "{n}w ago"
},
"wallet": {
"portfolio": "Portfolio",
"today": "Today",
"deposit": "Deposit",
"withdraw": "Withdraw",
"profitLoss": "Profit/Loss",
"allTime": "All-Time",
"pl1D": "1D",
"pl1W": "1W",
"pl1M": "1M",
"plAll": "ALL",
"positions": "Positions",
"openOrders": "Open orders",
"history": "History",
"searchPlaceholder": "Search",
"currentValue": "Current value",
"closeLosses": "Close Losses",
"all": "All",
"newest": "Newest",
"export": "Export",
"cancelAll": "Cancel all",
"noPositionsFound": "No positions found.",
"noOpenOrdersFound": "No open orders found.",
"noHistoryFound": "You haven't traded any polymarkets yet",
"market": "Market",
"avgNow": "AVG → NOW",
"bet": "BET",
"toWin": "TO WIN",
"value": "VALUE",
"action": "ACTION",
"side": "SIDE",
"outcome": "OUTCOME",
"price": "PRICE",
"filled": "FILLED",
"total": "TOTAL",
"expiration": "EXPIRATION",
"activity": "ACTIVITY",
"view": "View",
"expirationLabel": "Expiration:"
},
"deposit": {
"title": "Deposit",
"polymarketBalance": "Polymarket Balance:",
"transferCrypto": "Transfer Crypto",
"connectExchange": "Connect Exchange",
"noLimit": "No limit",
"instant": "Instant",
"twoMin": "2 min",
"network": "Network",
"supportedTip": "Supported: USDC, ETH",
"depositAddress": "Deposit address",
"copy": "Copy",
"copied": "Copied",
"connectDesc": "Connect your wallet to deposit. Send USDC or ETH to your deposit address after connecting.",
"connectedTip": "Connected. Send USDC or ETH to the address below.",
"metaMask": "MetaMask",
"coinbaseComingSoon": "Coinbase Wallet (Coming soon)",
"walletConnectComingSoon": "WalletConnect (Coming soon)",
"close": "Close",
"installMetaMask": "Please install MetaMask or another Web3 wallet."
},
"withdraw": {
"title": "Withdraw",
"polymarketBalance": "Polymarket Balance:",
"amountUsd": "Amount (USD)",
"max": "Max",
"network": "Network",
"withdrawTo": "Withdraw to",
"connectedWallet": "Connected wallet",
"customAddress": "Custom address",
"connectWallet": "Connect Wallet",
"walletAddress": "Wallet address",
"amountPlaceholder": "0.00",
"addressPlaceholder": "0x...",
"amountMustBePositive": "Amount must be greater than 0",
"insufficientBalance": "Insufficient balance",
"close": "Close"
},
"locale": {
"zh": "简体中文",
"en": "English",
"ja": "日本語",
"ko": "한국어"
}
}

172
src/locales/ja.json Normal file
View File

@ -0,0 +1,172 @@
{
"common": {
"login": "ログイン",
"logout": "ログアウト",
"balance": "残高",
"back": "戻る",
"search": "検索",
"filter": "フィルター",
"collapse": "閉じる",
"delete": "削除",
"clear": "クリア",
"loading": "読み込み中...",
"more": "その他",
"user": "ユーザー",
"chance": "確率"
},
"toast": {
"orderSuccess": "注文が完了しました"
},
"trade": {
"buy": "買う",
"sell": "売る",
"orderBook": "オーダーブック",
"buyLabel": "{label}を買う",
"sellLabel": "{label}を売る",
"merge": "マージ",
"split": "スプリット",
"market": "成行",
"limit": "指値",
"deposit": "入金",
"amount": "金額",
"shares": "シェア",
"limitPrice": "指値",
"setExpiration": "有効期限を設定",
"total": "合計",
"toWin": "獲得見込み",
"youllReceive": "受け取り額",
"avgPrice": "平均価格",
"max": "最大",
"balanceLabel": "残高",
"pleaseLogin": "先にログインしてください",
"pleaseSelectMarket": "市場を選択してくださいclobTokenIds が必要)",
"userError": "ユーザー情報エラー",
"orderFailed": "注文に失敗しました",
"expiration": {
"5m": "5分",
"15m": "15分",
"30m": "30分",
"1h": "1時間",
"2h": "2時間",
"4h": "4時間",
"8h": "8時間",
"12h": "12時間",
"1d": "1日",
"2d": "2日",
"3d": "3日"
}
},
"home": {
"searchHistory": "検索履歴",
"searchPlaceholder": "検索",
"loadMore": "もっと読み込む"
},
"error": {
"requestFailed": "リクエストに失敗しました",
"loadFailed": "読み込みに失敗しました",
"invalidId": "無効な ID または slug"
},
"activity": {
"comments": "コメント",
"topHolders": "持倉トップ",
"activity": "アクティビティ",
"noCommentsYet": "コメントはまだありません",
"topHoldersPlaceholder": "持倉トップがここに表示されます",
"minAmount": "最小金額",
"any": "任意",
"live": "ライブ",
"bought": "購入",
"sold": "売却",
"at": "で",
"viewTransaction": "取引を表示",
"justNow": "たった今",
"minutesAgo": "{n}分前",
"hoursAgo": "{n}時間前",
"daysAgo": "{n}日前",
"weeksAgo": "{n}週間前"
},
"wallet": {
"portfolio": "ポートフォリオ",
"today": "今日",
"deposit": "入金",
"withdraw": "出金",
"profitLoss": "損益",
"allTime": "全期間",
"pl1D": "1日",
"pl1W": "1週",
"pl1M": "1月",
"plAll": "全て",
"positions": "ポジション",
"openOrders": "未約定",
"history": "履歴",
"searchPlaceholder": "検索",
"currentValue": "現在価値",
"closeLosses": "損失決済",
"all": "全て",
"newest": "新しい順",
"export": "エクスポート",
"cancelAll": "一括キャンセル",
"noPositionsFound": "ポジションがありません",
"noOpenOrdersFound": "未約定注文がありません",
"noHistoryFound": "まだ取引履歴がありません",
"market": "市場",
"avgNow": "平均→現在",
"bet": "ベット",
"toWin": "獲得",
"value": "価値",
"action": "操作",
"side": "方向",
"outcome": "結果",
"price": "価格",
"filled": "約定",
"total": "合計",
"expiration": "有効期限",
"activity": "アクティビティ",
"view": "表示",
"expirationLabel": "有効期限:"
},
"deposit": {
"title": "入金",
"polymarketBalance": "Polymarket 残高:",
"transferCrypto": "暗号資産を送金",
"connectExchange": "取引所を接続",
"noLimit": "制限なし",
"instant": "即時",
"twoMin": "約2分",
"network": "ネットワーク",
"supportedTip": "対応USDC、ETH",
"depositAddress": "入金アドレス",
"copy": "コピー",
"copied": "コピーしました",
"connectDesc": "入金するにはウォレットを接続してください。接続後、USDC または ETH を入金アドレスに送金してください。",
"connectedTip": "接続済み。以下のアドレスに USDC または ETH を送金してください。",
"metaMask": "MetaMask",
"coinbaseComingSoon": "Coinbase Wallet近日公開",
"walletConnectComingSoon": "WalletConnect近日公開",
"close": "閉じる",
"installMetaMask": "MetaMask または他の Web3 ウォレットをインストールしてください。"
},
"withdraw": {
"title": "出金",
"polymarketBalance": "Polymarket 残高:",
"amountUsd": "金額 (USD)",
"max": "最大",
"network": "ネットワーク",
"withdrawTo": "出金先",
"connectedWallet": "接続済みウォレット",
"customAddress": "カスタムアドレス",
"connectWallet": "ウォレットを接続",
"walletAddress": "ウォレットアドレス",
"amountPlaceholder": "0.00",
"addressPlaceholder": "0x...",
"amountMustBePositive": "金額は 0 より大きい必要があります",
"insufficientBalance": "残高不足",
"close": "閉じる"
},
"locale": {
"zh": "简体中文",
"en": "English",
"ja": "日本語",
"ko": "한국어"
}
}

172
src/locales/ko.json Normal file
View File

@ -0,0 +1,172 @@
{
"common": {
"login": "로그인",
"logout": "로그아웃",
"balance": "잔액",
"back": "뒤로",
"search": "검색",
"filter": "필터",
"collapse": "접기",
"delete": "삭제",
"clear": "지우기",
"loading": "로딩 중...",
"more": "더보기",
"user": "사용자",
"chance": "확률"
},
"toast": {
"orderSuccess": "주문이 완료되었습니다"
},
"trade": {
"buy": "매수",
"sell": "매도",
"orderBook": "호가창",
"buyLabel": "{label} 매수",
"sellLabel": "{label} 매도",
"merge": "병합",
"split": "분할",
"market": "시장가",
"limit": "지정가",
"deposit": "입금",
"amount": "금액",
"shares": "주식",
"limitPrice": "지정가",
"setExpiration": "만료 설정",
"total": "합계",
"toWin": "획득 예상",
"youllReceive": "수령 예정",
"avgPrice": "평균 가격",
"max": "최대",
"balanceLabel": "잔액",
"pleaseLogin": "먼저 로그인하세요",
"pleaseSelectMarket": "시장을 선택하세요 (clobTokenIds 필요)",
"userError": "사용자 정보 오류",
"orderFailed": "주문 실패",
"expiration": {
"5m": "5분",
"15m": "15분",
"30m": "30분",
"1h": "1시간",
"2h": "2시간",
"4h": "4시간",
"8h": "8시간",
"12h": "12시간",
"1d": "1일",
"2d": "2일",
"3d": "3일"
}
},
"home": {
"searchHistory": "검색 기록",
"searchPlaceholder": "검색",
"loadMore": "더 불러오기"
},
"error": {
"requestFailed": "요청 실패",
"loadFailed": "로드 실패",
"invalidId": "잘못된 ID 또는 slug"
},
"activity": {
"comments": "댓글",
"topHolders": "보유자 순위",
"activity": "활동",
"noCommentsYet": "아직 댓글이 없습니다",
"topHoldersPlaceholder": "보유자 순위가 여기에 표시됩니다",
"minAmount": "최소 금액",
"any": "모두",
"live": "실시간",
"bought": "매수",
"sold": "매도",
"at": "에",
"viewTransaction": "거래 보기",
"justNow": "방금",
"minutesAgo": "{n}분 전",
"hoursAgo": "{n}시간 전",
"daysAgo": "{n}일 전",
"weeksAgo": "{n}주 전"
},
"wallet": {
"portfolio": "포트폴리오",
"today": "오늘",
"deposit": "입금",
"withdraw": "출금",
"profitLoss": "손익",
"allTime": "전체",
"pl1D": "1일",
"pl1W": "1주",
"pl1M": "1월",
"plAll": "전체",
"positions": "포지션",
"openOrders": "미체결",
"history": "내역",
"searchPlaceholder": "검색",
"currentValue": "현재 가치",
"closeLosses": "손실 청산",
"all": "전체",
"newest": "최신순",
"export": "내보내기",
"cancelAll": "전체 취소",
"noPositionsFound": "포지션이 없습니다",
"noOpenOrdersFound": "미체결 주문이 없습니다",
"noHistoryFound": "아직 거래 내역이 없습니다",
"market": "시장",
"avgNow": "평균→현재",
"bet": "베팅",
"toWin": "획득",
"value": "가치",
"action": "작업",
"side": "방향",
"outcome": "결과",
"price": "가격",
"filled": "체결",
"total": "합계",
"expiration": "만료",
"activity": "활동",
"view": "보기",
"expirationLabel": "만료:"
},
"deposit": {
"title": "입금",
"polymarketBalance": "Polymarket 잔액:",
"transferCrypto": "암호화폐 전송",
"connectExchange": "거래소 연결",
"noLimit": "제한 없음",
"instant": "즉시",
"twoMin": "약 2분",
"network": "네트워크",
"supportedTip": "지원: USDC, ETH",
"depositAddress": "입금 주소",
"copy": "복사",
"copied": "복사됨",
"connectDesc": "입금하려면 지갑을 연결하세요. 연결 후 USDC 또는 ETH를 입금 주소로 보내세요.",
"connectedTip": "연결됨. 아래 주소로 USDC 또는 ETH를 보내세요.",
"metaMask": "MetaMask",
"coinbaseComingSoon": "Coinbase Wallet (출시 예정)",
"walletConnectComingSoon": "WalletConnect (출시 예정)",
"close": "닫기",
"installMetaMask": "MetaMask 또는 다른 Web3 지갑을 설치해 주세요."
},
"withdraw": {
"title": "출금",
"polymarketBalance": "Polymarket 잔액:",
"amountUsd": "금액 (USD)",
"max": "최대",
"network": "네트워크",
"withdrawTo": "출금 대상",
"connectedWallet": "연결된 지갑",
"customAddress": "사용자 지정 주소",
"connectWallet": "지갑 연결",
"walletAddress": "지갑 주소",
"amountPlaceholder": "0.00",
"addressPlaceholder": "0x...",
"amountMustBePositive": "금액은 0보다 커야 합니다",
"insufficientBalance": "잔액 부족",
"close": "닫기"
},
"locale": {
"zh": "简体中文",
"en": "English",
"ja": "日本語",
"ko": "한국어"
}
}

172
src/locales/zh-CN.json Normal file
View File

@ -0,0 +1,172 @@
{
"common": {
"login": "登录",
"logout": "退出登录",
"balance": "余额",
"back": "返回",
"search": "搜索",
"filter": "筛选",
"collapse": "收起",
"delete": "删除",
"clear": "清空",
"loading": "加载中...",
"more": "更多操作",
"user": "用户",
"chance": "概率"
},
"toast": {
"orderSuccess": "下单成功"
},
"trade": {
"buy": "买入",
"sell": "卖出",
"orderBook": "订单簿",
"buyLabel": "买{label}",
"sellLabel": "卖{label}",
"merge": "合并",
"split": "拆分",
"market": "市价",
"limit": "限价",
"deposit": "入金",
"amount": "金额",
"shares": "份额",
"limitPrice": "限价",
"setExpiration": "设置到期",
"total": "合计",
"toWin": "可获得",
"youllReceive": "您将收到",
"avgPrice": "平均价",
"max": "最大",
"balanceLabel": "余额",
"pleaseLogin": "请先登录",
"pleaseSelectMarket": "请先选择市场(需包含 clobTokenIds",
"userError": "用户信息异常",
"orderFailed": "下单失败",
"expiration": {
"5m": "5分钟",
"15m": "15分钟",
"30m": "30分钟",
"1h": "1小时",
"2h": "2小时",
"4h": "4小时",
"8h": "8小时",
"12h": "12小时",
"1d": "1天",
"2d": "2天",
"3d": "3天"
}
},
"home": {
"searchHistory": "搜索历史",
"searchPlaceholder": "Search",
"loadMore": "加载更多"
},
"error": {
"requestFailed": "请求失败",
"loadFailed": "加载失败",
"invalidId": "无效的 ID 或 slug"
},
"activity": {
"comments": "评论",
"topHolders": "持仓大户",
"activity": "动态",
"noCommentsYet": "暂无评论",
"topHoldersPlaceholder": "持仓大户将在此显示",
"minAmount": "最小金额",
"any": "任意",
"live": "实时",
"bought": "买入",
"sold": "卖出",
"at": "以",
"viewTransaction": "查看交易",
"justNow": "刚刚",
"minutesAgo": "{n}分钟前",
"hoursAgo": "{n}小时前",
"daysAgo": "{n}天前",
"weeksAgo": "{n}周前"
},
"wallet": {
"portfolio": "资产组合",
"today": "今日",
"deposit": "入金",
"withdraw": "提现",
"profitLoss": "盈亏",
"allTime": "全部",
"pl1D": "1天",
"pl1W": "1周",
"pl1M": "1月",
"plAll": "全部",
"positions": "持仓",
"openOrders": "未成交",
"history": "历史",
"searchPlaceholder": "搜索",
"currentValue": "当前价值",
"closeLosses": "平仓亏损",
"all": "全部",
"newest": "最新",
"export": "导出",
"cancelAll": "全部撤单",
"noPositionsFound": "暂无持仓",
"noOpenOrdersFound": "暂无未成交订单",
"noHistoryFound": "您还未进行过任何交易",
"market": "市场",
"avgNow": "均价→现价",
"bet": "投注",
"toWin": "可赢",
"value": "价值",
"action": "操作",
"side": "方向",
"outcome": "结果",
"price": "价格",
"filled": "成交",
"total": "合计",
"expiration": "到期",
"activity": "活动",
"view": "查看",
"expirationLabel": "到期"
},
"deposit": {
"title": "入金",
"polymarketBalance": "Polymarket 余额:",
"transferCrypto": "转账加密货币",
"connectExchange": "连接交易所",
"noLimit": "无限制",
"instant": "即时到账",
"twoMin": "约 2 分钟",
"network": "网络",
"supportedTip": "支持USDC、ETH",
"depositAddress": "充值地址",
"copy": "复制",
"copied": "已复制",
"connectDesc": "连接钱包以充值。连接后,将 USDC 或 ETH 发送至您的充值地址。",
"connectedTip": "已连接。请将 USDC 或 ETH 发送至下方地址。",
"metaMask": "MetaMask",
"coinbaseComingSoon": "Coinbase Wallet即将推出",
"walletConnectComingSoon": "WalletConnect即将推出",
"close": "关闭",
"installMetaMask": "请安装 MetaMask 或其他 Web3 钱包。"
},
"withdraw": {
"title": "提现",
"polymarketBalance": "Polymarket 余额:",
"amountUsd": "金额 (USD)",
"max": "最大",
"network": "网络",
"withdrawTo": "提现至",
"connectedWallet": "已连接钱包",
"customAddress": "自定义地址",
"connectWallet": "连接钱包",
"walletAddress": "钱包地址",
"amountPlaceholder": "0.00",
"addressPlaceholder": "0x...",
"amountMustBePositive": "金额必须大于 0",
"insufficientBalance": "余额不足",
"close": "关闭"
},
"locale": {
"zh": "简体中文",
"en": "English",
"ja": "日本語",
"ko": "한국어"
}
}

View File

@ -4,11 +4,13 @@ import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import vuetify from './plugins/vuetify' import vuetify from './plugins/vuetify'
import { i18n } from './plugins/i18n'
const app = createApp(App) const app = createApp(App)
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
app.use(i18n)
app.use(vuetify) app.use(vuetify)
app.mount('#app') app.mount('#app')

44
src/plugins/i18n.ts Normal file
View File

@ -0,0 +1,44 @@
import { createI18n } from 'vue-i18n'
import zhCN from '../locales/zh-CN.json'
import en from '../locales/en.json'
import ja from '../locales/ja.json'
import ko from '../locales/ko.json'
export type LocaleCode = 'zh-CN' | 'en' | 'ja' | 'ko'
const LOCALE_STORAGE_KEY = 'poly-locale'
export const defaultLocale: LocaleCode = 'zh-CN'
function loadSavedLocale(): LocaleCode {
try {
const saved = localStorage.getItem(LOCALE_STORAGE_KEY)
if (saved && ['zh-CN', 'en', 'ja', 'ko'].includes(saved)) {
return saved as LocaleCode
}
} catch {
//
}
return defaultLocale
}
export const i18n = createI18n({
legacy: false,
locale: loadSavedLocale(),
fallbackLocale: 'en',
messages: {
'zh-CN': zhCN as Record<string, unknown>,
en: en as Record<string, unknown>,
ja: ja as Record<string, unknown>,
ko: ko as Record<string, unknown>,
},
})
export function setLocale(locale: LocaleCode) {
i18n.global.locale.value = locale
try {
localStorage.setItem(LOCALE_STORAGE_KEY, locale)
} catch {
//
}
}

21
src/stores/locale.ts Normal file
View File

@ -0,0 +1,21 @@
import { computed } from 'vue'
import { defineStore } from 'pinia'
import type { LocaleCode } from '@/plugins/i18n'
import { i18n, setLocale as setI18nLocale } from '@/plugins/i18n'
export const useLocaleStore = defineStore('locale', () => {
const currentLocale = computed(() => i18n.global.locale.value as LocaleCode)
const localeOptions: { value: LocaleCode; label: string }[] = [
{ value: 'zh-CN', label: '简体中文' },
{ value: 'en', label: 'English' },
{ value: 'ja', label: '日本語' },
{ value: 'ko', label: '한국어' },
]
function setLocale(loc: LocaleCode) {
setI18nLocale(loc)
}
return { currentLocale, localeOptions, setLocale }
})

View File

@ -3,7 +3,7 @@
<v-card v-if="detailLoading && !eventDetail" class="loading-card" elevation="0" rounded="lg"> <v-card v-if="detailLoading && !eventDetail" class="loading-card" elevation="0" rounded="lg">
<div class="loading-placeholder"> <div class="loading-placeholder">
<v-progress-circular indeterminate color="primary" size="48" /> <v-progress-circular indeterminate color="primary" size="48" />
<p>加载中...</p> <p>{{ t('common.loading') }}</p>
</div> </div>
</v-card> </v-card>
@ -155,10 +155,10 @@
</template> </template>
<v-list density="compact"> <v-list density="compact">
<v-list-item @click="openMergeFromBar"> <v-list-item @click="openMergeFromBar">
<v-list-item-title>Merge</v-list-item-title> <v-list-item-title>{{ t('trade.merge') }}</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item @click="openSplitFromBar"> <v-list-item @click="openSplitFromBar">
<v-list-item-title>Split</v-list-item-title> <v-list-item-title>{{ t('trade.split') }}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
@ -198,10 +198,12 @@ import {
type PmEventMarketItem, type PmEventMarketItem,
} from '../api/event' } from '../api/event'
import { MOCK_EVENT_LIST } from '../api/mockEventList' import { MOCK_EVENT_LIST } from '../api/mockEventList'
import { useI18n } from 'vue-i18n'
import { useUserStore } from '../stores/user' import { useUserStore } from '../stores/user'
import { useToastStore } from '../stores/toast' import { useToastStore } from '../stores/toast'
const route = useRoute() const route = useRoute()
const { t } = useI18n()
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const { mobile } = useDisplay() const { mobile } = useDisplay()
@ -564,7 +566,7 @@ function onTradeSubmit(payload: {
const toastStore = useToastStore() const toastStore = useToastStore()
function onOrderSuccess() { function onOrderSuccess() {
tradeSheetOpen.value = false tradeSheetOpen.value = false
toastStore.show('下单成功') toastStore.show(t('toast.orderSuccess'))
} }
function marketChance(market: PmEventMarketItem): number { function marketChance(market: PmEventMarketItem): number {
@ -619,7 +621,7 @@ async function loadEventDetail() {
const idRaw = route.params.id const idRaw = route.params.id
const idStr = String(idRaw ?? '').trim() const idStr = String(idRaw ?? '').trim()
if (!idStr) { if (!idStr) {
detailError.value = '无效的 ID 或 slug' detailError.value = t('error.invalidId')
eventDetail.value = null eventDetail.value = null
return return
} }
@ -646,7 +648,7 @@ async function loadEventDetail() {
eventDetail.value = fallback eventDetail.value = fallback
detailError.value = null detailError.value = null
} else { } else {
detailError.value = res.msg || '加载失败' detailError.value = res.msg || t('error.loadFailed')
eventDetail.value = null eventDetail.value = null
} }
} }
@ -656,7 +658,7 @@ async function loadEventDetail() {
eventDetail.value = fallback eventDetail.value = fallback
detailError.value = null detailError.value = null
} else { } else {
detailError.value = e instanceof Error ? e.message : '加载失败' detailError.value = e instanceof Error ? e.message : t('error.loadFailed')
eventDetail.value = null eventDetail.value = null
} }
} finally { } finally {

View File

@ -18,7 +18,7 @@
variant="text" variant="text"
size="small" size="small"
class="home-category-action-btn" class="home-category-action-btn"
aria-label="搜索" :aria-label="t('common.search')"
@click="expandSearch" @click="expandSearch"
> >
<v-icon size="20">mdi-magnify</v-icon> <v-icon size="20">mdi-magnify</v-icon>
@ -28,7 +28,7 @@
variant="text" variant="text"
size="small" size="small"
class="home-category-action-btn" class="home-category-action-btn"
aria-label="筛选" :aria-label="t('common.filter')"
> >
<v-icon size="20">mdi-filter-outline</v-icon> <v-icon size="20">mdi-filter-outline</v-icon>
</v-btn> </v-btn>
@ -43,7 +43,7 @@
v-model="searchKeyword" v-model="searchKeyword"
density="compact" density="compact"
hide-details hide-details
placeholder="Search" :placeholder="t('home.searchPlaceholder')"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
variant="outlined" variant="outlined"
class="home-search-overlay-field" class="home-search-overlay-field"
@ -55,7 +55,7 @@
variant="text" variant="text"
size="small" size="small"
class="home-search-close-btn" class="home-search-close-btn"
aria-label="收起" :aria-label="t('common.collapse')"
@click="collapseSearch" @click="collapseSearch"
> >
<v-icon size="18">mdi-close</v-icon> <v-icon size="18">mdi-close</v-icon>
@ -63,7 +63,7 @@
</div> </div>
<div v-if="searchHistoryList.length > 0" class="home-search-history"> <div v-if="searchHistoryList.length > 0" class="home-search-history">
<div class="home-search-history-header"> <div class="home-search-history-header">
<span class="home-search-history-title">搜索历史</span> <span class="home-search-history-title">{{ t('home.searchHistory') }}</span>
<v-btn <v-btn
variant="text" variant="text"
size="x-small" size="x-small"
@ -71,7 +71,7 @@
class="home-search-history-clear" class="home-search-history-clear"
@click="searchHistory.clearAll" @click="searchHistory.clearAll"
> >
清空 {{ t('common.clear') }}
</v-btn> </v-btn>
</div> </div>
<ul class="home-search-history-list"> <ul class="home-search-history-list">
@ -92,7 +92,7 @@
variant="text" variant="text"
size="x-small" size="x-small"
class="home-search-history-delete" class="home-search-history-delete"
aria-label="删除" :aria-label="t('common.delete')"
@click.stop="searchHistory.remove(idx)" @click.stop="searchHistory.remove(idx)"
> >
<v-icon size="16">mdi-close</v-icon> <v-icon size="16">mdi-close</v-icon>
@ -175,7 +175,7 @@
<div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" /> <div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" />
<div v-if="loadingMore" class="load-more-indicator"> <div v-if="loadingMore" class="load-more-indicator">
<v-progress-circular indeterminate size="24" width="2" /> <v-progress-circular indeterminate size="24" width="2" />
<span>加载中...</span> <span>{{ t('common.loading') }}</span>
</div> </div>
<div v-else-if="noMoreEvents" class="no-more-tip">没有更多了</div> <div v-else-if="noMoreEvents" class="no-more-tip">没有更多了</div>
<v-btn <v-btn
@ -186,7 +186,7 @@
:disabled="loadingMore" :disabled="loadingMore"
@click="loadMore" @click="loadMore"
> >
加载更多 {{ t('home.loadMore') }}
</v-btn> </v-btn>
</div> </div>
</div> </div>
@ -322,10 +322,12 @@ import {
resolveCategoryIconColor, resolveCategoryIconColor,
type CategoryTreeNode, type CategoryTreeNode,
} from '../api/category' } from '../api/category'
import { useI18n } from 'vue-i18n'
import { useSearchHistory } from '../composables/useSearchHistory' import { useSearchHistory } from '../composables/useSearchHistory'
import { useToastStore } from '../stores/toast' import { useToastStore } from '../stores/toast'
const { mobile } = useDisplay() const { mobile } = useDisplay()
const { t } = useI18n()
const searchHistory = useSearchHistory() const searchHistory = useSearchHistory()
const searchHistoryList = computed(() => searchHistory.list.value) const searchHistoryList = computed(() => searchHistory.list.value)
const isMobile = computed(() => mobile.value) const isMobile = computed(() => mobile.value)
@ -526,7 +528,7 @@ function onCardOpenTrade(
const toastStore = useToastStore() const toastStore = useToastStore()
function onOrderSuccess() { function onOrderSuccess() {
tradeDialogOpen.value = false tradeDialogOpen.value = false
toastStore.show('下单成功') toastStore.show(t('toast.orderSuccess'))
} }
/** 传给 TradeComponent 的 marketHome 弹窗/底部栏),供 Split、下单等使用 */ /** 传给 TradeComponent 的 marketHome 弹窗/底部栏),供 Split、下单等使用 */

View File

@ -8,14 +8,14 @@
<!-- 顶部标题当前概率Past / 日期 --> <!-- 顶部标题当前概率Past / 日期 -->
<div class="chart-header"> <div class="chart-header">
<h1 class="chart-title"> <h1 class="chart-title">
{{ detailLoading && !eventDetail ? '加载中...' : marketTitle }} {{ detailLoading && !eventDetail ? t('common.loading') : marketTitle }}
</h1> </h1>
<p v-if="detailError" class="chart-error">{{ detailError }}</p> <p v-if="detailError" class="chart-error">{{ detailError }}</p>
<div class="chart-controls-row"> <div class="chart-controls-row">
<v-btn variant="text" size="small" class="past-btn">Past </v-btn> <v-btn variant="text" size="small" class="past-btn">Past </v-btn>
<v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</v-btn> <v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</v-btn>
</div> </div>
<div class="chart-chance">{{ currentChance }}% chance</div> <div class="chart-chance">{{ currentChance }}% {{ t('common.chance') }}</div>
</div> </div>
<!-- 图表区域 --> <!-- 图表区域 -->
@ -53,22 +53,24 @@
:spread="clobSpread" :spread="clobSpread"
:loading="clobLoading" :loading="clobLoading"
:connected="clobConnected" :connected="clobConnected"
:yes-label="yesLabel"
:no-label="noLabel"
/> />
</v-card> </v-card>
<!-- Comments / Top Holders / Activity与左侧图表订单簿同宽 --> <!-- Comments / Top Holders / Activity与左侧图表订单簿同宽 -->
<v-card class="activity-card" elevation="0" rounded="lg"> <v-card class="activity-card" elevation="0" rounded="lg">
<v-tabs v-model="detailTab" class="detail-tabs" density="comfortable"> <v-tabs v-model="detailTab" class="detail-tabs" density="comfortable">
<v-tab value="comments">Comments</v-tab> <v-tab value="comments">{{ t('activity.comments') }}</v-tab>
<v-tab value="holders">Top Holders</v-tab> <v-tab value="holders">{{ t('activity.topHolders') }}</v-tab>
<v-tab value="activity">Activity</v-tab> <v-tab value="activity">{{ t('activity.activity') }}</v-tab>
</v-tabs> </v-tabs>
<v-window v-model="detailTab" class="detail-window"> <v-window v-model="detailTab" class="detail-window">
<v-window-item value="comments" class="detail-pane"> <v-window-item value="comments" class="detail-pane">
<div class="placeholder-pane">No comments yet.</div> <div class="placeholder-pane">{{ t('activity.noCommentsYet') }}</div>
</v-window-item> </v-window-item>
<v-window-item value="holders" class="detail-pane"> <v-window-item value="holders" class="detail-pane">
<div class="placeholder-pane">Top holders will appear here.</div> <div class="placeholder-pane">{{ t('activity.topHoldersPlaceholder') }}</div>
</v-window-item> </v-window-item>
<v-window-item value="activity" class="detail-pane"> <v-window-item value="activity" class="detail-pane">
<div class="activity-toolbar"> <div class="activity-toolbar">
@ -78,12 +80,12 @@
density="compact" density="compact"
hide-details hide-details
variant="outlined" variant="outlined"
label="Min amount" :label="t('activity.minAmount')"
class="min-amount-select" class="min-amount-select"
/> />
<span class="live-badge"> <span class="live-badge">
<span class="live-dot"></span> <span class="live-dot"></span>
Live {{ t('activity.live') }}
</span> </span>
</div> </div>
<div class="activity-list"> <div class="activity-list">
@ -98,18 +100,18 @@
</div> </div>
<div class="activity-body"> <div class="activity-body">
<span class="activity-user">{{ item.user }}</span> <span class="activity-user">{{ item.user }}</span>
<span class="activity-action">{{ item.action }}</span> <span class="activity-action">{{ t(item.action === 'bought' ? 'activity.bought' : 'activity.sold') }}</span>
<span <span
:class="['activity-amount', item.side === 'Yes' ? 'amount-yes' : 'amount-no']" :class="['activity-amount', item.side === 'Yes' ? 'amount-yes' : 'amount-no']"
> >
{{ item.amount }} {{ item.side }} {{ item.amount }} {{ item.side }}
</span> </span>
<span class="activity-price">at {{ item.price }}</span> <span class="activity-price">{{ t('activity.at') }} {{ item.price }}</span>
<span class="activity-total">({{ item.total }})</span> <span class="activity-total">({{ item.total }})</span>
</div> </div>
<div class="activity-meta"> <div class="activity-meta">
<span class="activity-time">{{ formatTimeAgo(item.time) }}</span> <span class="activity-time">{{ formatTimeAgo(item.time) }}</span>
<a href="#" class="activity-link" aria-label="View transaction" @click.prevent> <a href="#" class="activity-link" :aria-label="t('activity.viewTransaction')" @click.prevent>
<v-icon size="16">mdi-open-in-new</v-icon> <v-icon size="16">mdi-open-in-new</v-icon>
</a> </a>
</div> </div>
@ -167,10 +169,10 @@
</template> </template>
<v-list density="compact"> <v-list density="compact">
<v-list-item @click="openMergeFromBar"> <v-list-item @click="openMergeFromBar">
<v-list-item-title>Merge</v-list-item-title> <v-list-item-title>{{ t('trade.merge') }}</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item @click="openSplitFromBar"> <v-list-item @click="openSplitFromBar">
<v-list-item-title>Split</v-list-item-title> <v-list-item-title>{{ t('trade.split') }}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
@ -192,6 +194,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue' import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import type { ECharts } from 'echarts' import type { ECharts } from 'echarts'
@ -207,6 +210,8 @@ import {
import { getClobWsUrl } from '../api/request' import { getClobWsUrl } from '../api/request'
import { useUserStore } from '../stores/user' import { useUserStore } from '../stores/user'
import { useToastStore } from '../stores/toast' import { useToastStore } from '../stores/toast'
const { t } = useI18n()
import { import {
ClobSdk, ClobSdk,
type PriceSizePolyMsg, type PriceSizePolyMsg,
@ -271,7 +276,7 @@ async function loadEventDetail() {
const idRaw = route.params.id const idRaw = route.params.id
const idStr = String(idRaw ?? '').trim() const idStr = String(idRaw ?? '').trim()
if (!idStr) { if (!idStr) {
detailError.value = '无效的 ID 或 slug' detailError.value = t('error.invalidId')
eventDetail.value = null eventDetail.value = null
return return
} }
@ -292,11 +297,11 @@ async function loadEventDetail() {
if (res.code === 0 || res.code === 200) { if (res.code === 0 || res.code === 200) {
eventDetail.value = res.data ?? null eventDetail.value = res.data ?? null
} else { } else {
detailError.value = res.msg || '加载失败' detailError.value = res.msg || t('error.loadFailed')
eventDetail.value = null eventDetail.value = null
} }
} catch (e) { } catch (e) {
detailError.value = e instanceof Error ? e.message : '加载失败' detailError.value = e instanceof Error ? e.message : t('error.loadFailed')
eventDetail.value = null eventDetail.value = null
} finally { } finally {
detailLoading.value = false detailLoading.value = false
@ -531,19 +536,19 @@ function openSplitFromBar() {
const toastStore = useToastStore() const toastStore = useToastStore()
function onOrderSuccess() { function onOrderSuccess() {
tradeSheetOpen.value = false tradeSheetOpen.value = false
toastStore.show('下单成功') toastStore.show(t('toast.orderSuccess'))
} }
// Comments / Top Holders / Activity // Comments / Top Holders / Activity
const detailTab = ref('activity') const detailTab = ref('activity')
const activityMinAmount = ref<string>('0') const activityMinAmount = ref<string>('0')
const minAmountOptions = [ const minAmountOptions = computed(() => [
{ title: 'Any', value: '0' }, { title: t('activity.any'), value: '0' },
{ title: '$1', value: '1' }, { title: '$1', value: '1' },
{ title: '$10', value: '10' }, { title: '$10', value: '10' },
{ title: '$100', value: '100' }, { title: '$100', value: '100' },
{ title: '$500', value: '500' }, { title: '$500', value: '500' },
] ])
interface ActivityItem { interface ActivityItem {
id: string id: string
@ -636,11 +641,11 @@ const filteredActivity = computed(() => {
function formatTimeAgo(ts: number): string { function formatTimeAgo(ts: number): string {
const sec = Math.floor((Date.now() - ts) / 1000) const sec = Math.floor((Date.now() - ts) / 1000)
if (sec < 60) return 'just now' if (sec < 60) return t('activity.justNow')
if (sec < 3600) return `${Math.floor(sec / 60)}m ago` if (sec < 3600) return t('activity.minutesAgo', { n: Math.floor(sec / 60) })
if (sec < 86400) return `${Math.floor(sec / 3600)}h ago` if (sec < 86400) return t('activity.hoursAgo', { n: Math.floor(sec / 3600) })
if (sec < 604800) return `${Math.floor(sec / 86400)}d ago` if (sec < 604800) return t('activity.daysAgo', { n: Math.floor(sec / 86400) })
return `${Math.floor(sec / 604800)}w ago` return t('activity.weeksAgo', { n: Math.floor(sec / 604800) })
} }
// //

View File

@ -6,7 +6,7 @@
<v-card class="wallet-card portfolio-card" elevation="0" rounded="lg"> <v-card class="wallet-card portfolio-card" elevation="0" rounded="lg">
<div class="card-header"> <div class="card-header">
<span class="card-title"> <span class="card-title">
Portfolio {{ t('wallet.portfolio') }}
<v-icon size="16" class="title-icon">mdi-eye-off-outline</v-icon> <v-icon size="16" class="title-icon">mdi-eye-off-outline</v-icon>
</span> </span>
<div class="balance-badge"> <div class="balance-badge">
@ -15,7 +15,7 @@
</div> </div>
</div> </div>
<div class="card-value">${{ portfolioBalance }}</div> <div class="card-value">${{ portfolioBalance }}</div>
<div class="card-timeframe">Today</div> <div class="card-timeframe">{{ t('wallet.today') }}</div>
<div class="card-actions"> <div class="card-actions">
<v-btn <v-btn
color="primary" color="primary"
@ -24,7 +24,7 @@
prepend-icon="mdi-arrow-down" prepend-icon="mdi-arrow-down"
@click="depositDialogOpen = true" @click="depositDialogOpen = true"
> >
Deposit {{ t('wallet.deposit') }}
</v-btn> </v-btn>
<v-btn <v-btn
variant="outlined" variant="outlined"
@ -33,7 +33,7 @@
prepend-icon="mdi-arrow-up" prepend-icon="mdi-arrow-up"
@click="withdrawDialogOpen = true" @click="withdrawDialogOpen = true"
> >
Withdraw {{ t('wallet.withdraw') }}
</v-btn> </v-btn>
</div> </div>
</v-card> </v-card>
@ -43,19 +43,19 @@
<div class="card-header"> <div class="card-header">
<span class="card-title"> <span class="card-title">
<v-icon size="16" color="success">mdi-triangle-small-up</v-icon> <v-icon size="16" color="success">mdi-triangle-small-up</v-icon>
Profit/Loss {{ t('wallet.profitLoss') }}
</span> </span>
<div class="pl-tabs"> <div class="pl-tabs">
<v-btn <v-btn
v-for="t in plTimeRanges" v-for="tr in plTimeRanges"
:key="t" :key="tr.value"
:variant="plRange === t ? 'flat' : 'text'" :variant="plRange === tr.value ? 'flat' : 'text'"
:color="plRange === t ? 'primary' : undefined" :color="plRange === tr.value ? 'primary' : undefined"
size="small" size="small"
class="pl-tab" class="pl-tab"
@click="plRange = t" @click="plRange = tr.value"
> >
{{ t }} {{ tr.label }}
</v-btn> </v-btn>
</div> </div>
</div> </div>
@ -63,7 +63,7 @@
<span class="card-value">${{ profitLoss }}</span> <span class="card-value">${{ profitLoss }}</span>
<v-icon size="18" class="info-icon">mdi-information-outline</v-icon> <v-icon size="18" class="info-icon">mdi-information-outline</v-icon>
</div> </div>
<div class="card-timeframe">All-Time</div> <div class="card-timeframe">{{ t('wallet.allTime') }}</div>
<div ref="plChartRef" class="pl-chart"></div> <div ref="plChartRef" class="pl-chart"></div>
</v-card> </v-card>
</v-col> </v-col>
@ -72,14 +72,14 @@
<!-- 下方Positions / Open orders / History --> <!-- 下方Positions / Open orders / History -->
<div class="wallet-section"> <div class="wallet-section">
<v-tabs v-model="activeTab" class="wallet-tabs" density="comfortable"> <v-tabs v-model="activeTab" class="wallet-tabs" density="comfortable">
<v-tab value="positions">Positions</v-tab> <v-tab value="positions">{{ t('wallet.positions') }}</v-tab>
<v-tab value="orders">Open orders</v-tab> <v-tab value="orders">{{ t('wallet.openOrders') }}</v-tab>
<v-tab value="history">History</v-tab> <v-tab value="history">{{ t('wallet.history') }}</v-tab>
</v-tabs> </v-tabs>
<div class="toolbar"> <div class="toolbar">
<v-text-field <v-text-field
v-model="search" v-model="search"
placeholder="Search" :placeholder="t('wallet.searchPlaceholder')"
density="compact" density="compact"
hide-details hide-details
variant="outlined" variant="outlined"
@ -96,34 +96,34 @@
@click="closeLosses" @click="closeLosses"
> >
<v-icon size="18">mdi-delete-outline</v-icon> <v-icon size="18">mdi-delete-outline</v-icon>
Close Losses {{ t('wallet.closeLosses') }}
</v-btn> </v-btn>
<v-btn variant="outlined" size="small" class="filter-btn"> <v-btn variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-filter</v-icon> <v-icon size="18">mdi-filter</v-icon>
All {{ t('wallet.all') }}
</v-btn> </v-btn>
<v-btn variant="outlined" size="small" class="filter-btn"> <v-btn variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-sort</v-icon> <v-icon size="18">mdi-sort</v-icon>
Newest {{ t('wallet.newest') }}
</v-btn> </v-btn>
<v-btn variant="outlined" size="small" class="filter-btn" icon> <v-btn variant="outlined" size="small" class="filter-btn" icon>
<v-icon size="18">mdi-calendar</v-icon> <v-icon size="18">mdi-calendar</v-icon>
</v-btn> </v-btn>
<v-btn variant="outlined" size="small" class="filter-btn"> <v-btn variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-download</v-icon> <v-icon size="18">mdi-download</v-icon>
Export {{ t('wallet.export') }}
</v-btn> </v-btn>
</template> </template>
<template v-else-if="activeTab === 'positions'"> <template v-else-if="activeTab === 'positions'">
<v-btn variant="outlined" size="small" class="filter-btn"> <v-btn variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-filter</v-icon> <v-icon size="18">mdi-filter</v-icon>
Current value {{ t('wallet.currentValue') }}
</v-btn> </v-btn>
</template> </template>
<template v-else-if="activeTab === 'orders'"> <template v-else-if="activeTab === 'orders'">
<v-btn variant="outlined" size="small" class="filter-btn"> <v-btn variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-filter</v-icon> <v-icon size="18">mdi-filter</v-icon>
Market {{ t('wallet.market') }}
</v-btn> </v-btn>
<v-btn <v-btn
variant="outlined" variant="outlined"
@ -132,12 +132,12 @@
@click="cancelAllOrders" @click="cancelAllOrders"
> >
<v-icon size="18">mdi-close</v-icon> <v-icon size="18">mdi-close</v-icon>
Cancel all {{ t('wallet.cancelAll') }}
</v-btn> </v-btn>
</template> </template>
<v-btn v-else variant="outlined" size="small" class="filter-btn"> <v-btn v-else variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-filter</v-icon> <v-icon size="18">mdi-filter</v-icon>
Market {{ t('wallet.market') }}
</v-btn> </v-btn>
</div> </div>
<v-card class="table-card" elevation="0" rounded="lg"> <v-card class="table-card" elevation="0" rounded="lg">
@ -146,7 +146,7 @@
<!-- 移动端可折叠列表 --> <!-- 移动端可折叠列表 -->
<div v-if="mobile" class="positions-mobile-list"> <div v-if="mobile" class="positions-mobile-list">
<template v-if="filteredPositions.length === 0"> <template v-if="filteredPositions.length === 0">
<div class="empty-cell">No positions found.</div> <div class="empty-cell">{{ t('wallet.noPositionsFound') }}</div>
</template> </template>
<div <div
v-for="pos in paginatedPositions" v-for="pos in paginatedPositions"
@ -213,7 +213,7 @@
size="small" size="small"
class="position-sell-btn" class="position-sell-btn"
@click="sellPosition(pos.id)" @click="sellPosition(pos.id)"
>Sell</v-btn >{{ t('trade.sell') }}</v-btn
> >
<v-btn <v-btn
icon icon
@ -232,23 +232,23 @@
<v-table v-else class="wallet-table positions-table-full"> <v-table v-else class="wallet-table positions-table-full">
<thead> <thead>
<tr> <tr>
<th class="text-left">MARKET</th> <th class="text-left">{{ t('wallet.market') }}</th>
<th class="text-left"> <th class="text-left">
AVG NOW {{ t('wallet.avgNow') }}
<v-icon size="14" class="th-icon">mdi-information-outline</v-icon> <v-icon size="14" class="th-icon">mdi-information-outline</v-icon>
</th> </th>
<th class="text-left">BET</th> <th class="text-left">{{ t('wallet.bet') }}</th>
<th class="text-left">TO WIN</th> <th class="text-left">{{ t('wallet.toWin') }}</th>
<th class="text-left"> <th class="text-left">
VALUE {{ t('wallet.value') }}
<v-icon size="14" class="th-icon">mdi-chevron-down</v-icon> <v-icon size="14" class="th-icon">mdi-chevron-down</v-icon>
</th> </th>
<th class="text-right">ACTION</th> <th class="text-right">{{ t('wallet.action') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-if="filteredPositions.length === 0"> <tr v-if="filteredPositions.length === 0">
<td colspan="6" class="empty-cell">No positions found.</td> <td colspan="6" class="empty-cell">{{ t('wallet.noPositionsFound') }}</td>
</tr> </tr>
<tr v-for="pos in paginatedPositions" :key="pos.id" class="position-row"> <tr v-for="pos in paginatedPositions" :key="pos.id" class="position-row">
<td class="cell-market"> <td class="cell-market">
@ -296,7 +296,7 @@
size="small" size="small"
class="position-sell-btn" class="position-sell-btn"
@click="sellPosition(pos.id)" @click="sellPosition(pos.id)"
>Sell</v-btn >{{ t('trade.sell') }}</v-btn
> >
<v-btn <v-btn
icon icon
@ -317,7 +317,7 @@
<!-- 移动端挂单卡片列表 --> <!-- 移动端挂单卡片列表 -->
<div v-if="mobile" class="orders-mobile-list"> <div v-if="mobile" class="orders-mobile-list">
<template v-if="filteredOpenOrders.length === 0"> <template v-if="filteredOpenOrders.length === 0">
<div class="empty-cell">No open orders found.</div> <div class="empty-cell">{{ t('wallet.noOpenOrdersFound') }}</div>
</template> </template>
<div v-for="ord in paginatedOpenOrders" :key="ord.id" class="order-mobile-card"> <div v-for="ord in paginatedOpenOrders" :key="ord.id" class="order-mobile-card">
<div class="order-mobile-icon" :class="ord.iconClass"> <div class="order-mobile-icon" :class="ord.iconClass">
@ -346,7 +346,7 @@
<v-icon size="20">mdi-close</v-icon> <v-icon size="20">mdi-close</v-icon>
</v-btn> </v-btn>
<div class="order-mobile-filled">{{ ord.filledDisplay || ord.filled }}</div> <div class="order-mobile-filled">{{ ord.filledDisplay || ord.filled }}</div>
<div class="order-mobile-expiry">Expiration: {{ ord.expiration }}</div> <div class="order-mobile-expiry">{{ t('wallet.expirationLabel') }} {{ ord.expiration }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -354,19 +354,19 @@
<v-table v-else class="wallet-table"> <v-table v-else class="wallet-table">
<thead> <thead>
<tr> <tr>
<th class="text-left">MARKET</th> <th class="text-left">{{ t('wallet.market') }}</th>
<th class="text-left">SIDE</th> <th class="text-left">{{ t('wallet.side') }}</th>
<th class="text-left">OUTCOME</th> <th class="text-left">{{ t('wallet.outcome') }}</th>
<th class="text-left">PRICE</th> <th class="text-left">{{ t('wallet.price') }}</th>
<th class="text-left">FILLED</th> <th class="text-left">{{ t('wallet.filled') }}</th>
<th class="text-left">TOTAL</th> <th class="text-left">{{ t('wallet.total') }}</th>
<th class="text-left">EXPIRATION</th> <th class="text-left">{{ t('wallet.expiration') }}</th>
<th class="text-right">ACTION</th> <th class="text-right">{{ t('wallet.action') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-if="filteredOpenOrders.length === 0"> <tr v-if="filteredOpenOrders.length === 0">
<td colspan="8" class="empty-cell">No open orders found.</td> <td colspan="8" class="empty-cell">{{ t('wallet.noOpenOrdersFound') }}</td>
</tr> </tr>
<tr v-for="ord in paginatedOpenOrders" :key="ord.id"> <tr v-for="ord in paginatedOpenOrders" :key="ord.id">
<td class="cell-market">{{ ord.market }}</td> <td class="cell-market">{{ ord.market }}</td>
@ -397,7 +397,7 @@
<!-- 移动端历史卡片列表 --> <!-- 移动端历史卡片列表 -->
<div v-if="mobile" class="history-mobile-list"> <div v-if="mobile" class="history-mobile-list">
<template v-if="filteredHistory.length === 0"> <template v-if="filteredHistory.length === 0">
<div class="empty-cell">You haven't traded any polymarkets yet</div> <div class="empty-cell">{{ t('wallet.noHistoryFound') }}</div>
</template> </template>
<div <div
v-for="h in paginatedHistory" v-for="h in paginatedHistory"
@ -437,7 +437,7 @@
size="small" size="small"
class="history-view-btn" class="history-view-btn"
@click="viewHistory(h.id)" @click="viewHistory(h.id)"
>View</v-btn >{{ t('wallet.view') }}</v-btn
> >
<v-btn <v-btn
icon icon
@ -456,14 +456,14 @@
<v-table v-else class="wallet-table"> <v-table v-else class="wallet-table">
<thead> <thead>
<tr> <tr>
<th class="text-left">ACTIVITY</th> <th class="text-left">{{ t('wallet.activity') }}</th>
<th class="text-left">MARKET</th> <th class="text-left">{{ t('wallet.market') }}</th>
<th class="text-left">VALUE</th> <th class="text-left">{{ t('wallet.value') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-if="filteredHistory.length === 0"> <tr v-if="filteredHistory.length === 0">
<td colspan="3" class="empty-cell">You haven't traded any polymarkets yet</td> <td colspan="3" class="empty-cell">{{ t('wallet.noHistoryFound') }}</td>
</tr> </tr>
<tr v-for="h in paginatedHistory" :key="h.id"> <tr v-for="h in paginatedHistory" :key="h.id">
<td> <td>
@ -563,7 +563,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue' import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
const { t } = useI18n()
import * as echarts from 'echarts' import * as echarts from 'echarts'
import type { ECharts } from 'echarts' import type { ECharts } from 'echarts'
import DepositDialog from '../components/DepositDialog.vue' import DepositDialog from '../components/DepositDialog.vue'
@ -576,7 +579,12 @@ const userStore = useUserStore()
const portfolioBalance = computed(() => userStore.balance) const portfolioBalance = computed(() => userStore.balance)
const profitLoss = ref('0.00') const profitLoss = ref('0.00')
const plRange = ref('ALL') const plRange = ref('ALL')
const plTimeRanges = ['1D', '1W', '1M', 'ALL'] const plTimeRanges = computed(() => [
{ label: t('wallet.pl1D'), value: '1D' },
{ label: t('wallet.pl1W'), value: '1W' },
{ label: t('wallet.pl1M'), value: '1M' },
{ label: t('wallet.plAll'), value: 'ALL' },
])
const activeTab = ref<'positions' | 'orders' | 'history'>('positions') const activeTab = ref<'positions' | 'orders' | 'history'>('positions')
const search = ref('') const search = ref('')
const depositDialogOpen = ref(false) const depositDialogOpen = ref(false)