Commit 8ad1439fda6e05f59a710fca7b24df5887a0639c

Authored by luowenfeng
1 parent 81666e3b

feature(uniapp商品): 新增商品

yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java
... ... @@ -21,10 +21,7 @@ public class ProductSkuBaseVO {
21 21 @NotEmpty(message = "商品 SKU 名字不能为空")
22 22 private String name;
23 23  
24   - /**
25   - * 规格值数组
26   - */
27   - @NotNull(message = "规格值数组不能为空")
  24 + @ApiModelProperty(value = "规格值数组-json格式 单规格中无此列表, [{propertyId: , valueId: }, {propertyId: , valueId: }]")
28 25 private List<Property> properties;
29 26  
30 27 @ApiModelProperty(value = "销售价格,单位:分", required = true, example = "1024", notes = "单位:分")
... ... @@ -32,7 +29,7 @@ public class ProductSkuBaseVO {
32 29 private Integer price;
33 30  
34 31 @ApiModelProperty(value = "市场价", example = "1024", notes = "单位:分")
35   - private Integer originalPrice;
  32 + private Integer marketPrice;
36 33  
37 34 @ApiModelProperty(value = "成本价", example = "1024", notes = "单位:分")
38 35 private Integer costPrice;
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java
... ... @@ -36,7 +36,7 @@ public class ProductSpuBaseVO {
36 36 private Long categoryId;
37 37  
38 38 @ApiModelProperty(value = "商品品牌编号", required = true, example = "1")
39   - @NotNull(message = "商品品牌编号不能为空")
  39 +// @NotNull(message = "商品品牌编号不能为空")
40 40 private Long brandId;
41 41  
42 42 @ApiModelProperty(value = "商品图片的数组", required = true)
... ... @@ -44,7 +44,6 @@ public class ProductSpuBaseVO {
44 44 private List<String> picUrls;
45 45  
46 46 @ApiModelProperty(value = "商品视频", required = true)
47   - @NotEmpty(message = "商品视频不能为空")
48 47 private String videoUrl;
49 48  
50 49 @ApiModelProperty(value = "排序字段", required = true, example = "1")
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
... ... @@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
15 15 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
16 16 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
17 17 import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
  18 +import cn.iocoder.yudao.module.product.enums.spu.ProductSpuSpecTypeEnum;
18 19 import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
19 20 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
20 21 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
... ... @@ -57,13 +58,18 @@ public class ProductSpuServiceImpl implements ProductSpuService {
57 58 categoryService.validateProductCategory(createReqVO.getCategoryId());
58 59 // 校验SKU
59 60 List<ProductSkuCreateOrUpdateReqVO> skuCreateReqList = createReqVO.getSkus();
60   - if(createReqVO.getSpecType() == 1) {
  61 + // 多规格才需校验
  62 + if(Objects.equals(createReqVO.getSpecType(), ProductSpuSpecTypeEnum.DISABLE.getType())) {
61 63 productSkuService.validateSkus(skuCreateReqList);
62 64 }
63 65 // 插入SPU
64 66 ProductSpuDO spu = ProductSpuConvert.INSTANCE.convert(createReqVO);
  67 + spu.setMarketPrice(skuCreateReqList.stream().map(ProductSkuCreateOrUpdateReqVO::getMarketPrice).max(Integer::compare).orElse(0));
  68 + spu.setMaxPrice(skuCreateReqList.stream().map(ProductSkuCreateOrUpdateReqVO::getPrice).max(Integer::compare).orElse(0));
  69 + spu.setMinPrice(skuCreateReqList.stream().map(ProductSkuCreateOrUpdateReqVO::getPrice).min(Integer::compare).orElse(0));
65 70 ProductSpuMapper.insert(spu);
66 71 List<ProductSkuDO> skuDOList = ProductSkuConvert.INSTANCE.convertSkuDOList(skuCreateReqList);
  72 + skuDOList.forEach(v->v.setSpuId(spu.getId()));
67 73 // 批量插入sku
68 74 productSkuService.createSkus(skuDOList);
69 75 // 返回
... ... @@ -79,7 +85,8 @@ public class ProductSpuServiceImpl implements ProductSpuService {
79 85 categoryService.validateProductCategory(updateReqVO.getCategoryId());
80 86 // 校验SKU
81 87 List<ProductSkuCreateOrUpdateReqVO> skuCreateReqList = updateReqVO.getSkus();
82   - if(updateReqVO.getSpecType() == 1) {
  88 + // 多规格才需校验
  89 + if(updateReqVO.getSpecType().equals(ProductSpuSpecTypeEnum.DISABLE.getType())) {
83 90 productSkuService.validateSkus(skuCreateReqList);
84 91 }
85 92 // 更新
... ...
yudao-ui-admin/src/components/ImageUpload/index.vue
... ... @@ -85,8 +85,8 @@ export default {
85 85 value: {
86 86 handler(val) {
87 87 if (val) {
88   - // 首先将值转为数组
89   - const list = Array.isArray(val) ? val : this.value.split(',');
  88 + // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
  89 + const list = Array.isArray(val) ? val : Array.isArray(this.value.split(',')) ? this.value.split(','): Array.of(this.value);
90 90 // 然后将数组转为对象数组
91 91 this.fileList = list.map(item => {
92 92 if (typeof item === "string") {
... ...
yudao-ui-admin/src/views/mall/product/spu/index.vue
... ... @@ -178,7 +178,7 @@
178 178 />
179 179  
180 180  
181   - <el-dialog :title="title" :visible.sync="open" width="900px" append-to-body destroy-on-close>
  181 + <el-dialog :title="title" :visible.sync="open" width="900px" append-to-body destroy-on-close :close-on-click-modal="false" >
182 182 <save @closeDialog="open = false; getList()"/>
183 183 </el-dialog>
184 184 </div>
... ...
yudao-ui-admin/src/views/mall/product/spu/save.vue
1 1 <template>
2 2 <div class="container">
3   - <el-tabs v-model="activeName" class="tabs">
  3 + <el-tabs v-model="activeName" class="tabs" :before-leave="confirmLeave">
4 4 <!-- 基础设置 -->
5 5 <el-tab-pane label="基础设置" name="base">
6   - <el-form ref="baseForm" :model="baseForm" :rules="rules" label-width="100px" style="width: 95%">
  6 + <el-form ref="base" :model="baseForm" :rules="rules" label-width="100px" style="width: 95%">
7 7 <el-form-item label="商品名称" prop="name">
8 8 <el-input v-model="baseForm.name" placeholder="请输入商品名称"/>
9 9 </el-form-item>
10   - <el-form-item label="卖点" prop="sellPoint">
  10 + <el-form-item label="卖点">
11 11 <el-input v-model="baseForm.sellPoint" placeholder="请输入卖点"/>
12 12 </el-form-item>
13 13 <el-form-item label="分类id" prop="categoryIds">
... ... @@ -20,30 +20,27 @@
20 20 clearable
21 21 ></el-cascader>
22 22 </el-form-item>
23   - <el-form-item label="商品主图" prop="bannerUrl">
24   - <ImageUpload v-model="baseForm.bannerUrl" :limit="1"/>
25   - </el-form-item>
26 23 <el-form-item label="商品轮播图" prop="picUrls">
27   - <ImageUpload v-model="baseForm.picUrls" :limit="10"/>
  24 + <ImageUpload v-model="baseForm.picUrls" :value="baseForm.picUrls" :limit="10"/>
28 25 </el-form-item>
29   - <el-form-item label="排序字段" prop="sort">
30   - <el-input v-model="baseForm.sort" placeholder="请输入排序字段"/>
  26 + <el-form-item label="排序字段">
  27 + <el-input v-model="baseForm.sort" placeholder="请输入排序字段" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"/>
31 28 </el-form-item>
32 29 </el-form>
33 30 </el-tab-pane>
34 31  
35 32 <!-- 价格库存 -->
36 33 <el-tab-pane label="价格库存" name="rates" class="rates">
37   - <el-form :model="ratesForm" :rules="rules.ratesForm">
  34 + <el-form ref="rates" :model="ratesForm" :rules="rules">
38 35 <el-form-item label="商品规格">
39 36 <el-radio-group v-model="ratesForm.spec" @change="changeRadio">
40   - <el-radio :label="0">单规格</el-radio>
41   - <el-radio :label="1">多规格</el-radio>
  37 + <el-radio :label="1">单规格</el-radio>
  38 + <el-radio :label="2">多规格</el-radio>
42 39 </el-radio-group>
43 40 </el-form-item>
44 41  
45 42 <!-- 动态添加规格属性 -->
46   - <div v-show="ratesForm.spec === 1">
  43 + <div v-show="ratesForm.spec === 2">
47 44 <div
48 45 v-for="(specs, index) in dynamicSpec"
49 46 :key="index"
... ... @@ -71,14 +68,8 @@
71 68 </div>
72 69 <div class="spec-values">
73 70 <template v-for="(v, i) in specs.specValue">
74   - <el-input v-model="v.name" class="spec-value" :key="i"/>
  71 + <el-input v-model="v.name" class="spec-value" :key="i" disabled/>
75 72 </template>
76   - <!-- <el-button
77   - type="primary"
78   - icon="el-icon-plus"
79   - circle
80   - @click="dialogForSpec = true; currentSpec = index"
81   - ></el-button> -->
82 73 </div>
83 74 </div>
84 75 <el-button type="primary" @click="dynamicSpec.push({specValue: []}); ratesForm.rates = []"
... ... @@ -89,9 +80,8 @@
89 80  
90 81 <!-- 规格明细 -->
91 82 <el-form-item label="规格明细">
92   - <el-table :data="ratesForm.rates" border style="width: 100%" ref="rates">
93   -
94   - <template v-if="ratesForm.spec == 1">
  83 + <el-table :data="ratesForm.rates" border style="width: 100%" ref="ratesTable">
  84 + <template v-if="ratesForm.spec == 2">
95 85 <el-table-column :key="index" v-for="(item, index) in dynamicSpec.filter(v=>v.specName != undefined)"
96 86 :label="item.specName">
97 87 <template slot-scope="scope">
... ... @@ -103,54 +93,90 @@
103 93 </template>
104 94 </el-table-column>
105 95 </template>
106   -
107   - <el-table-column label="规格图片" width="120px">
  96 + <el-table-column label="规格图片" width="120px" :render-header="addRedStar" key="90">
108 97 <template slot-scope="scope">
109   - <ImageUpload v-model="scope.row.picUrl" :limit="1" :isShowTip="false"
  98 + <ImageUpload v-model="scope.row.picUrl" :limit="1" :isShowTip="false"
110 99 style="width: 100px; height: 50px"/>
111 100 </template>
112 101 </el-table-column>
113   - <el-table-column label="市场价(元)">
  102 + <template v-if="ratesForm.spec == 2">
  103 + <el-table-column label="sku名称" :render-header="addRedStar" key="91">
  104 + <template slot-scope="scope">
  105 + <el-form-item :prop="'rates.'+ scope.$index + '.name'" :rules="[{required: true, trigger: 'change'}]">
  106 + <el-input
  107 + v-model="scope.row.name"
  108 + ></el-input>
  109 + </el-form-item>
  110 + </template>
  111 + </el-table-column>
  112 + </template>
  113 +
  114 + <el-table-column label="市场价(元)" :render-header="addRedStar" key="92">
114 115 <template slot-scope="scope">
115   - <el-input
116   - v-model="scope.row.marketPrice"
117   - type="number"
118   - ></el-input>
  116 + <el-form-item :prop="'rates.'+ scope.$index + '.marketPrice'" :rules="[{required: true, trigger: 'change'}]">
  117 + <el-input
  118 + v-model="scope.row.marketPrice"
  119 + oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''"
  120 + ></el-input>
  121 + </el-form-item>
119 122 </template>
120 123 </el-table-column>
121   - <el-table-column label="销售价(元)" re>
  124 + <el-table-column label="销售价(元)" :render-header="addRedStar" key="93">
122 125 <template slot-scope="scope">
123   - <el-input v-model="scope.row.price" type="number"></el-input>
  126 + <el-form-item :prop="'rates.'+ scope.$index + '.price'" :rules="[{required: true, trigger: 'change'}]">
  127 + <el-input v-model="scope.row.price" oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''"></el-input>
  128 + </el-form-item>
124 129 </template>
125 130 </el-table-column>
126   - <el-table-column label="成本价">
  131 + <el-table-column label="成本价" :render-header="addRedStar" key="94">
127 132 <template slot-scope="scope">
  133 + <el-form-item :prop="'rates.'+ scope.$index + '.costPrice'" :rules="[{required: true, trigger: 'change'}]">
128 134 <el-input
129 135 v-model="scope.row.costPrice"
130   - type="number"
  136 + oninput="value= value.match(/\d+(\.\d{0,2})?/) ? value.match(/\d+(\.\d{0,2})?/)[0] : ''"
131 137 ></el-input>
  138 + </el-form-item>
  139 + </template>
  140 + </el-table-column>
  141 + <el-table-column label="库存" :render-header="addRedStar" key="95">
  142 + <template slot-scope="scope">
  143 + <el-form-item :prop="'rates.'+ scope.$index + '.stock'" :rules="[{required: true, trigger: 'change'}]">
  144 + <el-input v-model="scope.row.stock" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"></el-input>
  145 + </el-form-item>
132 146 </template>
133 147 </el-table-column>
134   - <el-table-column label="库存">
  148 + <el-table-column label="预警库存" key="96">
135 149 <template slot-scope="scope">
136   - <el-input v-model="scope.row.stock" type="number"></el-input>
  150 + <el-input v-model="scope.row.warnStock" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"></el-input>
137 151 </template>
138 152 </el-table-column>
139   - <el-table-column label="体积">
  153 + <el-table-column label="体积" key="97">
140 154 <template slot-scope="scope">
141   - <el-input v-model="scope.row.volume" type="number"></el-input>
  155 + <el-input v-model="scope.row.volume" ></el-input>
142 156 </template>
143 157 </el-table-column>
144   - <el-table-column label="重量">
  158 + <el-table-column label="重量" key="98">
145 159 <template slot-scope="scope">
146   - <el-input v-model="scope.row.weight" type="number"></el-input>
  160 + <el-input v-model="scope.row.weight" ></el-input>
147 161 </template>
148 162 </el-table-column>
149   - <el-table-column label="条码">
  163 + <el-table-column label="条码" key="99">
150 164 <template slot-scope="scope">
151 165 <el-input v-model="scope.row.barCode"></el-input>
152 166 </template>
153 167 </el-table-column>
  168 + <template v-if="ratesForm.spec == 2">
  169 + <el-table-column
  170 + fixed="right"
  171 + label="操作"
  172 + width="50"
  173 + key="100">
  174 + <template slot-scope="scope">
  175 + <el-button @click="scope.row.status = 1" type="text" size="small" v-show="scope.row.status == undefined || scope.row.status == 0 ">禁用</el-button>
  176 + <el-button @click="scope.row.status = 0" type="text" size="small" v-show="scope.row.status == 1">启用</el-button>
  177 + </template>
  178 + </el-table-column>
  179 + </template>
154 180 </el-table>
155 181 </el-form-item>
156 182 </el-form>
... ... @@ -158,16 +184,26 @@
158 184  
159 185 <!-- 商品详情 -->
160 186 <el-tab-pane label="商品描述" name="third">
161   - <editor v-model="baseForm.description" :min-height="400"/>
  187 + <el-form ref="third" :model="baseForm" :rules="rules">
  188 + <el-form-item prop="description">
  189 + <editor v-model="baseForm.description" :min-height="380"/>
  190 + </el-form-item>
  191 + </el-form>
162 192 </el-tab-pane>
163 193  
164 194 <!-- 销售设置 -->
165 195 <el-tab-pane label="销售设置" name="fourth">
166   - <el-form ref="baseForm" :model="baseForm" :rules="rules" label-width="100px" style="width: 95%">
167   - <el-form-item label="库存数量" prop="totalStock">
168   - <el-input v-model="baseForm.totalStock" placeholder="请输入库存数量"/>
  196 + <el-form ref="fourth" :model="baseForm" :rules="rules" label-width="100px" style="width: 95%">
  197 + <el-form-item label="虚拟销量" prop="virtualSalesCount">
  198 + <el-input v-model="baseForm.virtualSalesCount" placeholder="请输入虚拟销量" oninput="value=value.replace(/^(0+)|[^\d]+/g,'')"/>
169 199 </el-form-item>
170   - <el-form-item label="上下架状态" prop="status">
  200 + <el-form-item label="是否展示库存" prop="showStock">
  201 + <el-radio-group v-model="baseForm.showStock">
  202 + <el-radio :label="true">是</el-radio>
  203 + <el-radio :label="false">否</el-radio>
  204 + </el-radio-group>
  205 + </el-form-item>
  206 + <el-form-item label="商品状态" prop="status">
171 207 <el-radio-group v-model="baseForm.status">
172 208 <el-radio :label="0">上架</el-radio>
173 209 <el-radio :label="1">下架</el-radio>
... ... @@ -212,15 +248,15 @@ export default {
212 248 categoryIds: null,
213 249 sort: null,
214 250 description: null,
215   - bannerUrl: null,
216   - picUrls: [],
217   - totalStock: null,
218   - status: 0
  251 + picUrls: null,
  252 + status: 0,
  253 + virtualSalesCount: 0,
  254 + showStock: true,
219 255 },
220 256 categoryList: [],
221 257 // 价格库存
222 258 ratesForm: {
223   - spec: 0,
  259 + spec: 1,
224 260 // 规格明细
225 261 rates: [{}]
226 262 },
... ... @@ -239,28 +275,39 @@ export default {
239 275  
240 276 // 表单校验
241 277 rules: {
242   - description: [
243   - {required: true, message: "描述不能为空", trigger: "blur"},
244   - ],
245   - categoryIds: [
246   - {required: true, message: "分类id不能为空", trigger: "blur"},
247   - ],
248   - bannerUrl: [{required: true, message: "商品主图地址", trigger: "blur"}],
249   - sort: [
250   - {required: true, message: "排序字段不能为空", trigger: "blur"},
251   - ],
  278 + name:[{required: true, message: "商品名称不能为空", trigger: "blur"},],
  279 + description: [{required: true, message: "描述不能为空", trigger: "blur"},],
  280 + categoryIds: [{required: true, message: "分类id不能为空", trigger: "blur"},],
  281 + status: [{required: true, message: "商品状态不能为空", trigger: "blur"}],
  282 + picUrls: [{required: true, message: "商品轮播图地址不能为空", trigger: "blur"}],
  283 + virtualSalesCount: [{required: true, message: "虚拟销量不能为空", trigger: "blur"}],
252 284 },
253 285 };
254 286 },
  287 +
255 288 created() {
256 289 this.getListCategory();
257 290 this.getPropertyPageList();
258 291 },
259 292 methods: {
  293 + async confirmLeave(active, old){
  294 + await this.$refs[old].validate((valid) => {
  295 + console.log(valid)
  296 + if (!valid) {
  297 + return reject();
  298 + }
  299 + });
  300 + },
  301 + // 必选标识
  302 + addRedStar(h, { column }) {
  303 + return [
  304 + h('span', { style: 'color: #F56C6C' }, '*'),
  305 + h('span', ' ' + column.label)
  306 + ];
  307 + },
260 308 changeRadio() {
261   -
262   - this.$refs.rates.doLayout()
263   - if (this.ratesForm.spec == 0) {
  309 + this.$refs.ratesTable.doLayout()
  310 + if (this.ratesForm.spec == 1) {
264 311 this.ratesForm.rates = [{}]
265 312 } else {
266 313 this.ratesForm.rates = []
... ... @@ -289,7 +336,7 @@ export default {
289 336 return array;
290 337 })
291 338 .forEach(v => {
292   - rates.push({spec: v})
  339 + rates.push({spec: v, status: 0, name: Array.of(v).join()})
293 340 });
294 341 this.ratesForm.rates = rates
295 342 },
... ... @@ -304,38 +351,51 @@ export default {
304 351 this.$emit("closeDialog");
305 352 },
306 353 submit() {
  354 + this.$refs[this.activeName].validate((valid) => {
  355 + if (!valid) {
  356 + return;
  357 + }
307 358 let rates = this.ratesForm.rates;
308 359 // 动态规格调整字段
309   - if (this.ratesForm.spec == 1) {
  360 + if (this.ratesForm.spec == 2) {
310 361 rates.forEach(r => {
311 362 let properties = []
312   - if(r.spec instanceof Array){
313   - r.spec.forEach((v, i) => {
  363 + Array.of(r.spec).forEach((v, i) => {
314 364 let specValue = this.dynamicSpec[i].specValue.find(o => o.name == v);
315 365 let propertie = {};
316 366 propertie.propertyId = this.dynamicSpec[i].specId;
317 367 propertie.valueId = specValue.id;
318 368 properties.push(propertie);
319 369 })
320   - }else{
321   - let specValue = this.dynamicSpec[0].specValue.find(o => o.name == r.spec);
322   - let propertie = {};
323   - propertie.propertyId = this.dynamicSpec[0].specId;
324   - propertie.valueId = specValue.id;
325   - properties.push(propertie);
326   - }
327 370 r.properties = properties;
328 371 })
  372 + }else{
  373 + rates[0].name = this.baseForm.name;
  374 + rates[0].status = this.baseForm.status;
329 375 }
330   - this.baseForm.skus = rates;
331   - this.baseForm.specType = this.ratesForm.spec;
332   - this.baseForm.categoryId = this.baseForm.categoryIds[this.baseForm.categoryIds.length - 1];
333   - createSpu(this.baseForm).then((response) => {
  376 +
  377 + console.log(rates)
  378 +
  379 + let form = this.baseForm
  380 + if(form.picUrls instanceof Array){
  381 + form.picUrls = form.picUrls.flatMap(m=>m.split(','))
  382 + }else if(form.picUrls.split(',') instanceof Array){
  383 + form.picUrls = form.picUrls.split(',').flatMap(m=>m.split(','))
  384 + }else{
  385 + form.picUrls = Array.of(form.picUrls)
  386 + }
  387 +
  388 + form.skus = rates;
  389 + form.specType = this.ratesForm.spec;
  390 + form.categoryId = form.categoryIds[this.baseForm.categoryIds.length - 1];
  391 + createSpu(form).then((response) => {
334 392 console.log(response)
335 393 this.$modal.msgSuccess("新增成功");
336 394 this.$emit("closeDialog");
  395 + }).catch((err) => {
  396 + console.log(this.baseForm.picUrls)
  397 + });
337 398 });
338   -
339 399 },
340 400 /** 查询规格 */
341 401 getPropertyPageList() {
... ...