自定义省市区联动展示(全栈代码)

目录

最近碰到一个需求:要做一个省市区页面展示,网上百度一番之后发现地图组件element-china-area-data很不错,能大大简化前后端工作。但是领导说地区数据源不好控制,于是便自己做了一个自定义的。由此便记录一下。(我们是全栈哈,所以前后端都会讲到)。

前端(vue3+Element Plus):

1.对于这种有层次的逐级查看方式,便采用了Cascader 级联选择器组件。

2.查看其源代码可以知道前端大概需要的数据结构(父子级嵌套)

3.由于后台的代码结构可以满足,但是他的字段不一定是value、label这种

4.调用后端接口

5.有同学可能会遇到编辑,地区没法回显呀?

后端(java+mybatisplus+mysql):

1.数据库

2.CesDiquMapper.xml写法,

3.实体类(注意里面的嵌套子集)

4.mapper写法

5.接口实现类

6.返回数据结构

看看效果吧

总结


最近碰到一个需求:要做一个省市区页面展示,网上百度一番之后发现地图组件element-china-area-data很不错,能大大简化前后端工作。但是领导说地区数据源不好控制,于是便自己做了一个自定义的。由此便记录一下。(我们是全栈哈,所以前后端都会讲到)。
前端(vue3+Element Plus):
1.对于这种有层次的逐级查看方式,便采用了Cascader 级联选择器组件。

2.查看其源代码可以知道前端大概需要的数据结构(父子级嵌套)
<template>
  <el-cascader :options="options" clearable />
</template>
<script lang="ts" setup>
const options = [
  {
    value: 'guide',
    label: 'Guide',
    children: [
      {
        value: 'disciplines',
        label: 'Disciplines',
        children: [
          {
            value: 'consistency',
            label: 'Consistency',
          },
          {
            value: 'feedback',
            label: 'Feedback',
          },
          {
            value: 'efficiency',
            label: 'Efficiency',
          },
          {
            value: 'controllability',
            label: 'Controllability',
          },
        ],
      },
      {
        value: 'navigation',
        label: 'Navigation',
        children: [
          {
            value: 'side nav',
            label: 'Side Navigation',
          },
          {
            value: 'top nav',
            label: 'Top Navigation',
          },
        ],
      },
    ],
  },
  {
    value: 'component',
    label: 'Component',
    children: [
      {
        value: 'basic',
        label: 'Basic',
        children: [
          {
            value: 'layout',
            label: 'Layout',
          },
          {
            value: 'color',
            label: 'Color',
          },
          {
            value: 'typography',
            label: 'Typography',
          },
          {
            value: 'icon',
            label: 'Icon',
          },
          {
            value: 'button',
            label: 'Button',
          },
        ],
      },
      {
        value: 'form',
        label: 'Form',
        children: [
          {
            value: 'radio',
            label: 'Radio',
          },
          {
            value: 'checkbox',
            label: 'Checkbox',
          },
          {
            value: 'input',
            label: 'Input',
          },
          {
            value: 'input-number',
            label: 'InputNumber',
          },
          {
            value: 'select',
            label: 'Select',
          },
          {
            value: 'cascader',
            label: 'Cascader',
          },
          {
            value: 'switch',
            label: 'Switch',
          },
          {
            value: 'slider',
            label: 'Slider',
          },
          {
            value: 'time-picker',
            label: 'TimePicker',
          },
          {
            value: 'date-picker',
            label: 'DatePicker',
          },
          {
            value: 'datetime-picker',
            label: 'DateTimePicker',
          },
          {
            value: 'upload',
            label: 'Upload',
          },
          {
            value: 'rate',
            label: 'Rate',
          },
          {
            value: 'form',
            label: 'Form',
          },
        ],
      },
      {
        value: 'data',
        label: 'Data',
        children: [
          {
            value: 'table',
            label: 'Table',
          },
          {
            value: 'tag',
            label: 'Tag',
          },
          {
            value: 'progress',
            label: 'Progress',
          },
          {
            value: 'tree',
            label: 'Tree',
          },
          {
            value: 'pagination',
            label: 'Pagination',
          },
          {
            value: 'badge',
            label: 'Badge',
          },
        ],
      },
      {
        value: 'notice',
        label: 'Notice',
        children: [
          {
            value: 'alert',
            label: 'Alert',
          },
          {
            value: 'loading',
            label: 'Loading',
          },
          {
            value: 'message',
            label: 'Message',
          },
          {
            value: 'message-box',
            label: 'MessageBox',
          },
          {
            value: 'notification',
            label: 'Notification',
          },
        ],
      },
      {
        value: 'navigation',
        label: 'Navigation',
        children: [
          {
            value: 'menu',
            label: 'Menu',
          },
          {
            value: 'tabs',
            label: 'Tabs',
          },
          {
            value: 'breadcrumb',
            label: 'Breadcrumb',
          },
          {
            value: 'dropdown',
            label: 'Dropdown',
          },
          {
            value: 'steps',
            label: 'Steps',
          },
        ],
      },
      {
        value: 'others',
        label: 'Others',
        children: [
          {
            value: 'dialog',
            label: 'Dialog',
          },
          {
            value: 'tooltip',
            label: 'Tooltip',
          },
          {
            value: 'popover',
            label: 'Popover',
          },
          {
            value: 'card',
            label: 'Card',
          },
          {
            value: 'carousel',
            label: 'Carousel',
          },
          {
            value: 'collapse',
            label: 'Collapse',
          },
        ],
      },
    ],
  },
  {
    value: 'resource',
    label: 'Resource',
    children: [
      {
        value: 'axure',
        label: 'Axure Components',
      },
      {
        value: 'sketch',
        label: 'Sketch Templates',
      },
      {
        value: 'docs',
        label: 'Design Documentation',
      },
    ],
  },
]
</script>
3.由于后台的代码结构可以满足,但是他的字段不一定是value、label这种

