|
@@ -4,9 +4,9 @@
|
|
<!-- 步骤进度条 -->
|
|
<!-- 步骤进度条 -->
|
|
<div class="step-progress">
|
|
<div class="step-progress">
|
|
<el-steps :active="currentStep" align-center>
|
|
<el-steps :active="currentStep" align-center>
|
|
- <el-step title="商品信息" :status="getStepStatus(0)"></el-step>
|
|
|
|
- <el-step title="商品属性" :status="getStepStatus(1)"></el-step>
|
|
|
|
- <el-step title="提交" :status="getStepStatus(2)"></el-step>
|
|
|
|
|
|
+ <el-step :title="t('modules.goods.goodsInfo')" :status="getStepStatus(0)"></el-step>
|
|
|
|
+ <el-step :title="t('modules.goods.goodsAttributes')" :status="getStepStatus(1)"></el-step>
|
|
|
|
+ <el-step :title="t('common.submit')" :status="getStepStatus(2)"></el-step>
|
|
</el-steps>
|
|
</el-steps>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
@@ -14,73 +14,51 @@
|
|
<div class="step-content">
|
|
<div class="step-content">
|
|
<!-- 第一步:商品信息 -->
|
|
<!-- 第一步:商品信息 -->
|
|
<div v-show="currentStep === 0" class="step-panel">
|
|
<div v-show="currentStep === 0" class="step-panel">
|
|
- <el-form ref="basicFormRef" :model="formData" :rules="basicRules" label-width="120px">
|
|
|
|
|
|
+ <el-form ref="basicFormRef" :model="formData" :rules="basicRules" :label-width="formLabelWidth">
|
|
<el-row :gutter="40">
|
|
<el-row :gutter="40">
|
|
<!-- 左侧表单 -->
|
|
<!-- 左侧表单 -->
|
|
<el-col :span="14">
|
|
<el-col :span="14">
|
|
- <el-form-item label="商品分类" prop="cateId" required>
|
|
|
|
- <el-select
|
|
|
|
- v-model="formData.cateId"
|
|
|
|
- placeholder="请选择商品分类"
|
|
|
|
- clearable
|
|
|
|
- style="width: 100%"
|
|
|
|
- >
|
|
|
|
- <el-option
|
|
|
|
- v-for="category in categoryOptions"
|
|
|
|
- :key="category.id"
|
|
|
|
- :label="category.name"
|
|
|
|
- :value="category.id"
|
|
|
|
- />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.goodsCategory')" prop="cateId" required>
|
|
|
|
+ <el-select v-model="formData.cateId" :placeholder="t('form.selectCategory')" clearable
|
|
|
|
+ style="width: 100%">
|
|
|
|
+ <el-option v-for="category in categoryOptions" :key="category.id" :label="category.name"
|
|
|
|
+ :value="category.id" />
|
|
</el-select>
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="商品名称" prop="storeName" required>
|
|
|
|
- <el-input
|
|
|
|
- v-model="formData.storeName"
|
|
|
|
- placeholder="请输入商品名称(限100字符)"
|
|
|
|
- maxlength="100"
|
|
|
|
- show-word-limit
|
|
|
|
- />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.goodsName')" prop="storeName" required>
|
|
|
|
+ <el-input v-model="formData.storeName" :placeholder="t('form.inputGoodsName')" maxlength="100"
|
|
|
|
+ show-word-limit />
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="副标题" prop="keyword">
|
|
|
|
- <el-input
|
|
|
|
- v-model="formData.keyword"
|
|
|
|
- placeholder="请输入副标题(限50字符)"
|
|
|
|
- maxlength="50"
|
|
|
|
- show-word-limit
|
|
|
|
- />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.subtitle')" prop="keyword">
|
|
|
|
+ <el-input v-model="formData.keyword" :placeholder="t('form.inputSubtitle')" maxlength="50"
|
|
|
|
+ show-word-limit />
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="商品品牌" prop="itemBrand">
|
|
|
|
- <el-input v-model="formData.itemBrand" placeholder="请输入商品品牌" />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.goodsBrand')" prop="itemBrand">
|
|
|
|
+ <el-input v-model="formData.itemBrand" :placeholder="t('form.inputGoodsBrand')" />
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="商品介绍" prop="storeInfo">
|
|
|
|
- <el-input
|
|
|
|
- v-model="formData.storeInfo"
|
|
|
|
- type="textarea"
|
|
|
|
- :rows="4"
|
|
|
|
- placeholder="请输入商品介绍(限500字符)"
|
|
|
|
- maxlength="500"
|
|
|
|
- show-word-limit
|
|
|
|
- />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.goodsDescription')" prop="storeInfo">
|
|
|
|
+ <el-input v-model="formData.storeInfo" type="textarea" :rows="4"
|
|
|
|
+ :placeholder="t('form.inputGoodsDescription')" maxlength="500" show-word-limit />
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="运费模板" prop="tempId">
|
|
|
|
- <div class="mt-1px">包邮</div>
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.shippingTemplate')" prop="tempId">
|
|
|
|
+ <div class="mt-1px">{{ t('modules.goods.freeShipping') }}</div>
|
|
<!-- <el-select
|
|
<!-- <el-select
|
|
v-model="formData.tempId"
|
|
v-model="formData.tempId"
|
|
- placeholder="请选择运费模板"
|
|
|
|
|
|
+ :placeholder="t('form.selectShippingTemplate')"
|
|
style="width: 100%"
|
|
style="width: 100%"
|
|
>
|
|
>
|
|
- <el-option label="包邮" :value="1" />
|
|
|
|
|
|
+ <el-option :label="t('modules.goods.freeShipping')" :value="1" />
|
|
</el-select> -->
|
|
</el-select> -->
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="商品货号" prop="itemNumber">
|
|
|
|
- <el-input v-model="formData.itemNumber" placeholder="请输入商品货号" />
|
|
|
|
- <div class="form-tip">如果您不输入商品货号,系统将自动生成一个唯一的货号</div>
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.goodsNumber')" prop="itemNumber">
|
|
|
|
+ <el-input v-model="formData.itemNumber" :placeholder="t('form.inputGoodsNumber')" />
|
|
|
|
+ <div class="form-tip">{{ t('modules.goods.autoGenerateNumberTip') }}</div>
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
<!-- <el-form-item label="商品售价" prop="price" required>
|
|
<!-- <el-form-item label="商品售价" prop="price" required>
|
|
@@ -92,32 +70,19 @@
|
|
step="0.01"
|
|
step="0.01"
|
|
>
|
|
>
|
|
<template #append>৳</template>
|
|
<template #append>৳</template>
|
|
- </el-input>
|
|
|
|
- </el-form-item>
|
|
|
|
|
|
+</el-input>
|
|
|
|
+</el-form-item>
|
|
|
|
|
|
- <el-form-item label="市场价" prop="otPrice">
|
|
|
|
- <el-input
|
|
|
|
- v-model="formData.otPrice"
|
|
|
|
- placeholder="请输入市场价"
|
|
|
|
- type="number"
|
|
|
|
- min="0"
|
|
|
|
- step="0.01"
|
|
|
|
- >
|
|
|
|
- <template #append>৳</template>
|
|
|
|
- </el-input>
|
|
|
|
- </el-form-item>
|
|
|
|
|
|
+<el-form-item label="市场价" prop="otPrice">
|
|
|
|
+ <el-input v-model="formData.otPrice" placeholder="请输入市场价" type="number" min="0" step="0.01">
|
|
|
|
+ <template #append>৳</template>
|
|
|
|
+ </el-input>
|
|
|
|
+</el-form-item>
|
|
|
|
|
|
- <el-form-item label="商品库存" prop="stock" required>
|
|
|
|
- <el-input
|
|
|
|
- v-model="formData.stock"
|
|
|
|
- placeholder="请输入商品库存"
|
|
|
|
- type="number"
|
|
|
|
- min="0"
|
|
|
|
- />
|
|
|
|
- <div class="form-tip"
|
|
|
|
- >该设置只对单品有效,当商品存在多规格货品时为不可编辑状态,库存数值取决于货品数量</div
|
|
|
|
- >
|
|
|
|
- </el-form-item> -->
|
|
|
|
|
|
+<el-form-item label="商品库存" prop="stock" required>
|
|
|
|
+ <el-input v-model="formData.stock" placeholder="请输入商品库存" type="number" min="0" />
|
|
|
|
+ <div class="form-tip">该设置只对单品有效,当商品存在多规格货品时为不可编辑状态,库存数值取决于货品数量</div>
|
|
|
|
+</el-form-item> -->
|
|
|
|
|
|
<!-- <el-form-item label="库存预警值" prop="stockThreshold">
|
|
<!-- <el-form-item label="库存预警值" prop="stockThreshold">
|
|
<el-input
|
|
<el-input
|
|
@@ -128,57 +93,36 @@
|
|
/>
|
|
/>
|
|
</el-form-item> -->
|
|
</el-form-item> -->
|
|
|
|
|
|
- <el-form-item label="商品状态" prop="isShow" required>
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.goodsStatus')" prop="isShow" required>
|
|
<el-radio-group v-model="formData.isShow" :disabled="!isEdit">
|
|
<el-radio-group v-model="formData.isShow" :disabled="!isEdit">
|
|
- <el-radio :label="1">上架</el-radio>
|
|
|
|
- <el-radio :label="0">下架</el-radio>
|
|
|
|
|
|
+ <el-radio :label="1">{{ t('modules.goods.onSale') }}</el-radio>
|
|
|
|
+ <el-radio :label="0">{{ t('modules.goods.offSale') }}</el-radio>
|
|
</el-radio-group>
|
|
</el-radio-group>
|
|
- <div v-if="!isEdit" class="form-tip ml-20px mb-6px"
|
|
|
|
- >新增商品默认为下架状态,保存后可修改</div
|
|
|
|
- >
|
|
|
|
|
|
+ <div v-if="!isEdit" class="form-tip ml-20px mb-6px">
|
|
|
|
+ {{ t('modules.goods.newGoodsOffSaleTip') }}
|
|
|
|
+ </div>
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="商品供应商" prop="itemSupplier" required>
|
|
|
|
- <el-input v-model="formData.itemSupplier" placeholder="请输入商品供应商" />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.goodsSupplier')" prop="itemSupplier" required>
|
|
|
|
+ <el-input v-model="formData.itemSupplier" :placeholder="t('form.inputGoodsSupplier')" />
|
|
</el-form-item>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-col>
|
|
|
|
|
|
<!-- 右侧图片上传 -->
|
|
<!-- 右侧图片上传 -->
|
|
<el-col :span="10">
|
|
<el-col :span="10">
|
|
- <el-form-item label="白底主图" prop="image" required>
|
|
|
|
- <sa-upload-image
|
|
|
|
- v-model="formData.image"
|
|
|
|
- :max-count="1"
|
|
|
|
- :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
- :max-size="5"
|
|
|
|
- :direct-upload="true"
|
|
|
|
- :size="100"
|
|
|
|
- placeholder="上传白底主图"
|
|
|
|
- />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.mainImage')" prop="image" required>
|
|
|
|
+ <sa-upload-image v-model="formData.image" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
+ :max-size="5" :direct-upload="true" :size="100" :placeholder="t('form.uploadMainImage')" />
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="轮播图" prop="sliderImage" required>
|
|
|
|
- <sa-upload-image
|
|
|
|
- v-model="formData.sliderImage"
|
|
|
|
- :max-count="5"
|
|
|
|
- :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
- :max-size="5"
|
|
|
|
- :direct-upload="true"
|
|
|
|
- :size="100"
|
|
|
|
- placeholder="上传轮播图"
|
|
|
|
- />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.carouselImages')" prop="sliderImage" required>
|
|
|
|
+ <sa-upload-image v-model="formData.sliderImage" :max-count="5" :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
+ :max-size="5" :direct-upload="true" :size="100" :placeholder="t('form.uploadCarouselImages')" />
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="详情图" prop="flatPattern" required>
|
|
|
|
- <sa-upload-image
|
|
|
|
- v-model="formData.flatPattern"
|
|
|
|
- :max-count="10"
|
|
|
|
- :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
- :max-size="5"
|
|
|
|
- :direct-upload="true"
|
|
|
|
- :size="100"
|
|
|
|
- placeholder="上传详情图"
|
|
|
|
- />
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.detailImages')" prop="flatPattern" required>
|
|
|
|
+ <sa-upload-image v-model="formData.flatPattern" :max-count="10" :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
+ :max-size="5" :direct-upload="true" :size="100" :placeholder="t('form.uploadDetailImages')" />
|
|
</el-form-item>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-col>
|
|
</el-row>
|
|
</el-row>
|
|
@@ -187,143 +131,101 @@
|
|
|
|
|
|
<!-- 第二步:商品属性 -->
|
|
<!-- 第二步:商品属性 -->
|
|
<div v-show="currentStep === 1" class="" :key="forceUpdateKey">
|
|
<div v-show="currentStep === 1" class="" :key="forceUpdateKey">
|
|
- <el-form ref="attrFormRef" :model="formData" :rules="attrRules" label-width="120px">
|
|
|
|
|
|
+ <el-form ref="attrFormRef" :model="formData" :rules="attrRules" :label-width="formLabelWidth">
|
|
<!-- 多规格设置 -->
|
|
<!-- 多规格设置 -->
|
|
<el-card class="spec-card">
|
|
<el-card class="spec-card">
|
|
<template #header>
|
|
<template #header>
|
|
<div class="card-header">
|
|
<div class="card-header">
|
|
- <span>商品规格设置</span>
|
|
|
|
|
|
+ <span>{{ t('modules.goods.specificationSettings') }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<!-- 操作 -->
|
|
<!-- 操作 -->
|
|
<div class="sku-wrap" :key="`sku-wrap-${forceUpdateKey}`">
|
|
<div class="sku-wrap" :key="`sku-wrap-${forceUpdateKey}`">
|
|
- <div
|
|
|
|
- class="sku"
|
|
|
|
- v-for="(s, k) in formData.skus"
|
|
|
|
- :key="`sku-${k}-${forceUpdateKey}`"
|
|
|
|
- >
|
|
|
|
|
|
+ <div class="sku" v-for="(s, k) in formData.skus" :key="`sku-${k}-${forceUpdateKey}`">
|
|
<div class="sku-key sa-flex sa-row-between">
|
|
<div class="sku-key sa-flex sa-row-between">
|
|
<div class="sa-flex">
|
|
<div class="sa-flex">
|
|
- <div class="sa-m-r-16">规格名称</div>
|
|
|
|
- <el-input
|
|
|
|
- v-model="s.name"
|
|
|
|
- placeholder="请输入规格名称"
|
|
|
|
- class="sku-key-input"
|
|
|
|
- @input="buildSkuPriceTable"
|
|
|
|
- ></el-input>
|
|
|
|
|
|
+ <div class="sa-m-r-16">{{ t('modules.goods.specificationName') }}</div>
|
|
|
|
+ <el-input v-model="s.name" :placeholder="t('form.inputSpecificationName')" class="sku-key-input"
|
|
|
|
+ @input="buildSkuPriceTable"></el-input>
|
|
</div>
|
|
</div>
|
|
<el-icon @click="deleteMainSku(k)" class="sku-key-icon">
|
|
<el-icon @click="deleteMainSku(k)" class="sku-key-icon">
|
|
<CircleCloseFilled />
|
|
<CircleCloseFilled />
|
|
</el-icon>
|
|
</el-icon>
|
|
</div>
|
|
</div>
|
|
<div class="sku-value sa-flex sa-flex-wrap">
|
|
<div class="sku-value sa-flex sa-flex-wrap">
|
|
- <div class="sku-value-title sa-m-r-16 sa-m-b-16 sa-flex"> 规格值 </div>
|
|
|
|
|
|
+ <div class="sku-value-title sa-m-r-16 sa-m-b-16 sa-flex">{{
|
|
|
|
+ t('modules.goods.specificationValue')
|
|
|
|
+ }}</div>
|
|
<div v-for="(sc, c) in s.children" :key="c" class="sku-value-box sa-m-b-16">
|
|
<div v-for="(sc, c) in s.children" :key="c" class="sku-value-box sa-m-b-16">
|
|
<div class="sku-value-content">
|
|
<div class="sku-value-content">
|
|
- <el-input
|
|
|
|
- v-model="sc.name"
|
|
|
|
- placeholder="请输入规格值"
|
|
|
|
- class="sku-value-input"
|
|
|
|
- @input="buildSkuPriceTable"
|
|
|
|
- ></el-input>
|
|
|
|
|
|
+ <el-input v-model="sc.name" :placeholder="t('form.inputSpecificationValue')"
|
|
|
|
+ class="sku-value-input" @input="buildSkuPriceTable"></el-input>
|
|
<div class="sku-value-image sa-m-l-8">
|
|
<div class="sku-value-image sa-m-l-8">
|
|
- <sa-upload-image
|
|
|
|
- v-model="sc.imageList"
|
|
|
|
- :max-count="1"
|
|
|
|
- :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
- :max-size="5"
|
|
|
|
- :direct-upload="true"
|
|
|
|
- :size="30"
|
|
|
|
- :show-tip="false"
|
|
|
|
- :compact="true"
|
|
|
|
- placeholder=""
|
|
|
|
- />
|
|
|
|
|
|
+ <sa-upload-image v-model="sc.imageList" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
+ :max-size="5" :direct-upload="true" :size="30" :show-tip="false" :compact="true"
|
|
|
|
+ placeholder="" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<el-icon @click="deleteChildrenSku(k, c)" class="sku-value-icon">
|
|
<el-icon @click="deleteChildrenSku(k, c)" class="sku-value-icon">
|
|
<CircleCloseFilled />
|
|
<CircleCloseFilled />
|
|
</el-icon>
|
|
</el-icon>
|
|
</div>
|
|
</div>
|
|
- <div
|
|
|
|
- @click="addChildrenSku(k)"
|
|
|
|
- class="sku-value-add sa-m-r-24 sa-m-b-16 sa-flex cursor-pointer"
|
|
|
|
- >
|
|
|
|
- 添加规格值
|
|
|
|
|
|
+ <div @click="addChildrenSku(k)" class="sku-value-add sa-m-r-24 sa-m-b-16 sa-flex cursor-pointer">
|
|
|
|
+ {{ t('modules.goods.addSpecificationValue') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sku-tools sa-flex">
|
|
<div class="sku-tools sa-flex">
|
|
- <el-button type="primary" class="add" @click="addMainSku">+ 添加规格</el-button>
|
|
|
|
|
|
+ <el-button type="primary" class="add" @click="addMainSku">+ {{ t('modules.goods.addSpecification')
|
|
|
|
+ }}</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- 批量设置 -->
|
|
<!-- 批量设置 -->
|
|
<div class="sa-m-t-20" v-if="formData.sku_prices.length > 0">
|
|
<div class="sa-m-t-20" v-if="formData.sku_prices.length > 0">
|
|
- <el-form-item label="批量设置" label-width="80px">
|
|
|
|
|
|
+ <el-form-item :label="t('modules.goods.batchSettings')" label-width="110px">
|
|
<div class="sku sa-m-r-20" v-for="(item, index) in formData.skus" :key="index">
|
|
<div class="sku sa-m-r-20" v-for="(item, index) in formData.skus" :key="index">
|
|
- <el-select
|
|
|
|
- v-model="item.batchId"
|
|
|
|
- placeholder="请选择规格"
|
|
|
|
- class="sa-w-150"
|
|
|
|
- clearable
|
|
|
|
- >
|
|
|
|
|
|
+ <el-select v-model="item.batchId" :placeholder="t('form.selectSpecification')" class="sa-w-150"
|
|
|
|
+ clearable>
|
|
<template v-for="(citem, cindex) in item.children">
|
|
<template v-for="(citem, cindex) in item.children">
|
|
- <el-option
|
|
|
|
- :key="cindex"
|
|
|
|
- :label="citem.name"
|
|
|
|
- :value="citem.temp_id"
|
|
|
|
- v-if="citem.temp_id && citem.name"
|
|
|
|
- ></el-option>
|
|
|
|
|
|
+ <el-option :key="cindex" :label="citem.name" :value="citem.temp_id"
|
|
|
|
+ v-if="citem.temp_id && citem.name"></el-option>
|
|
</template>
|
|
</template>
|
|
</el-select>
|
|
</el-select>
|
|
</div>
|
|
</div>
|
|
<div class="warning-title" style="margin-left: 8px">
|
|
<div class="warning-title" style="margin-left: 8px">
|
|
- 未选择规格默认为全选批量设置
|
|
|
|
|
|
+ {{ t('modules.goods.batchSettingsTip') }}
|
|
</div>
|
|
</div>
|
|
</el-form-item>
|
|
</el-form-item>
|
|
<div class="sa-flex sa-flex-wrap">
|
|
<div class="sa-flex sa-flex-wrap">
|
|
- <el-select
|
|
|
|
- v-model="allEditObj.price"
|
|
|
|
- placeholder="请选择售价(৳)"
|
|
|
|
- class="sa-w-200 sa-m-r-10 sa-m-b-10"
|
|
|
|
- clearable
|
|
|
|
- >
|
|
|
|
|
|
+ <el-select v-model="allEditObj.price" :placeholder="t('form.selectSalePrice')"
|
|
|
|
+ class="sa-w-200 sa-m-r-10 sa-m-b-10" clearable>
|
|
<el-option :value="300" label="300৳" />
|
|
<el-option :value="300" label="300৳" />
|
|
<el-option :value="500" label="500৳" />
|
|
<el-option :value="500" label="500৳" />
|
|
<el-option :value="1000" label="1000৳" />
|
|
<el-option :value="1000" label="1000৳" />
|
|
<el-option :value="2000" label="2000৳" />
|
|
<el-option :value="2000" label="2000৳" />
|
|
<el-option :value="3000" label="3000৳" />
|
|
<el-option :value="3000" label="3000৳" />
|
|
</el-select>
|
|
</el-select>
|
|
- <el-select
|
|
|
|
- v-model="allEditObj.otPrice"
|
|
|
|
- placeholder="请选择市场价(৳)"
|
|
|
|
- class="sa-w-200 sa-m-r-10 sa-m-b-10"
|
|
|
|
- clearable
|
|
|
|
- >
|
|
|
|
|
|
+ <el-select v-model="allEditObj.otPrice" :placeholder="t('form.selectMarketPrice')"
|
|
|
|
+ class="sa-w-200 sa-m-r-10 sa-m-b-10" clearable>
|
|
<el-option :value="300" label="300৳" />
|
|
<el-option :value="300" label="300৳" />
|
|
<el-option :value="500" label="500৳" />
|
|
<el-option :value="500" label="500৳" />
|
|
<el-option :value="1000" label="1000৳" />
|
|
<el-option :value="1000" label="1000৳" />
|
|
<el-option :value="2000" label="2000৳" />
|
|
<el-option :value="2000" label="2000৳" />
|
|
<el-option :value="3000" label="3000৳" />
|
|
<el-option :value="3000" label="3000৳" />
|
|
</el-select>
|
|
</el-select>
|
|
- <el-input
|
|
|
|
- v-model="allEditObj.stock"
|
|
|
|
- placeholder="请输入库存"
|
|
|
|
- class="sa-w-250 sa-m-r-10 sa-m-b-10"
|
|
|
|
- >
|
|
|
|
- <template #prepend>库存</template>
|
|
|
|
|
|
+ <el-input v-model="allEditObj.stock" :placeholder="t('form.inputStock')"
|
|
|
|
+ class="sa-w-250 sa-m-r-10 sa-m-b-10">
|
|
|
|
+ <template #prepend>{{ t('modules.goods.goodsStock') }}</template>
|
|
</el-input>
|
|
</el-input>
|
|
- <el-input
|
|
|
|
- v-model="allEditObj.stockThreshold"
|
|
|
|
- placeholder="请输入库存预警值"
|
|
|
|
- class="sa-w-300 sa-m-r-10 sa-m-b-10"
|
|
|
|
- >
|
|
|
|
- <template #prepend>库存预警值</template>
|
|
|
|
|
|
+ <el-input v-model="allEditObj.stockThreshold" :placeholder="t('form.inputStockThreshold')"
|
|
|
|
+ class="sa-w-300 sa-m-r-10 sa-m-b-10">
|
|
|
|
+ <template #prepend>{{ t('modules.goods.stockThreshold') }}</template>
|
|
</el-input>
|
|
</el-input>
|
|
- <el-button type="primary" @click="batchEdit" class="sa-m-b-10"
|
|
|
|
- >批量设置</el-button
|
|
|
|
- >
|
|
|
|
|
|
+ <el-button type="primary" @click="batchEdit" class="sa-m-b-10">
|
|
|
|
+ {{ t('modules.goods.batchSettings') }}
|
|
|
|
+ </el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
@@ -335,13 +237,13 @@
|
|
<template v-for="(item, i) in formData.skus" :key="i">
|
|
<template v-for="(item, i) in formData.skus" :key="i">
|
|
<th v-if="item.children.length">{{ item.name }}</th>
|
|
<th v-if="item.children.length">{{ item.name }}</th>
|
|
</template>
|
|
</template>
|
|
- <th>图片</th>
|
|
|
|
- <th><span class="required">*</span>销售价格(৳)</th>
|
|
|
|
- <th><span class="required">*</span>市场价(৳)</th>
|
|
|
|
- <th><span class="required">*</span>商品库存</th>
|
|
|
|
- <th>库存预警值</th>
|
|
|
|
- <th>SKU编码</th>
|
|
|
|
- <th>操作</th>
|
|
|
|
|
|
+ <th>{{ t('modules.goods.image') }}</th>
|
|
|
|
+ <th><span class="required">*</span>{{ t('modules.goods.salePrice') }}(৳)</th>
|
|
|
|
+ <th><span class="required">*</span>{{ t('modules.goods.marketPrice') }}(৳)</th>
|
|
|
|
+ <th><span class="required">*</span>{{ t('modules.goods.goodsStock') }}</th>
|
|
|
|
+ <th>{{ t('modules.goods.stockThreshold') }}</th>
|
|
|
|
+ <th>{{ t('modules.goods.skuCode') }}</th>
|
|
|
|
+ <th>{{ t('common.actions') }}</th>
|
|
</tr>
|
|
</tr>
|
|
</thead>
|
|
</thead>
|
|
<tbody>
|
|
<tbody>
|
|
@@ -352,25 +254,13 @@
|
|
</td>
|
|
</td>
|
|
</template>
|
|
</template>
|
|
<td class="image">
|
|
<td class="image">
|
|
- <sa-upload-image
|
|
|
|
- v-model="item.imageList"
|
|
|
|
- :max-count="1"
|
|
|
|
- :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
- :max-size="5"
|
|
|
|
- :direct-upload="true"
|
|
|
|
- :size="30"
|
|
|
|
- :show-tip="false"
|
|
|
|
- :compact="true"
|
|
|
|
- placeholder=""
|
|
|
|
- />
|
|
|
|
|
|
+ <sa-upload-image v-model="item.imageList" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
|
|
|
|
+ :max-size="5" :direct-upload="true" :size="30" :show-tip="false" :compact="true"
|
|
|
|
+ placeholder="" />
|
|
</td>
|
|
</td>
|
|
<td>
|
|
<td>
|
|
- <el-select
|
|
|
|
- v-model="item.price"
|
|
|
|
- placeholder="选择价格"
|
|
|
|
- size="small"
|
|
|
|
- :class="{ 'is-error': !item.price || item.price <= 0 }"
|
|
|
|
- >
|
|
|
|
|
|
+ <el-select v-model="item.price" :placeholder="t('form.selectPrice')" size="small"
|
|
|
|
+ :class="{ 'is-error': !item.price || item.price <= 0 }">
|
|
<el-option :value="300" label="300(৳)" />
|
|
<el-option :value="300" label="300(৳)" />
|
|
<el-option :value="500" label="500(৳)" />
|
|
<el-option :value="500" label="500(৳)" />
|
|
<el-option :value="1000" label="1000(৳)" />
|
|
<el-option :value="1000" label="1000(৳)" />
|
|
@@ -379,12 +269,8 @@
|
|
</el-select>
|
|
</el-select>
|
|
</td>
|
|
</td>
|
|
<td>
|
|
<td>
|
|
- <el-select
|
|
|
|
- v-model="item.otPrice"
|
|
|
|
- placeholder="选择市场价"
|
|
|
|
- size="small"
|
|
|
|
- :class="{ 'is-error': !item.otPrice || item.otPrice <= 0 }"
|
|
|
|
- >
|
|
|
|
|
|
+ <el-select v-model="item.otPrice" :placeholder="t('form.selectMarketPrice')" size="small"
|
|
|
|
+ :class="{ 'is-error': !item.otPrice || item.otPrice <= 0 }">
|
|
<el-option :value="300" label="300(৳)" />
|
|
<el-option :value="300" label="300(৳)" />
|
|
<el-option :value="500" label="500(৳)" />
|
|
<el-option :value="500" label="500(৳)" />
|
|
<el-option :value="1000" label="1000(৳)" />
|
|
<el-option :value="1000" label="1000(৳)" />
|
|
@@ -393,37 +279,20 @@
|
|
</el-select>
|
|
</el-select>
|
|
</td>
|
|
</td>
|
|
<td class="stock">
|
|
<td class="stock">
|
|
- <el-input
|
|
|
|
- v-model="item.stock"
|
|
|
|
- placeholder="请输入库存"
|
|
|
|
- size="small"
|
|
|
|
- type="number"
|
|
|
|
- :step="1"
|
|
|
|
- :min="0"
|
|
|
|
- :class="{ 'is-error': !item.stock || item.stock < 0 }"
|
|
|
|
- ></el-input>
|
|
|
|
|
|
+ <el-input v-model="item.stock" :placeholder="t('form.inputStock')" size="small" type="number"
|
|
|
|
+ :step="1" :min="0" :class="{ 'is-error': !item.stock || item.stock < 0 }"></el-input>
|
|
</td>
|
|
</td>
|
|
<td class="stockThreshold">
|
|
<td class="stockThreshold">
|
|
- <el-input
|
|
|
|
- v-model="item.stockThreshold"
|
|
|
|
- placeholder="请输入预警值"
|
|
|
|
- size="small"
|
|
|
|
- type="number"
|
|
|
|
- :step="1"
|
|
|
|
- :min="0"
|
|
|
|
- ></el-input>
|
|
|
|
|
|
+ <el-input v-model="item.stockThreshold" :placeholder="t('form.inputStockThreshold')"
|
|
|
|
+ size="small" type="number" :step="1" :min="0"></el-input>
|
|
</td>
|
|
</td>
|
|
<td class="sn">
|
|
<td class="sn">
|
|
- <el-input
|
|
|
|
- v-model="item.skuCode"
|
|
|
|
- placeholder="请输入SKU编码"
|
|
|
|
- size="small"
|
|
|
|
- ></el-input>
|
|
|
|
|
|
+ <el-input v-model="item.skuCode" :placeholder="t('form.inputSkuCode')" size="small"></el-input>
|
|
</td>
|
|
</td>
|
|
<td>
|
|
<td>
|
|
- <el-button type="danger" size="small" text @click="deleteSkuPrice(i)"
|
|
|
|
- >删除</el-button
|
|
|
|
- >
|
|
|
|
|
|
+ <el-button type="danger" size="small" text @click="deleteSkuPrice(i)">
|
|
|
|
+ {{ t('common.delete') }}
|
|
|
|
+ </el-button>
|
|
</td>
|
|
</td>
|
|
</tr>
|
|
</tr>
|
|
</tbody>
|
|
</tbody>
|
|
@@ -441,18 +310,20 @@
|
|
<CircleCheck />
|
|
<CircleCheck />
|
|
</el-icon>
|
|
</el-icon>
|
|
<h3 class="mt-20">
|
|
<h3 class="mt-20">
|
|
- {{ isEdit ? '保存成功' : '商品添加成功,请等上架' }}
|
|
|
|
|
|
+ {{ isEdit ? t('message.saveSuccess') : t('modules.goods.goodsAddedSuccess') }}
|
|
</h3>
|
|
</h3>
|
|
<div v-if="!isEdit" class="action-buttons mt-30">
|
|
<div v-if="!isEdit" class="action-buttons mt-30">
|
|
- <el-button type="primary" @click="continueAction"> 继续添加 </el-button>
|
|
|
|
|
|
+ <el-button type="primary" @click="continueAction">{{
|
|
|
|
+ t('modules.goods.continueAdd')
|
|
|
|
+ }}</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="loading-content">
|
|
<div v-else class="loading-content">
|
|
<el-icon class="loading-icon" :size="80">
|
|
<el-icon class="loading-icon" :size="80">
|
|
<Loading />
|
|
<Loading />
|
|
</el-icon>
|
|
</el-icon>
|
|
- <h3>正在保存商品...</h3>
|
|
|
|
- <p>请稍候,系统正在处理您的请求</p>
|
|
|
|
|
|
+ <h3>{{ t('modules.goods.savingGoods') }}</h3>
|
|
|
|
+ <p>{{ t('modules.goods.pleaseWait') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@@ -462,1239 +333,1256 @@
|
|
<!-- 操作按钮 -->
|
|
<!-- 操作按钮 -->
|
|
<el-footer class="sa-footer--submit">
|
|
<el-footer class="sa-footer--submit">
|
|
<template v-if="currentStep < 2">
|
|
<template v-if="currentStep < 2">
|
|
- <el-button v-if="currentStep > 0" @click="prevStep">上一步</el-button>
|
|
|
|
|
|
+ <el-button v-if="currentStep > 0" @click="prevStep">{{ t('common.previous') }}</el-button>
|
|
<el-button type="primary" @click="nextStep" :loading="currentStep === 1 && submitLoading">
|
|
<el-button type="primary" @click="nextStep" :loading="currentStep === 1 && submitLoading">
|
|
- {{ currentStep === 1 ? '提交' : '下一步' }}
|
|
|
|
|
|
+ {{ currentStep === 1 ? t('common.submit') : t('common.next') }}
|
|
</el-button>
|
|
</el-button>
|
|
</template>
|
|
</template>
|
|
<template v-if="currentStep === 2">
|
|
<template v-if="currentStep === 2">
|
|
- <el-button @click="closeModal">关闭</el-button>
|
|
|
|
|
|
+ <el-button @click="closeModal">{{ t('common.close') }}</el-button>
|
|
</template>
|
|
</template>
|
|
</el-footer>
|
|
</el-footer>
|
|
</el-container>
|
|
</el-container>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
-<script>
|
|
|
|
- import { ref, reactive, onMounted, computed } from 'vue';
|
|
|
|
- import { ElMessage } from 'element-plus';
|
|
|
|
- import { CircleCheck, CircleCloseFilled, Edit, Loading } from '@element-plus/icons-vue';
|
|
|
|
-
|
|
|
|
- import { api } from '../goods.service';
|
|
|
|
- import { NIL } from 'uuid';
|
|
|
|
-
|
|
|
|
- export default {
|
|
|
|
- name: 'GoodsEditNew',
|
|
|
|
- components: {
|
|
|
|
- CircleCheck,
|
|
|
|
- CircleCloseFilled,
|
|
|
|
- Edit,
|
|
|
|
- Loading,
|
|
|
|
- },
|
|
|
|
- };
|
|
|
|
-</script>
|
|
|
|
-
|
|
|
|
<script setup>
|
|
<script setup>
|
|
- const props = defineProps({
|
|
|
|
- modal: {
|
|
|
|
- type: Object,
|
|
|
|
- default: () => ({}),
|
|
|
|
- },
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- const emit = defineEmits(['success', 'modalCallBack']);
|
|
|
|
-
|
|
|
|
- // 当前步骤
|
|
|
|
- const currentStep = ref(0);
|
|
|
|
-
|
|
|
|
- // 是否为编辑模式
|
|
|
|
- const isEdit = computed(() => props.modal?.params?.type === 'edit');
|
|
|
|
-
|
|
|
|
- // 强制重新渲染的key
|
|
|
|
- const forceUpdateKey = ref(0);
|
|
|
|
-
|
|
|
|
- // 表单引用
|
|
|
|
- const basicFormRef = ref();
|
|
|
|
- const attrFormRef = ref();
|
|
|
|
-
|
|
|
|
- // 表单数据
|
|
|
|
- const formData = reactive({
|
|
|
|
- // 基本信息
|
|
|
|
- id: '', // 商品ID,新增后获得
|
|
|
|
- cateId: '',
|
|
|
|
- storeName: '',
|
|
|
|
- keyword: '',
|
|
|
|
- itemBrand: '',
|
|
|
|
- storeInfo: '',
|
|
|
|
- // tempId: 1, // 运费模板ID,默认1
|
|
|
|
- itemNumber: '',
|
|
|
|
- price: '',
|
|
|
|
- otPrice: '',
|
|
|
|
- stock: '',
|
|
|
|
- stockThreshold: '',
|
|
|
|
- isShow: 0, // 状态 0-未上架 1-上架,新增默认下架
|
|
|
|
- itemSupplier: '',
|
|
|
|
- sort: 0,
|
|
|
|
- cost: '', // 成本价
|
|
|
|
- vipPrice: '', // 会员价格
|
|
|
|
-
|
|
|
|
- // 图片 - 数组格式,提交时转换为逗号分隔字符串
|
|
|
|
- image: [], // 商品主图
|
|
|
|
- sliderImage: [], // 轮播图(白底图)
|
|
|
|
- flatPattern: [], // 展示图(详情图)
|
|
|
|
- // 规格
|
|
|
|
- specType: 1, // 规格 0单 1多
|
|
|
|
- skus: [
|
|
|
|
- {
|
|
|
|
- id: 0,
|
|
|
|
- temp_id: 1,
|
|
|
|
- name: '',
|
|
|
|
- batchId: '',
|
|
|
|
- pid: 0,
|
|
|
|
- children: [],
|
|
|
|
- },
|
|
|
|
- ],
|
|
|
|
- sku_prices: [],
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- // 分类选项
|
|
|
|
- const categoryOptions = ref([]);
|
|
|
|
-
|
|
|
|
- // 表单验证规则
|
|
|
|
- const basicRules = {
|
|
|
|
- cateId: [{ required: true, message: '请选择商品分类', trigger: 'change' }],
|
|
|
|
- storeName: [{ required: true, message: '请输入商品名称', trigger: 'blur' }],
|
|
|
|
- // itemBrand: [{ required: true, message: '请输入商品品牌', trigger: 'blur' }],
|
|
|
|
- // tempId: [{ required: true, message: '请选择运费模板', trigger: 'change' }],
|
|
|
|
- // itemNumber: [{ required: true, message: '请输入商品货号', trigger: 'blur' }],
|
|
|
|
- // price: [{ required: true, message: '请输入商品售价', trigger: 'blur' }],
|
|
|
|
- // stock: [{ required: true, message: '请输入商品库存', trigger: 'blur' }],
|
|
|
|
- isShow: [{ required: true, message: '请选择商品状态', trigger: 'change' }],
|
|
|
|
- itemSupplier: [{ required: true, message: '请输入商品供应商', trigger: 'blur' }],
|
|
|
|
- image: [{ required: true, message: '请上传商品主图', trigger: 'change' }],
|
|
|
|
- sliderImage: [{ required: true, message: '请上传轮播图', trigger: 'change' }],
|
|
|
|
- flatPattern: [{ required: true, message: '请上传商品详情图', trigger: 'change' }],
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const attrRules = {
|
|
|
|
- spec_price: [{ required: true, message: '请输入价格', trigger: 'blur' }],
|
|
|
|
- spec_stock: [{ required: true, message: '请输入库存', trigger: 'blur' }],
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // 获取步骤状态
|
|
|
|
- const getStepStatus = (step) => {
|
|
|
|
- if (step < currentStep.value) return 'finish';
|
|
|
|
- if (step === currentStep.value) return 'process';
|
|
|
|
- return 'wait';
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // 多规格相关变量和函数
|
|
|
|
- const countId = ref(2);
|
|
|
|
- const childrenModal = [];
|
|
|
|
- const isResetSku = ref(0);
|
|
|
|
-
|
|
|
|
- // 批量操作相关变量
|
|
|
|
- const allEditObj = ref({
|
|
|
|
- price: null,
|
|
|
|
- otPrice: null,
|
|
|
|
- stock: null,
|
|
|
|
- stockThreshold: null,
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- // 添加主规格
|
|
|
|
- const addMainSku = () => {
|
|
|
|
- formData.skus.push({
|
|
|
|
|
|
+import { ref, reactive, onMounted, computed } from 'vue';
|
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
|
+import { CircleCheck, CircleCloseFilled, Edit, Loading } from '@element-plus/icons-vue';
|
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
|
+import { useFormConfig } from '@/hooks/useFormConfig';
|
|
|
|
+import { api } from '../goods.service';
|
|
|
|
+import { NIL } from 'uuid';
|
|
|
|
+
|
|
|
|
+const { t } = useI18n();
|
|
|
|
+
|
|
|
|
+// 使用表单配置hooks
|
|
|
|
+const { formLabelWidth, formLayout } = useFormConfig();
|
|
|
|
+
|
|
|
|
+const props = defineProps({
|
|
|
|
+ modal: {
|
|
|
|
+ type: Object,
|
|
|
|
+ default: () => ({}),
|
|
|
|
+ },
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const emit = defineEmits(['success', 'modalCallBack']);
|
|
|
|
+
|
|
|
|
+// 当前步骤
|
|
|
|
+const currentStep = ref(0);
|
|
|
|
+
|
|
|
|
+// 是否为编辑模式
|
|
|
|
+const isEdit = computed(() => props.modal?.params?.type === 'edit');
|
|
|
|
+
|
|
|
|
+// 强制重新渲染的key
|
|
|
|
+const forceUpdateKey = ref(0);
|
|
|
|
+
|
|
|
|
+// 表单引用
|
|
|
|
+const basicFormRef = ref();
|
|
|
|
+const attrFormRef = ref();
|
|
|
|
+
|
|
|
|
+// 表单数据
|
|
|
|
+const formData = reactive({
|
|
|
|
+ // 基本信息
|
|
|
|
+ id: '', // 商品ID,新增后获得
|
|
|
|
+ cateId: '',
|
|
|
|
+ storeName: '',
|
|
|
|
+ keyword: '',
|
|
|
|
+ itemBrand: '',
|
|
|
|
+ storeInfo: '',
|
|
|
|
+ // tempId: 1, // 运费模板ID,默认1
|
|
|
|
+ itemNumber: '',
|
|
|
|
+ price: '',
|
|
|
|
+ otPrice: '',
|
|
|
|
+ stock: '',
|
|
|
|
+ stockThreshold: '',
|
|
|
|
+ isShow: 0, // 状态 0-未上架 1-上架,新增默认下架
|
|
|
|
+ itemSupplier: '',
|
|
|
|
+ sort: 0,
|
|
|
|
+ cost: '', // 成本价
|
|
|
|
+ vipPrice: '', // 会员价格
|
|
|
|
+
|
|
|
|
+ // 图片 - 数组格式,提交时转换为逗号分隔字符串
|
|
|
|
+ image: [], // 商品主图
|
|
|
|
+ sliderImage: [], // 轮播图(白底图)
|
|
|
|
+ flatPattern: [], // 展示图(详情图)
|
|
|
|
+ // 规格
|
|
|
|
+ specType: 1, // 规格 0单 1多
|
|
|
|
+ skus: [
|
|
|
|
+ {
|
|
id: 0,
|
|
id: 0,
|
|
- temp_id: countId.value++,
|
|
|
|
|
|
+ temp_id: 1,
|
|
name: '',
|
|
name: '',
|
|
batchId: '',
|
|
batchId: '',
|
|
pid: 0,
|
|
pid: 0,
|
|
children: [],
|
|
children: [],
|
|
- });
|
|
|
|
- buildSkuPriceTable();
|
|
|
|
- };
|
|
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ sku_prices: [],
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// 分类选项
|
|
|
|
+const categoryOptions = ref([]);
|
|
|
|
+
|
|
|
|
+// 表单验证规则
|
|
|
|
+const basicRules = computed(() => ({
|
|
|
|
+ cateId: [{ required: true, message: t('form.categoryRequired'), trigger: 'change' }],
|
|
|
|
+ storeName: [{ required: true, message: t('form.goodsNameRequired'), trigger: 'blur' }],
|
|
|
|
+ // itemBrand: [{ required: true, message: t('form.goodsBrandRequired'), trigger: 'blur' }],
|
|
|
|
+ // tempId: [{ required: true, message: t('form.shippingTemplateRequired'), trigger: 'change' }],
|
|
|
|
+ // itemNumber: [{ required: true, message: t('form.goodsNumberRequired'), trigger: 'blur' }],
|
|
|
|
+ // price: [{ required: true, message: t('form.salePriceRequired'), trigger: 'blur' }],
|
|
|
|
+ // stock: [{ required: true, message: t('form.stockRequired'), trigger: 'blur' }],
|
|
|
|
+ isShow: [{ required: true, message: t('form.goodsStatusRequired'), trigger: 'change' }],
|
|
|
|
+ itemSupplier: [{ required: true, message: t('form.goodsSupplierRequired'), trigger: 'blur' }],
|
|
|
|
+ image: [{ required: true, message: t('form.mainImageRequired'), trigger: 'change' }],
|
|
|
|
+ sliderImage: [{ required: true, message: t('form.carouselImagesRequired'), trigger: 'change' }],
|
|
|
|
+ flatPattern: [{ required: true, message: t('form.detailImagesRequired'), trigger: 'change' }],
|
|
|
|
+}));
|
|
|
|
+
|
|
|
|
+const attrRules = {
|
|
|
|
+ spec_price: [{ required: true, message: '请输入价格', trigger: 'blur' }],
|
|
|
|
+ spec_stock: [{ required: true, message: '请输入库存', trigger: 'blur' }],
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 获取步骤状态
|
|
|
|
+const getStepStatus = (step) => {
|
|
|
|
+ if (step < currentStep.value) return 'finish';
|
|
|
|
+ if (step === currentStep.value) return 'process';
|
|
|
|
+ return 'wait';
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 多规格相关变量和函数
|
|
|
|
+const countId = ref(2);
|
|
|
|
+const childrenModal = [];
|
|
|
|
+const isResetSku = ref(0);
|
|
|
|
+
|
|
|
|
+// 批量操作相关变量
|
|
|
|
+const allEditObj = ref({
|
|
|
|
+ price: null,
|
|
|
|
+ otPrice: null,
|
|
|
|
+ stock: null,
|
|
|
|
+ stockThreshold: null,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// 添加主规格
|
|
|
|
+const addMainSku = () => {
|
|
|
|
+ formData.skus.push({
|
|
|
|
+ id: 0,
|
|
|
|
+ temp_id: countId.value++,
|
|
|
|
+ name: '',
|
|
|
|
+ batchId: '',
|
|
|
|
+ pid: 0,
|
|
|
|
+ children: [],
|
|
|
|
+ });
|
|
|
|
+ buildSkuPriceTable();
|
|
|
|
+};
|
|
|
|
|
|
- // 删除主规格
|
|
|
|
- const deleteMainSku = (k) => {
|
|
|
|
- let data = formData.skus[k];
|
|
|
|
|
|
+// 删除主规格
|
|
|
|
+const deleteMainSku = (k) => {
|
|
|
|
+ let data = formData.skus[k];
|
|
|
|
|
|
- // 删除主规格
|
|
|
|
- formData.skus.splice(k, 1);
|
|
|
|
|
|
+ // 删除主规格
|
|
|
|
+ formData.skus.splice(k, 1);
|
|
|
|
|
|
- // 如果当前删除的主规格存在子规格,则清空 skuPrice,不存在子规格则不清空
|
|
|
|
- if (data.children.length > 0) {
|
|
|
|
- formData.sku_prices = []; // 规格大变化,清空skuPrice
|
|
|
|
- isResetSku.value = 1; // 重置规格
|
|
|
|
|
|
+ // 如果当前删除的主规格存在子规格,则清空 skuPrice,不存在子规格则不清空
|
|
|
|
+ if (data.children.length > 0) {
|
|
|
|
+ formData.sku_prices = []; // 规格大变化,清空skuPrice
|
|
|
|
+ isResetSku.value = 1; // 重置规格
|
|
|
|
+ }
|
|
|
|
+ buildSkuPriceTable();
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 添加子规格
|
|
|
|
+const addChildrenSku = (k) => {
|
|
|
|
+ let isExist = false;
|
|
|
|
+ formData.skus[k].children.forEach((e) => {
|
|
|
|
+ if (e.name == childrenModal[k] && e.name != '') {
|
|
|
|
+ isExist = true;
|
|
}
|
|
}
|
|
- buildSkuPriceTable();
|
|
|
|
- };
|
|
|
|
|
|
+ });
|
|
|
|
+ if (isExist) {
|
|
|
|
+ ElMessage.warning(t('modules.goods.specificationValueExists'));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
|
|
- // 添加子规格
|
|
|
|
- const addChildrenSku = (k) => {
|
|
|
|
- let isExist = false;
|
|
|
|
- formData.skus[k].children.forEach((e) => {
|
|
|
|
- if (e.name == childrenModal[k] && e.name != '') {
|
|
|
|
- isExist = true;
|
|
|
|
|
|
+ // 修复:确保countId是全局最大的temp_id + 1
|
|
|
|
+ let maxTempId = 0;
|
|
|
|
+ formData.skus.forEach((sku) => {
|
|
|
|
+ sku.children.forEach((child) => {
|
|
|
|
+ if (child.temp_id > maxTempId) {
|
|
|
|
+ maxTempId = child.temp_id;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
- if (isExist) {
|
|
|
|
- ElMessage.warning('子规格已存在');
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
|
|
+ });
|
|
|
|
+ const newTempId = maxTempId + 1;
|
|
|
|
+
|
|
|
|
+ formData.skus[k].children.push({
|
|
|
|
+ id: 0,
|
|
|
|
+ temp_id: newTempId,
|
|
|
|
+ name: childrenModal[k] || '',
|
|
|
|
+ pid: formData.skus[k].id,
|
|
|
|
+ imageList: [], // 添加图片列表属性
|
|
|
|
+ });
|
|
|
|
+ childrenModal[k] = '';
|
|
|
|
|
|
- // 修复:确保countId是全局最大的temp_id + 1
|
|
|
|
- let maxTempId = 0;
|
|
|
|
- formData.skus.forEach((sku) => {
|
|
|
|
- sku.children.forEach((child) => {
|
|
|
|
- if (child.temp_id > maxTempId) {
|
|
|
|
- maxTempId = child.temp_id;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
- const newTempId = maxTempId + 1;
|
|
|
|
|
|
+ // 更新countId为新的最大值
|
|
|
|
+ countId.value = newTempId + 1;
|
|
|
|
|
|
- formData.skus[k].children.push({
|
|
|
|
- id: 0,
|
|
|
|
- temp_id: newTempId,
|
|
|
|
- name: childrenModal[k] || '',
|
|
|
|
- pid: formData.skus[k].id,
|
|
|
|
- imageList: [], // 添加图片列表属性
|
|
|
|
|
|
+ // 如果是添加的第一个子规格,清空 skuPrice
|
|
|
|
+ if (formData.skus[k].children.length == 1) {
|
|
|
|
+ formData.sku_prices = []; // 规格大变化,清空skuPrice
|
|
|
|
+ isResetSku.value = 1; // 重置规格
|
|
|
|
+ }
|
|
|
|
+ buildSkuPriceTable();
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 删除子规格
|
|
|
|
+const deleteChildrenSku = (k, i) => {
|
|
|
|
+ let data = formData.skus[k].children[i];
|
|
|
|
+ formData.skus[k].children.splice(i, 1);
|
|
|
|
+
|
|
|
|
+ // 查询 sku_prices 中包含被删除的子规格的项,然后移除
|
|
|
|
+ let deleteArr = [];
|
|
|
|
+ formData.sku_prices.forEach((item, index) => {
|
|
|
|
+ item.goods_sku_text.forEach((e, idx) => {
|
|
|
|
+ if (e == data.name) {
|
|
|
|
+ deleteArr.push(index);
|
|
|
|
+ }
|
|
});
|
|
});
|
|
- childrenModal[k] = '';
|
|
|
|
-
|
|
|
|
- // 更新countId为新的最大值
|
|
|
|
- countId.value = newTempId + 1;
|
|
|
|
-
|
|
|
|
- // 如果是添加的第一个子规格,清空 skuPrice
|
|
|
|
- if (formData.skus[k].children.length == 1) {
|
|
|
|
- formData.sku_prices = []; // 规格大变化,清空skuPrice
|
|
|
|
- isResetSku.value = 1; // 重置规格
|
|
|
|
- }
|
|
|
|
- buildSkuPriceTable();
|
|
|
|
- };
|
|
|
|
|
|
+ });
|
|
|
|
+ deleteArr.sort(function (a, b) {
|
|
|
|
+ return b - a;
|
|
|
|
+ });
|
|
|
|
+ // 移除有相关子规格的项
|
|
|
|
+ deleteArr.forEach((idx, e) => {
|
|
|
|
+ formData.sku_prices.splice(idx, 1);
|
|
|
|
+ });
|
|
|
|
|
|
- // 删除子规格
|
|
|
|
- const deleteChildrenSku = (k, i) => {
|
|
|
|
- let data = formData.skus[k].children[i];
|
|
|
|
- formData.skus[k].children.splice(i, 1);
|
|
|
|
-
|
|
|
|
- // 查询 sku_prices 中包含被删除的子规格的项,然后移除
|
|
|
|
- let deleteArr = [];
|
|
|
|
- formData.sku_prices.forEach((item, index) => {
|
|
|
|
- item.goods_sku_text.forEach((e, idx) => {
|
|
|
|
- if (e == data.name) {
|
|
|
|
- deleteArr.push(index);
|
|
|
|
- }
|
|
|
|
|
|
+ // 当前规格项,所有子规格都被删除,清空 sku_prices
|
|
|
|
+ if (formData.skus[k].children.length <= 0) {
|
|
|
|
+ formData.sku_prices = []; // 规格大变化,清空skuPrice
|
|
|
|
+ isResetSku.value = 1; // 重置规格
|
|
|
|
+ }
|
|
|
|
+ buildSkuPriceTable();
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 组成新的规格
|
|
|
|
+const buildSkuPriceTable = () => {
|
|
|
|
+ let arr = [];
|
|
|
|
+ // 遍历sku子规格生成新数组,然后执行递归笛卡尔积
|
|
|
|
+ formData.skus.forEach((s1, k1) => {
|
|
|
|
+ let children = s1.children;
|
|
|
|
+ let childrenIdArray = [];
|
|
|
|
+ if (children.length > 0) {
|
|
|
|
+ children.forEach((s2, k2) => {
|
|
|
|
+ childrenIdArray.push(s2.temp_id);
|
|
});
|
|
});
|
|
- });
|
|
|
|
- deleteArr.sort(function (a, b) {
|
|
|
|
- return b - a;
|
|
|
|
- });
|
|
|
|
- // 移除有相关子规格的项
|
|
|
|
- deleteArr.forEach((idx, e) => {
|
|
|
|
- formData.sku_prices.splice(idx, 1);
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- // 当前规格项,所有子规格都被删除,清空 sku_prices
|
|
|
|
- if (formData.skus[k].children.length <= 0) {
|
|
|
|
- formData.sku_prices = []; // 规格大变化,清空skuPrice
|
|
|
|
- isResetSku.value = 1; // 重置规格
|
|
|
|
|
|
+ // 如果 children 子规格数量为 0,则不渲染当前规格
|
|
|
|
+ arr.push(childrenIdArray);
|
|
}
|
|
}
|
|
- buildSkuPriceTable();
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // 组成新的规格
|
|
|
|
- const buildSkuPriceTable = () => {
|
|
|
|
- let arr = [];
|
|
|
|
- // 遍历sku子规格生成新数组,然后执行递归笛卡尔积
|
|
|
|
- formData.skus.forEach((s1, k1) => {
|
|
|
|
- let children = s1.children;
|
|
|
|
- let childrenIdArray = [];
|
|
|
|
- if (children.length > 0) {
|
|
|
|
- children.forEach((s2, k2) => {
|
|
|
|
- childrenIdArray.push(s2.temp_id);
|
|
|
|
- });
|
|
|
|
- // 如果 children 子规格数量为 0,则不渲染当前规格
|
|
|
|
- arr.push(childrenIdArray);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- recursionSku(arr, 0, []);
|
|
|
|
- };
|
|
|
|
|
|
+ });
|
|
|
|
|
|
- // 递归找笛卡尔规格集合
|
|
|
|
- const recursionSku = (arr, k, temp) => {
|
|
|
|
- if (k == arr.length && k != 0) {
|
|
|
|
- let tempDetail = [];
|
|
|
|
- let tempDetailIds = [];
|
|
|
|
-
|
|
|
|
- // 修复:每个temp_id只应该匹配一次,找到就跳出
|
|
|
|
- temp.forEach((item) => {
|
|
|
|
- let found = false;
|
|
|
|
- for (let sku of formData.skus) {
|
|
|
|
- if (found) break;
|
|
|
|
- for (let child of sku.children) {
|
|
|
|
- if (item == child.temp_id) {
|
|
|
|
- tempDetail.push(child.name);
|
|
|
|
- tempDetailIds.push(child.temp_id);
|
|
|
|
- found = true;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
|
|
+ recursionSku(arr, 0, []);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 递归找笛卡尔规格集合
|
|
|
|
+const recursionSku = (arr, k, temp) => {
|
|
|
|
+ if (k == arr.length && k != 0) {
|
|
|
|
+ let tempDetail = [];
|
|
|
|
+ let tempDetailIds = [];
|
|
|
|
+
|
|
|
|
+ // 修复:每个temp_id只应该匹配一次,找到就跳出
|
|
|
|
+ temp.forEach((item) => {
|
|
|
|
+ let found = false;
|
|
|
|
+ for (let sku of formData.skus) {
|
|
|
|
+ if (found) break;
|
|
|
|
+ for (let child of sku.children) {
|
|
|
|
+ if (item == child.temp_id) {
|
|
|
|
+ tempDetail.push(child.name);
|
|
|
|
+ tempDetailIds.push(child.temp_id);
|
|
|
|
+ found = true;
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- });
|
|
|
|
-
|
|
|
|
- let flag = false; // 默认添加新的
|
|
|
|
- for (let i = 0; i < formData.sku_prices.length; i++) {
|
|
|
|
- if (formData.sku_prices[i].goods_sku_temp_ids.join(',') == tempDetailIds.join(',')) {
|
|
|
|
- flag = i;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
|
|
+ });
|
|
|
|
|
|
- if (flag === false) {
|
|
|
|
- formData.sku_prices.push({
|
|
|
|
- id: 0,
|
|
|
|
- temp_id: formData.sku_prices.length + 1,
|
|
|
|
- goods_sku_ids: '',
|
|
|
|
- goods_id: 0,
|
|
|
|
- weigh: 0,
|
|
|
|
- image: '',
|
|
|
|
- imageList: [],
|
|
|
|
- stock: 0,
|
|
|
|
- stockThreshold: 0,
|
|
|
|
- price: 0,
|
|
|
|
- otPrice: 0,
|
|
|
|
- skuCode: '',
|
|
|
|
- goods_sku_text: tempDetail,
|
|
|
|
- goods_sku_temp_ids: tempDetailIds,
|
|
|
|
- });
|
|
|
|
- } else {
|
|
|
|
- formData.sku_prices[flag].goods_sku_text = tempDetail;
|
|
|
|
- formData.sku_prices[flag].goods_sku_temp_ids = tempDetailIds;
|
|
|
|
|
|
+ let flag = false; // 默认添加新的
|
|
|
|
+ for (let i = 0; i < formData.sku_prices.length; i++) {
|
|
|
|
+ if (formData.sku_prices[i].goods_sku_temp_ids.join(',') == tempDetailIds.join(',')) {
|
|
|
|
+ flag = i;
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
- return;
|
|
|
|
}
|
|
}
|
|
- if (arr.length) {
|
|
|
|
- for (let i = 0; i < arr[k].length; i++) {
|
|
|
|
- temp[k] = arr[k][i];
|
|
|
|
- recursionSku(arr, k + 1, temp);
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ if (flag === false) {
|
|
|
|
+ formData.sku_prices.push({
|
|
|
|
+ id: 0,
|
|
|
|
+ temp_id: formData.sku_prices.length + 1,
|
|
|
|
+ goods_sku_ids: '',
|
|
|
|
+ goods_id: 0,
|
|
|
|
+ weigh: 0,
|
|
|
|
+ image: '',
|
|
|
|
+ imageList: [],
|
|
|
|
+ stock: 0,
|
|
|
|
+ stockThreshold: 0,
|
|
|
|
+ price: 0,
|
|
|
|
+ otPrice: 0,
|
|
|
|
+ skuCode: '',
|
|
|
|
+ goods_sku_text: tempDetail,
|
|
|
|
+ goods_sku_temp_ids: tempDetailIds,
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ formData.sku_prices[flag].goods_sku_text = tempDetail;
|
|
|
|
+ formData.sku_prices[flag].goods_sku_temp_ids = tempDetailIds;
|
|
}
|
|
}
|
|
- };
|
|
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (arr.length) {
|
|
|
|
+ for (let i = 0; i < arr[k].length; i++) {
|
|
|
|
+ temp[k] = arr[k][i];
|
|
|
|
+ recursionSku(arr, k + 1, temp);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 批量操作
|
|
|
|
+const batchEdit = () => {
|
|
|
|
+ const batchIds = formData.skus.map((item) => item.batchId).filter((item) => Boolean(item));
|
|
|
|
+ formData.sku_prices.forEach((item) => {
|
|
|
|
+ if (
|
|
|
|
+ batchIds.length ? batchIds.every((citem) => item.goods_sku_temp_ids.includes(citem)) : true
|
|
|
|
+ ) {
|
|
|
|
+ const { price, otPrice, stock, stockThreshold } = allEditObj.value;
|
|
|
|
+ if (price) item.price = price;
|
|
|
|
+ if (otPrice) item.otPrice = otPrice;
|
|
|
|
+ if (stock) item.stock = stock;
|
|
|
|
+ if (stockThreshold) item.stockThreshold = stockThreshold;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- // 批量操作
|
|
|
|
- const batchEdit = () => {
|
|
|
|
- const batchIds = formData.skus.map((item) => item.batchId).filter((item) => Boolean(item));
|
|
|
|
- formData.sku_prices.forEach((item) => {
|
|
|
|
- if (
|
|
|
|
- batchIds.length ? batchIds.every((citem) => item.goods_sku_temp_ids.includes(citem)) : true
|
|
|
|
- ) {
|
|
|
|
- const { price, otPrice, stock, stockThreshold } = allEditObj.value;
|
|
|
|
- if (price) item.price = price;
|
|
|
|
- if (otPrice) item.otPrice = otPrice;
|
|
|
|
- if (stock) item.stock = stock;
|
|
|
|
- if (stockThreshold) item.stockThreshold = stockThreshold;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
+ // 清空输入框
|
|
|
|
+ allEditObj.value = {
|
|
|
|
+ price: null,
|
|
|
|
+ otPrice: null,
|
|
|
|
+ stock: null,
|
|
|
|
+ stockThreshold: null,
|
|
|
|
+ };
|
|
|
|
|
|
- // 清空输入框
|
|
|
|
- allEditObj.value = {
|
|
|
|
- price: null,
|
|
|
|
- otPrice: null,
|
|
|
|
- stock: null,
|
|
|
|
- stockThreshold: null,
|
|
|
|
- };
|
|
|
|
|
|
+ // 清空选择的规格
|
|
|
|
+ formData.skus.forEach((item) => {
|
|
|
|
+ item.batchId = '';
|
|
|
|
+ });
|
|
|
|
|
|
- // 清空选择的规格
|
|
|
|
- formData.skus.forEach((item) => {
|
|
|
|
- item.batchId = '';
|
|
|
|
- });
|
|
|
|
|
|
+ ElMessage.success(t('modules.goods.batchSettingsSuccess'));
|
|
|
|
+};
|
|
|
|
|
|
- ElMessage.success('批量设置成功');
|
|
|
|
- };
|
|
|
|
|
|
+// 删除规格组合
|
|
|
|
+const deleteSkuPrice = (index) => {
|
|
|
|
+ formData.sku_prices.splice(index, 1);
|
|
|
|
+ ElMessage.success(t('message.deleteSuccess'));
|
|
|
|
+};
|
|
|
|
|
|
- // 删除规格组合
|
|
|
|
- const deleteSkuPrice = (index) => {
|
|
|
|
- formData.sku_prices.splice(index, 1);
|
|
|
|
- ElMessage.success('删除成功');
|
|
|
|
- };
|
|
|
|
|
|
+// SKU校验
|
|
|
|
+const validateSku = () => {
|
|
|
|
+ if (formData.sku_prices.length === 0) {
|
|
|
|
+ ElMessage.error(t('modules.goods.pleaseAddSpecification'));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
|
|
- // SKU校验
|
|
|
|
- const validateSku = () => {
|
|
|
|
- if (formData.sku_prices.length === 0) {
|
|
|
|
- ElMessage.error('请先添加商品规格');
|
|
|
|
|
|
+ for (let i = 0; i < formData.sku_prices.length; i++) {
|
|
|
|
+ const item = formData.sku_prices[i];
|
|
|
|
+ if (!item.price || item.price <= 0) {
|
|
|
|
+ ElMessage.error(t('modules.goods.specificationSalePriceError', { index: i + 1 }));
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
-
|
|
|
|
- for (let i = 0; i < formData.sku_prices.length; i++) {
|
|
|
|
- const item = formData.sku_prices[i];
|
|
|
|
- if (!item.price || item.price <= 0) {
|
|
|
|
- ElMessage.error(`第${i + 1}个规格的销售价格不能为空且必须大于0`);
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- if (!item.otPrice || item.otPrice <= 0) {
|
|
|
|
- ElMessage.error(`第${i + 1}个规格的市场价不能为空且必须大于0`);
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- if (item.stock === null || item.stock === undefined || item.stock < 0) {
|
|
|
|
- ElMessage.error(`第${i + 1}个规格的商品库存不能为空且不能小于0`);
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
|
|
+ if (!item.otPrice || item.otPrice <= 0) {
|
|
|
|
+ ElMessage.error(t('modules.goods.specificationMarketPriceError', { index: i + 1 }));
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
- return true;
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // 提交表单
|
|
|
|
- const submitLoading = ref(false);
|
|
|
|
- const submitError = ref('');
|
|
|
|
- const submitSuccess = ref(false);
|
|
|
|
-
|
|
|
|
- // 数据转换工具方法
|
|
|
|
- const dataConverter = {
|
|
|
|
- // 图片数组转换为逗号分隔字符串
|
|
|
|
- imagesToString: (imageArray) => {
|
|
|
|
- return Array.isArray(imageArray) ? imageArray.join(',') : '';
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- // 图片字符串转换为数组
|
|
|
|
- stringToImages: (imageString) => {
|
|
|
|
- if (!imageString) return [];
|
|
|
|
- return imageString.split(',').filter((img) => img.trim());
|
|
|
|
- },
|
|
|
|
|
|
+ if (item.stock === null || item.stock === undefined || item.stock < 0) {
|
|
|
|
+ ElMessage.error(t('modules.goods.specificationStockError', { index: i + 1 }));
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 提交表单
|
|
|
|
+const submitLoading = ref(false);
|
|
|
|
+const submitError = ref('');
|
|
|
|
+const submitSuccess = ref(false);
|
|
|
|
+
|
|
|
|
+// 数据转换工具方法
|
|
|
|
+const dataConverter = {
|
|
|
|
+ // 图片数组转换为逗号分隔字符串
|
|
|
|
+ imagesToString: (imageArray) => {
|
|
|
|
+ return Array.isArray(imageArray) ? imageArray.join(',') : '';
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 图片字符串转换为数组
|
|
|
|
+ stringToImages: (imageString) => {
|
|
|
|
+ if (!imageString) return [];
|
|
|
|
+ return imageString.split(',').filter((img) => img.trim());
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 将后端返回的 attr 和 attrValue 数据转换为前端需要的格式
|
|
|
|
+ convertBackendToFrontend: (attr, attrValue) => {
|
|
|
|
+ const skus = [];
|
|
|
|
+ const sku_prices = [];
|
|
|
|
+ let tempId = 1;
|
|
|
|
+
|
|
|
|
+ // 处理 attr 数据,构建 skus 结构
|
|
|
|
+ if (attr && attr.length > 0) {
|
|
|
|
+ attr.forEach((attrItem) => {
|
|
|
|
+ // 处理规格值图片数据
|
|
|
|
+ const attrImgMap = {};
|
|
|
|
+ if (attrItem.attrImgValues && Array.isArray(attrItem.attrImgValues)) {
|
|
|
|
+ attrItem.attrImgValues.forEach((imgItem) => {
|
|
|
|
+ attrImgMap[imgItem.name] = imgItem.img ? [imgItem.img] : [];
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
|
|
- // 将后端返回的 attr 和 attrValue 数据转换为前端需要的格式
|
|
|
|
- convertBackendToFrontend: (attr, attrValue) => {
|
|
|
|
- const skus = [];
|
|
|
|
- const sku_prices = [];
|
|
|
|
- let tempId = 1;
|
|
|
|
-
|
|
|
|
- // 处理 attr 数据,构建 skus 结构
|
|
|
|
- if (attr && attr.length > 0) {
|
|
|
|
- attr.forEach((attrItem) => {
|
|
|
|
- // 处理规格值图片数据
|
|
|
|
- const attrImgMap = {};
|
|
|
|
- if (attrItem.attrImgValues && Array.isArray(attrItem.attrImgValues)) {
|
|
|
|
- attrItem.attrImgValues.forEach((imgItem) => {
|
|
|
|
- attrImgMap[imgItem.name] = imgItem.img ? [imgItem.img] : [];
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
|
|
+ const children = attrItem.attrValues.split(',').map((value) => ({
|
|
|
|
+ id: 0, // 子规格值没有单独的ID,保持为0
|
|
|
|
+ temp_id: tempId++,
|
|
|
|
+ name: value.trim(),
|
|
|
|
+ pid: attrItem.id, // 使用父规格的ID
|
|
|
|
+ imageList: attrImgMap[value.trim()] || [], // 添加图片列表
|
|
|
|
+ }));
|
|
|
|
|
|
- const children = attrItem.attrValues.split(',').map((value) => ({
|
|
|
|
- id: 0, // 子规格值没有单独的ID,保持为0
|
|
|
|
- temp_id: tempId++,
|
|
|
|
- name: value.trim(),
|
|
|
|
- pid: attrItem.id, // 使用父规格的ID
|
|
|
|
- imageList: attrImgMap[value.trim()] || [], // 添加图片列表
|
|
|
|
- }));
|
|
|
|
-
|
|
|
|
- skus.push({
|
|
|
|
- id: attrItem.id || 0, // 保留原有的规格ID
|
|
|
|
- temp_id: tempId++,
|
|
|
|
- name: attrItem.attrName,
|
|
|
|
- batchId: '',
|
|
|
|
- pid: 0,
|
|
|
|
- children: children,
|
|
|
|
- });
|
|
|
|
|
|
+ skus.push({
|
|
|
|
+ id: attrItem.id || 0, // 保留原有的规格ID
|
|
|
|
+ temp_id: tempId++,
|
|
|
|
+ name: attrItem.attrName,
|
|
|
|
+ batchId: '',
|
|
|
|
+ pid: 0,
|
|
|
|
+ children: children,
|
|
});
|
|
});
|
|
- }
|
|
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
|
|
- // 处理 attrValue 数据,构建 sku_prices 结构
|
|
|
|
- if (attrValue && attrValue.length > 0) {
|
|
|
|
- // 按照attr的顺序对attrValue进行排序
|
|
|
|
- const sortedAttrValue = [...attrValue].sort((a, b) => {
|
|
|
|
- try {
|
|
|
|
- const aAttrObj = JSON.parse(a.attrValue || '{}');
|
|
|
|
- const bAttrObj = JSON.parse(b.attrValue || '{}');
|
|
|
|
-
|
|
|
|
- // 按照attr的顺序比较每个属性值
|
|
|
|
- for (let i = 0; i < attr.length; i++) {
|
|
|
|
- const attrName = attr[i].attrName;
|
|
|
|
- const aValue = aAttrObj[attrName];
|
|
|
|
- const bValue = bAttrObj[attrName];
|
|
|
|
-
|
|
|
|
- if (aValue && bValue) {
|
|
|
|
- // 找到该属性值在attrValues中的索引
|
|
|
|
- const attrValues = attr[i].attrValues.split(',').map((v) => v.trim());
|
|
|
|
- const aIndex = attrValues.indexOf(aValue);
|
|
|
|
- const bIndex = attrValues.indexOf(bValue);
|
|
|
|
-
|
|
|
|
- if (aIndex !== bIndex) {
|
|
|
|
- return aIndex - bIndex;
|
|
|
|
- }
|
|
|
|
|
|
+ // 处理 attrValue 数据,构建 sku_prices 结构
|
|
|
|
+ if (attrValue && attrValue.length > 0) {
|
|
|
|
+ // 按照attr的顺序对attrValue进行排序
|
|
|
|
+ const sortedAttrValue = [...attrValue].sort((a, b) => {
|
|
|
|
+ try {
|
|
|
|
+ const aAttrObj = JSON.parse(a.attrValue || '{}');
|
|
|
|
+ const bAttrObj = JSON.parse(b.attrValue || '{}');
|
|
|
|
+
|
|
|
|
+ // 按照attr的顺序比较每个属性值
|
|
|
|
+ for (let i = 0; i < attr.length; i++) {
|
|
|
|
+ const attrName = attr[i].attrName;
|
|
|
|
+ const aValue = aAttrObj[attrName];
|
|
|
|
+ const bValue = bAttrObj[attrName];
|
|
|
|
+
|
|
|
|
+ if (aValue && bValue) {
|
|
|
|
+ // 找到该属性值在attrValues中的索引
|
|
|
|
+ const attrValues = attr[i].attrValues.split(',').map((v) => v.trim());
|
|
|
|
+ const aIndex = attrValues.indexOf(aValue);
|
|
|
|
+ const bIndex = attrValues.indexOf(bValue);
|
|
|
|
+
|
|
|
|
+ if (aIndex !== bIndex) {
|
|
|
|
+ return aIndex - bIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return 0;
|
|
|
|
- } catch (e) {
|
|
|
|
- return 0;
|
|
|
|
}
|
|
}
|
|
- });
|
|
|
|
|
|
+ return 0;
|
|
|
|
+ } catch (e) {
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- sortedAttrValue.forEach((item) => {
|
|
|
|
- let attrValueObj = {};
|
|
|
|
- try {
|
|
|
|
- attrValueObj = JSON.parse(item.attrValue || '{}');
|
|
|
|
- } catch (e) {
|
|
|
|
- console.warn('解析 attrValue 失败:', item.attrValue, e);
|
|
|
|
- }
|
|
|
|
|
|
+ sortedAttrValue.forEach((item) => {
|
|
|
|
+ let attrValueObj = {};
|
|
|
|
+ try {
|
|
|
|
+ attrValueObj = JSON.parse(item.attrValue || '{}');
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.warn('解析 attrValue 失败:', item.attrValue, e);
|
|
|
|
+ }
|
|
|
|
|
|
- // 构建 goods_sku_text 和 goods_sku_temp_ids
|
|
|
|
- const goods_sku_text = [];
|
|
|
|
- const goods_sku_temp_ids = [];
|
|
|
|
-
|
|
|
|
- // 根据 skus 的顺序构建 goods_sku_text
|
|
|
|
- skus.forEach((sku) => {
|
|
|
|
- const value = attrValueObj[sku.name];
|
|
|
|
- if (value) {
|
|
|
|
- goods_sku_text.push(value);
|
|
|
|
- // 找到对应的 temp_id
|
|
|
|
- const child = sku.children.find((c) => c.name === value);
|
|
|
|
- if (child) {
|
|
|
|
- goods_sku_temp_ids.push(child.temp_id);
|
|
|
|
- }
|
|
|
|
|
|
+ // 构建 goods_sku_text 和 goods_sku_temp_ids
|
|
|
|
+ const goods_sku_text = [];
|
|
|
|
+ const goods_sku_temp_ids = [];
|
|
|
|
+
|
|
|
|
+ // 根据 skus 的顺序构建 goods_sku_text
|
|
|
|
+ skus.forEach((sku) => {
|
|
|
|
+ const value = attrValueObj[sku.name];
|
|
|
|
+ if (value) {
|
|
|
|
+ goods_sku_text.push(value);
|
|
|
|
+ // 找到对应的 temp_id
|
|
|
|
+ const child = sku.children.find((c) => c.name === value);
|
|
|
|
+ if (child) {
|
|
|
|
+ goods_sku_temp_ids.push(child.temp_id);
|
|
}
|
|
}
|
|
- });
|
|
|
|
-
|
|
|
|
- sku_prices.push({
|
|
|
|
- id: parseInt(item.id) || 0, // 保留原有的SKU价格ID
|
|
|
|
- temp_id: sku_prices.length + 1,
|
|
|
|
- goods_sku_ids: '',
|
|
|
|
- goods_id: parseInt(item.productId) || 0,
|
|
|
|
- weigh: 0,
|
|
|
|
- image: item.image || '',
|
|
|
|
- imageList: item.image ? [item.image] : [],
|
|
|
|
- stock: parseInt(item.stock) || 0,
|
|
|
|
- stockThreshold: parseInt(item.stockThreshold) || 0,
|
|
|
|
- price: parseFloat(item.price) || 0,
|
|
|
|
- otPrice: parseFloat(item.otPrice) || 0,
|
|
|
|
- skuCode: item.skuCode || item.barCode || item.suk || '',
|
|
|
|
- goods_sku_text: goods_sku_text,
|
|
|
|
- goods_sku_temp_ids: goods_sku_temp_ids,
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return { skus, sku_prices };
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- // 将前端数据转换为后端需要的格式
|
|
|
|
- convertFrontendToBackend: (skus, sku_prices) => {
|
|
|
|
- // 生成 attr 数据
|
|
|
|
- const attr = skus
|
|
|
|
- .filter((sku) => sku.name && sku.children.length > 0)
|
|
|
|
- .map((sku) => ({
|
|
|
|
- id: sku.id || 0, // 保留规格ID,新增时为0
|
|
|
|
- attrName: sku.name,
|
|
|
|
- attrValues: sku.children.map((child) => child.name).join(','),
|
|
|
|
- // 生成 attrImgValues 数据 - 为所有规格值生成结构,没有图片时img为空字符串
|
|
|
|
- attrImgValues: sku.children.map((child) => ({
|
|
|
|
- name: child.name,
|
|
|
|
- img: child.imageList && child.imageList.length > 0 ? child.imageList[0] : '', // 有图片取第一张,没有则为空字符串
|
|
|
|
- })),
|
|
|
|
- }));
|
|
|
|
-
|
|
|
|
- // 生成 attrValue 数据
|
|
|
|
- const attrValue = sku_prices.map((item) => {
|
|
|
|
- const attrObj = {};
|
|
|
|
- const attrValueObj = {};
|
|
|
|
-
|
|
|
|
- // 根据 goods_sku_text 和对应的规格名称构建属性对象
|
|
|
|
- item.goods_sku_text.forEach((value, index) => {
|
|
|
|
- const specName = skus[index]?.name;
|
|
|
|
- if (specName) {
|
|
|
|
- attrObj[specName] = value;
|
|
|
|
- attrValueObj[specName] = value;
|
|
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
- return {
|
|
|
|
- id: item.id || 0, // 保留SKU价格ID,新增时为0
|
|
|
|
- image: item.imageList && item.imageList.length > 0 ? item.imageList[0] : '',
|
|
|
|
- price: item.price || '0',
|
|
|
|
- otPrice: item.otPrice || '0',
|
|
|
|
- stock: item.stock || 0,
|
|
|
|
- skuCode: item.skuCode || '',
|
|
|
|
- stockThreshold: item.stockThreshold || 0,
|
|
|
|
- attrValue: JSON.stringify(attrValueObj),
|
|
|
|
- ...attrObj,
|
|
|
|
- productId: item.goods_id || 0,
|
|
|
|
- };
|
|
|
|
|
|
+ sku_prices.push({
|
|
|
|
+ id: parseInt(item.id) || 0, // 保留原有的SKU价格ID
|
|
|
|
+ temp_id: sku_prices.length + 1,
|
|
|
|
+ goods_sku_ids: '',
|
|
|
|
+ goods_id: parseInt(item.productId) || 0,
|
|
|
|
+ weigh: 0,
|
|
|
|
+ image: item.image || '',
|
|
|
|
+ imageList: item.image ? [item.image] : [],
|
|
|
|
+ stock: parseInt(item.stock) || 0,
|
|
|
|
+ stockThreshold: parseInt(item.stockThreshold) || 0,
|
|
|
|
+ price: parseFloat(item.price) || 0,
|
|
|
|
+ otPrice: parseFloat(item.otPrice) || 0,
|
|
|
|
+ skuCode: item.skuCode || item.barCode || item.suk || '',
|
|
|
|
+ goods_sku_text: goods_sku_text,
|
|
|
|
+ goods_sku_temp_ids: goods_sku_temp_ids,
|
|
|
|
+ });
|
|
});
|
|
});
|
|
-
|
|
|
|
- return { attr, attrValue };
|
|
|
|
- },
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // 兼容旧方法名
|
|
|
|
- const convertImagesToString = dataConverter.imagesToString;
|
|
|
|
- const convertStringToImages = dataConverter.stringToImages;
|
|
|
|
-
|
|
|
|
- // 下一步
|
|
|
|
- const nextStep = async () => {
|
|
|
|
- if (currentStep.value === 0) {
|
|
|
|
- // 验证基本信息
|
|
|
|
- const valid = await basicFormRef.value?.validate().catch(() => false);
|
|
|
|
- if (!valid) {
|
|
|
|
- ElMessage.error('请完善基本信息');
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- // 进入下一步(商品属性设置)
|
|
|
|
- currentStep.value++;
|
|
|
|
- } else if (currentStep.value === 1) {
|
|
|
|
- // 第二步:提交所有数据(基本信息 + 商品属性)
|
|
|
|
- await submitGoods();
|
|
|
|
}
|
|
}
|
|
- };
|
|
|
|
|
|
|
|
- // 继续操作(只用于新增模式)
|
|
|
|
- const continueAction = () => {
|
|
|
|
- // 新增模式:重置表单继续添加
|
|
|
|
- resetForm();
|
|
|
|
- currentStep.value = 0;
|
|
|
|
- submitSuccess.value = false;
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // 关闭弹窗
|
|
|
|
- const closeModal = () => {
|
|
|
|
- emit('modalCallBack', {
|
|
|
|
- event: 'confirm',
|
|
|
|
- });
|
|
|
|
- };
|
|
|
|
|
|
+ return { skus, sku_prices };
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 将前端数据转换为后端需要的格式
|
|
|
|
+ convertFrontendToBackend: (skus, sku_prices) => {
|
|
|
|
+ // 生成 attr 数据
|
|
|
|
+ const attr = skus
|
|
|
|
+ .filter((sku) => sku.name && sku.children.length > 0)
|
|
|
|
+ .map((sku) => ({
|
|
|
|
+ id: sku.id || 0, // 保留规格ID,新增时为0
|
|
|
|
+ attrName: sku.name,
|
|
|
|
+ attrValues: sku.children.map((child) => child.name).join(','),
|
|
|
|
+ // 生成 attrImgValues 数据 - 为所有规格值生成结构,没有图片时img为空字符串
|
|
|
|
+ attrImgValues: sku.children.map((child) => ({
|
|
|
|
+ name: child.name,
|
|
|
|
+ img: child.imageList && child.imageList.length > 0 ? child.imageList[0] : '', // 有图片取第一张,没有则为空字符串
|
|
|
|
+ })),
|
|
|
|
+ }));
|
|
|
|
+
|
|
|
|
+ // 生成 attrValue 数据
|
|
|
|
+ const attrValue = sku_prices.map((item) => {
|
|
|
|
+ const attrObj = {};
|
|
|
|
+ const attrValueObj = {};
|
|
|
|
+
|
|
|
|
+ // 根据 goods_sku_text 和对应的规格名称构建属性对象
|
|
|
|
+ item.goods_sku_text.forEach((value, index) => {
|
|
|
|
+ const specName = skus[index]?.name;
|
|
|
|
+ if (specName) {
|
|
|
|
+ attrObj[specName] = value;
|
|
|
|
+ attrValueObj[specName] = value;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
|
|
- // 重置表单
|
|
|
|
- const resetForm = () => {
|
|
|
|
- Object.assign(formData, {
|
|
|
|
- title: '',
|
|
|
|
- category_ids: [],
|
|
|
|
- brand: '',
|
|
|
|
- goods_no: '',
|
|
|
|
- supplier: '',
|
|
|
|
- images: [],
|
|
|
|
- white_bg_image: [],
|
|
|
|
- detail_images: [],
|
|
|
|
- has_spec: true,
|
|
|
|
- skus: [
|
|
|
|
- {
|
|
|
|
- id: 0,
|
|
|
|
- temp_id: 1,
|
|
|
|
- name: '',
|
|
|
|
- batchId: '',
|
|
|
|
- pid: 0,
|
|
|
|
- children: [],
|
|
|
|
- },
|
|
|
|
- ],
|
|
|
|
- sku_prices: [],
|
|
|
|
|
|
+ return {
|
|
|
|
+ id: item.id || 0, // 保留SKU价格ID,新增时为0
|
|
|
|
+ image: item.imageList && item.imageList.length > 0 ? item.imageList[0] : '',
|
|
|
|
+ price: item.price || '0',
|
|
|
|
+ otPrice: item.otPrice || '0',
|
|
|
|
+ stock: item.stock || 0,
|
|
|
|
+ skuCode: item.skuCode || '',
|
|
|
|
+ stockThreshold: item.stockThreshold || 0,
|
|
|
|
+ attrValue: JSON.stringify(attrValueObj),
|
|
|
|
+ ...attrObj,
|
|
|
|
+ productId: item.goods_id || 0,
|
|
|
|
+ };
|
|
});
|
|
});
|
|
- };
|
|
|
|
|
|
|
|
- // 上一步
|
|
|
|
- const prevStep = () => {
|
|
|
|
- if (currentStep.value > 0) {
|
|
|
|
- currentStep.value--;
|
|
|
|
|
|
+ return { attr, attrValue };
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 兼容旧方法名
|
|
|
|
+const convertImagesToString = dataConverter.imagesToString;
|
|
|
|
+const convertStringToImages = dataConverter.stringToImages;
|
|
|
|
+
|
|
|
|
+// 下一步
|
|
|
|
+const nextStep = async () => {
|
|
|
|
+ if (currentStep.value === 0) {
|
|
|
|
+ // 验证基本信息
|
|
|
|
+ const valid = await basicFormRef.value?.validate().catch(() => false);
|
|
|
|
+ if (!valid) {
|
|
|
|
+ ElMessage.error(t('modules.goods.pleaseCompleteBasicInfo'));
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
- };
|
|
|
|
|
|
+ // 进入下一步(商品属性设置)
|
|
|
|
+ currentStep.value++;
|
|
|
|
+ } else if (currentStep.value === 1) {
|
|
|
|
+ // 第二步:提交所有数据(基本信息 + 商品属性)
|
|
|
|
+ await submitGoods();
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 继续操作(只用于新增模式)
|
|
|
|
+const continueAction = () => {
|
|
|
|
+ // 新增模式:重置表单继续添加
|
|
|
|
+ resetForm();
|
|
|
|
+ currentStep.value = 0;
|
|
|
|
+ submitSuccess.value = false;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 关闭弹窗
|
|
|
|
+const closeModal = () => {
|
|
|
|
+ emit('modalCallBack', {
|
|
|
|
+ event: 'confirm',
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 重置表单
|
|
|
|
+const resetForm = () => {
|
|
|
|
+ Object.assign(formData, {
|
|
|
|
+ title: '',
|
|
|
|
+ category_ids: [],
|
|
|
|
+ brand: '',
|
|
|
|
+ goods_no: '',
|
|
|
|
+ supplier: '',
|
|
|
|
+ images: [],
|
|
|
|
+ white_bg_image: [],
|
|
|
|
+ detail_images: [],
|
|
|
|
+ has_spec: true,
|
|
|
|
+ skus: [
|
|
|
|
+ {
|
|
|
|
+ id: 0,
|
|
|
|
+ temp_id: 1,
|
|
|
|
+ name: '',
|
|
|
|
+ batchId: '',
|
|
|
|
+ pid: 0,
|
|
|
|
+ children: [],
|
|
|
|
+ },
|
|
|
|
+ ],
|
|
|
|
+ sku_prices: [],
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
|
|
- // 保存草稿
|
|
|
|
- const saveDraft = async () => {
|
|
|
|
- try {
|
|
|
|
- const data = { ...formData, status: 'draft' };
|
|
|
|
- // 调用保存接口
|
|
|
|
- await saveGoods(data);
|
|
|
|
- ElMessage.success('保存草稿成功');
|
|
|
|
- emit('modalCallBack', { event: 'confirm' });
|
|
|
|
- } catch (error) {
|
|
|
|
- ElMessage.error('保存失败:' + error.message);
|
|
|
|
|
|
+// 上一步
|
|
|
|
+const prevStep = () => {
|
|
|
|
+ if (currentStep.value > 0) {
|
|
|
|
+ currentStep.value--;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 保存草稿
|
|
|
|
+const saveDraft = async () => {
|
|
|
|
+ try {
|
|
|
|
+ const data = { ...formData, status: 'draft' };
|
|
|
|
+ // 调用保存接口
|
|
|
|
+ await saveGoods(data);
|
|
|
|
+ ElMessage.success('保存草稿成功');
|
|
|
|
+ emit('modalCallBack', { event: 'confirm' });
|
|
|
|
+ } catch (error) {
|
|
|
|
+ ElMessage.error('保存失败:' + error.message);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 统一提交商品(包含基本信息和属性)
|
|
|
|
+const submitGoods = async () => {
|
|
|
|
+ try {
|
|
|
|
+ submitLoading.value = true;
|
|
|
|
+ submitError.value = '';
|
|
|
|
+
|
|
|
|
+ // 验证基本信息表单
|
|
|
|
+ const basicValid = await basicFormRef.value?.validate().catch(() => false);
|
|
|
|
+ if (!basicValid) {
|
|
|
|
+ ElMessage.error(t('modules.goods.pleaseCompleteBasicInfo'));
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
- };
|
|
|
|
|
|
|
|
- // 统一提交商品(包含基本信息和属性)
|
|
|
|
- const submitGoods = async () => {
|
|
|
|
- try {
|
|
|
|
- submitLoading.value = true;
|
|
|
|
- submitError.value = '';
|
|
|
|
-
|
|
|
|
- // 验证基本信息表单
|
|
|
|
- const basicValid = await basicFormRef.value?.validate().catch(() => false);
|
|
|
|
- if (!basicValid) {
|
|
|
|
- ElMessage.error('请完善商品基本信息');
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 验证商品属性(如果有SKU)
|
|
|
|
- if (formData.sku_prices && formData.sku_prices.length > 0) {
|
|
|
|
- // 验证SKU数据
|
|
|
|
- for (let i = 0; i < formData.sku_prices.length; i++) {
|
|
|
|
- const item = formData.sku_prices[i];
|
|
|
|
- if (!item.price || item.price <= 0) {
|
|
|
|
- ElMessage.error(`第${i + 1}个规格的销售价格不能为空且必须大于0`);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- if (!item.otPrice || item.otPrice <= 0) {
|
|
|
|
- ElMessage.error(`第${i + 1}个规格的市场价不能为空且必须大于0`);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- if (item.stock === null || item.stock === undefined || item.stock < 0) {
|
|
|
|
- ElMessage.error(`第${i + 1}个规格的商品库存不能为空且不能小于0`);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ // 验证商品属性(如果有SKU)
|
|
|
|
+ if (formData.sku_prices && formData.sku_prices.length > 0) {
|
|
|
|
+ // 验证SKU数据
|
|
|
|
+ for (let i = 0; i < formData.sku_prices.length; i++) {
|
|
|
|
+ const item = formData.sku_prices[i];
|
|
|
|
+ if (!item.price || item.price <= 0) {
|
|
|
|
+ ElMessage.error(t('modules.goods.specificationSalePriceError', { index: i + 1 }));
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (!item.otPrice || item.otPrice <= 0) {
|
|
|
|
+ ElMessage.error(t('modules.goods.specificationMarketPriceError', { index: i + 1 }));
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if (item.stock === null || item.stock === undefined || item.stock < 0) {
|
|
|
|
+ ElMessage.error(t('modules.goods.specificationStockError', { index: i + 1 }));
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- // 调用统一保存接口
|
|
|
|
- const data = { ...formData };
|
|
|
|
- await saveGoods(data);
|
|
|
|
- // 测试代码
|
|
|
|
-
|
|
|
|
- // 提交成功,跳转到成功页面
|
|
|
|
- submitSuccess.value = true;
|
|
|
|
- currentStep.value = 2;
|
|
|
|
- ElMessage.success('商品保存成功');
|
|
|
|
- } catch (error) {
|
|
|
|
- submitError.value = error.message || '提交失败';
|
|
|
|
- ElMessage.error('提交失败:' + error.message);
|
|
|
|
- } finally {
|
|
|
|
- submitLoading.value = false;
|
|
|
|
}
|
|
}
|
|
- };
|
|
|
|
|
|
|
|
- // 生成后端需要的数据格式(使用统一转换工具)
|
|
|
|
- const generateAttrValueData = () => {
|
|
|
|
- const { attrValue } = dataConverter.convertFrontendToBackend(
|
|
|
|
- formData.skus,
|
|
|
|
- formData.sku_prices,
|
|
|
|
- );
|
|
|
|
- return attrValue;
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const generateAttrData = () => {
|
|
|
|
- const { attr } = dataConverter.convertFrontendToBackend(formData.skus, formData.sku_prices);
|
|
|
|
- return attr;
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // 统一提交接口 - 保存商品信息和属性
|
|
|
|
- const saveGoods = async (data) => {
|
|
|
|
- try {
|
|
|
|
- // 准备基本商品数据
|
|
|
|
- const submitData = {
|
|
|
|
- ...data,
|
|
|
|
- // 图片转换为逗号分隔字符串
|
|
|
|
- image: convertImagesToString(data.image),
|
|
|
|
- sliderImage: convertImagesToString(data.sliderImage),
|
|
|
|
- flatPattern: convertImagesToString(data.flatPattern),
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // 添加商品属性数据
|
|
|
|
- if (formData.sku_prices && formData.sku_prices.length > 0) {
|
|
|
|
- submitData.attrValue = generateAttrValueData();
|
|
|
|
- submitData.attr = generateAttrData();
|
|
|
|
- }
|
|
|
|
|
|
+ // 调用统一保存接口
|
|
|
|
+ const data = { ...formData };
|
|
|
|
+ await saveGoods(data);
|
|
|
|
+ // 测试代码
|
|
|
|
+
|
|
|
|
+ // 提交成功,跳转到成功页面
|
|
|
|
+ submitSuccess.value = true;
|
|
|
|
+ currentStep.value = 2;
|
|
|
|
+ ElMessage.success(t('modules.goods.goodsSaveSuccess'));
|
|
|
|
+ } catch (error) {
|
|
|
|
+ submitError.value = error.message || '提交失败';
|
|
|
|
+ ElMessage.error('提交失败:' + error.message);
|
|
|
|
+ } finally {
|
|
|
|
+ submitLoading.value = false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 生成后端需要的数据格式(使用统一转换工具)
|
|
|
|
+const generateAttrValueData = () => {
|
|
|
|
+ const { attrValue } = dataConverter.convertFrontendToBackend(
|
|
|
|
+ formData.skus,
|
|
|
|
+ formData.sku_prices,
|
|
|
|
+ );
|
|
|
|
+ return attrValue;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const generateAttrData = () => {
|
|
|
|
+ const { attr } = dataConverter.convertFrontendToBackend(formData.skus, formData.sku_prices);
|
|
|
|
+ return attr;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 统一提交接口 - 保存商品信息和属性
|
|
|
|
+const saveGoods = async (data) => {
|
|
|
|
+ try {
|
|
|
|
+ // 准备基本商品数据
|
|
|
|
+ const submitData = {
|
|
|
|
+ ...data,
|
|
|
|
+ // 图片转换为逗号分隔字符串
|
|
|
|
+ image: convertImagesToString(data.image),
|
|
|
|
+ sliderImage: convertImagesToString(data.sliderImage),
|
|
|
|
+ flatPattern: convertImagesToString(data.flatPattern),
|
|
|
|
+ };
|
|
|
|
|
|
- // 调用配置文件中的接口 - 根据是否有ID判断新增还是编辑
|
|
|
|
- const { code } = isEdit.value
|
|
|
|
- ? await api.goods.edit(submitData)
|
|
|
|
- : await api.goods.add(submitData);
|
|
|
|
- if (code === '200') {
|
|
|
|
- return true;
|
|
|
|
- } else {
|
|
|
|
- throw new Error('保存失败');
|
|
|
|
- }
|
|
|
|
- } catch (error) {
|
|
|
|
- console.error('保存商品失败:', error);
|
|
|
|
- throw error;
|
|
|
|
|
|
+ // 添加商品属性数据
|
|
|
|
+ if (formData.sku_prices && formData.sku_prices.length > 0) {
|
|
|
|
+ submitData.attrValue = generateAttrValueData();
|
|
|
|
+ submitData.attr = generateAttrData();
|
|
}
|
|
}
|
|
- };
|
|
|
|
|
|
|
|
- // 获取分类数据
|
|
|
|
- const getCategoryData = async () => {
|
|
|
|
- try {
|
|
|
|
- const response = await api.category.list({ size: 100 });
|
|
|
|
- if (response.code == '200') {
|
|
|
|
- // 只使用一级分类
|
|
|
|
- categoryOptions.value = response.data.list;
|
|
|
|
- }
|
|
|
|
- } catch (error) {
|
|
|
|
- console.error('获取分类失败:', error);
|
|
|
|
|
|
+ // 调用配置文件中的接口 - 根据是否有ID判断新增还是编辑
|
|
|
|
+ const { code } = isEdit.value
|
|
|
|
+ ? await api.goods.edit(submitData)
|
|
|
|
+ : await api.goods.add(submitData);
|
|
|
|
+ if (code === '200') {
|
|
|
|
+ return true;
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error('保存失败');
|
|
}
|
|
}
|
|
- };
|
|
|
|
-
|
|
|
|
- // 初始化
|
|
|
|
- const init = () => {
|
|
|
|
- getCategoryData();
|
|
|
|
- if (props.modal?.params?.type === 'edit' && props.modal?.params?.id) {
|
|
|
|
- // 编辑模式,加载商品数据
|
|
|
|
- loadGoodsData(props.modal.params.id);
|
|
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('保存商品失败:', error);
|
|
|
|
+ throw error;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 获取分类数据
|
|
|
|
+const getCategoryData = async () => {
|
|
|
|
+ try {
|
|
|
|
+ const response = await api.category.list({ size: 100 });
|
|
|
|
+ if (response.code == '200') {
|
|
|
|
+ // 只使用一级分类
|
|
|
|
+ categoryOptions.value = response.data.list;
|
|
}
|
|
}
|
|
- };
|
|
|
|
-
|
|
|
|
- // 加载商品数据
|
|
|
|
- const loadGoodsData = async (id) => {
|
|
|
|
- try {
|
|
|
|
- const response = await api.goods.detail(id);
|
|
|
|
-
|
|
|
|
- if (response.code == '200') {
|
|
|
|
- const data = { ...response.data };
|
|
|
|
-
|
|
|
|
- // 转换图片字段:将逗号分隔的字符串转换为数组
|
|
|
|
- data.image = dataConverter.stringToImages(data.image);
|
|
|
|
- data.sliderImage = dataConverter.stringToImages(data.sliderImage);
|
|
|
|
- data.flatPattern = dataConverter.stringToImages(data.flatPattern);
|
|
|
|
-
|
|
|
|
- // 处理商品属性数据:将后端的 attr 和 attrValue 转换为前端格式
|
|
|
|
- if (data.attr && data.attrValue && data.attr.length > 0 && data.attrValue.length > 0) {
|
|
|
|
- const { skus, sku_prices } = dataConverter.convertBackendToFrontend(
|
|
|
|
- data.attr,
|
|
|
|
- data.attrValue,
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- data.skus = skus.length > 0 ? skus : formData.skus;
|
|
|
|
- data.sku_prices = sku_prices;
|
|
|
|
- data.specType = skus.length > 0 ? 1 : 0;
|
|
|
|
- } else {
|
|
|
|
- // 如果没有属性数据,保持默认的单规格结构
|
|
|
|
- data.skus = formData.skus;
|
|
|
|
- data.sku_prices = [];
|
|
|
|
- data.specType = 1; // 默认多规格模式
|
|
|
|
- }
|
|
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取分类失败:', error);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 初始化
|
|
|
|
+const init = () => {
|
|
|
|
+ getCategoryData();
|
|
|
|
+ if (props.modal?.params?.type === 'edit' && props.modal?.params?.id) {
|
|
|
|
+ // 编辑模式,加载商品数据
|
|
|
|
+ loadGoodsData(props.modal.params.id);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 加载商品数据
|
|
|
|
+const loadGoodsData = async (id) => {
|
|
|
|
+ try {
|
|
|
|
+ const response = await api.goods.detail(id);
|
|
|
|
+
|
|
|
|
+ if (response.code == '200') {
|
|
|
|
+ const data = { ...response.data };
|
|
|
|
+
|
|
|
|
+ // 转换图片字段:将逗号分隔的字符串转换为数组
|
|
|
|
+ data.image = dataConverter.stringToImages(data.image);
|
|
|
|
+ data.sliderImage = dataConverter.stringToImages(data.sliderImage);
|
|
|
|
+ data.flatPattern = dataConverter.stringToImages(data.flatPattern);
|
|
|
|
+
|
|
|
|
+ // 处理商品属性数据:将后端的 attr 和 attrValue 转换为前端格式
|
|
|
|
+ if (data.attr && data.attrValue && data.attr.length > 0 && data.attrValue.length > 0) {
|
|
|
|
+ const { skus, sku_prices } = dataConverter.convertBackendToFrontend(
|
|
|
|
+ data.attr,
|
|
|
|
+ data.attrValue,
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ data.skus = skus.length > 0 ? skus : formData.skus;
|
|
|
|
+ data.sku_prices = sku_prices;
|
|
|
|
+ data.specType = skus.length > 0 ? 1 : 0;
|
|
|
|
+ } else {
|
|
|
|
+ // 如果没有属性数据,保持默认的单规格结构
|
|
|
|
+ data.skus = formData.skus;
|
|
|
|
+ data.sku_prices = [];
|
|
|
|
+ data.specType = 1; // 默认多规格模式
|
|
|
|
+ }
|
|
|
|
|
|
- // 将处理后的数据填充到表单中 - 使用直接赋值确保响应式更新
|
|
|
|
- // 基本信息字段
|
|
|
|
- formData.id = data.id;
|
|
|
|
- formData.cateId = data.cateId;
|
|
|
|
- formData.storeName = data.storeName;
|
|
|
|
- formData.keyword = data.keyword;
|
|
|
|
- formData.itemBrand = data.itemBrand;
|
|
|
|
- formData.storeInfo = data.storeInfo;
|
|
|
|
- formData.itemNumber = data.itemNumber;
|
|
|
|
- formData.price = data.price;
|
|
|
|
- formData.otPrice = data.otPrice;
|
|
|
|
- formData.stock = data.stock;
|
|
|
|
- formData.stockThreshold = data.stockThreshold;
|
|
|
|
- formData.isShow = data.isShow;
|
|
|
|
- formData.itemSupplier = data.itemSupplier;
|
|
|
|
- formData.sort = data.sort;
|
|
|
|
- formData.cost = data.cost;
|
|
|
|
- formData.vipPrice = data.vipPrice;
|
|
|
|
-
|
|
|
|
- // 图片字段 - 使用nextTick避免响应式循环
|
|
|
|
- await new Promise((resolve) => {
|
|
|
|
- setTimeout(() => {
|
|
|
|
- formData.image = [...data.image];
|
|
|
|
- formData.sliderImage = [...data.sliderImage];
|
|
|
|
- formData.flatPattern = [...data.flatPattern];
|
|
|
|
- resolve();
|
|
|
|
- }, 0);
|
|
|
|
- });
|
|
|
|
|
|
+ // 将处理后的数据填充到表单中 - 使用直接赋值确保响应式更新
|
|
|
|
+ // 基本信息字段
|
|
|
|
+ formData.id = data.id;
|
|
|
|
+ formData.cateId = data.cateId;
|
|
|
|
+ formData.storeName = data.storeName;
|
|
|
|
+ formData.keyword = data.keyword;
|
|
|
|
+ formData.itemBrand = data.itemBrand;
|
|
|
|
+ formData.storeInfo = data.storeInfo;
|
|
|
|
+ formData.itemNumber = data.itemNumber;
|
|
|
|
+ formData.price = data.price;
|
|
|
|
+ formData.otPrice = data.otPrice;
|
|
|
|
+ formData.stock = data.stock;
|
|
|
|
+ formData.stockThreshold = data.stockThreshold;
|
|
|
|
+ formData.isShow = data.isShow;
|
|
|
|
+ formData.itemSupplier = data.itemSupplier;
|
|
|
|
+ formData.sort = data.sort;
|
|
|
|
+ formData.cost = data.cost;
|
|
|
|
+ formData.vipPrice = data.vipPrice;
|
|
|
|
+
|
|
|
|
+ // 图片字段 - 使用nextTick避免响应式循环
|
|
|
|
+ await new Promise((resolve) => {
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ formData.image = [...data.image];
|
|
|
|
+ formData.sliderImage = [...data.sliderImage];
|
|
|
|
+ formData.flatPattern = [...data.flatPattern];
|
|
|
|
+ resolve();
|
|
|
|
+ }, 0);
|
|
|
|
+ });
|
|
|
|
|
|
- // 规格字段 - 使用全新对象替换
|
|
|
|
- formData.specType = data.specType;
|
|
|
|
|
|
+ // 规格字段 - 使用全新对象替换
|
|
|
|
+ formData.specType = data.specType;
|
|
|
|
|
|
- // 重要:先清空数组,然后添加新元素
|
|
|
|
- formData.skus.length = 0;
|
|
|
|
- data.skus.forEach((item) => formData.skus.push(item));
|
|
|
|
|
|
+ // 重要:先清空数组,然后添加新元素
|
|
|
|
+ formData.skus.length = 0;
|
|
|
|
+ data.skus.forEach((item) => formData.skus.push(item));
|
|
|
|
|
|
- formData.sku_prices.length = 0;
|
|
|
|
- data.sku_prices.forEach((item) => formData.sku_prices.push(item));
|
|
|
|
|
|
+ formData.sku_prices.length = 0;
|
|
|
|
+ data.sku_prices.forEach((item) => formData.sku_prices.push(item));
|
|
|
|
|
|
- // 强制触发响应式更新
|
|
|
|
- await new Promise((resolve) => {
|
|
|
|
- setTimeout(() => {
|
|
|
|
- // 强制重新构建SKU表格
|
|
|
|
- buildSkuPriceTable();
|
|
|
|
|
|
+ // 强制触发响应式更新
|
|
|
|
+ await new Promise((resolve) => {
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ // 强制重新构建SKU表格
|
|
|
|
+ buildSkuPriceTable();
|
|
|
|
|
|
- // 强制重新渲染组件
|
|
|
|
- forceUpdateKey.value++;
|
|
|
|
- resolve();
|
|
|
|
- }, 100);
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
- } catch (error) {
|
|
|
|
- console.error('加载商品数据失败:', error);
|
|
|
|
|
|
+ // 强制重新渲染组件
|
|
|
|
+ forceUpdateKey.value++;
|
|
|
|
+ resolve();
|
|
|
|
+ }, 100);
|
|
|
|
+ });
|
|
}
|
|
}
|
|
- };
|
|
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('加载商品数据失败:', error);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
|
|
- // 组件挂载时初始化
|
|
|
|
- onMounted(() => {
|
|
|
|
- init();
|
|
|
|
- });
|
|
|
|
|
|
+// 组件挂载时初始化
|
|
|
|
+onMounted(() => {
|
|
|
|
+ init();
|
|
|
|
+});
|
|
</script>
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
- .goods-edit-new {
|
|
|
|
- .el-main {
|
|
|
|
- padding: 20px;
|
|
|
|
- background: #f5f5f5;
|
|
|
|
- overflow-y: auto;
|
|
|
|
- }
|
|
|
|
|
|
+.goods-edit-new {
|
|
|
|
+ .el-main {
|
|
|
|
+ padding: 20px;
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
+ overflow-y: auto;
|
|
|
|
+ }
|
|
|
|
|
|
- .step-progress {
|
|
|
|
- background: #fff;
|
|
|
|
|
|
+ .step-progress {
|
|
|
|
+ background: #fff;
|
|
|
|
+ padding: 30px;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .step-content {
|
|
|
|
+ background: #fff;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
+ min-height: 500px;
|
|
|
|
+
|
|
|
|
+ .step-panel {
|
|
padding: 30px;
|
|
padding: 30px;
|
|
- border-radius: 8px;
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- .step-content {
|
|
|
|
- background: #fff;
|
|
|
|
- border-radius: 8px;
|
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
- min-height: 500px;
|
|
|
|
|
|
+ .upload-section {
|
|
|
|
+ .upload-tip {
|
|
|
|
+ margin-top: 8px;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: #999;
|
|
|
|
+ line-height: 1.4;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- .step-panel {
|
|
|
|
- padding: 30px;
|
|
|
|
|
|
+ .spec-card {
|
|
|
|
+ .card-header {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ align-items: center;
|
|
|
|
+ font-weight: 500;
|
|
}
|
|
}
|
|
|
|
|
|
- .upload-section {
|
|
|
|
- .upload-tip {
|
|
|
|
- margin-top: 8px;
|
|
|
|
- font-size: 12px;
|
|
|
|
- color: #999;
|
|
|
|
- line-height: 1.4;
|
|
|
|
- }
|
|
|
|
|
|
+ .single-spec {
|
|
|
|
+ padding: 20px 0;
|
|
}
|
|
}
|
|
|
|
|
|
- .spec-card {
|
|
|
|
- .card-header {
|
|
|
|
- display: flex;
|
|
|
|
- justify-content: space-between;
|
|
|
|
- align-items: center;
|
|
|
|
- font-weight: 500;
|
|
|
|
|
|
+ .multi-spec {
|
|
|
|
+ padding: 20px 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .submit-panel {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ min-height: 500px;
|
|
|
|
+
|
|
|
|
+ .submit-success {
|
|
|
|
+ text-align: center;
|
|
|
|
+
|
|
|
|
+ .success-icon {
|
|
|
|
+ margin-bottom: 20px;
|
|
}
|
|
}
|
|
|
|
|
|
- .single-spec {
|
|
|
|
- padding: 20px 0;
|
|
|
|
|
|
+ h3 {
|
|
|
|
+ margin: 0 0 10px 0;
|
|
|
|
+ font-size: 20px;
|
|
|
|
+ color: #303133;
|
|
}
|
|
}
|
|
|
|
|
|
- .multi-spec {
|
|
|
|
- padding: 20px 0;
|
|
|
|
|
|
+ p {
|
|
|
|
+ margin: 0;
|
|
|
|
+ color: #606266;
|
|
|
|
+ font-size: 14px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.batch-operations {
|
|
|
|
+ margin: 16px 0;
|
|
|
|
+ padding: 16px;
|
|
|
|
+ background: #f8f9fa;
|
|
|
|
+ border-radius: 6px;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
+ gap: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.sku-wrap {
|
|
|
|
+ width: 100%;
|
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
|
+ padding: 8px;
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
+
|
|
|
|
+ .sku {
|
|
|
|
+ width: 100%;
|
|
|
|
+ min-height: 100px;
|
|
|
|
|
|
- .submit-panel {
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- justify-content: center;
|
|
|
|
- min-height: 500px;
|
|
|
|
|
|
+ .sku-key {
|
|
|
|
+ width: 100%;
|
|
|
|
+ height: 40px;
|
|
|
|
+ color: var(--sa-subtitle);
|
|
|
|
+ padding: 0 16px;
|
|
|
|
+ background: var(--sa-table-header-bg);
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
|
- .submit-success {
|
|
|
|
- text-align: center;
|
|
|
|
|
|
+ .sku-key-input {
|
|
|
|
+ width: 120px;
|
|
|
|
+ }
|
|
|
|
|
|
- .success-icon {
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
- }
|
|
|
|
|
|
+ .sku-key-icon {
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- h3 {
|
|
|
|
- margin: 0 0 10px 0;
|
|
|
|
- font-size: 20px;
|
|
|
|
- color: #303133;
|
|
|
|
- }
|
|
|
|
|
|
+ .sku-value {
|
|
|
|
+ padding: 12px 0 0 30px;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ color: var(--sa-subtitle);
|
|
|
|
|
|
- p {
|
|
|
|
- margin: 0;
|
|
|
|
- color: #606266;
|
|
|
|
- font-size: 14px;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ .sku-value-title {
|
|
|
|
+ height: 32px;
|
|
}
|
|
}
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
|
|
- .batch-operations {
|
|
|
|
- margin: 16px 0;
|
|
|
|
- padding: 16px;
|
|
|
|
- background: #f8f9fa;
|
|
|
|
- border-radius: 6px;
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
- gap: 10px;
|
|
|
|
- }
|
|
|
|
|
|
+ .sku-value-box {
|
|
|
|
+ position: relative;
|
|
|
|
+ margin-right: 24px;
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
+ border-radius: 6px;
|
|
|
|
+ padding: 8px;
|
|
|
|
+ background: #fafafa;
|
|
|
|
|
|
- .sku-wrap {
|
|
|
|
- width: 100%;
|
|
|
|
- border: 1px solid #d9d9d9;
|
|
|
|
- padding: 8px;
|
|
|
|
- box-sizing: border-box;
|
|
|
|
- .sku {
|
|
|
|
- width: 100%;
|
|
|
|
- min-height: 100px;
|
|
|
|
- .sku-key {
|
|
|
|
- width: 100%;
|
|
|
|
- height: 40px;
|
|
|
|
- color: var(--sa-subtitle);
|
|
|
|
- padding: 0 16px;
|
|
|
|
- background: var(--sa-table-header-bg);
|
|
|
|
- font-size: 14px;
|
|
|
|
- .sku-key-input {
|
|
|
|
- width: 120px;
|
|
|
|
- }
|
|
|
|
- .sku-key-icon {
|
|
|
|
- color: var(--el-color-primary);
|
|
|
|
|
|
+ .sku-value-content {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ gap: 8px;
|
|
}
|
|
}
|
|
- }
|
|
|
|
- .sku-value {
|
|
|
|
- padding: 12px 0 0 30px;
|
|
|
|
- font-size: 14px;
|
|
|
|
- color: var(--sa-subtitle);
|
|
|
|
- .sku-value-title {
|
|
|
|
- height: 32px;
|
|
|
|
|
|
+
|
|
|
|
+ .sku-value-input {
|
|
|
|
+ width: 104px;
|
|
}
|
|
}
|
|
- .sku-value-box {
|
|
|
|
- position: relative;
|
|
|
|
- margin-right: 24px;
|
|
|
|
- border: 1px solid #e4e7ed;
|
|
|
|
- border-radius: 6px;
|
|
|
|
- padding: 8px;
|
|
|
|
- background: #fafafa;
|
|
|
|
-
|
|
|
|
- .sku-value-content {
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- gap: 8px;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- .sku-value-input {
|
|
|
|
- width: 104px;
|
|
|
|
- }
|
|
|
|
|
|
+ .sku-value-image {
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
+ }
|
|
|
|
|
|
- .sku-value-image {
|
|
|
|
- flex-shrink: 0;
|
|
|
|
- }
|
|
|
|
|
|
+ .sku-value-icon {
|
|
|
|
+ position: absolute;
|
|
|
|
+ right: -9px;
|
|
|
|
+ top: -12px;
|
|
|
|
+ width: 18px;
|
|
|
|
+ height: 18px;
|
|
|
|
+ color: var(--el-color-danger);
|
|
|
|
+ background: white;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ z-index: 10;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
|
|
- .sku-value-icon {
|
|
|
|
- position: absolute;
|
|
|
|
- right: -9px;
|
|
|
|
- top: -12px;
|
|
|
|
- width: 18px;
|
|
|
|
- height: 18px;
|
|
|
|
|
|
+ &:hover {
|
|
color: var(--el-color-danger);
|
|
color: var(--el-color-danger);
|
|
- background: white;
|
|
|
|
- border-radius: 50%;
|
|
|
|
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
|
|
- cursor: pointer;
|
|
|
|
- z-index: 10;
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- justify-content: center;
|
|
|
|
-
|
|
|
|
- &:hover {
|
|
|
|
- color: var(--el-color-danger);
|
|
|
|
- background: var(--el-color-danger-light-9);
|
|
|
|
- }
|
|
|
|
|
|
+ background: var(--el-color-danger-light-9);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- .sku-value-add {
|
|
|
|
- width: 104px;
|
|
|
|
- height: 32px;
|
|
|
|
- font-size: 14px;
|
|
|
|
- color: var(--el-color-primary);
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
- }
|
|
|
|
- .sku-tools {
|
|
|
|
- width: 100%;
|
|
|
|
- height: 40px;
|
|
|
|
- color: #434343;
|
|
|
|
- padding-left: 16px;
|
|
|
|
- background: var(--sa-table-header-bg);
|
|
|
|
- font-size: 12px;
|
|
|
|
|
|
+
|
|
|
|
+ .sku-value-add {
|
|
|
|
+ height: 32px;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- .alledit-input {
|
|
|
|
- margin-bottom: 10px;
|
|
|
|
|
|
+
|
|
|
|
+ .sku-tools {
|
|
|
|
+ width: 100%;
|
|
|
|
+ height: 40px;
|
|
|
|
+ color: #434343;
|
|
|
|
+ padding-left: 16px;
|
|
|
|
+ background: var(--sa-table-header-bg);
|
|
|
|
+ font-size: 12px;
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- .sku-table-wrap {
|
|
|
|
|
|
+.alledit-input {
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.sku-table-wrap {
|
|
|
|
+ width: 100%;
|
|
|
|
+ overflow: auto;
|
|
|
|
+ margin-top: 16px;
|
|
|
|
+
|
|
|
|
+ .sku-table {
|
|
width: 100%;
|
|
width: 100%;
|
|
- overflow: auto;
|
|
|
|
- margin-top: 16px;
|
|
|
|
- .sku-table {
|
|
|
|
- width: 100%;
|
|
|
|
- border: 1px solid var(--sa-border);
|
|
|
|
- tbody {
|
|
|
|
- font-size: 12px;
|
|
|
|
|
|
+ border: 1px solid var(--sa-border);
|
|
|
|
+
|
|
|
|
+ tbody {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ th {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: var(--subtitle);
|
|
|
|
+ height: 32px;
|
|
|
|
+ line-height: 1;
|
|
|
|
+ padding-left: 12px;
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
+ text-align: left;
|
|
|
|
+
|
|
|
|
+ .sku-table-header-title {
|
|
|
|
+ margin-right: 10px;
|
|
}
|
|
}
|
|
- th {
|
|
|
|
|
|
+
|
|
|
|
+ .th-title {
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
color: var(--subtitle);
|
|
color: var(--subtitle);
|
|
- height: 32px;
|
|
|
|
- line-height: 1;
|
|
|
|
- padding-left: 12px;
|
|
|
|
- box-sizing: border-box;
|
|
|
|
- text-align: left;
|
|
|
|
- .sku-table-header-title {
|
|
|
|
- margin-right: 10px;
|
|
|
|
- }
|
|
|
|
- .th-title {
|
|
|
|
- font-size: 12px;
|
|
|
|
- color: var(--subtitle);
|
|
|
|
- font-weight: bold;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- td {
|
|
|
|
- min-width: 88px;
|
|
|
|
- padding: 0 10px;
|
|
|
|
- height: 40px;
|
|
|
|
- box-sizing: border-box;
|
|
|
|
- &.image {
|
|
|
|
- min-width: 48px;
|
|
|
|
- }
|
|
|
|
- &.stock {
|
|
|
|
- min-width: 138px;
|
|
|
|
- }
|
|
|
|
- &.stockThreshold {
|
|
|
|
- min-width: 168px;
|
|
|
|
- .sku-stock-switch {
|
|
|
|
- margin-right: 10px;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- &.sn {
|
|
|
|
- min-width: 116px;
|
|
|
|
- }
|
|
|
|
|
|
+ font-weight: bold;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- }
|
|
|
|
- .batch-icon {
|
|
|
|
- width: 12px;
|
|
|
|
- height: 12px;
|
|
|
|
- margin-left: 5px;
|
|
|
|
- cursor: pointer;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- // 弹窗中的特殊样式
|
|
|
|
- :deep(.sa-dialog) {
|
|
|
|
- .el-dialog__body {
|
|
|
|
- padding: 0;
|
|
|
|
- height: 80vh;
|
|
|
|
- overflow: hidden;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ td {
|
|
|
|
+ min-width: 88px;
|
|
|
|
+ padding: 0 10px;
|
|
|
|
+ height: 40px;
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
|
- // 表单样式优化
|
|
|
|
- :deep(.el-form-item) {
|
|
|
|
- margin-bottom: 24px;
|
|
|
|
|
|
+ &.image {
|
|
|
|
+ min-width: 48px;
|
|
|
|
+ }
|
|
|
|
|
|
- .el-form-item__label {
|
|
|
|
- font-weight: 500;
|
|
|
|
- color: #303133;
|
|
|
|
- }
|
|
|
|
|
|
+ &.stock {
|
|
|
|
+ min-width: 138px;
|
|
|
|
+ }
|
|
|
|
|
|
- .form-tip {
|
|
|
|
- margin-top: 5px;
|
|
|
|
- font-size: 12px;
|
|
|
|
- color: #909399;
|
|
|
|
- line-height: 1.4;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ &.stockThreshold {
|
|
|
|
+ min-width: 168px;
|
|
|
|
+
|
|
|
|
+ .sku-stock-switch {
|
|
|
|
+ margin-right: 10px;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- :deep(.el-input) {
|
|
|
|
- .el-input__inner {
|
|
|
|
- border-radius: 6px;
|
|
|
|
|
|
+ &.sn {
|
|
|
|
+ min-width: 116px;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.batch-icon {
|
|
|
|
+ width: 12px;
|
|
|
|
+ height: 12px;
|
|
|
|
+ margin-left: 5px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 弹窗中的特殊样式
|
|
|
|
+:deep(.sa-dialog) {
|
|
|
|
+ .el-dialog__body {
|
|
|
|
+ padding: 0;
|
|
|
|
+ height: 80vh;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
|
|
- :deep(.el-select) {
|
|
|
|
- width: 100%;
|
|
|
|
|
|
+// 表单样式优化
|
|
|
|
+:deep(.el-form-item) {
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
|
|
- .el-input__inner {
|
|
|
|
- border-radius: 6px;
|
|
|
|
- }
|
|
|
|
|
|
+ .el-form-item__label {
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ color: #303133;
|
|
}
|
|
}
|
|
|
|
|
|
- :deep(.el-cascader) {
|
|
|
|
- .el-input__inner {
|
|
|
|
- border-radius: 6px;
|
|
|
|
- }
|
|
|
|
|
|
+ .form-tip {
|
|
|
|
+ margin-top: 5px;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: #909399;
|
|
|
|
+ line-height: 1.4;
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- :deep(.el-textarea) {
|
|
|
|
- .el-textarea__inner {
|
|
|
|
- border-radius: 6px;
|
|
|
|
- }
|
|
|
|
|
|
+:deep(.el-input) {
|
|
|
|
+ .el-input__inner {
|
|
|
|
+ border-radius: 6px;
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- // 步骤条样式
|
|
|
|
- :deep(.el-steps) {
|
|
|
|
- .el-step__title {
|
|
|
|
- font-size: 16px;
|
|
|
|
- font-weight: 500;
|
|
|
|
- }
|
|
|
|
|
|
+:deep(.el-select) {
|
|
|
|
+ width: 100%;
|
|
|
|
|
|
- .el-step__description {
|
|
|
|
- font-size: 14px;
|
|
|
|
- }
|
|
|
|
|
|
+ .el-input__inner {
|
|
|
|
+ border-radius: 6px;
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- // 卡片样式
|
|
|
|
- :deep(.el-card) {
|
|
|
|
- border-radius: 8px;
|
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
|
-
|
|
|
|
- .el-card__header {
|
|
|
|
- padding: 20px;
|
|
|
|
- border-bottom: 1px solid #f0f0f0;
|
|
|
|
- }
|
|
|
|
|
|
+:deep(.el-cascader) {
|
|
|
|
+ .el-input__inner {
|
|
|
|
+ border-radius: 6px;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
|
|
- .el-card__body {
|
|
|
|
- padding: 20px;
|
|
|
|
- }
|
|
|
|
|
|
+:deep(.el-textarea) {
|
|
|
|
+ .el-textarea__inner {
|
|
|
|
+ border-radius: 6px;
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- /* 必填项样式 */
|
|
|
|
- .required {
|
|
|
|
- color: #f56c6c;
|
|
|
|
- margin-right: 4px;
|
|
|
|
|
|
+// 步骤条样式
|
|
|
|
+:deep(.el-steps) {
|
|
|
|
+ .el-step__title {
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ font-weight: 500;
|
|
}
|
|
}
|
|
|
|
|
|
- /* 错误状态样式 */
|
|
|
|
- .is-error .el-input__wrapper {
|
|
|
|
- border-color: #f56c6c !important;
|
|
|
|
- box-shadow: 0 0 0 1px #f56c6c inset !important;
|
|
|
|
|
|
+ .el-step__description {
|
|
|
|
+ font-size: 14px;
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- /* 成功页面样式 */
|
|
|
|
- .submit-result {
|
|
|
|
- text-align: center;
|
|
|
|
- padding: 60px 20px;
|
|
|
|
|
|
+// 卡片样式
|
|
|
|
+:deep(.el-card) {
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
|
|
|
- .success-content,
|
|
|
|
- .loading-content {
|
|
|
|
- .success-icon,
|
|
|
|
- .loading-icon {
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
- }
|
|
|
|
|
|
+ .el-card__header {
|
|
|
|
+ padding: 20px;
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
+ }
|
|
|
|
|
|
- h3 {
|
|
|
|
- font-size: 24px;
|
|
|
|
- color: #303133;
|
|
|
|
- margin-bottom: 12px;
|
|
|
|
- }
|
|
|
|
|
|
+ .el-card__body {
|
|
|
|
+ padding: 20px;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 必填项样式 */
|
|
|
|
+.required {
|
|
|
|
+ color: #f56c6c;
|
|
|
|
+ margin-right: 4px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 错误状态样式 */
|
|
|
|
+.is-error .el-input__wrapper {
|
|
|
|
+ border-color: #f56c6c !important;
|
|
|
|
+ box-shadow: 0 0 0 1px #f56c6c inset !important;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 成功页面样式 */
|
|
|
|
+.submit-result {
|
|
|
|
+ text-align: center;
|
|
|
|
+ padding: 60px 20px;
|
|
|
|
+
|
|
|
|
+ .success-content,
|
|
|
|
+ .loading-content {
|
|
|
|
+
|
|
|
|
+ .success-icon,
|
|
|
|
+ .loading-icon {
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+ }
|
|
|
|
|
|
- p {
|
|
|
|
- font-size: 14px;
|
|
|
|
- color: #909399;
|
|
|
|
- margin-bottom: 30px;
|
|
|
|
- }
|
|
|
|
|
|
+ h3 {
|
|
|
|
+ font-size: 24px;
|
|
|
|
+ color: #303133;
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
+ }
|
|
|
|
|
|
- .action-buttons {
|
|
|
|
- .el-button {
|
|
|
|
- margin: 0 8px;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ p {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ color: #909399;
|
|
|
|
+ margin-bottom: 30px;
|
|
}
|
|
}
|
|
|
|
|
|
- .loading-content {
|
|
|
|
- .loading-icon {
|
|
|
|
- animation: rotate 2s linear infinite;
|
|
|
|
|
|
+ .action-buttons {
|
|
|
|
+ .el-button {
|
|
|
|
+ margin: 0 8px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- @keyframes rotate {
|
|
|
|
- from {
|
|
|
|
- transform: rotate(0deg);
|
|
|
|
- }
|
|
|
|
- to {
|
|
|
|
- transform: rotate(360deg);
|
|
|
|
|
|
+ .loading-content {
|
|
|
|
+ .loading-icon {
|
|
|
|
+ animation: rotate 2s linear infinite;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@keyframes rotate {
|
|
|
|
+ from {
|
|
|
|
+ transform: rotate(0deg);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ to {
|
|
|
|
+ transform: rotate(360deg);
|
|
|
|
+ }
|
|
|
|
+}
|
|
</style>
|
|
</style>
|