当前位置: 首页 > article >正文

vue3+element-plus实现省市区三级地址多选

目录

  • 背景
  • 实现功能点
  • 遗留问题
  • 完整代码参考

背景

  • 需要实现:选择省级地址时,回传节点为 [ 省级地址 id], 选择市级地址时,回传节点为 [ 省级地址 id,市级地址 id], 选择区县地址时,回传节点为 [ 省级地址 id,市级地址 id,区县地址 id]。
  • 但 el-cascader-panel 双向绑定数据-已选区域,仅支持到最末级,即无论选择哪一级,回传节点均为[ 省级地址 id,市级地址 id,区县地址 id],与要求的数据结构不符。

实现功能点

  • 省市区三级地址多选,实现:选择省级地址时,回传节点为 [ 省级地址 id], 选择市级地址时,回传节点为 [ 省级地址 id,市级地址 id], 选择区县地址时,回传节点为 [ 省级地址 id,市级地址 id,区县地址 id]。
  • 过滤 不可选区域
    • checkDisabled 方法
      • 遍历树状结构areaData,为其每个节点添加全路径 path 属性(省级地址 path 为 [ 省级地址 id],市级地址 path 为 [ 省级地址 id,市级地址 id], 区县地址 path 为 [ 省级地址 id,市级地址 id,区县地址 id])
      • 遍历过程中,刷选属性 disabled 为 true 的节点,将其 path 添加到 result 变量中,从而实现将该节点及其祖先节点均添加到 result 变量中
      • 遍历后,对 result 数组元素进行去重,得到当前节点或子孙节点 disabled 为 true 且元素唯一的 disabledIds 数组
      • 在handleChange 方法中,通过过滤已选节点(disabledIds 中不包含该节点 id)实现,过滤不可选区域
  • 兼容后端返回已选区域(已选区域前端回显,以及变更选择区域后后端 api 入参)
    • initSelectedPaths 方法
      • 遍历后端返回的已选区域数组tempSelectedAreas,结合 areaData,将其转为 el-cascader-panel 双向绑定数据-已选区域要求的数据结构,即节点结构均为 [省级地址 id,市级地址 id,区县地址 id],从而实现前端正确回显已选区域
      • 获取到后端数据tempSelectedAreas 后,讲其浅拷贝给selectedAreas,避免当未进行任何操作直接点击保存时,selectedAreas 为空,丢失原有已选区域的问题

遗留问题

  • 树状结构层级硬编码:仅实现了树状结构层级不超过 3 级的场景,更多层级或者无限层级问题并未解决
  • 实现逻辑复杂,过多数据遍历:核心方法findDisabledIds、initSelectedPaths、handleChange 均存在频繁 forEach 遍历数组等情况,但未找到解决办法。

如有更好解决办法,欢迎大佬赐教!!!

完整代码参考

模拟数据-树状结构

[{"id": 1,"name": "河南省","children": [{"id": 4,"name": "郑州市","children": [{"id": 7,"name": "高新区","children": []},{"id": 8,"name": "中原区","children": []}]},{"id": 5,"name": "信阳市","children": [{"id": 9,"name": "息县","children": []}]}]
},{"id": 2,"name": "河北省","children": [{"id": 10,"name": "石家庄市","children": [{"id": 11,"name": "石家庄市区","children": []}]}]
},{"id": 3,"name": "北京市","children": [{"id": 12,"name": "北京市","children": [{"id": 13,"name": "朝阳区","children": []}]}]
}]

主要实现逻辑

<template><div><el-cascader-panelref="cascaderPanel"v-model="selectedPaths":options="areaData":props="cascaderProps"@change="handleChange"/></div>
</template><script lang="ts" setup>import {onMounted, ref} from "vue";import areaData from "../express/areaData.json"; // 假设这是您的树形数据import type {CascaderNode, CascaderProps} from "element-plus";// el-cascader-panelde v-model动态绑定对象const selectedPaths = ref<number[][]>([]);// 后端返回已选择的区域const tempSelectedAreas = ref<number[][]>([])// 选中的区域(包含已选和新选)const selectedAreas = ref<number[][]>([])const cascaderPanel = ref();/** 配置Cascader属性 **/const cascaderProps: CascaderProps = {value: "id", // 值字段名label: "name", // 显示字段名children: "children",disabled: "disabled",multiple: true, // 启用多选};type TreeNode = {id: number;name: string;children?: TreeNode[];disabled?: boolean | string; // 假设 disabled 是字符串类型path?: number[]};/*** 查找树形结构中当前节点或子孙节点 disabled 为 true 的所有 id* @returns 包含符合条件的 id 数组*/const findDisabledIds = (): number[] => {let result:number[] = [];const checkDisabled = (node: TreeNode) => {if (node.disabled === "true" || node.disabled === true) {result = [...result, ...node.path as number[]];}}// 格式化areaData(为每个节点添加全路径path),取出disabled的节点(包含其父节点、祖先节点)areaData.forEach((parentNode: TreeNode ) => {parentNode.path = [parentNode.id]checkDisabled(parentNode)if (parentNode.children && parentNode.children.length > 0) {parentNode.children.forEach((node: TreeNode) => {node.path = [parentNode.id, node.id]checkDisabled(node)if (node.children && node.children.length > 0) {node.children.forEach((childrenNode: TreeNode) => {childrenNode.path = [parentNode.id, node.id, childrenNode.id];checkDisabled(childrenNode)})}})}})const tempResult = new Set(result)return Array.from(tempResult)}// 当前节点或子孙节点 disabled 为 true 的所有 idconst disabledIds = findDisabledIds()// 此处仅为模拟,应该调用后端接口const getTempSelectedAreas = () => {tempSelectedAreas.value = [[1]]initSelectedPaths()selectedAreas.value = [...tempSelectedAreas.value]}// 根据后端返回已选择的区域,初始化el-cascader-panelde v-model动态绑定对象const initSelectedPaths = () => {if(!tempSelectedAreas.value || !tempSelectedAreas.value.length) returntempSelectedAreas.value.forEach(area => {const parentNode:TreeNode | undefined = areaData.find(item => item.id == area[0])if(area.length == 1){if(!parentNode) returnif(parentNode.children && parentNode.children.length > 0){parentNode.children.forEach((node:TreeNode) => {if(node.children && node.children.length > 0){node.children.forEach((childrenNode: TreeNode) => {selectedPaths.value.push(childrenNode.path as number[])})}else {selectedPaths.value.push(node.path as number[])}})}else {selectedPaths.value.push(parentNode.path as number[])}}else if(area.length == 2){const node:TreeNode | undefined = parentNode?.children?.find(item => item.id == area[1])if(!node) returnif(node.children && node.children.length > 0){node.children.forEach((childrenNode:TreeNode) => {selectedPaths.value.push(childrenNode.path as number[])})}else {selectedPaths.value.push(node.path as number[])}}else if(area.length == 3){const node:TreeNode | undefined = parentNode?.children?.find(item => item.id == area[1])const childrenNode:TreeNode | undefined = node?.children?.find(item => item.id == area[2])if(!childrenNode) returnselectedPaths.value.push(childrenNode.path as number[])}})
}/** 处理选中变化 **/
const handleChange = () => {selectedAreas.value = []const checkedNodes = cascaderPanel.value.getCheckedNodes().filter((node:CascaderNode) => !disabledIds.includes(node.value as number))if (!checkedNodes || !checkedNodes.length) returncheckedNodes.forEach( (node:CascaderNode) => {if(node.level == 1 ) {selectedAreas.value.forEach((area, index) => {if(area[0] == node.value){selectedAreas.value.splice(index, 1)}})selectedAreas.value.push(node.pathValues as number[])}else if(node.level == 2){selectedAreas.value.forEach((area, index) => {if(area[1] == node.value){selectedAreas.value.splice(index, 1)}})if(selectedAreas.value.findIndex(item => item.length == 1 && item[0] == node.pathValues[0]) == -1) {selectedAreas.value.push(node.pathValues as number[])}}else if(node.level == 3){selectedAreas.value.forEach((area, index) => {if(area[2] == node.value){selectedAreas.value.splice(index, 1)}})if(selectedAreas.value.findIndex(item => (item.length == 1 && item[0] == node.pathValues[0]) || (item.length == 2 &&  item[1] == node.pathValues[1]))  == -1 ) {selectedAreas.value.push(node.pathValues as number[])}}})
}
onMounted(() => {getTempSelectedAreas()
})
</script>

相关文章:

vue3+element-plus实现省市区三级地址多选

目录 背景实现功能点遗留问题完整代码参考 背景 需要实现&#xff1a;选择省级地址时&#xff0c;回传节点为 [ 省级地址 id]&#xff0c; 选择市级地址时&#xff0c;回传节点为 [ 省级地址 id&#xff0c;市级地址 id]&#xff0c; 选择区县地址时&#xff0c;回传节点为 [ …...

centos部署的openstack发布windows虚拟机

‌CentOS上部署的OpenStack可以发布Windows虚拟机‌。在CentOS上部署OpenStack后&#xff0c;可以通过OpenStack平台创建和管理Windows虚拟机。以下是具体的步骤和注意事项&#xff1a; ‌安装和配置OpenStack‌&#xff1a; 首先&#xff0c;确保系统满足OpenStack的最低硬件…...

Linux : 进程等待以及进程终止

进程控制之进程等待 &#xff08;一&#xff09;fork函数1*fork函数返回值2.父子进程的写时拷贝 &#xff08;二&#xff09;进程终止1.进程退出码2.进程常见退出方法&#xff08;1&#xff09;_exit&#xff08;2&#xff09;exit&#xff08;3&#xff09;return 3.进程的异常…...

LSTM结合LightGBM高纬时序预测

1. LSTM 时间序列预测 LSTM 是 RNN&#xff08;Recurrent Neural Network&#xff09;的一种变体&#xff0c;它解决了普通 RNN 训练时的梯度消失和梯度爆炸问题&#xff0c;适用于长期依赖的时间序列建模。 LSTM 结构 LSTM 由 输入门&#xff08;Input Gate&#xff09;、遗…...

详细解释MCP项目中安装命令 bunx 和 npx区别

详细解释 bunx 和 npx 1. bunx bunx 是 Bun 的一个命令行工具&#xff0c;用于自动安装和运行来自 npm 的包。它是 Bun 生态系统中类似于 npx 或 yarn dlx 的工具。以下是 bunx 的主要特点和使用方法&#xff1a; 自动安装和运行&#xff1a; bunx 会自动从 npm 安装所需的包…...

【统信UOS操作系统】python3.11安装numpy库及导入问题解决

一、安装Python3.11.4 首先来安装Python3.11.4。所用操作系统&#xff1a;统信UOS 前提是准备好Python3.11.4的安装包&#xff08;可从官网下载&#xff08;链接&#xff09;&#xff09;&#xff0c;并解压到本地&#xff1a; 右键&#xff0c;选择“在终端中打开”&#xff…...

【中间件】nginx反向代理实操

一、说明 nginx用于做反向代理&#xff0c;其目标是将浏览器中的请求进行转发&#xff0c;应用场景如下&#xff1a; 说明&#xff1a; 1、用户在浏览器中发送请求 2、nginx监听到浏览器中的请求时&#xff0c;将该请求转发到网关 3、网关再将请求转发至对应服务 二、具体操作…...

嵌入式硬件篇---加法减法积分微分器

文章目录 前言1. 加法器&#xff08;Summing Amplifier&#xff09;结构反相加法器同相加法器 特点反相输出虚地特性 应用 2. 减法器&#xff08;差分放大器&#xff09;结构特点差分放大共模抑制比 应用 3. 积分器结构特点直流漂移问题应用 4. 微分器结构特点应用关键注意事项…...

Spring Cloud Gateway 的执行链路详解

Spring Cloud Gateway 的执行链路详解 &#x1f3af; 核心目标 明确 Spring Cloud Gateway 的请求处理全过程&#xff08;从接收到请求 → 到转发 → 到返回响应&#xff09;&#xff0c;方便你在合适的生命周期节点插入你的逻辑。 &#x1f9f1; 核心执行链路图&#xff08;执…...

鸿蒙应用(医院诊疗系统)开发篇2·Axios网络请求封装全流程解析

一、项目初始化与环境准备 1. 创建鸿蒙工程 src/main/ets/ ├── api/ │ ├── api.ets # 接口聚合入口 │ ├── login.ets # 登录模块接口 │ └── request.ets # 网络请求核心封装 └── pages/ └── login.ets # 登录页面逻辑…...

突发重磅消息!!!CVE项目将被取消?

突发重磅消息&#xff01;&#xff01;&#xff01;CVE项目将被取消&#xff1f;突发&#xff01;来自可靠消息来源。MITRE 对 CVE 项目的支持将于明天到期。附件信件已发送给 CVE 董事会成员。https://mp.weixin.qq.com/s/N3qkiHaDfzDuBMK3JbBCjw...

详解与FTP服务器相关操作

目录 什么是FTP服务器 搭建FTP服务器相关 ​编辑 Unity中与FTP相关的类 上传文件到FTP服务器 使用FTP服务器上传文件的关键点 开始上传 从FTP服务器下载文件到客户端 使用FTP下载文件的关键点 开始下载 关于FTP服务器的其他操作 将文件的上传&#xff0c;下载&…...

远程登录一个Linux系统,如何用命令快速知道该系统属于Linux的哪个发行版,以及该服务器的各种配置参数,运行状态?

远程登录一个Linux系统&#xff0c;如何用命令快速知道该系统属于Linux的哪个发行版&#xff0c;以及该服务器的各种配置参数&#xff0c;运行状态&#xff1f; 查看Linux发行版信息 查看发行版名称和版本&#xff1a; cat /etc/*-release或 lsb_release -a查看内核版本&#…...

解决 .Net 6.0 项目发布到IIS报错:HTTP Error 500.30

今天在将自己开发许久的项目上线的时候&#xff0c;发现 IIS 发布后请求后端老是报一个 HTTP Error 500.30 的异常&#xff0c;如下图所示。   后来仔细调查了一下发现是自己的程序中写了 UseStaticFiles 的依赖注入&#xff0c;这个的主要作用就是发布后端后&#xff0c;想…...

STM32F103_HAL库+寄存器学习笔记16 - 监控CAN发送失败(轮询方式)

导言 《STM32F103_HAL库寄存器学习笔记15 - 梳理CAN发送失败时&#xff0c;涉及哪些寄存器》从上一章节看到&#xff0c;当CAN消息发送失败时&#xff0c;CAN错误状态寄存器ESR的TEC会持续累加&#xff0c;LEC等于0x03&#xff08;ACK错误&#xff09;。本次实验的目的是编写一…...

Java并发-AQS框架原理解析与实现类详解

什么是AQS&#xff1f; AQS&#xff08;AbstractQueuedSynchronizer&#xff09;是Java并发包&#xff08;JUC&#xff09;的核心基础框架&#xff0c;它为构建锁和同步器提供了高效、灵活的底层支持。本文将从设计原理、核心机制及典型实现类三个维度展开&#xff0c;帮助读者…...

实现定长的内存池

池化技术 所谓的池化技术&#xff0c;就是程序预先向系统申请过量的资源&#xff0c;然后自己管理起来&#xff0c;以备不时之需。这个操作的价值就是&#xff0c;如果申请与释放资源的开销较大&#xff0c;提前申请资源并在使用后并不释放而是重复利用&#xff0c;能够提高程序…...

vs2022使用git方法

1、创建git 2、在cmd下执行 git push -f origin master &#xff0c;会把本地代码全部推送到远程&#xff0c;同时会覆盖远程代码。 3、需要设置【Git全局设置】&#xff0c;修改的代码才会显示可以提交&#xff0c;否则是灰色的不能提交。 4、创建的分支&#xff0c;只要点击…...

Mysql中表的使用(3)

目录 1.updata的使用 2.delete(删除表中数据)drop&#xff08;删除表&#xff09; 数据库的约束 1.NOT NULL 指定列不能为空 2.UNIQUE指定列唯一 3.DEFAULT(默认值) 4.PRIMARY KEY 5.自增主键 1.updata的使用 1.0update 表名 set 列名x where 列名y; 2.0update 表名 s…...

BUUCTF-Web(1-20)

目录 一.SQL注入 (1)[极客大挑战 2019]EasySQL 万能密码 (7)[SUCTF 2019]EasySQL 堆叠注入 解一&#xff1a; 解二&#xff1a; (10)[强网杯 2019]随便注 堆叠注入 解一&#xff1a; 解二&#xff1a; 解三&#xff1a; (8)[极客大挑战 2019]LoveSQL 联…...

Uniapp:确认框

目录 一、 出现场景二、 效果展示三、具体使用 一、 出现场景 在项目的开发中&#xff0c;会经常出现删除数据的情况&#xff0c;如果直接删除的话&#xff0c;可能会存在误删&#xff0c;用户体验不好&#xff0c;所以需要增加一个消息提示&#xff0c;提醒用户是否删除。 二…...

【前端网络请求入门】XMLHttpRequest与Fetch保姆级教程

新手学前端时&#xff0c;经常会被「如何让网页和服务器说话」难住。今天我们用最通俗的语言&#xff0c;把浏览器最常用的两种网络请求方式——XMLHttpRequest和Fetch讲清楚&#xff0c;还会带完整的代码示例&#xff0c;跟着敲一遍就能上手&#xff01; 一、先搞懂「网络请求…...

力扣热题100—滑动窗口(c++)

3.无重复字符的最长子串 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 unordered_set<char> charSet; // 用于保存当前窗口的字符int left 0; // 窗口左指针int maxLength 0; // 最长子串的长度for (int right 0; right < s.siz…...

实验四 中断实验

一、实验目的 掌握中断服务程序的编写。 二、实验电路 三、实验内容 1&#xff0e;实验用PC机内部的中断控制器8259A&#xff0c;中断源用TPC-ZK实验箱上的单脉冲电路&#xff0c;将单脉冲电路的输出接中断请求信号IRQ&#xff0c;每按一次单脉冲按键产生一次…...

腾势品牌欧洲市场冲锋,科技豪华席卷米兰

在时尚与艺术的交汇点&#xff0c;米兰设计周的舞台上&#xff0c;一场汽车界的超级风暴正在酝酿&#xff0c;腾势品牌如一头勇猛无畏的雄狮&#xff0c;以雷霆万钧之势正式向欧洲市场发起了冲锋。其最新力作——腾势Z9GT的登场&#xff0c;仿佛是一道闪电划破夜空&#xff0c;…...

第五阶段:项目实践与后续学习指引

模块 10&#xff1a;综合项目实践 在这个模块中&#xff0c;我们将通过实际项目来综合运用前面所学的 Python 知识。我们会选择一个命令行记事本应用作为主要示例&#xff0c;同时简要介绍其他项目的思路。 项目&#xff1a;命令行记事本应用 1. 项目规划 良好的项目规划是…...

MoogDB数据库日常维护技巧与常见问题解析

在当今的数据驱动世界中&#xff0c;数据库作为信息存储与管理的核心组件&#xff0c;扮演着举足轻重的角色。MoogDB作为一款高性能、易扩展的数据库解决方案&#xff0c;越来越受到开发者和企业的青睐。为了确保MoogDB的稳定性与高性能&#xff0c;定期的日常维护及对常见问题…...

Java 中的各种锁详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…...

多店铺商城_多商户商城系统源码_免费开源!

在电商行业快速发展的今天&#xff0c;多店铺商城系统&#xff08;B2B2C模式&#xff09;已成为企业实现平台化运营的核心工具&#xff0c;就像我们平时用的淘宝&#xff0c;京东那样。如果你想做一个电商平台就需要这种多店铺商城系统。 本文将深入探讨多商户商城系统的核心功…...

【2025年泰迪杯数据挖掘挑战赛】A题 数据分析+问题建模与求解+Python代码直接分享

目录 2025年泰迪杯数据挖掘挑战赛A题完整论文&#xff1a;建模与求解Python代码1问题一的思路与求解1.1 问题一的思路1.1.1对统计数据进行必要说明&#xff1a;1.1.2统计流程&#xff1a;1.1.3特殊情况的考虑&#xff1a; 1.2 问题一的求解1.2.1代码实现1.2.2 问题一结果代码分…...