
<template>
<div class="sku-edit-simple">
<el-form ref="skuForm" label-width="110px">
<!-- SPU规格管理 -->
<el-form-item label="spu规格" required>
<div class="spec-container">
<div
v-for="(spec, specIndex) in specifications"
:key="specIndex"
class="spec-row"
>
<!-- 规格名称 -->
<div class="spec-header">
<el-select
v-model="spec.name"
filterable
allow-create
placeholder="选择或输入规格名称"
size="small"
style="width: 200px;"
@change="onSpecNameChange(specIndex, $event)"
@blur="validateSpecName(specIndex)"
>
<el-option
v-for="option in specOptions"
:key="option.value"
:label="option.label"
:value="option.label"
/>
</el-select>
<!-- 添加规格按钮 -->
<el-button
type="text"
icon="el-icon-circle-plus"
size="small"
@click="addSpecification"
class="add-btn"
/>
<!-- 删除规格按钮 -->
<el-button
v-if="specifications.length > 1"
type="text"
icon="el-icon-remove"
size="small"
@click="removeSpecification(specIndex)"
class="remove-btn"
/>
</div>
<!-- 规格值列表 -->
<div class="spec-values">
<div
v-for="(value, valueIndex) in spec.values"
:key="valueIndex"
class="value-item"
>
<el-input
v-model="spec.values[valueIndex]"
placeholder="输入规格值"
size="small"
style="width: 120px;"
@blur="validateSpecValue(specIndex, valueIndex)"
/>
<!-- 添加规格值按钮 -->
<el-button
type="text"
icon="el-icon-circle-plus"
size="small"
@click="addSpecValue(specIndex)"
class="add-btn"
/>
<!-- 删除规格值按钮 -->
<el-button
v-if="spec.values.length > 1"
type="text"
icon="el-icon-remove"
size="small"
@click="removeSpecValue(specIndex, valueIndex)"
class="remove-btn"
/>
</div>
</div>
</div>
</div>
</el-form-item>
<!-- SKU映射表格 -->
<el-form-item label="映射sku" required>
<div class="sku-table-container">
<el-table
:data="skuList"
border
size="small"
style="width: 100%"
empty-text="请先添加规格和规格值"
>
<!-- 动态规格列 -->
<el-table-column
v-for="(spec, index) in specifications"
:key="index"
:label="spec.name || `规格${index + 1}`"
width="120"
>
<template slot-scope="scope">
<span>{{ scope.row.specValues[index] || '-' }}</span>
</template>
</el-table-column>
<!-- 商品条码 -->
<el-table-column
prop="barcode"
label="商品条码"
width="150"
>
<template slot-scope="scope">
<el-input
v-model="scope.row.barcode"
size="small"
placeholder="请输入条码"
/>
</template>
</el-table-column>
<!-- 售价 -->
<el-table-column
prop="price"
label="售价"
width="120"
>
<template slot-scope="scope">
<el-input
v-model="scope.row.price"
size="small"
placeholder="请输入售价"
type="number"
/>
</template>
</el-table-column>
<!-- 市场价 -->
<el-table-column
prop="marketPrice"
label="市场价"
width="120"
>
<template slot-scope="scope">
<el-input
v-model="scope.row.marketPrice"
size="small"
placeholder="请输入市场价"
type="number"
/>
</template>
</el-table-column>
</el-table>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { getOptions } from '@/api/common/common'
export default {
name: 'SkuEditSimple',
props: {
initialData: {
type: Object,
default: () => ({
specifications: [],
skuList: []
})
},
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
specOptions: [],
specifications: [
{
name: '',
values: ['']
}
],
skuList: [],
validationErrors: []
}
},
watch: {
specifications: {
handler: 'generateSkuList',
deep: true
}
},
mounted() {
this.initData()
this.loadSpecOptions()
},
methods: {
initData() {
if (this.isEdit && this.initialData.specifications.length > 0) {
this.specifications = JSON.parse(JSON.stringify(this.initialData.specifications))
this.skuList = JSON.parse(JSON.stringify(this.initialData.skuList))
}
},
async loadSpecOptions() {
try {
const response = await getOptions({ option_names: ['spuSpecOptions'] })
this.specOptions = response.data.spuSpecOptions || []
} catch (error) {
console.error('加载规格选项失败:', error)
this.$message.error('加载规格选项失败')
}
},
addSpecification() {
this.specifications.push({
name: '',
values: ['']
})
},
removeSpecification(index) {
this.$confirm('确定要删除这个规格吗?删除后相关的SKU数据也会被删除。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.specifications.splice(index, 1)
this.$message.success('规格删除成功')
}).catch(() => {
})
},
addSpecValue(specIndex) {
this.specifications[specIndex].values.push('')
},
removeSpecValue(specIndex, valueIndex) {
this.$confirm('确定要删除这个规格值吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.specifications[specIndex].values.splice(valueIndex, 1)
this.$message.success('规格值删除成功')
}).catch(() => {
})
},
onSpecNameChange(specIndex, value) {
this.validateSpecName(specIndex)
this.generateSkuList()
},
validateSpecName(specIndex) {
const spec = this.specifications[specIndex]
if (!spec.name || spec.name.trim() === '') {
this.$message.warning('规格名称不能为空')
return false
}
const duplicateIndex = this.specifications.findIndex((s, index) =>
index !== specIndex && s.name === spec.name
)
if (duplicateIndex !== -1) {
this.$message.warning('规格名称不能重复')
return false
}
return true
},
validateSpecValue(specIndex, valueIndex) {
const spec = this.specifications[specIndex]
const value = spec.values[valueIndex]
if (!value || value.trim() === '') {
this.$message.warning('规格值不能为空')
return false
}
const duplicateIndex = spec.values.findIndex((v, index) =>
index !== valueIndex && v === value
)
if (duplicateIndex !== -1) {
this.$message.warning('规格值不能重复')
return false
}
return true
},
generateSkuList() {
const validSpecs = this.specifications.filter(spec =>
spec.name && spec.name.trim() !== '' &&
spec.values.length > 0 &&
spec.values.some(value => value && value.trim() !== '')
)
if (validSpecs.length === 0) {
this.skuList = []
return
}
const combinations = this.generateCombinations(validSpecs)
this.skuList = combinations.map(combination => {
const existingSku = this.findExistingSku(combination)
return {
id: existingSku ? existingSku.id : this.generateId(),
specValues: combination,
barcode: existingSku ? existingSku.barcode : '',
price: existingSku ? existingSku.price : '',
marketPrice: existingSku ? existingSku.marketPrice : ''
}
})
},
generateCombinations(specs, currentCombination = [], index = 0) {
if (index >= specs.length) {
return [currentCombination]
}
const combinations = []
const spec = specs[index]
const validValues = spec.values.filter(value => value && value.trim() !== '')
for (const value of validValues) {
const newCombination = [...currentCombination, value]
const subCombinations = this.generateCombinations(specs, newCombination, index + 1)
combinations.push(...subCombinations)
}
return combinations
},
findExistingSku(combination) {
if (!this.isEdit) return null
return this.skuList.find(sku => {
if (sku.specValues.length !== combination.length) return false
return combination.every((value, index) =>
sku.specValues[index] === value
)
})
},
generateId() {
return Date.now() + Math.random().toString(36).substr(2, 9)
},
validate() {
this.validationErrors = []
for (let i = 0; i < this.specifications.length; i++) {
const spec = this.specifications[i]
if (!spec.name || spec.name.trim() === '') {
this.validationErrors.push(`第${i + 1}个规格名称不能为空`)
continue
}
const validValues = spec.values.filter(value => value && value.trim() !== '')
if (validValues.length === 0) {
this.validationErrors.push(`规格"${spec.name}"至少需要一个规格值`)
continue
}
const uniqueValues = new Set(validValues)
if (uniqueValues.size !== validValues.length) {
this.validationErrors.push(`规格"${spec.name}"存在重复的规格值`)
}
}
const specNames = this.specifications.map(spec => spec.name).filter(name => name && name.trim() !== '')
const uniqueSpecNames = new Set(specNames)
if (uniqueSpecNames.size !== specNames.length) {
this.validationErrors.push('存在重复的规格名称')
}
return this.validationErrors.length === 0
},
getData() {
if (!this.validate()) {
this.$message.error('数据验证失败,请检查输入')
return null
}
return {
specifications: this.specifications.filter(spec =>
spec.name && spec.name.trim() !== '' &&
spec.values.some(value => value && value.trim() !== '')
),
skuList: this.skuList
}
},
reset() {
this.specifications = [{ name: '', values: [''] }]
this.skuList = []
this.validationErrors = []
}
}
}
</script>
<style lang="scss" scoped>
.sku-edit-simple {
padding: 20px;
.spec-container {
.spec-row {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #e4e7ed;
border-radius: 4px;
background-color: #fafafa;
.spec-header {
display: flex;
align-items: center;
margin-bottom: 10px;
.add-btn {
margin-left: 10px;
color: #409eff;
&:hover {
color: #66b1ff;
}
}
.remove-btn {
margin-left: 5px;
color: #f56c6c;
&:hover {
color: #f78989;
}
}
}
.spec-values {
display: flex;
flex-wrap: wrap;
gap: 10px;
.value-item {
display: flex;
align-items: center;
.add-btn {
margin-left: 5px;
color: #409eff;
&:hover {
color: #66b1ff;
}
}
.remove-btn {
margin-left: 5px;
color: #f56c6c;
&:hover {
color: #f78989;
}
}
}
}
}
}
.sku-table-container {
margin-top: 10px;
}
}
</style>