Browse Source

feat(mallApp): 团购下单支持选择配送方式

- 开团和参团统一进入混合结算页选择配送及收货信息
- 团购结算禁用会员优惠与红包,并透传跑腿报价字段由后端重算运费
shizhongqi 1 day ago
parent
commit
4a37a6750d

+ 2 - 0
mallApp/src/api/group-buy/index.js

@@ -12,6 +12,7 @@ export const getGroupDetail = data => https.get('/group-buy/detail', data)
 
 /**
  * 我要开团
+ * 入参与混合结算对齐:配送方式、收货信息;跑腿时带 deliveryPlatform / deliveryBracketContent,运费由后端重算
  * @param {Object} data 业务参数
  * @param {Object} config 请求控制,如 hideError 由页面自行弹失败提示(须透传到 post,否则第二参会被丢掉)
  */
@@ -19,6 +20,7 @@ export const createGroup = (data, config = {}) => https.post('/group-buy/create'
 
 /**
  * 我要参团
+ * 入参与开团对齐,含跑腿平台字段;运费由后端按混合单规则重算
  * @param {Object} data 业务参数
  * @param {Object} config 请求控制,如 hideError 由页面自行弹失败提示
  */

+ 138 - 21
mallApp/src/pages/billing/affirmMix.vue

@@ -2,6 +2,8 @@
   混合结算页(花束+花材),立即购买
   用途:商城混合购物车确认下单,支持多种配送方式、红包、贺卡与匿名配送
   整单优惠:会员折扣与门店满减互斥,左侧名称、右侧 -¥N,只展示优惠金额更大的一项
+  团购开团/参团:立即购买模式进入,不享会员折扣/门店满减、不可用红包,提交走开团/参团接口
+  团购跑腿与购物车结算同源:展示门店启用的跑腿方式,提交后由后端按混合单规则重算运费
   布局:贺卡/匿名/备注后为运费+折扣+红包同一卡片;贺卡默认不需要,匿名默认否,备注单行
   顶部公告:/shop-notice/show-list position=8(订单提交),公告条与分类页 shop-category 同款(noticeBar)
   限购超限 / 库存不足:create-mix-order 返回超限或库存不足商品后红框高亮,并写入本地供购物车继续展示
@@ -66,7 +68,7 @@
 						<view class="modify-link" @click="modifyItem">
 							<!-- 原 iconfont 36upx,颜色随文案 #77c34f -->
 							<zui-svg-icon icon="general-cartFull" :width="18" :height="18" color="#77c34f" style="margin-right: 8rpx" />
-							<text>返回修改</text>
+							<text>{{ isGroupBuyCheckout ? '返回团购' : '返回修改' }}</text>
 						</view>
 						<view class="product-total">
 							<text class="product-total-label">商品合计</text>
@@ -360,8 +362,8 @@
 					</view>
 				</view>
 
-				<!-- 运费、会员折扣、红包同一卡片;红包行单独可点,避免点运费也弹层 -->
-				<view class="affirm-card fee-card fee-hb-card">
+				<!-- 运费、会员折扣、红包同一卡片;团购单不展示折扣与红包 -->
+				<view class="affirm-card fee-card fee-hb-card" v-if="showFeeHbCard">
 					<view class="fee-row" v-if="Number(form.unMeetSendCost) > 0">
 						<text class="fee-label">运费</text>
 						<text class="accent-text">¥{{ form.unMeetSendCost }}</text>
@@ -375,6 +377,7 @@
 						<text :class="wholeDiscountAmountClass">{{ wholeDiscountAmountText }}</text>
 					</view>
 					<view
+						v-if="!isGroupBuyCheckout"
 						class="hb-row-inner"
 						:class="{ 'fee-row--follow': Number(form.unMeetSendCost) > 0 || !!wholeDiscountLabel }"
 						@click="openHbPopup"
@@ -486,12 +489,15 @@
 /**
  * 混合结算页(花束+花材),立即购买
  * 花束与花材合并下单,支持跑腿/自取/快递等多种配送及红包抵扣
+ * 团购开团/参团从详情页以立即购买进入:不打折不用红包,提交走 /group-buy/create 或 /group-buy/join
+ * 团购跑腿与混合单同源:结算页展示跑腿,后端按 calcMixRunnerFreight 重算运费
  * 配送时间弹层:最近7天 + 10分钟时段,默认当前时间推后一小时,不可选过去
  * 跑腿运费:包运费花束 / 不包运费花束 / 纯花材满减;calcType 跑腿或自定义,跑腿异常兜底自定义
  */
 import SpriteIcon from "@/components/sprite-icon/index.vue";
 import productMins from "@/mixins/cgProduct";
 import { createMixOrder, inform } from "@/api/order";
+import { createGroup, joinGroup } from "@/api/group-buy";
 import { allDeliveryQuotes } from "@/api/express/delivery";
 import { getUserDistance } from "@/api/express";
 import { currentInfo } from "@/api/user";
@@ -628,6 +634,7 @@ export default {
 				this.list = this.buildMergedCartList();
 			}
 		}
