QTreeView QAbstractItemModel 父级在删除项后崩溃,有时会崩溃 [英] QTreeView QAbstractItemModel parent collapses after deleting item and sometimes crashes

查看:115
本文介绍了QTreeView QAbstractItemModel 父级在删除项后崩溃,有时会崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 QTreeView 中构建一个小条目列表,并基于

我的理解是,这是因为删除项目的索引会发生变化,并且为了防止可以根据此线程使用 QtCore.QPersistentModelIndex():如何删除QTableView小部件中的多行?

尽管该示例使用了 QStandardItemModel(),但由于我的示例使用了 QAbstractItemModel(),我该如何实现类似的概念,并防止它崩溃...?

导入系统从 functools 导入部分从 PyQt4 导入 QtGui、QtCoreHORIZONTAL_HEADERS =(资产名称",添加日期")类资产类(对象):'''一个简单的自定义数据对象'''def __init__(self, **kwargs):如果不是 kwargs.get('name') 或不是 kwargs.get('type'):返回self.name = kwargs.get('name')self.date_Added = kwargs.get('date_ added')self.type = kwargs.get('type')def __repr__(self):返回%s - %s %s"% (self.type, self.name, self.date_ added)类树项目(对象):'''用于返回行/列数据的python对象,并注意这是父母和/或孩子'''def __init__(self, asset, header, parent_item):self.asset = 资产self.parent_item = parent_itemself.header = 标题self.child_items = []def appendChild(self, item):self.child_items.append(item)def removeChild(self, item):打印 'removeChild: item is %s' % item打印 'removeChild: self.child_items 是 %s' % self.child_itemsself.child_items.remove(item)定义孩子(自我,行):返回 self.child_items[row]def childCount(self):返回 len(self.child_items)def columnCount(self):返回 2定义数据(自我,列):如果 self.asset == 无:如果列 == 0:返回 QtCore.QVariant(self.header)如果列 == 1:返回 QtCore.QVariant("")别的:如果列 == 0:返回 QtCore.QVariant(self.asset.name)如果列 == 1:返回 QtCore.QVariant(self.asset.date_ added)返回 QtCore.QVariant()定义父(自己):返回 self.parent_item定义行(自我):如果 self.parent_item:返回 self.parent_item.child_items.index(self)返回 0类 TreeModel(QtCore.QAbstractItemModel):'''显示几个名字的模型,按性别排序'''def __init__(self, parent=None):super(TreeModel, self).__init__(parent)self.assets = []模型数据 = ((车辆",卡车",2020 年 5 月 27 日"),(车辆"、汽车"、2020 年 5 月 25 日"),(CHARACTER",Peter",2020 年 5 月 27 日"),(CHARACTER",Rachel",2020 年 5 月 29 日"),(PROP",主席",2020 年 5 月 27 日"),(PROP"、Axe"、2020 年 5 月 17 日"))对于模型数据中的资产类型、名称、日期:资产 = 资产类(类型=资产类型,名称=名称,添加日期=日期)self.assets.append(资产)self.rootItem = TreeItem(无,所有",无)self.parents = {0: self.rootItem}self.setupModelData()def columnCount(self, parent=None):如果 parent 和 parent.isValid():返回 parent.internalPointer().columnCount()别的:返回 len(HORIZONTAL_HEADERS)定义数据(自我,索引,角色):如果不是 index.isValid():返回 QtCore.QVariant()item = index.internalPointer()如果角色== QtCore.Qt.DisplayRole:返回 item.data(index.column())如果角色 == QtCore.Qt.UserRole:如果项目:返回 item.asset返回 QtCore.QVariant()def headerData(自我,列,方向,角色):如果(方向 == QtCore.Qt.Horizo​​ntal 和角色 == QtCore.Qt.DisplayRole):尝试:返回 QtCore.QVariant(HORIZONTAL_HEADERS[column])除了索引错误:经过返回 QtCore.QVariant()定义索引(自我,行,列,父):如果不是 self.hasIndex(row, column, parent):返回 QtCore.QModelIndex()如果不是 parent.isValid():parent_item = self.rootItem别的:parent_item = parent.internalPointer()childItem = parent_item.child(row)如果子项:返回 self.createIndex(row, column, childItem)别的:返回 QtCore.QModelIndex()def parent(self, index):如果不是 index.isValid():返回 QtCore.QModelIndex()childItem = index.internalPointer()如果不是 childItem:返回 QtCore.QModelIndex()parent_item = childItem.parent()如果 parent_item == self.rootItem:返回 QtCore.QModelIndex()返回 self.createIndex(parent_item.row(), 0, parent_item)def rowCount(self, parent=QtCore.QModelIndex()):如果 parent.column() >0:返回 0如果不是 parent.isValid():p_Item = self.rootItem别的:p_Item = parent.internalPointer()返回 p_Item.childCount()def setupModelData(self):对于 self.assets 中的资产:资产类型 = 资产类型如果不是 self.parents.has_key(asset_type):new_parent = TreeItem(无,资产类型,self.rootItem)self.rootItem.appendChild(new_parent)self.parents[asset_type] = new_parent打印 'self.parents:', self.parentsparent_item = self.parents[资产类型]new_item = TreeItem(资产,",parent_item)parent_item.appendChild(new_item)def addSubRow(self, new_asset):资产类型、名称、日期 = 新资产资产 = 资产类(类型=资产类型,名称=名称,添加日期=日期)parent_item = self.parents[资产类型]已经存在 = 假对于 parent_item.child_items 中的孩子:如果 child.asset.name == name 和 child.asset.type == asset_type:已经存在 = 真如果已经存在:打印此资产已存在"返回new_item = TreeItem(资产,",parent_item)parent_item.appendChild(new_item)def removeRow(self, rowIndexes):child_tree_item = rowIndexes[0].internalPointer()asset_type = rowIndexes[0].parent().data().toString()parent_item = self.parents[str(asset_type)]# 删除后保持树打开的提示:https://stackoverflow.com/questions/48121393/how-to-delete-multiple-rows-in-a-qtableview-widgetself.beginRemoveRows(QtCore.QModelIndex(), rowIndexes[0].row(), rowIndexes[0].row() + 1)parent_item.removeChild(child_tree_item)self.endRemoveRows()定义搜索模型(自我,资产):'''获取给定约会的模型索引'''定义搜索节点(节点):'''递归调用的函数,查看节点下的所有节点'''对于 node.child_items 中的孩子:打印 child.childCount()如果资产== child.asset:index = self.createIndex(child.row(), 0, child)回报指数如果 child.childCount() >0:结果 = searchNode(child)如果结果:返回结果retarg = searchNode(self.parents[0])打印重新输入返回重新输入def findAssetName(self, name):应用程序 = 无对于 self.assets 中的资产:打印资产名称如果资产名称 == 名称:应用 = 资产休息如果应用程序 != 无:索引 = self.searchModel(app)返回(真,索引)返回(假,无)类树视图(QtGui.QTreeView):right_button_clicked = QtCore.pyqtSignal(list, int)def __init__(self, parent=None):super(TreeView, self).__init__(parent)# self.clicked.connect(self.on_treeView_clicked)self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)self.customContextMenuRequested.connect(self.openMenu)def selectedRows(self):行 = []对于 self.selectedIndexes() 中的索引:如果 index.column() == 0:行.追加(索引.行())打印类型(行)返回行def openMenu(self, position):索引 = self.selectedIndexes()如果 len(indexes) >0:等级 = 0索引 = 索引[0]而 index.parent().isValid():index = index.parent()等级 += 1菜单 = QtGui.QMenu()编辑菜单 = 无如果级别 == 0:editMenu = QtGui.QAction(编辑人物", self)menu.addAction(editMenu)elif 级别 == 1:editMenu = QtGui.QAction(删除", self)menu.addAction(editMenu)elif 级别 == 2:editMenu = QtGui.QAction(编辑对象", self)menu.addAction(editMenu)如果编辑菜单:editMenu.triggered.connect(部分(self._on_right_click,索引,级别))menu.exec_(self.viewport().mapToGlobal(position))def _on_right_click(自我,索引,级别):self.right_button_clicked.emit(索引,级别)def delete_test(self):打印点击添加按钮"new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')资产类型、名称、日期 = 新资产资产 = 资产类(类型=资产类型,名称=名称,添加日期=日期)parent_item = self.tree_view.model().parents[asset_type]已经存在 = 假对于 parent_item.child_items 中的孩子:如果 child.asset.name == name 和 child.asset.type == asset_type:已经存在 = 真如果已经存在:打印此资产已存在"返回new_item = TreeItem(资产,",parent_item)parent_item.appendChild(new_item)self.tree_view.model().layoutChanged.emit()类对话框(QtGui.QDialog):add_signal = QtCore.pyqtSignal(int)def __init__(self, parent=None):super(Dialog, self).__init__(parent)self.setMinimumSize(300, 150)self.model = TreeModel()布局 = QtGui.QVBoxLayout(self)self.tree_view = TreeView(self)self.tree_view.setModel(self.model)self.tree_view.setAlternatingRowColors(True)self.tree_view.right_button_clicked.connect(self.deleteButtonClicked)layout.addWidget(self.tree_view)label = QtGui.QLabel(搜索下面的人")layout.addWidget(标签)但是 = []frame = QtGui.QFrame(self)layout2 = QtGui.QHBoxLayout(frame)对于 self.model.assets 中的资产:但是 = QtGui.QPushButton(asset.name, frame)buts.append(but)layout2.addWidget(但是)QtCore.QObject.connect(but, QtCore.SIGNAL("clicked()"), self.but_clicked)layout.addWidget(frame)self.add_button = QtGui.QPushButton("添加\"角色-史密斯\"")layout.addWidget(self.add_button)QtCore.QObject.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.addButtonClicked)self.delete_button = QtGui.QPushButton(删除选中")layout.addWidget(self.delete_button)QtCore.QObject.connect(self.delete_button, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)self.but = QtGui.QPushButton(清除选择")layout.addWidget(self.but)QtCore.QObject.connect(self.but, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)QtCore.QObject.connect(self.tree_view, QtCore.SIGNAL("clicked (QModelIndex)"), self.row_clicked)def row_clicked(self, index):'''单击一行时...显示名称'''打印 'row_clicked 索引类型:%s' % 索引打印 self.tree_view.model().data(index, QtCore.Qt.UserRole)def but_clicked(self):'''单击名称按钮时,我遍历模型,找到这个名字的人,并设置treeviews当前项目'''名称 = self.sender().text()打印BUTTON CLICKED:",名称结果,索引 = self.model.findAssetName(name)如果结果:如果索引:self.tree_view.setCurrentIndex(index)返回self.tree_view.clearSelection()def addButtonClicked(self):打印点击添加按钮"new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')self.tree_view.model().addSubRow(new_asset)self.tree_view.model().layoutChanged.emit()@QtCore.pyqtSlot(list, int)def deleteButtonClicked(自我,索引,级别):打印 'deleteButton 点击​​'self.tree_view.model().removeRow(indexes)self.tree_view.model().layoutChanged.emit()如果 __name__ == __main__":app = QtGui.QApplication(sys.argv)对话 = 对话()对话框显示()sys.exit(app.exec_())

