diff --git a/package.json b/package.json index 4d34149..1b37155 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "vue-clipboard3": "^2.0.0", "vue-i18n": "^11.1.0", "vue-router": "^4.5.0", - "ysk-utils": "^1.0.12" + "ysk-utils": "^1.0.35" }, "devDependencies": { "@commitlint/cli": "^19.7.1", diff --git a/src/store/modules/carts.ts b/src/store/modules/carts.ts index e8077fb..8bbcb5c 100644 --- a/src/store/modules/carts.ts +++ b/src/store/modules/carts.ts @@ -3,7 +3,9 @@ import WebSocketManager, { type ApifoxModel, msgType } from "@/utils/websocket"; import orderApi from "@/api/order/order"; import { useUserStoreHook } from "@/store/modules/user"; import productApi from "@/api/product/index"; - +import * as UTILS from "@/utils/coupon-utils.js"; +import { BigNumber } from "bignumber.js"; +import _ from "lodash"; // 导入工具库及相关类型 import { OrderPriceCalculator, @@ -212,6 +214,7 @@ export const useCartsStore = defineStore("carts", () => { pointsPerYuan: 100, maxDeductionAmount: Infinity }) + //使用积分数量 const userPoints = ref(0); // 订单额外配置(现在依赖响应式的 merchantReduction) const orderExtraConfig = computed(() => ({ @@ -231,26 +234,50 @@ export const useCartsStore = defineStore("carts", () => { return []; }); - const coupons = ref([]); function setCoupons(cps: BackendCoupon[]) { console.log('setCoupons', cps); + let goodsCoupon = cps.filter((v) => v.type == 2); + let otherCoupon = cps.filter((v) => v.type != 2); + const canDikouGoodsArr = UTILS.returnCanDikouGoods(allGoods.value, [], vipUser.value); + //商品订单金额 + const goodsOrderPrice = new BigNumber(orderCostSummary.value.goodsOriginalAmount) + .minus(orderCostSummary.value.goodsDiscountAmount) + .toFixed(2); + goodsCoupon = goodsCoupon.map((v) => { + const discount = UTILS.returnCouponDiscount(canDikouGoodsArr, v, vipUser.value, goodsOrderPrice, [], shopUser.userInfo); + return { + ...v, + discount, + discountAmount: discount ? discount.discountPrice : v.discountAmount + }; + }); + otherCoupon = otherCoupon.map((v) => { + const discount = UTILS.returnCouponDiscount(canDikouGoodsArr, v, vipUser.value, goodsOrderPrice, goodsCoupon, shopUser.userInfo); + return { + ...v, + discount, + discountAmount: discount ? discount.discountPrice : v.discountAmount + }; + }); coupons.value = cps; } + + + // 优惠券列表 - const backendCoupons = computed(() => { - return coupons.value; - }); + const coupons = ref([]); + // 商品加入购物车顺序 const cartOrder = ref>({}); - + let allGoods = ref([]); // 订单费用汇总(调用内部的 getAllGoodsList) const orderCostSummary = computed(() => { - const allGoods = getAllGoodsList(); + allGoods.value = getAllGoodsList(); const costSummary = OrderPriceCalculator.calculateOrderCostSummary( - allGoods, + allGoods.value, dinnerType.value, - backendCoupons.value, + coupons.value, activityList.value, orderExtraConfig.value, cartOrder.value, @@ -714,6 +741,12 @@ export const useCartsStore = defineStore("carts", () => { WebSocketManager.sendMessage(msg); } + function payParamsInit() { + coupons.value = [] + clearMerchantReduction() + userPoints.value = 0; + } + return { disconnect, dinnerType, @@ -764,8 +797,11 @@ export const useCartsStore = defineStore("carts", () => { clearMerchantReduction, seatFeeConfig, pointDeductionRule, + //使用积分数量 userPoints, - setCoupons + coupons, + setCoupons, + payParamsInit }; }); diff --git a/src/utils/goods-utils.js b/src/utils/coupon-utils.js similarity index 56% rename from src/utils/goods-utils.js rename to src/utils/coupon-utils.js index b8ea534..4a2b1ce 100644 --- a/src/utils/goods-utils.js +++ b/src/utils/coupon-utils.js @@ -1,33 +1,22 @@ import { BigNumber } from "bignumber.js"; import _ from "lodash"; -/** - * 返回可以抵扣优惠券的商品列表,过滤掉赠品、临时商品,价格从高到低排序 - * @param arr 商品列表 - * @param user 用户信息 - */ -export function returnCanDikouGoods(arr, user) { - return arr - .filter((v) => { - return v.is_temporary != 1 && v.is_gift != 1; - }) - .filter((v) => { - return v.num > 0; - }) - .sort((a, b) => { - return returnGoodsPrice(b, user) - returnGoodsPrice(a, user); - }); -} - /** * 返回商品单价 * @param goods 商品 * @param user 用户信息 + * @param {Object} shopInfo */ -export function returnGoodsPrice(goods, user) { +export function returnGoodsPrice(goods, user, shopInfo) { + if (!goods) { + return 0; + } if (goods.discount_sale_amount * 1 > 0) { return goods.discount_sale_amount; } + if (shopInfo && !shopInfo.isMemberPrice) { + return goods.salePrice; + } if (user.isVip && goods.memberPrice * 1 <= goods.salePrice * 1 && goods.memberPrice * 1 > 0) { return goods.memberPrice; } @@ -75,116 +64,183 @@ export function returnCoupType(coupon) { * @param user 用户信息 */ export function returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user) { + const types = [2, 4, 6]; + // 收集已抵扣商品并关联对应的优惠券类型 const goodsCouponGoods = selCoupon - .filter((v) => v.type == 2) - .reduce((prve, cur) => { - prve.push(...cur.discount.hasDiscountGoodsArr); - return prve; + .filter((v) => types.includes(v.type)) + .reduce((prev, cur) => { + // 给每个抵扣商品添加所属优惠券类型 + const goodsWithType = cur.discount.hasDiscountGoodsArr.map((goods) => ({ + ...goods, + couponType: cur.type, // 记录该商品是被哪种类型的优惠券抵扣的 + })); + prev.push(...goodsWithType); + return prev; }, []); + const arr = _.cloneDeep(canDikouGoodsArr) .map((v) => { const findCart = goodsCouponGoods.find((carts) => carts.id == v.id); if (findCart) { - v.num -= findCart.num; + // 根据优惠券类型判断扣减数量 + if ([4, 6].includes(findCart.couponType)) { + // 类型4(第二件半价)或6(买一送一),数量减2 + v.num -= 2; + } else { + // 其他类型(如类型2商品券),按原逻辑扣减对应数量 + v.num -= findCart.num; + } } return v; }) - .filter((v) => v.num > 0); + .filter((v) => v.num > 0); // 过滤掉数量<=0的商品 + return arr; } /** - * 判断优惠券是否可使用 + * 判断优惠券是否可使用,并返回不可用原因 * * @param {Object} args - 函数参数集合 - * @param {Array} args.canDikouGoodsArr - 可参与抵扣的商品列表,每个元素包含商品信息(productId、num等) + * @param {Array} args.canDikouGoodsArr - 可参与抵扣的商品列表 * @param {Object} args.coupon - 优惠券信息对象 - * @param {boolean} args.coupon.use - 优惠券是否启用(true为启用,false为禁用) - * @param {Array} args.coupon.useFoods - 优惠券适用的商品ID列表(空数组表示适用全部商品) + * @param {boolean} args.coupon.use - 优惠券是否启用 + * @param {Array} args.coupon.useFoods - 优惠券适用的商品ID列表 * @param {number} args.coupon.fullAmount - 优惠券使用门槛金额 - * @param {number} args.coupon.type - 优惠券类型(1:满减券, 2:商品券, 3:折扣券, 4:第二件半价券, 6:买一送一券) - * @param {number} args.goodsOrderPrice - 订单中所有商品的总金额(未筛选时的初始金额) - * @param {Object} args.user - 用户信息对象(用于计算商品价格,如会员价等) - * @param {Object} args.user - 用户信息对象(用于计算商品价格,如会员价等) + * @param {number} args.coupon.type - 优惠券类型 + * @param {number} args.goodsOrderPrice - 订单中所有商品的总金额 + * @param {Object} args.user - 用户信息对象 * @param {Object} args.selCoupon - 已经选择的优惠券信息对象 - * @returns {boolean} - 优惠券是否可用(true可用,false不可用) + * @param {Object} args.shopInfo + * @returns {Object} - { canUse: boolean, reason: string } 可用状态及不可用原因 */ export function returnCouponCanUse(args) { - let { canDikouGoodsArr, coupon, goodsOrderPrice, user, selCoupon } = args; + let { canDikouGoodsArr, coupon, goodsOrderPrice, user, selCoupon, shopInfo } = args; + + // 优惠券未启用 if (!coupon.use) { - return false; + return { + canUse: false, + reason: "优惠券未启用", + }; } - canDikouGoodsArr = returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user); // 计算门槛金额 let fullAmount = goodsOrderPrice; - //是否抵扣全部商品 - const isDikouAll = coupon.useFoods.length === 0; - let canCalcGoodsArr = []; - // 订单里参与门槛计算的商品 - if (!isDikouAll) { + canDikouGoodsArr = returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user, shopInfo); + + //优惠券指定门槛商品列表 + let canCalcGoodsArr = [...canDikouGoodsArr]; + //部分商品参与门槛计算 + if (coupon.thresholdFoods.length) { canCalcGoodsArr = canDikouGoodsArr.filter((v) => { - return coupon.useFoods.find((food) => food.id == v.productId); + return coupon.thresholdFoods.find((food) => food.id == v.productId); }); fullAmount = canCalcGoodsArr.reduce((pre, cur) => { - return pre + returnGoodsPrice(cur, user) * cur.num; + return pre + returnGoodsPrice(cur, user, shopInfo) * cur.num; }, 0); } - //没有符合商品 - if (!isDikouAll && canCalcGoodsArr.length == 0) { - return false; + // 是否全部商品可用 + const isDikouAll = coupon.useFoods.length === 0; + // 订单可用商品列表 + let canUseGoodsArr = []; + if (!isDikouAll) { + canUseGoodsArr = canDikouGoodsArr.filter((v) => { + return coupon.useFoods.find((food) => food.id == v.productId); + }); + } + if (user.isVip && !coupon.vipPriceShare) { + return { + canUse: false, + reason: "非会员可用", + }; + } + if (selCoupon.length > 0 && !selCoupon[0].otherCouponShare) { + return { + canUse: false, + reason: "当前选中的券不可与其他券同享", + }; + } + if (selCoupon.length > 0 && !coupon.otherCouponShare) { + return { + canUse: false, + reason: "当前选中的券不可与其他券同享", + }; + } + // 满减券和折扣券计算门槛金额是否满足 + if ([1, 3].includes(coupon.type)) { + if (canCalcGoodsArr.length <= 0) { + return { + canUse: false, + reason: "没有可参与计算门槛的商品", + }; + } + // 不满足门槛金额 + if (fullAmount < coupon.fullAmount) { + return { + canUse: false, + reason: `满${coupon.fullAmount}元可用,当前可参与金额${fullAmount}元`, + }; + } + } + // 商品兑换券,第二件半价和买一送一判断是否有可用商品 + if ([2, 4, 5].includes(coupon.type)) { + // 没有符合条件的商品 + if (!isDikouAll && canCalcGoodsArr.length === 0) { + return { + canUse: false, + reason: "没有符合条件的商品", + }; + } + } + //商品兑换券是否达到门槛金额 + if (coupon.type == 2 && goodsOrderPrice < coupon.fullAmount) { + return { + canUse: false, + reason: `满${coupon.fullAmount}元可用,当前可参与金额${fullAmount}元`, + }; } - //不满足门槛金额 - if (fullAmount < coupon.fullAmount) { - console.log("不满足门槛金额"); - return false; - } - - if (coupon.type == 2) { - //商品券 - return isDikouAll || canCalcGoodsArr.length > 0; - } - if (coupon.type == 1) { - //满减券 - return fullAmount >= coupon.fullAmount; - } - if (coupon.type == 6) { - //买一送一券 - + // 买一送一券特殊验证 + if (coupon.type === 6) { let canUse = false; if (isDikouAll) { canUse = canDikouGoodsArr.some((v) => v.num >= 2); + } else if (canCalcGoodsArr.length > 0) { + canUse = canCalcGoodsArr.some((v) => v.num >= 2); } - if (canCalcGoodsArr.length > 0) { - canUse = canDikouGoodsArr - .filter((v) => { - return coupon.useFoods.find((food) => food.id == v.productId); - }) - .some((v) => v.num >= 2); + + if (!canUse) { + return { + canUse: false, + reason: "需要购买至少2件相同的商品才能使用", + }; } - return canUse; } - if (coupon.type == 4) { - //第二件半价券 + + // 第二件半价券特殊验证 + if (coupon.type === 4) { let canUse = false; if (isDikouAll) { canUse = canDikouGoodsArr.some((v) => v.num >= 2); + } else if (canCalcGoodsArr.length > 0) { + canUse = canCalcGoodsArr.some((v) => v.num >= 2); } - if (canCalcGoodsArr.length > 0) { - canUse = canDikouGoodsArr - .filter((v) => { - return coupon.useFoods.find((food) => food.id == v.productId); - }) - .some((v) => v.num >= 2); + + if (!canUse) { + return { + canUse: false, + reason: "需要购买至少2件相同的商品才能使用", + }; } - return canUse; - } - if (coupon.type == 3) { - //折扣券 - return true; } + + // 所有条件都满足 + return { + canUse: true, + reason: "", + }; } /** @@ -192,8 +248,9 @@ export function returnCouponCanUse(args) { * @param discountGoodsArr 可抵扣商品列表 * @param discountNum 抵扣数量 * @param user 用户信息 + * @param {Object} shopInfo 店铺信息 */ -export function calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user) { +export function calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user, shopInfo) { let hasCountNum = 0; let discountPrice = 0; let hasDiscountGoodsArr = []; @@ -204,31 +261,38 @@ export function calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user) { const goods = discountGoodsArr[i]; const shengyuNum = discountNum - hasCountNum; const num = Math.min(goods.num, shengyuNum); - discountPrice += returnGoodsPrice(goods, user) * num; + discountPrice += returnGoodsPrice(goods, user, shopInfo) * num; hasCountNum += num; - hasDiscountGoodsArr.push({ ...goods, num }); + hasDiscountGoodsArr.push({ + ...goods, + num, + }); } - return { discountPrice, hasDiscountGoodsArr }; + return { + discountPrice, + hasDiscountGoodsArr, + }; } /** * 计算优惠券抵扣金额 - * @param canDikouGoodsArr 可抵扣商品列表 + * @param arr 可抵扣商品列表 * @param coupon 优惠券 * @param user 用户信息 * @param goodsOrderPrice 商品订单金额 * @param selCoupon 已选择的优惠券列表 + * @param shopInfo 店铺信息 */ -export function returnCouponDiscount(canDikouGoodsArr, coupon, user, goodsOrderPrice, selCoupon) { - canDikouGoodsArr = returnCanDikouGoodsArr(canDikouGoodsArr, selCoupon, user); +export function returnCouponDiscount(arr, coupon, user, goodsOrderPrice, selCoupon, shopInfo) { + const canDikouGoodsArr = returnCanDikouGoodsArr(arr, selCoupon, user); if (coupon.type == 2) { - return returnCouponProductDiscount(canDikouGoodsArr, coupon, user, goodsOrderPrice); + return returnCouponProductDiscount(canDikouGoodsArr, coupon, user, shopInfo); } if (coupon.type == 6) { - return returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, goodsOrderPrice); + return returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopInfo); } if (coupon.type == 4) { - return returnSecoendDiscount(canDikouGoodsArr, coupon, user, goodsOrderPrice); + return returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo); } if (coupon.type == 3) { return returnCouponZhekouDiscount(canDikouGoodsArr, coupon, user, goodsOrderPrice, selCoupon); @@ -253,21 +317,28 @@ export function returnCouponZhekouDiscount( ) { const { discountRate, maxDiscountAmount } = coupon; + // 计算商品优惠券折扣总和,使用BigNumber避免精度问题 const goodsCouponDiscount = selCoupon .filter((v) => v.type == 2) .reduce((prve, cur) => { - return prve + cur.discount.discountPrice; - }, 0); - goodsOrderPrice -= goodsCouponDiscount; + return new BigNumber(prve).plus(new BigNumber(cur.discount.discountPrice)); + }, new BigNumber(0)); - // 使用bignumber处理高精度计算 - // 1. 计算折扣率(百分比转小数):discountRate / 100 - const discountRatio = new BigNumber(discountRate).dividedBy(100); - // 2. 计算优惠比例:1 - 折扣率(例如:8折的优惠比例是 1 - 0.8 = 0.2) - const discountAmountRatio = new BigNumber(1).minus(discountRatio); - // 3. 计算折扣金额:商品订单金额 × 优惠比例 - let discountPrice = new BigNumber(goodsOrderPrice).times(discountAmountRatio).toNumber(); - if (maxDiscountAmount != 0) { + // 将商品订单价格转换为BigNumber并减去优惠券折扣 + const adjustedGoodsOrderPrice = new BigNumber(goodsOrderPrice).minus(goodsCouponDiscount); + console.log("adjustedGoodsOrderPrice", adjustedGoodsOrderPrice.toNumber()); + + // 计算优惠比例:(100 - 折扣率) / 100 + const discountAmountRatio = new BigNumber(100).minus(discountRate).dividedBy(100); + + // 计算折扣金额:调整后的商品订单金额 × 优惠比例 + let discountPrice = adjustedGoodsOrderPrice + .times(discountAmountRatio) + .decimalPlaces(2, BigNumber.ROUND_FLOOR) + .toNumber(); + + // 应用最大折扣金额限制 + if (maxDiscountAmount !== 0) { discountPrice = discountPrice >= maxDiscountAmount ? maxDiscountAmount : discountPrice; } @@ -282,13 +353,12 @@ export function returnCouponZhekouDiscount( * @param canDikouGoodsArr 可抵扣商品列表 * @param coupon 优惠券 * @param user 用户信息 + * @param shopInfo 店铺信息 */ -export function returnCouponProductDiscount(canDikouGoodsArr, coupon, user) { +export function returnCouponProductDiscount(canDikouGoodsArr, coupon, user, shopInfo) { const { useFoods, discountNum, useRule } = coupon; - //抵扣商品数组 let discountGoodsArr = []; - //抵扣全部商品 if (useFoods.length === 0) { if (useRule == "price_asc") { @@ -311,8 +381,7 @@ export function returnCouponProductDiscount(canDikouGoodsArr, coupon, user) { discountGoodsArr = discountSelGoodsArr.slice(0, discountNum); } } - const result = calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user); - console.log(result); + const result = calcDiscountGoodsArrPrice(discountGoodsArr, discountNum, user, shopInfo); return result; } @@ -321,8 +390,9 @@ export function returnCouponProductDiscount(canDikouGoodsArr, coupon, user) { * @param canDikouGoodsArr 可抵扣商品列表 * @param coupon 优惠券 * @param user 用户信息 + * @param shopInfo 店铺信息 */ -function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user) { +function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user, shopInfo) { const { useFoods, useRule } = coupon; //抵扣商品 let discountGoods = undefined; @@ -338,18 +408,18 @@ function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user) { } else { //符合抵扣条件的商品 const canUseGoods1 = canUseGoods.filter((v) => useFoods.find((food) => food.id == v.productId)); - console.log(canUseGoods1); if (useRule == "price_asc") { discountGoods = canUseGoods1[canUseGoods1.length - 1]; } else { discountGoods = canUseGoods1.slice(0, 1); } } - console.log("discountGoods"); - console.log(discountGoods); - const discountPrice = returnGoodsPrice(discountGoods, user); + const discountPrice = returnGoodsPrice(discountGoods, user, shopInfo); const hasDiscountGoodsArr = [discountGoods]; - return { discountPrice, hasDiscountGoodsArr }; + return { + discountPrice, + hasDiscountGoodsArr, + }; } /** @@ -357,8 +427,9 @@ function returnCouponBuyOneGiveOneDiscount(canDikouGoodsArr, coupon, user) { * @param canDikouGoodsArr 可抵扣商品列表 * @param coupon 优惠券 * @param user 用户信息 + * @param shopInfo 店铺信息 */ -function returnSecoendDiscount(canDikouGoodsArr, coupon, user) { +function returnSecoendDiscount(canDikouGoodsArr, coupon, user, shopInfo) { const { useFoods, useRule } = coupon; //抵扣商品 let discountGoods = undefined; @@ -374,14 +445,15 @@ function returnSecoendDiscount(canDikouGoodsArr, coupon, user) { } else { //符合抵扣条件的商品 const canUseGoods1 = canUseGoods.filter((v) => useFoods.find((food) => food.id == v.productId)); - console.log(canUseGoods1); if (useRule == "price_asc") { discountGoods = canUseGoods1[canUseGoods1.length - 1]; } else { discountGoods = canUseGoods1.slice(0, 1); } } - const discountPrice = returnGoodsPrice(discountGoods, user); + console.log("returnSecoendDiscount:discountGoods", discountGoods); + const discountPrice = returnGoodsPrice(discountGoods[0], user, shopInfo); + console.log("returnSecoendDiscount:discountPrice", discountPrice); const hasDiscountGoodsArr = [discountGoods]; //返回半价价格 return { @@ -389,3 +461,23 @@ function returnSecoendDiscount(canDikouGoodsArr, coupon, user) { hasDiscountGoodsArr, }; } + +/** + * 返回可以抵扣优惠券的商品列表,过滤掉赠品、临时商品,价格从高到低排序 + * @param arr 商品列表 + * @param user 用户信息 + * @param shopInfo 店铺信息 + */ +export function returnCanDikouGoods(arr, user, shopInfo) { + const result = arr + .filter((v) => { + return v.is_temporary != 1 && v.is_gift != 1; + }) + .filter((v) => { + return v.num > 0; + }) + .sort((a, b) => { + return returnGoodsPrice(b, user, shopInfo) - returnGoodsPrice(a, user, shopInfo); + }); + return result; +} diff --git a/src/utils/goods.ts b/src/utils/goods.ts index 8b8a43c..2bdf643 100644 --- a/src/utils/goods.ts +++ b/src/utils/goods.ts @@ -60,8 +60,8 @@ export interface BackendCoupon { id?: number; // 自增主键(int64) shopId?: number; // 店铺ID(int64) syncId?: number; // 同步Id(int64) - couponType?: number; // 优惠券类型:1-满减券,2-商品兑换券,3-折扣券,4-第二件半价券,5-消费送券,6-买一送一券,7-固定价格券,8-免配送费券 - title?: string; // 券名称 + type?: number; // 优惠券类型:1-满减券,2-商品兑换券,3-折扣券,4-第二件半价券,5-消费送券,6-买一送一券,7-固定价格券,8-免配送费券 + name?: string; // 券名称 useShopType?: string; // 可用门店类型:only-仅本店;all-所有门店,custom-指定门店 useShops?: string; // 可用门店(逗号分隔字符串,如"1,2,3") useType?: string; // 可使用类型:dine堂食/pickup自取/deliv配送/express快递 @@ -228,6 +228,7 @@ export interface OrderExtraConfig { userPoints: number; // 用户当前积分(用于积分抵扣) isMember: boolean; // 用户是否会员(用于会员优惠) memberDiscountRate?: number; // 会员折扣率(如0.95=95折,无会员价时用) + newUserDiscount?: number; // 新用户减免金额(元,默认0) } /** 订单费用汇总(修改:补充商家减免类型和明细) */ @@ -251,6 +252,9 @@ export interface OrderCostSummary { finalPayAmount: number; // 最终实付金额 couponUsed?: Coupon; // 实际使用的优惠券 pointUsed: number; // 实际使用的积分 + newUserDiscount: number; // 新用户减免金额(元,默认0) + dinnerType?: 'dine-in' | 'take-out'; // 就餐类型(堂食/自取/配送/快递) + } // ============================ 2. 基础工具函数(核心修正:所有商品ID匹配用product_id) ============================ @@ -269,15 +273,15 @@ export function convertBackendCouponToToolCoupon( currentTime: Date = new Date() ): Coupon | null { // 1. 基础校验:必选字段缺失直接返回null - if (!backendCoupon.id || backendCoupon.type === undefined || !backendCoupon.title) { + if (!backendCoupon.id || backendCoupon.type === undefined) { console.warn('优惠券必选字段缺失', backendCoupon); return null; } // 2. 转换券类型:后端数字枚举 → 工具库字符串枚举 - const couponType = mapBackendCouponTypeToTool(backendCoupon.couponType); + const couponType = mapBackendCouponTypeToTool(backendCoupon.type); if (!couponType) { - console.warn(`不支持的优惠券类型:${backendCoupon.couponType}(券ID:${backendCoupon.id})`); + console.warn(`不支持的优惠券类型:${backendCoupon.type}(券ID:${backendCoupon.id})`); return null; } @@ -286,16 +290,17 @@ export function convertBackendCouponToToolCoupon( ? [] // 空字符串/undefined → 全部商品(按商品ID匹配) : backendCoupon.foods.split(',').map(id => id.trim()); // 逗号分隔 → 指定商品ID数组 + const useType = backendCoupon?.useType?.split(',')?.map(v => v.replace(/[\[\]]/g, '').replace(/""/g, '"').replace(/["']/g, '')) || []; // 4. 计算基础公共字段(含多维度可用性校验) const baseCoupon: BaseCoupon = { id: backendCoupon.id, type: couponType, - name: backendCoupon.title, + name: backendCoupon.name || '', available: isCouponAvailable(backendCoupon, currentStoreId, dinnerType, currentTime), useShops: getApplicableStoreIds(backendCoupon, currentStoreId), discountShare: backendCoupon.discountShare === 1, vipPriceShare: backendCoupon.vipPriceShare === 1, - useType: backendCoupon.useType ? backendCoupon.useType.split(',') : [], + useType: useType, isValid: isCouponInValidPeriod(backendCoupon, currentTime), applicableProductIds: applicableProductIds, }; @@ -365,7 +370,7 @@ function isCouponAvailable( currentTime: Date = new Date() ): boolean { // 1. 状态校验:必须启用(status=1) - if (backendCoupon.status !== 1) return false; + if (backendCoupon.status === 0) return false; // 3. 有效期校验:必须在有效期内 if (!isCouponInValidPeriod(backendCoupon, currentTime)) return false; @@ -377,13 +382,13 @@ function isCouponAvailable( if (!isCouponInDailyTimeRange(backendCoupon, currentTime)) return false; // 6. 每周周期校验:当前星期几需在可用周期内(useDays非空时生效) - if (!isCouponInWeekDays(backendCoupon, currentTime)) return false; + // if (!isCouponInWeekDays(backendCoupon, currentTime)) return false; // 7. 门店匹配校验:当前门店需在适用门店范围内 - if (!isStoreMatch(backendCoupon, currentStoreId)) return false; + // if (!isStoreMatch(backendCoupon, currentStoreId)) return false; // 8. 就餐类型校验:当前就餐类型需在可用类型范围内 - if (!isDinnerTypeMatch(backendCoupon, dinnerType)) return false; + // if (!isDinnerTypeMatch(backendCoupon, dinnerType)) return false; return true; } @@ -1188,81 +1193,29 @@ export function calcCouponDeduction( usedCoupon?: Coupon; excludedProductIds: string[]; // 排除的商品ID列表(商品ID) } { - // 1. 后端优惠券转工具库Coupon(过滤无效/不支持的券) - const toolCoupons = backendCoupons - .map(coupon => convertBackendCouponToToolCoupon( - coupon, - config.currentStoreId, - config.dinnerType, - config.currentTime - )) - .filter(Boolean) as Coupon[]; - if (toolCoupons.length === 0) { - return { - deductionAmount: 0, - productCouponDeduction: 0, - fullCouponDeduction: 0, - excludedProductIds: [] - }; - } - console.log('toolCoupons', toolCoupons) - - // 2. 优惠券互斥逻辑:兑换券与其他券互斥,优先选最优 - const exchangeCoupons = toolCoupons.filter(c => c.type === CouponType.EXCHANGE); - const nonExchangeCoupons = toolCoupons.filter(c => c.type !== CouponType.EXCHANGE); + const goodsCoupon = backendCoupons.filter(v => v.type == 2) + const discountCoupon = backendCoupons.filter(v => v.type != 2) // 3. 计算非兑换券最优抵扣(传递已抵扣商品ID,避免重复,统计细分字段) let nonExchangeResult: CouponResult = { - deductionAmount: 0, + deductionAmount: discountCoupon.reduce((prve, cur): number => { + return prve + (cur.discountAmount || 0) + }, 0), excludedProductIds: [], usedCoupon: undefined, productCouponDeduction: 0, fullCouponDeduction: 0 }; - if (nonExchangeCoupons.length > 0) { - nonExchangeResult = nonExchangeCoupons.reduce((best, coupon) => { - const strategy = getCouponStrategy(coupon.type); - const result = strategy.calculate(coupon, goodsList, { - ...config, - excludedProductIds: best.excludedProductIds // 传递已排除的商品ID(商品ID) - }); - const currentResult: CouponResult = { - deductionAmount: result.deductionAmount, - excludedProductIds: result.excludedProductIds, - usedCoupon: coupon, - // 按策略返回的字段赋值细分抵扣 - productCouponDeduction: result.productCouponDeduction || 0, - fullCouponDeduction: result.fullCouponDeduction || 0 - }; - // 按总抵扣金额选择最优 - return new BigNumber(currentResult.deductionAmount).isGreaterThan(best.deductionAmount) - ? currentResult - : best; - }, nonExchangeResult); - } // 4. 计算兑换券抵扣(排除非兑换券已抵扣的商品ID,统计商品券细分) let exchangeResult: ExchangeCalculationResult = { - deductionAmount: 0, + deductionAmount: goodsCoupon.reduce((prve, cur): number => { + return prve + (cur.discountAmount || 0) + }, 0), excludedProductIds: [], productCouponDeduction: 0 }; - if (exchangeCoupons.length > 0) { - exchangeResult = exchangeCoupons.reduce((best, coupon) => { - const strategy = getCouponStrategy(coupon.type); - const result = strategy.calculate(coupon, goodsList, { - ...config, - excludedProductIds: [...nonExchangeResult.excludedProductIds, ...best.excludedProductIds] // 合并排除的商品ID - }); - return new BigNumber(result.deductionAmount).isGreaterThan(best.deductionAmount) - ? { - deductionAmount: result.deductionAmount, - excludedProductIds: result.excludedProductIds, - productCouponDeduction: result.productCouponDeduction || 0 // 兑换券属于商品券 - } - : best; - }, exchangeResult); - } + // 5. 汇总结果:兑换券与非兑换券不可同时使用,取抵扣金额大的 const exchangeBn = new BigNumber(exchangeResult.deductionAmount); @@ -1270,9 +1223,9 @@ export function calcCouponDeduction( const isExchangeBetter = exchangeBn.isGreaterThan(nonExchangeBn); return { - deductionAmount: truncateToTwoDecimals(isExchangeBetter ? exchangeResult.deductionAmount : nonExchangeResult.deductionAmount), - productCouponDeduction: isExchangeBetter ? exchangeResult.productCouponDeduction : nonExchangeResult.productCouponDeduction, - fullCouponDeduction: isExchangeBetter ? 0 : nonExchangeResult.fullCouponDeduction, // 兑换券与满减券互斥,满减券抵扣置0 + deductionAmount: exchangeBn.plus(nonExchangeBn).toNumber(), + productCouponDeduction: exchangeResult.deductionAmount, + fullCouponDeduction: nonExchangeResult.deductionAmount, // 兑换券与满减券互斥,满减券抵扣置0 usedCoupon: isExchangeBetter ? undefined : nonExchangeResult.usedCoupon, excludedProductIds: isExchangeBetter ? exchangeResult.excludedProductIds : nonExchangeResult.excludedProductIds }; @@ -1289,6 +1242,7 @@ export function calcTotalPackFee( goodsList: BaseCartItem[], dinnerType: 'dine-in' | 'take-out' ): number { + if (dinnerType !== 'take-out') return 0; let total = new BigNumber(0); for (const goods of goodsList) { @@ -1298,7 +1252,7 @@ export function calcTotalPackFee( // 计算单个商品打包数量(外卖全打包,堂食按配置,称重商品≤1) let packNum = dinnerType === 'take-out' ? availableNum - : (goods.packNumber || 0); + : (0); if (goods.product_type === GoodsType.WEIGHT) { packNum = Math.min(packNum, 1); } @@ -1415,8 +1369,11 @@ export function calculateOrderCostSummary( ); // 3. 其他费用计算(原有逻辑不变) + // 新客立减 + const newUserDiscount = config.newUserDiscount || 0; const packFee = calcTotalPackFee(goodsList, dinnerType); - const seatFee = calcSeatFee(config.seatFeeConfig); + let seatFee = calcSeatFee(config.seatFeeConfig); + seatFee = dinnerType === 'dine-in' ? seatFee : 0; // 外卖不收餐位费 const additionalFee = Math.max(0, config.additionalFee); // 4. 积分抵扣(原有逻辑不变,先于商家减免计算) @@ -1475,6 +1432,7 @@ export function calculateOrderCostSummary( const finalPayAmount = new BigNumber(goodsOriginalAmount) // 商品原价总和 .minus(goodsDiscountAmount) // 减去商品折扣 .minus(couponDeductionAmount) // 减去优惠券抵扣 + .minus(newUserDiscount) // 新客立减 .minus(pointDeductionAmount) // 减去积分抵扣 .minus(merchantReductionActualAmount) // 减去商家实际减免金额 .plus(seatFee) // 加上餐位费(不参与减免) @@ -1503,7 +1461,9 @@ export function calculateOrderCostSummary( additionalFee, finalPayAmount: truncateToTwoDecimals(finalPayAmountNonNegative), couponUsed: usedCoupon, - pointUsed: usedPoints + pointUsed: usedPoints, + newUserDiscount, + dinnerType }; } @@ -1522,6 +1482,7 @@ export const OrderPriceCalculator = { isWeightGoods, // 优惠券转换 convertBackendCouponToToolCoupon, + mapBackendCouponTypeToTool, // 商品价格计算 calcSingleGoodsRealPrice, calcGoodsOriginalAmount, diff --git a/src/views/admin/system/role/index.vue b/src/views/admin/system/role/index.vue index 3e42fe3..c1a8174 100644 --- a/src/views/admin/system/role/index.vue +++ b/src/views/admin/system/role/index.vue @@ -358,7 +358,7 @@ function handleSubmit() { } else { delete formData.id; - RoleApi.add({ ...formData, menuIdList: checkedMenuIds }) + RoleApi.add({ ...formData, menuIdList: [] }) .then(() => { ElMessage.success("新增成功"); handleCloseDialog(); diff --git a/src/views/tool/Instead/components/carts/list.vue b/src/views/tool/Instead/components/carts/list.vue index a6c2618..0323f85 100644 --- a/src/views/tool/Instead/components/carts/list.vue +++ b/src/views/tool/Instead/components/carts/list.vue @@ -125,7 +125,9 @@ 更多支付 diff --git a/src/views/tool/Instead/components/coupon/index.vue b/src/views/tool/Instead/components/coupon/index.vue new file mode 100644 index 0000000..46fca40 --- /dev/null +++ b/src/views/tool/Instead/components/coupon/index.vue @@ -0,0 +1,84 @@ + + + \ No newline at end of file diff --git a/src/views/tool/Instead/components/discount.vue b/src/views/tool/Instead/components/discount.vue index fa423af..fc79edb 100644 --- a/src/views/tool/Instead/components/discount.vue +++ b/src/views/tool/Instead/components/discount.vue @@ -148,11 +148,12 @@ function init(key) { const emits = defineEmits(["confirm"]); function confirm() { console.log(form.value); - if (discountType.value == 1) { - emits("confirm", { discount: form.value.discount }); - } else { - emits("confirm", { discountAmount: form.value.reduceMoney }); - } + // if (discountType.value == 1) { + // emits("confirm", { discount: form.value.discount }); + // } else { + // } + emits("confirm", { discountAmount: form.value.reduceMoney }); + close(); } function open(data) { diff --git a/src/views/tool/Instead/components/order.vue b/src/views/tool/Instead/components/order.vue index 72ffbd1..01809ad 100644 --- a/src/views/tool/Instead/components/order.vue +++ b/src/views/tool/Instead/components/order.vue @@ -47,6 +47,7 @@ v-if="score.sel == 1" v-model="usePointsNumber" step-strictly + :step="pointsRes.equivalentPoints" placeholder="请输入积分抵扣数量" :min="pointsRes.minDeductionPoints" :max="pointsRes.maxUsablePoints" @@ -80,9 +81,10 @@ -
+ {{ carts.orderCostSummary }} +
已选优惠券
- + @@ -294,8 +296,6 @@ function refCouponConfirm(e, goodsList) { quansSelArr.value = e; checkOrderPay.discountAmount = 0; checkOrderPay.discount = 0; - score.sel = -1; - usePointsNumber.value = 0; } function delQuan(row) { const index = quansSelArr.value.findIndex((v) => v.id == row.id); @@ -340,8 +340,7 @@ function returnMerchantReductionDiscount() { const total = goodsOriginalAmount - // 商品原价总和 goodsDiscountAmount - // 减去商品折扣 - couponDeductionAmount - // 减去优惠券抵扣 - pointDeductionAmount; // 减去积分抵扣 + couponDeductionAmount; // 减去优惠券抵扣 return total <= 0 ? 0 : total; } @@ -436,7 +435,6 @@ async function pointsInit() { carts.pointDeductionRule.pointsPerYuan = res.equivalentPoints; usePointsNumber.value = res.usable ? res.maxUsablePoints : 0; - carts.userPoints = usePointsNumber.value; if (res.usable) { pointsToMoney(); } else { @@ -491,6 +489,12 @@ watch( pointsInit(); } ); +watch( + () => usePointsNumber.value, + (newval) => { + carts.userPoints = newval; + } +); function canUsePayType(item) { if (currentpayMoney.value * 1 == 0) { return item.payType == "cash" ? false : true; @@ -512,6 +516,7 @@ function changePayType(i) { } function returnPayParams() { + console.log("carts.orderCostSummary", carts.orderCostSummary); return { money: currentpayMoney.value * 1, shopId: localStorage.getItem("shopId"), @@ -522,17 +527,15 @@ function returnPayParams() { // discountRatio: (checkOrderPay.discount / 100).toFixed(2), discountRatio: 0, seatNum: props.perpole * 1, - originAmount: carts.payMoney * 1 + seatAmount.value * 1, + originAmount: carts.orderCostSummary.goodsRealAmount, discountAmount: discountAmount.value, - productCouponDiscountAmount: productCouponDiscountAmount.value * 1, - otherCouponDiscountAmount: - carts.orderCostSummary.couponDeductionAmount - productCouponDiscountAmount.value * 1, - orderAmount: currentpayMoney.value * 1, + productCouponDiscountAmount: carts.orderCostSummary.productCouponDeduction, + otherCouponDiscountAmount: carts.orderCostSummary.fullCouponDeduction, + orderAmount: carts.orderCostSummary.finalPayAmount, // 最终订单金额 roundAmount: props.orderInfo.roundAmount, - pointsDiscountAmount: pointsDiscountAmount.value * 1, - pointsNum: usePointsNumber.value * 1, - fullCouponDiscountAmount: fullCouponDiscountAmount.value * 1, - couponList: quansSelArr.value.map((v) => v.id), + pointsDiscountAmount: carts.orderCostSummary.pointDeductionAmount, //积分抵扣金额 + pointsNum: carts.orderCostSummary.pointUsed, + couponList: carts.coupons.map((v) => v.id), userId: props.user.userId || "", allPack: carts.dinnerType == "take-out" ? 1 : 0, }, @@ -667,11 +670,7 @@ const fullCouponDiscountAmount = computed(() => { //商品券抵扣金额 const productCouponDiscountAmount = computed(() => { // 优先从 Store 扩展字段取,若无则用 props 数据(过渡方案) - return ( - carts.orderCostSummary.productCouponDeduction || - props.orderInfo.productCouponDiscountAmount || - 0 - ); + return carts.orderCostSummary.productCouponDeduction; }); //除开客座费,打包费总金额 const totalMoney = computed(() => { @@ -717,6 +716,7 @@ watch( } ); onMounted(() => { + carts.payParamsInit(); getPaytype(); }); defineExpose({ diff --git a/src/views/tool/Instead/components/popup-coupon.vue b/src/views/tool/Instead/components/popup-coupon.vue index 67c3a3a..3d73411 100644 --- a/src/views/tool/Instead/components/popup-coupon.vue +++ b/src/views/tool/Instead/components/popup-coupon.vue @@ -4,7 +4,12 @@
- +