惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
爱范儿
爱范儿
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Y
Y Combinator Blog
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
M
MIT News - Artificial intelligence
博客园 - Franky
V
Visual Studio Blog
I
InfoQ
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
博客园 - 司徒正美
L
LangChain Blog

博客园 - 伍华聪

使用Winform+WebView2+HTML构建弹性化、样式自定义化的管理系统界面 Cursor 里开发一个“一个后端 + 多个前端”项目的时候,推荐的项目目录结构组织方式 使用Cursor实现管理系统的主界面布局的Vue3前端开发 使用Cursor实现管理系统登录界面的快速开发 多租户系统框架的界面分析设计 多租户系统框架的基础模块设计和分析设计 多租户下的系统业务开发过程探讨 多租户下的ERP系统的仓储管理模块分析设计 多租户下的系统基础表设计 基于SqlSugar开发框架的基础上快速开发H5端的移动应用 在Python+FastAPI项目中使用SqlAlchemy操作数据的几种常见方式 在PySide6/PyQt6的项目中封装一些基础类库,包括文件对话框、字体对话框、颜色对话框、消息对话框等内容 在PySide6/PyQt6的项目中实现样式切换处理 如何在Python开发中实现无代码、纯配置的业务界面展示和常规数据操作的处理分析过程 在Winform开发中,动态增加标签分组,实现数据记录的快速录入 在基于FastAPI的Python开发框架后端,增加阿里云短信和邮件发送通知处理 介绍一个医疗物质数目清点系统的实现过程 在PySide6/PyQt6的开发框架中,增加对表格多种格式录入的处理,以及主从表的数据显示和保存操作。 使用PySide6/PyQt6实现自定义窗口布局,实现类似FluentWindow效果 WxPython跨平台开发框架之主从表展示和录入的界面处理--产品报价单和明细记录的处理 Vue3+ElementPlus的BS端主从表的快速开发 基于Python的FastAPI后端开发框架如何使用PyInstaller 进行打包与部署 基于 SocketIO 消息协议设计报文规范,构建FastAPI上的SocketIO 应用 使用PySide6/PyQt6实现系统图标的展示和选择处理
使用PySide6/PyQt6实现全国省市区的级联选择组件
伍华聪 · 2025-09-22 · via 博客园 - 伍华聪

在基于BS或者H5实现全国省市区的级联选择组件,相对比较容易,一般都要有现成的封装,如对于移动端H5或者小程序的Vant4界面库,他们直接安装使用内置的数据即可进行调用。参考对应组件的数据,我们可以使用PySide6/PyQt6实现全国省市区的级联选择组件案例。

1、Vant4界面库使用的省市区组件数据

如参考Vant4的Area省市区选择组件:https://vant-ui.github.io/vant/#/zh-CN/area

image

 Vant 提供了一份中国省市区数据,你可以安装 @vant/area-data npm 包来引入即可使用,跟踪其使用的代码,可以看到对应的全国省市区的数据集合。

image

2、使用PySide6/PyQt6实现全国省市区的级联选择组件案例

参考对应组件的数据,我们可以使用PySide6/PyQt6实现全国省市区的级联选择组件案例,首先我们根据上面的数据集合,定义一个JSON文件,用于Python组件进行加载的数据源。

我们来构建一个全国86编码下的省份集合,如下所示。

image

对应省份下的城市,城市下的分区,分区下的乡镇,都可以如此遍历下去。

image

 这样就构建一个多级遍历的集合,可以无穷层级下去(如果必要的话)。

我们要加载这个JSON文件,在Python中很简单,使用json.load即可。

    region_data = {
          "86": {
            "110000": "北京市",
            "120000": "天津市",
        },
        "110000": {
            "110100": "市辖区"
        },
    }

    # 加载数据
    region_file = "china_area_data.json"
    data_file_path = path.join(path.dirname(__file__), region_file)
    
    with open(data_file_path, "r", encoding="utf-8") as f:
        region_data = json.load(f)

主要的界面逻辑,就是动态生成省市区的标签和下拉组件,并结合事件进行级联的处理操作。

        layout = QHBoxLayout(self)
        # 动态创建多层级 ComboBox
        for i in range(levels):
            box_layout = QHBoxLayout()
            label = QLabel(self.labels[i] + ":")
            combo = QComboBox()

            box_layout.addWidget(label, 0)
            box_layout.addWidget(combo, 0)
            layout.addLayout(box_layout)
            self.combo_boxes.append(combo)

            # 绑定事件
            combo.currentIndexChanged.connect(
                lambda idx, level=i: self.on_selection_changed(level, idx)
            )

上面代码就是构建多级的显示名称和下拉组件,在触发选择的事件后,更新下一级控件(下拉列表)的数据集合即可。

    def on_selection_changed(self, level, index):
        """当某一层选择变化时,更新下级"""
        if index < 0:
            return
        code = self.combo_boxes[level].itemData(index)

        # 更新下一层
        if level + 1 < self.levels:
            self.populate_level(level + 1, code)

        # 更新结果
        self.update_result()

        # 发射信号
        self.selectionChanged.emit(self.get_selection())

其中的selectionChanged的信号事件,是我们为自定义类定义的一个信号变量。

class CascadeSelector(QWidget):
    selectionChanged = Signal(list)   # 信号,传递选中的 (code, name) 列表