解决方案

beginRemoveRows() 需要将 QModelIndex(要删除的 QModelIndex 的父级)作为第一个参数.关于您在表类型模型的代码注释中指出的示例,索引没有父级,因此传递了无效的 QModelIndex.

def removeRow(self, rowIndexes):child_tree_item = rowIndexes[0].internalPointer()parent_item = child_tree_item.parent()self.beginRemoveRows(rowIndexes[0].parent(), rowIndexes[0].row(), rowIndexes[0].row() + 1)parent_item.removeChild(child_tree_item)self.endRemoveRows()

Im trying to build a little list of entries within a QTreeView, and based on the example posted here, I got it to delete any child items via the right click context menu i added. but when i delete it the parent tree i have collapses. And in some cases if i delete a certain item in a certain order this crashes

My understanding is that this is because upon deletion the indices of the items changes, and to prevent that QtCore.QPersistentModelIndex() can be utilized according to this thread: How to delete multiple rows in a QTableView widget?

Although that example uses a QStandardItemModel(), since mine uses a QAbstractItemModel() how can I implement a similar concept, and also prevent this from crashing...?

import sys
from functools import partial
from PyQt4 import QtGui, QtCore

HORIZONTAL_HEADERS = ("Asset Name", "Date Added")


class AssetClass(object):
    '''
    a trivial custom data object
    '''

    def __init__(self, **kwargs):
        if not kwargs.get('name') or not kwargs.get('type'):
            return
        self.name = kwargs.get('name')
        self.date_added = kwargs.get('date_added')
        self.type = kwargs.get('type')

    def __repr__(self):
        return "%s - %s %s" % (self.type, self.name, self.date_added)


