vue利用级联选择器实现全国省市区乡村五级菜单联动

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: vue利用级联选择器实现全国省市区乡村五级菜单联动


现在是:2022年2月13日20:09:27

今天分享一个五级级联地址的组件的使用吧。

前言

接到这样的一个需求:需要根据地址查询列表信息,地址可以分别按照省、市、县、乡、村 五级作为条件查询。

如果但是这个需求还好说,以前使用的方式是放五个下拉列表,通过懒加载的方式,根据省得到该省下的市,根据市得到该市下的县……最后可以得到村的级别,然后作为条件查询,也就是下面的这种方式:

image-20220213201630629 image-20220213201641535

但是,客户说,太长了……因为他的另一个需求是这样的:

image-20220213203322417

如果放5个的话,就会造成装不下的bug。

image-20220213203822894

实现思路

原来采用的是select,现在看来,select显然不是最好的解决方式,去element-ui上面看了看,发现了另一个组件:Cascader 级联选择器,样式如下:

image-20220213204120997

于是就把这个Cascader 组件改了改,改成了省市区乡村的组件,同时也做成了公共组件,项目中可以多次共用。

数据表结构

全国省市区乡村没有使用json的方式,将所有的省市区乡村信息都放在了数据库中,用的时候根据父级编号查询,数据库表结构如下:

image-20220213204531326

级联实现地址联动

实现思路:页面初次加载的时候,将全国所有省查询出来,封装放在级联组件Cascader中,当用户选择某个省的时候,将当前省的编码作为父级编码,去查询所有的市。比如选的山东省,查询出来的就是山东所有市,济南,聊城,菏泽等。

为了解决系统性能问题,我将查询出来的信息都放在了redis缓存中了,缓存里面没有的话去数据库里面找去,有则在缓存中取,效率要高点儿。

下面是公共组件中的代码:

<!--
  作者:mxx
  时间:2022年2月11日16:22:07
  描述:el-Cascader实现省市区乡村菜单
-->
<template>
  <div>
    <div class="block">
      <el-cascader
        v-model="selectedOptions"
        :show-all-levels="false"
        ref="sysCascader"
        :size="size"
        :placeholder="placeholder"
        :props="props"
        @change="handleChange"
        clearable>
      </el-cascader>
    </div>
<!--  鼠标悬浮触发  -->
<!--    <div class="block">
      <span class="demonstration">hover 触发子菜单</span>
      <el-cascader
        expand-trigger="hover"
        :options="options"
        v-model="selectedOptions2"
        @change="handleChange">
      </el-cascader>
    </div>-->
  </div>
</template>
<script>
import {listArea} from "../../../api/system/area";
export default {
  name: "addressCommonComponentsUtils",
  props: {
    //组件大小
    size: {
      type: String,
      default: "mini"
    },
    //组件的提示信息
    placeholder: {
      type: String,
      default: "请选择地区"
    }
  },
  data() {
    let self = this;
    return {
      /*省市区的集合*/
      options: [],
      props: {
        lazy: true,
        checkStrictly: true,
        lazyLoad: this.getLazyLoad,
      },
      selectedOptions: [],
      // 地址查询参数
      qParams: {
        pid: null,
        type: null,
      },
      //省市区的值
      addressInfo: {
        //编码
        //省
        sonProvinceValue: null,
        //市
        sonCityValue: null,
        //区
        sonAreaValue: null,
        //街道
        sonStreetValue: null,
        //乡村
        sonCountryValue: null,
        //名字
        //省
        sonProvinceName: null,
        //市
        sonCityName: null,
        //区
        sonAreaName: null,
        //街道
        sonStreetName: null,
        //乡村
        sonCountryName: null,
      },
    };
  },
  created() {
  },
  methods: {
    //懒加载省份信息到组件中
    getLazyLoad(node, resolve) {
      setTimeout(() => {
        if (node.level == 0) {
          this.qParams.type = 1;
          listArea(this.qParams).then(res => {
            const cities = res.rows.map((value, i) => ({
              value: value.areaId,
              label: value.name,
              leaf: node.level >= 2,
            }));
            // 通过调用resolve将子节点数据返回,通知组件数据加载完成
            resolve(cities);
          })
            .catch(err => {
              console.log(err);
            });
        }
        if (node.level != 0 && node.level < 5) {
          this.qParams.type = node.level + 1;
          this.qParams.pid = node.value;
          listArea(this.qParams).then(res => {
            const areas = res.rows.map((value, i) => ({
              value: value.areaId,
              label: value.name,
              leaf: node.level >= 4
            }));
            //设置省市区街村的值
            this.setNameAndValue(node);
            // 通过调用resolve将子节点数据返回,通知组件数据加载完成
            resolve(areas);
          })
            .catch(err => {
              console.log(err);
            });
        } else if (node.level == 5) {
          //最后一个节点,不去查询了,直接赋值
          const areas = {
            value: node.value,
            label: node.label,
            leaf: node.level
          };
          //将值和名称赋值到变量里面
          this.addressInfo.sonCountryValue = node.value;
          this.addressInfo.sonCountryName = node.label;
          resolve(areas);
        }
      }, 10);
    },
    //通过当前选择的地区,设置到对应的变量中
    setNameAndValue(node) {
      switch (node.level) {
        case 1:
          this.addressInfo.sonProvinceValue = node.value;
          this.addressInfo.sonProvinceName = node.label;
          this.addressInfo.sonCityValue = null;
          this.addressInfo.sonCityName = null;
          this.addressInfo.sonAreaValue = null;
          this.addressInfo.sonAreaName = null;
          this.addressInfo.sonStreetValue = null;
          this.addressInfo.sonStreetName = null;
          this.addressInfo.sonCountryValue = null;
          this.addressInfo.sonCountryName = null;
          break;
        case 2:
          this.addressInfo.sonCityValue = node.value;
          this.addressInfo.sonCityName = node.label;
          this.addressInfo.sonAreaValue = null;
          this.addressInfo.sonAreaName = null;
          this.addressInfo.sonStreetValue = null;
          this.addressInfo.sonStreetName = null;
          this.addressInfo.sonCountryValue = null;
          this.addressInfo.sonCountryName = null;
          break;
        case 3:
          this.addressInfo.sonAreaValue = node.value;
          this.addressInfo.sonAreaName = node.label;
          this.addressInfo.sonStreetValue = null;
          this.addressInfo.sonStreetName = null;
          this.addressInfo.sonCountryValue = null;
          this.addressInfo.sonCountryName = null;
          break;
        case 4:
          this.addressInfo.sonStreetValue = node.value;
          this.addressInfo.sonStreetName = node.label;
          this.addressInfo.sonCountryValue = null;
          this.addressInfo.sonCountryName = null;
          break;
        case 5:
          this.addressInfo.sonCountryValue = node.value;
          this.addressInfo.sonCountryName = node.label;
          break;
      }
    },
    /*改变事件*/
    handleChange(event) {
      let node = this.$refs.sysCascader.getCheckedNodes()[0];
      //解决不点字  只点击单选按钮的问题
      this.setNameAndValue(node);
      //将该组件中的值传递给父组件
      //getCheckedAddressInfo: 是父组件指定的传数据绑定的函数,
      this.$emit('getCheckedAddressInfo', this.addressInfo)
    },
  }
}
</script>
<style scoped>
</style>

关键代码都有相应的注释,应该看起来都没有问题。

或许有些地方的代码冗余,但是我没法再优化了……这个组件大致的功能就是将用户选择的地址信息,传递到父组件中,父组件通过getCheckedAddressInfo函数拿到用户选择的地址信息,不管选择哪一级都可以,最后在根据地址信息做其他操作。

listArea方法:根据条件查询地址信息,传递的参数是地址实体,在本文的最后我附上后台的实现代码。

级联地址组件的使用

使用方法也很简单,只需这么几步:

1.先导入组件:

import addressCommonComponentsUtils from "../area/addressCommonComponentsUtils";

2.注册组件:

export default 里面写如下代码:

components: {
    "address-utils": addressCommonComponentsUtils,
    },

image-20220213212322706

3.在template中引入级联地址组件:

<address-utils
    :size="mini"
    :placeholder="checkdiqu"
    @getCheckedAddressInfo="getCheckedAddressInfo">
    </address-utils>

4.在methods中写getCheckedAddressInfo方法回去选择的地址信息:

//获取用户选择的地址信息
    getCheckedAddressInfo(value){
        //给地址栏中赋值
        this.queryParams.fieldProvince = value.sonProvinceName;
        this.queryParams.fieldCity = value.sonCityName;
        this.queryParams.fieldArea = value.sonAreaName;
        this.queryParams.fieldStreet = value.sonStreetName;
        this.queryParams.fieldCommunity = value.sonCountryName;
    },

参数说明:

编码:比如北京是110000,山西是:140000等

  • //省 `sonProvinceValue,
  • //市 `sonCityValue,
  • //区 `sonAreaValue,
  • //街道 `sonStreetValue,
  • //乡村 `sonCountryValue,

名字:比如北京是**【北京】,山西是【山西省】**

  • //省 sonProvinceName,
  • //市 `sonCityName,
  • //区 `sonAreaName,
  • //街道 `sonStreetName,
  • //乡村 sonCountryName,

最后实现的效果如下:

image-20220213213301547

附:后台查询地址的代码

area.js:

// 查询地址列表
export function listArea(query) {
  return request({
    url: '/system/area/list',
    method: 'get',
    params: query
  })
}

SysAreaController中的代码:

@Autowired
    private ISysAreaService sysAreaService;
    /**
     * 查询地址列表
     */
    //@PreAuthorize("@ss.hasPermi('system:area:list')")
    @GetMapping("/list")
    public TableDataInfo list(SysArea sysArea)
    {
       // startPage();
        List<SysArea> list = sysAreaService.selectSysAreaList(sysArea);
        return getDataTable(list);
    }

SysAreaServiceImpl中的代码:

@Autowired
    private SysAreaMapper sysAreaMapper;
    @Autowired
    private RedisCache redisCache;
    /**
     * 查询地址
     * 
     * @param id 地址主键
     * @return 地址
     */
    @Override
    public SysArea selectSysAreaById(Long id)
    {
        return sysAreaMapper.selectSysAreaById(id);
    }
    /**
     * 查询地址列表
     * 
     * @param sysArea 地址
     * @return 地址
     */
    @Override
    public List<SysArea> selectSysAreaList(SysArea sysArea) {
        //分批次判断地址
        List<SysArea> areaList = new ArrayList<>();
        //地址对象
        SysArea area = new SysArea();
        if(sysArea.getPid()==null){
            sysArea.setPid("100000");
        }
        //市、区县、街道、村
        if (redisCache.getCacheList(sysArea.getPid() + "ListCache").size()==0) {
            System.out.println("=========================走数据库了=========================");
            area.setType(sysArea.getType());
            //将父级编号放进来
            area.setPid(sysArea.getPid());
            areaList = sysAreaMapper.selectSysAreaList(area);
            redisCache.setCacheList(sysArea.getPid() + "ListCache", areaList);
        } else {
            System.out.println("=========================走缓存了=========================");
            //从缓存中取出来
            areaList = redisCache.getCacheList(sysArea.getPid() + "ListCache");
        }
        return areaList;
    }

Mapper层和Service的接口层就不放了,就正常写,返回集合就行:

/**
     * 查询地址列表
     * 
     * @param sysArea 地址
     * @return 地址集合
     */
    public List<SysArea> selectSysAreaList(SysArea sysArea);

最后是mapper.xml中的sql:

<sql id="selectSysAreaVo">
        select id, name, area_id, pid, type, status, del_flag, create_time, update_time, pids, m_pid, m_pids, m_type, region, remark from sys_area
    </sql>
    <select id="selectSysAreaList" parameterType="SysArea" resultMap="SysAreaResult">
        <include refid="selectSysAreaVo"/>
        <where>
            <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
            <if test="areaId != null  and areaId != ''"> and area_id = #{areaId}</if>
            <if test="pid != null  and pid != ''"> and pid = #{pid}</if>
            <if test="type != null "> and type = #{type}</if>
            <if test="status != null "> and status = #{status}</if>
            <if test="pids != null  and pids != ''"> and pids = #{pids}</if>
            <if test="mPid != null  and mPid != ''"> and m_pid = #{mPid}</if>
            <if test="mPids != null  and mPids != ''"> and m_pids = #{mPids}</if>
            <if test="mType != null "> and m_type = #{mType}</if>
            <if test="region != null  and region != ''"> and region = #{region}</if>
        </where>
    </select>

总结

好了,今天分享的就这些了,源码也都贴上来啦,记录记录,一来或许可以帮助大家,二来自己以后用的时候,也可以直接上来扒代码了。

下一篇:springboot解析word文档,包括复杂的图片,复选框等。

问:你知道CV工程师是啥吗?

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发
|
2天前
|
存储 JavaScript
Vue 状态管理工具vuex
Vue 状态管理工具vuex
|
8天前
|
JavaScript
如何在 Vue 中使用具名插槽
【10月更文挑战第25天】通过使用具名插槽,你可以更好地组织和定制组件的模板结构,使组件更具灵活性和可复用性。同时,具名插槽也有助于提高代码的可读性和可维护性。
13 2
|
8天前
|
JavaScript
Vue 中的插槽
【10月更文挑战第25天】插槽的使用可以大大提高组件的复用性和灵活性,使你能够根据具体需求在组件中插入不同的内容,同时保持组件的结构和样式的一致性。
12 2
|
8天前
|
前端开发 JavaScript 容器
在 vite+vue 中使用@originjs/vite-plugin-federation 模块联邦
【10月更文挑战第25天】模块联邦是一种强大的技术,它允许将不同的微前端模块组合在一起,形成一个统一的应用。在 vite+vue 项目中,使用@originjs/vite-plugin-federation 模块联邦可以实现高效的模块共享和组合。通过本文的介绍,相信你已经了解了如何在 vite+vue 项目中使用@originjs/vite-plugin-federation 模块联邦,包括安装、配置和使用等方面。在实际开发中,你可以根据自己的需求和项目的特点,灵活地使用模块联邦,提高项目的可维护性和扩展性。
|
存储 前端开发 JavaScript
为什么我不再用Vue,改用React?
当我走进现代前端开发行业的时候,我做了一个每位开发人员都要做的决策:选择一个合适的框架。当时正逢 jQuery 被淘汰,前端开发者们不再用它编写难看的、非结构化的老式 JavaScript 程序了。
|
10天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
10天前
|
缓存 JavaScript 搜索推荐
Vue SSR(服务端渲染)预渲染的工作原理
【10月更文挑战第23天】Vue SSR 预渲染通过一系列复杂的步骤和机制,实现了在服务器端生成静态 HTML 页面的目标。它为提升 Vue 应用的性能、SEO 效果以及用户体验提供了有力的支持。随着技术的不断发展,Vue SSR 预渲染技术也将不断完善和创新,以适应不断变化的互联网环境和用户需求。
28 9
|
9天前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
9天前
|
JavaScript 前端开发 UED
vue 提高 tree shaking 的效果
【10月更文挑战第23天】提高 Vue 中 Tree shaking 的效果需要综合考虑多个因素,包括模块的导出和引用方式、打包工具配置、代码结构等。通过不断地优化和调整,可以最大限度地发挥 Tree shaking 的优势,为 Vue 项目带来更好的性能和用户体验。