Zhangbw 2 месяцев назад
Родитель
Сommit
1d1e536f60

+ 7 - 0
.claude/settings.local.json

@@ -0,0 +1,7 @@
+{
+  "permissions": {
+    "additionalDirectories": [
+      "d:\\program\\gupiao\\RuoYI-Vue\\ruoyi-modules\\yp-stock\\src\\main\\resources\\sql"
+    ]
+  }
+}

+ 0 - 83
.kiro/specs/wx-history-query/requirements.md

@@ -1,83 +0,0 @@
-# Requirements Document
-
-## Introduction
-
-微信小程序历史查询模块,允许用户选择日期区间查询股票池中股票的历史信息。以列表形式展示股票代码、名称、收盘价格、隔日最高价(从下一天的历史信息表中获取)以及隔日最高涨幅。当下一天数据不存在时,显示"-"。
-
-## Glossary
-
-- **History_Query_Module**: 微信小程序中的历史查询模块,负责展示股票池历史数据
-- **Stock_Pool_History**: 股票池历史数据表,存储每日入池股票的详细信息
-- **Next_Day_Data**: 下一交易日的历史数据,用于计算隔日最高价和涨幅
-- **Date_Range_Picker**: 日期区间选择器,用户选择查询的开始和结束日期
-- **History_List**: 历史数据列表组件,展示查询结果
-
-## Requirements
-
-### Requirement 1: 日期区间选择
-
-**User Story:** As a 用户, I want 选择开始日期和结束日期, so that 我可以查询指定时间范围内的股票池历史数据。
-
-#### Acceptance Criteria
-
-1. WHEN 用户进入历史查询页面, THE Date_Range_Picker SHALL 显示默认的日期区间(当前月份)
-2. WHEN 用户点击开始日期选择器, THE Date_Range_Picker SHALL 弹出日期选择控件供用户选择
-3. WHEN 用户点击结束日期选择器, THE Date_Range_Picker SHALL 弹出日期选择控件供用户选择
-4. WHEN 用户选择的开始日期晚于结束日期, THE History_Query_Module SHALL 提示用户"开始日期不能晚于结束日期"
-5. WHEN 用户完成日期选择, THE Date_Range_Picker SHALL 以"YYYY年MM月DD日"格式显示所选日期
-
-### Requirement 2: 历史数据查询
-
-**User Story:** As a 用户, I want 点击查询按钮获取历史数据, so that 我可以查看指定日期区间内的股票池历史信息。
-
-#### Acceptance Criteria
-
-1. WHEN 用户点击查询按钮, THE History_Query_Module SHALL 向后端发送包含开始日期和结束日期的查询请求
-2. WHEN 查询请求发送中, THE History_Query_Module SHALL 显示加载状态
-3. WHEN 后端返回数据成功, THE History_Query_Module SHALL 将数据渲染到历史列表中
-4. WHEN 后端返回空数据, THE History_Query_Module SHALL 显示"暂无数据"提示
-5. IF 网络请求失败, THEN THE History_Query_Module SHALL 显示错误提示并允许用户重试
-
-### Requirement 3: 历史数据列表展示
-
-**User Story:** As a 用户, I want 以列表形式查看历史股票数据, so that 我可以了解每只股票的详细信息和次日表现。
-
-#### Acceptance Criteria
-
-1. THE History_List SHALL 显示以下字段:股票代码、股票名称、收盘价格、隔日最高价、隔日最高涨幅
-2. WHEN 显示股票代码, THE History_List SHALL 展示完整的股票代码(如:000001)
-3. WHEN 显示股票名称, THE History_List SHALL 展示股票的中文名称
-4. WHEN 显示收盘价格, THE History_List SHALL 展示当日收盘价,保留两位小数
-5. WHEN 该股票存在下一交易日数据, THE History_List SHALL 从下一天的历史记录中获取并显示隔日最高价(day_highest_price字段)
-6. WHEN 该股票不存在下一交易日数据, THE History_List SHALL 在隔日最高价列显示"-"
-7. WHEN 该股票存在下一交易日数据, THE History_List SHALL 直接从下一天的历史记录中获取隔日最高涨幅(high_trend字段)
-8. WHEN 该股票不存在下一交易日数据, THE History_List SHALL 在隔日最高涨幅列显示"-"
-9. WHEN 涨幅为正数, THE History_List SHALL 以红色显示涨幅值
-10. WHEN 涨幅为负数, THE History_List SHALL 以绿色显示涨幅值
-11. WHEN 隔日最高涨幅大于等于2%, THE History_List SHALL 显示"成功"标签(绿色背景)
-12. WHEN 隔日最高涨幅小于等于-3%, THE History_List SHALL 显示"失败"标签(红色背景)
-13. WHEN 隔日最高涨幅在-3%到2%之间, THE History_List SHALL 不显示成功/失败标签
-
-### Requirement 4: 后端API支持
-
-**User Story:** As a 系统, I want 提供历史数据查询API, so that 微信小程序可以获取带有隔日信息的历史数据。
-
-#### Acceptance Criteria
-
-1. THE Stock_Pool_History_API SHALL 接收开始日期和结束日期作为查询参数
-2. WHEN 查询历史数据, THE Stock_Pool_History_API SHALL 返回指定日期区间内的所有股票池记录
-3. FOR EACH 返回的股票记录, THE Stock_Pool_History_API SHALL 查询该股票下一交易日的历史数据
-4. WHEN 下一交易日数据存在, THE Stock_Pool_History_API SHALL 在返回结果中包含隔日最高价字段(取自下一天记录的day_highest_price)
-5. WHEN 下一交易日数据存在, THE Stock_Pool_History_API SHALL 在返回结果中包含隔日最高涨幅字段(取自下一天记录的high_trend)
-6. WHEN 下一交易日数据不存在, THE Stock_Pool_History_API SHALL 返回null表示无数据
-
-### Requirement 5: 列表交互体验
-
-**User Story:** As a 用户, I want 流畅地浏览历史数据列表, so that 我可以快速找到感兴趣的股票信息。
-
-#### Acceptance Criteria
-
-1. WHEN 数据量较大, THE History_List SHALL 支持下拉加载更多数据
-2. WHEN 用户下拉刷新, THE History_Query_Module SHALL 重新查询并更新列表数据
-3. WHEN 列表滚动, THE History_List SHALL 保持表头固定可见
-4. THE History_List SHALL 每行显示完整信息,不截断文字

+ 28 - 18
dist/dev/mp-weixin/components/HistorySearchCard.js

@@ -1,5 +1,7 @@
 "use strict";
 const common_vendor = require("../common/vendor.js");
