五、鼠标事件与目标焦点

这篇博客介绍了如何在QT中实现鼠标事件追踪,包括实时追踪、全屏追踪和鼠标穿透。全屏追踪通过应用级和系统级两种方案,其中系统级采用鼠标钩子实现。还探讨了坐标变换和屏幕自适应,确保窗口在不同分辨率和放缩因子下正常工作。文章提供了详细的实现代码和效果展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所谓辉煌的人生,不过是欲望的囚徒。

——叔本华

注:自本节开始,所有的示例都以 OpenGLWidget 实现,

一、鼠标事件

重写鼠标事件,

#ifndef MYOPENGL_H
#define MYOPENGL_H

#include <QOpenGLWidget>
#include <QTimer>
#include <QMouseEvent>

class MyOpenGL : public QOpenGLWidget
{
public:
    MyOpenGL(QWidget *parent = nullptr);
public slots:
    void onTimerUpdate();
protected:
    void initializeGL();
    void resizeGL(int w, int h);
    void paintGL();
    void mousePressEvent(QMouseEvent *event); // 鼠标点击事件
    void mouseMoveEvent(QMouseEvent *event); // 鼠标移动事件
    void mouseReleaseEvent(QMouseEvent *event); // 鼠标释放事件
private:
    QTimer * fpsTimer = nullptr;
    bool isPressed = false;
};

#endif // MYOPENGL_H
#include "live2d/LAppDelegate.hpp"
#include "live2d/LAppView.hpp"
#include "live2d/LAppPal.hpp"
#include "live2d/LAppLive2DManager.hpp"
#include "live2d/LAppDefine.hpp"

#include "myopengl.h"

MyOpenGL::MyOpenGL(QWidget *parent)
    :QOpenGLWidget{parent} {
    // 60 fps
    fpsTimer = new QTimer(this);
    fpsTimer->start((1.0/60.0)*1000);
    connect(fpsTimer,&QTimer::timeout,this,&MyOpenGL::onTimerUpdate);
}

void MyOpenGL::onTimerUpdate()
{
   update();
}

void MyOpenGL::initializeGL(){
    LAppDelegate::GetInstance()->Initialize(this);
}

void MyOpenGL::resizeGL(int w, int h){
    LAppDelegate::GetInstance()->resize(w,h);
}

void  MyOpenGL::paintGL(){
    LAppDelegate::GetInstance()->update();
}

void  MyOpenGL::mouseMoveEvent(QMouseEvent*e){
    LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(e->x(),e->y());
    update();
    e->ignore();
}

void  MyOpenGL::mousePressEvent(QMouseEvent*e){
    if(e->button() == Qt::LeftButton)
    {
        isPressed = true;
        LAppDelegate::GetInstance()->GetView()->OnTouchesBegan(e->x(),e->y());

    }
    e->ignore();
}
void  MyOpenGL::mouseReleaseEvent(QMouseEvent*e){
    if(e->button() == Qt::LeftButton)
    {
        isPressed = false;
        LAppDelegate::GetInstance()->GetView()->OnTouchesEnded(e->x(), e->y());
        update();
    }
    e->ignore();
}

效果如下,

二、实时追踪

窗口默认设置为不追踪鼠标移动,只有鼠标按键按下时才影响鼠标移动 mousePressEvent 事件,我们可以通过 setMouseTracking 方法调整为实时追踪,

setMouseTracking(true);
// ...

MyOpenGL::MyOpenGL(QWidget *parent)
    :QOpenGLWidget{parent} {
    // 60 fps
    fpsTimer = new QTimer(this);
    fpsTimer->start((1.0/60.0)*1000);
    connect(fpsTimer,&QTimer::timeout,this,&MyOpenGL::onTimerUpdate);

    setMouseTracking(true);
}

// ...

效果如下,

三、全屏追踪

全屏追踪的方案有两种:

  1. 窗口全屏(应用级)。重写 QT 的鼠标事件,是仅针对于当前窗口的鼠标捕获,一旦脱离了当前窗口,就没法捕获系统的鼠标事件。
  2. 使用系统钩子(系统级)。这种方法涉及到操作系统级别的编程,需要特殊权限和对应平台的 API 知识。

除了实现全屏追踪,我们还需要鼠标穿透到桌面来操作其他窗口,而应用级的鼠标穿透 Qt::WA_TransparentForMouseEvents 不会再接收系统级的鼠标事件。综合考虑,我们采用方案 2 实现全屏追踪。

