Prechádzať zdrojové kódy

历史记录查询新增

Zhangbw 4 mesiacov pred
rodič
commit
1a565879b8
41 zmenil súbory, kde vykonal 2081 pridanie a 625 odobranie
  1. 83 0
      .kiro/specs/wx-history-query/requirements.md
  2. 3 1
      dist/dev/mp-weixin/app.js
  3. 3 1
      dist/dev/mp-weixin/app.json
  4. 1 0
      dist/dev/mp-weixin/common/vendor.js
  5. 155 28
      dist/dev/mp-weixin/components/HistorySearchCard.js
  6. 0 1
      dist/dev/mp-weixin/components/HistorySearchCard.wxml
  7. 122 6
      dist/dev/mp-weixin/components/HistorySearchCard.wxss
  8. 1 1
      dist/dev/mp-weixin/components/PerformanceCard.js
  9. 1 1
      dist/dev/mp-weixin/components/PurchaseModal.js
  10. 1 1
      dist/dev/mp-weixin/components/UserInfoPopup.js
  11. 1 1
      dist/dev/mp-weixin/pages/admin/shortPool.js
  12. 93 95
      dist/dev/mp-weixin/pages/index/index.js
  13. 0 0
      dist/dev/mp-weixin/pages/index/index.wxml
  14. 92 98
      dist/dev/mp-weixin/pages/index/index.wxss
  15. 3 6
      dist/dev/mp-weixin/pages/login/login.js
  16. 1 1
      dist/dev/mp-weixin/pages/mine/mine.js
  17. 26 4
      dist/dev/mp-weixin/pages/order/order.js
  18. 74 29
      dist/dev/mp-weixin/pages/pool/pool.js
  19. 1 1
      dist/dev/mp-weixin/pages/pool/pool.wxml
  20. 1 1
      dist/dev/mp-weixin/pages/profile/edit.js
  21. 1 1
      dist/dev/mp-weixin/pages/rank/rank.js
  22. 53 36
      dist/dev/mp-weixin/pages/strong/strong.js
  23. 0 1
      dist/dev/mp-weixin/pages/strong/strong.json
  24. 1 1
      dist/dev/mp-weixin/pages/strong/strong.wxml
  25. 46 6
      dist/dev/mp-weixin/utils/api.js
  26. 7 0
      package-lock.json
  27. 1 0
      package.json
  28. 1 1
      project.config.json
  29. 346 52
      src/components/HistorySearchCard.vue
  30. 12 0
      src/pages.json
  31. 23 0
      src/pages/agreement/agreement.vue
  32. 455 0
      src/pages/history/history.vue
  33. 174 143
      src/pages/index/index.vue
  34. 2 5
      src/pages/login/login.vue
  35. 32 4
      src/pages/order/order.vue
  36. 86 34
      src/pages/pool/pool.vue
  37. 55 37
      src/pages/strong/strong.vue
  38. 92 28
      src/utils/api.js
  39. 3 0
      target/classes/META-INF/mps/autoMapper
  40. 1 0
      target/classes/META-INF/mps/autoMappers
  41. 28 0
      target/classes/META-INF/mps/mappers

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

@@ -0,0 +1,83 @@
+# 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 每行显示完整信息,不截断文字

+ 3 - 1
dist/dev/mp-weixin/app.js

@@ -13,6 +13,8 @@ if (!Math) {
   "./pages/profile/edit.js";
   "./pages/order/order.js";
   "./pages/admin/shortPool.js";
+  "./pages/agreement/agreement.js";
+  "./pages/history/history.js";
 }
 const _sfc_main = {
   globalData: {
@@ -39,7 +41,7 @@ const _sfc_main = {
     utils_auth.stopStatusCheck();
   }
 };
-const App = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao-wx/src/App.vue"]]);
+const App = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao/gupiao-wx/src/App.vue"]]);
 function createApp() {
   const app = common_vendor.createSSRApp(App);
   return {

+ 3 - 1
dist/dev/mp-weixin/app.json

@@ -8,7 +8,9 @@
     "pages/mine/mine",
     "pages/profile/edit",
     "pages/order/order",
-    "pages/admin/shortPool"
+    "pages/admin/shortPool",
+    "pages/agreement/agreement",
+    "pages/history/history"
   ],
   "window": {
     "navigationBarBackgroundColor": "#ffffff",

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

@@ -6809,6 +6809,7 @@ const onHide = /* @__PURE__ */ createHook(ON_HIDE);
 const onLoad = /* @__PURE__ */ createHook(ON_LOAD);
 const onUnload = /* @__PURE__ */ createHook(ON_UNLOAD);
 exports._export_sfc = _export_sfc;
+exports.computed = computed;
 exports.createSSRApp = createSSRApp;
 exports.e = e;
 exports.f = f;

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

@@ -3,49 +3,176 @@ const common_vendor = require("../common/vendor.js");
 const _sfc_main = {
   __name: "HistorySearchCard",
   props: {
-    defaultStartMonth: { type: String, default: "2025-01" },
-    defaultEndMonth: { type: String, default: "2025-11" }
+    poolType: { type: Number, default: 1 },
+    canSearch: { type: Boolean, default: false }
   },
-  emits: ["search"],
+  emits: ["dateChange"],
   setup(__props, { emit }) {
     const props = __props;
-    const startMonth = common_vendor.ref(props.defaultStartMonth);
-    const endMonth = common_vendor.ref(props.defaultEndMonth);
-    const formatMonth = (monthStr) => {
-      if (!monthStr)
+    const getDefaultDates = () => {
+      const now = /* @__PURE__ */ new Date();
+      const year = now.getFullYear();
+      const month = String(now.getMonth() + 1).padStart(2, "0");
+      const lastDay = new Date(year, now.getMonth() + 1, 0).getDate();
+      return {
+        start: `${year}-${month}-01`,
+        end: `${year}-${month}-${lastDay}`
+      };
+    };
+    const defaultDates = getDefaultDates();
+    const startDate = common_vendor.ref(defaultDates.start);
+    const endDate = common_vendor.ref(defaultDates.end);
+    const showDatePicker = common_vendor.ref(false);
+    const currentPickerType = common_vendor.ref("start");
+    const tempYear = common_vendor.ref((/* @__PURE__ */ new Date()).getFullYear());
+    const tempMonth = common_vendor.ref((/* @__PURE__ */ new Date()).getMonth() + 1);
+    const tempSelectedDay = common_vendor.ref(1);
+    const weekDays = ["日", "一", "二", "三", "四", "五", "六"];
+    const calendarDays = common_vendor.computed(() => {
+      const days = [];
+      const firstDay = new Date(tempYear.value, tempMonth.value - 1, 1).getDay();
+      const daysInMonth = new Date(tempYear.value, tempMonth.value, 0).getDate();
+      for (let i = 0; i < firstDay; i++) {
+        days.push(null);
+      }
+      for (let i = 1; i <= daysInMonth; i++) {
+        days.push(i);
+      }
+      return days;
+    });
+    const formatDateDisplay = (dateStr) => {
+      if (!dateStr)
         return "请选择";
-      const [year, month] = monthStr.split("-");
-      return `${year}年${month}月`;
+      const [year, month, day] = dateStr.split("-");
+      return `${year}/${month}/${day}`;
+    };
+    const emitDateChange = () => {
+      emit("dateChange", {
+        startDate: startDate.value,
+        endDate: endDate.value,
+        poolType: props.poolType
+      });
+    };
+    const openStartDatePicker = () => {
+      currentPickerType.value = "start";
+      const [year, month, day] = startDate.value.split("-").map(Number);
+      tempYear.value = year;
+      tempMonth.value = month;
+      tempSelectedDay.value = day;
+      showDatePicker.value = true;
+    };
+    const openEndDatePicker = () => {
+      currentPickerType.value = "end";
+      const [year, month, day] = endDate.value.split("-").map(Number);
+      tempYear.value = year;
+      tempMonth.value = month;
+      tempSelectedDay.value = day;
+      showDatePicker.value = true;
+    };
+    const closeDatePicker = () => {
+      showDatePicker.value = false;
+    };
+    const prevMonth = () => {
+      if (tempMonth.value === 1) {
+        tempMonth.value = 12;
+        tempYear.value--;
+      } else {
+        tempMonth.value--;
+      }
+    };
+    const nextMonth = () => {
+      if (tempMonth.value === 12) {
+        tempMonth.value = 1;
+        tempYear.value++;
+      } else {
+        tempMonth.value++;
+      }
+    };
+    const selectDay = (day) => {
+      tempSelectedDay.value = day;
+    };
+    const isSelected = (day) => {
+      return day === tempSelectedDay.value;
     };
-    const onStartMonthChange = (e) => {
-      startMonth.value = e.detail.value;
+    const isToday = (day) => {
+      const today = /* @__PURE__ */ new Date();
+      return tempYear.value === today.getFullYear() && tempMonth.value === today.getMonth() + 1 && day === today.getDate();
     };
-    const onEndMonthChange = (e) => {
-      endMonth.value = e.detail.value;
+    const confirmDate = () => {
+      const dateStr = `${tempYear.value}-${String(tempMonth.value).padStart(2, "0")}-${String(tempSelectedDay.value).padStart(2, "0")}`;
+      if (currentPickerType.value === "start") {
+        startDate.value = dateStr;
+      } else {
+        endDate.value = dateStr;
+      }
+      showDatePicker.value = false;
+      emitDateChange();
     };
     const onSearch = () => {
-      if (!startMonth.value || !endMonth.value) {
-        common_vendor.index.showToast({ title: "请选择开始和结束月份", icon: "none" });
+      if (!props.canSearch) {
+        common_vendor.index.showToast({ title: "请先订阅该股票池", icon: "none" });
+        return;
+      }
+      if (!startDate.value || !endDate.value) {
+        common_vendor.index.showToast({ title: "请选择开始和结束日期", icon: "none" });
         return;
       }
-      if (startMonth.value > endMonth.value) {
-        common_vendor.index.showToast({ title: "开始月份不能晚于结束月份", icon: "none" });
+      if (startDate.value > endDate.value) {
+        common_vendor.index.showToast({ title: "开始日期不能晚于结束日期", icon: "none" });
         return;
       }
-      emit("search", { startMonth: startMonth.value, endMonth: endMonth.value });
+      common_vendor.index.navigateTo({
+        url: `/pages/history/history?startDate=${startDate.value}&endDate=${endDate.value}&poolType=${props.poolType}`
+      });
     };
+    common_vendor.onMounted(() => {
+      emitDateChange();
+    });
     return (_ctx, _cache) => {
-      return {
-        a: common_vendor.t(formatMonth(startMonth.value)),
-        b: startMonth.value,
-        c: common_vendor.o(onStartMonthChange),
-        d: common_vendor.t(formatMonth(endMonth.value)),
-        e: endMonth.value,
-        f: common_vendor.o(onEndMonthChange),
-        g: common_vendor.o(onSearch)
-      };
+      return common_vendor.e({
+        a: common_vendor.t(formatDateDisplay(startDate.value)),
+        b: common_vendor.o(openStartDatePicker),
+        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
+      }, 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) => {
+          return {
+            a: common_vendor.t(day),
+            b: day
+          };
+        }),
+        p: common_vendor.f(common_vendor.unref(calendarDays), (day, index, i0) => {
+          return common_vendor.e({
+            a: day
+          }, day ? {
+            b: common_vendor.t(day)
+          } : {}, {
+            c: index,
+            d: common_vendor.n({
+              "empty": !day,
+              "selected": day && isSelected(day),
+              "today": day && isToday(day)
+            }),
+            e: common_vendor.o(($event) => day && selectDay(day), index)
+          });
+        }),
+        q: common_vendor.o(() => {
+        }),
+        r: common_vendor.o(closeDatePicker)
+      } : {});
     };
   }
 };
-const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-df43cc4c"], ["__file", "D:/program/gupiao-wx/src/components/HistorySearchCard.vue"]]);
+const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-df43cc4c"], ["__file", "D:/program/gupiao/gupiao-wx/src/components/HistorySearchCard.vue"]]);
 wx.createComponent(Component);

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 1
dist/dev/mp-weixin/components/HistorySearchCard.wxml


+ 122 - 6
dist/dev/mp-weixin/components/HistorySearchCard.wxss

@@ -24,26 +24,26 @@
   gap: 16rpx;
   margin-bottom: 24rpx;
 }
-.date-picker-half.data-v-df43cc4c {
-  flex: 1;
-}
 .date-separator.data-v-df43cc4c {
   font-size: 26rpx;
   color: #666a7f;
   padding: 0 8rpx;
 }
 .date-input.data-v-df43cc4c {
+  flex: 1;
   background: #f7f8fc;
   border-radius: 12rpx;
   padding: 24rpx;
-  font-size: 26rpx;
-  color: #222222;
   display: flex;
   justify-content: space-between;
   align-items: center;
 }
 .date-text.data-v-df43cc4c {
-  flex: 1;
+  font-size: 26rpx;
+  color: #222222;
+}
+.date-icon.data-v-df43cc4c {
+  font-size: 24rpx;
 }
 .history-search-button-full.data-v-df43cc4c {
   width: 100%;
@@ -65,3 +65,119 @@
   color: #9ca2b5;
   line-height: 1.6;
 }
+
+/* 日期选择器弹窗 */
+.date-picker-mask.data-v-df43cc4c {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 1000;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+}
+.date-picker-popup.data-v-df43cc4c {
+  width: 100%;
+  background: #ffffff;
+  border-radius: 32rpx 32rpx 0 0;
+  padding: 32rpx;
+  padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
+}
+.picker-header.data-v-df43cc4c {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 32rpx;
+}
+.picker-cancel.data-v-df43cc4c {
+  font-size: 28rpx;
+  color: #999999;
+  padding: 16rpx;
+}
+.picker-title.data-v-df43cc4c {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+}
+.picker-confirm.data-v-df43cc4c {
+  font-size: 28rpx;
+  color: #5d55e8;
+  font-weight: 600;
+  padding: 16rpx;
+}
+.month-selector.data-v-df43cc4c {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 24rpx;
+  gap: 48rpx;
+}
+.month-nav.data-v-df43cc4c {
+  width: 64rpx;
+  height: 64rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f7f8fc;
+  border-radius: 50%;
+}
+.nav-arrow.data-v-df43cc4c {
+  font-size: 36rpx;
+  color: #5d55e8;
+  font-weight: bold;
+}
+.current-month.data-v-df43cc4c {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+  min-width: 200rpx;
+  text-align: center;
+}
+.weekday-row.data-v-df43cc4c {
+  display: flex;
+  margin-bottom: 16rpx;
+}
+.weekday-item.data-v-df43cc4c {
+  flex: 1;
+  text-align: center;
+  font-size: 24rpx;
+  color: #999999;
+  padding: 16rpx 0;
+}
+.days-grid.data-v-df43cc4c {
+  display: flex;
+  flex-wrap: wrap;
+}
+.day-item.data-v-df43cc4c {
+  width: 14.28%;
+  aspect-ratio: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.day-item.empty.data-v-df43cc4c {
+  background: transparent;
+}
+.day-text.data-v-df43cc4c {
+  width: 72rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  text-align: center;
+  font-size: 28rpx;
+  color: #222222;
+  border-radius: 50%;
+}
+.day-item.selected .day-text.data-v-df43cc4c {
+  background: linear-gradient(135deg, #5d55e8, #7568ff);
+  color: #ffffff;
+  font-weight: 600;
+}
+.day-item.today .day-text.data-v-df43cc4c {
+  border: 2rpx solid #5d55e8;
+}
+.day-item.today.selected .day-text.data-v-df43cc4c {
+  border: none;
+}

+ 1 - 1
dist/dev/mp-weixin/components/PerformanceCard.js

@@ -17,5 +17,5 @@ const _sfc_main = {
     };
   }
 };
-const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-7eda9556"], ["__file", "D:/program/gupiao-wx/src/components/PerformanceCard.vue"]]);
+const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-7eda9556"], ["__file", "D:/program/gupiao/gupiao-wx/src/components/PerformanceCard.vue"]]);
 wx.createComponent(Component);

+ 1 - 1
dist/dev/mp-weixin/components/PurchaseModal.js

@@ -32,5 +32,5 @@ const _sfc_main = {
     };
   }
 };
-const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-1af578f4"], ["__file", "D:/program/gupiao-wx/src/components/PurchaseModal.vue"]]);
+const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-1af578f4"], ["__file", "D:/program/gupiao/gupiao-wx/src/components/PurchaseModal.vue"]]);
 wx.createComponent(Component);

+ 1 - 1
dist/dev/mp-weixin/components/UserInfoPopup.js

@@ -129,5 +129,5 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     i: common_vendor.o((...args) => $options.handleMaskClick && $options.handleMaskClick(...args))
   } : {});
 }