class TreeItem(object):
    '''
    a python object used to return row/column data, and keep note of
    it's parents and/or children
    '''

    def __init__(self, asset, header, parent_item):
        self.asset = asset
        self.parent_item = parent_item
        self.header = header
        self.child_items = []

    def appendChild(self, item):
        self.child_items.append(item)

    def removeChild(self, item):
        print 'removeChild: item is %s' % item
        print 'removeChild: self.child_items is %s' % self.child_items
        self.child_items.remove(item)

    def child(self, row):
        return self.child_items[row]

    def childCount(self):
        return len(self.child_items)

    def columnCount(self):
        return 2

    def data(self, column):
        if self.asset == None:
            if column == 0:
                return QtCore.QVariant(self.header)
            if column == 1:
                return QtCore.QVariant("")
        else:
            if column == 0:
                return QtCore.QVariant(self.asset.name)
            if column == 1:
                return QtCore.QVariant(self.asset.date_added)
        return QtCore.QVariant()

    def parent(self):
        return self.parent_item

    def row(self):
        if self.parent_item:
            return self.parent_item.child_items.index(self)
        return 0


class TreeModel(QtCore.QAbstractItemModel):
    '''
    a model to display a few names, ordered by sex
    '''

    def __init__(self, parent=None):
        super(TreeModel, self).__init__(parent)
        self.assets = []
        model_data = (("VEHICLE", "Truck", 'May 27th, 2020'),
                      ("VEHICLE", "Car", 'May 25th, 2020'),
                      ("CHARACTER", "Peter", 'May 27th, 2020'),
                      ("CHARACTER", "Rachel", 'May 29th, 2020'),
                      ("PROP", "Chair", 'May 27th, 2020'),
                      ("PROP", "Axe", 'May 17th, 2020'))
        for asset_type, name, date in model_data:
            asset = AssetClass(type=asset_type, name=name, date_added=date)
            self.assets.append(asset)

        self.rootItem = TreeItem(None, "ALL", None)
        self.parents = {0: self.rootItem}
        self.setupModelData()

    def columnCount(self, parent=None):
        if parent and parent.isValid():
            return parent.internalPointer().columnCount()
        else:
            return len(HORIZONTAL_HEADERS)

    def data(self, index, role):
        if not index.isValid():
            return QtCore.QVariant()

        item = index.internalPointer()
        if role == QtCore.Qt.DisplayRole:
            return item.data(index.column())
        if role == QtCore.Qt.UserRole:
            if item:
                return item.asset

        return QtCore.QVariant()

    def headerData(self, column, orientation, role):
        if (orientation == QtCore.Qt.Horizontal and
                role == QtCore.Qt.DisplayRole):
            try:
                return QtCore.QVariant(HORIZONTAL_HEADERS[column])
            except IndexError:
                pass

        return QtCore.QVariant()

    def index(self, row, column, parent):
        if not self.hasIndex(row, column, parent):
            return QtCore.QModelIndex()

        if not parent.isValid():
            parent_item = self.rootItem
        else:
            parent_item = parent.internalPointer()

        childItem = parent_item.child(row)
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()

    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()

        childItem = index.internalPointer()
        if not childItem:
            return QtCore.QModelIndex()

        parent_item = childItem.parent()

        if parent_item == self.rootItem:
            return QtCore.QModelIndex()

        return self.createIndex(parent_item.row(), 0, parent_item)

    def rowCount(self, parent=QtCore.QModelIndex()):
        if parent.column() > 0:
            return 0
        if not parent.isValid():
            p_Item = self.rootItem
        else:
            p_Item = parent.internalPointer()
        return p_Item.childCount()

    def setupModelData(self):
        for asset in self.assets:
            asset_type = asset.type

            if not self.parents.has_key(asset_type):
                new_parent = TreeItem(None, asset_type, self.rootItem)
                self.rootItem.appendChild(new_parent)

                self.parents[asset_type] = new_parent

            print 'self.parents: ', self.parents
            parent_item = self.parents[asset_type]
            new_item = TreeItem(asset, "", parent_item)
            parent_item.appendChild(new_item)

    def addSubRow(self, new_asset):
        asset_type, name, date = new_asset
        asset = AssetClass(type=asset_type, name=name, date_added=date)
        parent_item = self.parents[asset_type]
        already_exists = False
        for child in parent_item.child_items:
            if child.asset.name == name and child.asset.type == asset_type:
                already_exists = True
        if already_exists:
            print 'this asset already exists'
            return
        new_item = TreeItem(asset, "", parent_item)
        parent_item.appendChild(new_item)

    def removeRow(self, rowIndexes):
        child_tree_item = rowIndexes[0].internalPointer()
        asset_type = rowIndexes[0].parent().data().toString()
        parent_item = self.parents[str(asset_type)]
        # hint to keep the tree open after deleting: https://stackoverflow.com/questions/48121393/how-to-delete-multiple-rows-in-a-qtableview-widget
        self.beginRemoveRows(QtCore.QModelIndex(), rowIndexes[0].row(), rowIndexes[0].row() + 1)
        parent_item.removeChild(child_tree_item)
        self.endRemoveRows()

    def searchModel(self, asset):
        '''
        get the modelIndex for a given appointment
        '''

        def searchNode(node):
            '''
            a function called recursively, looking at all nodes beneath node
            '''
            for child in node.child_items:
                print child.childCount()
                if asset == child.asset:
                    index = self.createIndex(child.row(), 0, child)
                    return index

                if child.childCount() > 0:
                    result = searchNode(child)
                    if result:
                        return result

        retarg = searchNode(self.parents[0])
        print retarg
        return retarg

    def findAssetName(self, name):
        app = None
        for asset in self.assets:
            print asset.name
            if asset.name == name:
                app = asset
                break
        if app != None:
            index = self.searchModel(app)
            return (True, index)
        return (False, None)


