Эх сурвалжийг харах

Merge remote-tracking branch 'origin/feature/1.8.0-uniapp' into feature/1.8.0-uniapp

# Conflicts:
#	yudao-ui-admin/src/views/mall/product/spu/index.vue
YunaiV 3 жил өмнө
parent
commit
1976571ae8
23 өөрчлөгдсөн 1386 нэмэгдсэн , 647 устгасан
  1. 43 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/ProductPropertyViewRespVO.java
  2. 8 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuRespVO.java
  3. 40 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/AppCategoryController.java
  4. 38 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/vo/AppCategoryListRespVO.java
  5. 45 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
  6. 18 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageReqVO.java
  7. 52 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageRespVO.java
  8. 22 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuRespVO.java
  9. 2 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/CategoryConvert.java
  10. 7 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java
  11. 8 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryService.java
  12. 11 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImpl.java
  13. 11 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
  14. 46 1
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
  15. 606 497
      yudao-ui-admin/src/views/mall/product/spu/index.vue
  16. 5 0
      yudao-ui-app/api/category.js
  17. 8 0
      yudao-ui-app/api/product.js
  18. 10 2
      yudao-ui-app/pages.json
  19. 165 136
      yudao-ui-app/pages/category/category.vue
  20. 161 0
      yudao-ui-app/pages/category/product-list.vue
  21. 3 3
      yudao-ui-app/pages/index/index.vue
  22. 18 5
      yudao-ui-app/pages/product/product.vue
  23. 59 0
      yudao-ui-app/utils/tree.js

+ 43 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/ProductPropertyViewRespVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.product.controller.admin.property.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.ToString;
+
+import java.util.Set;
+
+/**
+ * @Description: ProductPropertyViewRespVO
+ * @Author: franky
+ * @CreateDate: 2022/7/5 21:29
+ * @Version: 1.0.0
+ */
+@ApiModel("管理后台 - 规格名称详情展示 Request VO")
+@Data
+@ToString(callSuper = true)
+public class ProductPropertyViewRespVO {
+
+    @ApiModelProperty(value = "规格名称id", example = "1")
+    public Long propertyId;
+
+    @ApiModelProperty(value = "规格名称", example = "内存")
+    public String name;
+
+    @ApiModelProperty(value = "规格属性值集合", example = "[{\"v1\":11,\"v2\":\"64G\"},{\"v1\":10,\"v2\":\"32G\"}]")
+    public Set<Tuple2> propertyValues;
+
+    @Data
+    @ApiModel(value = "规格属性值元组")
+    public static class Tuple2 {
+        private final long v1;
+        private final String v2;
+        public Tuple2(Long v1, String v2) {
+            this.v1 = v1;
+            this.v2 = v2;
+        }
+
+    }
+
+
+}

+ 8 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/SpuRespVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
 
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.ProductPropertyViewRespVO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
@@ -19,7 +20,7 @@ public class SpuRespVO extends ProductSpuBaseVO {
 
     // TODO @franky:注解要完整
 
-    @ApiModelProperty(value = "主键", required = true)
+    @ApiModelProperty(value = "主键", required = true, example = "1")
     private Long id;
 
     @ApiModelProperty(value = "创建时间")
@@ -28,9 +29,13 @@ public class SpuRespVO extends ProductSpuBaseVO {
     /**
      * SKU 数组
      */
-    List<ProductSkuRespVO> skus;
+    @ApiModelProperty(value = "sku 数组", example = "[{\"spuId\":4,\"properties\":[{\"propertyId\":3,\"valueId\":15},{\"propertyId\":2,\"valueId\":10}],\"price\":12,\"originalPrice\":32,\"costPrice\":22,\"barCode\":\"765670123123\",\"picUrl\":\"http://test.yudao.iocoder.cn/72938088f1ca8438837c3b51394aea43.jpg\",\"status\":0,\"id\":7,\"createTime\":1656683270000},{\"spuId\":4,\"properties\":[{\"propertyId\":3,\"valueId\":15},{\"propertyId\":2,\"valueId\":11}],\"price\":13,\"originalPrice\":33,\"costPrice\":23,\"barCode\":\"888788770999\",\"picUrl\":\"http://test.yudao.iocoder.cn/6b902c700e5d32e862b6fd9af2e1c0e4.jpg\",\"status\":0,\"id\":8,\"createTime\":1656683270000},{\"spuId\":4,\"properties\":[{\"propertyId\":3,\"valueId\":16},{\"propertyId\":2,\"valueId\":10}],\"price\":14,\"originalPrice\":34,\"costPrice\":24,\"barCode\":\"9999981212\",\"picUrl\":\"http://test.yudao.iocoder.cn/eddf3c79b1917160d94d05244e1f47da.jpg\",\"status\":0,\"id\":9,\"createTime\":1656683270000},{\"spuId\":4,\"properties\":[{\"propertyId\":3,\"valueId\":16},{\"propertyId\":2,\"valueId\":11}],\"price\":15,\"originalPrice\":35,\"costPrice\":25,\"barCode\":\"4444121212\",\"picUrl\":\"http://test.yudao.iocoder.cn/88ac3eb068ea9cfac4726879b2776ccf.jpg\",\"status\":0,\"id\":10,\"createTime\":1656683270000}]")
+    private List<ProductSkuRespVO> skus;
 
     @ApiModelProperty(value = "分类id数组,一直递归到一级父节点", example = "[1,2,4]")
-    LinkedList<Long> categoryIds;
+    private LinkedList<Long> categoryIds;
+
+    @ApiModelProperty(value = "规格属性修改和详情展示组合", example = "[{\"propertyId\":2,\"name\":\"内存\",\"propertyValues\":[{\"v1\":11,\"v2\":\"64G\"},{\"v1\":10,\"v2\":\"32G\"}]},{\"propertyId\":3,\"name\":\"尺寸\",\"propertyValues\":[{\"v1\":16,\"v2\":\"6.1\"},{\"v1\":15,\"v2\":\"5.7\"}]}]")
+    private List<ProductPropertyViewRespVO> productPropertyViews;
 
 }

+ 40 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/AppCategoryController.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.product.controller.app.category;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.product.controller.app.category.vo.AppCategoryListRespVO;
+import cn.iocoder.yudao.module.product.convert.category.CategoryConvert;
+import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
+import cn.iocoder.yudao.module.product.service.category.CategoryService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "用户 APP - 商品分类")
+@RestController
+@RequestMapping("/product/category")
+@Validated
+public class AppCategoryController {
+
+    @Resource
+    private CategoryService categoryService;
+
+    @GetMapping("/list")
+    @ApiOperation("获得商品分类列表")
+    public CommonResult<List<AppCategoryListRespVO>> listByQuery() {
+        List<CategoryDO> list = categoryService.getCategoryList();
+        list.sort(Comparator.comparing(CategoryDO::getSort));
+        return success(CategoryConvert.INSTANCE.convertList03(list));
+    }
+
+
+
+}

+ 38 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/category/vo/AppCategoryListRespVO.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.product.controller.app.category.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel(value = "App - 商品分类 列表 Request VO", description = "参数和 CategoryBaseVO 是一致的")
+public class AppCategoryListRespVO {
+
+    @ApiModelProperty(value = "分类编号", required = true, example = "2")
+    private Long id;
+
+    @ApiModelProperty(value = "父分类编号", required = true, example = "1")
+    @NotNull(message = "父分类编号不能为空")
+    private Long parentId;
+
+    @ApiModelProperty(value = "分类名称", required = true, example = "办公文具")
+    @NotBlank(message = "分类名称不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "分类图标")
+    @NotBlank(message = "分类图标不能为空")
+    private String icon;
+
+    @ApiModelProperty(value = "分类图片", required = true)
+    @NotBlank(message = "分类图片不能为空")
+    private String bannerUrl;
+
+    @ApiModelProperty(value = "分类排序", required = true, example = "1")
+    private Integer sort;
+
+    @ApiModelProperty(value = "分类描述", required = true, example = "描述")
+    private String description;
+}

+ 45 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.product.controller.app.spu;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.SpuRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuRespVO;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "用户 APP -  商品spu")
+@RestController
+@RequestMapping("/product/spu")
+@Validated
+public class AppProductSpuController {
+
+    @Resource
+    private ProductSpuService spuService;
+
+    @GetMapping("/page")
+    @ApiOperation("获得商品spu分页")
+    public CommonResult<PageResult<AppSpuPageRespVO>> getSpuPage(@Valid AppSpuPageReqVO pageVO) {
+        return success(spuService.getSpuPage(pageVO));
+    }
+
+    @GetMapping("/")
+    @ApiOperation("获取商品 - 通过商品id")
+    public CommonResult<AppSpuRespVO> getSpu(@RequestParam("spuId") Long spuId) {
+        AppSpuRespVO appSpuRespVO = BeanUtil.toBean(spuService.getSpu(spuId), AppSpuRespVO.class);
+        return success(appSpuRespVO);
+    }
+}

+ 18 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("App - 商品spu分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppSpuPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "分类id")
+    private Long categoryId;
+}

