소스 검색

chore: auto bump version and changelog [skip ci]

iwt 1 개월 전
부모
커밋
d2af00c294

+ 30 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/CouponController.java

@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import java.util.List;
+import java.util.ArrayList;
 import java.util.Map;
 
 @RestController
@@ -71,6 +72,35 @@ public class CouponController {
         return Result.success(couponService.listByProduct(productId));
     }
 
+    /**
+     * 结算页专用:返回可兑换但未拥有的优惠券(需要 CF 值兑换)
+     */
+    @PostMapping("/checkout-list")
+    public Result<List<Map<String, Object>>> checkoutList(
+            @RequestAttribute("userId") Long userId,
+            @RequestBody Map<String, Object> params) {
+        if (userId == null) return Result.error("未登录");
+        Long productId = params.get("productId") != null
+                ? Long.valueOf(params.get("productId").toString()) : null;
+        List<Coupon> coupons = couponService.listUnownedExchangeable(userId, productId);
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (Coupon c : coupons) {
+            Map<String, Object> m = new java.util.HashMap<>();
+            m.put("id", c.getId());
+            m.put("name", c.getName());
+            m.put("type", c.getType());
+            m.put("value", c.getValue());
+            m.put("discountRate", c.getDiscountRate());
+            m.put("pointsPrice", c.getPointsPrice());
+            m.put("minSpend", c.getMinSpend());
+            m.put("applicableTo", c.getApplicableTo());
+            m.put("status", "AVAILABLE");
+            m.put("isExchangeable", true);
+            result.add(m);
+        }
+        return Result.success(result);
+    }
+
     @PostMapping("/join-rules")
     public Result<List<Coupon>> joinRules() {
         return Result.success(couponService.listJoinRules());

+ 23 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/CouponService.java

@@ -251,6 +251,29 @@ public class CouponService {
                         .orderByAsc(Coupon::getPointsPrice));
     }
 
