Explorar o código

打分查询模糊搜索框更改

Zhangbw hai 3 meses
pai
achega
9d47dfc1b4

+ 9 - 0
.gitignore

@@ -12,3 +12,12 @@ $RECYCLE.BIN/
 
 # Node.js
 node_modules/
+
+# Build output
+dist/
+
+# WeChat Mini Program private config
+project.private.config.json
+
+# Logs
+*.log

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

@@ -11,7 +11,6 @@ if (!Math) {
   "./pages/rank/rank.js";
   "./pages/mine/mine.js";
   "./pages/profile/edit.js";
-  "./pages/transaction/transaction.js";
   "./pages/order/order.js";
   "./pages/admin/shortPool.js";
 }

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

@@ -7,7 +7,6 @@
     "pages/rank/rank",
     "pages/mine/mine",
     "pages/profile/edit",
-    "pages/transaction/transaction",
     "pages/order/order",
     "pages/admin/shortPool"
   ],

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

@@ -6812,7 +6812,6 @@ exports._export_sfc = _export_sfc;
 exports.createSSRApp = createSSRApp;
 exports.e = e;
 exports.f = f;
-exports.getCurrentInstance = getCurrentInstance;
 exports.index = index;
 exports.n = n;
 exports.nextTick$1 = nextTick$1;
@@ -6830,3 +6829,4 @@ exports.resolveComponent = resolveComponent;
 exports.sr = sr;
 exports.t = t;
 exports.unref = unref;
+exports.wx$1 = wx$1;

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

@@ -22,7 +22,7 @@ const _sfc_main = {
         if (token)
           header["Authorization"] = `Bearer ${token}`;
         common_vendor.index.request({
-          url: `http://localhost:8081${options.url}`,
+          url: `${utils_api.BASE_URL}${options.url}`,
           method: options.method || "GET",
           data: options.data || {},
           header,

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

@@ -12,85 +12,86 @@ 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 timer = null;
+    let searchTimer = 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: "量化交易大师" });
     });
-    const handleSearchClick = async () => {
-      console.log("=== 点击搜索按钮 ===");
-      console.log("当前登录状态:", isLoggedIn.value);
-      if (!isLoggedIn.value) {
-        console.log("未登录,跳转到登录页");
-        common_vendor.index.showModal({
-          title: "登录提示",
-          content: "此功能需要登录后使用,是否前往登录?",
-          confirmText: "去登录",
-          cancelText: "取消",
-          success: (res) => {
-            if (res.confirm) {
-              common_vendor.index.navigateTo({
-                url: "/pages/login/login"
-              });
-            }
-          }
-        });
-        return;
+    common_vendor.onHide(() => {
+      showDropdown.value = false;
+    });
+    const onInputFocus = () => {
+      if (suggestions.value.length > 0) {
+        showDropdown.value = true;
       }
-      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()) {
+      if (searchTimer)
+        clearTimeout(searchTimer);
+      if (!value || !value.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;
-      }
+      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) => {
-      const searchText = `${item.name} (${item.code})`;
-      keyword.value = searchText;
+      keyword.value = `${item.name} (${item.code})`;
       suggestions.value = [];
       showDropdown.value = false;
       doSearch(item.code);
     };
+    const handleSearchClick = () => {
+      if (!isLoggedIn.value) {
+        common_vendor.index.showModal({
+          title: "登录提示",
+          content: "此功能需要登录后使用,是否前往登录?",
+          confirmText: "去登录",
+          cancelText: "取消",
+          success: (res) => {
+            if (res.confirm) {
+              common_vendor.index.navigateTo({ url: "/pages/login/login" });
+            }
+          }
+        });
+        return;
+      }
+      onSearch();
+    };
     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) {
@@ -103,8 +104,8 @@ const _sfc_main = {
       hasSearched.value = true;
       errorMsg.value = "";
       result.value = null;
-      suggestions.value = [];
       showDropdown.value = false;
+      suggestions.value = [];
       try {
         const res = await utils_api.searchStocks(queryCode);
         if (res.code === 200 && res.data) {
@@ -118,11 +119,6 @@ const _sfc_main = {
         loading.value = false;
       }
     };
-    const onInputBlur = () => {
-      setTimeout(() => {
-        showDropdown.value = false;
-      }, 300);
-    };
     const getPriceClass = (changePercent) => {
       if (!changePercent)
         return "";
@@ -134,14 +130,18 @@ const _sfc_main = {
     };
     return (_ctx, _cache) => {
       return common_vendor.e({
-        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) => {
+        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) => {
           return {
             a: common_vendor.t(item.name),
             b: common_vendor.t(item.code),
@@ -150,59 +150,61 @@ const _sfc_main = {
           };
         })
       } : {}, {
-        h: common_vendor.t(isLoggedIn.value ? "" : "(需登录)"),
-        i: hasSearched.value
+        j: showDropdown.value && searching.value
+      }, showDropdown.value && searching.value ? {} : {}, {
+        k: common_vendor.t(isLoggedIn.value ? "" : "(需登录)"),
+        l: hasSearched.value
       }, hasSearched.value ? common_vendor.e({
-        j: loading.value
+        m: loading.value
       }, loading.value ? {} : errorMsg.value ? {
-        l: common_vendor.t(errorMsg.value)
+        o: common_vendor.t(errorMsg.value)
       } : result.value ? common_vendor.e({
-        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 || "--"),
+        q: common_vendor.t(result.value.stockName),
+        r: common_vendor.t(result.value.stockCode),
+        s: common_vendor.t(result.value.currentPrice || "--"),
         t: common_vendor.n(getPriceClass(result.value.changePercent)),
-        v: result.value.currentPrice
+        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
       }, result.value.currentPrice ? {
-        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 || "--")
+        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 || "--")
       } : {}, {
-        C: result.value.score
+        F: result.value.score
       }, result.value.score ? {
-        D: common_vendor.t(result.value.score)
+        G: common_vendor.t(result.value.score)
       } : {}, {
-        E: result.value.history && result.value.history.length > 0
+        H: result.value.history && result.value.history.length > 0
       }, result.value.history && result.value.history.length > 0 ? {
-        F: common_vendor.f(result.value.history, (item, index, i0) => {
+        I: common_vendor.f(result.value.history, (item, idx, 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: index
+            d: idx
           };
         })
       } : {}, {
-        G: result.value.factors && result.value.factors.length > 0
+        J: result.value.factors && result.value.factors.length > 0
       }, result.value.factors && result.value.factors.length > 0 ? {
-        H: common_vendor.f(result.value.factors, (item, index, i0) => {
+        K: common_vendor.f(result.value.factors, (item, idx, i0) => {
           return {
             a: common_vendor.t(item.name),
             b: common_vendor.t(item.value),
-            c: index
+            c: idx
           };
         })
       } : {}) : {}, {
-        k: errorMsg.value,
-        m: result.value
+        n: errorMsg.value,
+        p: result.value
       }) : {});
     };
   }
 };
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__file", "D:/program/gupiao-wx/src/pages/index/index.vue"]]);
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-83a5a03c"], ["__file", "D:/program/gupiao-wx/src/pages/index/index.vue"]]);
 wx.createPage(MiniProgramPage);

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
dist/dev/mp-weixin/pages/index/index.wxml


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

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

+ 5 - 14
dist/dev/mp-weixin/pages/mine/mine.js

@@ -62,14 +62,6 @@ const _sfc_main = {
         }
       });
     };
-    const handleSubscriptionRecord = () => {
-      if (!utils_auth.checkLogin()) {
-        return;
-      }
-      common_vendor.index.navigateTo({
-        url: "/pages/transaction/transaction"
-      });
-    };
     const handleOrderRecord = () => {
       if (!utils_auth.checkLogin()) {
         return;
@@ -92,15 +84,14 @@ const _sfc_main = {
         a: isLoggedIn.value && userInfo.value.avatar ? common_vendor.unref(utils_api.getImageUrl)(userInfo.value.avatar) : "/static/images/head.png",
         b: common_vendor.t(isLoggedIn.value ? userInfo.value.nickname || "微信用户" : "登录/注册"),
         c: common_vendor.o(handleUserCardClick),
-        d: common_vendor.o(handleSubscriptionRecord),
-        e: common_vendor.o(handleOrderRecord),
-        f: isAdmin.value
+        d: common_vendor.o(handleOrderRecord),
+        e: isAdmin.value
       }, isAdmin.value ? {
-        g: common_vendor.o(handleShortPoolManage)
+        f: common_vendor.o(handleShortPoolManage)
       } : {}, {
-        h: isLoggedIn.value
+        g: isLoggedIn.value
       }, isLoggedIn.value ? {
-        i: common_vendor.o(handleLogout)
+        h: common_vendor.o(handleLogout)
       } : {});
     };
   }

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

@@ -1 +1 @@
-<view class="page-mine"><scroll-view class="scroll-view" scroll-y><view class="content-wrapper"><view class="user-info-card" bindtap="{{c}}"><view class="user-header"><image class="user-avatar" src="{{a}}" mode="aspectFill"></image><view class="user-details"><text class="user-name">{{b}}</text></view><view class="arrow-icon">›</view></view></view><view class="menu-list"><view class="menu-item" bindtap="{{d}}"><view class="menu-left"><text class="menu-icon">📝</text><text class="menu-label">我的订阅</text></view><text class="menu-arrow">›</text></view><view class="menu-item" bindtap="{{e}}"><view class="menu-left"><text class="menu-icon">📋</text><text class="menu-label">我的订单</text></view><text class="menu-arrow">›</text></view><view wx:if="{{f}}" class="menu-item" bindtap="{{g}}"><view class="menu-left"><text class="menu-icon">⚡</text><text class="menu-label">超短池管理</text></view><text class="menu-arrow">›</text></view></view><view wx:if="{{h}}" class="logout-section"><button class="logout-btn" bindtap="{{i}}"> 退出登录 </button></view><view class="bottom-safe-area"></view></view></scroll-view></view>
+<view class="page-mine"><scroll-view class="scroll-view" scroll-y><view class="content-wrapper"><view class="user-info-card" bindtap="{{c}}"><view class="user-header"><image class="user-avatar" src="{{a}}" mode="aspectFill"></image><view class="user-details"><text class="user-name">{{b}}</text></view><view class="arrow-icon">›</view></view></view><view class="menu-list"><view class="menu-item" bindtap="{{d}}"><view class="menu-left"><text class="menu-icon">📋</text><text class="menu-label">我的订单</text></view><text class="menu-arrow">›</text></view><view wx:if="{{e}}" class="menu-item" bindtap="{{f}}"><view class="menu-left"><text class="menu-icon">⚡</text><text class="menu-label">超短池管理</text></view><text class="menu-arrow">›</text></view></view><view wx:if="{{g}}" class="logout-section"><button class="logout-btn" bindtap="{{h}}"> 退出登录 </button></view><view class="bottom-safe-area"></view></view></scroll-view></view>

