Quellcode durchsuchen

feat: 商品详情页迁移至 discover-detail + 微信订单中心商品明细

- 前端: 商品详情页从 shop/detail/detail 迁移至 discover-detail/product-detail,更新全部引用
- 后端: PaymentService 支持微信订单中心商品明细(goods_detail)展示
- 活动/会员/商品订单支付描述优化(用真实名称替代固定文案)
Xiaogang Liao vor 1 Monat
Ursprung
Commit
118dd4d2f3

+ 7 - 2
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -10416,6 +10416,10 @@ public class DatabaseInitializer implements CommandLineRunner {
             log.warn("迁移298第二步失败", e);
         }
 
+        // 迁移299: promotion_tier 表添加 rate_percent 列(实体新模型返佣比例;修复 PromotionTierCheckScheduledTask 每天01:00 Unknown column 崩溃,2026-09-06)
+        ensureColumn("promotion_tier", "rate_percent", "INT DEFAULT 0 COMMENT '当前返佣比例(%)'");
+ (feat: 商品详情页迁移至 discover-detail + 微信订单中心商品明细)
+
         // ===== 生产库对账候选风险记录(已核对生产库,2026-09,按用户决定不迁移/暂不动,仅记录)=====
         // 注:以下为 entity 期望但生产库未对齐.项目,用户已决定保持现状或记为候选,勿重复迁移。
         // 1. children 表缺 phone、id_card(Child 实体+ChildMapper 仍活跃,AccountCleanupService
@@ -10425,7 +10429,7 @@ public class DatabaseInitializer implements CommandLineRunner {
         //    实体期望 id/trigger_type/trigger_action/status/created_at 共 8 列,表 0 行且不在 schema.sql。
         //    UserSegmentationService.getTriggerRules 会查 status,命中时会报 Unknown column 'status'。候选。
         // 3. promotion_tier.rate_percent:生产用旧模型(tier/team_size_1st/2nd/3rd/total_*),实体期望新模型,
-        //    表 0 行。PromotionTier 服务广泛使用。候选。
+        //    表 0 行。已于迁移298补充 rate_percent 列(2026-09-06 定时任务崩溃修复)。
         // 4. promotion_tier_config:实体已废弃,改用 cf_rate_tier(迁移279),保持现状。
 
         // 迁移298: tasks表新增is_daily_progress/target_value字段 + source_type注释更新(统一任务系统)
@@ -10473,4 +10477,5 @@ public class DatabaseInitializer implements CommandLineRunner {
         }
         // 说明:growth_task_log 历史进度、onboarding_task 存量数据仅保留在旧表(只读备份),不回填 tasks(历史数据无法1:1映射到新模型)
     }
-}
+}
+        // PROBE_1788631291_298

+ 5 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/ActivityOrderService.java

@@ -130,8 +130,12 @@ public class ActivityOrderService {
         if (invalidOpenid) {
             return Result.error("未绑定微信账号,请通过微信登录后再支付");
         }
+        Activity activity = activityMapper.selectById(order.getActivityId());
+        String description = (activity != null && activity.getTitle() != null)
+                ? "活动报名-" + activity.getTitle()
+                : "活动报名";
         Map<String, Object> wechatPayParams = paymentService.createWechatPrepay(
-                orderNo, "活动报名", order.getTotalAmount(), openid);
+                orderNo, description, order.getTotalAmount(), openid);
         Map<String, Object> result = new HashMap<>();
         result.put("wechatPayParams", wechatPayParams);
         result.put("orderNo", orderNo);

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

@@ -631,7 +631,8 @@ public class MembershipService implements MembershipServiceInterface {
         }
 
         // 普通微信支付(v3 JSAPI)
-        String description = "浠艾福-" + levelCode + "会员";
+        String levelName = level != null && level.getLevelName() != null ? level.getLevelName() : levelCode;
+        String description = "浠艾福-" + levelName + "会员";
         Map<String, Object> payParams = paymentService.createWechatPrepay(
                 mainOrder.getOrderNo(), description, mainOrder.getAmount(), openid);
         resp.put("payType", "wechat");

+ 30 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/PaymentService.java

