文章目录

  • 一、商品基本信息录入
    • 1.电商概念SPU与SKU
    • 2.商品分类
      • 2.1需求分析
      • 2.2前端
    • 3.商品介绍
      • 3.1富文本编辑器介绍
      • 3.2使用kindeditor
    • 4.选择商品分类
      • 4.1一级分类下拉选择框
      • 4.2二级分类下拉选择框
      • 4.3三级分类下拉选择框
      • 4.4读取模板ID
    • 5.品牌选择
    • 6.扩展属性
  • 二、图片上传
    • 1.分布式文件服务器FastDFS
      • 1.1什么是FastDFS
      • 1.2文件上传流程
      • 1.3文件下载流程
      • 1.4FastDFS入门小Demo
    • 2.商品图片上传
      • 2.1后端
      • 2.2前端
      • 2.3图片列表
      • 2.4移除图片
  • 三、商品规格
    • 1.规格选择
      • 1.1显示规格选项列表
      • 1.2保存选中规格选项
    • 2.SKU商品信息
      • 2.1实现思路
      • 2.2克隆
      • 2.3生成SKU列表(深克隆)
      • 2.4显示SKU列表
      • 2.5后端
    • 3.是否启用规格
      • 3.1前端
      • 3.2后端
  • 四、商品管理
    • 1.商品列表
      • 1.1后端
      • 1.2前端
    • 2.商品修改
      • 2.1基本信息读取
      • 2.2读取商品图片和扩展属性
      • 2.3读取商品规格
      • 2.4读取SKU数据
      • 2.5保存数据
      • 2.6页面跳转
    • 3.商品审核
    • 4.商品删除
  • 结语
    • 4.商品删除
  • 结语

一、商品基本信息录入

1.电商概念SPU与SKU

SPU = Standard Product Unit (标准产品单位)
SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

例如:iphone7就是一个SPU,与商家,与颜色、款式、套餐都无关。

SKU=stock keeping unit(库存量单位)
SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。

例如:纺织品中一个SKU通常表示:规格、颜色、款式。

2.商品分类

2.1需求分析

实现三级商品分类列表查询功能

进入页面首先显示所以一级分类,效果如下:

点击列表行的查询下级按钮,进入下级分类列表,同时更新面包屑导航

再次点击表行的查询下级按钮,进入三级分类列表,因为三级分类属于最后一级,所以在列表中不显示查询下级按钮,同时更新面包屑导航。

点击面包屑导航,可以进行返回操作。

这里后端和淘淘商城差不多,主要提供前端代码。

2.2前端

列表实现:

(1)修改itemCatService.js

//根据上级ID查询下级列表
this.findByParentId=function(parentId){return $http.get('../itemCat/findByParentId.do?parentId='+parentId);
}

(2)修改itemCatController.js

//根据上级ID显示下级列表
$scope.findByParentId=function(parentId){itemCatService.findByParentId(parentId).success(function(response){$scope.list=response;}            );
}

面包屑导航:

我们需要返回上级列表,需要通过点击面包屑来实现,修改itemCatController.js

$scope.grade=1;//默认为1级
//设置级别
$scope.setGrade=function(value){$scope.grade=value;
}
//读取列表
$scope.selectList=function(p_entity){          if($scope.grade==1){//如果为1级$scope.entity_1=null; $scope.entity_2=null;}     if($scope.grade==2){//如果为2级$scope.entity_1=p_entity; $scope.entity_2=null;}     if($scope.grade==3){//如果为3级$scope.entity_2=p_entity;     }       $scope.findByParentId(p_entity.id); //查询此级下级列表
}

修改列表的查询下级按钮,设定级别值后 显示列表

<span ng-if="grade!=3">                                         <button type="button" class="btn bg-olive btn-xs" ng-click="setGrade(grade+1);selectList(entity)">查询下级</button>
</span>

这里我们使用了ng-if指令,用于条件判断,当级别不等于3的时候才显示“查询下级”按钮

绑定面包屑:

<ol class="breadcrumb">                             <li><a href="#" ng-click="grade=1;selectList({id:0})">顶级分类列表</a></li><li><a href="#" ng-click="grade=2;selectList(entity_1)">{{entity_1.name}}</a></li><li><a href="#" ng-click="grade=3;selectList(entity_2)">{{entity_2.name}}</a></li>
</ol>

3.商品介绍

实现商品介绍的录入,要求使用富文本编辑器

3.1富文本编辑器介绍

富文本编辑器,Rich Text Editor, 简称 RTE, 它提供类似于 Microsoft Word 的编辑功能。常用的富文本编辑器:

KindEditor http://kindeditor.net/

UEditor http://ueditor.baidu.com/website/

CKEditor http://ckeditor.com/

3.2使用kindeditor

在页面中添加JS代码,用于初始化kindeditor

<script type="text/javascript">var editor;KindEditor.ready(function(K) {editor = K.create('textarea[name="content"]', {allowFileManager : true});});
</script>

allowFileManager 【是否允许浏览服务器已上传文件】 默认值是:false

提取kindeditor编辑器的内容:

$scope.entity.goodsDesc.introduction=editor.html();

清空kindeditor编辑器的内容:

editor.html('');//清空富文本编辑器

4.选择商品分类

在商品录入界面实现商品分类的选择(三级分类)效果如下:

当用户选择一级分类后,二级分类列表要相应更新,当用户选择二级分类后,三级列表要相应更新。三级分类选好后,模板Id要同时更新

4.1一级分类下拉选择框

在goodsController增加代码

//读取一级分类
$scope.selectItemCat1List=function(){itemCatService.findByParentId(0).success(function(response){$scope.itemCat1List=response; });
}

页面加载调用该方法

<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="selectItemCat1List()">

修改goods_edit.html一级分类下拉选择框

<select class="form-control" ng-model="entity.goods.category1Id" ng-options="item.id as item.name for item in itemCat1List"></select>

ng-options语法:作为表单传递的值 as 显示的内容 for 别名 in List

ng-options属性可以在表达式中使用数组或对象来自动生成一个select中的option列表。ng-options与ng-repeat很相似,很多时候可以用ng-repeat来代替ng-options。但是ng-options提供了一些好处,例如减少内存提高速度,以及提供选择框的选项来让用户选择。

4.2二级分类下拉选择框

在goodsController增加代码:

//读取二级分类
$scope.$watch('entity.goods.category1Id', function(newValue, oldValue) {          //根据选择的值,查询二级分类itemCatService.findByParentId(newValue).success(function(response){$scope.itemCat2List=response;                     });
});

$watch方法用于监控某个变量的值,当被监控的值发生变化,就自动执行相应的函数。

修改goods_edit.html中二级分类下拉框

<select class="form-control select-sm" ng-model="entity.goods.category2Id" ng-options="item.id as item.name for item in itemCat2List"></select>

4.3三级分类下拉选择框

在goodsController增加代码:

//读取三级分类
$scope.$watch('entity.goods.category2Id', function(newValue, oldValue) {          //根据选择的值,查询二级分类itemCatService.findByParentId(newValue).success(function(response){$scope.itemCat3List=response;                     });
});

修改goods_edit.html中三级分类下拉框

<select class="form-control select-sm" ng-model="entity.goods.category3Id" ng-options="item.id as item.name for item in itemCat3List"></select>

4.4读取模板ID

在goodsController增加代码:

//三级分类选择后  读取模板ID
$scope.$watch('entity.goods.category3Id', function(newValue, oldValue) {    itemCatService.findOne(newValue).success(function(response){$scope.entity.goods.typeTemplateId=response.typeId; //更新模板ID    });
});

在goods_edit.html显示模板ID

模板ID:{{entity.goods.typeTemplateId}}

5.品牌选择

在用户选择商品分类后,品牌列表要根据用户所选择的分类进行更新。具体的逻辑是根据用户选择的三级分类找到对应的商品类型模板,商品类型模板中存储了品牌的列表json数据。

在goodsController引入typeTemplateService 并新增代码

//模板ID选择后  更新品牌列表
$scope.$watch('entity.goods.typeTemplateId', function(newValue, oldValue) {    typeTemplateService.findOne(newValue).success(function(response){$scope.typeTemplate=response;//获取类型模板$scope.typeTemplate.brandIds= JSON.parse( $scope.typeTemplate.brandIds);//品牌列表});
});

添加品牌选择框

<select class="form-control" ng-model="entity.goods.brandId" ng-options="item.id as item.text for item in typeTemplate.brandIds"></select>

成果:

6.扩展属性

修改goodsController.js ,在用户更新模板ID时,读取模板中的扩展属性赋给商品的扩展属性。

//模板ID选择后  更新模板对象
$scope.$watch('entity.goods.typeTemplateId', function(newValue, oldValue) {    typeTemplateService.findOne(newValue).success(function(response){$scope.typeTemplate=response;//获取类型模板$scope.typeTemplate.brandIds= JSON.parse( $scope.typeTemplate.brandIds);//品牌列表$scope.entity.goodsDesc.customAttributeItems=JSON.parse( $scope.typeTemplate.customAttributeItems);//扩展属性});
});

修改goods_edit.html

<!--扩展属性-->
<div class="tab-pane" id="customAttribute"><div class="row data-type">                                <div ng-repeat="pojo in entity.goodsDesc.customAttributeItems"><div class="col-md-2 title">{{pojo.text}}</div><div class="col-md-10 data"><input class="form-control" ng-model="pojo.value" placeholder="{{pojo.text}}">             </div></div>                </div>
</div>

二、图片上传

1.分布式文件服务器FastDFS

1.1什么是FastDFS

FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。

Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。

Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver 没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将storage称为存储服务器。

服务端两个角色:

Tracker:管理集群,tracker 也可以实现集群。每个 tracker 节点地位平等。收集 Storage 集群的状态。

Storage:实际保存文件 Storage 分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。

1.2文件上传流程

客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

组名:文件上传后所在的 storage 组名称,在文件上传成功后有 storage 服务器返回,需要客户端自行保存。

虚拟磁盘路径:storage 配置的虚拟路径,与磁盘选项 store_path*对应。如果配置了store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。

数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。

文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

1.3文件下载流程

最简单的 FastDFS 架构:

1.4FastDFS入门小Demo

需求:将本地图片上传至图片服务器,再控制台打印url

创建Maven工程fastDFSdemo

由于FastDFS客户端jar包并没有在中央仓库中,所以需要使用下列命令手动安装jar包到Maven本地仓库(将jar包放到d盘setup目录)

mvn install:install-file -DgroupId=org.csource.fastdfs -DartifactId=fastdfs  -Dversion=1.2 -Dpackaging=jar -Dfile=d:\setup\fastdfs_client_v1.20.jar

pom.xml中引入

<dependency><groupId>org.csource.fastdfs</groupId><artifactId>fastdfs</artifactId><version>1.2</version>
</dependency>

(2)添加配置文件fdfs_client.conf ,将其中的服务器地址设置为192.168.25.133

# connect timeout in seconds
# default value is 30s
connect_timeout=30# network timeout in seconds
# default value is 30s
network_timeout=60# the base path to store log files
base_path=/home/fastdfs# tracker_server can ocur more than once, and tracker_server format is
#  "host:port", host can be hostname or ip address
tracker_server=192.168.25.133:22122#standard log level as syslog, case insensitive, value list:
### emerg for emergency
### alert
### crit for critical
### error
### warn for warning
### notice
### info
### debug
log_level=info# if use connection pool
# default value is false
# since V4.05
use_connection_pool = false# connections whose the idle time exceeds this time will be closed
# unit: second
# default value is 3600
# since V4.05
connection_pool_max_idle_time = 3600# if load FastDFS parameters from tracker server
# since V4.05
# default value is false
load_fdfs_parameters_from_tracker=false# if use storage ID instead of IP address
# same as tracker.conf
# valid only when load_fdfs_parameters_from_tracker is false
# default value is false
# since V4.05
use_storage_id = false# specify storage ids filename, can use relative or absolute path
# same as tracker.conf
# valid only when load_fdfs_parameters_from_tracker is false
# since V4.05
storage_ids_filename = storage_ids.conf#HTTP settings
http.tracker_server_port=80#use "#include" directive to include HTTP other settiongs
##include http.conf

(3)创建java类,main方法代码如下:

// 1、加载配置文件,配置文件中的内容就是 tracker 服务的地址。
ClientGlobal.init("D:/maven_work/fastDFS-demo/src/fdfs_client.conf");
// 2、创建一个 TrackerClient 对象。直接 new 一个。
TrackerClient trackerClient = new TrackerClient();
// 3、使用 TrackerClient 对象创建连接,获得一个 TrackerServer 对象。
TrackerServer trackerServer = trackerClient.getConnection();
// 4、创建一个 StorageServer 的引用,值为 null
StorageServer storageServer = null;
// 5、创建一个 StorageClient 对象,需要两个参数 TrackerServer 对象、StorageServer 的引用
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
// 6、使用 StorageClient 对象上传图片。
//扩展名不带“.”
String[] strings = storageClient.upload_file("D:/pic/benchi.jpg", "jpg",null);
// 7、返回数组。包含组名和图片的路径。
for (String string : strings) {System.out.println(string);
}

控制台输出如下结果:

group1

M00/00/00/wKgZhVkMP4KAZEy-AAA-tCf93Fo973.jpg

在浏览器输入:

http://192.168.25.133/group1/M00/00/00/wKgZhVkMP4KAZEy-AAA-tCf93Fo973.jpg即可查看刚刚上传的图片

2.商品图片上传

2.1后端

(1)pinyougou-common工程pom.xml引入依赖

<!-- 文件上传组件 -->
<dependency><groupId>org.csource.fastdfs</groupId><artifactId>fastdfs</artifactId>
</dependency>
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId>
</dependency>

(2)pinyougou-common工程创建FastDFSClient.java工具类

package util;import org.csource.common.NameValuePair;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient1;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;public class FastDFSClient {private TrackerClient trackerClient = null;private TrackerServer trackerServer = null;private StorageServer storageServer = null;private StorageClient1 storageClient = null;public FastDFSClient(String conf) throws Exception {if (conf.contains("classpath:")) {conf = conf.replace("classpath:", this.getClass().getResource("/").getPath());}ClientGlobal.init(conf);trackerClient = new TrackerClient();trackerServer = trackerClient.getConnection();storageServer = null;storageClient = new StorageClient1(trackerServer, storageServer);}/*** 上传文件方法* <p>Title: uploadFile</p>* <p>Description: </p>* @param fileName 文件全路径* @param extName 文件扩展名,不包含(.)* @param metas 文件扩展信息* @return* @throws Exception*/public String uploadFile(String fileName, String extName, NameValuePair[] metas) throws Exception {String result = storageClient.upload_file1(fileName, extName, metas);return result;}public String uploadFile(String fileName) throws Exception {return uploadFile(fileName, null, null);}public String uploadFile(String fileName, String extName) throws Exception {return uploadFile(fileName, extName, null);}/*** 上传文件方法* <p>Title: uploadFile</p>* <p>Description: </p>* @param fileContent 文件的内容,字节数组* @param extName 文件扩展名* @param metas 文件扩展信息* @return* @throws Exception*/public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) throws Exception {String result = storageClient.upload_file1(fileContent, extName, metas);return result;}public String uploadFile(byte[] fileContent) throws Exception {return uploadFile(fileContent, null, null);}public String uploadFile(byte[] fileContent, String extName) throws Exception {return uploadFile(fileContent, extName, null);}
}

(3)fdfs_client.conf 拷贝到pinyougou-shop-web工程config文件夹

(4)在pinyougou-shop-web工程springmvc.xml添加配置:

<!-- 配置多媒体解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><property name="defaultEncoding" value="UTF-8"></property><!-- 设定文件上传的最大值5MB,5*1024*1024 --><property name="maxUploadSize" value="5242880"></property>
</bean>

(5)新建UploadController.java

/*** 文件上传Controller* @author Administrator**/
@RestController
public class UploadController {@Value("${FILE_SERVER_URL}")private String FILE_SERVER_URL;//文件服务器地址@RequestMapping("/upload")public Result upload( MultipartFile file){               //1、取文件的扩展名String originalFilename = file.getOriginalFilename();String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);try {//2、创建一个 FastDFS 的客户端FastDFSClient fastDFSClient
= new FastDFSClient("classpath:config/fdfs_client.conf");//3、执行上传处理String path = fastDFSClient.uploadFile(file.getBytes(), extName);//4、拼接返回的 url 和 ip 地址,拼装成完整的 urlString url = FILE_SERVER_URL + path;           return new Result(true,url);            } catch (Exception e) {e.printStackTrace();return new Result(false, "上传失败");}     }
}

2.2前端

(1)创建uploadService.js

//文件上传服务层
app.service("uploadService",function($http){this.uploadFile=function(){var formData=new FormData();formData.append("file",file.files[0]);   return $http({method:'POST',url:"../upload.do",data: formData,headers: {'Content-Type':undefined},transformRequest: angular.identity});     }
});

anjularjs对于post和get请求默认的Content-Type header 是application/json。通过设置‘Content-Type’: undefined,这样浏览器会帮我们把Content-Type 设置为 multipart/form-data.

通过设置 transformRequest: angular.identity ,anjularjs transformRequest function 将序列化我们的formdata object.

(2)将uploadService服务注入到goodsController 中,同时记住引入js

//商品控制层(商家后台)
app.controller('goodsController' ,function($scope,$controller   ,goodsService,itemCatService,uploadService){<script type="text/javascript" src="../js/service/uploadService.js">  </script>
/*** 上传图片*/
$scope.uploadFile=function(){    uploadService.uploadFile().success(function(response) {           if(response.success){//如果上传成功,取出url$scope.image_entity.url=response.message;//设置文件地址}else{alert(response.message);}}).error(function() {           alert("上传发生错误");});
};

(3)修改图片上传窗口,调用上传方法,回显上传图片

<img  src="{{image_entity.url}}" width="200px" height="200px">

2.3图片列表

(1)在goodsController.js增加方法

$scope.entity={goods:{},goodsDesc:{itemImages:[]}};//定义页面实体结构
//添加图片列表
$scope.add_image_entity=function(){        $scope.entity.goodsDesc.itemImages.push($scope.image_entity);
}

(2)修改上传窗口的保存按钮

<button class="btn btn-success" ng-click="add_image_entity()" data-dismiss="modal" aria-hidden="true">保存</button>

(3)遍历图片列表

<tr ng-repeat="pojo in entity.goodsDesc.itemImages"><td>{{pojo.color}}</td><td><img alt="" src="{{pojo.url}}" width="100px" height="100px"></td><td><button type="button" class="btn btn-default" title="删除" ><i class="fa fa-trash-o"></i> 删除</button></td>
</tr>

2.4移除图片

在goodsController.js增加代码

//列表中移除图片
$scope.remove_image_entity=function(index){$scope.entity.goodsDesc.itemImages.splice(index,1);
}

修改列表中的删除按钮

<button type="button" class="btn btn-default" title="删除" ng-click="remove_image_entity($index)"><i class="fa fa-trash-o"></i> 删除</button>

三、商品规格

1.规格选择

显示规格及选项列表(复选框)如下图,并保存用户选择的结果

1.1显示规格选项列表

由于我们的模板中只记录了规格名称,而我们除了显示规格名称还是显示规格下的规格选项,所以我们需要在后端扩充方法。

(1)在pinyougou-sellergoods-interface的TypeTemplateService.java新增方法定义

/*** 返回规格列表* @return*/
public List<Map> findSpecList(Long id);

(2)在pinyougou-sellergoods-service的TypeTemplateServiceImpl.java新增方法

@Autowired
private TbSpecificationOptionMapper specificationOptionMapper;@Override
public List<Map> findSpecList(Long id) {//查询模板TbTypeTemplate typeTemplate = typeTemplateMapper.selectByPrimaryKey(id);List<Map> list = JSON.parseArray(typeTemplate.getSpecIds(), Map.class)  ;for(Map map:list){//查询规格选项列表TbSpecificationOptionExample example=new TbSpecificationOptionExample();com.pinyougou.pojo.TbSpecificationOptionExample.Criteria criteria = example.createCriteria();criteria.andSpecIdEqualTo( new Long( (Integer)map.get("id") ) );List<TbSpecificationOption> options = specificationOptionMapper.selectByExample(example);map.put("options", options);}       return list;
}

(3)在pinyougou-shop-web的TypeTemplateController.java新增方法

@RequestMapping("/findSpecList")
public List<Map> findSpecList(Long id){return typeTemplateService.findSpecList(id);
}

测试后端代码:

(4)前端代码:修改typeTemplateService.js

//查询规格列表
this.findSpecList=function(id){return $http.get('../typeTemplate/findSpecList.do?id='+id);
}

(5)修改goodsController.js

//查询规格列表
typeTemplateService.findSpecList(newValue).success(function(response){$scope.specList=response;}
);

(6)修改goods_edit.html页面

<div ng-repeat="pojo in specList"><div class="col-md-2 title">{{pojo.text}}</div><div class="col-md-10 data">         <span ng-repeat="option in pojo.options"><input  type="checkbox" >{{option.optionName}}      </span>  </div>
</div>

1.2保存选中规格选项

我们需要将用户选中的选项保存在tb_goods_desc表的specification_items字段中,定义json格式如下:

[{“attributeName”:”规格名称”,”attributeValue”:[“规格选项1”,“规格选项2”… ] } , … ]

(1)在baseController.js增加代码

//从集合中按照key查询对象
$scope.searchObjectByKey=function(list,key,keyValue){for(var i=0;i<list.length;i++){if(list[i][key]==keyValue){return list[i];}            }       return null;
}

(2)在goodsController.js增加代码

$scope.updateSpecAttribute=function($event,name,value){var object=$scope.searchObjectByKey($scope.entity.goodsDesc.specificationItems,"attributeName",name);if(object!=null){//有值说明这个规格名称中已有被选中的//判断是否勾选if($event.target.checked){object.attributeValue.push(value);//在这个规格名称中添加规格参数}else{//取消勾选    object.attributeValue.splice(object.attributeValue.indexOf(value),1);//移除选项//如果选项都取消了,将此条记录移除if(object.attributeValue.length==0){$scope.entity.goodsDesc.specificationItems.splice($scope.entity.goodsDesc.specificationItems.indexOf(object),1);}}}else{$scope.entity.goodsDesc.specificationItems.push({"attributeName":name,"attributeValue":[value]});//添加新的记录}
}

(3)在goods_edit.html调用方法

<input  type="checkbox" ng-click="updateSpecAttribute($event,pojo.text,option.optionName)">{{option.optionName}}

2.SKU商品信息

基于上一步我们完成的规格选择,根据选择的规格录入商品的SKU信息,当用户选择相应的规格,下面的SKU列表就会自动生成,如下图:

2.1实现思路

(1)我们先定义一个初始的不带规格名称的集合,只有一条记录。

(2)循环用户选择的规格,根据规格名称和已选择的规格选项对原集合进行扩充,添加规格名称和值,新增的记录数与选择的规格选项个数相同

2.2克隆

常规上我们把克隆分为浅克隆和深克隆。浅克隆是克隆结果随着被克隆的那个发生变化,而深克隆恰恰相反。

  1. 浅克隆:var a={}; b=a;
  2. 深克隆:var a={name:‘abc’} var b={name:‘abc’}

技巧:var b = JSON.parse(JSON.stringify(a));

后端同理

2.3生成SKU列表(深克隆)

(1)在goodsController.js实现创建sku列表的方法

//创建SKU列表
$scope.createItemList=function(){$scope.entity.itemList=[{spec:{},price:0,num:99999,status:'0',isDefault:'0' } ];//初始化var items = $scope.entity.goodsDesc.specificationItems;    for(var i=0;i<items.length;i++){$scope.entity.itemList = addColumn( $scope.entity.itemList,items[i].attributeName,items[i].attributeValue );}
}//添加列值——在原有记录上出现了新的规格名称,需要添加
//list——每条记录        columnName——规格名称    conlumnValues——规格值
addColumn=function(list,columnName,conlumnValues){var newList=[];//新的集合for(var i=0;i<list.length;i++){//先遍历原有的var oldRow=list[i];for(var j=0;j<conlumnValues.length;j++){var newRow= JSON.parse(JSON.stringify(oldRow));//深克隆newRow.spec[columnName]=conlumnValues[j];newList.push(newRow);}}return newList;
}

(2)在更新规格属性后调用生成SKU列表的方法

<input  type="checkbox" ng-click="updateSpecAttribute($event,pojo.text,option.optionName);createItemList()">{{option.optionName}}

效果如下:

2.4显示SKU列表

goods_edit.html页面上绑定SKU列表

<table class="table table-bordered table-striped table-hover dataTable"><thead><tr>                                           <th class="sorting" ng-repeat="item in entity.goodsDesc.specificationItems">{{item.attributeName}}</th><th class="sorting">价格</th><th class="sorting">库存</th><th class="sorting">是否启用</th><th class="sorting">是否默认</th></tr></thead><tbody><tr ng-repeat="pojo in entity.itemList">                                             <td ng-repeat="item in entity.goodsDesc.specificationItems">{{pojo.spec[item.attributeName]}}</td>                                                    <td><input class="form-control" ng-model="pojo.price"  placeholder="价格"></td><td><input class="form-control" ng-model="pojo.num" placeholder="库存数量"></td><td><input type="checkbox" ng-model="pojo.status" ng-true-value="1" ng-false-value="0" ></td><td><input type="checkbox" ng-model="pojo.isDefault" ng-true-value="1" ng-false-value="0">                                                    </td></tr></tbody>
</table>

成果:

2.5后端

(1)在GoodsServiceImpl添加属性

@Autowired
private TbItemMapper itemMapper;@Autowired
private TbBrandMapper brandMapper;@Autowired
private TbItemCatMapper itemCatMapper;@Autowired
private TbSellerMapper sellerMapper;

(2)修改GoodsServiceImpl的add方法,增加代码,实现对SKU商品信息的保存

/*** 增加*/
@Override
public void add(Goods goods) {goods.getGoods().setAuditStatus("0");       goodsMapper.insert(goods.getGoods());   //插入商品表goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());goodsDescMapper.insert(goods.getGoodsDesc());//插入商品扩展数据for(TbItem item :goods.getItemList()){//标题String title= goods.getGoods().getGoodsName();Map<String,Object> specMap = JSON.parseObject(item.getSpec());for(String key:specMap.keySet()){title+=" "+ specMap.get(key);}item.setTitle(title);     item.setGoodsId(goods.getGoods().getId());//商品SPU编号item.setSellerId(goods.getGoods().getSellerId());//商家编号item.setCategoryid(goods.getGoods().getCategory3Id());//商品分类编号(3级)item.setCreateTime(new Date());//创建日期item.setUpdateTime(new Date());//修改日期 //品牌名称TbBrand brand = brandMapper.selectByPrimaryKey(goods.getGoods().getBrandId());item.setBrand(brand.getName());//分类名称TbItemCat itemCat = itemCatMapper.selectByPrimaryKey(goods.getGoods().getCategory3Id());item.setCategory(itemCat.getName());        //商家名称TbSeller seller = sellerMapper.selectByPrimaryKey(goods.getGoods().getSellerId());item.setSeller(seller.getNickName());      //图片地址(取spu的第一个图片)List<Map> imageList = JSON.parseArray(goods.getGoodsDesc().getItemImages(), Map.class) ;if(imageList.size()>0){item.setImage ( (String)imageList.get(0).get("url"));}       itemMapper.insert(item);}
}

3.是否启用规格

在规格面板添加是否启用规格,当用户没有选择该项,将原来的规格面板和SKU列表隐藏,用户保存商品后只生成一个SKU.

3.1前端

goods_add.html添加复选框

<div class="col-md-2 title">是否启用规格</div>
<div class="col-md-10 data"><input type="checkbox"  ng-model="entity.goods.isEnableSpec" ng-true-value="1" ng-false-value="0">
</div>
</div>

用if指令控制规格面板与SKU列表的显示与隐藏

<div ng-if="entity.goods.isEnableSpec==1">......SKU表格部分
</div>

3.2后端

修改GoodsServiceImpl的add方法

/*** 增加*/
@Override
public void add(Goods goods) {goods.getGoods().setAuditStatus("0");       goodsMapper.insert(goods.getGoods());   //插入商品表goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());goodsDescMapper.insert(goods.getGoodsDesc());//插入商品扩展数据if("1".equals(goods.getGoods().getIsEnableSpec())){for(TbItem item :goods.getItemList()){//标题String title= goods.getGoods().getGoodsName();Map<String,Object> specMap = JSON.parseObject(item.getSpec());for(String key:specMap.keySet()){title+=" "+ specMap.get(key);}item.setTitle(title);setItemValus(goods,item);itemMapper.insert(item);}      }else{                  TbItem item=new TbItem();item.setTitle(goods.getGoods().getGoodsName());//商品KPU+规格描述串作为SKU名称item.setPrice( goods.getGoods().getPrice() );//价格         item.setStatus("1");//状态item.setIsDefault("1");//是否默认           item.setNum(99999);//库存数量item.setSpec("{}");          setItemValus(goods,item);                   itemMapper.insert(item);}
}private void setItemValus(Goods goods,TbItem item) {item.setGoodsId(goods.getGoods().getId());//商品SPU编号item.setSellerId(goods.getGoods().getSellerId());//商家编号item.setCategoryid(goods.getGoods().getCategory3Id());//商品分类编号(3级)item.setCreateTime(new Date());//创建日期item.setUpdateTime(new Date());//修改日期 //品牌名称TbBrand brand = brandMapper.selectByPrimaryKey(goods.getGoods().getBrandId());item.setBrand(brand.getName());//分类名称TbItemCat itemCat = itemCatMapper.selectByPrimaryKey(goods.getGoods().getCategory3Id());item.setCategory(itemCat.getName());//商家名称TbSeller seller = sellerMapper.selectByPrimaryKey(goods.getGoods().getSellerId());item.setSeller(seller.getNickName());//图片地址(取spu的第一个图片)List<Map> imageList = JSON.parseArray(goods.getGoodsDesc().getItemImages(), Map.class) ;if(imageList.size()>0){item.setImage ( (String)imageList.get(0).get("url"));}
}

四、商品管理

1.商品列表

在商家后台,显示该商家的商品列表信息,如下图:

1.1后端

修改pinyougou-shop-web工程的GoodsController.java的search方法

修改pinyougou-shop-web工程的GoodsController.java的search方法@RequestMapping("/search")public PageResult search(@RequestBody TbGoods goods, int page, int rows  ){//获取商家IDString sellerId = SecurityContextHolder.getContext().getAuthentication().getName();//添加查询条件 goods.setSellerId(sellerId);       return goodsService.findPage(goods, page, rows);
}

修改pinyougou-sellergoods-service 工程com.pinyougou.sellergoods.service.impl 的findPage方法,修改条件构建部分代码,将原来的模糊匹配修改为精确匹配

if(goods.getSellerId()!=null && goods.getSellerId().length()>0){//criteria.andSellerIdLike("%"+goods.getSellerId()+"%");criteria.andSellerIdEqualTo(goods.getSellerId());
}

1.2前端

1、循环列表:

<tr ng-repeat="entity in list"><td><input  type="checkbox"></td>                                         <td>{{entity.id}}</td><td>{{entity.goodsName}}</td><td>{{entity.price}}</td><td>{{entity.category1Id}}</td><td>{{entity.category2Id}}</td><td>{{entity.category3Id}}</td><td>{{entity.auditStatus}}</td>                                        <td class="text-center">                                          <button type="button" class="btn bg-olive btn-xs">修改</button>                  </td>
</tr>

2、显示状态:

修改goodsController.js,添加state数组

$scope.status=['未审核','已审核','审核未通过','关闭'];//商品状态

修改列表显示

{{status[entity.auditStatus]}}

3、显示分类:

我们现在的列表中的分类仍然显示ID

如何才能显示分类的名称呢?

方案一:在后端代码写关联查询语句,返回的数据中直接有分类名称。

方案二:在前端代码用ID去查询后端,异步返回商品分类名称。

我们目前采用方案二,因为商品分类并不多,我们可以直接通过前端存入内存,这样效率高很多,而不是每次都要去后端查一下

(1)修改goodsController

$scope.itemCatList=[];//商品分类列表
//加载商品分类列表
$scope.findItemCatList=function(){     itemCatService.findAll().success(function(response){                            for(var i=0;i<response.length;i++){$scope.itemCatList[response[i].id]=response[i].name;}});
}

代码解释:因为我们需要根据分类ID得到分类名称,所以我们将返回的分页结果以数组形式再次封装。

(2)修改goods.html ,增加初始化调用

<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="findItemCatList()"><td>{{itemCatList[entity.category1Id]}}</td>
<td>{{itemCatList[entity.category2Id]}}</td>
<td>{{itemCatList[entity.category3Id]}}</td>

4、条件查询:

根据状态和商品名称进行查询,修改goods.html

<div class="has-feedback">状态:<select ng-model="searchEntity.auditStatus"><option value="">全部</option>      <option value="0">未审核</option>    <option value="1">已审核</option>    <option value="2">审核未通过</option>    <option value="3">关闭</option>                                     </select>商品名称:<input ng-model="searchEntity.goodsName">                       <button class="btn btn-default" ng-click="reloadList()">查询</button>
</div>

2.商品修改

在商品列表页面点击修改,进入商品编辑页面,并传递参数商品ID,商品编辑页面接受该参数后从数据库中读取商品信息,用户修改后保存信息。

2.1基本信息读取

我们首选读取商品分类、商品名称、品牌,副标题,价格,商品介绍等信息

读这类信息需要我们知道商品ID,而我们知道一般.html后面是不好带参数的,这里我们用到AngularJS内置的./html可以传参的方法$location,后端代码这里很简单,不过多赘述

(1)在goodsController中引入$location服务

(2)修改goodsController 添加代码:

//查询实体
$scope.findOne=function(){         var id= $location.search()['id'];//获取参数值if(id==null){return ;}goodsService.findOne(id).success(function(response){$scope.entity= response;    //向富文本编辑器添加商品介绍editor.html($scope.entity.goodsDesc.introduction);});
}

$location.search()其实就是把页面所有的变量都集合到一个数组中

测试:地址栏输入

http://localhost:9102/admin/goods_edit.html#?id=商品ID

注意: ?前要加# ,则是angularJS的地址路由的书写形式

2.2读取商品图片和扩展属性

修改goodsController.js 的findOne

//显示图片列表
$scope.entity.goodsDesc.itemImages= JSON.parse($scope.entity.goodsDesc.itemImages);
//显示扩展属性
$scope.entity.goodsDesc.customAttributeItems=  JSON.parse($scope.entity.goodsDesc.customAttributeItems);

经过测试,我们发现扩展属性值并没有读取出来,这是因为与之前读取扩展属性名称发生冲突,读出来后被初始化了。我们需要改写代码, 添加判断,当用户没有传递id参数时再执行此逻辑

//监控模板ID ,读取品牌列表
$scope.$watch('entity.goods.typeTemplateId',function(newValue,oldValue){//读取品牌列表和扩展属性typeTemplateService.findOne(newValue).success(function(response){.......//如果没有ID,则加载模板中的扩展数据if($location.search()['id']==null){$scope.entity.goodsDesc.customAttributeItems = JSON.parse($scope.typeTemplate.customAttributeItems);//扩展属性    }               });.......
});

2.3读取商品规格

修改findOne

//规格             $scope.entity.goodsDesc.specificationItems=JSON.parse($scope.entity.goodsDesc.specificationItems);

这里我们要把复选框状态显示出来,用到了ng-checked,方法返回true则勾选。

<input  type="checkbox"                                ng-click="updateSpecAttribute($event,pojo.text,p.optionName);createSKUTable()"               ng-checked="checkAttributeValue(pojo.text,p.optionName)">{{p.optionName}}
//根据规格名称和选项名称返回是否被勾选
$scope.checkAttributeValue=function(specName,optionName){var items= $scope.entity.goodsDesc.specificationItems;var object= $scope.searchObjectByKey(items,'attributeName',specName);if(object==null){return false;}else{if(object.attributeValue.indexOf(optionName)>=0){return true;}else{return false;}}
}

2.4读取SKU数据

在GoodsServiceImpl的findOne方法中加载SKU商品数据

//查询SKU商品列表
TbItemExample example=new TbItemExample();
com.pinyougou.pojo.TbItemExample.Criteria criteria = example.createCriteria();
criteria.andGoodsIdEqualTo(id);//查询条件:商品ID
List<TbItem> itemList = itemMapper.selectByExample(example);
goods.setItemList(itemList);

在goodsController.js修改findOne方法的代码

//SKU列表规格列转换——集合数组需要遍历转换
for( var i=0;i<$scope.entity.itemList.length;i++ ){$scope.entity.itemList[i].spec = JSON.parse( $scope.entity.itemList[i].spec);
}

成果:

2.5保存数据

修改pinyougou-sellergoods-service的GoodsServiceImpl ,将SKU列表插入的代码提取出来,封装到私有方法中

/*** 插入SKU列表数据* @param goods*/
private void saveItemList(Goods goods) {if("1".equals(goods.getGoods().getIsEnableSpec())){for(TbItem item :goods.getItemList()){//标题String title= goods.getGoods().getGoodsName();Map<String,Object> specMap = JSON.parseObject(item.getSpec());for(String key:specMap.keySet()){title+=" "+ specMap.get(key);}item.setTitle(title);setItemValus(goods,item);itemMapper.insert(item);}      }else{                  TbItem item=new TbItem();item.setTitle(goods.getGoods().getGoodsName());//商品KPU+规格描述串作为SKU名称item.setPrice( goods.getGoods().getPrice() );//价格         item.setStatus("1");//状态item.setIsDefault("1");//是否默认           item.setNum(99999);//库存数量item.setSpec("{}");          setItemValus(goods,item);                   itemMapper.insert(item);}
}

接下来,我们修改update方法,实现修改

public void update(Goods goods){goods.getGoods().setAuditStatus("0");//设置未申请状态:如果是经过修改的商品,需要重新设置状态goodsMapper.updateByPrimaryKey(goods.getGoods());//保存商品表goodsDescMapper.updateByPrimaryKey(goods.getGoodsDesc());//保存商品扩展表//删除原有的sku列表数据      TbItemExample example=new TbItemExample();com.pinyougou.pojo.TbItemExample.Criteria criteria = example.createCriteria();criteria.andGoodsIdEqualTo(goods.getGoods().getId()); itemMapper.deleteByExample(example);//添加新的sku列表数据saveItemList(goods);//插入商品SKU列表数据
}

修改GoodsController.java

@RequestMapping("/update")
public Result update(@RequestBody Goods goods){//校验是否是当前商家的id      Goods goods2 = goodsService.findOne(goods.getGoods().getId());//获取当前登录的商家IDString sellerId = SecurityContextHolder.getContext().getAuthentication().getName();//如果传递过来的商家ID并不是当前登录的用户的ID,则属于非法操作if(!goods2.getGoods().getSellerId().equals(sellerId) ||  !goods.getGoods().getSellerId().equals(sellerId) ){return new Result(false, "操作非法");       }       try {goodsService.update(goods);return new Result(true, "修改成功");} catch (Exception e) {e.printStackTrace();return new Result(false, "修改失败");}
}

代码解释:出于安全考虑,在商户后台执行的商品修改,必须要校验提交的商品属于该商户

修改goodsController.js ,新增保存的方法

//保存
$scope.save=function(){            //提取文本编辑器的值$scope.entity.goodsDesc.introduction=editor.html(); var serviceObject;//服务层对象               if($scope.entity.goods.id!=null){//如果有IDserviceObject=goodsService.update( $scope.entity ); //修改  }else{serviceObject=goodsService.add( $scope.entity  );//增加 }              serviceObject.success(function(response){if(response.success){alert('保存成功');                  $scope.entity={};editor.html("");}else{alert(response.message);}}        );
}

2.6页面跳转

(1)由商品列表页跳转到商品编辑页

<a href="goods_edit.html#?id={{entity.id}}" class="btn bg-olive btn-xs">修改</a>

(2)由商品编辑页跳转到商品列表

<a href="goods.html" class="btn btn-default">返回列表</a>

(3)保存成功后返回列表页面

if(response.success){                    location.href="goods.html";//跳转到商品列表页
}

3.商品审核

待审核商品列表:

<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="searchEntity={auditStatus:'0'};findItemCatList()">

需求:商品审核的状态值为1,驳回的状态值为2 。用户在列表中选中ID后,点击审核或驳回,修改商品状态,并刷新列表。

后端略

(1)修改pinyougou-manager-web的goodsService.js ,增加方法

//更改状态
this.updateStatus=function(ids,status){return $http.get('../goods/updateStatus.do?ids='+ids+"&status="+status);
}

(2)修改pinyougou-manager-web的goodsController.js ,增加方法

//更改状态
$scope.updateStatus=function(status){      goodsService.updateStatus($scope.selectIds,status).success(function(response){if(response.success){//成功$scope.reloadList();//刷新列表$scope.selectIds=[];//清空ID集合}else{alert(response.message);}});
}

(3)修改pinyougou-manager-web的goods.html 页面,为复选框绑定事件指令

<input  type="checkbox" ng-click="updateSelection($event,entity.id)" ><button type="button" class="btn btn-default" title="审核通过" ng-click="updateStatus('1')"><i class="fa fa-check"></i> 审核通过</button>
<button type="button" class="btn btn-default" title="驳回" ng-click="updateStatus('2')" ><i class="fa fa-ban"></i> 驳回</button>

4.商品删除

我们为商品管理提供商品删除功能,用户选中部分商品,点击删除按钮即可实现商品删除。注意,这里的删除并非是物理删除,而是修改tb_goods表的is_delete字段为1 ,我们可以称之为“逻辑删除”

修改pinyougou-sellergoods-service工程的GoodsServiceImpl.java的delete方法

/*** 批量删除*/
@Override
public void delete(Long[] ids) {for(Long id:ids){TbGoods goods = goodsMapper.selectByPrimaryKey(id);goods.setIsDelete("1");goodsMapper.updateByPrimaryKey(goods);}
}

排除已删除记录——修改pinyougou-sellergoods-service工程GoodsServiceImpl.java的findPage方法,添加以下代码:

criteria.andIsDeleteIsNull();//非删除状态

结语

不知道从什么时候开始,效率越来越差了இ௰இ

品优购系列可能要鸽几天了,建模搞得头疼

企业WEB项目商品管理图片上传相关推荐

  1. 电商生鲜网站开发(四)——后台开发:商品模块-图片上传/多条件拼接sql

    电商生鲜网站开发(四)--后台开发:商品模块-图片上传/多条件拼接sql 增加商品 上传图片 更新商品 删除商品 批量上下架 图片上传功能 文件名UUID 通用唯一识别码(Universally Un ...

  2. 详细阐述Web开发中的图片上传问题

    Web开发中,图片上传是一种极其常见的功能.但是呢,每次做上传,都花费了不少时间. 一个"小功能"花费我这么多时间,真心不愉快. So,要得认真分析下原因. 1.在最初学习Java ...

  3. XX健康:预约管理-套餐管理图片上传与预览Redis实现定时清理垃圾图片

    1. 新增套餐 1.1 需求分析 套餐其实就是检查组的集合,例如有一个套餐为"入职体检套餐",这个体检套餐可以包括多个检查组:一般检查.血常规.尿常规.肝功三项等.所以在添加套餐时 ...

  4. Springboot+vue前后端分离(所有项目通用)-实现图片上传

    前情说明:本代码暂时只供本人实用,可以实现效果,如大家有觉得需要改进或者可以用到的地方,可以随时给我建议和意见 谢谢 前端vue 按钮 <el-buttonv-model="form. ...

  5. 将idea的web项目打包war并上传到linux服务器

    一.使用maven将你写好的web项目打包成war 1.点击idea右侧的maven,并双击lifecycle中package 2. 然后静待war包生成,war包会生成在项目的target文件夹中, ...

  6. 项目中关于图片上传与剪裁的流程

    1.文件的上传主要是基于<ipt type="file">的属性 点击(点击行为则是通过在ipt上绑定ref="名字"属性获取元素,然后在点击的时候 ...

  7. 06_04_任务一:拉勾教育后台管理系统[课程管理模块、图片上传、 BeanUtils封装实体类](SSM)

    拉勾教育后台管理系统(SSM) 1. 项目架构 1.1 项目介绍 ​ 拉勾教育后台管理系统,是提供给拉勾教育的相关业务人员使用的一个后台管理系统, 业务人员可以在 这个后台管理系统中,对课程信息.广告 ...

  8. JAVA项目中如何实现图片上传?

    JAVA项目中实现图片上传 **前端div** <div class="layui-form-item"><label class="layui-for ...

  9. PHPMySQL图片上传及管理

    PHP&MySQL图片上传及管理 图片上传 HTML表单代码 <form action="uploadpic.php" method="post" ...

最新文章

  1. Vue父子组件通信小总结
  2. SAP最佳业务实践:重复制造(149)-4发料
  3. 【转】Docker —— 从入门到实践
  4. 挖掘频繁模式、关联和相关
  5. Python学习笔记:返回函数
  6. 机器人学习--智能移动机器人的有关技术演讲(浙大-熊蓉教授-2021年)
  7. C#,JAVA各版本之Thread.join()详解
  8. Streams全库复制
  9. WeWork通过向225,000个社区征税来拼命地从Meetup.com榨取现金
  10. Android 自定义环形圆形显示统计数据z
  11. ArcGIS10.5资源分享
  12. 数据分析第一步 | 做好数据埋点
  13. 网页加载出现没有合适的负载均衡器_一篇文章彻底了解清楚什么是负载均衡
  14. JMeter工具:场景设计,场景设置,场景运行,性能参数配置,测试监控
  15. Spring Boot:项目前端vue环境搭建
  16. 随手写了一段C++访问LDAP, 并且获取sid的代码
  17. Redis知识点整理(详讲)
  18. 伽马变换的原理以及python实现
  19. Linux环境下几种常用的文件系统
  20. GAMES101-现代计算机图形学学习笔记(作业01)

热门文章

  1. 分布式架构系统拆分原则、需求、微服务拆分步骤
  2. 光学变焦与数码变焦、光学防抖与电子防抖区别
  3. 国产ARM+FPGA+AD低成本高性能数据采集卡方案
  4. flash制作心得体会
  5. 实战演练 | 如何在数据库中创建模型
  6. 集合框架和泛型的学习(非常重要,day12)
  7. 长尾理论在网络营销中的应用
  8. 成员函数指针与高性能的C++委托 (Member Function Pointers and the Fastest Possible C++ Delegates)...
  9. dotnetty java netty,Netty(DotNetty)原理解析
  10. 基于web的救助捐赠系统的设计与实现060945毕业设计源码