Agent-Market-Arena / src /views /AssetRequestsView.vue
lfqian's picture
feat: update Agent Arena with email and voting system
5b78b55
raw
history blame
8.62 kB
<template>
<div class="page-container">
<div class="title-container">
<span class="main-title">Top Asset Requests</span>
<p class="subtitle">Community requested assets ranked by votes</p>
</div>
<div class="requests-container">
<Card class="requests-card">
<template #content>
<div v-if="loading" class="loading-state">
<ProgressSpinner />
<p>Loading requests...</p>
</div>
<div v-else-if="topRequests.length === 0" class="empty-state">
<i class="pi pi-inbox" style="font-size: 3rem; color: #9ca3af;"></i>
<p>No asset requests yet. Be the first to request one!</p>
<Button label="Request Asset" @click="$router.push('/add-asset')" />
</div>
<div v-else class="requests-list">
<div
v-for="(request, index) in topRequests"
:key="index"
class="request-item"
:class="{ 'top-rank': index < 3 }"
>
<div class="rank-badge" :class="`rank-${index + 1}`">
{{ index + 1 }}
</div>
<div class="request-content">
<div class="request-header">
<span class="asset-symbol">{{ request.symbol }}</span>
<span class="asset-type-badge" :class="`type-${request.type}`">
{{ request.type }}
</span>
</div>
<div v-if="request.reason" class="request-reason">
{{ request.reason }}
</div>
<div class="request-meta">
<span class="timestamp">
<i class="pi pi-clock"></i>
{{ formatDate(request.timestamp) }}
</span>
</div>
</div>
<div class="vote-section">
<Button
icon="pi pi-thumbs-up"
:label="String(request.votes)"
@click="voteForAsset(request.symbol)"
class="vote-button"
:disabled="hasVoted(request.symbol)"
/>
</div>
</div>
</div>
<div class="actions-footer">
<Button
label="Back to Submissions"
icon="pi pi-arrow-left"
@click="$router.push('/add-asset')"
outlined
/>
</div>
</template>
</Card>
</div>
</div>
</template>
<script>
import Card from 'primevue/card'
import Button from 'primevue/button'
import ProgressSpinner from 'primevue/progressspinner'
export default {
name: 'AssetRequestsView',
components: {
Card,
Button,
ProgressSpinner
},
data() {
return {
loading: true,
topRequests: [],
votedAssets: new Set()
}
},
mounted() {
this.loadRequests()
this.loadVotedAssets()
},
methods: {
loadRequests() {
this.loading = true
try {
const stored = localStorage.getItem('assetRequests')
if (stored) {
const requests = JSON.parse(stored)
// 按投票数排序,取前10个
this.topRequests = requests
.sort((a, b) => b.votes - a.votes)
.slice(0, 10)
}
} catch (e) {
console.error('Error loading requests:', e)
} finally {
this.loading = false
}
},
loadVotedAssets() {
try {
const stored = localStorage.getItem('votedAssets')
if (stored) {
this.votedAssets = new Set(JSON.parse(stored))
}
} catch (e) {
console.error('Error loading voted assets:', e)
}
},
voteForAsset(symbol) {
try {
// 获取所有请求
const stored = localStorage.getItem('assetRequests')
if (!stored) return
const requests = JSON.parse(stored)
const index = requests.findIndex(r => r.symbol.toUpperCase() === symbol.toUpperCase())
if (index >= 0) {
requests[index].votes += 1
requests[index].timestamp = new Date().toISOString()
// 保存更新
localStorage.setItem('assetRequests', JSON.stringify(requests))
// 记录投票
this.votedAssets.add(symbol.toUpperCase())
localStorage.setItem('votedAssets', JSON.stringify([...this.votedAssets]))
// 重新加载
this.loadRequests()
}
} catch (e) {
console.error('Error voting:', e)
}
},
hasVoted(symbol) {
return this.votedAssets.has(symbol.toUpperCase())
},
formatDate(timestamp) {
try {
const date = new Date(timestamp)
const now = new Date()
const diff = now - date
const minutes = Math.floor(diff / 60000)
const hours = Math.floor(diff / 3600000)
const days = Math.floor(diff / 86400000)
if (minutes < 1) return 'Just now'
if (minutes < 60) return `${minutes}m ago`
if (hours < 24) return `${hours}h ago`
if (days < 7) return `${days}d ago`
return date.toLocaleDateString()
} catch (e) {
return 'Recently'
}
}
}
}
</script>
<style scoped>
.page-container {
max-width: 900px;
margin: 0 auto;
padding: 1rem;
}
.title-container {
text-align: center;
margin-bottom: 2rem;
}
.main-title {
font-size: 2rem;
letter-spacing: -0.02em;
font-weight: 800;
color: #1f1f33;
}
.subtitle {
color: #6b7280;
margin-top: 0.5rem;
font-size: 1rem;
}
.requests-card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.loading-state,
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
gap: 1rem;
color: #6b7280;
}
.requests-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.request-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem;
border: 2px solid #e5e7eb;
border-radius: 12px;
transition: all 0.2s;
background: #fafafa;
}
.request-item:hover {
border-color: #d1d5db;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.request-item.top-rank {
background: linear-gradient(135deg, #fef3c7 0%, #fef9f3 100%);
border-color: #fbbf24;
}
.rank-badge {
min-width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: 900;
border-radius: 50%;
background: #e5e7eb;
color: #4b5563;
}
.rank-badge.rank-1 {
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
color: white;
box-shadow: 0 4px 12px rgba(251, 191, 36, 0.4);
}
.rank-badge.rank-2 {
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%);
color: white;
}
.rank-badge.rank-3 {
background: linear-gradient(135deg, #d97706 0%, #92400e 100%);
color: white;
}
.request-content {
flex: 1;
min-width: 0;
}
.request-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.asset-symbol {
font-size: 1.25rem;
font-weight: 700;
color: #1f1f33;
}
.asset-type-badge {
padding: 0.25rem 0.75rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.type-stock {
background: #dbeafe;
color: #1e40af;
}
.type-crypto {
background: #fef3c7;
color: #92400e;
}
.type-etf {
background: #d1fae5;
color: #065f46;
}
.type-other {
background: #f3f4f6;
color: #4b5563;
}
.request-reason {
color: #6b7280;
font-size: 0.9rem;
margin-bottom: 0.5rem;
line-height: 1.5;
}
.request-meta {
display: flex;
align-items: center;
gap: 1rem;
font-size: 0.875rem;
color: #9ca3af;
}
.timestamp {
display: flex;
align-items: center;
gap: 0.25rem;
}
.vote-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.vote-button {
min-width: 80px;
}
:deep(.vote-button .p-button-label) {
font-weight: 700;
font-size: 1rem;
}
.actions-footer {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid #e5e7eb;
display: flex;
justify-content: center;
}
@media (max-width: 768px) {
.main-title {
font-size: 1.5rem;
}
.request-item {
flex-direction: column;
text-align: center;
}
.request-header {
justify-content: center;
}
}
</style>