class TreeView(QtGui.QTreeView):
    right_button_clicked = QtCore.pyqtSignal(list, int)

    def __init__(self, parent=None):
        super(TreeView, self).__init__(parent)
        # self.clicked.connect(self.on_treeView_clicked)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.openMenu)

    def selectedRows(self):
        rows = []
        for index in self.selectedIndexes():
            if index.column() == 0:
                rows.append(index.row())
        print type(rows)
        return rows

    def openMenu(self, position):
        indexes = self.selectedIndexes()
        if len(indexes) > 0:

            level = 0
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level += 1

        menu = QtGui.QMenu()
        editMenu = None
        if level == 0:
            editMenu = QtGui.QAction("Edit person", self)
            menu.addAction(editMenu)
        elif level == 1:
            editMenu = QtGui.QAction("Delete", self)
            menu.addAction(editMenu)
        elif level == 2:
            editMenu = QtGui.QAction("Edit object", self)
            menu.addAction(editMenu)

        if editMenu:
            editMenu.triggered.connect(partial(self._on_right_click, indexes, level))

        menu.exec_(self.viewport().mapToGlobal(position))

    def _on_right_click(self, indexes, level):
        self.right_button_clicked.emit(indexes, level)

    def delete_test(self):
        print 'addButton clicked'
        new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')
        asset_type, name, date = new_asset
        asset = AssetClass(type=asset_type, name=name, date_added=date)
        parent_item = self.tree_view.model().parents[asset_type]
        already_exists = False
        for child in parent_item.child_items:
            if child.asset.name == name and child.asset.type == asset_type:
                already_exists = True
        if already_exists:
            print 'this asset already exists'
            return
        new_item = TreeItem(asset, "", parent_item)
        parent_item.appendChild(new_item)
        self.tree_view.model().layoutChanged.emit()

