目标
- 完成商家后台商品列表的功能
- 完成商家后台商品修改的功能
- 完成运营商后台商品审核的功能
- 完成运营商后台商品删除的功能
- 掌握注解式事务的配置
1. 商家后台-商品管理【商品列表】
1. 需求分析
在商家后台,显示该商家的商品列表信息,如图:
2. 查询商家商品列表
1. 后端
修改shop-web工程的GoodsController.java的search方法
@RequestMapping("/search")
public PageResult search(@RequestBody TbGoods goods, int page, int rows ){
// 获取商家ID
String sellerId = SecurityContextHolder.getContext().getAuthentication().getName();
goods.setSellerId(sellerId);//添加查询条件,根据商家名字查询
return goodsService.findPage(goods, page, rows);
}
修改sellergoods-service工程对应goodsService的findPage方法,修改条件构建部分代码,将模糊匹配改为精确匹配
if(goods.getSellerId()!=null && goods.getSellerId().length()>0){
criteria.andSellerIdEqualTo(goods.getSellerId());
}
2. 前端
修改goods.html,引入js
<script src="../plugins/angularjs/angular.min.js"></script>
<!--分页组件开始-->
<script src="../plugins/angularjs/pagination.js"></script>
<link rel="stylesheet" href="../plugins/angularjs/pagination.css">
<!--分页组件结束-->
<script type="text/javascript" src="../js/base_pagination.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/service/typeTemplateService.js"></script>
<script type="text/javascript" src="../js/controller/goodsController.js"></script>
<script type="text/javascript" src="../js/controller/baseController.js"></script>
添加指令
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="findItemCatList()">
在页面放置分页控件
<tm-pagination conf="paginationConf"></tm-pagination>
循环列表
<tr ng-repeat="entity in list">
<td><input type="checkbox"></td>
<td>{{entity.id}}</td>
<td>{{entity.goodsName}}</td>
<td>{{entity.price}}</td>
<td>{{itemCatList[entity.category1Id]}}</td>
<td>{{itemCatList[entity.category2Id]}}</td>
<td>{{itemCatList[entity.category3Id]}}</td>
<td>
<span>
{{status[entity.auditStatus]}}
</span>
</td>
3. 显示状态
修改goodsController.js,添加status数组
// 商品的状态
$scope.status = ["未审核","已审核","审核未通过","关闭"];
修改列表显示
<td>
<span>
{{status[entity.auditStatus]}}
</span>
</td>
4. 显示分类
分类显示仍为id
如何显示分类?
方案1:在后端代码写关联查询语句,返回的数据中直接有分类名称
方案2:在前端代码用id去查询后端,异步返回商品分类名称。
采用方案2,对后端代码无侵入性。
1. 修改goodsController
$scope.itemCatList = [];//商品分类列表
// 加载商品分类列表 根据分类id得到分类名称
$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,增加初始化调用
见上上个代码
3. 修改goods.html,修改列表
同上
5. 条件查询
根据状态和商品名称查询
修改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>
2. 商家后台-商品管理【商品修改】
1. 需求分析
在商品列表页面点击修改,进入商品编辑页面good_edit.html,并传递参数商品id,商品编辑页面接受该参数后从数据库中读取商品信息,用户修改后保存信息。
2. 基本信息读取
首选读取商品分类、商品名称、品牌、副标题、价格等信息
1. 后端
修改sellergoods-interface的GoodsService.java,TbGoods改为Goods
public Goods findOne(Long id);
修改sellergoods-service的GoodsServiceImpl.java
@Override
public Goods findOne(Long id){
Goods goods = new Goods();
goods.setGoods(goodsMapper.selectByPrimaryKey(id));
goods.setGoodsDesc(goodsDescMapper.selectByPrimaryKey(id));
return goods;
}
再修改两个web的GoodsController.java的findOne方法,修改Goods
2. 前端
在goodsController中引入$location
服务
app.controller('goodsController' ,function($scope,$controller,$location,goodsService,uploadService,itemCatService,typeTemplateService){...}
修改goodsController添加代码
//查询实体
$scope.findOne=function(){
var id = $location.search()['id'];//获取参数值
if(id==null){
return;
}
goodsService.findOne(id).success(
function(response){
$scope.entity= response;
}
);
}
在goods_edit.html添加指令
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="selectItemCat1List();findOne()">
测试
地址栏输入<http://localhost:9102/admin/goods_edit.html#?id=149187842867969>
注意:?前面需要加#,则是angularJS的地址路由的书写形式
3. 读取商品介绍(富文本编辑器)
修改前端代码goodsController
goodsService.findOne(id).success(
function(response){
$scope.entity= response;
editor.html($scope.entity.goodsDesc.introduction);//商品介绍
// 显示图片列表
$scope.entity.goodsDesc.itemImages = JSON.parse($scope.entity.goodsDesc.itemImages);
// 显示扩展属性
$scope.entity.goodsDesc.customAttributeItems = JSON.parse($scope.entity.goodsDesc.customAttributeItems);
// 商品规格 [{"attributeName":"网络制式","attributeValue":["移动3G","移动4G"]},{"attributeName":"屏幕尺寸","attributeValue":["6寸","5.5寸"]}]
$scope.entity.goodsDesc.specificationItems = JSON.parse($scope.entity.goodsDesc.specificationItems);
// sku列表规格列转换 {"网络":"联通3G","机身内存":"32G"}
for(var i=0;i<$scope.entity.itemList.length;i++){
$scope.entity.itemList[i].spec = JSON.parse($scope.entity.itemList[i].spec);
}
}
);
}
4. 显示商品图片列表
修改goodsController.js,将图片列表从字符串转换为json集合对象
代码见上面
5. 读取商品扩展属性
修改goodsController.js
代码见上,但是并没有读取出来,原因是被最开始编辑代码冲突,覆盖,将其更新
// 模板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集合
if($location.search()['id']==null){
$scope.entity.goodsDesc.customAttributeItems = JSON.parse($scope.typeTemplate.customAttributeItems);
}
}
);
6. 读取商品规格属性
修改goodsController,将规格部分转换为json,然后根据规格名称和选项名称返回是否被勾选
// 根据规格名称和选项名称返回是否被勾选 [{"attributeName":"网络制式","attributeValue":["移动3G","移动4G"]},
$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;
}
}
}
修改页面上规格面板的复选框,用ng-checked指令控制复选框的勾选状态
<input type="checkbox"
ng-click="updateSpecAttribute($event,pojo.text,option.optionName);createItemList()"
ng-checked="checkAttributeValue(pojo.text,option.optionName)">{{option.optionName}}
7. 读取SKU数据
显示SKU商品列表,并自动读取价格、库存等数据加载到列表
1. 后端
GoodsServiceImpl的findOne方法加载SKU商品数据
@Override
public Goods findOne(Long id){
Goods goods = new Goods();
goods.setGoods(goodsMapper.selectByPrimaryKey(id));
goods.setGoodsDesc(goodsDescMapper.selectByPrimaryKey(id));
// 查询SKU商品列表
TbItemExample example = new TbItemExample();
TbItemExample.Criteria criteria = example.createCriteria();
criteria.andGoodsIdEqualTo(id);
List<TbItem> itemList = itemMapper.selectByExample(example);//goods里面的itemList
goods.setItemList(itemList);
return goods;
}
2. 前端
修改goodsController的findOne方法
见之前代码
8. 保存数据
1. 后端代码
修改sellergoods-interface的GoodsService.java的update的goods的类
修改sellergoods-service的GoodsServiceImpl,
@Override
public void update(Goods goods){
goods.getGoods().setAuditStatus("0");//设置为未审核,如果修改过,需要重新设置
goodsMapper.updateByPrimaryKey(goods.getGoods());
goodsDescMapper.updateByPrimaryKey(goods.getGoodsDesc());
// SKU部分
// 先删除SKU列表数据
TbItemExample example = new TbItemExample();
TbItemExample.Criteria criteria = example.createCriteria();
criteria.andGoodsIdEqualTo(goods.getGoods().getId());
itemMapper.deleteByExample(example);
// save 插入商品SKU列表数据
saveItemList(goods);
}
需要共用插入列表的代码。将SKU列表插入的代码提取,封装为私有
private void saveItemList(Goods goods){
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);
}
}
修改manager-web的GoodsController.java的update方法的goods的类
修改shop-web工程的GoodsController.java
必须保证商家后台提交的商品属于该商户的!
@RequestMapping("/update")
public Result update(@RequestBody Goods goods){
// 必须确保是该商户提交该商户的商品
// 需要先检验是否是当前商家的id
Goods goods1 = goodsService.findOne(goods.getGoods().getId());
// 获取商家的登录名
String sellerId = SecurityContextHolder.getContext().getAuthentication().getName();
// 如果传递的商家id和当前登录的id不一致,属于非法操作
if(!goods1.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, "修改失败");
}
}
2. 前端代码
修改goodsController.js,新增保存的方法,将add方法也并入到save中
//保存
$scope.save=function(){
$scope.entity.goodsDesc.introduction = editor.html();
var serviceObject;//服务层对象
if($scope.entity.goods.id!=null){//如果有ID
serviceObject=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);
}
}
);
}
修改goods_edit.html调用
<div class="btn-toolbar list-toolbar">
<button class="btn btn-primary" ng-click="save()"><i class="fa fa-save" ></i>保存</button>
<button class="btn btn-default" >返回列表</button>
</div>
9. 页面跳转
由商品列表页跳转到商品编辑页
button按钮转为超链接
<a href="goods_edit.html#?id={{entity.id}}" class="btn bg-olive btn-xs">修改</a>
由商品编辑页跳转到商品列表
修改goods_edit.html的返回列表按钮
<a class="btn btn-default" href="goods.html">返回列表</a>
保存成功后返回列表页面
修改save方法
//保存
$scope.save=function(){
$scope.entity.goodsDesc.introduction = editor.html();
var serviceObject;//服务层对象
if($scope.entity.goods.id!=null){//如果有ID
serviceObject=goodsService.update( $scope.entity ); //修改
}else{
serviceObject=goodsService.add( $scope.entity );//增加
}
serviceObject.success(
function(response){
if(response.success){
alert("保存成功");
location.href = "goods.html";//跳转到商品列表页
}else{
alert(response.message);
}
}
);
}
3. 运营商后台-商品管理【商品审核】
1. 待审核商品列表
需求:参照商家后台商品列表
- 修改manager-web的goodsController.js,注入itemCatService,添加代码
// 商品的状态
$scope.status = ["未审核","已审核","审核未通过","关闭"];
$scope.itemCatList = [];//商品分类列表
// 加载商品分类列表 根据分类id得到分类名称
$scope.findItemCatList = function () {
itemCatService.findAll().success(
function (response) {
for(var i=0;i<response.length;i++){
$scope.itemCatList[response[i].id]=response[i].name;
}
}
);
}
- 修改goods.html,引入js
<script src="../plugins/angularjs/angular.min.js"></script>
<!--分页组件开始-->
<script src="../plugins/angularjs/pagination.js"></script>
<link rel="stylesheet" href="../plugins/angularjs/pagination.css">
<!--分页组件结束-->
<script type="text/javascript" src="../js/base_pagination.js"></script>
<script type="text/javascript" src="../js/service/goodsService.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>
- 指令,完成初始调用
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="findItemCatList();searchEntity={auditStatus:'0'}">
- 循环列表
<tr ng-repeat="entity in list">
<td><input type="checkbox" ng-click="updateSelection($event,entity.id)"></td>
<td>{{entity.id}}</td>
<td>{{entity.goodsName}}</td>
<td>{{entity.price}}</td>
<td>{{itemCatList[entity.category1Id]}}</td>
<td>{{itemCatList[entity.category2Id]}}</td>
<td>{{itemCatList[entity.category3Id]}}</td>
<td>
<span>
{{status[entity.auditStatus]}}
</span>
</td>
<td class="text-center">
<a href="goods_edit.html#?id={{entity.id}}" class="btn bg-olive btn-xs">详情</a>
</td>
</tr>
- 分页控件
<tm-pagination conf="paginationConf"></tm-pagination>
2. 商品详情展示
需求:点击列表右侧的“详情”按钮,弹出窗口显示商品信息
TODO(有空回头做)
3. 商品审核与驳回
需求,审核状态值为1,驳回状态值为2,用户在列表中选中id后,点击审核或驳回,修改商品状态,刷新列表
1. 后端代码
- 在sellergoods-interface的GoodsService.java新增方法
// 批量修改状态
public void updateStatus(Long[] ids,String status);
- sellergoods-service的GoodsServiceImpl.java实现该方法
@Override
public void updateStatus(Long[] ids, String status) {
for (Long id : ids) {
TbGoods goods = goodsMapper.selectByPrimaryKey(id);
goods.setAuditStatus(status);
goodsMapper.updateByPrimaryKey(goods);
}
}
- manager-web的GoodsController.java新增方法
@RequestMapping("/updateStatus")
public Result updateStatus(Long[] ids,String status){
try {
goodsService.updateStatus(ids,status);
return new Result(true,"成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"失败");
}
}
2. 前端代码
- 修改manager-web的goodsService.js,增加方法
// 更新状态
this.updateStatus = function (ids, status) {
return $http.get("../goods/updateStatus.do?ids="+ids+"&status="+status);
}
- 修改manager-web的goodsController.js,新增方法
// 更改状态
$scope.updateStatus=function (status) {
goodsService.updateStatus($scope.selectIds,status).success(
function (response) {
if(response.success){
$scope.reloadList();//刷新列表
$scope.selectIds = [];//清空
}else{
alert(response.message);
}
}
);
}
- 修改manager-web的goods.html页面,为复选框绑定事件指令
<td><input type="checkbox" ng-click="updateSelection($event,entity.id)"></td>
- 修改页面的审核通过和驳回按钮
<button type="button" ng-click="updateStatus('1')" class="btn btn-default" title="审核通过" ><i class="fa fa-check"></i> 审核通过</button>
<button type="button" ng-click="updateStatus('2')" class="btn btn-default" title="驳回" ><i class="fa fa-ban"></i> 驳回</button>
4. 运营商后台-商品管理【商品删除】
1. 需求分析
为商品管理提供商品删除功能,数据宝贵,不能物理删除,而是修改tb_goods表的is_delete字段为1,称为逻辑删除。类似回收站
为什么逻辑删除?
- 可能以后还要用
- 数据库索引,索引底层为B+tree, 删了之后,树上的某个位置就没有了,需要再旋转,消耗内存。
2. 逻辑删除的实现
1. 后端代码
修改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);
}
}
2. 前端代码
修改manager-web的goods.html上的删除按钮
<button type="button" class="btn btn-default" title="删除" ng-click="dele()"><i class="fa fa-trash-o"></i> 删除</button>
3. 排除已删除的记录
修改sellergoods-service的GoodsServiceImpl.java的findPage方法
@Override
public PageResult findPage(TbGoods goods, int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
TbGoodsExample example=new TbGoodsExample();
Criteria criteria = example.createCriteria();
criteria.andIsDeleteIsNull();// 排除逻辑删除的商品
//...
5. 商家后台-商品上下架
1. 需求分析
上下架是商品的一个状态,不同于审核,控制权在商家手中,自己决定是上下架。商家正常销售,下架暂停销售
2. 实现思路
上下架就是对上下架状态的修改,字段为tb_goods表的is_markable字段。1表示上架,0表示下架。
3. 代码实现
TODO
6. 注解式事务配置
1. 配置文件
在sellergoods-service的spring目录下创建applicationContext-tx.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--<import resource="applicationContext-dao.xml"/>-->
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启事务控制的注解支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
2. 在方法上添加注解
服务层,每个java类上都添加@Transactional