2 Commits 4f6ddf4c43 ... e76f36caa3

Author SHA1 Message Date
  shizhongqi e76f36caa3 feat(shop-notice): 接入商城首页公告轮播 3 days ago
  shizhongqi 5e2f39c4ad fix(group-buy): 完善拼团分享登录与门店上下文 3 days ago

+ 1 - 1
mallApp/src/api/shop-notice/index.js

@@ -1,6 +1,6 @@
 import https from '@/plugins/luch-request_0.0.7/request'
 
-/** 客户端公告列表(分类页 position=2) */
+/** 客户端公告列表(首页 position=1 / 分类页 position=2) */
 export const shopNoticeShowList = data => {
 	return https.get('/shop-notice/show-list', data)
 }

+ 84 - 5
mallApp/src/components/home/noticeBar.vue

@@ -1,19 +1,91 @@
 <!--
   店铺首页-公告条
-  纯静态样式展示,暂无后台配置开关;文案先写死,后续如需可再接配置
+  拉取 hdApp「店铺公告」中展示位置为「商城首页」(position=1) 的上架公告;
+  同位置多条时每 6 秒纵向轮播,点击当前条跳转公告详情。
 -->
 <template>
-  <view class="home-notice-bar">
+  <view
+    class="home-notice-bar"
+    v-if="noticeList.length"
+    @tap.stop="openNoticeDetail"
+  >
     <image class="notice-icon" src="/static/images/home/icon-notice-horn.svg" mode="aspectFit" />
     <text class="notice-tag">公告</text>
-    <text class="notice-text">每月11号会员日,会员全场8.8折</text>
+    <swiper
+      class="notice-swiper"
+      :key="'home-notice-' + noticeList.length"
+      vertical
+      :autoplay="noticeList.length > 1"
+      :circular="noticeList.length > 1"
+      :interval="6000"
+      :duration="400"
+      :skip-hidden-item-layout="true"
+      @change="onNoticeChange"
+    >
+      <swiper-item v-for="item in noticeList" :key="item.id">
+        <text class="notice-text">{{ item.summary }}</text>
+      </swiper-item>
+    </swiper>
     <text class="notice-arrow">›</text>
   </view>
 </template>
 
 <script>
+import { shopNoticeShowList } from '@/api/shop-notice'
+
+/** 公告展示位置:商城首页(与 hdApp shopNotice/add positionOptions 一致) */
+const NOTICE_POSITION_HOME = 1
+
 export default {
-  name: 'homeNoticeBar'
+  name: 'homeNoticeBar',
+  props: {
+    /** 店铺 account,跳转详情时带入,保证详情页请求落到正确门店 */
+    account: {
+      type: [String, Number],
+      default: ''
+    }
+  },
+  data() {
+    return {
+      noticeList: [],
+      currentNoticeIndex: 0
+    }
+  },
+  created() {
+    this.loadNoticeList()
+  },
+  methods: {
+    /**
+     * 拉取商城首页公告列表
+     * 无数据时组件自隐藏,避免空条占位
+     */
+    loadNoticeList() {
+      return shopNoticeShowList({ position: NOTICE_POSITION_HOME }).then((res) => {
+        const data = res.data || {}
+        this.noticeList = data.list || []
+        this.currentNoticeIndex = 0
+      }).catch(() => {
+        this.noticeList = []
+      })
+    },
+    /** 记录当前轮播下标,点击时打开对应公告详情 */
+    onNoticeChange(e) {
+      this.currentNoticeIndex = e.detail.current || 0
+    },
+    /**
+     * 跳转公告详情页
+     * 与分类页 notice-bar 一致,走 shop-notice-detail
+     */
+    openNoticeDetail() {
+      const item = this.noticeList[this.currentNoticeIndex]
+      if (!item || !item.id) return
+      const account = this.account || uni.getStorageSync('account') || ''
+      this.$util.pageTo({
+        url: '/pages/home/shop-notice-detail',
+        query: { id: item.id, account }
+      })
+    }
+  }
 }
 </script>
 
