Editable Tree Model Example

This example shows how to implement a simple item-based tree model that can be used with other classes the model/view framework.
这个例子展示了如何实现一个简单的基于项目的树模型,它可以与模型/视图框架中的其他类一起使用。

在这里插入图片描述
The model supports editable items, custom headers, and the ability to insert and remove rows and columns. With these features, it is also possible to insert new child items, and this is shown in the supporting example code.
该模型支持可编辑项、自定义标题以及插入和删除行和列的功能。有了这些特性,还可以插入新的子项,支持的示例代码中显示了这一点。

Overview

As described in the Model Subclassing Reference, models must provide implementations for the standard set of model functions: flags(), data(), headerData(), columnCount(), and rowCount(). In addition, hierarchical models, such as this one, need to provide implementations of index() and parent().
如模型子类化参考中所述,模型必须提供标准模型函数集的实现:flags()、data()、headerData()、columnCount()和rowCount()。此外,分层模型,比如这个模型,需要提供index()和parent()的实现。

An editable model needs to provide implementations of setData() and setHeaderData(), and must return a suitable combination of flags from its flags() function.
一个可编辑的模型需要提供setData()和setHeaderData()的实现,并且必须从它的flags()函数返回合适的标志组合。

Since this example allows the dimensions of the model to be changed, we must also implement insertRows(), insertColumns(), removeRows(), and removeColumns().
由于这个示例允许更改模型的维度,我们还必须实现insertRows()、insertColumns()、removeRows()和removeColumns()。

Design

As with the Simple Tree Model example, the model simply acts as a wrapper around a collection of instances of a TreeItem class. Each TreeItem is designed to hold data for a row of items in a tree view, so it contains a list of values corresponding to the data shown in each column.
与Simple Tree Model示例一样,该模型只是作为TreeItem类实例集合的包装器。每个TreeItem被设计为在树视图中保存一行项目的数据,因此它包含与每列中显示的数据相对应的值列表。

Since QTreeView provides a row-oriented view onto a model, it is natural to choose a row-oriented design for data structures that will supply data via a model to this kind of view. Although this makes the tree model less flexible, and possibly less useful for use with more sophisticated views, it makes it less complex to design and easier to implement.
由于QTreeView为模型提供了面向行的视图,因此很自然地可以为数据结构选择面向行的设计,这些数据结构将通过模型向这种视图提供数据。尽管这使得树模型的灵活性降低,并且在使用更复杂的视图时可能不太有用,但它使其设计更简单,实现更容易。
在这里插入图片描述
Relations between internal items
When designing a data structure for use with a custom model, it is useful to expose each item’s parent via a function like TreeItem::parent() because it will make writing the model’s own parent() function easier. Similarly, a function like TreeItem::child() is helpful when implementing the model’s index() function. As a result, each TreeItem maintains information about its parent and children, making it possible for us to traverse the tree structure.
在设计用于自定义模型的数据结构时,通过TreeItem::parent()这样的函数公开每个项的父项是很有用的,因为这将使编写模型自己的parent()函数变得更容易。类似地,TreeItem::child()这样的函数在实现模型的index()函数时也很有用。因此,每个TreeItem都维护关于其父节点和子节点的信息,使我们能够遍历树结构。

The diagram shows how TreeItem instances are connected via their parent() and child() functions.
In the example shown, two top-level items, A and B, can be obtained from the root item by calling its child() function, and each of these items return the root node from their parent() functions, though this is only shown for item A.
图中显示了TreeItem实例是如何通过它们的parent()和child()函数连接的。
在所示的示例中,可以通过调用根项的child()函数从根项获得两个顶级项A和B,这些项中的每个项都从它们的parent()函数返回根节点,不过这只在项A中显示。

Each TreeItem stores data for each column in the row it represents in its itemData private member (a list of QVariant objects). Since there is a one-to-one mapping between each column in the view and each entry in the list, we provide a simple data() function to read entries in the itemData list and a setData() function to allow them to be modified. As with other functions in the item, this simplifies the implemention of the model’s data() and setData() functions.
每个TreeItem在其itemData私有成员(一个QVariant对象列表)中表示的行中存储每一列的数据。由于视图中的每个列和列表中的每个条目之间存在一对一的映射,因此我们提供了一个简单的data()函数来读取itemData列表中的条目,并提供了一个setData()函数来允许对它们进行修改。与项目中的其他函数一样,这简化了模型的data()和setData()函数的实现。

We place an item at the root of the tree of items. This root item corresponds to the null model index, QModelIndex(), that is used to represent the parent of a top-level item when handling model indexes. Although the root item does not have a visible representation in any of the standard views, we use its internal list of QVariant objects to store a list of strings that will be passed to views for use as horizontal header titles.
我们在项目树的根处放置一个项目。这个根条目对应于空模型索引QModelIndex(),在处理模型索引时,该索引用于表示顶级条目的父项。尽管根条目在任何标准视图中都没有可见的表示形式,但我们使用它的内部QVariant对象列表来存储字符串列表这些字符串将被传递给视图作为水平标题使用

在这里插入图片描述
Accessing data via the model
In the case shown in the diagram, the piece of information represented by a can be obtained using the standard model/view API:
在图中所示的情况下,a表示的信息可以使用标准的模型/视图API获得:

QVariant a = model->index(0, 0, QModelIndex()).data();

Since each items holds pieces of data for each column in a given row, there can be many model indexes that map to the same TreeItem object. For example, the information represented by b can be obtained using the following code:
由于每个项保存给定行中每个列的数据片段,因此可以有许多映射到相同TreeItem对象的模型索引。例如,b所表示的信息可以通过以下代码获得:

QVariant b = model->index(1, 0, QModelIndex()).data();

The same underlying TreeItem would be accessed to obtain information for the other model indexes in the same row as b.
将访问相同的底层TreeItem,以获取与b同一行的其他模型索引的信息。

In the model class, TreeModel, we relate TreeItem objects to model indexes by passing a pointer for each item when we create its corresponding model index with QAbstractItemModel::createIndex() in our index() and parent() implementations. We can retrieve pointers stored in this way by calling the internalPointer() function on the relevant model index - we create our own getItem() function to do the work for us, and call it from our data() and parent() implementations.
在模型类TreeModel中,当我们在index()和parent()实现中使用QAbstractItemModel::createIndex()创建相应的模型索引时,通过传递每个项的指针,我们将TreeItem对象与模型索引关联起来。我们可以通过调用相关模型索引上的internalPointer()函数来检索以这种方式存储的指针——我们创建自己的getItem()函数来为我们完成这项工作,并从我们的data()和parent()实现中调用它。

Storing pointers to items is convenient when we control how they are created and destroyed since we can assume that an address obtained from internalPointer() is a valid pointer. However, some models need to handle items that are obtained from other components in a system, and in many cases it is not possible to fully control how items are created or destroyed. In such situations, a pure pointer-based approach needs to be supplemented by safeguards to ensure that the model does not attempt to access items that have been deleted.
当我们控制如何创建和销毁指针时,存储指向项的指针是很方便的,因为我们可以假设从internalPointer()获得的地址是一个有效指针。然而,有些模型需要处理从系统中其他组件获得的项,而且在许多情况下不可能完全控制项的创建或销毁方式。在这种情况下,纯基于指针的方法需要通过安全措施加以补充,以确保模型不会试图访问已被删除的项。

在这里插入图片描述
Storing information in the underlying data structure
Several pieces of data are stored as QVariant objects in the itemData member of each TreeItem instance.
一些数据片段作为QVariant对象存储在每个TreeItem实例的itemData成员中。

The diagram shows how pieces of information, represented by the labels a, b and c in the previous two diagrams, are stored in items A, B and C in the underlying data structure. Note that pieces of information from the same row in the model are all obtained from the same item. Each element in a list corresponds to a piece of information exposed by each column in a given row in the model.
该图显示了在前两个图中由标签a、b和c表示的信息片段如何存储在底层数据结构中的项a、b和c中。注意,来自模型中同一行的信息片段都是从同一项中获得的。列表中的每个元素都对应于模型中给定行的每个列所公开的一条信息。

