Просмотр исходного кода

1. 商品是无价格类型(或价格类型为0)时,提示不能加入购物车
2. 商品详情「立即购买」独立于购物车:只把当前商品带入结算,下单成功后不清空购物车

shizhongqi 20 часов назад
Родитель
Сommit
0ff78d2a43

+ 2 - 3
hdApp/src/App.vue

@@ -1,12 +1,11 @@
 <script>
 import store from '@/store'
-import { mapMutations, mapActions, mapGetters } from 'vuex'
+import { mapActions } from 'vuex'
 //import { setTimeout } from 'timers'
 import { getDictionaries } from '@/api/mini'
 import util from "./utils/util"
 import priceSocket from "./mixins/priceSocket";
-//https://ext.dcloud.net.cn/plugin?id=22675
-import permissionListener from "@/uni_modules/c-permission-listener";
+import permissionListener from "@/uni_modules/c-permission-listener"; //https://ext.dcloud.net.cn/plugin?id=22675
 export default {
 	mixins:[priceSocket],
   // 全端,数据共享,还可以解决页面初始化模板访问不到值得

+ 6 - 0
mallApp/src/constant/storageKeys.js

@@ -9,3 +9,9 @@ export const LEGACY_TOKEN_STORAGE_KEY = "token";
  * 切回「我的花店/消息/我的」时清除,避免原生 tabBar 被长期隐藏
  */
 export const SHOP_NAV_ACTIVE_KEY = "shopNavActive";
+
+/**
+ * 商品详情「立即购买」跳转混合结算页时传递商品明细的缓存 key。
+ * 立即购买独立于购物车:只把当前商品带入结算,下单成功后不清空购物车。
+ */
+export const BUY_NOW_MIX_LIST_KEY = "buyNowMixList";

+ 29 - 7
mallApp/src/mixins/cgProduct.js

@@ -418,29 +418,36 @@ export default {
 			return this.getLimitBuyPriceInfo(item).total;
 		},
 		/**
-		 * 花束加入购物车(property=0)
+		 * 组装花束购物车行(property=0):仅做校验并构造行结构,不写入 Vuex/缓存。
+		 * 「立即购买」复用该行结构,保证与加购的计价、展示口径一致,同时不污染购物车。
 		 * @param {Object} goods 主商品信息
 		 * @param {Object|null} spec 选中的规格子商品,单规格可为 null
 		 * @param {Number} num 数量,默认 1
+		 * @returns {Object|null} 构造失败返回 null(已通过 $msg 提示原因)
 		 */
-		addBouquetToCart(goods, spec = null, num = 1) {
+		buildBouquetRow(goods, spec = null, num = 1) {
 			if (!goods || !goods.id) {
 				this.$msg('商品信息无效');
-				return false;
+				return null;
 			}
 			const buyNum = Number(num) > 0 ? Number(num) : 1;
 			const saleGoods = spec && spec.id ? spec : goods;
 			const price = Number(saleGoods.price != null ? saleGoods.price : goods.price);
-			if (!(price > 0) || Number(goods.priceType) === 0) {
+			// 无价格类型或价格类型为0时,提示不能加入购物车
+			if (Number(goods.priceType) === 0) {
+				this.$msg('无价格商品不能加入购物车');
+				return null;
+			}
+			// 有价格类型且价格为0时,提示商品还没有价格
+			if ((Number(goods.priceType) === 1 && !(price > 0))) {
 				this.$msg('商品还没有价格哦');
-				return false;
+				return null;
 			}
 			const stock = Number(saleGoods.stock != null ? saleGoods.stock : goods.stock);
 			if (stock <= 0 && Number(saleGoods.stockSet) === 1) {
 				this.$msg('库存不足哦');
-				return false;
+				return null;
 			}
-			const list = Array.isArray(this.selectList) ? [...this.selectList] : [];
 			const row = {
 				id: goods.id,
 				goodsId: goods.id,
@@ -473,6 +480,21 @@ export default {
 				row.activityEndTime = Number(goods.activityEndTime) || 0;
 				row.originPrice = Number(goods.originPrice) || 0;
 			}
+			return row;
+		},
+		/**
+		 * 花束加入购物车(property=0)
+		 * @param {Object} goods 主商品信息
+		 * @param {Object|null} spec 选中的规格子商品,单规格可为 null
+		 * @param {Number} num 数量,默认 1
+		 */
+		addBouquetToCart(goods, spec = null, num = 1) {
+			const row = this.buildBouquetRow(goods, spec, num);
+			if (!row) {
+				return false;
+			}
+			const buyNum = Number(row.bigCount) || 1;
+			const list = Array.isArray(this.selectList) ? [...this.selectList] : [];
 			let found = false;
 			for (let i = 0; i < list.length; i++) {
 				if (this._selectRowMatches(list[i], row)) {

+ 1 - 1
mallApp/src/mixins/globalMixins.js

@@ -22,7 +22,7 @@ export default {
 	onLoad: function (option) {
 		let that = this
 		option = applyLaunchQuery(option || {})
-		this.option = option
+		that.option = option
 
 		if (option.token) {
 			uni.setStorageSync(TOKEN_STORAGE_KEY, option.token)

+ 48 - 13
mallApp/src/pages/billing/affirmMix.vue

@@ -512,6 +512,7 @@ import MxDatePicker from "@/components/mx-datepicker/mx-datepicker.vue";
 import HbSelect from "@/components/hb/hb-select";
 import { isHbAvailable } from "@/utils/hbScope";
 import HomeNoticeBar from "@/components/home/noticeBar.vue";
+import { BUY_NOW_MIX_LIST_KEY } from "@/constant/storageKeys";
 
 /** 配送方式 style 与雪碧图图标映射(名称取自接口 name) */
 const SEND_TYPE_ICON = {
@@ -536,6 +537,8 @@ export default {
 			pageType: "cg",
 			autoLoad: false,
 			list: [],
+			/** 是否「立即购买」模式:只结算详情页当前商品,不影响购物车 */
+			isBuyNow: false,
 			showDatePicker: false,
 			defaultPickerDate: "",
 			form: {
@@ -611,21 +614,17 @@ export default {
 		// 从地址列表返回时 storage 可能已被清空,需从 option 恢复 account/hdId
 		this.syncShopContext();
 		this._syncMergedSelectListIfNeeded();
-		const selectList = this.$util.copyObject(this.selectList);
-		const mergedMap = {};
-		const list = [];
-		for (let i = 0; i < selectList.length; i++) {
-			const item = selectList[i];
-			const key = this.getSelectRowKey(item) || String(item.id);
-			if (mergedMap[key]) {
-				mergedMap[key].bigCount = Number(mergedMap[key].bigCount || 0) + Number(item.bigCount || 0);
-				mergedMap[key].smallCount = Number(mergedMap[key].smallCount || 0) + Number(item.smallCount || 0);
+		// 首次进入时若存在「立即购买」商品缓存,则只结算该商品,不读取购物车(返回地址列表等场景不重复读取)
+		if (!this.isBuyNow) {
+			const buyNowList = uni.getStorageSync(BUY_NOW_MIX_LIST_KEY);
+			if (Array.isArray(buyNowList) && buyNowList.length) {
+				uni.removeStorageSync(BUY_NOW_MIX_LIST_KEY);
+				this.isBuyNow = true;
+				this.list = this.$util.copyObject(buyNowList);
 			} else {
-				mergedMap[key] = this.$util.copyObject(item);
-				list.push(mergedMap[key]);
+				this.list = this.buildMergedCartList();
 			}
 		}
-		this.list = list;
 
 		this.loadShopInfo();
 		this.loadSelectedAddress();
@@ -653,6 +652,18 @@ export default {
 		}
 	},
 	computed: {
+		/**
+		 * 红包最低消费口径:覆盖 mixin 的 selectList 统计。
+		 * 立即购买只按当前结算明细 list 计价,购物车结算时 list 即为合并后的明细,
+		 * 避免红包金额误用购物车合计。
+		 */
+		allPriceFun() {
+			let price = 0;
+			(this.list || []).forEach((item) => {
+				price += this.getSelectItemTotalPrice(item);
+			});
+			return Math.round(price * 100) / 100;
+		},
 		/** 混合结算商品金额(花束+花材,按行独立计价) */
 		mixGoodsPrice() {
 			let price = 0;
@@ -1004,6 +1015,27 @@ export default {
 		}
 	},
 	methods: {
+		/**
+		 * 将购物车 selectList 按行唯一键合并,得到结算明细(购物车结算用)。
+		 * 立即购买模式不走这里,直接使用详情页传入的单商品明细。
+		 */
+		buildMergedCartList() {
+			const selectList = this.$util.copyObject(this.selectList);
+			const mergedMap = {};
+			const list = [];
+			for (let i = 0; i < selectList.length; i++) {
+				const item = selectList[i];
+				const key = this.getSelectRowKey(item) || String(item.id);
+				if (mergedMap[key]) {
+					mergedMap[key].bigCount = Number(mergedMap[key].bigCount || 0) + Number(item.bigCount || 0);
+					mergedMap[key].smallCount = Number(mergedMap[key].smallCount || 0) + Number(item.smallCount || 0);
+				} else {
+					mergedMap[key] = this.$util.copyObject(item);
+					list.push(mergedMap[key]);
+				}
+			}
+			return list;
+		},
 		/** 是否秒杀/团购活动花束行(与 affirmMixv2 提交 activityType 一致) */
 		isActivityBouquetItem(item) {
 			return this.isBouquetItem(item) && (item.activityType === "seckill" || item.activityType === "groupBuy");
@@ -1988,7 +2020,10 @@ export default {
 						this.$msg(res.msg);
 					}
 				}
-				this.removeMemory();
+				// 立即购买下单成功不影响购物车:仅购物车结算成功后才清空购物车
+				if (!this.isBuyNow) {
+					this.removeMemory();
+				}
 			}).catch((err) => {
 				uni.hideLoading();
 				this.$msg(err.msg);

+ 19 - 4
mallApp/src/pages/goods/detail.vue

@@ -118,6 +118,7 @@ import { mapGetters } from "vuex";
 import { getDetail } from "@/api/goods";
 import { getHdInfo } from "@/api/hd";
 import { getInfo } from "@/api/shop";
+import { BUY_NOW_MIX_LIST_KEY } from "@/constant/storageKeys";
 import RichMediaViewer from "@/components/RichMediaViewer/index.vue";
 export default {
   name: "detail",
@@ -532,7 +533,7 @@ export default {
       return `/pages/billing/affirmMix?account=${account}&hdId=${hdId}`;
     },
     /**
-     * 弹层选中结果写入 Vuex 购物车(立即购买 / 加购共用)
+     * 弹层加购:选中结果写入 Vuex 购物车
      * 规格 SKU(masterId !== 0)时:goodsId 用主商品,spec 带当前规格 id/名称
      * @returns {boolean} 是否写入成功
      */
@@ -565,14 +566,28 @@ export default {
       }
     },
     /**
-     * 弹层立即购买:写入购物车后跳转混合结算页 affirmMix(弃用 order/buy)
+     * 组装「立即购买」的商品行:复用购物车行结构,但只构造、不写入购物车。
+     * 规格 SKU(masterId !== 0)时:goodsId 用主商品,spec 带当前规格 id/名称
+     * @returns {Object|null} 商品行,构造失败返回 null
+     */
+    buildBuyNowRow({ spec, buyNum, masterId }) {
+      let goods = this.data;
+      if (masterId && Number(masterId) > 0) {
+        goods = { ...this.data, id: Number(masterId), goodsId: Number(masterId) };
+      }
+      return this.buildBouquetRow(goods, spec || null, buyNum || 1);
+    },
+    /**
+     * 弹层立即购买:只把当前商品带入混合结算页,不写入/不清空购物车。
+     * 通过独立缓存 BUY_NOW_MIX_LIST_KEY 传递,affirmMix 按立即购买模式读取。
      */
     onBuyNowFromPopup(payload) {
-      const ok = this.syncPopupSelectionToCart(payload || {});
-      if (!ok) {
+      const row = this.buildBuyNowRow(payload || {});
+      if (!row) {
         return;
       }
       this.hidePopup();
+      uni.setStorageSync(BUY_NOW_MIX_LIST_KEY, [row]);
       this.$util.pageTo({ url: this.getAffirmUrl(), type: 2 });
     },
     hidePopup() {

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

@@ -198,6 +198,25 @@ export default {
     }
   },
   methods: {
+    /** 页面初始化:并行拉取首页配置与门店信息(merchantName) */
+    init() {
+      this.loading = true
+      this.refreshCartBadgeCount()
+      const account = this.account
+      if (account) {
+        uni.setStorageSync('account', account)
+      }
+      Promise.all([getHome(), this.syncMyCustomId()])
+        .then(([homeRes]) => {
+          if (homeRes.code === 1 && homeRes.data) {
+            this.pageData = homeRes.data
+          }
+          this.checkHbArrival()
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
     /**
      * 读取花材购物车本地缓存,计算底部 tab 角标
      * 缓存 key 与分类页/订单页一致:selectListcg_hd_{account}
@@ -281,25 +300,6 @@ export default {
       }).catch(() => {}).finally(() => {
         this.hbArrivalLoading = false
       })
-    },
-    /** 页面初始化:并行拉取首页配置与门店信息(merchantName) */
-    init() {
-      this.loading = true
-      this.refreshCartBadgeCount()
-      const account = this.account
-      if (account) {
-        uni.setStorageSync('account', account)
-      }
-      Promise.all([getHome(), this.syncMyCustomId()])
-        .then(([homeRes]) => {
-          if (homeRes.code === 1 && homeRes.data) {
-            this.pageData = homeRes.data
-          }
-          this.checkHbArrival()
-        })
-        .finally(() => {
-          this.loading = false
-        })
     }
   }
 }