浏览代码

联系我们功能添加

Zhangbw 2 月之前
父节点
当前提交
a8687a53e7

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

@@ -15,6 +15,7 @@ if (!Math) {
   "./pages/admin/shortPool.js";
   "./pages/agreement/agreement.js";
   "./pages/history/history.js";
+  "./pages/contact/contact.js";
 }
 const _sfc_main = {
   globalData: {

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

@@ -10,7 +10,8 @@
     "pages/order/order",
     "pages/admin/shortPool",
     "pages/agreement/agreement",
-    "pages/history/history"
+    "pages/history/history",
+    "pages/contact/contact"
   ],
   "window": {
     "navigationBarBackgroundColor": "#ffffff",
@@ -48,7 +49,7 @@
       },
       {
         "pagePath": "pages/rank/rank",
-        "text": "我的股票",
+        "text": "我的自选",
         "iconPath": "/static/images/tab_rank.png",
         "selectedIconPath": "/static/images/tab_rank_active.png"
       },

文件差异内容过多而无法显示
+ 0 - 0
dist/dev/mp-weixin/components/HistorySearchCard.wxml


文件差异内容过多而无法显示
+ 0 - 0
dist/dev/mp-weixin/pages/admin/shortPool.wxml


+ 65 - 62
dist/dev/mp-weixin/pages/index/index.js

@@ -109,7 +109,7 @@ const _sfc_main = {
       const kw = (keyword.value || "").trim();
       if (!kw) {
         common_vendor.index.showToast({
-          title: "请输入股票代码或名称",
+          title: "请输入市场代码或名称",
           icon: "none"
         });
         return;
@@ -172,10 +172,9 @@ const _sfc_main = {
         }
       }
     };
-    const onInputBlur = () => {
-      setTimeout(() => {
-        showDropdown.value = false;
-      }, 300);
+    const closeDropdown = () => {
+      showDropdown.value = false;
+      suggestions.value = [];
     };
     const getPriceClass = (changePercent) => {
       if (!changePercent)
@@ -225,7 +224,7 @@ const _sfc_main = {
       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),
+        c: common_vendor.o(($event) => showDropdown.value = true),
         d: keyword.value,
         e: common_vendor.o(handleSearchClick),
         f: showDropdown.value && suggestions.value && suggestions.value.length > 0
@@ -240,93 +239,97 @@ const _sfc_main = {
         })
       } : {}, {
         h: common_vendor.t(isLoggedIn.value ? "" : "(需登录)"),
-        i: hasSearched.value
+        i: showDropdown.value && suggestions.value.length > 0
+      }, showDropdown.value && suggestions.value.length > 0 ? {
+        j: common_vendor.o(closeDropdown)
+      } : {}, {
+        k: hasSearched.value
       }, hasSearched.value ? common_vendor.e({
-        j: loading.value
+        l: loading.value
       }, loading.value ? {} : errorMsg.value ? {
-        l: common_vendor.t(errorMsg.value)
+        n: 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 || "--"),
-        t: common_vendor.n(getPriceClass(result.value.changePercent)),
-        v: result.value.currentPrice
+        p: common_vendor.t(result.value.stockName),
+        q: common_vendor.t(result.value.stockCode),
+        r: common_vendor.t(result.value.currentPrice || "--"),
+        s: common_vendor.n(getPriceClass(result.value.changePercent)),
+        t: common_vendor.t(result.value.priceChange || "--"),
+        v: common_vendor.t(result.value.changePercent || "--"),
+        w: common_vendor.n(getPriceClass(result.value.changePercent)),
+        x: 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 || "--")
+        y: common_vendor.t(result.value.openPrice || "--"),
+        z: common_vendor.t(result.value.highPrice || "--"),
+        A: common_vendor.t(result.value.lowPrice || "--"),
+        B: common_vendor.t(result.value.volume || "--"),
+        C: common_vendor.t(result.value.amount || "--"),
+        D: common_vendor.t(result.value.turnoverRate || "--")
       } : {}, {
-        C: result.value.score
+        E: result.value.score
       }, result.value.score ? {
-        D: common_vendor.t(result.value.score)
+        F: common_vendor.t(result.value.score)
       } : {}, {
-        E: hasSearched.value && result.value
+        G: hasSearched.value && result.value
       }, hasSearched.value && result.value ? common_vendor.e({
-        F: common_vendor.o(onDateChange),
-        G: historyData.value && historyData.value.recordDate
+        H: common_vendor.o(onDateChange),
+        I: historyData.value && historyData.value.recordDate
       }, historyData.value && historyData.value.recordDate ? {
-        H: common_vendor.t(historyData.value.recordDate)
+        J: common_vendor.t(historyData.value.recordDate)
       } : {}, {
-        I: !historyData.value || !historyData.value.strengthScore && !historyData.value.highTrend
+        K: !historyData.value || !historyData.value.strengthScore && !historyData.value.highTrend
       }, !historyData.value || !historyData.value.strengthScore && !historyData.value.highTrend ? {
-        J: common_vendor.t(selectedDate.value ? "未找到该日期的历史数据" : "暂无历史数据")
+        L: common_vendor.t(selectedDate.value ? "未找到该日期的历史数据" : "暂无历史数据")
       } : common_vendor.e({
-        K: historyData.value.strengthScore !== null && historyData.value.strengthScore !== void 0
+        M: historyData.value.strengthScore !== null && historyData.value.strengthScore !== void 0
       }, historyData.value.strengthScore !== null && historyData.value.strengthScore !== void 0 ? {
-        L: common_vendor.t(formatStrengthScore(historyData.value.strengthScore)),
-        M: common_vendor.n(historyData.value.strengthScore >= 0 ? "capsule-up" : "capsule-down")
+        N: common_vendor.t(formatStrengthScore(historyData.value.strengthScore)),
+        O: common_vendor.n(historyData.value.strengthScore >= 0 ? "capsule-up" : "capsule-down")
       } : {}, {
-        N: historyData.value.closePrice
+        P: historyData.value.closePrice
       }, historyData.value.closePrice ? {
-        O: common_vendor.t(historyData.value.closePrice)
+        Q: common_vendor.t(historyData.value.closePrice)
       } : {}, {
-        P: historyData.value.changePercent
+        R: historyData.value.changePercent
       }, historyData.value.changePercent ? {
-        Q: common_vendor.t(formatPercent(historyData.value.changePercent)),
-        R: common_vendor.n(getChangeClass(historyData.value.changePercent))
+        S: common_vendor.t(formatPercent(historyData.value.changePercent)),
+        T: common_vendor.n(getChangeClass(historyData.value.changePercent))
       } : {}, {
-        S: historyData.value.totalAmount
+        U: historyData.value.totalAmount
       }, historyData.value.totalAmount ? {
-        T: common_vendor.t(formatAmount(historyData.value.totalAmount))
+        V: common_vendor.t(formatAmount(historyData.value.totalAmount))
       } : {}, {
-        U: historyData.value.circulationMarketValue
+        W: historyData.value.circulationMarketValue
       }, historyData.value.circulationMarketValue ? {
-        V: common_vendor.t(formatAmount(historyData.value.circulationMarketValue))
+        X: common_vendor.t(formatAmount(historyData.value.circulationMarketValue))
       } : {}, {
-        W: historyData.value.mainRisePeriod
+        Y: historyData.value.mainRisePeriod
       }, historyData.value.mainRisePeriod ? {
-        X: common_vendor.t(historyData.value.mainRisePeriod)
+        Z: common_vendor.t(historyData.value.mainRisePeriod)
       } : {}, {
-        Y: historyData.value.recentLimitUp
+        aa: historyData.value.recentLimitUp
       }, historyData.value.recentLimitUp ? {
-        Z: common_vendor.t(historyData.value.recentLimitUp)
+        ab: common_vendor.t(historyData.value.recentLimitUp)
       } : {}, {
-        aa: historyData.value.dayHighestPrice
+        ac: historyData.value.dayHighestPrice
       }, historyData.value.dayHighestPrice ? {
-        ab: common_vendor.t(historyData.value.dayHighestPrice)
+        ad: common_vendor.t(historyData.value.dayHighestPrice)
       } : {}, {
-        ac: historyData.value.dayLowestPrice
+        ae: historyData.value.dayLowestPrice
       }, historyData.value.dayLowestPrice ? {
-        ad: common_vendor.t(historyData.value.dayLowestPrice)
+        af: common_vendor.t(historyData.value.dayLowestPrice)
       } : {}, {
-        ae: historyData.value.dayClosePrice
+        ag: historyData.value.dayClosePrice
       }, historyData.value.dayClosePrice ? {
-        af: common_vendor.t(historyData.value.dayClosePrice)
+        ah: common_vendor.t(historyData.value.dayClosePrice)
       } : {}, {
-        ag: historyData.value.highTrend !== null && historyData.value.highTrend !== void 0
+        ai: historyData.value.highTrend !== null && historyData.value.highTrend !== void 0
       }, historyData.value.highTrend !== null && historyData.value.highTrend !== void 0 ? {
-        ah: common_vendor.t(formatPercent(historyData.value.highTrend)),
-        ai: common_vendor.n(getChangeClass(historyData.value.highTrend))
+        aj: common_vendor.t(formatPercent(historyData.value.highTrend)),
+        ak: common_vendor.n(getChangeClass(historyData.value.highTrend))
       } : {})) : {}, {
-        aj: result.value.history && result.value.history.length > 0
+        al: result.value.history && result.value.history.length > 0
       }, result.value.history && result.value.history.length > 0 ? {
-        ak: common_vendor.f(result.value.history, (item, index, i0) => {
+        am: common_vendor.f(result.value.history, (item, index, i0) => {
           return {
             a: common_vendor.t(item.date),
             b: common_vendor.t(item.score),
@@ -335,9 +338,9 @@ const _sfc_main = {
           };
         })
       } : {}, {
-        al: result.value.factors && result.value.factors.length > 0
+        an: result.value.factors && result.value.factors.length > 0
       }, result.value.factors && result.value.factors.length > 0 ? {
-        am: common_vendor.f(result.value.factors, (item, index, i0) => {
+        ao: common_vendor.f(result.value.factors, (item, index, i0) => {
           return {
             a: common_vendor.t(item.name),
             b: common_vendor.t(item.value),
@@ -345,8 +348,8 @@ const _sfc_main = {
           };
         })
       } : {}) : {}, {
-        k: errorMsg.value,
-        m: result.value
+        m: errorMsg.value,
+        o: result.value
       }) : {});
     };
   }

文件差异内容过多而无法显示
+ 0 - 0
dist/dev/mp-weixin/pages/index/index.wxml


+ 19 - 9
dist/dev/mp-weixin/pages/index/index.wxss

@@ -78,20 +78,19 @@
 /* 下拉列表样式 */
 .search-dropdown {
   position: absolute;
-  top: 100%; /* 相对于父元素定位,在搜索框下方 */
+  top: 100%;
   left: 0;
   right: 0;
-  margin-top: 8rpx;
+  margin-top: 12rpx;
   background: #ffffff;
   border-radius: 16rpx;
-  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15);
-  max-height: 400rpx;
+  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;
+  padding: 28rpx 24rpx;
   border-bottom: 1rpx solid #f5f6fb;
   display: flex;
   justify-content: space-between;
@@ -100,8 +99,19 @@
 .dropdown-item:last-child {
   border-bottom: none;
 }
-.dropdown-item-hover {
-  background-color: #f7f8fc;
+.dropdown-item:active {
+  background: #f9f9fb;
+}
+
+/* 遮罩 */
+.mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.3);
+  z-index: 99;
 }
 .item-name {
   font-size: 28rpx;

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

@@ -1 +1 @@
-<view class="login-container data-v-cdfe2409"><view class="back-button data-v-cdfe2409" bindtap="{{a}}"><text class="back-icon data-v-cdfe2409">←</text><text class="back-text data-v-cdfe2409">返回</text></view><view class="bg-decoration data-v-cdfe2409"><view class="circle circle-1 data-v-cdfe2409"></view><view class="circle circle-2 data-v-cdfe2409"></view></view><view class="header data-v-cdfe2409"><image class="logo data-v-cdfe2409" src="/static/images/logo.png" mode="aspectFit"></image><text class="title data-v-cdfe2409">量化交易大师</text><text class="subtitle data-v-cdfe2409">专业的股票量化分析工具</text></view><view class="login-actions data-v-cdfe2409"><button wx:if="{{b}}" class="login-btn primary-btn data-v-cdfe2409" bindtap="{{c}}"><text class="btn-text data-v-cdfe2409">微信一键登录</text></button><button wx:elif="{{d}}" class="login-btn primary-btn data-v-cdfe2409" open-type="getPhoneNumber" bindgetphonenumber="{{e}}"><text class="btn-icon data-v-cdfe2409">🔐</text><text class="btn-text data-v-cdfe2409">授权手机号完成注册</text></button><button wx:else class="login-btn primary-btn data-v-cdfe2409" bindtap="{{f}}"><text class="btn-icon data-v-cdfe2409">👤</text><text class="btn-text data-v-cdfe2409">授权登录</text></button><view class="agreement data-v-cdfe2409"><checkbox-group class="data-v-cdfe2409" bindchange="{{j}}"><label class="agreement-label data-v-cdfe2409"><checkbox class="data-v-cdfe2409" checked="{{g}}" color="#5d55e8"/><text class="agreement-text data-v-cdfe2409"> 我已阅读并同意 <text class="link data-v-cdfe2409" catchtap="{{h}}">《用户协议》</text> 和 <text class="link data-v-cdfe2409" catchtap="{{i}}">《隐私政策》</text></text></label></checkbox-group></view></view><user-info-popup class="r data-v-cdfe2409" u-r="userInfoPopup" bindconfirm="{{l}}" u-i="cdfe2409-0" bind:__l="__l"/></view>
+<view class="login-container data-v-cdfe2409"><view class="back-button data-v-cdfe2409" bindtap="{{a}}"><text class="back-icon data-v-cdfe2409">←</text><text class="back-text data-v-cdfe2409">返回</text></view><view class="bg-decoration data-v-cdfe2409"><view class="circle circle-1 data-v-cdfe2409"></view><view class="circle circle-2 data-v-cdfe2409"></view></view><view class="header data-v-cdfe2409"><image class="logo data-v-cdfe2409" src="/static/images/logo.png" mode="aspectFit"></image><text class="title data-v-cdfe2409">量化交易大师</text><text class="subtitle data-v-cdfe2409">专业的市场量化分析工具</text></view><view class="login-actions data-v-cdfe2409"><button wx:if="{{b}}" class="login-btn primary-btn data-v-cdfe2409" bindtap="{{c}}"><text class="btn-text data-v-cdfe2409">微信一键登录</text></button><button wx:elif="{{d}}" class="login-btn primary-btn data-v-cdfe2409" open-type="getPhoneNumber" bindgetphonenumber="{{e}}"><text class="btn-icon data-v-cdfe2409">🔐</text><text class="btn-text data-v-cdfe2409">授权手机号完成注册</text></button><button wx:else class="login-btn primary-btn data-v-cdfe2409" bindtap="{{f}}"><text class="btn-icon data-v-cdfe2409">👤</text><text class="btn-text data-v-cdfe2409">授权登录</text></button><view class="agreement data-v-cdfe2409"><checkbox-group class="data-v-cdfe2409" bindchange="{{j}}"><label class="agreement-label data-v-cdfe2409"><checkbox class="data-v-cdfe2409" checked="{{g}}" color="#5d55e8"/><text class="agreement-text data-v-cdfe2409"> 我已阅读并同意 <text class="link data-v-cdfe2409" catchtap="{{h}}">《用户协议》</text> 和 <text class="link data-v-cdfe2409" catchtap="{{i}}">《隐私政策》</text></text></label></checkbox-group></view></view><user-info-popup class="r data-v-cdfe2409" u-r="userInfoPopup" bindconfirm="{{l}}" u-i="cdfe2409-0" bind:__l="__l"/></view>

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

@@ -73,6 +73,14 @@ const _sfc_main = {
         url: "/pages/order/order"
       });
     };
