51、探索Qt与KDE开发:从基础组件到CD数据库应用

Qt与KDE开发:从组件到CD数据库应用

探索Qt与KDE开发:从基础组件到CD数据库应用

1. QListView组件详解

QListView 组件功能强大且复杂,它既可以作为项目列表,也能呈现项目树结构。使用时,需要为列表中的每个项目创建 QListViewItem 实例,每个实例都有一个父项。若以组件本身为父项,该项目将显示为顶级项目;若以另一个 QListViewItem 为父项,则显示为子项目。以下是其工作流程:

graph TD
    A[创建QListViewItem实例] --> B{确定父项}
    B -- 组件本身 --> C[显示为顶级项目]
    B -- 其他QListViewItem --> D[显示为子项目]

编译并运行 ListView 示例后,可看到 QListView 组件的实际效果。子行相对于其父行会有缩进,默认情况下,指示隐藏或可折叠行的加减框并不显示,可通过 setRootIsDecorated 方法进行设置。

2. 对话框类型及使用

在开发中,除了通过子类化 QMainWindow 创建界面,对于短期使用的对话框,可使用 QDialog 组件。Qt 提供了三种类型的对话框:
| 对话框类型 | 特点 | 适用场景 |
| ---- | ---- | ---- |
| 模态对话框 | 阻止对其他窗口的输入,强制用户响应 | 获取用户即时响应、显示关键错误信息 |
| 非模态对话框 | 非阻塞窗口,可与应用中的其他窗口正常交互 | 搜索或输入窗口,方便与主窗口进行复制粘贴操作 |
| 半模态对话框 | 无自己的事件循环,可将控制权返回给应用,但仍阻止除对话框外的输入 | 显示耗时关键操作的进度条,允许用户取消操作 |

2.1 QDialog 基础使用

QDialog 是 Qt 中的基础对话框类,提供了 exec show 方法来处理模态和非模态对话框,还包含一个可使用的 QLayout 以及多个用于响应按钮点击的信号和槽。以下是创建自定义对话框的示例代码:

#include <qdialog.h>
MyDialog::MyDialog(QWidget *parent, const char *name) : QDialog(parent, name)
{
    QHBoxLayout *hbox = new QHBoxLayout(this);
    hbox->addWidget(new Qlabel(“Enter your name”)); 
    hbox->addWidget(new QLineEdit());
    hbox->addWidget(ok_pushbutton);
    hbox->addWidget(cancel_pushbutton);
    connect (ok_pushbutton, SIGNAL(clicked()), this, SLOT(accept()));
    connect (cancel_pushbutton, SIGNAL(clicked()), this, SLOT(reject()));
}

与 QMainWindow 不同,可直接将 MyDialog 指定为 QLayout 对象的父项,无需创建虚拟 QWidget。QDialog 有 accept reject 两个槽,用于指示对话框的结果,该结果由 exec 方法返回。

2.2 模态对话框使用

使用模态对话框时,调用 exec 方法,根据激活的槽返回 QDialog::Accepted QDialog::Rejected

MyDialog *dialog = new MyDialog(this, “mydialog”);
if (dialog->exec() == QDialog::Accepted)
{
    // User clicked ‘Ok’
    doSomething();
}
else 
{
    // user clicked ‘Cancel’ or dialog killed
    doSomethingElse();
}
delete dialog;

exec 方法返回时,对话框会自动隐藏,但仍需从内存中删除该对象。需要注意的是,调用 exec 时会阻塞所有处理,因此若应用中有时间敏感的代码,使用非模态或半模态对话框更为合适。

2.3 非模态对话框使用

非模态对话框与普通主窗口类似,不同之处在于它会定位在其父窗口之上,共享任务栏条目,并在调用 accept reject 槽时自动隐藏。显示非模态对话框可使用 show 方法:

MyDialog *dialog = new MyDialog(this, “mydialog”);
dialog->show();

为处理按钮点击事件,需要编写并连接相应的槽:

MyDialog::MyDialog(QWidget *parent, const char *name) : QDialog(parent, name)
{
    ...
    connect (ok_pushbutton, SIGNAL(clicked()), this, SLOT(OkClicked()));
    connect (cancel_pushbutton, SIGNAL(clicked()), this, SLOT(CancelClicked()));
}
MyDialog::OkClicked()
{
    //Do some processing
}
MyDialog::CancelClicked()
{
    //Do some other processing
}