@@ -1,6 +1,7 @@
 package com.etotem.cfc.service;
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.entity.PackageOrder;
@@ -36,6 +37,7 @@ import java.util.Base64;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
@@ -263,6 +265,18 @@ public class PaymentService implements PaymentServiceInterface {
     }
 
     public Map<String, Object> createWechatPrepay(String orderNo, String description, Integer totalFee, String openid) {
+        return createWechatPrepay(orderNo, description, totalFee, openid, null);
+    }
+
+    /**
+     * 微信 JSAPI 下单(v3 /v3/pay/transactions/jsapi)
+     *
+     * @param goodsDetail 商品明细列表(微信订单中心展示用),元素为 Map,需包含
+     *                    merchant_goods_id / goods_name / quantity / unit_price(单位:分)。
+     *                    传 null 或空列表时不携带 detail 字段。
+     */
+    public Map<String, Object> createWechatPrepay(String orderNo, String description, Integer totalFee,
+                                                  String openid, List<Map<String, Object>> goodsDetail) {
         try {
             JSONObject body = new JSONObject();
             body.put("appid", appid);
@@ -280,6 +294,22 @@ public class PaymentService implements PaymentServiceInterface {
             amount.put("currency", "CNY");
             body.put("amount", amount);
 
+            // 商品明细(微信订单中心展示商品名/数量/单价)
+            if (goodsDetail != null && !goodsDetail.isEmpty()) {
+                JSONObject detail = new JSONObject();
+                JSONArray goodsArr = new JSONArray();
+                for (Map<String, Object> g : goodsDetail) {
+                    JSONObject item = new JSONObject();
+                    item.put("merchant_goods_id", g.get("merchant_goods_id"));
+                    item.put("goods_name", g.get("goods_name"));
+                    item.put("quantity", g.get("quantity"));
+                    item.put("unit_price", g.get("unit_price"));
+                    goodsArr.add(item);
+                }
+                detail.put("goods_detail", goodsArr);
+                body.put("detail", detail);
+            }
+
             String respBody = doPost("/v3/pay/transactions/jsapi", body.toJSONString());
             JSONObject resp = JSON.parseObject(respBody);
             String prepayId = resp.getString("prepay_id");

+ 8 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java

@@ -628,8 +628,15 @@ public class ProductOrderService {
                 }
                 return Result.error("未绑定微信账号,请通过微信登录后再支付");
             }
+            List<Map<String, Object>> goodsDetail = new ArrayList<>();
+            Map<String, Object> goods = new HashMap<>();
+            goods.put("merchant_goods_id", String.valueOf(order.getProductId()));
+            goods.put("goods_name", order.getProductName());
+            goods.put("quantity", order.getQuantity() != null ? order.getQuantity() : 1);
+            goods.put("unit_price", order.getUnitPrice() != null ? order.getUnitPrice() : moneyAmount);
+            goodsDetail.add(goods);
             Map<String, Object> wechatPayParams = paymentService.createWechatPrepay(
-                    orderNo, order.getProductName(), moneyAmount, user.getOpenid());
+                    orderNo, order.getProductName(), moneyAmount, user.getOpenid(), goodsDetail);
             Map<String, Object> result = new HashMap<>();
             result.put("wechatPayParams", wechatPayParams);
             result.put("pointsUsed", pointsUsed);

+ 0 - 6
cfc-frontend/pages.json

@@ -560,12 +560,6 @@
             "navigationBarTitleText": "编辑收货人"
           }
         },
-        {
-          "path": "detail/detail",
-          "style": {
-            "navigationBarTitleText": "商品详情"
-          }
-        },
         {
           "path": "after-sales/after-sales",
           "style": {

+ 1 - 1
cfc-frontend/pages/body-detail/dimension-detail.vue

@@ -325,7 +325,7 @@ export default {
     },
     goProduct: function(product) {
       if (product && product.id) {
-        uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + product.id })
+        uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + product.id })
       }
     },
     getImageUrl: function(path) {

+ 1 - 1
cfc-frontend/pages/discover/index.vue

@@ -194,7 +194,7 @@ export default {
       uni.navigateTo({ url: '/pages/shop/index/index' })
     },
     goDetail(id) {
-      uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id })
+      uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + id })
     },
     handleLogin() {
       uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/discover/index') })

+ 1 - 1
cfc-frontend/pages/growth-main/index.vue

@@ -311,7 +311,7 @@ export default {
     goToCaution: function() { uni.navigateTo({ url: '/pages/growth/caution-detail' }) },
     goToFoods: function() { uni.navigateTo({ url: '/pages/health/gut-flora-foods-detail' }) },
     goToArticle: function(article) { var id = article && article.id ? article.id : article; if (!id) return; uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id }) },
-    goToProduct: function(prod) { var id = prod && prod.id ? prod.id : prod; if (!id) return; uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id }) },
+    goToProduct: function(prod) { var id = prod && prod.id ? prod.id : prod; if (!id) return; uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + id }) },
     goMoreProducts: function() { uni.navigateTo({ url: '/pages/shop/index/index' }) },
     goMoreArticles: function() { uni.navigateTo({ url: '/pages/article-center/index?dimension=action' }) },
     goPage: function(url) { uni.navigateTo({ url: url }) },

+ 0 - 18
cfc-frontend/pages/shop/detail/detail.vue

@@ -1,18 +0,0 @@
-<template>
-  <view class="redirect"></view>
-</template>
-
-<script>
-export default {
-  onLoad(options) {
-    const id = options.id || ''
-    uni.redirectTo({
-      url: '/pages/discover-detail/product-detail/product-detail?id=' + id + '&source=shop'
-    })
-  }
-}
-</script>
-
-<style scoped>
-.redirect { background: #fff; min-height: 100vh; }
-</style>

+ 1 - 1
cfc-frontend/pages/shop/index/index.vue

@@ -283,7 +283,7 @@ export default {
       uni.navigateTo({ url: '/pages/membership/upgrade' })
     },
     goDetail(id) {
-      uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id })
+      uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + id })
     },
     goSearch() {
       uni.navigateTo({ url: '/pages/shop/search/search' })

+ 1 - 1
cfc-frontend/pages/shop/order-detail/order-detail.vue

@@ -220,7 +220,7 @@ export default {
     },
     onBuyAgain() {
       if (this.order.items && this.order.items.length > 0) {
-        uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + this.order.items[0].productId })
+        uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + this.order.items[0].productId })
       }
     },
     onAfterSales() {

+ 1 - 1
cfc-frontend/pages/shop/search/search.vue

@@ -147,7 +147,7 @@ export default {
       uni.navigateBack()
     },
     goDetail(id) {
-      uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id })
+      uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + id })
     },
     formatPriceWithSymbol(price) {
       if (price === null || price === undefined) return ''

+ 1 - 1
cfc-frontend/pages/wealth/index.vue

@@ -619,7 +619,7 @@ export default {
       }).catch(function() {})
     },
     goProductDetail(id) {
-      uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id })
+      uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + id })
     },
     getImageUrl: function(path) {
       return config.API_BASE_URL + path