目标
- 完成选择商品分类功能
- 完成品牌选择功能
- 完成扩展属性功能
- 完成规格选择功能
- 完成SKU商品信息功能
- 完成是否启用规格功能
1. 商品录入【选择商品分类】
1. 需求分析
实现效果为商品分类右边显示三级效果
当用户选择一级分类后,二级分类对应更新,当用户选择二级分类后,三级分类要对应更新
2. 准备
shop-web工程创建ItemCatController,拷贝运营商部分的代码即可
创建item_catService.js,拷贝运营商部分的代码
修改goodsController.js,引入itemCatService
修改goods_edit.html,添加引用
<script src="../plugins/angularjs/angular.min.js"></script>
<script type="text/javascript" src="../js/base.js"></script>
<script type="text/javascript" src="../js/service/goodsService.js"></script>
<script type="text/javascript" src="../js/service/uploadService.js"></script>
<script type="text/javascript" src="../js/service/itemCatService.js"></script>
<script type="text/javascript" src="../js/controller/goodsController.js"></script>
<script type="text/javascript" src="../js/controller/baseController.js"></script>
3. 代码实现
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属性可以在表达式中使用数组或对象来自动生成一个select中的option列表。ng-options与ng-repeat很相似,很多时候可以用ng-repeat来代替ng-options。但是ng-options提供了一些好处,例如减少内存提高速度,以及提供选择框的选项来让用户选择。
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>
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. 读取模板id
在goodsController增加代码
// 查询三级分类
$scope.$watch('entity.goods.category3Id',function (newValue, oldValue) {
itemCatService.findOne(newValue).success(
function (response) {
$scope.entity.goods.typeTemplateId = response.typeId;
}
);
})
在goods_edit.html显示模板id
模板ID:{{entity.goods.typeTemplateId}}
2. 商品录入【品牌选择】
1. 需求分析
用户选择商品分类后,品牌列表根据用户选择的分类进行更新。具体逻辑为:根据用户选择的三级分类找到对应的商品类型模板,商品类型模板中存储品牌的列表json数据。
2. 代码实现
1. shop-web工程创建TypeTemplateController
从运营商部分拷贝
2. shop-web工程创建typeTemplateService.js
从运营商部分拷贝
3. 在goodsController引入typeTemplateService,新增代码
// 模板id选择后,更新品牌列表
$scope.$watch('entity.goods.typeTemplateId',function (newValue, oldValue) {
typeTemplateService.findOne(newValue).success(
function (response) {
$scope.typeTemplate = response;//根据模板id获取类型模板
$scope.typeTemplate.brandIds = JSON.parse($scope.typeTemplate.brandIds);//字符串转换为json集合
}
);
})
在页面goods_edit.html引入js
<script type="text/javascript" src="../js/service/typeTemplateService.js"></script>
添加品牌选择框
<div class="col-md-2 title">品牌</div>
<div class="col-md-10 data">
<select class="form-control" ng-model="entity.goods.brandId"
ng-options="brand.id as brand.text for brand in typeTemplate.brandIds"></select>
</div>
注意:如果选项更改为brand,要保持brand的属性id text跟数据库保持一致。
3. 商品录入【扩展属性】
1. 需求分析
实现扩展属性的录入
2. 代码实现
修改goodsController.js,在用户更新模板id时,读取模板中的扩展属性赋给商品的扩展属性
// 模板id选择后,更新品牌列表
$scope.$watch('entity.goods.typeTemplateId',function (newValue, oldValue) {
typeTemplateService.findOne(newValue).success(
function (response) {
$scope.typeTemplate = response;//根据模板id获取类型模板
$scope.typeTemplate.brandIds = JSON.parse($scope.typeTemplate.brandIds);//字符串转换为json集合
$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="item in entity.goodsDesc.customAttributeItems">
<div class="col-md-2 title">{{item.text}}</div>
<div class="col-md-10 data">
<input class="form-control" placeholder="{{item.text}}" ng-model="item.value">
</div>
</div>
</div>
</div>
4. 商品录入【规格选择】
1. 需求分析
显示规格及选项列表(复选框),并保存用户选择的结果
从模板中提取规格列表
[{"id":27,"text":"网络"},{"id":32,"text":"机身内存"}]
得到List<Map>
,遍历
通过id查询规格选项列表
[{"id":27,"text":"网络",options:[{},{}]},{"id":32,"text":"机身内存",options:[{},{}]}]
2. 显示规格选项列表
模板中只记录了规格名称,也需要显示规格的规格选项。
1. sellergoods-interface的TypeTemplateService.java新增方法定义
// 返回规格列表
public List<Map> findSpecList(Long id);
2. 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();
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;
}
最难理解的就是类型转换部分,是否是根据id得到的就是integer类型?强转只是为了确保是integer,然后再转换为long类型?
3. 在shop-web的TypeTemplateController.java新增方法
@RequestMapping("/findSpecList")
public List<Map> findSpecList(Long id){
return typeTemplateService.findSpecList(id);
}
4. 测试后端代码
通过
5. 前端
修改shop-web的typeTemplateService.js
// 查询规格列表
this.findSpecList = function (id) {
return $http.get('../typeTemplate/findSpecList.do?id='+id);
}
修改shop-web的goodsController.js
// $scope.specList = ["options":[]];
// 模板id选择后,更新品牌列表 扩展属性 规格列表
$scope.$watch('entity.goods.typeTemplateId',function (newValue, oldValue) {
typeTemplateService.findOne(newValue).success(
function (response) {
$scope.typeTemplate = response;//根据模板id获取类型模板
$scope.typeTemplate.brandIds = JSON.parse($scope.typeTemplate.brandIds);//字符串转换为json集合
$scope.entity.goodsDesc.customAttributeItems = JSON.parse($scope.typeTemplate.customAttributeItems);
}
);
// 查询规格列表
typeTemplateService.findSpecList(newValue).success(
function (response) {
$scope.specList = response;
$scope.specList = JSON.parse($scope.specList);
alert($scope.specList);
}
);
})
小插曲:之前没成功,先alert,发现undefined,想着是不是没设置好初始的格式,并alert,发现更不靠谱了,想着是不是没有将字符串转json,果真,就显示规格选项成功了。
修改goods_edit.html页面
<div class="row data-type">
<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>
</div>
3. 规格选项的保存(难点)
1. 需求分析
需要将用户选中的选项保存在tb_goods_desc表中的specification_items字段中,定义json格式如下
[{"attributeName":"网络","attributeValue":["移动3G","移动4G"]},{"attributeName":"机身内存","attributeValue":["16G","32G"]}]
记录的就是勾选的结果。attributeName就是规格的名称,attributeValue就是勾选的规格的选项。
思路分析:
最终要存到entity.goodsDesc.specificationItems=[]
,push往里面加
得到entity.goodsDesc.specificationItems=[{"attributeName":"网络","attributeValue":["移动3G","移动4G"]}]
存在两种情况:
- 选择的选项,规格名称已经存在
- 选择的选项,规格名称不存在
判断集合中attributeName的值有没有当前我要添加的选项的规格名称,集合需要遍历
编写通用方法,在集合中查询对象中某个属性值是否存在,查到返回对象,查不到null
2. 代码
在baseController.js增加代码
// 从集合中按照key查询对象
$scope.searchObjectByKey = function (list, key, keyValue) {
for(var i=0;i<list.length;i++){
if(list[i][key]===keyValue){//存在这个键值对 list[i]每个元素
return list[i];
}
}
return null;
}
在goodsController.js增加代码
// 类似于删除的时候勾选的复选框,操作之前要初始化结构
$scope.updateSpecAttribute = function ($event, name, value) {//要传的参数:网络、勾选的移动3G
// 看该attributeName是否存在
var object = $scope.searchObjectByKey($scope.entity.goodsDesc.specificationItems,'attributeName',name);
// 需要追加attributeValue
if(object!=null){
if($event.target.checked){
alert("123");
object.attributeValue.push(value);// 追加到attributeValue的变量中
}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{
// attributeName不存在,添加整个结构
$scope.entity.goodsDesc.specificationItems.push(
{"attributeName":name,"attributeValue":[value]}
)
}
}
另外需要初始化specificationItems
$scope.entity={goodsDesc:{itemImages:[],specificationItems:[]}};
在goods_edit.html调用该方法
<div class="row data-type">
<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" ng-click="updateSpecAttribute($event,pojo.text,option.optionName)">{{option.optionName}}
</span>
</div>
</div>
</div>
可以再页面上某个区域临时添加表达式,观察测试结果
{{entity.goodsDesc.specificationItems}}
4. 商品录入【SKU商品信息】
1. 需求分析
基于上一步完成的规格选择,根据选择的规格录入商品的SKU信息。当用户选择相应的规格,下面的SKU列表会自动生成。
实现思路:
- 定义一个初始的不带规格名称的集合,只有一条记录。
- 循环用户选择的规格,根据规格名称和已选择的规格选项对原集合扩充,添加规格名称和值。新增的记录数和选择的规格选项个数相同。
如何实现?
采用克隆
浅克隆 var a={} b=a;
深克隆 var a={‘name’:‘abc’} var b={‘name’:‘abc’}
技巧:var b = JSON.parse(JSON.stringify(a))
2. 前端
1. 生成SKU列表(深克隆)
在goodsController.js实现创建sku列表的方法
// 创建SKU列表
$scope.createItemList = function () {
// 初始化itemList,将spec,price,num,status,isDefault这些属性放进去
$scope.entity.itemList = [{spec:{},price:0,num:9999,status:"0",isDefault:"0"}];
// 简化spec
var items = $scope.entity.goodsDesc.specificationItems;
for(var i=0;i<items.length;i++){
// 驴打滚,每次itemList都更新过了
$scope.entity.itemList = addColumn($scope.entity.itemList,items[i].attributeName,items[i].attributeValue);
}
}
// 增加列的方法,参数列名称,列的值
addColumn=function (list, columnName, columnValues) {
var newList = [];//新集合
for(var i=0;i<list.length;i++){
var oldRow = list[i];//之前的行
for(var j=0;j<columnValues.length;j++){
var newRow = JSON.parse(JSON.stringify(oldRow));//对原来的行深克隆
newRow.spec[columnName] = columnValues[j];
newList.push(newRow);
}
}
return newList;
}
- 在更新规格属性后调用生成sku列表的方法
<input type="checkbox" ng-click="updateSpecAttribute($event,pojo.text,option.optionName);createItemList()">{{option.optionName}}
- 在页面添加表达式,测试
{{entity.itemList}}
显示点击出来的列表
2. 显示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>
<!--$scope.entity.itemList = [{spec:{},price:0,num:9999,status:"0",isDefault:"0"}];-->
<tr ng-repeat="pojo in entity.itemList">
<!--和上面attributeName的一样-->
<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>
删除测试表达式
3. 后端
比较麻烦,但是理解很简单,就是给serviceImpl的GoodsServiceImpl.java的add方法添加itemList
@Autowired
private TbGoodsDescMapper goodsDescMapper;
@Autowired
private TbBrandMapper brandMapper;
@Autowired
private TbItemCatMapper itemCatMapper;
@Autowired
private TbSellerMapper sellerMapper;
@Autowired
private TbItemMapper itemMapper;
/**
* 增加
*/
@Override
public void add(Goods goods) {
goods.getGoods().setAuditStatus("0");//设置未申请状态
goodsMapper.insert(goods.getGoods());
goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());//设置id
goodsDescMapper.insert(goods.getGoodsDesc());//插入商品细节数据
// itemList
for (TbItem item : goods.getItemList()) {
// title eg.苹果(Apple) iPhone 4s 8GB 黑色 联通3G手机
String title = goods.getGoods().getGoodsName();//商品名
// {"机身内存":"16G","网络":"联通3G"}
Map<String,Object> specMap =JSON.parseObject(item.getSpec());
for (String key : specMap.keySet()) {
title += " "+specMap.get(key);//完善title
}
item.setTitle(title);
item.setGoodsId(goods.getGoods().getId());
item.setSellerId(goods.getGoods().getSellerId());
item.setCategoryid(goods.getGoods().getCategory3Id());
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的第一个图片)
// eg.[{"color":"白色","url":"http://192.168.25.133/group1/M00/00/00/wKgZhVmNXEWAWuHOAAjlKdWCzvg949.jpg"},{"color":"黑色","url":"http://192.168.25.133/group1/M00/00/00/wKgZhVmNXEuAB_ujAAETwD7A1Is158.jpg"},{"color":"蓝色","url":"http://192.168.25.133/group1/M00/00/00/wKgZhVmNXFWANtjTAAFa4hmtWek619.jpg"}]
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);
}
}
5. 商品录入【是否启用规格】
1. 需求分析
在规格面板添加是否启用规格,用户没有选择该项时,将原来的规格面板和SKU列表隐藏,保存商品后只生成一个SKU
2. 前端代码
goods_add.html添加复选框,用if指令控制规格面板和SKU列表的显示和隐藏
<div class="tab-pane" id="spec">
<div class="row data-type">
<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>
</div>
3. 后端代码
修改GoodsServiceImpl的add方法
@Autowired
private TbGoodsDescMapper goodsDescMapper;
@Autowired
private TbBrandMapper brandMapper;
@Autowired
private TbItemCatMapper itemCatMapper;
@Autowired
private TbSellerMapper sellerMapper;
@Autowired
private TbItemMapper itemMapper;
/**
* 增加
*/
@Override
public void add(Goods goods) {
goods.getGoods().setAuditStatus("0");//设置未申请状态
goodsMapper.insert(goods.getGoods());
goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());//设置id
goodsDescMapper.insert(goods.getGoodsDesc());//插入商品细节数据
if("1".equals(goods.getGoods().getIsEnableSpec())){
// itemList
for (TbItem item : goods.getItemList()) {
// title eg.苹果(Apple) iPhone 4s 8GB 黑色 联通3G手机
String title = goods.getGoods().getGoodsName();//商品名
// {"机身内存":"16G","网络":"联通3G"}
Map<String,Object> specMap =JSON.parseObject(item.getSpec());
for (String key : specMap.keySet()) {
title += " "+specMap.get(key);//完善title
}
item.setTitle(title);
setItemValues(item,goods);
itemMapper.insert(item);
}
}else{
TbItem item = new TbItem();
item.setTitle(goods.getGoods().getGoodsName());
item.setPrice(goods.getGoods().getPrice());
item.setStatus("1");//状态
item.setIsDefault("1");//是否默认
item.setNum(9999);//库存数量
item.setSpec("{}");
setItemValues(item,goods);
itemMapper.insert(item);
}
}
private void setItemValues(TbItem item,Goods goods){
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的第一个图片)
// eg.[{"color":"白色","url":"http://192.168.25.133/group1/M00/00/00/wKgZhVmNXEWAWuHOAAjlKdWCzvg949.jpg"},{"color":"黑色","url":"http://192.168.25.133/group1/M00/00/00/wKgZhVmNXEuAB_ujAAETwD7A1Is158.jpg"},{"color":"蓝色","url":"http://192.168.25.133/group1/M00/00/00/wKgZhVmNXFWANtjTAAFa4hmtWek619.jpg"}]
List<Map> imageList =JSON.parseArray(goods.getGoodsDesc().getItemImages(),Map.class);
if(imageList.size()>0){
item.setImage((String)imageList.get(0).get("url"));
}
}