Parcourir la source

未登录首页页面优化

ouyang il y a 10 heures
Parent
commit
4c92d09a20
2 fichiers modifiés avec 387 ajouts et 120 suppressions
  1. 15 0
      mallApp/src/api/shop/index.js
  2. 372 120
      mallApp/src/pages/home/recent.vue

+ 15 - 0
mallApp/src/api/shop/index.js

@@ -59,6 +59,21 @@ export const getList = data => {
 	return https.get('/shop/list', data)
 }
 
+/**
+ * 未登录首页按花店名称搜索(xhShop ptStyle=1)
+ * @param {{ keyword: string }} data
+ */
+export const searchShop = data => {
+	return https.get('/shop/search', data, {}, { hideError: true })
+}
+
+/**
+ * 未登录首页推荐花店(后端按环境写死 shopId/名称/地址/营业时间/封面)
+ */
+export const recommendShop = data => {
+	return https.get('/shop/recommend', data || {}, {}, { hideError: true })
+}
+
 /** *
  * 修改门店 b
  */

+ 372 - 120
mallApp/src/pages/home/recent.vue

@@ -1,43 +1,75 @@
 <!--
   个人首页
-  未登录展示服务说明与登录引导(符合审核可先浏览);已登录管理真实花店,并在列表加载后接收尚未通知的红包到账提醒。
+  未登录:搜店名 + 推荐花店(写死,分测试/正式)引导选店进店;已登录管理真实花店,并在列表加载后接收尚未通知的红包到账提醒。
   已登录时每次打开或回到前台都会重新请求花店列表,保证余额、休店状态与列表本身是最新的。
 -->
 <template>
-  <view class="page-container">
-    <!-- 未登录:服务说明 + 如何体验 + 自愿登录入口 -->
+  <view class="page-container" :class="{ 'is-guest': loginStyle == 0 }">
+    <!-- 未登录:搜索花店 + 推荐花店 + 底部选店引导 -->
     <block v-if="loginStyle == 0">
       <no-network-bar></no-network-bar>
-      <view class="main-content guest-guide">
-        <view class="guest-section-title">我们提供以下商品和服务</view>
-        <view class="guest-service-list">
-          <view
-            class="guest-service-card"
-            v-for="(item, index) in guestServiceList"
-            :key="index"
-          >
-            <text class="guest-service-name">{{ item.title }}</text>
-            <text class="guest-service-desc">{{ item.desc }}</text>
-          </view>
+      <view class="guest-home">
+        <!-- 仅搜花店名称,不搜城市;icon.search -->
+        <view class="guest-search-bar">
+          <text class="iconfont iconsearch guest-search-icon"></text>
+          <input
+            class="guest-search-input"
+            type="text"
+            confirm-type="search"
+            :value="guestKeyword"
+            placeholder="搜索花店名称"
+            placeholder-class="guest-search-ph"
+            @input="onGuestKeywordInput"
+            @confirm="searchGuestShops"
+          />
+          <text
+            v-if="guestKeyword"
+            class="guest-search-clear"
+            @click="clearGuestSearch"
+          >清除</text>
         </view>
 
-        <view class="guest-howto-card">
-          <text class="guest-howto-title">如何体验</text>
-          <view
-            class="guest-howto-item"
-            v-for="(step, index) in guestHowtoList"
-            :key="index"
-          >
-            <text class="guest-howto-index">{{ index + 1 }}.</text>
-            <text class="guest-howto-text">{{ step }}</text>
+        <view class="guest-banner">
+          <image
+            class="guest-banner-img"
+            :src="guestBannerUrl"
+            mode="aspectFill"
+          />
+          <view class="guest-banner-mask">
+            <text class="guest-banner-title">找到您身边的好花店</text>
+            <text class="guest-banner-sub">选择门店后即可浏览商品并下单</text>
           </view>
         </view>
 
