|
@@ -4,15 +4,143 @@
|
|
|
<view class="page-title-card">
|
|
<view class="page-title-card">
|
|
|
<text class="page-title-text">量化选股大师</text>
|
|
<text class="page-title-text">量化选股大师</text>
|
|
|
</view>
|
|
</view>
|
|
|
- <view class="content-area">
|
|
|
|
|
- <text class="title">模拟排名</text>
|
|
|
|
|
- <text class="sub">这里后续可以展示策略或股票排名</text>
|
|
|
|
|
- </view>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <scroll-view class="scroll-view" scroll-y>
|
|
|
|
|
+ <view class="content-wrapper">
|
|
|
|
|
+ <!-- 我的模拟资产卡片 -->
|
|
|
|
|
+ <view class="portfolio-card">
|
|
|
|
|
+ <view class="portfolio-header">
|
|
|
|
|
+ <view class="portfolio-icon">💼</view>
|
|
|
|
|
+ <text class="portfolio-title">我的模拟资产</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="portfolio-content">
|
|
|
|
|
+ <view class="portfolio-main">
|
|
|
|
|
+ <view class="portfolio-label">当前账户余额 (¥)</view>
|
|
|
|
|
+ <view class="portfolio-amount">{{ formatAmount(portfolio.balance) }}</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="portfolio-profit">
|
|
|
|
|
+ <view class="profit-label">总盈亏 / 收益率</view>
|
|
|
|
|
+ <view :class="['profit-value', portfolio.profitRate >= 0 ? 'profit-positive' : 'profit-negative']">
|
|
|
|
|
+ ¥ {{ formatProfit(portfolio.profit) }} ({{ formatRate(portfolio.profitRate) }}%)
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 模拟交易排行榜 -->
|
|
|
|
|
+ <view class="leaderboard-section">
|
|
|
|
|
+ <view class="section-header">
|
|
|
|
|
+ <view class="trophy-icon">🏆</view>
|
|
|
|
|
+ <text class="section-title">模拟交易排行榜</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 当前用户排名 -->
|
|
|
|
|
+ <view class="my-rank-card">
|
|
|
|
|
+ <view class="rank-number">#{{ myRank.rank }}</view>
|
|
|
|
|
+ <view class="rank-info">
|
|
|
|
|
+ <text class="rank-name">您 (本期收益)</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view :class="['rank-rate', myRank.rate >= 0 ? 'rate-positive' : 'rate-negative']">
|
|
|
|
|
+ {{ myRank.rate >= 0 ? '+' : '' }}{{ myRank.rate }}%
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 排行榜列表 -->
|
|
|
|
|
+ <view class="leaderboard-list">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(item, index) in leaderboard"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="leaderboard-item"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view :class="['rank-badge', getRankClass(item.rank)]">
|
|
|
|
|
+ #{{ item.rank }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="user-info">
|
|
|
|
|
+ <text class="user-name">{{ item.name }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view :class="['user-rate', item.rate >= 0 ? 'rate-positive' : 'rate-negative']">
|
|
|
|
|
+ {{ item.rate >= 0 ? '+' : '' }}{{ item.rate }}%
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="leaderboard-note">
|
|
|
|
|
+ <text class="note-text">排名每日更新,收益基于系统信号的模拟交易。</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 预留底部空间 -->
|
|
|
|
|
+ <view class="bottom-safe-area"></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </scroll-view>
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-// Logic for rank page
|
|
|
|
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
|
|
+import { getUserPortfolio, getLeaderboard } from '../../utils/api.js'
|
|
|
|
|
+
|
|
|
|
|
+const portfolio = ref({
|
|
|
|
|
+ balance: 100000,
|
|
|
|
|
+ profit: 0,
|
|
|
|
|
+ profitRate: 0
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const myRank = ref({
|
|
|
|
|
+ rank: 5,
|
|
|
|
|
+ rate: 0
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const leaderboard = ref([
|
|
|
|
|
+ { rank: 1, name: '量化王者 608', rate: 35.2 },
|
|
|
|
|
+ { rank: 2, name: '短线猎手 012', rate: 28.9 },
|
|
|
|
|
+ { rank: 3, name: '趋势追踪者', rate: 22.1 }
|
|
|
|
|
+])
|
|
|
|
|
+
|
|
|
|
|
+const formatAmount = (amount) => {
|
|
|
|
|
+ return amount.toLocaleString('zh-CN')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const formatProfit = (profit) => {
|
|
|
|
|
+ return Math.abs(profit).toLocaleString('zh-CN')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const formatRate = (rate) => {
|
|
|
|
|
+ return rate.toFixed(2)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const getRankClass = (rank) => {
|
|
|
|
|
+ if (rank === 1) return 'rank-first'
|
|
|
|
|
+ if (rank === 2) return 'rank-second'
|
|
|
|
|
+ if (rank === 3) return 'rank-third'
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loadData = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const portfolioRes = await getUserPortfolio()
|
|
|
|
|
+ if (portfolioRes.code === 0 && portfolioRes.data) {
|
|
|
|
|
+ portfolio.value = portfolioRes.data
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('获取资产数据失败:', err)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const leaderboardRes = await getLeaderboard()
|
|
|
|
|
+ if (leaderboardRes.code === 0 && leaderboardRes.data) {
|
|
|
|
|
+ leaderboard.value = leaderboardRes.data.list || []
|
|
|
|
|
+ myRank.value = leaderboardRes.data.myRank || { rank: 5, rate: 0 }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('获取排行榜数据失败:', err)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadData()
|
|
|
|
|
+})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style>
|
|
<style>
|
|
@@ -20,7 +148,7 @@
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
background: #f5f6fb;
|
|
background: #f5f6fb;
|
|
|
- min-height: 100vh;
|
|
|
|
|
|
|
+ height: 100vh;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.page-title-card {
|
|
.page-title-card {
|
|
@@ -31,10 +159,6 @@
|
|
|
border-radius: 0;
|
|
border-radius: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.content-area {
|
|
|
|
|
- padding: 32rpx;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
.page-title-text {
|
|
.page-title-text {
|
|
|
font-size: 36rpx;
|
|
font-size: 36rpx;
|
|
|
font-weight: 800;
|
|
font-weight: 800;
|
|
@@ -42,13 +166,216 @@
|
|
|
letter-spacing: 2rpx;
|
|
letter-spacing: 2rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.title {
|
|
|
|
|
|
|
+.scroll-view {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-wrapper {
|
|
|
|
|
+ padding: 32rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 模拟资产卡片 */
|
|
|
|
|
+.portfolio-card {
|
|
|
|
|
+ background: linear-gradient(135deg, #5d55e8, #7568ff);
|
|
|
|
|
+ border-radius: 24rpx;
|
|
|
|
|
+ padding: 40rpx 32rpx;
|
|
|
|
|
+ margin-bottom: 32rpx;
|
|
|
|
|
+ box-shadow: 0 16rpx 40rpx rgba(93, 85, 232, 0.3);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.portfolio-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 32rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.portfolio-icon {
|
|
|
|
|
+ font-size: 40rpx;
|
|
|
|
|
+ margin-right: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.portfolio-title {
|
|
|
|
|
+ font-size: 32rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.portfolio-content {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.portfolio-main {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.portfolio-label {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.8);
|
|
|
|
|
+ margin-bottom: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.portfolio-amount {
|
|
|
|
|
+ font-size: 56rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.portfolio-profit {
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.profit-label {
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.7);
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.profit-value {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.profit-positive {
|
|
|
|
|
+ color: #4fffb0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.profit-negative {
|
|
|
|
|
+ color: #ff6b9d;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 排行榜区域 */
|
|
|
|
|
+.leaderboard-section {
|
|
|
|
|
+ background: #ffffff;
|
|
|
|
|
+ border-radius: 24rpx;
|
|
|
|
|
+ padding: 32rpx;
|
|
|
|
|
+ box-shadow: 0 16rpx 40rpx rgba(37, 52, 94, 0.08);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.section-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.trophy-icon {
|
|
|
font-size: 36rpx;
|
|
font-size: 36rpx;
|
|
|
- font-weight: bold;
|
|
|
|
|
- margin-bottom: 20rpx;
|
|
|
|
|
|
|
+ margin-right: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.section-title {
|
|
|
|
|
+ font-size: 32rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #222222;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 当前用户排名卡片 */
|
|
|
|
|
+.my-rank-card {
|
|
|
|
|
+ background: linear-gradient(135deg, #fff9e6, #fffbf0);
|
|
|
|
|
+ border: 2rpx solid #ffd966;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ padding: 24rpx 28rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-number {
|
|
|
|
|
+ font-size: 40rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #ff9800;
|
|
|
|
|
+ margin-right: 24rpx;
|
|
|
|
|
+ min-width: 80rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-info {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-name {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #333333;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-rate {
|
|
|
|
|
+ font-size: 32rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rate-positive {
|
|
|
|
|
+ color: #00c853;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rate-negative {
|
|
|
|
|
+ color: #ff5252;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 排行榜列表 */
|
|
|
|
|
+.leaderboard-list {
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.leaderboard-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 24rpx 0;
|
|
|
|
|
+ border-bottom: 1rpx solid #f5f6fb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.leaderboard-item:last-child {
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-badge {
|
|
|
|
|
+ font-size: 32rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #666666;
|
|
|
|
|
+ margin-right: 24rpx;
|
|
|
|
|
+ min-width: 80rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-first {
|
|
|
|
|
+ color: #ff5252;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-second {
|
|
|
|
|
+ color: #ff9800;
|
|
|
}
|
|
}
|
|
|
-.sub {
|
|
|
|
|
|
|
+
|
|
|
|
|
+.rank-third {
|
|
|
|
|
+ color: #ffc107;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.user-info {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.user-name {
|
|
|
font-size: 28rpx;
|
|
font-size: 28rpx;
|
|
|
- color: #888;
|
|
|
|
|
|
|
+ color: #333333;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.user-rate {
|
|
|
|
|
+ font-size: 32rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.leaderboard-note {
|
|
|
|
|
+ padding-top: 16rpx;
|
|
|
|
|
+ border-top: 1rpx solid #f5f6fb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.note-text {
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ color: #999999;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bottom-safe-area {
|
|
|
|
|
+ height: 80rpx;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|