Sfoglia il codice sorgente

股票实时数据优化

Zhangbw 3 mesi fa
parent
commit
5d88ecfdc4

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

@@ -6824,8 +6824,8 @@ exports.onShow = onShow;
 exports.onUnload = onUnload;
 exports.onUnmounted = onUnmounted;
 exports.p = p;
+exports.reactive = reactive;
 exports.ref = ref;
 exports.resolveComponent = resolveComponent;
 exports.sr = sr;
 exports.t = t;
-exports.watch = watch;

+ 0 - 256
dist/dev/mp-weixin/components/StockListItem.js

@@ -1,256 +0,0 @@
-"use strict";
-const common_vendor = require("../common/vendor.js");
-const _sfc_main = {
-  __name: "StockListItem",
-  props: {
-    stock: {
-      type: Object,
-      required: true
-    },
-    showDelete: {
-      type: Boolean,
-      default: false
-    }
-  },
-  emits: ["delete"],
-  setup(__props, { emit }) {
-    const props = __props;
-    let componentInstance = null;
-    const deleteWidth = 60;
-    const moveX = common_vendor.ref(0);
-    let currentX = 0;
-    let autoResetTimer = null;
-    let checkTimer = null;
-    const AUTO_RESET_DELAY = 2500;
-    let isSlideOpen = false;
-    const forceReset = () => {
-      console.log("[滑动删除] 强制还原");
-      moveX.value = 0;
-      currentX = 0;
-      isSlideOpen = false;
-      clearAutoResetTimer();
-    };
-    const startAutoResetTimer = () => {
-      clearAutoResetTimer();
-      isSlideOpen = true;
-      console.log("[滑动删除] 启动自动还原计时器");
-      autoResetTimer = setTimeout(() => {
-        console.log("[滑动删除] 主计时器触发");
-        forceReset();
-      }, AUTO_RESET_DELAY);
-      checkTimer = setInterval(() => {
-        if (isSlideOpen && moveX.value < 0) {
-          console.log("[滑动删除] 备用检查中...");
-        } else if (isSlideOpen && moveX.value === 0) {
-          isSlideOpen = false;
-          clearAutoResetTimer();
-        }
-      }, 500);
-      setTimeout(() => {
-        if (isSlideOpen) {
-          console.log("[滑动删除] 保险计时器触发");
-          forceReset();
-        }
-      }, AUTO_RESET_DELAY + 1e3);
-    };
-    const clearAutoResetTimer = () => {
-      if (autoResetTimer) {
-        clearTimeout(autoResetTimer);
-        autoResetTimer = null;
-      }
-      if (checkTimer) {
-        clearInterval(checkTimer);
-        checkTimer = null;
-      }
-    };
-    const canvasId = common_vendor.ref(`chart-${props.stock.code}-${Math.random().toString(36).slice(2, 11)}`);
-    const getMarketTag = (code) => {
-      if (code.startsWith("6"))
-        return "沪";
-      if (code.startsWith("0"))
-        return "深";
-      if (code.startsWith("3"))
-        return "创";
-      return "沪";
-    };
-    const getMarketClass = (code) => {
-      if (code.startsWith("6"))
-        return "market-sh";
-      if (code.startsWith("0"))
-        return "market-sz";
-      if (code.startsWith("3"))
-        return "market-cy";
-      return "market-sh";
-    };
-    const getChangeClass = (changePercent) => {
-      if (!changePercent)
-        return "";
-      const str = String(changePercent).replace("%", "").replace("+", "");
-      const value = parseFloat(str);
-      if (value > 0)
-        return "change-up";
-      if (value < 0)
-        return "change-down";
-      return "";
-    };
-    const formatChangePercent = (changePercent) => {
-      if (!changePercent)
-        return "--";
-      return String(changePercent);
-    };
-    const formatPrice = (price) => {
-      if (!price)
-        return "--";
-      return parseFloat(price).toFixed(2);
-    };
-    const hasValidChange = (changePercent) => {
-      if (!changePercent)
-        return false;
-      const str = String(changePercent).replace("%", "").replace("+", "");
-      const value = parseFloat(str);
-      return value !== 0 && !isNaN(value);
-    };
-    const drawTrendChart = (instance) => {
-      let trendData = props.stock.trendData;
-      if (!trendData || !Array.isArray(trendData) || trendData.length === 0) {
-        trendData = generateMockTrendData();
-      }
-      const ctx = common_vendor.index.createCanvasContext(canvasId.value, instance);
-      const width = 100;
-      const height = 30;
-      const padding = 2;
-      const maxValue = Math.max(...trendData);
-      const minValue = Math.min(...trendData);
-      const dataRange = maxValue - minValue;
-      const avgValue = (maxValue + minValue) / 2;
-      const minRange = avgValue * 0.03 || 1;
-      const range = Math.max(dataRange, minRange);
-      const baseValue = trendData[0];
-      const baseY = height - padding - (baseValue - minValue) / range * (height - padding * 2);
-      const changePercent = parseFloat(String(props.stock.changePercent || "0").replace("%", "").replace("+", ""));
-      const isUp = changePercent >= 0;
-      const lineColor = isUp ? "#FF3B30" : "#34C759";
-      const fillColor = isUp ? "rgba(255, 59, 48, 0.15)" : "rgba(52, 199, 89, 0.15)";
-      ctx.beginPath();
-      ctx.setStrokeStyle("#e0e0e0");
-      ctx.setLineWidth(0.5);
-      ctx.setLineDash([2, 2], 0);
-      ctx.moveTo(padding, baseY);
-      ctx.lineTo(width - padding, baseY);
-      ctx.stroke();
-      ctx.setLineDash([], 0);
-      ctx.beginPath();
-      ctx.moveTo(padding, baseY);
-      trendData.forEach((value, index) => {
-        const x = padding + index / (trendData.length - 1) * (width - padding * 2);
-        const y = height - padding - (value - minValue) / range * (height - padding * 2);
-        ctx.lineTo(x, y);
-      });
-      ctx.lineTo(width - padding, baseY);
-      ctx.closePath();
-      ctx.setFillStyle(fillColor);
-      ctx.fill();
-      ctx.beginPath();
-      trendData.forEach((value, index) => {
-        const x = padding + index / (trendData.length - 1) * (width - padding * 2);
-        const y = height - padding - (value - minValue) / range * (height - padding * 2);
-        if (index === 0) {
-          ctx.moveTo(x, y);
-        } else {
-          ctx.lineTo(x, y);
-        }
-      });
-      ctx.setStrokeStyle(lineColor);
-      ctx.setLineWidth(1.5);
-      ctx.stroke();
-      ctx.draw();
-    };
-    const generateMockTrendData = () => {
-      const changePercent = parseFloat(String(props.stock.changePercent || "0").replace("%", "").replace("+", ""));
-      const points = 15;
-      const data = [];
-      let baseValue = 100;
-      const trend = changePercent / 100;
-      for (let i = 0; i < points; i++) {
-        const randomChange = (Math.random() - 0.5) * 6;
-        const trendChange = i / points * trend * 100;
-        baseValue = baseValue + randomChange + trendChange / points;
-        data.push(baseValue);
-      }
-      return data;
-    };
-    const handleMoveChange = (e) => {
-      currentX = e.detail.x;
-    };
-    const handleMoveEnd = () => {
-      if (!props.showDelete)
-        return;
-      if (currentX < -deleteWidth / 3) {
-        moveX.value = -deleteWidth;
-        startAutoResetTimer();
-      } else {
-        forceReset();
-      }
-    };
-    const handleDelete = () => {
-      forceReset();
-      emit("delete");
-    };
-    common_vendor.onMounted(() => {
-      componentInstance = common_vendor.getCurrentInstance();
-      common_vendor.nextTick$1(() => {
-        setTimeout(() => {
-          drawTrendChart(componentInstance);
-        }, 300);
-      });
-    });
-    common_vendor.watch(() => props.stock.trendData, (newData) => {
-      if (newData && componentInstance) {
-        common_vendor.nextTick$1(() => {
-          drawTrendChart(componentInstance);
-        });
-      }
-    }, { deep: true });
-    common_vendor.watch(() => props.stock.changePercent, () => {
-      if (componentInstance) {
-        common_vendor.nextTick$1(() => {
-          drawTrendChart(componentInstance);
-        });
-      }
-    });
-    common_vendor.watch(moveX, (newVal) => {
-      if (newVal === 0 && isSlideOpen) {
-        isSlideOpen = false;
-        clearAutoResetTimer();
-      }
-    });
-    common_vendor.onUnmounted(() => {
-      clearAutoResetTimer();
-    });
-    return (_ctx, _cache) => {
-      return common_vendor.e({
-        a: common_vendor.t(__props.stock.name),
-        b: common_vendor.t(getMarketTag(__props.stock.code)),
-        c: common_vendor.n(getMarketClass(__props.stock.code)),
-        d: common_vendor.t(__props.stock.code),
-        e: canvasId.value,
-        f: canvasId.value,
-        g: hasValidChange(__props.stock.changePercent)
-      }, hasValidChange(__props.stock.changePercent) ? {
-        h: common_vendor.t(formatChangePercent(__props.stock.changePercent)),
-        i: common_vendor.n(getChangeClass(__props.stock.changePercent))
-      } : {}, {
-        j: common_vendor.t(formatPrice(__props.stock.currentPrice)),
-        k: __props.showDelete
-      }, __props.showDelete ? {
-        l: common_vendor.o(handleDelete)
-      } : {}, {
-        m: moveX.value,
-        n: common_vendor.o(handleMoveChange),
-        o: common_vendor.o(handleMoveEnd)
-      });
-    };
-  }
-};
-const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-29af7fd7"], ["__file", "D:/program/gupiao-wx/src/components/StockListItem.vue"]]);
-wx.createComponent(Component);

+ 0 - 4
dist/dev/mp-weixin/components/StockListItem.json

@@ -1,4 +0,0 @@
-{
-  "component": true,
-  "usingComponents": {}
-}

+ 0 - 1
dist/dev/mp-weixin/components/StockListItem.wxml

@@ -1 +0,0 @@
-<view class="stock-item-wrapper data-v-29af7fd7"><movable-area class="movable-area data-v-29af7fd7"><movable-view class="movable-view data-v-29af7fd7" direction="horizontal" x="{{m}}" damping="{{40}}" friction="{{5}}" out-of-bounds="{{false}}" bindchange="{{n}}" bindtouchend="{{o}}"><view class="stock-list-item data-v-29af7fd7"><view class="stock-left data-v-29af7fd7"><view class="stock-name-row data-v-29af7fd7"><text class="stock-name data-v-29af7fd7">{{a}}</text><text class="{{['data-v-29af7fd7', 'stock-tag', c]}}">{{b}}</text></view><text class="stock-code data-v-29af7fd7">{{d}}</text></view><view class="stock-chart data-v-29af7fd7"><block wx:if="{{r0}}"><canvas canvas-id="{{e}}" id="{{f}}" class="trend-canvas data-v-29af7fd7"></canvas></block></view><view class="stock-right data-v-29af7fd7"><view wx:if="{{g}}" class="{{['data-v-29af7fd7', 'change-percent', i]}}">{{h}}</view><text class="stock-price data-v-29af7fd7">{{j}}</text></view><view wx:if="{{k}}" class="delete-action data-v-29af7fd7" catchtap="{{l}}"><view class="delete-icon-wrapper data-v-29af7fd7"><text class="delete-icon data-v-29af7fd7">−</text></view></view></view></movable-view></movable-area></view>

+ 0 - 134
dist/dev/mp-weixin/components/StockListItem.wxss