+const utils_auth = require("../utils/auth.js");
+require("../utils/api.js");
 const _sfc_main = {
   __name: "HistorySearchCard",
   props: {
@@ -109,10 +111,6 @@ const _sfc_main = {
       emitDateChange();
     };
     const onSearch = () => {
-      if (!props.canSearch) {
-        common_vendor.index.showToast({ title: "请先订阅该股票池", icon: "none" });
-        return;
-      }
       if (!startDate.value || !endDate.value) {
         common_vendor.index.showToast({ title: "请选择开始和结束日期", icon: "none" });
         return;
@@ -121,6 +119,20 @@ const _sfc_main = {
         common_vendor.index.showToast({ title: "开始日期不能晚于结束日期", icon: "none" });
         return;
       }
+      if (!utils_auth.isLoggedIn()) {
+        common_vendor.index.showModal({
+          title: "登录提示",
+          content: "此功能需要登录后使用,是否前往登录?",
+          confirmText: "去登录",
+          cancelText: "取消",
+          success: (res) => {
+            if (res.confirm) {
+              common_vendor.index.navigateTo({ url: "/pages/login/login" });
+            }
+          }
+        });
+        return;
+      }
       common_vendor.index.navigateTo({
         url: `/pages/history/history?startDate=${startDate.value}&endDate=${endDate.value}&poolType=${props.poolType}`
       });
@@ -135,24 +147,22 @@ const _sfc_main = {
         c: common_vendor.t(formatDateDisplay(endDate.value)),
         d: common_vendor.o(openEndDatePicker),
         e: common_vendor.o(onSearch),
-        f: !__props.canSearch
-      }, !__props.canSearch ? {} : {}, {
-        g: showDatePicker.value
+        f: showDatePicker.value
       }, showDatePicker.value ? {
-        h: common_vendor.o(closeDatePicker),
-        i: common_vendor.t(currentPickerType.value === "start" ? "选择开始日期" : "选择结束日期"),
-        j: common_vendor.o(confirmDate),
-        k: common_vendor.o(prevMonth),
-        l: common_vendor.t(tempYear.value),
-        m: common_vendor.t(tempMonth.value),
-        n: common_vendor.o(nextMonth),
-        o: common_vendor.f(weekDays, (day, k0, i0) => {
+        g: common_vendor.o(closeDatePicker),
+        h: common_vendor.t(currentPickerType.value === "start" ? "选择开始日期" : "选择结束日期"),
+        i: common_vendor.o(confirmDate),
+        j: common_vendor.o(prevMonth),
+        k: common_vendor.t(tempYear.value),
+        l: common_vendor.t(tempMonth.value),
+        m: common_vendor.o(nextMonth),
+        n: common_vendor.f(weekDays, (day, k0, i0) => {
           return {
             a: common_vendor.t(day),
             b: day
           };
         }),
-        p: common_vendor.f(common_vendor.unref(calendarDays), (day, index, i0) => {
+        o: common_vendor.f(common_vendor.unref(calendarDays), (day, index, i0) => {
           return common_vendor.e({
             a: day
           }, day ? {
@@ -167,9 +177,9 @@ const _sfc_main = {
             e: common_vendor.o(($event) => day && selectDay(day), index)
           });
         }),
-        q: common_vendor.o(() => {
+        p: common_vendor.o(() => {
         }),
-        r: common_vendor.o(closeDatePicker)
+        q: common_vendor.o(closeDatePicker)
       } : {});
     };
   }

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/dev/mp-weixin/components/HistorySearchCard.wxml


+ 9 - 2
dist/dev/mp-weixin/pages/admin/shortPool.js

@@ -45,13 +45,20 @@ const _sfc_main = {
     });
     common_vendor.onHide(() => stopAutoRefresh());
     common_vendor.onUnmounted(() => stopAutoRefresh());
+    const getRandomInterval = () => 4500 + Math.random() * 1e3;
     const startAutoRefresh = () => {
       stopAutoRefresh();
-      refreshTimer = setInterval(() => loadStockList(true), 5e3);
+      const scheduleNextRefresh = () => {
+        refreshTimer = setTimeout(async () => {
+          await loadStockList(true);
+          scheduleNextRefresh();
+        }, getRandomInterval());
+      };
+      scheduleNextRefresh();
     };
     const stopAutoRefresh = () => {
       if (refreshTimer) {
-        clearInterval(refreshTimer);
+        clearTimeout(refreshTimer);
         refreshTimer = null;
       }
     };

+ 95 - 42
dist/dev/mp-weixin/pages/index/index.js

@@ -2,6 +2,10 @@
 const common_vendor = require("../../common/vendor.js");
 const utils_api = require("../../utils/api.js");
 const utils_auth = require("../../utils/auth.js");
+if (!Math) {
+  DateSelector();
+}
+const DateSelector = () => "../../components/DateSelector.js";
 const _sfc_main = {
   __name: "index",
   setup(__props) {
@@ -14,6 +18,8 @@ const _sfc_main = {
     const suggestions = common_vendor.ref([]);
     const showDropdown = common_vendor.ref(false);
     const isLoggedIn = common_vendor.ref(false);
+    const selectedDate = common_vendor.ref("");
+    const currentStockCode = common_vendor.ref("");
     let timer = null;
     common_vendor.onMounted(() => {
       isLoggedIn.value = utils_auth.isLoggedIn();
@@ -77,6 +83,22 @@ const _sfc_main = {
       }
     };
     const onSelectSuggestion = (item) => {
+      if (!isLoggedIn.value) {
+        common_vendor.index.showModal({
+          title: "登录提示",
+          content: "此功能需要登录后使用,是否前往登录?",
+          confirmText: "去登录",
+          cancelText: "取消",
+          success: (res) => {
+            if (res.confirm) {
+              common_vendor.index.navigateTo({
+                url: "/pages/login/login"
+              });
+            }
+          }
+        });
+        return;
+      }
       const searchText = `${item.name} (${item.code})`;
       keyword.value = searchText;
       suggestions.value = [];
@@ -108,25 +130,48 @@ const _sfc_main = {
       suggestions.value = [];
       showDropdown.value = false;
       try {
-        const [stockRes, historyRes] = await Promise.all([
-          utils_api.searchStocks(queryCode),
-          utils_api.searchStockHistory(queryCode)
-        ]);
+        const stockRes = await utils_api.searchStocks(queryCode);
         if (stockRes.code === 200 && stockRes.data) {
           result.value = stockRes.data;
+          currentStockCode.value = queryCode;
+          try {
+            const historyRes = await utils_api.searchStockHistory(queryCode, selectedDate.value);
+            if (historyRes.code === 200 && historyRes.data && historyRes.data.found) {
+              historyData.value = historyRes.data;
+              console.log("历史数据:", historyData.value);
+            } else {
+              console.log("未找到历史数据");
+            }
+          } catch (historyErr) {
+            console.error("查询历史数据失败:", historyErr);
+          }
         } else {
           errorMsg.value = stockRes.message || "未查询到相关股票数据";
         }
-        if (historyRes.code === 200 && historyRes.data && historyRes.data.found) {
-          historyData.value = historyRes.data;
-          console.log("历史数据:", historyData.value);
-        }
       } catch (err) {
         errorMsg.value = "网络请求失败,请检查网络连接";
       } finally {
         loading.value = false;
       }
     };
+    const onDateChange = async (dateStr) => {
+      selectedDate.value = dateStr;
+      console.log("选择的日期:", selectedDate.value);
+      if (currentStockCode.value && hasSearched.value && result.value) {
+        historyData.value = null;
+        try {
+          const historyRes = await utils_api.searchStockHistory(currentStockCode.value, selectedDate.value);
+          if (historyRes.code === 200 && historyRes.data && historyRes.data.found) {
+            historyData.value = historyRes.data;
+            console.log("历史数据:", historyData.value);
+          } else {
+            console.log("未找到该日期的历史数据");
+          }
+        } catch (historyErr) {
+          console.error("查询历史数据失败:", historyErr);
+        }
+      }
+    };
     const onInputBlur = () => {
       setTimeout(() => {
         showDropdown.value = false;
@@ -221,59 +266,67 @@ const _sfc_main = {
       }, result.value.score ? {
         D: common_vendor.t(result.value.score)
       } : {}, {
-        E: historyData.value && (historyData.value.strengthScore !== null || historyData.value.highTrend !== null)
-      }, historyData.value && (historyData.value.strengthScore !== null || historyData.value.highTrend !== null) ? common_vendor.e({
-        F: common_vendor.t(historyData.value.recordDate),
-        G: historyData.value.strengthScore !== null && historyData.value.strengthScore !== void 0
+        E: hasSearched.value && result.value
+      }, hasSearched.value && result.value ? common_vendor.e({
+        F: common_vendor.o(onDateChange),
+        G: historyData.value && historyData.value.recordDate
+      }, historyData.value && historyData.value.recordDate ? {
+        H: common_vendor.t(historyData.value.recordDate)
+      } : {}, {
+        I: !historyData.value || !historyData.value.strengthScore && !historyData.value.highTrend
+      }, !historyData.value || !historyData.value.strengthScore && !historyData.value.highTrend ? {
+        J: common_vendor.t(selectedDate.value ? "未找到该日期的历史数据" : "暂无历史数据")
+      } : common_vendor.e({
+        K: historyData.value.strengthScore !== null && historyData.value.strengthScore !== void 0
       }, historyData.value.strengthScore !== null && historyData.value.strengthScore !== void 0 ? {
-        H: common_vendor.t(formatStrengthScore(historyData.value.strengthScore)),
-        I: common_vendor.n(historyData.value.strengthScore >= 0 ? "capsule-up" : "capsule-down")
+        L: common_vendor.t(formatStrengthScore(historyData.value.strengthScore)),
+        M: common_vendor.n(historyData.value.strengthScore >= 0 ? "capsule-up" : "capsule-down")
       } : {}, {
-        J: historyData.value.closePrice
+        N: historyData.value.closePrice
       }, historyData.value.closePrice ? {
-        K: common_vendor.t(historyData.value.closePrice)
+        O: common_vendor.t(historyData.value.closePrice)
       } : {}, {
-        L: historyData.value.changePercent
+        P: historyData.value.changePercent
       }, historyData.value.changePercent ? {
-        M: common_vendor.t(formatPercent(historyData.value.changePercent)),
-        N: common_vendor.n(getChangeClass(historyData.value.changePercent))
+        Q: common_vendor.t(formatPercent(historyData.value.changePercent)),
+        R: common_vendor.n(getChangeClass(historyData.value.changePercent))
       } : {}, {
-        O: historyData.value.totalAmount
+        S: historyData.value.totalAmount
       }, historyData.value.totalAmount ? {
-        P: common_vendor.t(formatAmount(historyData.value.totalAmount))
+        T: common_vendor.t(formatAmount(historyData.value.totalAmount))
       } : {}, {
-        Q: historyData.value.circulationMarketValue
+        U: historyData.value.circulationMarketValue
       }, historyData.value.circulationMarketValue ? {
-        R: common_vendor.t(formatAmount(historyData.value.circulationMarketValue))
+        V: common_vendor.t(formatAmount(historyData.value.circulationMarketValue))
       } : {}, {
-        S: historyData.value.mainRisePeriod
+        W: historyData.value.mainRisePeriod
       }, historyData.value.mainRisePeriod ? {
-        T: common_vendor.t(historyData.value.mainRisePeriod)
+        X: common_vendor.t(historyData.value.mainRisePeriod)
       } : {}, {
-        U: historyData.value.recentLimitUp
+        Y: historyData.value.recentLimitUp
       }, historyData.value.recentLimitUp ? {
-        V: common_vendor.t(historyData.value.recentLimitUp)
+        Z: common_vendor.t(historyData.value.recentLimitUp)
       } : {}, {
-        W: historyData.value.highTrend !== null && historyData.value.highTrend !== void 0
-      }, historyData.value.highTrend !== null && historyData.value.highTrend !== void 0 ? common_vendor.e({
-        X: historyData.value.dayHighestPrice
+        aa: historyData.value.dayHighestPrice
       }, historyData.value.dayHighestPrice ? {
-        Y: common_vendor.t(historyData.value.dayHighestPrice)
+        ab: common_vendor.t(historyData.value.dayHighestPrice)
       } : {}, {
-        Z: historyData.value.dayLowestPrice
+        ac: historyData.value.dayLowestPrice
       }, historyData.value.dayLowestPrice ? {
-        aa: common_vendor.t(historyData.value.dayLowestPrice)
+        ad: common_vendor.t(historyData.value.dayLowestPrice)
       } : {}, {
-        ab: historyData.value.dayClosePrice
+        ae: historyData.value.dayClosePrice
       }, historyData.value.dayClosePrice ? {
-        ac: common_vendor.t(historyData.value.dayClosePrice)
+        af: common_vendor.t(historyData.value.dayClosePrice)
       } : {}, {
-        ad: common_vendor.t(formatPercent(historyData.value.highTrend)),
-        ae: common_vendor.n(getChangeClass(historyData.value.highTrend))
-      }) : {}) : {}, {
-        af: result.value.history && result.value.history.length > 0
+        ag: historyData.value.highTrend !== null && historyData.value.highTrend !== void 0
+      }, historyData.value.highTrend !== null && historyData.value.highTrend !== void 0 ? {
+        ah: common_vendor.t(formatPercent(historyData.value.highTrend)),
+        ai: common_vendor.n(getChangeClass(historyData.value.highTrend))
+      } : {})) : {}, {
+        aj: result.value.history && result.value.history.length > 0
       }, result.value.history && result.value.history.length > 0 ? {
-        ag: common_vendor.f(result.value.history, (item, index, i0) => {
+        ak: common_vendor.f(result.value.history, (item, index, i0) => {
           return {
             a: common_vendor.t(item.date),
             b: common_vendor.t(item.score),
@@ -282,9 +335,9 @@ const _sfc_main = {
           };
         })
       } : {}, {
-        ah: result.value.factors && result.value.factors.length > 0
+        al: result.value.factors && result.value.factors.length > 0
       }, result.value.factors && result.value.factors.length > 0 ? {
-        ai: common_vendor.f(result.value.factors, (item, index, i0) => {
+        am: common_vendor.f(result.value.factors, (item, index, i0) => {
           return {
             a: common_vendor.t(item.name),
             b: common_vendor.t(item.value),

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

@@ -1,4 +1,6 @@
 {
   "navigationBarTitleText": "量化交易大师",
-  "usingComponents": {}
+  "usingComponents": {
+    "date-selector": "../../components/DateSelector"
+  }
 }

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/dev/mp-weixin/pages/index/index.wxml


+ 20 - 0
dist/dev/mp-weixin/pages/index/index.wxss

@@ -70,6 +70,11 @@
   color: #9ca2b5;
 }
 
+/* 日期选择器内嵌样式 */
+.date-selector-inline {
+  margin-top: 24rpx;
+}
+
 /* 下拉列表样式 */
 .search-dropdown {
   position: absolute;
@@ -454,3 +459,18 @@
   font-weight: 600;
   color: #333;
 }
+
+/* 无历史数据提示样式 */
+.no-history-tip {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 40rpx 20rpx;
+  background: #f8f9fc;
+  border-radius: 12rpx;
+  margin-top: 16rpx;
+}
+.tip-text {
+  font-size: 26rpx;
+  color: #9da3b5;
+}

+ 24 - 0
dist/dev/mp-weixin/pages/order/order.js

@@ -8,6 +8,23 @@ const _sfc_main = {
     const orders = common_vendor.ref([]);
     const loading = common_vendor.ref(false);
     const isLoggedIn = common_vendor.ref(false);
+    let refreshTimer = null;
+    const startAutoRefresh = () => {
+      stopAutoRefresh();
+      if (!isLoggedIn.value)
+        return;
+      refreshTimer = setInterval(() => {
+        if (isLoggedIn.value) {
+          loadOrders();
+        }
+      }, 3e4);
+    };
+    const stopAutoRefresh = () => {
+      if (refreshTimer) {
+        clearInterval(refreshTimer);
+        refreshTimer = null;
+      }
+    };
     const checkLogin = () => {
       isLoggedIn.value = utils_auth.isLoggedIn();
     };
@@ -89,6 +106,13 @@ const _sfc_main = {
       checkLogin();
       if (isLoggedIn.value)
         loadOrders();
+      startAutoRefresh();
+    });
+    common_vendor.onHide(() => {
+      stopAutoRefresh();
+    });
+    common_vendor.onUnmounted(() => {
+      stopAutoRefresh();
     });
     return (_ctx, _cache) => {
       return common_vendor.e({

+ 26 - 1
dist/dev/mp-weixin/pages/pool/pool.js

@@ -18,12 +18,17 @@ const _sfc_main = {
     const shortPrice = common_vendor.ref(1);
     const stockList = common_vendor.ref([]);
     let refreshTimer = null;
+    let subscriptionTimer = null;
     const performanceStats = common_vendor.reactive({
       successRate: "0%",
       avgTrend: "+0%",
       totalCount: 0
     });
     const onDateChange = async ({ startDate, endDate, poolType }) => {
+      if (!checkLogin()) {
+        console.log("[超短池] 未登录,跳过统计数据查询");
+        return;
+      }
       try {
         const res = await utils_api.getStockHistoryStats({
           startDate,
@@ -71,6 +76,22 @@ const _sfc_main = {
         refreshTimer = null;
       }
     };
+    const startSubscriptionRefresh = () => {
+      stopSubscriptionRefresh();
+      if (!isPageVisible.value)
+        return;
+      subscriptionTimer = setInterval(() => {
+        if (isPageVisible.value) {
+          checkPurchaseStatus();
+        }
+      }, 3e4);
+    };
+    const stopSubscriptionRefresh = () => {
+      if (subscriptionTimer) {
+        clearInterval(subscriptionTimer);
+        subscriptionTimer = null;
+      }
+    };
     const checkLogin = () => {
       isLoggedIn.value = utils_auth.isLoggedIn();
       return isLoggedIn.value;
@@ -232,21 +253,25 @@ const _sfc_main = {
       console.log("[超短池] onLoad");
       isPageVisible.value = true;
       checkPurchaseStatus();
+      startSubscriptionRefresh();
     });
     common_vendor.onShow(() => {
       console.log("[超短池] onShow");
       isPageVisible.value = true;
       checkPurchaseStatus();
+      startSubscriptionRefresh();
       common_vendor.index.setNavigationBarTitle({ title: "量化交易大师" });
     });
     common_vendor.onHide(() => {
       console.log("[超短池] onHide");
       isPageVisible.value = false;
       stopAutoRefresh();
+      stopSubscriptionRefresh();
     });
     common_vendor.onUnmounted(() => {
       isPageVisible.value = false;
       stopAutoRefresh();
+      stopSubscriptionRefresh();
     });
     return (_ctx, _cache) => {
       return common_vendor.e({
@@ -274,7 +299,7 @@ const _sfc_main = {
         e: common_vendor.o(onDateChange),
         f: common_vendor.p({
           poolType: 1,
-          canSearch: isPurchased.value
+          canSearch: true
         }),
         g: common_vendor.o(closePurchaseModal),
         h: common_vendor.o(handlePurchase),

+ 17 - 9
dist/dev/mp-weixin/pages/rank/rank.js

@@ -452,9 +452,6 @@ const _sfc_main = {
         trendRefreshTimer = null;
       }
     };
-    const goToLogin = () => {
-      common_vendor.index.navigateTo({ url: "/pages/login/login" });
-    };
     const removeStock = async (idx) => {
       const stock = myStocks.value[idx];
       resetSlide(stock.code);
@@ -496,6 +493,22 @@ const _sfc_main = {
       }
       const wasLoggedIn = isLoggedIn.value;
       isLoggedIn.value = utils_auth.isLoggedIn();
+      if (!isLoggedIn.value) {
+        myStocks.value = [];
+        stopAutoRefresh();
+        common_vendor.index.showModal({
+          title: "登录提示",
+          content: "此功能需要登录后使用,是否前往登录",
+          confirmText: "去登录",
+          cancelText: "取消",
+          success: (res) => {
+            if (res.confirm) {
+              common_vendor.index.navigateTo({ url: "/pages/login/login" });
+            }
+          }
+        });
+        return;
+      }
       if (wasLoggedIn !== isLoggedIn.value) {
         loadMyStocks(true);
       } else {
@@ -565,12 +578,7 @@ const _sfc_main = {
         r: viewMode.value === "table",
         s: myStocks.value.length === 0 ? 1 : "",
         t: myStocks.value.length === 0
-      }, myStocks.value.length === 0 ? {} : {}, {
-        v: !isLoggedIn.value ? 1 : "",
-        w: !isLoggedIn.value
-      }, !isLoggedIn.value ? {
-        x: common_vendor.o(goToLogin)
-      } : {});
+      }, myStocks.value.length === 0 ? {} : {});
     };
   }
 };

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/dev/mp-weixin/pages/rank/rank.wxml


+ 11 - 125
dist/dev/mp-weixin/pages/strong/strong.js

@@ -3,17 +3,13 @@ const common_vendor = require("../../common/vendor.js");
 const utils_auth = require("../../utils/auth.js");
 const utils_api = require("../../utils/api.js");
 if (!Math) {
-  (HistorySearchCard + PurchaseModal)();
+  HistorySearchCard();
 }
-const PurchaseModal = () => "../../components/PurchaseModal.js";
 const HistorySearchCard = () => "../../components/HistorySearchCard.js";
 const _sfc_main = {
   __name: "strong",
   setup(__props) {
-    const isPurchased = common_vendor.ref(false);
-    const showModal = common_vendor.ref(false);
     const isPageVisible = common_vendor.ref(false);
-    const strongPrice = common_vendor.ref(98);
     const stockList = common_vendor.ref([]);
     let refreshTimer = null;
     const getChangeClass = (changePercent) => {
@@ -21,7 +17,7 @@ const _sfc_main = {
         return "";
       return changePercent.startsWith("+") ? "text-red" : "text-green";
     };
-    const getRandomInterval = () => 2e3 + Math.random() * 1e3;
+    const getRandomInterval = () => 4500 + Math.random() * 1e3;
     const startAutoRefresh = () => {
       if (!isPageVisible.value)
         return;
@@ -48,27 +44,6 @@ const _sfc_main = {
         refreshTimer = null;
       }
     };
-    const checkPurchaseStatus = async () => {
-      if (!utils_auth.isLoggedIn()) {
-        isPurchased.value = false;
-        stopAutoRefresh();
-        return;
-      }
-      try {
-        const res = await utils_api.checkSubscription(2);
-        if (res.code === 200 && res.data.hasSubscription) {
-          isPurchased.value = true;
-          loadAndStartRefresh();
-        } 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);
@@ -83,85 +58,11 @@ const _sfc_main = {
       await loadStockPool();
       startAutoRefresh();
     };
-    const showPurchaseModal = async () => {
-      if (!utils_auth.isLoggedIn()) {
-        common_vendor.index.showModal({
-          title: "登录提示",
-          content: "此功能需要登录后使用,是否前往登录?",
-          confirmText: "去登录",
-          cancelText: "取消",
-          success: (res) => {
-            if (res.confirm) {
-              common_vendor.index.navigateTo({ url: "/pages/login/login" });
-            }
-          }
-        });
-        return;
-      }
-      try {
-        const res = await utils_api.getPaymentConfig(2);
-        if (res.code === 200 && res.data) {
-          strongPrice.value = res.data.price || 98;
-        }
-      } catch (e) {
-        console.error("获取价格配置失败:", e);
-      }
-      showModal.value = true;
-    };
-    const closePurchaseModal = () => {
-      showModal.value = false;
-    };
-    const pollOrderStatus = async (orderNo, maxRetries = 5, interval = 1e3) => {
-      for (let i = 0; i < maxRetries; i++) {
-        try {
-          const res = await utils_api.queryOrder(orderNo);
-          if (res.code === 200 && res.data && res.data.orderStatus === 1) {
-            return true;
-          }
-        } catch (e) {
-          console.log("查询订单状态失败:", e);
-        }
-        if (i < maxRetries - 1) {
-          await new Promise((r) => setTimeout(r, interval));
-        }
-      }
-      return false;
-    };
-    const handlePurchase = async () => {
-      try {
-        common_vendor.index.showLoading({ title: "正在支付..." });
-        const res = await utils_api.createOrder({ poolType: 2 });
-        if (res.code !== 200) {
-          throw new Error(res.message || "创建订单失败");
-        }
-        const orderNo = res.data.orderNo;
-        common_vendor.index.hideLoading();
-        await utils_api.wxPay(res.data);
-        common_vendor.index.showLoading({ title: "确认支付结果..." });
-        const confirmed = await pollOrderStatus(orderNo);
-        common_vendor.index.hideLoading();
-        if (confirmed) {
-          isPurchased.value = true;
-          closePurchaseModal();
-          common_vendor.index.showToast({ title: "支付成功", icon: "success" });
-          loadAndStartRefresh();
-        } else {
-          closePurchaseModal();
-          common_vendor.index.showToast({ title: "支付处理中,请稍后刷新", icon: "none" });
-        }
-      } catch (e) {
-        common_vendor.index.hideLoading();
-        common_vendor.index.showToast({ title: e.message || "支付失败", icon: "none" });
-      }
-    };
     const addToMyStocks = async (stock) => {
-      const tokenDirect = common_vendor.index.getStorageSync("user_token");
-      console.log("[强势池] 点击自选 - 直接读取token:", tokenDirect ? "有值" : "空");
-      console.log("[强势池] 点击自选 - checkLoginStatus():", utils_auth.isLoggedIn());
       if (!utils_auth.isLoggedIn()) {
         common_vendor.index.showModal({
           title: "登录提示",
-          content: "添加自选股票需要登录,是否前往登录?",
+          content: "此功能需要登录后使用,是否前往登录?",
           confirmText: "去登录",
           cancelText: "取消",
           success: (res) => {
@@ -207,12 +108,12 @@ const _sfc_main = {
     common_vendor.onLoad(() => {
       console.log("[强势池] onLoad");
       isPageVisible.value = true;
-      checkPurchaseStatus();
+      loadAndStartRefresh();
     });
     common_vendor.onShow(() => {
       console.log("[强势池] onShow");
       isPageVisible.value = true;
-      checkPurchaseStatus();
+      loadAndStartRefresh();
       common_vendor.index.setNavigationBarTitle({ title: "量化交易大师" });
     });
     common_vendor.onHide(() => {
@@ -225,12 +126,8 @@ const _sfc_main = {
       stopAutoRefresh();
     });
     return (_ctx, _cache) => {
-      return common_vendor.e({
-        a: !isPurchased.value
-      }, !isPurchased.value ? {
-        b: common_vendor.o(showPurchaseModal)
-      } : {
-        c: common_vendor.f(stockList.value, (stock, index, i0) => {
+      return {
+        a: common_vendor.f(stockList.value, (stock, index, i0) => {
           return {
             a: common_vendor.t(stock.name),
             b: common_vendor.t(stock.code),
@@ -240,23 +137,12 @@ const _sfc_main = {
             f: common_vendor.o(($event) => addToMyStocks(stock), index),
             g: index
           };
-        })
-      }, {
-        d: common_vendor.p({
-          poolType: 2,
-          canSearch: isPurchased.value
         }),
-        e: common_vendor.o(closePurchaseModal),
-        f: common_vendor.o(handlePurchase),
-        g: common_vendor.p({
-          visible: showModal.value,
-          icon: "📅",
-          title: "年订阅解锁",
-          description: "订阅全年,解锁强势趋势池内容",
-          amountLabel: "订阅金额:",
-          amount: strongPrice.value
+        b: common_vendor.p({
+          poolType: 2,
+          canSearch: true
         })
-      });
+      };
     };
   }
 };

+ 0 - 1
dist/dev/mp-weixin/pages/strong/strong.json

@@ -1,7 +1,6 @@
 {
   "navigationBarTitleText": "量化交易大师",
   "usingComponents": {
-    "purchase-modal": "../../components/PurchaseModal",
     "history-search-card": "../../components/HistorySearchCard"
   }
 }

+ 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><view class="card pool-card"><view wx:if="{{a}}" 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="{{b}}"><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="{{c}}" 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 wx:if="{{d}}" u-i="fb001722-0" bind:__l="__l" u-p="{{d}}"/><view class="bottom-safe-area"></view></view></scroll-view><purchase-modal wx:if="{{g}}" bindclose="{{e}}" bindconfirm="{{f}}" u-i="fb001722-1" 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><view class="card pool-card"><view 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="{{a}}" 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 wx:if="{{b}}" u-i="fb001722-0" bind:__l="__l" u-p="{{b}}"/><view class="bottom-safe-area"></view></view></scroll-view></view>

+ 1 - 1
dist/dev/mp-weixin/project.config.json

@@ -13,7 +13,7 @@
   },
   "compileType": "miniprogram",
   "libVersion": "",
-  "appid": "wx9d7e6e3592830447",
+  "appid": "touristappid",
   "projectname": "miniprogram-1",
   "condition": {
     "search": {

+ 20 - 7
dist/dev/mp-weixin/utils/api.js

@@ -1,6 +1,6 @@
 "use strict";
 const common_vendor = require("../common/vendor.js");
-const ENV = "prod";
+const ENV = "dev";
 const CONFIG = {
   dev: "http://localhost:8081",
   // 开发环境
@@ -31,6 +31,11 @@ const request = (options) => {
     if (token) {
       header["Authorization"] = `Bearer ${token}`;
     }
+    const publicApis = [
+      "/v1/stock/pool/list",
+      "/v1/order/config"
+    ];
+    const isPublicApi = publicApis.some((api) => options.url.includes(api));
     common_vendor.index.request({
       url: `${BASE_URL}${options.url}`,
       method: options.method || "GET",
@@ -45,6 +50,10 @@ const request = (options) => {
         if (res.statusCode === 200) {
           resolve(res.data);
         } else if (res.statusCode === 401) {
+          if (isPublicApi) {
+            reject(new Error("未授权"));
+            return;
+          }
           common_vendor.index.showToast({
             title: "登录已过期,请重新登录",
             icon: "none",
@@ -55,13 +64,13 @@ const request = (options) => {
           setTimeout(() => {
             common_vendor.index.showModal({
               title: "登录提示",
-              content: '登录已过期,请前往"模拟排名"或"强势池"页面重新登录',
+              content: "登录已过期,请重新登录",
               confirmText: "去登录",
               cancelText: "取消",
               success: (modalRes) => {
                 if (modalRes.confirm) {
-                  common_vendor.index.switchTab({
-                    url: "/pages/rank/rank"
+                  common_vendor.index.navigateTo({
+                    url: "/pages/login/login"
                   });
                 }
               }
@@ -72,7 +81,7 @@ const request = (options) => {
           reject(new Error(res.data.message || "服务暂不可用"));
         }
       },
-      fail: (err) => {
+      fail: () => {
         reject(new Error("网络异常"));
       }
     });
@@ -274,11 +283,15 @@ const getStockHistoryStats = (params) => {
     data: params
   });
 };
-const searchStockHistory = (keyword) => {
+const searchStockHistory = (keyword, recordDate) => {
+  const data = { keyword };
+  if (recordDate) {
+    data.recordDate = recordDate;
+  }
   return request({
     url: "/v1/stock/history/search",
     method: "GET",
-    data: { keyword }
+    data
   });
 };
 exports.BASE_URL = BASE_URL;

+ 1 - 1
project.private.config.json

@@ -1,6 +1,6 @@
 {
   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
-  "projectname": "miniprogram-1",
+  "projectname": "gupiao-wx",
   "setting": {
     "compileHotReLoad": true,
     "urlCheck": false,

+ 377 - 0
src/components/DateSelector.vue

@@ -0,0 +1,377 @@
+<template>
+  <view class="date-selector-card">
+    <view class="selector-header">
+      <text class="selector-label">历史数据日期</text>
+      <text v-if="selectedDate" class="clear-btn" @click="clearDate">清除</text>
+    </view>
+
+    <view class="date-input" @click="openDatePicker">
+      <text class="date-text">{{ selectedDate ? formatDateDisplay(selectedDate) : '最新数据' }}</text>
+      <text class="date-icon">📅</text>
+    </view>
+
+    <!-- 自定义日期选择弹窗 -->
+    <view v-if="showDatePicker" class="date-picker-mask" @tap="closeDatePicker">
+      <view class="date-picker-popup" @tap.stop>
+        <view class="picker-header">
+          <text class="picker-cancel" @tap="closeDatePicker">取消</text>
+          <text class="picker-title">选择日期</text>
+          <text class="picker-confirm" @tap="confirmDate">确定</text>
+        </view>
+
+        <!-- 年月选择 -->
+        <view class="month-selector">
+          <view class="month-nav" @tap="prevMonth">
+            <text class="nav-arrow">‹</text>
+          </view>
+          <text class="current-month">{{ tempYear }}年{{ tempMonth }}月</text>
+          <view class="month-nav" @tap="nextMonth">
+            <text class="nav-arrow">›</text>
+          </view>
+        </view>
+
+        <!-- 星期标题 -->
+        <view class="weekday-row">
+          <text class="weekday-item" v-for="day in weekDays" :key="day">{{ day }}</text>
+        </view>
+
+        <!-- 日期网格 -->
+        <view class="days-grid">
+          <view
+            v-for="(day, index) in calendarDays"
+            :key="index"
+            :class="['day-item', {
+              'empty': !day,
+              'selected': day && isSelected(day),
+              'today': day && isToday(day)
+            }]"
+            @tap="selectDayHandler(day)"
+          >
+            <text v-if="day" class="day-text">{{ day }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+
+const emit = defineEmits(['dateChange'])
+
+const selectedDate = ref('')
+const showDatePicker = ref(false)
+const tempYear = ref(0)
+const tempMonth = ref(0)
+const tempDay = ref(0)
+
+const weekDays = ['日', '一', '二', '三', '四', '五', '六']
+
+// 获取今天的日期
+const getTodayDate = () => {
+  const now = new Date()
+  return {
+    year: now.getFullYear(),
+    month: now.getMonth() + 1,
+    day: now.getDate()
+  }
+}
+
+// 格式化日期显示
+const formatDateDisplay = (dateStr) => {
+  if (!dateStr) return '最新数据'
+  const [year, month, day] = dateStr.split('-')
+  return `${year}年${month}月${day}日`
+}
+
+// 打开日期选择器
+const openDatePicker = () => {
+  const today = getTodayDate()
+  if (selectedDate.value) {
+    const [year, month, day] = selectedDate.value.split('-')
+    tempYear.value = parseInt(year)
+    tempMonth.value = parseInt(month)
+    tempDay.value = parseInt(day)
+  } else {
+    tempYear.value = today.year
+    tempMonth.value = today.month
+    tempDay.value = 0
+  }
+  showDatePicker.value = true
+}
+
+// 关闭日期选择器
+const closeDatePicker = () => {
+  showDatePicker.value = false
+}
+
+// 上一个月
+const prevMonth = () => {
+  if (tempMonth.value === 1) {
+    tempYear.value--
+    tempMonth.value = 12
+  } else {
+    tempMonth.value--
+  }
+}
+
+// 下一个月
+const nextMonth = () => {
+  const today = getTodayDate()
+  if (tempYear.value === today.year && tempMonth.value === today.month) {
+    return
+  }
+  if (tempMonth.value === 12) {
+    tempYear.value++
+    tempMonth.value = 1
+  } else {
+    tempMonth.value++
+  }
+}
+
+// 生成日历天数
+const calendarDays = computed(() => {
+  const firstDay = new Date(tempYear.value, tempMonth.value - 1, 1).getDay()
+  const daysInMonth = new Date(tempYear.value, tempMonth.value, 0).getDate()
+  const days = []
+
+  for (let i = 0; i < firstDay; i++) {
+    days.push(null)
+  }
+
+  for (let i = 1; i <= daysInMonth; i++) {
+    days.push(i)
+  }
+
+  return days
+})
+
+// 判断是否选中(临时选择状态)
+const isSelected = (day) => {
+  return day === tempDay.value
+}
+
+// 判断是否今天
+const isToday = (day) => {
+  const today = getTodayDate()
+  return day === today.day &&
+         tempYear.value === today.year &&
+         tempMonth.value === today.month
+}
+
+// 选择日期
+const selectDay = (day) => {
+  tempDay.value = day
+}
+
+// 日期选择处理函数(用于模板)
+const selectDayHandler = (day) => {
+  if (!day) return
+  tempDay.value = day
+  console.log('选择日期:', day)
+}
+
+// 确认日期
+const confirmDate = () => {
+  if (!tempDay.value) {
+    uni.showToast({ title: '请选择日期', icon: 'none' })
+    return
+  }
+
+  const dateStr = `${tempYear.value}-${String(tempMonth.value).padStart(2, '0')}-${String(tempDay.value).padStart(2, '0')}`
+  selectedDate.value = dateStr
+  showDatePicker.value = false
+
+  emit('dateChange', dateStr)
+}
+
+// 清除日期
+const clearDate = () => {
+  selectedDate.value = ''
+  emit('dateChange', '')
+}
+</script>
+
+<style scoped>
+.date-selector-card {
+  background: #ffffff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 4rpx 12rpx rgba(37, 52, 94, 0.04);
+}
+
+.selector-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+
+.selector-label {
+  font-size: 26rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.clear-btn {
+  font-size: 24rpx;
+  color: #ef4444;
+  padding: 8rpx 16rpx;
+  background: rgba(239, 68, 68, 0.1);
+  border-radius: 8rpx;
+}
+
+.date-input {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20rpx 24rpx;
+  background: #f7f8fc;
+  border-radius: 12rpx;
+  border: 2rpx solid #e5e7eb;
+}
+
+.date-text {
+  font-size: 28rpx;
+  color: #222222;
+}
+
+.date-icon {
+  font-size: 32rpx;
+}
+
+/* 日期选择器弹窗 */
+.date-picker-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 99999;
+}
+
+.date-picker-popup {
+  width: 90%;
+  max-width: 600rpx;
+  background: #ffffff;
+  border-radius: 24rpx;
+  padding: 32rpx;
+}
+
+.picker-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 32rpx;
+}
+
+.picker-cancel, .picker-confirm {
+  font-size: 28rpx;
+  color: #5d55e8;
+  padding: 8rpx 16rpx;
+}
+
+.picker-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.month-selector {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+  padding: 16rpx 0;
+}
+
+.month-nav {
+  width: 64rpx;
+  height: 64rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f7f8fc;
+  border-radius: 50%;
+}
+
+.nav-arrow {
+  font-size: 48rpx;
+  color: #5d55e8;
+  font-weight: bold;
+}
+
+.current-month {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.weekday-row {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 8rpx;
+  margin-bottom: 16rpx;
+}
+
+.weekday-item {
+  text-align: center;
+  font-size: 24rpx;
+  color: #9ca3af;
+  padding: 12rpx 0;
+}
+
+.days-grid {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 8rpx;
+}
+
+.day-item {
+  aspect-ratio: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 12rpx;
+  cursor: pointer;
+  transition: background-color 0.2s;
+}
+
+.day-item:active {
+  background: #f0f0f0;
+}
+
+.day-item.empty {
+  visibility: hidden;
+  pointer-events: none;
+}
+
+.day-text {
+  font-size: 28rpx;
+  color: #222222;
+}
+
+.day-item.selected {
+  background: #5d55e8;
+}
+
+.day-item.selected .day-text {
+  color: #ffffff;
+  font-weight: 600;
+}
+
+.day-item.today .day-text {
+  color: #5d55e8;
+  font-weight: 600;
+}
+
+.day-item.today.selected .day-text {
+  color: #ffffff;
+}
+</style>

+ 20 - 9
src/components/HistorySearchCard.vue

@@ -24,8 +24,7 @@
       <text class="search-button-text">🔍 查询历史数据</text>
     </view>
     
-    <text v-if="!canSearch" class="history-tip">订阅后可查询该期间的入池股票及表现。</text>
-    <text v-else class="history-tip">选择时间区间,查询该期间的入池股票及表现。</text>
+    <text class="history-tip">选择时间区间,查询该期间的入池股票及表现。</text>
     
     <!-- 自定义日期选择弹窗 -->
     <view v-if="showDatePicker" class="date-picker-mask" @click="closeDatePicker">
@@ -74,6 +73,7 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue'
+import { isLoggedIn as checkLoginStatus } from '../utils/auth.js'
 
 const props = defineProps({
   poolType: { type: Number, default: 1 },
@@ -218,21 +218,32 @@ const confirmDate = () => {
 }
 
 const onSearch = () => {
-  if (!props.canSearch) {
-    uni.showToast({ title: '请先订阅该股票池', icon: 'none' })
-    return
-  }
-  
   if (!startDate.value || !endDate.value) {
     uni.showToast({ title: '请选择开始和结束日期', icon: 'none' })
     return
   }
-  
+
   if (startDate.value > endDate.value) {
     uni.showToast({ title: '开始日期不能晚于结束日期', icon: 'none' })
     return
   }
-  
+
+  // 检查登录状态
+  if (!checkLoginStatus()) {
+    uni.showModal({
+      title: '登录提示',
+      content: '此功能需要登录后使用,是否前往登录?',
+      confirmText: '去登录',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          uni.navigateTo({ url: '/pages/login/login' })
+        }
+      }
+    })
+    return
+  }
+
   // 跳转到历史查询结果页面
   uni.navigateTo({
     url: `/pages/history/history?startDate=${startDate.value}&endDate=${endDate.value}&poolType=${props.poolType}`

+ 13 - 2
src/pages/admin/shortPool.vue

@@ -167,14 +167,25 @@ onShow(() => {
 onHide(() => stopAutoRefresh())
 onUnmounted(() => stopAutoRefresh())
 
+// 获取随机刷新间隔 (4500-5500ms,平均5秒)
+const getRandomInterval = () => 4500 + Math.random() * 1000
+
 const startAutoRefresh = () => {
   stopAutoRefresh()
-  refreshTimer = setInterval(() => loadStockList(true), 5000)
+
+  const scheduleNextRefresh = () => {
+    refreshTimer = setTimeout(async () => {
+      await loadStockList(true)
+      scheduleNextRefresh()
+    }, getRandomInterval())
+  }
+
+  scheduleNextRefresh()
 }
 
 const stopAutoRefresh = () => {
   if (refreshTimer) {
-    clearInterval(refreshTimer)
+    clearTimeout(refreshTimer)
     refreshTimer = null
   }
 }

+ 69 - 6
src/pages/history/history.vue

@@ -17,8 +17,8 @@
           <text class="query-value">{{ formatDate(startDate) }} 至 {{ formatDate(endDate) }}</text>
         </view>
         
-        <!-- 统计信息 -->
-        <view class="stats-card">
+        <!-- 统计信息 (仅超短池显示) -->
+        <view v-if="poolType === 1" class="stats-card">
           <view class="stat-item">
             <text class="stat-value">{{ total }}</text>
             <text class="stat-label">总记录</text>
@@ -34,18 +34,22 @@
             <text class="stat-label">失败</text>
           </view>
         </view>
-        
+
         <!-- 表头 -->
-        <view class="table-header">
+        <view v-if="poolType === 1" class="table-header">
           <text class="th-name">名称/代码</text>
           <text class="th-close">收盘价</text>
           <text class="th-high">隔日最高</text>
           <text class="th-trend">隔日涨幅</text>
         </view>
-        
+        <view v-else class="table-header-simple">
+          <text class="th-name-simple">名称/代码</text>
+          <text class="th-trend-simple">最高涨幅</text>
+        </view>
+
         <!-- 数据列表 -->
         <view class="stock-list">
-          <view class="stock-item" v-for="(item, index) in historyList" :key="index">
+          <view v-if="poolType === 1" class="stock-item" v-for="(item, index) in historyList" :key="index">
             <view class="td-name">
               <text class="stock-name">{{ item.stockName }}</text>
               <text class="stock-code">{{ item.stockCode }}</text>
@@ -61,6 +65,18 @@
               <text v-else-if="item.status === 'fail'" class="status-tag fail">失败</text>
             </view>
           </view>
+          <view v-else class="stock-item-simple" v-for="(item, index) in historyList" :key="index">
+            <view class="td-name-simple">
+              <text class="stock-name">{{ item.stockName }}</text>
+              <text class="stock-code">{{ item.stockCode }}</text>
+              <text class="stock-date">入池: {{ formatRecordDate(item.recordDate) }}</text>
+            </view>
+            <view class="td-trend-simple">
+              <text :class="['trend-value', getTrendClass(item.nextDayHighTrend)]">
+                {{ formatTrend(item.nextDayHighTrend) }}
+              </text>
+            </view>
+          </view>
         </view>
         
         <!-- 加载状态 -->
@@ -314,6 +330,15 @@ onLoad((options) => {
   margin-bottom: 16rpx;
 }
 
+.table-header-simple {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 24rpx;
+  background: #eef0f5;
+  border-radius: 12rpx;
+  margin-bottom: 16rpx;
+}
+
 .th-name {
   width: 180rpx;
   font-size: 24rpx;
@@ -345,6 +370,21 @@ onLoad((options) => {
   text-align: right;
 }
 
+.th-name-simple {
+  flex: 1;
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #666a7f;
+}
+
+.th-trend-simple {
+  width: 200rpx;
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #666a7f;
+  text-align: right;
+}
+
 /* 数据列表 */
 .stock-list {
   display: flex;
@@ -361,6 +401,15 @@ onLoad((options) => {
   box-shadow: 0 4rpx 12rpx rgba(37, 52, 94, 0.04);
 }
 
+.stock-item-simple {
+  display: flex;
+  align-items: center;
+  padding: 24rpx;
+  background: #ffffff;
+  border-radius: 16rpx;
+  box-shadow: 0 4rpx 12rpx rgba(37, 52, 94, 0.04);
+}
+
 .td-name {
   width: 180rpx;
   display: flex;
@@ -409,6 +458,20 @@ onLoad((options) => {
   gap: 6rpx;
 }
 
+.td-name-simple {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.td-trend-simple {
+  width: 200rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  gap: 6rpx;
+}
+
 .trend-value {
   font-size: 28rpx;
   font-weight: 700;

+ 123 - 46
src/pages/index/index.vue

@@ -24,14 +24,14 @@
               <image class="search-icon" src="/static/images/tab_search.png" mode="aspectFit"></image>
             </view>
           </view>
-          
+
           <!-- 模糊搜索下拉列表 -->
           <view class="search-dropdown" v-if="showDropdown && suggestions && suggestions.length > 0">
-            <view 
-              v-for="(item, index) in suggestions" 
+            <view
+              v-for="(item, index) in suggestions"
               :key="index"
-              class="dropdown-item" 
-              hover-class="dropdown-item-hover" 
+              class="dropdown-item"
+              hover-class="dropdown-item-hover"
               @tap.stop="onSelectSuggestion(item)"
             >
               <text class="item-name">{{item.name}}</text>
@@ -106,14 +106,26 @@
             </view>
 
             <!-- 历史数据 -->
-            <view class="section" v-if="historyData && (historyData.strengthScore !== null || historyData.highTrend !== null)">
+            <view class="section" v-if="hasSearched && result">
+              <!-- 日期选择器 -->
+              <view class="history-date-selector">
+                <DateSelector @dateChange="onDateChange" />
+              </view>
+
               <!-- 历史数据标题 -->
               <view class="history-header">
                 <text class="history-title">历史数据</text>
-                <text class="history-date-tag">{{historyData.recordDate}}</text>
+                <text class="history-date-tag" v-if="historyData && historyData.recordDate">{{historyData.recordDate}}</text>
               </view>
-              
-              <!-- 强度评分胶囊 -->
+
+              <!-- 无历史数据提示 -->
+              <view v-if="!historyData || (!historyData.strengthScore && !historyData.highTrend)" class="no-history-tip">
+                <text class="tip-text">{{selectedDate ? '未找到该日期的历史数据' : '暂无历史数据'}}</text>
+              </view>
+
+              <!-- 有历史数据时显示 -->
+              <view v-else>
+                <!-- 强度评分胶囊 -->
               <view class="strength-row" v-if="historyData.strengthScore !== null && historyData.strengthScore !== undefined">
                 <text class="strength-label">强度评分</text>
                 <view class="strength-capsule" :class="historyData.strengthScore >= 0 ? 'capsule-up' : 'capsule-down'">
@@ -147,30 +159,24 @@
                   <text class="detail-label">近期涨停</text>
                   <text class="detail-value">{{historyData.recentLimitUp}}</text>
                 </view>
-              </view>
-              
-              <!-- 隔日表现 -->
-              <view class="next-day-section" v-if="historyData.highTrend !== null && historyData.highTrend !== undefined">
-                <view class="next-day-title">隔日表现</view>
-                <view class="next-day-grid">
-                  <view class="next-day-item" v-if="historyData.dayHighestPrice">
-                    <text class="next-day-label">最高价</text>
-                    <text class="next-day-value">{{historyData.dayHighestPrice}}</text>
-                  </view>
-                  <view class="next-day-item" v-if="historyData.dayLowestPrice">
-                    <text class="next-day-label">最低价</text>
-                    <text class="next-day-value">{{historyData.dayLowestPrice}}</text>
-                  </view>
-                  <view class="next-day-item" v-if="historyData.dayClosePrice">
-                    <text class="next-day-label">收盘价</text>
-                    <text class="next-day-value">{{historyData.dayClosePrice}}</text>
-                  </view>
-                  <view class="next-day-item">
-                    <text class="next-day-label">最高涨幅</text>
-                    <text class="next-day-value" :class="getChangeClass(historyData.highTrend)">{{formatPercent(historyData.highTrend)}}</text>
-                  </view>
+                <view class="history-detail-item" v-if="historyData.dayHighestPrice">
+                  <text class="detail-label">最高价</text>
+                  <text class="detail-value">{{historyData.dayHighestPrice}}</text>
+                </view>
+                <view class="history-detail-item" v-if="historyData.dayLowestPrice">
+                  <text class="detail-label">最低价</text>
+                  <text class="detail-value">{{historyData.dayLowestPrice}}</text>
+                </view>
+                <view class="history-detail-item" v-if="historyData.dayClosePrice">
+                  <text class="detail-label">收盘价</text>
+                  <text class="detail-value">{{historyData.dayClosePrice}}</text>
+                </view>
+                <view class="history-detail-item" v-if="historyData.highTrend !== null && historyData.highTrend !== undefined">
+                  <text class="detail-label">最高涨幅</text>
+                  <text class="detail-value" :class="getChangeClass(historyData.highTrend)">{{formatPercent(historyData.highTrend)}}</text>
                 </view>
               </view>
+              </view>
             </view>
 
             <!-- 历史评分趋势 -->
@@ -241,6 +247,7 @@
 import { ref, onMounted } from 'vue'
 import { getSuggestions, searchStocks, searchStockHistory } from '../../utils/api.js'
 import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
+import DateSelector from '../../components/DateSelector.vue'
 
 // 引入 onShow 生命周期
 import { onShow } from '@dcloudio/uni-app'
@@ -254,6 +261,8 @@ const historyData = ref(null) // 历史数据(强度评分等)
 const suggestions = ref([])
 const showDropdown = ref(false)
 const isLoggedIn = ref(false)
+const selectedDate = ref('') // 选中的日期
+const currentStockCode = ref('') // 当前查询的股票代码
 let timer = null
 
 /**
@@ -279,7 +288,7 @@ onShow(() => {
 const handleSearchClick = async () => {
   console.log('=== 点击搜索按钮 ===')
   console.log('当前登录状态:', isLoggedIn.value)
-  
+
   // 检查登录状态
   if (!isLoggedIn.value) {
     console.log('未登录,跳转到登录页')
@@ -298,7 +307,7 @@ const handleSearchClick = async () => {
     })
     return
   }
-  
+
   console.log('已登录,执行搜索')
   // 已登录,执行搜索
   onSearch()
@@ -343,11 +352,29 @@ const doSearchSuggestions = async (kw) => {
 }
 
 const onSelectSuggestion = (item) => {
+  // 检查登录状态
+  if (!isLoggedIn.value) {
+    uni.showModal({
+      title: '登录提示',
+      content: '此功能需要登录后使用,是否前往登录?',
+      confirmText: '去登录',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          uni.navigateTo({
+            url: '/pages/login/login'
+          })
+        }
+      }
+    })
+    return
+  }
+
   const searchText = `${item.name} (${item.code})`
   keyword.value = searchText
   suggestions.value = []
   showDropdown.value = false
-  
+
   doSearch(item.code)
 }
 
@@ -380,24 +407,30 @@ const doSearch = async (queryCode) => {
   showDropdown.value = false
 
   try {
-    // 同时查询股票信息和历史数据
-    const [stockRes, historyRes] = await Promise.all([
-      searchStocks(queryCode),
-      searchStockHistory(queryCode)
-    ])
-    
+    // 查询股票基本信息
+    const stockRes = await searchStocks(queryCode)
+
     // 处理股票基本信息
     if (stockRes.code === 200 && stockRes.data) {
       result.value = stockRes.data
+      currentStockCode.value = queryCode // 保存股票代码
+
+      // 股票信息查询成功后,再查询历史数据(失败不影响股票信息显示)
+      try {
+        const historyRes = await searchStockHistory(queryCode, selectedDate.value)
+        if (historyRes.code === 200 && historyRes.data && historyRes.data.found) {
+          historyData.value = historyRes.data
+          console.log('历史数据:', historyData.value)
+        } else {
+          console.log('未找到历史数据')
+        }
+      } catch (historyErr) {
+        console.error('查询历史数据失败:', historyErr)
+        // 历史数据查询失败不影响股票基本信息的显示
+      }
     } else {
       errorMsg.value = stockRes.message || '未查询到相关股票数据'
     }
-    
-    // 处理历史数据(强度评分等)
-    if (historyRes.code === 200 && historyRes.data && historyRes.data.found) {
-      historyData.value = historyRes.data
-      console.log('历史数据:', historyData.value)
-    }
   } catch (err) {
     errorMsg.value = '网络请求失败,请检查网络连接'
   } finally {
@@ -405,6 +438,29 @@ const doSearch = async (queryCode) => {
   }
 }
 
+// 日期选择器变化事件
+const onDateChange = async (dateStr) => {
+  selectedDate.value = dateStr
+  console.log('选择的日期:', selectedDate.value)
+
+  // 如果已经有搜索结果,只重新查询历史数据
+  if (currentStockCode.value && hasSearched.value && result.value) {
+    historyData.value = null // 清空历史数据
+
+    try {
+      const historyRes = await searchStockHistory(currentStockCode.value, selectedDate.value)
+      if (historyRes.code === 200 && historyRes.data && historyRes.data.found) {
+        historyData.value = historyRes.data
+        console.log('历史数据:', historyData.value)
+      } else {
+        console.log('未找到该日期的历史数据')
+      }
+    } catch (historyErr) {
+      console.error('查询历史数据失败:', historyErr)
+    }
+  }
+}
+
 const onInputBlur = () => {
   // 延迟关闭,给点击下拉项留出时间
   setTimeout(() => {
@@ -552,6 +608,11 @@ const formatStrengthScore = (value) => {
   color: #9ca2b5;
 }
 
+/* 日期选择器内嵌样式 */
+.date-selector-inline {
+  margin-top: 24rpx;
+}
+
 /* 下拉列表样式 */
 .search-dropdown {
   position: absolute;
@@ -1003,4 +1064,20 @@ const formatStrengthScore = (value) => {
   font-weight: 600;
   color: #333;
 }
+
+/* 无历史数据提示样式 */
+.no-history-tip {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 40rpx 20rpx;
+  background: #f8f9fc;
+  border-radius: 12rpx;
+  margin-top: 16rpx;
+}
+
+.tip-text {
+  font-size: 26rpx;
+  color: #9da3b5;
+}
 </style>

+ 9 - 3
src/pages/pool/pool.vue

@@ -60,10 +60,10 @@
           </view>
         </view>
 
-        <!-- 历史股票池回顾(仅订阅用户可见) -->
-        <HistorySearchCard 
+        <!-- 历史股票池回顾(登录用户可见) -->
+        <HistorySearchCard
           :poolType="1"
-          :canSearch="isPurchased"
+          :canSearch="true"
           @dateChange="onDateChange"
         />
 
@@ -114,6 +114,12 @@ const performanceStats = reactive({
 
 // 日期变化时加载统计数据
 const onDateChange = async ({ startDate, endDate, poolType }) => {
+  // 检查登录状态,未登录则不查询
+  if (!checkLogin()) {
+    console.log('[超短池] 未登录,跳过统计数据查询')
+    return
+  }
+
   try {
     const res = await getStockHistoryStats({
       startDate,

+ 25 - 27
src/pages/rank/rank.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="page-rank">
     <scroll-view class="scroll-view" scroll-y>
-      <view class="content-wrapper" :class="{ 'blur-content': !isLoggedIn }">
+      <view class="content-wrapper">
         <!-- 上证指数卡片 -->
         <view class="index-card">
           <view class="index-left">
@@ -146,28 +146,16 @@
       </view>
     </scroll-view>
 
-    <!-- 未登录遮罩层 -->
-    <view v-if="!isLoggedIn" class="login-mask">
-      <view class="login-prompt">
-        <view class="lock-icon">🔒</view>
-        <text class="prompt-title">登录后查看我的股票</text>
-        <text class="prompt-desc">使用微信授权快速登录</text>
-        <button class="login-button-native" @click="goToLogin">
-          <text>登录</text>
-        </button>
-      </view>
-    </view>
   </view>
 </template>
 
 <script setup>
-import { ref, nextTick, getCurrentInstance, reactive } from 'vue'
+import { ref, nextTick, 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'
 
-// 保存组件实例
-let componentInstance = null
+// 保存组件实例(已移除,不再需要)
 
 const isLoggedIn = ref(false)
 const myStocks = ref([])
@@ -739,13 +727,6 @@ const stopAutoRefresh = () => {
   }
 }
 
-// 跳转到登录页
-const goToLogin = () => {
-  uni.navigateTo({ url: '/pages/login/login' })
-}
-
-
-
 // 删除股票
 const removeStock = async (idx) => {
   const stock = myStocks.value[idx]
@@ -783,7 +764,6 @@ const removeStock = async (idx) => {
 
 onLoad(() => {
   console.log('[我的股票] onLoad 触发')
-  componentInstance = getCurrentInstance()
   isLoggedIn.value = checkLoginStatus()
   isPageVisible.value = true
   loadMyStocks(true) // 首次加载强制刷新
@@ -792,15 +772,33 @@ onLoad(() => {
 onShow(() => {
   console.log('[我的股票] onShow 触发')
   isPageVisible.value = true
-  
+
   // 立即清空所有 canvas,避免显示异常的长条
   if (myStocks.value.length > 0) {
     clearAllCanvases()
   }
-  
+
   const wasLoggedIn = isLoggedIn.value
   isLoggedIn.value = checkLoginStatus()
-  
+
+  // 如果未登录,清空数据并显示登录提示弹窗
+  if (!isLoggedIn.value) {
+    myStocks.value = []
+    stopAutoRefresh()
+    uni.showModal({
+      title: '登录提示',
+      content: '此功能需要登录后使用,是否前往登录',
+      confirmText: '去登录',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          uni.navigateTo({ url: '/pages/login/login' })
+        }
+      }
+    })
+    return
+  }
+
   // 如果登录状态变化了,强制刷新
   if (wasLoggedIn !== isLoggedIn.value) {
     loadMyStocks(true)
@@ -808,7 +806,7 @@ onShow(() => {
     // 否则使用缓存策略
     loadMyStocks(false)
   }
-  
+
   uni.setNavigationBarTitle({ title: '量化交易大师' })
 })
 

+ 19 - 188
src/pages/strong/strong.vue

@@ -13,23 +13,11 @@
 
         <!-- 强势趋势池区域 -->
         <view class="card pool-card">
-          <!-- 未购买时显示锁定状态 -->
-          <view v-if="!isPurchased" 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" @click="showPurchaseModal">
-              <text class="unlock-button-text">立即打赏</text>
-            </view>
-          </view>
-
-          <!-- 已购买时显示内容 -->
-          <view v-else class="unlocked-content">
+          <!-- 直接显示内容(强势池公开访问) -->
+          <view class="unlocked-content">
             <view class="unlocked-header">
               <view class="unlocked-dot"></view>
-              <text class="unlocked-tip">已解锁内容</text>
+              <text class="unlocked-tip">实时更新</text>
             </view>
             <view class="stock-list">
               <view class="stock-item" v-for="(stock, index) in stockList" :key="index">
@@ -53,28 +41,16 @@
           </view>
         </view>
 
-        <!-- 历史股票池回顾(仅订阅用户可见) -->
-        <HistorySearchCard 
+        <!-- 历史股票池回顾(登录用户可见) -->
+        <HistorySearchCard
           :poolType="2"
-          :canSearch="isPurchased"
+          :canSearch="true"
         />
 
         <!-- 预留底部空间 -->
         <view class="bottom-safe-area"></view>
       </view>
     </scroll-view>
-
-    <!-- 购买弹窗 -->
-    <PurchaseModal
-      :visible="showModal"
-      icon="📅"
-      title="年订阅解锁"
-      description="订阅全年,解锁强势趋势池内容"
-      amountLabel="订阅金额:"
-      :amount="strongPrice"
-      @close="closePurchaseModal"
-      @confirm="handlePurchase"
-    />
   </view>
 </template>
 
@@ -82,18 +58,12 @@
 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, createOrder, wxPay, checkSubscription, getPaymentConfig, queryOrder } from '../../utils/api.js'
-import PurchaseModal from '../../components/PurchaseModal.vue'
+import { getStockQuotes, addUserStock, getStockPoolList } from '../../utils/api.js'
 import HistorySearchCard from '../../components/HistorySearchCard.vue'
 
-const isPurchased = ref(false)
-const showModal = ref(false)
 const isPageVisible = ref(false) // 页面是否可见
-const strongPrice = ref(98) // 强势池价格,默认98
-
 const stockList = ref([])
 let refreshTimer = null
-let subscriptionTimer = null
 
 // 获取涨跌样式
 const getChangeClass = (changePercent) => {
@@ -101,20 +71,20 @@ const getChangeClass = (changePercent) => {
   return changePercent.startsWith('+') ? 'text-red' : 'text-green'
 }
 
-// 获取随机刷新间隔 (2000-3000ms)
-const getRandomInterval = () => 2000 + Math.random() * 1000
+// 获取随机刷新间隔 (4500-5500ms,平均5秒)
+const getRandomInterval = () => 4500 + Math.random() * 1000
 
 // 启动自动刷新
 const startAutoRefresh = () => {
   if (!isPageVisible.value) return
   stopAutoRefresh()
-  
+
   const scheduleNextRefresh = () => {
     if (!isPageVisible.value) {
       stopAutoRefresh()
       return
     }
-    
+
     refreshTimer = setTimeout(async () => {
       if (!isPageVisible.value) {
         stopAutoRefresh()
@@ -124,7 +94,7 @@ const startAutoRefresh = () => {
       scheduleNextRefresh()
     }, getRandomInterval())
   }
-  
+
   scheduleNextRefresh()
 }
 
@@ -136,47 +106,6 @@ const stopAutoRefresh = () => {
   }
 }
 
-const startSubscriptionRefresh = () => {
-  stopSubscriptionRefresh()
-  if (!isPageVisible.value) return
-  subscriptionTimer = setInterval(() => {
-    if (isPageVisible.value) {
-      checkPurchaseStatus()
-    }
-  }, 30000)
-}
-
-const stopSubscriptionRefresh = () => {
-  if (subscriptionTimer) {
-    clearInterval(subscriptionTimer)
-    subscriptionTimer = null
-  }
-}
-
-// 检查购买状态(从后端查询)
-const checkPurchaseStatus = async () => {
-  if (!checkLoginStatus()) {
-    isPurchased.value = false
-    stopAutoRefresh()
-    return
-  }
-  
-  try {
-    const res = await checkSubscription(2)  // 2=强势池
-    if (res.code === 200 && res.data.hasSubscription) {
-      isPurchased.value = true
-      loadAndStartRefresh()
-    } else {
-      isPurchased.value = false
-      stopAutoRefresh()
-    }
-  } catch (e) {
-    console.error('检查订阅状态失败:', e)
-    isPurchased.value = false
-    stopAutoRefresh()
-  }
-}
-
 // 加载股票池数据
 const loadStockPool = async () => {
   try {
@@ -195,106 +124,12 @@ const loadAndStartRefresh = async () => {
   startAutoRefresh()
 }
 
-// 显示购买弹窗(需要登录)
-const showPurchaseModal = async () => {
-  if (!checkLoginStatus()) {
-    uni.showModal({
-      title: '登录提示',
-      content: '此功能需要登录后使用,是否前往登录?',
-      confirmText: '去登录',
-      cancelText: '取消',
-      success: (res) => {
-        if (res.confirm) {
-          uni.navigateTo({ url: '/pages/login/login' })
-        }
-      }
-    })
-    return
-  }
-  
-  // 获取最新价格配置
-  try {
-    const res = await getPaymentConfig(2)  // 2=强势池
-    if (res.code === 200 && res.data) {
-      strongPrice.value = res.data.price || 98
-    }
-  } catch (e) {
-    console.error('获取价格配置失败:', e)
-  }
-  
-  showModal.value = true
-}
-
-const closePurchaseModal = () => {
-  showModal.value = false
-}
-
-// 轮询订单状态,确认服务端已处理支付回调
-const pollOrderStatus = async (orderNo, maxRetries = 5, interval = 1000) => {
-  for (let i = 0; i < maxRetries; i++) {
-    try {
-      const res = await queryOrder(orderNo)
-      if (res.code === 200 && res.data && res.data.orderStatus === 1) {
-        return true
-      }
-    } catch (e) {
-      console.log('查询订单状态失败:', e)
-    }
-    if (i < maxRetries - 1) {
-      await new Promise(r => setTimeout(r, interval))
-    }
-  }
-  return false
-}
-
-// 处理购买(调用后端支付接口)
-const handlePurchase = async () => {
-  try {
-    uni.showLoading({ title: '正在支付...' })
-    
-    // 1. 创建订单
-    const res = await createOrder({ poolType: 2 })  // 2=强势池
-    if (res.code !== 200) {
-      throw new Error(res.message || '创建订单失败')
-    }
-    
-    const orderNo = res.data.orderNo
-    uni.hideLoading()
-    
-    // 2. 调起微信支付
-    await wxPay(res.data)
-    
-    // 3. 轮询确认订单状态
-    uni.showLoading({ title: '确认支付结果...' })
-    const confirmed = await pollOrderStatus(orderNo)
-    uni.hideLoading()
-    
-    if (confirmed) {
-      isPurchased.value = true
-      closePurchaseModal()
-      uni.showToast({ title: '支付成功', icon: 'success' })
-      loadAndStartRefresh()
-    } else {
-      closePurchaseModal()
-      uni.showToast({ title: '支付处理中,请稍后刷新', icon: 'none' })
-    }
-  } catch (e) {
-    uni.hideLoading()
-    uni.showToast({ title: e.message || '支付失败', icon: 'none' })
-  }
-}
-
 // 添加到我的股票
 const addToMyStocks = async (stock) => {
-  // 调试:直接读取storage检查
-  const tokenDirect = uni.getStorageSync('user_token')
-  console.log('[强势池] 点击自选 - 直接读取token:', tokenDirect ? '有值' : '空')
-  console.log('[强势池] 点击自选 - checkLoginStatus():', checkLoginStatus())
-  
   if (!checkLoginStatus()) {
     uni.showModal({
       title: '登录提示',
-      content: '添加自选股票需要登录,是否前往登录?',
+      content: '此功能需要登录后使用,是否前往登录?',
       confirmText: '去登录',
       cancelText: '取消',
       success: (res) => {
@@ -308,7 +143,7 @@ const addToMyStocks = async (stock) => {
 
   try {
     uni.showLoading({ title: '添加中...' })
-    
+
     let currentPrice = null
     try {
       const quoteRes = await getStockQuotes(stock.code)
@@ -318,16 +153,16 @@ const addToMyStocks = async (stock) => {
     } catch (e) {
       console.error('获取行情数据失败:', e)
     }
-    
+
     const addRes = await addUserStock({
       stockCode: stock.code,
       stockName: stock.name,
       currentPrice: currentPrice,
       poolType: 2  // 强势池
     })
-    
+
     uni.hideLoading()
-    
+
     if (addRes.code === 200 && addRes.data === true) {
       uni.showToast({ title: '添加成功', icon: 'success' })
     } else if (addRes.code === 200 && addRes.data === false) {
@@ -345,15 +180,13 @@ const addToMyStocks = async (stock) => {
 onLoad(() => {
   console.log('[强势池] onLoad')
   isPageVisible.value = true
-  checkPurchaseStatus()
-  startSubscriptionRefresh()
+  loadAndStartRefresh()
 })
 
 onShow(() => {
   console.log('[强势池] onShow')
   isPageVisible.value = true
-  checkPurchaseStatus()
-  startSubscriptionRefresh()
+  loadAndStartRefresh()
   uni.setNavigationBarTitle({ title: '量化交易大师' })
 })
 
@@ -361,13 +194,11 @@ onHide(() => {
   console.log('[强势池] onHide')
   isPageVisible.value = false
   stopAutoRefresh()
-  stopSubscriptionRefresh()
 })
 
 onUnmounted(() => {
   isPageVisible.value = false
   stopAutoRefresh()
-  stopSubscriptionRefresh()
 })
 </script>
 

+ 36 - 16
src/utils/api.js

@@ -4,7 +4,7 @@
 
 // ============ 配置区域 ============
 
-const ENV = 'prod' // 'dev' | 'prod'
+const ENV = 'dev' // 'dev' | 'prod'
 
 const CONFIG = {
   dev: 'http://localhost:8081',      // 开发环境
@@ -62,7 +62,16 @@ const request = (options) => {
     if (token) {
       header['Authorization'] = `Bearer ${token}`
     }
-    
+
+    // 公开接口列表(不需要登录的接口)
+    const publicApis = [
+      '/v1/stock/pool/list',
+      '/v1/order/config'
+    ]
+
+    // 判断是否为公开接口
+    const isPublicApi = publicApis.some(api => options.url.includes(api))
+
     uni.request({
       url: `${BASE_URL}${options.url}`,
       method: options.method || 'GET',
@@ -75,12 +84,18 @@ const request = (options) => {
           console.log('检测到新token,自动续期')
           uni.setStorageSync('user_token', newToken)
         }
-        
+
         // 统一处理响应
         if (res.statusCode === 200) {
           resolve(res.data)
         } else if (res.statusCode === 401) {
-          // token过期或未登录
+          // 公开接口返回401时,静默处理(不显示登录弹窗)
+          if (isPublicApi) {
+            reject(new Error('未授权'))
+            return
+          }
+
+          // 非公开接口:token过期或未登录
           uni.showToast({
             title: '登录已过期,请重新登录',
             icon: 'none',
@@ -89,31 +104,31 @@ const request = (options) => {
           // 清除token
           uni.removeStorageSync('user_token')
           uni.removeStorageSync('user_info')
-          
+
           // 延迟后显示登录弹窗
           setTimeout(() => {
             uni.showModal({
               title: '登录提示',
-              content: '登录已过期,请前往"模拟排名"或"强势池"页面重新登录',
+              content: '登录已过期,请重新登录',
               confirmText: '去登录',
               cancelText: '取消',
               success: (modalRes) => {
                 if (modalRes.confirm) {
-                  // 跳转到模拟排名页面
-                  uni.switchTab({
-                    url: '/pages/rank/rank'
+                  // 跳转到登录页面
+                  uni.navigateTo({
+                    url: '/pages/login/login'
                   })
                 }
               }
             })
           }, 2000)
-          
+
           reject(new Error('未授权'))
         } else {
           reject(new Error(res.data.message || '服务暂不可用'))
         }
       },
-      fail: (err) => {
+      fail: () => {
         reject(new Error('网络异常'))
       }
     })
@@ -487,14 +502,19 @@ export const getStockHistoryStats = (params) => {
 }
 
 /**
- * 根据股票代码或名称模糊查询最新的历史记录
- * @param {string} keyword - 搜索关键词(股票代码或名称)
- * @returns {Promise} 返回最新的历史记录
+ * 股票详情查询(历史数据)
+ * @param {string} keyword - 股票代码或名称
+ * @param {string} recordDate - 可选的记录日期 (yyyy-MM-dd)
+ * @returns {Promise} 返回股票历史数据
  */
-export const searchStockHistory = (keyword) => {
+export const searchStockHistory = (keyword, recordDate) => {
+  const data = { keyword }
+  if (recordDate) {
+    data.recordDate = recordDate
+  }
   return request({
     url: '/v1/stock/history/search',
     method: 'GET',
-    data: { keyword }
+    data: data
   })
 }

+ 4 - 1
target/classes/META-INF/mps/autoMapper

@@ -1 +1,4 @@
-com.yingpai.miniapp.domain.vo.MiniappUserVo
+org.dromara.web.domain.vo.TenantListVo
+com.yingpai.stock.domain.vo.StockPoolVo
+com.yingpai.stock.domain.vo.StockInfoVo
+com.yingpai.stock.domain.vo.StockPoolHistoryVo

+ 2 - 0
target/classes/META-INF/mps/mappers

@@ -1,6 +1,8 @@
+org.dromara.web.domain.vo.TenantListVoToSysTenantVoMapper
 com.yingpai.stock.domain.vo.StockPoolVoToStockPoolMapper
 com.yingpai.stock.domain.StockPoolToStockPoolVoMapper
 com.yingpai.stock.domain.StockInfoToStockInfoVoMapper
+org.dromara.system.domain.vo.SysTenantVoToTenantListVoMapper
 com.yingpai.stock.domain.vo.StockPoolHistoryVoToStockPoolHistoryMapper
 com.yingpai.stock.domain.StockPoolHistoryToStockPoolHistoryVoMapper
 com.yingpai.stock.domain.vo.StockInfoVoToStockInfoMapper

Некоторые файлы не были показаны из-за большого количества измененных файлов