class Dialog(QtGui.QDialog):
    add_signal = QtCore.pyqtSignal(int)

    def __init__(self, parent=None):
        super(Dialog, self).__init__(parent)
        self.setMinimumSize(300, 150)

        self.model = TreeModel()
        layout = QtGui.QVBoxLayout(self)

        self.tree_view = TreeView(self)
        self.tree_view.setModel(self.model)
        self.tree_view.setAlternatingRowColors(True)
        self.tree_view.right_button_clicked.connect(self.deleteButtonClicked)
        layout.addWidget(self.tree_view)

        label = QtGui.QLabel("Search for the following person")
        layout.addWidget(label)

        buts = []
        frame = QtGui.QFrame(self)
        layout2 = QtGui.QHBoxLayout(frame)

        for asset in self.model.assets:
            but = QtGui.QPushButton(asset.name, frame)
            buts.append(but)
            layout2.addWidget(but)
            QtCore.QObject.connect(but, QtCore.SIGNAL("clicked()"), self.but_clicked)

        layout.addWidget(frame)

        self.add_button = QtGui.QPushButton("Add \"Character - Smith\"")
        layout.addWidget(self.add_button)
        QtCore.QObject.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.addButtonClicked)

        self.delete_button = QtGui.QPushButton("Delete Selected")
        layout.addWidget(self.delete_button)
        QtCore.QObject.connect(self.delete_button, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)

        self.but = QtGui.QPushButton("Clear Selection")
        layout.addWidget(self.but)
        QtCore.QObject.connect(self.but, QtCore.SIGNAL("clicked()"), self.tree_view.clearSelection)

        QtCore.QObject.connect(self.tree_view, QtCore.SIGNAL("clicked (QModelIndex)"), self.row_clicked)

    def row_clicked(self, index):
        '''
        when a row is clicked... show the name
        '''
        print 'row_clicked index type: %s' % index
        print self.tree_view.model().data(index, QtCore.Qt.UserRole)


    def but_clicked(self):
        '''
        when a name button is clicked, I iterate over the model,
        find the person with this name, and set the treeviews current item
        '''
        name = self.sender().text()
        print "BUTTON CLICKED:", name
        result, index = self.model.findAssetName(name)
        if result:
            if index:
                self.tree_view.setCurrentIndex(index)
                return
        self.tree_view.clearSelection()

    def addButtonClicked(self):
        print 'addButton clicked'
        new_asset = ("CHARACTER", "Smith", 'May 28th, 2020')
        self.tree_view.model().addSubRow(new_asset)
        self.tree_view.model().layoutChanged.emit()

    @QtCore.pyqtSlot(list, int)
    def deleteButtonClicked(self, indexes, level):
        print 'deleteButton clicked'
        self.tree_view.model().removeRow(indexes)
        self.tree_view.model().layoutChanged.emit()

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    dialog = Dialog()
    dialog.show()
    sys.exit(app.exec_())

解决方案

beginRemoveRows() expects the QModelIndex, which is the parent of the QModelIndex to be removed, as the first parameter. Regarding the example that you indicate in the comments of your code in the table type models, the indexes do not have a parent, so it is passed an invalid QModelIndex.

def removeRow(self, rowIndexes):
    child_tree_item = rowIndexes[0].internalPointer()
    parent_item = child_tree_item.parent()
    self.beginRemoveRows(
        rowIndexes[0].parent(), rowIndexes[0].row(), rowIndexes[0].row() + 1
    )
    parent_item.removeChild(child_tree_item)
    self.endRemoveRows()

这篇关于QTreeView QAbstractItemModel 父级在删除项后崩溃,有时会崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