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>
相关推荐
前端大卫11 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘11 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare11 小时前
浅浅看一下设计模式
前端
Lee川11 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix11 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人11 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl11 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅11 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人12 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼12 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端