猿实战是一个原创系列文章,通过实战的方式,采用前后端分离的技术结合SpringMVC Spring Mybatis,手把手教你撸一个完整的电商系统,跟着教程走下来,变身猿人找到工作不是问题。想要一起实战吗?关注公号即可获取基础代码。
上一章节,猿人君教会了你一个新鲜的东西——猿实战05——手把手教你拥有自己的代码生成器。大家掌握原理,知道怎么去抽象你的代码就好了,莫要过于纠结。今天我们来学习新的东西——地址管理。
也许你会好奇,电商系统里怎么会有地址管理这个概念呢?嗯,地址其实是电商系统的通用数据,网购过的朋友都知道,下单的时候一定会叫你填写收货地址。地址数据在此时尤为重要,在构建地址数据库时,尽量的让它标准化统一化,方便整个系统的使用,我们先看看,要做的功能。
数据库设计
其实一个用于运营的电商系统中,地址的数据是相当庞大而且繁复的。你在填写用户地址的时,往往要求填写的是四级地址而非三级地址。为什么会是四级?国家地址库一般不是三级吗?这个四级地址是哪里来的呢?
嗯,这个四级地址其实是很宝贵的资源。都是配送人员在实际的工作中收集下来,最后经过筛选,合并,形成一套标准地址。这些地址,往往表达的是某某街道,某某片区,某村几社,这个级别了,这是长期工作的积累,到目前为止,数据量其实是在3亿条以上,各个公司的数据都会不同。猿人君也没有办法搞到4级地址,所以我们在功能上只做了三级。
不过考虑到未来的扩展,那么四级地址的维护任务会比较繁重,所以在设计上和传统的设计会有一些区别。相信大家见过太多的地址表,往往是父子结构的,一张表搞定。不过我们为了未来的四级地址考虑,还是拆开来设计,分别维护,省、市、区、三级地址,以后要做四级地址时,再扩展一张表(考虑数据量够吗?)就好了。
代码生成初体验
既然我们已经自己写过代码生成器了,准备牛刀小试一下吧。三张表,要是自己手写,还是比较麻烦的。不过今天也就是体验下昨天的成果,对于新手同学而言,仅此一次吧,以后还是老老实实去码,太早用这类东西,对你的发展不是很好。
我们打开我们编写的代码生成器,然后开始做修改一些基本的配置,如下图:
打开CgTest类,三张表,我们定义好表名,以及PoJo名就好了,如下图。
然后运行程序,代码就好了。
我们将生成的代码copy到对应目录,并完成相应的配置。
Controller
考虑到每个页面的功能都比较类似,都是新增,修改以及列表的功能,不过数据还是分开维护的,我们编写三个Controller,去应对这些功能就可以了。
/**
* Copyright(c) 2004-2020 pangzi
* com.pz.basic.mall.controller.sys.MallProvinceController.java
*/
package com.pz.basic.mall.controller.sys;
import com.pz.basic.mall.domain.base.Result;
import com.pz.basic.mall.domain.sys.MallProvince;
import com.pz.basic.mall.domain.sys.query.QueryMallProvince;
import com.pz.basic.mall.service.sys.MallProvinceService;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
*
* @author pangzi
* @date 2020-06-22 20:47:27
*
*
*/
@RestController
@RequestMapping("/provinceManage")
public class MallProvinceController {
private MallProvinceService mallProvinceService;
public void setMallProvinceService(MallProvinceService mallProvinceService) {
this.mallProvinceService = mallProvinceService;
}
/**
* 新增省
* @param mallProvince
* @return
*/
@RequestMapping("/addMallProvince")
public Result<MallProvince> addMallProvince(@RequestBody MallProvince mallProvince){
try{
return mallProvinceService.addMallProvince(mallProvince);
}catch(Exception e){
e.printStackTrace();
return new Result(false);
}
}
/**
* 修改省
* @param mallProvince
* @return
*/
@RequestMapping("/updateMallProvince")
public Result updateMallProvince(@RequestBody MallProvince mallProvince){
try{
return mallProvinceService.updateMallProvinceById(mallProvince);
}catch(Exception e){
e.printStackTrace();
return new Result(false);
}
}
/**
* 分页返回省列表
* @param queryMallProvince
* @return
*/
@RequestMapping("/findByPage")
public Result<List<MallProvince>> findByPage(@RequestBody QueryMallProvince queryMallProvince){
return mallProvinceService.getMallProvincesByPage(queryMallProvince);
}
}
/** * Copyright(c) 2004-2020 pangzi * com.pz.basic.mall.controller.sys.MallCityController.java */package com.pz.basic.mall.controller.sys; import com.pz.basic.mall.domain.base.Result;import com.pz.basic.mall.domain.sys.MallCity;import com.pz.basic.mall.domain.sys.query.QueryMallCity;import com.pz.basic.mall.service.sys.MallCityService;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * * @author pangzi * @date 2020-06-22 20:47:27 * * */@RestController@RequestMapping("/cityManage")public class MallCityController { private MallCityService mallCityService; public void setMallCityService(MallCityService mallCityService) { this.mallCityService = mallCityService; } /** * 新增城市 * @param mallCity * @return */ @RequestMapping("/addMallCity") public Result<MallCity> addMallCity(@RequestBody MallCity mallCity){ try{ return mallCityService.addMallCity(mallCity); }catch(Exception e){ e.printStackTrace(); return new Result(false); } } /** * 修改城市 * @param area * @return */ @RequestMapping("/updateMallCity") public Result updateMallCity(@RequestBody MallCity area){ try{ return mallCityService.updateMallCityById(area); }catch(Exception e){ e.printStackTrace(); return new Result(false); } } /** * 分页返回城市列表 * @param queryMallCity * @return */ @RequestMapping("/findByPage") public Result<List<MallCity>> findByPage(@RequestBody QueryMallCity queryMallCity){ return mallCityService.getMallCitysByPage(queryMallCity); } }
/**
* Copyright(c) 2004-2020 pangzi
* com.pz.basic.mall.controller.sys.MallAreaController.java
*/
package com.pz.basic.mall.controller.sys;
import com.pz.basic.mall.domain.base.Result;
import com.pz.basic.mall.domain.sys.MallArea;
import com.pz.basic.mall.domain.sys.query.QueryMallArea;
import com.pz.basic.mall.service.sys.MallAreaService;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
*
* @author pangzi
* @date 2020-06-22 20:47:27
*
*
*/
@RestController
@RequestMapping("/areaManage")
public class MallAreaController {
private MallAreaService mallAreaService;
public void setMallAreaService(MallAreaService mallAreaService) {
this.mallAreaService = mallAreaService;
}
/**
* 新增地区
* @param area
* @return
*/
@RequestMapping("/addMallArea")
public Result<MallArea> addMallArea(@RequestBody MallArea area){
try{
return mallAreaService.addMallArea(area);
}catch(Exception e){
e.printStackTrace();
return new Result(false);
}
}
/**
* 修改地区
* @param area
* @return
*/
@RequestMapping("/updateMallArea")
public Result updateMallArea(@RequestBody MallArea area){
try{
return mallAreaService.updateMallAreaById(area);
}catch(Exception e){
e.printStackTrace();
return new Result(false);
}
}
/**
* 分页返回地区列表
* @param queryMallArea
* @return
*/
@RequestMapping("/findByPage")
public Result<List<MallArea>> findByPage(@RequestBody QueryMallArea queryMallArea){
return mallAreaService.getMallAreasByPage(queryMallArea);
}
}
前端页面
考虑到在每个页面点击查看下级按钮,将进入到每一个地址的下级地址维护页面,你暂时也没有使用过自定义组件,在这里,我们可以考虑使用一个view来处理,然后在这个view中,使用自定义的三个组件(分别是省、市、区地址维护),代码如下。
<template> <div id="addressBaseDiv"> <div v-if="provinceShow"> <provinceSearch ref="provinceSearch" @lookSubordinate="lookOneSubordinate" /> </div> <div v-if="cityShow"> <citySearch ref="citySearch" :pid="provinceId" @lookSubordinate="lookTwoSubordinate" @returnBack="returnTwoBack" /> </div> <div v-if="areaShow"> <areaSearch ref="areaThreeSearch" :pid="cityId" @returnBack="returnThreeBack" /> </div> </div></template> <script>import provinceSearch from '@/components/basedataManage/province'import citySearch from '@/components/basedataManage/city'import areaSearch from '@/components/basedataManage/area' export default { components: { provinceSearch, citySearch, areaSearch }, data() { return { // 一级类目 provinceShow: false, // 二级类目 cityShow: false, // 三级类目 areaShow: false, provinceId: '', cityId: '' } }, created() { // 显示一级地址 this.provinceShow = true }, methods: { // 二级回退 returnTwoBack() { // 一级二级三级类目显示设置 this.provinceShow = true this.cityShow = false this.areaShow = false }, // 三级回退 returnThreeBack() { // 一级二级三级类目显示设置 this.provinceShow = false this.cityShow = true this.areaShow = false }, // 一级查看下级类目 lookOneSubordinate(row) { this.provinceId = row.provinceId console.log(row) // 一级二级三级类目显示设置 this.provinceShow = false this.cityShow = true this.areaShow = false }, // 二级查看下级类目 lookTwoSubordinate(row) { this.cityId = row.cityId console.log(row) // 一级二级三级类目显示设置 this.provinceShow = false this.cityShow = false this.areaShow = true } } }</script> <style scoped></style>
我们看看组件的使用。
你需要注意的是,在查看二级地址,三级地址时,将上级地址的id传入下级组件。
自定义组件
关于自定义组件的使用,我们着重讲二级地址的使用,因为省、市、区的维护,从功能上讲,都差不多,而二级地址,有返回上级和查看下级的功能比较有代表性。
<template> <div id="citySearchDiv"> <div style="float:right"> <span @click="returnBack">< <span style="font-size:15px;margin-top:50px;line-height: 30px;">返回上一级</span></span> <el-button type="primary" icon="el-icon-edit" style="margin-bottom:20px;float: left;margin-right: 40px;" @click="addDate()">新增二级地址</el-button> </div> <div> <el-table ref="table" v-loading="listLoading" :data="list" style="width: 100%" border > <el-table-column label="二级地址ID"> <template slot-scope="scope">{{ scope.row.cityId }}</template> </el-table-column> <el-table-column label="二级地址名"> <template slot-scope="scope">{{ scope.row.cityName }}</template> </el-table-column> <el-table-column label="上级地址ID"> <template slot-scope="scope">{{ scope.row.provinceId }}</template> </el-table-column> <el-table-column label="地址级别"> 二级地址 </el-table-column> <el-table-column label="操作" width="200"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="handleSubordinate(scope.row)" >查看下级 </el-button> <el-button type="primary" size="mini" @click="handleUpdate(scope.row)" >修改 </el-button> </template> </el-table-column> </el-table> </div> <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.pageSize" @pagination="getList" /> <!-- 新增/编辑弹框 --> <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible"> <el-form ref="dataForm" :rules="rules" :model="temp" label-position="right" label-width="120px" style="width: 320px; margin-left:50px;"> <!--注意prop 属性为需要验证的属性 --> <el-form-item label="一级地址编码:" prop="provinceId"> <el-input v-model="temp.provinceId" :value="temp.provinceId" placeholder="请输入一级地址编码" :readonly="true" /> </el-form-item> <el-form-item label="二级地址编码:" prop="cityId"> <el-input v-model="temp.cityId" placeholder="请输入二级地址编码" /> </el-form-item> <el-form-item label="二级地址名称:" prop="cityName"> <el-input v-model="temp.cityName" placeholder="请输入二级地址名" /> </el-form-item> </el-form> <div slot="footer"> <el-button @click="dialogFormVisible = false"> 取消 </el-button> <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()"> 确定 </el-button> </div> </el-dialog> </div></template> <script>import Pagination from '@/components/Pagination' // secondary package based on el-paginationimport { fetchCityList, createCity, updateCity } from '@/api/basedataManage/basedataManage'export default { components: { Pagination }, props: ['pid'], data() { return { dialogStatus: '', // 弹框是否显示 dialogFormVisible: false, // 弹框校验规则 rules: { cityName: [{ required: true, message: '二级地址名称必须填写', trigger: 'change' }], cityId: [{ required: true, message: '二级地址编码必须填写', trigger: 'change' }] }, temp: { id: undefined, // 一级地址名称: cityName: '', cityId: '' }, // 状态 valueList: [{ value: '是', label: '是' }, { value: '否', label: '否' }], textMap: { update: '二级地址修改', create: '二级地址新增' }, // table集合 list: null, multipleSelection: [], // 分页 total: 0, // loading listLoading: true, listQuery: { page: 1, pageSize: 10, provinceId: this.pid } } }, watch: { pid(value, old) { if (value) { console.log(value) this.temp.provinceId = this.pid } } }, created() { // 列表查询 this.getList(this.listQuery) this.temp.provinceId = this.pid }, methods: { /** * 回退 */ returnBack() { this.$emit('returnBack') }, // 查看下级 handleSubordinate(row) { this.$emit('lookSubordinate', row) }, // 编辑 handleUpdate(row) { this.temp = Object.assign({}, row) // copy obj this.dialogStatus = 'update' this.dialogFormVisible = true this.$nextTick(() => { this.$refs['dataForm'].clearValidate() }) }, // 重置 resetTemp() { this.temp = { id: undefined, // 一级地址名称: cityName: '', cityId: '', provinceId: this.pid } }, // 新增一级类目 addDate() { this.resetTemp() this.dialogStatus = 'create' this.dialogFormVisible = true this.$nextTick(() => { this.$refs['dataForm'].clearValidate() }) }, // 更新保存方法 updateData() { this.$refs['dataForm'].validate((valid) => { if (valid) { const tempData = Object.assign({}, this.temp) updateCity(tempData).then(() => { const index = this.list.findIndex(v => v.id === this.temp.id) this.list.splice(index, 1, this.temp) this.dialogFormVisible = false this.$notify({ title: 'Success', message: 'Update Successfully', type: 'success', duration: 2000 }) }) } }) }, // 创建保存方法 createData() { this.$refs['dataForm'].validate((valid) => { console.log(valid) if (valid) { createCity(this.temp).then((res) => { this.temp.id = res.model.id this.list.unshift(this.temp) this.dialogFormVisible = false this.$notify({ title: 'Success', message: 'Created Successfully', type: 'success', duration: 2000 }) }) } }) }, // 列表查询 getList() { this.listLoading = true fetchCityList(this.listQuery).then(response => { this.list = response.model this.total = response.totalItem // Just to simulate the time of the request setTimeout(() => { this.listLoading = false }, 1.5 * 1000) }) } }}</script> <style scoped> </style>
你需要特别注意的是,上级参数的接收。
由于二级地址维护的是上一级地址下的二级地址,你在做查询时,需要带上上一级地址的id,接收父组件的参数后,在查询时使用(this.变量名)。
回退功能的实现,你也需要注意下,this.$emit的使用.
子组件直接通过this.$emit("自定义事件"),然后父组件在组件中添加@自定义事件=“event”。this.$emit('returnBack')触发了,父组上绑定的returnBack事件,从而调用了绑定的returnTwoBack函数,完成回退功能。
至于API的封装,以及新增修改功能的实现,大家请自行参考,品牌管理的实现。不动手自己搞点儿什么东西,是始终学不会的。