Compare commits
No commits in common. "e41d6b5b0d1f5baf8e1e9a50c444fa3f7e4e4998" and "84ed7de42c31b9d1cc1d8b678bd00ea106f897d7" have entirely different histories.
e41d6b5b0d
...
84ed7de42c
71
package-lock.json
generated
71
package-lock.json
generated
@ -15,7 +15,6 @@
|
|||||||
"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"
|
||||||
@ -1397,50 +1396,6 @@
|
|||||||
"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",
|
||||||
@ -8764,32 +8719,6 @@
|
|||||||
"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",
|
||||||
|
|||||||
@ -25,7 +25,6 @@
|
|||||||
"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"
|
||||||
|
|||||||
30
src/App.vue
30
src/App.vue
@ -1,14 +1,9 @@
|
|||||||
<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'
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { t } = useI18n()
|
|
||||||
const localeStore = useLocaleStore()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
@ -45,36 +40,20 @@ watch(
|
|||||||
icon
|
icon
|
||||||
variant="text"
|
variant="text"
|
||||||
class="back-btn"
|
class="back-btn"
|
||||||
:aria-label="t('common.back')"
|
aria-label="返回"
|
||||||
@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' }"
|
||||||
>
|
>
|
||||||
{{ t('common.login') }}
|
Login
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<v-btn
|
<v-btn
|
||||||
@ -98,10 +77,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 || t('common.user')"
|
:title="userStore.user?.nickName || userStore.user?.userName || 'User'"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<v-list-item :title="t('common.logout')" @click="userStore.logout()" />
|
<v-list-item title="退出登录" @click="userStore.logout()" />
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</template>
|
</template>
|
||||||
@ -113,7 +92,6 @@ watch(
|
|||||||
</keep-alive>
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
</v-main>
|
</v-main>
|
||||||
<Toast />
|
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -9,9 +9,9 @@
|
|||||||
>
|
>
|
||||||
<v-card rounded="lg">
|
<v-card rounded="lg">
|
||||||
<div class="deposit-header">
|
<div class="deposit-header">
|
||||||
<h2 class="deposit-title">{{ t('deposit.title') }}</h2>
|
<h2 class="deposit-title">Deposit</h2>
|
||||||
<p class="deposit-balance">{{ t('deposit.polymarketBalance') }} ${{ balance }}</p>
|
<p class="deposit-balance">Polymarket Balance: ${{ balance }}</p>
|
||||||
<v-btn icon variant="text" class="close-btn" :aria-label="t('deposit.close')" @click="close">
|
<v-btn icon variant="text" class="close-btn" aria-label="关闭" @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">{{ t('deposit.transferCrypto') }}</div>
|
<div class="method-name">Transfer Crypto</div>
|
||||||
<div class="method-desc">{{ t('deposit.noLimit') }} • {{ t('deposit.instant') }}</div>
|
<div class="method-desc">No limit • 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">{{ t('deposit.connectExchange') }}</div>
|
<div class="method-name">Connect Exchange</div>
|
||||||
<div class="method-desc">{{ t('deposit.noLimit') }} • {{ t('deposit.twoMin') }}</div>
|
<div class="method-desc">No limit • 2 min</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">{{ t('deposit.transferCrypto') }}</span>
|
<span class="step-title">Transfer Crypto</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="t('deposit.network')"
|
label="Network"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
hide-details
|
hide-details
|
||||||
class="network-select"
|
class="network-select"
|
||||||
/>
|
/>
|
||||||
<p class="support-tip">{{ t('deposit.supportedTip') }}</p>
|
<p class="support-tip">Supported: USDC, ETH</p>
|
||||||
<div class="address-box">
|
<div class="address-box">
|
||||||
<label class="address-label">{{ t('deposit.depositAddress') }}</label>
|
<label class="address-label">Deposit address</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 ? t('deposit.copied') : t('deposit.copy') }}
|
{{ copied ? 'Copied' : 'Copy' }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="qr-wrap">
|
<div class="qr-wrap">
|
||||||
@ -92,11 +92,12 @@
|
|||||||
<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">{{ t('deposit.connectExchange') }}</span>
|
<span class="step-title">Connect Exchange</span>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="!exchangeConnected">
|
<template v-if="!exchangeConnected">
|
||||||
<p class="connect-desc">
|
<p class="connect-desc">
|
||||||
{{ t('deposit.connectDesc') }}
|
Connect your wallet to deposit. Send USDC or ETH to your deposit address after
|
||||||
|
connecting.
|
||||||
</p>
|
</p>
|
||||||
<div class="wallet-buttons">
|
<div class="wallet-buttons">
|
||||||
<v-btn
|
<v-btn
|
||||||
@ -108,29 +109,29 @@
|
|||||||
@click="connectMetaMask"
|
@click="connectMetaMask"
|
||||||
>
|
>
|
||||||
<v-icon start>mdi-wallet</v-icon>
|
<v-icon start>mdi-wallet</v-icon>
|
||||||
{{ t('deposit.metaMask') }}
|
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>
|
||||||
{{ t('deposit.coinbaseComingSoon') }}
|
Coinbase Wallet (Coming soon)
|
||||||
</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>
|
||||||
{{ t('deposit.walletConnectComingSoon') }}
|
WalletConnect (Coming soon)
|
||||||
</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>
|
||||||
{{ t('deposit.connectedTip') }}
|
Connected. Send USDC or ETH to the address below.
|
||||||
</p>
|
</p>
|
||||||
<div class="address-box">
|
<div class="address-box">
|
||||||
<label class="address-label">{{ t('deposit.depositAddress') }}</label>
|
<label class="address-label">Deposit address</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 ? t('deposit.copied') : t('deposit.copy') }}
|
{{ copied ? 'Copied' : 'Copy' }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="qr-wrap">
|
<div class="qr-wrap">
|
||||||
@ -146,9 +147,7 @@
|
|||||||
|
|
||||||
<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
|
||||||
@ -205,7 +204,7 @@ async function copyAddress() {
|
|||||||
|
|
||||||
async function connectMetaMask() {
|
async function connectMetaMask() {
|
||||||
if (!window.ethereum) {
|
if (!window.ethereum) {
|
||||||
alert(t('deposit.installMetaMask'))
|
alert('Please install MetaMask or another Web3 wallet.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connecting.value = true
|
connecting.value = true
|
||||||
|
|||||||
@ -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">{{ t('common.chance') }}</span>
|
<span class="chance-label">chance</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -164,11 +164,9 @@
|
|||||||
|
|
||||||
<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: [
|
||||||
|
|||||||
@ -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">{{ t('trade.orderBook') }}</h3>
|
<h3 class="order-book-title">Order Book</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>
|
||||||
{{ t('activity.live') }}
|
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">{{ t('trade.buyLabel', { label: props.yesLabel }) }}</v-tab>
|
<v-tab value="up" class="trade-tab">Trade Up</v-tab>
|
||||||
<v-tab value="down" class="trade-tab">{{ t('trade.buyLabel', { label: props.noLabel }) }}</v-tab>
|
<v-tab value="down" class="trade-tab">Trade Down</v-tab>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,11 +80,8 @@
|
|||||||
|
|
||||||
<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
|
||||||
@ -98,10 +95,6 @@ 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: () => [],
|
||||||
@ -110,8 +103,6 @@ const props = withDefaults(
|
|||||||
spread: undefined,
|
spread: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
connected: false,
|
connected: false,
|
||||||
yesLabel: 'Yes',
|
|
||||||
noLabel: 'No',
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,143 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<div class="toast-container">
|
|
||||||
<TransitionGroup name="toast" tag="div" class="toast-stack">
|
|
||||||
<div
|
|
||||||
v-for="item in toastStore.displaying"
|
|
||||||
:key="item.id"
|
|
||||||
:class="['toast-item', `toast-item--${item.type}`]"
|
|
||||||
role="status"
|
|
||||||
aria-live="polite"
|
|
||||||
>
|
|
||||||
<span class="toast-message">
|
|
||||||
{{ item.count > 1 ? `${item.message} (×${item.count})` : item.message }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</TransitionGroup>
|
|
||||||
</div>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, onUnmounted } from 'vue'
|
|
||||||
import { useToastStore } from '@/stores/toast'
|
|
||||||
import type { ToastItem } from '@/stores/toast'
|
|
||||||
|
|
||||||
const toastStore = useToastStore()
|
|
||||||
const TIMEOUT_MS = 3000
|
|
||||||
const timers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
||||||
|
|
||||||
function scheduleRemove(item: ToastItem) {
|
|
||||||
const t = setTimeout(() => {
|
|
||||||
toastStore.remove(item.id)
|
|
||||||
timers.delete(item.id)
|
|
||||||
}, TIMEOUT_MS)
|
|
||||||
timers.set(item.id, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncTimers() {
|
|
||||||
for (const item of toastStore.displaying) {
|
|
||||||
const existing = timers.get(item.id)
|
|
||||||
if (existing) clearTimeout(existing)
|
|
||||||
scheduleRemove(item)
|
|
||||||
}
|
|
||||||
for (const [id] of timers) {
|
|
||||||
if (!toastStore.displaying.some((d) => d.id === id)) {
|
|
||||||
clearTimeout(timers.get(id)!)
|
|
||||||
timers.delete(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
syncTimers()
|
|
||||||
const stop = toastStore.$subscribe(syncTimers)
|
|
||||||
onUnmounted(() => {
|
|
||||||
stop()
|
|
||||||
timers.forEach((t) => clearTimeout(t))
|
|
||||||
timers.clear()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.toast-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 10000;
|
|
||||||
pointer-events: none;
|
|
||||||
/* 避开顶部导航栏(约 64px)+ 安全区域 */
|
|
||||||
padding: max(72px, calc(64px + env(safe-area-inset-top))) 16px 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-stack {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
width: min(520px, calc(100vw - 32px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-item {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 28px;
|
|
||||||
border-radius: 8px;
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-item--success {
|
|
||||||
background: rgba(76, 175, 80, 0.65);
|
|
||||||
color: rgba(255, 255, 255, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-item--error {
|
|
||||||
background: rgba(244, 67, 54, 0.65);
|
|
||||||
color: rgba(255, 255, 255, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-item--warning {
|
|
||||||
background: rgba(255, 152, 0, 0.65);
|
|
||||||
color: rgba(33, 33, 33, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-item--info {
|
|
||||||
background: rgba(33, 150, 243, 0.65);
|
|
||||||
color: rgba(255, 255, 255, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-message {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Transition */
|
|
||||||
.toast-enter-active,
|
|
||||||
.toast-leave-active {
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-12px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-8px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 下方 Toast 在上方消失时平缓上移 */
|
|
||||||
.toast-move {
|
|
||||||
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -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">{{ t('trade.buy') }}</v-tab>
|
<v-tab value="buy" class="minimal-tab">Buy</v-tab>
|
||||||
<v-tab value="sell" class="minimal-tab">{{ t('trade.sell') }}</v-tab>
|
<v-tab value="sell" class="minimal-tab">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>
|
||||||
{{ limitTypeDisplay }}
|
{{ limitType }}
|
||||||
<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>{{ t('trade.market') }}</v-list-item-title>
|
<v-list-item-title>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>{{ t('trade.limit') }}</v-list-item-title>
|
<v-list-item-title>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>{{ t('trade.merge') }}</v-list-item-title>
|
<v-list-item-title>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>{{ t('trade.split') }}</v-list-item-title>
|
<v-list-item-title>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">{{ t('trade.amount') }}</span>
|
<span class="label amount-label">Amount</span>
|
||||||
<span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
<span class="balance-label">Balance ${{ 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">{{ t('trade.max') }}</v-btn>
|
<v-btn class="amount-btn" @click="setMaxAmount">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">{{ t('trade.shares') }}</span>
|
<span class="label">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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">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">{{ t('trade.total') }}</span>
|
<span class="label">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">{{ t('trade.toWin') }}</span>
|
<span class="label">To win</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">{{ t('trade.youllReceive') }}</span>
|
<span class="label">You'll receive</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">
|
||||||
{{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
|
Avg. Price {{ 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,不足显示 {{ t('trade.deposit') }};Sell 只显示 Sell Yes/No -->
|
<!-- Action Button: Buy 余额足够显示 Buy Yes/No,不足显示 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"
|
||||||
>
|
>
|
||||||
{{ t('trade.deposit') }}
|
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">{{ t('trade.amount') }}</span>
|
<span class="label amount-label">Amount</span>
|
||||||
<span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
<span class="balance-label">Balance ${{ 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">{{ t('trade.max') }}</v-btn>
|
<v-btn class="amount-btn" @click="setMaxAmount">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">{{ t('trade.toWin') }}</span>
|
<span class="label">To win</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 余额不足时显示 {{ t('trade.deposit') }} -->
|
<!-- Buy 余额不足时显示 Deposit -->
|
||||||
<v-btn class="deposit-btn" @click="deposit">{{ t('trade.deposit') }}</v-btn>
|
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Sell: Shares + To receive + Avg. Price,只显示 Sell Yes/No(无 {{ t('trade.deposit') }}) -->
|
<!-- Sell: Shares + To receive + Avg. Price,只显示 Sell Yes/No(无 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">{{ t('trade.shares') }}</span>
|
<span class="label">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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">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">{{ t('trade.youllReceive') }}</span>
|
<span class="label">You'll receive</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">
|
||||||
{{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
|
Avg. Price {{ 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">{{ t('trade.limitPrice') }}</span>
|
<span class="label">Limit Price</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">{{ t('trade.shares') }}</span>
|
<span class="label">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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">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">{{ t('trade.setExpiration') }}</span>
|
<span class="label">Set Expiration</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,8 +362,6 @@
|
|||||||
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"
|
||||||
@ -375,11 +373,11 @@
|
|||||||
<!-- Buy模式 -->
|
<!-- Buy模式 -->
|
||||||
<template v-if="activeTab === 'buy'">
|
<template v-if="activeTab === 'buy'">
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.total') }}</span>
|
<span class="label">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">{{ t('trade.toWin') }}</span>
|
<span class="label">To win</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 }}
|
||||||
@ -389,7 +387,7 @@
|
|||||||
<!-- Sell模式 -->
|
<!-- Sell模式 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.youllReceive') }}</span>
|
<span class="label">You'll receive</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 }}
|
||||||
@ -416,29 +414,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">{{ t('trade.buy') }}</v-tab>
|
<v-tab value="buy" class="minimal-tab">Buy</v-tab>
|
||||||
<v-tab value="sell" class="minimal-tab">{{ t('trade.sell') }}</v-tab>
|
<v-tab value="sell" class="minimal-tab">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>
|
||||||
{{ limitTypeDisplay }}
|
{{ limitType }}
|
||||||
<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>{{ t('trade.market') }}</v-list-item-title></v-list-item
|
><v-list-item-title>Market</v-list-item-title></v-list-item
|
||||||
>
|
>
|
||||||
<v-list-item @click="limitType = 'Limit'"
|
<v-list-item @click="limitType = 'Limit'"
|
||||||
><v-list-item-title>{{ t('trade.limit') }}</v-list-item-title></v-list-item
|
><v-list-item-title>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>{{ t('trade.merge') }}</v-list-item-title></v-list-item
|
><v-list-item-title>Merge</v-list-item-title></v-list-item
|
||||||
>
|
>
|
||||||
<v-list-item @click="openSplitDialog"
|
<v-list-item @click="openSplitDialog"
|
||||||
><v-list-item-title>{{ t('trade.split') }}</v-list-item-title></v-list-item
|
><v-list-item-title>Split</v-list-item-title></v-list-item
|
||||||
>
|
>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@ -466,8 +464,8 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="amount-header">
|
<div class="amount-header">
|
||||||
<div>
|
<div>
|
||||||
<span class="label amount-label">{{ t('trade.amount') }}</span>
|
<span class="label amount-label">Amount</span>
|
||||||
<span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -475,7 +473,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">{{ t('trade.max') }}</v-btn>
|
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -483,7 +481,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">{{ t('trade.shares') }}</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:model-value="shares"
|
:model-value="shares"
|
||||||
@ -501,17 +499,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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">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">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
|
<span class="label">Total</span><span class="total-value">${{ totalPrice }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.toWin') }}</span
|
<span class="label">To win</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
|
||||||
>
|
>
|
||||||
@ -519,7 +517,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
<span class="label">You'll receive</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
|
||||||
@ -528,7 +526,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="total-row avg-price-row">
|
<div class="total-row avg-price-row">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
{{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
|
Avg. Price {{ 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>
|
||||||
@ -540,7 +538,7 @@
|
|||||||
class="deposit-btn"
|
class="deposit-btn"
|
||||||
@click="deposit"
|
@click="deposit"
|
||||||
>
|
>
|
||||||
{{ t('trade.deposit') }}
|
Deposit
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-else
|
v-else
|
||||||
@ -573,8 +571,8 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="amount-header">
|
<div class="amount-header">
|
||||||
<div>
|
<div>
|
||||||
<span class="label amount-label">{{ t('trade.amount') }}</span
|
<span class="label amount-label">Amount</span
|
||||||
><span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
><span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -582,23 +580,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">{{ t('trade.max') }}</v-btn>
|
<v-btn class="amount-btn" @click="setMaxAmount">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">{{ t('trade.toWin') }}</span>
|
<span class="label">To win</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">{{ t('trade.deposit') }}</v-btn>
|
<v-btn class="deposit-btn" @click="deposit">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">{{ t('trade.shares') }}</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:model-value="shares"
|
:model-value="shares"
|
||||||
@ -616,12 +614,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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">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">{{ t('trade.youllReceive') }}</span>
|
<span class="label">You'll receive</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 }}
|
||||||
@ -629,7 +627,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="total-row avg-price-row">
|
<div class="total-row avg-price-row">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
{{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
|
Avg. Price {{ 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>
|
||||||
@ -664,7 +662,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">{{ t('trade.limitPrice') }}</span>
|
<span class="label">Limit Price</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
|
||||||
@ -691,7 +689,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">{{ t('trade.shares') }}</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:model-value="shares"
|
:model-value="shares"
|
||||||
@ -715,12 +713,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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">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">{{ t('trade.setExpiration') }}</span>
|
<span class="label">Set expiration</span>
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="expirationEnabled"
|
v-model="expirationEnabled"
|
||||||
class="expiration-switch"
|
class="expiration-switch"
|
||||||
@ -732,8 +730,6 @@
|
|||||||
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"
|
||||||
@ -742,10 +738,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">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
|
<span class="label">Total</span><span class="total-value">${{ totalPrice }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.toWin') }}</span
|
<span class="label">To win</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
|
||||||
>
|
>
|
||||||
@ -753,7 +749,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
<span class="label">You'll receive</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
|
||||||
>
|
>
|
||||||
@ -785,7 +781,7 @@
|
|||||||
block
|
block
|
||||||
@click="openSheet('yes')"
|
@click="openSheet('yes')"
|
||||||
>
|
>
|
||||||
{{ t('trade.buyLabel', { label: yesLabel }) }} {{ yesPriceCents }}¢
|
Buy {{ yesLabel }} {{ yesPriceCents }}¢
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
class="mobile-bar-btn mobile-bar-no"
|
class="mobile-bar-btn mobile-bar-no"
|
||||||
@ -795,7 +791,7 @@
|
|||||||
block
|
block
|
||||||
@click="openSheet('no')"
|
@click="openSheet('no')"
|
||||||
>
|
>
|
||||||
{{ t('trade.buyLabel', { label: noLabel }) }} {{ noPriceCents }}¢
|
Buy {{ noLabel }} {{ noPriceCents }}¢
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -804,8 +800,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">{{ t('trade.buy') }}</v-tab>
|
<v-tab value="buy" class="minimal-tab">Buy</v-tab>
|
||||||
<v-tab value="sell" class="minimal-tab">{{ t('trade.sell') }}</v-tab>
|
<v-tab value="sell" class="minimal-tab">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 }">
|
||||||
@ -823,10 +819,10 @@
|
|||||||
>
|
>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-list-item @click="openMergeDialog"
|
<v-list-item @click="openMergeDialog"
|
||||||
><v-list-item-title>{{ t('trade.merge') }}</v-list-item-title></v-list-item
|
><v-list-item-title>Merge</v-list-item-title></v-list-item
|
||||||
>
|
>
|
||||||
<v-list-item @click="openSplitDialog"
|
<v-list-item @click="openSplitDialog"
|
||||||
><v-list-item-title>{{ t('trade.split') }}</v-list-item-title></v-list-item
|
><v-list-item-title>Split</v-list-item-title></v-list-item
|
||||||
>
|
>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@ -854,8 +850,8 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="amount-header">
|
<div class="amount-header">
|
||||||
<div>
|
<div>
|
||||||
<span class="label amount-label">{{ t('trade.amount') }}</span>
|
<span class="label amount-label">Amount</span>
|
||||||
<span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -863,7 +859,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">{{ t('trade.max') }}</v-btn>
|
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -871,7 +867,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">{{ t('trade.shares') }}</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:model-value="shares"
|
:model-value="shares"
|
||||||
@ -889,7 +885,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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -900,7 +896,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">{{ t('trade.toWin') }}</span
|
<span class="label">To win</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
|
||||||
>
|
>
|
||||||
@ -908,7 +904,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
<span class="label">You'll receive</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
|
||||||
@ -917,7 +913,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="total-row avg-price-row">
|
<div class="total-row avg-price-row">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
{{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
|
Avg. Price {{ 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>
|
||||||
@ -929,7 +925,7 @@
|
|||||||
class="deposit-btn"
|
class="deposit-btn"
|
||||||
@click="deposit"
|
@click="deposit"
|
||||||
>
|
>
|
||||||
{{ t('trade.deposit') }}
|
Deposit
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-else
|
v-else
|
||||||
@ -962,8 +958,8 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="amount-header">
|
<div class="amount-header">
|
||||||
<div>
|
<div>
|
||||||
<span class="label amount-label">{{ t('trade.amount') }}</span
|
<span class="label amount-label">Amount</span
|
||||||
><span class="balance-label">{{ t('trade.balanceLabel') }} ${{ balance.toFixed(2) }}</span>
|
><span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -971,23 +967,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">{{ t('trade.max') }}</v-btn>
|
<v-btn class="amount-btn" @click="setMaxAmount">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">{{ t('trade.toWin') }}</span>
|
<span class="label">To win</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">{{ t('trade.deposit') }}</v-btn>
|
<v-btn class="deposit-btn" @click="deposit">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">{{ t('trade.shares') }}</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:model-value="shares"
|
:model-value="shares"
|
||||||
@ -1005,12 +1001,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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">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">{{ t('trade.youllReceive') }}</span>
|
<span class="label">You'll receive</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 }}
|
||||||
@ -1018,7 +1014,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="total-row avg-price-row">
|
<div class="total-row avg-price-row">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
{{ t('trade.avgPrice') }} {{ avgPriceCents }}¢
|
Avg. Price {{ 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>
|
||||||
@ -1053,7 +1049,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">{{ t('trade.limitPrice') }}</span>
|
<span class="label">Limit Price</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
|
||||||
@ -1079,7 +1075,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">{{ t('trade.shares') }}</span>
|
<span class="label">Shares</span>
|
||||||
<div class="shares-input">
|
<div class="shares-input">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:model-value="shares"
|
:model-value="shares"
|
||||||
@ -1103,12 +1099,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)">{{ t('trade.max') }}</v-btn>
|
<v-btn class="share-btn" @click="setSharesPercentage(100)">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">{{ t('trade.setExpiration') }}</span>
|
<span class="label">Set expiration</span>
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="expirationEnabled"
|
v-model="expirationEnabled"
|
||||||
class="expiration-switch"
|
class="expiration-switch"
|
||||||
@ -1120,8 +1116,6 @@
|
|||||||
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"
|
||||||
@ -1130,10 +1124,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">{{ t('trade.total') }}</span><span class="total-value">${{ totalPrice }}</span>
|
<span class="label">Total</span><span class="total-value">${{ totalPrice }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.toWin') }}</span
|
<span class="label">To win</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
|
||||||
>
|
>
|
||||||
@ -1141,7 +1135,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<span class="label">{{ t('trade.youllReceive') }}</span
|
<span class="label">You'll receive</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
|
||||||
@ -1290,14 +1284,12 @@
|
|||||||
|
|
||||||
<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 个价格档位(0–1 区间),规则:1–9/10–90/100–9900/9910–9990/9991–9999 */
|
/** 限价单允许的 135 个价格档位(0–1 区间),规则:1–9/10–90/100–9900/9910–9990/9991–9999 */
|
||||||
@ -1451,10 +1443,7 @@ 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 EXPIRATION_VALUES = ['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d'] as const
|
const expirationOptions = ref(['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d']) // 过期时间选项
|
||||||
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')
|
||||||
@ -1472,7 +1461,6 @@ const orderError = ref('')
|
|||||||
// Emits
|
// Emits
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
optionChange: [option: 'yes' | 'no']
|
optionChange: [option: 'yes' | 'no']
|
||||||
orderSuccess: []
|
|
||||||
submit: [
|
submit: [
|
||||||
payload: {
|
payload: {
|
||||||
side: 'buy' | 'sell'
|
side: 'buy' | 'sell'
|
||||||
@ -1514,13 +1502,11 @@ 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
|
||||||
return tab === 'buy' ? t('trade.buyLabel', { label }) : t('trade.sellLabel', { label })
|
const tabCapitalized = tab.charAt(0).toUpperCase() + tab.slice(1)
|
||||||
|
return `${tabCapitalized} ${label}`
|
||||||
})
|
})
|
||||||
|
|
||||||
function applyInitialOption(option: 'yes' | 'no') {
|
function applyInitialOption(option: 'yes' | 'no') {
|
||||||
@ -1682,9 +1668,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
|
||||||
@ -1724,12 +1710,12 @@ async function submitOrder() {
|
|||||||
emit('submit', payload)
|
emit('submit', payload)
|
||||||
|
|
||||||
if (!tokenId) {
|
if (!tokenId) {
|
||||||
orderError.value = t('trade.pleaseSelectMarket')
|
orderError.value = '请先选择市场(需包含 clobTokenIds)'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const headers = userStore.getAuthHeaders()
|
const headers = userStore.getAuthHeaders()
|
||||||
if (!headers) {
|
if (!headers) {
|
||||||
orderError.value = t('trade.pleaseLogin')
|
orderError.value = '请先登录'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const uid = userStore?.user?.ID ?? 0
|
const uid = userStore?.user?.ID ?? 0
|
||||||
@ -1742,7 +1728,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 = t('trade.userError')
|
orderError.value = '用户信息异常'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1778,12 +1764,11 @@ async function submitOrder() {
|
|||||||
)
|
)
|
||||||
if (res.code === 0 || res.code === 200) {
|
if (res.code === 0 || res.code === 200) {
|
||||||
userStore.fetchUsdcBalance()
|
userStore.fetchUsdcBalance()
|
||||||
emit('orderSuccess')
|
|
||||||
} else {
|
} else {
|
||||||
orderError.value = res.msg || t('trade.orderFailed')
|
orderError.value = res.msg || '下单失败'
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
orderError.value = e instanceof Error ? e.message : t('error.requestFailed')
|
orderError.value = e instanceof Error ? e.message : 'Request failed'
|
||||||
} finally {
|
} finally {
|
||||||
orderLoading.value = false
|
orderLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,9 @@
|
|||||||
>
|
>
|
||||||
<v-card rounded="lg">
|
<v-card rounded="lg">
|
||||||
<div class="withdraw-header">
|
<div class="withdraw-header">
|
||||||
<h2 class="withdraw-title">{{ t('withdraw.title') }}</h2>
|
<h2 class="withdraw-title">Withdraw</h2>
|
||||||
<p class="withdraw-balance">{{ t('withdraw.polymarketBalance') }} ${{ balance }}</p>
|
<p class="withdraw-balance">Polymarket Balance: ${{ balance }}</p>
|
||||||
<v-btn icon variant="text" class="close-btn" :aria-label="t('withdraw.close')" @click="close">
|
<v-btn icon variant="text" class="close-btn" aria-label="关闭" @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="t('withdraw.amountUsd')"
|
label="Amount (USD)"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
hide-details
|
hide-details
|
||||||
class="amount-field"
|
class="amount-field"
|
||||||
:placeholder="t('withdraw.amountPlaceholder')"
|
placeholder="0.00"
|
||||||
@keypress="allowDecimal"
|
@keypress="allowDecimal"
|
||||||
>
|
>
|
||||||
<template #append-inner>
|
<template #append-inner>
|
||||||
<v-btn variant="text" size="small" class="max-btn" @click="setMax">{{ t('withdraw.max') }}</v-btn>
|
<v-btn variant="text" size="small" class="max-btn" @click="setMax">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="t('withdraw.network')"
|
label="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">{{ t('withdraw.withdrawTo') }}</label>
|
<label class="section-label">Withdraw to</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="t('withdraw.connectedWallet')" />
|
<v-radio value="wallet" label="Connected wallet" />
|
||||||
<v-radio value="address" :label="t('withdraw.customAddress')" />
|
<v-radio value="address" label="Custom address" />
|
||||||
</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>
|
||||||
{{ t('withdraw.connectWallet') }}
|
Connect Wallet
|
||||||
</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="t('withdraw.walletAddress')"
|
label="Wallet address"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
hide-details
|
hide-details
|
||||||
:placeholder="t('withdraw.addressPlaceholder')"
|
placeholder="0x..."
|
||||||
class="address-field"
|
class="address-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -95,7 +95,7 @@
|
|||||||
:disabled="!canSubmit"
|
:disabled="!canSubmit"
|
||||||
@click="submitWithdraw"
|
@click="submitWithdraw"
|
||||||
>
|
>
|
||||||
{{ t('withdraw.title') }}
|
Withdraw
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
@ -104,9 +104,7 @@
|
|||||||
|
|
||||||
<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
|
||||||
@ -136,8 +134,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 t('withdraw.amountMustBePositive')
|
if (amountNum.value <= 0) return 'Amount must be greater than 0'
|
||||||
if (amountNum.value > balanceNum.value) return t('withdraw.insufficientBalance')
|
if (amountNum.value > balanceNum.value) return 'Insufficient balance'
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -175,7 +173,7 @@ function allowDecimal(e: KeyboardEvent) {
|
|||||||
|
|
||||||
async function connectWallet() {
|
async function connectWallet() {
|
||||||
if (!window.ethereum) {
|
if (!window.ethereum) {
|
||||||
alert(t('deposit.installMetaMask'))
|
alert('Please install MetaMask or another Web3 wallet.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connecting.value = true
|
connecting.value = true
|
||||||
|
|||||||
@ -1,172 +0,0 @@
|
|||||||
{
|
|
||||||
"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": "한국어"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
{
|
|
||||||
"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": "한국어"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
{
|
|
||||||
"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": "한국어"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
{
|
|
||||||
"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": "한국어"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,13 +4,11 @@ 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')
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
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 {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
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 }
|
|
||||||
})
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import { ref } from 'vue'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export type ToastType = 'success' | 'error' | 'info' | 'warning'
|
|
||||||
|
|
||||||
export interface ToastItem {
|
|
||||||
id: string
|
|
||||||
message: string
|
|
||||||
type: ToastType
|
|
||||||
count: number
|
|
||||||
addedAt: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEDUP_MS = 3000
|
|
||||||
const MAX_VISIBLE = 3
|
|
||||||
let idCounter = 0
|
|
||||||
|
|
||||||
function genId() {
|
|
||||||
return `toast-${++idCounter}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeMsg(msg: string) {
|
|
||||||
return msg.trim().replace(/\s+/g, ' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useToastStore = defineStore('toast', () => {
|
|
||||||
/** 当前正在显示的 Toast(最多 MAX_VISIBLE 条) */
|
|
||||||
const displaying = ref<ToastItem[]>([])
|
|
||||||
/** 待显示的队列 */
|
|
||||||
const queue = ref<ToastItem[]>([])
|
|
||||||
|
|
||||||
/** 每种 message+type 最后一次出现的时间(添加或合并时更新) */
|
|
||||||
const lastSeenAt = ref<Record<string, number>>({})
|
|
||||||
|
|
||||||
function mergeKey(msg: string, type: ToastType) {
|
|
||||||
return `${type}:${normalizeMsg(msg)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 尝试合并:同 message+type 且在 DEDUP_MS 内(以最后一次为准)合并为 count+1 */
|
|
||||||
function tryMerge(item: ToastItem): boolean {
|
|
||||||
const now = Date.now()
|
|
||||||
const key = mergeKey(item.message, item.type)
|
|
||||||
const last = lastSeenAt.value[key] ?? 0
|
|
||||||
if (now - last >= DEDUP_MS) return false
|
|
||||||
|
|
||||||
const msg = normalizeMsg(item.message)
|
|
||||||
const inDisplayingIdx = displaying.value.findLastIndex(
|
|
||||||
(d) => normalizeMsg(d.message) === msg && d.type === item.type
|
|
||||||
)
|
|
||||||
if (inDisplayingIdx >= 0) {
|
|
||||||
displaying.value = displaying.value.map((x, i) =>
|
|
||||||
i === inDisplayingIdx
|
|
||||||
? { ...x, count: x.count + 1, addedAt: now }
|
|
||||||
: x
|
|
||||||
)
|
|
||||||
lastSeenAt.value[key] = now
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
const inQueueReversed = [...queue.value].reverse()
|
|
||||||
const inQueueIdx = inQueueReversed.findIndex(
|
|
||||||
(q) => normalizeMsg(q.message) === msg && q.type === item.type
|
|
||||||
)
|
|
||||||
if (inQueueIdx >= 0) {
|
|
||||||
const origIdx = queue.value.length - 1 - inQueueIdx
|
|
||||||
queue.value = queue.value.map((x, i) =>
|
|
||||||
i === origIdx ? { ...x, count: x.count + 1, addedAt: now } : x
|
|
||||||
)
|
|
||||||
lastSeenAt.value[key] = now
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function show(msg: string, toastType: ToastType = 'success') {
|
|
||||||
const item: ToastItem = {
|
|
||||||
id: genId(),
|
|
||||||
message: msg.trim(),
|
|
||||||
type: toastType,
|
|
||||||
count: 1,
|
|
||||||
addedAt: Date.now(),
|
|
||||||
}
|
|
||||||
if (tryMerge(item)) return
|
|
||||||
|
|
||||||
const key = mergeKey(item.message, item.type)
|
|
||||||
lastSeenAt.value[key] = item.addedAt
|
|
||||||
|
|
||||||
if (displaying.value.length < MAX_VISIBLE) {
|
|
||||||
displaying.value = [...displaying.value, item]
|
|
||||||
} else {
|
|
||||||
queue.value = [...queue.value, item]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 某条 Toast 关闭后调用 */
|
|
||||||
function remove(id: string) {
|
|
||||||
displaying.value = displaying.value.filter((d) => d.id !== id)
|
|
||||||
if (queue.value.length > 0) {
|
|
||||||
const next = queue.value[0]
|
|
||||||
queue.value = queue.value.slice(1)
|
|
||||||
displaying.value = [...displaying.value, next]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { displaying, queue, show, remove }
|
|
||||||
})
|
|
||||||
@ -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>{{ t('common.loading') }}</p>
|
<p>加载中...</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>{{ t('trade.merge') }}</v-list-item-title>
|
<v-list-item-title>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>{{ t('trade.split') }}</v-list-item-title>
|
<v-list-item-title>Split</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@ -174,7 +174,6 @@
|
|||||||
:initial-option="tradeInitialOption"
|
:initial-option="tradeInitialOption"
|
||||||
embedded-in-sheet
|
embedded-in-sheet
|
||||||
@submit="onTradeSubmit"
|
@submit="onTradeSubmit"
|
||||||
@order-success="onOrderSuccess"
|
|
||||||
/>
|
/>
|
||||||
</v-bottom-sheet>
|
</v-bottom-sheet>
|
||||||
</template>
|
</template>
|
||||||
@ -198,12 +197,9 @@ 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'
|
|
||||||
|
|
||||||
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()
|
||||||
@ -563,12 +559,6 @@ function onTradeSubmit(payload: {
|
|||||||
console.log('Trade submit', payload)
|
console.log('Trade submit', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastStore = useToastStore()
|
|
||||||
function onOrderSuccess() {
|
|
||||||
tradeSheetOpen.value = false
|
|
||||||
toastStore.show(t('toast.orderSuccess'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function marketChance(market: PmEventMarketItem): number {
|
function marketChance(market: PmEventMarketItem): number {
|
||||||
const raw = market?.outcomePrices?.[0]
|
const raw = market?.outcomePrices?.[0]
|
||||||
if (raw == null) return 0
|
if (raw == null) return 0
|
||||||
@ -621,7 +611,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 = t('error.invalidId')
|
detailError.value = '无效的 ID 或 slug'
|
||||||
eventDetail.value = null
|
eventDetail.value = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -648,7 +638,7 @@ async function loadEventDetail() {
|
|||||||
eventDetail.value = fallback
|
eventDetail.value = fallback
|
||||||
detailError.value = null
|
detailError.value = null
|
||||||
} else {
|
} else {
|
||||||
detailError.value = res.msg || t('error.loadFailed')
|
detailError.value = res.msg || '加载失败'
|
||||||
eventDetail.value = null
|
eventDetail.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -658,7 +648,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 : t('error.loadFailed')
|
detailError.value = e instanceof Error ? e.message : '加载失败'
|
||||||
eventDetail.value = null
|
eventDetail.value = null
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
variant="text"
|
variant="text"
|
||||||
size="small"
|
size="small"
|
||||||
class="home-category-action-btn"
|
class="home-category-action-btn"
|
||||||
:aria-label="t('common.search')"
|
aria-label="搜索"
|
||||||
@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="t('common.filter')"
|
aria-label="筛选"
|
||||||
>
|
>
|
||||||
<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="t('home.searchPlaceholder')"
|
placeholder="Search"
|
||||||
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="t('common.collapse')"
|
aria-label="收起"
|
||||||
@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">{{ t('home.searchHistory') }}</span>
|
<span class="home-search-history-title">搜索历史</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="t('common.delete')"
|
aria-label="删除"
|
||||||
@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>{{ t('common.loading') }}</span>
|
<span>加载中...</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>
|
||||||
@ -207,7 +207,6 @@
|
|||||||
:key="`trade-${tradeDialogMarket?.id}-${tradeDialogSide}`"
|
:key="`trade-${tradeDialogMarket?.id}-${tradeDialogSide}`"
|
||||||
:market="homeTradeMarketPayload"
|
:market="homeTradeMarketPayload"
|
||||||
:initial-option="tradeDialogSide"
|
:initial-option="tradeDialogSide"
|
||||||
@order-success="onOrderSuccess"
|
|
||||||
/>
|
/>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
<v-bottom-sheet v-else v-model="tradeDialogOpen" content-class="trade-bottom-sheet">
|
<v-bottom-sheet v-else v-model="tradeDialogOpen" content-class="trade-bottom-sheet">
|
||||||
@ -216,7 +215,6 @@
|
|||||||
:market="homeTradeMarketPayload"
|
:market="homeTradeMarketPayload"
|
||||||
:initial-option="tradeDialogSide"
|
:initial-option="tradeDialogSide"
|
||||||
embedded-in-sheet
|
embedded-in-sheet
|
||||||
@order-success="onOrderSuccess"
|
|
||||||
/>
|
/>
|
||||||
</v-bottom-sheet>
|
</v-bottom-sheet>
|
||||||
</v-container>
|
</v-container>
|
||||||
@ -322,12 +320,9 @@ 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'
|
|
||||||
|
|
||||||
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)
|
||||||
@ -525,12 +520,6 @@ function onCardOpenTrade(
|
|||||||
tradeDialogOpen.value = true
|
tradeDialogOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastStore = useToastStore()
|
|
||||||
function onOrderSuccess() {
|
|
||||||
tradeDialogOpen.value = false
|
|
||||||
toastStore.show(t('toast.orderSuccess'))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 传给 TradeComponent 的 market(Home 弹窗/底部栏),供 Split、下单等使用 */
|
/** 传给 TradeComponent 的 market(Home 弹窗/底部栏),供 Split、下单等使用 */
|
||||||
const homeTradeMarketPayload = computed(() => {
|
const homeTradeMarketPayload = computed(() => {
|
||||||
const m = tradeDialogMarket.value
|
const m = tradeDialogMarket.value
|
||||||
|
|||||||
@ -8,14 +8,14 @@
|
|||||||
<!-- 顶部:标题、当前概率、Past / 日期 -->
|
<!-- 顶部:标题、当前概率、Past / 日期 -->
|
||||||
<div class="chart-header">
|
<div class="chart-header">
|
||||||
<h1 class="chart-title">
|
<h1 class="chart-title">
|
||||||
{{ detailLoading && !eventDetail ? t('common.loading') : marketTitle }}
|
{{ detailLoading && !eventDetail ? '加载中...' : 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 }}% {{ t('common.chance') }}</div>
|
<div class="chart-chance">{{ currentChance }}% chance</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图表区域 -->
|
<!-- 图表区域 -->
|
||||||
@ -53,24 +53,22 @@
|
|||||||
: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">{{ t('activity.comments') }}</v-tab>
|
<v-tab value="comments">Comments</v-tab>
|
||||||
<v-tab value="holders">{{ t('activity.topHolders') }}</v-tab>
|
<v-tab value="holders">Top Holders</v-tab>
|
||||||
<v-tab value="activity">{{ t('activity.activity') }}</v-tab>
|
<v-tab value="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">{{ t('activity.noCommentsYet') }}</div>
|
<div class="placeholder-pane">No comments yet.</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">{{ t('activity.topHoldersPlaceholder') }}</div>
|
<div class="placeholder-pane">Top holders will appear here.</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">
|
||||||
@ -80,12 +78,12 @@
|
|||||||
density="compact"
|
density="compact"
|
||||||
hide-details
|
hide-details
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
:label="t('activity.minAmount')"
|
label="Min amount"
|
||||||
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>
|
||||||
{{ t('activity.live') }}
|
Live
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="activity-list">
|
<div class="activity-list">
|
||||||
@ -100,18 +98,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">{{ t(item.action === 'bought' ? 'activity.bought' : 'activity.sold') }}</span>
|
<span class="activity-action">{{ item.action }}</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">{{ t('activity.at') }} {{ item.price }}</span>
|
<span class="activity-price">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="t('activity.viewTransaction')" @click.prevent>
|
<a href="#" class="activity-link" aria-label="View transaction" @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>
|
||||||
@ -169,10 +167,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>{{ t('trade.merge') }}</v-list-item-title>
|
<v-list-item-title>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>{{ t('trade.split') }}</v-list-item-title>
|
<v-list-item-title>Split</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@ -183,7 +181,6 @@
|
|||||||
:market="tradeMarketPayload"
|
:market="tradeMarketPayload"
|
||||||
:initial-option="tradeInitialOptionFromBar"
|
:initial-option="tradeInitialOptionFromBar"
|
||||||
embedded-in-sheet
|
embedded-in-sheet
|
||||||
@order-success="onOrderSuccess"
|
|
||||||
/>
|
/>
|
||||||
</v-bottom-sheet>
|
</v-bottom-sheet>
|
||||||
</template>
|
</template>
|
||||||
@ -194,7 +191,6 @@
|
|||||||
<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'
|
||||||
@ -209,9 +205,6 @@ import {
|
|||||||
} from '../api/event'
|
} from '../api/event'
|
||||||
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'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
import {
|
import {
|
||||||
ClobSdk,
|
ClobSdk,
|
||||||
type PriceSizePolyMsg,
|
type PriceSizePolyMsg,
|
||||||
@ -276,7 +269,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 = t('error.invalidId')
|
detailError.value = '无效的 ID 或 slug'
|
||||||
eventDetail.value = null
|
eventDetail.value = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -297,11 +290,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 || t('error.loadFailed')
|
detailError.value = res.msg || '加载失败'
|
||||||
eventDetail.value = null
|
eventDetail.value = null
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
detailError.value = e instanceof Error ? e.message : t('error.loadFailed')
|
detailError.value = e instanceof Error ? e.message : '加载失败'
|
||||||
eventDetail.value = null
|
eventDetail.value = null
|
||||||
} finally {
|
} finally {
|
||||||
detailLoading.value = false
|
detailLoading.value = false
|
||||||
@ -533,22 +526,16 @@ function openSplitFromBar() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastStore = useToastStore()
|
|
||||||
function onOrderSuccess() {
|
|
||||||
tradeSheetOpen.value = false
|
|
||||||
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 = computed(() => [
|
const minAmountOptions = [
|
||||||
{ title: t('activity.any'), value: '0' },
|
{ title: '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
|
||||||
@ -641,11 +628,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 t('activity.justNow')
|
if (sec < 60) return 'just now'
|
||||||
if (sec < 3600) return t('activity.minutesAgo', { n: Math.floor(sec / 60) })
|
if (sec < 3600) return `${Math.floor(sec / 60)}m ago`
|
||||||
if (sec < 86400) return t('activity.hoursAgo', { n: Math.floor(sec / 3600) })
|
if (sec < 86400) return `${Math.floor(sec / 3600)}h ago`
|
||||||
if (sec < 604800) return t('activity.daysAgo', { n: Math.floor(sec / 86400) })
|
if (sec < 604800) return `${Math.floor(sec / 86400)}d ago`
|
||||||
return t('activity.weeksAgo', { n: Math.floor(sec / 604800) })
|
return `${Math.floor(sec / 604800)}w ago`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 时间粒度
|
// 时间粒度
|
||||||
|
|||||||
@ -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">
|
||||||
{{ t('wallet.portfolio') }}
|
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">{{ t('wallet.today') }}</div>
|
<div class="card-timeframe">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"
|
||||||
>
|
>
|
||||||
{{ t('wallet.deposit') }}
|
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"
|
||||||
>
|
>
|
||||||
{{ t('wallet.withdraw') }}
|
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>
|
||||||
{{ t('wallet.profitLoss') }}
|
Profit/Loss
|
||||||
</span>
|
</span>
|
||||||
<div class="pl-tabs">
|
<div class="pl-tabs">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-for="tr in plTimeRanges"
|
v-for="t in plTimeRanges"
|
||||||
:key="tr.value"
|
:key="t"
|
||||||
:variant="plRange === tr.value ? 'flat' : 'text'"
|
:variant="plRange === t ? 'flat' : 'text'"
|
||||||
:color="plRange === tr.value ? 'primary' : undefined"
|
:color="plRange === t ? 'primary' : undefined"
|
||||||
size="small"
|
size="small"
|
||||||
class="pl-tab"
|
class="pl-tab"
|
||||||
@click="plRange = tr.value"
|
@click="plRange = t"
|
||||||
>
|
>
|
||||||
{{ tr.label }}
|
{{ t }}
|
||||||
</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">{{ t('wallet.allTime') }}</div>
|
<div class="card-timeframe">All-Time</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">{{ t('wallet.positions') }}</v-tab>
|
<v-tab value="positions">Positions</v-tab>
|
||||||
<v-tab value="orders">{{ t('wallet.openOrders') }}</v-tab>
|
<v-tab value="orders">Open orders</v-tab>
|
||||||
<v-tab value="history">{{ t('wallet.history') }}</v-tab>
|
<v-tab value="history">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="t('wallet.searchPlaceholder')"
|
placeholder="Search"
|
||||||
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>
|
||||||
{{ t('wallet.closeLosses') }}
|
Close Losses
|
||||||
</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>
|
||||||
{{ t('wallet.all') }}
|
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>
|
||||||
{{ t('wallet.newest') }}
|
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>
|
||||||
{{ t('wallet.export') }}
|
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>
|
||||||
{{ t('wallet.currentValue') }}
|
Current value
|
||||||
</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>
|
||||||
{{ t('wallet.market') }}
|
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>
|
||||||
{{ t('wallet.cancelAll') }}
|
Cancel all
|
||||||
</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>
|
||||||
{{ t('wallet.market') }}
|
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">{{ t('wallet.noPositionsFound') }}</div>
|
<div class="empty-cell">No positions found.</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)"
|
||||||
>{{ t('trade.sell') }}</v-btn
|
>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">{{ t('wallet.market') }}</th>
|
<th class="text-left">MARKET</th>
|
||||||
<th class="text-left">
|
<th class="text-left">
|
||||||
{{ t('wallet.avgNow') }}
|
AVG → NOW
|
||||||
<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">{{ t('wallet.bet') }}</th>
|
<th class="text-left">BET</th>
|
||||||
<th class="text-left">{{ t('wallet.toWin') }}</th>
|
<th class="text-left">TO WIN</th>
|
||||||
<th class="text-left">
|
<th class="text-left">
|
||||||
{{ t('wallet.value') }}
|
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">{{ t('wallet.action') }}</th>
|
<th class="text-right">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">{{ t('wallet.noPositionsFound') }}</td>
|
<td colspan="6" class="empty-cell">No positions found.</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)"
|
||||||
>{{ t('trade.sell') }}</v-btn
|
>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">{{ t('wallet.noOpenOrdersFound') }}</div>
|
<div class="empty-cell">No open orders found.</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">{{ t('wallet.expirationLabel') }} {{ ord.expiration }}</div>
|
<div class="order-mobile-expiry">Expiration: {{ 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">{{ t('wallet.market') }}</th>
|
<th class="text-left">MARKET</th>
|
||||||
<th class="text-left">{{ t('wallet.side') }}</th>
|
<th class="text-left">SIDE</th>
|
||||||
<th class="text-left">{{ t('wallet.outcome') }}</th>
|
<th class="text-left">OUTCOME</th>
|
||||||
<th class="text-left">{{ t('wallet.price') }}</th>
|
<th class="text-left">PRICE</th>
|
||||||
<th class="text-left">{{ t('wallet.filled') }}</th>
|
<th class="text-left">FILLED</th>
|
||||||
<th class="text-left">{{ t('wallet.total') }}</th>
|
<th class="text-left">TOTAL</th>
|
||||||
<th class="text-left">{{ t('wallet.expiration') }}</th>
|
<th class="text-left">EXPIRATION</th>
|
||||||
<th class="text-right">{{ t('wallet.action') }}</th>
|
<th class="text-right">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">{{ t('wallet.noOpenOrdersFound') }}</td>
|
<td colspan="8" class="empty-cell">No open orders found.</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">{{ t('wallet.noHistoryFound') }}</div>
|
<div class="empty-cell">You haven't traded any polymarkets yet</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)"
|
||||||
>{{ t('wallet.view') }}</v-btn
|
>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">{{ t('wallet.activity') }}</th>
|
<th class="text-left">ACTIVITY</th>
|
||||||
<th class="text-left">{{ t('wallet.market') }}</th>
|
<th class="text-left">MARKET</th>
|
||||||
<th class="text-left">{{ t('wallet.value') }}</th>
|
<th class="text-left">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">{{ t('wallet.noHistoryFound') }}</td>
|
<td colspan="3" class="empty-cell">You haven't traded any polymarkets yet</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="h in paginatedHistory" :key="h.id">
|
<tr v-for="h in paginatedHistory" :key="h.id">
|
||||||
<td>
|
<td>
|
||||||
@ -563,10 +563,7 @@
|
|||||||
|
|
||||||
<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'
|
||||||
@ -579,12 +576,7 @@ 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 = computed(() => [
|
const plTimeRanges = ['1D', '1W', '1M', 'ALL']
|
||||||
{ 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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user