-        <view class="guest-login-wrap">
-          <button class="guest-login-btn" @click="goLoginFromGuest">登录后查看我的花店</button>
-          <text class="guest-login-tip">登录后,可查看我的花店、订单、余额与会员权益</text>
+        <view class="guest-section" id="guestRecommend">
+          <text class="guest-section-title">{{ guestListTitle }}</text>
+          <view v-if="guestShopList.length" class="guest-shop-list">
+            <view
+              class="guest-shop-card"
+              v-for="(item, index) in guestShopList"
+              :key="'guest-shop-' + index + '-' + (item.shopId || item.id || 0)"
+              :data-shop-id="item.shopId || item.account || item.id || 0"
+              @click="enterGuestShop"
+            >
+              <image class="guest-shop-cover" :src="guestCoverUrl(item)" mode="aspectFill" />
+              <view class="guest-shop-meta">
+                <text class="guest-shop-name">{{ item.name }}</text>
+                <text class="guest-shop-address">{{ item.address }}</text>
+                <text v-if="item.businessHours" class="guest-shop-hours">营业时间:{{ item.businessHours }}</text>
+              </view>
+              <text class="iconfont iconxiangyou guest-shop-arrow"></text>
+            </view>
+          </view>
+          <view v-else class="guest-search-empty">
+            <text>{{ guestSearching ? '搜索中...' : '未找到相关花店' }}</text>
+          </view>
         </view>
       </view>
+
+      <view class="guest-footer">
+        <text class="guest-login-link" @click="goLoginFromGuest">登录查看更多花店</text>
+        <button class="guest-cta-btn" @click="goLoginFromGuest">选择门店开始购花</button>
+      </view>
     </block>
 
     <!-- 已登录状态 -->
@@ -184,6 +216,7 @@ import TuiListCell from "@/components/plugin/list-cell";
 import TuiListView from "@/components/plugin/list-view";
 import AppHbArrival from "@/components/module/app-hb-arrival.vue";
 import { getList, delHd } from "@/api/hd";
+import { searchShop, recommendShop } from "@/api/shop";
 import { checkArrival } from "@/api/hb";
 import { share } from "@/mixins";
 import AppAvatarModule from "@/components/module/app-avatar";