+    const handleContactUs = () => {
+      if (!utils_auth.checkLogin()) {
+        return;
+      }
+      common_vendor.index.navigateTo({
+        url: "/pages/contact/contact"
+      });
+    };
     const handleShortPoolManage = () => {
       if (!isAdmin.value) {
         common_vendor.index.showToast({ title: "无权限访问", icon: "none" });
@@ -88,13 +96,14 @@ const _sfc_main = {
         b: common_vendor.t(isLoggedIn.value ? userInfo.value.nickname || "微信用户" : "登录/注册"),
         c: common_vendor.o(handleUserCardClick),
         d: common_vendor.o(handleOrderRecord),
-        e: isAdmin.value
+        e: common_vendor.o(handleContactUs),
+        f: isAdmin.value
       }, isAdmin.value ? {
-        f: common_vendor.o(handleShortPoolManage)
+        g: common_vendor.o(handleShortPoolManage)
       } : {}, {
-        g: isLoggedIn.value
+        h: isLoggedIn.value
       }, isLoggedIn.value ? {
-        h: common_vendor.o(handleLogout)
+        i: 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 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>
+<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>

+ 4 - 4
dist/dev/mp-weixin/pages/pool/pool.js

@@ -124,7 +124,7 @@ const _sfc_main = {
           stockList.value = res.data;
         }
       } catch (e) {
-        console.error("加载股票池失败:", e);
+        console.error("加载标的池失败:", e);
       }
     };
     const loadAndStartRefresh = async () => {
@@ -206,7 +206,7 @@ const _sfc_main = {
       if (!checkLogin()) {
         common_vendor.index.showModal({
           title: "登录提示",
-          content: "添加自选股票需要登录,是否前往登录?",
+          content: "添加自选需要登录,是否前往登录?",
           confirmText: "去登录",
           cancelText: "取消",
           success: (res) => {
@@ -239,13 +239,13 @@ const _sfc_main = {
         if (addRes.code === 200 && addRes.data === true) {
           common_vendor.index.showToast({ title: "添加成功", icon: "success" });
         } else if (addRes.code === 200 && addRes.data === false) {
-          common_vendor.index.showToast({ title: "股票已存在", icon: "none" });
+          common_vendor.index.showToast({ title: "样本已存在", icon: "none" });
         } else {
           common_vendor.index.showToast({ title: addRes.message || "添加失败", icon: "none" });
         }
       } catch (e) {
         common_vendor.index.hideLoading();
-        console.error("添加股票失败:", e);
+        console.error("添加样本失败:", e);
         common_vendor.index.showToast({ title: "添加失败", icon: "none" });
       }
     };

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

@@ -170,7 +170,7 @@ const _sfc_main = {
     const drawTrendChart = (stock) => {
       const canvasId = "chart-" + stock.code;
       let trendData = stock.trendData;
-      if (!trendData || !Array.isArray(trendData) || trendData.length === 0) {
+      if (!trendData || !Array.isArray(trendData) || trendData.length < 2) {
         trendData = generateMockTrendData(stock.changePercent);
       }
       const query = common_vendor.index.createSelectorQuery();

文件差异内容过多而无法显示
+ 0 - 0
dist/dev/mp-weixin/pages/rank/rank.wxml


+ 2 - 2
dist/dev/mp-weixin/pages/strong/strong.js

@@ -95,13 +95,13 @@ const _sfc_main = {
         if (addRes.code === 200 && addRes.data === true) {
           common_vendor.index.showToast({ title: "添加成功", icon: "success" });
         } else if (addRes.code === 200 && addRes.data === false) {
-          common_vendor.index.showToast({ title: "股票已存在", icon: "none" });
+          common_vendor.index.showToast({ title: "样本已存在", icon: "none" });
         } else {
           common_vendor.index.showToast({ title: addRes.message || "添加失败", icon: "none" });
         }
       } catch (e) {
         common_vendor.index.hideLoading();
-        console.error("添加股票失败:", e);
+        console.error("添加样本失败:", e);
         common_vendor.index.showToast({ title: "添加失败", icon: "none" });
       }
     };

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

@@ -4,7 +4,7 @@ const ENV = "dev";
 const CONFIG = {
   dev: "http://localhost:8081",
   // 开发环境
-  local: "http://10.167.44.71:8081",
+  local: "http://192.168.1.171:8081",
   prod: "https://www.whzhangsheng.cn/applet-api"
   // 生产环境
 };
@@ -294,6 +294,20 @@ const searchStockHistory = (keyword, recordDate) => {
     data
   });
 };
+const submitFeedback = (data) => {
+  console.log("[submitFeedback] 开始提交反馈,数据:", data);
+  return request({
+    url: "/v1/user/feedback/submit",
+    method: "POST",
+    header: {
+      "content-type": "application/json"
+    },
+    data
+  });
+};
+const uploadFeedbackImage = {
+  url: `${BASE_URL}/v1/file/upload`
+};
 exports.BASE_URL = BASE_URL;
 exports.addUserStock = addUserStock;
 exports.checkSubscription = checkSubscription;
@@ -314,7 +328,9 @@ exports.queryOrder = queryOrder;
 exports.repayOrder = repayOrder;
 exports.searchStockHistory = searchStockHistory;
 exports.searchStocks = searchStocks;
+exports.submitFeedback = submitFeedback;
 exports.updateUserProfile = updateUserProfile;
+exports.uploadFeedbackImage = uploadFeedbackImage;
 exports.uploadFile = uploadFile;
 exports.wxCompleteUserInfoApi = wxCompleteUserInfoApi;
 exports.wxPay = wxPay;

+ 1 - 0
dist/dev/mp-weixin/utils/auth.js

@@ -192,6 +192,7 @@ const checkLogin = (callback) => {
   return false;
 };
 exports.checkLogin = checkLogin;
+exports.getToken = getToken;
 exports.getUserInfo = getUserInfo;
 exports.isLoggedIn = isLoggedIn;
 exports.logout = logout;

+ 2 - 2
src/components/HistorySearchCard.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="card history-card">
     <view class="history-header">
-      <text class="history-title">📊 历史股票池回顾</text>
+      <text class="history-title">📊 历史标的池回顾</text>
     </view>
     
     <!-- 日期区间选择 -->
@@ -24,7 +24,7 @@
       <text class="search-button-text">🔍 查询历史数据</text>
     </view>
     
-    <text class="history-tip">选择时间区间,查询该期间的入池股票及表现。</text>
+    <text class="history-tip">选择时间区间,查询该期间的入池样本及表现。</text>
     
     <!-- 自定义日期选择弹窗 -->
     <view v-if="showDatePicker" class="date-picker-mask" @click="closeDatePicker">

+ 7 - 1
src/pages.json

@@ -65,6 +65,12 @@
       "style": {
         "navigationStyle": "custom"
       }
+    },
+    {
+      "path": "pages/contact/contact",
+      "style": {
+        "navigationBarTitleText": "联系我们"
+      }
     }
   ],
   "globalStyle": {
@@ -103,7 +109,7 @@
       },
       {
         "pagePath": "pages/rank/rank",
-        "text": "我的股票",
+        "text": "我的自选",
         "iconPath": "/static/images/tab_rank.png",
         "selectedIconPath": "/static/images/tab_rank_active.png"
       },

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

@@ -16,7 +16,7 @@
         <!-- 搜索添加区域 -->
         <view class="search-card">
           <view class="search-header">
-            <text class="search-title">添加股票</text>
+            <text class="search-title">添加标的</text>
           </view>
           <view class="search-box">
             <view class="search-input-wrap">
@@ -24,7 +24,7 @@
               <input 
                 class="search-input" 
                 v-model="searchKeyword" 
-                placeholder="搜索股票名称或代码"
+                placeholder="搜索市场名称或代码"
                 @input="handleSearchInput"
                 @focus="showSuggestions = true"
               />
@@ -60,7 +60,7 @@
           
           <view v-if="showSuggestions && !searching && searchKeyword && suggestions.length === 0" class="suggestions-dropdown">
             <view class="suggestion-empty">
-              <text>未找到相关股票</text>
+              <text>未找到相关样本</text>
             </view>
           </view>
         </view>
@@ -73,7 +73,7 @@
           <view class="card-header">
             <view class="header-left">
               <view class="header-dot"></view>
-              <text class="header-title">超短池股票</text>
+              <text class="header-title">超短池</text>
             </view>
             <text class="header-count">共 {{ stockList.length }} 只</text>
           </view>

+ 365 - 0
src/pages/contact/contact.vue

@@ -0,0 +1,365 @@
+<template>
+  <view class="page-contact">
+    <scroll-view class="scroll-view" scroll-y>
+      <view class="content-wrapper">
+        <!-- 反馈表单卡片 -->
+        <view class="form-card">
+          <view class="card-title">意见反馈</view>
+
+          <!-- 文本输入框 -->
+          <view class="form-item">
+            <textarea
+              class="feedback-textarea"
+              v-model="feedbackText"
+              placeholder="请输入您的意见或建议..."
+              maxlength="500"
+              :show-confirm-bar="false"
+            ></textarea>
+            <view class="char-count">{{ feedbackText.length }}/500</view>
+          </view>
+
+          <!-- 图片上传 -->
+          <view class="form-item">
+            <view class="upload-label">上传图片(最多6张)</view>
+            <view class="image-upload-container">
+              <view class="image-item" v-for="(image, index) in imageList" :key="index">
+                <image class="upload-image" :src="image" mode="aspectFill"></image>
+                <view class="delete-btn" @click="deleteImage(index)">×</view>
+              </view>
+              <view class="upload-btn" v-if="imageList.length < 6" @click="chooseImage">
+                <text class="upload-icon">+</text>
+                <text class="upload-text">添加图片</text>
+              </view>
+            </view>
+          </view>
+        </view>
+
+        <!-- 提交按钮 -->
+        <view class="submit-section">
+          <button class="submit-btn" @click="handleSubmit" :disabled="submitting">
+            {{ submitting ? '提交中...' : '提交反馈' }}
+          </button>
+        </view>
+
+        <!-- 预留底部空间 -->
+        <view class="bottom-safe-area"></view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import { submitFeedback, uploadFeedbackImage } from '../../utils/api.js'
+import { getToken } from '../../utils/auth.js'
+
+const feedbackText = ref('')
+const imageList = ref([])
+const submitting = ref(false)
+
+onShow(() => {
+  uni.setNavigationBarTitle({ title: '联系我们' })
+})
+
+/**
+ * 选择图片
+ */
+const chooseImage = () => {
+  const remainCount = 6 - imageList.value.length
+  uni.chooseImage({
+    count: remainCount,
+    sizeType: ['compressed'],
+    sourceType: ['album', 'camera'],
+    success: (res) => {
+      imageList.value = imageList.value.concat(res.tempFilePaths)
+    }
+  })
+}
+
+/**
+ * 删除图片
+ */
+const deleteImage = (index) => {
+  imageList.value.splice(index, 1)
+}
+
+/**
+ * 上传单张图片
+ */
+const uploadImage = (filePath) => {
+  return new Promise((resolve, reject) => {
+    uni.uploadFile({
+      url: uploadFeedbackImage.url,
+      filePath: filePath,
+      name: 'file',
+      header: {
+        'Authorization': 'Bearer ' + getToken()
+      },
+      success: (res) => {
+        try {
+          const data = JSON.parse(res.data)
+          if (data.code === 200) {
+            resolve(data.data.url)
+          } else {
+            reject(data.message || '上传失败')
+          }
+        } catch (e) {
+          reject('上传失败')
+        }
+      },
+      fail: (err) => {
+        reject(err.errMsg || '上传失败')
+      }
+    })
+  })
+}
+
+/**
+ * 提交反馈
+ */
+const handleSubmit = async () => {
+  if (!feedbackText.value.trim()) {
+    uni.showToast({
+      title: '请输入反馈内容',
+      icon: 'none'
+    })
+    return
+  }
+
+  submitting.value = true
+
+  try {
+    // 上传所有图片
+    const imageUrls = []
+    if (imageList.value.length > 0) {
+      uni.showLoading({ title: '上传图片中...' })
+
+      for (const imagePath of imageList.value) {
+        try {
+          const url = await uploadImage(imagePath)
+          imageUrls.push(url)
+        } catch (err) {
+          console.error('图片上传失败:', err)
+          uni.hideLoading()
+          uni.showToast({
+            title: '图片上传失败',
+            icon: 'none'
+          })
+          submitting.value = false
+          return
+        }
+      }
+
+      uni.hideLoading()
+    }
+
+    // 提交反馈
+    uni.showLoading({ title: '提交中...' })
+
+    const res = await submitFeedback({
+      content: feedbackText.value.trim(),
+      images: imageUrls.join(',')
+    })
+
+    uni.hideLoading()
+
+    if (res.code === 200) {
+      uni.showToast({
+        title: '提交成功,感谢您的反馈',
+        icon: 'success',
+        duration: 2000
+      })
+
+      // 清空表单
+      feedbackText.value = ''
+      imageList.value = []
+
+      // 延迟返回上一页
+      setTimeout(() => {
+        uni.navigateBack()
+      }, 2000)
+    } else {
+      uni.showToast({
+        title: res.message || '提交失败',
+        icon: 'none'
+      })
+    }
+  } catch (err) {
+    uni.hideLoading()
+    console.error('提交反馈失败:', err)
+    uni.showToast({
+      title: '提交失败,请稍后重试',
+      icon: 'none'
+    })
+  } finally {
+    submitting.value = false
+  }
+}
+</script>
+
+<style>
+.page-contact {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #f5f6fb;
+}
+
+.scroll-view {
+  flex: 1;
+  height: 0;
+}
+
+.content-wrapper {
+  padding: 32rpx;
+  background: #f5f6fb;
+  min-height: 100%;
+}
+
+/* 表单卡片 */
+.form-card {
+  background: #ffffff;
+  border-radius: 24rpx;
+  padding: 32rpx;
+  margin-bottom: 32rpx;
+  box-shadow: 0 16rpx 40rpx rgba(37, 52, 94, 0.08);
+}
+
+.card-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+  margin-bottom: 32rpx;
+}
+
+.form-item {
+  margin-bottom: 32rpx;
+}
+
+.form-item:last-child {
+  margin-bottom: 0;
+}
+
+/* 文本输入框 */
+.feedback-textarea {
+  width: 100%;
+  min-height: 300rpx;
+  padding: 24rpx;
+  background: #f5f6fb;
+  border-radius: 16rpx;
+  font-size: 28rpx;
+  color: #222222;
+  line-height: 1.6;
+  box-sizing: border-box;
+}
+
+.char-count {
+  text-align: right;
+  font-size: 24rpx;
+  color: #9ca2b5;
+  margin-top: 12rpx;
+}
+
+/* 图片上传 */
+.upload-label {
+  font-size: 28rpx;
+  color: #222222;
+  margin-bottom: 16rpx;
+}
+
+.image-upload-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16rpx;
+}
+
+.image-item {
+  position: relative;
+  width: 200rpx;
+  height: 200rpx;
+}
+
+.upload-image {
+  width: 100%;
+  height: 100%;
+  border-radius: 16rpx;
+  background: #f5f6fb;
+}
+
+.delete-btn {
+  position: absolute;
+  top: -12rpx;
+  right: -12rpx;
+  width: 48rpx;
+  height: 48rpx;
+  background: #ff4444;
+  color: #ffffff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 36rpx;
+  line-height: 1;
+  box-shadow: 0 4rpx 12rpx rgba(255, 68, 68, 0.3);
+}
+
+.upload-btn {
+  width: 200rpx;
+  height: 200rpx;
+  background: #f5f6fb;
+  border-radius: 16rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border: 2rpx dashed #d0d4e0;
+}
+
+.upload-icon {
+  font-size: 64rpx;
+  color: #9ca2b5;
+  line-height: 1;
+  margin-bottom: 8rpx;
+}
+
+.upload-text {
+  font-size: 24rpx;
+  color: #9ca2b5;
+}
+
+/* 提交按钮 */
+.submit-section {
+  margin-bottom: 32rpx;
+}
+
+.submit-btn {
+  width: 100%;
+  background: #5d55e8;
+  color: #ffffff;
+  border-radius: 16rpx;
+  padding: 28rpx 0;
+  text-align: center;
+  font-size: 30rpx;
+  font-weight: 600;
+  box-shadow: 0 12rpx 24rpx rgba(93, 85, 232, 0.3);
+  border: none;
+  line-height: 1;
+}
+
+.submit-btn::after {
+  border: none;
+}
+
+.submit-btn:active {
+  opacity: 0.8;
+}
+
+.submit-btn[disabled] {
+  opacity: 0.6;
+}
+
+.bottom-safe-area {
+  height: 80rpx;
+}
+</style>

+ 1 - 1
src/pages/history/history.vue

@@ -5,7 +5,7 @@
       <view class="nav-back" @click="goBack">
         <text class="back-icon">←</text>
       </view>
-      <text class="nav-title">历史股票池回顾</text>
+      <text class="nav-title">历史标的池回顾</text>
       <view class="nav-placeholder"></view>
     </view>
     

+ 33 - 23
src/pages/index/index.vue

@@ -11,13 +11,13 @@
             <input
               class="search-input"
               type="text"
-              placeholder="请输入股票代码/名称 (如: 600030)"
+              placeholder="请输入市场代码/名称 (如: 600030)"
               placeholder-class="search-placeholder"
               confirm-type="search"
               v-model="keyword"
               @input="onKeywordChange"
               @confirm="handleSearchClick"
-              @blur="onInputBlur"
+              @focus="showDropdown = true"
             />
             <!-- 搜索按钮(统一处理登录检查) -->
             <view class="search-button" @click="handleSearchClick">
@@ -31,17 +31,19 @@
               v-for="(item, index) in suggestions"
               :key="index"
               class="dropdown-item"
-              hover-class="dropdown-item-hover"
-              @tap.stop="onSelectSuggestion(item)"
+              @click="onSelectSuggestion(item)"
             >
               <text class="item-name">{{item.name}}</text>
               <text class="item-code">{{item.code}}</text>
             </view>
           </view>
 
-          <text class="search-tip">支持A股代码或名称模糊查询{{ isLoggedIn ? '' : '(需登录)' }}</text>
+          <text class="search-tip">支持全市场代码或名称模糊查询{{ isLoggedIn ? '' : '(需登录)' }}</text>
         </view>
 
+        <!-- 遮罩层 -->
+        <view v-if="showDropdown && suggestions.length > 0" class="mask" @click="closeDropdown"></view>
+
         <!-- 查询结果 -->
         <view v-if="hasSearched" class="card result-card">
           <view v-if="loading">
@@ -217,7 +219,7 @@
           </view>
           <view class="advantage-item">
             <text class="advantage-label">高效覆盖广度:</text>
-            <text class="advantage-desc">能同时分析数千只股票,人工无法企及。</text>
+            <text class="advantage-desc">能同时分析数千只样本,人工无法企及。</text>
           </view>
         </view>
 
@@ -231,8 +233,8 @@
           </view>
 
           <text class="risk-text">
-            本系统的量化分数和股票池信息均基于历史数据和特定模型计算,并非对未来市场的保证或预测。市场环境瞬息万变,
-            量化模型可能存在失效或回撤风险。请勿将本系统数据作为投资决策的唯一依据,请您充分理解股票投资风险,并独立做出投资判断。
+            本系统的量化分数和标的池信息均基于历史数据和特定模型计算,并非对未来市场的保证或预测。市场环境瞬息万变,
+            量化模型可能存在失效或回撤风险。请勿将本系统数据作为投资决策的唯一依据,请您充分理解市场投资风险,并独立做出投资判断。
           </text>
         </view>
 
@@ -382,7 +384,7 @@ const onSearch = () => {
   const kw = (keyword.value || '').trim()
   if (!kw) {
     uni.showToast({
-      title: '请输入股票代码或名称',
+      title: '请输入市场代码或名称',
       icon: 'none'
     })
     return
@@ -461,11 +463,9 @@ const onDateChange = async (dateStr) => {
   }
 }
 
-const onInputBlur = () => {
-  // 延迟关闭,给点击下拉项留出时间
-  setTimeout(() => {
-    showDropdown.value = false
-  }, 300)
+const closeDropdown = () => {
+  showDropdown.value = false
+  suggestions.value = []
 }
 
 /**
@@ -616,21 +616,20 @@ const formatStrengthScore = (value) => {
 /* 下拉列表样式 */
 .search-dropdown {
   position: absolute;
-  top: 100%; /* 相对于父元素定位,在搜索框下方 */
+  top: 100%;
   left: 0;
   right: 0;
-  margin-top: 8rpx;
+  margin-top: 12rpx;
   background: #ffffff;
   border-radius: 16rpx;
-  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.15);
-  max-height: 400rpx;
+  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;
+  padding: 28rpx 24rpx;
   border-bottom: 1rpx solid #f5f6fb;
   display: flex;
   justify-content: space-between;
@@ -641,8 +640,19 @@ const formatStrengthScore = (value) => {
   border-bottom: none;
 }
 
-.dropdown-item-hover {
-  background-color: #f7f8fc;
+.dropdown-item:active {
+  background: #f9f9fb;
+}
+
+/* 遮罩 */
+.mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.3);
+  z-index: 99;
 }
 
 .item-name {

+ 1 - 1
src/pages/login/login.vue

@@ -16,7 +16,7 @@
     <view class="header">
       <image class="logo" src="/static/images/logo.png" mode="aspectFit"></image>
       <text class="title">量化交易大师</text>
-      <text class="subtitle">专业的股票量化分析工具</text>
+      <text class="subtitle">专业的市场量化分析工具</text>
     </view>
 
     <!-- 登录按钮区域 -->

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

@@ -26,7 +26,15 @@
             </view>
             <text class="menu-arrow">›</text>
           </view>
-          
+
+          <view class="menu-item" @click="handleContactUs">
+            <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" v-if="isAdmin" @click="handleShortPoolManage">
             <view class="menu-left">
@@ -154,13 +162,28 @@ const handleOrderRecord = () => {
   })) {
     return
   }
-  
+
   // 跳转到订单记录页面
   uni.navigateTo({
     url: '/pages/order/order'
   })
 }
 
+/**
+ * 联系我们
+ */
+const handleContactUs = () => {
+  if (!requireLogin(() => {
+    handleContactUs()
+  })) {
+    return
+  }
+
+  uni.navigateTo({
+    url: '/pages/contact/contact'
+  })
+}
+
 /**
  * 超短池管理(管理员专属)
  */

+ 4 - 4
src/pages/pool/pool.vue

@@ -232,7 +232,7 @@ const loadStockPool = async () => {
       stockList.value = res.data
     }
   } catch (e) {
-    console.error('加载股票池失败:', e)
+    console.error('加载标的池失败:', e)
   }
 }
 