1、鼠标钩子

#ifndef MYOPENGL_H
#define MYOPENGL_H

#include <QOpenGLWidget>
#include <QTimer>
#include <QMouseEvent>
#include <Windows.h>
#include <QWindow>

class MyOpenGL : public QOpenGLWidget
{
public:
    MyOpenGL(QWidget *parent = nullptr);
    static MyOpenGL* GetInstance();
    ~MyOpenGL();
    void mouseMoveCallBack(float x,float y);
    void mouseReleaseCallBack(float x,float y);
    void mousePressCallBack(float x,float y);
public slots:
    void onTimerUpdate();
protected:
    void initFpsTimer();
    void initializeGL();
    void resizeGL(int w, int h);
    void paintGL();
    void mousePressEvent(QMouseEvent *event); // 鼠标点击事件
    void mouseMoveEvent(QMouseEvent *event); // 鼠标移动事件
    void mouseReleaseEvent(QMouseEvent *event); // 鼠标释放事件
    void releaseInstance();
private:
    QTimer * fpsTimer = nullptr;
    bool isPressed = false;
    HHOOK mouseHook;
    bool isInitializeGL = false;
};

#endif // MYOPENGL_H
#include "live2d/LAppDelegate.hpp"
#include "live2d/LAppView.hpp"
#include "live2d/LAppPal.hpp"
#include "live2d/LAppLive2DManager.hpp"
#include "live2d/LAppDefine.hpp"

#include "myopengl.h"

namespace {
MyOpenGL* s_instance = NULL;
}

DWORD lastCallbackTime = 0;
DWORD THROTTLE_INTERVAL = (1.0/30.0)*1000;

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    DWORD currentTime = GetTickCount(); // 获取当前时间戳
    if (currentTime - lastCallbackTime >= THROTTLE_INTERVAL) { // 检查是否满足节流间隔
        if (nCode >= 0) {
            MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam;
            // 处理鼠标移动事件
            float x = pMouseStruct->pt.x;
            float y = pMouseStruct->pt.y;
            //qDebug() << "Mouse position: (" << x << ", " << y << ")";

            switch (wParam)
            {
            case WM_LBUTTONDOWN:   // 鼠标左键按下
                MyOpenGL::GetInstance()->mousePressCallBack(x,y);
                break;
            case WM_LBUTTONUP:     // 鼠标左键抬起
                MyOpenGL::GetInstance()->mouseReleaseCallBack(x,y);
                break;
            case WM_MOUSEMOVE:     // 鼠标移动
                MyOpenGL::GetInstance()->mouseMoveCallBack(x,y);
                break;
            }
        }
        lastCallbackTime = currentTime; // 更新上次触发回调函数的时间戳
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}


MyOpenGL::MyOpenGL(QWidget *parent)
    :QOpenGLWidget{parent} {
    // 60 fps
    initFpsTimer();

    setMouseTracking(true);

    if (s_instance == NULL)
    {
        s_instance = this;
    }
}

MyOpenGL::~MyOpenGL()
{
    UnhookWindowsHookEx(mouseHook);

    LAppDelegate::GetInstance()->Release();
    LAppDelegate::ReleaseInstance();

    releaseInstance();
}

void MyOpenGL::initFpsTimer(){
    fpsTimer = new QTimer(this);
    fpsTimer->start((1.0/30.0)*1000);
    connect(fpsTimer,&QTimer::timeout,this,&MyOpenGL::onTimerUpdate);
}

void MyOpenGL::releaseInstance(){
    if (s_instance != NULL)
    {
        delete s_instance;
    }

    s_instance = NULL;
}

MyOpenGL* MyOpenGL::GetInstance()
{
    return s_instance;
}

void MyOpenGL::onTimerUpdate()
{
   update();
}

void MyOpenGL::mouseReleaseCallBack(float x,float y){
    if(!isInitializeGL){
        return;
    }

    LAppDelegate::GetInstance()->GetView()->OnTouchesEnded(x,y);
    update();
}
void MyOpenGL::mousePressCallBack(float x,float y){
    if(!isInitializeGL){
        return;
    }

    LAppDelegate::GetInstance()->GetView()->OnTouchesBegan(x,y);
}

void MyOpenGL::mouseMoveCallBack(float x,float y){
    if(!isInitializeGL){
        return;
    }

    LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(x,y);
    update();
}