@@ -1,134 +0,0 @@
-
-.stock-item-wrapper.data-v-29af7fd7 {
-  position: relative;
-  height: 120rpx;
-  overflow: hidden;
-  background: #ffffff;
-}
-.movable-area.data-v-29af7fd7 {
-  width: 100%;
-  height: 100%;
-}
-.movable-view.data-v-29af7fd7 {
-  width: calc(100% + 120rpx);
-  height: 100%;
-}
-.stock-list-item.data-v-29af7fd7 {
-  display: flex;
-  align-items: center;
-  padding: 24rpx 0;
-  height: 100%;
-  box-sizing: border-box;
-  background: #ffffff;
-  border-bottom: 1rpx solid #f1f2f6;
-}
-.stock-item-wrapper:last-child .stock-list-item.data-v-29af7fd7 {
-  border-bottom: none;
-}
-
-/* 滑动删除按钮 */
-.delete-action.data-v-29af7fd7 {
-  flex-shrink: 0;
-  width: 120rpx;
-  height: 100%;
-  background: #ffffff;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.delete-icon-wrapper.data-v-29af7fd7 {
-  width: 64rpx;
-  height: 64rpx;
-  background: #FF3B30;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 4rpx 12rpx rgba(255, 59, 48, 0.3);
-}
-.delete-icon.data-v-29af7fd7 {
-  font-size: 32rpx;
-  color: #ffffff;
-  font-weight: bold;
-}
-
-/* 左侧股票信息 */
-.stock-left.data-v-29af7fd7 {
-  flex-shrink: 0;
-  width: 160rpx;
-  display: flex;
-  flex-direction: column;
-}
-.stock-name-row.data-v-29af7fd7 {
-  display: flex;
-  align-items: center;
-  margin-bottom: 8rpx;
-}
-.stock-name.data-v-29af7fd7 {
-  font-size: 26rpx;
-  font-weight: 600;
-  color: #222222;
-  margin-right: 8rpx;
-}
-.stock-tag.data-v-29af7fd7 {
-  font-size: 18rpx;
-  padding: 2rpx 6rpx;
-  border-radius: 4rpx;
-  color: #ffffff;
-  font-weight: 500;
-}
-.market-sh.data-v-29af7fd7 {
-  background: #FF3B30;
-}
-.market-sz.data-v-29af7fd7 {
-  background: #34C759;
-}
-.market-cy.data-v-29af7fd7 {
-  background: #FF9500;
-}
-.stock-code.data-v-29af7fd7 {
-  font-size: 22rpx;
-  color: #9ca2b5;
-}
-
-/* 中间趋势图 */
-.stock-chart.data-v-29af7fd7 {
-  flex: 1;
-  height: 60rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin: 0 16rpx;
-}
-.trend-canvas.data-v-29af7fd7 {
-  width: 200rpx;
-  height: 60rpx;
-}
-
-/* 右侧涨跌幅和价格 */
-.stock-right.data-v-29af7fd7 {
-  flex-shrink: 0;
-  width: 120rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: flex-end;
-}
-.change-percent.data-v-29af7fd7 {
-  font-size: 26rpx;
-  font-weight: 700;
-  padding: 4rpx 12rpx;
-  border-radius: 6rpx;
-  margin-bottom: 8rpx;
-}
-.change-up.data-v-29af7fd7 {
-  background: #FF3B30;
-  color: #ffffff;
-}
-.change-down.data-v-29af7fd7 {
-  background: #34C759;
-  color: #ffffff;
-}
-.stock-price.data-v-29af7fd7 {
-  font-size: 22rpx;
-  color: #666a7f;
-}

+ 133 - 8
dist/dev/mp-weixin/pages/pool/pool.js

@@ -1,7 +1,7 @@
 "use strict";
 const common_vendor = require("../../common/vendor.js");
 const utils_auth = require("../../utils/auth.js");
-require("../../utils/api.js");
+const utils_api = require("../../utils/api.js");
 if (!Math) {
   (PerformanceCard + HistorySearchCard + PurchaseModal)();
 }
