shish 1 ماه پیش
والد
کامیت
c6edbf36bd

+ 118 - 6
hdApp/src/mixins/cgProduct.js

@@ -253,6 +253,108 @@ export default {
 			}
 			return element.id == item.id && element.classId == item.classId;
 		},
+		// ----------- 购物车价格同步(修复 storage 旧缓存缺 reachPrice 导致确认页 ¥0)-----------
+		/**
+		 * 扁平化当前页花材目录,供购物车价格同步使用
+		 * V2 接口优先用 itemList;V1 从分类 child 展开
+		 */
+		_getCatalogFlatList() {
+			// ghs-item-v2 返回的扁平列表,含 reachPrice 等最新价格字段
+			if (Array.isArray(this.globalItemList) && this.globalItemList.length > 0) {
+				return this.globalItemList;
+			}
+			const list = [];
+			const productInfo = this.productInfo;
+			if (!Array.isArray(productInfo)) {
+				return list;
+			}
+			// ghs-item 按分类嵌套,需遍历 child 拼成 id 索引表
+			productInfo.forEach(cls => {
+				if (cls && Array.isArray(cls.child)) {
+					cls.child.forEach(item => {
+						if (item && item.id != null) {
+							list.push(item);
+						}
+					});
+				}
+			});
+			return list;
+		},
+		/**
+		 * 用最新花材目录补全购物车价格字段
+		 * 解决 localStorage 旧缓存缺少 reachPrice 导致确认页显示 ¥0 的问题
+		 * 仅覆盖价格/展示相关字段,不覆盖 bigCount、smallCount、autoPrice
+		 * @param {Array} cachedList 购物车行(可能来自 vuex 或 storage)
+		 * @returns {Array} 补全价格字段后的购物车
+		 */
+		syncSelectListCatalogPrices(cachedList) {
+			if (!Array.isArray(cachedList) || cachedList.length === 0) {
+				return cachedList;
+			}
+			// 门店开单等非采购场景不走目录同步,避免误改其它业务购物车
+			if (!this._shouldMergeSelectByProductId()) {
+				return cachedList;
+			}
+			const catalog = this._getCatalogFlatList();
+			// 花材列表尚未加载完成时,保持原数据,等待下次 init 或提交前再同步
+			if (!catalog.length) {
+				return cachedList;
+			}
+			const catalogMap = new Map();
+			catalog.forEach(item => {
+				catalogMap.set(String(item.id), item);
+			});
+			// 允许从接口覆盖的字段:价格、满减、展示信息;不含用户操作类字段
+			const catalogSyncKeys = [
+				'price', 'bigPrice', 'smallPrice', 'prePrice',
+				'reachNum', 'reachNumDiscount', 'reachPrice',
+				'discountPrice', 'hjPrice', 'skPrice', 'hjDiscountPrice', 'skDiscountPrice',
+				'name', 'itemName', 'cover', 'weight', 'ratio', 'bigUnit', 'smallUnit',
+				'ratioType', 'smallRatio', 'stock', 'bigNum', 'smallNum',
+				'presell', 'limitBuy', 'itemRemark', 'variety'
+			];
+			return cachedList.map(row => {
+				const fresh = catalogMap.get(String(row.id));
+				// 商品已下架或目录中找不到:保留原行,避免静默丢单
+				if (!fresh) {
+					return row;
+				}
+				const patched = { ...row };
+				catalogSyncKeys.forEach(key => {
+					if (fresh[key] !== undefined && fresh[key] !== null) {
+						patched[key] = fresh[key];
+					}
+				});
+				return patched;
+			});
+		},
+		/**
+		 * 解析满减达标后的扎单价(reachPrice)
+		 * 优先用接口/缓存;旧缓存缺失时本地重算,避免确认页单价显示 ¥0
+		 * 计算规则与后端 ProductClass::hdCgGroup 一致:原价减 reachNumDiscount,最低 0.01
+		 * @param {Object} item 购物车中的花材行
+		 * @returns {number} 满减后大单位单价
+		 */
+		_resolveReachPrice(item) {
+			const reachNum = Number(item.reachNum) || 0;
+			// 未配置满减门槛,直接返回缓存值(通常为 0)
+			if (reachNum <= 0) {
+				return this.toSafeNumber(item.reachPrice);
+			}
+			const cached = Number(item.reachPrice);
+			// 缓存里已有有效满减价,优先信任接口最新值
+			if (!isNaN(cached) && cached > 0) {
+				return cached;
+			}
+			// 旧缓存仅有 reachNum / reachNumDiscount,缺少 reachPrice 时的兜底重算
+			const original = this.toSafeNumber(item.bigPrice || item.price);
+			const discount = Number(item.reachNumDiscount) || 0;
+			if (discount <= 0) {
+				return original;
+			}
+			const computed = Math.round((original - discount) * 100) / 100;
+			return computed <= 0 ? 0.01 : computed;
+		},
 		// ------------end---------
 		
 		getLimitBuyItemById(id) {
@@ -273,6 +375,7 @@ export default {
 			const itemInfo = this.getSelectItemById(item.id, item.classId);
 			return Number(itemInfo.bigCount) >= reachNum;
 		},
+		// 满减优惠总额,确认页底部「省 ¥X」使用
 		getReachDiscountAmount(item) {
 			if (!this.isReachDiscountReached(item)) {
 				return 0;
@@ -280,13 +383,14 @@ export default {
 			const itemInfo = this.getSelectItemById(item.id, item.classId);
 			const bigCount = Number(itemInfo.bigCount) || 0;
 			const originalBig = this.toSafeNumber(item.bigPrice || item.price);
-			const reachPrice = this.toSafeNumber(item.reachPrice);
+			const reachPrice = this._resolveReachPrice(item);
 			return Math.round(Math.max(0, bigCount * (originalBig - reachPrice)) * 100) / 100;
 		},
 		getCartItemUnitPrice(item) {
 			const itemInfo = this.getSelectItemById(item.id, item.classId);
+			// 满减达标后展示/计价用 reachPrice,须走统一解析避免旧缓存为 0
 			if (Number(itemInfo.bigCount) > 0 && this.isReachDiscountReached(item)) {
-				return parseFloat(this.toSafeNumber(item.reachPrice));
+				return parseFloat(this._resolveReachPrice(item));
 			}
 			return parseFloat(this.toSafeNumber(item.bigPrice || item.price));
 		},
@@ -301,7 +405,8 @@ export default {
 			const bigCount = Number(itemInfo.bigCount) || 0;
 			const smallCount = Number(itemInfo.smallCount) || 0;
 			if (!result.isLimitSpecial) {
-				const reachPrice = this.toSafeNumber(item.reachPrice);
+				// 普通满减:扎数 × 满减后单价 + 支数 × 小单位价
+				const reachPrice = this._resolveReachPrice(item);
 				const smallPrice = this.toSafeNumber(item.smallPrice);
 				result.total = bigCount * reachPrice + smallCount * smallPrice;
 			} else {
@@ -359,8 +464,9 @@ export default {
 			const priceInfo = this.getLimitBuyPriceInfo(item);
 			const fmt = (n) => parseFloat(Number(n) || 0);
 			const itemInfo = this.getSelectItemById(item.id, item.classId);
+			// 满减达标且非限购特价:主价显示满减单价,原价划线
 			if (priceInfo.reachDiscountActive && !priceInfo.isLimitSpecial && Number(itemInfo.bigCount) > 0) {
-				const reachPrice = fmt(item.reachPrice);
+				const reachPrice = fmt(this._resolveReachPrice(item));
 				const originalBig = fmt(item.bigPrice || item.price);
 				return {
 					showStrike: reachPrice < originalBig,
@@ -473,8 +579,11 @@ export default {
 					let targetId = this.option.id ? this.option.id : 0
 					let currentInfo = uni.getStorageSync('selectList'+this.pageType+'_ghs_'+targetId)
 					if(!this.$util.isEmpty(currentInfo)){
+						// 恢复记忆购物车:合并重复行 → 用最新目录补价格 → 写回 storage 修复旧缓存
 						const mergedInfo = this._mergeDuplicateSelectRows(currentInfo);
-						this.setSelectInfoByType({ type: this.pageType, info: mergedInfo })
+						const syncedInfo = this.syncSelectListCatalogPrices(mergedInfo);
+						this.setSelectInfoByType({ type: this.pageType, info: syncedInfo });
+						this.rememberProduct();
 					}else{
 						//临时解决方案
 						this.resetSelectInfoByType(this.pageType);
@@ -499,8 +608,11 @@ export default {
 					let targetId = this.option.id ? this.option.id : 0
 					let currentInfo = uni.getStorageSync('selectList'+this.pageType+'_ghs_'+targetId)
 					if(!this.$util.isEmpty(currentInfo)){
+						// 与 initGhsDataV2 相同:恢复时同步最新价格字段
 						const mergedInfo = this._mergeDuplicateSelectRows(currentInfo);
-						this.setSelectInfoByType({ type: this.pageType, info: mergedInfo })
+						const syncedInfo = this.syncSelectListCatalogPrices(mergedInfo);
+						this.setSelectInfoByType({ type: this.pageType, info: syncedInfo });
+						this.rememberProduct();
 					}else{
 						//临时解决方案
 						this.resetSelectInfoByType(this.pageType);

+ 110 - 3
hdApp/src/mixins/cgProduct2.js

@@ -194,7 +194,106 @@ export default {
 	},
 	methods: {
 		...mapActions(["setSelectInfoByType", "resetSelectInfoByType"]),
-		//记忆已选的花材
+		// ----------- 购物车价格同步(ghsProduct2 / bookProduct2 与 cgProduct 对齐)-----------
+		/** 安全转数字,NaN 时返回 0,供价格同步逻辑使用 */
+		toSafeNumber(value) {
+			const num = Number(value);
+			return isNaN(num) ? 0 : num;
+		},
+		/** 采购/预订场景下同一花材按 productId 合并购物车行 */
+		_shouldMergeSelectByProductId() {
+			return this.pageType === "cg" || this.pageType === "bookCg";
+		},
+		/** 合并购物车中 id 重复的行,扎数/支数累加 */
+		_mergeDuplicateSelectRows(list) {
+			if (!Array.isArray(list) || list.length === 0) {
+				return Array.isArray(list) ? [...list] : [];
+			}
+			const map = new Map();
+			const order = [];
+			list.forEach(ele => {
+				const key = String(ele.id);
+				if (!map.has(key)) {
+					const copy = { ...ele };
+					copy.bigCount = Number(copy.bigCount) || 0;
+					copy.smallCount = Number(copy.smallCount) || 0;
+					map.set(key, copy);
+					order.push(key);
+				} else {
+					const prev = map.get(key);
+					prev.bigCount = Number(prev.bigCount) + (Number(ele.bigCount) || 0);
+					prev.smallCount = Number(prev.smallCount) + (Number(ele.smallCount) || 0);
+				}
+			});
+			return order.map(k => map.get(k));
+		},
+		/**
+		 * 扁平化花材目录(与 cgProduct.js 逻辑一致)
+		 * ghsProduct2 等页面用此 mixin,需独立维护同步能力
+		 */
+		_getCatalogFlatList() {
+			if (Array.isArray(this.globalItemList) && this.globalItemList.length > 0) {
+				return this.globalItemList;
+			}
+			const list = [];
+			const productInfo = this.productInfo;
+			if (!Array.isArray(productInfo)) {
+				return list;
+			}
+			productInfo.forEach(cls => {
+				if (cls && Array.isArray(cls.child)) {
+					cls.child.forEach(item => {
+						if (item && item.id != null) {
+							list.push(item);
+						}
+					});
+				}
+			});
+			return list;
+		},
+		/**
+		 * 用最新接口花材数据补全购物车价格字段
+		 * 修复 storage 旧数据缺 reachPrice 等问题;不改动用户已选数量
+		 */
+		syncSelectListCatalogPrices(cachedList) {
+			if (!Array.isArray(cachedList) || cachedList.length === 0) {
+				return cachedList;
+			}
+			if (!this._shouldMergeSelectByProductId()) {
+				return cachedList;
+			}
+			const catalog = this._getCatalogFlatList();
+			if (!catalog.length) {
+				return cachedList;
+			}
+			const catalogMap = new Map();
+			catalog.forEach(item => {
+				catalogMap.set(String(item.id), item);
+			});
+			// 与 cgProduct 保持同一套可同步字段,确认页 affirmGhs 依赖这些价格字段
+			const catalogSyncKeys = [
+				'price', 'bigPrice', 'smallPrice', 'prePrice',
+				'reachNum', 'reachNumDiscount', 'reachPrice',
+				'discountPrice', 'hjPrice', 'skPrice', 'hjDiscountPrice', 'skDiscountPrice',
+				'name', 'itemName', 'cover', 'weight', 'ratio', 'bigUnit', 'smallUnit',
+				'ratioType', 'smallRatio', 'stock', 'bigNum', 'smallNum',
+				'presell', 'limitBuy', 'itemRemark', 'variety'
+			];
+			return cachedList.map(row => {
+				const fresh = catalogMap.get(String(row.id));
+				if (!fresh) {
+					return row;
+				}
+				const patched = { ...row };
+				catalogSyncKeys.forEach(key => {
+					if (fresh[key] !== undefined && fresh[key] !== null) {
+						patched[key] = fresh[key];
+					}
+				});
+				return patched;
+			});
+		},
+		// 记忆已选的花材到 localStorage
 		rememberProduct(){
 			//采购或预订的记忆花材
 			if(this.pageType == 'cg' || this.pageType == 'bookCg' ){
@@ -247,7 +346,11 @@ export default {
 					let targetId = this.option.id ? this.option.id : 0
 					let currentInfo = uni.getStorageSync('selectList'+this.pageType+'_ghs_'+targetId)
 					if(!this.$util.isEmpty(currentInfo)){
-						this.setSelectInfoByType({ type: this.pageType, info: currentInfo })
+						// 进入采购页:合并记忆购物车并用最新目录刷新价格
+						const mergedInfo = this._mergeDuplicateSelectRows(currentInfo);
+						const syncedInfo = this.syncSelectListCatalogPrices(mergedInfo);
+						this.setSelectInfoByType({ type: this.pageType, info: syncedInfo });
+						this.rememberProduct();
 					}else{
 						//临时解决方案
 						this.resetSelectInfoByType(this.pageType);
@@ -272,7 +375,11 @@ export default {
 					let targetId = this.option.id ? this.option.id : 0
 					let currentInfo = uni.getStorageSync('selectList'+this.pageType+'_ghs_'+targetId)
 					if(!this.$util.isEmpty(currentInfo)){
-						this.setSelectInfoByType({ type: this.pageType, info: currentInfo })
+						// V1 花材列表恢复记忆时同样同步价格
+						const mergedInfo = this._mergeDuplicateSelectRows(currentInfo);
+						const syncedInfo = this.syncSelectListCatalogPrices(mergedInfo);
+						this.setSelectInfoByType({ type: this.pageType, info: syncedInfo });
+						this.rememberProduct();
 					}else{
 						//临时解决方案
 						this.resetSelectInfoByType(this.pageType);

+ 3 - 0
hdApp/src/pagesPurchase/bookProduct.vue

@@ -647,7 +647,10 @@ export default {
 					this.$msg("请选择花材")
 					return
 				}
+				// 预订采购进确认页前刷新购物车价格字段
+				list = this.syncSelectListCatalogPrices(list);
 				this.setSelectInfoByType({ type: this.pageType, info: list });
+				this.rememberProduct();
 
 				this.pageTo({url: '/admin/billing/affirmGhsBook',query:{id:this.options.id,miniKilo:this.miniKilo,kiloFee:this.kiloFee},type: 2})
 

+ 3 - 0
hdApp/src/pagesPurchase/bookProduct2.vue

@@ -651,7 +651,10 @@ export default {
 					this.$msg("请选择花材")
 					return
 				}
+				// 预订采购(V2 列表)提交前同步最新满减/单价
+				list = this.syncSelectListCatalogPrices(list);
 				this.setSelectInfoByType({ type: this.pageType, info: list });
+				this.rememberProduct();
 
 				this.pageTo({url: '/admin/billing/affirmGhsBook',query:{id:this.options.id,miniKilo:this.miniKilo,kiloFee:this.kiloFee},type: 2})
 

+ 6 - 0
hdApp/src/pagesPurchase/ghsProduct.vue

@@ -703,13 +703,19 @@ export default {
 						}
 						this.limitBuyWarnList = []
 						this.setLimitBuyInfoByType({ type: this.pageType, info: limitBuyInfo });
+						// 进确认页前用最新花材目录刷新价格,避免旧缓存满减价显示 ¥0
+						list = this.syncSelectListCatalogPrices(list);
 						this.setSelectInfoByType({ type: this.pageType, info: list });
+						this.rememberProduct();
 						this.pageTo({url: '/admin/billing/affirmGhs?id='+this.options.id,type: 2})
 					})
 				} else {
 					this.limitBuyWarnList = []
 					this.setLimitBuyInfoByType({ type: this.pageType, info: [] });
+					// 无限购校验时同样同步价格再跳转
+					list = this.syncSelectListCatalogPrices(list);
 					this.setSelectInfoByType({ type: this.pageType, info: list });
+					this.rememberProduct();
 					this.pageTo({url: '/admin/billing/affirmGhs?id='+this.options.id,type: 2})
 				}
 			} else {

+ 3 - 0
hdApp/src/pagesPurchase/ghsProduct2.vue

@@ -746,7 +746,10 @@ export default {
 					this.$msg("请选择花材")
 					return
 				}
+				// 提交订单前同步目录价格,修复 localStorage 缺少 reachPrice 的缓存
+				list = this.syncSelectListCatalogPrices(list);
 				this.setSelectInfoByType({ type: this.pageType, info: list });
+				this.rememberProduct();
 
 				this.pageTo({url: '/admin/billing/affirmGhs?id='+this.options.id,type: 2})