答:通过属性props来转换字段

看代码:

      <el-form-item label="地区" prop="id">
        <el-cascader :options="areaDictDataList" clearable :props="optionProps" v-model="detailAddressOptions" />
      </el-form-item>
const optionProps = ref({
  checkStrictly: true,
  value: "id",
  label: "name",
  children: "child"
})
4.调用后端接口
/** 查询地区数据 */
function getRegionDictData() {

  queryRegionData(queryParams.value).then(response => {
    regionDictDataList.value = response.data;
  });
}
5.有同学可能会遇到编辑,地区没法回显呀?

答:好吧,不瞒你说,我也遇到了。。。

言归正传,当我们选中了省市区之后呢,我们传给后端的其实是一个数组字段,编辑回显拿到的后端的字段也是数组字段,我没有回显的原因是因为数据类型不一样,前端是整形数组,后端是字符数组,统一之后就正常回显啦。网上那些方法我试了对我没有用

贴一下图让大家瞅瞅

后端(java+mybatisplus+mysql):
1.数据库
CREATE TABLE `ces_diqu` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增长id',
  `parent_id` int(10) unsigned DEFAULT NULL COMMENT '父级id',
  `type` mediumint(8) unsigned DEFAULT NULL COMMENT '区域级别:1省,2:市,3:区',
  `name` varchar(50) NOT NULL COMMENT '地区名',
  `create_by` varchar(20) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_by` varchar(20) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3370 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='地区表';

2.CesDiquMapper.xml写法,

1.特别注意里面的resultMap写法,有几层嵌套就有有几个collection

2.getCustomSqlSegment相当于where条件,这里我加了sr1.type=1(在第五步接口实现类有提

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.CesDiquMapper">

    <resultMap type="com.ruoyi.system.domain.CesDiqu" id="CesDiquResult">
        <result property="id" column="id"/>
        <result property="parentId" column="parent_id"/>
        <result property="type" column="type"/>
        <result property="name" column="name"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <collection property="child" ofType="com.ruoyi.system.domain.CesDiqu">
            <id column="id_2" property="id"/>
            <result column="parent_id_2" property="parentId"/>
            <result column="name_2" property="name"/>
            <collection property="child" ofType="com.ruoyi.system.domain.CesDiqu">
                <id column="id_3" property="id"/>
                <result column="parent_id_3" property="parentId"/>
                <result column="name_3" property="name"/>
            </collection>
        </collection>
    </resultMap>

    <select id="queryAllDictData" resultMap="CesDiquResult">
        SELECT sr1.id,
               sr1.parent_id,
               sr1.name,
               sr2.id        as id_2,
               sr2.parent_id as parent_id_2,
               sr2.name      as name_2,
               sr3.id        as id_3,
               sr3.parent_id as parent_id_3,
               sr3.name     as name_3
        FROM ces_diqu sr1
                 left join
             ces_diqu sr2 on sr1.id = sr2.parent_id
                 left join ces_diqu sr3
                           on sr2.id = sr3.parent_id
            ${ew.getCustomSqlSegment};

    </select>


</mapper>

注意:这里用的右连接,内连接会丢数据

3.实体类(注意里面的嵌套子集
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.List;

/**
 * @Author: 菜鸟
 * @Date: 2024/06/22/21:08
 * @Description: 地区对象 ces_diqu
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("ces_diqu")
public class CesDiqu extends BaseEntity {


    private static final long serialVersionUID=1L;

    /**
     * 自增长id
     */
    @TableId(value = "id")
    private Long id;
    /**
     * 父级id
     */
    private Integer parentId;
    /**
     * 区域级别:1省,2:市,3:区
     */
    private Integer type;
    /**
     * 地区名
     */
    private String name;

    /**
     * 子集地区数据
     */
    @TableField(exist = false)
    private List<CesDiqu> child;
}
4.mapper写法
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import java.util.List;

/**
 * @Author: 菜鸟
 * @Date: 2024/06/22/21:12
 * @Description: 地区Mapper接口
 */
public interface CesDiquMapper extends BaseMapperPlus<CesDiquMapper, CesDiqu, CesDiquVo> {

    /**
     * 查询地区数据
     *
     * @return 省市区数据
     */
    List<CesDiqu> queryAllDictData(@Param(Constants.WRAPPER) Wrapper<CesDiqu> queryWrapper);

}
5.接口实现类

6.返回数据结构

剩下的的后台代码就不贴了,重点都在上面了

看看效果吧

总结

1.上述方式合适数据量小的,数据量大了就会有性能问题,此时就需要动态加载,与之对应的后端接口也得调整,这部分优化我会放到下一篇去讲。

2.后端的难点主要在于父子级数据的查询,主要是连表查询。不熟的同学可以多看看连表

其实我对连表查询也很生疏。。。

3.有同学可能会问,你地区数据哪来的?

答:爬虫搞来的

4.我的前端就入门水平,前端代码轻喷

5.有不足的地方欢迎大家一起讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值