@@ -215,37 +248,30 @@ export default {
       hbArrivalInfo: {},
       hbArrivalIds: [],
       hbArrivalLoading: false,
-      /**
-       * 未登录引导:花店能力说明(静态文案,不请求门店数据)
-       */
-      guestServiceList: [
-        {
-          title: '绿植花材',
-          desc: '进入店铺后按分类挑选绿植花材'
-        },
-        {
-          title: '花束选购',
-          desc: '浏览花束花礼商品,按需加购'
-        },
-        {
-          title: '商品相册',
-          desc: '查看店铺商品相册与样品'
-        },
-        {
-          title: '订单与会员',
-          desc: '登录后可查看我的花店、订单、余额与会员权益'
-        }
-      ],
-      /** 未登录「如何体验」步骤 */
-      guestHowtoList: [
-        '打开花店发给您的店铺分享链接或小程序码',
-        '进入店铺后即可浏览花材、花束与相册',
-        '需要查看「我的花店」或下单结算时,选择登录'
-      ],
+      /** 未登录搜店 */
+      guestKeyword: "",
+      guestSearchKeyword: "",
+      guestSearchList: [],
+      guestSearching: false,
+      guestSearchTimer: null,
+      /** 推荐花店(/shop/recommend,后端写死) */
+      guestRecommendList: [],
+      guestBannerUrl: "",
+      guestRecommendLoading: false,
     };
   },
   computed: {
     ...mapGetters({ userInfo: "getUser", loginInfo: "getLoginInfo" }),
+    /** 有搜索词时展示接口结果,否则展示推荐 */
+    guestShopList() {
+      if (this.guestSearchKeyword) {
+        return this.guestSearchList;
+      }
+      return this.guestRecommendList;
+    },
+    guestListTitle() {
+      return this.guestSearchKeyword ? "搜索结果" : "推荐花店";
+    },
   },
   watch: {
     loginInfo(newVal) {
@@ -264,6 +290,7 @@ export default {
       this.loginStyle = 1;
     } else {
       this.loginStyle = 0;
+      this.loadGuestRecommend();
     }
     // 切 Tab、从子页返回、App 回前台都会进 onShow,已登录则立刻重拉花店列表
     if (this.loginStyle == 1) {
@@ -272,6 +299,13 @@ export default {
     this.pageAction.refresh = 0;
   },
   onPullDownRefresh() {
+    if (this.loginStyle != 1) {
+      // 游客下拉刷新推荐
+      this.loadGuestRecommend().finally(() => {
+        uni.stopPullDownRefresh();
+      });
+      return;
+    }
     this.resetList();
     // 重新获取数据
     this.getMyHdList()
@@ -283,6 +317,9 @@ export default {
       });
   },
   onReachBottom() {
+    if (this.loginStyle != 1) {
+      return;
+    }
     // 滚动到底部加载更多
     this.toBottom();
   },
@@ -290,6 +327,128 @@ export default {
     init() {
       this.getLoginInfo()
     },
+    /**
+     * 拉取未登录推荐花店(后端按环境写死)
+     */
+    loadGuestRecommend() {
+      if (this.guestRecommendLoading) {
+        return Promise.resolve();
+      }
+      this.guestRecommendLoading = true;
+      return recommendShop({}).then((res) => {
+        const data = (res && res.code == 1 && res.data) ? res.data : {};
+        if (data.banner) {
+          this.guestBannerUrl = data.banner;
+        }
+        const list = Array.isArray(data.list) ? data.list : [];
+        this.guestRecommendList = list.map((row) => ({
+          shopId: Number(row.shopId || row.account || row.id) || 0,
+          name: row.name || "",
+          address: row.address || "",
+          businessHours: row.businessHours || "",
+          cover: row.cover || row.avatar || "",
+          avatar: row.avatar || row.cover || ""
+        }));
+      }).catch(() => {
+        // 失败保留已有列表,避免闪空
+      }).finally(() => {
+        this.guestRecommendLoading = false;
+      });
+    },
+    /**
+     * 封面:接口已返回完整 URL;兼容相对路径时拼 imgUrl
+     * @param {{cover?:string,avatar?:string}} item
+     */
+    guestCoverUrl(item) {
+      const raw = (item && (item.cover || item.avatar)) || "";
+      if (!raw) {
+        return "";
+      }
+      if (/^https?:\/\//i.test(raw)) {
+        return raw;
+      }
+      const base = (this.constant && this.constant.imgUrl) || "";
+      return String(base).replace(/\/$/, "") + (raw.charAt(0) === "/" ? raw : "/" + raw);
+    },
+    /**
+     * 搜索框输入:防抖后按花店名称请求 /shop/search
+     */
+    onGuestKeywordInput(e) {
+      const val = (e && e.detail && e.detail.value != null) ? String(e.detail.value) : "";
+      this.guestKeyword = val;
+      if (this.guestSearchTimer) {
+        clearTimeout(this.guestSearchTimer);
+      }
+      this.guestSearchTimer = setTimeout(() => {
+        this.guestSearchTimer = null;
+        this.searchGuestShops();
+      }, 350);
+    },
+    /** 清空搜索,恢复推荐列表 */
+    clearGuestSearch() {
+      this.guestKeyword = "";
+      this.guestSearchKeyword = "";
+      this.guestSearchList = [];
+      if (this.guestSearchTimer) {
+        clearTimeout(this.guestSearchTimer);
+        this.guestSearchTimer = null;
+      }
+    },
+    /**
+     * 按店名搜索零售花店(xhShop ptStyle=1)
+     */
+    searchGuestShops() {
+      const keyword = String(this.guestKeyword || "").trim();
+      if (!keyword) {
+        this.clearGuestSearch();
+        return;
+      }
+      this.guestSearching = true;
+      this.guestSearchKeyword = keyword;
+      return searchShop({ keyword }).then((res) => {
+        const list = (res && res.code == 1 && res.data && Array.isArray(res.data.list))
+          ? res.data.list
+          : [];
+        // 统一字段:搜索结果用 avatar 当封面
+        this.guestSearchList = list.map((row) => ({
+          shopId: Number(row.account || row.id) || 0,
+          name: row.name || "",
+          address: row.address || "",
+          businessHours: row.businessHours || "",
+          cover: row.avatar || "",
+          avatar: row.avatar || ""
+        }));
+      }).catch(() => {
+        this.guestSearchList = [];
+      }).finally(() => {
+        this.guestSearching = false;
+      });
+    },
+    /**
+     * 进入选中花店首页(未登录可浏览)
+     * 小程序里 @click="fn(item)" 有时拿到的是事件对象而非列表项,故用 data-shop-id 取值
+     * @param {Event|{shopId?:number,account?:number,id?:number}} e 点击事件或门店对象
+     */
+    enterGuestShop(e) {
+      let account = 0;
+      // 兼容:直接传入推荐项对象(如「暂不选择」)
+      if (e && typeof e === "object" && !e.currentTarget && !e.target) {
+        account = Number(e.shopId || e.account || e.id || 0) || 0;
+      } else {
+        const ds =
+          (e && e.currentTarget && e.currentTarget.dataset) ||
+          (e && e.target && e.target.dataset) ||
+          {};
+        // data-shop-id → dataset.shopId(部分端为 shopid)
+        account = Number(ds.shopId != null ? ds.shopId : ds.shopid) || 0;
+      }
+      if (account <= 0) {
+        this.$msg("门店无效");
+        return;
+      }
+      uni.setStorageSync("account", account);
+      this.pageTo({ url: "/pages/home/index?account=" + account });
+    },
     /**
      * 未登录引导页点击登录:自愿进入授权页,登录成功后可按 needBack 返回本页。
      */
@@ -545,6 +704,11 @@ page {
   min-height: 100vh;
   background-color: #f3f4f6;
   padding-bottom: calc(20upx + env(safe-area-inset-bottom));
+
+  &.is-guest {
+    /* 底部固定栏 + 原生 tabBar 预留,避免推荐列表被挡住 */
+    padding-bottom: calc(220upx + env(safe-area-inset-bottom));
+  }
 }
 
 // 主要内容区域
@@ -552,126 +716,214 @@ page {
   width: 100%;
 }
 
-/* 未登录:服务说明引导(对齐审核可浏览要求) */
-.guest-guide {
-  padding: 24upx 24upx 48upx;
+/* 未登录首页:搜索 + Banner + 推荐花店 */
+.guest-home {
+  padding: 16upx 24upx 24upx;
   box-sizing: border-box;
 }
 
-.guest-section-title {
-  display: block;
+.guest-search-bar {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  height: 72upx;
+  padding: 0 24upx;
   margin-bottom: 20upx;
-  font-size: 34upx;
-  font-weight: 700;
+  background: #ffffff;
+  border-radius: 36upx;
+  box-shadow: 0 2upx 12upx rgba(15, 23, 42, 0.04);
+}
+
+.guest-search-icon {
+  flex-shrink: 0;
+  margin-right: 12upx;
+  font-size: 32upx;
+  color: #9ca3af;
+  line-height: 1;
+}
+
+.guest-search-input {
+  flex: 1;
+  min-width: 0;
+  height: 72upx;
+  font-size: 28upx;
   color: #111827;
-  line-height: 1.4;
 }
 
-.guest-service-list {
-  margin-bottom: 24upx;
+.guest-search-ph {
+  color: #9ca3af;
+  font-size: 28upx;
 }
 
-.guest-service-card {
-  background: #ffffff;
-  border-radius: 16upx;
-  padding: 28upx 28upx;
-  margin-bottom: 16upx;
-  box-shadow: 0 4upx 16upx rgba(15, 23, 42, 0.04);
+.guest-search-clear {
+  flex-shrink: 0;
+  margin-left: 12upx;
+  font-size: 24upx;
+  color: $mainColor1;
+  line-height: 1;
+}
+
+.guest-banner {
+  position: relative;
+  height: 200upx;
+  margin-bottom: 28upx;
+  border-radius: 20upx;
+  overflow: hidden;
+  background: #e8f5e9;
 }
 
-.guest-service-name {
+.guest-banner-img {
+  width: 100%;
+  height: 100%;
   display: block;
-  font-size: 30upx;
-  font-weight: 700;
-  color: #111827;
-  line-height: 1.4;
-  margin-bottom: 10upx;
 }
 
-.guest-service-desc {
+.guest-banner-mask {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  padding: 0 36upx 0 46%;
+  box-sizing: border-box;
+  background: linear-gradient(90deg, rgba(232, 245, 233, 0.15) 0%, rgba(232, 245, 233, 0.75) 42%, rgba(241, 248, 241, 0.92) 100%);
+}
+
+.guest-banner-title {
   display: block;
-  font-size: 26upx;
-  color: #6b7280;
-  line-height: 1.5;
+  font-size: 34upx;
+  font-weight: 700;
+  color: $mainColor1;
+  line-height: 1.35;
+  margin-bottom: 10upx;
 }
 
-.guest-howto-card {
-  background: #ffffff;
-  border-radius: 16upx;
-  padding: 28upx;
-  margin-bottom: 40upx;
-  box-shadow: 0 4upx 16upx rgba(15, 23, 42, 0.04);
+.guest-banner-sub {
+  display: block;
+  font-size: 24upx;
+  color: #8a6d3b;
+  line-height: 1.4;
 }
 
-.guest-howto-title {
+.guest-section-title {
   display: block;
-  font-size: 30upx;
+  margin-bottom: 20upx;
+  font-size: 32upx;
   font-weight: 700;
   color: #111827;
-  margin-bottom: 20upx;
   line-height: 1.4;
 }
 
-.guest-howto-item {
+.guest-shop-card {
   display: flex;
   flex-direction: row;
-  align-items: flex-start;
+  align-items: center;
+  padding: 24upx;
   margin-bottom: 16upx;
-
-  &:last-child {
-    margin-bottom: 0;
-  }
+  background: #ffffff;
+  border-radius: 16upx;
+  box-shadow: 0 4upx 16upx rgba(15, 23, 42, 0.04);
 }
 
-.guest-howto-index {
+.guest-shop-cover {
+  width: 112upx;
+  height: 112upx;
+  border-radius: 12upx;
   flex-shrink: 0;
-  width: 40upx;
-  font-size: 26upx;
-  color: #374151;
-  line-height: 1.55;
-  font-weight: 600;
+  background: #f3f4f6;
+  margin-right: 20upx;
 }
 
-.guest-howto-text {
+.guest-shop-meta {
   flex: 1;
   min-width: 0;
+  display: flex;
+  flex-direction: column;
+}
+
+.guest-shop-name {
+  font-size: 30upx;
+  font-weight: 700;
+  color: #111827;
+  line-height: 1.3;
+  margin-bottom: 8upx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.guest-shop-address,
+.guest-shop-hours {
+  font-size: 24upx;
+  color: #9ca3af;
+  line-height: 1.4;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.guest-shop-address {
+  margin-bottom: 4upx;
+}
+
+.guest-shop-arrow {
+  flex-shrink: 0;
+  margin-left: 8upx;
+  font-size: 28upx;
+  color: #c0c4cc;
+  line-height: 1;
+}
+
+.guest-search-empty {
+  padding: 48upx 24upx;
+  text-align: center;
   font-size: 26upx;
-  color: #4b5563;
-  line-height: 1.55;
+  color: #9ca3af;
 }
 
-.guest-login-wrap {
-  padding: 8upx 8upx 0;
+.guest-footer {
+  position: fixed;
+  left: 0;
+  right: 0;
+  /* 压在原生 tabBar 上方 */
+  bottom: calc(100upx + env(safe-area-inset-bottom));
+  z-index: 20;
+  padding: 12upx 40upx 16upx;
+  background: linear-gradient(180deg, rgba(243, 244, 246, 0) 0%, #f3f4f6 28%, #f3f4f6 100%);
+  box-sizing: border-box;
 }
 
-.guest-login-btn {
+.guest-login-link {
+  display: block;
+  text-align: center;
+  font-size: 28upx;
+  color: $mainColor1;
+  line-height: 1.4;
+  margin-bottom: 16upx;
+}
+
+.guest-cta-btn {
   width: 100%;
-  height: 96upx;
-  line-height: 96upx;
-  border-radius: 48upx;
+  height: 88upx;
+  line-height: 88upx;
+  border-radius: 44upx;
   border: none;
   padding: 0;
   margin: 0;
   color: #ffffff;
   font-size: 32upx;
   font-weight: 700;
-  background: linear-gradient(90deg, #ff8a3d 0%, #ff4d4f 100%);
-  box-shadow: 0 10upx 24upx rgba(255, 77, 79, 0.28);
+  background: $mainColor1;
+  box-shadow: 0 8upx 20upx rgba(4, 158, 44, 0.28);
 
   &::after {
     border: none;
   }
 }
 
-.guest-login-tip {
-  display: block;
-  margin-top: 20upx;
-  text-align: center;
-  font-size: 24upx;
-  color: #9ca3af;
-  line-height: 1.5;
-}
-
 // 花店列表
 .shop-list {
   padding: 20upx 24upx 40upx;