按钮被按下时,对话框会像模态对话框一样自动隐藏。

2.4 半模态对话框使用

创建半模态对话框时,需在 QDialog 构造函数中设置模态标志,并使用 show 方法:

MySMDialog::MySMDialog(QWidget *parent, const char *name):QDialog(parent, name, TRUE)
{
    ...
}
MySMDialog *dialog = MySMDialog(this, “semimodal”);
dialog->show();
while (processing)
{
    doSomeProcessing();
    app->processEvents();
    if (dialog->wasCancelled())
        break;
}

在继续处理之前,需检查对话框是否已被取消。注意, wasCancelled 方法并非 QDialog 的一部分,需要自行实现。

3. 预定义对话框使用

Qt 提供了一些预定义的 QDialog 子类,用于特定任务,如文件选择、文本输入、进度条和消息框等。使用这些组件可节省大量开发时间。

3.1 QMessageBox

QMessageBox 是一种模态对话框,用于显示带有图标和按钮的简单消息。图标取决于消息的严重程度,该类提供了静态方法来创建和显示三种类型的消息框:

#include <qmessagebox.h>
int information (QWidget *parent, const QString &caption, const QString &text, 
                 int button0, int button1=0, int button2=0)
int warning     (QWidget *parent, const QString &caption, const QString &text,
                 int button0, int button1, int button2=0)
int critical    (QWidget *parent, const QString &caption, const QString &text,
                 int button0, int button1, int button2=0) 

可从一系列标准按钮中选择按钮,并根据静态方法的返回值进行相应处理:

int result = QMessageBox::information(this, 
                                      “Engine Room Query”, “Do you wish to engage the HyperDrive?”,
                                      QMessageBox::Yes | QMessageBox::Default, 
                                      QMessageBox::No  | QMessageBox::Escape);
switch (result) {
    case QMessageBox::Yes:
        hyperdrive->engage();
        break;
    case QMessageBox::No:
        // do something else
        break;
}
3.2 QInputDialog

QInputDialog 用于从用户处输入单个值,可输入文本、下拉列表中的选项、整数或浮点数。该类提供了静态方法,虽然参数较多,但大多数参数有默认值:

#include <qinputdialog.h>
QString getText  (const QString &caption, const QString &label, 
                  QLineEdit::EchoMode mode=QLineEdit::Normal, 
                  const QString &text=QString::null, bool * ok = 0, 
                  QWidget * parent = 0, const char * name = 0)
QString getItem  (const QString &caption, const QString &label,
                  const QStringList &list, int current=0, bool editable=TRUE,
                  bool * ok=0, QWidget *parent = 0, const char *name=0)
int getInteger   (const QString &caption, const QString &label, int num=0, 
                  int from = -2147483647, int to = 2147483647, int step = 1,
                  bool * ok = 0, QWidget * parent = 0, const char * name = 0)
double getDouble (const QString &caption, const QString &label, double num = 0,
                  double from = -2147483647, double to = 2147483647, 
                  int decimals = 1, bool * ok = 0, QWidget * parent = 0, 
                  const char * name = 0 )

以下是输入单行文本的示例:

bool result;
QString text = QInputDialog::getText(“Question”, “What is your Quest?:”,
                                     QLineEdit::Normal,
                                     QString::null, &result, this, “input” );
if (result) {
    doSomething(text);
} else {
    // user pressed cancel
}
4. 使用 qmake 简化 Makefile 编写

编译同时使用 KDE 和 Qt 库的应用程序时,Makefile 会变得非常复杂,因为需要使用 moc 且库文件分散在各处。幸运的是,Qt 提供了 qmake 工具来自动创建 Makefile。qmake 以 .pro 文件为输入,该文件包含编译所需的基本信息,如源文件、头文件、目标二进制文件以及 KDE/Qt 库的位置。以下是一个典型的 KDE .pro 文件示例:

TARGET = app
MOC_DIR = moc
OBJECTS_DIR = obj
INCLUDEPATH = /usr/include/kde
QMAKE_LIBDIR_X11 += /usr/lib
QMAKE_LIBS_X11 += -lkdeui -lkdecore 
SOURCES = main.cpp window.cpp
HEADERS = window.h

