Просмотр исходного кода

feat(backend): 新增套餐内容表 product_combo_items(迁移437)与实体/Mapper

Xiaogang Liao 1 неделя назад
Родитель
Сommit
cc5ca873ab

+ 27 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -3406,6 +3406,9 @@ insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达
 
         // 迁移437: T35 活动管理团队 —— activities.team_id + activity_team/activity_team_member/service_assignment 表
         migrateActivityTeam();
+        // 迁移440: 套餐内容明细表(套餐=独立商品,内容=子商品SKU×数量,挂套餐SKU级)
+        migrateProductComboItems();
+
     }
 
     /**
@@ -3611,6 +3614,30 @@ insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达
         }
     }
 
+    /**
+     * 迁移440:套餐内容明细表 product_combo_items。
+     * <p>套餐=独立商品(product_type='bundle'),内容=套餐包含哪些子商品(精确到子商品SKU)×数量;
+     * 内容挂套餐SKU级(combo_sku_id)。下单/支付只动套餐SKU自身库存,不扣子商品库存。
+     * <p>幂等:CREATE TABLE IF NOT EXISTS + 异常兜底。
+     */
+    private void migrateProductComboItems() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `product_combo_items` ("
+                    + "`id` BIGINT NOT NULL AUTO_INCREMENT, "
+                    + "`combo_sku_id` BIGINT NOT NULL COMMENT '套餐SKU ID(product_skus.id),内容挂SKU级', "
+                    + "`child_product_id` BIGINT NOT NULL COMMENT '子商品ID', "
+                    + "`child_sku_id` BIGINT NOT NULL COMMENT '子商品SKU ID(精确到规格,必填)', "
+                    + "`quantity` INT NOT NULL DEFAULT 1 COMMENT '该子商品数量', "
+                    + "`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                    + "PRIMARY KEY (`id`), "
+                    + "UNIQUE KEY `uk_combo_sku_child` (`combo_sku_id`, `child_sku_id`) "
+                    + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='套餐内容明细(套餐商品包含的子商品SKU×数量)'");
+            log.info("迁移440: 已创建 product_combo_items 表");
+        } catch (Exception e) {
+            log.warn("迁移440: 创建 product_combo_items 表失败(可能已存在): {}", e.getMessage());
+        }
+    }
+
     private void migrateHealthDimensionsTable() {
         try {
             if (tableExists("health_dimension_score")) {

+ 7 - 16
cfc-backend/src/main/java/com/etotem/cfc/entity/ProductComboItem.java

@@ -9,12 +9,6 @@ import lombok.Data;
 import java.io.Serializable;
 import java.util.Date;
 
-/**
- * 商品套餐内容(商品级定义、SKU 级键控):
- * 一个套餐商品包含哪些子商品(子商品 SKU × 数量)。
- * 归属列 combo_sku_id = 套餐 SKU(套餐商品通常单规格,即套餐唯一 SKU)。
- * 不引用旧命名(product_sku_combo_items / product_bundle_items),避免与旧机制混淆。
- */
 @Data
 @TableName("product_combo_items")
 public class ProductComboItem implements Serializable {
@@ -22,25 +16,22 @@ public class ProductComboItem implements Serializable {
     @TableId(type = IdType.AUTO)
     private Long id;
 
-    /** 套餐 SKU id(内容归属) */
+    /** 套餐SKU ID(内容挂套餐SKU级) */
     private Long comboSkuId;
-
-    /** 子商品 */
+    /** 子商品ID */
     private Long childProductId;
-
-    /** 子商品 SKU(必填,精确到规格) */
+    /** 子商品SKU ID(精确到规格,必填) */
     private Long childSkuId;
-
-    /** 数量(>0) */
+    /** 该子商品数量 */
     private Integer quantity;
-
     private Date createdAt;
 
-    /* ---- 展示字段(不落库,listComboItems 回填) ---- */
     @TableField(exist = false)
     private String childProductName;
     @TableField(exist = false)
     private String childProductImage;
     @TableField(exist = false)
+    private Integer childProductPrice;
+    @TableField(exist = false)
     private String childSkuSpecs;
-}
+}

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/mapper/ProductComboItemMapper.java

@@ -6,4 +6,4 @@ import org.apache.ibatis.annotations.Mapper;
 
 @Mapper
 public interface ProductComboItemMapper extends BaseMapper<ProductComboItem> {
-}
+}

+ 12 - 0
cfc-backend/src/main/resources/schema.sql

@@ -5656,6 +5656,18 @@ CREATE TABLE IF NOT EXISTS product_sku_combo_items (
     INDEX idx_child_product (child_product_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SKU组合明细';
 
+-- 套餐内容明细表(套餐=独立商品 product_type='bundle',内容=子商品SKU×数量,挂套餐SKU级)
+CREATE TABLE IF NOT EXISTS product_combo_items (
+    id BIGINT NOT NULL AUTO_INCREMENT,
+    combo_sku_id BIGINT NOT NULL COMMENT '套餐SKU ID(product_skus.id),内容挂SKU级',
+    child_product_id BIGINT NOT NULL COMMENT '子商品ID',
+    child_sku_id BIGINT NOT NULL COMMENT '子商品SKU ID(精确到规格,必填)',
+    quantity INT NOT NULL DEFAULT 1 COMMENT '该子商品数量',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_combo_sku_child (combo_sku_id, child_sku_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='套餐内容明细(套餐商品包含的子商品SKU×数量)';
+
 -- 联系人表(cfclub 联系人管理)
 CREATE TABLE IF NOT EXISTS contacts (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,

+ 9 - 4
cfc-web/src/views/Dashboard.vue

@@ -65,7 +65,7 @@
             </div>
           </div>
           <div class="dwell-list">
-            <div class="dwell-item" v-for="(row, i) in this[sec.key]" :key="i">
+            <div class="dwell-item" v-for="(row, i) in sectionList(sec.key)" :key="i">
               <span class="dwell-rank" :class="'rank-' + (i + 1)">{{ i + 1 }}</span>
               <div class="dwell-main">
                 <div class="dwell-name" :title="row.name">{{ row.name }}</div>
@@ -78,7 +78,7 @@
                 <span class="dwell-val__meta">{{ row.meta }}</span>
               </div>
             </div>
-            <div v-if="!((this[sec.key] || []).length)" class="dwell-empty">
+            <div v-if="!sectionList(sec.key).length" class="dwell-empty">
               <i class="el-icon-data-analysis"></i>
               <span>{{ sec.emptyHint }}</span>
             </div>
@@ -132,7 +132,7 @@
             <span class="hot-card__sub">{{ sec.sub }}</span>
           </div>
           <div class="hot-list">
-            <div class="hot-item" v-for="(row, i) in this[sec.key]" :key="i">
+            <div class="hot-item" v-for="(row, i) in sectionList(sec.key)" :key="i">
               <span class="hot-rank" :class="'rank-' + (i + 1)">{{ i + 1 }}</span>
               <div class="hot-main">
                 <div class="hot-name" :title="row.name">{{ row.name }}</div>
@@ -142,7 +142,7 @@
               </div>
               <div class="hot-val adm-num">{{ row.value }}<span class="hot-val__unit">{{ row.unit }}</span></div>
             </div>
-            <div v-if="!((this[sec.key] || []).length)" class="hot-empty">暂无数据</div>
+            <div v-if="!sectionList(sec.key).length" class="hot-empty">暂无数据</div>
           </div>
         </div>
       </el-col>
@@ -410,6 +410,11 @@ export default {
       return v || ''
     },
     // 停留时长 / 热度 相对最大值,用于条形宽度
+    sectionList(key) {
+      // v-for 回调内 this 不是组件(Vue2 普通函数 this=undefined),
+      // 故模板用 sectionList(sec.key) 而非 this[sec.key](会抛 undefined['articleDwell'])
+      return this[key] || []
+    },
     sectionMax(key) {
       const list = this[key] || []
       if (!list.length) return 0