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

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

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

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

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

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

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

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

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

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

我们要加载这个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()

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

通过初始化自定义组件 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())

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

相关推荐
伍华聪7 天前
基于 SocketIO 消息协议规范,并构建FastAPI上的SocketIO 应用
python开发
伍华聪10 天前
使用PySide6/PyQt6实现系统图标的展示和选择处理
python开发·pyside6/pyqt6开发
伍华聪12 天前
使用PySide6/PyQt6实现程序启动画面的处理
python开发·pyside6/pyqt6开发
伍华聪16 天前
使用PySide6/PyQt6或者WxPython实现对列表界面和树列表界面的整合处理
python开发
伍华聪5 个月前
在Python后端项目FastApi中使用MongoDB进行数据处理
python开发·mongodb数据库
伍华聪6 个月前
使用PySide6/PyQt6实现Python跨平台通用列表页面的基类设计
python开发
伍华聪6 个月前
使用PySide6/PyQt6实现Python跨平台表格数据分页打印预览处理
python开发
伍华聪7 个月前
使用PySide6/PyQt6实现Python跨平台GUI框架的开发
python开发
伍华聪9 个月前
一问一答学习PyQT6,对比WxPython和PyQt6的差异
python开发