Преглед изворни кода

feat(web): 商品编辑页支持套餐类型及套餐组成配置

Xiaogang Liao пре 1 месец
родитељ
комит
d1baa0a114
1 измењених фајлова са 102 додато и 2 уклоњено
  1. 102 2
      cfc-web/src/views/admin/ProductEdit.vue

+ 102 - 2
cfc-web/src/views/admin/ProductEdit.vue

@@ -19,6 +19,7 @@
             <el-option label="虚拟商品" value="virtual" />
             <el-option label="优惠券" value="coupon" />
             <el-option label="测评商品" value="assessment" />
+            <el-option label="套餐" value="bundle" />
           </el-select>
         </el-form-item>
 
@@ -53,6 +54,25 @@
           </el-row>
         </template>
 
+        <!-- 套餐商品扩展信息 -->
+        <template v-if="form.productType === 'bundle'">
+          <el-divider content-position="left">套餐组成</el-divider>
+          <el-form-item label="组成商品">
+            <div v-for="(item, idx) in bundleItems" :key="'bi' + idx" style="display:flex; align-items:center; margin-bottom:8px; gap:8px; flex-wrap:wrap;">
+              <el-select v-model="item.childProductId" filterable remote placeholder="搜索子商品" :remote-method="(q) => searchBundleProduct(q, idx)" style="width:220px;" @change="(pid) => onBundleProductChange(pid, idx)">
+                <el-option v-for="p in item.productOptions" :key="p.id" :label="(p.name || 'ID:' + p.id) + ' (库存:' + (p.stock || 0) + ')'" :value="p.id" />
+              </el-select>
+              <el-select v-if="item.skuOptions.length" v-model="item.childSkuId" placeholder="选择规格" clearable style="width:160px;">
+                <el-option v-for="s in item.skuOptions" :key="s.id" :label="(s.specs || '默认') + ' (库存:' + (s.stock || 0) + ')'" :value="s.id" />
+              </el-select>
+              <el-input-number v-model="item.quantity" :min="1" :precision="0" size="small" style="width:110px;" />
+              <el-button type="danger" size="mini" @click="removeBundleItem(idx)">删除</el-button>
+            </div>
+            <el-button type="primary" size="mini" plain @click="addBundleItem">+ 添加子商品</el-button>
+            <div style="margin-top:8px; font-size:12px; color:#999;">套餐出库时自动完成所有组成商品出库(数量 × 套餐数量)</div>
+          </el-form-item>
+        </template>
+
         <!-- 关联成长档案 -->
         <el-divider content-position="left">关联成长档案</el-divider>
         <el-form-item label="关联成长档案">
@@ -293,7 +313,7 @@
 </template>
 
 <script>
-import { getProduct, createProduct, updateProduct, listAllSupplySystems, getPredefinedPurchaseFields, getProductPurchaseFields, saveProductPurchaseFields, getMySupplySystem, saveProductGiftRule, listProductGiftRules, listMembershipLevels, getGrowthArchiveProduct, saveGrowthArchiveProduct, listDeliveryServicePersons, saveDeliveryServicePersons, listDeliveryOnlineSlots, saveDeliveryOnlineSlots } from '@/api/admin'
+import { getProduct, createProduct, updateProduct, listAllSupplySystems, getPredefinedPurchaseFields, getProductPurchaseFields, saveProductPurchaseFields, getMySupplySystem, saveProductGiftRule, listProductGiftRules, listMembershipLevels, getGrowthArchiveProduct, saveGrowthArchiveProduct, listDeliveryServicePersons, saveDeliveryServicePersons, listDeliveryOnlineSlots, saveDeliveryOnlineSlots, getProductList, getProductSkuList, saveBundleItems, getBundleItems } from '@/api/admin'
 import { saveDimensionWeights, getDimensionWeights } from '@/api/dimension-weight'
 import DimensionWeightPicker from '@/components/DimensionWeightPicker.vue'
 import ProductSkuEditor from './components/ProductSkuEditor.vue'