void MyOpenGL::initializeGL(){
    LAppDelegate::GetInstance()->Initialize(this);

    HINSTANCE hInstance = GetModuleHandle(NULL);
    mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, hInstance, 0);

    isInitializeGL = true;

}

void MyOpenGL::resizeGL(int w, int h){
    LAppDelegate::GetInstance()->resize(w,h);
}

void  MyOpenGL::paintGL(){
    LAppDelegate::GetInstance()->update();
}


void  MyOpenGL::mouseMoveEvent(QMouseEvent*e){
    LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(e->x(),e->y());
    update();
    e->ignore();
}

void  MyOpenGL::mousePressEvent(QMouseEvent*e){
    if(e->button() == Qt::LeftButton)
    {
        isPressed = true;
        LAppDelegate::GetInstance()->GetView()->OnTouchesBegan(e->x(),e->y());

    }
    e->ignore();
}
void  MyOpenGL::mouseReleaseEvent(QMouseEvent*e){
    if(e->button() == Qt::LeftButton)
    {
        isPressed = false;
        LAppDelegate::GetInstance()->GetView()->OnTouchesEnded(e->x(), e->y());
        update();
    }
    e->ignore();
}

2、鼠标穿透

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QGuiApplication>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    setGeometry(QGuiApplication::primaryScreen()->availableGeometry());

    // 一定要先设置鼠标穿透,否则无法穿透,应该是属性中间有值影响
    setAttribute(Qt::WA_TransparentForMouseEvents);
    setAttribute(Qt::WA_TranslucentBackground);
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);

    //setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool);
    myopengl = new MyOpenGL(this);
    setCentralWidget(myopengl);

}

MainWindow::~MainWindow()
{
    delete ui;
}

3、运行效果

四、补充:坐标变换

当窗口大小是远小于屏幕大小的时候,需要转换鼠标坐标与窗口坐标,

void MyOpenGL::mouseMoveCallBack(float x,float y){
    // 模型坐标
    QRect rect = this->rect();

    // window point
    int renderTargetWidth = this->width();
    int renderTargetHeight = this->height();

    // mouse point
    long posx = x;
    long posy = y;

    //
    long myWindowWidth = GetSystemMetrics(SM_CXSCREEN);
    long myWindowHeight = GetSystemMetrics(SM_CYSCREEN);

    //::GetWindowRect(_hw, &rect);

    long rectX = rect.left() + renderTargetWidth * 0.5;
    long rectY = rect.top() + renderTargetHeight * 0.5;

    if (x <= rectX)
    {
        posx = LONG(((float)posx / (float)rectX)*((float)renderTargetWidth * 0.5));
    }
    else {
        posx -= rectX;
        posx = LONG(((float)posx / (float)(myWindowWidth - rectX))*((float)renderTargetWidth * 0.5));
        posx += LONG((float)renderTargetWidth * 0.5);
    }

    if (posy <= rectY)
    {
        posy = LONG(((float)posy / (float)rectY)*((float)renderTargetHeight * 0.5));
    }
    else {
        posy -= rectY;
        posy = LONG(((float)posy / (float)(myWindowHeight - rectY))*((float)renderTargetHeight * 0.5));
        posy += LONG((float)renderTargetHeight * 0.5);
    }

    LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(posx,posy);
    update();
}
#include "live2d/LAppDelegate.hpp"
#include "live2d/LAppView.hpp"
#include "live2d/LAppPal.hpp"
#include "live2d/LAppLive2DManager.hpp"
#include "live2d/LAppDefine.hpp"

#include "myopengl.h"

namespace {
MyOpenGL* s_instance = NULL;
}

DWORD lastCallbackTime = 0;
DWORD THROTTLE_INTERVAL = (1.0/30.0)*1000;

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
    DWORD currentTime = GetTickCount(); // 获取当前时间戳
    if (currentTime - lastCallbackTime >= THROTTLE_INTERVAL) { // 检查是否满足节流间隔
        if (nCode >= 0) {
            MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam;
            // 处理鼠标移动事件
            float x = pMouseStruct->pt.x;
            float y = pMouseStruct->pt.y;
            //qDebug() << "Mouse position: (" << x << ", " << y << ")";

            switch (wParam)
            {
            case WM_LBUTTONDOWN:   // 鼠标左键按下
                MyOpenGL::GetInstance()->mousePressCallBack(x,y);
                break;
            case WM_LBUTTONUP:     // 鼠标左键抬起
                MyOpenGL::GetInstance()->mouseReleaseCallBack(x,y);
                break;
            case WM_MOUSEMOVE:     // 鼠标移动
                MyOpenGL::GetInstance()->mouseMoveCallBack(x,y);
                break;
            }
        }
        lastCallbackTime = currentTime; // 更新上次触发回调函数的时间戳
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}