@@ -336,7 +336,7 @@ const addToMyStocks = async (stock) => {
   if (!checkLogin()) {
     uni.showModal({
       title: '登录提示',
-      content: '添加自选股票需要登录,是否前往登录?',
+      content: '添加自选需要登录,是否前往登录?',
       confirmText: '去登录',
       cancelText: '取消',
       success: (res) => {
@@ -373,13 +373,13 @@ const addToMyStocks = async (stock) => {
     if (addRes.code === 200 && addRes.data === true) {
       uni.showToast({ title: '添加成功', icon: 'success' })
     } else if (addRes.code === 200 && addRes.data === false) {
-      uni.showToast({ title: '股票已存在', icon: 'none' })
+      uni.showToast({ title: '样本已存在', icon: 'none' })
     } else {
       uni.showToast({ title: addRes.message || '添加失败', icon: 'none' })
     }
   } catch (e) {
     uni.hideLoading()
-    console.error('添加股票失败:', e)
+    console.error('添加样本失败:', e)
     uni.showToast({ title: '添加失败', icon: 'none' })
   }
 }

+ 5 - 5
src/pages/rank/rank.vue

@@ -104,7 +104,7 @@
           </view>
           <!-- 表头 -->
           <view class="table-header">
-            <text class="th-name">股票</text>
+            <text class="th-name">自选</text>
             <text class="th-date">自选日</text>
             <text class="th-price">自选价</text>
             <text class="th-profit">收益</text>
@@ -137,8 +137,8 @@
         <!-- 空状态 -->
         <view v-if="myStocks.length === 0" class="empty-content">
           <view class="empty-icon">📊</view>
-          <text class="empty-text">暂无收藏股票</text>
-          <text class="empty-desc">在强势池中点击"+"按钮添加股票</text>
+          <text class="empty-text">暂无自选</text>
+          <text class="empty-desc">在强势池中点击"+"按钮添加自选</text>
         </view>
 
         <!-- 底部安全区域 -->
@@ -360,8 +360,8 @@ const drawTrendChart = (stock) => {
   const canvasId = 'chart-' + stock.code
   let trendData = stock.trendData
   
-  // 如果没有趋势数据,生成模拟数据
-  if (!trendData || !Array.isArray(trendData) || trendData.length === 0) {
+  // 如果没有趋势数据或数据点不足,生成模拟数据
+  if (!trendData || !Array.isArray(trendData) || trendData.length < 2) {
     trendData = generateMockTrendData(stock.changePercent)
   }
   

+ 3 - 3
src/pages/strong/strong.vue

@@ -41,7 +41,7 @@
           </view>
         </view>
 
-        <!-- 历史股票池回顾(登录用户可见) -->
+        <!-- 历史股票池回顾 -->
         <HistorySearchCard
           :poolType="2"
           :canSearch="true"
@@ -166,13 +166,13 @@ const addToMyStocks = async (stock) => {
     if (addRes.code === 200 && addRes.data === true) {
       uni.showToast({ title: '添加成功', icon: 'success' })
     } else if (addRes.code === 200 && addRes.data === false) {
-      uni.showToast({ title: '股票已存在', icon: 'none' })
+      uni.showToast({ title: '样本已存在', icon: 'none' })
     } else {
       uni.showToast({ title: addRes.message || '添加失败', icon: 'none' })
     }
   } catch (e) {
     uni.hideLoading()
-    console.error('添加股票失败:', e)
+    console.error('添加样本失败:', e)
     uni.showToast({ title: '添加失败', icon: 'none' })
   }
 }

+ 28 - 1
src/utils/api.js

@@ -8,7 +8,7 @@ const ENV = 'dev' // 'dev' | 'prod'
 
 const CONFIG = {
   dev: 'http://localhost:8081',      // 开发环境
-  local:'http://10.167.44.71:8081',
+  local:'http://192.168.1.171:8081',
   prod: 'https://www.whzhangsheng.cn/applet-api'    // 生产环境
 }
 
@@ -518,3 +518,30 @@ export const searchStockHistory = (keyword, recordDate) => {
     data: data
   })
 }
+
+/**
+ * 提交用户反馈
+ * @param {object} data - 反馈数据
+ * @param {string} data.content - 反馈内容
+ * @param {string} data.images - 图片URL列表,逗号分隔
+ * @returns {Promise} 返回提交结果
+ */
+export const submitFeedback = (data) => {
+  console.log('[submitFeedback] 开始提交反馈,数据:', data)
+  return request({
+    url: '/v1/user/feedback/submit',
+    method: 'POST',
+    header: {
+      'content-type': 'application/json'
+    },
+    data: data
+  })
+}
+
+/**
+ * 上传反馈图片
+ * 注意:此函数返回上传配置,实际上传需使用 uni.uploadFile
+ */
+export const uploadFeedbackImage = {
+  url: `${BASE_URL}/v1/file/upload`
+}

部分文件因为文件数量过多而无法显示