@@ -329,6 +349,7 @@ export default {
         guideScope: 'all',
         sortOrder: 0
       },
+      bundleItems: [], // 套餐组成: [{childProductId, childSkuId, quantity, productOptions:[], skuOptions:[]}]
       servicePersons: [], // 线上服务者(deliveryMethod=4): [{userId,name,title,intro,avatar,price,originalPrice,slots:[{startTime,endTime,maxCapacity}]}]
       onlineSlots: [], // 单人线上时段(deliveryMethod=5): [{startTime,endTime,maxCapacity}]
       growthArchive: {
@@ -373,8 +394,73 @@ export default {
               }
             }
           }
+}
+    },
+    addBundleItem() {
+      this.bundleItems.push({
+        childProductId: null,
+        childSkuId: null,
+        quantity: 1,
+        productOptions: [],
+        skuOptions: []
+      })
+    },
+    removeBundleItem(idx) {
+      this.bundleItems.splice(idx, 1)
+    },
+    async searchBundleProduct(query, idx) {
+      if (!query || query.length < 1) return
+      try {
+        var res = await getProductList({ keyword: query, page: 1, size: 20 })
+        if (res.code === 200 && res.data && Array.isArray(res.data.records)) {
+          this.bundleItems[idx].productOptions = res.data.records
         }
-      },
+      } catch (e) { /* ignore */ }
+    },
+    async onBundleProductChange(productId, idx) {
+      var item = this.bundleItems[idx]
+      item.childSkuId = null
+      item.skuOptions = []
+      if (!productId) return
+      try {
+        var res = await getProductSkuList(productId)
+        if (res.code === 200 && Array.isArray(res.data)) {
+          item.skuOptions = res.data
+        }
+      } catch (e) { /* ignore */ }
+    },
+    buildBundleItemPayload() {
+      return this.bundleItems
+        .filter(function(b) { return b.childProductId })
+        .map(function(b) {
+          return {
+            childProductId: b.childProductId,
+            childSkuId: b.childSkuId || null,
+            quantity: b.quantity || 1
+          }
+        })
+    },
+    async loadBundleItems(productId) {
+      try {
+        var res = await getBundleItems(productId)
+        if (res.code === 200 && Array.isArray(res.data)) {
+          this.bundleItems = res.data.map(function(b) {
+            return {
+              childProductId: b.childProductId,
+              childSkuId: b.childSkuId || null,
+              quantity: b.quantity || 1,
+              productOptions: [],
+              skuOptions: []
+            }
+          })
+          this.bundleItems.forEach(function(item, idx) {
+            if (item.childProductId) {
+              this.onBundleProductChange(item.childProductId, idx)
+            }
+          }, this)
+        }
+      } catch (e) { /* ignore */ }
+    },
       toolbarConfig: {},
       editorInstance: null
     }
@@ -493,6 +579,10 @@ export default {
           this.loadGrowthArchive(p.id)
           // Load delivery config (service persons / online slots)
           this.loadDeliveryConfig(p.id)
+          // Load bundle items config(套餐商品)
+          if (p.productType === 'bundle') {
+            this.loadBundleItems(p.id)
+          }
           // v-model 已绑定 form.description,editor 内容自动更新
         }
       } catch (e) {
@@ -806,6 +896,16 @@ async loadGiftRule(productId) {
                 console.warn('保存线上服务配置失败', e)
               }
             }
+            // Save bundle items config(套餐商品组成)
+            if (productId) {
+              try {
+                var bundlePayload = this.buildBundleItemPayload()
+                await saveBundleItems(productId, bundlePayload)
+              } catch (e) {
+                this.$message.error('保存套餐组成失败')
+                console.warn('保存套餐组成失败', e)
+              }
+            }
             this.$message.success(this.isEdit ? '保存成功' : '创建成功')
             this.$router.push('/product-manage')
           } else {