使用以下命令生成 Makefile:

$ qmake file.pro –o Makefile

然后即可像往常一样运行 make 命令进行编译。对于任何复杂度的 KDE/Qt 程序,使用 qmake 可简化构建过程。

5. KDE 中的菜单和工具栏

KDE 提供了强大的组件来创建菜单和工具栏,与普通 Qt 或其他 GUI 工具包相比,可节省大量时间和精力。通常在 GUI 库中,菜单项和工具栏项是独立的元素,需要分别创建对象并跟踪更改。而 KDE 定义了 KAction 组件来表示应用程序可以执行的操作,如打开新文档、保存文件或显示帮助框等。

5.1 KAction 使用

KAction 可设置文本、键盘快捷键、图标和激活时调用的槽:

KAction *new_file = new KAction(“New”, “filenew”,
                                KstdAccel::shortcut(KstdAccel::New),
                                this, SLOT(newFile()), this, “newaction”);

然后可将 KAction 插入到菜单和工具栏中,无需进一步描述:

new_file->plug(a_menu);
new_file->plug(a_toolbar);

若需要禁用 KAction,只需调用 setEnabled(FALSE) 方法。

5.2 示例代码

以下是一个使用 KAction 创建菜单和工具栏的示例:

// KDEMenu.h
#include <kde/kmainwindow.h>
class KDEMenu : public KMainWindow
{
    Q_OBJECT
public:
    KDEMenu(const char * name = 0);
private slots:
    void newFile();
    void aboutApp();
};