@@ -27,6 +99,7 @@ export default {
   border-radius: 32upx;
   background: #FFF0F3;
   box-shadow: 0 2upx 12upx rgba(255, 77, 125, 0.1);
+  box-sizing: border-box;
 }
 .notice-icon {
   flex-shrink: 0;
@@ -46,11 +119,17 @@ export default {
   border-radius: 6upx;
   box-sizing: border-box;
 }
-.notice-text {
+.notice-swiper {
   flex: 1;
+  width: 0;
+  height: 40upx;
   min-width: 0;
+}
+.notice-text {
+  display: block;
   font-size: 24upx;
   color: #FF4D7D;
+  line-height: 40upx;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;

+ 1 - 1
mallApp/src/pages.json

@@ -10,7 +10,7 @@
         { "path": "pages/home/category", "style": { "navigationBarTitleText": "花束" } },
         { "path": "pages/home/shop-category", "style": { "navigationBarTitleText": "分类" } },
         { "path": "pages/home/cart", "style": { "navigationBarTitleText": "购物车", "navigationBarTextStyle": "black" } },
-        { "path": "pages/home/shop-notice-detail", "style": { "navigationBarTitleText": "公告详情" } },
+        { "path": "pages/home/shop-notice-detail", "style": { "navigationBarTitleText": "公告" } },
         { "path": "pages/home/pic-text-detail", "style": { "navigationBarTitleText": "图文详情" } },
         { "path": "pages/home/mall", "style": { "navigationBarTitleText": "相册" } },
         { "path": "pages/home/shop", "style": { "navigationBarTitleText": "门店" } },

+ 71 - 9
mallApp/src/pages/groupBuy/detail.vue

@@ -4,6 +4,7 @@
   展示商品信息、当前拼团卡片、开团玩法、底部开团/参团/分享操作
 -->
 <template>
+  <view class="group-buy-detail-page">
   <view class="group-buy-detail" v-if="loaded">
     <!-- 商品图 -->
     <view class="hero">
@@ -103,16 +104,22 @@
       </view>
     </view>
   </view>
+
+  <!-- 未登录时弹出手机号登录/注册(与花材页一致;置于 loaded 外,加载中也能弹出) -->
+  <wangCg :loginStyle.sync="globalLoginStyle" @loginSuccess="loginSuccess()"></wangCg>
+  </view>
 </template>
 
 <script>
 /**
  * 团购详情页
  * 入参:goodsId(首页进入)或 id(分享进入的 groupBuyId)
- * 开团/参团成功后跳转待付款页
+ * 开团/参团成功后跳转待付款页;未登录时通过 wangCg 引导登录/注册
  */
+import { mapGetters } from 'vuex'
 import CountDown from '@/components/CountDown.vue'
 import GroupAvatarStack from '@/components/GroupAvatarStack.vue'
+import wangCg from '@/components/wangCg'
 import share from '@/mixins/share'
 import {
   getGoodsLanding,
@@ -124,7 +131,7 @@ import { GROUP_BUY_HD_NOT_BOUND } from '@/constant/errCode'
 
 export default {
   name: 'GroupBuyDetail',
-  components: { CountDown, GroupAvatarStack },
+  components: { CountDown, GroupAvatarStack, wangCg },
   mixins: [share],
   data() {
     return {
@@ -147,12 +154,10 @@ export default {
     }
   },
   computed: {
+    ...mapGetters({ loginInfo: 'getLoginInfo' }),
     account() {
       return (this.option && this.option.account) || uni.getStorageSync('account') || ''
     },
-    hdId() {
-      return (this.option && this.option.hdId) || uni.getStorageSync('hdId') || ''
-    },
     swiperList() {
       const cover = this.goods.coverUrl || this.goods.cover
       const list = []
@@ -168,6 +173,20 @@ export default {
       return 0
     }
   },
+  // 监听登录态变化,驱动 wangCg 显隐(0 未登录弹层 / 1 已登录关闭)
+  watch: {
+    loginInfo: {
+      deep: true,
+      handler: function (newVal) {
+        if (!this.$util.isEmpty(newVal)) {
+          this.globalLoginStyle = 1
+        } else {
+          this.globalLoginStyle = 0
+        }
+      },
+      immediate: true
+    }
+  },
   onLoad(option) {
     this.option = option || {}
     this.goodsId = Number(option.goodsId || 0)
@@ -180,6 +199,14 @@ export default {
     }
     this.init()
   },
+  // 每次回到页面时同步登录态,避免从其他页登录回来仍显示登录弹层
+  onShow() {
+    if (!this.$util.isEmpty(this.loginInfo)) {
+      this.globalLoginStyle = 1
+    } else {
+      this.globalLoginStyle = 0
+    }
+  },
   onUnload() {
     this.clearRemainTimer()
   },
@@ -192,6 +219,14 @@ export default {
       if (isNaN(n)) return '0'
       return n % 1 === 0 ? String(n) : n.toFixed(2)
     },
+    /**
+     * wangCg 登录/注册成功回调
+     * 刷新详情数据(分享进页会回填 hdId,供开团/参团/支付使用)
+     */
+    loginSuccess() {
+      this.globalLoginStyle = 1
+      this.init()
+    },
     init() {
       uni.showLoading({ title: '加载中', mask: true })
       const tasks = []
@@ -216,10 +251,11 @@ export default {
               return
             }
             if (res.code === 1 && res.data) {
-              // TODO: 临时处理,后续需要增加判断登录(或注册情况)
+              // 分享进页无 hdId,接口回填后需 $set 保证 Vue2 响应式,供 goPay/跳转使用
               if (res.data.hdId) {
                 uni.setStorageSync('hdId', res.data.hdId)
-                this.option.hdId = res.data.hdId
+                if (!this.option) this.option = {}
+                this.$set(this.option, 'hdId', res.data.hdId)
               }
               this.applyGroupDetail(res.data)
               // 分享进入时若无 goodsId,用团上的商品再拉落地信息补全详情图
@@ -340,13 +376,36 @@ export default {
     buyAlone() {
       const id = this.goods.goodsId || this.goodsId
       if (!id) return
+      const hdId = this.resolveHdId()
       this.pageTo({
-        url: `/pages/goods/detail?id=${id}&account=${this.account}&hdId=${this.hdId}`
+        url: `/pages/goods/detail?id=${id}&account=${this.account}&hdId=${hdId}`
       })
     },
+    /**
+     * 解析当前门店 hdId:优先 option(接口 $set 回填),再读 storage
+     * 不用 computed 缓存,避免 setStorageSync / 非响应式写入后拿到过期空值
+     */
+    resolveHdId() {
+      const fromOption = this.option && this.option.hdId
+      const fromStore = uni.getStorageSync('hdId')
+      return Number(fromOption || fromStore || 0) || 0
+    },
+    /**
+     * 开团/参团前校验登录:未登录则弹出 wangCg,不发请求
+     * @returns {boolean} 是否已登录可继续
+     */
+    ensureLogin() {
+      if (!this.$util.isEmpty(this.loginInfo) && this.globalLoginStyle === 1) {
+        return true
+      }
+      this.globalLoginStyle = 0
+      this.$msg('请先登录')
+      return false
+    },
     /** 我要开团:默认自取下单,成功后去待付款 */
     startGroup() {
       if (this.submitting) return
+      if (!this.ensureLogin()) return
       const activityGoodsId = this.goods.activityGoodsId
       if (!activityGoodsId) {
         this.$msg('团购商品无效')
@@ -379,6 +438,7 @@ export default {
     },
     doJoin(groupBuyId) {
       if (this.submitting) return
+      if (!this.ensureLogin()) return
       this.submitting = true
       uni.showLoading({ title: '参团中', mask: true })
       joinGroup({
@@ -404,9 +464,11 @@ export default {
       const orderSn = data.orderSn || ''
       const id = data.id || 0
       const totalPrice = data.totalPrice || 0
+      // 实时解析 hdId,避免 computed 在接口回填后仍返回空值导致支付页被写成 0
+      const hdId = this.resolveHdId()
       this.$util.pageTo({
         url:
-          `/pages/callback/pay?pageStatus=1&orderSn=${orderSn}&hdId=${this.hdId}&account=${this.account}&id=${id}&couponId=0&totalPrice=${totalPrice}`,
+          `/pages/callback/pay?pageStatus=1&orderSn=${orderSn}&hdId=${hdId}&account=${this.account}&id=${id}&couponId=0&totalPrice=${totalPrice}`,
         type: 2
       })
     },

+ 1 - 1
mallApp/src/pages/home/index.vue

@@ -29,7 +29,7 @@
               :account="account"
               :hd-id="hdId"
             />
-            <home-notice-bar />
+            <home-notice-bar :account="account" />
           </template>
           <home-nav-grid
             v-else-if="mod.key === 'navGrid'"

+ 7 - 2
mallApp/src/pages/home/shop-notice-detail.vue

@@ -1,11 +1,12 @@
 <!--
   商城公告详情页
-  用途:展示 xhShopNotice 富媒体内容,从分类页通知栏进入
+  用途:展示 xhShopNotice 富媒体内容,从首页/分类页通知栏进入;
+  导航栏标题优先用公告标题,无标题时回落为「公告」
 -->
 <template>
   <view class="notice-detail-page">
     <view class="page-header">
-      <text class="page-title">{{ title || '公告详情' }}</text>
+      <text class="page-title">{{ title || '公告' }}</text>
       <text class="page-summary" v-if="summary">{{ summary }}</text>
     </view>
     <view class="preview-content">
@@ -36,6 +37,8 @@ export default {
   },
   methods: {
     init() {
+      // 进入页先设默认导航标题,详情返回后再换成公告标题
+      uni.setNavigationBarTitle({ title: '公告' })
       this.loadDetail()
     },
     loadDetail() {
@@ -45,6 +48,8 @@ export default {
         if (this.$util.isEmpty(res.data)) return
         this.title = res.data.title || ''
         this.summary = res.data.summary || ''
+        // 导航栏标题:有公告标题用标题,否则保持「公告」
+        uni.setNavigationBarTitle({ title: this.title || '公告' })
         let content = []
         if (typeof res.data.content === 'string') {
           try {

+ 2 - 0
mallApp/src/pages/login/account.vue

@@ -56,6 +56,8 @@ export default {
             store.commit('setLoginInfo', res.data.user)
             uni.navigateBack()
           }
+        } else {
+          this.$msg(res.msg)
         }
       })
     },

+ 18 - 25
mallApp/src/plugins/luch-request_0.0.7/request.js

@@ -1,15 +1,13 @@
 /**
- * Request 0.0.7
- * @Class uni-app request网络请求库
- * @Author lu-ch
- * @Date 2019-07-11
- * @Email webwork.s@qq.com
- * http://ext.dcloud.net.cn/plugin?id=392
- * **/
+ * Request 0.0.7(mallApp 网络请求封装)
+ * 用途:统一 uni.request,附带 token/account 等上下文
+ * 谁用:mallApp 各 api 模块通过 https.get / https.post 调用
+ * 说明:post 的 query 仅拼 account/hdId,业务参数只走 body,避免敏感字段进 URL
+ * 基于:luch-request 0.0.7 / http://ext.dcloud.net.cn/plugin?id=392
+ */
 import qs from 'qs'
 import constant from '@/constant/index'
 import { TOKEN_STORAGE_KEY } from '@/constant/storageKeys'
-// import common from '@/utils/common'
 import { parse } from '@/utils/util'
 import store from '@/store'
 
@@ -266,24 +264,19 @@ class Request {
 		return this.request(options, config)
 	}
 
+	/**
+	 * POST 请求:query 仅附带 account/hdId(店铺上下文),业务参数只放 body
+	 * 避免把 mobile/password 等敏感字段拼进 URL(日志、代理、Referer 可能泄漏)
+	 * @param {string} url 接口路径
+	 * @param {Object} data 业务请求体;若含 account/hdId 则优先用于 query
+	 * @param {Object} options uni.request 额外选项
+	 * @param {Object} config 业务控制参数 { loading?, hideError? }
+	 */
 	post(url, data = {}, options = {}, config = {}) {
-		const account = uni.getStorageSync('account') || 0;
-		const hdId = uni.getStorageSync('hdId') || 0;
-		if(account != 0 && hdId != 0){
-			options.url = url+'?account='+account+'&hdId='+hdId
-		} else {
-			//console.log("-----post----- data: ", data);
-			// 判断data中有无account和hdId
-			if(data.account && data.hdId){
-				options.url = url + parse({...data, 'account':data.account, 'hdId':data.hdId})
-			} else if(data.account && !data.hdId){
-				options.url = url + parse({...data, 'hdId':hdId})
-			} else if(!data.account && data.hdId){
-				options.url = url + parse({...data, 'account':account})
-			} else {
-				options.url = url + parse({...data, 'account':account, 'hdId':hdId})
-			}
-		}
+		// 优先用调用方传入的 account/hdId,否则回退本地缓存,无则 0
+		const account = (data && data.account) || uni.getStorageSync('account') || 0
+		const hdId = (data && data.hdId) || uni.getStorageSync('hdId') || 0
+		options.url = url + '?account=' + account + '&hdId=' + hdId
 		options.data = data
 		options.method = 'POST'
 		return this.request(options, config)

+ 3 - 2
mallApp/src/utils/launchScene.js

@@ -100,6 +100,8 @@ export function buildLaunchUrl(path, query) {
 
 /**
  * 写入 account / hdId / currentQuery(与原 globalMixins 一致)
+ * hdId:仅当本次入参带有效值时写入;缺省不覆盖本地已有绑定,
+ * 避免分享进页(故意不带 hdId)或跳转漏参时把接口回填的 hdId 冲成 0
  */
 export function persistLaunchStorage(option) {
 	if (option.account) {
@@ -107,10 +109,9 @@ export function persistLaunchStorage(option) {
 	} else {
 		uni.setStorageSync('account', 0)
 	}
+	// 无有效 hdId 时保留 storage,防止全局 onLoad 误清空门店绑定
 	if (option.hdId) {
 		uni.setStorageSync('hdId', option.hdId)
-	} else {
-		uni.setStorageSync('hdId', 0)
 	}
 	uni.setStorageSync('currentQuery', JSON.stringify(option))
 }