Since the TreeModel implementation has been designed for use with QTreeView, we have added a restriction on the way it uses TreeItem instances: each item must expose the same number of columns of data. This makes viewing the model consistent, allowing us to use the root item to determine the number of columns for any given row, and only adds the requirement that we create items containing enough data for the total number of columns. As a result, inserting and removing columns are time-consuming operations because we need to traverse the entire tree to modify every item.
由于TreeModel实现是为与QTreeView一起使用而设计的,所以我们在使用TreeItem实例的方式上增加了一个限制:每个项必须暴露相同数量的数据列。这使得查看模型保持一致,允许我们使用根项来确定任意给定行的列数,并且只添加了创建包含足够列数的数据的项的需求。因此,插入和删除列是耗时的操作,因为我们需要遍历整个树来修改每个条目

An alternative approach would be to design the TreeModel class so that it truncates or expands the list of data in individual TreeItem instances as items of data are modified. However, this “lazy” resizing approach would only allow us to insert and remove columns at the end of each row and would not allow columns to be inserted or removed at arbitrary positions in each row.
另一种方法是设计TreeModel类,以便在修改数据项时截断或展开单个TreeItem实例中的数据列表。然而,这种“惰性”调整大小方法只允许我们在每行的末尾插入和删除列,而不允许在每行的任意位置插入或删除列

TreeItem Class Definition

The TreeItem class provides simple items that contain several pieces of data, including information about their parent and child items:
TreeItem类提供了包含几段数据的简单项,包括它们的父项和子项的信息:

class TreeItem
  {
  public:
      explicit TreeItem(const QVector<QVariant> &data, TreeItem *parent = nullptr);
      ~TreeItem();

      TreeItem *child(int number);
      int childCount() const;
      int columnCount() const;
      QVariant data(int column) const;
      bool insertChildren(int position, int count, int columns);
      bool insertColumns(int position, int columns);
      TreeItem *parent();
      bool removeChildren(int position, int count);
      bool removeColumns(int position, int columns);
      int childNumber() const;
      bool setData(int column, const QVariant &value);

  private:
      QVector<TreeItem*> childItems;
      QVector<QVariant> itemData;
      TreeItem *parentItem;
  };

We have designed the API to be similar to that provided by QAbstractItemModel by giving each item functions to return the number of columns of information, read and write data, and insert and remove columns. However, we make the relationship between items explicit by providing functions to deal with “children” rather than “rows”.
我们将API设计为类似于QAbstractItemModel提供的API,通过为每个项提供函数来返回信息的列数、读取和写入数据以及插入和删除列。但是,我们通过提供处理“子”而不是“行”的函数来显式地处理项之间的关系。

Each item contains a list of pointers to child items, a pointer to its parent item, and a list of QVariant objects that correspond to information held in columns in a given row in the model.
每个项目包含一个指向子项目的指针列表,一个指向父项目的指针,以及一个QVariant对象列表,这些对象对应于模型中给定行中的列中保存的信息。

TreeItem Class Implementation

Each TreeItem is constructed with a list of data and an optional parent item:
每个TreeItem都由一个数据列表和一个可选的父项组成:

TreeItem::TreeItem(const QVector<QVariant> &data, TreeItem *parent)
      : itemData(data),
        parentItem(parent)
  {}

Initially, each item has no children. These are added to the item’s internal childItems member using the insertChildren() function described later.
最初,每件物品都没有孩子。使用后面介绍的insertChildren()函数将这些元素添加到项目的内部childItems成员。

The destructor ensures that each child added to the item is deleted when the item itself is deleted:
析构函数确保当项本身被删除时,添加到项中的每一个子项也被删除:

TreeItem::~TreeItem()
  {
      qDeleteAll(childItems);
  }

Since each item stores a pointer to its parent, the parent() function is trivial:
由于每个元素都存储了一个指向其父元素的指针,因此parent()函数就不重要了:

TreeItem *TreeItem::parent()
  {
      return parentItem;
  }

Three functions provide information about the children of an item. child() returns a specific child from the internal list of children:
有三个函数提供关于项的子项的信息。Child()从内部子类列表中返回一个特定的子类:

TreeItem *TreeItem::child(int number)
  {
      if (number < 0 || number >= childItems.size())
          return nullptr;
      return childItems.at(number);
  }

