1.易错点
1.错误做法
新增收货地址页面的三个下拉列表的内容展示没有和数据库进行交互,而是通过前端实现的(将代码逻辑放在了distpicker.data.js文件中),实现方法是在加载新增收货地址页面时加载该js文件,这种做法不可取
2.正确做法
把这些数据保存到数据库中,用户点击下拉列表时相应的数据会被详细的展示出来,然后监听用户选择了哪一项以便后面的下拉列表进行二级关联
3.主要步骤
要将省市区进行展示,则
1.需要先从数据库获取全部数据
2.然后当选中【省】时,【市】可以自动找到对应的数据,找到【市】的时候可以自动找到【区】---二级联动
要点一:获取省市区列表
1.创建数据库
t_dict_district表
CREATE TABLE t_dict_district (
id INT(11) NOT NULL AUTO_INCREMENT,
parent VARCHAR(6) DEFAULT NULL,
`code` VARCHAR(6) DEFAULT NULL,
`name` VARCHAR(16) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
- code和name需要加``
- parent代表父区域的代码号
- code代表自身的代码号
- 省的父代码号是+86,代表中国
2.向该表中插入省市区数据
LOCK TABLES t_dict_district WRITE;
INSERT INTO t_dict_district VALUES (1,'110100','110101','东城区'),(2,'110100','110102','西城区')等等等等;
UNLOCK TABLES;
2.创建省市区的实体类
在包entity下创建实体类District(不需要继承BaseEntity,但因为没有继承BaseEntity所以需要实现接口Serializable序列化)
/**
* 表示省市区的数据实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class District extends BaseEntity {
private Integer id;
private String parent;
private String code;
private String name;
}
3. 持久层[Mapper]
1 规划需执行的SQL语句
select * from t_dict_district where parent=? order by ASC
2 设计接口和抽象方法
日后可能开发新的模块仍要用到省市区列表,那么为了降低耦合性,就要创建新的接口
在mapper层下创建接口DistrictMapper
public interface DistrictMapper {
/**
* 根据用户的父代号查询区域信息
* @param parent
*/
//查询的结果可能是多个,所以放在集合中
List<District> findByParent(String parent);
}
3 编写映射
创建一个DistrictMapper.xml映射文件并配置上述抽象方法的映射
<select id="findByParent" resultType="com.example.mycomputerstore.entity.District">
select *
from t_dict_district where parent=#{parent}
order by code asc;
</select>
4 单元测试
创建DistrictMapperTests测试类编写代码进行测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistrictMapperTests {
@Autowired
private DistrictMapper districtMapper;
@Test
public void findByParent() {
List<District> list = districtMapper.findByParent("210100");
for (District district : list) {
System.out.println(district);
}
}
}
4.业务层[Service]
1规划异常
没有异常需要处理
2 设计接口和抽象方法及实现
1.创建一个接口IDistrictService,并定义抽象方法
public interface IDistrictService {
/**
* 根据父代号来查询区域信息(省市区)
* @param parent
* @return 多个区域信息
*/
List<District> getByParent(String parent);
}
2.创建DistrictServiceImpl实现类来实现抽象方法
@Service
public class IDistrictServiceImpl implements IDistrictService {
@Autowired
private DistrictMapper districtMapper;
/**
* 根据父代号查找区域
*
* @param parent
* @return
*/
@Override
public List<District> getByParent(String parent) {
List<District> list = districtMapper.findByParent(parent);
//在进行网络数据传输时,为了尽量避免无效数据的传递,可以将无效数据设置为null
//可以节省浏览,另一方面提升效率
for (District d:list){
d.setId(null);
d.setParent(null);
}
return list;
}
}
3单元测试
在test下的service文件夹下创建DistrictServiceTests测试类
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistrictServiceTests {
@Autowired
private IDistrictService districtService;
@Test
public void getByParent() {
//86代表中国,所有的省父代码号都是86
List<District> list = districtService.getByParent("86");
for (District district : list) {
System.err.println(district);
}
}
}
5.控制层[Controller]
1 设计请求
- /districts/
- GET
- String parent
- JsonResult<List<District>>
2 处理请求
1.创建一个DistrictController类,在类中编写处理请求的方法
@RequestMapping("/district")
@RestController
public class DistrictController extends BaseController{
@Autowired
private IDistrictService districtService;
/**
* 请求路径和父路径相同时用@RequestMapping({"/",""}),表
* 示districts后面跟/或者什么也不跟都会进入这个方法
* 点进RequestMapping发现参数类型是String[],且传入一
* 个路径时默认有{},传入一个以上路径时需要手动添加{}
*/
@GetMapping({"/",""})
public JsonResult<List<District>> getByParent(String parent){
List<District> data = districtService.getByParent(parent);
return new JsonResult<>(OK,data);
}
}
2.为了能不登录也可以访问该数据,需要将districts请求添加到白名单中:
在LoginInterceptorConfigure类的addInterceptors方法中添加代码:patterns.add(“/districts/**”);
3.启动服务器,不登录账号,直接在地址栏输入http://localhost:8080/districts?parent=86测试能否正常获取数据
6.前端页面
1.原始的下拉列表展示是将数据放在js,再动态获取js中的数据,而目前为止我们已经将数据放在了数据库,所以不能让它再使用这种办法了,所以需要注释掉addAddress.html页面的这两行js代码:
<script type="text/javascript" src="../js/distpicker.data.js"></script>
<script type="text/javascript" src="../js/distpicker.js"></script>
关于这两行js代码:前者是为了获取数据,后者是为了将获取到的数据展示到下拉列表中
2.检查前端页面在提交省市区数据时是否有相关name属性和id属性(name用于提交数据,id用于监听用户的点击)
3.启动服务器,在前端验证一下是否还可以正常保存数据(除了省市区)
要点二:获取省市区名称
上一个模块获取省市区列表是通过父代码号获取子代码号完成联动,该模块获取省市区名称是通过自身的code获取自身的name
1.持久层[Mapper]
1规划需要执行的SQL语句
根据当前code来获取当前省市区的名称,对应就是一条查询语句
select * from t_dict_district where code=?
2 设计接口和抽象方法
在DistrictMapper接口定义findNameByCode方法
String findNameByCode(String code);
3 编写映射
在DistrictMapper.xml文件中添加findNameByCode方法的映射
<select id="findNameByCode" resultType="java.lang.String">
select name from t_dict_district where code=#{code}
</select>
4 单元测试
在DistrictMapperTests编写测试代码
@Test
public void findNameByCode() {
String name = districtMapper.findNameByCode("610000");
System.out.println(name);
}
2.业务层[Service]
1规划异常
没有异常需要处理
2 设计接口和抽象方法及实现
1.在IDistrictService接口定义对应的业务层接口中的抽象方法
String getNameByCode(String code);
2.在DistrictServiceImpl实现此方法
@Override
public String getNameByCode(String code) {
return districtMapper.findNameByCode(code);
}
3 单元测试
业务层只是调用持久层对应的方法然后返回,没有什么额外的实现,可以不用测试(一般超过8行的代码都要进行测试)
3.业务层[Controller]
实际开发中在获取省市区名称时并不需要前端传控制层,然后传业务层,再传持久层,而是在新增收货地址的业务层需要获取省市区名称,也就是说获取省市区名称的模块不需要控制层,只是需要被新增收货地址的业务层所依赖
4.业务层优化
1.在新增收货地址的业务层需要对address进行封装,使其存有所有数据,然后将address传给持久层(记住,持久层只会根据传过来的参数调用某个方法与数据库交互,永远不会有额外的实现),而此时新增收货地址的业务层并没有省市区的数据,所以需要依赖于获取省市区列表的业务层对应的接口中的getNameByCode方法
所以需要在业务层实现类AddressServiceImpl中加
@Autowired
private IDistrictService districtService;
2.在AddressServiceImpl的方法中将DistrictService接口中获取到的省市区数据封装到address对象,此时address就包含了所有用户收货地址的数据
/**
* 对address对象中的数据进行补全:省市区的名字看前端代码发现前端传递过来的省市区的name分别为:
* provinceCode,cityCode,areaCode,所以这里可以用address对象的get方法获取这三个的数据
*/
String provinceName = districtService.getNameByCode(address.getProvinceCode());
String cityName = districtService.getNameByCode(address.getCityCode());
String areaName = districtService.getNameByCode(address.getAreaCode());
address.setProvinceName(provinceName);
address.setCityName(cityName);
address.setAreaName(areaName);
5.前端页面
在addAddress.html页面中来编写对应的省市区展示及根据用户的不同选择来限制对应的标签中的内容
分析:
- 在加载该页面时三个下拉列表的内容都显示为"-----请选择-----"
- 没有选择市时如果点击区的下拉列表则列表中只有一个"-----请选择-----"
- 加载该页面时需要自动发送一个请求把parent=86发送出去,然后将返回的省/直辖市填充到select标签中
- 点击四川省后发送请求获取其下的市,并且将获取到的市罗列在市区域下拉列表中
- 省点击"-----请选择-----“则需要把市,县内容填充为”-----请选择-----"终止请求而不是程序继续跑下去
- 切换省份时,市,县内容更换为"-----请选择-----"
在addAddress.html中编写js代码
<script type="text/javascript">
<!-- 下拉框的默认选项:select的下拉默认选中的是option-->
//value属性用于表示当前中国区域的code值
/**因为清空后下拉列表的select标签没有option标签,所以需要设置一个默认的option标
* 签并给市,县加上该标签.option标签并不会把内容发送到后端,而是将value值发
* 送给后端,所以用value表示当前这个区域的code值
* */
let defaultOption="<option value='0'>---- 请选择 -----</option>"
/*
* 打开页面自动加载
* */
$(document).ready(function (){
//加载省的数据罗列时代码量较多,建议定义在外部方法中,然后在这里调用定义的方法
showProvinceList();
//设置默认的"请选择“的值,作为控制的默认值
/**
* select标签默认获取第一个option的内容填充到下拉列表中,所以即使加载
* 页面时省区域的下拉列表中已经有了所有省但仍然会显示-----请选择-----
* */
$("#province-list").append(defaultOption)
$("#city-list").append(defaultOption);
$("#area-list").append(defaultOption)
})
/*
* change()函数用于监听某一个控件是否发生改变,一旦发生改变就会触发参数的函数
* */
$("#province-list").change(function (){
//先获取行政区父代码
let parent = $("#province-list").val();
//1.先清空市区的select下拉列表中的所有option元素:【请选择】的默认数据
/**
* 如果我选择了河南省洛阳市涧西区,然后又选择了河北省,此时需要
* 将市,县下拉列表的所有option清除并显示内容-----请选择-----
* empty()表示某标签的所有子标签(针对此页面来说select的子标
* 签只有option)
* */
$("city-list").empty();
$("#area-list").empty()
//2.填充默认值
$("city-list").append(defaultOption);
$("#area-list").append(defaultOption)
//如果父亲code为0,没有选择
if(parent==0){//如果继续程序,后面的ajax接收的json数据中的data是
return;//空集合[],进不了for循环,没有任何意义,所以直接在这里终止程序
}
$.ajax({
url:"/district",
type:"GET",
data:"parent="+parent,
dataType:"JSON",
success(e){
if(e.state==200){
let list=e.data;
for(let i=0;i<list.length;i++){
// "<option value=''>name</option>"
let opt = "<option value='" + list[i].code + "'>" + list[i].name + "</option>";
$("#city-list").append(opt)
}
}else{
alert("城市信息加载失败")
}
},
})
})
$("#city-list").change(function (){
//先获取行政区父代码
let parent = $("#city-list").val();
//1.先清空市区的select下拉列表中的所有option元素:【请选择】的默认数据
$("#area-list").empty()
//2.填充默认值
$("#area-list").append(defaultOption)
//如果父亲code为0,没有选择
if(parent==0){
return;
}
$.ajax({
url:"/district",
type:"GET",
data:"parent="+parent,
dataType:"JSON",
success(e){
if(e.state==200){
let list=e.data;
for(let i=0;i<list.length;i++){
// "<option value=''>name</option>"
let opt = "<option value='" + list[i].code + "'>" + list[i].name + "</option>";
$("#area-list").append(opt)
}
}else{
alert("区县信息加载失败")
}
},
})
})
//省的下拉列表数据展示
function showProvinceList(){
$.ajax({
url:"/district",
type:"GET",
data:"parent=86",
dataType:"JSON",
success(e){
if(e.state==200){
let list=e.data;//获取所有省对象的list集合
for(let i=0;i<list.length;i++){
// "<option value=''>name</option>"
let opt = "<option value='" + list[i].code + "'>" + list[i].name + "</option>";
$("#province-list").append(opt)
}
}else{
alert("省/直辖市信息加载失败")
}
},
})
}
$("#btn-add-new-address").click(function (){
$.ajax({
url:"/address/add_new_address",
type:"POST",
data:$("#form-add-new-address").serialize(),
dataType:"JSON",
success(e){
if(e.state==200){
alert("新增收货地址成功")
}else{
alert("新增收货地址失败")
}
},
error(xhr){
alert("新增收货地址产生未知的异常"+xhr.status)
}
})
})
</script>