-const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-a5b292f7"], ["__file", "D:/program/gupiao-wx/src/components/UserInfoPopup.vue"]]);
+const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-a5b292f7"], ["__file", "D:/program/gupiao/gupiao-wx/src/components/UserInfoPopup.vue"]]);
 wx.createComponent(Component);

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

@@ -206,5 +206,5 @@ const _sfc_main = {
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-98720f9d"], ["__file", "D:/program/gupiao-wx/src/pages/admin/shortPool.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-98720f9d"], ["__file", "D:/program/gupiao/gupiao-wx/src/pages/admin/shortPool.vue"]]);
 wx.createPage(MiniProgramPage);

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

@@ -12,63 +12,21 @@ const _sfc_main = {
     const result = common_vendor.ref(null);
     const suggestions = common_vendor.ref([]);
     const showDropdown = common_vendor.ref(false);
-    const searching = common_vendor.ref(false);
     const isLoggedIn = common_vendor.ref(false);
-    let searchTimer = null;
+    let timer = null;
     common_vendor.onMounted(() => {
       isLoggedIn.value = utils_auth.isLoggedIn();
+      console.log("[首页] 登录状态:", isLoggedIn.value);
     });
     common_vendor.onShow(() => {
       isLoggedIn.value = utils_auth.isLoggedIn();
       common_vendor.index.setNavigationBarTitle({ title: "量化交易大师" });
     });
-    common_vendor.onHide(() => {
-      showDropdown.value = false;
-    });
-    const onInputFocus = () => {
-      if (suggestions.value.length > 0) {
-        showDropdown.value = true;
-      }
-    };
-    const onKeywordChange = (e) => {
-      const value = e.detail.value;
-      keyword.value = value;
-      if (searchTimer)
-        clearTimeout(searchTimer);
-      if (!value || !value.trim()) {
-        suggestions.value = [];
-        showDropdown.value = false;
-        return;
-      }
-      searching.value = true;
-      showDropdown.value = true;
-      searchTimer = setTimeout(async () => {
-        try {
-          const response = await utils_api.getSuggestions(value.trim());
-          if (response.code === 200 && response.data) {
-            suggestions.value = Array.isArray(response.data) ? response.data : [];
-          } else {
-            suggestions.value = [];
-          }
-        } catch (err) {
-          console.error("搜索建议错误:", err);
-          suggestions.value = [];
-        } finally {
-          searching.value = false;
-        }
-      }, 300);
-    };
-    const closeDropdown = () => {
-      showDropdown.value = false;
-    };
-    const onSelectSuggestion = (item) => {
-      keyword.value = `${item.name} (${item.code})`;
-      suggestions.value = [];
-      showDropdown.value = false;
-      doSearch(item.code);
-    };
-    const handleSearchClick = () => {
+    const handleSearchClick = async () => {
+      console.log("=== 点击搜索按钮 ===");
+      console.log("当前登录状态:", isLoggedIn.value);
       if (!isLoggedIn.value) {
+        console.log("未登录,跳转到登录页");
         common_vendor.index.showModal({
           title: "登录提示",
           content: "此功能需要登录后使用,是否前往登录?",
@@ -76,22 +34,63 @@ const _sfc_main = {
           cancelText: "取消",
           success: (res) => {
             if (res.confirm) {
-              common_vendor.index.navigateTo({ url: "/pages/login/login" });
+              common_vendor.index.navigateTo({
+                url: "/pages/login/login"
+              });
             }
           }
         });
         return;
       }
+      console.log("已登录,执行搜索");
       onSearch();
     };
+    const onKeywordChange = (e) => {
+      const value = e.detail.value;
+      keyword.value = value;
+      console.log("输入关键词:", value);
+      if (timer) {
+        clearTimeout(timer);
+      }
+      timer = setTimeout(() => {
+        doSearchSuggestions(value);
+      }, 500);
+    };
+    const doSearchSuggestions = async (kw) => {
+      if (!kw || !kw.trim()) {
+        suggestions.value = [];
+        showDropdown.value = false;
+        return;
+      }
+      try {
+        const response = await utils_api.getSuggestions(kw.trim());
+        console.log("模糊查询返回数据:", response);
+        const list = response.code === 200 && response.data ? response.data : [];
+        suggestions.value = Array.isArray(list) ? list : [];
+        showDropdown.value = suggestions.value.length > 0;
+        console.log("下拉框状态:", { showDropdown: showDropdown.value, suggestionsLength: suggestions.value.length });
+      } catch (err) {
+        console.error("模糊查询错误:", err);
+        suggestions.value = [];
+        showDropdown.value = false;
+      }
+    };
+    const onSelectSuggestion = (item) => {
+      const searchText = `${item.name} (${item.code})`;
+      keyword.value = searchText;
+      suggestions.value = [];
+      showDropdown.value = false;
+      doSearch(item.code);
+    };
     const onSearch = () => {
       const kw = (keyword.value || "").trim();
       if (!kw) {
-        common_vendor.index.showToast({ title: "请输入股票代码或名称", icon: "none" });
+        common_vendor.index.showToast({
+          title: "请输入股票代码或名称",
+          icon: "none"
+        });
         return;
       }
-      showDropdown.value = false;
-      suggestions.value = [];
       let searchCode = kw;
       const codeMatch = kw.match(/\((\d{6})\)/);
       if (codeMatch) {
@@ -104,8 +103,8 @@ const _sfc_main = {
       hasSearched.value = true;
       errorMsg.value = "";
       result.value = null;
-      showDropdown.value = false;
       suggestions.value = [];
+      showDropdown.value = false;
       try {
         const res = await utils_api.searchStocks(queryCode);
         if (res.code === 200 && res.data) {
@@ -119,6 +118,11 @@ const _sfc_main = {
         loading.value = false;
       }
     };
+    const onInputBlur = () => {
+      setTimeout(() => {
+        showDropdown.value = false;
+      }, 300);
+    };
     const getPriceClass = (changePercent) => {
       if (!changePercent)
         return "";
@@ -130,18 +134,14 @@ const _sfc_main = {
     };
     return (_ctx, _cache) => {
       return common_vendor.e({
-        a: showDropdown.value && suggestions.value.length > 0
-      }, showDropdown.value && suggestions.value.length > 0 ? {
-        b: common_vendor.o(closeDropdown)
-      } : {}, {
-        c: common_vendor.o([($event) => keyword.value = $event.detail.value, onKeywordChange]),
-        d: common_vendor.o(handleSearchClick),
-        e: common_vendor.o(onInputFocus),
-        f: keyword.value,
-        g: common_vendor.o(handleSearchClick),
-        h: showDropdown.value && suggestions.value.length > 0
-      }, showDropdown.value && suggestions.value.length > 0 ? {
-        i: common_vendor.f(suggestions.value, (item, index, i0) => {
+        a: common_vendor.o([($event) => keyword.value = $event.detail.value, onKeywordChange]),
+        b: common_vendor.o(handleSearchClick),
+        c: common_vendor.o(onInputBlur),
+        d: keyword.value,
+        e: common_vendor.o(handleSearchClick),
+        f: showDropdown.value && suggestions.value && suggestions.value.length > 0
+      }, showDropdown.value && suggestions.value && suggestions.value.length > 0 ? {
+        g: common_vendor.f(suggestions.value, (item, index, i0) => {
           return {
             a: common_vendor.t(item.name),
             b: common_vendor.t(item.code),
@@ -150,61 +150,59 @@ const _sfc_main = {
           };
         })
       } : {}, {
-        j: showDropdown.value && searching.value
-      }, showDropdown.value && searching.value ? {} : {}, {
-        k: common_vendor.t(isLoggedIn.value ? "" : "(需登录)"),
-        l: hasSearched.value
+        h: common_vendor.t(isLoggedIn.value ? "" : "(需登录)"),
+        i: hasSearched.value
       }, hasSearched.value ? common_vendor.e({
-        m: loading.value
+        j: loading.value
       }, loading.value ? {} : errorMsg.value ? {
-        o: common_vendor.t(errorMsg.value)
+        l: common_vendor.t(errorMsg.value)
       } : result.value ? common_vendor.e({
-        q: common_vendor.t(result.value.stockName),
-        r: common_vendor.t(result.value.stockCode),
-        s: common_vendor.t(result.value.currentPrice || "--"),
+        n: common_vendor.t(result.value.stockName),
+        o: common_vendor.t(result.value.stockCode),
+        p: common_vendor.t(result.value.currentPrice || "--"),
+        q: common_vendor.n(getPriceClass(result.value.changePercent)),
+        r: common_vendor.t(result.value.priceChange || "--"),
+        s: common_vendor.t(result.value.changePercent || "--"),
         t: common_vendor.n(getPriceClass(result.value.changePercent)),
-        v: common_vendor.t(result.value.priceChange || "--"),
-        w: common_vendor.t(result.value.changePercent || "--"),
-        x: common_vendor.n(getPriceClass(result.value.changePercent)),
-        y: result.value.currentPrice
+        v: result.value.currentPrice
       }, result.value.currentPrice ? {
-        z: common_vendor.t(result.value.openPrice || "--"),
-        A: common_vendor.t(result.value.highPrice || "--"),
-        B: common_vendor.t(result.value.lowPrice || "--"),
-        C: common_vendor.t(result.value.volume || "--"),
-        D: common_vendor.t(result.value.amount || "--"),
-        E: common_vendor.t(result.value.turnoverRate || "--")
+        w: common_vendor.t(result.value.openPrice || "--"),
+        x: common_vendor.t(result.value.highPrice || "--"),
+        y: common_vendor.t(result.value.lowPrice || "--"),
+        z: common_vendor.t(result.value.volume || "--"),
+        A: common_vendor.t(result.value.amount || "--"),
+        B: common_vendor.t(result.value.turnoverRate || "--")
       } : {}, {
-        F: result.value.score
+        C: result.value.score
       }, result.value.score ? {
-        G: common_vendor.t(result.value.score)
+        D: common_vendor.t(result.value.score)
       } : {}, {
-        H: result.value.history && result.value.history.length > 0
+        E: result.value.history && result.value.history.length > 0
       }, result.value.history && result.value.history.length > 0 ? {
-        I: common_vendor.f(result.value.history, (item, idx, i0) => {
+        F: common_vendor.f(result.value.history, (item, index, i0) => {
           return {
             a: common_vendor.t(item.date),
             b: common_vendor.t(item.score),
             c: common_vendor.n(item.score >= 90 ? "tag-danger" : item.score >= 80 ? "tag-success" : "tag-info"),
-            d: idx
+            d: index
           };
         })
       } : {}, {
-        J: result.value.factors && result.value.factors.length > 0
+        G: result.value.factors && result.value.factors.length > 0
       }, result.value.factors && result.value.factors.length > 0 ? {
-        K: common_vendor.f(result.value.factors, (item, idx, i0) => {
+        H: common_vendor.f(result.value.factors, (item, index, i0) => {
           return {
             a: common_vendor.t(item.name),
             b: common_vendor.t(item.value),
-            c: idx
+            c: index
           };
         })
       } : {}) : {}, {
-        n: errorMsg.value,
-        p: result.value
+        k: errorMsg.value,
+        m: result.value
       }) : {});
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-83a5a03c"], ["__file", "D:/program/gupiao-wx/src/pages/index/index.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao/gupiao-wx/src/pages/index/index.vue"]]);
 wx.createPage(MiniProgramPage);

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
dist/dev/mp-weixin/pages/index/index.wxml


+ 92 - 98
dist/dev/mp-weixin/pages/index/index.wxss

@@ -1,53 +1,38 @@
 
-.page-container.data-v-83a5a03c {
+/** 首页量化查询界面样式 **/
+.page-container {
   height: 100vh;
   display: flex;
   flex-direction: column;
   background: #f5f6fb;
 }
-.scroll-view.data-v-83a5a03c {
+.scroll-view {
   flex: 1;
   height: 0;
 }
-.content-wrapper.data-v-83a5a03c {
+.content-wrapper {
   padding: 32rpx 32rpx 48rpx;
 }
-
-/* 遮罩层 */
-.mask.data-v-83a5a03c {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.3);
-  z-index: 99;
-}
-.card.data-v-83a5a03c {
+.card {
   background: #ffffff;
   border-radius: 24rpx;
   padding: 32rpx 32rpx 36rpx;
   box-shadow: 0 16rpx 40rpx rgba(37, 52, 94, 0.08);
   margin-bottom: 32rpx;
+  position: relative;
 }
-
-/* 搜索卡片 - 关键:设置 z-index 和 position */
-.search-card.data-v-83a5a03c {
+.search-card {
   margin-top: 16rpx;
-  position: relative;
-  z-index: 100;
+  z-index: 10;
 }
-.card-title.data-v-83a5a03c {
+.card-title {
   display: block;
   font-size: 32rpx;
   font-weight: 600;
   color: #222222;
   margin-bottom: 32rpx;
 }
-.search-box.data-v-83a5a03c {
-  position: relative;
-}
-.search-input-wrap.data-v-83a5a03c {
+.search-row {
   display: flex;
   align-items: center;
   background: #f7f8fc;
@@ -56,12 +41,15 @@
   height: 96rpx;
   box-sizing: border-box;
 }
-.search-input.data-v-83a5a03c {
+.search-input {
   flex: 1;
   font-size: 28rpx;
   color: #222222;
 }
-.search-button.data-v-83a5a03c {
+.search-placeholder {
+  color: #b4b8c6;
+}
+.search-button {
   width: 96rpx;
   height: 80rpx;
   margin-left: 12rpx;
@@ -71,71 +59,65 @@
   align-items: center;
   justify-content: center;
 }
-.search-icon.data-v-83a5a03c {
+.search-icon {
   width: 48rpx;
   height: 48rpx;
 }
-.search-tip.data-v-83a5a03c {
+.search-tip {
   display: block;
   margin-top: 20rpx;
   font-size: 24rpx;
   color: #9ca2b5;
 }
 
-/* 搜索建议下拉 - 关键样式 */
-.suggestions-dropdown.data-v-83a5a03c {
+/* 下拉列表样式 */
+.search-dropdown {
   position: absolute;
-  top: 100%;
+  top: 100%; /* 相对于父元素定位,在搜索框下方 */
   left: 0;
   right: 0;
+  margin-top: 8rpx;
   background: #ffffff;
   border-radius: 16rpx;
-  margin-top: 12rpx;
-  box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
-  max-height: 480rpx;
+  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15);
+  max-height: 400rpx;
   overflow-y: auto;
-  z-index: 101;
+  z-index: 1000; /* 提高层级,确保在最上层 */
+  border: 1rpx solid #f0f0f0;
 }
-.suggestion-item.data-v-83a5a03c {
+.dropdown-item {
+  padding: 24rpx 32rpx;
+  border-bottom: 1rpx solid #f5f6fb;
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 28rpx 24rpx;
-  border-bottom: 1rpx solid #f5f6fb;
 }
-.suggestion-item.data-v-83a5a03c:last-child {
+.dropdown-item:last-child {
   border-bottom: none;
 }
-.suggestion-item.data-v-83a5a03c:active {
-  background: #f9f9fb;
+.dropdown-item-hover {
+  background-color: #f7f8fc;
 }
-.suggestion-name.data-v-83a5a03c {
+.item-name {
   font-size: 28rpx;
-  font-weight: 600;
-  color: #222222;
+  color: #333;
 }
-.suggestion-code.data-v-83a5a03c {
+.item-code {
   font-size: 24rpx;
-  color: #9ca2b5;
-}
-.suggestion-loading.data-v-83a5a03c {
-  padding: 48rpx;
-  text-align: center;
-  color: #9ca2b5;
-  font-size: 28rpx;
+  color: #999;
 }
-.result-card.data-v-83a5a03c {
+.result-card {
   margin-top: 8rpx;
 }
-.result-loading.data-v-83a5a03c {
+.result-loading {
   font-size: 26rpx;
   color: #666a7f;
 }
-.result-error.data-v-83a5a03c {
+.result-error {
   font-size: 26rpx;
   color: #ff5b5b;
 }
-.detail-header.data-v-83a5a03c {
+.detail-header {
   display: flex;
   justify-content: space-between;
   align-items: flex-start;
@@ -143,68 +125,77 @@
   padding-bottom: 24rpx;
   border-bottom: 1rpx solid #f1f2f6;
 }
-.stock-info-left.data-v-83a5a03c {
+.stock-info-left {
   flex: 1;
 }
-.detail-name.data-v-83a5a03c {
+.detail-name {
   font-size: 34rpx;
   font-weight: 700;
   color: #1f1f2e;
 }
-.detail-code.data-v-83a5a03c {
+.detail-code {
   margin-top: 8rpx;
   font-size: 26rpx;
   color: #9da3b5;
 }
-.stock-price-right.data-v-83a5a03c {
+.detail-sub {
+  margin-top: 8rpx;
+  font-size: 24rpx;
+  color: #9da3b5;
+}
+.stock-price-right {
   text-align: right;
 }
-.current-price.data-v-83a5a03c {
+.current-price {
   font-size: 44rpx;
   font-weight: 700;
   color: #333;
 }
-.price-change.data-v-83a5a03c {
+.price-change {
   margin-top: 6rpx;
   font-size: 26rpx;
 }
-.price-up.data-v-83a5a03c { color: #e74c3c;
+.price-up {
+  color: #e74c3c;
 }
-.price-down.data-v-83a5a03c { color: #27ae60;
+.price-down {
+  color: #27ae60;
 }
-.quote-section.data-v-83a5a03c {
+
+/* 实时行情网格 */
+.quote-section {
   margin-bottom: 24rpx;
 }
-.quote-grid.data-v-83a5a03c {
+.quote-grid {
   display: flex;
   flex-wrap: wrap;
   background: #f8f9fc;
   border-radius: 16rpx;
   padding: 20rpx 16rpx;
 }
-.quote-item.data-v-83a5a03c {
+.quote-item {
   width: 33.33%;
   display: flex;
   flex-direction: column;
   align-items: center;
   padding: 12rpx 0;
 }
-.quote-label.data-v-83a5a03c {
+.quote-label {
   font-size: 24rpx;
   color: #9da3b5;
   margin-bottom: 8rpx;
 }
-.quote-value.data-v-83a5a03c {
+.quote-value {
   font-size: 28rpx;
   font-weight: 600;
   color: #333;
 }
-.score-display.data-v-83a5a03c {
+.score-display {
   display: flex;
   justify-content: center;
   margin-top: 16rpx;
 }
-.score-badge.data-v-83a5a03c {
+.score-badge {
   min-width: 140rpx;
   padding: 12rpx 22rpx;
   border-radius: 20rpx;
@@ -212,33 +203,33 @@
   color: #ffffff;
   font-size: 40rpx;
   font-weight: 700;
-  text-align: center;
+  text-align: right;
   box-shadow: 0 10rpx 24rpx rgba(241, 101, 101, 0.35);
 }
-.section.data-v-83a5a03c {
+.section {
   margin-top: 20rpx;
 }
-.section-title.data-v-83a5a03c {
+.section-title {
   font-size: 28rpx;
   font-weight: 700;
   color: #1f1f2e;
   margin-bottom: 16rpx;
 }
-.history-row.data-v-83a5a03c {
+.history-row {
   display: flex;
   align-items: center;
   justify-content: space-between;
   padding: 18rpx 0;
   border-bottom: 1rpx solid #f1f2f6;
 }
-.history-row.data-v-83a5a03c:last-child {
+.history-row:last-child {
   border-bottom: none;
 }
-.history-date.data-v-83a5a03c {
+.history-date {
   font-size: 26rpx;
   color: #3c4050;
 }
-.history-score.data-v-83a5a03c {
+.history-score {
   min-width: 96rpx;
   text-align: center;
   padding: 6rpx 18rpx;
@@ -247,44 +238,47 @@
   font-weight: 700;
   color: #ffffff;
 }
-.tag-danger.data-v-83a5a03c { background: #f16565;
+.tag-danger {
+  background: #f16565;
 }
-.tag-success.data-v-83a5a03c { background: #3abf81;
+.tag-success {
+  background: #3abf81;
 }
-.tag-info.data-v-83a5a03c { background: #4c86ff;
+.tag-info {
+  background: #4c86ff;
 }
-.history-note.data-v-83a5a03c {
+.history-note {
   display: block;
   margin-top: 12rpx;
   font-size: 22rpx;
   color: #9da3b5;
 }
-.factor-row.data-v-83a5a03c {
+.factor-row {
   display: flex;
   align-items: center;
   justify-content: space-between;
   padding: 14rpx 0;
 }
-.factor-name.data-v-83a5a03c {
+.factor-name {
   font-size: 26rpx;
   color: #3c4050;
 }
-.factor-score.data-v-83a5a03c {
+.factor-score {
   font-size: 28rpx;
   font-weight: 600;
   color: #4c86ff;
 }
-.card-header.data-v-83a5a03c {
+.card-header {
   display: flex;
   align-items: center;
   margin-bottom: 24rpx;
 }
-.card-header-title.data-v-83a5a03c {
+.card-header-title {
   font-size: 30rpx;
   font-weight: 600;
   color: #222222;
 }
-.icon-circle.data-v-83a5a03c {
+.icon-circle {
   width: 40rpx;
   height: 40rpx;
   border-radius: 50%;
@@ -294,26 +288,26 @@
   justify-content: center;
   margin-right: 16rpx;
 }
-.icon-check.data-v-83a5a03c {
+.icon-check {
   font-size: 24rpx;
   color: #28a745;
 }
-.advantage-item.data-v-83a5a03c {
+.advantage-item {
   margin-bottom: 12rpx;
   font-size: 26rpx;
   line-height: 1.6;
   color: #4a4f63;
 }
-.advantage-label.data-v-83a5a03c {
+.advantage-label {
   font-weight: 600;
 }
-.advantage-desc.data-v-83a5a03c {
+.advantage-desc {
   font-weight: 400;
 }
-.risk-card.data-v-83a5a03c {
+.risk-card {
   margin-bottom: 24rpx;
 }
-.icon-warning.data-v-83a5a03c {
+.icon-warning {
   width: 40rpx;
   height: 40rpx;
   border-radius: 50%;
@@ -323,16 +317,16 @@
   justify-content: center;
   margin-right: 16rpx;
 }
-.icon-warning-text.data-v-83a5a03c {
+.icon-warning-text {
   font-size: 26rpx;
   color: #ff5b5b;
 }
-.risk-text.data-v-83a5a03c {
+.risk-text {
   display: block;
   font-size: 24rpx;
   line-height: 1.8;
   color: #666a7f;
 }
-.bottom-safe-area.data-v-83a5a03c {
+.bottom-safe-area {
   height: 80rpx;
 }

+ 3 - 6
dist/dev/mp-weixin/pages/login/login.js

@@ -293,11 +293,8 @@ const _sfc_main = {
      * 显示协议内容
      */
     showAgreement(type) {
-      const title = type === "user" ? "用户协议" : "隐私政策";
-      common_vendor.index.showModal({
-        title,
-        content: "这里显示协议内容...",
-        showCancel: false
+      common_vendor.index.navigateTo({
+        url: `/pages/agreement/agreement?type=${type}`
       });
     },
     /**
@@ -342,5 +339,5 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     l: common_vendor.o($options.handleUserInfoConfirm)
   });
 }
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-cdfe2409"], ["__file", "D:/program/gupiao-wx/src/pages/login/login.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-cdfe2409"], ["__file", "D:/program/gupiao/gupiao-wx/src/pages/login/login.vue"]]);
 wx.createPage(MiniProgramPage);

+ 1 - 1
dist/dev/mp-weixin/pages/mine/mine.js

@@ -96,5 +96,5 @@ const _sfc_main = {
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao-wx/src/pages/mine/mine.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao/gupiao-wx/src/pages/mine/mine.vue"]]);
 wx.createPage(MiniProgramPage);

+ 26 - 4
dist/dev/mp-weixin/pages/order/order.js

@@ -30,15 +30,37 @@ const _sfc_main = {
         loading.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 handlePay = async (order) => {
       try {
         common_vendor.index.showLoading({ title: "正在支付..." });
-        const res = await utils_api.createOrder({ planId: order.planId });
+        const res = await utils_api.repayOrder(order.orderNo);
         if (res.code !== 200) {
-          throw new Error(res.message || "创建订单失败");
+          throw new Error(res.message || "获取支付参数失败");
         }
         await utils_api.wxPay(res.data);
-        common_vendor.index.showToast({ title: "支付成功", icon: "success" });
+        common_vendor.index.showLoading({ title: "确认支付结果..." });
+        const confirmed = await pollOrderStatus(order.orderNo);
+        if (confirmed) {
+          common_vendor.index.showToast({ title: "支付成功", icon: "success" });
+        } else {
+          common_vendor.index.showToast({ title: "支付处理中,请稍后查看", icon: "none" });
+        }
         setTimeout(() => loadOrders(), 1500);
       } catch (e) {
         common_vendor.index.showToast({ title: e.message || "支付失败", icon: "none" });
@@ -101,5 +123,5 @@ const _sfc_main = {
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-88bf5328"], ["__file", "D:/program/gupiao-wx/src/pages/order/order.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-88bf5328"], ["__file", "D:/program/gupiao/gupiao-wx/src/pages/order/order.vue"]]);
 wx.createPage(MiniProgramPage);

+ 74 - 29
dist/dev/mp-weixin/pages/pool/pool.js

@@ -15,8 +15,30 @@ const _sfc_main = {
     const showModal = common_vendor.ref(false);
     const isLoggedIn = common_vendor.ref(false);
     const isPageVisible = common_vendor.ref(false);
+    const shortPrice = common_vendor.ref(1);
     const stockList = common_vendor.ref([]);
     let refreshTimer = null;
+    const performanceStats = common_vendor.reactive({
+      successRate: "0%",
+      avgTrend: "+0%",
+      totalCount: 0
+    });
+    const onDateChange = async ({ startDate, endDate, poolType }) => {
+      try {
+        const res = await utils_api.getStockHistoryStats({
+          startDate,
+          endDate,
+          poolType
+        });
+        if (res.code === 200 && res.data) {
+          performanceStats.successRate = res.data.successRate || "0%";
+          performanceStats.avgTrend = res.data.avgTrend || "+0%";
+          performanceStats.totalCount = res.data.totalCount || 0;
+        }
+      } catch (e) {
+        console.error("加载统计数据失败:", e);
+      }
+    };
     const getChangeClass = (changePercent) => {
       if (!changePercent || changePercent === "-")
         return "";
@@ -88,7 +110,7 @@ const _sfc_main = {
       await loadStockPool();
       startAutoRefresh();
     };
-    const showPurchaseModal = () => {
+    const showPurchaseModal = async () => {
       if (!checkLogin()) {
         common_vendor.index.showModal({
           title: "登录提示",
@@ -103,11 +125,35 @@ const _sfc_main = {
         });
         return;
       }
+      try {
+        const res = await utils_api.getPaymentConfig(1);
+        if (res.code === 200 && res.data) {
+          shortPrice.value = res.data.price || 1;
+        }
+      } 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: "正在支付..." });
@@ -115,31 +161,26 @@ const _sfc_main = {
         if (res.code !== 200) {
           throw new Error(res.message || "创建订单失败");
         }
-        const payRes = await utils_api.mockPay(res.data.orderNo);
-        if (payRes.code !== 200) {
-          throw new Error(payRes.message || "支付失败");
-        }
+        const orderNo = res.data.orderNo;
         common_vendor.index.hideLoading();
-        isPurchased.value = true;
-        closePurchaseModal();
-        common_vendor.index.showToast({ title: "支付成功", icon: "success" });
-        loadAndStartRefresh();
+        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 onHistorySearch = ({ startMonth, endMonth }) => {
-      const formatMonth = (monthStr) => {
-        const [year, month] = monthStr.split("-");
-        return `${year}年${month}月`;
-      };
-      common_vendor.index.showToast({
-        title: `查询${formatMonth(startMonth)}至${formatMonth(endMonth)}`,
-        icon: "none",
-        duration: 2e3
-      });
-    };
     const addToMyStocks = async (stock) => {
       if (!checkLogin()) {
         common_vendor.index.showModal({
@@ -210,9 +251,9 @@ const _sfc_main = {
     return (_ctx, _cache) => {
       return common_vendor.e({
         a: common_vendor.p({
-          successRate: "75%",
-          profitRate: "+3.2%",
-          totalTrades: 120
+          successRate: performanceStats.successRate,
+          profitRate: performanceStats.avgTrend,
+          totalTrades: performanceStats.totalCount
         }),
         b: !isPurchased.value
       }, !isPurchased.value ? {
@@ -230,20 +271,24 @@ const _sfc_main = {
           };
         })
       }, {
-        e: common_vendor.o(onHistorySearch),
-        f: common_vendor.o(closePurchaseModal),
-        g: common_vendor.o(handlePurchase),
-        h: common_vendor.p({
+        e: common_vendor.o(onDateChange),
+        f: common_vendor.p({
+          poolType: 1,
+          canSearch: isPurchased.value
+        }),
+        g: common_vendor.o(closePurchaseModal),
+        h: common_vendor.o(handlePurchase),
+        i: common_vendor.p({
           visible: showModal.value,
           icon: "💰",
           title: "打赏解锁",
           description: "支持作者,解锁今日超短池内容",
           amountLabel: "打赏金额:",
-          amount: 1
+          amount: shortPrice.value
         })
       });
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao-wx/src/pages/pool/pool.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao/gupiao-wx/src/pages/pool/pool.vue"]]);
 wx.createPage(MiniProgramPage);

+ 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"><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>
+<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="be859ce2-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 wx:if="{{f}}" binddateChange="{{e}}" u-i="be859ce2-1" bind:__l="__l" u-p="{{f}}"/><view class="bottom-safe-area"></view></view></scroll-view><purchase-modal wx:if="{{i}}" bindclose="{{g}}" bindconfirm="{{h}}" u-i="be859ce2-2" bind:__l="__l" u-p="{{i}}"/></view>

+ 1 - 1
dist/dev/mp-weixin/pages/profile/edit.js

@@ -140,5 +140,5 @@ const _sfc_main = {
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-7e5a80f3"], ["__file", "D:/program/gupiao-wx/src/pages/profile/edit.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-7e5a80f3"], ["__file", "D:/program/gupiao/gupiao-wx/src/pages/profile/edit.vue"]]);
 wx.createPage(MiniProgramPage);

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

@@ -574,5 +574,5 @@ const _sfc_main = {
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao-wx/src/pages/rank/rank.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao/gupiao-wx/src/pages/rank/rank.vue"]]);
 wx.createPage(MiniProgramPage);

+ 53 - 36
dist/dev/mp-weixin/pages/strong/strong.js

@@ -3,10 +3,9 @@ const common_vendor = require("../../common/vendor.js");
 const utils_auth = require("../../utils/auth.js");
 const utils_api = require("../../utils/api.js");
 if (!Math) {
-  (PerformanceCard + HistorySearchCard + PurchaseModal)();
+  (HistorySearchCard + PurchaseModal)();
 }
 const PurchaseModal = () => "../../components/PurchaseModal.js";
-const PerformanceCard = () => "../../components/PerformanceCard.js";
 const HistorySearchCard = () => "../../components/HistorySearchCard.js";
 const _sfc_main = {
   __name: "strong",
@@ -14,6 +13,7 @@ const _sfc_main = {
     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) => {
@@ -83,7 +83,7 @@ const _sfc_main = {
       await loadStockPool();
       startAutoRefresh();
     };
-    const showPurchaseModal = () => {
+    const showPurchaseModal = async () => {
       if (!utils_auth.isLoggedIn()) {
         common_vendor.index.showModal({
           title: "登录提示",
@@ -98,11 +98,35 @@ const _sfc_main = {
         });
         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: "正在支付..." });
@@ -110,31 +134,26 @@ const _sfc_main = {
         if (res.code !== 200) {
           throw new Error(res.message || "创建订单失败");
         }
-        const payRes = await utils_api.mockPay(res.data.orderNo);
-        if (payRes.code !== 200) {
-          throw new Error(payRes.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();
-        isPurchased.value = true;
-        closePurchaseModal();
-        common_vendor.index.showToast({ title: "支付成功", icon: "success" });
-        loadAndStartRefresh();
+        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 onHistorySearch = ({ startMonth, endMonth }) => {
-      const formatMonth = (monthStr) => {
-        const [year, month] = monthStr.split("-");
-        return `${year}年${month}月`;
-      };
-      common_vendor.index.showToast({
-        title: `查询${formatMonth(startMonth)}至${formatMonth(endMonth)}`,
-        icon: "none",
-        duration: 2e3
-      });
-    };
     const addToMyStocks = async (stock) => {
       const tokenDirect = common_vendor.index.getStorageSync("user_token");
       console.log("[强势池] 点击自选 - 直接读取token:", tokenDirect ? "有值" : "空");
@@ -207,16 +226,11 @@ const _sfc_main = {
     });
     return (_ctx, _cache) => {
       return common_vendor.e({
-        a: common_vendor.p({
-          successRate: "88%",
-          profitRate: "+12.5%",
-          totalTrades: 45
-        }),
-        b: !isPurchased.value
+        a: !isPurchased.value
       }, !isPurchased.value ? {
-        c: common_vendor.o(showPurchaseModal)
+        b: common_vendor.o(showPurchaseModal)
       } : {
-        d: common_vendor.f(stockList.value, (stock, index, i0) => {
+        c: common_vendor.f(stockList.value, (stock, index, i0) => {
           return {
             a: common_vendor.t(stock.name),
             b: common_vendor.t(stock.code),
@@ -228,20 +242,23 @@ const _sfc_main = {
           };
         })
       }, {
-        e: common_vendor.o(onHistorySearch),
-        f: common_vendor.o(closePurchaseModal),
-        g: common_vendor.o(handlePurchase),
-        h: common_vendor.p({
+        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: 98
+          amount: strongPrice.value
         })
       });
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao-wx/src/pages/strong/strong.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao/gupiao-wx/src/pages/strong/strong.vue"]]);
 wx.createPage(MiniProgramPage);

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

@@ -2,7 +2,6 @@
   "navigationBarTitleText": "量化交易大师",
   "usingComponents": {
     "purchase-modal": "../../components/PurchaseModal",
-    "performance-card": "../../components/PerformanceCard",
     "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><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>
+<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>

+ 46 - 6
dist/dev/mp-weixin/utils/api.js

@@ -182,6 +182,13 @@ const getStockPoolList = (poolType) => {
     data: { poolType }
   });
 };
+const getPaymentConfig = (poolType) => {
+  return request({
+    url: "/v1/order/config",
+    method: "GET",
+    data: { poolType }
+  });
+};
 const createOrder = (data) => {
   return request({
     url: "/v1/order/create",
@@ -192,12 +199,25 @@ const createOrder = (data) => {
     data
   });
 };
+const queryOrder = (orderNo) => {
+  return request({
+    url: "/v1/order/query",
+    method: "GET",
+    data: { orderNo }
+  });
+};
 const getUserOrders = () => {
   return request({
     url: "/v1/order/list",
     method: "GET"
   });
 };
+const repayOrder = (orderNo) => {
+  return request({
+    url: `/v1/order/repay?orderNo=${encodeURIComponent(orderNo)}`,
+    method: "POST"
+  });
+};
 const checkSubscription = (poolType) => {
   return request({
     url: "/v1/order/check-subscription",
@@ -206,6 +226,11 @@ const checkSubscription = (poolType) => {
   });
 };
 const wxPay = (payParams) => {
+  const totalFee = payParams.total_fee || payParams.totalFee;
+  console.log("调起支付,参数:", JSON.stringify(payParams), "totalFee:", totalFee);
+  if (!totalFee) {
+    console.error("缺少 total_fee 参数,payParams:", payParams);
+  }
   return new Promise((resolve, reject) => {
     common_vendor.index.requestPayment({
       provider: "wxpay",
@@ -214,23 +239,34 @@ const wxPay = (payParams) => {
       package: payParams.packageValue,
       signType: payParams.signType,
       paySign: payParams.paySign,
+      totalFee,
+      // 开发者工具模拟支付需要
       success: (res) => {
         resolve(res);
       },
       fail: (err) => {
-        if (err.errMsg.includes("cancel")) {
+        console.log("支付失败:", err);
+        if (err.errMsg && err.errMsg.includes("cancel")) {
           reject(new Error("用户取消支付"));
         } else {
-          reject(new Error("支付失败:" + err.errMsg));
+          reject(new Error("支付失败:" + (err.errMsg || JSON.stringify(err))));
         }
       }
     });
   });
 };
-const mockPay = (orderNo) => {
+const getStockHistory = (params) => {
   return request({
-    url: `/v1/order/mock-pay?orderNo=${encodeURIComponent(orderNo)}`,
-    method: "POST"
+    url: "/v1/stock/history/list",
+    method: "GET",
+    data: params
+  });
+};
+const getStockHistoryStats = (params) => {
+  return request({
+    url: "/v1/stock/history/stats",
+    method: "GET",
+    data: params
   });
 };
 exports.BASE_URL = BASE_URL;
@@ -240,13 +276,17 @@ exports.createOrder = createOrder;
 exports.deleteUserStock = deleteUserStock;
 exports.getImageUrl = getImageUrl;
 exports.getIndexQuote = getIndexQuote;
+exports.getPaymentConfig = getPaymentConfig;
+exports.getStockHistory = getStockHistory;
+exports.getStockHistoryStats = getStockHistoryStats;
 exports.getStockPoolList = getStockPoolList;
 exports.getStockQuotes = getStockQuotes;
 exports.getSuggestions = getSuggestions;
 exports.getUserInfoApi = getUserInfoApi;
 exports.getUserOrders = getUserOrders;
 exports.getUserStocks = getUserStocks;
-exports.mockPay = mockPay;
+exports.queryOrder = queryOrder;
+exports.repayOrder = repayOrder;
 exports.searchStocks = searchStocks;
 exports.updateUserProfile = updateUserProfile;
 exports.uploadFile = uploadFile;

+ 7 - 0
package-lock.json

@@ -22,6 +22,7 @@
         "@dcloudio/uni-mp-weixin": "3.0.0-3090820231124001",
         "@dcloudio/uni-mp-xhs": "3.0.0-3090820231124001",
         "@dcloudio/uni-quickapp-webview": "3.0.0-3090820231124001",
+        "mp-html": "^2.5.2",
         "vue": "^3.3.4",
         "vue-i18n": "^9.2.2"
       },
@@ -8084,6 +8085,12 @@
       "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==",
       "license": "MIT"
     },
+    "node_modules/mp-html": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/mp-html/-/mp-html-2.5.2.tgz",
+      "integrity": "sha512-45e8c32Qgux4YU4iC3qCSFsOh3y+RwPwZ+iz/vvLkDgSGWk+1zsL4WUzWWQc9w3AsAfkaD/QR0oIufIDngBmXA==",
+      "license": "MIT"
+    },
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

+ 1 - 0
package.json

@@ -50,6 +50,7 @@
     "@dcloudio/uni-mp-weixin": "3.0.0-3090820231124001",
     "@dcloudio/uni-mp-xhs": "3.0.0-3090820231124001",
     "@dcloudio/uni-quickapp-webview": "3.0.0-3090820231124001",
+    "mp-html": "^2.5.2",
     "vue": "^3.3.4",
     "vue-i18n": "^9.2.2"
   },

+ 1 - 1
project.config.json

@@ -37,6 +37,6 @@
     "tabIndent": "auto",
     "tabSize": 2
   },
-  "appid": "wxcf9eec0da6a6b696",
+  "appid": "wx9d7e6e3592830447",
   "simulatorPluginLibVersion": {}
 }

+ 346 - 52
src/components/HistorySearchCard.vue

@@ -1,36 +1,22 @@
 <template>
   <view class="card history-card">
     <view class="history-header">
-      <text class="history-title">历史股票池回顾</text>
+      <text class="history-title">📊 历史股票池回顾</text>
     </view>
     
-    <!-- 月份区间选择 -->
+    <!-- 日期区间选择 -->
     <view class="date-range-row">
-      <picker 
-        mode="date"
-        fields="month"
-        :value="startMonth"
-        @change="onStartMonthChange"
-        class="date-picker-half"
-      >
-        <view class="date-input">
-          <text class="date-text">{{ formatMonth(startMonth) }}</text>
-        </view>
-      </picker>
+      <view class="date-input" @click="openStartDatePicker">
+        <text class="date-text">{{ formatDateDisplay(startDate) }}</text>
+        <text class="date-icon">📅</text>
+      </view>
       
       <text class="date-separator">至</text>
       
-      <picker 
-        mode="date"
-        fields="month"
-        :value="endMonth"
-        @change="onEndMonthChange"
-        class="date-picker-half"
-      >
-        <view class="date-input">
-          <text class="date-text">{{ formatMonth(endMonth) }}</text>
-        </view>
-      </picker>
+      <view class="date-input" @click="openEndDatePicker">
+        <text class="date-text">{{ formatDateDisplay(endDate) }}</text>
+        <text class="date-icon">📅</text>
+      </view>
     </view>
     
     <!-- 查询按钮 -->
@@ -38,51 +24,225 @@
       <text class="search-button-text">🔍 查询历史数据</text>
     </view>
     
-    <text class="history-tip">选择时间区间,查询该期间的入池股票及表现。</text>
+    <text v-if="!canSearch" class="history-tip">订阅后可查询该期间的入池股票及表现。</text>
+    <text v-else class="history-tip">选择时间区间,查询该期间的入池股票及表现。</text>
+    
+    <!-- 自定义日期选择弹窗 -->
+    <view v-if="showDatePicker" class="date-picker-mask" @click="closeDatePicker">
+      <view class="date-picker-popup" @click.stop>
+        <view class="picker-header">
+          <text class="picker-cancel" @click="closeDatePicker">取消</text>
+          <text class="picker-title">{{ currentPickerType === 'start' ? '选择开始日期' : '选择结束日期' }}</text>
+          <text class="picker-confirm" @click="confirmDate">确定</text>
+        </view>
+        
+        <!-- 年月选择 -->
+        <view class="month-selector">
+          <view class="month-nav" @click="prevMonth">
+            <text class="nav-arrow">‹</text>
+          </view>
+          <text class="current-month">{{ tempYear }}年{{ tempMonth }}月</text>
+          <view class="month-nav" @click="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)
+            }]"
+            @click="day && selectDay(day)"
+          >
+            <text v-if="day" class="day-text">{{ day }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
   </view>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 
 const props = defineProps({
-  defaultStartMonth: { type: String, default: '2025-01' },
-  defaultEndMonth: { type: String, default: '2025-11' }
+  poolType: { type: Number, default: 1 },
+  canSearch: { type: Boolean, default: false }
 })
 
-const emit = defineEmits(['search'])
+const emit = defineEmits(['dateChange'])
 
-const startMonth = ref(props.defaultStartMonth)
-const endMonth = ref(props.defaultEndMonth)
+// 获取默认日期(当前月份的第一天和最后一天)
+const getDefaultDates = () => {
+  const now = new Date()
+  const year = now.getFullYear()
+  const month = String(now.getMonth() + 1).padStart(2, '0')
+  const lastDay = new Date(year, now.getMonth() + 1, 0).getDate()
+  return {
+    start: `${year}-${month}-01`,
+    end: `${year}-${month}-${lastDay}`
+  }
+}
+
+const defaultDates = getDefaultDates()
+const startDate = ref(defaultDates.start)
+const endDate = ref(defaultDates.end)
+
+// 日期选择器状态
+const showDatePicker = ref(false)
+const currentPickerType = ref('start') // 'start' | 'end'
+const tempYear = ref(new Date().getFullYear())
+const tempMonth = ref(new Date().getMonth() + 1)
+const tempSelectedDay = ref(1)
+
+const weekDays = ['日', '一', '二', '三', '四', '五', '六']
 
-// 格式化月份显示
-const formatMonth = (monthStr) => {
-  if (!monthStr) return '请选择'
-  const [year, month] = monthStr.split('-')
-  return `${year}年${month}月`
+// 计算当月日历
+const calendarDays = computed(() => {
+  const days = []
+  const firstDay = new Date(tempYear.value, tempMonth.value - 1, 1).getDay()
+  const daysInMonth = new Date(tempYear.value, tempMonth.value, 0).getDate()
+  
+  // 填充空白
+  for (let i = 0; i < firstDay; i++) {
+    days.push(null)
+  }
+  // 填充日期
+  for (let i = 1; i <= daysInMonth; i++) {
+    days.push(i)
+  }
+  return days
+})
+
+// 格式化日期显示
+const formatDateDisplay = (dateStr) => {
+  if (!dateStr) return '请选择'
+  const [year, month, day] = dateStr.split('-')
+  return `${year}/${month}/${day}`
+}
+
+// 通知父组件日期变化
+const emitDateChange = () => {
+  emit('dateChange', {
+    startDate: startDate.value,
+    endDate: endDate.value,
+    poolType: props.poolType
+  })
+}
+
+// 打开开始日期选择器
+const openStartDatePicker = () => {
+  currentPickerType.value = 'start'
+  const [year, month, day] = startDate.value.split('-').map(Number)
+  tempYear.value = year
+  tempMonth.value = month
+  tempSelectedDay.value = day
+  showDatePicker.value = true
+}
+
+// 打开结束日期选择器
+const openEndDatePicker = () => {
+  currentPickerType.value = 'end'
+  const [year, month, day] = endDate.value.split('-').map(Number)
+  tempYear.value = year
+  tempMonth.value = month
+  tempSelectedDay.value = day
+  showDatePicker.value = true
+}
+
+// 关闭日期选择器
+const closeDatePicker = () => {
+  showDatePicker.value = false
+}
+
+// 上一月
+const prevMonth = () => {
+  if (tempMonth.value === 1) {
+    tempMonth.value = 12
+    tempYear.value--
+  } else {
+    tempMonth.value--
+  }
 }
 
-const onStartMonthChange = (e) => {
-  startMonth.value = e.detail.value
+// 下一月
+const nextMonth = () => {
+  if (tempMonth.value === 12) {
+    tempMonth.value = 1
+    tempYear.value++
+  } else {
+    tempMonth.value++
+  }
+}
+
+// 选择日期
+const selectDay = (day) => {
+  tempSelectedDay.value = day
 }
 
-const onEndMonthChange = (e) => {
-  endMonth.value = e.detail.value
+// 判断是否选中
+const isSelected = (day) => {
+  return day === tempSelectedDay.value
+}
+
+// 判断是否今天
+const isToday = (day) => {
+  const today = new Date()
+  return tempYear.value === today.getFullYear() && 
+         tempMonth.value === today.getMonth() + 1 && 
+         day === today.getDate()
+}
+
+// 确认选择
+const confirmDate = () => {
+  const dateStr = `${tempYear.value}-${String(tempMonth.value).padStart(2, '0')}-${String(tempSelectedDay.value).padStart(2, '0')}`
+  
+  if (currentPickerType.value === 'start') {
+    startDate.value = dateStr
+  } else {
+    endDate.value = dateStr
+  }
+  
+  showDatePicker.value = false
+  emitDateChange()
 }
 
 const onSearch = () => {
-  if (!startMonth.value || !endMonth.value) {
-    uni.showToast({ title: '请选择开始和结束月份', icon: 'none' })
+  if (!props.canSearch) {
+    uni.showToast({ title: '请先订阅该股票池', icon: 'none' })
+    return
+  }
+  
+  if (!startDate.value || !endDate.value) {
+    uni.showToast({ title: '请选择开始和结束日期', icon: 'none' })
     return
   }
   
-  if (startMonth.value > endMonth.value) {
-    uni.showToast({ title: '开始月份不能晚于结束月份', icon: 'none' })
+  if (startDate.value > endDate.value) {
+    uni.showToast({ title: '开始日期不能晚于结束日期', icon: 'none' })
     return
   }
   
-  emit('search', { startMonth: startMonth.value, endMonth: endMonth.value })
+  // 跳转到历史查询结果页面
+  uni.navigateTo({
+    url: `/pages/history/history?startDate=${startDate.value}&endDate=${endDate.value}&poolType=${props.poolType}`
+  })
 }
+
+// 组件挂载时通知父组件默认日期
+onMounted(() => {
+  emitDateChange()
+})
 </script>
 
 <style scoped>
@@ -116,10 +276,6 @@ const onSearch = () => {
   margin-bottom: 24rpx;
 }
 
-.date-picker-half {
-  flex: 1;
-}
-
 .date-separator {
   font-size: 26rpx;
   color: #666a7f;
@@ -127,18 +283,22 @@ const onSearch = () => {
 }
 
 .date-input {
+  flex: 1;
   background: #f7f8fc;
   border-radius: 12rpx;
   padding: 24rpx;
-  font-size: 26rpx;
-  color: #222222;
   display: flex;
   justify-content: space-between;
   align-items: center;
 }
 
 .date-text {
-  flex: 1;
+  font-size: 26rpx;
+  color: #222222;
+}
+
+.date-icon {
+  font-size: 24rpx;
 }
 
 .history-search-button-full {
@@ -163,4 +323,138 @@ const onSearch = () => {
   color: #9ca2b5;
   line-height: 1.6;
 }
+
+/* 日期选择器弹窗 */
+.date-picker-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 1000;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+}
+
+.date-picker-popup {
+  width: 100%;
+  background: #ffffff;
+  border-radius: 32rpx 32rpx 0 0;
+  padding: 32rpx;
+  padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
+}
+
+.picker-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 32rpx;
+}
+
+.picker-cancel {
+  font-size: 28rpx;
+  color: #999999;
+  padding: 16rpx;
+}
+
+.picker-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.picker-confirm {
+  font-size: 28rpx;
+  color: #5d55e8;
+  font-weight: 600;
+  padding: 16rpx;
+}
+
+.month-selector {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 24rpx;
+  gap: 48rpx;
+}
+
+.month-nav {
+  width: 64rpx;
+  height: 64rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f7f8fc;
+  border-radius: 50%;
+}
+
+.nav-arrow {
+  font-size: 36rpx;
+  color: #5d55e8;
+  font-weight: bold;
+}
+
+.current-month {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+  min-width: 200rpx;
+  text-align: center;
+}
+
+.weekday-row {
+  display: flex;
+  margin-bottom: 16rpx;
+}
+
+.weekday-item {
+  flex: 1;
+  text-align: center;
+  font-size: 24rpx;
+  color: #999999;
+  padding: 16rpx 0;
+}
+
+.days-grid {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.day-item {
+  width: 14.28%;
+  aspect-ratio: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.day-item.empty {
+  background: transparent;
+}
+
+.day-text {
+  width: 72rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  text-align: center;
+  font-size: 28rpx;
+  color: #222222;
+  border-radius: 50%;
+}
+
+.day-item.selected .day-text {
+  background: linear-gradient(135deg, #5d55e8, #7568ff);
+  color: #ffffff;
+  font-weight: 600;
+}
+
+.day-item.today .day-text {
+  border: 2rpx solid #5d55e8;
+}
+
+.day-item.today.selected .day-text {
+  border: none;
+}
 </style>

+ 12 - 0
src/pages.json

@@ -53,6 +53,18 @@
       "style": {
         "navigationStyle": "custom"
       }
+    },
+    {
+      "path": "pages/agreement/agreement",
+      "style": {
+        "navigationBarTitleText": "协议"
+      }
+    },
+    {
+      "path": "pages/history/history",
+      "style": {
+        "navigationStyle": "custom"
+      }
     }
   ],
   "globalStyle": {

+ 23 - 0
src/pages/agreement/agreement.vue

@@ -0,0 +1,23 @@
+<template>
+  <web-view :src="agreementUrl"></web-view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import { BASE_URL } from '@/utils/api.js'
+
+const agreementUrl = ref('')
+
+onLoad((options) => {
+  const type = options.type || 'user'
+  
+  if (type === 'user') {
+    agreementUrl.value = `${BASE_URL}/v1/agreement/page?type=user`
+    uni.setNavigationBarTitle({ title: '用户协议' })
+  } else {
+    agreementUrl.value = `${BASE_URL}/v1/agreement/page?type=privacy`
+    uni.setNavigationBarTitle({ title: '隐私政策' })
+  }
+})
+</script>

+ 455 - 0
src/pages/history/history.vue

@@ -0,0 +1,455 @@
+<template>
+  <view class="page-container">
+    <!-- 自定义导航栏 -->
+    <view class="nav-bar">
+      <view class="nav-back" @click="goBack">
+        <text class="back-icon">←</text>
+      </view>
+      <text class="nav-title">历史股票池回顾</text>
+      <view class="nav-placeholder"></view>
+    </view>
+    
+    <scroll-view class="scroll-view" scroll-y @scrolltolower="loadMore">
+      <view class="content-wrapper">
+        <!-- 查询条件显示 -->
+        <view class="query-info">
+          <text class="query-label">查询区间:</text>
+          <text class="query-value">{{ formatDate(startDate) }} 至 {{ formatDate(endDate) }}</text>
+        </view>
+        
+        <!-- 统计信息 -->
+        <view class="stats-card">
+          <view class="stat-item">
+            <text class="stat-value">{{ total }}</text>
+            <text class="stat-label">总记录</text>
+          </view>
+          <view class="stat-divider"></view>
+          <view class="stat-item">
+            <text class="stat-value success-text">{{ successCount }}</text>
+            <text class="stat-label">成功</text>
+          </view>
+          <view class="stat-divider"></view>
+          <view class="stat-item">
+            <text class="stat-value fail-text">{{ failCount }}</text>
+            <text class="stat-label">失败</text>
+          </view>
+        </view>
+        
+        <!-- 表头 -->
+        <view 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 class="stock-list">
+          <view 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>
+              <text class="stock-date">入池: {{ formatRecordDate(item.recordDate) }}</text>
+            </view>
+            <text class="td-close">{{ item.closePrice ? item.closePrice.toFixed(2) : '-' }}</text>
+            <text class="td-high">{{ item.nextDayHighPrice ? item.nextDayHighPrice.toFixed(2) : '-' }}</text>
+            <view class="td-trend">
+              <text :class="['trend-value', getTrendClass(item.nextDayHighTrend)]">
+                {{ formatTrend(item.nextDayHighTrend) }}
+              </text>
+              <text v-if="item.status === 'success'" class="status-tag success">成功</text>
+              <text v-else-if="item.status === 'fail'" class="status-tag fail">失败</text>
+            </view>
+          </view>
+        </view>
+        
+        <!-- 加载状态 -->
+        <view class="load-status">
+          <text v-if="loading" class="load-text">加载中...</text>
+          <text v-else-if="!hasMore && historyList.length > 0" class="load-text">已加载全部</text>
+          <text v-else-if="historyList.length === 0 && !loading" class="load-text">暂无数据</text>
+        </view>
+        
+        <!-- 底部安全区 -->
+        <view class="bottom-safe-area"></view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import { getStockHistory } from '../../utils/api.js'
+
+const startDate = ref('')
+const endDate = ref('')
+const poolType = ref(1)
+const historyList = ref([])
+const total = ref(0)
+const hasMore = ref(false)
+const loading = ref(false)
+const pageNum = ref(1)
+const pageSize = ref(20)
+
+// 统计成功和失败数量
+const successCount = computed(() => historyList.value.filter(item => item.status === 'success').length)
+const failCount = computed(() => historyList.value.filter(item => item.status === 'fail').length)
+
+// 格式化日期显示
+const formatDate = (dateStr) => {
+  if (!dateStr) return ''
+  const [year, month, day] = dateStr.split('-')
+  return `${year}年${month}月${day}日`
+}
+
+// 格式化入池日期(简短格式)
+const formatRecordDate = (dateStr) => {
+  if (!dateStr) return '-'
+  // 处理可能的日期格式:2025-01-10 或 数组格式 [2025, 1, 10]
+  if (Array.isArray(dateStr)) {
+    const [year, month, day] = dateStr
+    return `${month}/${day}`
+  }
+  const parts = dateStr.split('-')
+  if (parts.length >= 3) {
+    return `${parseInt(parts[1])}/${parseInt(parts[2])}`
+  }
+  return dateStr
+}
+
+// 获取涨幅样式
+const getTrendClass = (trend) => {
+  if (trend === null || trend === undefined) return ''
+  return trend >= 0 ? 'text-red' : 'text-green'
+}
+
+// 格式化涨幅
+const formatTrend = (trend) => {
+  if (trend === null || trend === undefined) return '-'
+  const prefix = trend >= 0 ? '+' : ''
+  return `${prefix}${trend.toFixed(2)}%`
+}
+
+// 返回上一页
+const goBack = () => {
+  uni.navigateBack()
+}
+
+// 加载历史数据
+const loadHistoryData = async () => {
+  if (loading.value) return
+  
+  loading.value = true
+  try {
+    const res = await getStockHistory({
+      startDate: startDate.value,
+      endDate: endDate.value,
+      poolType: poolType.value,
+      pageNum: pageNum.value,
+      pageSize: pageSize.value
+    })
+    
+    if (res.code === 200 && res.data) {
+      if (pageNum.value === 1) {
+        historyList.value = res.data.list || []
+      } else {
+        historyList.value = [...historyList.value, ...(res.data.list || [])]
+      }
+      total.value = res.data.total || 0
+      hasMore.value = res.data.hasMore || false
+    }
+  } catch (e) {
+    console.error('加载历史数据失败:', e)
+    uni.showToast({ title: '加载失败', icon: 'none' })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 加载更多
+const loadMore = () => {
+  if (loading.value || !hasMore.value) return
+  pageNum.value++
+  loadHistoryData()
+}
+
+onLoad((options) => {
+  startDate.value = options.startDate || ''
+  endDate.value = options.endDate || ''
+  poolType.value = parseInt(options.poolType) || 1
+  
+  // 设置导航栏标题
+  const poolName = poolType.value === 1 ? '超短池' : '强势池'
+  uni.setNavigationBarTitle({ title: `${poolName}历史回顾` })
+  
+  loadHistoryData()
+})
+</script>
+
+<style scoped>
+.page-container {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #f5f6fb;
+}
+
+/* 自定义导航栏 */
+.nav-bar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 44px 32rpx 20rpx;
+  background: #ffffff;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+}
+
+.nav-back {
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.back-icon {
+  font-size: 36rpx;
+  color: #333;
+}
+
+.nav-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.nav-placeholder {
+  width: 60rpx;
+}
+
+.scroll-view {
+  flex: 1;
+  height: 0;
+}
+
+.content-wrapper {
+  padding: 24rpx 32rpx;
+}
+
+/* 查询条件 */
+.query-info {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 24rpx;
+  background: #ffffff;
+  border-radius: 16rpx;
+  margin-bottom: 24rpx;
+}
+
+.query-label {
+  font-size: 26rpx;
+  color: #666a7f;
+}
+
+.query-value {
+  font-size: 26rpx;
+  color: #222222;
+  font-weight: 500;
+}
+
+/* 统计卡片 */
+.stats-card {
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+  padding: 32rpx 24rpx;
+  background: #ffffff;
+  border-radius: 20rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.06);
+}
+
+.stat-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  flex: 1;
+}
+
+.stat-value {
+  font-size: 40rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 8rpx;
+}
+
+.stat-value.success-text {
+  color: #22c55e;
+}
+
+.stat-value.fail-text {
+  color: #ef4444;
+}
+
+.stat-label {
+  font-size: 24rpx;
+  color: #9ca3af;
+}
+
+.stat-divider {
+  width: 1rpx;
+  height: 60rpx;
+  background: #eef0f5;
+}
+
+/* 表头 */
+.table-header {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 24rpx;
+  background: #eef0f5;
+  border-radius: 12rpx;
+  margin-bottom: 16rpx;
+}
+
+.th-name {
+  width: 180rpx;
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #666a7f;
+}
+
+.th-close {
+  width: 120rpx;
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #666a7f;
+  text-align: center;
+}
+
+.th-high {
+  width: 120rpx;
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #666a7f;
+  text-align: center;
+}
+
+.th-trend {
+  flex: 1;
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #666a7f;
+  text-align: right;
+}
+
+/* 数据列表 */
+.stock-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+}
+
+.stock-item {
+  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;
+  flex-direction: column;
+}
+
+.stock-name {
+  font-size: 28rpx;
+  font-weight: 700;
+  color: #1a1a2e;
+  margin-bottom: 4rpx;
+}
+
+.stock-code {
+  font-size: 22rpx;
+  color: #9ca3af;
+}
+
+.stock-date {
+  font-size: 20rpx;
+  color: #5d55e8;
+  margin-top: 4rpx;
+}
+
+.td-close {
+  width: 120rpx;
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1a1a2e;
+  text-align: center;
+}
+
+.td-high {
+  width: 120rpx;
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1a1a2e;
+  text-align: center;
+}
+
+.td-trend {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  gap: 6rpx;
+}
+
+.trend-value {
+  font-size: 28rpx;
+  font-weight: 700;
+}
+
+.text-red {
+  color: #ef4444;
+}
+
+.text-green {
+  color: #22c55e;
+}
+
+.status-tag {
+  font-size: 20rpx;
+  padding: 6rpx 16rpx;
+  border-radius: 8rpx;
+  font-weight: 600;
+}
+
+.status-tag.success {
+  background: rgba(34, 197, 94, 0.15);
+  color: #22c55e;
+}
+
+.status-tag.fail {
+  background: rgba(239, 68, 68, 0.15);
+  color: #ef4444;
+}
+
+/* 加载状态 */
+.load-status {
+  padding: 40rpx 0;
+  text-align: center;
+}
+
+.load-text {
+  font-size: 26rpx;
+  color: #9ca3af;
+}
+
+.bottom-safe-area {
+  height: 40rpx;
+}
+</style>

+ 174 - 143
src/pages/index/index.vue

@@ -1,48 +1,41 @@
 <template>
   <view class="page-container">
-    <!-- 遮罩层 -->
-    <view v-if="showDropdown && suggestions.length > 0" class="mask" @click="closeDropdown"></view>
-
     <scroll-view class="scroll-view" scroll-y>
       <view class="content-wrapper">
 
         <!-- 顶部查询卡片 -->
-        <view class="card search-card">
+        <view class="card search-card" style="position: relative; z-index: 100;">
           <text class="card-title">量化分数实时查询 & 历史数据</text>
 
-          <view class="search-box">
-            <view class="search-input-wrap">
-              <input
-                class="search-input"
-                type="text"
-                placeholder="请输入股票代码/名称 (如: 600030)"
-                v-model="keyword"
-                @input="onKeywordChange"
-                @confirm="handleSearchClick"
-                @focus="onInputFocus"
-              />
-              <view class="search-button" @click="handleSearchClick">
-                <image class="search-icon" src="/static/images/tab_search.png" mode="aspectFit"></image>
-              </view>
+          <view class="search-row">
+            <input
+              class="search-input"
+              type="text"
+              placeholder="请输入股票代码/名称 (如: 600030)"
+              placeholder-class="search-placeholder"
+              confirm-type="search"
+              v-model="keyword"
+              @input="onKeywordChange"
+              @confirm="handleSearchClick"
+              @blur="onInputBlur"
+            />
+            <!-- 搜索按钮(统一处理登录检查) -->
+            <view class="search-button" @click="handleSearchClick">
+              <image class="search-icon" src="/static/images/tab_search.png" mode="aspectFit"></image>
             </view>
           </view>
           
-          <!-- 搜索建议下拉 -->
-          <view v-if="showDropdown && suggestions.length > 0" class="suggestions-dropdown">
+          <!-- 模糊搜索下拉列表 -->
+          <view class="search-dropdown" v-if="showDropdown && suggestions && suggestions.length > 0">
             <view 
               v-for="(item, index) in suggestions" 
               :key="index"
-              class="suggestion-item"
-              @click="onSelectSuggestion(item)"
+              class="dropdown-item" 
+              hover-class="dropdown-item-hover" 
+              @tap.stop="onSelectSuggestion(item)"
             >
-              <text class="suggestion-name">{{ item.name }}</text>
-              <text class="suggestion-code">{{ item.code }}</text>
-            </view>
-          </view>
-          
-          <view v-if="showDropdown && searching" class="suggestions-dropdown">
-            <view class="suggestion-loading">
-              <text>搜索中...</text>
+              <text class="item-name">{{item.name}}</text>
+              <text class="item-code">{{item.code}}</text>
             </view>
           </view>
 
@@ -58,6 +51,7 @@
             <text class="result-error">{{errorMsg}}</text>
           </view>
           <view v-else-if="result">
+            <!-- 头部:名称 / 代码 / 实时价格 -->
             <view class="detail-header">
               <view class="stock-info-left">
                 <view class="detail-name">{{result.stockName}}</view>
@@ -73,6 +67,7 @@
               </view>
             </view>
 
+            <!-- 实时行情数据 -->
             <view class="quote-section" v-if="result.currentPrice">
               <view class="quote-grid">
                 <view class="quote-item">
@@ -102,6 +97,7 @@
               </view>
             </view>
 
+            <!-- 量化评分(如果有) -->
             <view class="section" v-if="result.score">
               <view class="section-title">量化系统评分</view>
               <view class="score-display">
@@ -109,9 +105,10 @@
               </view>
             </view>
 
+            <!-- 历史评分趋势 -->
             <view class="section" v-if="result.history && result.history.length > 0">
               <view class="section-title">历史评分趋势</view>
-              <view class="history-row" v-for="(item, idx) in result.history" :key="idx">
+              <view class="history-row" v-for="(item, index) in result.history" :key="index">
                 <text class="history-date">{{item.date}} 的量化评分:</text>
                 <text :class="['history-score', item.score >= 90 ? 'tag-danger' : (item.score >= 80 ? 'tag-success' : 'tag-info')]">
                   {{item.score}}
@@ -120,9 +117,10 @@
               <text class="history-note">(注意:此为模拟历史数据,仅供展示。)</text>
             </view>
 
+            <!-- 评分因子构成 -->
             <view class="section" v-if="result.factors && result.factors.length > 0">
               <view class="section-title">评分因子构成</view>
-              <view class="factor-row" v-for="(item, idx) in result.factors" :key="idx">
+              <view class="factor-row" v-for="(item, index) in result.factors" :key="index">
                 <text class="factor-name">{{item.name}}</text>
                 <text class="factor-score">{{item.value}}</text>
               </view>
@@ -138,6 +136,7 @@
             </view>
             <text class="card-header-title">量化投资的优势</text>
           </view>
+
           <view class="advantage-item">
             <text class="advantage-label">客观纪律性:</text>
             <text class="advantage-desc">排除情绪干扰,严格执行预设的交易信号。</text>
@@ -148,7 +147,7 @@
           </view>
         </view>
 
-        <!-- 风险提示 -->
+        <!-- 风险提示(免责声明) -->
         <view class="card risk-card">
           <view class="card-header">
             <view class="icon-warning">
@@ -156,12 +155,14 @@
             </view>
             <text class="card-header-title">风险提示(免责声明)</text>
           </view>
+
           <text class="risk-text">
             本系统的量化分数和股票池信息均基于历史数据和特定模型计算,并非对未来市场的保证或预测。市场环境瞬息万变,
             量化模型可能存在失效或回撤风险。请勿将本系统数据作为投资决策的唯一依据,请您充分理解股票投资风险,并独立做出投资判断。
           </text>
         </view>
 
+        <!-- 预留底部空间,避免被 tabBar 遮挡 -->
         <view class="bottom-safe-area"></view>
       </view>
     </scroll-view>
@@ -172,7 +173,9 @@
 import { ref, onMounted } from 'vue'
 import { getSuggestions, searchStocks } from '../../utils/api.js'
 import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
-import { onShow, onHide } from '@dcloudio/uni-app'
+
+// 引入 onShow 生命周期
+import { onShow } from '@dcloudio/uni-app'
 
 const keyword = ref('')
 const loading = ref(false)
@@ -181,105 +184,120 @@ const errorMsg = ref('')
 const result = ref(null)
 const suggestions = ref([])
 const showDropdown = ref(false)
-const searching = ref(false)
 const isLoggedIn = ref(false)
-let searchTimer = null
+let timer = null
 
+/**
+ * 页面加载时检查登录状态
+ */
 onMounted(() => {
   isLoggedIn.value = checkLoginStatus()
+  console.log('[首页] 登录状态:', isLoggedIn.value)
 })
 
+/**
+ * 页面显示时检查登录状态(从登录页返回时会触发)
+ */
 onShow(() => {
   isLoggedIn.value = checkLoginStatus()
+  // 设置导航栏标题
   uni.setNavigationBarTitle({ title: '量化交易大师' })
 })
 
-onHide(() => {
-  showDropdown.value = false
-})
-
-const onInputFocus = () => {
-  if (suggestions.value.length > 0) {
-    showDropdown.value = true
+/**
+ * 处理搜索按钮点击(统一登录校验UI)
+ */
+const handleSearchClick = async () => {
+  console.log('=== 点击搜索按钮 ===')
+  console.log('当前登录状态:', isLoggedIn.value)
+  
+  // 检查登录状态
+  if (!isLoggedIn.value) {
+    console.log('未登录,跳转到登录页')
+    uni.showModal({
+      title: '登录提示',
+      content: '此功能需要登录后使用,是否前往登录?',
+      confirmText: '去登录',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          uni.navigateTo({
+            url: '/pages/login/login'
+          })
+        }
+      }
+    })
+    return
   }
+  
+  console.log('已登录,执行搜索')
+  // 已登录,执行搜索
+  onSearch()
 }
 
 const onKeywordChange = (e) => {
   const value = e.detail.value
   keyword.value = value
+  console.log('输入关键词:', value)
   
-  if (searchTimer) clearTimeout(searchTimer)
+  if (timer) {
+    clearTimeout(timer)
+  }
   
-  if (!value || !value.trim()) {
+  timer = setTimeout(() => {
+    doSearchSuggestions(value)
+  }, 500)
+}
+
+const doSearchSuggestions = async (kw) => {
+  if (!kw || !kw.trim()) {
     suggestions.value = []
     showDropdown.value = false
     return
   }
-  
-  searching.value = true
-  showDropdown.value = true
-  
-  searchTimer = setTimeout(async () => {
-    try {
-      const response = await getSuggestions(value.trim())
-      if (response.code === 200 && response.data) {
-        suggestions.value = Array.isArray(response.data) ? response.data : []
-      } else {
-        suggestions.value = []
-      }
-    } catch (err) {
-      console.error('搜索建议错误:', err)
-      suggestions.value = []
-    } finally {
-      searching.value = false
-    }
-  }, 300)
-}
 
-const closeDropdown = () => {
-  showDropdown.value = false
+  try {
+    // getSuggestions 返回完整响应对象 {code, message, data}
+    const response = await getSuggestions(kw.trim())
+    console.log('模糊查询返回数据:', response)
+    
+    // 从响应中提取 data 数组
+    const list = (response.code === 200 && response.data) ? response.data : []
+    suggestions.value = Array.isArray(list) ? list : []
+    showDropdown.value = suggestions.value.length > 0
+    console.log('下拉框状态:', { showDropdown: showDropdown.value, suggestionsLength: suggestions.value.length })
+  } catch (err) {
+    console.error('模糊查询错误:', err)
+    suggestions.value = []
+    showDropdown.value = false
+  }
 }
 
 const onSelectSuggestion = (item) => {
-  keyword.value = `${item.name} (${item.code})`
+  const searchText = `${item.name} (${item.code})`
+  keyword.value = searchText
   suggestions.value = []
   showDropdown.value = false
+  
   doSearch(item.code)
 }
 
-const handleSearchClick = () => {
-  if (!isLoggedIn.value) {
-    uni.showModal({
-      title: '登录提示',
-      content: '此功能需要登录后使用,是否前往登录?',
-      confirmText: '去登录',
-      cancelText: '取消',
-      success: (res) => {
-        if (res.confirm) {
-          uni.navigateTo({ url: '/pages/login/login' })
-        }
-      }
-    })
-    return
-  }
-  onSearch()
-}
-
 const onSearch = () => {
   const kw = (keyword.value || '').trim()
   if (!kw) {
-    uni.showToast({ title: '请输入股票代码或名称', icon: 'none' })
+    uni.showToast({
+      title: '请输入股票代码或名称',
+      icon: 'none'
+    })
     return
   }
-  
-  showDropdown.value = false
-  suggestions.value = []
-  
+
   let searchCode = kw
   const codeMatch = kw.match(/\((\d{6})\)/)
   if (codeMatch) {
     searchCode = codeMatch[1]
   }
+
   doSearch(searchCode)
 }
 
@@ -288,8 +306,8 @@ const doSearch = async (queryCode) => {
   hasSearched.value = true
   errorMsg.value = ''
   result.value = null
-  showDropdown.value = false
   suggestions.value = []
+  showDropdown.value = false
 
   try {
     const res = await searchStocks(queryCode)
@@ -305,6 +323,16 @@ const doSearch = async (queryCode) => {
   }
 }
 
+const onInputBlur = () => {
+  // 延迟关闭,给点击下拉项留出时间
+  setTimeout(() => {
+    showDropdown.value = false
+  }, 300)
+}
+
+/**
+ * 根据涨跌幅返回样式类
+ */
 const getPriceClass = (changePercent) => {
   if (!changePercent) return ''
   if (changePercent.startsWith('+')) return 'price-up'
@@ -313,7 +341,9 @@ const getPriceClass = (changePercent) => {
 }
 </script>
 
-<style scoped>
+<style>
+/** 首页量化查询界面样式 **/
+
 .page-container {
   height: 100vh;
   display: flex;
@@ -330,30 +360,18 @@ const getPriceClass = (changePercent) => {
   padding: 32rpx 32rpx 48rpx;
 }
 
-/* 遮罩层 */
-.mask {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.3);
-  z-index: 99;
-}
-
 .card {
   background: #ffffff;
   border-radius: 24rpx;
   padding: 32rpx 32rpx 36rpx;
   box-shadow: 0 16rpx 40rpx rgba(37, 52, 94, 0.08);
   margin-bottom: 32rpx;
+  position: relative;
 }
 
-/* 搜索卡片 - 关键:设置 z-index 和 position */
 .search-card {
   margin-top: 16rpx;
-  position: relative;
-  z-index: 100;
+  z-index: 10;
 }
 
 .card-title {
@@ -364,11 +382,7 @@ const getPriceClass = (changePercent) => {
   margin-bottom: 32rpx;
 }
 
-.search-box {
-  position: relative;
-}
-
-.search-input-wrap {
+.search-row {
   display: flex;
   align-items: center;
   background: #f7f8fc;
@@ -384,6 +398,10 @@ const getPriceClass = (changePercent) => {
   color: #222222;
 }
 
+.search-placeholder {
+  color: #b4b8c6;
+}
+
 .search-button {
   width: 96rpx;
   height: 80rpx;
@@ -407,53 +425,46 @@ const getPriceClass = (changePercent) => {
   color: #9ca2b5;
 }
 
-/* 搜索建议下拉 - 关键样式 */
-.suggestions-dropdown {
+/* 下拉列表样式 */
+.search-dropdown {
   position: absolute;
-  top: 100%;
+  top: 100%; /* 相对于父元素定位,在搜索框下方 */
   left: 0;
   right: 0;
+  margin-top: 8rpx;
   background: #ffffff;
   border-radius: 16rpx;
-  margin-top: 12rpx;
-  box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
-  max-height: 480rpx;
+  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15);
+  max-height: 400rpx;
   overflow-y: auto;
-  z-index: 101;
+  z-index: 1000; /* 提高层级,确保在最上层 */
+  border: 1rpx solid #f0f0f0;
 }
 
-.suggestion-item {
+.dropdown-item {
+  padding: 24rpx 32rpx;
+  border-bottom: 1rpx solid #f5f6fb;
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 28rpx 24rpx;
-  border-bottom: 1rpx solid #f5f6fb;
 }
 
-.suggestion-item:last-child {
+.dropdown-item:last-child {
   border-bottom: none;
 }
 
-.suggestion-item:active {
-  background: #f9f9fb;
+.dropdown-item-hover {
+  background-color: #f7f8fc;
 }
 
-.suggestion-name {
+.item-name {
   font-size: 28rpx;
-  font-weight: 600;
-  color: #222222;
+  color: #333;
 }
 
-.suggestion-code {
+.item-code {
   font-size: 24rpx;
-  color: #9ca2b5;
-}
-
-.suggestion-loading {
-  padding: 48rpx;
-  text-align: center;
-  color: #9ca2b5;
-  font-size: 28rpx;
+  color: #999;
 }
 
 .result-card {
@@ -495,6 +506,12 @@ const getPriceClass = (changePercent) => {
   color: #9da3b5;
 }
 
+.detail-sub {
+  margin-top: 8rpx;
+  font-size: 24rpx;
+  color: #9da3b5;
+}
+
 .stock-price-right {
   text-align: right;
 }
@@ -510,9 +527,15 @@ const getPriceClass = (changePercent) => {
   font-size: 26rpx;
 }
 
-.price-up { color: #e74c3c; }
-.price-down { color: #27ae60; }
+.price-up {
+  color: #e74c3c;
+}
 
+.price-down {
+  color: #27ae60;
+}
+
+/* 实时行情网格 */
 .quote-section {
   margin-bottom: 24rpx;
 }
@@ -559,7 +582,7 @@ const getPriceClass = (changePercent) => {
   color: #ffffff;
   font-size: 40rpx;
   font-weight: 700;
-  text-align: center;
+  text-align: right;
   box-shadow: 0 10rpx 24rpx rgba(241, 101, 101, 0.35);
 }
 
@@ -601,9 +624,17 @@ const getPriceClass = (changePercent) => {
   color: #ffffff;
 }
 
-.tag-danger { background: #f16565; }
-.tag-success { background: #3abf81; }
-.tag-info { background: #4c86ff; }
+.tag-danger {
+  background: #f16565;
+}
+
+.tag-success {
+  background: #3abf81;
+}
+
+.tag-info {
+  background: #4c86ff;
+}
 
 .history-note {
   display: block;

+ 2 - 5
src/pages/login/login.vue

@@ -433,11 +433,8 @@ export default {
      * 显示协议内容
      */
     showAgreement(type) {
-      const title = type === 'user' ? '用户协议' : '隐私政策'
-      uni.showModal({
-        title: title,
-        content: '这里显示协议内容...',
-        showCancel: false
+      uni.navigateTo({
+        url: `/pages/agreement/agreement?type=${type}`
       })
     },
 

+ 32 - 4
src/pages/order/order.vue

@@ -90,7 +90,7 @@
 import { ref, onMounted } from 'vue'
 import { onLoad, onShow } from '@dcloudio/uni-app'
 import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
-import { getUserOrders, createOrder, wxPay } from '../../utils/api.js'
+import { getUserOrders, repayOrder, wxPay, queryOrder } from '../../utils/api.js'
 
 const orders = ref([])
 const loading = ref(false)
@@ -121,17 +121,45 @@ const loadOrders = async () => {
   }
 }
 
+// 轮询订单状态,确认服务端已处理支付回调
+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 handlePay = async (order) => {
   try {
     uni.showLoading({ title: '正在支付...' })
     
-    const res = await createOrder({ planId: order.planId })
+    // 使用已有订单号继续支付
+    const res = await repayOrder(order.orderNo)
     if (res.code !== 200) {
-      throw new Error(res.message || '创建订单失败')
+      throw new Error(res.message || '获取支付参数失败')
     }
     
     await wxPay(res.data)
-    uni.showToast({ title: '支付成功', icon: 'success' })
+    
+    // 轮询确认订单状态
+    uni.showLoading({ title: '确认支付结果...' })
+    const confirmed = await pollOrderStatus(order.orderNo)
+    
+    if (confirmed) {
+      uni.showToast({ title: '支付成功', icon: 'success' })
+    } else {
+      uni.showToast({ title: '支付处理中,请稍后查看', icon: 'none' })
+    }
     setTimeout(() => loadOrders(), 1500)
   } catch (e) {
     uni.showToast({ title: e.message || '支付失败', icon: 'none' })

+ 86 - 34
src/pages/pool/pool.vue

@@ -11,11 +11,11 @@
           <text class="pool-desc">今日更新,高频捕捉短期爆发机会</text>
         </view>
 
-        <!-- 性能指标卡片 -->
+        <!-- 性能指标卡片(动态计算) -->
         <PerformanceCard 
-          successRate="75%" 
-          profitRate="+3.2%" 
-          :totalTrades="120" 
+          :successRate="performanceStats.successRate" 
+          :profitRate="performanceStats.avgTrend" 
+          :totalTrades="performanceStats.totalCount" 
         />
 
         <!-- 超短精选池区域 -->
@@ -60,8 +60,12 @@
           </view>
         </view>
 
-        <!-- 历史股票池回顾 -->
-        <HistorySearchCard @search="onHistorySearch" />
+        <!-- 历史股票池回顾(仅订阅用户可见) -->
+        <HistorySearchCard 
+          :poolType="1"
+          :canSearch="isPurchased"
+          @dateChange="onDateChange"
+        />
 
         <!-- 预留底部空间 -->
         <view class="bottom-safe-area"></view>
@@ -75,7 +79,7 @@
       title="打赏解锁"
       description="支持作者,解锁今日超短池内容"
       amountLabel="打赏金额:"
-      :amount="1"
+      :amount="shortPrice"
       @close="closePurchaseModal"
       @confirm="handlePurchase"
     />
@@ -83,10 +87,10 @@
 </template>
 
 <script setup>
-import { ref, onUnmounted } from 'vue'
+import { ref, onUnmounted, reactive } from 'vue'
 import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
 import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
-import { getStockQuotes, addUserStock, getStockPoolList, createOrder, mockPay, checkSubscription } from '../../utils/api.js'
+import { getStockQuotes, addUserStock, getStockPoolList, createOrder, wxPay, checkSubscription, getPaymentConfig, queryOrder, getStockHistoryStats } from '../../utils/api.js'
 import PurchaseModal from '../../components/PurchaseModal.vue'
 import PerformanceCard from '../../components/PerformanceCard.vue'
 import HistorySearchCard from '../../components/HistorySearchCard.vue'
@@ -95,10 +99,36 @@ const isPurchased = ref(false)
 const showModal = ref(false)
 const isLoggedIn = ref(false)
 const isPageVisible = ref(false) // 页面是否可见
+const shortPrice = ref(1) // 超短池价格,默认1
 
 const stockList = ref([])
 let refreshTimer = null
 
+// 性能统计数据
+const performanceStats = reactive({
+  successRate: '0%',
+  avgTrend: '+0%',
+  totalCount: 0
+})
+
+// 日期变化时加载统计数据
+const onDateChange = async ({ startDate, endDate, poolType }) => {
+  try {
+    const res = await getStockHistoryStats({
+      startDate,
+      endDate,
+      poolType
+    })
+    if (res.code === 200 && res.data) {
+      performanceStats.successRate = res.data.successRate || '0%'
+      performanceStats.avgTrend = res.data.avgTrend || '+0%'
+      performanceStats.totalCount = res.data.totalCount || 0
+    }
+  } catch (e) {
+    console.error('加载统计数据失败:', e)
+  }
+}
+
 // 获取涨跌样式
 const getChangeClass = (changePercent) => {
   if (!changePercent || changePercent === '-') return ''
@@ -189,7 +219,7 @@ const loadAndStartRefresh = async () => {
 }
 
 // 显示购买弹窗(需要登录)
-const showPurchaseModal = () => {
+const showPurchaseModal = async () => {
   if (!checkLogin()) {
     uni.showModal({
       title: '登录提示',
@@ -204,6 +234,17 @@ const showPurchaseModal = () => {
     })
     return
   }
+  
+  // 获取最新价格配置
+  try {
+    const res = await getPaymentConfig(1)  // 1=超短池
+    if (res.code === 200 && res.data) {
+      shortPrice.value = res.data.price || 1
+    }
+  } catch (e) {
+    console.error('获取价格配置失败:', e)
+  }
+  
   showModal.value = true
 }
 
@@ -211,6 +252,24 @@ 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 {
@@ -222,39 +281,32 @@ const handlePurchase = async () => {
       throw new Error(res.message || '创建订单失败')
     }
     
-    // 2. 模拟支付成功(测试用,正式环境改用 wxPay)
-    const payRes = await mockPay(res.data.orderNo)
-    if (payRes.code !== 200) {
-      throw new Error(payRes.message || '支付失败')
-    }
+    const orderNo = res.data.orderNo
+    uni.hideLoading()
+    
+    // 2. 调起微信支付
+    await wxPay(res.data)
     
-    // 3. 支付成功
+    // 3. 轮询确认订单状态
+    uni.showLoading({ title: '确认支付结果...' })
+    const confirmed = await pollOrderStatus(orderNo)
     uni.hideLoading()
-    isPurchased.value = true
-    closePurchaseModal()
-    uni.showToast({ title: '支付成功', icon: 'success' })
     
-    // 解锁后加载数据并启动自动刷新
-    loadAndStartRefresh()
+    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 onHistorySearch = ({ startMonth, endMonth }) => {
-  const formatMonth = (monthStr) => {
-    const [year, month] = monthStr.split('-')
-    return `${year}年${month}月`
-  }
-  uni.showToast({
-    title: `查询${formatMonth(startMonth)}至${formatMonth(endMonth)}`,
-    icon: 'none',
-    duration: 2000
-  })
-}
-
 // 添加到我的股票
 const addToMyStocks = async (stock) => {
   if (!checkLogin()) {

+ 55 - 37
src/pages/strong/strong.vue

@@ -11,13 +11,6 @@
           <text class="pool-desc">本月精选,专注于中长期趋势跟踪</text>
         </view>
 
-        <!-- 性能指标卡片 -->
-        <PerformanceCard 
-          successRate="88%" 
-          profitRate="+12.5%" 
-          :totalTrades="45" 
-        />
-
         <!-- 强势趋势池区域 -->
         <view class="card pool-card">
           <!-- 未购买时显示锁定状态 -->
@@ -60,8 +53,11 @@
           </view>
         </view>
 
-        <!-- 历史股票池回顾 -->
-        <HistorySearchCard @search="onHistorySearch" />
+        <!-- 历史股票池回顾(仅订阅用户可见) -->
+        <HistorySearchCard 
+          :poolType="2"
+          :canSearch="isPurchased"
+        />
 
         <!-- 预留底部空间 -->
         <view class="bottom-safe-area"></view>
@@ -75,7 +71,7 @@
       title="年订阅解锁"
       description="订阅全年,解锁强势趋势池内容"
       amountLabel="订阅金额:"
-      :amount="98"
+      :amount="strongPrice"
       @close="closePurchaseModal"
       @confirm="handlePurchase"
     />
@@ -86,14 +82,14 @@
 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, mockPay, checkSubscription } from '../../utils/api.js'
+import { getStockQuotes, addUserStock, getStockPoolList, createOrder, wxPay, checkSubscription, getPaymentConfig, queryOrder } 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 strongPrice = ref(98) // 强势池价格,默认98
 
 const stockList = ref([])
 let refreshTimer = null
@@ -182,7 +178,7 @@ const loadAndStartRefresh = async () => {
 }
 
 // 显示购买弹窗(需要登录)
-const showPurchaseModal = () => {
+const showPurchaseModal = async () => {
   if (!checkLoginStatus()) {
     uni.showModal({
       title: '登录提示',
@@ -197,6 +193,17 @@ const showPurchaseModal = () => {
     })
     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
 }
 
@@ -204,6 +211,24 @@ 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 {
@@ -215,39 +240,32 @@ const handlePurchase = async () => {
       throw new Error(res.message || '创建订单失败')
     }
     
-    // 2. 模拟支付成功(测试用,正式环境改用 wxPay)
-    const payRes = await mockPay(res.data.orderNo)
-    if (payRes.code !== 200) {
-      throw new Error(payRes.message || '支付失败')
-    }
+    const orderNo = res.data.orderNo
+    uni.hideLoading()
+    
+    // 2. 调起微信支付
+    await wxPay(res.data)
     
-    // 3. 支付成功
+    // 3. 轮询确认订单状态
+    uni.showLoading({ title: '确认支付结果...' })
+    const confirmed = await pollOrderStatus(orderNo)
     uni.hideLoading()
-    isPurchased.value = true
-    closePurchaseModal()
-    uni.showToast({ title: '支付成功', icon: 'success' })
     
-    // 解锁后加载数据并启动自动刷新
-    loadAndStartRefresh()
+    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 onHistorySearch = ({ startMonth, endMonth }) => {
-  const formatMonth = (monthStr) => {
-    const [year, month] = monthStr.split('-')
-    return `${year}年${month}月`
-  }
-  uni.showToast({
-    title: `查询${formatMonth(startMonth)}至${formatMonth(endMonth)}`,
-    icon: 'none',
-    duration: 2000
-  })
-}
-
 // 添加到我的股票
 const addToMyStocks = async (stock) => {
   // 调试:直接读取storage检查

+ 92 - 28
src/utils/api.js

@@ -309,21 +309,21 @@ export const getStockPoolList = (poolType) => {
 // ==================== 订单与支付相关API ====================
 
 /**
- * 获取套餐列表
- * @param {number} poolType - 池类型:1-超短池,2-强势池(可选)
- * @returns {Promise} 返回套餐列表
+ * 获取支付配置(价格等)
+ * @param {number} poolType - 池类型:1-超短池,2-强势池
+ * @returns {Promise} 返回 { poolType, poolName, price, description }
  */
-export const getPlanList = (poolType) => {
+export const getPaymentConfig = (poolType) => {
   return request({
-    url: '/v1/order/plans',
+    url: '/v1/order/config',
     method: 'GET',
-    data: poolType ? { poolType } : {}
+    data: { poolType }
   })
 }
 
 /**
  * 创建订单(获取支付参数)
- * @param {object} data - { planId } 或 { planCode }
+ * @param {object} data - { poolType }
  * @returns {Promise} 返回微信支付参数
  */
 export const createOrder = (data) => {
@@ -361,6 +361,18 @@ export const getUserOrders = () => {
   })
 }
 
+/**
+ * 继续支付已有订单(获取支付参数)
+ * @param {string} orderNo - 订单号
+ * @returns {Promise} 返回微信支付参数
+ */
+export const repayOrder = (orderNo) => {
+  return request({
+    url: `/v1/order/repay?orderNo=${encodeURIComponent(orderNo)}`,
+    method: 'POST'
+  })
+}
+
 /**
  * 检查用户是否有有效订阅
  * @param {number} poolType - 池类型:1-超短池,2-强势池
@@ -374,25 +386,20 @@ export const checkSubscription = (poolType) => {
   })
 }
 
-/**
- * 取消订单
- * @param {string} orderNo - 订单号
- * @returns {Promise}
- */
-export const cancelOrder = (orderNo) => {
-  return request({
-    url: '/v1/order/cancel',
-    method: 'POST',
-    data: { orderNo }
-  })
-}
-
 /**
  * 调起微信支付
  * @param {object} payParams - 支付参数(从createOrder返回)
  * @returns {Promise}
  */
 export const wxPay = (payParams) => {
+  // 兼容 total_fee 和 totalFee 两种命名
+  const totalFee = payParams.total_fee || payParams.totalFee
+  console.log('调起支付,参数:', JSON.stringify(payParams), 'totalFee:', totalFee)
+  
+  if (!totalFee) {
+    console.error('缺少 total_fee 参数,payParams:', payParams)
+  }
+  
   return new Promise((resolve, reject) => {
     uni.requestPayment({
       provider: 'wxpay',
@@ -401,28 +408,85 @@ export const wxPay = (payParams) => {
       package: payParams.packageValue,
       signType: payParams.signType,
       paySign: payParams.paySign,
+      totalFee: totalFee,  // 开发者工具模拟支付需要
       success: (res) => {
         resolve(res)
       },
       fail: (err) => {
-        if (err.errMsg.includes('cancel')) {
+        console.log('支付失败:', err)
+        if (err.errMsg && err.errMsg.includes('cancel')) {
           reject(new Error('用户取消支付'))
         } else {
-          reject(new Error('支付失败:' + err.errMsg))
+          reject(new Error('支付失败:' + (err.errMsg || JSON.stringify(err))))
         }
       }
     })
   })
 }
 
+
+
+
+// ==================== 协议相关API ====================
+
 /**
- * 模拟支付成功(测试用)
- * @param {string} orderNo - 订单号
- * @returns {Promise}
+ * 获取协议内容
+ * @param {string} type - 协议类型:'user' 用户协议,'privacy' 隐私政策
+ * @returns {Promise} 返回协议HTML内容
  */
-export const mockPay = (orderNo) => {
+export const getAgreementContent = (type) => {
   return request({
-    url: `/v1/order/mock-pay?orderNo=${encodeURIComponent(orderNo)}`,
-    method: 'POST'
+    url: '/v1/agreement/content',
+    method: 'GET',
+    data: { type }
+  })
+}
+
+// ==================== 历史数据查询API ====================
+
+/**
+ * 分页查询历史数据(带隔日信息)
+ * @param {object} params - 查询参数
+ * @param {string} params.startDate - 开始日期 (yyyy-MM-dd)
+ * @param {string} params.endDate - 结束日期 (yyyy-MM-dd)
+ * @param {number} params.poolType - 池类型:1-超短池,2-强势池
+ * @param {number} params.pageNum - 页码(从1开始)
+ * @param {number} params.pageSize - 每页数量
+ * @returns {Promise} 返回历史数据列表和分页信息
+ */
+export const getStockHistory = (params) => {
+  return request({
+    url: '/v1/stock/history/list',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 查询历史统计数据(成功率、平均收益、总交易次数)
+ * @param {object} params - 查询参数
+ * @param {string} params.startDate - 开始日期 (yyyy-MM-dd)
+ * @param {string} params.endDate - 结束日期 (yyyy-MM-dd)
+ * @param {number} params.poolType - 池类型:1-超短池,2-强势池
+ * @returns {Promise} 返回统计数据
+ */
+export const getStockHistoryStats = (params) => {
+  return request({
+    url: '/v1/stock/history/stats',
+    method: 'GET',
+    data: params
+  })
+}
+
+/**
+ * 根据股票代码或名称模糊查询最新的历史记录
+ * @param {string} keyword - 搜索关键词(股票代码或名称)
+ * @returns {Promise} 返回最新的历史记录
+ */
+export const searchStockHistory = (keyword) => {
+  return request({
+    url: '/v1/stock/history/search',
+    method: 'GET',
+    data: { keyword }
   })
 }

+ 3 - 0
target/classes/META-INF/mps/autoMapper

@@ -0,0 +1,3 @@
+com.yingpai.stock.domain.vo.StockPoolVo
+com.yingpai.stock.domain.vo.StockInfoVo
+com.yingpai.stock.domain.vo.StockPoolHistoryVo

+ 1 - 0
target/classes/META-INF/mps/autoMappers

@@ -0,0 +1 @@
+org.dromara.system.domain.bo.SysOperLogBo

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

@@ -0,0 +1,28 @@
+com.yingpai.stock.domain.vo.StockPoolHistoryVoToStockPoolHistoryMapper
+com.yingpai.stock.domain.vo.StockInfoVoToStockInfoMapper
+com.yingpai.stock.domain.vo.StockPoolVoToStockPoolMapper__1
+com.yingpai.stock.domain.vo.StockPoolVoToStockPoolMapper__2
+com.yingpai.stock.domain.vo.StockPoolVoToStockPoolMapper__3
+com.yingpai.stock.domain.StockPoolToStockPoolVoMapper
+com.yingpai.stock.domain.vo.StockPoolHistoryVoToStockPoolHistoryMapper__4
+com.yingpai.stock.domain.vo.StockPoolHistoryVoToStockPoolHistoryMapper__1
+com.yingpai.stock.domain.vo.StockInfoVoToStockInfoMapper__1
+com.yingpai.stock.domain.vo.StockPoolHistoryVoToStockPoolHistoryMapper__2
+com.yingpai.stock.domain.vo.StockInfoVoToStockInfoMapper__2
+com.yingpai.stock.domain.vo.StockPoolHistoryVoToStockPoolHistoryMapper__3
+com.yingpai.stock.domain.vo.StockInfoVoToStockInfoMapper__3
+com.yingpai.stock.domain.vo.StockInfoVoToStockInfoMapper__4
+com.yingpai.stock.domain.StockInfoToStockInfoVoMapper
+com.yingpai.stock.domain.StockPoolHistoryToStockPoolHistoryVoMapper
+com.yingpai.stock.domain.vo.StockPoolVoToStockPoolMapper
+com.yingpai.stock.domain.StockPoolToStockPoolVoMapper__1
+com.yingpai.stock.domain.StockInfoToStockInfoVoMapper__3
+com.yingpai.stock.domain.StockPoolToStockPoolVoMapper__2
+com.yingpai.stock.domain.StockInfoToStockInfoVoMapper__4
+com.yingpai.stock.domain.StockPoolToStockPoolVoMapper__3
+com.yingpai.stock.domain.StockInfoToStockInfoVoMapper__1
+com.yingpai.stock.domain.StockInfoToStockInfoVoMapper__2
+com.yingpai.stock.domain.StockPoolHistoryToStockPoolHistoryVoMapper__4
+com.yingpai.stock.domain.StockPoolHistoryToStockPoolHistoryVoMapper__3
+com.yingpai.stock.domain.StockPoolHistoryToStockPoolHistoryVoMapper__2
+com.yingpai.stock.domain.StockPoolHistoryToStockPoolHistoryVoMapper__1

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov