EverydayOneCat
Cat学会用黑屏吓唬人。
一、商品基本信息录入
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
1 | //根据上级ID查询下级列表 |
(2)修改itemCatController.js
1 | //根据上级ID显示下级列表 |
面包屑导航:
我们需要返回上级列表,需要通过点击面包屑来实现,修改itemCatController.js
1 | $scope.grade=1;//默认为1级 |
修改列表的查询下级按钮,设定级别值后 显示列表
1 | <span ng-if="grade!=3"> |
这里我们使用了ng-if指令,用于条件判断,当级别不等于3的时候才显示“查询下级”按钮
绑定面包屑:
1 | <ol class="breadcrumb"> |
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
1 | <script type="text/javascript"> |
allowFileManager 【是否允许浏览服务器已上传文件】 默认值是:false
提取kindeditor编辑器的内容:
1 | $scope.entity.goodsDesc.introduction=editor.html(); |
清空kindeditor编辑器的内容:
1 | editor.html('');//清空富文本编辑器 |
4.选择商品分类
在商品录入界面实现商品分类的选择(三级分类)效果如下:
当用户选择一级分类后,二级分类列表要相应更新,当用户选择二级分类后,三级列表要相应更新。三级分类选好后,模板Id要同时更新
4.1一级分类下拉选择框
在goodsController增加代码
1 | //读取一级分类 |
页面加载调用该方法
1 | <body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="selectItemCat1List()"> |
修改goods_edit.html一级分类下拉选择框
1 | <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增加代码:
1 | //读取二级分类 |
$watch方法用于监控某个变量的值,当被监控的值发生变化,就自动执行相应的函数。
修改goods_edit.html中二级分类下拉框
1 | <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增加代码:
1 | //读取三级分类 |
修改goods_edit.html中三级分类下拉框
1 | <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增加代码:
1 | //三级分类选择后 读取模板ID |
在goods_edit.html显示模板ID
1 | 模板ID:{{entity.goods.typeTemplateId}} |
5.品牌选择
在用户选择商品分类后,品牌列表要根据用户所选择的分类进行更新。具体的逻辑是根据用户选择的三级分类找到对应的商品类型模板,商品类型模板中存储了品牌的列表json数据。
在goodsController引入typeTemplateService 并新增代码
1 | //模板ID选择后 更新品牌列表 |
添加品牌选择框
1 | <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时,读取模板中的扩展属性赋给商品的扩展属性。
1 | //模板ID选择后 更新模板对象 |
修改goods_edit.html
1 | <!--扩展属性--> |
二、图片上传
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目录)
1 | 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中引入
1 | <dependency> |
(2)添加配置文件fdfs_client.conf ,将其中的服务器地址设置为192.168.25.133
1 | # connect timeout in seconds |
(3)创建java类,main方法代码如下:
1 | // 1、加载配置文件,配置文件中的内容就是 tracker 服务的地址。 |
控制台输出如下结果:
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引入依赖
1 | <!-- 文件上传组件 --> |
(2)pinyougou-common工程创建FastDFSClient.java工具类
1 | package util; |
(3)fdfs_client.conf 拷贝到pinyougou-shop-web工程config文件夹
(4)在pinyougou-shop-web工程springmvc.xml添加配置:
1 | <!-- 配置多媒体解析器 --> |
(5)新建UploadController.java
1 | /** |
2.2前端
(1)创建uploadService.js
1 | //文件上传服务层 |
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
1 | //商品控制层(商家后台) |
1 | /** |
(3)修改图片上传窗口,调用上传方法,回显上传图片
1 | <img src="{{image_entity.url}}" width="200px" height="200px"> |
2.3图片列表
(1)在goodsController.js增加方法
1 | $scope.entity={goods:{},goodsDesc:{itemImages:[]}};//定义页面实体结构 |
(2)修改上传窗口的保存按钮
1 | <button class="btn btn-success" ng-click="add_image_entity()" data-dismiss="modal" aria-hidden="true">保存</button> |
(3)遍历图片列表
1 | <tr ng-repeat="pojo in entity.goodsDesc.itemImages"> |
2.4移除图片
在goodsController.js增加代码
1 | //列表中移除图片 |
修改列表中的删除按钮
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新增方法定义
1 | /** |
(2)在pinyougou-sellergoods-service的TypeTemplateServiceImpl.java新增方法
1 |
|
(3)在pinyougou-shop-web的TypeTemplateController.java新增方法
1 |
|
测试后端代码:
(4)前端代码:修改typeTemplateService.js
1 | //查询规格列表 |
(5)修改goodsController.js
1 | //查询规格列表 |
(6)修改goods_edit.html页面
1 | <div ng-repeat="pojo in specList"> |
1.2保存选中规格选项
我们需要将用户选中的选项保存在tb_goods_desc表的specification_items字段中,定义json格式如下:
[{“attributeName”:”规格名称”,”attributeValue”:[“规格选项1”,“规格选项2”…. ] } , …. ]
(1)在baseController.js增加代码
1 | //从集合中按照key查询对象 |
(2)在goodsController.js增加代码
1 | $scope.updateSpecAttribute=function($event,name,value){ |
(3)在goods_edit.html调用方法
1 | <input type="checkbox" ng-click="updateSpecAttribute($event,pojo.text,option.optionName)">{{option.optionName}} |
2.SKU商品信息
基于上一步我们完成的规格选择,根据选择的规格录入商品的SKU信息,当用户选择相应的规格,下面的SKU列表就会自动生成,如下图:
2.1实现思路
(1)我们先定义一个初始的不带规格名称的集合,只有一条记录。
(2)循环用户选择的规格,根据规格名称和已选择的规格选项对原集合进行扩充,添加规格名称和值,新增的记录数与选择的规格选项个数相同
2.2克隆
常规上我们把克隆分为浅克隆和深克隆。浅克隆是克隆结果随着被克隆的那个发生变化,而深克隆恰恰相反。
- 浅克隆:var a={}; b=a;
- 深克隆:var a={name:’abc’} var b={name:’abc’}
技巧:var b = JSON.parse(JSON.stringify(a));
后端同理
2.3生成SKU列表(深克隆)
(1)在goodsController.js实现创建sku列表的方法
1 | //创建SKU列表 |
(2)在更新规格属性后调用生成SKU列表的方法
1 | <input type="checkbox" ng-click="updateSpecAttribute($event,pojo.text,option.optionName);createItemList()">{{option.optionName}} |
效果如下:
2.4显示SKU列表
goods_edit.html页面上绑定SKU列表
1 | <table class="table table-bordered table-striped table-hover dataTable"> |
成果:
2.5后端
(1)在GoodsServiceImpl添加属性
1 |
|
(2)修改GoodsServiceImpl的add方法,增加代码,实现对SKU商品信息的保存
1 | /** |
3.是否启用规格
在规格面板添加是否启用规格,当用户没有选择该项,将原来的规格面板和SKU列表隐藏,用户保存商品后只生成一个SKU.
3.1前端
goods_add.html添加复选框
1 | <div class="col-md-2 title">是否启用规格</div> |
用if指令控制规格面板与SKU列表的显示与隐藏
1 | <div ng-if="entity.goods.isEnableSpec==1"> |
3.2后端
修改GoodsServiceImpl的add方法
1 | /** |
四、商品管理
1.商品列表
在商家后台,显示该商家的商品列表信息,如下图:
1.1后端
修改pinyougou-shop-web工程的GoodsController.java的search方法
1 | 修改pinyougou-shop-web工程的GoodsController.java的search方法 |
修改pinyougou-sellergoods-service 工程com.pinyougou.sellergoods.service.impl 的findPage方法,修改条件构建部分代码,将原来的模糊匹配修改为精确匹配
1 | if(goods.getSellerId()!=null && goods.getSellerId().length()>0){ |
1.2前端
1、循环列表:
1 | <tr ng-repeat="entity in list"> |
2、显示状态:
修改goodsController.js,添加state数组
1 | $scope.status=['未审核','已审核','审核未通过','关闭'];//商品状态 |
修改列表显示
1 | {{status[entity.auditStatus]}} |
3、显示分类:
我们现在的列表中的分类仍然显示ID
如何才能显示分类的名称呢?
方案一:在后端代码写关联查询语句,返回的数据中直接有分类名称。
方案二:在前端代码用ID去查询后端,异步返回商品分类名称。
我们目前采用方案二,因为商品分类并不多,我们可以直接通过前端存入内存,这样效率高很多,而不是每次都要去后端查一下
(1)修改goodsController
1 | $scope.itemCatList=[];//商品分类列表 |
代码解释:因为我们需要根据分类ID得到分类名称,所以我们将返回的分页结果以数组形式再次封装。
(2)修改goods.html ,增加初始化调用
1 | <body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="findItemCatList()"> |
4、条件查询:
根据状态和商品名称进行查询,修改goods.html
1 | <div class="has-feedback"> |
2.商品修改
在商品列表页面点击修改,进入商品编辑页面,并传递参数商品ID,商品编辑页面接受该参数后从数据库中读取商品信息,用户修改后保存信息。
2.1基本信息读取
我们首选读取商品分类、商品名称、品牌,副标题,价格,商品介绍等信息
读这类信息需要我们知道商品ID,而我们知道一般.html后面是不好带参数的,这里我们用到AngularJS内置的./html可以传参的方法$location,后端代码这里很简单,不过多赘述
(1)在goodsController中引入$location服务
(2)修改goodsController 添加代码:
1 | //查询实体 |
$location.search()其实就是把页面所有的变量都集合到一个数组中
测试:地址栏输入
http://localhost:9102/admin/goods_edit.html#?id=商品ID
注意: ?前要加# ,则是angularJS的地址路由的书写形式
2.2读取商品图片和扩展属性
修改goodsController.js 的findOne
1 | //显示图片列表 |
经过测试,我们发现扩展属性值并没有读取出来,这是因为与之前读取扩展属性名称发生冲突,读出来后被初始化了。我们需要改写代码, 添加判断,当用户没有传递id参数时再执行此逻辑
1 | //监控模板ID ,读取品牌列表 |
2.3读取商品规格
修改findOne
1 | //规格 $scope.entity.goodsDesc.specificationItems=JSON.parse($scope.entity.goodsDesc.specificationItems); |
这里我们要把复选框状态显示出来,用到了ng-checked,方法返回true则勾选。
1 | <input type="checkbox" |
1 | //根据规格名称和选项名称返回是否被勾选 |
2.4读取SKU数据
在GoodsServiceImpl的findOne方法中加载SKU商品数据
1 | //查询SKU商品列表 |
在goodsController.js修改findOne方法的代码
1 | //SKU列表规格列转换——集合数组需要遍历转换 |
成果:
2.5保存数据
修改pinyougou-sellergoods-service的GoodsServiceImpl ,将SKU列表插入的代码提取出来,封装到私有方法中
1 | /** |
接下来,我们修改update方法,实现修改
1 | public void update(Goods goods){ |
修改GoodsController.java
1 |
|
代码解释:出于安全考虑,在商户后台执行的商品修改,必须要校验提交的商品属于该商户
修改goodsController.js ,新增保存的方法
1 | //保存 |
2.6页面跳转
(1)由商品列表页跳转到商品编辑页
1 | <a href="goods_edit.html#?id={{entity.id}}" class="btn bg-olive btn-xs">修改</a> |
(2)由商品编辑页跳转到商品列表
1 | <a href="goods.html" class="btn btn-default">返回列表</a> |
(3)保存成功后返回列表页面
1 | if(response.success){ |
3.商品审核
待审核商品列表:
1 | <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 ,增加方法
1 | //更改状态 |
(2)修改pinyougou-manager-web的goodsController.js ,增加方法
1 | //更改状态 |
(3)修改pinyougou-manager-web的goods.html 页面,为复选框绑定事件指令
1 | <input type="checkbox" ng-click="updateSelection($event,entity.id)" > |
4.商品删除
我们为商品管理提供商品删除功能,用户选中部分商品,点击删除按钮即可实现商品删除。注意,这里的删除并非是物理删除,而是修改tb_goods表的is_delete字段为1 ,我们可以称之为“逻辑删除”
修改pinyougou-sellergoods-service工程的GoodsServiceImpl.java的delete方法
1 | /** |
排除已删除记录——修改pinyougou-sellergoods-service工程GoodsServiceImpl.java的findPage方法,添加以下代码:
1 | criteria.andIsDeleteIsNull();//非删除状态 |
结语
不知道从什么时候开始,效率越来越差了இ௰இ
品优购系列可能要鸽几天了,建模搞得头疼🤕