1.持久层[Mapper]
1.1规划需要执行的SQL语句
用户在购物车列表页中通过随机勾选相关的商品,在点击"结算"按钮后跳转到"确认订单页",在这个页面中需要展示用户在上个页面所勾选的"购物车列表页"中对应的数据.说白了也就是列表展示,且展示的内容还是来自于购物车表.但是用户勾选了哪些商品呢,所以"购物车列表页"需要将用户勾选的商品id传递给"确认订单页"
所以在持久层需要完成“根据若干个不确定的id值,查询购物车数据表,显示购物车中的数据信息”。则需要执行的SQL语句大致是。
select
cid,
uid,
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
where
cid in (?,?,?)
order by
t_cart.created_time desc
注意where cid in (?,?,?),这里是需要传入cid的集合
1.2 设计接口和抽象方法
在CartMapper接口中添加findVOByCids抽象方法
List<CartVO> findVOByCids(Integer[] cids);
1.3配置映射
1.在CartMapper.xml文件中添加SQL语句的映射配置
<select id="findVOByCids" resultType="com.cy.store.vo.CartVO">
select
cid,
uid,
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
where
cid in (
<foreach collection="array" item="cid" separator=",">
#{cid}
</foreach>
)
order by
t_cart.created_time desc
</select>
foreach循环就是一个for循环
- collection标识循环的是list集合还是数组,如果是list集合就用collection=“list”
- item用来接收每次循环获取的值
- separator标识循环出来的值中间用什么隔开,且最后循环出来的值后面不加
1.4 单元测试
@Test
public void findVOByCids() {
Integer[] cids = {1, 2, 6, 8, 100};//可以写表中不存在的,无非就是查不到数据,并不会报错
List<CartVO> list = cartMapper.findVOByCids(cids);
for (CartVO item : list) {
System.out.println(item);
}
}
2.业务层[Service]
2.1规划异常
查询语句,没有需要规划的异常,在业务层判断这几条购物车商品的数据归属是否正确,如果不正确也不需要抛出异常,直接从查询到的数据中移除该商品就行了
2.2设计接口和抽象方法及实现
1.在ICartService接口中添加getVOByCids()抽象方法
List<CartVO> getVOByCids(Integer uid, Integer[] cids);//uid是为了判断数据归属是否正确
2.在CartServiceImpl类中重写业务接口中的抽象方法
@Override
public List<CartVO> getVOByCids(Integer uid, Integer[] cids) {
List<CartVO> list = cartMapper.findVOByCids(cids);
//可以使用for遍历,这里玩个新的,用迭代器遍历
Iterator<CartVO> it = list.iterator();
while (it.hasNext()) {
//指向的是该元素之前,所以需要next得到该元素
CartVO cart = it.next();
if (!cart.getUid().equals(uid)) {
/**
* 不能用list.remove(cart)
* 在迭代器进行遍历的时候不能使用集合的移除
* 方法,需要用迭代器特有的移除方法
*/
it.remove();
}
}
return list;
}
2.3单元测试
业务层只是调用持久层获取数据并判断归属是否正确,这里不再测试
3.控制层[Controller]
3.1处理异常
业务层没有抛出异常,所以不需要处理异常
3.2设计请求
- /carts/list
- GET
- Integer[] cids, HttpSession session
- JsonResult<List<CartVO>>
3.3处理请求
1.在CartController类中添加处理请求的getVOByCids()方法。
@RequestMapping("list")
public JsonResult<List<CartVO>> findVOByCids(Integer[] cids, HttpSession session) {
List<CartVO> data = cartService.getVOByCids(getUidFromSession(session), cids);
return new JsonResult<>(OK, data);
}
4.确定订单--前端页面
4.1显示勾选的购物车数据
1.检查cart.html页面,里面form标签的action="orderConfirm.html"属性(规定表单数据提交到哪里)和结算按钮的类型"type=submit"是必不可少的,这样点击"结算"时才能将数据传给"确认订单页"并在"确认订单页"展示选中的商品数据
2.在orderConfirm.html页面中实现自动加载从cart.html页面中传递过来的cids数据,再去请求ajax,然后将后端返回的数据填充在页面的某个区域中
3.orderConfirm.js文件中
- $(“.link-pay”).click(……)作用:点击"在线支付"后跳转到支付页面,这个其实就是下个模块要做的"创建订单"功能,该功能需要和数据库交互,所以不是在前端实现的,所以这行代码无用
- $(“.link-success”).click(…):在orderConfirm.html页面没有class为link-success的标签,所以这行代码不会被执行
- 综上两条,orderConfirm.js文件在orderConfirm.html页面中无用,但存在可能会和下个模块"创建订单"功能冲突(下个模块会实现点击"创建订单"后页面跳转),所以注释掉
下面在orderConfirm.html页面编写js代码
<script type="text/javascript">
$(document).ready(function (){
showCartList()
})
function showCartList() {
$("#cart-list").empty();
/*
* location.search.substr:是获取url中的数据
* 1:表示查找?之前
* 0:表示查找?之后【请求参数】
*
* 这里要传data是因为后端接口需要传入参数,所以这里才要写
* */
$.ajax({
url: "/cart/list",
data: location.search.substr(1),
type: "GET",
dataType: "JSON",
success: function(json) {
let list = json.data;
console.log("count=" + list.length);
let allCount = 0;
let allPrice = 0;
for (let i = 0; i < list.length; i++) {
console.log(list[i].title);
let tr = '<tr>'
+ '<td><img src="..#{image}collect.png" class="img-responsive" /></td>'
+ '<td><input type="hidden" name="cid" value="#{cid}" />#{title}</td>'
+ '<td>¥<span>#{realPrice}</span></td>'
+ '<td>#{num}</td>'
+ '<td>¥<span>#{totalPrice}</span></td>'
+ '</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(/#{realPrice}/g, list[i].realPrice);
tr = tr.replace(/#{num}/g, list[i].num);
tr = tr.replace(/#{totalPrice}/g, list[i].realPrice * list[i].num);
$("#cart-list").append(tr);
allCount += list[i].num;
allPrice += list[i].realPrice * list[i].num;
}
$("#all-count").html(allCount);
$("#all-price").html(allPrice);
},
error: function() {
alert("购物车数据加载未知异常 + 原因:" + xhr.status);
}
});
}
</script>
1.为什么点击购物车列表页面的"结算"按钮后地址栏中会请求http://localhost:8080/web/orderConfirm.html?cids=6&cids=5呢,因为该按钮有一个type=submit属性,且表单有一个action="orderConfirm.html"属性,所以点击该按钮后会携带表单中参数自动跳转
会携带哪些参数呢:把表单中有name属性的标签的value值传递出去,针对这个请求传递的是name"cids",其value值根据勾选的商品而定,可以是1或3或10
2.data: location.search.substr(1)这个API的参数为0表示截取地址栏中?后面的数据,即参数
如果这个API的参数为0则表示截取地址栏中?前面的数据,即请求地址
4.2显示选择收货地址
收货地址存放在前端的一个select下拉列表中,我们需要将查询到的当前登录用户的收货地址动态的加载到这个下拉列表中.从数据库的角度看,是一个select查询语句,在"收货地址列表展示"模块已经编写了该持久层,业务层,控制层,所以这里只需要编写对应的前端页面就可以了
1.在orderConfirm.html页面中的ready函数中添加showAddressList方法的调用,使确认订单页加载时能够自动从后端获取该用户地址填充到select控件中并将第一个地址显示出来
$(document).ready(function() {
showCartList();
showAddressList();
});
2.在orderConfirm.html页面中编写showAddressList方法
function showAddressList(){
//清空select标签中的数据
$("#address-list").empty();
$.ajax({
url:"/address",
type: "GET",
dataType:"JSON",
success(e) {
if(e.state==200){
let list=e.data;
for(let i=0;i<list.length;i++){
/**
value="#{aid}"在该模块没有用,但是扔写上,只要是从数据库查到到的数据,都要让前端页
面的该条数据和id绑定(因为可能干别的什么时需要用到,就比如说下一个"创建订单"模块
就需要根据前端传给后端的aid查询用户选中的是哪一个地址然后将其加入订单表)
* */
let opt='<option value="#{aid}">#{name} #{tag} #{provinceName}#{cityName}#{areaName}#{address}' +
' #{phone}</option>\n';
opt = opt.replace(/#{aid}/g, list[i].aid);
opt = opt.replace(/#{tag}/g, list[i].tag);
opt = opt.replace("#{name}", list[i].name);
opt = opt.replace("#{provinceName}", list[i].provinceName);
opt = opt.replace("#{cityName}", list[i].cityName);
opt = opt.replace("#{areaName}", list[i].areaName);
opt = opt.replace("#{address}", list[i].address);
opt = opt.replace("#{phone}", list[i].phone);
$("#address-list").append(opt)
}
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
})
}