Ver código fonte

feat(admin): 套餐内容编辑改挂商品级、SKU编辑器移除组合模式

Xiaogang Liao 1 semana atrás
pai
commit
34f7d46db7

+ 3 - 2
cfc-web/src/__tests__/components/ProductSkuEditor.mode.test.js

@@ -13,7 +13,7 @@ const localVue = createLocalVue()
 localVue.use(ElementUI)
 localVue.use(ElementUI)
 
 
 describe("ProductSkuEditor 模式选择", () => {
 describe("ProductSkuEditor 模式选择", () => {
-  it("添加SKU时不再提供「关联商品」单选模式", async () => {
+  it("添加SKU时仅保留「规格变体」模式(「商品组合」「关联商品」均已移除)", async () => {
     const wrapper = mount(ProductSkuEditor, {
     const wrapper = mount(ProductSkuEditor, {
       localVue,
       localVue,
       propsData: { productId: null },
       propsData: { productId: null },
@@ -24,7 +24,8 @@ describe("ProductSkuEditor 模式选择", () => {
     await wrapper.vm.$nextTick()
     await wrapper.vm.$nextTick()
     const buttons = wrapper.findAll(".el-radio-button").wrappers.map(w => w.text())
     const buttons = wrapper.findAll(".el-radio-button").wrappers.map(w => w.text())
     expect(buttons).not.toContain("关联商品")
     expect(buttons).not.toContain("关联商品")
-    expect(buttons).toEqual(expect.arrayContaining(["规格变体", "商品组合"]))
+    expect(buttons).not.toContain("商品组合")
+    expect(buttons).toEqual(["规格变体"])
     wrapper.destroy()
     wrapper.destroy()
   })
   })
 })
 })

+ 6 - 14
cfc-web/src/api/admin.js

@@ -2057,27 +2057,19 @@ export function getSpecialPriceQrcode(id) {
   return request({ url: '/api/admin/special-price/qrcode', method: 'post', data: { id } })
   return request({ url: '/api/admin/special-price/qrcode', method: 'post', data: { id } })
 }
 }
 
 
-export function saveBundleItems(bundleProductId, items) {
+export function saveComboItems(comboSkuId, items) {
   return request({
   return request({
-    url: '/api/admin/inventory/bundle/items',
+    url: '/api/admin/inventory/combo/items',
     method: 'post',
     method: 'post',
-    data: { bundleProductId, items }
+    data: { comboSkuId, items }
   })
   })
 }
 }
 
 
-export function getBundleItems(bundleProductId) {
+export function listComboItems(comboSkuId) {
   return request({
   return request({
-    url: '/api/admin/inventory/bundle/items/list',
+    url: '/api/admin/inventory/combo/items/list',
     method: 'post',
     method: 'post',
-    data: { bundleProductId }
-  })
-}
-
-export function checkBundleStock(bundleProductId, quantity) {
-  return request({
-    url: '/api/admin/inventory/bundle/check-stock',
-    method: 'post',
-    data: { bundleProductId, quantity }
+    data: { comboSkuId }
   })
   })
 }
 }
 
 

+ 58 - 60
cfc-web/src/views/admin/ProductEdit.vue

@@ -117,23 +117,20 @@
           </el-row>
           </el-row>
         </template>
         </template>
 
 
-        <!-- 套餐商品扩展信息 -->
+        <!-- 套餐商品扩展信息(新模型:内容=子商品SKU×数量,挂套餐SKU级 product_combo_items) -->
         <template v-if="form.productType === 'bundle'">
         <template v-if="form.productType === 'bundle'">
-          <SectionTitle title="套餐组成" />
-          <el-form-item label="组成商品">
+          <SectionTitle title="套餐内容" />
+          <el-form-item label="套餐内容">
+            <el-button size="small" type="primary" plain icon="el-icon-plus" @click="openComboPicker">选择子商品SKU</el-button>
             <div v-for="(item, idx) in bundleItems" :key="'bi' + idx" class="bundle-row">
             <div v-for="(item, idx) in bundleItems" :key="'bi' + idx" class="bundle-row">
-              <el-select v-model="item.childProductId" filterable remote placeholder="搜索子商品" :remote-method="(q) => searchBundleProduct(q, idx)" class="w-220" @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 class="w-160">
-                <el-option v-for="s in item.skuOptions" :key="s.id" :label="(s.specs || '默认') + ' (库存:' + (s.stock || 0) + ')'" :value="s.id" />
-              </el-select>
+              <span class="bundle-item-name">商品 #{{ item.childProductId }}</span>
+              <span v-if="item.childSkuId" class="bundle-item-spec">· 规格 #{{ item.childSkuId }}</span>
               <el-input-number v-model="item.quantity" :min="1" :precision="0" size="small" class="w-110" />
               <el-input-number v-model="item.quantity" :min="1" :precision="0" size="small" class="w-110" />
               <el-button type="danger" size="mini" @click="removeBundleItem(idx)">删除</el-button>
               <el-button type="danger" size="mini" @click="removeBundleItem(idx)">删除</el-button>
             </div>
             </div>
-            <el-button type="primary" size="mini" plain @click="addBundleItem()">+ 添加子商品</el-button>
-            <div class="form-hint-mt8">套餐出库时自动完成所有组成商品出库(数量 × 套餐数量)</div>
+            <div class="form-hint-mt8">套餐内容挂套餐SKU级;下单/支付只扣套餐SKU库存,不扣子商品库存(需先添加套餐SKU作锚点)。</div>
           </el-form-item>
           </el-form-item>
+          <ProductPicker ref="comboPicker" :multiple="true" :cross-category="true" :selected="comboPickerSelected" @confirm="onComboPickerConfirm" />
         </template>
         </template>
 
 
         <!-- 关联成长档案 -->
         <!-- 关联成长档案 -->
@@ -426,7 +423,7 @@
 </template>
 </template>
 
 
 <script>
 <script>
-import { getProduct, createProduct, updateProduct, listAllSupplySystems, getPredefinedPurchaseFields, getProductPurchaseFields, saveProductPurchaseFields, getMySupplySystem, saveProductGiftRule, listProductGiftRules, listMembershipLevels, getGrowthArchiveProduct, saveGrowthArchiveProduct, listDeliveryServicePersons, saveDeliveryServicePersons, listDeliveryOnlineSlots, saveDeliveryOnlineSlots, getProductList, getProductSkuList, saveBundleItems, getBundleItems, getApprovedGuides, saveProductGift, getProductGiftList, getCategoryTree } from '@/api/admin'
+import { getProduct, createProduct, updateProduct, listAllSupplySystems, getPredefinedPurchaseFields, getProductPurchaseFields, saveProductPurchaseFields, getMySupplySystem, saveProductGiftRule, listProductGiftRules, listMembershipLevels, getGrowthArchiveProduct, saveGrowthArchiveProduct, listDeliveryServicePersons, saveDeliveryServicePersons, listDeliveryOnlineSlots, saveDeliveryOnlineSlots, getProductList, getProductSkuList, saveComboItems, listComboItems, getApprovedGuides, saveProductGift, getProductGiftList, getCategoryTree } from '@/api/admin'
 import { saveDimensionWeights, getDimensionWeights } from '@/api/dimension-weight'
 import { saveDimensionWeights, getDimensionWeights } from '@/api/dimension-weight'
 import DimensionWeightPicker from '@/components/DimensionWeightPicker.vue'
 import DimensionWeightPicker from '@/components/DimensionWeightPicker.vue'
 import ContentDimensionThresholdEditor from '@/components/ContentDimensionThresholdEditor.vue'
 import ContentDimensionThresholdEditor from '@/components/ContentDimensionThresholdEditor.vue'
@@ -434,11 +431,12 @@ import ProductSkuEditor from './components/ProductSkuEditor.vue'
 import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
 import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
 import '@wangeditor/editor/dist/css/style.css'
 import '@wangeditor/editor/dist/css/style.css'
 import AssetPicker from '@/components/AssetPicker.vue'
 import AssetPicker from '@/components/AssetPicker.vue'
+import ProductPicker from './components/ProductPicker.vue'
 import { buildEditorAssetMenuConf, insertAssetIntoEditor, getEditorCustomAlert, registerAssetLibraryMenu, setAssetMenuHandler, buildAssetToolbarConfig } from '@/utils/editorAssetUpload'
 import { buildEditorAssetMenuConf, insertAssetIntoEditor, getEditorCustomAlert, registerAssetLibraryMenu, setAssetMenuHandler, buildAssetToolbarConfig } from '@/utils/editorAssetUpload'
 
 
 export default {
 export default {
   name: 'ProductEdit',
   name: 'ProductEdit',
-  components: { DimensionWeightPicker, ContentDimensionThresholdEditor, ProductSkuEditor, Editor, Toolbar, AssetPicker },
+  components: { DimensionWeightPicker, ContentDimensionThresholdEditor, ProductSkuEditor, Editor, Toolbar, AssetPicker, ProductPicker },
   data() {
   data() {
     return {
     return {
       form: {
       form: {
@@ -478,7 +476,7 @@ export default {
         sortOrder: 0,
         sortOrder: 0,
         categoryId: null
         categoryId: null
       },
       },
-      bundleItems: [], // 套餐组成: [{childProductId, childSkuId, quantity, productOptions:[], skuOptions:[]}]
+      bundleItems: [], // 套餐内容: [{childProductId, childSkuId, quantity}](挂套餐SKU级 product_combo_items)
       servicePersons: [], // 线上服务者(deliveryMethod=4): [{userId,name,title,intro,avatar,price,originalPrice}]
       servicePersons: [], // 线上服务者(deliveryMethod=4): [{userId,name,title,intro,avatar,price,originalPrice}]
       approvedGuides: [], // 已入驻服务商列表
       approvedGuides: [], // 已入驻服务商列表
       selectedGuideId: '',
       selectedGuideId: '',
@@ -514,40 +512,9 @@ export default {
       assetPickerMode: 'single',
       assetPickerMode: 'single',
       assetPickerType: 'image',
       assetPickerType: 'image',
       assetPickerTarget: 'content',
       assetPickerTarget: 'content',
-    addBundleItem() {
-      this.bundleItems = this.bundleItems || []
-      this.bundleItems.push({
-        childProductId: null,
-        childSkuId: null,
-        quantity: 1,
-        productOptions: [],
-        skuOptions: []
-      })
-    },
     removeBundleItem(idx) {
     removeBundleItem(idx) {
       this.bundleItems.splice(idx, 1)
       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() {
     buildBundleItemPayload() {
       return this.bundleItems
       return this.bundleItems
         .filter(function(b) { return b.childProductId })
         .filter(function(b) { return b.childProductId })
@@ -561,24 +528,36 @@ export default {
     },
     },
     async loadBundleItems(productId) {
     async loadBundleItems(productId) {
       try {
       try {
-        var res = await getBundleItems(productId)
+        const skuRes = await getProductSkuList(productId)
+        const skus = (skuRes && skuRes.code === 200 && Array.isArray(skuRes.data)) ? skuRes.data : []
+        if (!skus.length) {
+          this.bundleItems = []
+          return
+        }
+        const comboSkuId = skus[0].id
+        const res = await listComboItems(comboSkuId)
         if (res.code === 200 && Array.isArray(res.data)) {
         if (res.code === 200 && Array.isArray(res.data)) {
           this.bundleItems = res.data.map(function(b) {
           this.bundleItems = res.data.map(function(b) {
             return {
             return {
               childProductId: b.childProductId,
               childProductId: b.childProductId,
               childSkuId: b.childSkuId || null,
               childSkuId: b.childSkuId || null,
-              quantity: b.quantity || 1,
-              productOptions: [],
-              skuOptions: []
+              quantity: b.quantity || 1
             }
             }
           })
           })
-          this.bundleItems.forEach(function(item, idx) {
-            if (item.childProductId) {
-              this.onBundleProductChange(item.childProductId, idx)
-            }
-          }, this)
         }
         }
       } catch (e) { /* ignore */ }
       } catch (e) { /* ignore */ }
+    },
+    openComboPicker() {
+      this.$refs.comboPicker.open()
+    },
+    onComboPickerConfirm(items) {
+      this.bundleItems = (items || []).map(function(it) {
+        return {
+          childProductId: it.childProductId || it.id,
+          childSkuId: it.childSkuId || null,
+          quantity: it.quantity || 1
+        }
+      })
     }
     }
     }
     }
   },
   },
@@ -586,6 +565,15 @@ export default {
     isEdit() {
     isEdit() {
       return !!this.$route.query.id
       return !!this.$route.query.id
     },
     },
+    comboPickerSelected() {
+      return this.bundleItems.map(function(it) {
+        return {
+          childProductId: it.childProductId,
+          childSkuId: it.childSkuId,
+          quantity: it.quantity
+        }
+      })
+    },
     categoryGroups() {
     categoryGroups() {
       const groups = []
       const groups = []
       const map = {}
       const map = {}
@@ -1256,14 +1244,22 @@ async loadGiftRule(productId) {
                 console.warn('保存线上服务配置失败', e)
                 console.warn('保存线上服务配置失败', e)
               }
               }
             }
             }
-            // Save bundle items config(套餐商品组成)
-            if (productId) {
+            // Save 套餐内容(新模型:内容挂套餐SKU级 product_combo_items,锚点=首个套餐SKU)
+            if (productId && this.form.productType === 'bundle') {
               try {
               try {
-                var bundlePayload = this.buildBundleItemPayload()
-                await saveBundleItems(productId, bundlePayload)
+                var skuRes = await getProductSkuList(productId)
+                var skus = (skuRes && skuRes.code === 200 && Array.isArray(skuRes.data)) ? skuRes.data : []
+                if (!skus.length) {
+                  if (this.bundleItems.length) {
+                    this.$message.warning('请先添加套餐SKU(作为套餐售价/库存锚点),再保存套餐内容')
+                  }
+                } else {
+                  var bundlePayload = this.buildBundleItemPayload()
+                  await saveComboItems(skus[0].id, bundlePayload)
+                }
               } catch (e) {
               } catch (e) {
-                this.$message.error('保存套餐组成失败')
-                console.warn('保存套餐组成失败', e)
+                this.$message.error('保存套餐内容失败')
+                console.warn('保存套餐内容失败', e)
               }
               }
             }
             }
             this.$message.success(this.isEdit ? '保存成功' : '创建成功')
             this.$message.success(this.isEdit ? '保存成功' : '创建成功')
@@ -1316,6 +1312,8 @@ async loadGiftRule(productId) {
   gap: 8px;
   gap: 8px;
   flex-wrap: wrap;
   flex-wrap: wrap;
 }
 }
+.bundle-item-name { font-size: 13px; }
+.bundle-item-spec { font-size: 12px; color: var(--adm-text-45); }
 .sp-opt-name {
 .sp-opt-name {
   float: left;
   float: left;
 }
 }

+ 17 - 188
cfc-web/src/views/admin/components/ProductSkuEditor.vue

@@ -8,10 +8,9 @@
         <div class="adm-table-wrap">
         <div class="adm-table-wrap">
 <el-table :data="effectiveSkus" border stripe size="small" height="100%">
 <el-table :data="effectiveSkus" border stripe size="small" height="100%">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="id" label="ID" width="60" />
-      <el-table-column label="规格/组合" min-width="160">
+      <el-table-column label="规格" min-width="160">
         <template slot-scope="{ row }">
         <template slot-scope="{ row }">
-          <span v-if="row.comboItems && row.comboItems.length">组合:{{ formatComboSummary(row.comboItems) }}</span>
-          <span v-else>{{ parseSpecs(row.specs) }}</span>
+          <span>{{ parseSpecs(row.specs) }}</span>
         </template>
         </template>
       </el-table-column>
       </el-table-column>
       <el-table-column label="售价(元)" width="100">
       <el-table-column label="售价(元)" width="100">
@@ -49,7 +48,6 @@
       <el-form ref="skuForm" :model="skuForm" :rules="skuRules" label-width="80px">
       <el-form ref="skuForm" :model="skuForm" :rules="skuRules" label-width="80px">
         <el-radio-group v-model="skuMode" size="small" class="mb-16">
         <el-radio-group v-model="skuMode" size="small" class="mb-16">
           <el-radio-button label="spec">规格变体</el-radio-button>
           <el-radio-button label="spec">规格变体</el-radio-button>
-          <el-radio-button label="combo">商品组合</el-radio-button>
         </el-radio-group>
         </el-radio-group>
 
 
         <!-- 规格变体模式(原有) -->
         <!-- 规格变体模式(原有) -->
@@ -85,37 +83,6 @@
         </el-form-item>
         </el-form-item>
         </template>
         </template>
 
 
-        <!-- 商品组合模式 -->
-        <template v-else-if="skuMode === 'combo'">
-          <el-form-item label="显示标签">
-            <el-input v-model="skuForm.label" placeholder="默认显示“商品组合”" />
-          </el-form-item>
-          <el-form-item label="组合商品">
-            <el-button size="small" type="primary" plain icon="el-icon-plus" @click="openProductPicker">选择商品</el-button>
-            <div v-if="comboItems.length" class="combo-list">
-              <div v-for="(it, idx) in comboItems" :key="(it.childProductId || it.id) + '-' + (it.childSkuId || '')" class="combo-row">
-                <img v-if="it.coverImage" :src="it.coverImage" class="combo-thumb" />
-                <span v-else class="combo-thumb combo-thumb-placeholder"></span>
-                <div class="combo-main">
-                  <div>{{ it.name }}</div>
-                  <div class="combo-meta">
-                    <span v-if="it.childSkuSpecs">规格:{{ parseSpecs(it.childSkuSpecs) }}</span>
-                    <span>单价 ¥{{ ((it.price || 0)/100).toFixed(2) }}</span>
-                  </div>
-                </div>
-                <el-input-number v-model="it.quantity" :min="1" :precision="0" size="mini" class="combo-qty" />
-                <el-button type="text" class="combo-remove" @click="comboItems.splice(idx, 1)">删除</el-button>
-              </div>
-            </div>
-            <div v-else class="combo-empty">尚未添加组合商品</div>
-          </el-form-item>
-          <el-form-item label="组合售价(元)" prop="price">
-            <el-input-number v-model="skuForm.price" :min="0" :precision="2" class="full-width" />
-          </el-form-item>
-          <el-form-item label="展示库存">
-            <el-input-number v-model="skuForm.stock" :min="0" :precision="0" class="full-width" />
-          </el-form-item>
-        </template>
 
 
         <el-form-item label="启用">
         <el-form-item label="启用">
           <el-switch v-model="skuForm.enabled" :active-value="1" :inactive-value="0" />
           <el-switch v-model="skuForm.enabled" :active-value="1" :inactive-value="0" />
@@ -127,15 +94,6 @@
       </span>
       </span>
     </el-dialog>
     </el-dialog>
 
 
-    <ProductPicker
-      ref="productPicker"
-      :product-id="productId"
-      :cross-category="skuMode === 'combo'"
-      :multiple="skuMode === 'combo'"
-      :selected="comboItems.map(i => ({ childProductId: i.childProductId || i.id, childSkuId: i.childSkuId, quantity: i.quantity }))"
-      @selected="onProductSelected"
-      @confirm="onComboConfirm"
-    />
 
 
     <!-- 素材库选择器 -->
     <!-- 素材库选择器 -->
     <AssetPicker
     <AssetPicker
@@ -150,13 +108,11 @@
 
 
 <script>
 <script>
 import { getProductSkuList, createProductSku, updateProductSku, deleteProductSku } from '@/api/admin'
 import { getProductSkuList, createProductSku, updateProductSku, deleteProductSku } from '@/api/admin'
-import { formatComboSummary, normalizeComboPayload } from '@/utils/skuCombo'
-import ProductPicker from './ProductPicker.vue'
 import AssetPicker from '@/components/AssetPicker.vue'
 import AssetPicker from '@/components/AssetPicker.vue'
 
 
 export default {
 export default {
   name: 'ProductSkuEditor',
   name: 'ProductSkuEditor',
-  components: { ProductPicker, AssetPicker },
+  components: { AssetPicker },
   props: {
   props: {
     productId: {
     productId: {
       type: Number,
       type: Number,
@@ -173,7 +129,6 @@ export default {
       saving: false,
       saving: false,
       editingId: null,
       editingId: null,
       skuMode: 'spec',
       skuMode: 'spec',
-      comboItems: [],
       skuForm: {
       skuForm: {
         specs: '',
         specs: '',
         price: 0,
         price: 0,
@@ -246,65 +201,20 @@ export default {
     showDialog(sku) {
     showDialog(sku) {
       if (sku) {
       if (sku) {
         this.editingId = sku.id
         this.editingId = sku.id
-        const comboRaw = sku.comboItems || []
-        this.comboItems = comboRaw.map(it => ({
-          childProductId: it.childProductId,
-          childSkuId: it.childSkuId,
-          quantity: it.quantity || 1,
-          name: it.childProductName || ('商品#' + it.childProductId),
-          coverImage: it.childProductImage,
-          price: it.childProductPrice || 0,
-          childSkuSpecs: it.childSkuSpecs
-        }))
-        if (sku.linkedProductId != null && !comboRaw.length) {
-          // 旧「关联商品」SKU → 转换为单条商品组合(保留关联,不丢数据)
-          this.skuMode = 'combo'
-          this.comboItems = [{
-            childProductId: sku.linkedProductId,
-            childSkuId: null,
-            quantity: 1,
-            name: sku.linkedProductName || ('商品#' + sku.linkedProductId),
-            coverImage: sku.linkedProductImage || '',
-            price: sku.linkedProductPrice || 0,
-            childSkuSpecs: ''
-          }]
-        } else {
-          this.skuMode = comboRaw.length ? 'combo' : 'spec'
-        }
-        if (this.skuMode === 'spec') {
-          this.skuForm = {
-            specs: this.formatSpecsForInput(sku.specs),
-            price: sku.price ? (sku.price / 100) : 0,
-            originalPrice: sku.originalPrice ? (sku.originalPrice / 100) : 0,
-            stock: sku.stock || 0,
-            image: sku.image || '',
-            enabled: sku.enabled !== undefined ? (sku.enabled ? 1 : 0) : 1,
-            label: '',
-            linkedProductId: null,
-            linkedProductName: '',
-            linkedProductPrice: 0,
-            linkedProductImage: '',
-            linkedProductStock: 0
-          }
-        } else {
-          this.skuForm = {
-            label: sku.label || '',
-            price: sku.price ? (sku.price / 100) : 0,
-            stock: sku.stock || 0,
-            enabled: sku.enabled !== undefined ? (sku.enabled ? 1 : 0) : 1,
-            specs: '',
-            originalPrice: 0,
-            image: '',
-            linkedProductId: null,
-            linkedProductName: '',
-            linkedProductPrice: 0,
-            linkedProductImage: '',
-            linkedProductStock: 0
-          }
-          if (sku.linkedProductId != null && !comboRaw.length) {
-            if (!this.skuForm.price) this.skuForm.price = (sku.linkedProductPrice || 0) / 100
-            if (!this.skuForm.stock) this.skuForm.stock = sku.linkedProductStock || 0
-          }
+        this.skuMode = 'spec'
+        this.skuForm = {
+          specs: this.formatSpecsForInput(sku.specs),
+          price: sku.price ? (sku.price / 100) : 0,
+          originalPrice: sku.originalPrice ? (sku.originalPrice / 100) : 0,
+          stock: sku.stock || 0,
+          image: sku.image || '',
+          enabled: sku.enabled !== undefined ? (sku.enabled ? 1 : 0) : 1,
+          label: '',
+          linkedProductId: null,
+          linkedProductName: '',
+          linkedProductPrice: 0,
+          linkedProductImage: '',
+          linkedProductStock: 0
         }
         }
       } else {
       } else {
         this.editingId = null
         this.editingId = null
@@ -314,7 +224,6 @@ export default {
     },
     },
     resetForm() {
     resetForm() {
       this.skuMode = 'spec'
       this.skuMode = 'spec'
-      this.comboItems = []
       this.skuForm = {
       this.skuForm = {
         specs: '',
         specs: '',
         price: 0,
         price: 0,
@@ -437,74 +346,7 @@ export default {
       if (!val) return '¥0.00'
       if (!val) return '¥0.00'
       return '¥' + (parseFloat(val) / 100).toFixed(2)
       return '¥' + (parseFloat(val) / 100).toFixed(2)
     },
     },
-    openProductPicker() {
-      this.$refs.productPicker.open()
-    },
-    onProductSelected(product) {
-      this.skuForm.linkedProductId = product.id
-      this.skuForm.linkedProductName = product.name
-      this.skuForm.linkedProductPrice = product.price
-      this.skuForm.linkedProductImage = product.coverImage || product.image || ''
-      this.skuForm.linkedProductStock = product.stock
-    },
-    onComboConfirm(items) {
-      this.comboItems = (items || []).map(it => ({
-        childProductId: it.childProductId || it.id,
-        childSkuId: it.childSkuId || null,
-        quantity: it.quantity || 1,
-        name: it.name,
-        coverImage: it.coverImage,
-        price: it.price || 0,
-        childSkuSpecs: it.childSkuSpecs || null
-      }))
-    },
     async handleSave() {
     async handleSave() {
-      // 商品组合模式:手填售价 + 组合明细(多商品+数量),不校验 specs
-      if (this.skuMode === 'combo') {
-        if (!this.comboItems.length) { this.$message.warning('请至少添加一个组合商品'); return }
-        if (!(this.skuForm.price > 0)) { this.$message.warning('请填写组合售价'); return }
-        this.saving = true
-        try {
-          const payload = {
-            productId: this.productId,
-            comboItems: normalizeComboPayload(this.comboItems),
-            price: Math.round(parseFloat(this.skuForm.price) * 100),
-            stock: this.skuForm.stock || 0,
-            label: this.skuForm.label || null,
-            enabled: this.skuForm.enabled,
-            linkedProductId: null
-          }
-          if (!this.productId) {
-            payload.id = Date.now()
-            delete payload.productId
-            this.pendingSkus.push(payload)
-            this.dialogVisible = false
-            this.$message.success('添加成功,保存商品时将自动创建SKU')
-            this.saving = false
-            return
-          }
-          let res
-          if (this.editingId) {
-            payload.id = this.editingId
-            delete payload.productId
-            res = await updateProductSku(payload)
-          } else {
-            res = await createProductSku(payload)
-          }
-          if (res.code === 200) {
-            await this.loadSkus()
-            this.$message.success('保存成功')
-            this.dialogVisible = false
-          } else {
-            this.$message.error(res.message || '保存失败')
-          }
-        } catch (e) {
-          this.$message.error('保存失败')
-        } finally {
-          this.saving = false
-        }
-        return
-      }
       this.$refs.skuForm.validate(async (valid) => {
       this.$refs.skuForm.validate(async (valid) => {
         if (!valid) return
         if (!valid) return
         this.saving = true
         this.saving = true
@@ -517,7 +359,6 @@ export default {
             stock: this.skuForm.stock,
             stock: this.skuForm.stock,
             image: this.skuForm.image,
             image: this.skuForm.image,
             enabled: this.skuForm.enabled,
             enabled: this.skuForm.enabled,
-            comboItems: [],
             linkedProductId: null
             linkedProductId: null
           }
           }
           // 新建商品时productId为空,暂存到本地
           // 新建商品时productId为空,暂存到本地
@@ -617,17 +458,5 @@ export default {
 .sku-thumb { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; }
 .sku-thumb { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; }
 .text-muted { color: var(--adm-text-45); }
 .text-muted { color: var(--adm-text-45); }
 .mb-16 { margin-bottom: 16px; }
 .mb-16 { margin-bottom: 16px; }
-.linked-card { margin-top: 8px; display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--adm-bg-header); border-radius: 4px; }
-.linked-thumb { width: 50px; height: 50px; object-fit: cover; border-radius: 4px; }
-.linked-info { font-size: 12px; color: var(--adm-text-45); }
 .full-width { width: 100%; }
 .full-width { width: 100%; }
-.combo-list { margin-top: 8px; display: flex; flex-direction: column; gap: 8px; }
-.combo-row { display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--adm-bg-header); border-radius: 4px; }
-.combo-thumb { width: 46px; height: 46px; object-fit: cover; border-radius: 4px; flex-shrink: 0; }
-.combo-thumb-placeholder { background: #f0f0f0; display: inline-block; }
-.combo-main { flex: 1; min-width: 0; font-size: 13px; }
-.combo-meta { font-size: 12px; color: var(--adm-text-45); display: flex; gap: 8px; }
-.combo-qty { width: 90px; }
-.combo-remove { color: #f56c6c; }
-.combo-empty { margin-top: 8px; padding: 12px; text-align: center; color: var(--adm-text-45); background: var(--adm-bg-header); border-radius: 4px; font-size: 12px; }
 </style>
 </style>