// KDEMenu.cpp
#include “KDEMenu.h”
#include <kde/kapp.h>
#include <kde/kaction.h>
#include <kde/kstdaccel.h>
#include <kde/kmenubar.h>
#include <kde/kaboutdialog.h>
KDEMenu::KDEMenu(const char *name = 0) : KMainWindow (0L, name )
{
    KAction *new_file = new KAction(“New”, “filenew”,
                                    KstdAccel::shortcut(KstdAccel::New),
                                    this, SLOT(newFile()), this, “newaction”);
    KAction *quit_action = KStdAction::quit(KApplication::kApplication(), 
                                            SLOT(quit()), actionCollection());
    KAction *help_action = KStdAction::aboutApp(this, SLOT(aboutApp()),
                                                actionCollection());
    QPopupMenu *file_menu = new QPopupMenu;
    QPopupMenu *help_menu = new QPopupMenu;
    menuBar()->insertItem(“&File”, file_menu);
    menuBar()->insertItem(“&Help”, help_menu);
    new_file->plug(file_menu);
    file_menu->insertSeparator();
    quit_action->plug(file_menu);
    help_action->plug(help_menu);
    new_file->plug(toolBar());
    quit_action->plug(toolBar());
}
void KDEMenu::newFile()
{
    // Create new File
}
void KDEMenu::aboutApp()
{
    KAboutDialog *about = new KAboutDialog(this, “dialog”);
    about->setAuthor(QString(“A. N. Author”), QString(“an@email.net”),
                     QString(“http://url.com”), QString(“work”));
    about->setVersion(“1.0”);
    about->show();
}
int main(int argc, char **argv)
{
    KApplication app( argc, argv, “cdapp” );
    KDEMenu *window = new KDEMenu(“kdemenu”);
    app.setMainWidget(window);
    window->show();
    return app.exec();
}

还需要一个 menu.pro 文件用于 qmake:

TARGET  = kdemenu
MOC_DIR = moc
OBJECTS_DIR = obj
INCLUDEPATH = /usr/include/kde
QMAKE_LIBDIR_X11 += -L$KDEDIR/lib
QMAKE_LIBS_X11 += -lkdeui -lkdecore 
SOURCES = KDEMenu.cpp
HEADERS = KDEMenu.h

运行以下命令生成 Makefile 并编译运行:

$ qmake menu.pro –o Makefile
$ make
$ ./kdemenu

通过以上步骤,可利用 KDE 的强大功能创建出功能丰富、易于维护的菜单和工具栏。

探索Qt与KDE开发:从基础组件到CD数据库应用

6. CD数据库应用开发

接下来将使用 KDE/Qt 开发一个 CD 数据库应用程序,该应用程序需要实现以下功能:
- 从 GUI 登录数据库
- 搜索 CD
- 显示 CD 和曲目信息
- 向数据库添加 CD
- 显示关于窗口

6.1 主窗口开发

主窗口包含搜索输入框和搜索结果列表,以下是详细的开发步骤:

步骤 1:定义主窗口类
MainWindow.h 中定义主窗口类,需要包含 qlistview.h qlineedit.h 头文件:

#include <kde/kmainwindow.h>
#include <qlistview.h>
#include <qlineedit.h>
class MainWindow : public KMainWindow
{
    Q_OBJECT
public:
    MainWindow (const char *name);
public slots:
    void doSearch();
    void AddCd();
private:
    QListView *list;
    QLineEdit *search_entry;
};

步骤 2:实现主窗口构造函数
MainWindow.cpp 中实现主窗口的构造函数,创建主窗口界面并连接信号和槽:

#include “MainWindow.h”
#include “AddCdDialog.h”
#include “app_mysql.h”
#include <qvbox.h>
#include <qlineedit.h>
#include <qpushbutton.h>
#include <qlabel.h>
#include <qlistview.h>
#include <kde/kapp.h>
#include <kde/kmenubar.h>
#include <kde/klocale.h>
#include <kde/kpopupmenu.h>
#include <kde/kstatusbar.h>
#include <kde/kaction.h>
#include <kde/kstdaccel.h>
#include <string.h>
MainWindow::MainWindow ( const char * name ) : KMainWindow ( 0L, name )
{
    setCaption(“CD Database”);
    // 创建菜单和工具栏条目
    KAction *addcd_action = new KAction(“&Add CD”, “filenew”,
                                        KStdAccel::shortcut(KStdAccel::New),
                                        this,
                                        SLOT(AddCd()),
                                        this);
    KAction *quit_action = KStdAction::quit(KApplication::kApplication(),
                                            SLOT(quit()), actionCollection());
    QPopupMenu * file_menu = new QPopupMenu;
    QPopupMenu * help_menu = new QPopupMenu;
    menuBar()->insertItem(“&File”, file_menu);
    menuBar()->insertItem(“&Help”, help_menu);
    addcd_action->plug(file_menu);
    file_menu->insertSeparator();
    quit_action->plug(file_menu);
    addcd_action->plug(toolBar());
    quit_action->plug(toolBar());
    // 使用 QBox 布局
    QVBox *vbox = new QVBox (this);
    QHBox *hbox = new QHBox (vbox);
    QLabel *label = new QLabel(hbox);
    label->setText( “Search Text:” );
    search_entry = new QLineEdit ( hbox );
    QPushButton *button = new QPushButton( “Search”, hbox);
    // 创建 QListView 并设置列
    list = new QListView( vbox, “name”, 0L);
    list->setRootIsDecorated(TRUE);
    list->addColumn(“Title”);
    list->addColumn(“Artist”);
    list->addColumn(“Catalogue”);
    // 连接信号和槽
    connect(button, SIGNAL (clicked()), this, SLOT (doSearch()));
    connect(search_entry , SIGNAL(returnPressed()), this, SLOT(doSearch()));
    statusBar()->message(“”);    
    setCentralWidget(vbox);
    resize (300,400);
}

步骤 3:实现搜索功能
doSearch 槽函数实现了搜索功能,读取搜索字符串并获取匹配的 CD 和曲目信息:

void MainWindow::doSearch()
{
    cd_search_st *cd_res = new cd_search_st;
    current_cd_st *cd = new current_cd_st;
    struct current_tracks_st ct;
    int res1, i, j, res2, res3;
    char track_title[110];
    char search_text[100];
    char statusBar_text[200];
    QListViewItem *cd_item;
    strcpy(search_text, search_entry->text());
    // 获取匹配的 CD ID
    res1 = find_cds(search_text, cd_res);
    sprintf(statusBar_text, “ Displaying %d result(s) for search string ‘ %s ‘“,
            res1, search_text);
    statusBar()->message(statusBar_text);
    i = 0;
    list->clear();
    // 遍历每个 CD ID
    while (i < res1) {
        res2 = get_cd(cd_res->cd_id[i], cd);
        cd_item = new QListViewItem(list, cd->title, cd->artist_name, cd->catalogue);
        res3 = get_cd_tracks(cd_res->cd_id[i++], &ct);
        j = 0;
        // 显示每个 CD 的曲目信息
        while (j < res3) {
            sprintf(track_title, “ Track %d. “, j+1);
            strcat(track_title, ct.track[j++]);
            new QListViewItem(cd_item, track_title);
        }    
    }
}

步骤 4:实现添加 CD 功能
AddCd 槽函数在点击添加 CD 菜单项或工具栏按钮时被调用:

void MainWindow::AddCd()
{
    AddCdDialog *dialog = new AddCdDialog(this);
    dialog->show();  
}
6.2 添加 CD 对话框开发

添加 CD 对话框用于向数据库中添加新的 CD,以下是开发步骤:

步骤 1:定义添加 CD 对话框类
AddCdDialog.h 中定义添加 CD 对话框类,继承自 KDialogBase

#include <kde/kdialogbase.h>
#include <qlineedit.h>
class AddCdDialog : public KDialogBase
{
    Q_OBJECT
public:
    AddCdDialog (QWidget *parent);
private:
    QLineEdit *artist_entry, *title_entry, *catalogue_entry;
public slots:
    void okClicked();
};

步骤 2:实现添加 CD 对话框构造函数
AddCdDialog.cpp 中实现对话框的构造函数,并在 okClicked 槽函数中调用添加 CD 的函数:

#include “AddCdDialog.h”
#include “app_mysql.h”
#include <qlayout.h>
#include <qlabel.h>
AddCdDialog::AddCdDialog( QWidget *parent)
    : KDialogBase( parent, “AddCD”, false, “Add CD”,
                   KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true )
{
    QWidget *widget = new QWidget(this);
    setMainWidget(widget);
    QGridLayout *grid = new QGridLayout(widget,3,2,10, 5,”grid”);
    grid->addWidget(new QLabel(“Artist”, widget, “artistlabel”), 0, 0, 0);
    grid->addWidget(new QLabel(“Title”, widget, “titlelabel”), 1, 0, 0);
    grid->addWidget(new QLabel(“Catalogue”, widget, “cataloguelabel”), 2, 0, 0);
    artist_entry = new QLineEdit( widget, “artist_entry”);
    title_entry = new QLineEdit( widget, “title_entry”);
    catalogue_entry = new QLineEdit( widget, “catalogue_entry”);
    grid->addWidget(artist_entry, 0,1, 0);
    grid->addWidget(title_entry, 1,1, 0);
    grid->addWidget(catalogue_entry, 2,1, 0);
    connect (this, SIGNAL(okClicked()), this, SLOT(okClicked()));
}
void AddCdDialog::okClicked()
{
    char artist[200];
    char title[200];
    char catalogue[200];
    int cd_id = 0;
    strcpy(artist, artist_entry->text());
    strcpy(title, title_entry->text());
    strcpy(catalogue, catalogue_entry->text());
    add_cd(artist, title, catalogue, &cd_id);
}
6.3 登录对话框开发

登录对话框用于用户输入数据库登录凭证,以下是开发步骤:

步骤 1:定义登录对话框类
LogonDialog.h 中定义登录对话框类,继承自 QDialog

#include <qdialog.h>
#include <qlineedit.h>
class LogonDialog : public QDialog
{
    Q_OBJECT
public:
    LogonDialog (QWidget *parent = 0, const char *name = 0);
    QString getUsername();
    QString getPassword();
private:
    QLineEdit *username_entry, *password_entry;
};

步骤 2:实现登录对话框构造函数和方法
LogonDialog.cpp 中实现对话框的构造函数和获取用户名、密码的方法:

#include “LogonDialog.h”
#include “app_mysql.h”
#include <qpushbutton.h>
#include <qlayout.h>
#include <qlabel.h>
LogonDialog::LogonDialog( QWidget *parent, const char *name): QDialog(parent, name)
{
    QGridLayout *grid = new QGridLayout(this, 3, 2, 10, 5,”grid”);
    grid->addWidget(new QLabel(“Username”, this, “usernamelabel”), 0, 0, 0);
    grid->addWidget(new QLabel(“Password”, this, “passwordlabel”), 1, 0, 0);
    username_entry = new QLineEdit( this, “username_entry”);
    password_entry = new QLineEdit( this, “password_entry”);
    password_entry->setEchoMode(QLineEdit::Password);
    grid->addWidget(username_entry, 0, 1, 0);
    grid->addWidget(password_entry, 1, 1, 0);
    QPushButton *button = new QPushButton (“Ok”, this, “button”);
    grid->addWidget(button, 2, 1,Qt::AlignRight);
    connect (button, SIGNAL(clicked()), this, SLOT(accept()));
}
QString LogonDialog::getUsername()
{
    if (username_entry == NULL)
        return NULL;
    return username_entry->text();
}
QString LogonDialog::getPassword()
{
    if (password_entry == NULL)
        return NULL;
    return password_entry->text();
}
6.4 主函数实现

main.cpp 中实现主函数,打开登录对话框并处理登录逻辑:

#include “MainWindow.h”
#include “app_mysql.h”
#include “LogonDialog.h”
#include <kde/kapp.h>
#include <qmessagebox.h>
int main( int argc, char **argv )
{
    char username[100];
    char password[100];
    KApplication a( argc, argv, “cdapp” );
    LogonDialog *dialog = new LogonDialog();
    while (1)
    {
        if (dialog->exec() == QDialog::Accepted)
        {
            strcpy(username, dialog->getUsername());
            strcpy(password, dialog->getPassword());
            if (database_start(username, password))
                break;
            QMessageBox::information(0, “Title”, 
                                     “Could not Logon: Check username and/or password”,
                                     QMessageBox::Ok);
            continue;
        }
        else
        {
            if (QMessageBox::information(0, “Title”, 
                                         “Are you sure you want to quit?”,
                                         QMessageBox::Yes, QMessageBox::No) 
                == QMessageBox::Yes)
                return 0;
        }
    }
    MainWindow *window = new MainWindow(“mainwindow”);
    a.setMainWidget(window);
    window->show();
    return a.exec();
}

总结

通过以上步骤,我们完成了一个基于 Qt 和 KDE 的 CD 数据库应用程序的开发。从基础的 QListView 组件和对话框使用,到使用 qmake 简化编译过程,再到利用 KDE 的 KAction 组件创建菜单和工具栏,最后实现了 CD 数据库应用的各项功能。整个开发过程展示了 Qt 和 KDE 在 GUI 开发中的强大功能和便捷性,希望这些内容能帮助你更好地进行 Qt 和 KDE 开发。

以下是整个 CD 数据库应用的开发流程 mermaid 图:

graph LR
    A[开始] --> B[创建主窗口]
    B --> C[创建登录对话框]
    C --> D{登录成功?}
    D -- 是 --> E[显示主窗口]
    D -- 否 --> C
    E --> F[实现搜索功能]
    E --> G[实现添加 CD 功能]
    F --> H[显示搜索结果]
    G --> I[添加 CD 到数据库]
    H --> J[结束]
    I --> J

这个流程图展示了 CD 数据库应用的主要开发流程,从登录到搜索、添加 CD 等功能的实现,最后结束应用。通过这个流程,你可以更清晰地了解整个应用的开发过程。

【EI复现】基于深度强化学习的微能源网能量管理优化策略研究(Python代码实现)内容概要:本文围绕“基于深度强化学习的微能源网能量管理优化策略”展开研究,重点利用深度Q网络(DQN)等深度强化学习算法对微能源网中的能量调度进行建模优化,旨在应对可再生能源出力波动、负荷变化及运行成本等问题。文中结合Python代码实现,构建了包含光伏、储能、负荷等元素的微能源网模型,通过强化学习智能体动态决策能量分配策略,实现经济性、稳定性和能效的多重优化目标,并可能其他优化算法进行对比分析以验证有效性。研究属于电力系统人工智能交叉领域,具有较强的工程应用背景和学术参考价值。; 适合人群:具备一定Python编程基础和机器学习基础知识,从事电力系统、能源互联网、智能优化等相关方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①学习如何将深度强化学习应用于微能源网的能量管理;②掌握DQN等算法在实际能源系统调度中的建模实现方法;③为相关课题研究或项目开发提供代码参考和技术思路。; 阅读建议:建议读者结合提供的Python代码进行实践操作,理解环境建模、状态空间、动作空间及奖励函数的设计逻辑,同时可扩展学习其他强化学习算法在能源系统中的应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值