+    /**
+     * 查询用户尚未拥有的可兑换优惠券(用于结算页直接兑换)
+     * @param productId 商品ID,非空时只返回该商品绑定的券
+     */
+    public List<Coupon> listUnownedExchangeable(Long userId, Long productId) {
+        Date now = new Date();
+        List<Long> ownedIds = userCouponMapper.selectList(
+                new LambdaQueryWrapper<UserCoupon>()
+                        .eq(UserCoupon::getUserId, userId)
+                        .eq(UserCoupon::getStatus, "AVAILABLE"))
+                .stream().map(UserCoupon::getCouponId).collect(java.util.stream.Collectors.toList());
+        LambdaQueryWrapper<Coupon> wrapper = new LambdaQueryWrapper<Coupon>()
+                .gt(Coupon::getPointsPrice, 0)
+                .le(Coupon::getValidFrom, now)
+                .ge(Coupon::getValidUntil, now)
+                .notIn(!ownedIds.isEmpty(), Coupon::getId, ownedIds)
+                .orderByAsc(Coupon::getPointsPrice);
+        if (productId != null) {
+            wrapper.eq(Coupon::getProductId, productId);
+        }
+        return couponMapper.selectList(wrapper);
+    }
+
     /** JOIN 权益展示:所有加入赠券模板 */
     public List<Coupon> listJoinRules() {
         return couponMapper.selectList(

+ 94 - 3
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -234,8 +234,10 @@
           <text class="picker-close" @click="closeCouponPicker">关闭</text>
         </view>
         <scroll-view scroll-y class="picker-list">
+          <!-- 已拥有优惠券 -->
+          <view class="picker-section-title" v-if="ownedCoupons.length > 0">已拥有优惠券</view>
           <view
-            v-for="coupon in availableCoupons"
+            v-for="coupon in ownedCoupons"
             :key="coupon.id"
             class="picker-item"
             :class="selectedCoupon && selectedCoupon.id === coupon.id ? 'picker-item-active' : ''"
@@ -252,7 +254,31 @@
               <text class="check-icon">✓</text>
             </view>
           </view>
-          <view class="picker-empty" v-if="availableCoupons.length === 0">
+          <!-- 可兑换优惠券(需CF值) -->
+          <view class="picker-section-title" v-if="exchangeableCoupons.length > 0">
+            可用CF值兑换(当前余额 {{ cfBalance }} CF)
+          </view>
+          <view
+            v-for="coupon in exchangeableCoupons"
+            :key="coupon.id"
+            class="picker-item picker-item-exchange"
+          >
+            <view class="picker-item-left">
+              <text class="picker-item-value">{{ formatPriceWithSymbol(coupon.value) }}</text>
+            </view>
+            <view class="picker-item-right">
+              <text class="picker-item-name">{{ coupon.name }}</text>
+              <text class="picker-item-condition">{{ getConditionText(coupon.minSpend) }}</text>
+            </view>
+            <button
+              class="exchange-btn"
+              :disabled="coupon.exchanging"
+              @click.stop="doExchangeCoupon(coupon)"
+            >
+              {{ coupon.exchanging ? '兑换中...' : '兑换' }}
+            </button>
+          </view>
+          <view class="picker-empty" v-if="ownedCoupons.length === 0 && exchangeableCoupons.length === 0">
             <text>暂无可用优惠券</text>
           </view>
         </scroll-view>
@@ -386,7 +412,7 @@
 
 <script>
 import config from '@/config.js'
-import { getCouponList, addressList, getProductRequiredFields, getMyMembership, productDetail } from '../../../utils/api.js'
+import { getCouponList, getCouponCheckoutList, addressList, getProductRequiredFields, getMyMembership, productDetail, getFamilyPlatformBalance, exchangeCouponByCf } from '../../../utils/api.js'
 import { parseDate } from '@/utils/format.js'
 
 export default {
@@ -403,6 +429,8 @@ export default {
       submitting: false,
       actualAmount: 0,
       coupons: [],
+      exchangeableCoupons: [],
+      cfBalance: 0,
       selectedCoupon: null,
       showCouponPicker: false,
       pointsUsed: 0,
@@ -449,6 +477,15 @@ export default {
     pointsYuanEquivalent() {
       return (this.pointsUsed / 100).toFixed(2)
     },
+    ownedCoupons() {
+      var self = this
+      return this.coupons.filter(function(c) {
+        if (c.status && c.status !== 'AVAILABLE') return false
+        if (c.applicableTo && c.applicableTo !== 'ALL') return false
+        if (c.minSpend && c.minSpend > self.actualAmount) return false
+        return true
+      })
+    },
     availableCoupons() {
       var self = this
       return this.coupons.filter(function(c) {
@@ -493,6 +530,8 @@ export default {
     this.loadDefaultAddress()
     this.loadRequiredFields()
     this.loadCoupons()
+    this.loadExchangeableCoupons()
+    this.loadCfBalance()
     this.loadUserPoints()
     this.loadDeliveryConfig()
   },
@@ -516,6 +555,58 @@ export default {
         this.coupons = []
       }
     },
+    async loadExchangeableCoupons() {
+      try {
+        var productId = null
+        if (this.items && this.items.length > 0) {
+          productId = this.items[0].productId
+        }
+        var res = await getCouponCheckoutList({ productId: productId })
+        this.exchangeableCoupons = res.data || []
+      } catch (e) {
+        console.error('加载可兑换优惠券失败', e)
+        this.exchangeableCoupons = []
+      }
+    },
+    async loadCfBalance() {
+      try {
+        var res = await getFamilyPlatformBalance()
+        if (res.code === 200 && res.data) {
+          this.cfBalance = res.data.available || 0
+        }
+      } catch (e) {
+        console.error('加载CF余额失败', e)
+      }
+    },
+    async doExchangeCoupon(coupon) {
+      if (coupon.pointsPrice > this.cfBalance) {
+        uni.showToast({ title: 'CF值不足,当前可用 ' + this.cfBalance + ' CF值', icon: 'none' })
+        return
+      }
+      uni.showLoading({ title: '兑换中...' })
+      try {
+        var res = await exchangeCouponByCf({ couponId: coupon.id })
+        uni.hideLoading()
+        if (res.code === 200) {
+          uni.showToast({ title: '兑换成功', icon: 'success' })
+          // 从可兑换列表移除,加入已拥有列表
+          var idx = this.exchangeableCoupons.findIndex(function(c) { return c.id === coupon.id })
+          if (idx >= 0) {
+            this.exchangeableCoupons.splice(idx, 1)
+          }
+          coupon.isExchangeable = false
+          coupon.status = 'AVAILABLE'
+          this.coupons.push(coupon)
+          // 刷新余额
+          this.loadCfBalance()
+        } else {
+          uni.showToast({ title: res.message || '兑换失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.hideLoading()
+        uni.showToast({ title: '兑换失败', icon: 'none' })
+      }
+    },
     openCouponPicker() {
       if (this.availableCoupons.length === 0 && !this.selectedCoupon) {
         uni.showToast({ title: '暂无可用优惠券', icon: 'none' })

+ 4 - 0
cfc-frontend/utils/api.js

@@ -2262,6 +2262,10 @@ export const getExchangeableCoupons = () => {
   return request('/api/coupon/exchangeable', 'POST', {})
 }
 
+export const getCouponCheckoutList = (data) => {
+  return request('/api/coupon/checkout-list', 'POST', data)
+}
+
 export const getProductCoupons = (data) => {
   return request('/api/coupon/product', 'POST', data)
 }

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-846e8263f422abc9f018c50ecda06fc5a13a7180
+bf06b88231e64e312e8c1d5434d667a97444055b

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1018",
+  "version": "1.0.1019",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1018",
+      "version": "1.0.1019",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1019",
+  "version": "1.0.1020",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 13 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,19 @@
 
 ---
 
+## v1.0.1020 (2026-08-13)
+
+### 新功能
+- nutrition_product 增加成份/配料表(ingredients)字段及后台管理展示
+
+### 其他
+- - schema.sql: nutrition_product 建表同步 ingredients TEXT 列, suitable_for 升级为 TEXT
+- - DatabaseInitializer: 新增迁移228(ingredients 列 + suitable_for MODIFY), 幂等
+- - cfc-web: NutritionProducts 列表/详情展示成份配料表
+- - 数据已通过独立脚本提交至京东云 RDS 生产库(zxyj)
+- 
+
+
 ## v1.0.1019 (2026-08-13)
 
 ### 新功能

+ 14 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1019
+> 当前版本: v1.0.1020
 
 ## 历史版本
 
@@ -8,6 +8,19 @@
 
 ---
 
+## v1.0.1020 (2026-08-13)
+
+### 新功能
+- nutrition_product 增加成份/配料表(ingredients)字段及后台管理展示
+
+### 其他
+- - schema.sql: nutrition_product 建表同步 ingredients TEXT 列, suitable_for 升级为 TEXT
+- - DatabaseInitializer: 新增迁移228(ingredients 列 + suitable_for MODIFY), 幂等
+- - cfc-web: NutritionProducts 列表/详情展示成份配料表
+- - 数据已通过独立脚本提交至京东云 RDS 生产库(zxyj)
+- 
+
+
 ## v1.0.1019 (2026-08-13)
 
 ### 新功能