فهرست منبع

零售端,登录功能优化

ouyang 21 ساعت پیش
والد
کامیت
ca6e074b32

+ 18 - 2
mallApp/src/components/home/bannerSwiper.vue

@@ -183,12 +183,25 @@ export default {
       const cached = uni.getStorageSync('currentShop') || {}
       return ((cached && cached.serviceWx) || '').trim()
     },
+    /**
+     * 未登录则通知父页弹出 wangCg(与加购入口一致,不跳转登录页)
+     * @returns {boolean} true=已登录可继续
+     */
+    requireLogin() {
+      if (!this.$util.isEmpty(this.loginInfo)) {
+        return true
+      }
+      this.$emit('require-login')
+      return false
+    },
     /**
      * 首页客服悬浮入口
-     * 已设置微信二维码 → 客服展示页(二维码/电话/在线咨询)
-     * 未设置微信二维码 → 直接进入在线聊天页
+     * 未登录 → wangCg;已设置微信二维码 → 客服页;否则直达在线聊天
      */
     goChat() {
+      if (!this.requireLogin()) {
+        return
+      }
       const account = this.account || uni.getStorageSync('account') || ''
       const hdId = this.hdId || uni.getStorageSync('hdId') || ''
       // 有二维码走客服页;无二维码跳过中间页,直达聊天
@@ -205,6 +218,9 @@ export default {
      * 参数与客服页 / 商品详情 goChat 保持一致(不含商品信息)
      */
     goChatPage() {
+      if (!this.requireLogin()) {
+        return
+      }
       if (isNaN(parseInt(this.loginInfo && this.loginInfo.id))) {
         this.$msg('登录用户数据异常,请稍后再试')
         return

+ 14 - 1
mallApp/src/components/home/goodsSection.vue

@@ -55,6 +55,7 @@
 <script>
 import productMins from '@/mixins/cgProduct'
 import AppLazyImg from '@/components/app-lazy-img.vue'
+import { mapGetters } from 'vuex'
 
 export default {
   name: 'homeGoodsSection',
@@ -95,6 +96,11 @@ export default {
     }
   },
   computed: {
+    ...mapGetters({ loginInfo: 'getLoginInfo' }),
+    /** 有用户信息视为已登录;加购前校验,未登录由父页弹 wangCg */
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo)
+    },
     cols() {
       const c = parseInt(this.data && this.data.layoutCols, 10)
       return [1, 2, 3].includes(c) ? c : 3
@@ -155,13 +161,20 @@ export default {
     goDetail(g) {
       this.pageTo({ url: `/pages/goods/detail?id=${g.id}&account=${this.account}&hdId=${this.hdId}` })
     },
-    /** 单规格直接加购,多规格(goodsStyle=1)跳转详情选规格;无价格商品拦截并提示询价 */
+    /**
+     * 单规格直接加购,多规格(goodsStyle=1)跳转详情选规格;无价格商品拦截并提示询价
+     * 未登录:仅拦截「直接加购」,多规格仍可进详情;由父页弹 wangCg
+     */
     handleCartClick(g) {
       if (!g || !g.id) return
       if (Number(g.goodsStyle) === 1 || Number(g.specEnabled) === 1) {
         this.goDetail(g)
         return
       }
+      if (!this.isLoggedIn) {
+        this.$emit('require-login')
+        return
+      }
       if (this.isNoPrice(g)) {
         this.$msg('请向商家咨询价格')
         return

+ 15 - 1
mallApp/src/components/home/topNav.vue

@@ -79,9 +79,23 @@ export default {
       })
     },
     /**
-     * 消息中心入口:跳转 消息列表(pages/message/index)
+     * 未登录则通知父页弹出 wangCg(与客服入口一致)
+     * @returns {boolean} true=已登录可继续
+     */
+    requireLogin() {
+      if (!this.$util.isEmpty(this.loginInfo)) {
+        return true
+      }
+      this.$emit('require-login')
+      return false
+    },
+    /**
+     * 消息中心入口:未登录弹 wangCg;已登录进消息列表
      */
     goMessageCenter() {
+      if (!this.requireLogin()) {
+        return
+      }
       uni.navigateTo({ url: '/pages/message/index' })
     }
   }

+ 92 - 84
mallApp/src/components/wangCg.vue

@@ -1,12 +1,16 @@
+<!--
+  商城手机号一键登录弹层
+  用途:未登录时引导授权手机号;loginStyle=0 显示 / 1 隐藏(.sync)
+  右上角关闭、底部「暂不登录」均可收起弹层,不强制登录
+-->
 <template>
-  <view class="login-overlay" v-if="loginStyle == 0">
-    <view class="login-modal">
-      <!-- 背景装饰 -->
-      <view class="bg-decoration">
-        <view class="circle circle-1"></view>
-        <view class="circle circle-2"></view>
+  <view class="login-overlay" v-if="loginStyle == 0" @tap.stop="">
+    <view class="login-modal" @tap.stop="">
+      <!-- 右上角关闭:收起弹层,继续浏览 -->
+      <view class="close-btn" @tap.stop="dismissLogin">
+        <text class="close-icon">×</text>
       </view>
-      
+
       <!-- 主要内容 -->
       <view class="content-wrapper">
         <!-- 图标区域 -->
@@ -15,25 +19,27 @@
             <text class="icon-text">🌻</text>
           </view>
         </view>
-        
+
         <!-- 文字区域 -->
         <view class="text-section">
           <view class="title">请先登录</view>
         </view>
-        
+
         <!-- 按钮区域 -->
         <view class="button-section">
           <button class="login-btn" open-type="getPhoneNumber" @getphonenumber="getWxMobile">
             <text class="btn-text">手机号一键登录</text>
           </button>
+          <!-- 最下方:暂不登录,与关闭按钮同效果,便于继续逛店 -->
+          <view class="skip-btn" @tap.stop="dismissLogin">
+            <text class="skip-text">暂不登录</text>
+          </view>
         </view>
-        
+
         <!-- 说明文字 -->
         <view class="notice-section">
-          <text class="notice-text">为了更好提供服务,需要获取您的手机号,如不接受,你可以拒绝使用,点右上角关闭此小程序</text>
+          <text class="notice-text">为了更好提供服务,需要获取您的手机号;也可暂不登录继续浏览</text>
         </view>
-
-
       </view>
     </view>
   </view>
@@ -47,6 +53,7 @@ import { TOKEN_STORAGE_KEY } from "@/constant/storageKeys";
 
 export default {
   props: {
+    /** 0=显示弹层,1=隐藏;父页用 .sync 双向绑定 */
     loginStyle: {
       type: Number,
       default: 0,
@@ -60,15 +67,23 @@ export default {
     };
   },
   methods: {
+    /**
+     * 关闭弹层(右上角 / 暂不登录)
+     * 通过 update:loginStyle 通知父组件隐藏,不跳转登录页
+     */
+    dismissLogin() {
+      this.$emit('update:loginStyle', 1)
+      this.$emit('close')
+    },
     getWxMobile(e) {
       let that = this
-    if (e.detail.errMsg === "getPhoneNumber:ok") {
-      let code = e.detail.code
-      getMiniMobile({ code: code }).then(subRes => {
-        this.form.mobile = subRes.data.mobile
-        that.confirmFn()
-      })
-    } else {
+      if (e.detail.errMsg === "getPhoneNumber:ok") {
+        let code = e.detail.code
+        getMiniMobile({ code: code }).then(subRes => {
+          this.form.mobile = subRes.data.mobile
+          that.confirmFn()
+        })
+      } else {
         this.$msg("获取手机失败");
       }
     },
@@ -76,12 +91,13 @@ export default {
       let currentMiniOpenId = uni.getStorageSync('miniOpenId')
       let that = this
       create({ miniOpenId: currentMiniOpenId, ...this.form }).then(res => {
-        if(res.code == 1){
+        if (res.code == 1) {
           uni.setStorageSync(TOKEN_STORAGE_KEY, res.data.token)
-          if(res.data.user && !that.$util.isEmpty(res.data.user)){
+          if (res.data.user && !that.$util.isEmpty(res.data.user)) {
             store.commit('setLoginInfo', res.data.user)
             that.$msg('登录成功')
-            // 登录成功后触发事件通知父组件
+            // 登录成功后关闭弹层并通知父组件
+            this.$emit('update:loginStyle', 1)
             this.$emit('loginSuccess', res.data.user)
           }
         }
@@ -116,6 +132,26 @@ export default {
   animation: modalShow 0.3s ease-out;
 }
 
+/* 右上角关闭按钮 */
+.close-btn {
+  position: absolute;
+  top: 16upx;
+  right: 16upx;
+  z-index: 3;
+  width: 56upx;
+  height: 56upx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.close-icon {
+  font-size: 48upx;
+  line-height: 1;
+  color: #999999;
+  font-weight: 300;
+}
+
 @keyframes modalShow {
   from {
     opacity: 0;
@@ -127,59 +163,17 @@ export default {
   }
 }
 
-// 背景装饰
-.bg-decoration {
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  
-  .circle {
-    position: absolute;
-    border-radius: 50%;
-    background: rgba(0, 0, 0, 0.05);
-    animation: float 4s ease-in-out infinite;
-    
-    &.circle-1 {
-      width: 80upx;
-      height: 80upx;
-      top: 20%;
-      right: 10%;
-      animation-delay: 0s;
-    }
-    
-    &.circle-2 {
-      width: 60upx;
-      height: 60upx;
-      bottom: 30%;
-      left: 10%;
-      animation-delay: 2s;
-    }
-  }
-}
-
-@keyframes float {
-  0%, 100% {
-    transform: translateY(0px) rotate(0deg);
-  }
-  50% {
-    transform: translateY(-10px) rotate(90deg);
-  }
-}
-
 .content-wrapper {
   position: relative;
   z-index: 2;
   text-align: center;
-  padding: 60upx 40upx;
+  padding: 60upx 40upx 40upx;
 }
 
 // 图标区域
 .icon-section {
   margin-bottom: 40upx;
-  
+
   .login-icon {
     width: 100upx;
     height: 100upx;
@@ -192,7 +186,7 @@ export default {
     backdrop-filter: blur(10px);
     border: 2upx solid rgba(0, 0, 0, 0.1);
     box-shadow: 0 8upx 32upx rgba(0, 0, 0, 0.1);
-    
+
     .icon-text {
       font-size: 70upx;
     }
@@ -202,7 +196,7 @@ export default {
 // 文字区域
 .text-section {
   margin-bottom: 50upx;
-  
+
   .title {
     font-size: 50upx;
     font-weight: 600;
@@ -210,7 +204,7 @@ export default {
     margin-bottom: 16upx;
     text-shadow: none;
   }
-  
+
   .subtitle {
     font-size: 36upx;
     color: #666666;
@@ -222,7 +216,7 @@ export default {
 // 按钮区域
 .button-section {
   margin-bottom: 30upx;
-  
+
   .login-btn {
     display: flex;
     align-items: center;
@@ -237,21 +231,21 @@ export default {
     cursor: pointer;
     padding: 0;
     margin: 0;
-    
+
     &:active {
       transform: translateY(2upx);
       box-shadow: 0 4upx 12upx rgba(238, 90, 36, 0.4);
     }
-    
+
     &::after {
       border: none;
     }
-    
+
     .btn-icon {
       font-size: 32upx;
       margin-right: 12upx;
     }
-    
+
     .btn-text {
       font-size: 40upx;
       font-weight: 600;
@@ -259,6 +253,20 @@ export default {
       letter-spacing: 1upx;
     }
   }
+
+  /* 暂不登录:主按钮下方文字按钮 */
+  .skip-btn {
+    margin-top: 28upx;
+    padding: 12upx 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .skip-text {
+    font-size: 28upx;
+    color: #999999;
+  }
 }
 
 // 说明文字区域
@@ -277,34 +285,34 @@ export default {
   .login-modal {
     width: 580upx;
   }
-  
+
   .content-wrapper {
-    padding: 50upx 30upx;
+    padding: 50upx 30upx 36upx;
   }
-  
+
   .icon-section .login-icon {
     width: 80upx;
     height: 80upx;
-    
+
     .icon-text {
       font-size: 56upx;
     }
   }
-  
+
   .text-section .title {
     font-size: 42upx;
   }
-  
+
   .text-section .subtitle {
     font-size: 34upx;
   }
-  
+
   .button-section .login-btn {
     height: 80upx;
-    
+
     .btn-text {
       font-size: 38upx;
     }
   }
 }
-</style>
+</style>

+ 18 - 9
mallApp/src/pages/goods/detail.vue

@@ -3,7 +3,8 @@
   谁用:mallApp 顾客查看花束并加购/购买
   解决问题:多规格 specList 首项即主记录可售规格,选中后 specGoodsId 可等于主商品 id;
   顶部信息卡按标题/分享、副文案、价格已售、服务保障排布,秒杀条保留在价格上方;
-  无价格商品作为秒杀时展示秒杀价,底部走加购/购买而不是询价
+  无价格商品作为秒杀时展示秒杀价,底部走加购/购买而不是询价;
+  未登录可浏览详情:仅点购物车/咨询/加入购物车/购买(含询价)时弹 wangCg
 -->
 <template>
   <view class="page-container">
@@ -162,7 +163,7 @@
       :activity-stock="activityStock"
       @goChat="goChat"
     />
-    <!-- 未登录弹出手机号一键登录(与分类页 shop-category 一致) -->
+    <!-- 按需弹出:仅购物车/咨询/加购/购买触发,浏览详情默认不弹 -->
     <wangCg :loginStyle.sync="globalLoginStyle" @goToLogin="loginTo" @loginSuccess="loginSuccess" />
   </view>
 </template>
@@ -224,6 +225,8 @@ export default {
       _seckillTimer: null,
       /** 覆盖秒杀价前的原 price/priceType,活动结束后还原,避免无价格商品仍显示可买 */
       _originPriceSnapshot: null,
+      /** wangCg:1=隐藏,0=显示;覆盖全局 mixin 默认 0,避免进页即弹 */
+      globalLoginStyle: 1,
     };
   },
   onLoad() {
@@ -241,6 +244,7 @@ export default {
   },
   /** 返回本页时从本地缓存恢复购物车,保证底部角标与加购合并正确 */
   onShow() {
+    // 已登录则收起弹层;未登录不主动弹出
     this.syncLoginStyle();
     this.restoreCartFromStorage();
   },
@@ -248,9 +252,9 @@ export default {
     loginInfo: {
       deep: true,
       handler() {
+        // 登录成功后收起;未登录不因 watch 强制弹出
         this.syncLoginStyle();
-      },
-      immediate: true
+      }
     }
   },
   onShareAppMessage() {
@@ -344,9 +348,13 @@ export default {
     },
   },
   methods: {
-    /** 按登录态驱动 wangCg:0 未登录弹层 / 1 已登录关闭 */
+    /**
+     * 同步 wangCg:仅在已登录时强制关闭;未登录保持现状(由操作入口 ensureLogin 弹出)
+     */
     syncLoginStyle() {
-      this.globalLoginStyle = this.$util.isEmpty(this.loginInfo) ? 0 : 1;
+      if (!this.$util.isEmpty(this.loginInfo)) {
+        this.globalLoginStyle = 1;
+      }
     },
     loginTo() {
       this.$util.pageTo({
@@ -356,16 +364,17 @@ export default {
     },
     /** 登录成功后刷新门店客户与商品详情 */
     loginSuccess() {
-      this.syncLoginStyle();
+      this.globalLoginStyle = 1;
       this.syncMyCustomId();
       this.loadGoodsDetail();
     },
     /**
-     * 购买/客服前校验登录:未登录则弹出 wangCg
+     * 购物车 / 咨询 / 加入购物车 / 购买前校验登录:未登录则弹出 wangCg
      * @returns {boolean}
      */
     ensureLogin() {
-      if (!this.$util.isEmpty(this.loginInfo) && this.globalLoginStyle === 1) {
+      if (!this.$util.isEmpty(this.loginInfo)) {
+        this.globalLoginStyle = 1;
         return true;
       }
       this.globalLoginStyle = 0;

+ 38 - 13
mallApp/src/pages/groupBuy/detail.vue

@@ -2,6 +2,7 @@
   团购详情 / 开团入口页
   从首页团购专区点击进入(goodsId),或从分享卡片进入(id=groupBuyId)
   展示商品信息、活动规则(land.desc)、商品详情图文(picTextGoods)、开团玩法、底部操作
+  未登录可浏览:仅开团/参团/邀请好友时弹 wangCg;单独购买不弹(进商品详情)
 -->
 <template>
   <view class="group-buy-detail-page">
@@ -107,7 +108,12 @@
         <text class="foot-side-text">单独购买</text>
       </button>
       <button class="foot-main" @click="startGroup">我要开团</button>
-      <button class="foot-side" open-type="share" @click="prepareShare">
+      <!-- 已登录才挂 open-type=share;未登录点邀请先弹 wangCg -->
+      <button
+        class="foot-side"
+        :open-type="isLoggedIn ? 'share' : ''"
+        @click="onInviteClick"
+      >
         <i class="iconfont iconfenxiang foot-side-icon"></i>
         <text class="foot-side-text">邀请好友</text>
       </button>
@@ -123,7 +129,7 @@
     </view>
   </view>
 
-  <!-- 未登录时弹出手机号登录/注册(与分类页 shop-category 一致) -->
+  <!-- 按需弹出:开团/参团/邀请才显示,浏览与单独购买不弹 -->
   <wangCg :loginStyle.sync="globalLoginStyle" @goToLogin="loginTo" @loginSuccess="loginSuccess" />
   </view>
 </template>
@@ -132,7 +138,7 @@
 /**
  * 团购详情页
  * 入参:goodsId(首页进入)或 id(分享进入的 groupBuyId)
- * 开团/参团成功后跳转待付款页;未登录时通过 wangCg 引导登录/注册
+ * 开团/参团成功后跳转待付款页;未登录时开团/参团/邀请通过 wangCg 引导,单独购买不弹
  */
 import { mapGetters } from 'vuex'
 import CountDown from '@/components/CountDown.vue'
@@ -171,6 +177,8 @@ export default {
       submitting: false,
       /** 分享 path 追加 inviterCustomId 用,不透传 hdId */
       myCustomId: 0,
+      /** wangCg:1=隐藏,0=显示;覆盖全局 mixin 默认 0,避免进页即弹 */
+      globalLoginStyle: 1,
       steps: [
         { name: '选拼商品', desc: '挑选心仪花束' },
         { name: '邀请好友', desc: '分享邀请好友参团' },
@@ -180,6 +188,10 @@ export default {
   },
   computed: {
     ...mapGetters({ loginInfo: 'getLoginInfo' }),
+    /** 有用户信息视为已登录;控制邀请按钮 open-type */
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo)
+    },
     account() {
       return (this.option && this.option.account) || uni.getStorageSync('account') || ''
     },
@@ -210,14 +222,13 @@ export default {
       return 0
     }
   },
-  // 监听登录态变化,驱动 wangCg 显隐(0 未登录弹层 / 1 已登录关闭)
+  // 登录成功后收起弹层;未登录不因 watch 强制弹出
   watch: {
     loginInfo: {
       deep: true,
       handler() {
         this.syncLoginStyle()
-      },
-      immediate: true
+      }
     }
   },
   onLoad(option) {
@@ -232,7 +243,7 @@ export default {
     }
     this.init()
   },
-  // 每次回到页面时同步登录态,避免从其他页登录回来仍显示登录弹层
+  // 已登录则收起弹层;未登录不主动弹出(避免单独购买浏览时挡页)
   onShow() {
     this.syncLoginStyle()
   },
@@ -251,9 +262,13 @@ export default {
     return this.buildSharePayload()
   },
   methods: {
-    /** 按登录态驱动 wangCg:0 未登录弹层 / 1 已登录关闭 */
+    /**
+     * 同步 wangCg:仅在已登录时强制关闭;未登录保持现状(由开团/参团/邀请 ensureLogin 弹出)
+     */
     syncLoginStyle() {
-      this.globalLoginStyle = this.$util.isEmpty(this.loginInfo) ? 0 : 1
+      if (!this.$util.isEmpty(this.loginInfo)) {
+        this.globalLoginStyle = 1
+      }
     },
     loginTo() {
       this.$util.pageTo({
@@ -271,7 +286,7 @@ export default {
      * 刷新详情数据(分享进页会回填 hdId,供开团/参团/支付使用)
      */
     loginSuccess() {
-      this.syncLoginStyle()
+      this.globalLoginStyle = 1
       this.init()
     },
     init() {
@@ -442,6 +457,7 @@ export default {
       this.$msg('活动已结束')
     },
     buyAlone() {
+      // 单独购买:直接进商品详情,不弹登录(详情页加购/购买再校验)
       const id = this.goods.goodsId || this.goodsId
       if (!id) return
       const hdId = this.resolveHdId()
@@ -499,15 +515,15 @@ export default {
       return path + sep + 'inviterCustomId=' + this.myCustomId
     },
     /**
-     * 开团/参团前校验登录:未登录则弹出 wangCg,不发请求
+     * 开团/参团/邀请前校验登录:未登录则弹出 wangCg,不发请求
      * @returns {boolean} 是否已登录可继续
      */
     ensureLogin() {
-      if (!this.$util.isEmpty(this.loginInfo) && this.globalLoginStyle === 1) {
+      if (!this.$util.isEmpty(this.loginInfo)) {
+        this.globalLoginStyle = 1
         return true
       }
       this.globalLoginStyle = 0
-      this.$msg('请先登录')
       return false
     },
     /** 我要开团:默认自取下单,成功后去待付款 */
@@ -612,6 +628,15 @@ export default {
       this.showShareMenu = true
       // #endif
     },
+    /**
+     * 邀请好友:未登录弹 wangCg;已登录走分享(小程序 open-type=share / 非小程序菜单)
+     */
+    onInviteClick() {
+      if (!this.ensureLogin()) {
+        return
+      }
+      this.prepareShare()
+    },
     onDirectShare() {
       this.showShareMenu = false
       this.setupShare()

+ 48 - 2
mallApp/src/pages/home/cart.vue

@@ -3,10 +3,21 @@
   用途:展示花材+花束购物车(Vuex cg 选型,property 区分),支持勾选、改数量、删除;去结算统一跳转 affirmMix
   顶部公告:/shop-notice/show-list position=4(购物车),公告条与分类页 shop-category 同款(noticeBar)
   滚动:页面原生滚动(不用 scroll-view),公告条 disableTouch,减少滑动卡顿
+  未登录:参考消息 Tab,空态提示并点击跳转登录,不请求需登录接口
   路由:pages/home/cart?account=xxx&hdId=xxx
 -->
 <template>
-  <view class="cart-page" :class="{ 'cart-page--has-checkout': displayCartList.length }">
+  <view
+    class="cart-page"
+    :class="{ 'cart-page--has-checkout': isLoggedIn && displayCartList.length }"
+    @tap="onGuestPageTap"
+  >
+    <block v-if="!isLoggedIn">
+      <view class="cart-empty">
+        <app-wrapper-empty title="登录后查看购物车" :is-empty="true" />
+      </view>
+    </block>
+    <block v-else>
     <!-- 使用页面原生滚动,避免 scroll-view + 竖向公告 swiper 抢手势导致滑动卡顿 -->
     <!-- 间距写在页面容器上:小程序自定义组件根节点 margin 不生效 -->
     <view class="cart-notice">
@@ -143,6 +154,7 @@
         </view>
       </template>
     </modal-module>
+    </block>
 
     <shop-tab-bar current="cart" :account="shopAccount" :hd-id="hdId" :cart-count="cartBadgeCount" />
   </view>
@@ -188,11 +200,17 @@ export default {
         bigCount: 0,
         smallCount: 0
       },
-      limitBuyWarnList: []
+      limitBuyWarnList: [],
+      /** 本页是否已完成过登录态下的 init(登录返回后可补 init) */
+      cartBooted: false
     }
   },
   computed: {
     ...mapGetters({ loginInfo: 'getLoginInfo' }),
+    /** 与消息 Tab 一致:有用户信息视为已登录 */
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo)
+    },
     /** 过滤无效项,避免 selectList 含空项导致 bigCount 报错 */
     displayCartList() {
       return this.sanitizeCartList(this.selectList)
@@ -262,6 +280,16 @@ export default {
     }
   },
   onShow() {
+    // 未登录不拉门店/购物车接口;登录返回后补 init
+    if (!this.isLoggedIn) {
+      this.cartBooted = false
+      return
+    }
+    if (!this.cartBooted) {
+      this.init()
+      this.cartBooted = true
+      return
+    }
     this.applyShopFromCache()
     this.cleanupCartList()
     this.restoreItemCartFromStorage()
@@ -269,6 +297,18 @@ export default {
     this.syncCheckedMap()
   },
   methods: {
+    /**
+     * 未登录点击任意区域:跳转登录页(对齐消息 Tab)
+     */
+    onGuestPageTap() {
+      if (this.isLoggedIn) {
+        return
+      }
+      this.pageTo({
+        url: '/pages/login/index',
+        query: { needBack: 1, account: this.shopAccount || uni.getStorageSync('account') || '' }
+      })
+    },
     /** 清洗购物车数组,去掉空项并规范数量字段 */
     sanitizeCartList(list) {
       if (!Array.isArray(list)) {
@@ -311,10 +351,16 @@ export default {
       if (this.hdId) {
         uni.setStorageSync('hdId', this.hdId)
       }
+      // 未登录不拉门店/购物车数据,避免需登录接口 toast
+      if (!this.isLoggedIn) {
+        this.cartBooted = false
+        return
+      }
       this.applyShopFromCache()
       this.restoreItemCartFromStorage()
       this.syncCheckedMap()
       this.loadShopInfo()
+      this.cartBooted = true
     },
     /** 从本地缓存恢复店铺名称,接口失败时仍能展示 */
     applyShopFromCache() {

+ 6 - 1
mallApp/src/pages/home/components/order-list-view.vue

@@ -37,7 +37,7 @@
         </view>
       </block>
       <block v-else>
-        <AppWrapperEmpty title="暂无数据" :is-empty="$util.isEmpty(list.data)" />
+        <AppWrapperEmpty :title="emptyTitle" :is-empty="$util.isEmpty(list.data)" />
       </block>
     </view>
 
@@ -83,6 +83,11 @@ export default {
     hdId: {
       type: [Number, String],
       default: 0
+    },
+    /** 空列表文案;订单 Tab 未登录可传「登录后查看订单」 */
+    emptyTitle: {
+      type: String,
+      default: "暂无数据"
     }
   },
   data() {

+ 88 - 16
mallApp/src/pages/home/course.vue

@@ -1,28 +1,40 @@
 <!--
   店铺内订单页(pages/home/course)
   用途:shop-tab-bar「订单」入口;锁定当前门店;列表逻辑在 order-list-view 内
+  未登录:参考原生订单 Tab(order.vue),仍展示状态 Tab + 空态文案,点击跳转登录,不请求订单接口
   参数:account、hdId(底栏透传并写入缓存)
 -->
 <template>
-  <order-list-view
-    ref="orderList"
-    mode="shop"
-    :hd-id="hdId"
-  >
-    <template #footer>
-      <shop-tab-bar
-        current="order"
-        :account="shopAccount"
-        :hd-id="hdId"
-        :cart-count="cartBadgeCount"
-      />
-    </template>
-  </order-list-view>
+  <view class="course-page">
+    <!-- 始终挂载列表,保证状态 Tab 可见;未登录不调 init,避免 toast -->
+    <order-list-view
+      ref="orderList"
+      mode="shop"
+      :hd-id="hdId"
+      :empty-title="isLoggedIn ? '暂无数据' : '登录后查看订单'"
+    >
+      <template #footer>
+        <shop-tab-bar
+          current="order"
+          :account="shopAccount"
+          :hd-id="hdId"
+          :cart-count="cartBadgeCount"
+        />
+      </template>
+    </order-list-view>
+
+    <!-- 未登录透明遮罩:挡住 Tab/列表点击并跳转登录,状态标签仍可见 -->
+    <view v-if="!isLoggedIn" class="guest-catch" @tap="goLoginFromGuest"></view>
+  </view>
 </template>
 
 <script>
+/**
+ * 店铺订单:已登录才拉列表;未登录保留状态 Tab,点击跳转登录页(对齐 order.vue)
+ */
 import OrderListView from "@/pages/home/components/order-list-view.vue";
 import ShopTabBar from "@/pages/home/components/shop-tab-bar.vue";
+import { mapGetters } from "vuex";
 
 export default {
   name: "shopCourseOrder",
@@ -35,23 +47,62 @@ export default {
       hdId: 0,
       shopAccount: "",
       cartBadgeCount: 0,
-      refreshPage: 0
+      refreshPage: 0,
+      /** 本页是否已完成过登录态下的 init(登录返回后可补 init) */
+      orderListBooted: false
     };
   },
+  computed: {
+    ...mapGetters({ loginInfo: "getLoginInfo" }),
+    /** 与「我的」页一致:vuex 有用户信息视为已登录 */
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo);
+    }
+  },
   onReachBottom() {
+    if (!this.isLoggedIn) {
+      return;
+    }
     this.$refs.orderList && this.$refs.orderList.loadMore();
   },
   onPullDownRefresh() {
+    if (!this.isLoggedIn) {
+      uni.stopPullDownRefresh();
+      return;
+    }
     this.$refs.orderList && this.$refs.orderList.pullRefresh();
   },
   onShow() {
     this.refreshCartBadgeCount();
+    // 未登录不拉订单接口,避免「请先登录哈」;状态 Tab 仍由列表组件渲染
+    if (!this.isLoggedIn) {
+      this.orderListBooted = false;
+      return;
+    }
     if (this.refreshPage == 1) {
       this.refreshPage = 0;
       this.$refs.orderList && this.$refs.orderList.pullRefresh();
+      return;
+    }
+    // 首次 onLoad 时可能尚未登录而跳过 init;登录后回到本页补初始化
+    if (!this.orderListBooted) {
+      this.init();
+      this.orderListBooted = true;
     }
   },
   methods: {
+    /**
+     * 未登录点击任意区域:跳转登录页(needBack 便于登录后返回本页)
+     */
+    goLoginFromGuest() {
+      this.pageTo({
+        url: "/pages/login/index",
+        query: {
+          needBack: 1,
+          account: this.shopAccount || uni.getStorageSync("account") || ""
+        }
+      });
+    },
     /**
      * 同步 account / hdId,保证底栏互切参数不断
      * @param {Object} query 路由参数
@@ -88,10 +139,13 @@ export default {
       });
       this.cartBadgeCount = count;
     },
-    /** 页面 init:同步店铺参数后初始化列表组件 */
+    /** 页面 init:同步店铺参数后初始化列表组件;未登录直接跳过 */
     init() {
       this.applyShopParams(this.option || {});
       this.refreshCartBadgeCount();
+      if (!this.isLoggedIn) {
+        return;
+      }
       this.$nextTick(() => {
         this.$refs.orderList && this.$refs.orderList.init();
       });
@@ -99,3 +153,21 @@ export default {
   }
 };
 </script>
+
+<style lang="scss" scoped>
+.course-page {
+  min-height: 100vh;
+  background: #f7f7f7;
+}
+
+/* 挡住列表交互,状态 Tab 仍可见(z-index 低于底栏) */
+.guest-catch {
+  position: fixed;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: $shopTabBarTotalHeight;
+  z-index: 200;
+  background: transparent;
+}
+</style>

+ 53 - 31
mallApp/src/pages/home/index.vue

@@ -4,6 +4,8 @@
   顶部导航与搜索、轮播图、公告、金刚区导航、秒杀专区、团购专区、热门推荐、今日上新、下拉商品。
   数据来自 home-page-config 接口的 getHome,与 hdApp 后台「预览」使用同一套后端组装逻辑,
   保证顾客看到的效果与商家预览一致。
+  未登录可浏览首页:默认不弹登录层;加购 / 客服 / 消息入口再弹 wangCg。
+  浏览类接口不强制登录(避免「请先登录哈」toast)。
 -->
 <template>
   <view class="app-main app-content">
@@ -26,6 +28,7 @@
             :data="pageData.topNav"
             :account="account"
             :hd-id="hdId"
+            @require-login="showCartLoginPopup"
           />
           <!-- 轮播+公告整块白底,与页面灰底区分 -->
           <view v-else-if="mod.key === 'banner'" class="home-banner-block">
@@ -37,10 +40,12 @@
               :service-wx="bannerServiceWx"
               :custom-id="myCustomId"
               :shop-info="shopInfo"
+              @require-login="showCartLoginPopup"
             />
             <!-- 间距写在页面容器上,避免组件根节点 margin 在小程序不生效 -->
             <view class="home-notice-wrap">
-              <home-notice-bar :account="account" :flush="true" />
+              <!-- 公告接口需登录;未登录不请求,避免「请先登录哈」toast -->
+              <home-notice-bar v-if="isLoggedIn" :account="account" :flush="true" />
             </view>
           </view>
           <home-nav-grid
@@ -70,6 +75,7 @@
             :account="account"
             :hd-id="hdId"
             @cart-changed="refreshCartBadgeCount"
+            @require-login="showCartLoginPopup"
           />
           <home-goods-section
             v-else-if="mod.key === 'new'"
@@ -78,6 +84,7 @@
             :account="account"
             :hd-id="hdId"
             @cart-changed="refreshCartBadgeCount"
+            @require-login="showCartLoginPopup"
           />
           <!-- 下拉商品后端不截断,用 limit 做前端分页,滚动接近底部再追加 -->
           <home-goods-section
@@ -88,6 +95,7 @@
             :account="account"
             :hd-id="hdId"
             @cart-changed="refreshCartBadgeCount"
+            @require-login="showCartLoginPopup"
           />
         </template>
       </block>
@@ -100,14 +108,18 @@
       :hd-id="hdId"
       :cart-count="cartBadgeCount"
     />
-    <!-- 新红包仅提醒一次,用户操作后由组件回写已通知状态 -->
+    <!-- 新红包仅提醒一次,用户操作后由组件回写已通知状态;未登录不查不弹 -->
     <app-hb-arrival
       :show.sync="hbArrivalShow"
       :info="hbArrivalInfo"
       :hb-ids="hbArrivalIds"
     />
-    <!-- 未登录弹出手机号一键登录(与分类页 shop-category 一致) -->
-    <wangCg :loginStyle.sync="globalLoginStyle" @goToLogin="loginTo" @loginSuccess="loginSuccess" />
+    <!-- 加购时按需弹出:loginStyle=0 显示,默认 1 隐藏,不影响浏览 -->
+    <wangCg
+      :loginStyle.sync="cartLoginStyle"
+      @goToLogin="goLoginPage"
+      @loginSuccess="onCartLoginSuccess"
+    />
   </view>
 </template>
 
@@ -157,6 +169,8 @@ export default {
     return {
       loading: true,
       pageData: null,
+      /** wangCg:1=隐藏,0=显示;仅加购未登录时置 0 */
+      cartLoginStyle: 1,
       /** 门店信息,用于补充 merchantName(get-home 未返回时兜底) */
       shopInfo: null,
       /** 底部 tab 购物车角标数量(本地缓存非响应式,需 onShow 主动刷新) */
@@ -187,20 +201,21 @@ export default {
   },
   /** 从分类/购物车返回时刷新角标,避免仍显示旧数量 */
   onShow() {
-    this.syncLoginStyle()
     this.refreshCartBadgeCount()
-    // 首次进入等待 init 刷新门店信息,避免沿用上一家门店缓存查询错红包。
-    if (this.pageData && this.shopInfo && this.shopInfo.id) {
+    // 已登录则收起加购登录层
+    if (this.isLoggedIn) {
+      this.cartLoginStyle = 1
+    }
+    // 首次进入等待 init 刷新门店信息;未登录不查红包,避免接口 toast
+    if (this.isLoggedIn && this.pageData && this.shopInfo && this.shopInfo.id) {
       this.checkHbArrival()
     }
   },
   watch: {
-    loginInfo: {
-      deep: true,
-      handler() {
-        this.syncLoginStyle()
-      },
-      immediate: true
+    loginInfo(val) {
+      if (!this.$util.isEmpty(val)) {
+        this.cartLoginStyle = 1
+      }
     }
   },
   onShareAppMessage() {
@@ -212,6 +227,10 @@ export default {
   },
   computed: {
     ...mapGetters({ loginInfo: 'getLoginInfo' }),
+    /** 有 vuex 用户信息视为已登录;首页未登录可浏览,不弹登录层 */
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo)
+    },
     /** 商户名称:优先 get-home.topNav,其次 /shop/info 与本地缓存 */
     merchantName() {
       const topNav = this.pageData && this.pageData.topNav
@@ -266,6 +285,24 @@ export default {
     }
   },
   methods: {
+    /**
+     * 未登录需授权时:弹出手机号一键登录层(加购 / 客服 / 消息)
+     */
+    showCartLoginPopup() {
+      this.cartLoginStyle = 0
+    },
+    /** wangCg 备用跳转:进入登录页(needBack 便于返回首页) */
+    goLoginPage() {
+      this.$util.pageTo({
+        url: '/pages/login/index',
+        query: { needBack: 1, account: this.account }
+      })
+    },
+    /** 加购登录成功:收起弹层并刷新购物车角标 */
+    onCartLoginSuccess() {
+      this.cartLoginStyle = 1
+      this.refreshCartBadgeCount()
+    },
     /**
      * 页面初始化
      * 首屏只等 getHome:门店信息(syncMyCustomId)仅供分享参数与红包提醒使用,
@@ -293,7 +330,7 @@ export default {
           // 必须等 loading 关掉、模块真正上屏后再观察,否则查不到底部哨兵节点
           this.initLoadMoreObserver()
         })
-      // 红包提醒依赖 shopInfo.id,必须等门店信息就绪后再查
+      // 红包提醒依赖登录态与 shopInfo.id;未登录不请求,避免「请先登录哈」
       this.syncMyCustomId()
         .then(() => {
           this.checkHbArrival()
@@ -359,21 +396,6 @@ export default {
         this.initLoadMoreObserver()
       }
     },
-    /** 按登录态驱动 wangCg:0 未登录弹层 / 1 已登录关闭 */
-    syncLoginStyle() {
-      this.globalLoginStyle = this.$util.isEmpty(this.loginInfo) ? 0 : 1
-    },
-    loginTo() {
-      this.$util.pageTo({
-        url: '/pages/login/index',
-        query: { needBack: 1, account: this.account }
-      })
-    },
-    /** 登录成功后刷新首页与门店客户信息 */
-    loginSuccess() {
-      this.syncLoginStyle()
-      this.init()
-    },
     /**
      * 读取花材购物车本地缓存,计算底部 tab 角标
      * 缓存 key 与分类页/订单页一致:selectListcg_hd_{account}
@@ -436,10 +458,10 @@ export default {
     },
     /**
      * 查询当前门店的新到账红包。
-     * 必须在门店信息就绪后请求,避免 shopId 缺失导致提醒到其他门店的红包
+     * 必须登录且门店信息就绪;未登录不请求,避免全局 toast「请先登录哈」
      */
     checkHbArrival() {
-      if (this.hbArrivalLoading) {
+      if (!this.isLoggedIn || this.hbArrivalLoading) {
         return Promise.resolve()
       }
       const shopId = Number(this.shopInfo && this.shopInfo.id) || Number(this.hdId) || 0

+ 74 - 40
mallApp/src/pages/home/message.vue

@@ -1,48 +1,55 @@
 <!--
   商城「消息」Tab 页:聊天会话列表
   谁用:底部 tabBar「消息」入口
-  解决:展示用户近期聊天门店列表,点击后进入 chatPage 会话详情
+  解决:展示用户近期聊天门店列表,点击后进入 chatPage 会话详情;
+  未登录不请求需登录接口(避免「请先登录哈」toast),点击任意区域跳转登录页
 -->
 <template>
-  <view class="app-main app-content">
-    <view class="list-wrap">
-      <block v-if="$util.isEmpty(list.data) === false">
-        <block v-for="(item, index) in list.data" :key="index">
-          <tui-list-cell :arrow="true" @click="handleItemClick(item)">
-            <view class="user-card">
-              <view class="user-avatar">
-                <image
-                  :src="item.avatar || getDefaultImage()"
-                  class="avatar-img"
-                  mode="aspectFill"
-                />
-              </view>
-              <view class="user-info">
-                <view class="user-main">
-                  <!-- 首店用商户名/手机号兜底,避免显示无意义的「首店」 -->
-                  <view class="user-name">{{
-                    item.shopName != "首店"
-                      ? item.shopName
-                      : item.merchantName || item.mobile
-                  }}</view>
+  <view class="app-main app-content" @tap="onGuestPageTap">
+    <!-- 未登录:不拉列表,整页可点进登录(对齐 user.vue requireLogin) -->
+    <block v-if="!isLoggedIn">
+      <app-wrapper-empty title="登录后查看消息" :is-empty="true" />
+    </block>
+    <block v-else>
+      <view class="list-wrap">
+        <block v-if="$util.isEmpty(list.data) === false">
+          <block v-for="(item, index) in list.data" :key="index">
+            <tui-list-cell :arrow="true" @click="handleItemClick(item)">
+              <view class="user-card">
+                <view class="user-avatar">
+                  <image
+                    :src="item.avatar || getDefaultImage()"
+                    class="avatar-img"
+                    mode="aspectFill"
+                  />
                 </view>
-                <view class="user-meta">
-                  <view class="visit-time">
-                    {{ formatChatTime(item.chatTime) }}
+                <view class="user-info">
+                  <view class="user-main">
+                    <!-- 首店用商户名/手机号兜底,避免显示无意义的「首店」 -->
+                    <view class="user-name">{{
+                      item.shopName != "首店"
+                        ? item.shopName
+                        : item.merchantName || item.mobile
+                    }}</view>
+                  </view>
+                  <view class="user-meta">
+                    <view class="visit-time">
+                      {{ formatChatTime(item.chatTime) }}
+                    </view>
                   </view>
                 </view>
+                <view v-if="item.unReadNum > 0" class="unread-badge">
+                  {{ item.unReadNum > 9 ? '9+' : item.unReadNum }}
+                </view>
               </view>
-              <view v-if="item.unReadNum > 0" class="unread-badge">
-                {{ item.unReadNum > 9 ? '9+' : item.unReadNum }}
-              </view>
-            </view>
-          </tui-list-cell>
+            </tui-list-cell>
+          </block>
         </block>
-      </block>
-      <block v-else>
-        <app-wrapper-empty :is-empty="true" />
-      </block>
-    </view>
+        <block v-else>
+          <app-wrapper-empty :is-empty="true" />
+        </block>
+      </view>
+    </block>
   </view>
 </template>
 <script>
@@ -86,33 +93,57 @@ export default {
     this.updateDateStrings();
   },
   onPullDownRefresh() {
+    if (!this.isLoggedIn) {
+      uni.stopPullDownRefresh();
+      return;
+    }
     this.resetList();
     this.getMyShopList().then(() => {
       uni.stopPullDownRefresh();
     });
   },
   onReachBottom() {
+    if (!this.isLoggedIn) {
+      return;
+    }
     if (!this.list.finished) {
       this.getMyShopList();
     }
   },
   computed: {
-    ...mapGetters(["getLoginInfo"]),
+    ...mapGetters({ loginInfo: "getLoginInfo" }),
+    /** 与「我的」页一致:vuex 有用户信息视为已登录 */
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo);
+    },
   },
   onLoad() {
     // 当前登录用户头像:用于进入聊天页时作为自己的头像
-    this.shopAvatar = this.getLoginInfo.avatar != ''
-      ? this.getLoginInfo.avatar
+    this.shopAvatar = this.loginInfo && this.loginInfo.avatar
+      ? this.loginInfo.avatar
       : this.$constant.imgUrl + '/hhb_small.png?x-oss-process=image/resize,m_fill,h_130,w_130';
   },
   onShow() {
     // 从店铺订单 Tab 返回时恢复原生底栏,并清除店铺导航标记
     uni.removeStorageSync(SHOP_NAV_ACTIVE_KEY);
     uni.showTabBar({ animation: false });
+    // 未登录不请求会话列表,避免接口返回「请先登录哈」被全局 toast
+    if (!this.isLoggedIn) {
+      return;
+    }
     // Tab 页保活,每次切回消息 Tab 都刷新会话列表与未读数
     this.getMyShopList();
   },
   methods: {
+    /**
+     * 未登录时点击页面任意区域:跳转登录页(needBack 便于登录后返回本 Tab)
+     */
+    onGuestPageTap() {
+      if (this.isLoggedIn) {
+        return;
+      }
+      this.pageTo({ url: "/pages/login/index?needBack=1" });
+    },
     /**
      * 点击会话项:本地清未读,经 eventChannel 打开 chatPage
      * @param {Object} item 列表项,含 customId/id/shopName/unReadNum 等
@@ -125,8 +156,8 @@ export default {
       const params = {
         customId: item.customId,
         shopId: item.id,
-        name: this.getLoginInfo.name,
-        avatar: this.getLoginInfo.avatar,
+        name: this.loginInfo.name,
+        avatar: this.loginInfo.avatar,
         chatPerson: item.shopName != "首店" ? item.shopName : item.merchantName || item.mobile,
       };
       uni.navigateTo({
@@ -143,6 +174,9 @@ export default {
      * @returns {Promise} 接口 Promise,供下拉刷新 await
      */
     getMyShopList() {
+      if (!this.isLoggedIn) {
+        return Promise.resolve();
+      }
       return getLatestUsers({}).then(res => {
         if (res.code == 1) {
           this.list.data = res.data;

+ 74 - 3
mallApp/src/pages/home/order.vue

@@ -1,14 +1,17 @@
 <!--
   原生 Tab「订单」页
   用途:跨店订单列表;店铺筛选为本页独有,列表逻辑在 order-list-view 内
+  未登录仍展示订单状态 Tab,但不请求接口;点击任意区域跳转登录页(对齐 user.vue)
 -->
 <template>
-  <view>
+  <view class="order-page">
+    <!-- 始终挂载列表,保证状态 Tab 可见;未登录不调 init,避免 toast -->
     <order-list-view
       ref="orderList"
       mode="tab"
       :has-filter="true"
       :hd-id="hdId"
+      :empty-title="isLoggedIn ? '暂无数据' : '登录后查看订单'"
     >
       <template #filter>
         <view class="filter-bar">
@@ -48,13 +51,20 @@
         </scroll-view>
       </view>
     </tui-bottom-popup>
+
+    <!-- 未登录透明遮罩:挡住 Tab/列表点击并跳转登录,状态标签仍可见 -->
+    <view v-if="!isLoggedIn" class="guest-catch" @tap="goLoginFromGuest"></view>
   </view>
 </template>
 
 <script>
+/**
+ * 订单 Tab:已登录才拉店铺筛选与订单列表;未登录保留状态 Tab,点击跳转登录页
+ */
 import OrderListView from "@/pages/home/components/order-list-view.vue";
 import TuiBottomPopup from "@/components/plugin/bottom-popup.vue";
 import { getList as getHdList } from "@/api/hd";
+import { mapGetters } from "vuex";
 
 export default {
   name: "homeOrder",
@@ -70,21 +80,41 @@ export default {
       storeList: [],
       /** 详情页返回时由对方置 1 */
       refreshPage: 0,
-      pendingStatus: ""
+      pendingStatus: "",
+      /** 本页是否已完成过登录态下的 init(登录返回后可补 init) */
+      orderListBooted: false
     };
   },
   computed: {
+    ...mapGetters({ loginInfo: "getLoginInfo" }),
+    /** 与「我的」页一致:vuex 有用户信息视为已登录 */
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo);
+    },
     storeOptions() {
       return [{ id: 0, name: "全部店铺" }].concat(this.storeList);
     }
   },
   onReachBottom() {
+    if (!this.isLoggedIn) {
+      return;
+    }
     this.$refs.orderList && this.$refs.orderList.loadMore();
   },
   onPullDownRefresh() {
+    if (!this.isLoggedIn) {
+      uni.stopPullDownRefresh();
+      return;
+    }
     this.$refs.orderList && this.$refs.orderList.pullRefresh();
   },
   onShow() {
+    // 未登录不拉订单/店铺接口,避免「请先登录哈」;状态 Tab 仍由列表组件渲染
+    if (!this.isLoggedIn) {
+      this.orderListBooted = false;
+      return;
+    }
+
     const tabQuery = uni.getStorageSync("switchTabQuery");
     let queryUpdated = false;
     if (tabQuery) {
@@ -94,13 +124,27 @@ export default {
     }
     if (queryUpdated) {
       this.init();
+      this.orderListBooted = true;
+      return;
     }
     if (this.refreshPage == 1) {
       this.refreshPage = 0;
       this.$refs.orderList && this.$refs.orderList.pullRefresh();
+      return;
+    }
+    // 首次 onLoad 时可能尚未登录而跳过 init;登录后回到本 Tab 补初始化
+    if (!this.orderListBooted) {
+      this.init();
+      this.orderListBooted = true;
     }
   },
   methods: {
+    /**
+     * 未登录点击任意区域:跳转登录页(needBack 便于登录后返回订单 Tab)
+     */
+    goLoginFromGuest() {
+      this.pageTo({ url: "/pages/login/index?needBack=1" });
+    },
     /** 合并 switchTab / 路由 query */
     applyPageQuery(query) {
       if (!query || typeof query !== "object") {
@@ -118,6 +162,9 @@ export default {
       }
     },
     loadStoreList() {
+      if (!this.isLoggedIn) {
+        return Promise.resolve();
+      }
       return getHdList({ page: 1, pageSize: 100 }).then((res) => {
         if (res.code !== 1) {
           return;
@@ -136,6 +183,10 @@ export default {
       }
     },
     openStorePopup() {
+      if (!this.isLoggedIn) {
+        this.goLoginFromGuest();
+        return;
+      }
       this.storePopupShow = true;
       if (this.$util.isEmpty(this.storeList)) {
         this.loadStoreList();
@@ -153,8 +204,11 @@ export default {
       this.hdName = this.hdId ? (item.name || "") : "";
       this.storePopupShow = false;
     },
-    /** 页面 init:解析筛选项后交给列表组件 */
+    /** 页面 init:解析筛选项后交给列表组件;未登录直接跳过 */
     init() {
+      if (!this.isLoggedIn) {
+        return;
+      }
       this.applyPageQuery(this.option || {});
       const opt = this.option || {};
       const optionHdId = Number(opt.hdId || opt.id || opt.ghsId || 0);
@@ -167,6 +221,7 @@ export default {
         if (this.$refs.orderList) {
           this.$refs.orderList.init({ status: this.pendingStatus });
           this.pendingStatus = "";
+          this.orderListBooted = true;
         }
       });
     }
@@ -175,6 +230,22 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+.order-page {
+  position: relative;
+  min-height: 100%;
+}
+
+/* 未登录拦截层:盖住内容区点击,状态 Tab 仍透出可见 */
+.guest-catch {
+  position: fixed;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  z-index: 200;
+  background: transparent;
+}
+
 .filter-bar {
   position: fixed;
   left: 0;

+ 99 - 33
mallApp/src/pages/home/shop-category.vue

@@ -2,6 +2,7 @@
   店铺分类页(合并花材 + 花束)
   用途:商城消费者在一个页面浏览花材/花束分类、公告、广告与商品;
   顶部搜索框点搜索/键盘确认后跳转商品列表页,并带上 searchText 关键词
+  未登录:可浏览分类与商品列表,不弹登录;公告详情/列表、加购与去结算弹 wangCg
   路由:pages/home/shop-category?account=xxx&hdId=xxx
 -->
 <template>
@@ -230,10 +231,10 @@
                 :offVerifyRepertory="true"
                 :bigCount="getSelectItemById(productItem.id, activeItemClassId).bigCount"
                 :smallCount="getSelectItemById(productItem.id, activeItemClassId).smallCount"
-                @customNum="customNum"
-                @add="addEvent"
-                @addOneEvent="addCustomNumEvent"
-                @del="delEvent"
+                @customNum="onItemCustomNum"
+                @add="onItemAdd"
+                @addOneEvent="onItemAddOne"
+                @del="onItemDel"
                 @showBigCover="showItemBigCover"
               />
             </view>
@@ -332,18 +333,23 @@
     </view>
 
     <shop-tab-bar current="category" :account="shopAccount" :hd-id="hdId" :cart-count="cartBadgeCount" />
-    <wangCg :loginStyle.sync="globalLoginStyle" @goToLogin="loginTo" @loginSuccess="loginSuccess" />
+    <!-- 加购/结算/公告等需登录时按需弹出;默认隐藏不影响浏览 -->
+    <wangCg
+      :loginStyle.sync="cartLoginStyle"
+      @goToLogin="goLoginPage"
+      @loginSuccess="onCartLoginSuccess"
+    />
   </view>
 </template>
 
 <script>
 import { mapGetters } from 'vuex'
-import wangCg from '@/components/wangCg'
 import AppWrapperEmpty from '@/components/app-wrapper-empty'
 import ShopTabBar from '@/pages/home/components/shop-tab-bar.vue'
 import Commondity from '@/pages/item/components/subItem'
 import FooterCart from '@/components/item/module/app-footer-cart'
 import ModalModule from '@/components/item/plugin/modal'
+import wangCg from '@/components/wangCg'
 import allMoreSelectInputMins from '@/mixins/allMoreSelectInput'
 import productMins from '@/mixins/cgProduct'
 import { getClass, getList } from '@/api/category'
@@ -361,12 +367,12 @@ const NOTICE_POSITION_CATEGORY = 2
 export default {
   name: 'shopCategory',
   components: {
-    wangCg,
     AppWrapperEmpty,
     ShopTabBar,
     Commondity,
     FooterCart,
-    ModalModule
+    ModalModule,
+    wangCg
   },
   mixins: [list, share, productMins, allMoreSelectInputMins],
   data() {
@@ -376,6 +382,8 @@ export default {
       isGhsProduct: true,
       isFocus: false,
       isModel: false,
+      /** wangCg:1=隐藏,0=显示;加购等操作未登录时置 0 */
+      cartLoginStyle: 1,
       customData: {
         bigCount: 0,
         smallCount: 0
@@ -465,6 +473,18 @@ export default {
     modalTitle() {
       const name = this.customData && this.customData.itemName
       return name ? String(name) : ''
+    },
+    /** 有 vuex 用户信息视为已登录;未登录可浏览分类,不弹登录层 */
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo)
+    }
+  },
+  watch: {
+    /** 登录成功后自动收起加购登录层 */
+    loginInfo(val) {
+      if (!this.$util.isEmpty(val)) {
+        this.cartLoginStyle = 1
+      }
     }
   },
   onShareAppMessage() {
@@ -478,18 +498,6 @@ export default {
       path
     }
   },
-  onShow() {
-    this.syncLoginStyle()
-  },
-  watch: {
-    loginInfo: {
-      deep: true,
-      handler() {
-        this.syncLoginStyle()
-      },
-      immediate: true
-    }
-  },
   methods: {
     init() {
       this.hdId = this.option.hdId
@@ -509,8 +517,55 @@ export default {
         this.loadPageData()
       })
     },
-    syncLoginStyle() {
-      this.globalLoginStyle = this.$util.isEmpty(this.loginInfo) ? 0 : 1
+    /**
+     * 未登录则弹出手机号登录层(wangCg),不跳转登录页以免打断浏览
+     * @returns {boolean} true=已登录可继续
+     */
+    requireLogin() {
+      if (this.isLoggedIn) {
+        return true
+      }
+      this.cartLoginStyle = 0
+      return false
+    },
+    /** wangCg 备用:进入登录页 */
+    goLoginPage() {
+      this.$util.pageTo({
+        url: '/pages/login/index',
+        query: { needBack: 1, account: this.shopAccount }
+      })
+    },
+    /** 登录成功后收起弹层 */
+    onCartLoginSuccess() {
+      this.cartLoginStyle = 1
+    },
+    /** 花材加购:未登录先弹登录层 */
+    onItemAdd(info) {
+      if (!this.requireLogin()) {
+        return
+      }
+      this.addEvent(info)
+    },
+    /** 花材自定义数量加购入口 */
+    onItemAddOne(info, params) {
+      if (!this.requireLogin()) {
+        return
+      }
+      this.addCustomNumEvent(info, params)
+    },
+    /** 花材减购也需登录,避免未登录改本地车 */
+    onItemDel(info) {
+      if (!this.requireLogin()) {
+        return
+      }
+      this.delEvent(info)
+    },
+    /** 打开扎数弹窗前校验登录 */
+    onItemCustomNum(info) {
+      if (!this.requireLogin()) {
+        return
+      }
+      this.customNum(info)
     },
     /**
      * 同步搜索框内容(不用 v-model,与首页 topNav 一致,避免小程序端取值不稳)
@@ -736,6 +791,9 @@ export default {
       this.currentNoticeIndex = e.detail.current || 0
     },
     openNoticeDetail() {
+      if (!this.requireLogin()) {
+        return
+      }
       const item = this.noticeList[this.currentNoticeIndex]
       if (!item || !item.id) return
       this.$util.pageTo({
@@ -745,6 +803,9 @@ export default {
     },
     /** 点击右侧 ›:进入公告列表(全部上架公告) */
     openNoticeList() {
+      if (!this.requireLogin()) {
+        return
+      }
       this.$util.pageTo({
         url: '/pages/home/shop-notice-list',
         query: {
@@ -813,10 +874,14 @@ export default {
       if (!item || !item.id) {
         return
       }
+      // 多规格先进详情,浏览不强制登录
       if (Number(item.goodsStyle) === 1 || Number(item.specEnabled) === 1) {
         this.toBouquetDetail(item)
         return
       }
+      if (!this.requireLogin()) {
+        return
+      }
       if (Number(item.priceType) === 0) {
         this.$msg('请向商家咨询价格')
         return
@@ -874,18 +939,27 @@ export default {
       return `超出限购${info.currentBuyNum + info.exceedNum}份`
     },
     addCurrent(info) {
+      if (!this.requireLogin()) {
+        return
+      }
       if (info.$orig) {
         info = info.$orig
       }
       this.addEvent(info)
     },
     delCurrent(info) {
+      if (!this.requireLogin()) {
+        return
+      }
       if (info.$orig) {
         info = info.$orig
       }
       this.delEvent(info)
     },
     customUpdate(info) {
+      if (!this.requireLogin()) {
+        return
+      }
       if (info.$orig) {
         info = info.$orig
       }
@@ -945,6 +1019,9 @@ export default {
     },
     /** 选好了:校验限购后跳转确认页 */
     confirmSelectEvent() {
+      if (!this.requireLogin()) {
+        return
+      }
       if (!this.selectList.length) {
         this.$msg('请选择花材')
         return
@@ -1017,17 +1094,6 @@ export default {
       if (pre > 0) return this.formatPrice(pre)
       if (sk > 0) return this.formatPrice(sk)
       return '0'
-    },
-    loginTo() {
-      this.$util.pageTo({
-        url: '/pages/login/index',
-        query: { needBack: 1, account: this.shopAccount }
-      })
-    },
-    loginSuccess() {
-      this.loadShopInfo().then(() => {
-        this.loadPageData()
-      })
     }
   }
 }