Browse Source

模拟排名小程序初始化

Zhangbw 3 tháng trước cách đây
mục cha
commit
c593825027

+ 1 - 0
dist/dev/mp-weixin/common/vendor.js

@@ -6755,6 +6755,7 @@ exports.index = index;
 exports.n = n;
 exports.o = o;
 exports.onLoad = onLoad;
+exports.onMounted = onMounted;
 exports.onShow = onShow;
 exports.ref = ref;
 exports.t = t;

+ 76 - 1
dist/dev/mp-weixin/pages/rank/rank.js

@@ -1,10 +1,85 @@
 "use strict";
 const common_vendor = require("../../common/vendor.js");
+const utils_api = require("../../utils/api.js");
 const _sfc_main = {
   __name: "rank",
   setup(__props) {
+    const portfolio = common_vendor.ref({
+      balance: 1e5,
+      profit: 0,
+      profitRate: 0
+    });
+    const myRank = common_vendor.ref({
+      rank: 5,
+      rate: 0
+    });
+    const leaderboard = common_vendor.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 utils_api.getUserPortfolio();
+        if (portfolioRes.code === 0 && portfolioRes.data) {
+          portfolio.value = portfolioRes.data;
+        }
+      } catch (err) {
+        console.error("获取资产数据失败:", err);
+      }
+      try {
+        const leaderboardRes = await utils_api.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);
+      }
+    };
+    common_vendor.onMounted(() => {
+      loadData();
+    });
     return (_ctx, _cache) => {
-      return {};
+      return {
+        a: common_vendor.t(formatAmount(portfolio.value.balance)),
+        b: common_vendor.t(formatProfit(portfolio.value.profit)),
+        c: common_vendor.t(formatRate(portfolio.value.profitRate)),
+        d: common_vendor.n(portfolio.value.profitRate >= 0 ? "profit-positive" : "profit-negative"),
+        e: common_vendor.t(myRank.value.rank),
+        f: common_vendor.t(myRank.value.rate >= 0 ? "+" : ""),
+        g: common_vendor.t(myRank.value.rate),
+        h: common_vendor.n(myRank.value.rate >= 0 ? "rate-positive" : "rate-negative"),
+        i: common_vendor.f(leaderboard.value, (item, index, i0) => {
+          return {
+            a: common_vendor.t(item.rank),
+            b: common_vendor.n(getRankClass(item.rank)),
+            c: common_vendor.t(item.name),
+            d: common_vendor.t(item.rate >= 0 ? "+" : ""),
+            e: common_vendor.t(item.rate),
+            f: common_vendor.n(item.rate >= 0 ? "rate-positive" : "rate-negative"),
+            g: index
+          };
+        })
+      };
     };
   }
 };

+ 1 - 1
dist/dev/mp-weixin/pages/rank/rank.wxml

@@ -1 +1 @@
-<view class="page-rank"><view class="page-title-card"><text class="page-title-text">量化选股大师</text></view><view class="content-area"><text class="title">模拟排名</text><text class="sub">这里后续可以展示策略或股票排名</text></view></view>
+<view class="page-rank"><view class="page-title-card"><text class="page-title-text">量化选股大师</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">{{a}}</view></view><view class="portfolio-profit"><view class="profit-label">总盈亏 / 收益率</view><view class="{{['profit-value', d]}}"> ¥ {{b}} ({{c}}%) </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">#{{e}}</view><view class="rank-info"><text class="rank-name">您 (本期收益)</text></view><view class="{{['rank-rate', h]}}">{{f}}{{g}}% </view></view><view class="leaderboard-list"><view wx:for="{{i}}" wx:for-item="item" wx:key="g" class="leaderboard-item"><view class="{{['rank-badge', item.b]}}"> #{{item.a}}</view><view class="user-info"><text class="user-name">{{item.c}}</text></view><view class="{{['user-rate', item.f]}}">{{item.d}}{{item.e}}% </view></view></view><view class="leaderboard-note"><text class="note-text">排名每日更新,收益基于系统信号的模拟交易。</text></view></view><view class="bottom-safe-area"></view></view></scroll-view></view>

