1425 lines
38 KiB
Vue
1425 lines
38 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="product-create">
|
|||
|
|
<el-card>
|
|||
|
|
<template #header>
|
|||
|
|
<div class="card-header">
|
|||
|
|
<span>新增商品</span>
|
|||
|
|
<el-button @click="goBack">返回</el-button>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<el-form
|
|||
|
|
ref="formRef"
|
|||
|
|
:model="form"
|
|||
|
|
:rules="rules"
|
|||
|
|
label-width="120px"
|
|||
|
|
label-position="left"
|
|||
|
|
>
|
|||
|
|
<el-form-item label="商品名称" prop="name">
|
|||
|
|
<el-input v-model="form.name" placeholder="请输入商品名称" />
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="商品价格" prop="price">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="form.price"
|
|||
|
|
:precision="2"
|
|||
|
|
:min="0.01"
|
|||
|
|
:max="999999.99"
|
|||
|
|
placeholder="请输入商品价格"
|
|||
|
|
style="width: 100%"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="主图" prop="mainImages">
|
|||
|
|
<div class="image-upload-section">
|
|||
|
|
<!-- 多图片上传 -->
|
|||
|
|
<el-upload
|
|||
|
|
class="image-uploader-multiple"
|
|||
|
|
:http-request="handleMainImagesUpload"
|
|||
|
|
:on-remove="handleMainImageRemove"
|
|||
|
|
:file-list="mainImageFileList"
|
|||
|
|
:before-upload="beforeUpload"
|
|||
|
|
multiple
|
|||
|
|
list-type="picture-card"
|
|||
|
|
:limit="10"
|
|||
|
|
accept="image/*"
|
|||
|
|
>
|
|||
|
|
<el-icon class="uploader-icon-multiple"><Plus /></el-icon>
|
|||
|
|
</el-upload>
|
|||
|
|
<div class="upload-tip">
|
|||
|
|
点击上传主图(支持jpg、png、gif等,最大10MB,最多10张)
|
|||
|
|
</div>
|
|||
|
|
<!-- 图片预览列表 -->
|
|||
|
|
<div v-if="form.mainImages && form.mainImages.length > 0" class="image-preview-list">
|
|||
|
|
<div
|
|||
|
|
v-for="(image, index) in form.mainImages"
|
|||
|
|
:key="index"
|
|||
|
|
class="image-preview-item"
|
|||
|
|
>
|
|||
|
|
<el-image
|
|||
|
|
:src="image"
|
|||
|
|
class="preview-image"
|
|||
|
|
fit="cover"
|
|||
|
|
:preview-src-list="form.mainImages"
|
|||
|
|
:initial-index="index"
|
|||
|
|
/>
|
|||
|
|
<div class="image-actions">
|
|||
|
|
<el-button
|
|||
|
|
type="danger"
|
|||
|
|
:icon="Delete"
|
|||
|
|
circle
|
|||
|
|
size="small"
|
|||
|
|
@click="removeMainImage(index)"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="店铺ID" prop="shopId">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="form.shopId"
|
|||
|
|
:min="1"
|
|||
|
|
placeholder="请输入店铺ID"
|
|||
|
|
style="width: 100%"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="商品状态" prop="status">
|
|||
|
|
<el-radio-group v-model="form.status">
|
|||
|
|
<el-radio label="ACTIVE">上架</el-radio>
|
|||
|
|
<el-radio label="INACTIVE">下架</el-radio>
|
|||
|
|
</el-radio-group>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="销售地区" prop="salesRegions">
|
|||
|
|
<el-checkbox-group v-model="form.salesRegions">
|
|||
|
|
<el-checkbox label="MY">马来西亚 (MYR)</el-checkbox>
|
|||
|
|
<el-checkbox label="PH">菲律宾 (PHP)</el-checkbox>
|
|||
|
|
<el-checkbox label="TH">泰国 (THB)</el-checkbox>
|
|||
|
|
<el-checkbox label="VN">越南 (VND)</el-checkbox>
|
|||
|
|
<el-checkbox label="SG">新加坡 (SGD)</el-checkbox>
|
|||
|
|
<el-checkbox label="US">美国 (USD)</el-checkbox>
|
|||
|
|
<el-checkbox label="CN">中国 (CNY)</el-checkbox>
|
|||
|
|
<el-checkbox label="EU">欧洲 (EUR)</el-checkbox>
|
|||
|
|
<el-checkbox label="GB">英国 (GBP)</el-checkbox>
|
|||
|
|
</el-checkbox-group>
|
|||
|
|
<div class="form-tip">
|
|||
|
|
提示:选择该商品将在哪些地区销售,批量设置价格时会显示对应地区的价格输入框
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<!-- 物流信息(商品级别,所有SKU共享) -->
|
|||
|
|
<el-divider content-position="left">
|
|||
|
|
<span style="color: #f56c6c">*</span>
|
|||
|
|
<span>物流信息</span>
|
|||
|
|
</el-divider>
|
|||
|
|
|
|||
|
|
<el-form-item label="包裹重量" required>
|
|||
|
|
<el-row :gutter="10" style="width: 100%">
|
|||
|
|
<el-col :span="16">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="form.weightValue"
|
|||
|
|
:precision="2"
|
|||
|
|
:min="0"
|
|||
|
|
placeholder="请输入重量"
|
|||
|
|
style="width: 100%"
|
|||
|
|
/>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="8">
|
|||
|
|
<el-select v-model="form.weightUnit" style="width: 100%">
|
|||
|
|
<el-option label="克(g)" value="g" />
|
|||
|
|
<el-option label="千克(kg)" value="kg" />
|
|||
|
|
<el-option label="磅(lb)" value="lb" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="包裹尺寸" required>
|
|||
|
|
<el-row :gutter="10" style="width: 100%">
|
|||
|
|
<el-col :span="6">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="form.sizeLength"
|
|||
|
|
:precision="2"
|
|||
|
|
:min="0"
|
|||
|
|
placeholder="长"
|
|||
|
|
style="width: 100%"
|
|||
|
|
/>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="6">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="form.sizeWidth"
|
|||
|
|
:precision="2"
|
|||
|
|
:min="0"
|
|||
|
|
placeholder="宽"
|
|||
|
|
style="width: 100%"
|
|||
|
|
/>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="6">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="form.sizeHeight"
|
|||
|
|
:precision="2"
|
|||
|
|
:min="0"
|
|||
|
|
placeholder="高"
|
|||
|
|
style="width: 100%"
|
|||
|
|
/>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="6">
|
|||
|
|
<el-select v-model="form.sizeUnit" style="width: 100%">
|
|||
|
|
<el-option label="厘米(cm)" value="cm" />
|
|||
|
|
<el-option label="米(m)" value="m" />
|
|||
|
|
<el-option label="英寸(in)" value="in" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-divider>SKU信息</el-divider>
|
|||
|
|
|
|||
|
|
<!-- SKU新增模块 -->
|
|||
|
|
<el-card shadow="never" class="sku-add-module-card" style="margin-bottom: 20px">
|
|||
|
|
<template #header>
|
|||
|
|
<div class="sku-module-header">
|
|||
|
|
<span class="module-title">SKU配置</span>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- SKU配置列表 -->
|
|||
|
|
<div class="sku-config-list">
|
|||
|
|
<div
|
|||
|
|
v-for="(skuItem, skuIndex) in form.skus"
|
|||
|
|
:key="skuIndex"
|
|||
|
|
class="sku-config-item"
|
|||
|
|
>
|
|||
|
|
<div class="sku-config-content">
|
|||
|
|
<!-- SKU描述编辑 -->
|
|||
|
|
<div class="sku-description-section">
|
|||
|
|
<el-input
|
|||
|
|
v-model="skuItem.sku"
|
|||
|
|
type="textarea"
|
|||
|
|
:rows="2"
|
|||
|
|
placeholder='例如:【✅性价比首选】浅水蓝+浅水蓝+米白色【⭐️毛巾实惠3条装】'
|
|||
|
|
maxlength="2000"
|
|||
|
|
show-word-limit
|
|||
|
|
class="sku-description-input"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- SKU图片关联 -->
|
|||
|
|
<div class="sku-images-section">
|
|||
|
|
<div class="section-label">SKU图片关联</div>
|
|||
|
|
<div class="sku-image-wrapper">
|
|||
|
|
<!-- 已上传的图片 -->
|
|||
|
|
<div
|
|||
|
|
v-if="skuItem.skuImage"
|
|||
|
|
class="sku-image-item"
|
|||
|
|
>
|
|||
|
|
<el-image
|
|||
|
|
:src="skuItem.skuImage"
|
|||
|
|
fit="cover"
|
|||
|
|
class="sku-preview-image"
|
|||
|
|
>
|
|||
|
|
<template #error>
|
|||
|
|
<div class="image-slot">
|
|||
|
|
<el-icon><Picture /></el-icon>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
</el-image>
|
|||
|
|
<div class="image-actions">
|
|||
|
|
<el-button
|
|||
|
|
type="danger"
|
|||
|
|
:icon="Delete"
|
|||
|
|
circle
|
|||
|
|
size="small"
|
|||
|
|
@click="removeSkuImage(skuIndex)"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 上传按钮 -->
|
|||
|
|
<div
|
|||
|
|
v-else
|
|||
|
|
class="sku-upload-item"
|
|||
|
|
>
|
|||
|
|
<el-upload
|
|||
|
|
:action="''"
|
|||
|
|
:http-request="(options) => handleSkuImageUpload(options, skuIndex)"
|
|||
|
|
:show-file-list="false"
|
|||
|
|
:before-upload="beforeUpload"
|
|||
|
|
accept="image/*"
|
|||
|
|
class="sku-image-uploader"
|
|||
|
|
>
|
|||
|
|
<div class="upload-placeholder">
|
|||
|
|
<el-icon class="upload-icon"><Plus /></el-icon>
|
|||
|
|
<div class="upload-text">上传图片</div>
|
|||
|
|
</div>
|
|||
|
|
</el-upload>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 删除按钮 -->
|
|||
|
|
<div class="sku-actions">
|
|||
|
|
<el-button
|
|||
|
|
type="danger"
|
|||
|
|
:icon="Delete"
|
|||
|
|
circle
|
|||
|
|
@click="removeSku(skuIndex)"
|
|||
|
|
:disabled="form.skus.length <= 1"
|
|||
|
|
title="删除SKU"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 添加SKU按钮 -->
|
|||
|
|
<div class="add-sku-button-wrapper">
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
:icon="Plus"
|
|||
|
|
@click="addSku"
|
|||
|
|
class="add-sku-btn"
|
|||
|
|
>
|
|||
|
|
添加选项
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
</el-card>
|
|||
|
|
|
|||
|
|
<el-form-item label="SKU列表" prop="skus">
|
|||
|
|
<!-- 批量编辑按钮 -->
|
|||
|
|
<div class="sku-list-header" v-if="form.salesRegions && form.salesRegions.length > 0">
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
@click="showBatchEdit = !showBatchEdit"
|
|||
|
|
class="batch-edit-toggle-btn"
|
|||
|
|
>
|
|||
|
|
<el-icon style="margin-right: 5px">
|
|||
|
|
<ArrowDown v-if="!showBatchEdit" />
|
|||
|
|
<ArrowUp v-else />
|
|||
|
|
</el-icon>
|
|||
|
|
{{ showBatchEdit ? '收起批量编辑' : '批量编辑' }}
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 批量操作区域(可折叠) -->
|
|||
|
|
<el-collapse-transition>
|
|||
|
|
<el-card
|
|||
|
|
v-show="showBatchEdit && form.salesRegions && form.salesRegions.length > 0"
|
|||
|
|
shadow="never"
|
|||
|
|
class="batch-operation-card"
|
|||
|
|
style="margin-bottom: 20px"
|
|||
|
|
>
|
|||
|
|
<div class="batch-operation-content">
|
|||
|
|
<div class="batch-regions-grid">
|
|||
|
|
<div
|
|||
|
|
v-for="region in selectedSalesRegions"
|
|||
|
|
:key="region.code"
|
|||
|
|
class="batch-region-item"
|
|||
|
|
>
|
|||
|
|
<div class="region-label-wrapper">
|
|||
|
|
<span class="region-code-badge">{{ region.code }}</span>
|
|||
|
|
<span class="region-name-text">{{ region.name }}</span>
|
|||
|
|
</div>
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="batchRegionPrices[region.code]"
|
|||
|
|
:precision="2"
|
|||
|
|
:min="0"
|
|||
|
|
placeholder="价格"
|
|||
|
|
style="width: 100%"
|
|||
|
|
size="small"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="batch-actions-row">
|
|||
|
|
<div class="batch-stock-wrapper">
|
|||
|
|
<span class="batch-label">批量设置库存:</span>
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="batchStock"
|
|||
|
|
:min="0"
|
|||
|
|
placeholder="库存数量"
|
|||
|
|
style="width: 180px"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
size="default"
|
|||
|
|
@click="applyAllBatchSettings"
|
|||
|
|
class="batch-apply-btn"
|
|||
|
|
>
|
|||
|
|
应用批量设置
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</el-card>
|
|||
|
|
</el-collapse-transition>
|
|||
|
|
|
|||
|
|
<el-alert
|
|||
|
|
v-if="!form.salesRegions || form.salesRegions.length === 0"
|
|||
|
|
type="info"
|
|||
|
|
:closable="false"
|
|||
|
|
style="margin-bottom: 20px"
|
|||
|
|
>
|
|||
|
|
请先在商品信息中选择销售地区,批量设置功能将根据选择的地区显示对应的价格输入框
|
|||
|
|
</el-alert>
|
|||
|
|
<div class="sku-table-wrapper">
|
|||
|
|
<div class="sku-table-container">
|
|||
|
|
<el-table
|
|||
|
|
:data="form.skus"
|
|||
|
|
border
|
|||
|
|
class="sku-table"
|
|||
|
|
>
|
|||
|
|
<el-table-column label="SKU" min-width="300" fixed="left">
|
|||
|
|
<template #default="{ row, $index }">
|
|||
|
|
<el-input
|
|||
|
|
v-model="row.sku"
|
|||
|
|
type="textarea"
|
|||
|
|
:rows="2"
|
|||
|
|
placeholder='例如:【✅性价比首选】浅水蓝+浅水蓝+米白色【⭐️毛巾实惠3条装】'
|
|||
|
|
maxlength="2000"
|
|||
|
|
show-word-limit
|
|||
|
|
style="width: 100%"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
|
|||
|
|
<el-table-column
|
|||
|
|
v-for="region in selectedSalesRegions"
|
|||
|
|
:key="region.code"
|
|||
|
|
:label="`*${region.code} 定价`"
|
|||
|
|
width="150"
|
|||
|
|
align="center"
|
|||
|
|
>
|
|||
|
|
<template #default="{ row }">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="row.regionPrices[region.code]"
|
|||
|
|
:precision="2"
|
|||
|
|
:min="0"
|
|||
|
|
style="width: 100%"
|
|||
|
|
placeholder="0.00"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
|
|||
|
|
<el-table-column label="*商品库存" width="150" align="center" fixed="right">
|
|||
|
|
<template #default="{ row }">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="row.stock"
|
|||
|
|
:min="0"
|
|||
|
|
style="width: 100%"
|
|||
|
|
placeholder="库存"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
|
|||
|
|
<el-table-column label="操作" width="100" align="center" fixed="right">
|
|||
|
|
<template #default="{ $index }">
|
|||
|
|
<el-button
|
|||
|
|
type="danger"
|
|||
|
|
link
|
|||
|
|
@click="removeSku($index)"
|
|||
|
|
:disabled="form.skus.length <= 1"
|
|||
|
|
>
|
|||
|
|
删除
|
|||
|
|
</el-button>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item>
|
|||
|
|
<el-button type="primary" @click="submitForm" :loading="submitting">
|
|||
|
|
创建商品
|
|||
|
|
</el-button>
|
|||
|
|
<el-button @click="resetForm">重置</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
</el-card>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, reactive, computed, watch } from 'vue'
|
|||
|
|
import { useRouter } from 'vue-router'
|
|||
|
|
import { ElMessage } from 'element-plus'
|
|||
|
|
import { Plus, Delete, ArrowDown, ArrowUp, Picture } from '@element-plus/icons-vue'
|
|||
|
|
import request from '../api/request'
|
|||
|
|
|
|||
|
|
const router = useRouter()
|
|||
|
|
const submitting = ref(false)
|
|||
|
|
const formRef = ref()
|
|||
|
|
|
|||
|
|
// 主图文件列表(用于el-upload组件)
|
|||
|
|
const mainImageFileList = ref([])
|
|||
|
|
|
|||
|
|
// 表单数据
|
|||
|
|
const form = reactive({
|
|||
|
|
name: '',
|
|||
|
|
price: null,
|
|||
|
|
mainImage: '', // 兼容字段,取mainImages的第一个
|
|||
|
|
mainImages: [], // 主图列表
|
|||
|
|
status: 'ACTIVE',
|
|||
|
|
shopId: null,
|
|||
|
|
salesRegions: [], // 销售地区列表,如:['MY', 'PH', 'TH', 'VN']
|
|||
|
|
// 物流信息(商品级别,所有SKU共享)
|
|||
|
|
weightValue: null,
|
|||
|
|
weightUnit: 'g',
|
|||
|
|
sizeLength: null,
|
|||
|
|
sizeWidth: null,
|
|||
|
|
sizeHeight: null,
|
|||
|
|
sizeUnit: 'cm',
|
|||
|
|
skus: [
|
|||
|
|
{
|
|||
|
|
sku: '', // SKU描述
|
|||
|
|
regionPrices: {}, // 按地区存储价格,如:{ 'MY': 100, 'PH': 200 }
|
|||
|
|
stock: 0,
|
|||
|
|
skuImage: '', // SKU图片
|
|||
|
|
status: 'ACTIVE'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 地区到货币的映射
|
|||
|
|
const regionCurrencyMap = {
|
|||
|
|
'MY': { code: 'MY', name: '马来西亚', currency: 'MYR' },
|
|||
|
|
'PH': { code: 'PH', name: '菲律宾', currency: 'PHP' },
|
|||
|
|
'TH': { code: 'TH', name: '泰国', currency: 'THB' },
|
|||
|
|
'VN': { code: 'VN', name: '越南', currency: 'VND' },
|
|||
|
|
'SG': { code: 'SG', name: '新加坡', currency: 'SGD' },
|
|||
|
|
'US': { code: 'US', name: '美国', currency: 'USD' },
|
|||
|
|
'CN': { code: 'CN', name: '中国', currency: 'CNY' },
|
|||
|
|
'EU': { code: 'EU', name: '欧洲', currency: 'EUR' },
|
|||
|
|
'GB': { code: 'GB', name: '英国', currency: 'GBP' }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量操作相关
|
|||
|
|
const batchStock = ref(null)
|
|||
|
|
const batchRegionPrices = ref({}) // 按地区存储批量价格,如:{ 'MY': 100, 'PH': 200 }
|
|||
|
|
const showBatchEdit = ref(false) // 控制批量编辑模块的显示/隐藏
|
|||
|
|
|
|||
|
|
// 计算已选择的销售地区信息
|
|||
|
|
const selectedSalesRegions = computed(() => {
|
|||
|
|
if (!form.salesRegions || form.salesRegions.length === 0) {
|
|||
|
|
return []
|
|||
|
|
}
|
|||
|
|
return form.salesRegions
|
|||
|
|
.map(regionCode => regionCurrencyMap[regionCode])
|
|||
|
|
.filter(Boolean)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 监听销售地区变化,初始化批量价格对象和SKU的regionPrices
|
|||
|
|
watch(() => form.salesRegions, (newRegions, oldRegions) => {
|
|||
|
|
// 清空之前的批量价格
|
|||
|
|
batchRegionPrices.value = {}
|
|||
|
|
// 为每个新选择的地区初始化价格
|
|||
|
|
if (newRegions && newRegions.length > 0) {
|
|||
|
|
newRegions.forEach(regionCode => {
|
|||
|
|
if (regionCurrencyMap[regionCode]) {
|
|||
|
|
batchRegionPrices.value[regionCode] = null
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新所有SKU的regionPrices结构
|
|||
|
|
form.skus.forEach(sku => {
|
|||
|
|
if (!sku.regionPrices) {
|
|||
|
|
sku.regionPrices = {}
|
|||
|
|
}
|
|||
|
|
// 移除已取消选择的地区
|
|||
|
|
if (oldRegions && oldRegions.length > 0) {
|
|||
|
|
oldRegions.forEach(oldRegion => {
|
|||
|
|
if (!newRegions || !newRegions.includes(oldRegion)) {
|
|||
|
|
delete sku.regionPrices[oldRegion]
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
// 为新选择的地区初始化价格
|
|||
|
|
if (newRegions && newRegions.length > 0) {
|
|||
|
|
newRegions.forEach(regionCode => {
|
|||
|
|
if (sku.regionPrices[regionCode] === undefined) {
|
|||
|
|
sku.regionPrices[regionCode] = null
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}, { immediate: true })
|
|||
|
|
|
|||
|
|
// 应用所有批量设置(价格、库存和SKU后缀)
|
|||
|
|
const applyAllBatchSettings = () => {
|
|||
|
|
let hasPriceUpdate = false
|
|||
|
|
let hasStockUpdate = false
|
|||
|
|
const updatedRegions = []
|
|||
|
|
|
|||
|
|
// 批量应用地区价格
|
|||
|
|
Object.keys(batchRegionPrices.value).forEach(regionCode => {
|
|||
|
|
const price = batchRegionPrices.value[regionCode]
|
|||
|
|
if (price !== null && price > 0) {
|
|||
|
|
form.skus.forEach(sku => {
|
|||
|
|
if (!sku.regionPrices) {
|
|||
|
|
sku.regionPrices = {}
|
|||
|
|
}
|
|||
|
|
sku.regionPrices[regionCode] = price
|
|||
|
|
})
|
|||
|
|
hasPriceUpdate = true
|
|||
|
|
updatedRegions.push(regionCode)
|
|||
|
|
// 清空已应用的价格
|
|||
|
|
batchRegionPrices.value[regionCode] = null
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 批量应用库存
|
|||
|
|
let stockValue = null
|
|||
|
|
if (batchStock.value !== null && batchStock.value >= 0) {
|
|||
|
|
stockValue = batchStock.value
|
|||
|
|
form.skus.forEach(sku => {
|
|||
|
|
sku.stock = batchStock.value
|
|||
|
|
})
|
|||
|
|
hasStockUpdate = true
|
|||
|
|
batchStock.value = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示成功消息
|
|||
|
|
const messages = []
|
|||
|
|
if (hasPriceUpdate) {
|
|||
|
|
const regionNames = updatedRegions.join('、')
|
|||
|
|
messages.push(`价格(${regionNames})`)
|
|||
|
|
}
|
|||
|
|
if (hasStockUpdate) {
|
|||
|
|
messages.push(`库存(${stockValue})`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (messages.length > 0) {
|
|||
|
|
ElMessage.success(`已批量更新 ${form.skus.length} 个SKU的 ${messages.join('、')}`)
|
|||
|
|
} else {
|
|||
|
|
ElMessage.warning('请填写要批量设置的价格或库存')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 表单验证规则
|
|||
|
|
const rules = {
|
|||
|
|
name: [{ required: true, message: '请输入商品名称', trigger: 'blur' }],
|
|||
|
|
price: [{ required: true, message: '请输入商品价格', trigger: 'blur' }],
|
|||
|
|
shopId: [{ required: true, message: '请输入店铺ID', trigger: 'blur' }],
|
|||
|
|
skus: [
|
|||
|
|
{ required: true, message: '至少需要一个SKU', trigger: 'change' },
|
|||
|
|
{
|
|||
|
|
validator: (rule, value, callback) => {
|
|||
|
|
if (!value || value.length === 0) {
|
|||
|
|
callback(new Error('至少需要一个SKU'))
|
|||
|
|
} else {
|
|||
|
|
callback()
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
trigger: 'change'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加SKU
|
|||
|
|
const addSku = () => {
|
|||
|
|
const newSku = {
|
|||
|
|
sku: '', // SKU描述
|
|||
|
|
regionPrices: {}, // 按地区存储价格
|
|||
|
|
stock: 0,
|
|||
|
|
skuImage: '', // SKU图片(单张)
|
|||
|
|
status: 'ACTIVE'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 为已选择的销售地区初始化价格
|
|||
|
|
if (form.salesRegions && form.salesRegions.length > 0) {
|
|||
|
|
form.salesRegions.forEach(regionCode => {
|
|||
|
|
newSku.regionPrices[regionCode] = null
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
form.skus.push(newSku)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除SKU
|
|||
|
|
const removeSku = (index) => {
|
|||
|
|
if (form.skus.length > 1) {
|
|||
|
|
form.skus.splice(index, 1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 自定义多图片上传(单个文件上传)
|
|||
|
|
const handleMainImagesUpload = async (options) => {
|
|||
|
|
const { file, onSuccess, onError } = options
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 验证文件
|
|||
|
|
const isValid = beforeUpload(file)
|
|||
|
|
if (!isValid) {
|
|||
|
|
onError(new Error('文件验证失败'))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否超过限制
|
|||
|
|
if (form.mainImages.length >= 10) {
|
|||
|
|
ElMessage.warning('最多只能上传10张图片')
|
|||
|
|
onError(new Error('超过图片数量限制'))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建FormData(单个文件)
|
|||
|
|
const formData = new FormData()
|
|||
|
|
formData.append('file', file)
|
|||
|
|
|
|||
|
|
// 上传文件(使用相对路径,baseURL已包含/api)
|
|||
|
|
const response = await request.post('/product/upload/image', formData)
|
|||
|
|
|
|||
|
|
if (response.code === '0000' && response.data) {
|
|||
|
|
const uploadedUrl = response.data.url || response.data.fileName
|
|||
|
|
|
|||
|
|
if (uploadedUrl) {
|
|||
|
|
form.mainImages.push(uploadedUrl)
|
|||
|
|
|
|||
|
|
// 更新mainImage为第一张图片(兼容)
|
|||
|
|
if (form.mainImages.length === 1) {
|
|||
|
|
form.mainImage = uploadedUrl
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新文件列表
|
|||
|
|
mainImageFileList.value.push({
|
|||
|
|
name: file.name,
|
|||
|
|
url: uploadedUrl,
|
|||
|
|
status: 'success',
|
|||
|
|
response: response
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
onSuccess(response, file)
|
|||
|
|
ElMessage.success('图片上传成功')
|
|||
|
|
} else {
|
|||
|
|
onError(new Error('未获取到图片URL'))
|
|||
|
|
ElMessage.error('图片上传失败:未获取到图片URL')
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
onError(new Error(response.message || '图片上传失败'))
|
|||
|
|
ElMessage.error(response.message || '图片上传失败')
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('图片上传失败:', error)
|
|||
|
|
onError(error)
|
|||
|
|
ElMessage.error('图片上传失败: ' + (error.message || '未知错误'))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SKU图片上传
|
|||
|
|
const handleSkuImageUpload = async (options, skuIndex) => {
|
|||
|
|
const { file, onSuccess, onError } = options
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 验证文件
|
|||
|
|
const isValid = beforeUpload(file)
|
|||
|
|
if (!isValid) {
|
|||
|
|
onError(new Error('文件验证失败'))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建FormData(单个文件)
|
|||
|
|
const formData = new FormData()
|
|||
|
|
formData.append('file', file)
|
|||
|
|
|
|||
|
|
// 上传文件(使用相对路径,baseURL已包含/api)
|
|||
|
|
const response = await request.post('/product/upload/image', formData)
|
|||
|
|
|
|||
|
|
if (response.code === '0000' && response.data) {
|
|||
|
|
const uploadedUrl = response.data.url || response.data.fileName
|
|||
|
|
|
|||
|
|
if (uploadedUrl) {
|
|||
|
|
form.skus[skuIndex].skuImage = uploadedUrl
|
|||
|
|
onSuccess(response, file)
|
|||
|
|
ElMessage.success('图片上传成功')
|
|||
|
|
} else {
|
|||
|
|
onError(new Error('未获取到图片URL'))
|
|||
|
|
ElMessage.error('图片上传失败:未获取到图片URL')
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
onError(new Error(response.message || '图片上传失败'))
|
|||
|
|
ElMessage.error(response.message || '图片上传失败')
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('SKU图片上传失败:', error)
|
|||
|
|
onError(error)
|
|||
|
|
ElMessage.error('图片上传失败: ' + (error.message || '未知错误'))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除SKU图片
|
|||
|
|
const removeSkuImage = (skuIndex) => {
|
|||
|
|
form.skus[skuIndex].skuImage = ''
|
|||
|
|
ElMessage.success('图片已删除')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除主图
|
|||
|
|
const handleMainImageRemove = (file, fileList) => {
|
|||
|
|
// 从mainImages中移除对应的URL
|
|||
|
|
if (file.url) {
|
|||
|
|
const index = form.mainImages.indexOf(file.url)
|
|||
|
|
if (index > -1) {
|
|||
|
|
form.mainImages.splice(index, 1)
|
|||
|
|
}
|
|||
|
|
} else if (file.response && file.response.data && file.response.data.url) {
|
|||
|
|
// 处理上传成功的文件
|
|||
|
|
const url = file.response.data.url
|
|||
|
|
const index = form.mainImages.indexOf(url)
|
|||
|
|
if (index > -1) {
|
|||
|
|
form.mainImages.splice(index, 1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新mainImage
|
|||
|
|
if (form.mainImages.length > 0) {
|
|||
|
|
form.mainImage = form.mainImages[0]
|
|||
|
|
} else {
|
|||
|
|
form.mainImage = ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新文件列表
|
|||
|
|
mainImageFileList.value = fileList
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 手动删除主图
|
|||
|
|
const removeMainImage = (index) => {
|
|||
|
|
form.mainImages.splice(index, 1)
|
|||
|
|
// 更新mainImage
|
|||
|
|
if (form.mainImages.length > 0) {
|
|||
|
|
form.mainImage = form.mainImages[0]
|
|||
|
|
} else {
|
|||
|
|
form.mainImage = ''
|
|||
|
|
}
|
|||
|
|
// 更新文件列表
|
|||
|
|
mainImageFileList.value = mainImageFileList.value.filter((_, i) => i !== index)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 上传前验证
|
|||
|
|
const beforeUpload = (file) => {
|
|||
|
|
const isImage = file.type.startsWith('image/')
|
|||
|
|
const isLt10M = file.size / 1024 / 1024 < 10
|
|||
|
|
|
|||
|
|
if (!isImage) {
|
|||
|
|
ElMessage.error('只能上传图片文件!')
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if (!isLt10M) {
|
|||
|
|
ElMessage.error('图片大小不能超过10MB!')
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否超过限制
|
|||
|
|
if (form.mainImages.length >= 10) {
|
|||
|
|
ElMessage.warning('最多只能上传10张图片')
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提交表单
|
|||
|
|
const submitForm = async () => {
|
|||
|
|
if (!formRef.value) return
|
|||
|
|
|
|||
|
|
await formRef.value.validate(async (valid) => {
|
|||
|
|
if (!valid) {
|
|||
|
|
ElMessage.error('请填写完整信息')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
submitting.value = true
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 验证物流信息(商品级别)
|
|||
|
|
if (!form.weightValue || form.weightValue <= 0) {
|
|||
|
|
ElMessage.error('请填写包裹重量')
|
|||
|
|
submitting.value = false
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
if (!form.sizeLength || !form.sizeWidth || !form.sizeHeight ||
|
|||
|
|
form.sizeLength <= 0 || form.sizeWidth <= 0 || form.sizeHeight <= 0) {
|
|||
|
|
ElMessage.error('请填写完整的包裹尺寸(长、宽、高)')
|
|||
|
|
submitting.value = false
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 转换重量:统一转换为克(g)
|
|||
|
|
let weightInGrams = null
|
|||
|
|
if (form.weightValue != null && form.weightValue > 0) {
|
|||
|
|
if (form.weightUnit === 'g') {
|
|||
|
|
weightInGrams = form.weightValue
|
|||
|
|
} else if (form.weightUnit === 'kg') {
|
|||
|
|
weightInGrams = form.weightValue * 1000
|
|||
|
|
} else if (form.weightUnit === 'lb') {
|
|||
|
|
weightInGrams = form.weightValue * 453.592
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 转换尺寸:统一转换为厘米(cm),并生成JSON
|
|||
|
|
let sizeJson = null
|
|||
|
|
if (form.sizeLength != null && form.sizeWidth != null && form.sizeHeight != null) {
|
|||
|
|
let length = form.sizeLength
|
|||
|
|
let width = form.sizeWidth
|
|||
|
|
let height = form.sizeHeight
|
|||
|
|
|
|||
|
|
// 转换为厘米
|
|||
|
|
if (form.sizeUnit === 'm') {
|
|||
|
|
length = length * 100
|
|||
|
|
width = width * 100
|
|||
|
|
height = height * 100
|
|||
|
|
} else if (form.sizeUnit === 'in') {
|
|||
|
|
length = length * 2.54
|
|||
|
|
width = width * 2.54
|
|||
|
|
height = height * 2.54
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sizeJson = JSON.stringify({
|
|||
|
|
length: parseFloat(length.toFixed(2)),
|
|||
|
|
width: parseFloat(width.toFixed(2)),
|
|||
|
|
height: parseFloat(height.toFixed(2)),
|
|||
|
|
unit: 'cm'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建请求数据
|
|||
|
|
const requestData = {
|
|||
|
|
name: form.name,
|
|||
|
|
price: form.price,
|
|||
|
|
// 优先使用mainImages,如果没有则使用mainImage(兼容)
|
|||
|
|
mainImages: form.mainImages.length > 0 ? form.mainImages : (form.mainImage ? [form.mainImage] : []),
|
|||
|
|
mainImage: form.mainImages.length > 0 ? form.mainImages[0] : form.mainImage, // 兼容字段
|
|||
|
|
status: form.status,
|
|||
|
|
shopId: form.shopId,
|
|||
|
|
skus: form.skus.flatMap(sku => {
|
|||
|
|
// 为每个有价格的地区创建一个SKU记录
|
|||
|
|
const skuRecords = []
|
|||
|
|
|
|||
|
|
if (sku.regionPrices && Object.keys(sku.regionPrices).length > 0) {
|
|||
|
|
Object.keys(sku.regionPrices).forEach(regionCode => {
|
|||
|
|
const price = sku.regionPrices[regionCode]
|
|||
|
|
if (price !== null && price > 0) {
|
|||
|
|
const region = regionCurrencyMap[regionCode]
|
|||
|
|
if (region) {
|
|||
|
|
skuRecords.push({
|
|||
|
|
sku: sku.sku, // SKU描述
|
|||
|
|
price: price,
|
|||
|
|
currency: region.currency,
|
|||
|
|
stock: sku.stock,
|
|||
|
|
skuImage: sku.skuImage || null, // SKU图片
|
|||
|
|
// 所有SKU共享商品级别的重量和尺寸
|
|||
|
|
weight: weightInGrams,
|
|||
|
|
size: sizeJson,
|
|||
|
|
// 销售属性和规格描述设为null(简化SKU,不需要这些字段)
|
|||
|
|
salesAttrs: null,
|
|||
|
|
specification: null,
|
|||
|
|
status: sku.status
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有地区价格,至少创建一个默认SKU
|
|||
|
|
if (skuRecords.length === 0) {
|
|||
|
|
skuRecords.push({
|
|||
|
|
sku: sku.sku,
|
|||
|
|
price: 0,
|
|||
|
|
currency: form.salesRegions && form.salesRegions.length > 0
|
|||
|
|
? regionCurrencyMap[form.salesRegions[0]].currency
|
|||
|
|
: 'USD',
|
|||
|
|
stock: sku.stock,
|
|||
|
|
skuImage: sku.skuImage || null, // SKU图片
|
|||
|
|
weight: weightInGrams,
|
|||
|
|
size: sizeJson,
|
|||
|
|
salesAttrs: null,
|
|||
|
|
specification: null,
|
|||
|
|
status: sku.status
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return skuRecords
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const response = await request.post('/product', requestData)
|
|||
|
|
|
|||
|
|
if (response.code === '0000') {
|
|||
|
|
ElMessage.success('商品创建成功')
|
|||
|
|
router.push('/manage/product')
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(response.message || '商品创建失败')
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('创建商品失败:', error)
|
|||
|
|
ElMessage.error(error.response?.data?.message || '商品创建失败,请稍后重试')
|
|||
|
|
} finally {
|
|||
|
|
submitting.value = false
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重置表单
|
|||
|
|
const resetForm = () => {
|
|||
|
|
if (formRef.value) {
|
|||
|
|
formRef.value.resetFields()
|
|||
|
|
}
|
|||
|
|
form.name = ''
|
|||
|
|
form.price = null
|
|||
|
|
form.mainImage = ''
|
|||
|
|
form.mainImages = []
|
|||
|
|
form.status = 'ACTIVE'
|
|||
|
|
form.shopId = null
|
|||
|
|
form.salesRegions = []
|
|||
|
|
// 重置物流信息
|
|||
|
|
form.weightValue = null
|
|||
|
|
form.weightUnit = 'g'
|
|||
|
|
form.sizeLength = null
|
|||
|
|
form.sizeWidth = null
|
|||
|
|
form.sizeHeight = null
|
|||
|
|
form.sizeUnit = 'cm'
|
|||
|
|
form.skus = [
|
|||
|
|
{
|
|||
|
|
sku: '', // SKU描述
|
|||
|
|
regionPrices: {}, // 按地区存储价格
|
|||
|
|
stock: 0,
|
|||
|
|
skuImage: '', // SKU图片
|
|||
|
|
status: 'ACTIVE'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
// 为SKU初始化regionPrices
|
|||
|
|
if (form.salesRegions && form.salesRegions.length > 0) {
|
|||
|
|
form.skus[0].regionPrices = {}
|
|||
|
|
form.salesRegions.forEach(regionCode => {
|
|||
|
|
form.skus[0].regionPrices[regionCode] = null
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mainImageFileList.value = []
|
|||
|
|
|
|||
|
|
// 重置批量操作状态
|
|||
|
|
batchStock.value = null
|
|||
|
|
batchRegionPrices.value = {}
|
|||
|
|
showBatchEdit.value = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回
|
|||
|
|
const goBack = () => {
|
|||
|
|
router.push('/manage/product')
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.product-create {
|
|||
|
|
max-width: 1400px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 图片上传 */
|
|||
|
|
.image-upload-section {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.upload-tip {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #909399;
|
|||
|
|
margin-top: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.form-tip {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #909399;
|
|||
|
|
margin-top: 5px;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 多图片上传样式 */
|
|||
|
|
.image-uploader-multiple {
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.image-uploader-multiple :deep(.el-upload-list--picture-card) {
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.image-uploader-multiple :deep(.el-upload--picture-card) {
|
|||
|
|
width: 120px;
|
|||
|
|
height: 120px;
|
|||
|
|
line-height: 120px;
|
|||
|
|
border: 1px dashed #d9d9d9;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.uploader-icon-multiple {
|
|||
|
|
font-size: 28px;
|
|||
|
|
color: #8c939d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 图片预览列表 */
|
|||
|
|
.image-preview-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 10px;
|
|||
|
|
margin-top: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.image-preview-item {
|
|||
|
|
position: relative;
|
|||
|
|
width: 120px;
|
|||
|
|
height: 120px;
|
|||
|
|
border: 1px solid #e4e7ed;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.preview-image {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.image-actions {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 5px;
|
|||
|
|
right: 5px;
|
|||
|
|
opacity: 0;
|
|||
|
|
transition: opacity 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.image-preview-item:hover .image-actions {
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 批量操作区域 */
|
|||
|
|
.batch-operation-card {
|
|||
|
|
background: linear-gradient(135deg, #f0f9ff 0%, #e6f4ff 100%);
|
|||
|
|
border: 1px solid #b3d8ff;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* SKU列表头部 */
|
|||
|
|
.sku-list-header {
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-edit-toggle-btn {
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-operation-content {
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-regions-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|||
|
|
gap: 15px;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
padding-bottom: 20px;
|
|||
|
|
border-bottom: 1px solid #d4e8ff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-region-item {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.region-label-wrapper {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.region-code-badge {
|
|||
|
|
display: inline-block;
|
|||
|
|
padding: 4px 10px;
|
|||
|
|
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
|
|||
|
|
color: #fff;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
font-size: 14px;
|
|||
|
|
min-width: 40px;
|
|||
|
|
text-align: center;
|
|||
|
|
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.region-name-text {
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #606266;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-actions-row {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
padding-top: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-stock-wrapper {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-label {
|
|||
|
|
font-size: 14px;
|
|||
|
|
color: #606266;
|
|||
|
|
font-weight: 500;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.batch-apply-btn {
|
|||
|
|
padding: 10px 24px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* SKU新增模块样式 */
|
|||
|
|
.sku-add-module-card {
|
|||
|
|
border: 1px solid #e4e7ed;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-module-header {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.module-title {
|
|||
|
|
font-size: 16px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #303133;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-config-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 20px;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-config-item {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 15px;
|
|||
|
|
padding: 20px;
|
|||
|
|
background: #fafafa;
|
|||
|
|
border: 1px solid #e4e7ed;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
transition: all 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-config-item:hover {
|
|||
|
|
border-color: #409eff;
|
|||
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-config-content {
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-description-section {
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-description-input {
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-images-section {
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-label {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #606266;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-image-wrapper {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-image-item {
|
|||
|
|
position: relative;
|
|||
|
|
width: 120px;
|
|||
|
|
height: 120px;
|
|||
|
|
border: 1px solid #e4e7ed;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
transition: all 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-image-item:hover {
|
|||
|
|
border-color: #409eff;
|
|||
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-preview-image {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.image-slot {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
background: #f5f7fa;
|
|||
|
|
color: #909399;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-image-item .image-actions {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 5px;
|
|||
|
|
right: 5px;
|
|||
|
|
opacity: 0;
|
|||
|
|
transition: opacity 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-image-item:hover .image-actions {
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-upload-item {
|
|||
|
|
width: 120px;
|
|||
|
|
height: 120px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-image-uploader {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-image-uploader :deep(.el-upload) {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
border: 1px dashed #d9d9d9;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
position: relative;
|
|||
|
|
overflow: hidden;
|
|||
|
|
transition: all 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-image-uploader :deep(.el-upload:hover) {
|
|||
|
|
border-color: #409eff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.upload-placeholder {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
background: #fafafa;
|
|||
|
|
color: #8c939d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.upload-icon {
|
|||
|
|
font-size: 28px;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.upload-text {
|
|||
|
|
font-size: 12px;
|
|||
|
|
margin-bottom: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
.sku-actions {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
padding-top: 5px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.add-sku-button-wrapper {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
padding-top: 10px;
|
|||
|
|
border-top: 1px solid #e4e7ed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.add-sku-btn {
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* SKU表格样式 */
|
|||
|
|
.sku-table-wrapper {
|
|||
|
|
width: 100%;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
overflow-y: visible;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-table-container {
|
|||
|
|
margin-top: 10px;
|
|||
|
|
min-width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-table {
|
|||
|
|
width: 100%;
|
|||
|
|
min-width: 800px;
|
|||
|
|
table-layout: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-table :deep(.el-table__cell) {
|
|||
|
|
padding: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-table :deep(.el-textarea__inner) {
|
|||
|
|
border: none;
|
|||
|
|
padding: 0;
|
|||
|
|
resize: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 横向滚动条样式优化 */
|
|||
|
|
.sku-table-wrapper::-webkit-scrollbar {
|
|||
|
|
height: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-table-wrapper::-webkit-scrollbar-track {
|
|||
|
|
background: #f1f1f1;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-table-wrapper::-webkit-scrollbar-thumb {
|
|||
|
|
background: #c1c1c1;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sku-table-wrapper::-webkit-scrollbar-thumb:hover {
|
|||
|
|
background: #a8a8a8;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|