The childCount() function returns the total number of children:
函数的作用是:返回子结点的总数:

int TreeItem::childCount() const
  {
      return childItems.count();
  }

The childNumber() function is used to determine the index of the child in its parent’s list of children. It accesses the parent’s childItems member directly to obtain this information:
函数的作用是:确定子节点在父节点的子节点列表中的索引。它直接访问父节点的childItems成员来获取这个信息:

int TreeItem::childNumber() const
  {
      if (parentItem)
          return parentItem->childItems.indexOf(const_cast<TreeItem*>(this));
      return 0;
  }

The root item has no parent item; for this item, we return zero to be consistent with the other items.
The columnCount() function simply returns the number of elements in the internal itemData list of QVariant objects:
根项目没有父项目;对于这个项,我们返回0以与其他项保持一致。
columnCount()函数只是返回QVariant对象内部itemData列表中的元素数量:

int TreeItem::columnCount() const
  {
      return itemData.count();
  }

Data is retrieved using the data() function, which accesses the appropriate element in the itemData list:
使用Data()函数检索数据,该函数访问itemData列表中的适当元素:

QVariant TreeItem::data(int column) const
  {
      if (column < 0 || column >= itemData.size())
          return QVariant();
      return itemData.at(column);
  }

Data is set using the setData() function, which only stores values in the itemData list for valid list indexes, corresponding to column values in the model:
Data是使用setData()函数设置的,该函数只在itemData列表中存储有效列表索引的值,对应于模型中的列值:

bool TreeItem::setData(int column, const QVariant &value)
  {
      if (column < 0 || column >= itemData.size())
          return false;

      itemData[column] = value;
      return true;
  }

To make implementation of the model easier, we return true to indicate that the data was set successfully.
为了使模型的实现更容易,我们返回true以表示数据已成功设置。

Editable models often need to be resizable, enabling rows and columns to be inserted and removed. The insertion of rows beneath a given model index in the model leads to the insertion of new child items in the corresponding item, handled by the insertChildren() function:
可编辑模型通常需要调整大小,以便插入和删除行和列。在模型中给定的模型索引下插入行会导致在相应的项目中插入新的子项目,由insertChildren()函数处理:

bool TreeItem::insertChildren(int position, int count, int columns)
  {
      if (position < 0 || position > childItems.size())
          return false;

      for (int row = 0; row < count; ++row) {
          QVector<QVariant> data(columns);
          TreeItem *item = new TreeItem(data, this);
          childItems.insert(position, item);
      }

      return true;
  }

This ensures that new items are created with the required number of columns and inserted at a valid position in the internal childItems list. Items are removed with the removeChildren() function:
这确保使用所需的列数创建新项,并将其插入到内部childItems列表的有效位置。通过removeChildren()函数可以删除项目:

bool TreeItem::removeChildren(int position, int count)
  {
      if (position < 0 || position + count > childItems.size())
          return false;

      for (int row = 0; row < count; ++row)
          delete childItems.takeAt(position);

      return true;
  }

As discussed above, the functions for inserting and removing columns are used differently to those for inserting and removing child items because they are expected to be called on every item in the tree. We do this by recursively calling this function on each child of the item:
如上所述,用于插入和删除列的函数与用于插入和删除子项的函数使用不同,因为它们预计将对树中的每个项调用。我们通过递归地在item的每个子元素上调用这个函数来实现:

bool TreeItem::insertColumns(int position, int columns)
  {
      if (position < 0 || position > itemData.size())
          return false;

      for (int column = 0; column < columns; ++column)
          itemData.insert(position, QVariant());

      for (TreeItem *child : qAsConst(childItems))
          child->insertColumns(position, columns);

      return true;
  }

TreeModel Class Definition

The TreeModel class provides an implementation of the QAbstractItemModel class, exposing the necessary interface for a model that can be edited and resized.
TreeModel类提供了QAbstractItemModel类的实现,为模型公开了可以编辑和调整大小的必要接口。