通过它的绑定处理,我们就可以在调用代码中获得选择的集合。

    region_data = {
          "86": {
            "110000": "北京市",
            "120000": "天津市",
        },
        "110000": {
            "110100": "市辖区"
        },
    }

    # 加载数据
    region_file = "china_area_data.json"
    data_file_path = path.join(path.dirname(__file__), region_file)
    
    with open(data_file_path, "r", encoding="utf-8") as f:
        region_data = json.load(f)

    win = CascadeSelector(region_data, root_code="86", levels=3, labels=["省", "市", "区"])
    # 主界面绑定信号
    win.selectionChanged.connect(lambda sel: print("主界面收到:", sel))
    win.resize(400, 200)
    win.show()

运行界面效果,如下所示。

image

通过初始化自定义组件 CascadeSelector ,我们就可以实现类似省市区等多层级的数据级联选择的处理效果。

CascadeSelector组件的完整代码如下所示。

import sys
import json
from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QComboBox, QLabel, QHBoxLayout
)
from PySide6.QtCore import Signal

from os import path
from pathlib import Path

class CascadeSelector(QWidget):
    selectionChanged = Signal(list)   # 信号,传递选中的 (code, name) 列表

    def __init__(self, data, root_code="86", levels=3, labels=None, parent=None):
        """
        :param data: 行政区划映射数据 (dict)
        :param root_code: 起始父节点 (默认 "86" -> 全国)
        :param levels: 层级数量 (如 3 = 省、市、区)
        :param labels: 每层的提示文字 (如 ["省", "市", "区"])
        """
        super().__init__(parent)

        self.data = data
        self.root_code = root_code
        self.levels = levels
        self.labels = labels or [f"Level{i+1}" for i in range(levels)]
        self.combo_boxes :list[QComboBox] = []

        layout = QHBoxLayout(self)
        # 动态创建多层级 ComboBox
        for i in range(levels):
            box_layout = QHBoxLayout()
            label = QLabel(self.labels[i] + ":")
            combo = QComboBox()

            box_layout.addWidget(label, 0)
            box_layout.addWidget(combo, 0)
            layout.addLayout(box_layout)
            self.combo_boxes.append(combo)

            # 绑定事件
            combo.currentIndexChanged.connect(
                lambda idx, level=i: self.on_selection_changed(level, idx)
            )

        layout.addStretch(1)
        # 结果显示
        # self.result_label = QLabel("选择结果:")
        # layout.addWidget(self.result_label)

        # 初始化第一级
        self.populate_level(0, self.root_code)

    def populate_level(self, level, parent_code):
        """填充某一层级的 ComboBox"""
        if level >= self.levels:
            return
        combo : QComboBox = self.combo_boxes[level]
        combo.blockSignals(True)
        combo.clear()
        children = self.data.get(parent_code, {})
        for code, name in children.items():
            combo.addItem(name, code)
        combo.blockSignals(False)

        # 自动联动下一层
        if combo.count() > 0:
            self.on_selection_changed(level, 0)

    def on_selection_changed(self, level, index):
        """当某一层选择变化时,更新下级"""
        if index < 0:
            return
        code = self.combo_boxes[level].itemData(index)

        # 更新下一层
        if level + 1 < self.levels:
            self.populate_level(level + 1, code)

        # 更新结果
        self.update_result()

        # 发射信号
        self.selectionChanged.emit(self.get_selection())

    def update_result(self):
        names = [cb.currentText() for cb in self.combo_boxes if cb.currentIndex() >= 0]
        # self.result_label.setText("选择结果:" + " - ".join(names))

    def get_selection(self):
        """获取完整的选择结果 (code 和 name)"""
        result = []
        for cb in self.combo_boxes:
            cb:QComboBox
            idx = cb.currentIndex()
            if idx >= 0:
                result.append((cb.itemData(idx), cb.currentText()))
        return result


# -------------------- 测试 --------------------
if __name__ == "__main__":
    app = QApplication(sys.argv)

    # 模拟数据(和你给的格式一致)
    region_data = {
          "86": {
            "110000": "北京市",
            "120000": "天津市",
        },
        "110000": {
            "110100": "市辖区"
        },
        "110100": {
            "110101": "东城区",
            "110102": "西城区",
            "110105": "朝阳区",
            "110106": "丰台区",
            "110107": "石景山区",
            "110108": "海淀区",
            "110109": "门头沟区",
            "110111": "房山区",
            "110112": "通州区",
            "110113": "顺义区",
            "110114": "昌平区",
            "110115": "大兴区",
            "110116": "怀柔区",
            "110117": "平谷区",
            "110118": "密云区",
            "110119": "延庆区"
        },
        "120000": {
            "120100": "市辖区"
        },
        "120100": {
            "120101": "和平区",
            "120102": "河东区",
            "120103": "河西区",
            "120104": "南开区",
            "120105": "河北区",
            "120106": "红桥区",
            "120110": "东丽区",
            "120111": "西青区",
            "120112": "津南区",
            "120113": "北辰区",
            "120114": "武清区",
            "120115": "宝坻区",
            "120116": "滨海新区",
            "120117": "宁河区",
            "120118": "静海区",
            "120119": "蓟州区"
        },
    }

    # 加载数据
    region_file = "china_area_data.json"
    data_file_path = path.join(path.dirname(__file__), region_file)
    
    with open(data_file_path, "r", encoding="utf-8") as f:
        region_data = json.load(f)

    win = CascadeSelector(region_data, root_code="86", levels=3, labels=["", "", ""])
    # 主界面绑定信号
    win.selectionChanged.connect(lambda sel: print("主界面收到:", sel))
    win.resize(400, 200)
    win.show()

    sys.exit(app.exec())

通过这样,我们把自定义组件作为一个界面元素,可以放在任何需要的地方呈现,实现了数据的封装,并获得事件的信号处理即可。

image