+		this.syncGroupBuyNavTitle();
 
 		this.loadShopInfo();
 		this.loadSelectedAddress();
@@ -723,6 +730,9 @@ export default {
 		 * 是否至少有一个满足商品范围、活动限制及最低消费的红包。
 		 */
 		hasAvailableHb() {
+			if (this.isGroupBuyCheckout) {
+				return false;
+			}
 			if (!this.hbData || this.hbData.length === 0) {
 				return false;
 			}
@@ -730,6 +740,26 @@ export default {
 				return isHbAvailable(item, this.hbProductList, this.allPriceFun);
 			});
 		},
+		/**
+		 * 团购立即购买:单商品开团/参团,不走购物车混合单
+		 */
+		isGroupBuyCheckout() {
+			return (this.list || []).some((item) => item && item.activityType === "groupBuy");
+		},
+		/** 团购结算动作:create 开团 / join 参团 */
+		groupBuyCheckoutAction() {
+			const row = (this.list || []).find((item) => item && item.activityType === "groupBuy");
+			return row && row.groupBuyAction === "join" ? "join" : "create";
+		},
+		/**
+		 * 费用+红包卡片:团购只在有附加运费时展示,且不含折扣/红包
+		 */
+		showFeeHbCard() {
+			if (this.isGroupBuyCheckout) {
+				return Number(this.form.unMeetSendCost) > 0;
+			}
+			return true;
+		},
 		/**
 		 * 秒杀花束行合计。秒杀已是活动价,不进入会员折/门店满减基数,与后端 seckillGoodsPrice 对齐。
 		 */