class TreeModel : public QAbstractItemModel
  {
      Q_OBJECT

  public:
      TreeModel(const QStringList &headers, const QString &data,
                QObject *parent = nullptr);
      ~TreeModel();

The constructor and destructor are specific to this model.
构造函数和析构函数是特定于此模型的。

      QVariant data(const QModelIndex &index, int role) const override;
      QVariant headerData(int section, Qt::Orientation orientation,
                          int role = Qt::DisplayRole) const override;

      QModelIndex index(int row, int column,
                        const QModelIndex &parent = QModelIndex()) const override;
      QModelIndex parent(const QModelIndex &index) const override;

      int rowCount(const QModelIndex &parent = QModelIndex()) const override;
      int columnCount(const QModelIndex &parent = QModelIndex()) const override;

Read-only tree models only need to provide the above functions. The following public functions provide support for editing and resizing:
只读树模型只需要提供上述功能。以下公共函数提供了编辑和调整大小的支持:

	  Qt::ItemFlags flags(const QModelIndex &index) const override;
      bool setData(const QModelIndex &index, const QVariant &value,
                   int role = Qt::EditRole) override;
      bool setHeaderData(int section, Qt::Orientation orientation,
                         const QVariant &value, int role = Qt::EditRole) override;

      bool insertColumns(int position, int columns,
                         const QModelIndex &parent = QModelIndex()) override;
      bool removeColumns(int position, int columns,
                         const QModelIndex &parent = QModelIndex()) override;
      bool insertRows(int position, int rows,
                      const QModelIndex &parent = QModelIndex()) override;
      bool removeRows(int position, int rows,
                      const QModelIndex &parent = QModelIndex()) override;

  private:
      void setupModelData(const QStringList &lines, TreeItem *parent);
      TreeItem *getItem(const QModelIndex &index) const;

      TreeItem *rootItem;
  };

To simplify this example, the data exposed by the model is organized into a data structure by the model’s setupModelData() function. Many real world models will not process the raw data at all, but simply work with an existing data structure or library API.
为了简化这个示例,模型公开的数据通过模型的setupModelData()函数组织成一个数据结构。许多真实世界的模型根本不会处理原始数据,而只是使用现有的数据结构或库API。

TreeModel Class Implementation

The constructor creates a root item and initializes it with the header data supplied:
构造函数创建一个根项目,并使用提供的头数据初始化它:

TreeModel::TreeModel(const QStringList &headers, const QString &data, QObject *parent)
      : QAbstractItemModel(parent)
  {
      QVector<QVariant> rootData;
      for (const QString &header : headers)
          rootData << header;

      rootItem = new TreeItem(rootData);
      setupModelData(data.split('\n'), rootItem);
  }

We call the internal setupModelData() function to convert the textual data supplied to a data structure we can use with the model. Other models may be initialized with a ready-made data structure, or use an API from a library that maintains its own data.
我们调用内部setupModelData()函数,将提供的文本数据转换为可以与模型一起使用的数据结构。其他模型可以使用现成的数据结构进行初始化,或者使用来自维护自身数据的库的API。

The destructor only has to delete the root item, which will cause all child items to be recursively deleted.
析构函数只需要删除根项,这将导致所有子项被递归删除。

 TreeModel::~TreeModel()
  {
      delete rootItem;
  }

Since the model’s interface to the other model/view components is based on model indexes, and since the internal data structure is item-based, many of the functions implemented by the model need to be able to convert any given model index to its corresponding item. For convenience and consistency, we have defined a getItem() function to perform this repetitive task:
由于模型到其他模型/视图组件的接口是基于模型索引的,并且由于内部数据结构是基于项的,因此模型实现的许多函数需要能够将任何给定的模型索引转换为相应的项。为了方便和一致性,我们定义了一个getItem()函数来执行这个重复的任务:

TreeItem *TreeModel::getItem(const QModelIndex &index) const
  {
      if (index.isValid()) {
          TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
          if (item)
              return item;
      }
      return rootItem;
  }

Each model index passed to this function should correspond to a valid item in memory. If the index is invalid, or its internal pointer does not refer to a valid item, the root item is returned instead.
传递给这个函数的每个模型索引应该对应于内存中的一个有效项。如果索引无效,或其内部指针没有指向有效项,则返回根项。

The model’s rowCount() implementation is simple: it first uses the getItem() function to obtain the relevant item; then it returns the number of children it contains:
模型的rowCount()实现很简单:它首先使用getItem()函数来获取相关的项;然后它返回它所包含的子元素的数量:

