新增:交易历史记录

This commit is contained in:
ivan 2026-02-08 18:42:28 +08:00
parent 642f7990dc
commit 3688ce656d

View File

@ -87,13 +87,31 @@
class="search-field"
prepend-inner-icon="mdi-magnify"
/>
<v-btn variant="outlined" size="small" class="filter-btn">
<template v-if="activeTab === 'history'">
<v-btn variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-filter</v-icon>
All
</v-btn>
<v-btn variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-sort</v-icon>
Newest
</v-btn>
<v-btn variant="outlined" size="small" class="filter-btn" icon>
<v-icon size="18">mdi-calendar</v-icon>
</v-btn>
<v-btn variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-download</v-icon>
Export
</v-btn>
</template>
<v-btn v-else variant="outlined" size="small" class="filter-btn">
<v-icon size="18">mdi-filter</v-icon>
Current value
Market
</v-btn>
</div>
<v-card class="table-card" elevation="0" rounded="lg">
<v-table class="positions-table">
<!-- 持仓 -->
<v-table v-if="activeTab === 'positions'" class="wallet-table">
<thead>
<tr>
<th class="text-left">MARKET</th>
@ -110,13 +128,11 @@
</tr>
</thead>
<tbody>
<tr v-if="positions.length === 0">
<td colspan="5" class="empty-cell">
No positions found.
</td>
<tr v-if="filteredPositions.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>
<tr v-for="pos in paginatedPositions" :key="pos.id">
<td class="cell-market">{{ pos.market }}</td>
<td>{{ pos.avgNow }}</td>
<td>{{ pos.bet }}</td>
<td>{{ pos.toWin }}</td>
@ -124,6 +140,87 @@
</tr>
</tbody>
</v-table>
<!-- 未成交 -->
<v-table v-else-if="activeTab === 'orders'" class="wallet-table">
<thead>
<tr>
<th class="text-left">MARKET</th>
<th class="text-left">SIDE</th>
<th class="text-left">OUTCOME</th>
<th class="text-left">PRICE</th>
<th class="text-left">FILLED</th>
<th class="text-left">TOTAL</th>
<th class="text-left">EXPIRATION</th>
<th class="text-right">ACTION</th>
</tr>
</thead>
<tbody>
<tr v-if="filteredOpenOrders.length === 0">
<td colspan="8" class="empty-cell">No open orders found.</td>
</tr>
<tr v-for="ord in paginatedOpenOrders" :key="ord.id">
<td class="cell-market">{{ ord.market }}</td>
<td>
<span :class="ord.side === 'Yes' ? 'side-yes' : 'side-no'">{{ ord.side }}</span>
</td>
<td>{{ ord.outcome }}</td>
<td>{{ ord.price }}</td>
<td>{{ ord.filled }}</td>
<td>{{ ord.total }}</td>
<td>{{ ord.expiration }}</td>
<td class="text-right">
<v-btn variant="text" size="small" color="error" @click="cancelOrder(ord.id)">Cancel</v-btn>
</td>
</tr>
</tbody>
</v-table>
<!-- 历史记录 -->
<v-table v-else class="wallet-table">
<thead>
<tr>
<th class="text-left">ACTIVITY</th>
<th class="text-left">MARKET</th>
<th class="text-left">VALUE</th>
</tr>
</thead>
<tbody>
<tr v-if="filteredHistory.length === 0">
<td colspan="3" class="empty-cell">You haven't traded any polymarkets yet</td>
</tr>
<tr v-for="h in paginatedHistory" :key="h.id">
<td>
<span :class="h.side === 'Yes' ? 'side-yes' : 'side-no'">{{ h.activity }}</span>
</td>
<td class="cell-market">{{ h.market }}</td>
<td>{{ h.value }}</td>
</tr>
</tbody>
</v-table>
<!-- 分页 -->
<div v-if="currentListTotal > 0" class="pagination-bar">
<span class="pagination-info">
{{ currentPageStart }}{{ currentPageEnd }} of {{ currentListTotal }}
</span>
<div class="pagination-controls">
<v-select
v-model="itemsPerPage"
:items="pageSizeOptions"
density="compact"
hide-details
variant="outlined"
class="page-size-select"
@update:model-value="page = 1"
/>
<v-pagination
v-model="page"
:length="currentTotalPages"
:total-visible="5"
density="comfortable"
class="pagination"
@update:model-value="onPageChange"
/>
</div>
</div>
</v-card>
</div>
@ -152,11 +249,100 @@ 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 activeTab = ref<'positions' | 'orders' | 'history'>('positions')
const search = ref('')
const depositDialogOpen = ref(false)
const withdrawDialogOpen = ref(false)
const positions = ref<{ id: string; market: string; avgNow: string; bet: string; toWin: string; value: string }[]>([])
interface Position {
id: string
market: string
avgNow: string
bet: string
toWin: string
value: string
}
interface OpenOrder {
id: string
market: string
side: 'Yes' | 'No'
outcome: string
price: string
filled: string
total: string
expiration: string
}
interface HistoryItem {
id: string
market: string
side: 'Yes' | 'No'
activity: string
value: string
}
const positions = ref<Position[]>([
{ id: 'p1', market: 'Will Bitcoin hit $100k by end of 2025?', avgNow: '72¢ → 75¢', bet: '$50', toWin: '$18', value: '$52' },
{ id: 'p2', market: 'Will ETH merge complete by Q3?', avgNow: '45¢ → 48¢', bet: '$30', toWin: '$36', value: '$32' },
])
const openOrders = ref<OpenOrder[]>([
{ id: 'o1', market: 'Will Bitcoin hit $100k by end of 2025?', side: 'Yes', outcome: 'Yes', price: '70¢', filled: '0', total: '$70', expiration: 'Dec 31, 2025' },
{ id: 'o2', market: 'Will ETH merge complete by Q3?', side: 'No', outcome: 'No', price: '52¢', filled: '25', total: '$26', expiration: 'Sep 30, 2025' },
])
const history = ref<HistoryItem[]>([
{ id: 'h1', market: 'Will Bitcoin hit $100k by end of 2025?', side: 'Yes', activity: 'Buy Yes', value: '$68' },
{ id: 'h2', market: 'Will ETH merge complete by Q3?', side: 'No', activity: 'Sell No', value: '$35.20' },
])
function matchSearch(text: string): boolean {
const q = search.value.trim().toLowerCase()
return !q || text.toLowerCase().includes(q)
}
const filteredPositions = computed(() => positions.value.filter((p) => matchSearch(p.market)))
const filteredOpenOrders = computed(() => openOrders.value.filter((o) => matchSearch(o.market)))
const filteredHistory = computed(() => history.value.filter((h) => matchSearch(h.market)))
const page = ref(1)
const itemsPerPage = ref(10)
const pageSizeOptions = [5, 10, 25, 50]
function paginate<T>(list: T[]) {
const start = (page.value - 1) * itemsPerPage.value
return list.slice(start, start + itemsPerPage.value)
}
const paginatedPositions = computed(() => paginate(filteredPositions.value))
const paginatedOpenOrders = computed(() => paginate(filteredOpenOrders.value))
const paginatedHistory = computed(() => paginate(filteredHistory.value))
const totalPagesPositions = computed(() => Math.max(1, Math.ceil(filteredPositions.value.length / itemsPerPage.value)))
const totalPagesOrders = computed(() => Math.max(1, Math.ceil(filteredOpenOrders.value.length / itemsPerPage.value)))
const totalPagesHistory = computed(() => Math.max(1, Math.ceil(filteredHistory.value.length / itemsPerPage.value)))
const currentListTotal = computed(() => {
if (activeTab.value === 'positions') return filteredPositions.value.length
if (activeTab.value === 'orders') return filteredOpenOrders.value.length
return filteredHistory.value.length
})
const currentTotalPages = computed(() => {
if (activeTab.value === 'positions') return totalPagesPositions.value
if (activeTab.value === 'orders') return totalPagesOrders.value
return totalPagesHistory.value
})
const currentPageStart = computed(() => currentListTotal.value === 0 ? 0 : (page.value - 1) * itemsPerPage.value + 1)
const currentPageEnd = computed(() => Math.min(page.value * itemsPerPage.value, currentListTotal.value))
watch(activeTab, () => { page.value = 1 })
watch([currentListTotal, itemsPerPage], () => {
const maxPage = currentTotalPages.value
if (page.value > maxPage) page.value = Math.max(1, maxPage)
})
function onPageChange() {
//
}
function cancelOrder(id: string) {
openOrders.value = openOrders.value.filter((o) => o.id !== id)
}
const plChartRef = ref<HTMLElement | null>(null)
let plChartInstance: ECharts | null = null
@ -418,10 +604,12 @@ function onWithdrawSuccess() {
overflow: hidden;
}
.positions-table {
.positions-table,
.wallet-table {
font-size: 14px;
}
.wallet-table th,
.positions-table th {
font-size: 11px;
font-weight: 600;
@ -437,15 +625,74 @@ function onWithdrawSuccess() {
color: #9ca3af;
}
.positions-table td {
.positions-table td,
.wallet-table td {
padding: 12px 16px;
color: #374151;
}
.cell-market {
max-width: 280px;
word-break: break-word;
}
.side-yes {
color: #059669;
font-weight: 500;
}
.side-no {
color: #dc2626;
font-weight: 500;
}
.empty-cell {
text-align: center;
color: #6b7280;
padding: 48px 16px !important;
font-size: 14px;
}
.pagination-bar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
padding: 12px 16px;
border-top: 1px solid #e5e7eb;
font-size: 14px;
color: #6b7280;
}
.pagination-info {
flex-shrink: 0;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 12px;
}
.page-size-select {
min-width: 88px;
width: 88px;
font-size: 14px;
}
.page-size-select :deep(.v-field) {
font-size: 14px;
}
.page-size-select :deep(.v-field__input) {
min-width: 2ch;
}
.pagination :deep(.v-pagination__item),
.pagination :deep(.v-pagination__prev),
.pagination :deep(.v-pagination__next) {
min-width: 32px;
height: 32px;
}
</style>