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

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
A
About on SuperTechFans
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
Microsoft Azure Blog
Microsoft Azure Blog
腾讯CDC
爱范儿
爱范儿
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
V
Visual Studio Blog
有赞技术团队
有赞技术团队
U
Unit 42
D
Docker
小众软件
小众软件
F
Full Disclosure
I
Intezer
Scott Helme
Scott Helme
P
Privacy International News Feed
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
B
Blog
Martin Fowler
Martin Fowler
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Spread Privacy
Spread Privacy
宝玉的分享
宝玉的分享
S
Security Affairs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
月光博客
月光博客
C
Cisco Blogs
云风的 BLOG
云风的 BLOG
Schneier on Security
Schneier on Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Threat Research - Cisco Blogs
量子位
Hacker News - Newest:
Hacker News - Newest: "LLM"
H
Heimdal Security Blog
N
Netflix TechBlog - Medium
H
Hacker News: Front Page
P
Proofpoint News Feed
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
S
Schneier on Security

博客园 - 伍华聪

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