1.持久层
1.1规划需要执行的SQL语句
这里需要将商品表和购物车表进行连表查询
显示某用户的购物车列表数据的SQL语句大致是
多表查询如果字段不重复则不需要显示的表面表名
select
cid, #日后勾选购物车商品模块需要用到cid来确定勾选的是购物车表的哪一条数据
uid, #感觉没必要,因为uid可以从session中拿的呀,难道是为
#了后面提交购物车订单时判断提交的商品的uid和登录的uid是否一致?
pid, #日购提交订单模块需要用到pid来确定购买的是商品表的哪件商
#品,然后对商品表的该商品的库存,销售热度等信息进行修改
t_cart.price, #两个表都有该字段,需要指定获取的是哪个数据表的
t_cart.num, #两个表都有该字段且含义不同,需要指定获取的是哪个数据表的
title,
t_product.price as realPrice, #为了在购物车列表页展示两个价格的差值
image
from t_cart
left join t_product on t_cart.pid = t_product.id #把t_cart作为主表
where
uid = #{uid}
order by
t_cart.created_time desc #进行排序使最新加入购物车的在最上面
1.2 VO对象:显示给前端页面的数据【多表操作】
VO全称Value Object,值对象。当进行select查询时,查询的结果属于多张表中的内容,此时发现结果集不能直接使用某个POJO实体类来接收,因为POJO实体类不能包含多表查询出来的信息,解决方式是:重新去构建一个新的对象,这个对象用于存储所查询出来的结果集对应的映射,所以把这个对象称之为值对象.
在store包下创建一个vo包,在该包下面创建CartVO类,不需要继承BaseController类,那相应的就需要单独实现Serializable接口
/**
* 不需要继承BaseController类,那相应的就需要单独实现Serializable接口
* 购物车数据的VO类(Value Object)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CartVO implements Serializable {
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
private String title;
private String image;
private Long realPrice;
}
2.在CartMapper接口中添加抽象方法
/**
* 查询某用户的购物车数据
* @param uid 用户id
* @return 该用户的购物车数据的列表
*/
List<CartVO> findVOByUid(Integer uid);
1.3 编写映射
<select id="findVOByUid" resultType="com.example.mycomputerstore.VO.CartVO" >
SELECT cid, uid, pid,
t_cart.price, t_cart.num,
t_product.title, t_product.image,
t_product.price AS realprice
FROM t_cart
LEFT JOIN t_product
ON t_cart.pid = t_product.id
WHERE uid = #{uid}
ORDER BY t_cart.created_time DESC
</select>
1.4 单元测试
在CartMapperTests测试类中添加findVOByUid()方法的测试
@Test
public void findVOByUid(){
System.out.println(cartMapper.findVOByUid(2));
}
2.业务层
2.1 规划异常
查询不到就返回空,不需要规划异常【查询没有异常】
2.2设计接口和抽象方法及实现
1.在ICartService接口中添加findVOByUid()抽象方法
/**
* 查询某用户的购物车数据
* @param uid 用户id
* @return 该用户的购物车数据的列表
*/
List<CartVO> getVOByUid(Integer uid);
2.在CartServiceImpl类中重写业务接口中的抽象方法
@Override
public List<CartVO> getVOByUid(Integer uid) {
return cartMapper.findVOByUid(uid);
}
2.3单元测试
该业务层只是调用了持久层的方法并返回,可以不再测试
3.控制层
3.1 处理异常
业务层没有抛出异常,所以这里不需要处理异常
3.2 设计请求
- /carts/
- GET
- HttpSession session
- JsonResult<List<CartVO>>
3.3 处理请求
在CartController类中编写处理请求的代码。
/**
* 根据用户id查询购物车
* @param session: 为了获取用户的uid
* @return
*/
@GetMapping({"/",""})//表示直接输入/cart就可以访问
public JsonResult<List<CartVO>> getVOByUid(HttpSession session){
List<CartVO> data = cartService.getVOByUid(getuidFromSession(session));
return new JsonResult<>(OK,data);
}
4. 前端页面
1.将cart.html页面的head头标签内引入的cart.js文件注释掉(这个就是文件的功能:点击"+“,”-",“删除”,"全选"等按钮时执行相应的操作)
<!-- <script src="../js/cart.js" type="text/javascript" charset="utf-8"></script> -->
多说一下,form标签的action="orderConfirm.html"属性(规定表单数据提交到哪里)和结算按钮的类型"type=submit"是必不可少的,这样点击"结算"时才能将数据传给"确认订单页"并在"确认订单页"展示选中的商品数据
当然也可以把这两个删掉,然后给结算按钮添加"type=button"然后给该按钮绑定一个点击事件实现页面跳转和数据传递,但是这样太麻烦了
submit和button的区别
submit和button,二者都以按钮的形式展现,看起来都是按钮,所不同的是type属性和处发响应的事件上,submit会提交表单,button不会提交表单. submit默认为form提交,可以提交表单(form). button则响应用户自定义的事件,如果不指定onclick等事件处理函数,它是不做任何事情.
2.注意点:form表单结构。
-
action="orderConfirm.html"
-
id="cart-list"
-
type="button" value=" 结 算 "
3.使用ajax发起请求用户购物车的数据
<script type="text/javascript">
/*
* 读取当前页面的加载
* */
$(document).ready(function (){
//展示购物车列表
showCartList()
})
//展示购物车列表
function showCartList(){
//清空tbody
$("#cart-list").empty()
$.ajax({
url:"/cart",
type:"GET",
dataType:"JSON",
success: function (e) {
console.log(e.data)
//获取数据
let list = e.data;
for (let i=0;i<list.length;i++) {
/*
* name="cids" value="#{cid}":为了传到“确认支付”页面做准备
* ..#{image}collect.png==数据库中的样式【/images/portal/00GuangBo1040A5GBR0731/】
* 实际上需要的是【../images/portal/00GuangBo1040A5GBR0731/collect.png】
* msg:表示真实价格
* */
let tr = '<tr>\n' +
'<td>\n' +
'<input name="cids" value="#{cid}" type="checkbox" class="ckitem" />\n' +
'</td>\n' +
'<td><img src="..#{image}collect.png" class="img-responsive" /></td>\n' +
'<td>#{title}#{msg}</td>\n' +
'<td>¥<span id="goodsPrice#{cid}">#{singlePrice}</span></td>\n' +
'<td>\n' +
'<input id="price-#{cid}" type="button" value="-" class="num-btn" onclick="reduceNum(2)" />\n' +
'<input id="goodsCount#{cid}" type="text" size="2" readonly="readonly" class="num-text" value="#{num}">\n' +
'<input id="price+#{cid}" class="num-btn" type="button" value="+" onclick="addNum(2)" />\n' +
'</td>\n' +
'<td><span id="goodsCast#{cid}">#{totalPrice}</span></td>\n' +
'<td>\n' +
'<input type="button" onclick="delCartItem(this)" class="cart-del btn btn-default btn-xs" value="删除" />\n' +
'</td>\n' +
'</tr>'
tr=tr.replace(/#{cid}/g,list[i].cid);
tr=tr.replace(/#{image}/g,list[i].image);
tr=tr.replace(/#{title}/g,list[i].title)
//当前商品的真实的价格
tr=tr.replace(/#{msg}/g,list[i].realPrice)
tr=tr.replace("#{num}",list[i].num)
//单价
tr=tr.replace("#{singlePrice}",list[i].price)
//总价
tr=tr.replace(/#{totalPrice}/g,list[i].price*list[i].num)
//将数据加入表中
$("#cart-list").append(tr);
}
},
error(xhr){
alert("购物车列表数据加载产生未知的异常"+xhr.status)
}
})
}
</script>
这tr变量是怎么声明的呢:
先敲下var=‘’;然后在上面的html里面找到tbody下的任意一个tr标签复制在单引号里面,然后删掉制表符.最后对该字符串稍加改动:
1.第18行name=“cids” value="#{cid}"是为"点击结算按钮跳转到确认订单页面"模块做准备。这两个属性都是自己添加的,在tbody复制的tr标签里面没有,这两个属性是为了跳转到"确认订单页"时能够携带该参数(比如传递cids=1)
2.第26οnclick="addNum(#{cid})“是为"在购物车列表增加商品数量"模块做准备。是为了点击”+"后能调用addNum函数并传入对应的cid
3.第22行id="goodsPrice#{cid}"和第25行id="goodsCount#{cid}"和第28行id="goodsCast#{cid}"都是为"在购物车列表增加商品数量"模块做准备。在后端更新完商品数量相应的前端页面也要更新:
根据id="goodsCount#{cid}"获取数量相关的控件后更新其value属性的值(value属性用.val()赋值)
根据id="goodsPrice#{cid}"获取价格相关的控件后拿到其单价
将单价和数量相乘后,根据id="goodsCast#{cid}"获取总价相关的控件并更新其文本值(文本用.html()更新)
4.上面这三条都是和本模块无关的,其余的修改都是和本模块相关的,在tbody复制的tr标签里面都有,比葫芦画瓢就可以了
点击"结算"按钮页面跳转的实现:在cart.html页面点击"结算"后会跳转到"确认订单页"并将表单中的数据作为参数传递给"确认订单页"