|
@@ -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>
|