@@ -14,6 +14,41 @@ const _sfc_main = {
     const isPurchased = common_vendor.ref(false);
     const showModal = common_vendor.ref(false);
     const isLoggedIn = common_vendor.ref(false);
+    const isPageVisible = common_vendor.ref(false);
+    const stockList = common_vendor.ref([]);
+    let refreshTimer = null;
+    const getChangeClass = (changePercent) => {
+      if (!changePercent || changePercent === "-")
+        return "";
+      return changePercent.startsWith("+") ? "text-red" : "text-green";
+    };
+    const getRandomInterval = () => 2e3 + Math.random() * 1e3;
+    const startAutoRefresh = () => {
+      if (!isPageVisible.value)
+        return;
+      stopAutoRefresh();
+      const scheduleNextRefresh = () => {
+        if (!isPageVisible.value) {
+          stopAutoRefresh();
+          return;
+        }
+        refreshTimer = setTimeout(async () => {
+          if (!isPageVisible.value) {
+            stopAutoRefresh();
+            return;
+          }
+          await loadStockPool();
+          scheduleNextRefresh();
+        }, getRandomInterval());
+      };
+      scheduleNextRefresh();
+    };
+    const stopAutoRefresh = () => {
+      if (refreshTimer) {
+        clearTimeout(refreshTimer);
+        refreshTimer = null;
+      }
+    };
     const checkLogin = () => {
       isLoggedIn.value = utils_auth.isLoggedIn();
       return isLoggedIn.value;
@@ -21,6 +56,7 @@ const _sfc_main = {
     const checkPurchaseStatus = () => {
       if (!checkLogin()) {
         isPurchased.value = false;
+        stopAutoRefresh();
         return;
       }
       try {
@@ -29,18 +65,36 @@ const _sfc_main = {
           const now = Date.now();
           if (now < purchaseInfo.expireTime) {
             isPurchased.value = true;
+            loadAndStartRefresh();
           } else {
             common_vendor.index.removeStorageSync("pool_purchase");
             isPurchased.value = false;
+            stopAutoRefresh();
           }
         } else {
           isPurchased.value = false;
+          stopAutoRefresh();
         }
       } catch (e) {
         console.error("检查购买状态失败:", e);
         isPurchased.value = false;
+        stopAutoRefresh();
+      }
+    };
+    const loadStockPool = async () => {
+      try {
+        const res = await utils_api.getStockPoolList(1);
+        if (res.code === 200 && res.data) {
+          stockList.value = res.data;
+        }
+      } catch (e) {
+        console.error("加载股票池失败:", e);
       }
     };
+    const loadAndStartRefresh = async () => {
+      await loadStockPool();
+      startAutoRefresh();
+    };
     const showPurchaseModal = () => {
       if (!checkLogin()) {
         common_vendor.index.showModal({
@@ -73,6 +127,7 @@ const _sfc_main = {
       isPurchased.value = true;
       closePurchaseModal();
       common_vendor.index.showToast({ title: "解锁成功", icon: "success" });
+      loadAndStartRefresh();
     };
     const onHistorySearch = ({ startMonth, endMonth }) => {
       const formatMonth = (monthStr) => {
@@ -85,15 +140,73 @@ const _sfc_main = {
         duration: 2e3
       });
     };
+    const addToMyStocks = async (stock) => {
+      if (!checkLogin()) {
+        common_vendor.index.showModal({
+          title: "登录提示",
+          content: "添加自选股票需要登录,是否前往登录?",
+          confirmText: "去登录",
+          cancelText: "取消",
+          success: (res) => {
+            if (res.confirm) {
+              common_vendor.index.navigateTo({ url: "/pages/login/login" });
+            }
+          }
+        });
+        return;
+      }
+      try {
+        common_vendor.index.showLoading({ title: "添加中..." });
+        let currentPrice = null;
+        try {
+          const quoteRes = await utils_api.getStockQuotes(stock.code);
+          if (quoteRes.code === 200 && quoteRes.data && quoteRes.data.length > 0) {
+            currentPrice = quoteRes.data[0].currentPrice;
+          }
+        } catch (e) {
+          console.error("获取行情数据失败:", e);
+        }
+        const addRes = await utils_api.addUserStock({
+          stockCode: stock.code,
+          stockName: stock.name,
+          currentPrice,
+          poolType: 1
+          // 超短池
+        });
+        common_vendor.index.hideLoading();
+        if (addRes.code === 200 && addRes.data === true) {
+          common_vendor.index.showToast({ title: "添加成功", icon: "success" });
+        } else if (addRes.code === 200 && addRes.data === false) {
+          common_vendor.index.showToast({ title: "股票已存在", icon: "none" });
+        } else {
+          common_vendor.index.showToast({ title: addRes.message || "添加失败", icon: "none" });
+        }
+      } catch (e) {
+        common_vendor.index.hideLoading();
+        console.error("添加股票失败:", e);
+        common_vendor.index.showToast({ title: "添加失败", icon: "none" });
+      }
+    };
     common_vendor.onLoad(() => {
-      console.log("[超短池] 登录状态:", checkLogin());
+      console.log("[超短池] onLoad");
+      isPageVisible.value = true;
       checkPurchaseStatus();
     });
     common_vendor.onShow(() => {
-      console.log("[超短池] 登录状态:", checkLogin());
+      console.log("[超短池] onShow");
+      isPageVisible.value = true;
       checkPurchaseStatus();
       common_vendor.index.setNavigationBarTitle({ title: "量化交易大师" });
     });
+    common_vendor.onHide(() => {
+      console.log("[超短池] onHide");
+      isPageVisible.value = false;
+      stopAutoRefresh();
+    });
+    common_vendor.onUnmounted(() => {
+      isPageVisible.value = false;
+      stopAutoRefresh();
+    });
     return (_ctx, _cache) => {
       return common_vendor.e({
         a: common_vendor.p({
@@ -104,11 +217,23 @@ const _sfc_main = {
         b: !isPurchased.value
       }, !isPurchased.value ? {
         c: common_vendor.o(showPurchaseModal)
-      } : {}, {
-        d: common_vendor.o(onHistorySearch),
-        e: common_vendor.o(closePurchaseModal),
-        f: common_vendor.o(handlePurchase),
-        g: common_vendor.p({
+      } : {
+        d: common_vendor.f(stockList.value, (stock, index, i0) => {
+          return {
+            a: common_vendor.t(stock.name),
+            b: common_vendor.t(stock.code),
+            c: common_vendor.t(stock.currentPrice || "-"),
+            d: common_vendor.t(stock.changePercent || "-"),
+            e: common_vendor.n(getChangeClass(stock.changePercent)),
+            f: common_vendor.o(($event) => addToMyStocks(stock), index),
+            g: index
+          };
+        })
+      }, {
+        e: common_vendor.o(onHistorySearch),
+        f: common_vendor.o(closePurchaseModal),
+        g: common_vendor.o(handlePurchase),
+        h: common_vendor.p({
           visible: showModal.value,
           icon: "💰",
           title: "打赏解锁",

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

@@ -1 +1 @@
-<view class="page-container"><scroll-view class="scroll-view" scroll-y><view class="content-wrapper"><view class="pool-header-section"><view class="pool-header"><text class="pool-icon">⚡</text><text class="pool-title">超短精选池</text></view><text class="pool-desc">今日更新,高频捕捉短期爆发机会</text></view><performance-card wx:if="{{a}}" u-i="52f91f2d-0" bind:__l="__l" u-p="{{a}}"/><view class="card pool-card"><view wx:if="{{b}}" class="locked-content"><view class="lock-icon-wrapper"><text class="lock-icon">🔒</text></view><text class="lock-text">权限锁定: 查看 超短精选池 </text><text class="lock-desc">超短池为每日实时更新,捕捉短期爆发机会。</text><view class="unlock-button" bindtap="{{c}}"><text class="unlock-button-text">立即打赏</text></view></view><view wx:else class="unlocked-content"><text class="unlocked-tip">您已解锁,可以查看超短精选池内容</text><view class="stock-list-placeholder"><text class="placeholder-text">股票池内容将在此显示</text></view></view></view><history-search-card bindsearch="{{d}}" u-i="52f91f2d-1" bind:__l="__l"/><view class="bottom-safe-area"></view></view></scroll-view><purchase-modal wx:if="{{g}}" bindclose="{{e}}" bindconfirm="{{f}}" u-i="52f91f2d-2" bind:__l="__l" u-p="{{g}}"/></view>
+<view class="page-container"><scroll-view class="scroll-view" scroll-y><view class="content-wrapper"><view class="pool-header-section"><view class="pool-header"><text class="pool-icon">⚡</text><text class="pool-title">超短精选池</text></view><text class="pool-desc">今日更新,高频捕捉短期爆发机会</text></view><performance-card wx:if="{{a}}" u-i="52f91f2d-0" bind:__l="__l" u-p="{{a}}"/><view class="card pool-card"><view wx:if="{{b}}" class="locked-content"><view class="lock-icon-wrapper"><text class="lock-icon">🔒</text></view><text class="lock-text">权限锁定: 查看 超短精选池 </text><text class="lock-desc">超短池为每日实时更新,捕捉短期爆发机会。</text><view class="unlock-button" bindtap="{{c}}"><text class="unlock-button-text">立即打赏</text></view></view><view wx:else class="unlocked-content"><view class="unlocked-header"><view class="unlocked-dot"></view><text class="unlocked-tip">已解锁内容</text></view><view class="stock-list"><view wx:for="{{d}}" wx:for-item="stock" wx:key="g" class="stock-item"><view class="stock-main"><view class="stock-info"><text class="stock-name">{{stock.a}}</text><text class="stock-code">{{stock.b}}</text></view><view class="stock-quote"><text class="stock-price">{{stock.c}}</text><text class="{{['stock-change', stock.e]}}">{{stock.d}}</text></view></view><view class="stock-action"><view class="add-btn" bindtap="{{stock.f}}"><text class="add-btn-text">+ 自选</text></view></view></view></view></view></view><history-search-card bindsearch="{{e}}" u-i="52f91f2d-1" bind:__l="__l"/><view class="bottom-safe-area"></view></view></scroll-view><purchase-modal wx:if="{{h}}" bindclose="{{f}}" bindconfirm="{{g}}" u-i="52f91f2d-2" bind:__l="__l" u-p="{{h}}"/></view>

+ 90 - 11
dist/dev/mp-weixin/pages/pool/pool.wxss

@@ -84,23 +84,102 @@
   color: #ffffff;
 }
 .unlocked-content {
-  margin-top: 32rpx;
+  padding: 8rpx 0;
+}
+.unlocked-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 32rpx;
+}
+.unlocked-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #3abf81;
+  margin-right: 12rpx;
 }
 .unlocked-tip {
   font-size: 26rpx;
+  font-weight: 500;
   color: #3abf81;
-  margin-bottom: 24rpx;
-  display: block;
+  letter-spacing: 1rpx;
 }
-.stock-list-placeholder {
-  padding: 60rpx 0;
-  text-align: center;
-  background: #f7f8fc;
-  border-radius: 16rpx;
+.stock-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
 }
-.placeholder-text {
-  font-size: 26rpx;
-  color: #9ca2b5;
+.stock-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 28rpx 24rpx;
+  background: #fafbfc;
+  border-radius: 20rpx;
+  transition: all 0.2s ease;
+}
+.stock-main {
+  display: flex;
+  align-items: center;
+  flex: 1;
+}
+.stock-info {
+  display: flex;
+  flex-direction: column;
+  min-width: 160rpx;
+}
+.stock-name {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 6rpx;
+  letter-spacing: 1rpx;
+}
+.stock-code {
+  font-size: 22rpx;
+  color: #9ca3af;
+  font-weight: 400;
+}
+.stock-quote {
+  display: flex;
+  align-items: center;
+  margin-left: 32rpx;
+}
+.stock-price {
+  font-size: 32rpx;
+  font-weight: 800;
+  color: #1a1a2e;
+  margin-right: 16rpx;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
+}
+.stock-change {
+  font-size: 24rpx;
+  font-weight: 600;
+  padding: 6rpx 12rpx;
+  border-radius: 8rpx;
+}
+.text-red {
+  color: #ef4444;
+  background: rgba(239, 68, 68, 0.1);
+}
+.text-green {
+  color: #22c55e;
+  background: rgba(34, 197, 94, 0.1);
+}
+.stock-action {
+  margin-left: 20rpx;
+}
+.add-btn {
+  background: #5B5AEA;
+  border-radius: 40rpx;
+  padding: 16rpx 28rpx;
+  box-shadow: 0 8rpx 20rpx rgba(91, 90, 234, 0.3);
+}
+.add-btn-text {
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #ffffff;
+  letter-spacing: 1rpx;
 }
 .bottom-safe-area {
   height: 80rpx;

+ 180 - 53
dist/dev/mp-weixin/pages/rank/rank.js

@@ -2,19 +2,64 @@
 const common_vendor = require("../../common/vendor.js");
 const utils_auth = require("../../utils/auth.js");
 const utils_api = require("../../utils/api.js");
-if (!Math) {
-  StockListItem();
-}
-const StockListItem = () => "../../components/StockListItem.js";
 const _sfc_main = {
   __name: "rank",
   setup(__props) {
+    let componentInstance = null;
     const isLoggedIn = common_vendor.ref(false);
     const myStocks = common_vendor.ref([]);
     const viewMode = common_vendor.ref("list");
     const isLoading = common_vendor.ref(false);
     const lastLoadTime = common_vendor.ref(0);
     const CACHE_DURATION = 5e3;
+    const isPageVisible = common_vendor.ref(false);
+    const SLIDE_WIDTH = 100;
+    const slideStates = common_vendor.reactive({});
+    let slideTimers = {};
+    const AUTO_RESET_DELAY = 2e3;
+    const initSlideState = (code) => {
+      if (!slideStates[code]) {
+        slideStates[code] = { x: 0, currentX: 0 };
+      }
+    };
+    const handleSlideChange = (e, code) => {
+      initSlideState(code);
+      slideStates[code].currentX = e.detail.x;
+    };
+    const handleSlideEnd = (code) => {
+      initSlideState(code);
+      const state = slideStates[code];
+      if (state.currentX < -20) {
+        state.x = -SLIDE_WIDTH;
+        startAutoResetTimer(code);
+      } else {
+        resetSlide(code);
+      }
+    };
+    const resetSlide = (code) => {
+      if (slideStates[code]) {
+        slideStates[code].x = 0;
+        slideStates[code].currentX = 0;
+      }
+      clearSlideTimer(code);
+    };
+    const startAutoResetTimer = (code) => {
+      clearSlideTimer(code);
+      slideTimers[code] = setTimeout(() => {
+        resetSlide(code);
+      }, AUTO_RESET_DELAY);
+    };
+    const clearSlideTimer = (code) => {
+      if (slideTimers[code]) {
+        clearTimeout(slideTimers[code]);
+        delete slideTimers[code];
+      }
+    };
+    const clearAllSlideTimers = () => {
+      Object.keys(slideTimers).forEach((code) => {
+        clearSlideTimer(code);
+      });
+    };
     const setViewMode = (mode) => {
       viewMode.value = mode;
     };
@@ -86,6 +131,89 @@ const _sfc_main = {
         return "market-cy";
       return "market-sh";
     };
+    const drawTrendChart = (stock) => {
+      if (!componentInstance)
+        return;
+      const canvasId = "chart-" + stock.code;
+      let trendData = stock.trendData;
+      if (!trendData || !Array.isArray(trendData) || trendData.length === 0) {
+        trendData = generateMockTrendData(stock.changePercent);
+      }
+      const ctx = common_vendor.index.createCanvasContext(canvasId, componentInstance);
+      const width = 100;
+      const height = 30;
+      const padding = 2;
+      const maxValue = Math.max(...trendData);
+      const minValue = Math.min(...trendData);
+      const dataRange = maxValue - minValue;
+      const avgValue = (maxValue + minValue) / 2;
+      const minRange = avgValue * 0.03 || 1;
+      const range = Math.max(dataRange, minRange);
+      const baseValue = trendData[0];
+      const baseY = height - padding - (baseValue - minValue) / range * (height - padding * 2);
+      const changePercent = parseFloat(String(stock.changePercent || "0").replace("%", "").replace("+", ""));
+      const isUp = changePercent >= 0;
+      const lineColor = isUp ? "#ef4444" : "#22c55e";
+      const fillColor = isUp ? "rgba(239, 68, 68, 0.15)" : "rgba(34, 197, 94, 0.15)";
+      ctx.beginPath();
+      ctx.setStrokeStyle("#e5e7eb");
+      ctx.setLineWidth(0.5);
+      ctx.setLineDash([2, 2], 0);
+      ctx.moveTo(padding, baseY);
+      ctx.lineTo(width - padding, baseY);
+      ctx.stroke();
+      ctx.setLineDash([], 0);
+      ctx.beginPath();
+      ctx.moveTo(padding, baseY);
+      trendData.forEach((value, index) => {
+        const x = padding + index / (trendData.length - 1) * (width - padding * 2);
+        const y = height - padding - (value - minValue) / range * (height - padding * 2);
+        ctx.lineTo(x, y);
+      });
+      ctx.lineTo(width - padding, baseY);
+      ctx.closePath();
+      ctx.setFillStyle(fillColor);
+      ctx.fill();
+      ctx.beginPath();
+      trendData.forEach((value, index) => {
+        const x = padding + index / (trendData.length - 1) * (width - padding * 2);
+        const y = height - padding - (value - minValue) / range * (height - padding * 2);
+        if (index === 0) {
+          ctx.moveTo(x, y);
+        } else {
+          ctx.lineTo(x, y);
+        }
+      });
+      ctx.setStrokeStyle(lineColor);
+      ctx.setLineWidth(1.5);
+      ctx.stroke();
+      ctx.draw();
+    };
+    const generateMockTrendData = (changePercent) => {
+      const change = parseFloat(String(changePercent || "0").replace("%", "").replace("+", ""));
+      const points = 15;
+      const data = [];
+      let baseValue = 100;
+      const trend = change / 100;
+      for (let i = 0; i < points; i++) {
+        const randomChange = (Math.random() - 0.5) * 6;
+        const trendChange = i / points * trend * 100;
+        baseValue = baseValue + randomChange + trendChange / points;
+        data.push(baseValue);
+      }
+      return data;
+    };
+    const drawAllTrendCharts = () => {
+      if (myStocks.value.length === 0)
+        return;
+      common_vendor.nextTick$1(() => {
+        setTimeout(() => {
+          myStocks.value.forEach((stock) => {
+            drawTrendChart(stock);
+          });
+        }, 300);
+      });
+    };
     const loadMyStocks = async (forceRefresh = false) => {
       console.log("[我的股票] loadMyStocks 开始执行, isLoggedIn=", isLoggedIn.value, "forceRefresh=", forceRefresh);
       if (!isLoggedIn.value) {
@@ -135,6 +263,7 @@ const _sfc_main = {
         await fetchIndexData();
         if (myStocks.value.length > 0) {
           await refreshAllQuotes();
+          drawAllTrendCharts();
         }
         startAutoRefresh();
       } catch (e) {
@@ -180,12 +309,20 @@ const _sfc_main = {
       }
     };
     const startAutoRefresh = () => {
-      if (!isLoggedIn.value)
+      if (!isLoggedIn.value || !isPageVisible.value)
         return;
       stopAutoRefresh();
       const scheduleNextRefresh = () => {
+        if (!isPageVisible.value) {
+          stopAutoRefresh();
+          return;
+        }
         const delay = 3e3 + Math.random() * 1e3;
         refreshTimer = setTimeout(async () => {
+          if (!isPageVisible.value) {
+            stopAutoRefresh();
+            return;
+          }
           await fetchIndexData();
           if (myStocks.value.length > 0) {
             await refreshAllQuotes();
@@ -204,36 +341,9 @@ const _sfc_main = {
     const goToLogin = () => {
       common_vendor.index.navigateTo({ url: "/pages/login/login" });
     };
-    const handleStockClick = async (stockItem, idx) => {
-      console.log("点击股票:", stockItem.name, idx);
-      try {
-        const quoteRes = await utils_api.getStockQuotes(stockItem.code);
-        if (quoteRes.code === 200 && quoteRes.data && quoteRes.data.length > 0) {
-          const quoteData = quoteRes.data[0];
-          const stock = myStocks.value[idx];
-          if (stock) {
-            stock.priceChange = quoteData.priceChange;
-            stock.changePercent = quoteData.changePercent;
-            stock.currentPrice = quoteData.currentPrice;
-            stock.name = quoteData.stockName || stock.name;
-            stock.trendData = quoteData.trendData || null;
-            if (stock.addPrice && quoteData.currentPrice) {
-              const addPrice = parseFloat(stock.addPrice);
-              const currentPrice = parseFloat(quoteData.currentPrice);
-              if (addPrice > 0) {
-                const profit = ((currentPrice - addPrice) / addPrice * 100).toFixed(2);
-                stock.profitPercent = profit >= 0 ? `+${profit}%` : `${profit}%`;
-              }
-            }
-          }
-        }
-      } catch (e) {
-        console.error("刷新股票行情失败:", e);
-      }
-      common_vendor.index.showToast({ title: "股票详情功能开发中", icon: "none" });
-    };
     const removeStock = async (idx) => {
       const stock = myStocks.value[idx];
+      resetSlide(stock.code);
       common_vendor.index.showModal({
         title: "确认删除",
         content: `确定要删除 ${stock.name} 吗?`,
@@ -243,6 +353,8 @@ const _sfc_main = {
           if (res.confirm) {
             try {
               await utils_api.deleteUserStock(stock.code);
+              delete slideStates[stock.code];
+              clearSlideTimer(stock.code);
               myStocks.value.splice(idx, 1);
               common_vendor.index.showToast({ title: "删除成功", icon: "success" });
               if (myStocks.value.length === 0) {
@@ -258,11 +370,14 @@ const _sfc_main = {
     };
     common_vendor.onLoad(() => {
       console.log("[我的股票] onLoad 触发");
+      componentInstance = common_vendor.getCurrentInstance();
       isLoggedIn.value = utils_auth.isLoggedIn();
+      isPageVisible.value = true;
       loadMyStocks(true);
     });
     common_vendor.onShow(() => {
       console.log("[我的股票] onShow 触发");
+      isPageVisible.value = true;
       const wasLoggedIn = isLoggedIn.value;
       isLoggedIn.value = utils_auth.isLoggedIn();
       if (wasLoggedIn !== isLoggedIn.value) {
@@ -273,10 +388,15 @@ const _sfc_main = {
       common_vendor.index.setNavigationBarTitle({ title: "量化交易大师" });
     });
     common_vendor.onHide(() => {
+      console.log("[我的股票] onHide 触发");
+      isPageVisible.value = false;
       stopAutoRefresh();
     });
     common_vendor.onUnload(() => {
+      console.log("[我的股票] onUnload 触发");
+      isPageVisible.value = false;
       stopAutoRefresh();
+      clearAllSlideTimers();
     });
     return (_ctx, _cache) => {
       return common_vendor.e({
@@ -292,21 +412,29 @@ const _sfc_main = {
         j: common_vendor.o(($event) => setViewMode("list")),
         k: viewMode.value === "table" ? 1 : "",
         l: common_vendor.o(($event) => setViewMode("table")),
-        m: common_vendor.f(myStocks.value, (stock, index, i0) => {
+        m: common_vendor.t(myStocks.value.length),
+        n: common_vendor.f(myStocks.value, (stock, index, i0) => {
+          var _a;
           return {
-            a: stock.code,
-            b: common_vendor.o(($event) => removeStock(index), stock.code),
-            c: common_vendor.o(($event) => handleStockClick(stock, index), stock.code),
-            d: "2482292d-0-" + i0,
-            e: common_vendor.p({
-              stock,
-              ["show-delete"]: true
-            })
+            a: common_vendor.t(stock.name),
+            b: common_vendor.t(getMarketTag(stock.code)),
+            c: common_vendor.n(getMarketClass(stock.code)),
+            d: common_vendor.t(stock.code),
+            e: "chart-" + stock.code,
+            f: "chart-" + stock.code,
+            g: common_vendor.t(stock.currentPrice || "-"),
+            h: common_vendor.t(stock.changePercent || "-"),
+            i: common_vendor.n(getProfitClass(stock.changePercent)),
+            j: common_vendor.o(($event) => removeStock(index), stock.code),
+            k: ((_a = slideStates[stock.code]) == null ? void 0 : _a.x) || 0,
+            l: common_vendor.o((e) => handleSlideChange(e, stock.code), stock.code),
+            m: common_vendor.o(() => handleSlideEnd(stock.code), stock.code),
+            n: stock.code
           };
         }),
-        n: viewMode.value === "list",
-        o: myStocks.value.length === 0 ? 1 : "",
-        p: common_vendor.f(myStocks.value, (stock, index, i0) => {
+        o: viewMode.value === "list",
+        p: myStocks.value.length === 0 ? 1 : "",
+        q: common_vendor.f(myStocks.value, (stock, index, i0) => {
           return {
             a: common_vendor.t(stock.name),
             b: common_vendor.t(getMarketTag(stock.code)),
@@ -316,18 +444,17 @@ const _sfc_main = {
             f: common_vendor.t(formatPrice(stock.addPrice)),
             g: common_vendor.t(stock.profitPercent || "--"),
             h: common_vendor.n(getProfitClass(stock.profitPercent)),
-            i: stock.code,
-            j: common_vendor.o(($event) => handleStockClick(stock, index), stock.code)
+            i: stock.code
           };
         }),
-        q: viewMode.value === "table",
-        r: myStocks.value.length === 0 ? 1 : "",
-        s: myStocks.value.length === 0
+        r: viewMode.value === "table",
+        s: myStocks.value.length === 0 ? 1 : "",
+        t: myStocks.value.length === 0
       }, myStocks.value.length === 0 ? {} : {}, {
-        t: !isLoggedIn.value ? 1 : "",
-        v: !isLoggedIn.value
+        v: !isLoggedIn.value ? 1 : "",
+        w: !isLoggedIn.value
       }, !isLoggedIn.value ? {
-        w: common_vendor.o(goToLogin)
+        x: common_vendor.o(goToLogin)
       } : {});
     };
   }

+ 1 - 3
dist/dev/mp-weixin/pages/rank/rank.json

@@ -1,6 +1,4 @@
 {
   "navigationBarTitleText": "量化交易大师",
-  "usingComponents": {
-    "stock-list-item": "../../components/StockListItem"
-  }
+  "usingComponents": {}
 }

File diff suppressed because it is too large
+ 0 - 0
dist/dev/mp-weixin/pages/rank/rank.wxml


+ 200 - 58
dist/dev/mp-weixin/pages/rank/rank.wxss

@@ -121,57 +121,216 @@
   display: none !important;
 }
 
+/* 股票列表卡片 */
+.stock-list-card,
+.stock-table-card {
+  background: #ffffff;
+  border-radius: 32rpx;
+  padding: 32rpx;
+  box-shadow: 0 16rpx 48rpx rgba(37, 52, 94, 0.1);
+}
+.list-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 28rpx;
+}
+.list-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #3abf81;
+  margin-right: 12rpx;
+}
+.list-dot-blue {
+  background: #5B5AEA;
+}
+.list-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1a1a2e;
+  letter-spacing: 1rpx;
+}
+.list-count {
+  font-size: 24rpx;
+  color: #9ca3af;
+  margin-left: 12rpx;
+}
+
 /* 股票列表 */
 .stock-list {
   display: flex;
   flex-direction: column;
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 0 32rpx;
-  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+  gap: 20rpx;
 }
 
-/* 表格视图 */
-.stock-table {
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 0 24rpx;
-  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+/* 滑动删除容器 */
+.stock-item-wrapper {
+  position: relative;
+  height: 140rpx;
+  overflow: hidden;
+  border-radius: 20rpx;
+  background: #fafbfc;
+}
+.movable-area {
+  width: 100%;
+  height: 100%;
+}
+.movable-view {
+  width: calc(100% + 200rpx);
+  height: 100%;
+}
+.stock-item {
+  display: flex;
+  align-items: center;
+  padding: 28rpx 24rpx;
+  background: #fafbfc;
+  border-radius: 20rpx;
+  height: 100%;
+  box-sizing: border-box;
+}
+
+/* 滑动删除按钮 */
+.slide-delete-btn {
+  flex-shrink: 0;
+  width: 200rpx;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #fafbfc;
+}
+.delete-icon-circle {
+  width: 72rpx;
+  height: 72rpx;
+  background: #ef4444;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 6rpx 16rpx rgba(239, 68, 68, 0.4);
+}
+.delete-icon-text {
+  font-size: 40rpx;
+  color: #ffffff;
+  font-weight: bold;
+  line-height: 1;
+}
+.stock-main {
+  display: flex;
+  align-items: center;
+  flex: 1;
+}
+.stock-info {
+  display: flex;
+  flex-direction: column;
+  min-width: 140rpx;
+}
+.stock-name {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 6rpx;
+  letter-spacing: 1rpx;
+}
+.stock-code-row {
+  display: flex;
+  align-items: center;
+}
+.stock-tag {
+  font-size: 18rpx;
+  padding: 2rpx 8rpx;
+  border-radius: 6rpx;
+  color: #ffffff;
+  font-weight: 600;
+  margin-right: 8rpx;
+}
+.market-sh {
+  background: #ef4444;
+}
+.market-sz {
+  background: #22c55e;
 }
+.market-cy {
+  background: #f59e0b;
+}
+.stock-code {
+  font-size: 22rpx;
+  color: #9ca3af;
+  font-weight: 400;
+}
+.stock-quote {
+  display: flex;
+  align-items: center;
+  margin-left: 16rpx;
+}
+
+/* 趋势图 */
+.stock-chart {
+  flex: 1;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin: 0 16rpx;
+}
+.trend-canvas {
+  width: 200rpx;
+  height: 60rpx;
+}
+.stock-price {
+  font-size: 32rpx;
+  font-weight: 800;
+  color: #1a1a2e;
+  margin-right: 16rpx;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
+}
+.stock-change {
+  font-size: 24rpx;
+  font-weight: 600;
+  padding: 6rpx 12rpx;
+  border-radius: 8rpx;
+}
+
+/* 表格视图 */
 .table-header {
   display: flex;
   align-items: center;
-  padding: 24rpx 0;
-  border-bottom: 1rpx solid #f1f2f6;
+  padding: 20rpx 16rpx;
+  background: #f8f9fc;
+  border-radius: 12rpx;
+  margin-bottom: 16rpx;
 }
 .table-header text {
   font-size: 24rpx;
-  color: #999999;
-  font-weight: 500;
+  color: #9ca3af;
+  font-weight: 600;
 }
 .th-name {
   flex: 1;
 }
 .th-date {
-  width: 160rpx;
+  width: 140rpx;
   text-align: center;
 }
 .th-price {
-  width: 140rpx;
+  width: 120rpx;
   text-align: center;
 }
 .th-profit {
-  width: 140rpx;
+  width: 120rpx;
   text-align: right;
 }
+.table-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+}
 .table-row {
   display: flex;
   align-items: center;
-  padding: 24rpx 0;
-  border-bottom: 1rpx solid #f1f2f6;
-}
-.table-row:last-child {
-  border-bottom: none;
+  padding: 24rpx 16rpx;
+  background: #fafbfc;
+  border-radius: 16rpx;
 }
 .td-name {
   flex: 1;
@@ -180,58 +339,41 @@
 }
 .td-name .stock-name {
   font-size: 28rpx;
-  font-weight: 600;
-  color: #222222;
+  font-weight: 700;
+  color: #1a1a2e;
   margin-bottom: 4rpx;
 }
-.stock-code-row {
-  display: flex;
-  align-items: center;
-}
-.stock-tag {
-  font-size: 18rpx;
-  padding: 2rpx 6rpx;
-  border-radius: 4rpx;
-  color: #ffffff;
-  font-weight: 500;
-  margin-right: 8rpx;
-}
-.market-sh {
-  background: #FF3B30;
-}
-.market-sz {
-  background: #34C759;
-}
-.market-cy {
-  background: #FF9500;
-}
-.stock-code {
-  font-size: 22rpx;
-  color: #9ca2b5;
-}
 .td-date {
-  width: 160rpx;
+  width: 140rpx;
   text-align: center;
   font-size: 24rpx;
-  color: #666666;
+  color: #6b7280;
 }
 .td-price {
-  width: 140rpx;
+  width: 120rpx;
   text-align: center;
   font-size: 26rpx;
-  color: #333333;
+  font-weight: 600;
+  color: #1a1a2e;
+}
+.td-profit-wrap {
+  width: 120rpx;
+  display: flex;
+  justify-content: flex-end;
 }
 .td-profit {
-  width: 140rpx;
-  text-align: right;
-  font-size: 26rpx;
+  font-size: 24rpx;
   font-weight: 600;
+  padding: 6rpx 12rpx;
+  border-radius: 8rpx;
 }
 .profit-up {
-  color: #FF3B30;
+  color: #ef4444;
+  background: rgba(239, 68, 68, 0.1);
 }
 .profit-down {
-  color: #34C759;
+  color: #22c55e;
+  background: rgba(34, 197, 94, 0.1);
 }
 
 /* 空状态 */

+ 76 - 9
dist/dev/mp-weixin/pages/strong/strong.js

@@ -13,13 +13,45 @@ const _sfc_main = {
   setup(__props) {
     const isPurchased = common_vendor.ref(false);
     const showModal = common_vendor.ref(false);
-    const stockList = common_vendor.ref([
-      { name: "信维通信", code: "300136" },
-      { name: "中国卫星", code: "600118" }
-    ]);
+    const isPageVisible = common_vendor.ref(false);
+    const stockList = common_vendor.ref([]);
+    let refreshTimer = null;
+    const getChangeClass = (changePercent) => {
+      if (!changePercent || changePercent === "-")
+        return "";
+      return changePercent.startsWith("+") ? "text-red" : "text-green";
+    };
+    const getRandomInterval = () => 2e3 + Math.random() * 1e3;
+    const startAutoRefresh = () => {
+      if (!isPageVisible.value)
+        return;
+      stopAutoRefresh();
+      const scheduleNextRefresh = () => {
+        if (!isPageVisible.value) {
+          stopAutoRefresh();
+          return;
+        }
+        refreshTimer = setTimeout(async () => {
+          if (!isPageVisible.value) {
+            stopAutoRefresh();
+            return;
+          }
+          await loadStockPool();
+          scheduleNextRefresh();
+        }, getRandomInterval());
+      };
+      scheduleNextRefresh();
+    };
+    const stopAutoRefresh = () => {
+      if (refreshTimer) {
+        clearTimeout(refreshTimer);
+        refreshTimer = null;
+      }
+    };
     const checkPurchaseStatus = () => {
       if (!utils_auth.isLoggedIn()) {
         isPurchased.value = false;
+        stopAutoRefresh();
         return;
       }
       try {
@@ -28,18 +60,36 @@ const _sfc_main = {
           const now = Date.now();
           if (now < purchaseInfo.expireTime) {
             isPurchased.value = true;
+            loadAndStartRefresh();
           } else {
             common_vendor.index.removeStorageSync("strong_pool_purchase");
             isPurchased.value = false;
+            stopAutoRefresh();
           }
         } else {
           isPurchased.value = false;
+          stopAutoRefresh();
         }
       } catch (e) {
         console.error("检查购买状态失败:", e);
         isPurchased.value = false;
+        stopAutoRefresh();
       }
     };
+    const loadStockPool = async () => {
+      try {
+        const res = await utils_api.getStockPoolList(2);
+        if (res.code === 200 && res.data) {
+          stockList.value = res.data;
+        }
+      } catch (e) {
+        console.error("加载股票池失败:", e);
+      }
+    };
+    const loadAndStartRefresh = async () => {
+      await loadStockPool();
+      startAutoRefresh();
+    };
     const showPurchaseModal = () => {
       if (!utils_auth.isLoggedIn()) {
         common_vendor.index.showModal({
@@ -71,6 +121,7 @@ const _sfc_main = {
       isPurchased.value = true;
       closePurchaseModal();
       common_vendor.index.showToast({ title: "解锁成功", icon: "success" });
+      loadAndStartRefresh();
     };
     const onHistorySearch = ({ startMonth, endMonth }) => {
       const formatMonth = (monthStr) => {
@@ -112,7 +163,9 @@ const _sfc_main = {
         const addRes = await utils_api.addUserStock({
           stockCode: stock.code,
           stockName: stock.name,
-          currentPrice
+          currentPrice,
+          poolType: 2
+          // 强势池
         });
         common_vendor.index.hideLoading();
         if (addRes.code === 200 && addRes.data === true) {
@@ -129,14 +182,25 @@ const _sfc_main = {
       }
     };
     common_vendor.onLoad(() => {
-      console.log("[强势池] 登录状态:", utils_auth.isLoggedIn());
+      console.log("[强势池] onLoad");
+      isPageVisible.value = true;
       checkPurchaseStatus();
     });
     common_vendor.onShow(() => {
-      console.log("[强势池] 登录状态:", utils_auth.isLoggedIn());
+      console.log("[强势池] onShow");
+      isPageVisible.value = true;
       checkPurchaseStatus();
       common_vendor.index.setNavigationBarTitle({ title: "量化交易大师" });
     });
+    common_vendor.onHide(() => {
+      console.log("[强势池] onHide");
+      isPageVisible.value = false;
+      stopAutoRefresh();
+    });
+    common_vendor.onUnmounted(() => {
+      isPageVisible.value = false;
+      stopAutoRefresh();
+    });
     return (_ctx, _cache) => {
       return common_vendor.e({
         a: common_vendor.p({
@@ -152,8 +216,11 @@ const _sfc_main = {
           return {
             a: common_vendor.t(stock.name),
             b: common_vendor.t(stock.code),
-            c: common_vendor.o(($event) => addToMyStocks(stock), index),
-            d: index
+            c: common_vendor.t(stock.currentPrice || "-"),
+            d: common_vendor.t(stock.changePercent || "-"),
+            e: common_vendor.n(getChangeClass(stock.changePercent)),
+            f: common_vendor.o(($event) => addToMyStocks(stock), index),
+            g: index
           };
         })
       }, {

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

@@ -1 +1 @@
-<view class="page-container"><scroll-view class="scroll-view" scroll-y><view class="content-wrapper"><view class="pool-header-section"><view class="pool-header"><text class="pool-icon">📈</text><text class="pool-title">强势趋势池</text></view><text class="pool-desc">本月精选,专注于中长期趋势跟踪</text></view><performance-card wx:if="{{a}}" u-i="98a039e6-0" bind:__l="__l" u-p="{{a}}"/><view class="card pool-card"><view wx:if="{{b}}" class="locked-content"><view class="lock-icon-wrapper"><text class="lock-icon">🔒</text></view><text class="lock-text">权限锁定: 查看 强势趋势池 </text><text class="lock-desc">强势池为每日实时更新,捕捉中长期趋势机会。</text><view class="unlock-button" bindtap="{{c}}"><text class="unlock-button-text">立即打赏</text></view></view><view wx:else class="unlocked-content"><text class="unlocked-tip">您已解锁,可以查看强势趋势池内容</text><view wx:for="{{d}}" wx:for-item="stock" wx:key="d" class="stock-item"><view class="stock-info"><view class="stock-name-row"><text class="stock-name">{{stock.a}}</text><text class="stock-code">{{stock.b}}</text></view></view><view class="stock-right"><view class="action-btn buy-btn" bindtap="{{stock.c}}"><text class="action-icon">+</text></view></view></view></view></view><history-search-card bindsearch="{{e}}" u-i="98a039e6-1" bind:__l="__l"/><view class="bottom-safe-area"></view></view></scroll-view><purchase-modal wx:if="{{h}}" bindclose="{{f}}" bindconfirm="{{g}}" u-i="98a039e6-2" bind:__l="__l" u-p="{{h}}"/></view>
+<view class="page-container"><scroll-view class="scroll-view" scroll-y><view class="content-wrapper"><view class="pool-header-section"><view class="pool-header"><text class="pool-icon">📈</text><text class="pool-title">强势趋势池</text></view><text class="pool-desc">本月精选,专注于中长期趋势跟踪</text></view><performance-card wx:if="{{a}}" u-i="98a039e6-0" bind:__l="__l" u-p="{{a}}"/><view class="card pool-card"><view wx:if="{{b}}" class="locked-content"><view class="lock-icon-wrapper"><text class="lock-icon">🔒</text></view><text class="lock-text">权限锁定: 查看 强势趋势池 </text><text class="lock-desc">强势池为每日实时更新,捕捉中长期趋势机会。</text><view class="unlock-button" bindtap="{{c}}"><text class="unlock-button-text">立即打赏</text></view></view><view wx:else class="unlocked-content"><view class="unlocked-header"><view class="unlocked-dot"></view><text class="unlocked-tip">已解锁内容</text></view><view class="stock-list"><view wx:for="{{d}}" wx:for-item="stock" wx:key="g" class="stock-item"><view class="stock-main"><view class="stock-info"><text class="stock-name">{{stock.a}}</text><text class="stock-code">{{stock.b}}</text></view><view class="stock-quote"><text class="stock-price">{{stock.c}}</text><text class="{{['stock-change', stock.e]}}">{{stock.d}}</text></view></view><view class="stock-action"><view class="add-btn" bindtap="{{stock.f}}"><text class="add-btn-text">+ 自选</text></view></view></view></view></view></view><history-search-card bindsearch="{{e}}" u-i="98a039e6-1" bind:__l="__l"/><view class="bottom-safe-area"></view></view></scroll-view><purchase-modal wx:if="{{h}}" bindclose="{{f}}" bindconfirm="{{g}}" u-i="98a039e6-2" bind:__l="__l" u-p="{{h}}"/></view>

+ 72 - 31
dist/dev/mp-weixin/pages/strong/strong.wxss

@@ -84,61 +84,102 @@
   color: #ffffff;
 }
 .unlocked-content {
-  margin-top: 32rpx;
+  padding: 8rpx 0;
+}
+.unlocked-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 32rpx;
+}
+.unlocked-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #3abf81;
+  margin-right: 12rpx;
 }
 .unlocked-tip {
   font-size: 26rpx;
+  font-weight: 500;
   color: #3abf81;
-  margin-bottom: 24rpx;
-  display: block;
+  letter-spacing: 1rpx;
+}
+.stock-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
 }
 .stock-item {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 24rpx 0;
-  border-bottom: 1rpx solid #f1f2f6;
+  padding: 28rpx 24rpx;
+  background: #fafbfc;
+  border-radius: 20rpx;
+  transition: all 0.2s ease;
 }
-.stock-item:last-child {
-  border-bottom: none;
-}
-.stock-info {
+.stock-main {
+  display: flex;
+  align-items: center;
   flex: 1;
 }
-.stock-name-row {
+.stock-info {
   display: flex;
-  align-items: center;
+  flex-direction: column;
+  min-width: 160rpx;
 }
 .stock-name {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #222222;
-  margin-right: 16rpx;
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 6rpx;
+  letter-spacing: 1rpx;
 }
 .stock-code {
-  font-size: 24rpx;
-  color: #9ca2b5;
+  font-size: 22rpx;
+  color: #9ca3af;
+  font-weight: 400;
 }
-.stock-right {
+.stock-quote {
   display: flex;
   align-items: center;
+  margin-left: 32rpx;
 }
-.action-btn {
-  width: 56rpx;
-  height: 56rpx;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
+.stock-price {
+  font-size: 32rpx;
+  font-weight: 800;
+  color: #1a1a2e;
+  margin-right: 16rpx;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
 }
-.buy-btn {
-  background: linear-gradient(135deg, #5d55e8, #7568ff);
+.stock-change {
+  font-size: 24rpx;
+  font-weight: 600;
+  padding: 6rpx 12rpx;
+  border-radius: 8rpx;
 }
-.action-icon {
-  font-size: 32rpx;
-  font-weight: 700;
+.text-red {
+  color: #ef4444;
+  background: rgba(239, 68, 68, 0.1);
+}
+.text-green {
+  color: #22c55e;
+  background: rgba(34, 197, 94, 0.1);
+}
+.stock-action {
+  margin-left: 20rpx;
+}
+.add-btn {
+  background: #5B5AEA;
+  border-radius: 40rpx;
+  padding: 16rpx 28rpx;
+  box-shadow: 0 8rpx 20rpx rgba(91, 90, 234, 0.3);
+}
+.add-btn-text {
+  font-size: 24rpx;
+  font-weight: 600;
   color: #ffffff;
+  letter-spacing: 1rpx;
 }
 .bottom-safe-area {
   height: 80rpx;

+ 9 - 1
dist/dev/mp-weixin/utils/api.js

@@ -1,6 +1,6 @@
 "use strict";
 const common_vendor = require("../common/vendor.js");
-const DEV_BASE_URL = "http://192.168.1.3:8081";
+const DEV_BASE_URL = "http://localhost:8081";
 const BASE_URL = DEV_BASE_URL;
 const getToken = () => {
   return common_vendor.index.getStorageSync("user_token") || null;
@@ -161,9 +161,17 @@ const deleteUserStock = (stockCode) => {
     method: "DELETE"
   });
 };
+const getStockPoolList = (poolType) => {
+  return request({
+    url: "/v1/stock/pool/list",
+    method: "GET",
+    data: { poolType }
+  });
+};
 exports.addUserStock = addUserStock;
 exports.deleteUserStock = deleteUserStock;
 exports.getIndexQuote = getIndexQuote;
+exports.getStockPoolList = getStockPoolList;
 exports.getStockQuotes = getStockQuotes;
 exports.getSuggestions = getSuggestions;
 exports.getUserInfoApi = getUserInfoApi;

+ 268 - 18
src/pages/pool/pool.vue

@@ -34,9 +34,28 @@
 
           <!-- 已购买时显示内容 -->
           <view v-else class="unlocked-content">
-            <text class="unlocked-tip">您已解锁,可以查看超短精选池内容</text>
-            <view class="stock-list-placeholder">
-              <text class="placeholder-text">股票池内容将在此显示</text>
+            <view class="unlocked-header">
+              <view class="unlocked-dot"></view>
+              <text class="unlocked-tip">已解锁内容</text>
+            </view>
+            <view class="stock-list">
+              <view class="stock-item" v-for="(stock, index) in stockList" :key="index">
+                <view class="stock-main">
+                  <view class="stock-info">
+                    <text class="stock-name">{{ stock.name }}</text>
+                    <text class="stock-code">{{ stock.code }}</text>
+                  </view>
+                  <view class="stock-quote">
+                    <text class="stock-price">{{ stock.currentPrice || '-' }}</text>
+                    <text :class="['stock-change', getChangeClass(stock.changePercent)]">{{ stock.changePercent || '-' }}</text>
+                  </view>
+                </view>
+                <view class="stock-action">
+                  <view class="add-btn" @click="addToMyStocks(stock)">
+                    <text class="add-btn-text">+ 自选</text>
+                  </view>
+                </view>
+              </view>
             </view>
           </view>
         </view>
@@ -64,9 +83,10 @@
 </template>
 
 <script setup>
-import { ref } from 'vue'
-import { onLoad, onShow } from '@dcloudio/uni-app'
+import { ref, onUnmounted } from 'vue'
+import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
 import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
+import { getStockQuotes, addUserStock, getStockPoolList } from '../../utils/api.js'
 import PurchaseModal from '../../components/PurchaseModal.vue'
 import PerformanceCard from '../../components/PerformanceCard.vue'
 import HistorySearchCard from '../../components/HistorySearchCard.vue'
@@ -74,6 +94,51 @@ import HistorySearchCard from '../../components/HistorySearchCard.vue'
 const isPurchased = ref(false)
 const showModal = ref(false)
 const isLoggedIn = ref(false)
+const isPageVisible = ref(false) // 页面是否可见
+
+const stockList = ref([])
+let refreshTimer = null
+
+// 获取涨跌样式
+const getChangeClass = (changePercent) => {
+  if (!changePercent || changePercent === '-') return ''
+  return changePercent.startsWith('+') ? 'text-red' : 'text-green'
+}
+
+// 获取随机刷新间隔 (2000-3000ms)
+const getRandomInterval = () => 2000 + Math.random() * 1000
+
+// 启动自动刷新
+const startAutoRefresh = () => {
+  if (!isPageVisible.value) return
+  stopAutoRefresh()
+  
+  const scheduleNextRefresh = () => {
+    if (!isPageVisible.value) {
+      stopAutoRefresh()
+      return
+    }
+    
+    refreshTimer = setTimeout(async () => {
+      if (!isPageVisible.value) {
+        stopAutoRefresh()
+        return
+      }
+      await loadStockPool()
+      scheduleNextRefresh()
+    }, getRandomInterval())
+  }
+  
+  scheduleNextRefresh()
+}
+
+// 停止自动刷新
+const stopAutoRefresh = () => {
+  if (refreshTimer) {
+    clearTimeout(refreshTimer)
+    refreshTimer = null
+  }
+}
 
 // 检查登录状态
 const checkLogin = () => {
@@ -85,6 +150,7 @@ const checkLogin = () => {
 const checkPurchaseStatus = () => {
   if (!checkLogin()) {
     isPurchased.value = false
+    stopAutoRefresh()
     return
   }
   
@@ -94,19 +160,42 @@ const checkPurchaseStatus = () => {
       const now = Date.now()
       if (now < purchaseInfo.expireTime) {
         isPurchased.value = true
+        // 已解锁,加载股票池数据并启动自动刷新
+        loadAndStartRefresh()
       } else {
         uni.removeStorageSync('pool_purchase')
         isPurchased.value = false
+        stopAutoRefresh()
       }
     } else {
       isPurchased.value = false
+      stopAutoRefresh()
     }
   } catch (e) {
     console.error('检查购买状态失败:', e)
     isPurchased.value = false
+    stopAutoRefresh()
+  }
+}
+
+// 加载股票池数据
+const loadStockPool = async () => {
+  try {
+    const res = await getStockPoolList(1)  // 1=超短池
+    if (res.code === 200 && res.data) {
+      stockList.value = res.data
+    }
+  } catch (e) {
+    console.error('加载股票池失败:', e)
   }
 }
 
+// 加载数据并启动自动刷新
+const loadAndStartRefresh = async () => {
+  await loadStockPool()
+  startAutoRefresh()
+}
+
 // 显示购买弹窗(需要登录)
 const showPurchaseModal = () => {
   if (!checkLogin()) {
@@ -145,6 +234,8 @@ const handlePurchase = () => {
   isPurchased.value = true
   closePurchaseModal()
   uni.showToast({ title: '解锁成功', icon: 'success' })
+  // 解锁后加载数据并启动自动刷新
+  loadAndStartRefresh()
 }
 
 // 历史查询
@@ -160,16 +251,82 @@ const onHistorySearch = ({ startMonth, endMonth }) => {
   })
 }
 
+// 添加到我的股票
+const addToMyStocks = async (stock) => {
+  if (!checkLogin()) {
+    uni.showModal({
+      title: '登录提示',
+      content: '添加自选股票需要登录,是否前往登录?',
+      confirmText: '去登录',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          uni.navigateTo({ url: '/pages/login/login' })
+        }
+      }
+    })
+    return
+  }
+
+  try {
+    uni.showLoading({ title: '添加中...' })
+    
+    let currentPrice = null
+    try {
+      const quoteRes = await getStockQuotes(stock.code)
+      if (quoteRes.code === 200 && quoteRes.data && quoteRes.data.length > 0) {
+        currentPrice = quoteRes.data[0].currentPrice
+      }
+    } catch (e) {
+      console.error('获取行情数据失败:', e)
+    }
+    
+    const addRes = await addUserStock({
+      stockCode: stock.code,
+      stockName: stock.name,
+      currentPrice: currentPrice,
+      poolType: 1  // 超短池
+    })
+    
+    uni.hideLoading()
+    
+    if (addRes.code === 200 && addRes.data === true) {
+      uni.showToast({ title: '添加成功', icon: 'success' })
+    } else if (addRes.code === 200 && addRes.data === false) {
+      uni.showToast({ title: '股票已存在', icon: 'none' })
+    } else {
+      uni.showToast({ title: addRes.message || '添加失败', icon: 'none' })
+    }
+  } catch (e) {
+    uni.hideLoading()
+    console.error('添加股票失败:', e)
+    uni.showToast({ title: '添加失败', icon: 'none' })
+  }
+}
+
 onLoad(() => {
-  console.log('[超短池] 登录状态:', checkLogin())
+  console.log('[超短池] onLoad')
+  isPageVisible.value = true
   checkPurchaseStatus()
 })
 
 onShow(() => {
-  console.log('[超短池] 登录状态:', checkLogin())
+  console.log('[超短池] onShow')
+  isPageVisible.value = true
   checkPurchaseStatus()
   uni.setNavigationBarTitle({ title: '量化交易大师' })
 })
+
+onHide(() => {
+  console.log('[超短池] onHide')
+  isPageVisible.value = false
+  stopAutoRefresh()
+})
+
+onUnmounted(() => {
+  isPageVisible.value = false
+  stopAutoRefresh()
+})
 </script>
 
 <style>
@@ -275,26 +432,119 @@ onShow(() => {
 }
 
 .unlocked-content {
-  margin-top: 32rpx;
+  padding: 8rpx 0;
+}
+
+.unlocked-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 32rpx;
+}
+
+.unlocked-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #3abf81;
+  margin-right: 12rpx;
 }
 
 .unlocked-tip {
   font-size: 26rpx;
+  font-weight: 500;
   color: #3abf81;
-  margin-bottom: 24rpx;
-  display: block;
+  letter-spacing: 1rpx;
 }
 
-.stock-list-placeholder {
-  padding: 60rpx 0;
-  text-align: center;
-  background: #f7f8fc;
-  border-radius: 16rpx;
+.stock-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
 }
 
-.placeholder-text {
-  font-size: 26rpx;
-  color: #9ca2b5;
+.stock-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 28rpx 24rpx;
+  background: #fafbfc;
+  border-radius: 20rpx;
+  transition: all 0.2s ease;
+}
+
+.stock-main {
+  display: flex;
+  align-items: center;
+  flex: 1;
+}
+
+.stock-info {
+  display: flex;
+  flex-direction: column;
+  min-width: 160rpx;
+}
+
+.stock-name {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 6rpx;
+  letter-spacing: 1rpx;
+}
+
+.stock-code {
+  font-size: 22rpx;
+  color: #9ca3af;
+  font-weight: 400;
+}
+
+.stock-quote {
+  display: flex;
+  align-items: center;
+  margin-left: 32rpx;
+}
+
+.stock-price {
+  font-size: 32rpx;
+  font-weight: 800;
+  color: #1a1a2e;
+  margin-right: 16rpx;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
+}
+
+.stock-change {
+  font-size: 24rpx;
+  font-weight: 600;
+  padding: 6rpx 12rpx;
+  border-radius: 8rpx;
+}
+
+.text-red {
+  color: #ef4444;
+  background: rgba(239, 68, 68, 0.1);
+}
+
+.text-green {
+  color: #22c55e;
+  background: rgba(34, 197, 94, 0.1);
+}
+
+.stock-action {
+  margin-left: 20rpx;
+}
+
+.add-btn {
+  background: #5B5AEA;
+  border-radius: 40rpx;
+  padding: 16rpx 28rpx;
+  box-shadow: 0 8rpx 20rpx rgba(91, 90, 234, 0.3);
+}
+
+.add-btn-text {
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #ffffff;
+  letter-spacing: 1rpx;
 }
 
 .bottom-safe-area {

+ 508 - 121
src/pages/rank/rank.vue

@@ -42,45 +42,95 @@
         </view>
 
         <!-- 热力图视图 -->
-        <view v-show="viewMode === 'list'" class="stock-list" :class="{ 'hidden-list': myStocks.length === 0 }">
-          <stock-list-item 
-            v-for="(stock, index) in myStocks" 
-            :key="stock.code"
-            :stock="stock"
-            :show-delete="true"
-            @delete="removeStock(index)"
-            @click="handleStockClick(stock, index)"
-          />
+        <view v-show="viewMode === 'list'" class="stock-list-card" :class="{ 'hidden-list': myStocks.length === 0 }">
+          <view class="list-header">
+            <view class="list-dot"></view>
+            <text class="list-title">我的自选</text>
+            <text class="list-count">{{ myStocks.length }}只</text>
+          </view>
+          <view class="stock-list">
+            <view class="stock-item-wrapper" v-for="(stock, index) in myStocks" :key="stock.code">
+              <movable-area class="movable-area">
+                <movable-view 
+                  class="movable-view"
+                  direction="horizontal"
+                  :x="slideStates[stock.code]?.x || 0"
+                  :damping="40"
+                  :friction="5"
+                  :out-of-bounds="false"
+                  @change="(e) => handleSlideChange(e, stock.code)"
+                  @touchend="() => handleSlideEnd(stock.code)"
+                >
+                  <view class="stock-item">
+                    <view class="stock-main">
+                      <view class="stock-info">
+                        <text class="stock-name">{{ stock.name }}</text>
+                        <view class="stock-code-row">
+                          <text :class="['stock-tag', getMarketClass(stock.code)]">{{ getMarketTag(stock.code) }}</text>
+                          <text class="stock-code">{{ stock.code }}</text>
+                        </view>
+                      </view>
+                      <!-- 趋势图 -->
+                      <view class="stock-chart">
+                        <canvas 
+                          :canvas-id="'chart-' + stock.code" 
+                          :id="'chart-' + stock.code"
+                          class="trend-canvas"
+                        ></canvas>
+                      </view>
+                      <view class="stock-quote">
+                        <text class="stock-price">{{ stock.currentPrice || '-' }}</text>
+                        <text :class="['stock-change', getProfitClass(stock.changePercent)]">{{ stock.changePercent || '-' }}</text>
+                      </view>
+                    </view>
+                    <!-- 滑动删除按钮 -->
+                    <view class="slide-delete-btn" @click.stop="removeStock(index)">
+                      <view class="delete-icon-circle">
+                        <text class="delete-icon-text">−</text>
+                      </view>
+                    </view>
+                  </view>
+                </movable-view>
+              </movable-area>
+            </view>
+          </view>
         </view>
 
         <!-- 详情表格视图 -->
-        <view v-show="viewMode === 'table'" class="stock-table" :class="{ 'hidden-list': myStocks.length === 0 }">
+        <view v-show="viewMode === 'table'" class="stock-table-card" :class="{ 'hidden-list': myStocks.length === 0 }">
+          <view class="list-header">
+            <view class="list-dot list-dot-blue"></view>
+            <text class="list-title">详情数据</text>
+          </view>
           <!-- 表头 -->
           <view class="table-header">
             <text class="th-name">股票</text>
             <text class="th-date">自选日</text>
             <text class="th-price">自选价</text>
-            <text class="th-profit">自选收益</text>
+            <text class="th-profit">收益</text>
           </view>
           <!-- 表格内容 -->
-          <view 
-            v-for="(stock, index) in myStocks" 
-            :key="stock.code"
-            class="table-row"
-            @click="handleStockClick(stock, index)"
-          >
-            <view class="td-name">
-              <text class="stock-name">{{ stock.name }}</text>
-              <view class="stock-code-row">
-                <text :class="['stock-tag', getMarketClass(stock.code)]">{{ getMarketTag(stock.code) }}</text>
-                <text class="stock-code">{{ stock.code }}</text>
+          <view class="table-list">
+            <view 
+              v-for="(stock, index) in myStocks" 
+              :key="stock.code"
+              class="table-row"
+            >
+              <view class="td-name">
+                <text class="stock-name">{{ stock.name }}</text>
+                <view class="stock-code-row">
+                  <text :class="['stock-tag', getMarketClass(stock.code)]">{{ getMarketTag(stock.code) }}</text>
+                  <text class="stock-code">{{ stock.code }}</text>
+                </view>
+              </view>
+              <text class="td-date">{{ stock.addDate || '--' }}</text>
+              <text class="td-price">{{ formatPrice(stock.addPrice) }}</text>
+              <view class="td-profit-wrap">
+                <text :class="['td-profit', getProfitClass(stock.profitPercent)]">
+                  {{ stock.profitPercent || '--' }}
+                </text>
               </view>
             </view>
-            <text class="td-date">{{ stock.addDate || '--' }}</text>
-            <text class="td-price">{{ formatPrice(stock.addPrice) }}</text>
-            <text :class="['td-profit', getProfitClass(stock.profitPercent)]">
-              {{ stock.profitPercent || '--' }}
-            </text>
           </view>
         </view>
 
@@ -111,11 +161,13 @@
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, nextTick, getCurrentInstance, reactive } from 'vue'
 import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
 import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
 import { getStockQuotes, getIndexQuote, getUserStocks, deleteUserStock } from '../../utils/api.js'
-import StockListItem from '../../components/StockListItem.vue'
+
+// 保存组件实例
+let componentInstance = null
 
 const isLoggedIn = ref(false)
 const myStocks = ref([])
@@ -123,6 +175,74 @@ const viewMode = ref('list') // 'list' 或 'table'
 const isLoading = ref(false) // 加载状态
 const lastLoadTime = ref(0) // 上次加载时间
 const CACHE_DURATION = 5000 // 缓存有效期5秒
+const isPageVisible = ref(false) // 页面是否可见
+
+// 滑动删除相关
+const SLIDE_WIDTH = 100 // 滑动距离(三分之一宽度约100rpx转换)
+const slideStates = reactive({}) // 每个股票的滑动状态
+let slideTimers = {} // 自动还原计时器
+const AUTO_RESET_DELAY = 2000 // 2秒后自动还原
+
+// 初始化滑动状态
+const initSlideState = (code) => {
+  if (!slideStates[code]) {
+    slideStates[code] = { x: 0, currentX: 0 }
+  }
+}
+
+// 处理滑动变化
+const handleSlideChange = (e, code) => {
+  initSlideState(code)
+  slideStates[code].currentX = e.detail.x
+}
+
+// 处理滑动结束
+const handleSlideEnd = (code) => {
+  initSlideState(code)
+  const state = slideStates[code]
+  
+  // 滑动超过20px就显示删除按钮(吸附到三分之一处)
+  if (state.currentX < -20) {
+    state.x = -SLIDE_WIDTH
+    // 启动自动还原计时器(2秒)
+    startAutoResetTimer(code)
+  } else {
+    // 滑回初始位置
+    resetSlide(code)
+  }
+}
+
+// 重置滑动位置
+const resetSlide = (code) => {
+  if (slideStates[code]) {
+    slideStates[code].x = 0
+    slideStates[code].currentX = 0
+  }
+  clearSlideTimer(code)
+}
+
+// 启动自动还原计时器
+const startAutoResetTimer = (code) => {
+  clearSlideTimer(code)
+  slideTimers[code] = setTimeout(() => {
+    resetSlide(code)
+  }, AUTO_RESET_DELAY) // 2秒后自动还原
+}
+
+// 清除计时器
+const clearSlideTimer = (code) => {
+  if (slideTimers[code]) {
+    clearTimeout(slideTimers[code])
+    delete slideTimers[code]
+  }
+}
+
+// 清除所有计时器
+const clearAllSlideTimers = () => {
+  Object.keys(slideTimers).forEach(code => {
+    clearSlideTimer(code)
+  })
+}
 
 // 设置视图模式
 const setViewMode = (mode) => {
@@ -197,6 +317,117 @@ const getMarketClass = (code) => {
   return 'market-sh'
 }
 
+// 绘制单个股票的趋势图
+const drawTrendChart = (stock) => {
+  if (!componentInstance) return
+  
+  const canvasId = 'chart-' + stock.code
+  let trendData = stock.trendData
+  
+  // 如果没有趋势数据,生成模拟数据
+  if (!trendData || !Array.isArray(trendData) || trendData.length === 0) {
+    trendData = generateMockTrendData(stock.changePercent)
+  }
+  
+  const ctx = uni.createCanvasContext(canvasId, componentInstance)
+  
+  const width = 100
+  const height = 30
+  const padding = 2
+  
+  const maxValue = Math.max(...trendData)
+  const minValue = Math.min(...trendData)
+  const dataRange = maxValue - minValue
+  const avgValue = (maxValue + minValue) / 2
+  const minRange = avgValue * 0.03 || 1
+  const range = Math.max(dataRange, minRange)
+  
+  const baseValue = trendData[0]
+  const baseY = height - padding - ((baseValue - minValue) / range) * (height - padding * 2)
+  
+  const changePercent = parseFloat(String(stock.changePercent || '0').replace('%', '').replace('+', ''))
+  const isUp = changePercent >= 0
+  const lineColor = isUp ? '#ef4444' : '#22c55e'
+  const fillColor = isUp ? 'rgba(239, 68, 68, 0.15)' : 'rgba(34, 197, 94, 0.15)'
+  
+  // 绘制基准虚线
+  ctx.beginPath()
+  ctx.setStrokeStyle('#e5e7eb')
+  ctx.setLineWidth(0.5)
+  ctx.setLineDash([2, 2], 0)
+  ctx.moveTo(padding, baseY)
+  ctx.lineTo(width - padding, baseY)
+  ctx.stroke()
+  ctx.setLineDash([], 0)
+  
+  // 绘制填充区域
+  ctx.beginPath()
+  ctx.moveTo(padding, baseY)
+  
+  trendData.forEach((value, index) => {
+    const x = padding + (index / (trendData.length - 1)) * (width - padding * 2)
+    const y = height - padding - ((value - minValue) / range) * (height - padding * 2)
+    ctx.lineTo(x, y)
+  })
+  
+  ctx.lineTo(width - padding, baseY)
+  ctx.closePath()
+  ctx.setFillStyle(fillColor)
+  ctx.fill()
+  
+  // 绘制折线
+  ctx.beginPath()
+  
+  trendData.forEach((value, index) => {
+    const x = padding + (index / (trendData.length - 1)) * (width - padding * 2)
+    const y = height - padding - ((value - minValue) / range) * (height - padding * 2)
+    
+    if (index === 0) {
+      ctx.moveTo(x, y)
+    } else {
+      ctx.lineTo(x, y)
+    }
+  })
+  
+  ctx.setStrokeStyle(lineColor)
+  ctx.setLineWidth(1.5)
+  ctx.stroke()
+  
+  ctx.draw()
+}
+
+// 生成模拟趋势数据
+const generateMockTrendData = (changePercent) => {
+  const change = parseFloat(String(changePercent || '0').replace('%', '').replace('+', ''))
+  const points = 15
+  const data = []
+  
+  let baseValue = 100
+  const trend = change / 100
+  
+  for (let i = 0; i < points; i++) {
+    const randomChange = (Math.random() - 0.5) * 6
+    const trendChange = (i / points) * trend * 100
+    baseValue = baseValue + randomChange + trendChange / points
+    data.push(baseValue)
+  }
+  
+  return data
+}
+
+// 绘制所有股票的趋势图
+const drawAllTrendCharts = () => {
+  if (myStocks.value.length === 0) return
+  
+  nextTick(() => {
+    setTimeout(() => {
+      myStocks.value.forEach(stock => {
+        drawTrendChart(stock)
+      })
+    }, 300)
+  })
+}
+
 // 加载我的股票列表(从服务器数据库查询)
 const loadMyStocks = async (forceRefresh = false) => {
   console.log('[我的股票] loadMyStocks 开始执行, isLoggedIn=', isLoggedIn.value, 'forceRefresh=', forceRefresh)
@@ -265,6 +496,8 @@ const loadMyStocks = async (forceRefresh = false) => {
     // 如果有股票数据,刷新行情
     if (myStocks.value.length > 0) {
       await refreshAllQuotes()
+      // 绘制趋势图
+      drawAllTrendCharts()
     }
     
     // 登录后启动定时刷新
@@ -320,12 +553,24 @@ const refreshAllQuotes = async () => {
 
 // 启动定时刷新
 const startAutoRefresh = () => {
-  if (!isLoggedIn.value) return
+  if (!isLoggedIn.value || !isPageVisible.value) return
   stopAutoRefresh()
   
   const scheduleNextRefresh = () => {
+    // 每次刷新前检查页面是否可见
+    if (!isPageVisible.value) {
+      stopAutoRefresh()
+      return
+    }
+    
     const delay = 3000 + Math.random() * 1000
     refreshTimer = setTimeout(async () => {
+      // 再次检查页面是否可见
+      if (!isPageVisible.value) {
+        stopAutoRefresh()
+        return
+      }
+      
       await fetchIndexData()
       if (myStocks.value.length > 0) {
         await refreshAllQuotes()
@@ -350,42 +595,14 @@ const goToLogin = () => {
   uni.navigateTo({ url: '/pages/login/login' })
 }
 
-// 点击股票项
-const handleStockClick = async (stockItem, idx) => {
-  console.log('点击股票:', stockItem.name, idx)
-  // 刷新该股票的最新行情
-  try {
-    const quoteRes = await getStockQuotes(stockItem.code)
-    if (quoteRes.code === 200 && quoteRes.data && quoteRes.data.length > 0) {
-      const quoteData = quoteRes.data[0]
-      const stock = myStocks.value[idx]
-      if (stock) {
-        stock.priceChange = quoteData.priceChange
-        stock.changePercent = quoteData.changePercent
-        stock.currentPrice = quoteData.currentPrice
-        stock.name = quoteData.stockName || stock.name
-        stock.trendData = quoteData.trendData || null
-        
-        // 计算自选收益
-        if (stock.addPrice && quoteData.currentPrice) {
-          const addPrice = parseFloat(stock.addPrice)
-          const currentPrice = parseFloat(quoteData.currentPrice)
-          if (addPrice > 0) {
-            const profit = ((currentPrice - addPrice) / addPrice * 100).toFixed(2)
-            stock.profitPercent = profit >= 0 ? `+${profit}%` : `${profit}%`
-          }
-        }
-      }
-    }
-  } catch (e) {
-    console.error('刷新股票行情失败:', e)
-  }
-  uni.showToast({ title: '股票详情功能开发中', icon: 'none' })
-}
+
 
 // 删除股票
 const removeStock = async (idx) => {
   const stock = myStocks.value[idx]
+  // 先重置滑动状态
+  resetSlide(stock.code)
+  
   uni.showModal({
     title: '确认删除',
     content: `确定要删除 ${stock.name} 吗?`,
@@ -396,6 +613,9 @@ const removeStock = async (idx) => {
         try {
           // 调用服务器删除接口
           await deleteUserStock(stock.code)
+          // 清理滑动状态
+          delete slideStates[stock.code]
+          clearSlideTimer(stock.code)
           // 从本地列表删除
           myStocks.value.splice(idx, 1)
           uni.showToast({ title: '删除成功', icon: 'success' })
@@ -414,12 +634,15 @@ const removeStock = async (idx) => {
 
 onLoad(() => {
   console.log('[我的股票] onLoad 触发')
+  componentInstance = getCurrentInstance()
   isLoggedIn.value = checkLoginStatus()
+  isPageVisible.value = true
   loadMyStocks(true) // 首次加载强制刷新
 })
 
 onShow(() => {
   console.log('[我的股票] onShow 触发')
+  isPageVisible.value = true
   const wasLoggedIn = isLoggedIn.value
   isLoggedIn.value = checkLoginStatus()
   
@@ -435,11 +658,16 @@ onShow(() => {
 })
 
 onHide(() => {
+  console.log('[我的股票] onHide 触发')
+  isPageVisible.value = false
   stopAutoRefresh()
 })
 
 onUnload(() => {
+  console.log('[我的股票] onUnload 触发')
+  isPageVisible.value = false
   stopAutoRefresh()
+  clearAllSlideTimers()
 })
 </script>
 
@@ -583,78 +811,129 @@ onUnload(() => {
   display: none !important;
 }
 
-/* 股票列表 */
-.stock-list {
-  display: flex;
-  flex-direction: column;
+/* 股票列表卡片 */
+.stock-list-card,
+.stock-table-card {
   background: #ffffff;
-  border-radius: 24rpx;
-  padding: 0 32rpx;
-  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
-}
-
-/* 表格视图 */
-.stock-table {
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 0 24rpx;
-  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+  border-radius: 32rpx;
+  padding: 32rpx;
+  box-shadow: 0 16rpx 48rpx rgba(37, 52, 94, 0.1);
 }
 
-.table-header {
+.list-header {
   display: flex;
   align-items: center;
-  padding: 24rpx 0;
-  border-bottom: 1rpx solid #f1f2f6;
+  margin-bottom: 28rpx;
 }
 
-.table-header text {
+.list-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #3abf81;
+  margin-right: 12rpx;
+}
+
+.list-dot-blue {
+  background: #5B5AEA;
+}
+
+.list-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1a1a2e;
+  letter-spacing: 1rpx;
+}
+
+.list-count {
   font-size: 24rpx;
-  color: #999999;
-  font-weight: 500;
+  color: #9ca3af;
+  margin-left: 12rpx;
 }
 
-.th-name {
-  flex: 1;
+/* 股票列表 */
+.stock-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
 }
 
-.th-date {
-  width: 160rpx;
-  text-align: center;
+/* 滑动删除容器 */
+.stock-item-wrapper {
+  position: relative;
+  height: 140rpx;
+  overflow: hidden;
+  border-radius: 20rpx;
+  background: #fafbfc;
 }
 
-.th-price {
-  width: 140rpx;
-  text-align: center;
+.movable-area {
+  width: 100%;
+  height: 100%;
 }
 
-.th-profit {
-  width: 140rpx;
-  text-align: right;
+.movable-view {
+  width: calc(100% + 200rpx);
+  height: 100%;
 }
 
-.table-row {
+.stock-item {
   display: flex;
   align-items: center;
-  padding: 24rpx 0;
-  border-bottom: 1rpx solid #f1f2f6;
+  padding: 28rpx 24rpx;
+  background: #fafbfc;
+  border-radius: 20rpx;
+  height: 100%;
+  box-sizing: border-box;
 }
 
-.table-row:last-child {
-  border-bottom: none;
+/* 滑动删除按钮 */
+.slide-delete-btn {
+  flex-shrink: 0;
+  width: 200rpx;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #fafbfc;
 }
 
-.td-name {
+.delete-icon-circle {
+  width: 72rpx;
+  height: 72rpx;
+  background: #ef4444;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 6rpx 16rpx rgba(239, 68, 68, 0.4);
+}
+
+.delete-icon-text {
+  font-size: 40rpx;
+  color: #ffffff;
+  font-weight: bold;
+  line-height: 1;
+}
+
+.stock-main {
+  display: flex;
+  align-items: center;
   flex: 1;
+}
+
+.stock-info {
   display: flex;
   flex-direction: column;
+  min-width: 140rpx;
 }
 
-.td-name .stock-name {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #222222;
-  margin-bottom: 4rpx;
+.stock-name {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 6rpx;
+  letter-spacing: 1rpx;
 }
 
 .stock-code-row {
@@ -664,57 +943,165 @@ onUnload(() => {
 
 .stock-tag {
   font-size: 18rpx;
-  padding: 2rpx 6rpx;
-  border-radius: 4rpx;
+  padding: 2rpx 8rpx;
+  border-radius: 6rpx;
   color: #ffffff;
-  font-weight: 500;
+  font-weight: 600;
   margin-right: 8rpx;
 }
 
 .market-sh {
-  background: #FF3B30;
+  background: #ef4444;
 }
 
 .market-sz {
-  background: #34C759;
+  background: #22c55e;
 }
 
 .market-cy {
-  background: #FF9500;
+  background: #f59e0b;
 }
 
 .stock-code {
   font-size: 22rpx;
-  color: #9ca2b5;
+  color: #9ca3af;
+  font-weight: 400;
+}
+
+.stock-quote {
+  display: flex;
+  align-items: center;
+  margin-left: 16rpx;
+}
+
+/* 趋势图 */
+.stock-chart {
+  flex: 1;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin: 0 16rpx;
+}
+
+.trend-canvas {
+  width: 200rpx;
+  height: 60rpx;
+}
+
+.stock-price {
+  font-size: 32rpx;
+  font-weight: 800;
+  color: #1a1a2e;
+  margin-right: 16rpx;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
+}
+
+.stock-change {
+  font-size: 24rpx;
+  font-weight: 600;
+  padding: 6rpx 12rpx;
+  border-radius: 8rpx;
+}
+
+/* 表格视图 */
+.table-header {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 16rpx;
+  background: #f8f9fc;
+  border-radius: 12rpx;
+  margin-bottom: 16rpx;
+}
+
+.table-header text {
+  font-size: 24rpx;
+  color: #9ca3af;
+  font-weight: 600;
+}
+
+.th-name {
+  flex: 1;
+}
+
+.th-date {
+  width: 140rpx;
+  text-align: center;
+}
+
+.th-price {
+  width: 120rpx;
+  text-align: center;
+}
+
+.th-profit {
+  width: 120rpx;
+  text-align: right;
+}
+
+.table-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+}
+
+.table-row {
+  display: flex;
+  align-items: center;
+  padding: 24rpx 16rpx;
+  background: #fafbfc;
+  border-radius: 16rpx;
+}
+
+.td-name {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.td-name .stock-name {
+  font-size: 28rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 4rpx;
 }
 
 .td-date {
-  width: 160rpx;
+  width: 140rpx;
   text-align: center;
   font-size: 24rpx;
-  color: #666666;
+  color: #6b7280;
 }
 
 .td-price {
-  width: 140rpx;
+  width: 120rpx;
   text-align: center;
   font-size: 26rpx;
-  color: #333333;
+  font-weight: 600;
+  color: #1a1a2e;
+}
+
+.td-profit-wrap {
+  width: 120rpx;
+  display: flex;
+  justify-content: flex-end;
 }
 
 .td-profit {
-  width: 140rpx;
-  text-align: right;
-  font-size: 26rpx;
+  font-size: 24rpx;
   font-weight: 600;
+  padding: 6rpx 12rpx;
+  border-radius: 8rpx;
 }
 
 .profit-up {
-  color: #FF3B30;
+  color: #ef4444;
+  background: rgba(239, 68, 68, 0.1);
 }
 
 .profit-down {
-  color: #34C759;
+  color: #22c55e;
+  background: rgba(34, 197, 94, 0.1);
 }
 
 /* 空状态 */

+ 187 - 51
src/pages/strong/strong.vue

@@ -34,17 +34,26 @@
 
           <!-- 已购买时显示内容 -->
           <view v-else class="unlocked-content">
-            <text class="unlocked-tip">您已解锁,可以查看强势趋势池内容</text>
-            <view class="stock-item" v-for="(stock, index) in stockList" :key="index">
-              <view class="stock-info">
-                <view class="stock-name-row">
-                  <text class="stock-name">{{ stock.name }}</text>
-                  <text class="stock-code">{{ stock.code }}</text>
+            <view class="unlocked-header">
+              <view class="unlocked-dot"></view>
+              <text class="unlocked-tip">已解锁内容</text>
+            </view>
+            <view class="stock-list">
+              <view class="stock-item" v-for="(stock, index) in stockList" :key="index">
+                <view class="stock-main">
+                  <view class="stock-info">
+                    <text class="stock-name">{{ stock.name }}</text>
+                    <text class="stock-code">{{ stock.code }}</text>
+                  </view>
+                  <view class="stock-quote">
+                    <text class="stock-price">{{ stock.currentPrice || '-' }}</text>
+                    <text :class="['stock-change', getChangeClass(stock.changePercent)]">{{ stock.changePercent || '-' }}</text>
+                  </view>
                 </view>
-              </view>
-              <view class="stock-right">
-                <view class="action-btn buy-btn" @click="addToMyStocks(stock)">
-                  <text class="action-icon">+</text>
+                <view class="stock-action">
+                  <view class="add-btn" @click="addToMyStocks(stock)">
+                    <text class="add-btn-text">+ 自选</text>
+                  </view>
                 </view>
               </view>
             </view>
@@ -74,26 +83,67 @@
 </template>
 
 <script setup>
-import { ref } from 'vue'
-import { onLoad, onShow } from '@dcloudio/uni-app'
+import { ref, onUnmounted } from 'vue'
+import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
 import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
-import { getStockQuotes, addUserStock } from '../../utils/api.js'
+import { getStockQuotes, addUserStock, getStockPoolList } from '../../utils/api.js'
 import PurchaseModal from '../../components/PurchaseModal.vue'
 import PerformanceCard from '../../components/PerformanceCard.vue'
 import HistorySearchCard from '../../components/HistorySearchCard.vue'
 
 const isPurchased = ref(false)
 const showModal = ref(false)
+const isPageVisible = ref(false) // 页面是否可见
+
+const stockList = ref([])
+let refreshTimer = null
+
+// 获取涨跌样式
+const getChangeClass = (changePercent) => {
+  if (!changePercent || changePercent === '-') return ''
+  return changePercent.startsWith('+') ? 'text-red' : 'text-green'
+}
 
-const stockList = ref([
-  { name: '信维通信', code: '300136' },
-  { name: '中国卫星', code: '600118' }
-])
+// 获取随机刷新间隔 (2000-3000ms)
+const getRandomInterval = () => 2000 + Math.random() * 1000
+
+// 启动自动刷新
+const startAutoRefresh = () => {
+  if (!isPageVisible.value) return
+  stopAutoRefresh()
+  
+  const scheduleNextRefresh = () => {
+    if (!isPageVisible.value) {
+      stopAutoRefresh()
+      return
+    }
+    
+    refreshTimer = setTimeout(async () => {
+      if (!isPageVisible.value) {
+        stopAutoRefresh()
+        return
+      }
+      await loadStockPool()
+      scheduleNextRefresh()
+    }, getRandomInterval())
+  }
+  
+  scheduleNextRefresh()
+}
+
+// 停止自动刷新
+const stopAutoRefresh = () => {
+  if (refreshTimer) {
+    clearTimeout(refreshTimer)
+    refreshTimer = null
+  }
+}
 
 // 检查购买状态
 const checkPurchaseStatus = () => {
   if (!checkLoginStatus()) {
     isPurchased.value = false
+    stopAutoRefresh()
     return
   }
   
@@ -103,19 +153,42 @@ const checkPurchaseStatus = () => {
       const now = Date.now()
       if (now < purchaseInfo.expireTime) {
         isPurchased.value = true
+        // 已解锁,加载股票池数据并启动自动刷新
+        loadAndStartRefresh()
       } else {
         uni.removeStorageSync('strong_pool_purchase')
         isPurchased.value = false
+        stopAutoRefresh()
       }
     } else {
       isPurchased.value = false
+      stopAutoRefresh()
     }
   } catch (e) {
     console.error('检查购买状态失败:', e)
     isPurchased.value = false
+    stopAutoRefresh()
+  }
+}
+
+// 加载股票池数据
+const loadStockPool = async () => {
+  try {
+    const res = await getStockPoolList(2)  // 2=强势池
+    if (res.code === 200 && res.data) {
+      stockList.value = res.data
+    }
+  } catch (e) {
+    console.error('加载股票池失败:', e)
   }
 }
 
+// 加载数据并启动自动刷新
+const loadAndStartRefresh = async () => {
+  await loadStockPool()
+  startAutoRefresh()
+}
+
 // 显示购买弹窗(需要登录)
 const showPurchaseModal = () => {
   if (!checkLoginStatus()) {
@@ -153,6 +226,8 @@ const handlePurchase = () => {
   isPurchased.value = true
   closePurchaseModal()
   uni.showToast({ title: '解锁成功', icon: 'success' })
+  // 解锁后加载数据并启动自动刷新
+  loadAndStartRefresh()
 }
 
 // 历史查询
@@ -201,7 +276,8 @@ const addToMyStocks = async (stock) => {
     const addRes = await addUserStock({
       stockCode: stock.code,
       stockName: stock.name,
-      currentPrice: currentPrice
+      currentPrice: currentPrice,
+      poolType: 2  // 强势池
     })
     
     uni.hideLoading()
@@ -221,15 +297,28 @@ const addToMyStocks = async (stock) => {
 }
 
 onLoad(() => {
-  console.log('[强势池] 登录状态:', checkLoginStatus())
+  console.log('[强势池] onLoad')
+  isPageVisible.value = true
   checkPurchaseStatus()
 })
 
 onShow(() => {
-  console.log('[强势池] 登录状态:', checkLoginStatus())
+  console.log('[强势池] onShow')
+  isPageVisible.value = true
   checkPurchaseStatus()
   uni.setNavigationBarTitle({ title: '量化交易大师' })
 })
+
+onHide(() => {
+  console.log('[强势池] onHide')
+  isPageVisible.value = false
+  stopAutoRefresh()
+})
+
+onUnmounted(() => {
+  isPageVisible.value = false
+  stopAutoRefresh()
+})
 </script>
 
 <style>
@@ -335,72 +424,119 @@ onShow(() => {
 }
 
 .unlocked-content {
-  margin-top: 32rpx;
+  padding: 8rpx 0;
+}
+
+.unlocked-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 32rpx;
+}
+
+.unlocked-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #3abf81;
+  margin-right: 12rpx;
 }
 
 .unlocked-tip {
   font-size: 26rpx;
+  font-weight: 500;
   color: #3abf81;
-  margin-bottom: 24rpx;
-  display: block;
+  letter-spacing: 1rpx;
+}
+
+.stock-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
 }
 
 .stock-item {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 24rpx 0;
-  border-bottom: 1rpx solid #f1f2f6;
+  padding: 28rpx 24rpx;
+  background: #fafbfc;
+  border-radius: 20rpx;
+  transition: all 0.2s ease;
 }
 
-.stock-item:last-child {
-  border-bottom: none;
+.stock-main {
+  display: flex;
+  align-items: center;
+  flex: 1;
 }
 
 .stock-info {
-  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-width: 160rpx;
 }
 
-.stock-name-row {
+.stock-name {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 6rpx;
+  letter-spacing: 1rpx;
+}
+
+.stock-code {
+  font-size: 22rpx;
+  color: #9ca3af;
+  font-weight: 400;
+}
+
+.stock-quote {
   display: flex;
   align-items: center;
+  margin-left: 32rpx;
 }
 
-.stock-name {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #222222;
+.stock-price {
+  font-size: 32rpx;
+  font-weight: 800;
+  color: #1a1a2e;
   margin-right: 16rpx;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
 }
 
-.stock-code {
+.stock-change {
   font-size: 24rpx;
-  color: #9ca2b5;
+  font-weight: 600;
+  padding: 6rpx 12rpx;
+  border-radius: 8rpx;
 }
 
-.stock-right {
-  display: flex;
-  align-items: center;
+.text-red {
+  color: #ef4444;
+  background: rgba(239, 68, 68, 0.1);
 }
 
-.action-btn {
-  width: 56rpx;
-  height: 56rpx;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
+.text-green {
+  color: #22c55e;
+  background: rgba(34, 197, 94, 0.1);
 }
 
-.buy-btn {
-  background: linear-gradient(135deg, #5d55e8, #7568ff);
+.stock-action {
+  margin-left: 20rpx;
 }
 
-.action-icon {
-  font-size: 32rpx;
-  font-weight: 700;
+.add-btn {
+  background: #5B5AEA;
+  border-radius: 40rpx;
+  padding: 16rpx 28rpx;
+  box-shadow: 0 8rpx 20rpx rgba(91, 90, 234, 0.3);
+}
+
+.add-btn-text {
+  font-size: 24rpx;
+  font-weight: 600;
   color: #ffffff;
+  letter-spacing: 1rpx;
 }
 
 .bottom-safe-area {

+ 17 - 3
src/utils/api.js

@@ -7,8 +7,8 @@
 // 例如:const DEV_BASE_URL = 'http://192.168.1.3:8080'
 // 可以通过命令 ipconfig (Windows) 或 ifconfig (Mac/Linux) 查看IP地址
 // const DEV_BASE_URL = 'http://192.168.1.3:8080'
-const DEV_BASE_URL = 'http://192.168.1.3:8081'
-// const DEV_BASE_URL = 'http://localhost:8081'
+// const DEV_BASE_URL = 'http://192.168.1.3:8081'
+const DEV_BASE_URL = 'http://localhost:8081'
 
 // 生产环境配置(部署后的服务器地址)
 const PROD_BASE_URL = 'https://your-domain.com'
@@ -248,7 +248,7 @@ export const getUserStocks = () => {
 
 /**
  * 添加自选股票
- * @param {object} data - { stockCode, stockName, currentPrice }
+ * @param {object} data - { stockCode, stockName, poolType, currentPrice }
  * @returns {Promise} 返回添加结果
  */
 export const addUserStock = (data) => {
@@ -274,3 +274,17 @@ export const deleteUserStock = (stockCode) => {
   })
 }
 
+
+
+/**
+ * 获取股票池列表
+ * @param {number} poolType - 池类型:1-超短池,2-强势池
+ * @returns {Promise} 返回股票池列表
+ */
+export const getStockPoolList = (poolType) => {
+  return request({
+    url: '/v1/stock/pool/list',
+    method: 'GET',
+    data: { poolType }
+  })
+}

Some files were not shown because too many files changed in this diff