Kaynağa Gözat

feat(shop): 下单页兑换CF券后自动选中并立即使用

- CouponService.listUnownedExchangeable: productId 非空时同时放行全场通用券(product_id IS NULL),与 ownedCoupons 的 ALL 判定一致
- FamilyPlatformPointsService.exchangeCouponByCf: 返回券实例ID(family_coupon.id);FamilyPlatformBalanceController 透传到 Result.data
- checkout.vue doExchangeCoupon: 兑换成功后按实例ID在 /coupon/list 结果中精确匹配并自动选中,交服务端 applyCouponDiscount 计算折扣
zxyj-web 2 hafta önce
ebeveyn
işleme
6c123aaff6

+ 3 - 4
cfc-backend/src/main/java/com/etotem/cfc/controller/FamilyPlatformBalanceController.java

@@ -55,7 +55,7 @@ public class FamilyPlatformBalanceController {
 
     /** 用家庭CF值兑换优惠券 */
     @PostMapping("/exchange-coupon")
-    public Result<String> exchangeCoupon(
+    public Result<Long> exchangeCoupon(
             @RequestAttribute("userId") Long userId,
             @RequestBody Map<String, Object> params) {
         if (userId == null) return Result.error("未登录");
@@ -66,9 +66,8 @@ public class FamilyPlatformBalanceController {
         Long couponId = ParamUtils.getLong(params.get("couponId"));
         if (couponId == null) return Result.error("couponId不能为空");
         try {
-            familyPlatformPointsService.exchangeCouponByCf(user.getFamilyId(), couponId, userId);
-            // TODO: 实际发券逻辑待实现
-            return Result.success("兑换成功");
+            Long familyCouponId = familyPlatformPointsService.exchangeCouponByCf(user.getFamilyId(), couponId, userId);
+            return Result.success("兑换成功", familyCouponId);
         } catch (RuntimeException e) {
             return Result.error(e.getMessage());
         }

+ 2 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/CouponService.java

@@ -565,7 +565,8 @@ public class CouponService {
                 .notIn(!ownedIds.isEmpty(), Coupon::getId, ownedIds)
                 .orderByAsc(Coupon::getPointsPrice);
         if (productId != null) {
-            wrapper.eq(Coupon::getProductId, productId);
+            // 商品券 + 全场通用券(product_id 为 NULL)都可兑换,与 ownedCoupons 的 ALL 判定保持一致
+            wrapper.and(w -> w.isNull(Coupon::getProductId).or().eq(Coupon::getProductId, productId));
         }
         return couponMapper.selectList(wrapper);
     }

+ 4 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyPlatformPointsService.java

@@ -217,7 +217,7 @@ public class FamilyPlatformPointsService {
      * 用家庭CF值兑换优惠券:校验 → 扣家庭池 → 发券 → 落记录
      */
     @Transactional
-    public void exchangeCouponByCf(Long familyId, Long couponId, Long refUserId) {
+    public Long exchangeCouponByCf(Long familyId, Long couponId, Long refUserId) {
         // 1. 校验优惠券
         Coupon coupon = couponMapper.selectById(couponId);
         if (coupon == null) {
@@ -295,6 +295,9 @@ public class FamilyPlatformPointsService {
         record.setCreatedAt(now);
         record.setUpdatedAt(now);
         exchangeRecordMapper.insert(record);
+
+        // 返回券实例ID(family_coupon.id),供前端兑换后直接选中该券
+        return familyCouponId;
     }
 
     /**

+ 19 - 0
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -765,6 +765,25 @@ export default {
           await Promise.all([this.loadCoupons(), this.loadExchangeableCoupons()])
           // 刷新 CF 余额(刚扣掉一笔)
           this.loadCfBalance()
+          // 兑换后已自动选中:后端返回券实例ID(family_coupon.id,
+          // 与 /coupon/list 里的 id/userCouponId 同值),匹配到即选中并交由服务端算折扣。
+          // 匹配不到(列表未刷到/券已失效)不报错,维持未选中的现状。
+          var newCouponId = parseInt(res.data)
+          if (!isNaN(newCouponId) && newCouponId > 0) {
+            var matched = null
+            var myCoupons = this.coupons || []
+            for (var i = 0; i < myCoupons.length; i++) {
+              if (myCoupons[i] && parseInt(myCoupons[i].id) === newCouponId) {
+                matched = myCoupons[i]
+                break
+              }
+            }
+            if (matched) {
+              this.selectedCoupon = matched
+              // 门槛/范围不满足时该方法会自动清空选中并提示,属预期(券已入账我的优惠券)
+              this.applyCouponDiscount()
+            }
+          }
         } else {
           uni.showToast({ title: res.message || '兑换失败', icon: 'none' })
         }