Browse Source

超短池管理修正

Zhangbw 3 months ago
parent
commit
0b0e5e911c

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

@@ -10,6 +10,7 @@ if (!Math) {
   "./pages/mine/mine.js";
   "./pages/profile/edit.js";
   "./pages/transaction/transaction.js";
+  "./pages/admin/shortPool.js";
 }
 const _sfc_main = {
   globalData: {

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

@@ -7,7 +7,8 @@
     "pages/rank/rank",
     "pages/mine/mine",
     "pages/profile/edit",
-    "pages/transaction/transaction"
+    "pages/transaction/transaction",
+    "pages/admin/shortPool"
   ],
   "window": {
     "navigationBarBackgroundColor": "#ffffff",

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

@@ -0,0 +1,210 @@
+"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: "shortPool",
+  setup(__props) {
+    const searchKeyword = common_vendor.ref("");
+    const suggestions = common_vendor.ref([]);
+    const showSuggestions = common_vendor.ref(false);
+    const searching = common_vendor.ref(false);
+    const stockList = common_vendor.ref([]);
+    const loading = common_vendor.ref(false);
+    const poolType = 1;
+    let searchTimer = null;
+    let refreshTimer = null;
+    const getToken = () => common_vendor.index.getStorageSync("user_token") || null;
+    const request = (options) => {
+      return new Promise((resolve, reject) => {
+        const token = getToken();
+        const header = options.header || {};
+        if (token)
+          header["Authorization"] = `Bearer ${token}`;
+        common_vendor.index.request({
+          url: `http://localhost:8081${options.url}`,
+          method: options.method || "GET",
+          data: options.data || {},
+          header,
+          success: (res) => {
+            var _a;
+            return res.statusCode === 200 ? resolve(res.data) : reject(new Error(((_a = res.data) == null ? void 0 : _a.message) || "请求失败"));
+          },
+          fail: () => reject(new Error("网络异常"))
+        });
+      });
+    };
+    const handleBack = () => {
+      const pages = getCurrentPages();
+      pages.length > 1 ? common_vendor.index.navigateBack() : common_vendor.index.switchTab({ url: "/pages/mine/mine" });
+    };
+    common_vendor.onShow(() => {
+      checkAdminPermission();
+      loadStockList();
+      startAutoRefresh();
+    });
+    common_vendor.onHide(() => stopAutoRefresh());
+    common_vendor.onUnmounted(() => stopAutoRefresh());
+    const startAutoRefresh = () => {
+      stopAutoRefresh();
+      refreshTimer = setInterval(() => loadStockList(true), 5e3);
+    };
+    const stopAutoRefresh = () => {
+      if (refreshTimer) {
+        clearInterval(refreshTimer);
+        refreshTimer = null;
+      }
+    };
+    const checkAdminPermission = () => {
+      const userInfo = utils_auth.getUserInfo();
+      if (!userInfo || userInfo.status !== 2) {
+        common_vendor.index.showToast({ title: "无权限访问", icon: "none" });
+        setTimeout(() => common_vendor.index.navigateBack(), 1500);
+      }
+    };
+    const loadStockList = async (silent = false) => {
+      if (!silent)
+        loading.value = true;
+      try {
+        const res = await request({ url: "/v1/stock/pool/admin/list", data: { poolType } });
+        if (res.code === 200)
+          stockList.value = res.data || [];
+      } catch (e) {
+        console.error("加载失败", e);
+      } finally {
+        if (!silent)
+          loading.value = false;
+      }
+    };
+    const getChangeClass = (changePercent) => {
+      if (!changePercent || changePercent === "-")
+        return "";
+      return changePercent.startsWith("+") ? "up" : changePercent.startsWith("-") ? "down" : "";
+    };
+    const handleSearchInput = () => {
+      if (searchTimer)
+        clearTimeout(searchTimer);
+      if (!searchKeyword.value.trim()) {
+        suggestions.value = [];
+        showSuggestions.value = false;
+        return;
+      }
+      searching.value = true;
+      showSuggestions.value = true;
+      searchTimer = setTimeout(async () => {
+        try {
+          const res = await utils_api.getSuggestions(searchKeyword.value.trim());
+          suggestions.value = (res.code === 0 || res.code === 200) && res.data ? res.data : [];
+        } catch (e) {
+          suggestions.value = [];
+        } finally {
+          searching.value = false;
+        }
+      }, 300);
+    };
+    const clearSearch = () => {
+      searchKeyword.value = "";
+      suggestions.value = [];
+      showSuggestions.value = false;
+    };
+    const closeSuggestions = () => showSuggestions.value = false;
+    const handleAddFromSuggestion = async (item) => {
+      if (stockList.value.some((s) => s.code === item.code)) {
+        common_vendor.index.showToast({ title: "该股票已在超短池中", icon: "none" });
+        return;
+      }
+      try {
+        const res = await request({
+          url: "/v1/stock/pool/admin/add",
+          method: "POST",
+          header: { "content-type": "application/json" },
+          data: { stockCode: item.code, poolType }
+        });
+        if (res.code === 200) {
+          common_vendor.index.showToast({ title: "添加成功", icon: "success" });
+          clearSearch();
+          loadStockList();
+        } else {
+          common_vendor.index.showToast({ title: res.message || "添加失败", icon: "none" });
+        }
+      } catch (e) {
+        common_vendor.index.showToast({ title: "添加失败", icon: "none" });
+      }
+    };
+    const handleDeleteStock = (item) => {
+      common_vendor.index.showModal({
+        title: "确认撤出",
+        content: `确定要将 ${item.name} 从超短池撤出吗?`,
+        success: async (res) => {
+          if (res.confirm) {
+            try {
+              const result = await request({
+                url: "/v1/stock/pool/admin/delete",
+                method: "POST",
+                header: { "content-type": "application/json" },
+                data: { stockCode: item.code, poolType }
+              });
+              if (result.code === 200) {
+                common_vendor.index.showToast({ title: "撤出成功", icon: "success" });
+                loadStockList();
+              } else {
+                common_vendor.index.showToast({ title: result.message || "撤出失败", icon: "none" });
+              }
+            } catch (e) {
+              common_vendor.index.showToast({ title: "撤出失败", icon: "none" });
+            }
+          }
+        }
+      });
+    };
+    return (_ctx, _cache) => {
+      return common_vendor.e({
+        a: common_vendor.o(handleBack),
+        b: common_vendor.o([($event) => searchKeyword.value = $event.detail.value, handleSearchInput]),
+        c: common_vendor.o(($event) => showSuggestions.value = true),
+        d: searchKeyword.value,
+        e: searchKeyword.value
+      }, searchKeyword.value ? {
+        f: common_vendor.o(clearSearch)
+      } : {}, {
+        g: showSuggestions.value && suggestions.value.length > 0
+      }, showSuggestions.value && suggestions.value.length > 0 ? {
+        h: common_vendor.f(suggestions.value, (item, index, i0) => {
+          return {
+            a: common_vendor.t(item.name),
+            b: common_vendor.t(item.code),
+            c: index,
+            d: common_vendor.o(($event) => handleAddFromSuggestion(item), index)
+          };
+        })
+      } : {}, {
+        i: showSuggestions.value && searching.value
+      }, showSuggestions.value && searching.value ? {} : {}, {
+        j: showSuggestions.value && !searching.value && searchKeyword.value && suggestions.value.length === 0
+      }, showSuggestions.value && !searching.value && searchKeyword.value && suggestions.value.length === 0 ? {} : {}, {
+        k: showSuggestions.value && suggestions.value.length > 0
+      }, showSuggestions.value && suggestions.value.length > 0 ? {
+        l: common_vendor.o(closeSuggestions)
+      } : {}, {
+        m: common_vendor.t(stockList.value.length),
+        n: loading.value && stockList.value.length === 0
+      }, loading.value && stockList.value.length === 0 ? {} : stockList.value.length === 0 ? {} : {
+        p: common_vendor.f(stockList.value, (item, index, i0) => {
+          return {
+            a: common_vendor.t(item.name),
+            b: common_vendor.t(item.code),
+            c: common_vendor.t(item.currentPrice || "-"),
+            d: common_vendor.t(item.changePercent || "-"),
+            e: common_vendor.n(getChangeClass(item.changePercent)),
+            f: common_vendor.o(($event) => handleDeleteStock(item), item.code),
+            g: item.code
+          };
+        })
+      }, {
+        o: stockList.value.length === 0
+      });
+    };
+  }
+};
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-98720f9d"], ["__file", "D:/program/gupiao-wx/src/pages/admin/shortPool.vue"]]);
+wx.createPage(MiniProgramPage);

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

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

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


+ 305 - 0
dist/dev/mp-weixin/pages/admin/shortPool.wxss

@@ -0,0 +1,305 @@
+
+.page-container.data-v-98720f9d {
+  min-height: 100vh;
+  background: #f5f6fb;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 导航栏 */
+.custom-navbar.data-v-98720f9d {
+  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-98720f9d {
+  width: 80rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+}
+.back-icon.data-v-98720f9d {
+  font-size: 40rpx;
+  color: #222222;
+  font-weight: bold;
+}
+.navbar-title.data-v-98720f9d {
+  position: absolute;
+  left: 50%;
+  transform: translateX(-50%);
+}
+.title-text.data-v-98720f9d {
+  font-size: 36rpx;
+  font-weight: 600;
+  color: #222222;
+}
+.navbar-placeholder.data-v-98720f9d {
+  width: 80rpx;
+}
+
+/* 内容区 */
+.scroll-view.data-v-98720f9d {
+  flex: 1;
+  height: 0;
+}
+.content-wrapper.data-v-98720f9d {
+  padding: 32rpx;
+}
+
+/* 搜索卡片 */
+.search-card.data-v-98720f9d {
+  background: #ffffff;
+  border-radius: 24rpx;
+  padding: 32rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+  position: relative;
+  z-index: 100;
+}
+.search-header.data-v-98720f9d {
+  margin-bottom: 20rpx;
+}
+.search-title.data-v-98720f9d {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #3abf81;
+}
+.search-box.data-v-98720f9d {
+  position: relative;
+}
+.search-input-wrap.data-v-98720f9d {
+  display: flex;
+  align-items: center;
+  background: #f5f6fb;
+  border-radius: 16rpx;
+  padding: 0 24rpx;
+  height: 88rpx;
+}
+.search-icon.data-v-98720f9d {
+  font-size: 32rpx;
+  margin-right: 16rpx;
+}
+.search-input.data-v-98720f9d {
+  flex: 1;
+  height: 88rpx;
+  font-size: 28rpx;
+  color: #222222;
+}
+.clear-btn.data-v-98720f9d {
+  width: 48rpx;
+  height: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #d0d0d0;
+  border-radius: 50%;
+}
+.clear-btn text.data-v-98720f9d {
+  font-size: 32rpx;
+  color: #fff;
+  line-height: 1;
+}
+
+/* 搜索建议 */
+.suggestions-dropdown.data-v-98720f9d {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  background: #ffffff;
+  border-radius: 16rpx;
+  margin-top: 12rpx;
+  box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
+  max-height: 480rpx;
+  overflow-y: auto;
+  z-index: 101;
+}
+.suggestion-item.data-v-98720f9d {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 28rpx 24rpx;
+  border-bottom: 1rpx solid #f5f6fb;
+}
+.suggestion-item.data-v-98720f9d:last-child {
+  border-bottom: none;
+}
+.suggestion-item.data-v-98720f9d:active {
+  background: #f9f9fb;
+}
+.suggestion-info.data-v-98720f9d {
+  display: flex;
+  flex-direction: column;
+}
+.suggestion-name.data-v-98720f9d {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #222222;
+}
+.suggestion-code.data-v-98720f9d {
+  font-size: 24rpx;
+  color: #9ca2b5;
+  margin-top: 6rpx;
+}
+.add-btn-small.data-v-98720f9d {
+  padding: 12rpx 28rpx;
+  background: #5B5AEA;
+  border-radius: 32rpx;
+}
+.add-btn-small text.data-v-98720f9d {
+  font-size: 24rpx;
+  color: #ffffff;
+  font-weight: 500;
+}
+.suggestion-loading.data-v-98720f9d, .suggestion-empty.data-v-98720f9d {
+  padding: 48rpx;
+  text-align: center;
+  color: #9ca2b5;
+  font-size: 28rpx;
+}
+
+/* 遮罩 */
+.mask.data-v-98720f9d {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.3);
+  z-index: 99;
+}
+
+/* 股票列表卡片 */
+.stock-card.data-v-98720f9d {
+  background: #ffffff;
+  border-radius: 24rpx;
+  padding: 32rpx;
+  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+}
+.card-header.data-v-98720f9d {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+  padding-bottom: 24rpx;
+  border-bottom: 1rpx solid #f1f2f6;
+}
+.header-left.data-v-98720f9d {
+  display: flex;
+  align-items: center;
+}
+.header-dot.data-v-98720f9d {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #5B5AEA;
+  margin-right: 12rpx;
+}
+.header-title.data-v-98720f9d {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #222222;
+}
+.header-count.data-v-98720f9d {
+  font-size: 26rpx;
+  color: #9ca2b5;
+}
+
+/* 股票列表 */
+.stock-list.data-v-98720f9d {
+  display: flex;
+  flex-direction: column;
+}
+.stock-item.data-v-98720f9d {
+  display: flex;
+  align-items: center;
+  padding: 28rpx 0;
+  border-bottom: 1rpx solid #f5f6fb;
+}
+.stock-item.data-v-98720f9d:last-child {
+  border-bottom: none;
+}
+.stock-left.data-v-98720f9d {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+.stock-name.data-v-98720f9d {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #222222;
+}
+.stock-code.data-v-98720f9d {
+  font-size: 24rpx;
+  color: #9ca2b5;
+  margin-top: 8rpx;
+}
+.stock-center.data-v-98720f9d {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  margin-right: 32rpx;
+}
+.stock-price.data-v-98720f9d {
+  font-size: 32rpx;
+  font-weight: 700;
+  color: #222222;
+}
+.stock-change.data-v-98720f9d {
+  font-size: 26rpx;
+  color: #9ca2b5;
+  margin-top: 6rpx;
+  font-weight: 500;
+}
+.stock-change.up.data-v-98720f9d {
+  color: #f16565;
+}
+.stock-change.down.data-v-98720f9d {
+  color: #3abf81;
+}
+.stock-right.data-v-98720f9d {
+  flex-shrink: 0;
+}
+.action-btn.data-v-98720f9d {
+  padding: 14rpx 32rpx;
+  background: #5B5AEA;
+  border-radius: 32rpx;
+}
+.action-btn text.data-v-98720f9d {
+  font-size: 26rpx;
+  color: #ffffff;
+  font-weight: 500;
+}
+
+/* 空状态 */
+.loading-state.data-v-98720f9d, .empty-state.data-v-98720f9d {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 80rpx 40rpx;
+}
+.loading-text.data-v-98720f9d {
+  font-size: 28rpx;
+  color: #9ca2b5;
+}
+.empty-icon.data-v-98720f9d {
+  font-size: 100rpx;
+  margin-bottom: 24rpx;
+}
+.empty-text.data-v-98720f9d {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #333333;
+  margin-bottom: 12rpx;
+}
+.empty-desc.data-v-98720f9d {
+  font-size: 26rpx;
+  color: #999999;
+}
+.bottom-safe-area.data-v-98720f9d {
+  height: 80rpx;
+}

+ 37 - 6
dist/dev/mp-weixin/pages/index/index.js

@@ -123,6 +123,15 @@ const _sfc_main = {
         showDropdown.value = false;
       }, 300);
     };
+    const getPriceClass = (changePercent) => {
+      if (!changePercent)
+        return "";
+      if (changePercent.startsWith("+"))
+        return "price-up";
+      if (changePercent.startsWith("-"))
+        return "price-down";
+      return "";
+    };
     return (_ctx, _cache) => {
       return common_vendor.e({
         a: common_vendor.o([($event) => keyword.value = $event.detail.value, onKeywordChange]),
@@ -147,26 +156,48 @@ const _sfc_main = {
         j: loading.value
       }, loading.value ? {} : errorMsg.value ? {
         l: common_vendor.t(errorMsg.value)
-      } : result.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.score),
-        q: common_vendor.f(result.value.history, (item, index, i0) => {
+        p: common_vendor.t(result.value.currentPrice || "--"),
+        q: common_vendor.n(getPriceClass(result.value.changePercent)),
+        r: common_vendor.t(result.value.priceChange || "--"),
+        s: common_vendor.t(result.value.changePercent || "--"),
+        t: common_vendor.n(getPriceClass(result.value.changePercent)),
+        v: 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 || "--")
+      } : {}, {
+        C: result.value.score
+      }, result.value.score ? {
+        D: common_vendor.t(result.value.score)
+      } : {}, {
+        E: 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) => {
           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
           };
-        }),
-        r: common_vendor.f(result.value.factors, (item, index, i0) => {
+        })
+      } : {}, {
+        G: 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) => {
           return {
             a: common_vendor.t(item.name),
             b: common_vendor.t(item.value),
             c: index
           };
         })