MyOpenGL::MyOpenGL(QWidget *parent)
    :QOpenGLWidget{parent} {
    // 60 fps
    initFpsTimer();

    setMouseTracking(true);

    if (s_instance == NULL)
    {
        s_instance = this;
    }
}

MyOpenGL::~MyOpenGL()
{
    UnhookWindowsHookEx(mouseHook);

    LAppDelegate::GetInstance()->Release();
    LAppDelegate::ReleaseInstance();

    releaseInstance();
}

void MyOpenGL::initFpsTimer(){
    fpsTimer = new QTimer(this);
    fpsTimer->start((1.0/30.0)*1000);
    connect(fpsTimer,&QTimer::timeout,this,&MyOpenGL::onTimerUpdate);
}

void MyOpenGL::releaseInstance(){
    if (s_instance != NULL)
    {
        delete s_instance;
    }

    s_instance = NULL;
}

MyOpenGL* MyOpenGL::GetInstance()
{
    return s_instance;
}

void MyOpenGL::onTimerUpdate()
{
   update();
}

void MyOpenGL::mouseReleaseCallBack(float x,float y){
    if(!isInitializeGL){
        return;
    }

    LAppDelegate::GetInstance()->GetView()->OnTouchesEnded(x,y);
    update();
}
void MyOpenGL::mousePressCallBack(float x,float y){
    if(!isInitializeGL){
        return;
    }

    LAppDelegate::GetInstance()->GetView()->OnTouchesBegan(x,y);
}

// void MyOpenGL::mouseMoveCallBack(float x,float y){
//     if(!isInitializeGL){
//         return;
//     }

//     LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(x,y);
//     update();
// }

void MyOpenGL::mouseMoveCallBack(float x,float y){
    // 模型坐标
    QRect rect = this->rect();

    // window point
    int renderTargetWidth = this->width();
    int renderTargetHeight = this->height();

    // mouse point
    long posx = x;
    long posy = y;

    //
    long myWindowWidth = GetSystemMetrics(SM_CXSCREEN);
    long myWindowHeight = GetSystemMetrics(SM_CYSCREEN);

    //::GetWindowRect(_hw, &rect);

    long rectX = rect.left() + renderTargetWidth * 0.5;
    long rectY = rect.top() + renderTargetHeight * 0.5;

    if (x <= rectX)
    {
        posx = LONG(((float)posx / (float)rectX)*((float)renderTargetWidth * 0.5));
    }
    else {
        posx -= rectX;
        posx = LONG(((float)posx / (float)(myWindowWidth - rectX))*((float)renderTargetWidth * 0.5));
        posx += LONG((float)renderTargetWidth * 0.5);
    }

    if (posy <= rectY)
    {
        posy = LONG(((float)posy / (float)rectY)*((float)renderTargetHeight * 0.5));
    }
    else {
        posy -= rectY;
        posy = LONG(((float)posy / (float)(myWindowHeight - rectY))*((float)renderTargetHeight * 0.5));
        posy += LONG((float)renderTargetHeight * 0.5);
    }

    LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(posx,posy);
    update();
}

void MyOpenGL::initializeGL(){
    LAppDelegate::GetInstance()->Initialize(this);

    HINSTANCE hInstance = GetModuleHandle(NULL);
    mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, hInstance, 0);

    isInitializeGL = true;

}

void MyOpenGL::resizeGL(int w, int h){
    LAppDelegate::GetInstance()->resize(w,h);
}

void  MyOpenGL::paintGL(){
    LAppDelegate::GetInstance()->update();
}


void  MyOpenGL::mouseMoveEvent(QMouseEvent*e){
    LAppDelegate::GetInstance()->GetView()->OnTouchesMoved(e->x(),e->y());
    update();
    e->ignore();
}

void  MyOpenGL::mousePressEvent(QMouseEvent*e){
    if(e->button() == Qt::LeftButton)
    {
        isPressed = true;
        LAppDelegate::GetInstance()->GetView()->OnTouchesBegan(e->x(),e->y());

    }
    e->ignore();
}
void  MyOpenGL::mouseReleaseEvent(QMouseEvent*e){
    if(e->button() == Qt::LeftButton)
    {
        isPressed = false;
        LAppDelegate::GetInstance()->GetView()->OnTouchesEnded(e->x(), e->y());
        update();
    }
    e->ignore();
}

