历史记录
一、运行环境
- windows10
- IDEA 2021.1 专业版
- JDK8
- SpringBoot2
- Druid 1.2.5
- Bootstrap 4.6.0
- MySQL 8
- Navicat 11
二、根据商品类查询
在实现此功能时有几个难点,如:1)搜索的同时要支持分页显示,保证下一页仍是当前类型;2)支持显示当前选择的类型。
解决思路:
对所有到商品页面的请求添加一个键值对,键为 comType,值为商品类型的ID,若是所有商品就设置为-1,这样在前端页面就可以用th:style 标签直接使用三目运算符进行判断,因显示分类标签时是通过th:each 循环显示的,所以可以得知当前的商品类型的ID。
除了分类按钮的显示外,还有分页的功能,之前分页请求的地址和固定类型后的请求地址不同,所以需要使用th:href 标签借助三目运算符进行判断,若当前comType是-1,则跟原先的请求地址一样/index/页数 ,若是其他的值,那么请求的地址需要变化/index/type/页数 。
至此,解决了分类显示、分页的问题,最后还有搜索商品的问题:之前的搜索是默认为所有商品的,现在需要加上一个类型的选择,这样用户可以搜索固定的类别,在前端样式上需稍作修改。
前端代码-搜索部分:
<nav class="nav input-group input-group-sm mt-3 container" th:fragment="search">
<div class="input-group-prepend">
<button id="btn-ctype" class="btn btn-outline-dark dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false"
th:text="${comType.comTypeName}" th:data-id="${comType.id}">
所有商品
</button>
<div class="dropdown-menu">
<a class="dropdown-item">所有商品</a>
<a class="dropdown-item"
th:each="ct: ${session.ctypeInfo}"
th:text="${ct.comTypeName}"
th:onclick="chose([[${ct.id}]], [[${ct.comTypeName}]])"
th:data-id="${ct.id}"
/>
</div>
</div>
<input id="input-search" th:value="${searchText}" type="text" class="form-control" placeholder="请输入查询的商品,商品名称/种类/...">
<div class="input-group-append">
<button class="input-group-text btn btn-info" id="btn-search">
<i class="bi bi-search"></i>
</button>
</div>
</nav>
前端代码-商品分页部分
<h1 th:text="'第' + ${pageInfo.pageNum} + '页 共' + ${pageInfo.pages} + '页'" class="u-text-sm text-center"></h1>
<nav aria-label="Page navigation" class="u-text-sm" th:if="${comInfo.size > 0}">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link u-a-com"
th:data-type="${comType.id}"
th:data-searchtext="${searchText}"
th:data-pagenow="${pageInfo.navigateFirstPage}">
首页
</a>
</li>
<li class="page-item">
<a class="page-link u-a-com"
th:data-type="${comType.id}"
th:data-searchtext="${searchText}"
th:data-pagenow="${pageInfo.prePage}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="page-item" th:each="pageNow : ${pageInfo.navigatepageNums}">
<a th:data-type="${comType.id}"
th:data-searchtext="${searchText}"
th:data-pagenow="${pageNow}" th:text="${pageNow}"
th:class="'page-link u-a-com' + ${pageNow == pageInfo.pageNum ? ' bg-info text-light' : ''}">
</a>
</li>
<li class="page-item">
<a class="page-link u-a-com"
th:data-type="${comType.id}"
th:data-searchtext="${searchText}"
th:data-pagenow="${pageInfo.nextPage}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
<li class="page-item">
<a class="page-link u-a-com"
th:data-type="${comType.id}"
th:data-searchtext="${searchText}"
th:data-pagenow="${pageInfo.navigateLastPage}">
末页
</a>
</li>
</ul>
</nav>
分页部分JS代码:
$('.u-a-com').click(function (){
let type = $(this).data('type')
let searchText = $(this).data('searchtext')
let pageNow = $(this).data('pagenow')
if(searchText != null)
$(this).attr('href', '/index.html?type=' + type + '&searchText=' + searchText + '&pageNow=' + pageNow)
else
$(this).attr('href', '/index.html?type=' + type + '&pageNow=' + pageNow)
})
控制层代码:
package com.uni.controller;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.uni.dao.CommodityMapper;
import com.uni.dao.CommodityTypeMapper;
import com.uni.pojo.Commodity;
import com.uni.pojo.CommodityExample;
import com.uni.pojo.CommodityType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Controller
public class CommodityAction {
@Autowired
CommodityMapper commodityMapper;
@Autowired
CommodityTypeMapper commodityTypeMapper;
@GetMapping("/index.html")
public String findAllByPage(ModelMap modelMap,
@RequestParam(value="type", defaultValue = "0") Integer type,
@RequestParam(value="searchText", defaultValue = "") String searchText,
@RequestParam(value = "pageNow" , defaultValue = "1") Integer pageNow,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize){
PageHelper.startPage(pageNow, pageSize);
CommodityExample example = new CommodityExample();
CommodityExample.Criteria criteria = example.createCriteria();
if(type != 0)
criteria.andComTypeEqualTo(type);
criteria.andComNameLike("%" + searchText + "%");
List<Commodity> comInfo = commodityMapper.selectByExample(example);
PageInfo<Commodity> pageInfo = new PageInfo<>(comInfo);
if(pageNow == 1) pageInfo.setPrePage(1);
if(pageInfo.getNextPage() == 0) pageInfo.setNextPage(Math.min(pageInfo.getPrePage() + 1, pageInfo.getPageNum()));
comInfo.forEach(com -> {
com.setCommodityType(commodityTypeMapper.selectByPrimaryKey(com.getComType()));
});
modelMap.addAttribute("pageNow", pageNow);
modelMap.addAttribute("pageSize", pageSize);
modelMap.addAttribute("comInfo", comInfo);
modelMap.addAttribute("pageInfo", pageInfo);
modelMap.addAttribute("searchText", searchText);
modelMap.addAttribute("comType", type == 0 ?
new CommodityType(0, "所有商品"): commodityTypeMapper.selectByPrimaryKey(type));
return "index";
}
}
三、使用JQuery插件实现图片上传
插件地址:点击访问
下载好插件后将JS、CSS插件复制到项目里,然后参考给出的index.html样例对自己的项目进行修改,最后的效果:
四、JS实现省市区三级联动
4.1 基本实现
全国省市地区信息的 MySql 数据代码 :点击查看
SQL脚本执行后的结果:
为了方便JDBC操作,笔者依旧采用之前MyBatis逆向工程的方式【点击查看】,不用手写Mapper接口和映射文件。MyBatis逆向工程的配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MySqlTables" defaultModelType="flat"
targetRuntime="MyBatis3">
<property name="beginningDelimiter" value="`" />
<property name="endingDelimiter" value="`" />
<property name="javaFileEncoding" value="UTF-8" />
<property name="javaFormatter"
value="org.mybatis.generator.api.dom.DefaultJavaFormatter" />
<property name="xmlFormatter"
value="org.mybatis.generator.api.dom.DefaultXmlFormatter" />
<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
<commentGenerator>
<property name="suppressDate" value="true" />
<property name="suppressAllComments" value="true" />
</commentGenerator>
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/simple_shop_system_2?characterEncoding=UTF-8"
userId="root"
password="数据库密码">
</jdbcConnection>
<javaTypeResolver
type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<javaModelGenerator targetPackage="com.uni.pojo"
targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="mybatis.mapper"
targetProject="src/main/resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.uni.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<table tableName="province" domainObjectName="Province">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="city" domainObjectName="City">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="area" domainObjectName="Area">
<property name="useActualColumnNames" value="true"/>
</table>
</context>
</generatorConfiguration>
跟之前一样,使用Maven插件生成完毕后,在DAO层的Mapper接口类上方添加@Component 注解,注入到Spring IoC容器中,同时在相应的pojo类都添加toString() 方法,笔者建议用lombok包,使用@Data 注解就能实现,特别方便。
PS:SpringBoot项目记得设置支持扫描通过注解标记的Mapper接口类的包,方法有多种,笔者是在SpringBoot启动类上方使用@MapperScan(value = "DAO层所在包名") 的注解。
测试代码:
@Autowired
ProvinceMapper provinceMapper;
@Autowired
CityMapper cityMapper;
@Test
public void TestProvinceCityArea(){
ProvinceExample provinceExample = new ProvinceExample(){{
createCriteria().andNameLike("%浙江%");
}};
List<Province> provinces = provinceMapper.selectByExample(provinceExample);
Province ZheJiang = provinces.get(0);
CityExample cityExample = new CityExample(){{
createCriteria().andProvincecodeEqualTo(ZheJiang.getCode());
}};
System.out.println(ZheJiang);
cityMapper.selectByExample(cityExample).forEach(city -> System.out.println(city));
}
运行结果: 控制层,负责返回数据库的数据:
package com.uni.controller;
import com.alibaba.fastjson.JSON;
import com.uni.dao.AreaMapper;
import com.uni.dao.CityMapper;
import com.uni.dao.ProvinceMapper;
import com.uni.pojo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class AddressAction {
@Autowired
ProvinceMapper provinceMapper;
@Autowired
CityMapper cityMapper;
@Autowired
AreaMapper areaMapper;
@GetMapping("/getProvinces")
public String getProvinces(){
return JSON.toJSONString(provinceMapper.selectByExample(new ProvinceExample()));
}
@GetMapping("/getCities")
public String getCities(String provCode){
CityExample cityExample = new CityExample() {{
createCriteria().andProvincecodeEqualTo(provCode);
}};
List<City> cities = cityMapper.selectByExample(cityExample);
return JSON.toJSONString(cities);
}
@GetMapping("/getAreas")
public String getAreas(String cityCode){
AreaExample areaExample = new AreaExample(){{
createCriteria().andCitycodeEqualTo(cityCode);
}};
List<Area> areas = areaMapper.selectByExample(areaExample);
return JSON.toJSONString(areas);
}
}
实现省市区三级联动的JS代码:
var userProvince = $('select[name="userProvince"]').data('addr')
var userCity = $('select[name="userCity"]').data('addr')
var userArea = $('select[name="userArea"]').data('addr')
var userAddress = $('input[name="userAddress"]').data('addr')
$(function() {
initAddress();
$("select[name='userProvince']").change(function() {
var provCode = $("select[name='userProvince']").val();
getCity(provCode);
});
$("select[name='userCity']").change(function() {
var cityCode = $("select[name='userCity']").val();
getArea(cityCode);
});
});
function initAddress() {
$('input[name="userAddress"]').val(userAddress)
var firstProvCode ='0';
$.get({
url: "/getProvinces",
dataType: 'JSON',
error: (err) => {
alert('获取省份失败.')
console.error(err.responseText)
},
success: (data) => {
$.each(data, function(i, d) {
if(d.name === userProvince) firstProvCode = d.code
$("select[name='userProvince']").append(
"<option value='" + d.code + (d.name===userProvince ? "' selected>" : "'>") + d.n
+ "</option>");
});
if(firstProvCode != '0')
$("select[name='userProvince']").css('value', userProvince)
else
firstProvCode = data[0].code;
getCity(firstProvCode);
}
});
}
function getCity(provCode) {
var firstCityCode = '0';
$.get({
url: "/getCities",
data: {provCode:provCode},
dataType: 'JSON',
error: (err) => {
alert('获取市区失败.')
console.error(err.responseText)
},
success: (data) => {
$("select[name='userCity']").empty();
$.each(data, function(i, d) {
if(d.name === userCity) firstCityCode = d.code
$("select[name='userCity']").append(
"<option value='" + d.code + (d.name===userCity ? "' selected>" : "'>") + d.name
+ "</option>");
});
if(firstCityCode != '0') {
$("select[name='userCity']").attr('value', userCity)
}else firstCityCode = data[0].code;
getArea(firstCityCode);
}
})
}
function getArea(cityCode) {
$.get({
url: '/getAreas',
data: {cityCode:cityCode},
dataType: 'JSON',
error: (err) =>{
alert('获取地区信息失败.')
console.error(err.responseText)
},
success: (data) => {
$("select[name='userArea']").empty();
$.each(data, function (i, d) {
$("select[name='userArea']").append(
"<option value='" + d.code + (d.name===userArea ? "' selected>" : "'>") + d.name
+ "</option>");
});
}
})
}
前端HTML代码:
<li>
<label>地址:</label>
<select name="userProvince" th:data-addr="${userProvince}" style="font-size: 10px"></select>
<select name="userCity" th:data-addr="${userCity}" style="font-size: 10px"></select>
<select name="userArea" th:data-addr="${userArea}" style="font-size: 10px"></select>
</li>
<li>
<label>详细地址:</label>
<input name="userAddress" th:data-addr="${userAddress}" type="text" placeholder="详细地址">
</li>
4.2 优化:修改时先显示用户的地区信息
在之前SQL表的基础上,User用户信息表里address字段表示当前用户的地址,而真正的地址有四个部分,分别为省、市/区 、县/镇、详细地址。
这里笔者采用四合一方式,使用分隔符 - 将四个信息连接起来成一个字符串序列
控制层代码:
package com.uni.controller;
import com.uni.pojo.User;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
@Log4j2
@Controller
public class UserAction {
@GetMapping("/user.html")
public String toUserCenter(){
return "redirect:/user/info.html";
}
@GetMapping("/user/info.html")
public String toInfo(HttpSession session, ModelMap modelMap){
User user = (User) session.getAttribute("userInfo");
String[] addr = user.getAddress().split("-");
if(addr!=null && addr.length == 4){
modelMap.addAttribute("userProvince", addr[0]);
modelMap.addAttribute("userCity", addr[1]);
modelMap.addAttribute("userArea", addr[2]);
modelMap.addAttribute("userAddress", addr[3]);
}
modelMap.addAttribute("type", "info");
return "user";
}
}
五、Bug & DeBug
在使用 Bootstrap4.6版本的下拉菜单时报错
bootstrap.js:1667 Uncaught TypeError: Bootstrap's dropdowns require Popper (https://popper.js.org)
at Dropdown.show (bootstrap.js:1667:17)
at Dropdown.toggle (bootstrap.js:1635:12)
at HTMLButtonElement.<anonymous> (bootstrap.js:1863:23)
at Function.each (jquery.js:354:19)
at jQuery.fn.init.each (jquery.js:189:17)
at jQuery.fn.init._jQueryInterface [as dropdown] (bootstrap.js:1848:19)
at HTMLButtonElement.<anonymous> (bootstrap.js:2030:31)
at HTMLDocument.dispatch (jquery.js:5183:27)
at HTMLDocument.elemData.handle (jquery.js:4991:28)
解决方式一:在导入bootstrap.js前导入popper.js 脚本,这是下拉菜单用到的拓展插件,插件官网:点击查看
pom.xml
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>popper.js</artifactId>
<version>1.16.1</version>
</dependency>
Thymeleaf页面导入脚本:
<script type="text/javascript" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
<script type="text/javascript" th:src="@{/webjars/popper.js/1.16.1/dist/umd/popper.js}"></script>
<script type="text/javascript" th:src="@{/webjars/bootstrap/4.6.0/js/bootstrap.js}"></script>
解决方式二:使用Bootstrap4框架提供的集成后的JS脚本 bootstrap.bundle.js
<script type="text/javascript" th:src="@{/webjars/bootstrap/4.6.0/js/bootstrap.bundle.js}"></script>
|