uniapp使用uview UI,自定义级联选择组件
一、需求:
1.省市区级联选择,可多选
2.可以一键选择某个区域下的所有数据
3.点击省展开市,点击市展开区,以此类推(可返回上一层或多层)
4.只获取选择的人
效果视频
二、注意事项以及源码
1.需要安装uView UI组件库,安装地址uView UI官网
2.源码,复制即用
<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>