效果如下,

五、补充:屏幕自适应

当窗口大小等于屏幕大小时,窗口需要自适应屏幕分辨率与放缩。Qt 5.6 版本之前,默认情况下是不开启 DPI 支持的,需要手动设置启用 ,并且需要手动计算放缩因子。从 Qt 5.6 版本开始,默认情况下,Qt 会自动开启 DPI(像素密度)支持。Qt 会根据系统的显示设置自动进行 DPI 缩放,以适应不同的屏幕密度和分辨率。

window 缩放因子 / 100% = logicalDotsPerInch * devicePixelRatio / 96

目前使用 QT6 做屏幕自适应方案,主窗口宽高为客户区的宽高(QGuiApplication::primaryScreen()->availableGeometry().size()),逻辑如下:
1、应用首次启动时,获取客户区屏幕的分辨率,作为参数初始化窗口宽高
2、connect 屏幕物料密度信号与槽函数,当屏幕大小、分辨率发生变化时,触发槽函数
3、槽函数获取当前客户区屏幕的分辨率,resize 窗口宽高
4、OpenGLWidget 会自适应窗口分辨率 => 主窗口宽高变化,触发 OpenGLWidget resizeGL

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "myopengl.h"
#include <QScreen>
#include <QGuiApplication>

#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void onPhysicalDotsPerInchChanged(qreal dpi);
private:
    Ui::MainWindow *ui;
    MyOpenGL *myopengl = nullptr;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QGuiApplication>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    setGeometry(QGuiApplication::primaryScreen()->availableGeometry());

    // 一定要先设置鼠标穿透,否则无法穿透,应该是属性中间有值影响
    setAttribute(Qt::WA_TransparentForMouseEvents);
    setAttribute(Qt::WA_TranslucentBackground);
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);

    //setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool);
    myopengl = new MyOpenGL(this);
    setCentralWidget(myopengl);


    // 切换缩放比时,EnableHighDpiScaling 会触发 physicalDotsPerInchChanged
    // 此时 Qt6 不触发 logicalDotsPerInchChanged
    QScreen *screen = qApp->primaryScreen();
    connect(screen,&QScreen::physicalDotsPerInchChanged,this,&MainWindow::onPhysicalDotsPerInchChanged);

}

void MainWindow::onPhysicalDotsPerInchChanged(qreal dpi)
{
    resize(QGuiApplication::primaryScreen()->availableGeometry().size());
}

MainWindow::~MainWindow()
{
    delete ui;
}

 效果如下,

当主窗口宽高完全为屏幕宽高时(QGuiApplication::primaryScreen()->size()),这会出现一个问题(在虚拟机中不会,仅在物理机中出现):在 100% 放缩因子下,主窗口透明背景为黑屏,而 OpenGLWidget 正常渲染模型。此时考虑了是主窗口与 OpenGLWidget 的宽高关系,实际上,尝试了:

1、主窗口宽高 = 屏幕宽高,OpenGLWidget 宽高 = 屏幕宽高各减一,结果 fail

2、主窗口宽高 > 屏幕宽高,OpenGLWidget 宽高 = 屏幕宽高,结果 success

3、主窗口宽高 < 屏幕宽高,OpenGLWidget 宽高 = 屏幕宽高,结果 success

最终得出这么一个方案,将主窗口宽高设为屏幕宽高 +-2,然后 (x,y) 偏移 +-1,使得主窗口居中,然后 OpenGLWidget 就比较随意设置了,都不会导致主屏幕透明背景黑屏,

void MainWindow::initWindowSettings(){
    // ...
    setGeometry(QRect(-1,-1,QGuiApplication::primaryScreen()->size().width() + 2, QGuiApplication::primaryScreen()->size().height() + 2));
    // ...
}

void MainWindow::onPhysicalDotsPerInchChanged(qreal dpi)
{
    // ...
    resize(QGuiApplication::primaryScreen()->size().width() + 2, QGuiApplication::primaryScreen()->size().height() + 2);
    // ...
}
void MyOpenGL::resizeGL(int w, int h){
    LAppDelegate::GetInstance()->resize(QGuiApplication::primaryScreen()->geometry().width(),QGuiApplication::primaryScreen()->geometry().height());
}

效果如下,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金汐脉动 | PulseTide

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值