专栏首页猿人工厂猿实战06——不一样的地址管理

猿实战06——不一样的地址管理

上一章节,猿人君教会了你一个新鲜的东西——猿实战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-pagination
import { 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的封装,以及新增修改功能的实现,大家请自行参考,品牌管理的实现。不动手自己搞点儿什么东西,是始终学不会的。

本文分享自微信公众号 - 猿人工厂(gh_deca5a88e287),作者:山旮旯的胖子

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 猿设计9——真电商之商品实体识别

    经过前一章节的讨论相信你已经能够正确的区分SPU与SKU两个概念。商品系统的设计与构建,从某种程度上来讲,就是围绕SPU和SKU来进行的。但是只有这两个粗浅的概...

    山旮旯的胖子
  • 猿实战13——实现你没听说过的前台类目

    上几个章节,猿人君教会了你实现了属性/属性值和后台类目的绑定关系,今天,猿人君就带你来实现前台类目。

    山旮旯的胖子
  • 猿实战10——动态表单之实现类目属性绑定

    猿实战是一个原创系列文章,通过实战的方式,采用前后端分离的技术结合SpringMVC Spring Mybatis,手把手教你撸一个完整的电商系统,跟着教程走下...

    山旮旯的胖子
  • 从 0 到 1 实现 React 系列 —— 5.PureComponent 实现 && HOC 探幽

    本系列文章在实现一个 cpreact 的同时帮助大家理顺 React 框架的核心内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/Pur...

    牧云云
  • 利用js实现输入框动态提示信息

    为了提高和用户的交互性,现在的输入框往往都采用输入信息自动提示的功能,类似于百度输入框中的提示功能。 设计思路是:在输入框input的组件下面放置一个div,这...

    林老师带你学编程
  • vue实现分页组件

    分页需要的字段:当前页(curPage),每页大小(pageSize),总页数(total) 作为一个组件,所以以上这些参数最好是从父组件传递过来,可以如下定义...

    陨石坠灭
  • 用HTML5-Canvas 写一个桌球游戏!

    这只是一个简单的DEMO。游戏性、游戏规则没怎么考虑,如果有兴趣细化的朋友可以细化一下,比如细化一下规则,游戏开关,加个声音,细化一下进球检测,更严谨甚至可以去...

    用户5997198

扫码关注云+社区

领取腾讯云代金券