+ 21 - 14
dist/dev/mp-weixin/pages/order/order.js

@@ -49,13 +49,13 @@ const _sfc_main = {
     const getStatusClass = (status) => {
       switch (status) {
         case 0:
-          return "pending";
+          return "status-pending";
         case 1:
-          return "paid";
+          return "status-paid";
         case 2:
-          return "cancelled";
+          return "status-cancelled";
         default:
-          return "closed";
+          return "status-closed";
       }
     };
     common_vendor.onLoad(() => checkLogin());
@@ -73,19 +73,26 @@ const _sfc_main = {
         a: common_vendor.o(handleBack),
         b: loading.value
       }, loading.value ? {} : orders.value.length === 0 ? {} : {
-        d: common_vendor.f(orders.value, (order, k0, i0) => {
+        d: common_vendor.f(orders.value, (order, index, i0) => {
           return common_vendor.e({
-            a: common_vendor.t(order.orderNo),
-            b: common_vendor.t(order.orderStatusName),
-            c: common_vendor.n(getStatusClass(order.orderStatus)),
-            d: common_vendor.t(order.planName),
-            e: common_vendor.t(order.amount),
-            f: common_vendor.t(order.createTime),
-            g: order.orderStatus === 0
+            a: common_vendor.t(order.poolType === 1 ? "⚡" : "📈"),
+            b: common_vendor.t(order.poolName || (order.poolType === 1 ? "超短池" : "强势池")),
+            c: common_vendor.n(order.poolType === 1 ? "tag-orange" : "tag-purple"),
+            d: common_vendor.t(order.orderStatusName),
+            e: common_vendor.n(getStatusClass(order.orderStatus)),
+            f: common_vendor.t(order.orderNo),
+            g: common_vendor.t(order.amount),
+            h: order.expireTime
+          }, order.expireTime ? {
+            i: common_vendor.t(order.expireTime)
+          } : {}, {
+            j: common_vendor.t(order.createTime),
+            k: order.orderStatus === 0
           }, order.orderStatus === 0 ? {
-            h: common_vendor.o(($event) => handlePay(order), order.orderNo)
+            l: common_vendor.o(($event) => handlePay(order), order.orderNo)
           } : {}, {
-            i: order.orderNo
+            m: order.orderNo,
+            n: index === orders.value.length - 1 ? 1 : ""
           });
         })
       }, {

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 1
dist/dev/mp-weixin/pages/order/order.wxml


+ 187 - 73
dist/dev/mp-weixin/pages/order/order.wxss

@@ -1,29 +1,35 @@
 
 .page-container.data-v-88bf5328 {
   min-height: 100vh;
-  background: #f5f6fb;
+  background: #F0F1F5;
   display: flex;
   flex-direction: column;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif;
 }
+
+/* 导航栏 */
 .custom-navbar.data-v-88bf5328 {
-  background: #ffffff;
+  background: #FFFFFF;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 80rpx 32rpx 30rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+  padding: 88rpx 28rpx 28rpx;
   position: relative;
 }
 .navbar-back.data-v-88bf5328 {
-  width: 80rpx;
-  height: 60rpx;
+  width: 68rpx;
+  height: 68rpx;
+  background: #F5F6F8;
+  border-radius: 50%;
   display: flex;
   align-items: center;
+  justify-content: center;
 }
 .back-icon.data-v-88bf5328 {
-  font-size: 40rpx;
-  color: #222222;
-  font-weight: bold;
+  font-size: 48rpx;
+  color: #1F2937;
+  font-weight: 300;
+  margin-top: -4rpx;
 }
 .navbar-title.data-v-88bf5328 {
   position: absolute;
@@ -31,118 +37,226 @@
   transform: translateX(-50%);
 }
 .title-text.data-v-88bf5328 {
-  font-size: 36rpx;
+  font-size: 34rpx;
   font-weight: 600;
-  color: #222222;
+  color: #1F2937;
 }
 .navbar-placeholder.data-v-88bf5328 {
-  width: 80rpx;
+  width: 68rpx;
 }
+
+/* 滚动区域 */
 .scroll-view.data-v-88bf5328 {
   flex: 1;
   height: 0;
 }
 .content-wrapper.data-v-88bf5328 {
-  padding: 32rpx;
+  padding: 28rpx;
 }
-.order-card.data-v-88bf5328 {
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 32rpx;
-  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+
+/* 主卡片 - 弥散阴影 */
+.main-card.data-v-88bf5328 {
+  background: #FFFFFF;
+  border-radius: 32rpx;
+  padding: 8rpx;
+  box-shadow: 
+    0 2rpx 8rpx rgba(0, 0, 0, 0.02),
+    0 8rpx 24rpx rgba(0, 0, 0, 0.04),
+    0 24rpx 48rpx rgba(0, 0, 0, 0.04);
 }
-.loading-state.data-v-88bf5328, .empty-state.data-v-88bf5328 {
+
+/* 加载状态 */
+.loading-state.data-v-88bf5328 {
   display: flex;
   flex-direction: column;
   align-items: center;
-  padding: 60rpx 40rpx;
+  padding: 100rpx 40rpx;
 }
-.empty-icon.data-v-88bf5328 {
-  font-size: 80rpx;
+.loading-spinner.data-v-88bf5328 {
+  width: 44rpx;
+  height: 44rpx;
+  border: 3rpx solid #E5E7EB;
+  border-top-color: #6366F1;
+  border-radius: 50%;
+  animation: spin-88bf5328 0.8s linear infinite;
   margin-bottom: 20rpx;
 }
-.empty-text.data-v-88bf5328 {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333333;
+@keyframes spin-88bf5328 {
+to { transform: rotate(360deg);
 }
-.order-list.data-v-88bf5328 {
+}
+.loading-text.data-v-88bf5328 {
+  font-size: 26rpx;
+  color: #9CA3AF;
+}
+
+/* 空状态 */
+.empty-state.data-v-88bf5328 {
   display: flex;
   flex-direction: column;
-  gap: 16rpx;
+  align-items: center;
+  padding: 100rpx 40rpx;
+}
+.empty-icon-wrap.data-v-88bf5328 {
+  width: 120rpx;
+  height: 120rpx;
+  background: #F9FAFB;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 24rpx;
+}
+.empty-icon.data-v-88bf5328 {
+  font-size: 52rpx;
+}
+.empty-title.data-v-88bf5328 {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #1F2937;
+  margin-bottom: 8rpx;
+}
+.empty-desc.data-v-88bf5328 {
+  font-size: 26rpx;
+  color: #9CA3AF;
+}
+
+/* 订单列表 */
+.order-list.data-v-88bf5328 {
+  padding: 16rpx;
 }
 .order-item.data-v-88bf5328 {
-  background: #f9f9fb;
-  border-radius: 12rpx;
-  padding: 24rpx;
+  padding: 28rpx 0;
+  border-bottom: 1rpx solid #F3F4F6;
+}
+.order-item.no-border.data-v-88bf5328 {
+  border-bottom: none;
 }
-.order-header.data-v-88bf5328 {
+
+/* 顶部:标签 + 状态 */
+.order-top.data-v-88bf5328 {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 16rpx;
+  margin-bottom: 20rpx;
 }
-.order-no.data-v-88bf5328 {
-  font-size: 22rpx;
-  color: #999;
-  font-family: monospace;
+.pool-tag.data-v-88bf5328 {
+  display: flex;
+  align-items: center;
+  padding: 10rpx 18rpx;
+  border-radius: 12rpx;
+  gap: 8rpx;
+}
+.pool-tag.tag-purple.data-v-88bf5328 {
+  background: rgba(139, 92, 246, 0.1);
 }
-.order-status.data-v-88bf5328 {
-  padding: 4rpx 16rpx;
-  border-radius: 20rpx;
+.pool-tag.tag-orange.data-v-88bf5328 {
+  background: rgba(251, 146, 60, 0.12);
+}
+.tag-icon.data-v-88bf5328 {
+  font-size: 26rpx;
+}
+.tag-text.data-v-88bf5328 {
+  font-size: 26rpx;
+  font-weight: 600;
+  color: #374151;
+}
+.status-badge.data-v-88bf5328 {
+  padding: 8rpx 20rpx;
+  border-radius: 32rpx;
   font-size: 22rpx;
+  font-weight: 500;
 }
-.order-status.pending.data-v-88bf5328 {
-  background: #fff3e0;
-  color: #ff9800;
+.status-badge.status-paid.data-v-88bf5328 {
+  background: rgba(34, 197, 94, 0.12);
+  color: #16A34A;
 }
-.order-status.paid.data-v-88bf5328 {
-  background: #e8f5e9;
-  color: #4caf50;
+.status-badge.status-pending.data-v-88bf5328 {
+  background: rgba(251, 191, 36, 0.15);
+  color: #D97706;
 }
-.order-status.cancelled.data-v-88bf5328 {
-  background: #f5f5f5;
-  color: #999;
+.status-badge.status-cancelled.data-v-88bf5328 {
+  background: #F3F4F6;
+  color: #9CA3AF;
 }
-.order-status.closed.data-v-88bf5328 {
-  background: #ffebee;
-  color: #f44336;
+.status-badge.status-closed.data-v-88bf5328 {
+  background: rgba(239, 68, 68, 0.1);
+  color: #DC2626;
 }
-.order-body.data-v-88bf5328 {
+
+/* 中间:订单号 + 价格 */
+.order-middle.data-v-88bf5328 {
   display: flex;
   justify-content: space-between;
-  align-items: center;
-  margin-bottom: 12rpx;
+  align-items: flex-start;
+  margin-bottom: 16rpx;
 }
-.order-plan.data-v-88bf5328 {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333;
+.order-info.data-v-88bf5328 {
+  display: flex;
+  flex-direction: column;
+  gap: 4rpx;
 }
-.order-amount.data-v-88bf5328 {
-  font-size: 30rpx;
+.info-label.data-v-88bf5328 {
+  font-size: 22rpx;
+  color: #9CA3AF;
+}
+.order-no.data-v-88bf5328 {
+  font-size: 24rpx;
+  color: #6B7280;
+  font-family: 'SF Mono', Monaco, Consolas, monospace;
+}
+.order-price.data-v-88bf5328 {
+  font-size: 40rpx;
   font-weight: 700;
-  color: #f16565;
+  color: #EF4444;
 }
-.order-footer.data-v-88bf5328 {
+
+/* 底部:时间信息 */
+.order-bottom.data-v-88bf5328 {
+  display: flex;
+  flex-direction: column;
+  gap: 8rpx;
+}
+.time-row.data-v-88bf5328 {
   display: flex;
-  justify-content: space-between;
   align-items: center;
+  gap: 12rpx;
 }
-.order-time.data-v-88bf5328 {
+.time-label.data-v-88bf5328 {
   font-size: 22rpx;
-  color: #999;
+  color: #9CA3AF;
+  min-width: 100rpx;
+}
+.time-value.data-v-88bf5328 {
+  font-size: 24rpx;
+  color: #6B7280;
+}
+.time-value.highlight.data-v-88bf5328 {
+  color: #3B82F6;
+  font-weight: 500;
+}
+
+/* 支付按钮 */
+.order-action.data-v-88bf5328 {
+  margin-top: 20rpx;
+  display: flex;
+  justify-content: flex-end;
 }
 .pay-btn.data-v-88bf5328 {
-  padding: 10rpx 28rpx;
-  background: #ff9800;
-  border-radius: 24rpx;
+  background: linear-gradient(135deg, #6366F1, #8B5CF6);
+  padding: 16rpx 36rpx;
+  border-radius: 32rpx;
+  box-shadow: 0 6rpx 20rpx rgba(99, 102, 241, 0.3);
 }
 .pay-btn text.data-v-88bf5328 {
-  font-size: 24rpx;
-  color: #fff;
-  font-weight: 500;
+  font-size: 26rpx;
+  color: #FFFFFF;
+  font-weight: 600;
+}
+.pay-btn.data-v-88bf5328:active {
+  opacity: 0.9;
+  transform: scale(0.98);
 }
 .bottom-safe-area.data-v-88bf5328 {
-  height: 80rpx;
+  height: 60rpx;
 }

+ 129 - 66
dist/dev/mp-weixin/pages/rank/rank.js

@@ -5,7 +5,6 @@ const utils_api = require("../../utils/api.js");
 const _sfc_main = {
   __name: "rank",
   setup(__props) {
-    let componentInstance = null;
     const isLoggedIn = common_vendor.ref(false);
     const myStocks = common_vendor.ref([]);
     const viewMode = common_vendor.ref("list");
@@ -62,6 +61,12 @@ const _sfc_main = {
     };
     const setViewMode = (mode) => {
       viewMode.value = mode;
+      if (mode === "list" && myStocks.value.length > 0) {
+        common_vendor.nextTick$1(() => {
+          clearAllCanvases();
+          drawAllTrendCharts();
+        });
+      }
     };
     const indexData = common_vendor.ref({
       stockCode: "000001",
@@ -131,63 +136,103 @@ const _sfc_main = {
         return "market-cy";
       return "market-sh";
     };
+    const getDevicePixelRatio = () => {
+      try {
+        const windowInfo = common_vendor.wx$1.getWindowInfo();
+        return windowInfo.pixelRatio || 2;
+      } catch (e) {
+        return 2;
+      }
+    };
+    const clearCanvas = (canvasId) => {
+      const query = common_vendor.index.createSelectorQuery();
+      query.select("#" + canvasId).fields({ node: true, size: true }).exec((res) => {
+        if (!res || !res[0] || !res[0].node)
+          return;
+        const canvas = res[0].node;
+        const ctx = canvas.getContext("2d");
+        const dpr = getDevicePixelRatio();
+        const width = res[0].width;
+        const height = res[0].height;
+        canvas.width = width * dpr;
+        canvas.height = height * dpr;
+        ctx.setTransform(1, 0, 0, 1, 0, 0);
+        ctx.clearRect(0, 0, width * dpr, height * dpr);
+      });
+    };
+    const clearAllCanvases = () => {
+      myStocks.value.forEach((stock) => {
+        clearCanvas("chart-" + stock.code);
+      });
+    };
     const drawTrendChart = (stock) => {
-      if (!componentInstance)
-        return;
       const canvasId = "chart-" + stock.code;
       let trendData = stock.trendData;
       if (!trendData || !Array.isArray(trendData) || trendData.length === 0) {
         trendData = generateMockTrendData(stock.changePercent);
       }
-      const ctx = common_vendor.index.createCanvasContext(canvasId, componentInstance);
-      const width = 120;
-      const height = 28;
-      const padding = 1;
-      const maxValue = Math.max(...trendData);
-      const minValue = Math.min(...trendData);
-      const dataRange = maxValue - minValue;
-      const avgValue = (maxValue + minValue) / 2;
-      const minRange = avgValue * 0.03 || 1;
-      const range = Math.max(dataRange, minRange);
-      const baseValue = trendData[0];
-      const baseY = height - padding - (baseValue - minValue) / range * (height - padding * 2);
-      const changePercent = parseFloat(String(stock.changePercent || "0").replace("%", "").replace("+", ""));
-      const isUp = changePercent >= 0;
-      const lineColor = isUp ? "#ef4444" : "#22c55e";
-      const fillColor = isUp ? "rgba(239, 68, 68, 0.15)" : "rgba(34, 197, 94, 0.15)";
-      ctx.beginPath();
-      ctx.setStrokeStyle("#e5e7eb");
-      ctx.setLineWidth(0.5);
-      ctx.setLineDash([2, 2], 0);
-      ctx.moveTo(padding, baseY);
-      ctx.lineTo(width - padding, baseY);
-      ctx.stroke();
-      ctx.setLineDash([], 0);
-      ctx.beginPath();
-      ctx.moveTo(padding, baseY);
-      trendData.forEach((value, index) => {
-        const x = padding + index / (trendData.length - 1) * (width - padding * 2);
-        const y = height - padding - (value - minValue) / range * (height - padding * 2);
-        ctx.lineTo(x, y);
-      });
-      ctx.lineTo(width - padding, baseY);
-      ctx.closePath();
-      ctx.setFillStyle(fillColor);
-      ctx.fill();
-      ctx.beginPath();
-      trendData.forEach((value, index) => {
-        const x = padding + index / (trendData.length - 1) * (width - padding * 2);
-        const y = height - padding - (value - minValue) / range * (height - padding * 2);
-        if (index === 0) {
-          ctx.moveTo(x, y);
-        } else {
-          ctx.lineTo(x, y);
+      const query = common_vendor.index.createSelectorQuery();
+      query.select("#" + canvasId).fields({ node: true, size: true }).exec((res) => {
+        if (!res || !res[0] || !res[0].node) {
+          console.warn("[趋势图] 获取canvas节点失败:", canvasId);
+          return;
         }
+        const canvas = res[0].node;
+        const ctx = canvas.getContext("2d");
+        const dpr = getDevicePixelRatio();
+        const width = res[0].width;
+        const height = res[0].height;
+        canvas.width = width * dpr;
+        canvas.height = height * dpr;
+        ctx.setTransform(1, 0, 0, 1, 0, 0);
+        ctx.scale(dpr, dpr);
+        const padding = 1;
+        const maxValue = Math.max(...trendData);
+        const minValue = Math.min(...trendData);
+        const dataRange = maxValue - minValue;
+        const avgValue = (maxValue + minValue) / 2;
+        const minRange = avgValue * 0.03 || 1;
+        const range = Math.max(dataRange, minRange);
+        const baseValue = trendData[0];
+        const baseY = height - padding - (baseValue - minValue) / range * (height - padding * 2);
+        const changePercent = parseFloat(String(stock.changePercent || "0").replace("%", "").replace("+", ""));
+        const isUp = changePercent >= 0;
+        const lineColor = isUp ? "#ef4444" : "#22c55e";
+        const fillColor = isUp ? "rgba(239, 68, 68, 0.15)" : "rgba(34, 197, 94, 0.15)";
+        ctx.clearRect(0, 0, width, height);
+        ctx.beginPath();
+        ctx.strokeStyle = "#e5e7eb";
+        ctx.lineWidth = 0.5;
+        ctx.setLineDash([2, 2]);
+        ctx.moveTo(padding, baseY);
+        ctx.lineTo(width - padding, baseY);
+        ctx.stroke();
+        ctx.setLineDash([]);
+        ctx.beginPath();
+        ctx.moveTo(padding, baseY);
+        trendData.forEach((value, index) => {
+          const x = padding + index / (trendData.length - 1) * (width - padding * 2);
+          const y = height - padding - (value - minValue) / range * (height - padding * 2);
+          ctx.lineTo(x, y);
+        });
+        ctx.lineTo(width - padding, baseY);
+        ctx.closePath();
+        ctx.fillStyle = fillColor;
+        ctx.fill();
+        ctx.beginPath();
+        trendData.forEach((value, index) => {
+          const x = padding + index / (trendData.length - 1) * (width - padding * 2);
+          const y = height - padding - (value - minValue) / range * (height - padding * 2);
+          if (index === 0) {
+            ctx.moveTo(x, y);
+          } else {
+            ctx.lineTo(x, y);
+          }
+        });
+        ctx.strokeStyle = lineColor;
+        ctx.lineWidth = 1.5;
+        ctx.stroke();
       });
-      ctx.setStrokeStyle(lineColor);
-      ctx.setLineWidth(1.5);
-      ctx.stroke();
-      ctx.draw();
     };
     const generateMockTrendData = (changePercent) => {
       const change = parseFloat(String(changePercent || "0").replace("%", "").replace("+", ""));
@@ -206,12 +251,20 @@ const _sfc_main = {
     const drawAllTrendCharts = () => {
       if (myStocks.value.length === 0)
         return;
+      if (!isPageVisible.value)
+        return;
       common_vendor.nextTick$1(() => {
         setTimeout(() => {
-          myStocks.value.forEach((stock) => {
-            drawTrendChart(stock);
+          if (!isPageVisible.value)
+            return;
+          myStocks.value.forEach((stock, index) => {
+            setTimeout(() => {
+              if (isPageVisible.value) {
+                drawTrendChart(stock);
+              }
+            }, index * 30);
           });
-        }, 300);
+        }, 350);
       });
     };
     const loadMyStocks = async (forceRefresh = false) => {
@@ -230,18 +283,23 @@ const _sfc_main = {
         console.log("[我的股票] 使用缓存数据,只刷新行情");
         await fetchIndexData();
         await refreshAllQuotes();
+        drawAllTrendCharts();
         startAutoRefresh();
         return;
       }
       try {
         isLoading.value = true;
+        let showedLoading = false;
         if (myStocks.value.length === 0 || forceRefresh) {
           common_vendor.index.showLoading({ title: "加载中..." });
+          showedLoading = true;
         }
         console.log("[我的股票] 调用 getUserStocks 接口");
         const res = await utils_api.getUserStocks();
         console.log("[我的股票] 服务器返回:", JSON.stringify(res));
-        common_vendor.index.hideLoading();
+        if (showedLoading) {
+          common_vendor.index.hideLoading();
+        }
         if (res.code === 200 && res.data) {
           myStocks.value = res.data.map((item) => ({
             code: item.stockCode,
@@ -267,7 +325,10 @@ const _sfc_main = {
         }
         startAutoRefresh();
       } catch (e) {
-        common_vendor.index.hideLoading();
+        try {
+          common_vendor.index.hideLoading();
+        } catch (err) {
+        }
         console.error("[我的股票] 加载失败:", e);
         if (myStocks.value.length === 0) {
           myStocks.value = [];
@@ -317,7 +378,7 @@ const _sfc_main = {
           stopAutoRefresh();
           return;
         }
-        const delay = 3e3 + Math.random() * 1e3;
+        const delay = 1e4;
         refreshTimer = setTimeout(async () => {
           if (!isPageVisible.value) {
             stopAutoRefresh();
@@ -326,6 +387,7 @@ const _sfc_main = {
           await fetchIndexData();
           if (myStocks.value.length > 0) {
             await refreshAllQuotes();
+            drawAllTrendCharts();
           }
           scheduleNextRefresh();
         }, delay);
@@ -370,7 +432,6 @@ const _sfc_main = {
     };
     common_vendor.onLoad(() => {
       console.log("[我的股票] onLoad 触发");
-      componentInstance = common_vendor.getCurrentInstance();
       isLoggedIn.value = utils_auth.isLoggedIn();
       isPageVisible.value = true;
       loadMyStocks(true);
@@ -378,6 +439,9 @@ const _sfc_main = {
     common_vendor.onShow(() => {
       console.log("[我的股票] onShow 触发");
       isPageVisible.value = true;
+      if (myStocks.value.length > 0) {
+        clearAllCanvases();
+      }
       const wasLoggedIn = isLoggedIn.value;
       isLoggedIn.value = utils_auth.isLoggedIn();
       if (wasLoggedIn !== isLoggedIn.value) {
@@ -421,15 +485,14 @@ const _sfc_main = {
             c: common_vendor.n(getMarketClass(stock.code)),
             d: common_vendor.t(stock.code),
             e: "chart-" + stock.code,
-            f: "chart-" + stock.code,
-            g: common_vendor.t(stock.currentPrice || "-"),
-            h: common_vendor.t(stock.changePercent || "-"),
-            i: common_vendor.n(getProfitClass(stock.changePercent)),
-            j: common_vendor.o(($event) => removeStock(index), stock.code),
-            k: ((_a = slideStates[stock.code]) == null ? void 0 : _a.x) || 0,
-            l: common_vendor.o((e) => handleSlideChange(e, stock.code), stock.code),
-            m: common_vendor.o(() => handleSlideEnd(stock.code), stock.code),
-            n: stock.code
+            f: common_vendor.t(stock.currentPrice || "-"),
+            g: common_vendor.t(stock.changePercent || "-"),
+            h: common_vendor.n(getProfitClass(stock.changePercent)),
+            i: common_vendor.o(($event) => removeStock(index), stock.code),
+            j: ((_a = slideStates[stock.code]) == null ? void 0 : _a.x) || 0,
+            k: common_vendor.o((e) => handleSlideChange(e, stock.code), stock.code),
+            l: common_vendor.o(() => handleSlideEnd(stock.code), stock.code),
+            m: stock.code
           };
         }),
         o: viewMode.value === "list",

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
dist/dev/mp-weixin/pages/rank/rank.wxml


+ 3 - 0
dist/dev/mp-weixin/pages/strong/strong.js

@@ -136,6 +136,9 @@ const _sfc_main = {
       });
     };
     const addToMyStocks = async (stock) => {
+      const tokenDirect = common_vendor.index.getStorageSync("user_token");
+      console.log("[强势池] 点击自选 - 直接读取token:", tokenDirect ? "有值" : "空");
+      console.log("[强势池] 点击自选 - checkLoginStatus():", utils_auth.isLoggedIn());
       if (!utils_auth.isLoggedIn()) {
         common_vendor.index.showModal({
           title: "登录提示",

+ 0 - 71
dist/dev/mp-weixin/pages/transaction/transaction.js

@@ -1,71 +0,0 @@
-"use strict";
-const common_vendor = require("../../common/vendor.js");
-const utils_auth = require("../../utils/auth.js");
-const utils_api = require("../../utils/api.js");
-const _sfc_main = {
-  __name: "transaction",
-  setup(__props) {
-    const subscriptions = common_vendor.ref([]);
-    const loading = common_vendor.ref(false);
-    const isLoggedIn = common_vendor.ref(false);
-    const checkLogin = () => {
-      isLoggedIn.value = utils_auth.isLoggedIn();
-    };
-    const handleBack = () => {
-      const pages = getCurrentPages();
-      pages.length > 1 ? common_vendor.index.navigateBack() : common_vendor.index.switchTab({ url: "/pages/mine/mine" });
-    };
-    const loadSubscriptions = async () => {
-      if (!isLoggedIn.value)
-        return;
-      loading.value = true;
-      try {
-        const res = await utils_api.getUserSubscriptions();
-        if (res.code === 200) {
-          subscriptions.value = res.data || [];
-        }
-      } catch (e) {
-        console.error("加载订阅记录失败:", e);
-      } finally {
-        loading.value = false;
-      }
-    };
-    common_vendor.onLoad(() => checkLogin());
-    common_vendor.onMounted(() => {
-      if (isLoggedIn.value)
-        loadSubscriptions();
-    });
-    common_vendor.onShow(() => {
-      checkLogin();
-      if (isLoggedIn.value)
-        loadSubscriptions();
-    });
-    return (_ctx, _cache) => {
-      return common_vendor.e({
-        a: common_vendor.o(handleBack),
-        b: loading.value
-      }, loading.value ? {} : subscriptions.value.length === 0 ? {} : {
-        d: common_vendor.f(subscriptions.value, (item, index, i0) => {
-          return common_vendor.e({
-            a: common_vendor.t(item.poolType === 1 ? "⚡" : "📈"),
-            b: common_vendor.t(item.poolName),
-            c: common_vendor.t(item.isActive ? "生效中" : "已过期"),
-            d: common_vendor.n(item.isActive ? "active" : "expired"),
-            e: common_vendor.t(item.amount),
-            f: common_vendor.t(item.startTime),
-            g: common_vendor.t(item.expireTime),
-            h: item.isActive
-          }, item.isActive ? {
-            i: common_vendor.t(item.remainDays)
-          } : {}, {
-            j: index
-          });
-        })
-      }, {
-        c: subscriptions.value.length === 0
-      });
-    };
-  }
-};
-const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-a901d1cb"], ["__file", "D:/program/gupiao-wx/src/pages/transaction/transaction.vue"]]);
-wx.createPage(MiniProgramPage);

+ 0 - 4
dist/dev/mp-weixin/pages/transaction/transaction.json

@@ -1,4 +0,0 @@
-{
-  "navigationStyle": "custom",
-  "usingComponents": {}
-}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
dist/dev/mp-weixin/pages/transaction/transaction.wxml


+ 0 - 146
dist/dev/mp-weixin/pages/transaction/transaction.wxss

@@ -1,146 +0,0 @@
-
-.page-container.data-v-a901d1cb {
-  min-height: 100vh;
-  background: #f5f6fb;
-  display: flex;
-  flex-direction: column;
-}
-.custom-navbar.data-v-a901d1cb {
-  background: #ffffff;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 80rpx 32rpx 30rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
-  position: relative;
-}
-.navbar-back.data-v-a901d1cb {
-  width: 80rpx;
-  height: 60rpx;
-  display: flex;
-  align-items: center;
-}
-.back-icon.data-v-a901d1cb {
-  font-size: 40rpx;
-  color: #222222;
-  font-weight: bold;
-}
-.navbar-title.data-v-a901d1cb {
-  position: absolute;
-  left: 50%;
-  transform: translateX(-50%);
-}
-.title-text.data-v-a901d1cb {
-  font-size: 36rpx;
-  font-weight: 600;
-  color: #222222;
-}
-.navbar-placeholder.data-v-a901d1cb {
-  width: 80rpx;
-}
-.scroll-view.data-v-a901d1cb {
-  flex: 1;
-  height: 0;
-}
-.content-wrapper.data-v-a901d1cb {
-  padding: 32rpx;
-}
-.subscription-card.data-v-a901d1cb {
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 32rpx;
-  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
-}
-.loading-state.data-v-a901d1cb, .empty-state.data-v-a901d1cb {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 60rpx 40rpx;
-}
-.empty-icon.data-v-a901d1cb {
-  font-size: 80rpx;
-  margin-bottom: 20rpx;
-}
-.empty-text.data-v-a901d1cb {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333333;
-  margin-bottom: 8rpx;
-}
-.empty-desc.data-v-a901d1cb {
-  font-size: 24rpx;
-  color: #999999;
-}
-.subscription-list.data-v-a901d1cb {
-  display: flex;
-  flex-direction: column;
-  gap: 20rpx;
-}
-.subscription-item.data-v-a901d1cb {
-  background: #f9f9fb;
-  border-radius: 16rpx;
-  padding: 28rpx;
-}
-.item-header.data-v-a901d1cb {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20rpx;
-}
-.pool-info.data-v-a901d1cb {
-  display: flex;
-  align-items: center;
-}
-.pool-icon.data-v-a901d1cb {
-  font-size: 32rpx;
-  margin-right: 12rpx;
-}
-.pool-name.data-v-a901d1cb {
-  font-size: 30rpx;
-  font-weight: 700;
-  color: #222222;
-}
-.status-badge.data-v-a901d1cb {
-  padding: 8rpx 20rpx;
-  border-radius: 32rpx;
-  font-size: 24rpx;
-  font-weight: 500;
-}
-.status-badge.active.data-v-a901d1cb {
-  background: #e7f7ef;
-  color: #3abf81;
-}
-.status-badge.expired.data-v-a901d1cb {
-  background: #f5f5f5;
-  color: #999999;
-}
-.item-body.data-v-a901d1cb {
-  display: flex;
-  flex-direction: column;
-  gap: 14rpx;
-}
-.info-row.data-v-a901d1cb {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.info-label.data-v-a901d1cb {
-  font-size: 26rpx;
-  color: #9ca2b5;
-}
-.info-value.data-v-a901d1cb {
-  font-size: 26rpx;
-  color: #222222;
-  font-weight: 500;
-}
-.info-value.price.data-v-a901d1cb {
-  color: #f16565;
-  font-weight: 700;
-}
-.info-value.highlight.data-v-a901d1cb {
-  color: #5B5AEA;
-  font-weight: 700;
-}
-.bottom-safe-area.data-v-a901d1cb {
-  height: 80rpx;
-}

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

@@ -1,7 +1,13 @@
 "use strict";
 const common_vendor = require("../common/vendor.js");
-const DEV_BASE_URL = "http://localhost:8081";
-const BASE_URL = DEV_BASE_URL;
+const ENV = "dev";
+const CONFIG = {
+  dev: "http://localhost:8081",
+  // 开发环境(开发者工具)
+  prod: "http://192.168.1.2:8081"
+  // 生产环境
+};
+const BASE_URL = CONFIG[ENV];
 const getImageUrl = (url) => {
   if (!url)
     return "";
@@ -192,12 +198,6 @@ const getUserOrders = () => {
     method: "GET"
   });
 };
-const getUserSubscriptions = () => {
-  return request({
-    url: "/v1/order/subscriptions",
-    method: "GET"
-  });
-};
 const checkSubscription = (poolType) => {
   return request({
     url: "/v1/order/check-subscription",
@@ -246,7 +246,6 @@ exports.getSuggestions = getSuggestions;
 exports.getUserInfoApi = getUserInfoApi;
 exports.getUserOrders = getUserOrders;
 exports.getUserStocks = getUserStocks;
-exports.getUserSubscriptions = getUserSubscriptions;
 exports.mockPay = mockPay;
 exports.searchStocks = searchStocks;
 exports.updateUserProfile = updateUserProfile;

+ 0 - 6
src/pages.json

@@ -42,12 +42,6 @@
         "navigationStyle": "custom"
       }
     },
-    {
-      "path": "pages/transaction/transaction",
-      "style": {
-        "navigationStyle": "custom"
-      }
-    },
     {
       "path": "pages/order/order",
       "style": {

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

@@ -121,7 +121,7 @@
 import { ref, onUnmounted } from 'vue'
 import { onShow, onHide } from '@dcloudio/uni-app'
 import { refreshUserInfo } from '@/utils/auth.js'
-import { getSuggestions } from '@/utils/api.js'
+import { getSuggestions, BASE_URL } from '@/utils/api.js'
 
 const searchKeyword = ref('')
 const suggestions = ref([])
@@ -143,7 +143,7 @@ const request = (options) => {
     if (token) header['Authorization'] = `Bearer ${token}`
     
     uni.request({
-      url: `http://localhost:8081${options.url}`,
+      url: `${BASE_URL}${options.url}`,
       method: options.method || 'GET',
       data: options.data || {},
       header,

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

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

+ 0 - 24
src/pages/mine/mine.vue

@@ -19,14 +19,6 @@
 
         <!-- 菜单列表 -->
         <view class="menu-list">
-          <view class="menu-item" @click="handleSubscriptionRecord">
-            <view class="menu-left">
-              <text class="menu-icon">📝</text>
-              <text class="menu-label">我的订阅</text>
-            </view>
-            <text class="menu-arrow">›</text>
-          </view>
-          
           <view class="menu-item" @click="handleOrderRecord">
             <view class="menu-left">
               <text class="menu-icon">📋</text>
@@ -150,22 +142,6 @@ const handleLogout = () => {
   })
 }
 
-/**
- * 订阅记录
- */
-const handleSubscriptionRecord = () => {
-  if (!requireLogin(() => {
-    handleSubscriptionRecord()
-  })) {
-    return
-  }
-  
-  // 跳转到订阅记录页面
-  uni.navigateTo({
-    url: '/pages/transaction/transaction'
-  })
-}
-
 /**
  * 订单记录
  */

+ 242 - 96
src/pages/order/order.vue

@@ -3,7 +3,7 @@
     <!-- 顶部导航栏 -->
     <view class="custom-navbar">
       <view class="navbar-back" @click="handleBack">
-        <text class="back-icon"></text>
+        <text class="back-icon"></text>
       </view>
       <view class="navbar-title">
         <text class="title-text">我的订单</text>
@@ -13,39 +13,67 @@
 
     <scroll-view class="scroll-view" scroll-y>
       <view class="content-wrapper">
-        <view class="order-card">
+        <!-- 主卡片容器 -->
+        <view class="main-card">
           <!-- 加载中 -->
           <view v-if="loading" class="loading-state">
-            <text>加载中...</text>
+            <view class="loading-spinner"></view>
+            <text class="loading-text">加载中...</text>
           </view>
 
           <!-- 空状态 -->
           <view v-else-if="orders.length === 0" class="empty-state">
-            <text class="empty-icon">📋</text>
-            <text class="empty-text">暂无订单记录</text>
+            <view class="empty-icon-wrap">
+              <text class="empty-icon">📋</text>
+            </view>
+            <text class="empty-title">暂无订单</text>
+            <text class="empty-desc">您还没有任何订单记录</text>
           </view>
 
           <!-- 订单列表 -->
           <view v-else class="order-list">
             <view 
-              v-for="order in orders" 
+              v-for="(order, index) in orders" 
               :key="order.orderNo"
               class="order-item"
+              :class="{ 'no-border': index === orders.length - 1 }"
             >
-              <view class="order-header">
-                <text class="order-no">{{ order.orderNo }}</text>
-                <view class="order-status" :class="getStatusClass(order.orderStatus)">
+              <!-- 顶部:标签 + 状态 -->
+              <view class="order-top">
+                <view class="pool-tag" :class="order.poolType === 1 ? 'tag-orange' : 'tag-purple'">
+                  <text class="tag-icon">{{ order.poolType === 1 ? '⚡' : '📈' }}</text>
+                  <text class="tag-text">{{ order.poolName || (order.poolType === 1 ? '超短池' : '强势池') }}</text>
+                </view>
+                <view class="status-badge" :class="getStatusClass(order.orderStatus)">
                   <text>{{ order.orderStatusName }}</text>
                 </view>
               </view>
-              <view class="order-body">
-                <text class="order-plan">{{ order.planName }}</text>
-                <text class="order-amount">¥{{ order.amount }}</text>
+
+              <!-- 中间:订单号 + 价格 -->
+              <view class="order-middle">
+                <view class="order-info">
+                  <text class="info-label">订单号</text>
+                  <text class="order-no">{{ order.orderNo }}</text>
+                </view>
+                <text class="order-price">¥{{ order.amount }}</text>
               </view>
-              <view class="order-footer">
-                <text class="order-time">{{ order.createTime }}</text>
-                <view v-if="order.orderStatus === 0" class="pay-btn" @click="handlePay(order)">
-                  <text>去支付</text>
+
+              <!-- 底部:时间信息 -->
+              <view class="order-bottom">
+                <view v-if="order.expireTime" class="time-row">
+                  <text class="time-label">到期时间</text>
+                  <text class="time-value highlight">{{ order.expireTime }}</text>
+                </view>
+                <view class="time-row">
+                  <text class="time-label">创建时间</text>
+                  <text class="time-value">{{ order.createTime }}</text>
+                </view>
+              </view>
+
+              <!-- 待支付按钮 -->
+              <view v-if="order.orderStatus === 0" class="order-action">
+                <view class="pay-btn" @click="handlePay(order)">
+                  <text>立即支付</text>
                 </view>
               </view>
             </view>
@@ -77,7 +105,6 @@ const handleBack = () => {
   pages.length > 1 ? uni.navigateBack() : uni.switchTab({ url: '/pages/mine/mine' })
 }
 
-// 加载订单列表
 const loadOrders = async () => {
   if (!isLoggedIn.value) return
   loading.value = true
@@ -94,7 +121,6 @@ const loadOrders = async () => {
   }
 }
 
-// 去支付
 const handlePay = async (order) => {
   try {
     uni.showLoading({ title: '正在支付...' })
@@ -114,13 +140,12 @@ const handlePay = async (order) => {
   }
 }
 
-// 获取订单状态样式类
 const getStatusClass = (status) => {
   switch (status) {
-    case 0: return 'pending'
-    case 1: return 'paid'
-    case 2: return 'cancelled'
-    default: return 'closed'
+    case 0: return 'status-pending'
+    case 1: return 'status-paid'
+    case 2: return 'status-cancelled'
+    default: return 'status-closed'
   }
 }
 
@@ -137,32 +162,37 @@ onShow(() => {
 <style scoped>
 .page-container {
   min-height: 100vh;
-  background: #f5f6fb;
+  background: #F0F1F5;
   display: flex;
   flex-direction: column;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif;
 }
 
+/* 导航栏 */
 .custom-navbar {
-  background: #ffffff;
+  background: #FFFFFF;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 80rpx 32rpx 30rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+  padding: 88rpx 28rpx 28rpx;
   position: relative;
 }
 
 .navbar-back {
-  width: 80rpx;
-  height: 60rpx;
+  width: 68rpx;
+  height: 68rpx;
+  background: #F5F6F8;
+  border-radius: 50%;
   display: flex;
   align-items: center;
+  justify-content: center;
 }
 
 .back-icon {
-  font-size: 40rpx;
-  color: #222222;
-  font-weight: bold;
+  font-size: 48rpx;
+  color: #1F2937;
+  font-weight: 300;
+  margin-top: -4rpx;
 }
 
 .navbar-title {
@@ -172,143 +202,259 @@ onShow(() => {
 }
 
 .title-text {
-  font-size: 36rpx;
+  font-size: 34rpx;
   font-weight: 600;
-  color: #222222;
+  color: #1F2937;
 }
 
 .navbar-placeholder {
-  width: 80rpx;
+  width: 68rpx;
 }
 
+/* 滚动区域 */
 .scroll-view {
   flex: 1;
   height: 0;
 }
 
 .content-wrapper {
-  padding: 32rpx;
+  padding: 28rpx;
 }
 
-.order-card {
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 32rpx;
-  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+/* 主卡片 - 弥散阴影 */
+.main-card {
+  background: #FFFFFF;
+  border-radius: 32rpx;
+  padding: 8rpx;
+  box-shadow: 
+    0 2rpx 8rpx rgba(0, 0, 0, 0.02),
+    0 8rpx 24rpx rgba(0, 0, 0, 0.04),
+    0 24rpx 48rpx rgba(0, 0, 0, 0.04);
 }
 
-.loading-state, .empty-state {
+/* 加载状态 */
+.loading-state {
   display: flex;
   flex-direction: column;
   align-items: center;
-  padding: 60rpx 40rpx;
+  padding: 100rpx 40rpx;
 }
 
-.empty-icon {
-  font-size: 80rpx;
+.loading-spinner {
+  width: 44rpx;
+  height: 44rpx;
+  border: 3rpx solid #E5E7EB;
+  border-top-color: #6366F1;
+  border-radius: 50%;
+  animation: spin 0.8s linear infinite;
   margin-bottom: 20rpx;
 }
 
-.empty-text {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333333;
+@keyframes spin {
+  to { transform: rotate(360deg); }
 }
 
-.order-list {
+.loading-text {
+  font-size: 26rpx;
+  color: #9CA3AF;
+}
+
+/* 空状态 */
+.empty-state {
   display: flex;
   flex-direction: column;
-  gap: 16rpx;
+  align-items: center;
+  padding: 100rpx 40rpx;
+}
+
+.empty-icon-wrap {
+  width: 120rpx;
+  height: 120rpx;
+  background: #F9FAFB;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 24rpx;
+}
+
+.empty-icon {
+  font-size: 52rpx;
+}
+
+.empty-title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #1F2937;
+  margin-bottom: 8rpx;
+}
+
+.empty-desc {
+  font-size: 26rpx;
+  color: #9CA3AF;
+}
+
+/* 订单列表 */
+.order-list {
+  padding: 16rpx;
 }
 
 .order-item {
-  background: #f9f9fb;
-  border-radius: 12rpx;
-  padding: 24rpx;
+  padding: 28rpx 0;
+  border-bottom: 1rpx solid #F3F4F6;
+}
+
+.order-item.no-border {
+  border-bottom: none;
 }
 
-.order-header {
+/* 顶部:标签 + 状态 */
+.order-top {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 16rpx;
+  margin-bottom: 20rpx;
 }
 
-.order-no {
-  font-size: 22rpx;
-  color: #999;
-  font-family: monospace;
+.pool-tag {
+  display: flex;
+  align-items: center;
+  padding: 10rpx 18rpx;
+  border-radius: 12rpx;
+  gap: 8rpx;
 }
 
-.order-status {
-  padding: 4rpx 16rpx;
-  border-radius: 20rpx;
+.pool-tag.tag-purple {
+  background: rgba(139, 92, 246, 0.1);
+}
+
+.pool-tag.tag-orange {
+  background: rgba(251, 146, 60, 0.12);
+}
+
+.tag-icon {
+  font-size: 26rpx;
+}
+
+.tag-text {
+  font-size: 26rpx;
+  font-weight: 600;
+  color: #374151;
+}
+
+.status-badge {
+  padding: 8rpx 20rpx;
+  border-radius: 32rpx;
   font-size: 22rpx;
+  font-weight: 500;
 }
 
-.order-status.pending {
-  background: #fff3e0;
-  color: #ff9800;
+.status-badge.status-paid {
+  background: rgba(34, 197, 94, 0.12);
+  color: #16A34A;
 }
 
-.order-status.paid {
-  background: #e8f5e9;
-  color: #4caf50;
+.status-badge.status-pending {
+  background: rgba(251, 191, 36, 0.15);
+  color: #D97706;
 }
 
-.order-status.cancelled {
-  background: #f5f5f5;
-  color: #999;
+.status-badge.status-cancelled {
+  background: #F3F4F6;
+  color: #9CA3AF;
 }
 
-.order-status.closed {
-  background: #ffebee;
-  color: #f44336;
+.status-badge.status-closed {
+  background: rgba(239, 68, 68, 0.1);
+  color: #DC2626;
 }
 
-.order-body {
+/* 中间:订单号 + 价格 */
+.order-middle {
   display: flex;
   justify-content: space-between;
-  align-items: center;
-  margin-bottom: 12rpx;
+  align-items: flex-start;
+  margin-bottom: 16rpx;
 }
 
-.order-plan {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333;
+.order-info {
+  display: flex;
+  flex-direction: column;
+  gap: 4rpx;
 }
 
-.order-amount {
-  font-size: 30rpx;
+.info-label {
+  font-size: 22rpx;
+  color: #9CA3AF;
+}
+
+.order-no {
+  font-size: 24rpx;
+  color: #6B7280;
+  font-family: 'SF Mono', Monaco, Consolas, monospace;
+}
+
+.order-price {
+  font-size: 40rpx;
   font-weight: 700;
-  color: #f16565;
+  color: #EF4444;
 }
 
-.order-footer {
+/* 底部:时间信息 */
+.order-bottom {
+  display: flex;
+  flex-direction: column;
+  gap: 8rpx;
+}
+
+.time-row {
   display: flex;
-  justify-content: space-between;
   align-items: center;
+  gap: 12rpx;
 }
 
-.order-time {
+.time-label {
   font-size: 22rpx;
-  color: #999;
+  color: #9CA3AF;
+  min-width: 100rpx;
+}
+
+.time-value {
+  font-size: 24rpx;
+  color: #6B7280;
+}
+
+.time-value.highlight {
+  color: #3B82F6;
+  font-weight: 500;
+}
+
+/* 支付按钮 */
+.order-action {
+  margin-top: 20rpx;
+  display: flex;
+  justify-content: flex-end;
 }
 
 .pay-btn {
-  padding: 10rpx 28rpx;
-  background: #ff9800;
-  border-radius: 24rpx;
+  background: linear-gradient(135deg, #6366F1, #8B5CF6);
+  padding: 16rpx 36rpx;
+  border-radius: 32rpx;
+  box-shadow: 0 6rpx 20rpx rgba(99, 102, 241, 0.3);
 }
 
 .pay-btn text {
-  font-size: 24rpx;
-  color: #fff;
-  font-weight: 500;
+  font-size: 26rpx;
+  color: #FFFFFF;
+  font-weight: 600;
+}
+
+.pay-btn:active {
+  opacity: 0.9;
+  transform: scale(0.98);
 }
 
 .bottom-safe-area {
-  height: 80rpx;
+  height: 60rpx;
 }
 </style>

+ 169 - 77
src/pages/rank/rank.vue

@@ -70,10 +70,10 @@
                           <text class="stock-code">{{ stock.code }}</text>
                         </view>
                       </view>
-                      <!-- 趋势图 -->
+                      <!-- 趋势图 - 使用 canvas 2d 模式解决滚动分离问题 -->
                       <view class="stock-chart">
                         <canvas 
-                          :canvas-id="'chart-' + stock.code" 
+                          type="2d"
                           :id="'chart-' + stock.code"
                           class="trend-canvas"
                         ></canvas>
@@ -247,6 +247,14 @@ const clearAllSlideTimers = () => {
 // 设置视图模式
 const setViewMode = (mode) => {
   viewMode.value = mode
+  
+  // 切换到热力图视图时,先清空再重绘趋势图
+  if (mode === 'list' && myStocks.value.length > 0) {
+    nextTick(() => {
+      clearAllCanvases()
+      drawAllTrendCharts()
+    })
+  }
 }
 
 const indexData = ref({
@@ -317,10 +325,46 @@ const getMarketClass = (code) => {
   return 'market-sh'
 }
 
-// 绘制单个股票的趋势图
+// 获取设备像素比(使用新API替代废弃的getSystemInfoSync)
+const getDevicePixelRatio = () => {
+  try {
+    const windowInfo = wx.getWindowInfo()
+    return windowInfo.pixelRatio || 2
+  } catch (e) {
+    return 2 // 默认值
+  }
+}
+
+// 清空单个 canvas
+const clearCanvas = (canvasId) => {
+  const query = uni.createSelectorQuery()
+  query.select('#' + canvasId)
+    .fields({ node: true, size: true })
+    .exec((res) => {
+      if (!res || !res[0] || !res[0].node) return
+      
+      const canvas = res[0].node
+      const ctx = canvas.getContext('2d')
+      const dpr = getDevicePixelRatio()
+      const width = res[0].width
+      const height = res[0].height
+      
+      canvas.width = width * dpr
+      canvas.height = height * dpr
+      ctx.setTransform(1, 0, 0, 1, 0, 0)
+      ctx.clearRect(0, 0, width * dpr, height * dpr)
+    })
+}
+
+// 清空所有趋势图 canvas
+const clearAllCanvases = () => {
+  myStocks.value.forEach(stock => {
+    clearCanvas('chart-' + stock.code)
+  })
+}
+
+// 绘制单个股票的趋势图 (使用 Canvas 2D API)
 const drawTrendChart = (stock) => {
-  if (!componentInstance) return
-  
   const canvasId = 'chart-' + stock.code
   let trendData = stock.trendData
   
@@ -329,72 +373,95 @@ const drawTrendChart = (stock) => {
     trendData = generateMockTrendData(stock.changePercent)
   }
   
-  const ctx = uni.createCanvasContext(canvasId, componentInstance)
-  
-  // 趋势图尺寸(与 CSS 中 trend-canvas 对应)
-  const width = 120
-  const height = 28
-  const padding = 1
-  
-  const maxValue = Math.max(...trendData)
-  const minValue = Math.min(...trendData)
-  const dataRange = maxValue - minValue
-  const avgValue = (maxValue + minValue) / 2
-  const minRange = avgValue * 0.03 || 1
-  const range = Math.max(dataRange, minRange)
-  
-  const baseValue = trendData[0]
-  const baseY = height - padding - ((baseValue - minValue) / range) * (height - padding * 2)
-  
-  const changePercent = parseFloat(String(stock.changePercent || '0').replace('%', '').replace('+', ''))
-  const isUp = changePercent >= 0
-  const lineColor = isUp ? '#ef4444' : '#22c55e'
-  const fillColor = isUp ? 'rgba(239, 68, 68, 0.15)' : 'rgba(34, 197, 94, 0.15)'
-  
-  // 绘制基准虚线
-  ctx.beginPath()
-  ctx.setStrokeStyle('#e5e7eb')
-  ctx.setLineWidth(0.5)
-  ctx.setLineDash([2, 2], 0)
-  ctx.moveTo(padding, baseY)
-  ctx.lineTo(width - padding, baseY)
-  ctx.stroke()
-  ctx.setLineDash([], 0)
-  
-  // 绘制填充区域
-  ctx.beginPath()
-  ctx.moveTo(padding, baseY)
-  
-  trendData.forEach((value, index) => {
-    const x = padding + (index / (trendData.length - 1)) * (width - padding * 2)
-    const y = height - padding - ((value - minValue) / range) * (height - padding * 2)
-    ctx.lineTo(x, y)
-  })
-  
-  ctx.lineTo(width - padding, baseY)
-  ctx.closePath()
-  ctx.setFillStyle(fillColor)
-  ctx.fill()
-  
-  // 绘制折线
-  ctx.beginPath()
-  
-  trendData.forEach((value, index) => {
-    const x = padding + (index / (trendData.length - 1)) * (width - padding * 2)
-    const y = height - padding - ((value - minValue) / range) * (height - padding * 2)
-    
-    if (index === 0) {
-      ctx.moveTo(x, y)
-    } else {
-      ctx.lineTo(x, y)
-    }
-  })
-  
-  ctx.setStrokeStyle(lineColor)
-  ctx.setLineWidth(1.5)
-  ctx.stroke()
-  
-  ctx.draw()
+  // 使用 Canvas 2D 模式
+  const query = uni.createSelectorQuery()
+  query.select('#' + canvasId)
+    .fields({ node: true, size: true })
+    .exec((res) => {
+      if (!res || !res[0] || !res[0].node) {
+        console.warn('[趋势图] 获取canvas节点失败:', canvasId)
+        return
+      }
+      
+      const canvas = res[0].node
+      const ctx = canvas.getContext('2d')
+      const dpr = getDevicePixelRatio()
+      
+      // 设置 canvas 实际尺寸
+      const width = res[0].width
+      const height = res[0].height
+      
+      // 重置 canvas 尺寸(强制清除之前的绘制状态,解决页面切换后显示异常)
+      canvas.width = width * dpr
+      canvas.height = height * dpr
+      
+      // 重置变换矩阵后再设置缩放
+      ctx.setTransform(1, 0, 0, 1, 0, 0)
+      ctx.scale(dpr, dpr)
+      
+      const padding = 1
+      
+      const maxValue = Math.max(...trendData)
+      const minValue = Math.min(...trendData)
+      const dataRange = maxValue - minValue
+      const avgValue = (maxValue + minValue) / 2
+      const minRange = avgValue * 0.03 || 1
+      const range = Math.max(dataRange, minRange)
+      
+      const baseValue = trendData[0]
+      const baseY = height - padding - ((baseValue - minValue) / range) * (height - padding * 2)
+      
+      const changePercent = parseFloat(String(stock.changePercent || '0').replace('%', '').replace('+', ''))
+      const isUp = changePercent >= 0
+      const lineColor = isUp ? '#ef4444' : '#22c55e'
+      const fillColor = isUp ? 'rgba(239, 68, 68, 0.15)' : 'rgba(34, 197, 94, 0.15)'
+      
+      // 清除画布
+      ctx.clearRect(0, 0, width, height)
+      
+      // 绘制基准虚线
+      ctx.beginPath()
+      ctx.strokeStyle = '#e5e7eb'
+      ctx.lineWidth = 0.5
+      ctx.setLineDash([2, 2])
+      ctx.moveTo(padding, baseY)
+      ctx.lineTo(width - padding, baseY)
+      ctx.stroke()
+      ctx.setLineDash([])
+      
+      // 绘制填充区域
+      ctx.beginPath()
+      ctx.moveTo(padding, baseY)
+      
+      trendData.forEach((value, index) => {
+        const x = padding + (index / (trendData.length - 1)) * (width - padding * 2)
+        const y = height - padding - ((value - minValue) / range) * (height - padding * 2)
+        ctx.lineTo(x, y)
+      })
+      
+      ctx.lineTo(width - padding, baseY)
+      ctx.closePath()
+      ctx.fillStyle = fillColor
+      ctx.fill()
+      
+      // 绘制折线
+      ctx.beginPath()
+      
+      trendData.forEach((value, index) => {
+        const x = padding + (index / (trendData.length - 1)) * (width - padding * 2)
+        const y = height - padding - ((value - minValue) / range) * (height - padding * 2)
+        
+        if (index === 0) {
+          ctx.moveTo(x, y)
+        } else {
+          ctx.lineTo(x, y)
+        }
+      })
+      
+      ctx.strokeStyle = lineColor
+      ctx.lineWidth = 1.5
+      ctx.stroke()
+    })
 }
 
 // 生成模拟趋势数据
@@ -420,12 +487,22 @@ const generateMockTrendData = (changePercent) => {
 const drawAllTrendCharts = () => {
   if (myStocks.value.length === 0) return
   
+  // 确保页面可见时才绘制
+  if (!isPageVisible.value) return
+  
   nextTick(() => {
+    // 增加延迟确保 canvas 节点完全渲染(解决页面切换后 canvas 上下文丢失问题)
     setTimeout(() => {
-      myStocks.value.forEach(stock => {
-        drawTrendChart(stock)
+      if (!isPageVisible.value) return
+      
+      myStocks.value.forEach((stock, index) => {
+        setTimeout(() => {
+          if (isPageVisible.value) {
+            drawTrendChart(stock)
+          }
+        }, index * 30)
       })
-    }, 300)
+    }, 350)
   })
 }
 
@@ -452,6 +529,8 @@ const loadMyStocks = async (forceRefresh = false) => {
     // 只刷新行情数据
     await fetchIndexData()
     await refreshAllQuotes()
+    // 重新绘制趋势图(解决页面切换回来后canvas显示异常的问题)
+    drawAllTrendCharts()
     startAutoRefresh()
     return
   }
@@ -460,8 +539,10 @@ const loadMyStocks = async (forceRefresh = false) => {
     isLoading.value = true
     
     // 只有首次加载或强制刷新时显示loading
+    let showedLoading = false
     if (myStocks.value.length === 0 || forceRefresh) {
       uni.showLoading({ title: '加载中...' })
+      showedLoading = true
     }
     
     // 从服务器获取用户自选股票
@@ -469,7 +550,9 @@ const loadMyStocks = async (forceRefresh = false) => {
     const res = await getUserStocks()
     console.log('[我的股票] 服务器返回:', JSON.stringify(res))
     
-    uni.hideLoading()
+    if (showedLoading) {
+      uni.hideLoading()
+    }
     
     if (res.code === 200 && res.data) {
       // 转换数据格式
@@ -504,7 +587,8 @@ const loadMyStocks = async (forceRefresh = false) => {
     // 登录后启动定时刷新
     startAutoRefresh()
   } catch (e) {
-    uni.hideLoading()
+    // 安全地隐藏loading
+    try { uni.hideLoading() } catch (err) {}
     console.error('[我的股票] 加载失败:', e)
     // 加载失败时不清空已有数据
     if (myStocks.value.length === 0) {
@@ -564,7 +648,7 @@ const startAutoRefresh = () => {
       return
     }
     
-    const delay = 3000 + Math.random() * 1000
+    const delay = 10000 // 10秒刷新一次
     refreshTimer = setTimeout(async () => {
       // 再次检查页面是否可见
       if (!isPageVisible.value) {
@@ -575,6 +659,8 @@ const startAutoRefresh = () => {
       await fetchIndexData()
       if (myStocks.value.length > 0) {
         await refreshAllQuotes()
+        // 刷新行情后重绘趋势图
+        drawAllTrendCharts()
       }
       scheduleNextRefresh()
     }, delay)
@@ -644,6 +730,12 @@ onLoad(() => {
 onShow(() => {
   console.log('[我的股票] onShow 触发')
   isPageVisible.value = true
+  
+  // 立即清空所有 canvas,避免显示异常的长条
+  if (myStocks.value.length > 0) {
+    clearAllCanvases()
+  }
+  
   const wasLoggedIn = isLoggedIn.value
   isLoggedIn.value = checkLoginStatus()
   

+ 5 - 0
src/pages/strong/strong.vue

@@ -250,6 +250,11 @@ const onHistorySearch = ({ startMonth, endMonth }) => {
 
 // 添加到我的股票
 const addToMyStocks = async (stock) => {
+  // 调试:直接读取storage检查
+  const tokenDirect = uni.getStorageSync('user_token')
+  console.log('[强势池] 点击自选 - 直接读取token:', tokenDirect ? '有值' : '空')
+  console.log('[强势池] 点击自选 - checkLoginStatus():', checkLoginStatus())
+  
   if (!checkLoginStatus()) {
     uni.showModal({
       title: '登录提示',

+ 0 - 295
src/pages/transaction/transaction.vue

@@ -1,295 +0,0 @@
-<template>
-  <view class="page-container">
-    <!-- 顶部导航栏 -->
-    <view class="custom-navbar">
-      <view class="navbar-back" @click="handleBack">
-        <text class="back-icon">←</text>
-      </view>
-      <view class="navbar-title">
-        <text class="title-text">我的订阅</text>
-      </view>
-      <view class="navbar-placeholder"></view>
-    </view>
-
-    <scroll-view class="scroll-view" scroll-y>
-      <view class="content-wrapper">
-        <view class="subscription-card">
-          <!-- 加载中 -->
-          <view v-if="loading" class="loading-state">
-            <text>加载中...</text>
-          </view>
-
-          <!-- 空状态 -->
-          <view v-else-if="subscriptions.length === 0" class="empty-state">
-            <text class="empty-icon">📋</text>
-            <text class="empty-text">暂无订阅记录</text>
-            <text class="empty-desc">前往超短池或强势池订阅服务</text>
-          </view>
-
-          <!-- 订阅列表 -->
-          <view v-else class="subscription-list">
-            <view 
-              v-for="(item, index) in subscriptions" 
-              :key="index"
-              class="subscription-item"
-            >
-              <view class="item-header">
-                <view class="pool-info">
-                  <text class="pool-icon">{{ item.poolType === 1 ? '⚡' : '📈' }}</text>
-                  <text class="pool-name">{{ item.poolName }}</text>
-                </view>
-                <view class="status-badge" :class="item.isActive ? 'active' : 'expired'">
-                  <text>{{ item.isActive ? '生效中' : '已过期' }}</text>
-                </view>
-              </view>
-              
-              <view class="item-body">
-                <view class="info-row">
-                  <text class="info-label">订阅金额</text>
-                  <text class="info-value price">¥{{ item.amount }}</text>
-                </view>
-                <view class="info-row">
-                  <text class="info-label">开始时间</text>
-                  <text class="info-value">{{ item.startTime }}</text>
-                </view>
-                <view class="info-row">
-                  <text class="info-label">到期时间</text>
-                  <text class="info-value">{{ item.expireTime }}</text>
-                </view>
-                <view v-if="item.isActive" class="info-row">
-                  <text class="info-label">剩余天数</text>
-                  <text class="info-value highlight">{{ item.remainDays }}天</text>
-                </view>
-              </view>
-            </view>
-          </view>
-        </view>
-
-        <view class="bottom-safe-area"></view>
-      </view>
-    </scroll-view>
-  </view>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-import { onLoad, onShow } from '@dcloudio/uni-app'
-import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
-import { getUserSubscriptions } from '../../utils/api.js'
-
-const subscriptions = ref([])
-const loading = ref(false)
-const isLoggedIn = ref(false)
-
-const checkLogin = () => {
-  isLoggedIn.value = checkLoginStatus()
-}
-
-const handleBack = () => {
-  const pages = getCurrentPages()
-  pages.length > 1 ? uni.navigateBack() : uni.switchTab({ url: '/pages/mine/mine' })
-}
-
-// 加载订阅列表
-const loadSubscriptions = async () => {
-  if (!isLoggedIn.value) return
-  loading.value = true
-  
-  try {
-    const res = await getUserSubscriptions()
-    if (res.code === 200) {
-      subscriptions.value = res.data || []
-    }
-  } catch (e) {
-    console.error('加载订阅记录失败:', e)
-  } finally {
-    loading.value = false
-  }
-}
-
-onLoad(() => checkLogin())
-onMounted(() => {
-  if (isLoggedIn.value) loadSubscriptions()
-})
-onShow(() => {
-  checkLogin()
-  if (isLoggedIn.value) loadSubscriptions()
-})
-</script>
-
-<style scoped>
-.page-container {
-  min-height: 100vh;
-  background: #f5f6fb;
-  display: flex;
-  flex-direction: column;
-}
-
-.custom-navbar {
-  background: #ffffff;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 80rpx 32rpx 30rpx;
-  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
-  position: relative;
-}
-
-.navbar-back {
-  width: 80rpx;
-  height: 60rpx;
-  display: flex;
-  align-items: center;
-}
-
-.back-icon {
-  font-size: 40rpx;
-  color: #222222;
-  font-weight: bold;
-}
-
-.navbar-title {
-  position: absolute;
-  left: 50%;
-  transform: translateX(-50%);
-}
-
-.title-text {
-  font-size: 36rpx;
-  font-weight: 600;
-  color: #222222;
-}
-
-.navbar-placeholder {
-  width: 80rpx;
-}
-
-.scroll-view {
-  flex: 1;
-  height: 0;
-}
-
-.content-wrapper {
-  padding: 32rpx;
-}
-
-.subscription-card {
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 32rpx;
-  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
-}
-
-.loading-state, .empty-state {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 60rpx 40rpx;
-}
-
-.empty-icon {
-  font-size: 80rpx;
-  margin-bottom: 20rpx;
-}
-
-.empty-text {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333333;
-  margin-bottom: 8rpx;
-}
-
-.empty-desc {
-  font-size: 24rpx;
-  color: #999999;
-}
-
-.subscription-list {
-  display: flex;
-  flex-direction: column;
-  gap: 20rpx;
-}
-
-.subscription-item {
-  background: #f9f9fb;
-  border-radius: 16rpx;
-  padding: 28rpx;
-}
-
-.item-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20rpx;
-}
-
-.pool-info {
-  display: flex;
-  align-items: center;
-}
-
-.pool-icon {
-  font-size: 32rpx;
-  margin-right: 12rpx;
-}
-
-.pool-name {
-  font-size: 30rpx;
-  font-weight: 700;
-  color: #222222;
-}
-
-.status-badge {
-  padding: 8rpx 20rpx;
-  border-radius: 32rpx;
-  font-size: 24rpx;
-  font-weight: 500;
-}
-
-.status-badge.active {
-  background: #e7f7ef;
-  color: #3abf81;
-}
-
-.status-badge.expired {
-  background: #f5f5f5;
-  color: #999999;
-}
-
-.item-body {
-  display: flex;
-  flex-direction: column;
-  gap: 14rpx;
-}
-
-.info-row {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.info-label {
-  font-size: 26rpx;
-  color: #9ca2b5;
-}
-
-.info-value {
-  font-size: 26rpx;
-  color: #222222;
-  font-weight: 500;
-}
-
-.info-value.price {
-  color: #f16565;
-  font-weight: 700;
-}
-
-.info-value.highlight {
-  color: #5B5AEA;
-  font-weight: 700;
-}
-
-.bottom-safe-area {
-  height: 80rpx;
-}
-</style>

+ 12 - 23
src/utils/api.js

@@ -2,20 +2,20 @@
  * API请求工具类
  */
 
-// 开发环境配置
-// 真机调试时,请将 DEV_BASE_URL 改为你电脑的局域网IP地址
-// 例如:const DEV_BASE_URL = 'http://192.168.1.3:8080'
-// 可以通过命令 ipconfig (Windows) 或 ifconfig (Mac/Linux) 查看IP地址
-// const DEV_BASE_URL = 'http://192.168.1.3:8080'
-// const DEV_BASE_URL = 'http://192.168.1.3:8081'
-const DEV_BASE_URL = 'http://localhost:8081'
+// ============ 配置区域 - 请根据实际情况修改 ============
+// 开发环境:微信开发者工具中使用 localhost
+// 真机调试:改为你电脑的局域网IP(通过 ipconfig 或 ifconfig 查看)
+// 生产环境:改为正式服务器地址
 
-// 生产环境配置(部署后的服务器地址)
-const PROD_BASE_URL = 'https://your-domain.com'
+const ENV = 'dev' // 'dev' | 'prod'
 
-// 根据环境自动选择BASE_URL
-// 在微信开发者工具中使用 localhost,真机调试使用局域网IP
-const BASE_URL = DEV_BASE_URL
+const CONFIG = {
+  dev: 'http://localhost:8081',      // 开发环境(开发者工具)
+  prod: 'http://192.168.1.2:8081'    // 生产环境
+}
+
+const BASE_URL = CONFIG[ENV]
+// ============ 配置区域结束 ============
 
 /**
  * 获取完整的图片URL
@@ -364,17 +364,6 @@ export const getUserOrders = () => {
   })
 }
 
-/**
- * 获取用户订阅列表
- * @returns {Promise} 返回订阅列表
- */
-export const getUserSubscriptions = () => {
-  return request({
-    url: '/v1/order/subscriptions',
-    method: 'GET'
-  })
-}
-
 /**
  * 检查用户是否有有效订阅
  * @param {number} poolType - 池类型:1-超短池,2-强势池

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio