uniapp使用uview UI,自定义级联选择组件

一、需求:

1.省市区级联选择,可多选
2.可以一键选择某个区域下的所有数据
3.点击省展开市,点击市展开区,以此类推(可返回上一层或多层)
4.只获取选择的人

效果视频

二、注意事项以及源码

1.需要安装uView UI组件库,安装地址uView UI官网

2.源码,复制即用

javascript 复制代码
<template>
  <view class="container">
    <!-- 层级导航 -->
    <view class="breadcrumb">
      <text class="breadcrumb-item" v-for="(item, index) in breadcrumbList" :key="index" @click="goBackToLevel(index)"
        :class="{ 'breadcrumb-item-active': index === breadcrumbList.length - 1 }">
        {{ item }}
        <text v-if="index !== breadcrumbList.length - 1" class="breadcrumb-separator"> ></text>
      </text>
    </view>

    <!-- 层级内容 -->
    <view class="level-content">
      <view class="level-item" v-for="(item, index) in currentLevelData" :key="item.id">
        <!-- 将点击事件移到内部元素,避免与复选框冲突 -->
        <view class="level-item-left" @click="handleLevelItemClick(item)">
          <view class="level-icon" :style="{ backgroundColor: getLevelColor(item) }">
            <text class="level-icon-text">{{ getLevelCode(item) }}</text>
          </view>
          <view class="level-info">
            <text class="level-title">{{ item.name }}</text>
            <text class="level-desc">
              {{ item.leaf ? item.emergencyPersonnel.phone : `${getAllLeafCount(item)} 个人员` }}
            </text>
          </view>
        </view>

        <view class="select-checkbox">
          <u-checkbox :checked="isNodeSelected(item)" :indeterminate="isNodeIndeterminate(item)"
            @change="(value) => handleNodeSelect(item, value)" shape="circle" active-color="#4F46E5"></u-checkbox>
        </view>
      </view>
    </view>

    <!-- 底部操作栏 -->
    <view class="footer-bar">
      <view class="selected-info">
        <u-icon name="account-fill" size="30" color="#4F46E5"></u-icon>
        <text class="selected-count">已选择 {{ selectedPersons.length }} 人</text>
      </view>
      <u-button type="primary" @click="toFormPage" :disabled="selectedPersons.length === 0"
        class="next-button">下一步</u-button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      // 人员数据结构(修改后:新增省份、简化字段、常见姓名)
      personnelData: [
        {
          "id": "510000",
          "name": "四川省",
          "children": [
            {
              "id": "511400",
              "name": "眉山市",
              "children": [
                {
                  "id": "市管理员",
                  "name": "市管理员",
                  "children": [
                    {
                      "id": "10",
                      "name": "赵六",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "市管理员",
                        "unitAddress": "眉山市政务服务中心",
                        "personName": "赵六",
                        "phone": "19162984018",
                        "provinceName": "四川省",
                        "cityName": "眉山市",
                        "countyName": ""
                      },
                      "leaf": true
                    },
                    {
                      "id": "11",
                      "name": "孙七",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "市管理员",
                        "unitAddress": "眉山市政务服务中心",
                        "personName": "孙七",
                        "phone": "18180809001",
                        "provinceName": "四川省",
                        "cityName": "眉山市",
                        "countyName": ""
                      },
                      "leaf": true
                    }
                  ],
                  "emergencyPersonnel": null,
                  "leaf": false
                }
              ],
              "emergencyPersonnel": null,
              "leaf": false
            }
          ],
          "emergencyPersonnel": null,
          "leaf": false
        },
        {
          "id": "440000",
          "name": "广东省",
          "children": [
            {
              "id": "440100",
              "name": "广州市",
              "children": [
                {
                  "id": "天河区管理员",
                  "name": "天河区管理员",
                  "children": [
                    {
                      "id": "12",
                      "name": "周八",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "天河区管理员",
                        "unitAddress": "广州市天河区政务中心",
                        "personName": "周八",
                        "phone": "18228880309",
                        "provinceName": "广东省",
                        "cityName": "广州市",
                        "countyName": "天河区"
                      },
                      "leaf": true
                    },
                    {
                      "id": "13",
                      "name": "吴九",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "天河区管理员",
                        "unitAddress": "广州市天河区政务中心",
                        "personName": "吴九",
                        "phone": "18990370720",
                        "provinceName": "广东省",
                        "cityName": "广州市",
                        "countyName": "天河区"
                      },
                      "leaf": true
                    }
                  ],
                  "emergencyPersonnel": null,
                  "leaf": false
                },
                {
                  "id": "海珠区管理员",
                  "name": "海珠区管理员",
                  "children": [
                    {
                      "id": "14",
                      "name": "郑十",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "海珠区管理员",
                        "unitAddress": "广州市海珠区政务中心",
                        "personName": "郑十",
                        "phone": "15508337779",
                        "provinceName": "广东省",
                        "cityName": "广州市",
                        "countyName": "海珠区"
                      },
                      "leaf": true
                    }
                  ],
                  "emergencyPersonnel": null,
                  "leaf": false
                }
              ],
              "emergencyPersonnel": null,
              "leaf": false
            },
            {
              "id": "440300",
              "name": "深圳市",
              "children": [
                {
                  "id": "南山区管理员",
                  "name": "南山区管理员",
                  "children": [
                    {
                      "id": "15",
                      "name": "钱十一",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "南山区管理员",
                        "unitAddress": "深圳市南山区政务中心",
                        "personName": "钱十一",
                        "phone": "15983336111",
                        "provinceName": "广东省",
                        "cityName": "深圳市",
                        "countyName": "南山区"
                      },
                      "leaf": true
                    }
                  ],
                  "emergencyPersonnel": null,
                  "leaf": false
                }
              ],
              "emergencyPersonnel": null,
              "leaf": false
            }
          ],
          "emergencyPersonnel": null,
          "leaf": false
        },
        {
          "id": "330000",
          "name": "浙江省",
          "children": [
            {
              "id": "330100",
              "name": "杭州市",
              "children": [
                {
                  "id": "西湖区管理员",
                  "name": "西湖区管理员",
                  "children": [
                    {
                      "id": "16",
                      "name": "冯十二",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "西湖区管理员",
                        "unitAddress": "杭州市西湖区政务中心",
                        "personName": "冯十二",
                        "phone": "18783398823",
                        "provinceName": "浙江省",
                        "cityName": "杭州市",
                        "countyName": "西湖区"
                      },
                      "leaf": true
                    },
                    {
                      "id": "17",
                      "name": "陈十三",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "西湖区管理员",
                        "unitAddress": "杭州市西湖区政务中心",
                        "personName": "陈十三",
                        "phone": "13547674447",
                        "provinceName": "浙江省",
                        "cityName": "杭州市",
                        "countyName": "西湖区"
                      },
                      "leaf": true
                    }
                  ],
                  "emergencyPersonnel": null,
                  "leaf": false
                },
                {
                  "id": "余杭区管理员",
                  "name": "余杭区管理员",
                  "children": [
                    {
                      "id": "18",
                      "name": "褚十四",
                      "children": [],
                      "emergencyPersonnel": {
                        "unitName": "余杭区管理员",
                        "unitAddress": "杭州市余杭区政务中心",
                        "personName": "褚十四",
                        "phone": "18160172259",
                        "provinceName": "浙江省",
                        "cityName": "杭州市",
                        "countyName": "余杭区"
                      },
                      "leaf": true
                    }
                  ],
                  "emergencyPersonnel": null,
                  "leaf": false
                }
              ],
              "emergencyPersonnel": null,
              "leaf": false
            }
          ],
          "emergencyPersonnel": null,
          "leaf": false
        }
      ],

      // 当前层级数据
      currentLevelData: [],
      // 层级导航路径
      breadcrumbList: [],
      // 已选择的人员
      selectedPersons: [],
      // 层级历史记录,用于返回
      levelHistory: []
    };
  },

  onLoad() {
    // 初始化层级数据(因personnelData改为数组,此处调整为加载所有省份)
    this.currentLevelData = this.personnelData;
    this.breadcrumbList = ["全国"];
    this.levelHistory = [this.currentLevelData];
  },

  methods: {
    // 获取层级颜色
    getLevelColor(item) {
      if (item.leaf) {
        return '#E0E7FF';
      }

      const colors = ['#E0F2FE', '#DBEAFE', '#EFF6FF', '#F0FDF4', '#FEF3C7'];
      let hash = 0;
      for (let i = 0; i < item.name.length; i++) {
        hash = item.name.charCodeAt(i) + ((hash << 5) - hash);
      }
      return colors[Math.abs(hash) % colors.length];
    },

    // 获取层级代码
    getLevelCode(item) {
      if (item.leaf) {
        return item.name.charAt(0);
      }

      return item.name.substring(0, 2);
    },

    // 递归获取某个节点下的所有叶子节点
    getAllLeafNodes(node) {
      let leafNodes = [];
      if (node.leaf) {
        leafNodes.push(node);
      } else if (node.children && node.children.length > 0) {
        node.children.forEach(child => {
          leafNodes = leafNodes.concat(this.getAllLeafNodes(child));
        });
      }
      return leafNodes;
    },

    // 计算某个节点下的叶子节点总数
    getAllLeafCount(node) {
      return this.getAllLeafNodes(node).length;
    },

    // 判断某个节点的选中状态
    isNodeSelected(node) {
      const leafNodes = this.getAllLeafNodes(node);
      if (node.leaf) {
        return this.selectedPersons.some(p => p.id === node.id);
      }
      return leafNodes.every(leaf =>
        this.selectedPersons.some(p => p.id === leaf.id)
      );
    },

    // 判断某个非叶子节点是否半选
    isNodeIndeterminate(node) {
      if (node.leaf) return false;

      const leafNodes = this.getAllLeafNodes(node);
      const selectedLeafCount = leafNodes.filter(leaf =>
        this.selectedPersons.some(p => p.id === leaf.id)
      ).length;

      return selectedLeafCount > 0 && selectedLeafCount < leafNodes.length;
    },

    // 将叶子节点转换为selectedPersons格式
    convertLeafToSelected(leafNode) {
      return {
        id: leafNode.id,
        name: leafNode.name,
        phone: leafNode.emergencyPersonnel.phone,
        unitName: leafNode.emergencyPersonnel.unitName,
        unitAddress: leafNode.emergencyPersonnel.unitAddress,
        provinceName: leafNode.emergencyPersonnel.provinceName,
        cityName: leafNode.emergencyPersonnel.cityName,
        countyName: leafNode.emergencyPersonnel.countyName
      };
    },

    // 处理节点选择 - 修正事件参数问题
    handleNodeSelect(node, checked) {
      const leafNodes = this.getAllLeafNodes(node);

      if (checked) {
        // 选中操作:添加所有未选中的叶子节点
        leafNodes.forEach(leaf => {
          const isExist = this.selectedPersons.some(p => p.id === leaf.id);
          if (!isExist) {
            this.selectedPersons.push(this.convertLeafToSelected(leaf));
          }
        });
      } else {
        // 取消选中:移除所有相关叶子节点
        this.selectedPersons = this.selectedPersons.filter(p =>
          !leafNodes.some(leaf => leaf.id === p.id)
        );
      }
    },

    // 处理层级项点击
    handleLevelItemClick(item) {
      if (!item.leaf && item.children && item.children.length > 0) {
        // 判断当前层级是否是最外层(全国层级)
        const isRootLevel = this.breadcrumbList.length === 1 && this.breadcrumbList[0] === "全国";
        if (!isRootLevel) {
          this.breadcrumbList.push(item.name);
        } else {
          // 从全国进入省份时,更新导航路径
          this.breadcrumbList = ["全国", item.name];
        }
        this.currentLevelData = item.children;
        this.levelHistory.push(this.currentLevelData);
      }
    },

    // 返回上一级
    goBackToLevel(index) {
      if (index >= this.breadcrumbList.length - 1) return;

      // 调整导航路径
      this.breadcrumbList = this.breadcrumbList.slice(0, index + 1);
      // 调整层级历史
      this.levelHistory = this.levelHistory.slice(0, index + 1);
      // 更新当前层级数据
      this.currentLevelData = this.levelHistory[index];
    },

    // 前往表单页面
    toFormPage() {
      console.log(this.selectedPersons);
      if (this.selectedPersons.length === 0) {
        this.$u.toast('请至少选择一个人员');
        return;
      }
    }
  }
};
</script>

<style scoped>
/* 样式保持不变 */
.container {
  background-color: #F8F8F8;
  min-height: 100vh;
  padding-bottom: 60px;
}

.breadcrumb {
  padding: 12px 15px;
  background-color: white;
  display: flex;
  align-items: center;
  font-size: 14px;
  overflow-x: auto;
  white-space: nowrap;
}

.breadcrumb-item {
  color: #6B7280;
  padding: 0 2px;
}

.breadcrumb-item-active {
  color: #4F46E5;
  font-weight: 500;
}

.breadcrumb-separator {
  margin: 0 4px;
  color: #D1D5DB;
}

.level-content {
  background-color: white;
  padding-top: 10px;
}

.level-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 15px;
  border-bottom: 1px solid #F3F4F6;
}

.level-item-left {
  display: flex;
  align-items: center;
  width: 80%;
}

.level-icon {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.level-icon-text {
  font-size: 14px;
  font-weight: 500;
  color: #4F46E5;
}

.level-info {
  margin-left: 12px;
}

.level-title {
  font-size: 16px;
  color: #1F2937;
}

.level-desc {
  font-size: 12px;
  color: #6B7280;
  margin-top: 2px;
  display: block;
}

.select-checkbox {
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.footer-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: white;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 15px;
  border-top: 1px solid #F3F4F6;

}

.selected-info {
  display: flex;
  align-items: center;
  color: #1F2937;
  font-size: 14px;
}

.selected-count {
  margin-left: 5px;
}

.next-button {
  width: 120px;
  border-radius: 20px;
}
</style>
相关推荐
java水泥工6 小时前
基于Echarts+HTML5可视化数据大屏展示-惠民服务平台
前端·echarts·html5
万少7 小时前
可可图片编辑 HarmonyOS(3)应用间分享图片
前端·harmonyos·客户端
Hy行者勇哥7 小时前
现代软件系统架构:前端、后端、数据库、部署、算法与AI学习的结构与交互分析
前端·数据库·学习
前端开发爱好者8 小时前
90% 前端都不知道的 20 个「零依赖」浏览器原生能力!
前端·javascript·vue.js
讨厌吃蛋黄酥8 小时前
React语法全景指南:面试官问我用了哪些语法时,我这样回答拿到了offer
前端·react.js·面试
Bling_Bling_18 小时前
面试常考css:三列布局实现方式
前端·html·css3
讨厌吃蛋黄酥9 小时前
Promise的底层揭秘:微任务与观察者模式的完美共舞
前端·javascript·面试
月下点灯9 小时前
一探究竟bilibili自动进入画中画视频小窗继续播放
前端·javascript·html
咔咔一顿操作9 小时前
第五章 vue3 + Three.js 实现高级镜面反射效果案例解析
前端·javascript·vue.js·人工智能·信息可视化·threejs