@@ -783,6 +813,10 @@ export default {
 		 * 会员折扣与门店满减互斥结果:type=member|shop|none,amount 为优惠额。
 		 */
 		wholeDiscountPick() {
+			// 团购价已是活动价,整单不再叠加会员折扣或门店满减
+			if (this.isGroupBuyCheckout) {
+				return { type: "none", amount: 0 };
+			}
 			return this.pickWholeOrderDiscount(this.wholeDiscountBasePrice, this.mixMeetCutBigNum);
 		},
 		/** 确认页费用行左侧:会员折扣 / 门店满减优惠 */
@@ -964,7 +998,7 @@ export default {
 		showDeliveryTimeSection() {
 			return ![3, 4].includes(Number(this.form.sendType));
 		},
-		/** 可选购买方式:接口 xhPsMethod 启用项,按 sort 升序 */
+		/** 可选购买方式:接口 xhPsMethod 启用项,按 sort 升序;团购与混合单展示同一套 */
 		sendTypeOptions() {
 			return this.getEnabledPsMethods().map((method) => {
 				const value = Number(method.style);
@@ -1489,9 +1523,9 @@ export default {
 				this.defaultAddressLoaded = true;
 			});
 		},
-		/** 打开红包选择弹层(整行可点) */
+		/** 打开红包选择弹层(整行可点);团购单不可用红包 */
 		openHbPopup() {
-			if (!this.hasAvailableHb) {
+			if (this.isGroupBuyCheckout || !this.hasAvailableHb) {
 				return;
 			}
 			if (this.$refs.hbSelectRef && this.$refs.hbSelectRef.openPopup) {
@@ -2313,7 +2347,7 @@ export default {
 				return false;
 			}
 
-			this.$util.confirmModal({ content: "确认提交?" }, () => {
+			this.$util.confirmModal({ content: this.isGroupBuyCheckout ? "确认提交团购订单?" : "确认提交?" }, () => {
 				this.submitMyForm();
 			});
 		},
@@ -2357,7 +2391,8 @@ export default {
 				unMeetSendCost: this.form.unMeetSendCost || 0
 			};
 
-			if (this.hasAvailableHb && this.selectedHbId) {
+			// 团购单不使用红包;普通混合单才带上选中的红包
+			if (!this.isGroupBuyCheckout && this.hasAvailableHb && this.selectedHbId) {
 				params.hbId = this.selectedHbId;
 				params.hbAmount = this.selectedHb.amount;
 			}
@@ -2400,23 +2435,15 @@ export default {
 				}
 			}
 			uni.showLoading({ mask: true });
+			if (this.isGroupBuyCheckout) {
+				this.submitGroupBuyOrder(params, hdId);
+				return;
+			}
 			createMixOrder(params).then((res) => {
 				uni.hideLoading();
 				if (res.code && res.code == 1) {
 					this.clearLimitExceedState();
-					const account = this.option.account ? this.option.account : uni.getStorageSync("account") || 0;
-					const getPayType = res.data.getPayType ? res.data.getPayType : 0;
-					const totalPrice = res.data.totalPrice ? res.data.totalPrice : 0;
-					const orderSn = res.data.orderSn ? res.data.orderSn : "";
-					const id = res.data.id ? res.data.id : 0;
-					this.$util.pageTo({
-						url: "/pages/callback/pay?account=" + account + "&id=" + id + "&hdId=" + hdId + "&pageStatus=1&getPayType=" + getPayType + "&totalPrice=" + totalPrice + "&orderSn=" + orderSn,
-						type: 2
-					});
-					// 仅下单成功才清空购物车记忆;失败时保留,便于返回购物车减数量
-					if (!this.isBuyNow) {
-						this.removeMemory();
-					}
+					this.goPayAfterOrder(res.data || {}, hdId);
 				} else if (this.applyLimitExceedFromResponse(res)) {
 					this.$msg(res.msg || "商品超出可购买数量");
 				} else {
@@ -2430,7 +2457,80 @@ export default {
 				this.$msg(err.msg);
 			});
 		},
+		/**
+		 * 团购提交:走开团或参团接口,带上结算页选好的配送方式(含跑腿平台报价字段),不传红包。
+		 * 跑腿运费以后端 calcMixRunnerFreight 重算为准,与混合单一致。
+		 * hideError 避免拦截器短 toast 被 hideLoading 立刻盖掉。
+		 */
+		submitGroupBuyOrder(params, hdId) {
+			const row = (this.list || []).find((item) => item && item.activityType === "groupBuy") || {};
+			const activityGoodsId = Number(row.activityGoodsId) || 0;
+			const groupBuyId = Number(row.groupBuyId) || 0;
+			const goodsNum = Number(row.bigCount) || 1;
+			const payload = {
+				...params,
+				activityGoodsId,
+				groupBuyId,
+				goodsNum
+			};
+			delete payload.hbId;
+			delete payload.hbAmount;
+			delete payload.product;
+			const isJoin = this.groupBuyCheckoutAction === "join";
+			if (isJoin && groupBuyId <= 0) {
+				uni.hideLoading();
+				this.$msg("拼团无效");
+				return;
+			}
+			if (!isJoin && activityGoodsId <= 0) {
+				uni.hideLoading();
+				this.$msg("团购商品无效");
+				return;
+			}
+			const request = isJoin
+				? joinGroup(payload, { hideError: true })
+				: createGroup(payload, { hideError: true });
+			request.then((res) => {
+				uni.hideLoading();
+				if (res.code && res.code == 1 && res.data) {
+					this.goPayAfterOrder(res.data, hdId);
+					return;
+				}
+				if (res && res.msg) {
+					uni.showToast({
+						title: res.msg,
+						icon: "none",
+						mask: true,
+						duration: 3000
+					});
+				}
+			}).catch((err) => {
+				uni.hideLoading();
+				this.$msg((err && err.msg) || "提交失败");
+			});
+		},
+		/**
+		 * 下单成功跳转待付款页(混合单与团购单共用)
+		 */
+		goPayAfterOrder(data, hdId) {
+			const account = this.option.account ? this.option.account : uni.getStorageSync("account") || 0;
+			const getPayType = data.getPayType ? data.getPayType : 0;
+			const totalPrice = data.totalPrice ? data.totalPrice : 0;
+			const orderSn = data.orderSn ? data.orderSn : "";
+			const id = data.id ? data.id : 0;
+			this.$util.pageTo({
+				url: "/pages/callback/pay?account=" + account + "&id=" + id + "&hdId=" + hdId + "&pageStatus=1&getPayType=" + getPayType + "&totalPrice=" + totalPrice + "&orderSn=" + orderSn,
+				type: 2
+			});
+			if (!this.isBuyNow) {
+				this.removeMemory();
+			}
+		},
 		modifyItem() {
+			if (this.isGroupBuyCheckout) {
+				this.$util.pageTo(1);
+				return;
+			}
 			const account = this.option.account ? this.option.account : uni.getStorageSync("account") || 0;
 			const hdId = this.option.hdId ? this.option.hdId : uni.getStorageSync("hdId") || 0;
 			this.$util.pageTo({ url: "/pages/home/cart?account=" + account + "&hdId=" + hdId, type: 2 });
@@ -2474,6 +2574,17 @@ export default {
 			this.limitExceedList = [];
 			clearMixLimitExceed();
 		},
+		/**
+		 * 团购结算改导航标题,避免仍显示「立即购买」
+		 */
+		syncGroupBuyNavTitle() {
+			if (!this.isGroupBuyCheckout) {
+				return;
+			}
+			uni.setNavigationBarTitle({
+				title: this.groupBuyCheckoutAction === "join" ? "确认参团" : "确认开团"
+			});
+		},
 		showItemTips(item) {
 			if (!item) {
 				return false;
@@ -2505,6 +2616,12 @@ export default {
 		 * 若不拦截会连打三次 /hb/available-hb;失败时清标记以便重试。
 		 */
 		getHbData() {
+			if (this.isGroupBuyCheckout) {
+				this.hbData = [];
+				this.selectedHbId = null;
+				this.selectedHb = null;
+				return;
+			}
 			if (!this.shopInfo || !this.shopInfo.id) {
 				return;
 			}

+ 87 - 89
mallApp/src/pages/groupBuy/detail.vue

@@ -2,6 +2,7 @@
   团购详情 / 开团入口页
   从首页团购专区点击进入(goodsId),或从分享卡片进入(id=groupBuyId)
   展示商品信息、活动规则(land.desc)、商品详情图文(picTextGoods)、开团玩法、底部操作
+  开团/参团先跳混合结算页选配送方式(含跑腿),提交后再创建团购单
   未登录可浏览:仅开团/参团/邀请好友时弹 wangCg;单独购买不弹(进商品详情)
 -->
 <template>
@@ -138,7 +139,8 @@
 /**
  * 团购详情页
  * 入参:goodsId(首页进入)或 id(分享进入的 groupBuyId)
- * 开团/参团成功后跳转待付款页;未登录时开团/参团/邀请通过 wangCg 引导,单独购买不弹
+ * 开团/参团先进入混合结算页选配送(含跑腿,计费与购物车结算同源),提交后再由结算页调开团/参团接口建单
+ * 未登录时开团/参团/邀请通过 wangCg 引导,单独购买不弹
  */
 import { mapGetters } from 'vuex'
 import CountDown from '@/components/CountDown.vue'
@@ -148,13 +150,12 @@ import wangCg from '@/components/wangCg'
 import share from '@/mixins/share'
 import {
   getGoodsLanding,
-  getGroupDetail,
-  createGroup,
-  joinGroup
+  getGroupDetail
 } from '@/api/group-buy'
 import { getHdInfo } from '@/api/hd'
 import { getInfo, refreshShopInfoAfterLogin } from '@/api/shop'
 import { GROUP_BUY_HD_NOT_BOUND } from '@/constant/errCode'
+import { BUY_NOW_MIX_LIST_KEY } from '@/constant/storageKeys'
 
 export default {
   name: 'GroupBuyDetail',
@@ -174,7 +175,6 @@ export default {
       remainText: '00:00:00',
       _remainTimer: null,
       showShareMenu: false,
-      submitting: false,
       /** 分享 path 追加 inviterCustomId 用,不透传 hdId */
       myCustomId: 0,
       /** wangCg:1=隐藏,0=显示;覆盖全局 mixin 默认 0,避免进页即弹 */
@@ -385,7 +385,11 @@ export default {
         startTime: data.startTime,
         subtitle: data.subtitle,
         desc: data.desc, // 团购活动说明(活动规则卡片)
-        title: data.title
+        title: data.title,
+        stock: data.stock,
+        limit: data.limit,
+        // 花束是否包运费:结算页跑腿按混合单规则判断 bouquetIncluded
+        freightType: Number(data.freightType) || 0
       }
       this.detailImages = data.detailImages || []
       // 解析商品详情图文,供 rich-media-viewer 使用(与 goods/detail 一致)
@@ -419,6 +423,7 @@ export default {
       if (data.goods) {
         this.goods = Object.assign({}, this.goods, {
           goodsId: data.goods.goodsId || data.goodsId,
+          activityGoodsId: data.activityGoodsId || this.goods.activityGoodsId,
           name: data.goods.name || this.goods.name,
           cover: data.goods.cover || this.goods.cover,
           coverUrl: data.goods.cover || this.goods.coverUrl,
@@ -540,101 +545,94 @@ export default {
       this.globalLoginStyle = 0
       return false
     },
-    /** 我要开团:默认自取下单,成功后去待付款 */
-    startGroup() {
-      if (this.submitting) return
-      if (!this.ensureLogin()) return
-      const activityGoodsId = this.goods.activityGoodsId
-      if (!activityGoodsId) {
+    /**
+     * 组装团购立即购买行,写入独立缓存后进结算页选配送方式。
+     * 不写入购物车;团购价仅用于展示,实际核价由开团/参团接口完成后端完成。
+     * freightType 带给结算页,用于跑腿包运费/不包运费分支,与混合单一致。
+     * @param {string} action create=开团 join=参团
+     * @returns {Object|null}
+     */
+    buildGroupBuyCheckoutRow(action) {
+      const goodsId = Number(this.goods.goodsId || this.goodsId) || 0
+      const activityGoodsId = Number(this.goods.activityGoodsId) || 0
+      const price = Number(this.goods.price) || 0
+      if (!goodsId || !(price > 0)) {
         this.$msg('团购商品无效')
-        return
+        return null
       }
-      this.submitting = true
-      uni.showLoading({ title: '开团中', mask: true })
-      createGroup({
+      if (action === 'create' && activityGoodsId <= 0) {
+        this.$msg('团购商品无效')
+        return null
+      }
+      const groupBuyId = Number((this.openGroup && this.openGroup.id) || this.groupBuyId || 0)
+      if (action === 'join' && groupBuyId <= 0) {
+        this.$msg('拼团无效')
+        return null
+      }
+      const originPrice = Number(this.goods.originPrice) || 0
+      return {
+        id: goodsId,
+        goodsId,
+        specGoodsId: 0,
+        property: 0,
+        classId: 0,
+        name: this.goods.name || '',
+        itemName: this.goods.name || '',
+        specName: '',
+        cover: this.goods.coverUrl || this.goods.cover || '',
+        price,
+        bigPrice: price,
+        originPrice,
+        prePrice: originPrice,
+        priceType: 1,
+        activityType: 'groupBuy',
         activityGoodsId,
-        goodsNum: 1,
-        sendType: 1
-      },
-      { hideError: true }
-    ).then((res) => {
-        uni.hideLoading()
-        this.submitting = false
-        if (res.code === 1 && res.data) {
-          this.groupBuyId = res.data.groupBuyId
-          this.setupShare()
-          this.goPay(res.data)
-        }
-        if (res && res.msg) {
-          uni.showToast({
-            title: res.msg,
-            icon: 'none',
-            mask: true,
-            duration: 3000
-          })
-        }
-      })
-      .catch(() => {
-        uni.hideLoading()
-        this.submitting = false
+        groupBuyAction: action,
+        groupBuyId: action === 'join' ? groupBuyId : 0,
+        stock: Number(this.goods.stock) || 0,
+        activityLimit: Number(this.goods.limit) || 0,
+        activityStock: Number(this.goods.stock) || 0,
+        activityEndTime: Number(this.goods.endTime) || 0,
+        // 花束是否包运费:与购物车结算同源,决定跑腿走包运费还是平台/自定义报价
+        freightType: Number(this.goods.freightType) || 0,
+        bigCount: 1,
+        smallCount: 0,
+        ratio: 1,
+        bigUnit: '份',
+        smallUnit: '份'
+      }
+    },
+    /**
+     * 开团/参团:登录后跳转混合结算页,由客户选择配送方式再提交建单
+     * @param {string} action create|join
+     */
+    goGroupBuyCheckout(action) {
+      if (!this.ensureLogin()) return
+      const row = this.buildGroupBuyCheckoutRow(action)
+      if (!row) return
+      const hdId = this.resolveHdId()
+      const account = this.account || uni.getStorageSync('account') || 0
+      uni.setStorageSync(BUY_NOW_MIX_LIST_KEY, [row])
+      this.$util.pageTo({
+        url: `/pages/billing/affirmMix?account=${account}&hdId=${hdId}`
       })
     },
+    /** 我要开团:先去结算页选配送,不在本页直接建单 */
+    startGroup() {
+      this.goGroupBuyCheckout('create')
+    },
     joinOpenGroup() {
       if (!this.openGroup || !this.openGroup.id) return
       this.doJoin(this.openGroup.id)
     },
     /**
-     * 参团:默认自取下单,成功后去待付款
-     * hideError 避免拦截器先弹默认 1.5s toast,再被 hideLoading 立刻关掉
+     * 参团:与开团一样先进入结算页,提交时再挂到指定团上
      */
     doJoin(groupBuyId) {
-      if (this.submitting) return
-      if (!this.ensureLogin()) return
-      this.submitting = true
-      uni.showLoading({ title: '参团中', mask: true })
-      joinGroup(
-        {
-          groupBuyId,
-          goodsNum: 1,
-          sendType: 1
-        },
-        { hideError: true }
-      )
-        .then((res) => {
-          uni.hideLoading()
-          this.submitting = false
-          if (res.code === 1 && res.data) {
-            this.groupBuyId = res.data.groupBuyId
-            this.setupShare()
-            this.goPay(res.data)
-            return
-          }
-          // 如「已在该团中」:先关 loading 再弹,时长在默认 1500ms 上加长 1.5s
-          if (res && res.msg) {
-            uni.showToast({
-              title: res.msg,
-              icon: 'none',
-              mask: true,
-              duration: 3000
-            })
-          }
-        })
-        .catch(() => {
-          uni.hideLoading()
-          this.submitting = false
-        })
-    },
-    goPay(data) {
-      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=${hdId}&account=${this.account}&id=${id}&couponId=0&totalPrice=${totalPrice}`,
-        type: 2
-      })
+      if (groupBuyId) {
+        this.openGroup = Object.assign({}, this.openGroup || {}, { id: groupBuyId })
+      }
+      this.goGroupBuyCheckout('join')
     },
     prepareShare() {
       this.setupShare()

+ 2 - 1
mallApp/src/utils/hbScope.js

@@ -36,7 +36,7 @@ function isEnabledFlag(value) {
 
 /**
  * 判断购物车行是否属于特价或活动商品。
- * 红包 specialApplicable 不开启时,这些行不计入适用金额。
+ * 红包 specialApplicable 不开启时,这些行不计入适用金额;团购行一律视为活动商品
  */
 export function isHbSpecialOrActivityItem(item) {
 	if (!item) {
@@ -46,6 +46,7 @@ export function isHbSpecialOrActivityItem(item) {
 		|| isEnabledFlag(item.isActivity)
 		|| isEnabledFlag(item.seckill)
 		|| item.activityType === 'seckill'
+		|| item.activityType === 'groupBuy'
 }
 
 /**