选择商品分类需求分析
在商品录入界面实现商品分类的选择(包括三级分类)效果如下:
当用户选择一级分类后,二级分类列表要相应更新,当用户选择二级分类后,三级列表要相应更新。
1.首先在页面加载的时候先查询到顶级分类的数据:
//读取一级分类
$scope.selectItemCat1List=function(){
itemCatService.findByParentId(0).success(
function(response){
$scope.itemCat1List=response;
}
);
}
在页面的下拉框中绑定:
查询出来的itemCat1List包含了所有顶级分类的列表,在页面中的select不使用ng-repeat循环,而是使用ng-options进行选项的循环绑定,item.name代表在select下拉列表中显示出来的文字选项,item.id代表如果选择了一个选项,传递给后台的不是文字而是这个id,相当于选择一个文字传递的不是文字而是这个文字代表的id,又因为之前写了一个ng-model所以将选择的id绑定到entity.goods.category1Id上,当点击下拉列表时显示所有选项的文字,选择了一个选项后,其实选择的是id,又将id绑定在entity.goods.category1Id上通过双向绑定可以向后台传递,itemCat1List是集合每一个顶级分类的子类数据都绑定了一个id。
2.二级下拉列表和三级下拉列表:
$scope.$watch('entity.goods.category1Id', function(newValue, oldValue) {
//根据选择的值,查询二级分类
itemCatService.findByParentId(newValue).success(
function(response){
$scope.itemCat2List=response;
}
);
});
$watch方法用于监控某个变量的值,当被监控的值发生变化,就自动执行相应的函数。需要在Controller中引入这个服务才可以使用。
在此方法中$watch负责监控页面上的entity.goods.category1Id,当这个变量发生数据变化时会自动调用此方法,newValue和oldValue分别代表entity.goods.category1Id变化之前的值和变化之后的值,通过变化的新的category1Id的一级分类id查询以一级分类为父类的子数据们,将查询出来的子数据们(二级分类数据)再装到itemCat2List这个集合中。在前端页面的第二个下拉列表上依然通过ng-options和ng-model来循环和绑定。
三级下拉列表同二级下拉列表,只不过三级下拉列表是监控二级下拉列表。
品牌和扩展属性
注:数据库中存储的品牌和扩展属性是json格式的字符串,所以传到前端的时候拿到的数据也是一个一个字符串对象,想要在页面上通过一个字符串对象的键取到它对应的值那是不可能的,所以在拿到后台传回来的数据时要进行JSON对象的转换。
$scope.entity.goodsDesc.customAttributeItems=JSON.parse( $scope.typeTemplate.customAttributeItems);//扩展属性
$scope.typeTemplate.brandIds= JSON.parse( $scope.typeTemplate.brandIds);//品牌列表
扩展属性还有需要注意的地方就是扩展属性是从模板表读出来的数据,格式为item的格式,但是商品录入最终的数据要存入的是商品表中的custom_attribute_items字段中,此字段的格式为上图中的customAttributeItems,所以在input输入框中需要绑定ng-model=“item.value”,这样的话,整个item对象的text的值就是查询出来的item的text字段,value的值变成了输入框中输入的数据,而每一个item对象又是customAttributeItems集合的其中一个数据,最终在提交的时候就可以将这个格式的数据提交给后台了。
规格查询
需求分析:
规格存储在数据库中也是json格式的字符串,并且是通过某一个模板查询对应对的规格,所以查询规格需要模板的id,在前面已经介绍过$watch服务了,所以前端省略,通过监控模板绑定的变量来调用调用,使用的模板不一样则使用的规格就不一样。
后端service层:
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;
}
定义一个方法,接收前端传过来的模板的id,首先根据这个id把对应的一个模板对象查询出来,因为模板的规格字段中存储的也是一个个JSON格式的规格字符串,并不能取到规格的id并将规格对应的值查询到,在后台用JSON.parseArray(xx,yy);将规格转换为存储Map集合的List集合(第一个参数是需要转换的JSON格式的字符串,第二个参数是Map.class表示List中每一个元素都是一个Map),每一个Map集合都是一个键值对,保存的就是JSON字符串中对应的键和值。取到对象后遍历整个list,通过map中存储的规格id查询规格表中的规格集合,并将查询出来的数据封装为list依旧放入map中返回。最后map中的数据就变成了既有规格id和规格,还有规格id对应的SKU。
[{"id":27,"text":"网络",options:[]},{"id":32,"text":"机身内存",options:[]}]
注:将int类型的id转换为long对象类型:
Long id = new Long( (Integer)id;
规格保存
上一步查询出来的数据的格式是[{"id":27,"text":"网络",options:[]},{"id":32,"text":"机身内存",options:[]}]
,现在要将数组中每一个对象的text拿出来当作SKU的名字,options中的每一个对象的option_name当作SKU的值显示到页面上的每一个复选框中。又因为商品表中规格字段的格式为specification_items : [ { "attributeName":"网络","attributeValue":["移动3G","移动4G"] } ]
,所以要将相同text作为一个attributeName存放,将相同的text对应的不同的attributeValue作为一个数组存放。
存放思路:
(1)选择的选项attributeName已经存在,说明点击的这个SKU的名字已经出现了,现在是最少第二次选择,代表此次的attributeName对应的attributeValue有两个值所以直接存放。
(2)选择的选项attributeName不存在,代表是第一次选择这个规格,那就将attributeName和attributeValue。
前端代码:
1.写一个方法searchObjectByKey()判断集合中attributeName对应的attributeValue是否第二次被选。
说明:
参数一:list代表将哪一个集合将进行存放,本次操作list都是specificationItems 格式的。
参数二:key在本次操作中代表attributeName,标明要放入哪一个attributeName中。
参数三:keyValue是key所对应的值,本次操作代表SKU。
首先遍历传入的集合,每一个attributeName都要进行遍历存放,当用户选择了多个attributeName和每个attributeName表示的多个attributeValue时(list中有多个此格式的对象)才会循环多次,第一次list[i]代表第一个attributeName和attributeValue的组合对象,判断如果这个对象的attributeName如果等于此次传入的keyValue,说明attributeName已经被选择过一次了,这次添加不再增加一个对象而是返回这一个已经存在的对象,如果不存在就代表是第一次选择返回null。
2.在点击勾选checkbox的时候,调用判断操作,有:追加,没有:添加。
说明:
这个方法绑定在每一个SKU选项的复选框上,当点击的时候触发,传入$event来控制复选框的勾选,name代表attributeName,value代表每一个复选框中的attributeValue,先将传入的参数调用写好的searchObjectByKey()方法传入目前已经存在的
specification_items : [ { "attributeName":"网络","attributeValue":["移动3G","移动4G"] },
{ "attributeName":"内存","attributeValue":["8G","4G"] }... ]
和用于判断键的attributeName和传入的name,假如说如果这次点击的SKU选项(网络)对应的attributeName已经存在返回{ “attributeName”:“网络”,“attributeValue”:[“移动3G”,“移动4G”]}对象,如果(网络)不存在返回null。将返回的object作一个判断,如果不等于空则将传入的value也就是一个SKU选项直接加入返回的对象的attributeValue数组中。如果等于空说明之前attributeName不存在,那就将attributeName和attributeValue[]都放入specification_items中。
3.取消勾选,移除数据:
$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);//移除选项
//如果选项(attributeValue的长度等于0)都取消了,将此条(attributeName和attributeValue的组合)记录移除
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]})
}
}
4.前端绑定:
<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>
SKU商品列表-思路分析
实现的效果:
深克隆(深拷贝)和浅克隆(浅拷贝)
(1)浅克隆:var a={}; b=a;a和b都指向同一个地址,当a变化的时候b也会变化,当b变化的时候a也会变化。
(2)深克隆:var a={};var b=JSON.parse(JSON.stringify(a));
将a对象先转换为JSON格式的字符串再将这个字符串转换为JSON对象,经过这两次转换之后,a和b在内存中占用两个地址代表两个不同的对象,变化时互相不影响。
//创建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++) {//循环1
$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++) {//循环2
var oldRow = list[i];
for (var j = 0; j < columnValues.length; j++) {//循环3
var newRow = JSON.parse(JSON.stringify(oldRow));//深克隆
newRow.spec[columnName] = columnValues[j];
newList.push(newRow);
}
}
return newList;
}
首先将entity.itemList初始化一个格式,这个格式是将要循环显示到页面的一个数组,第一个循环是判断attributeName和attributeValue[]组合的对象有几个,有几个就要判断几次。addColumn 才是真正做操作给格式中加列和行的方法,先定义一个newList 表示新的集合最终要代表的是itemList,第二个循环代表初始化格式中已经存了几个对象,这几个对象如果要加列必须被循环,将已经存进去的第一个对象给oldRow,第三个循环代表attributeValue的循环(增加行),深克隆之后newRow和oldRow(attributeName和attributeValue[]组合的对象)就没有关系了,如果newRow的数据增加了,oldRow还是原来的数据,最终newRow的attributeName因为循环添加了多个attributeValue。
前端:
<th class="sorting" ng-repeat="item in entity.goodsDesc.specificationItems">{{item.attributeName}}</th>
and
<tr ng-repeat="pojo in entity.itemList">
<td ng-repeat="item in entity.goodsDesc.specificationItems">{{pojo.spec[item.attributeName]}}</td>
th中显示的是SKU的名字,有几条数据就是几列
tr循环初始化的格式的数据,有几条数据就是几行。