增加:钱包页面
This commit is contained in:
parent
3117e34238
commit
cb52d8340f
39
src/App.vue
39
src/App.vue
@ -14,12 +14,32 @@ const currentRoute = computed(() => {
|
||||
<template>
|
||||
<v-app>
|
||||
<v-app-bar color="primary" dark>
|
||||
<v-app-bar-title>PolyMarket</v-app-bar-title>
|
||||
<v-btn
|
||||
v-if="currentRoute !== '/'"
|
||||
icon
|
||||
variant="text"
|
||||
class="back-btn"
|
||||
aria-label="返回"
|
||||
@click="$router.back()"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<v-app-bar-title v-if="currentRoute === '/'">PolyMarket</v-app-bar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn v-if="!userStore.isLoggedIn" text to="/login" :class="{ active: currentRoute === '/login' }">
|
||||
Login
|
||||
</v-btn>
|
||||
<v-menu v-else location="bottom" :close-on-content-click="false">
|
||||
<template v-else>
|
||||
<v-btn
|
||||
class="balance-btn"
|
||||
variant="text"
|
||||
min-width="auto"
|
||||
padding="4 12"
|
||||
@click="$router.push('/wallet')"
|
||||
>
|
||||
<span class="balance-text">${{ userStore.balance }}</span>
|
||||
</v-btn>
|
||||
<v-menu location="bottom" :close-on-content-click="false">
|
||||
<template #activator="{ props }">
|
||||
<v-btn v-bind="props" icon variant="text" class="avatar-btn">
|
||||
<v-avatar size="36" color="primary">
|
||||
@ -33,6 +53,7 @@ const currentRoute = computed(() => {
|
||||
<v-list-item title="退出登录" @click="userStore.logout()" />
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<v-main>
|
||||
<router-view />
|
||||
@ -46,4 +67,18 @@ const currentRoute = computed(() => {
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.balance-btn {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.balance-text {
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -3,6 +3,7 @@ import Home from '../views/Home.vue'
|
||||
import Trade from '../views/Trade.vue'
|
||||
import Login from '../views/Login.vue'
|
||||
import TradeDetail from '../views/TradeDetail.vue'
|
||||
import Wallet from '../views/Wallet.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -26,6 +27,11 @@ const router = createRouter({
|
||||
path: '/trade-detail/:id',
|
||||
name: 'trade-detail',
|
||||
component: TradeDetail
|
||||
},
|
||||
{
|
||||
path: '/wallet',
|
||||
name: 'wallet',
|
||||
component: Wallet
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
@ -43,6 +43,8 @@ export const useUserStore = defineStore('user', () => {
|
||||
|
||||
const isLoggedIn = computed(() => !!token.value && !!user.value)
|
||||
const avatarUrl = computed(() => user.value?.headerImg ?? '')
|
||||
/** 钱包余额显示,如 "0.00",可从接口更新 */
|
||||
const balance = ref<string>('0.00')
|
||||
|
||||
function setUser(loginData: { token?: string; user?: UserInfo }) {
|
||||
const t = loginData.token ?? ''
|
||||
@ -59,5 +61,5 @@ export const useUserStore = defineStore('user', () => {
|
||||
clearStorage()
|
||||
}
|
||||
|
||||
return { token, user, isLoggedIn, avatarUrl, setUser, logout }
|
||||
return { token, user, isLoggedIn, avatarUrl, balance, setUser, logout }
|
||||
})
|
||||
|
||||
331
src/views/Wallet.vue
Normal file
331
src/views/Wallet.vue
Normal file
@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<v-container class="wallet-container">
|
||||
<!-- 顶部:Portfolio + Profit/Loss 卡片 -->
|
||||
<v-row class="wallet-cards">
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="wallet-card portfolio-card" elevation="0" rounded="lg">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
Portfolio
|
||||
<v-icon size="16" class="title-icon">mdi-eye-off-outline</v-icon>
|
||||
</span>
|
||||
<div class="balance-badge">
|
||||
<v-icon size="14">mdi-sack</v-icon>
|
||||
<span>${{ portfolioBalance }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-value">${{ portfolioBalance }}</div>
|
||||
<div class="card-timeframe">Today</div>
|
||||
<div class="card-actions">
|
||||
<v-btn color="primary" variant="flat" class="action-btn" prepend-icon="mdi-arrow-down">
|
||||
Deposit
|
||||
</v-btn>
|
||||
<v-btn variant="outlined" color="grey" class="action-btn" prepend-icon="mdi-arrow-up">
|
||||
Withdraw
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="wallet-card pl-card" elevation="0" rounded="lg">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<v-icon size="16" color="success">mdi-triangle-small-up</v-icon>
|
||||
Profit/Loss
|
||||
</span>
|
||||
<div class="pl-tabs">
|
||||
<v-btn
|
||||
v-for="t in plTimeRanges"
|
||||
:key="t"
|
||||
:variant="plRange === t ? 'flat' : 'text'"
|
||||
:color="plRange === t ? 'primary' : undefined"
|
||||
size="small"
|
||||
class="pl-tab"
|
||||
@click="plRange = t"
|
||||
>
|
||||
{{ t }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-value-row">
|
||||
<span class="card-value">${{ profitLoss }}</span>
|
||||
<v-icon size="18" class="info-icon">mdi-information-outline</v-icon>
|
||||
</div>
|
||||
<div class="card-timeframe">All-Time</div>
|
||||
<div class="polymarket-logo">
|
||||
<span class="logo-p">P</span>
|
||||
</div>
|
||||
<div class="pl-bar">
|
||||
<div class="pl-bar-fill" />
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 下方:Positions / Open orders / History -->
|
||||
<div class="wallet-section">
|
||||
<v-tabs v-model="activeTab" class="wallet-tabs" density="comfortable">
|
||||
<v-tab value="positions">Positions</v-tab>
|
||||
<v-tab value="orders">Open orders</v-tab>
|
||||
<v-tab value="history">History</v-tab>
|
||||
</v-tabs>
|
||||
<div class="toolbar">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
placeholder="Search"
|
||||
density="compact"
|
||||
hide-details
|
||||
variant="outlined"
|
||||
rounded
|
||||
class="search-field"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
/>
|
||||
<v-btn variant="outlined" size="small" class="filter-btn">
|
||||
<v-icon size="18">mdi-filter</v-icon>
|
||||
Current value
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-card class="table-card" elevation="0" rounded="lg">
|
||||
<v-table class="positions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">MARKET</th>
|
||||
<th class="text-left">
|
||||
AVG → NOW
|
||||
<v-icon size="14" class="th-icon">mdi-information-outline</v-icon>
|
||||
</th>
|
||||
<th class="text-left">BET</th>
|
||||
<th class="text-left">TO WIN</th>
|
||||
<th class="text-left">
|
||||
VALUE
|
||||
<v-icon size="14" class="th-icon">mdi-chevron-down</v-icon>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="positions.length === 0">
|
||||
<td colspan="5" class="empty-cell">
|
||||
No positions found.
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="pos in positions" :key="pos.id">
|
||||
<td>{{ pos.market }}</td>
|
||||
<td>{{ pos.avgNow }}</td>
|
||||
<td>{{ pos.bet }}</td>
|
||||
<td>{{ pos.toWin }}</td>
|
||||
<td>{{ pos.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useUserStore } from '../stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const portfolioBalance = computed(() => userStore.balance)
|
||||
const profitLoss = ref('0.00')
|
||||
const plRange = ref('ALL')
|
||||
const plTimeRanges = ['1D', '1W', '1M', 'ALL']
|
||||
const activeTab = ref('positions')
|
||||
const search = ref('')
|
||||
const positions = ref<{ id: string; market: string; avgNow: string; bet: string; toWin: string; value: string }[]>([])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wallet-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.wallet-cards {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.wallet-card {
|
||||
padding: 20px;
|
||||
border: 1px solid #e5e7eb;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.balance-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #059669;
|
||||
background-color: #d1fae5;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-value-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-value-row .card-value {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.card-timeframe {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.pl-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pl-tab {
|
||||
min-width: 40px;
|
||||
text-transform: none;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.polymarket-logo {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.logo-p {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: linear-gradient(135deg, #1a73e8 0%, #34a853 100%);
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.pl-bar {
|
||||
height: 6px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pl-bar-fill {
|
||||
height: 100%;
|
||||
width: 40%;
|
||||
background: linear-gradient(90deg, #93c5fd 0%, #bfdbfe 100%);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.wallet-section {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.wallet-tabs {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-field {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.search-field :deep(.v-field) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
text-transform: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
border: 1px solid #e5e7eb;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.positions-table {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.positions-table th {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.th-icon {
|
||||
vertical-align: middle;
|
||||
margin-left: 2px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.positions-table td {
|
||||
padding: 12px 16px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.empty-cell {
|
||||
text-align: center;
|
||||
color: #6b7280;
|
||||
padding: 48px 16px !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user