int TreeModel::rowCount(const QModelIndex &parent) const
  {
      const TreeItem *parentItem = getItem(parent);

      return parentItem ? parentItem->childCount() : 0;
  }

By contrast, the columnCount() implementation does not need to look for a particular item because all items are defined to have the same number of columns associated with them.
相比之下,columnCount()实现不需要查找特定的项,因为所有项都被定义为具有相同数量的关联列。

int TreeModel::columnCount(const QModelIndex &parent) const
  {
      Q_UNUSED(parent);
      return rootItem->columnCount();
  }

As a result, the number of columns can be obtained directly from the root item.
To enable items to be edited and selected, the flags() function needs to be implemented so that it returns a combination of flags that includes the Qt::ItemIsEditable and Qt::ItemIsSelectable flags as well as Qt::ItemIsEnabled:
因此,可以直接从根项目获得列的数量。
为了使项目能够被编辑和选择,需要实现flags()函数,以便它返回一个标志的组合,包括Qt::ItemIsEditable和Qt::ItemIsSelectable以及Qt::ItemIsEnabled:

Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
  {
      if (!index.isValid())
          return Qt::NoItemFlags;

      return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
  }

The model needs to be able to generate model indexes to allow other components to request data and information about its structure. This task is performed by the index() function, which is used to obtain model indexes corresponding to children of a given parent item:
模型需要能够生成模型索引,以允许其他组件请求关于其结构的数据和信息。此任务由index()函数执行,该函数用于获取与给定父项目的子项目对应的模型索引:

QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
  {
      if (parent.isValid() && parent.column() != 0)
          return QModelIndex();

In this model, we only return model indexes for child items if the parent index is invalid (corresponding to the root item) or if it has a zero column number.
在这个模型中,只有当父项索引无效(对应于根项)或列号为零时,我们才返回子项的模型索引。

We use the custom getItem() function to obtain a TreeItem instance that corresponds to the model index supplied, and request its child item that corresponds to the specified row.
我们使用自定义的getItem()函数来获取与提供的模型索引相对应的TreeItem实例,并请求与指定行相对应的子项。

      TreeItem *parentItem = getItem(parent);
      if (!parentItem)
          return QModelIndex();

      TreeItem *childItem = parentItem->child(row);
      if (childItem)
          return createIndex(row, column, childItem);
      return QModelIndex();
  }

Since each item contains information for an entire row of data, we create a model index to uniquely identify it by calling createIndex() it with the row and column numbers and a pointer to the item. In the data() function, we will use the item pointer and column number to access the data associated with the model index; in this model, the row number is not needed to identify data.
由于每个项都包含一整行数据的信息,我们通过调用createIndex()来创建一个模型索引,用行号和列号以及指向该项的指针来唯一地标识它。在data()函数中,我们将使用项目指针和列号来访问与模型索引关联的数据;在这个模型中,不需要行号来标识数据。

The parent() function supplies model indexes for parents of items by finding the corresponding item for a given model index, using its parent() function to obtain its parent item, then creating a model index to represent the parent. (See the above diagram).
parent()函数为项的父项提供模型索引,方法是为给定的模型索引找到相应的项,使用其parent()函数来获取其父项,然后创建一个模型索引来表示父项。(见上图)。

QModelIndex TreeModel::parent(const QModelIndex &index) const
  {
      if (!index.isValid())
          return QModelIndex();

      TreeItem *childItem = getItem(index);
      TreeItem *parentItem = childItem ? childItem->parent() : nullptr;

      if (parentItem == rootItem || !parentItem)
          return QModelIndex();

      return createIndex(parentItem->childNumber(), 0, parentItem);
  }

Items without parents, including the root item, are handled by returning a null model index. Otherwise, a model index is created and returned as in the index() function, with a suitable row number, but with a zero column number to be consistent with the scheme used in the index() implementation.
没有父项的项(包括根项)将通过返回空模型索引来处理。否则,将像index()函数中那样创建并返回一个模型索引,该索引具有合适的行号,但列号为零,以与index()实现中使用的方案一致。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值