前言:
1、基本的功能包括登录、商品展示、商品添加、购物车展示、总价动态展示。
2、前端的框架这次采用了Bootstrap,说一下用它的优点是什么 --- Bootstrap table可以自动分页、有搜索的按钮,都是基于前端就能实现的。
3、mybatis逆向工程文件生成所需要的dao、mapper和entity。
前端实现:
1、login.html文件用于做登录功能,这里使用到Bootstrap的form表单。
2、index.html文件用于展示商品,这里使用到Bootstrap的table插件,后台再对它进行渲染。
3、cart.html用于展示购物车,在主页添加的商品都会在这里生成对应的订单。
4、每个表格的后面都要能添加监听事件,用于监听行事件,例如主页就是 监听添加的事件选定的行信息,购物车页面则是修改和删除
登录页面:
首页:
购物车页面:
(上面的计算总价还有一个bug还没解决,后续会给出解决的方法)
实现过程
1、登录功能我们可以参照我的上一篇博客。https://blog.csdn.net/qq_34093082/article/details/99551022,在springboot的应用中,登录功能的写法是最常见的其中一种,使用form表单提交信息,后台控制层判断用户是否存在,如果用户存在,再判断用户的密码是否正确,若全部正确,就返回首页。这里给出这部分控制层的代码。
LoginController.java
package com.txy.omall.controller;
import com.txy.omall.model.User;
import com.txy.omall.service.IUser;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/login")
public class LoginController {
private static final String SUCCESS_CODE = "200";
private static final String FAIL_CODE = "500";
private static final String ERROR_CODE = "400";
@Autowired
private IUser UserService;
@RequestMapping("/page")
public String getLoginPage(){
return "login.html";
}
@PostMapping("/checkLogin")
@ResponseBody
public String checkLoginInfo(@Param("userName") String userName , @Param("password") String password){
User user =UserService.getUserByName(userName);
String userPassword = "";
if(user != null){
userPassword = user.getUserPwd();
}
if(user == null){
System.out.println("用户不存在");
return FAIL_CODE;
}else if (userPassword.equals(password)){
System.out.println("成功登陆");
return SUCCESS_CODE;
}else{
System.out.println("密码错误");
return ERROR_CODE;
}
}
}
2、首页功能的实现,包括商品展示,商品添加到购物车。
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zmall商城主页</title>
<!-- Bootstrap -->
<link href="../bootstrap/css/bootstrap.css" rel="stylesheet">
<!-- 引入bootstrap-table样式 -->
<link href="../css/bootstrap-table.css" rel="stylesheet">
<!-- 引入bootstrap的css样式-->
<link href="../css/editTable.css" rel="stylesheet">
</head>
<body>
<h1>Zmall网上商城</h1>
<a href="/login/page" class="btn btn-success active" role="button">登录</a>
<a href="/cart/page" class="btn btn-success active" role="button">购物车</a>
<br/>
<table class="table table-striped table-bordered table-hover" id="goodsTable"></table>
<div class="modal fade" id="addContent">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">购买情况</h4>
</div>
<form id="form_data" onsubmit="return addCartItem()" method="post">
<div class="modal-body">
<label for="goodsId">商品编号</label>
<input type="text" id="goodsId" value="" readonly="readonly"/><br/>
<label for="goodsName">商品名称</label>
<input type="text" id="goodsName" value="" readonly="readonly"/><br/>
<label for="goodsPrice">商品价格</label>
<input type="text" id="goodsPrice" value="" readonly="readonly"/><br/>
<label for="amount">购买数量</label>
<input type="text" id="amount" value=""/><br/>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-default" >提交</button>
</div>
</form>
</div>
</div>
</div>
<a href = "/Goods/list">显示数据</a>
<script src="../js/jquery.min.js"></script>
<script src="../bootstrap/js/bootstrap.js"></script>
<!-- 引入中文语言包 -->
<script src="../js/bootstrap-table.js"></script>
<!-- bootstrap-table.min.js -->
<script src="../js/bootstrap-table-editable.js"></script>
<script src="../js/bootstrap-table-zh-CN.js"></script>
<script src="../js/bootstrap-table-export.js"></script>
<script src="../js/index.js"></script>
</body>
</html>
需要注意两点,第一点是 js的引入顺序,一定要保证基本的js要先引入,假如出现的是XX is not a function ,就查看一下自己的JS是否正确引入(可以用alert($)来查看jquery是否正确引入,如果是undefined就是没有引入,但是如果是有引入它会显示一个function出来),第二点是table的加载我们放在js渲染了,唯一的一个addContent的div是默认隐藏的,只有当点击表格的购买按钮,该部分的内容才会显示出来。
index.js
// alert($);
function operateBtns(value, row, index) {
return [
'<button id="addToCart" type="button" class="btn btn-default" >购买</button>',
'<button id="deleteGoods" type="button" class="btn btn-default">删除</button>',
].join('');
}
function refresh(){
$.ajax({
url:"/cart/list",
success: function(data){
alert(data);
return data;
}
})
}
// 显示弹框,并且将这一行的信息填入进去
function addOrder(goodsId,goodsPrice,goodsName){
$('#goodsId').val(goodsId);
$('#goodsName').val(goodsName);
$('#goodsPrice').val(goodsPrice);
$('#addContent').modal('show');
}
// 提交订单
function addCartItem(){
$.post({
url:"/Goods/addToCart",
data:{
"goodsId":$("#goodsId").val() ,
"goodsName":$("#goodsName").val(),
"goodsPrice":$("#goodsPrice").val(),
"amount":$("#amount").val()
},
error : function () {
alert("插入出错");
window.location.href="/";
},
success: function(data){
if (data == "200"){
alert("插入成功");
window.location.href="/";
}
else{
alert("插入失败");
}
// $('#addContent').modal('hide');
}
})
}
window.operateEvents = {
'click #deleteGoods': function (e, value, row, index) {
$.ajax({
url: "/goods/deleteGoods",
data: {
goodsId:row.goodsId
},
dataType: "json",
async:true,
success: function (goodsData) {
if (goodsData != null) {
alert("删除成功");
}else{
alert("删除失败");
}
}
})
},'click #addToCart': function (e, value, row, index) {
addOrder(row.goodsId,row.goodsPrice,row.goodsName);
}
};
$(function ( ) {
$('#goodsTable').bootstrapTable('destroy');
$('#goodsTable').bootstrapTable({//表格初始化
url: '/Goods/list',
search: true,
searchOnEnterKey: true,
searchAlign: "left",
buttonsAlign: "left",
editable: true,//开启编辑模式
clickToSelect: true,
clickEdit: true,
method: 'get',
pageSize: 10, //每页3条
pageNumber: 1, //第1页
pageList: [8, 25], //在使用过程中根据情况调整每页条数.虽然你现在定义的每页3条,但你可以随时调整为10条或25条。
cache: false, //不缓存
striped: true,
pagination: true,
sidePagination: 'client',
showRefresh: true,
showExport: false,
showFooter: true,
showToggle: true,
columns: [
{field: 'goodsId', title: '商品号', sortable: true, align:"center",edit: {required: true, type: 'text'}},
{field: 'type', title: '类型', sortable: true, align:"center"},
{field: 'goodsName', title: '名称', sortable: true, align:"center"},
{field: 'goodsPrice', title: '价格', sortable: true, align:"center"},
{field: 'Button', title: '操作', align:"center", events: operateEvents, formatter: operateBtns, width: 180}]
});
})
operateBtns() 用于展示按钮,通过 table 的 formatter 引入
operateEvents() 用于获取行信息的点击事件,里头放了两个点击动作,一个是删除的点击动作,直接向后台传了条删除信息,一个是购买的点击动作,对应一个addOrder的函数,将我们事先隐藏的addContent的div显示出来,并且将行信息的数据赋值到里面的 input的框当中。
addCartItem() 用于处理input框的表单提交之后往后台传值的那部分动作。
$('#goodsTable').bootstrapTable('destroy'); //表格初始化前,需要先清空加载的内容,否则会出错
$('#goodsTable').bootstrapTable({}) //表格初始化
从上面的代码可以看到,我们展示商品的功能是在表格初始化的时候进行的,bootstrap和layui在传数据的时候的区别是,layui除了data以外还需要status,message,total这几个属性加入,所以我常常多做一步数据的组合,其他几个属性我也能用到,就多做了一步操作其实也便利了很多(毕竟前端可以直接调用就可以),而bootstrap-table 它就是可以直接使用json格式来传,bootstrap的分页功能也很好用,它可以选择在client端分页或是在server端分页。
每步的操作都伴随着状态码的返回,不同的状态码对应了不同的查询(更新、插入)所碰到的情况。
后端的控制层有一个业务逻辑判断需要提一下:
添加到购物车的时候,如果购物车里头该商品已经存在,现在的添加应该是在原有的商品的数量上面再加上现在的数量,如果该商品之前没有存在,添加的动作就是新建一条购物车的条目。
因此它包含三条操作,查询订单是否存在,插入一条新的订单,更新订单的数量(后两者是互斥的)
GoodsController.java
package com.txy.omall.controller;
import com.txy.omall.model.CartItem;
import com.txy.omall.model.Goods;
import com.txy.omall.service.IGoods;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/Goods")
public class GoodsController {
private final static String SUCCESS_CODE = "200";
private final static String FAIL_CODE ="500";
@Autowired
private IGoods goodsService;
@GetMapping("/list")
@ResponseBody
public List<Goods> getGoodsAll(){
Map<String,Object> map = new HashMap<String,Object>();
List<Goods> goodsList = goodsService.getGoodsAll();
if(goodsList != null){
map.put("data",goodsList);
}
return goodsList;
}
@PostMapping("/addToCart")
@ResponseBody
public String addToCart(@Param("goodsId") String goodsId ,@Param("goodsName") String goodsName ,@Param("goodsPrice") String goodsPrice,@Param("amount")String amount){
int inputAmount = Integer.parseInt(amount);
CartItem cartItem = new CartItem();
cartItem = goodsService.getCartItemById(goodsId);
boolean insertResult;
if(cartItem == null){
insertResult = goodsService.addToCart(goodsId,goodsName,goodsPrice,amount);
}else{
int oldAmount = Integer.parseInt(cartItem.getAmount());
int newAmount = inputAmount+oldAmount;
String newAmountStr =String.valueOf(newAmount);
insertResult = goodsService.addAmount( goodsId ,newAmountStr);
}
if (insertResult){
return SUCCESS_CODE;
}else {
return FAIL_CODE;
}
}
}
业务层和持久层的代码就不给了吧,它和layui的那篇博客的后台是差不多,大家可以参照它。
3、购物车功能
购物车功能的实现包括 删除购物车条目、更改购物车信息(主要是数量)、计算总价、显示总价
首先更改购物车信息这个功能和前面的(订单已经存在,再添加就是修改数量)功能是一样的,都是通过goodsId找到一条购物车的信息,然后update。
UPDATE cart_item SET amount= #{amount} where goodsId= #{goodsId}
这边主要是删除功能的区别。
cart.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zmall商城购物车页</title>
<!-- Bootstrap -->
<link href="../bootstrap/css/bootstrap.css" rel="stylesheet">
<!-- 引入bootstrap-table样式 -->
<link href="../css/bootstrap-table.css" rel="stylesheet">
<!-- 引入bootstrap的css样式-->
<link href="../css/editTable.css" rel="stylesheet">
</head>
<body>
<h1>Zmall网上商城</h1>
<a href="/" class="btn btn-success active" role="button">主页</a>
<!--<a href="/" class="btn btn-success active" role="button" onclick="refresh()">表格数据</a>-->
<br/>
<table class="table table-striped table-bordered table-hover" id="cartTable"></table>
<div class="modal fade" id="updateContent">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">修改订单</h4>
</div>
<form id="form_data" onsubmit="return updateCartItem()" method="post">
<div class="modal-body">
<label for="goodsId">商品编号</label>
<input type="text" id="goodsId" value="" readonly="readonly"/><br/>
<label for="goodsName">商品名称</label>
<input type="text" id="goodsName" value="" readonly="readonly"/><br/>
<label for="amount">购买数量</label>
<input type="text" id="amount" value=""/><br/>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-default">提交</button>
</div>
</form>
</div>
</div>
</div>
<a href = "/cart/list">显示数据</a>
<script src="../js/jquery.min.js"></script>
<script src="../bootstrap/js/bootstrap.js"></script>
<!-- 引入中文语言包 -->
<script src="../js/bootstrap-table.js"></script>
<!-- bootstrap-table.min.js -->
<script src="../js/bootstrap-table-editable.js"></script>
<script src="../js/bootstrap-table-zh-CN.js"></script>
<script src="../js/bootstrap-table-export.js"></script>
<script src="../js/cart.js"></script>
</body>
</html>
cart.js
// alert($);
function operateBtns(value, row, index) {
return [
'<button id="updateCartItem" type="button" class="btn btn-default" >修改</button>',
'<button id="deleteCartItem" type="button" class="btn btn-default">删除</button>',
].join('');
}
// function refresh(){
// $.ajax({
// url:"/cart/list",
// success: function(data){
// alert(data);
// return data;
// }
// })
// }
// 显示弹框,并且将这一行的信息填入进去
function updateAmount(goodsId,goodsName,amount){
$('#goodsId').val(goodsId);
$('#goodsName').val(goodsName);
$('#amount').val(amount);
$('#updateContent').modal('show');
}
// 提交订单
function updateCartItem(){
$.post({
url:"/cart/updateCartItem",
data:{
"goodsId":$("#goodsId").val(),
"amount":$("#amount").val()
},
error : function () {
alert("插入出错");
},
success: function(data){
if (data == "200"){
alert("插入成功");
}
else{
alert("插入失败");
}
// $('#addContent').modal('hide');
}
})
}
function sumOrderPrice(value){
console.log(value);
var count = 0; for (var i in value) { count += value[i].totalPrice; } return count;
}
window.operateEvents = {
'click #deleteCartItem': function (e, value, row, index) {
$.ajax({
url: "/cart/deleteCartItem",
data: {
id:row.id
},
dataType: "json",
async:true,
success: function (cartData) {
if (cartData != null) {
alert("删除成功");
}else{
alert("删除失败");
}
}
})
},'click #updateCartItem': function (e, value, row, index) {
updateAmount(row.goodsId,row.goodsName,row.amount);
}
};
$(function ( ) {
$('#cartTable').bootstrapTable('destroy');
$('#cartTable').bootstrapTable({//表格初始化
url: '/cart/list',
search: true, //用于开启搜索框
searchAlign: "left",
buttonsAlign: "left",
editable: true,//开启编辑模式 ,bootstrap 可编辑表格没有开
clickToSelect: true,
clickEdit: true,
method: 'get',
pageSize: 10, //每页3条
pageNumber: 1, //第1页
pageList: [8, 25], //在使用过程中根据情况调整每页条数.虽然你现在定义的每页3条,但你可以随时调整为10条或25条。
cache: false, //不缓存
striped: true,
pagination: true,
sidePagination: 'client', //设置分页是在客户端分页,设置成server就是在服务器端分页
showRefresh: true, //刷新表格按钮
showExport: false, //导出表格按钮,需要引用export.js
showFooter: true, //如果列数过多以至于超过屏幕的话会出现滚动条
showToggle: true,
columns: [
{field: 'id', title: '订单号', sortable: true, align:"center",edit: {required: true, type: 'text'}},
{field: 'goodsId', title: '商品编号', sortable: true,visible:false, align:"center"},
{field: 'goodsName', title: '名称', sortable: true, align:"center"},
{field: 'goodsPrice', title: '单价', sortable: true, align:"center"},
{field: 'amount', title: '数量', sortable: true,align:"center",footerFormatter: sumOrderPrice},
{field: 'totalPrice',title: '总价', align:"center",formatter:function(value, row, index){return row.goodsPrice*row.amount},footerFormatter:function (value) { console.log(value);
var count = 0; for (var i in value) { count += value[i].totalPrice; } return count; }},
{field: 'Button', title: '操作', align:"center", events: operateEvents, formatter: operateBtns, width: 180}]
});
})
cartController.java
package com.txy.omall.controller;
import com.txy.omall.model.Cart;
import com.txy.omall.model.CartItem;
import com.txy.omall.service.ICart;
import com.txy.omall.service.IGoods;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/cart")
public class CartController {
private static final String SUCCESS_CODE = "200";
private static final String FAIL_CODE = "500";
@RequestMapping("/page")
public String getCartPage(){
return "cart.html";
}
@Autowired
private ICart cartService;
@Autowired
private IGoods goodsService;
@GetMapping("/list")
@ResponseBody
public List<CartItem> getCartList(){
List<CartItem> cartItemList = new ArrayList<>();
cartItemList = cartService.getCartItemList();
return cartItemList;
}
@GetMapping("/deleteCartItem")
@ResponseBody
public List<CartItem> deleteCartItem(@Param("id") String id ){
int inputId = Integer.parseInt(id);
if(cartService.deleteCartItem(inputId)){
List<CartItem> cartItemList = new ArrayList<>();
cartItemList = cartService.getCartItemList();
return cartItemList;
}else{
return null;
}
}
@PostMapping("/updateCartItem")
@ResponseBody
public String updateCartItem(@Param("amount")String amount,@Param("goodsId")String goodsId){
boolean updateResult = goodsService.addAmount(goodsId,amount);
if(updateResult){
return SUCCESS_CODE;
}else{
return FAIL_CODE;
}
}
}
为什么这里return的是一个list,当初,考虑到的是重新加载表格的时候,可以做到局部刷新,这个地方还有问题。
然后回到功能需求,计算总价是怎么做到的,我们先算行总价(单个订单的总价),再通过行总价计算列总价。
行总价是这样实现的:
{field: 'totalPrice',title: '总价', align:"center",formatter:function(value, row, index){return row.goodsPrice*row.amount}}
列总价还存在问题,获取data的时候获取不到totoalPrice这一列的值,其他的却都能获取得到。猜想也许是因为totalPrice不是从后端传来的值,而formatter和footformatter是同时加载的,所以得到的值为空。【已经通过其他方法解决,详情见https://blog.csdn.net/qq_34093082/article/details/100151900】
总结:
1、bootstrap和layui都是很好的框架,二者使用的时候有太多相似的地方了,对layui熟悉之后再去看bootstrap有很多的相似的地方。
2、尽量使用对象来封装传值的结果,要考虑到代码的拓展性。
3、若传值对象叫做User,User为NULL ,此时使用User.get方法会报NULL_Exception,因此我们在获取对象的第一步是做判断是否为空。
4、bootstrap-table如果想做成可编辑的样式,需要引入bootstrap-table-editable的js文件和css样式。这个过程我没引入成功,因此最后修改的过程换成了弹出框的形式。
5、使用bootstrap做局部刷新,include一个页面将该table包括进去来刷新。