+ 175 - 9
dist/dev/mp-weixin/pages/rank/rank.wxss

@@ -3,7 +3,7 @@
   display: flex;
   flex-direction: column;
   background: #f5f6fb;
-  min-height: 100vh;
+  height: 100vh;
 }
 .page-title-card {
   background: #ffffff;
@@ -12,21 +12,187 @@
   box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
   border-radius: 0;
 }
-.content-area {
-  padding: 32rpx;
-}
 .page-title-text {
   font-size: 36rpx;
   font-weight: 800;
   color: #3F51F7;
   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-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;
-  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;
 }

+ 38 - 0
dist/dev/mp-weixin/utils/api.js

@@ -46,5 +46,43 @@ const searchStocks = (keyword) => {
     });
   });
 };
+const getUserPortfolio = () => {
+  return new Promise((resolve, reject) => {
+    common_vendor.index.request({
+      url: `${BASE_URL}/v1/user/portfolio`,
+      method: "GET",
+      success: (res) => {
+        if (res.statusCode === 200 && res.data) {
+          resolve(res.data);
+        } else {
+          reject(new Error("服务暂不可用"));
+        }
+      },
+      fail: (err) => {
+        reject(new Error("网络异常"));
+      }
+    });
+  });
+};
+const getLeaderboard = () => {
+  return new Promise((resolve, reject) => {
+    common_vendor.index.request({
+      url: `${BASE_URL}/v1/rank/leaderboard`,
+      method: "GET",
+      success: (res) => {
+        if (res.statusCode === 200 && res.data) {
+          resolve(res.data);
+        } else {
+          reject(new Error("服务暂不可用"));
+        }
+      },
+      fail: (err) => {
+        reject(new Error("网络异常"));
+      }
+    });
+  });
+};
+exports.getLeaderboard = getLeaderboard;
 exports.getSuggestions = getSuggestions;
+exports.getUserPortfolio = getUserPortfolio;
 exports.searchStocks = searchStocks;

+ 342 - 15
src/pages/rank/rank.vue

@@ -4,15 +4,143 @@
     <view class="page-title-card">
       <text class="page-title-text">量化选股大师</text>
     </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>
 </template>
 
 <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>
 
 <style>
@@ -20,7 +148,7 @@
   display: flex;
   flex-direction: column;
   background: #f5f6fb;
-  min-height: 100vh;
+  height: 100vh;
 }
 
 .page-title-card {
@@ -31,10 +159,6 @@
   border-radius: 0;
 }
 
-.content-area {
-  padding: 32rpx;
-}
-
 .page-title-text {
   font-size: 36rpx;
   font-weight: 800;
@@ -42,13 +166,216 @@
   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-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;
-  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>

+ 40 - 0
src/utils/api.js

@@ -48,3 +48,43 @@ export const searchStocks = (keyword) => {
     })
   })
 }
+
+// 获取用户模拟资产
+export const getUserPortfolio = () => {
+  return new Promise((resolve, reject) => {
+    uni.request({
+      url: `${BASE_URL}/v1/user/portfolio`,
+      method: 'GET',
+      success: (res) => {
+        if (res.statusCode === 200 && res.data) {
+          resolve(res.data)
+        } else {
+          reject(new Error('服务暂不可用'))
+        }
+      },
+      fail: (err) => {
+        reject(new Error('网络异常'))
+      }
+    })
+  })
+}
+
+// 获取模拟交易排行榜
+export const getLeaderboard = () => {
+  return new Promise((resolve, reject) => {
+    uni.request({
+      url: `${BASE_URL}/v1/rank/leaderboard`,
+      method: 'GET',
+      success: (res) => {
+        if (res.statusCode === 200 && res.data) {
+          resolve(res.data)
+        } else {
+          reject(new Error('服务暂不可用'))
+        }
+      },
+      fail: (err) => {
+        reject(new Error('网络异常'))
+      }
+    })
+  })
+}