-      } : {}, {
+      } : {}) : {}, {
         k: errorMsg.value,
         m: result.value
       }) : {});

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


+ 64 - 2
dist/dev/mp-weixin/pages/index/index.wxss

@@ -120,19 +120,81 @@
 .detail-header {
   display: flex;
   justify-content: space-between;
-  align-items: center;
+  align-items: flex-start;
   margin-bottom: 24rpx;
+  padding-bottom: 24rpx;
+  border-bottom: 1rpx solid #f1f2f6;
+}
+.stock-info-left {
+  flex: 1;
 }
 .detail-name {
-  font-size: 30rpx;
+  font-size: 34rpx;
   font-weight: 700;
   color: #1f1f2e;
 }
+.detail-code {
+  margin-top: 8rpx;
+  font-size: 26rpx;
+  color: #9da3b5;
+}
 .detail-sub {
   margin-top: 8rpx;
   font-size: 24rpx;
   color: #9da3b5;
 }
+.stock-price-right {
+  text-align: right;
+}
+.current-price {
+  font-size: 44rpx;
+  font-weight: 700;
+  color: #333;
+}
+.price-change {
+  margin-top: 6rpx;
+  font-size: 26rpx;
+}
+.price-up {
+  color: #e74c3c;
+}
+.price-down {
+  color: #27ae60;
+}
+
+/* 实时行情网格 */
+.quote-section {
+  margin-bottom: 24rpx;
+}
+.quote-grid {
+  display: flex;
+  flex-wrap: wrap;
+  background: #f8f9fc;
+  border-radius: 16rpx;
+  padding: 20rpx 16rpx;
+}
+.quote-item {
+  width: 33.33%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 12rpx 0;
+}
+.quote-label {
+  font-size: 24rpx;
+  color: #9da3b5;
+  margin-bottom: 8rpx;
+}
+.quote-value {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #333;
+}
+.score-display {
+  display: flex;
+  justify-content: center;
+  margin-top: 16rpx;
+}
 .score-badge {
   min-width: 140rpx;
   padding: 12rpx 22rpx;

+ 23 - 4
dist/dev/mp-weixin/pages/mine/mine.js

@@ -6,9 +6,11 @@ const _sfc_main = {
   __name: "mine",
   setup(__props) {
     const isLoggedIn = common_vendor.ref(false);
+    const isAdmin = common_vendor.ref(false);
     const userInfo = common_vendor.ref({
       nickname: "",
-      avatar: ""
+      avatar: "",
+      status: 0
     });
     common_vendor.onShow(() => {
       loadUserInfo();
@@ -19,10 +21,14 @@ const _sfc_main = {
       console.log("[个人中心] 登录状态:", isLoggedIn.value);
       if (isLoggedIn.value) {
         const storedInfo = utils_auth.getUserInfo();
+        console.log("[个人中心] 存储的用户信息:", JSON.stringify(storedInfo));
         if (storedInfo) {
           userInfo.value = storedInfo;
-          console.log("[个人中心] 加载用户信息:", userInfo.value);
+          isAdmin.value = storedInfo.status === 2;
+          console.log("[个人中心] status值:", storedInfo.status, "是否管理员:", isAdmin.value);
         }
+      } else {
+        isAdmin.value = false;
       }
     };
     const handleUserCardClick = () => {
@@ -58,15 +64,28 @@ const _sfc_main = {
         url: "/pages/transaction/transaction"
       });
     };
+    const handleShortPoolManage = () => {
+      if (!isAdmin.value) {
+        common_vendor.index.showToast({ title: "无权限访问", icon: "none" });
+        return;
+      }
+      common_vendor.index.navigateTo({
+        url: "/pages/admin/shortPool"
+      });
+    };
     return (_ctx, _cache) => {
       return common_vendor.e({
         a: isLoggedIn.value && userInfo.value.avatar ? 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: isLoggedIn.value
+        e: isAdmin.value
+      }, isAdmin.value ? {
+        f: common_vendor.o(handleShortPoolManage)
+      } : {}, {
+        g: isLoggedIn.value
       }, isLoggedIn.value ? {
-        f: 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><view wx:if="{{e}}" class="logout-section"><button class="logout-btn" bindtap="{{f}}"> 退出登录 </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>

+ 8 - 0
dist/dev/mp-weixin/pages/mine/mine.wxss

@@ -87,6 +87,14 @@
   color: #9ca2b5;
   font-weight: 300;
 }
+.admin-badge {
+  background: linear-gradient(135deg, #ff9500, #ff5e3a);
+  color: #ffffff;
+  font-size: 20rpx;
+  padding: 4rpx 12rpx;
+  border-radius: 20rpx;
+  margin-right: 12rpx;
+}
 
 /* 退出登录按钮 */
 .logout-section {

+ 15 - 22
dist/dev/mp-weixin/pages/transaction/transaction.js

@@ -9,17 +9,10 @@ const _sfc_main = {
     const isLoggedIn = common_vendor.ref(false);
     const checkLogin = () => {
       isLoggedIn.value = utils_auth.isLoggedIn();
-      console.log("[订阅记录] 登录状态:", isLoggedIn.value);
     };
     const handleBack = () => {
       const pages = getCurrentPages();
-      if (pages.length > 1) {
-        common_vendor.index.navigateBack();
-      } else {
-        common_vendor.index.switchTab({
-          url: "/pages/mine/mine"
-        });
-      }
+      pages.length > 1 ? common_vendor.index.navigateBack() : common_vendor.index.switchTab({ url: "/pages/mine/mine" });
     };
     const formatDateTime = (timestamp) => {
       const date = new Date(timestamp);
@@ -64,12 +57,8 @@ const _sfc_main = {
         subscriptions.value = [];
       }
     };
-    common_vendor.onLoad(() => {
-      checkLogin();
-    });
-    common_vendor.onMounted(() => {
-      loadSubscriptions();
-    });
+    common_vendor.onLoad(() => checkLogin());
+    common_vendor.onMounted(() => loadSubscriptions());
     common_vendor.onShow(() => {
       checkLogin();
       loadSubscriptions();
@@ -77,21 +66,25 @@ const _sfc_main = {
     return (_ctx, _cache) => {
       return common_vendor.e({
         a: common_vendor.o(handleBack),
-        b: common_vendor.f(subscriptions.value, (item, index, i0) => {
-          return {
+        b: common_vendor.t(subscriptions.value.length),
+        c: subscriptions.value.length === 0
+      }, subscriptions.value.length === 0 ? {} : {
+        d: common_vendor.f(subscriptions.value, (item, index, i0) => {
+          return common_vendor.e({
             a: common_vendor.t(item.poolType === "pool" ? "⚡" : "📈"),
             b: common_vendor.t(item.poolName),
             c: common_vendor.t(item.isActive ? "生效中" : "已过期"),
-            d: common_vendor.n(item.isActive ? "status-active" : "status-expired"),
+            d: common_vendor.n(item.isActive ? "active" : "expired"),
             e: common_vendor.t(item.planName),
             f: common_vendor.t(item.amount),
             g: common_vendor.t(formatDateTime(item.purchaseTime)),
             h: common_vendor.t(formatDateTime(item.expireTime)),
-            i: index
-          };
-        }),
-        c: subscriptions.value.length === 0
-      }, subscriptions.value.length === 0 ? {} : {});
+            i: item.isActive
+          }, item.isActive ? {} : {}, {
+            j: index
+          });
+        })
+      });
     };
   }
 };

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


+ 76 - 24
dist/dev/mp-weixin/pages/transaction/transaction.wxss

@@ -6,7 +6,7 @@
   flex-direction: column;
 }
 
-/* 自定义导航栏 */
+/* 导航栏 */
 .custom-navbar.data-v-a901d1cb {
   background: #ffffff;
   display: flex;
@@ -21,7 +21,6 @@
   height: 60rpx;
   display: flex;
   align-items: center;
-  justify-content: flex-start;
 }
 .back-icon.data-v-a901d1cb {
   font-size: 40rpx;
@@ -42,22 +41,23 @@
   width: 80rpx;
 }
 
-/* 订阅记录列表 */
+/* 内容区 */
 .scroll-view.data-v-a901d1cb {
   flex: 1;
   height: 0;
 }
-.subscription-list.data-v-a901d1cb {
+.content-wrapper.data-v-a901d1cb {
   padding: 32rpx;
 }
-.subscription-item.data-v-a901d1cb {
+
+/* 记录卡片 */
+.record-card.data-v-a901d1cb {
   background: #ffffff;
   border-radius: 24rpx;
   padding: 32rpx;
-  margin-bottom: 24rpx;
   box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
 }
-.item-header.data-v-a901d1cb {
+.card-header.data-v-a901d1cb {
   display: flex;
   justify-content: space-between;
   align-items: center;
@@ -65,6 +65,44 @@
   padding-bottom: 24rpx;
   border-bottom: 1rpx solid #f1f2f6;
 }
+.header-left.data-v-a901d1cb {
+  display: flex;
+  align-items: center;
+}
+.header-dot.data-v-a901d1cb {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #5B5AEA;
+  margin-right: 12rpx;
+}
+.header-title.data-v-a901d1cb {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #222222;
+}
+.header-count.data-v-a901d1cb {
+  font-size: 26rpx;
+  color: #9ca2b5;
+}
+
+/* 订阅列表 */
+.subscription-list.data-v-a901d1cb {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+}
+.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;
@@ -75,25 +113,23 @@
 }
 .pool-name.data-v-a901d1cb {
   font-size: 30rpx;
-  font-weight: 600;
+  font-weight: 700;
   color: #222222;
 }
 .status-badge.data-v-a901d1cb {
   padding: 8rpx 20rpx;
-  border-radius: 20rpx;
+  border-radius: 32rpx;
   font-size: 24rpx;
+  font-weight: 500;
 }
-.status-active.data-v-a901d1cb {
+.status-badge.active.data-v-a901d1cb {
   background: #e7f7ef;
   color: #3abf81;
 }
-.status-expired.data-v-a901d1cb {
+.status-badge.expired.data-v-a901d1cb {
   background: #f5f5f5;
   color: #999999;
 }
-.status-text.data-v-a901d1cb {
-  font-weight: 500;
-}
 .item-body.data-v-a901d1cb {
   display: flex;
   flex-direction: column;
@@ -101,14 +137,15 @@
 }
 .info-row.data-v-a901d1cb {
   display: flex;
+  justify-content: space-between;
   align-items: center;
-  font-size: 26rpx;
 }
 .info-label.data-v-a901d1cb {
-  color: #666a7f;
-  min-width: 160rpx;
+  font-size: 26rpx;
+  color: #9ca2b5;
 }
 .info-value.data-v-a901d1cb {
+  font-size: 26rpx;
   color: #222222;
   font-weight: 500;
 }
@@ -117,30 +154,45 @@
   font-weight: 700;
   font-size: 28rpx;
 }
+.item-footer.data-v-a901d1cb {
+  margin-top: 24rpx;
+  padding-top: 20rpx;
+  border-top: 1rpx solid #eee;
+  display: flex;
+  justify-content: flex-end;
+}
+.renew-btn.data-v-a901d1cb {
+  padding: 14rpx 40rpx;
+  background: #5B5AEA;
+  border-radius: 32rpx;
+}
+.renew-btn text.data-v-a901d1cb {
+  font-size: 26rpx;
+  color: #ffffff;
+  font-weight: 500;
+}
 
 /* 空状态 */
 .empty-state.data-v-a901d1cb {
   display: flex;
   flex-direction: column;
   align-items: center;
-  justify-content: center;
-  padding: 200rpx 60rpx;
+  padding: 80rpx 40rpx;
 }
 .empty-icon.data-v-a901d1cb {
-  font-size: 120rpx;
-  margin-bottom: 32rpx;
+  font-size: 100rpx;
+  margin-bottom: 24rpx;
 }
 .empty-text.data-v-a901d1cb {
-  font-size: 32rpx;
+  font-size: 30rpx;
   font-weight: 600;
   color: #333333;
-  margin-bottom: 16rpx;
+  margin-bottom: 12rpx;
 }
 .empty-desc.data-v-a901d1cb {
   font-size: 26rpx;
   color: #999999;
   text-align: center;
-  line-height: 1.6;
 }
 .bottom-safe-area.data-v-a901d1cb {
   height: 80rpx;

+ 6 - 0
src/pages.json

@@ -47,6 +47,12 @@
       "style": {
         "navigationStyle": "custom"
       }
+    },
+    {
+      "path": "pages/admin/shortPool",
+      "style": {
+        "navigationStyle": "custom"
+      }
     }
   ],
   "globalStyle": {

+ 639 - 0
src/pages/admin/shortPool.vue

@@ -0,0 +1,639 @@
+<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="search-card">
+          <view class="search-header">
+            <text class="search-title">添加股票</text>
+          </view>
+          <view class="search-box">
+            <view class="search-input-wrap">
+              <text class="search-icon">🔍</text>
+              <input 
+                class="search-input" 
+                v-model="searchKeyword" 
+                placeholder="搜索股票名称或代码"
+                @input="handleSearchInput"
+                @focus="showSuggestions = true"
+              />
+              <view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
+                <text>×</text>
+              </view>
+            </view>
+          </view>
+          
+          <!-- 搜索建议下拉 -->
+          <view v-if="showSuggestions && suggestions.length > 0" class="suggestions-dropdown">
+            <view 
+              v-for="(item, index) in suggestions" 
+              :key="index"
+              class="suggestion-item"
+              @click="handleAddFromSuggestion(item)"
+            >
+              <view class="suggestion-info">
+                <text class="suggestion-name">{{ item.name }}</text>
+                <text class="suggestion-code">{{ item.code }}</text>
+              </view>
+              <view class="add-btn-small">
+                <text>添加</text>
+              </view>
+            </view>
+          </view>
+          
+          <view v-if="showSuggestions && searching" class="suggestions-dropdown">
+            <view class="suggestion-loading">
+              <text>搜索中...</text>
+            </view>
+          </view>
+          
+          <view v-if="showSuggestions && !searching && searchKeyword && suggestions.length === 0" class="suggestions-dropdown">
+            <view class="suggestion-empty">
+              <text>未找到相关股票</text>
+            </view>
+          </view>
+        </view>
+
+        <!-- 遮罩层 -->
+        <view v-if="showSuggestions && suggestions.length > 0" class="mask" @click="closeSuggestions"></view>
+
+        <!-- 股票列表卡片 -->
+        <view class="stock-card">
+          <view class="card-header">
+            <view class="header-left">
+              <view class="header-dot"></view>
+              <text class="header-title">超短池股票</text>
+            </view>
+            <text class="header-count">共 {{ stockList.length }} 只</text>
+          </view>
+
+          <view v-if="loading && stockList.length === 0" class="loading-state">
+            <text class="loading-text">加载中...</text>
+          </view>
+
+          <view v-else-if="stockList.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="stock-list">
+            <view 
+              v-for="(item, index) in stockList" 
+              :key="item.code"
+              class="stock-item"
+            >
+              <view class="stock-left">
+                <text class="stock-name">{{ item.name }}</text>
+                <text class="stock-code">{{ item.code }}</text>
+              </view>
+              <view class="stock-center">
+                <text class="stock-price">{{ item.currentPrice || '-' }}</text>
+                <text class="stock-change" :class="getChangeClass(item.changePercent)">{{ item.changePercent || '-' }}</text>
+              </view>
+              <view class="stock-right">
+                <view class="action-btn" @click="handleDeleteStock(item)">
+                  <text>撤出</text>
+                </view>
+              </view>
+            </view>
+          </view>
+        </view>
+
+        <view class="bottom-safe-area"></view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref, onUnmounted } from 'vue'
+import { onShow, onHide } from '@dcloudio/uni-app'
+import { getUserInfo } from '@/utils/auth.js'
+import { getSuggestions } from '@/utils/api.js'
+
+const searchKeyword = ref('')
+const suggestions = ref([])
+const showSuggestions = ref(false)
+const searching = ref(false)
+const stockList = ref([])
+const loading = ref(false)
+const poolType = 1
+
+let searchTimer = null
+let refreshTimer = null
+
+const getToken = () => uni.getStorageSync('user_token') || null
+
+const request = (options) => {
+  return new Promise((resolve, reject) => {
+    const token = getToken()
+    const header = options.header || {}
+    if (token) header['Authorization'] = `Bearer ${token}`
+    
+    uni.request({
+      url: `http://localhost:8081${options.url}`,
+      method: options.method || 'GET',
+      data: options.data || {},
+      header,
+      success: (res) => res.statusCode === 200 ? resolve(res.data) : reject(new Error(res.data?.message || '请求失败')),
+      fail: () => reject(new Error('网络异常'))
+    })
+  })
+}
+
+const handleBack = () => {
+  const pages = getCurrentPages()
+  pages.length > 1 ? uni.navigateBack() : uni.switchTab({ url: '/pages/mine/mine' })
+}
+
+onShow(() => {
+  checkAdminPermission()
+  loadStockList()
+  startAutoRefresh()
+})
+
+onHide(() => stopAutoRefresh())
+onUnmounted(() => stopAutoRefresh())
+
+const startAutoRefresh = () => {
+  stopAutoRefresh()
+  refreshTimer = setInterval(() => loadStockList(true), 5000)
+}
+
+const stopAutoRefresh = () => {
+  if (refreshTimer) {
+    clearInterval(refreshTimer)
+    refreshTimer = null
+  }
+}
+
+const checkAdminPermission = () => {
+  const userInfo = getUserInfo()
+  if (!userInfo || userInfo.status !== 2) {
+    uni.showToast({ title: '无权限访问', icon: 'none' })
+    setTimeout(() => uni.navigateBack(), 1500)
+  }
+}
+
+const loadStockList = async (silent = false) => {
+  if (!silent) loading.value = true
+  try {
+    const res = await request({ url: '/v1/stock/pool/admin/list', data: { poolType } })
+    if (res.code === 200) stockList.value = res.data || []
+  } catch (e) {
+    console.error('加载失败', e)
+  } finally {
+    if (!silent) loading.value = false
+  }
+}
+
+const getChangeClass = (changePercent) => {
+  if (!changePercent || changePercent === '-') return ''
+  return changePercent.startsWith('+') ? 'up' : changePercent.startsWith('-') ? 'down' : ''
+}
+
+const handleSearchInput = () => {
+  if (searchTimer) clearTimeout(searchTimer)
+  if (!searchKeyword.value.trim()) {
+    suggestions.value = []
+    showSuggestions.value = false
+    return
+  }
+  searching.value = true
+  showSuggestions.value = true
+  searchTimer = setTimeout(async () => {
+    try {
+      const res = await getSuggestions(searchKeyword.value.trim())
+      suggestions.value = (res.code === 0 || res.code === 200) && res.data ? res.data : []
+    } catch (e) {
+      suggestions.value = []
+    } finally {
+      searching.value = false
+    }
+  }, 300)
+}
+
+const clearSearch = () => {
+  searchKeyword.value = ''
+  suggestions.value = []
+  showSuggestions.value = false
+}
+
+const closeSuggestions = () => showSuggestions.value = false
+
+const handleAddFromSuggestion = async (item) => {
+  if (stockList.value.some(s => s.code === item.code)) {
+    uni.showToast({ title: '该股票已在超短池中', icon: 'none' })
+    return
+  }
+  try {
+    const res = await request({
+      url: '/v1/stock/pool/admin/add',
+      method: 'POST',
+      header: { 'content-type': 'application/json' },
+      data: { stockCode: item.code, poolType }
+    })
+    if (res.code === 200) {
+      uni.showToast({ title: '添加成功', icon: 'success' })
+      clearSearch()
+      loadStockList()
+    } else {
+      uni.showToast({ title: res.message || '添加失败', icon: 'none' })
+    }
+  } catch (e) {
+    uni.showToast({ title: '添加失败', icon: 'none' })
+  }
+}
+
+const handleDeleteStock = (item) => {
+  uni.showModal({
+    title: '确认撤出',
+    content: `确定要将 ${item.name} 从超短池撤出吗?`,
+    success: async (res) => {
+      if (res.confirm) {
+        try {
+          const result = await request({
+            url: '/v1/stock/pool/admin/delete',
+            method: 'POST',
+            header: { 'content-type': 'application/json' },
+            data: { stockCode: item.code, poolType }
+          })
+          if (result.code === 200) {
+            uni.showToast({ title: '撤出成功', icon: 'success' })
+            loadStockList()
+          } else {
+            uni.showToast({ title: result.message || '撤出失败', icon: 'none' })
+          }
+        } catch (e) {
+          uni.showToast({ title: '撤出失败', icon: 'none' })
+        }
+      }
+    }
+  })
+}
+</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;
+}
+
+/* 搜索卡片 */
+.search-card {
+  background: #ffffff;
+  border-radius: 24rpx;
+  padding: 32rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+  position: relative;
+  z-index: 100;
+}
+
+.search-header {
+  margin-bottom: 20rpx;
+}
+
+.search-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #3abf81;
+}
+
+.search-box {
+  position: relative;
+}
+
+.search-input-wrap {
+  display: flex;
+  align-items: center;
+  background: #f5f6fb;
+  border-radius: 16rpx;
+  padding: 0 24rpx;
+  height: 88rpx;
+}
+
+.search-icon {
+  font-size: 32rpx;
+  margin-right: 16rpx;
+}
+
+.search-input {
+  flex: 1;
+  height: 88rpx;
+  font-size: 28rpx;
+  color: #222222;
+}
+
+.clear-btn {
+  width: 48rpx;
+  height: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #d0d0d0;
+  border-radius: 50%;
+}
+
+.clear-btn text {
+  font-size: 32rpx;
+  color: #fff;
+  line-height: 1;
+}
+
+/* 搜索建议 */
+.suggestions-dropdown {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  background: #ffffff;
+  border-radius: 16rpx;
+  margin-top: 12rpx;
+  box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);
+  max-height: 480rpx;
+  overflow-y: auto;
+  z-index: 101;
+}
+
+.suggestion-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 28rpx 24rpx;
+  border-bottom: 1rpx solid #f5f6fb;
+}
+
+.suggestion-item:last-child {
+  border-bottom: none;
+}
+
+.suggestion-item:active {
+  background: #f9f9fb;
+}
+
+.suggestion-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.suggestion-name {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.suggestion-code {
+  font-size: 24rpx;
+  color: #9ca2b5;
+  margin-top: 6rpx;
+}
+
+.add-btn-small {
+  padding: 12rpx 28rpx;
+  background: #5B5AEA;
+  border-radius: 32rpx;
+}
+
+.add-btn-small text {
+  font-size: 24rpx;
+  color: #ffffff;
+  font-weight: 500;
+}
+
+.suggestion-loading, .suggestion-empty {
+  padding: 48rpx;
+  text-align: center;
+  color: #9ca2b5;
+  font-size: 28rpx;
+}
+
+/* 遮罩 */
+.mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.3);
+  z-index: 99;
+}
+
+/* 股票列表卡片 */
+.stock-card {
+  background: #ffffff;
+  border-radius: 24rpx;
+  padding: 32rpx;
+  box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+  padding-bottom: 24rpx;
+  border-bottom: 1rpx solid #f1f2f6;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+}
+
+.header-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #5B5AEA;
+  margin-right: 12rpx;
+}
+
+.header-title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.header-count {
+  font-size: 26rpx;
+  color: #9ca2b5;
+}
+
+/* 股票列表 */
+.stock-list {
+  display: flex;
+  flex-direction: column;
+}
+
+.stock-item {
+  display: flex;
+  align-items: center;
+  padding: 28rpx 0;
+  border-bottom: 1rpx solid #f5f6fb;
+}
+
+.stock-item:last-child {
+  border-bottom: none;
+}
+
+.stock-left {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.stock-name {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #222222;
+}
+
+.stock-code {
+  font-size: 24rpx;
+  color: #9ca2b5;
+  margin-top: 8rpx;
+}
+
+.stock-center {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  margin-right: 32rpx;
+}
+
+.stock-price {
+  font-size: 32rpx;
+  font-weight: 700;
+  color: #222222;
+}
+
+.stock-change {
+  font-size: 26rpx;
+  color: #9ca2b5;
+  margin-top: 6rpx;
+  font-weight: 500;
+}
+
+.stock-change.up {
+  color: #f16565;
+}
+
+.stock-change.down {
+  color: #3abf81;
+}
+
+.stock-right {
+  flex-shrink: 0;
+}
+
+.action-btn {
+  padding: 14rpx 32rpx;
+  background: #5B5AEA;
+  border-radius: 32rpx;
+}
+
+.action-btn text {
+  font-size: 26rpx;
+  color: #ffffff;
+  font-weight: 500;
+}
+
+/* 空状态 */
+.loading-state, .empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 80rpx 40rpx;
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: #9ca2b5;
+}
+
+.empty-icon {
+  font-size: 100rpx;
+  margin-bottom: 24rpx;
+}
+
+.empty-text {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #333333;
+  margin-bottom: 12rpx;
+}
+
+.empty-desc {
+  font-size: 26rpx;
+  color: #999999;
+}
+
+.bottom-safe-area {
+  height: 80rpx;
+}
+</style>

+ 138 - 9
src/pages/index/index.vue

@@ -51,17 +51,62 @@
             <text class="result-error">{{errorMsg}}</text>
           </view>
           <view v-else-if="result">
-            <!-- 头部:名称 / 代码 / 分数徽章 -->
+            <!-- 头部:名称 / 代码 / 实时价格 -->
             <view class="detail-header">
-              <view>
-                <view class="detail-name">{{result.stockName}}({{result.stockCode}})</view>
-                <view class="detail-sub">最新量化系统评分</view>
+              <view class="stock-info-left">
+                <view class="detail-name">{{result.stockName}}</view>
+                <view class="detail-code">{{result.stockCode}}</view>
+              </view>
+              <view class="stock-price-right">
+                <view class="current-price" :class="getPriceClass(result.changePercent)">
+                  {{result.currentPrice || '--'}}
+                </view>
+                <view class="price-change" :class="getPriceClass(result.changePercent)">
+                  {{result.priceChange || '--'}} ({{result.changePercent || '--'}})
+                </view>
+              </view>
+            </view>
+
+            <!-- 实时行情数据 -->
+            <view class="quote-section" v-if="result.currentPrice">
+              <view class="quote-grid">
+                <view class="quote-item">
+                  <text class="quote-label">开盘</text>
+                  <text class="quote-value">{{result.openPrice || '--'}}</text>
+                </view>
+                <view class="quote-item">
+                  <text class="quote-label">最高</text>
+                  <text class="quote-value price-up">{{result.highPrice || '--'}}</text>
+                </view>
+                <view class="quote-item">
+                  <text class="quote-label">最低</text>
+                  <text class="quote-value price-down">{{result.lowPrice || '--'}}</text>
+                </view>
+                <view class="quote-item">
+                  <text class="quote-label">成交量</text>
+                  <text class="quote-value">{{result.volume || '--'}}</text>
+                </view>
+                <view class="quote-item">
+                  <text class="quote-label">成交额</text>
+                  <text class="quote-value">{{result.amount || '--'}}</text>
+                </view>
+                <view class="quote-item">
+                  <text class="quote-label">换手率</text>
+                  <text class="quote-value">{{result.turnoverRate || '--'}}</text>
+                </view>
+              </view>
+            </view>
+
+            <!-- 量化评分(如果有) -->
+            <view class="section" v-if="result.score">
+              <view class="section-title">量化系统评分</view>
+              <view class="score-display">
+                <view class="score-badge">{{result.score}}</view>
               </view>
-              <view class="score-badge">{{result.score}}</view>
             </view>
 
             <!-- 历史评分趋势 -->
-            <view class="section">
+            <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">
                 <text class="history-date">{{item.date}} 的量化评分:</text>
@@ -73,7 +118,7 @@
             </view>
 
             <!-- 评分因子构成 -->
-            <view class="section">
+            <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">
                 <text class="factor-name">{{item.name}}</text>
@@ -284,6 +329,16 @@ const onInputBlur = () => {
     showDropdown.value = false
   }, 300)
 }
+
+/**
+ * 根据涨跌幅返回样式类
+ */
+const getPriceClass = (changePercent) => {
+  if (!changePercent) return ''
+  if (changePercent.startsWith('+')) return 'price-up'
+  if (changePercent.startsWith('-')) return 'price-down'
+  return ''
+}
 </script>
 
 <style>
@@ -429,22 +484,96 @@ const onInputBlur = () => {
 .detail-header {
   display: flex;
   justify-content: space-between;
-  align-items: center;
+  align-items: flex-start;
   margin-bottom: 24rpx;
+  padding-bottom: 24rpx;
+  border-bottom: 1rpx solid #f1f2f6;
+}
+
+.stock-info-left {
+  flex: 1;
 }
 
 .detail-name {
-  font-size: 30rpx;
+  font-size: 34rpx;
   font-weight: 700;
   color: #1f1f2e;
 }
 
+.detail-code {
+  margin-top: 8rpx;
+  font-size: 26rpx;
+  color: #9da3b5;
+}
+
 .detail-sub {
   margin-top: 8rpx;
   font-size: 24rpx;
   color: #9da3b5;
 }
 
+.stock-price-right {
+  text-align: right;
+}
+
+.current-price {
+  font-size: 44rpx;
+  font-weight: 700;
+  color: #333;
+}
+
+.price-change {
+  margin-top: 6rpx;
+  font-size: 26rpx;
+}
+
+.price-up {
+  color: #e74c3c;
+}
+
+.price-down {
+  color: #27ae60;
+}
+
+/* 实时行情网格 */
+.quote-section {
+  margin-bottom: 24rpx;
+}
+
+.quote-grid {
+  display: flex;
+  flex-wrap: wrap;
+  background: #f8f9fc;
+  border-radius: 16rpx;
+  padding: 20rpx 16rpx;
+}
+
+.quote-item {
+  width: 33.33%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 12rpx 0;
+}
+
+.quote-label {
+  font-size: 24rpx;
+  color: #9da3b5;
+  margin-bottom: 8rpx;
+}
+
+.quote-value {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #333;
+}
+
+.score-display {
+  display: flex;
+  justify-content: center;
+  margin-top: 16rpx;
+}
+
 .score-badge {
   min-width: 140rpx;
   padding: 12rpx 22rpx;

+ 40 - 2
src/pages/mine/mine.vue

@@ -26,6 +26,15 @@
             </view>
             <text class="menu-arrow">›</text>
           </view>
+          
+          <!-- 管理员专属菜单 -->
+          <view class="menu-item" v-if="isAdmin" @click="handleShortPoolManage">
+            <view class="menu-left">
+              <text class="menu-icon">⚡</text>
+              <text class="menu-label">超短池管理</text>
+            </view>
+            <text class="menu-arrow">›</text>
+          </view>
         </view>
 
         <!-- 退出登录按钮(仅登录后显示) -->
@@ -48,9 +57,11 @@ import { onShow } from '@dcloudio/uni-app'
 import { isLoggedIn as checkLogin, getUserInfo as getStoredUserInfo, logout, checkLogin as requireLogin } from '@/utils/auth.js'
 
 const isLoggedIn = ref(false)
+const isAdmin = ref(false)
 const userInfo = ref({
   nickname: '',
-  avatar: ''
+  avatar: '',
+  status: 0
 })
 
 /**
@@ -70,10 +81,15 @@ const loadUserInfo = async () => {
   console.log('[个人中心] 登录状态:', isLoggedIn.value)
   if (isLoggedIn.value) {
     const storedInfo = getStoredUserInfo()
+    console.log('[个人中心] 存储的用户信息:', JSON.stringify(storedInfo))
     if (storedInfo) {
       userInfo.value = storedInfo
-      console.log('[个人中心] 加载用户信息:', userInfo.value)
+      // 判断是否为管理员(status === 2)
+      isAdmin.value = storedInfo.status === 2
+      console.log('[个人中心] status值:', storedInfo.status, '是否管理员:', isAdmin.value)
     }
+  } else {
+    isAdmin.value = false
   }
 }
 
@@ -131,6 +147,19 @@ const handleSubscriptionRecord = () => {
     url: '/pages/transaction/transaction'
   })
 }
+
+/**
+ * 超短池管理(管理员专属)
+ */
+const handleShortPoolManage = () => {
+  if (!isAdmin.value) {
+    uni.showToast({ title: '无权限访问', icon: 'none' })
+    return
+  }
+  uni.navigateTo({
+    url: '/pages/admin/shortPool'
+  })
+}
 </script>
 
 <style>
@@ -236,6 +265,15 @@ const handleSubscriptionRecord = () => {
   font-weight: 300;
 }
 
+.admin-badge {
+  background: linear-gradient(135deg, #ff9500, #ff5e3a);
+  color: #ffffff;
+  font-size: 20rpx;
+  padding: 4rpx 12rpx;
+  border-radius: 20rpx;
+  margin-right: 12rpx;
+}
+
 /* 退出登录按钮 */
 .logout-section {
   margin-top: 20rpx;

+ 144 - 91
src/pages/transaction/transaction.vue

@@ -11,53 +11,70 @@
       <view class="navbar-placeholder"></view>
     </view>
 
-    <!-- 订阅记录列表 -->
     <scroll-view class="scroll-view" scroll-y>
-      <view 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 === 'pool' ? '⚡' : '📈' }}</text>
-              <text class="pool-name">{{ item.poolName }}</text>
-            </view>
-            <view :class="['status-badge', item.isActive ? 'status-active' : 'status-expired']">
-              <text class="status-text">{{ item.isActive ? '生效中' : '已过期' }}</text>
+      <view class="content-wrapper">
+        <!-- 订阅记录卡片 -->
+        <view class="record-card">
+          <view class="card-header">
+            <view class="header-left">
+              <view class="header-dot"></view>
+              <text class="header-title">我的订阅</text>
             </view>
+            <text class="header-count">共 {{ subscriptions.length }} 条</text>
           </view>
-          
-          <view class="item-body">
-            <view class="info-row">
-              <text class="info-label">订阅方案:</text>
-              <text class="info-value">{{ item.planName }}</text>
-            </view>
-            <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">{{ formatDateTime(item.purchaseTime) }}</text>
-            </view>
-            <view class="info-row">
-              <text class="info-label">到期时间:</text>
-              <text class="info-value">{{ formatDateTime(item.expireTime) }}</text>
-            </view>
+
+          <!-- 空状态 -->
+          <view v-if="subscriptions.length === 0" class="empty-state">
+            <text class="empty-icon">📋</text>
+            <text class="empty-text">暂无订阅记录</text>
+            <text class="empty-desc">前往超短池或强势池订阅服务</text>
           </view>
-        </view>
 
-        <!-- 空状态 -->
-        <view v-if="subscriptions.length === 0" class="empty-state">
-          <text class="empty-icon">📋</text>
-          <text class="empty-text">暂无订阅记录</text>
-          <text class="empty-desc">前往超短池或强势池订阅服务</text>
+          <!-- 订阅列表 -->
+          <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 === 'pool' ? '⚡' : '📈' }}</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">{{ item.planName }}</text>
+                </view>
+                <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">{{ formatDateTime(item.purchaseTime) }}</text>
+                </view>
+                <view class="info-row">
+                  <text class="info-label">到期时间</text>
+                  <text class="info-value">{{ formatDateTime(item.expireTime) }}</text>
+                </view>
+              </view>
+
+              <view v-if="item.isActive" class="item-footer">
+                <view class="renew-btn">
+                  <text>续费</text>
+                </view>
+              </view>
+            </view>
+          </view>
         </view>
 
-        <!-- 底部安全区域 -->
         <view class="bottom-safe-area"></view>
       </view>
     </scroll-view>
@@ -72,27 +89,15 @@ import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
 const subscriptions = ref([])
 const isLoggedIn = ref(false)
 
-// 检查登录状态
 const checkLogin = () => {
   isLoggedIn.value = checkLoginStatus()
-  console.log('[订阅记录] 登录状态:', isLoggedIn.value)
 }
 
-// 返回上一页
 const handleBack = () => {
   const pages = getCurrentPages()
-  if (pages.length > 1) {
-    // 有上一页,返回
-    uni.navigateBack()
-  } else {
-    // 没有上一页,跳转到个人中心
-    uni.switchTab({
-      url: '/pages/mine/mine'
-    })
-  }
+  pages.length > 1 ? uni.navigateBack() : uni.switchTab({ url: '/pages/mine/mine' })
 }
 
-// 格式化日期时间
 const formatDateTime = (timestamp) => {
   const date = new Date(timestamp)
   const year = date.getFullYear()
@@ -100,17 +105,14 @@ const formatDateTime = (timestamp) => {
   const day = String(date.getDate()).padStart(2, '0')
   const hours = String(date.getHours()).padStart(2, '0')
   const minutes = String(date.getMinutes()).padStart(2, '0')
-  
   return `${year}-${month}-${day} ${hours}:${minutes}`
 }
 
-// 加载订阅记录
 const loadSubscriptions = () => {
   try {
     const now = Date.now()
     const allSubscriptions = []
     
-    // 加载超短池订阅记录
     const poolPurchase = uni.getStorageSync('pool_purchase')
     if (poolPurchase) {
       allSubscriptions.push({
@@ -124,7 +126,6 @@ const loadSubscriptions = () => {
       })
     }
     
-    // 加载强势池订阅记录
     const strongPurchase = uni.getStorageSync('strong_pool_purchase')
     if (strongPurchase) {
       allSubscriptions.push({
@@ -138,7 +139,6 @@ const loadSubscriptions = () => {
       })
     }
     
-    // 按购买时间倒序排列
     subscriptions.value = allSubscriptions.sort((a, b) => b.purchaseTime - a.purchaseTime)
   } catch (e) {
     console.error('加载订阅记录失败:', e)
@@ -146,14 +146,8 @@ const loadSubscriptions = () => {
   }
 }
 
-onLoad(() => {
-  checkLogin()
-})
-
-onMounted(() => {
-  loadSubscriptions()
-})
-
+onLoad(() => checkLogin())
+onMounted(() => loadSubscriptions())
 onShow(() => {
   checkLogin()
   loadSubscriptions()
@@ -168,7 +162,7 @@ onShow(() => {
   flex-direction: column;
 }
 
-/* 自定义导航栏 */
+/* 导航栏 */
 .custom-navbar {
   background: #ffffff;
   display: flex;
@@ -184,7 +178,6 @@ onShow(() => {
   height: 60rpx;
   display: flex;
   align-items: center;
-  justify-content: flex-start;
 }
 
 .back-icon {
@@ -209,25 +202,25 @@ onShow(() => {
   width: 80rpx;
 }
 
-/* 订阅记录列表 */
+/* 内容区 */
 .scroll-view {
   flex: 1;
   height: 0;
 }
 
-.subscription-list {
+.content-wrapper {
   padding: 32rpx;
 }
 
-.subscription-item {
+/* 记录卡片 */
+.record-card {
   background: #ffffff;
   border-radius: 24rpx;
   padding: 32rpx;
-  margin-bottom: 24rpx;
   box-shadow: 0 8rpx 24rpx rgba(37, 52, 94, 0.08);
 }
 
-.item-header {
+.card-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
@@ -236,6 +229,50 @@ onShow(() => {
   border-bottom: 1rpx solid #f1f2f6;
 }
 
+.header-left {
+  display: flex;
+  align-items: center;
+}
+
+.header-dot {
+  width: 12rpx;
+  height: 12rpx;
+  border-radius: 50%;
+  background: #5B5AEA;
+  margin-right: 12rpx;
+}
+
+.header-title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.header-count {
+  font-size: 26rpx;
+  color: #9ca2b5;
+}
+
+/* 订阅列表 */
+.subscription-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+}
+
+.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;
@@ -248,30 +285,27 @@ onShow(() => {
 
 .pool-name {
   font-size: 30rpx;
-  font-weight: 600;
+  font-weight: 700;
   color: #222222;
 }
 
 .status-badge {
   padding: 8rpx 20rpx;
-  border-radius: 20rpx;
+  border-radius: 32rpx;
   font-size: 24rpx;
+  font-weight: 500;
 }
 
-.status-active {
+.status-badge.active {
   background: #e7f7ef;
   color: #3abf81;
 }
 
-.status-expired {
+.status-badge.expired {
   background: #f5f5f5;
   color: #999999;
 }
 
-.status-text {
-  font-weight: 500;
-}
-
 .item-body {
   display: flex;
   flex-direction: column;
@@ -280,16 +314,17 @@ onShow(() => {
 
 .info-row {
   display: flex;
+  justify-content: space-between;
   align-items: center;
-  font-size: 26rpx;
 }
 
 .info-label {
-  color: #666a7f;
-  min-width: 160rpx;
+  font-size: 26rpx;
+  color: #9ca2b5;
 }
 
 .info-value {
+  font-size: 26rpx;
   color: #222222;
   font-weight: 500;
 }
@@ -300,32 +335,50 @@ onShow(() => {
   font-size: 28rpx;
 }
 
+.item-footer {
+  margin-top: 24rpx;
+  padding-top: 20rpx;
+  border-top: 1rpx solid #eee;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.renew-btn {
+  padding: 14rpx 40rpx;
+  background: #5B5AEA;
+  border-radius: 32rpx;
+}
+
+.renew-btn text {
+  font-size: 26rpx;
+  color: #ffffff;
+  font-weight: 500;
+}
+
 /* 空状态 */
 .empty-state {
   display: flex;
   flex-direction: column;
   align-items: center;
-  justify-content: center;
-  padding: 200rpx 60rpx;
+  padding: 80rpx 40rpx;
 }
 
 .empty-icon {
-  font-size: 120rpx;
-  margin-bottom: 32rpx;
+  font-size: 100rpx;
+  margin-bottom: 24rpx;
 }
 
 .empty-text {
-  font-size: 32rpx;
+  font-size: 30rpx;
   font-weight: 600;
   color: #333333;
-  margin-bottom: 16rpx;
+  margin-bottom: 12rpx;
 }
 
 .empty-desc {
   font-size: 26rpx;
   color: #999999;
   text-align: center;
-  line-height: 1.6;
 }
 
 .bottom-safe-area {

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