+ 52 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageRespVO.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@ApiModel("App - 商品spu分页 Request VO")
+@Data
+public class AppSpuPageRespVO  {
+
+    @ApiModelProperty(value = "主键", required = true, example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "商品名称")
+    private String name;
+
+    @ApiModelProperty(value = "卖点", required = true)
+    @NotNull(message = "卖点不能为空")
+    private String sellPoint;
+
+    @ApiModelProperty(value = "描述", required = true)
+    @NotNull(message = "描述不能为空")
+    private String description;
+
+    @ApiModelProperty(value = "分类id", required = true)
+    @NotNull(message = "分类id不能为空")
+    private Long categoryId;
+
+    @ApiModelProperty(value = "商品主图地址,* 数组,以逗号分隔,最多上传15张", required = true)
+    @NotNull(message = "商品主图地址,* 数组,以逗号分隔,最多上传15张不能为空")
+    private List<String> picUrls;
+
+    @ApiModelProperty(value = "排序字段", required = true)
+    @NotNull(message = "排序字段不能为空")
+    private Integer sort;
+
+    @ApiModelProperty(value = "点赞初始人数")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "价格 单位使用:分")
+    private Integer price;
+
+    @ApiModelProperty(value = "库存数量")
+    private Integer quantity;
+
+    @ApiModelProperty(value = "上下架状态: 0 上架(开启) 1 下架(禁用)")
+    private Boolean status;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.product.controller.app.spu.vo;
+
+import cn.iocoder.yudao.module.product.controller.admin.spu.vo.SpuRespVO;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * <p>
+ *
+ * </p>
+ *
+ * @author LuoWenFeng
+ */
+@ApiModel("App - 商品spu Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AppSpuRespVO extends SpuRespVO {
+
+
+}

+ 2 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/category/CategoryConvert.java

@@ -4,6 +4,7 @@ import java.util.*;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
+import cn.iocoder.yudao.module.product.controller.app.category.vo.AppCategoryListRespVO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 import cn.iocoder.yudao.module.product.controller.admin.category.vo.*;
@@ -31,4 +32,5 @@ public interface CategoryConvert {
 
     List<CategoryExcelVO> convertList02(List<CategoryDO> list);
 
+    List<AppCategoryListRespVO> convertList03(List<CategoryDO> list);
 }

+ 7 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/spu/ProductSpuConvert.java

@@ -4,6 +4,8 @@ import java.util.*;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Named;
@@ -40,6 +42,11 @@ public interface ProductSpuConvert {
 
     List<SpuExcelVO> convertList02(List<ProductSpuDO> list);
 
+    SpuPageReqVO convert(AppSpuPageReqVO bean);
+
+    @Mapping(source = "picUrls", target = "picUrls", qualifiedByName = "tokenizeToStringArray")
+    AppSpuPageRespVO convertAppResp(ProductSpuDO list);
+
     @Named("tokenizeToStringArray")
     default List<String> translatePicUrlsArrayFromString(String picUrls) {
         return Arrays.asList(StringUtils.tokenizeToStringArray(picUrls, ","));

+ 8 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryService.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.service.category;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.product.controller.admin.category.vo.*;
+import cn.iocoder.yudao.module.product.controller.app.category.vo.AppCategoryListRespVO;
 import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
 
 import javax.validation.Valid;
@@ -83,4 +84,11 @@ public interface CategoryService {
      * @param categoryId 分类id
      */
     void validatedCategoryById(Long categoryId);
+
+    /**
+     * app端获得商品分类列表
+     *
+     * @return 商品分类列表
+     */
+    List<CategoryDO> getCategoryList();
 }

+ 11 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/category/CategoryServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.product.service.category;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -13,6 +14,7 @@ import org.springframework.validation.annotation.Validated;
 import javax.annotation.Resource;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
@@ -106,4 +108,13 @@ public class CategoryServiceImpl implements CategoryService {
         return categoryMapper.selectList(treeListReqVO);
     }
 
+    @Override
+    public List<CategoryDO> getCategoryList() {
+        return categoryMapper.selectList()
+                .stream()
+                .filter(v->v.getStatus().equals(CommonStatusEnum.ENABLE.getStatus()))
+                .collect(Collectors.toList());
+    }
+
+
 }

+ 11 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.product.service.spu;
 import java.util.*;
 import javax.validation.*;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 
@@ -67,4 +69,13 @@ public interface ProductSpuService {
      */
     List<ProductSpuDO> getSpuList(SpuExportReqVO exportReqVO);
 
+    /**
+     * 获得商品spu分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 商品spu分页
+     */
+    PageResult<AppSpuPageRespVO> getSpuPage(AppSpuPageReqVO pageReqVO);
+
+
 }

+ 46 - 1
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java

@@ -1,16 +1,22 @@
 package cn.iocoder.yudao.module.product.service.spu;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.ProductPropertyRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.property.vo.ProductPropertyViewRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.propertyvalue.vo.ProductPropertyValueRespVO;
+import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateReqVO;
 import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuRespVO;
 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
 import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
-import cn.iocoder.yudao.module.product.dal.dataobject.category.CategoryDO;
 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
 import cn.iocoder.yudao.module.product.service.category.CategoryService;
+import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -41,6 +47,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
     @Resource
     private ProductSkuService productSkuService;
 
+    @Resource
+    private ProductPropertyService productPropertyService;
+
     @Override
     @Transactional
     public Long createSpu(ProductSpuCreateReqVO createReqVO) {
@@ -104,6 +113,29 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         if (null != spuVO) {
             List<ProductSkuRespVO> skuReqs = ProductSkuConvert.INSTANCE.convertList(productSkuService.getSkusBySpuId(id));
             spuVO.setSkus(skuReqs);
+            List<ProductSkuRespVO.Property> properties = new ArrayList<>();
+            // 组合 sku 规格属性
+            for (ProductSkuRespVO productSkuRespVO : skuReqs) {
+                properties.addAll(productSkuRespVO.getProperties());
+            }
+            Map<Long, List<ProductSkuBaseVO.Property>> propertyMaps = properties.stream().collect(Collectors.groupingBy(ProductSkuBaseVO.Property::getPropertyId));
+            List<ProductPropertyRespVO> propertyAndValueList = productPropertyService.selectByIds(new ArrayList<>(propertyMaps.keySet()));
+            // 装载组装过后的属性
+            List<ProductPropertyViewRespVO> productPropertyViews = new ArrayList<>();
+            propertyAndValueList.forEach(p -> {
+                ProductPropertyViewRespVO productPropertyViewRespVO = new ProductPropertyViewRespVO();
+                productPropertyViewRespVO.setPropertyId(p.getId());
+                productPropertyViewRespVO.setName(p.getName());
+                Set<ProductPropertyViewRespVO.Tuple2> propertyValues = new HashSet<>();
+                Map<Long, ProductPropertyValueRespVO> propertyValueMaps = p.getPropertyValueList().stream().collect(Collectors.toMap(ProductPropertyValueRespVO::getId, pv -> pv));
+                propertyMaps.get(p.getId()).forEach(pv -> {
+                    ProductPropertyViewRespVO.Tuple2 tuple2 = new ProductPropertyViewRespVO.Tuple2(pv.getValueId(), propertyValueMaps.get(pv.getValueId()).getName());
+                    propertyValues.add(tuple2);
+                });
+                productPropertyViewRespVO.setPropertyValues(propertyValues);
+                productPropertyViews.add(productPropertyViewRespVO);
+            });
+            spuVO.setProductPropertyViews(productPropertyViews);
             // 组合分类
             if (null != spuVO.getCategoryId()) {
                 LinkedList<Long> categoryArray = new LinkedList<>();
@@ -144,4 +176,17 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         return ProductSpuMapper.selectList(exportReqVO);
     }
 
+    @Override
+    public PageResult<AppSpuPageRespVO> getSpuPage(AppSpuPageReqVO pageReqVO) {
+        PageResult<ProductSpuDO> productSpuDOPageResult = ProductSpuMapper.selectPage(ProductSpuConvert.INSTANCE.convert(pageReqVO));
+        PageResult<AppSpuPageRespVO> pageResult = new PageResult<>();
+        List<AppSpuPageRespVO> collect = productSpuDOPageResult.getList()
+                .stream()
+                .map(ProductSpuConvert.INSTANCE::convertAppResp)
+                .collect(Collectors.toList());
+        pageResult.setList(collect);
+        pageResult.setTotal(productSpuDOPageResult.getTotal());
+        return pageResult;
+    }
+
 }

+ 606 - 497
yudao-ui-admin/src/views/mall/product/spu/index.vue

@@ -25,9 +25,9 @@
           <el-option label="下架" value="1"/>
         </el-select>
       </el-form-item>
-      <el-form-item label="创建时间" prop="createTime">
-        <el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
-                        range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
+      <el-form-item label="创建时间">
+        <el-date-picker v-model="dateRangeCreateTime" style="width: 240px" value-format="yyyy-MM-dd"
+                        type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"/>
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
@@ -55,8 +55,8 @@
     <el-table v-loading="loading" :data="list">
       <el-table-column label="主键" align="center" prop="id"/>
       <el-table-column label="商品名称" align="center" prop="name"/>
-<!--      <el-table-column label="卖点" align="center" prop="sellPoint"/>-->
-<!--      <el-table-column label="描述" align="center" prop="description"/>-->
+      <!--      <el-table-column label="卖点" align="center" prop="sellPoint"/>-->
+      <!--      <el-table-column label="描述" align="center" prop="description"/>-->
       <el-table-column label="分类id" align="center" prop="categoryId"/>
       <el-table-column label="商品主图地址" align="center" prop="picUrls">
         <template slot-scope="scope">
@@ -67,7 +67,7 @@
       <el-table-column label="点赞初始人数" align="center" prop="likeCount"/>
       <el-table-column label="价格 (分)" align="center" prop="price"/>
       <el-table-column label="库存数量" align="center" prop="quantity"/>
-<!--      <el-table-column label="状态" align="center" prop="status"/>-->
+      <!--      <el-table-column label="状态" align="center" prop="status"/>-->
       <el-table-column label="创建时间" align="center" prop="createTime" width="180">
         <template slot-scope="scope">
           <span>{{ parseTime(scope.row.createTime) }}</span>
@@ -102,12 +102,12 @@
         </el-form-item>
         <el-form-item label="分类id" prop="categoryIds">
           <el-cascader
-            v-model="form.categoryIds"
-            placeholder="请输入分类id"
-            style="width: 100%"
-            :options="categoryList"
-            :props="propName"
-            clearable></el-cascader>
+              v-model="form.categoryIds"
+              placeholder="请输入分类id"
+              style="width: 100%"
+              :options="categoryList"
+              :props="propName"
+              clearable></el-cascader>
         </el-form-item>
         <el-form-item label="商品主图地址" prop="picUrls">
           <ImageUpload v-model="form.picUrls" :limit="10"/>
@@ -115,17 +115,17 @@
         <el-form-item label="商品规格">
           <el-button size="mini" @click="shopTagInput()">添加规格</el-button>
           <div v-for="(tag, tagIndex) in skuTags" :key="tagIndex">
-            <span>{{tag.name}}</span>
+            <span>{{ tag.name }}</span>
             <el-button style="margin-left: 10px" class="button-new-tag" type="text" icon="el-icon-delete"
                        @click="removeTag(tagIndex)">删除
             </el-button>
             <br/>
             <el-tag
-              v-for="(tagItem, tagItemIndex) in tag.selectValues"
-              :key="tagItem"
-              style="margin-right: 10px"
-              :disable-transitions="false">
-              {{tagItem}}
+                v-for="(tagItem, tagItemIndex) in tag.selectValues"
+                :key="tagItem"
+                style="margin-right: 10px"
+                :disable-transitions="false">
+              {{ tagItem }}
             </el-tag>
             <!--            <el-input-->
             <!--              class="input-new-tag"-->
@@ -143,10 +143,10 @@
             <el-select v-model="addTagInput.name" filterable allow-create default-first-option placeholder="请选择"
                        @change="handleTagClick">
               <el-option
-                v-for="item in unUseTags"
-                :key="item.id"
-                :label="item.name"
-                :value="item.name">
+                  v-for="item in unUseTags"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.name">
               </el-option>
             </el-select>
           </el-col>
@@ -156,10 +156,10 @@
             <el-select v-model="addTagInput.selectValues" multiple filterable allow-create default-first-option
                        placeholder="请选择">
               <el-option
-                v-for="item in dbTagValues"
-                :key="item.id"
-                :label="item.name"
-                :value="item.name">
+                  v-for="item in dbTagValues"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.name">
               </el-option>
             </el-select>
           </el-col>
@@ -170,13 +170,13 @@
         </el-form-item>
         <el-form-item v-if="form.skus.length>0">
           <el-table
-            :data="form.skus"
-            border
-            style="width: 100%; margin-top: 20px"
-            :span-method="tableSpanMethod">
+              :data="form.skus"
+              border
+              style="width: 100%; margin-top: 20px"
+              :span-method="tableSpanMethod">
             <el-table-column v-for="(leftTitle, index) in skuTags" :key="index" :label="leftTitle.name">
               <template slot-scope="scope">
-                {{scope.row.propertyChildNames[index]}}
+                {{ scope.row.propertyChildNames[index] }}
               </template>
             </el-table-column>
             <el-table-column v-if="skuTags.length"
@@ -189,62 +189,63 @@
               </template>
             </el-table-column>
             <el-table-column
-              prop="prodName"
-              label="条形码"
-              width="250" v-if="skuTags.length">
+                prop="prodName"
+                label="条形码"
+                width="250" v-if="skuTags.length">
               <template slot-scope="scope">
-                <el-input v-model="scope.row.barCode" type="textarea" :disabled="scope.row.status===1"></el-input>
+                <el-input v-model="scope.row.barCode" type="textarea" :disabled="scope.row.status==1"></el-input>
               </template>
             </el-table-column>
             <el-table-column
-              prop="price"
-              label="销售价">
+                prop="price"
+                label="销售价">
               <template slot-scope="scope">
                 <el-input-number
-                  size="small"
-                  v-model="scope.row.price"
-                  controls-position="right"
-                  :precision="2"
-                  :max="1000000000"
-                  :min="0.01"
-                  :disabled="scope.row.status===1">
+                    size="small"
+                    v-model="scope.row.price"
+                    controls-position="right"
+                    :precision="2"
+                    :max="1000000000"
+                    :min="0.01"
+                    :disabled="scope.row.status==1">
                 </el-input-number>
               </template>
             </el-table-column>
             <el-table-column
-              prop="oriPrice"
-              label="成本价">
+                prop="oriPrice"
+                label="成本价">
               <template slot-scope="scope">
                 <el-input-number
-                  size="small"
-                  v-model="scope.row.costPrice"
-                  controls-position="right"
-                  :precision="2"
-                  :max="1000000000"
-                  :min="0.01"
-                  :disabled="scope.row.status===1">
+                    size="small"
+                    v-model="scope.row.costPrice"
+                    controls-position="right"
+                    :precision="2"
+                    :max="1000000000"
+                    :min="0.01"
+                    :disabled="scope.row.status==1">
                 </el-input-number>
               </template>
             </el-table-column>
             <el-table-column
-              prop="oriPrice"
-              label="原价">
+                prop="oriPrice"
+                label="原价">
               <template slot-scope="scope">
                 <el-input-number
-                  size="small"
-                  v-model="scope.row.originalPrice"
-                  controls-position="right"
-                  :precision="2"
-                  :max="1000000000"
-                  :min="0.01"
-                  :disabled="scope.row.status===1">
+                    size="small"
+                    v-model="scope.row.originalPrice"
+                    controls-position="right"
+                    :precision="2"
+                    :max="1000000000"
+                    :min="0.01"
+                    :disabled="scope.row.status==1">
                 </el-input-number>
               </template>
             </el-table-column>
             <el-table-column
-              label="操作">
+                label="操作">
               <template slot-scope="scope">
-                <el-button type="text" size="small" @click="changeSkuStatus(`${scope.$index}`)" v-if="scope.row.status===0">
+                <el-button type="text" size="small" @click="changeSkuStatus(`${scope.$index}`)"
+                           v-if="scope.row.status===0">
                   正常
                 </el-button>
                 <el-button type="text" size="small" @click="changeSkuStatus(`${scope.$index}`)" v-else>已禁用</el-button>
@@ -280,456 +281,564 @@
 </template>
 
 <script>
-    import {createSpu, updateSpu, deleteSpu, getSpu, getSpuPage, exportSpuExcel} from "@/api/mall/product/spu";
-    import {
-        createCategory,
-        deleteCategory,
-        exportCategoryExcel,
-        getCategory,
-        listCategory,
-        updateCategory
-    } from "@/api/mall/product/category";
-    import {
-        createProperty,
-        updateProperty,
-        deleteProperty,
-        getProperty,
-        getPropertyPage,
-        exportPropertyExcel
-    } from "@/api/mall/product/property";
+import {createSpu, updateSpu, deleteSpu, getSpu, getSpuPage, exportSpuExcel} from "@/api/mall/product/spu";
+import {
+  createCategory,
+  deleteCategory,
+  exportCategoryExcel,
+  getCategory,
+  listCategory,
+  updateCategory
+} from "@/api/mall/product/category";
+import {
+  createProperty,
+  updateProperty,
+  deleteProperty,
+  getProperty,
+  getPropertyPage,
+  exportPropertyExcel
+} from "@/api/mall/product/property";
 
-    import Editor from '@/components/Editor';
-    import ImageUpload from '@/components/ImageUpload';
+import Editor from '@/components/Editor';
+import ImageUpload from '@/components/ImageUpload';
 
-    export default {
-        name: "Spu",
-        components: {
-            Editor, ImageUpload
-        },
-        data() {
-            return {
-                tableLeftTitles: [],
-                dbTagValues: [],
-                allhistoryTags: [],
-                unUseTags: [],
-                propertyPageList: [],
-                isShowTagInput: false,
-                addTagInput: {
-                    name: '',
-                    propertyId: '',
-                    selectValues: [],
-                    selectValueIds: [],
-                },
-                skuTags: [],
-                propName: {
-                    checkStrictly: true,
-                    label: 'name',
-                    value: 'id'
-                },
-                categoryList: [],
-                // 遮罩层
-                loading: true,
-                // 导出遮罩层
-                exportLoading: false,
-                // 显示搜索条件
-                showSearch: true,
-                // 总条数
-                total: 0,
-                // 商品spu列表
-                list: [],
-                // 弹出层标题
-                title: "",
-                // 是否显示弹出层
-                open: false,
-                // 查询参数
-                queryParams: {
-                    pageNo: 1,
-                    pageSize: 10,
-                    name: null,
-                    sellPoint: null,
-                    description: null,
-                    categoryId: null,
-                    picUrls: null,
-                    sort: null,
-                    likeCount: null,
-                    price: null,
-                    quantity: null,
-                    status: null,
-                    createTime: []
-                },
-                // 表单参数
-                form: {
-                    id: undefined,
-                    name: undefined,
-                    sellPoint: undefined,
-                    description: undefined,
-                    categoryId: undefined,
-                    categoryIds: [],
-                    picUrls: undefined,
-                    sort: undefined,
-                    likeCount: undefined,
-                    price: undefined,
-                    quantity: undefined,
-                    status: undefined,
-                    isShowTagInput: undefined,
-                    skus:[],
-                },
-                // 表单校验
-                rules: {
-                    sellPoint: [{required: true, message: "卖点不能为空", trigger: "blur"}],
-                    description: [{required: true, message: "描述不能为空", trigger: "blur"}],
-                    categoryIds: [{required: true, message: "分类id不能为空", trigger: "blur"}],
-                    picUrls: [{required: true, message: "商品主图地址", trigger: "blur"}],
-                    sort: [{required: true, message: "排序字段不能为空", trigger: "blur"}],
-                },
-                tagIndex:0,
-            };
-        },
-        created() {
+export default {
+  name: "Spu",
+  components: {
+    Editor, ImageUpload
+  },
+  data() {
+    return {
+      tableLeftTitles: [],
+      dbTagValues: [],
+      allhistoryTags: [],
+      unUseTags: [],
+      propertyPageList: [],
+      isShowTagInput: false,
+      addTagInput: {
+        name: '',
+        propertyId: '',
+        selectValues: [],
+        selectValueIds: [],
+        selectObect:[],
+      },
+      skuTags: [],
+      propName: {
+        checkStrictly: true,
+        label: 'name',
+        value: 'id'
+      },
+      categoryList: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 商品spu列表
+      list: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      dateRangeCreateTime: [],
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        name: null,
+        sellPoint: null,
+        description: null,
+        categoryId: null,
+        picUrls: null,
+        sort: null,
+        likeCount: null,
+        price: null,
+        quantity: null,
+        status: null,
+      },
+      // 表单参数
+      form: {
+        id: undefined,
+        name: undefined,
+        sellPoint: undefined,
+        description: undefined,
+        categoryId: undefined,
+        categoryIds: [],
+        picUrls: undefined,
+        sort: undefined,
+        likeCount: undefined,
+        price: undefined,
+        quantity: undefined,
+        status: undefined,
+        isShowTagInput: undefined,
+        skus: [],
+      },
+      // 表单校验
+      rules: {
+        sellPoint: [{required: true, message: "卖点不能为空", trigger: "blur"}],
+        description: [{required: true, message: "描述不能为空", trigger: "blur"}],
+        categoryIds: [{required: true, message: "分类id不能为空", trigger: "blur"}],
+        picUrls: [{required: true, message: "商品主图地址", trigger: "blur"}],
+        sort: [{required: true, message: "排序字段不能为空", trigger: "blur"}],
+      },
+      tagIndex: 0,
+    };
+  },
+  created() {
 
-            this.getList();
+    this.getList();
+    this.getPropertyPageList();
+    this.getListCategory();
+  },
+  methods: {
+    getTableSpecData() {
+      return this.value
+    },
+    tableSpanMethod({row, column, rowIndex, columnIndex}) {
 
-        },
-        methods: {
-            getTableSpecData() {
-                return this.value
-            },
-            tableSpanMethod({row, column, rowIndex, columnIndex}) {
+    },
+    changeSkuStatus(tagIndex) {
+      if (this.form.skus[tagIndex].status == 0) {
+        this.form.skus[tagIndex].status = 1;
+      } else {
+        this.form.skus[tagIndex].status = 0;
+      }
 
-            },
-            changeSkuStatus(tagIndex) {
-                if(this.form.skus[tagIndex].status === 0){
-                    this.form.skus[tagIndex].status = 1 ;
-                }else {
-                    this.form.skus[tagIndex].status = 0 ;
-                }
+    },
+    skuAddProdName() {
+      if (this.initing) {
+        return
+      }
+      let skuList = []
+      for (let i = 0; i < this.value.length; i++) {
+        const sku = Object.assign({}, this.value[i])
+        if (!sku.properties) {
+          return
+        }
+        sku.skuName = ''
+        let properties = sku.properties.split(';')
+        for (const propertiesKey in properties) {
+          sku.skuName += properties[propertiesKey].split(':')[1] + ' '
+        }
+        sku.prodName = this.prodName + ' ' + sku.skuName
+        skuList.push(sku)
+      }
+      this.$emit('input', skuList)
+    },
+    handleTagClose(tagIndex, tagItemIndex) {
 
-            },
-            skuAddProdName() {
-                if (this.initing) {
-                    return
-                }
-                let skuList = []
-                for (let i = 0; i < this.value.length; i++) {
-                    const sku = Object.assign({}, this.value[i])
-                    if (!sku.properties) {
-                        return
-                    }
-                    sku.skuName = ''
-                    let properties = sku.properties.split(';')
-                    for (const propertiesKey in properties) {
-                        sku.skuName += properties[propertiesKey].split(':')[1] + ' '
-                    }
-                    sku.prodName = this.prodName + ' ' + sku.skuName
-                    skuList.push(sku)
-                }
-                this.$emit('input', skuList)
-            },
-            handleTagClose(tagIndex, tagItemIndex) {
-            },
-            //确定添加sku规格
-            addTag() {
-                let skus = this.unUseTags.map(function (item, index) {
-                    return item.name
-                });
-                console.log("skus=="+JSON.stringify(skus))
-                let index = skus.indexOf(this.addTagInput.name);
-                console.log("index=="+index)
-                console.log("skus[index].id=="+this.unUseTags[index].id)
-                console.log("this.unUseTags[index].propertyValueList=="+JSON.stringify(this.unUseTags[index].propertyValueList))
-                this.addTagInput.propertyId = this.unUseTags[index].id;
-                for (let i = 0; i < this.addTagInput.selectValues.length; i++) {
-                    for (let j = 0; j < this.unUseTags[index].propertyValueList.length; j++) {
-                        if (this.addTagInput.selectValues[i] === this.unUseTags[index].propertyValueList[j].name) {
-                            this.addTagInput.selectValueIds.push(this.unUseTags[index].propertyValueList[j].id)
-                        }
-                    }
-                }
-                let addTagInput = JSON.parse(JSON.stringify(this.addTagInput))
-                console.log("addTagInput=="+JSON.stringify(addTagInput))
-                this.skuTags.push(addTagInput);
-                this.unUseTags.splice(index, 1);
-                this.isShowTagInput = false;
-                this.getTable();
-            },
-            getTable(){
-                this.form.skus=[];
-                let skuTags = JSON.parse(JSON.stringify(this.skuTags));
-                let sku1s = [];
-                let skuIds = [];
-                let propertyIds = [];
-                let propertyNames = [];
-                for (let i = 0; i < skuTags.length; i++) {
-                    sku1s.push(skuTags[i].selectValues);
-                   skuIds.push(skuTags[i].selectValueIds);
-                   propertyIds.push(skuTags[i].propertyId);
-                   propertyNames.push(skuTags[i].name);
-                }
-                let skuAll = sku1s.reduce((x,y) =>{
-                    let arr = [];
-                    x.forEach(m => y.forEach(y => arr.push(m.concat([y]))))
-                    return arr;
-                },[[]])
-                console.log(skuAll);
+    },
+    //确定添加sku规格
+    addTag() {
 
-                let skuIdAll = skuIds.reduce((x,y) =>{
-                    let arr = [];
-                    x.forEach(m => y.forEach(y => arr.push(m.concat([y]))))
-                    return arr;
-                },[[]])
-                console.log(skuIdAll);
-                for (let i = 0; i < skuAll.length; i++) {
-                    let han = {
-                        propertyNames:propertyNames,
-                        propertyIds:propertyIds,
-                        propertyChildNames:skuAll[i],
-                        propertyChildIds:skuIdAll[i],
-                        properties: [],
-                        picUrl: '',
-                        costPrice: '',
-                        originalPrice: '',
-                        spuId: '',
-                        prodName: '',
-                        price: '',
-                        barCode: '',
-                        status: '0',
-                    }
-                    this.form.skus.push(han);
-                }
-                this.form.skus.forEach(x=>{
-                    x.properties=[];
-                    for (let i = 0; i <x.propertyIds.length ; i++) {
-                        x.properties.push({
-                            propertyId:x.propertyIds[i],
-                            valueId:x.propertyChildIds[i]
-                        })
-                    }
-                })
+      let skus = this.unUseTags.map(function (item, index) {
+        return item.name
+      });
+      let index = skus.indexOf(this.addTagInput.name);
 
-                console.log("this.skus=="+JSON.stringify(this.form.skus))
-            },
-            hideTagInput() {
-                this.isShowTagInput = false;
-                this.addTagInput = {
-                    name: '',
-                    propertyId: '',
-                    selectValues: [],
-                    selectValueIds: [],
-                };
-            },
-            shopTagInput() {
-                if (this.unUseTags.length <= 0) {
-                    return this.$message.error("规格已经添加完毕")
-                }
-                this.isShowTagInput = true;
-                this.addTagInput = {
-                    name: '',
-                    propertyId: '',
-                    selectValues: [],
-                    selectValueIds: [],
-                };
-            },
-            //删除已选的规格
-            removeTag(row) {
-                let skus = this.allhistoryTags.map(function (item, index) {
-                    return item.name
-                })
-                let index = skus.indexOf(this.skuTags[row].name);
-                this.unUseTags.push(this.allhistoryTags[index]);
-                this.skuTags.splice(row, 1);
-                this.getTable();
-            },
-            handleTagClick(row) {
-                for (let i = 0; i < this.propertyPageList.length; i++) {
-                    if (row == this.propertyPageList[i].name) {
-                        this.dbTagValues = this.propertyPageList[i].propertyValueList
-                    }
-                }
-            },
-            /** 查询规格 */
-            getPropertyPageList() {
-                // 执行查询
-                getPropertyPage().then(response => {
-                    this.propertyPageList = response.data.list;
+      this.addTagInput.propertyId = this.unUseTags[index].id;
+      for (let i = 0; i < this.addTagInput.selectValues.length; i++) {
+        for (let j = 0; j < this.unUseTags[index].propertyValueList.length; j++) {
+          if (this.addTagInput.selectValues[i] === this.unUseTags[index].propertyValueList[j].name) {
+            this.addTagInput.selectValueIds.push(this.unUseTags[index].propertyValueList[j].id)
+            this.addTagInput.selectObect.push({
+              id:this.unUseTags[index].propertyValueList[j].id,
+              name:this.unUseTags[index].propertyValueList[j].name,
+            })
+          }
+        }
+      }
+      let addTagInput = JSON.parse(JSON.stringify(this.addTagInput))
+      this.skuTags.push(addTagInput);
 
-                    this.unUseTags = this.propertyPageList.map(function (item, index) {
-                        return item
-                    })
-                    this.allhistoryTags = JSON.parse(JSON.stringify(this.unUseTags));
-                    console.log(this.propertyPageList)
-                });
-            },
-            /** 查询分类 */
-            getListCategory() {
-                // 执行查询
-                listCategory().then(response => {
-                    this.categoryList = this.handleTree(response.data, "id", "parentId");
+      // if (this.skuTags.length > 1) {
+      this.skuTags = this.skuTags.sort((a, b) => a.propertyId - b.propertyId);
+      this.skuTags.forEach(function (item,index) {
+        item.selectObect = item.selectObect.sort((a, b) => a.id - b.id)
+      })
 
-                });
-            },
-            /** 查询列表 */
-            getList() {
-                this.loading = true;
-                // 执行查询
-                getSpuPage(this.queryParams).then(response => {
-                    this.list = response.data.list;
-                    this.total = response.data.total;
-                    this.loading = false;
-                });
-            },
-            /** 取消按钮 */
-            cancel() {
-                this.open = false;
-                this.reset();
-            },
-            /** 表单重置 */
-            reset() {
+      for (let i = 0; i <this.skuTags.length ; i++) {
+        let selectValueIds=[];
+        let selectValues=[];
+        for (let j = 0; j < this.skuTags[i].selectObect.length; j++) {
+          selectValueIds.push(this.skuTags[i].selectObect[j].id);
+          selectValues.push(this.skuTags[i].selectObect[j].name);
+        }
+        this.skuTags[i].selectValues = selectValues;
+        this.skuTags[i].selectValueIds = selectValueIds;
+      }
+
+      this.unUseTags.splice(index, 1);
+      this.isShowTagInput = false;
+      this.getTable();
+    },
+    getTable() {
+      this.form.skus = [];
+      let skuTags = JSON.parse(JSON.stringify(this.skuTags));
+      let sku1s = [];
+      let skuIds = [];
+      let propertyIds = [];
+      let propertyNames = [];
+      for (let i = 0; i < skuTags.length; i++) {
+        sku1s.push(skuTags[i].selectValues);
+        skuIds.push(skuTags[i].selectValueIds);
+        propertyIds.push(skuTags[i].propertyId);
+        propertyNames.push(skuTags[i].name);
+      }
 
-                this.form = {
-                    id: undefined,
-                    name: undefined,
-                    sellPoint: undefined,
-                    description: undefined,
-                    categoryId: undefined,
-                    categoryIds: [],
-                    picUrls: undefined,
-                    sort: undefined,
-                    likeCount: undefined,
-                    price: undefined,
-                    quantity: undefined,
-                    status: undefined,
-                    isShowTagInput: undefined,
-                    skus:[],
-                };
-                this.skuTags=[];
-                this.resetForm("form");
-            },
-            /** 搜索按钮操作 */
-            handleQuery() {
-                this.queryParams.pageNo = 1;
-                this.getList();
-            },
-            /** 重置按钮操作 */
-            resetQuery() {
-                this.dateRangeCreateTime = [];
-                this.resetForm("queryForm");
-                this.handleQuery();
-            },
-            /** 新增按钮操作 */
-            handleAdd() {
-                this.reset();
-                this.open = true;
-                this.title = "添加商品spu";
-                this.getListCategory();
-                this.getPropertyPageList();
-            },
-            /** 修改按钮操作 */
-            handleUpdate(row) {
-                this.reset();
-                getSpu(row.id).then(response => {
-                  console.log(">>>>>> response.data:" + JSON.stringify(response.data))
-                    let dataSpu = response.data;
-                    this.form = {
-                        id: dataSpu.id,
-                        name: dataSpu.name,
-                        sellPoint: dataSpu.sellPoint,
-                        description: dataSpu.sellPoint,
-                        categoryId: dataSpu.sellPoint,
-                        categoryIds: dataSpu.categoryIds,
-                        picUrls: dataSpu.picUrls,
-                        sort: dataSpu.sort,
-                        likeCount: dataSpu.likeCount,
-                        price: dataSpu.price,
-                        quantity: dataSpu.quantity,
-                        status: dataSpu.status,
-                        isShowTagInput:undefined,
-                        skus:dataSpu.skus
-                        // skus:dataSpu.productSkuRespVOS,
-                    };
-                    this.open = true;
-                    this.title = "修改商品spu";
-                });
-            },
+      let skuAll = sku1s.reduce((x, y) => {
+        let arr = [];
+        x.forEach(m => y.forEach(y => arr.push(m.concat([y]))))
+        return arr;
+      }, [[]])
 
-            /** 提交按钮 */
-            submitForm() {
-                console.log(this.form.picUrls.split(','));
-                this.$refs["form"].validate(valid => {
-                    if (!valid) {
-                        return;
-                    }
-                    this.form.picUrls = this.form.picUrls.split(',');
-                    this.form.categoryId = this.form.categoryIds[(this.form.categoryIds.length - 1)];
-                    this.form.status = Number(this.form.status);
-                    // 修改的提交
-                    if (this.form.id != null) {
-                        updateSpu(this.form).then(response => {
-                            this.$modal.msgSuccess("修改成功");
-                            this.open = false;
-                            this.getList();
-                        });
-                        return;
-                    }
-                    // 添加的提交
-                    createSpu(this.form).then(response => {
-                        this.$modal.msgSuccess("新增成功");
-                        this.open = false;
-                        this.getList();
-                    });
-                });
-            },
-            /** 删除按钮操作 */
-            handleDelete(row) {
-                const id = row.id;
-                this.$modal.confirm('是否确认删除商品spu编号为"' + id + '"的数据项?').then(function () {
-                    return deleteSpu(id);
-                }).then(() => {
-                    this.getList();
-                    this.$modal.msgSuccess("删除成功");
-                }).catch(() => {
-                });
-            },
-            /** 导出按钮操作 */
-            handleExport() {
-                // 处理查询参数
-                let params = {...this.queryParams};
-                params.pageNo = undefined;
-                params.pageSize = undefined;
-                // 执行导出
-                this.$modal.confirm('是否确认导出所有商品spu数据项?').then(() => {
-                    this.exportLoading = true;
-                    return exportSpuExcel(params);
-                }).then(response => {
-                    this.$download.excel(response, '商品spu.xls');
-                    this.exportLoading = false;
-                }).catch(() => {
-                });
-            }
+      let skuIdAll = skuIds.reduce((x, y) => {
+        let arr = [];
+        x.forEach(m => y.forEach(y => arr.push(m.concat([y]))))
+        return arr;
+      }, [[]])
+
+      for (let i = 0; i < skuAll.length; i++) {
+        let han = {
+          propertyNames: propertyNames,
+          propertyIds: propertyIds,
+          propertyChildNames: skuAll[i],
+          propertyChildIds: skuIdAll[i],
+          properties: [],
+          picUrl: '',
+          costPrice: '',
+          originalPrice: '',
+          spuId: '',
+          prodName: '',
+          price: '',
+          barCode: '',
+          status: '0',
         }
-    };
+        this.form.skus.push(han);
+      }
+      this.form.skus.forEach(x => {
+        x.properties = [];
+        for (let i = 0; i < x.propertyIds.length; i++) {
+          x.properties.push({
+            propertyId: x.propertyIds[i],
+            valueId: x.propertyChildIds[i]
+          })
+        }
+      })
+
+    },
+    hideTagInput() {
+      this.isShowTagInput = false;
+      this.addTagInput = {
+        name: '',
+        propertyId: '',
+        selectValues: [],
+        selectValueIds: [],
+        selectObect: [],
+      };
+    },
+    shopTagInput() {
+      if (this.unUseTags.length <= 0) {
+        return this.$message.error("规格已经添加完毕")
+      }
+      this.isShowTagInput = true;
+      this.addTagInput = {
+        name: '',
+        propertyId: '',
+        selectValues: [],
+        selectValueIds: [],
+        selectObect: [],
+      };
+    },
+    //删除已选的规格
+    removeTag(row) {
+      let skus = this.allhistoryTags.map(function (item, index) {
+        return item.name
+      })
+      let index = skus.indexOf(this.skuTags[row].name);
+      this.unUseTags.push(this.allhistoryTags[index]);
+      this.skuTags.splice(row, 1);
+      this.getTable();
+    },
+    handleTagClick(row) {
+      for (let i = 0; i < this.propertyPageList.length; i++) {
+        if (row == this.propertyPageList[i].name) {
+          this.dbTagValues = this.propertyPageList[i].propertyValueList
+        }
+      }
+    },
+    /** 查询规格 */
+    getPropertyPageList() {
+      // 执行查询
+      getPropertyPage().then(response => {
+        this.propertyPageList = response.data.list;
+
+        this.unUseTags = this.propertyPageList.map(function (item, index) {
+          return item
+        })
+        this.allhistoryTags = JSON.parse(JSON.stringify(this.unUseTags));
+      });
+    },
+    /** 查询分类 */
+    getListCategory() {
+      // 执行查询
+      listCategory().then(response => {
+        this.categoryList = this.handleTree(response.data, "id", "parentId");
+
+      });
+    },
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 处理查询参数
+      let params = {...this.queryParams};
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行查询
+      getSpuPage(params).then(response => {
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+
+      this.form = {
+        id: undefined,
+        name: undefined,
+        sellPoint: undefined,
+        description: undefined,
+        categoryId: undefined,
+        categoryIds: [],
+        picUrls: undefined,
+        sort: undefined,
+        likeCount: undefined,
+        price: undefined,
+        quantity: undefined,
+        status: undefined,
+        isShowTagInput: undefined,
+        skus: [],
+      };
+      this.skuTags = [];
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRangeCreateTime = [];
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加商品spu";
+      this.getPropertyPageList();
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id;
+      getSpu(id).then(response => {
+        let dataSpu = response.data;
+        this.form = {
+          id: dataSpu.id,
+          name: dataSpu.name,
+          sellPoint: dataSpu.sellPoint,
+          description: dataSpu.sellPoint,
+          categoryId: dataSpu.sellPoint,
+          categoryIds: dataSpu.categoryIds,
+          picUrls: dataSpu.picUrls,
+          sort: dataSpu.sort,
+          likeCount: dataSpu.likeCount,
+          price: dataSpu.price,
+          quantity: dataSpu.quantity,
+          status: dataSpu.status,
+          isShowTagInput: undefined,
+          skus: [],
+          skusList: dataSpu.skus,
+          productPropertyViews: dataSpu.productPropertyViews,
+          // skus:dataSpu.productSkuRespVOS,
+        };
+        this.getDataHandle();
+        this.open = true;
+        this.title = "修改商品spu";
+      });
+    },
+    getDataHandle() {
+      let that = this;
+      let productPropertyViews = JSON.parse(JSON.stringify(this.form.productPropertyViews));
+      productPropertyViews = productPropertyViews.sort((a, b) => a.propertyId - b.propertyId);
+      productPropertyViews.forEach(item => {
+        item.propertyValues = item.propertyValues.sort((a, b) => a.v1 - b.v1);
+      })
+      let skuIds = [];
+      for (let i = 0; i < productPropertyViews.length; i++) {
+        let han = {
+          name: productPropertyViews[i].name,
+          propertyId: productPropertyViews[i].propertyId,
+          selectValues: [],
+          selectValueIds: [],
+        }
+        for (let j = 0; j < productPropertyViews[i].propertyValues.length; j++) {
+          han.selectValues.push(productPropertyViews[i].propertyValues[j].v2);
+          han.selectValueIds.push(productPropertyViews[i].propertyValues[j].v1);
+        }
+        skuIds.push(han)
+      }
+      this.skuTags = skuIds;
+      this.unUseTags = this.allhistoryTags.filter((v) =>
+          skuIds.every((val) => val.name != v.name)
+      )
+      this.getHandleTable();
+    },
+    getHandleTable() {
+      this.form.skus = [];
+      let skuTags = JSON.parse(JSON.stringify(this.skuTags));
+      let sku1s = [];
+      let skuIds = [];
+      let propertyIds = [];
+      let propertyNames = [];
+      for (let i = 0; i < skuTags.length; i++) {
+        sku1s.push(skuTags[i].selectValues);
+        skuIds.push(skuTags[i].selectValueIds);
+        propertyIds.push(skuTags[i].propertyId);
+        propertyNames.push(skuTags[i].name);
+      }
+      let skuAll = sku1s.reduce((x, y) => {
+        let arr = [];
+        x.forEach(m => y.forEach(y => arr.push(m.concat([y]))))
+        return arr;
+      }, [[]])
+
+      let skuIdAll = skuIds.reduce((x, y) => {
+        let arr = [];
+        x.forEach(m => y.forEach(y => arr.push(m.concat([y]))))
+        return arr;
+      }, [[]])
+
+      for (let i = 0; i < skuAll.length; i++) {
+        let han = {
+          propertyNames: propertyNames,
+          propertyIds: propertyIds,
+          propertyChildNames: skuAll[i],
+          propertyChildIds: skuIdAll[i],
+          properties: this.form.skusList[i].properties,
+          picUrl: this.form.skusList[i].picUrl,
+          costPrice: this.form.skusList[i].costPrice,
+          originalPrice: this.form.skusList[i].originalPrice,
+          spuId: this.form.skusList[i].spuId,
+          prodName: this.form.skusList[i].prodName,
+          price: this.form.skusList[i].price,
+          barCode: this.form.skusList[i].barCode,
+          status: this.form.skusList[i].status,
+        }
+        this.form.skus.push(han);
+      }
+      this.form.skus.forEach(x => {
+        x.properties = [];
+        for (let i = 0; i < x.propertyIds.length; i++) {
+          x.properties.push({
+            propertyId: x.propertyIds[i],
+            valueId: x.propertyChildIds[i]
+          })
+        }
+      })
+
+    },
+    /** 提交按钮 */
+    submitForm() {
+
+      this.$refs["form"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+        this.form.picUrls = this.form.picUrls.split(',');
+        this.form.categoryId = this.form.categoryIds[(this.form.categoryIds.length - 1)];
+        this.form.status = Number(this.form.status);
+        // 修改的提交
+        if (this.form.id != null) {
+          updateSpu(this.form).then(response => {
+            this.$modal.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          });
+          return;
+        }
+        // 添加的提交
+        createSpu(this.form).then(response => {
+          this.$modal.msgSuccess("新增成功");
+          this.open = false;
+          this.getList();
+        });
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const id = row.id;
+      this.$modal.confirm('是否确认删除商品spu编号为"' + id + '"的数据项?').then(function () {
+        return deleteSpu(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      // 处理查询参数
+      let params = {...this.queryParams};
+      params.pageNo = undefined;
+      params.pageSize = undefined;
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行导出
+      this.$modal.confirm('是否确认导出所有商品spu数据项?').then(() => {
+        this.exportLoading = true;
+        return exportSpuExcel(params);
+      }).then(response => {
+        this.$download.excel(response, '商品spu.xls');
+        this.exportLoading = false;
+      }).catch(() => {
+      });
+    }
+  }
+};
 </script>
 <style lang="scss">
-  .app-container {
-    .el-tag + .el-tag {
-      margin-left: 10px;
-    }
+.app-container {
+  .el-tag + .el-tag {
+    margin-left: 10px;
+  }
 
-    .button-new-tag {
-      margin-left: 10px;
-      height: 32px;
-      line-height: 30px;
-      padding-top: 0;
-      padding-bottom: 0;
-    }
+  .button-new-tag {
+    margin-left: 10px;
+    height: 32px;
+    line-height: 30px;
+    padding-top: 0;
+    padding-bottom: 0;
+  }
 
-    .input-new-tag {
-      width: 90px;
-      margin-left: 10px;
-      vertical-align: bottom;
-    }
+  .input-new-tag {
+    width: 90px;
+    margin-left: 10px;
+    vertical-align: bottom;
+  }
 
-    .img-height {
-      height: 65px;
-    }
+  .img-height {
+    height: 65px;
   }
+}
 </style>

+ 5 - 0
yudao-ui-app/api/category.js

@@ -0,0 +1,5 @@
+//请求工具参考https://ext.dcloud.net.cn/plugin?id=392
+const { http } = uni.$u
+
+// 查询分类列表
+export const categoryListData = params => http.get('product/category/list', { params })

+ 8 - 0
yudao-ui-app/api/product.js

@@ -0,0 +1,8 @@
+//请求工具参考https://ext.dcloud.net.cn/plugin?id=392
+const { http } = uni.$u
+
+// 查询商品spu列表
+export const productSpuPage = params => http.get('product/spu/page', { params })
+
+// 查询商品
+export const productSpu = params => http.get('product/spu/', { params })

+ 10 - 2
yudao-ui-app/pages.json

@@ -12,6 +12,14 @@
 				"navigationBarTitleText": "分类"
 			}
 		},
+    {
+    	"path": "pages/category/product-list",
+    	"style": {
+        "navigationBarTitleText": "",
+        "navigationStyle": "custom",
+        "navigationBarTextStyle": "white"
+    	}
+    },
 		{
 			"path": "pages/cart/cart",
 			"style": {
@@ -112,7 +120,7 @@
 	"globalStyle": {
 		"navigationBarTextStyle": "black",
 		"navigationBarTitleText": "yudao-ui-app",
-		"navigationBarBackgroundColor": "#F8F8F8",
-		"backgroundColor": "#FFFFFF"
+		"navigationBarBackgroundColor": "#ffffff",
+		"backgroundColor": "#ffffff"
 	}
 }

+ 165 - 136
yudao-ui-app/pages/category/category.vue

@@ -1,176 +1,205 @@
 <template>
   <view class="container">
+    <!-- 搜索框 -->
     <view class="search-wrap">
-      <u-search placeholder="搜索" disabled height="32" :show-action="false" @click="handleSearchClick"></u-search>
+      <u-search placeholder="搜索" disabled height="32" bgColor="#f2f2f2" margin="0 20rpx" :show-action="false"
+        @click="handleSearchClick"></u-search>
     </view>
+
+    <!-- 分类内容 -->
     <view class="category-box">
-      <view class="box-left">
-        <view>
-          <view class="category-item" v-for="(item, index) in categoryList" :key="item.id">
-            <view class="item-title" :class="{ active: currentIndex === index }" @click="handleCategoryClick(index)">
-              <text>{{ item.name }}</text>
-            </view>
+      <!-- 左侧导航栏 -->
+      <scroll-view scroll-y="true" class='box-left'>
+        <view class="category-item" v-for="(item, index) in categoryList" :key="item.id">
+          <view class="item-title" :class="{ active: currentIndex === index }" @click="handleCategoryClick(index)">
+            <text>{{ item.name }}</text>
           </view>
         </view>
-      </view>
-      <view class="box-right">
-        <image class="category-image" :showLoading="true" :src="categoryList[currentIndex].image" width="530rpx" height="160rpx" @click="click"></image>
+      </scroll-view>
+
+      <!-- 右侧分类内容 -->
+      <scroll-view scroll-y="true" class="box-right">
+        <view class="category-image">
+          <image :showLoading="true" :src="categoryList[currentIndex].bannerUrl" mode='widthFix' @click="click"></image>
+        </view>
+
         <view class="sub-category-box" v-for="(item, index) in categoryList[currentIndex].children" :key="item.id">
           <view class="sub-category-header">
-            <view class="title">{{ item.title }}</view>
-            <view class="more">查看更多</view>
+            <view class="title">{{ item.name }}</view>
+            <view class="more" @click="handleCategory(item, 0)">查看更多</view>
+          </view>
+
+          <view class="sub-category-grid">
+            <u-grid col="3">
+              <u-grid-item v-for="(subItem, subIndex) in item.children" :key="subItem.id">
+                <view class="sub-category-item" @click="handleCategory(item, subIndex)">
+                  <u-icon name="photo" :size="80" v-if="subItem.bannerUrl === null"></u-icon>
+                  <image :src="item.bannerUrl" v-if="subItem.bannerUrl != null" mode='widthFix' />
+                  <text class="sub-category-title">{{ subItem.name }}</text>
+                </view>
+              </u-grid-item>
+            </u-grid>
           </view>
-          <u-grid class="sub-category-grid" col="3">
-            <u-grid-item v-for="(subItem, subIndex) in item.category" :key="subItem.id">
-              <view class="sub-category-item">
-                <u-icon name="photo" :size="80"></u-icon>
-                <text class="sub-category-title">{{ subItem.title }}</text>
-              </view>
-            </u-grid-item>
-          </u-grid>
         </view>
-      </view>
+      </scroll-view>
     </view>
   </view>
 </template>
 
 <script>
-export default {
-  data() {
-    return {
-      currentIndex: 0,
-      categoryList: []
-    }
-  },
-  onLoad() {
-    for (let i = 0; i < 10; i++) {
-      this.categoryList.push({
-        id: i,
-        image: 'https://cdn.uviewui.com/uview/swiper/swiper1.png',
-        name: '商品分类' + i,
-        children: [
-          {
-            id: 0,
-            title: '分类' + i + '-1',
-            category: [
-              {
-                id: 0,
-                image: '',
-                title: '分类' + i + '-1-1'
-              },
-              {
-                id: 2,
-                image: '',
-                title: '分类' + i + '-1-2'
-              },
-              {
-                id: 3,
-                image: '',
-                title: '分类' + i + '-1-3'
-              }
-            ]
-          },
-          {
-            id: 1,
-            title: '分类' + i + '-2',
-            category: [
-              {
-                id: 0,
-                image: '',
-                title: '分类' + i + '-2-1'
-              },
-              {
-                id: 2,
-                image: '',
-                title: '分类' + i + '-2-2'
-              },
-              {
-                id: 3,
-                image: '',
-                title: '分类' + i + '-2-3'
-              }
-            ]
-          }
-        ]
-      })
-    }
-  },
-  methods: {
-    handleSearchClick(e) {
-      uni.$u.route('/pages/search/search')
+  import { categoryListData } from '../../api/category';
+  import { handleTree, convertTree } from '../../utils/tree.js';
+
+  export default {
+    data() {
+      return {
+        currentIndex: 0,
+        categoryList: []
+      }
     },
-    handleCategoryClick(index) {
-      if (this.currentIndex !== index) {
-        this.currentIndex = index
+    onLoad() {
+      this.handleCategoryList();
+    },
+    methods: {
+      // 点击搜索框
+      handleSearchClick(e) {
+        uni.$u.route('/pages/search/search')
+      },
+      // 点击左侧导航栏
+      handleCategoryClick(index) {
+        if (this.currentIndex !== index) {
+          this.currentIndex = index
+        }
+      },
+      // 获取分类列表并构建树形结构
+      handleCategoryList() {
+        categoryListData().then(res => {
+          this.categoryList = handleTree(res.data, "id", "parentId");
+        })
+      },
+      handleCategory(item, index){
+        // console.log(item)
+        // console.log(index)
+        uni.navigateTo({
+          url:"./product-list?item="+encodeURIComponent(JSON.stringify(item))+"&index="+index
+        })
       }
     }
   }
-}
 </script>
 
 <style lang="scss" scoped>
+  .search-wrap {
+    background: #ffffff;
+    position: fixed;
+    top: 0;
+    left: 0;
+    box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.07);
+    padding: 20rpx 0;
+    width: 100%;
+    z-index: 3;
+  }
+
+  .category-box {
+    position: fixed;
+    display: flex;
+    overflow: hidden;
+    margin-top: 100rpx;
+    height: calc(100% - 100rpx);
+
+    .box-left {
+      width: 200rpx;
+      padding-top: 5rpx;
+      overflow: scroll;
+      z-index: 2;
+      background-color: #f2f2f2;
+
+      .category-item {
+        line-height: 80rpx;
+        height: 80rpx;
+        text-align: center;
+        color: #777;
 
-.search-wrap {
-  background: $custom-bg-color;
-  padding: 20rpx;
-}
-
-.category-box {
-  display: flex;
-  .box-left {
-    width: 200rpx;
-    padding-top: 20rpx;
-    border-right: $custom-border-style;
-    .category-item {
-      border-bottom: $custom-border-style;
-      padding: 20rpx 0;
-      .item-title {
-        padding-left: 30rpx;
-        font-size: 28rpx;
-        &.active {
-          border-left: 6rpx solid $u-primary;
-          font-weight: 700;
+        .item-title {
+          font-size: 28rpx;
+
+          &.active {
+            font-size: 28rpx;
+            font-weight: bold;
+            position: relative;
+            background: #fff;
+            color: $u-primary;
+          }
+
+          &.active::before {
+            position: absolute;
+            left: 0;
+            content: "";
+            width: 8rpx;
+            height: 32rpx;
+            top: 25rpx;
+            background: $u-primary;
+          }
         }
       }
     }
-  }
-  .box-right {
-    flex: 1;
-    .category-image {
-      width: 510rpx;
-      height: 160rpx;
-      padding: 20rpx;
-    }
 
-    .sub-category-box {
-      .sub-category-header {
-        @include flex-space-between;
-        padding: 30rpx 20rpx;
+    .box-right {
+      width: 550rpx;
+      height: 100%;
+      box-sizing: border-box;
+      z-index: 1;
 
-        .title {
-          font-size: 28rpx;
-          font-weight: 700;
-        }
-        .more {
-          font-size: 22rpx;
-          color: #939393;
+      .category-image {
+        width: 510rpx;
+        box-sizing: border-box;
+        overflow: hidden;
+        position: relative;
+        margin: 30rpx 20rpx 0;
+
+        image {
+          width: 100%;
         }
       }
 
-      .sub-category-grid {
-        padding: 0 15rpx;
+      .sub-category-box {
+        .sub-category-header {
+          @include flex-space-between;
+          padding: 20rpx 20rpx;
+
+          .title {
+            font-size: 28rpx;
+            font-weight: bolder;
+          }
+
+          .more {
+            font-size: 22rpx;
+            color: #939393;
+          }
+        }
+
+        .sub-category-grid {
+          padding: 0 15rpx;
+
+          .sub-category-item {
+            @include flex-center(column);
+            background: #fff;
 
-        .sub-category-item {
-          @include flex-center(column);
-          background: #fff;
+            image {
+              text-align: center;
+              width: 150rpx;
+              height: 150rpx;
+              line-height: 150rpx;
+              font-size: 0;
+            }
 
-          .sub-category-title {
-            margin: 15rpx 0;
-            font-size: 24rpx;
+            .sub-category-title {
+              margin: 15rpx 0;
+              font-size: 22rpx;
+            }
           }
         }
       }
     }
   }
-}
 </style>

+ 161 - 0
yudao-ui-app/pages/category/product-list.vue

@@ -0,0 +1,161 @@
+<template>
+  <view class="container">
+    <u-navbar :title="title" :autoBack="true" placeholder="true" titleStyle="font-size: 28rpx">
+    </u-navbar>
+    <view class="context">
+      <view class="tabs-top">
+        <u-tabs :list="categoryList" @click="changeTabs" :current="current" lineHeight="2" lineWidth="85rpx"
+          itemStyle="padding-left: 15px; padding-right: 15px; height: 85rpx;"></u-tabs>
+      </view>
+      <scroll-view scroll-y="true" class="product-list" enable-flex="true">
+        <view class="flex-box">
+          <block v-for="(item, index) in productList[current]" :key="index">
+            <view class="product-item">
+              <view class="product-image">
+                <image :src="item.picUrls[0]" mode='widthFix' />
+              </view>
+              <view class="product-button">
+                <view class="product-text">【{{ item.sellPoint }}】{{ item.name }}</view>
+                <view class="product-price-button">
+                  <text class="product-price">¥
+                    <text class="price-size">{{ towNumber(item.price) }}</text></text>
+                  <text class="product-like-ccount">销量 {{ item.likeCount }}</text>
+                </view>
+              </view>
+            </view>
+          </block>
+        </view>
+      </scroll-view>
+    </view>
+  </view>
+</template>
+
+<script>
+  import {
+    productSpuPage
+  } from '../../api/product';
+
+  export default {
+    data() {
+      return {
+        title: "",
+        current: 0,
+        categoryList: [],
+        productList: {}
+      }
+    },
+    onLoad(option) {
+      const item = JSON.parse(decodeURIComponent(option.item))
+      this.title = item.name
+      this.categoryList = item.children
+      this.handleProductSpu(option.index);
+    },
+    methods: {
+      changeTabs(item) {
+        if (item.index != this.current) {
+          this.handleProductSpu(item.index)
+        }
+      },
+      handleProductSpu(index) {
+        let param = {}
+        param.categoryId = this.categoryList[index].id
+        console.log(this.categoryList)
+        console.log(index)
+        productSpuPage(param).then(res => {
+          this.productList[index] = res.data.list
+          this.current = index
+        })
+      },
+      towNumber(val) {
+        return (val / 100).toFixed(2)
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .context {
+    width: 100vw;
+    position: fixed;
+    top: 160rpx;
+    left: 0;
+  }
+
+  .tabs-top {
+    position: relative;
+    top: 0;
+    width: 100%;
+    height: 85rpx;
+  }
+
+  .product-list {
+    position: relative;
+    background-color: #f2f2f2;
+    height: calc(100vh - 88rpx - 100rpx - var(--status-bar-height));
+    width: 100%;
+
+    .flex-box {
+      width: 730rpx;
+      margin: 0 auto;
+      @include flex;
+      flex-wrap: wrap;
+      justify-content: left;
+
+      .product-item {
+        width: 345rpx;
+        height: 450rpx;
+        background-color: #ffffff;
+        margin: 20rpx 10rpx 0;
+        border-radius: 20rpx;
+
+        .product-image {
+          width: 100%;
+          height: 300rpx;
+          overflow: hidden;
+          border-radius: 20rpx;
+          image {
+            width: 100%;
+          }
+        }
+
+        .product-button {
+          width: 330rpx;
+          margin: 15rpx auto 0;
+
+          .product-text {
+            font-size: 25rpx;
+            height: 70rpx;
+            overflow: hidden;
+            -webkit-line-clamp: 2;
+            text-overflow: ellipsis;
+            display: -webkit-box;
+            -webkit-box-orient: vertical;
+          }
+
+          .product-price-button {
+            font-size: 20rpx;
+            margin-top: 20rpx;
+
+            .product-price {
+              color: red;
+
+              .price-size {
+                font-size: 26rpx;
+              }
+            }
+
+            .product-like-ccount {
+              font-size: 16rpx;
+              margin-left: 10rpx;
+            }
+          }
+
+        }
+      }
+
+    }
+
+    // }
+
+  }
+</style>

+ 3 - 3
yudao-ui-app/pages/index/index.vue

@@ -169,8 +169,8 @@ export default {
     }
   },
   onLoad() {
-    //this.loadBannerData();
-    //this.loadNoticeData();
+    this.loadBannerData();
+    this.loadNoticeData();
   },
   methods: {
     loadBannerData() {
@@ -302,7 +302,7 @@ export default {
       margin: 10rpx;
       background: #ffffff;
       border-radius: 10rpx;
-      box-shadow: -1rpx 1rpx 2rpx #afd3f5, 1rpx 1rpx 0rpx #afd3f5;
+      box-shadow: 0rpx 6rpx 8rpx rgba(58,134,185,0.2);
       .prod-image {
         width: 224rpx;
         height: 224rpx;

+ 18 - 5
yudao-ui-app/pages/product/product.vue

@@ -43,7 +43,7 @@
     <u-popup :show="skuPopup" :round="10" :closeable="true" :closeOnClickOverlay="false" @close="skuPopup = false">
       <view class="sku-popup-slot">
         <view class="current-sku-info">
-          <u--image class="current-sku-img" :showLoading="true" :src="product.sku[currentSkuIndex].image" width="120rpx" height="120rpx"></u--image>
+          <u--image class="current-sku-img" :showLoading="true" :src="product.sku[currentSkuIndex].picUrl" width="120rpx" height="120rpx"></u--image>
           <view class="current-sku-desc">
             <view class="name">{{ product.sku[currentSkuIndex].desc }}</view>
             <custom-text-price color="red" size="12" intSize="18" :price="product.sku[currentSkuIndex].price"></custom-text-price>
@@ -178,6 +178,8 @@
 </template>
 
 <script>
+    import { productSpu } from '../../api/product';
+
 export default {
   data() {
     return {
@@ -194,19 +196,19 @@ export default {
         sku: [
           {
             id: 0,
-            image: 'https://cdn.uviewui.com/uview/album/1.jpg',
+            picUrl: 'https://cdn.uviewui.com/uview/album/1.jpg',
             price: 13.0,
             desc: '山不在高,有仙则名。'
           },
           {
             id: 1,
-            image: 'https://cdn.uviewui.com/uview/album/2.jpg',
+            picUrl: 'https://cdn.uviewui.com/uview/album/2.jpg',
             price: 11.0,
             desc: '水不在深,有龙则灵。'
           },
           {
             id: 2,
-            image: 'https://cdn.uviewui.com/uview/album/3.jpg',
+            picUrl: 'https://cdn.uviewui.com/uview/album/3.jpg',
             price: 10.0,
             desc: '斯是陋室,惟吾德馨。'
           }
@@ -302,7 +304,18 @@ export default {
     }
   },
   methods: {
-    loadProductData() {},
+    loadProductData() {
+      let param = {}
+      param.spuId =  this.product.id
+      productSpu(param).then(res => {
+        this.product.images = res.data.picUrls;
+        this.product.sku = res.data.skus;
+        this.product.desc = res.data.description.replace(/<[^>]*>/g,'');
+        this.product.price = res.data.price;
+        this.product.title = res.data.name;
+        console.log(res)
+      })
+    },
     handleSkuItemClick(index) {
       this.currentSkuIndex = index
     },

+ 59 - 0
yudao-ui-app/utils/tree.js

@@ -0,0 +1,59 @@
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ * @param {*} rootId 根Id 默认 0
+ */
+export function handleTree(data, id, parentId, children, rootId) {
+  id = id || 'id'
+  parentId = parentId || 'parentId'
+  children = children || 'children'
+  rootId = rootId || Math.min.apply(Math, data.map(item => {
+    return item[parentId]
+  })) || 0
+  //对源数据深度克隆
+  const cloneData = JSON.parse(JSON.stringify(data))
+  //循环所有项
+  const treeData = cloneData.filter(father => {
+    let branchArr = cloneData.filter(child => {
+      //返回每一项的子级数组
+      return father[id] === child[parentId]
+    });
+    branchArr.length > 0 ? father.children = branchArr : '';
+    //返回第一层
+    return father[parentId] === rootId;
+  });
+  return treeData !== '' ? treeData : data;
+}
+
+/**
+ * 树形结构进行删除深度不够的分支
+ * 目前只删除了不够三层的分支
+ * 对于高于三层的部分目前不做处理
+ * todo 暴力遍历,可用递归修改
+ * @param {*} data 树形结构
+ */
+export function convertTree(data) {
+   //对源数据深度克隆
+  const cloneData = JSON.parse(JSON.stringify(data))
+  // 遍历克隆数据,对源数据进行删除操作
+  for (let first = 0; first < cloneData.length; first++) {
+    for (let second = 0; second < cloneData[first].children.length; second++) {
+      for (let three = 0; three < cloneData[first].children[second].children.length; three++) {
+        if (data[first].children[second].children[three].children == undefined ||
+          data[first].children[second].children[three].children === 0) {
+          data[first].children[second].children.splice(second, 1);
+        }
+      }
+      if (data[first].children[second].children == undefined || data[first].children[second].children === 0) {
+        data[first].children.splice(second, 1);
+      }
+    }
+    if (data[first].children == undefined || data[first].children.length === 0) {
+      data.splice(first, 1);
+    }
+  }
+  return data;
+}