Qt:QtCharts绘制图表实时采集温度

目录

Qt Charts介绍

视图-QChartView

图表-QChart

系列-QAbstractSeries

坐标轴-QAbstractAxis

图例-Legend

 创建GUI界面

 界面布局

完整代码


Qt Charts介绍

自从 Qt 发布以来,给广大跨平台界面研发人员带来了无数的福利。但是Qt自己却一直没有提供自带的图表库,这就使得 QWT、QCustomPlot 等第三方图表库有了巨大的生存空间,为了降低开发成本,大家都涌向了这些第三方库。这种情况一直持续到 Qt5.7 版本后 Qt Charts 的发布。Qt Charts 是 Qt 自带的组件库,包含折线、曲线、饼图、棒图、散点图、雷达图等等各种常用的图表。协议的约束:GPLV3。

视图-QChartView

负责 QChart 的展示。QChart 本身只负责图表内容的组织、管理。图表的展示由视图负责,这个视图就是 QChartView。QChartView 派生自 QGraphicsView,只是它专门提供了几个面向 QChart 的接口,比如 setChart(QChart*)等。

QtCreator 中使用 QChartView 可以放置一个 QWidget,然后升级为 QChartView

QChartView 的内容很少,建议直接过一遍文档 QChartView

QChartView继承关系如下:

图表-QChart

QChart 管理不同类型的序列和其他与图表相关的对象,例如坐标轴及图例。

QChart 承担了一个组织、管理的角色。QChart 派生自 QGraphicsObject,因此它实际上是一个图元 item。我们可以从 QChart 获取到坐标轴对象、数据系列对象、图例等等,并且可以设置图表的主题、背景色等样式信息。

QChart 继承关系如下:

系列-QAbstractSeries

不论是曲线、饼图、棒图还是其他图表,其中展示的内容本质都是数据。一条曲线是一组数据,一个饼图也对应一组数据。在 Qt Charts 中,这些一组组的数据被称作系列。对应不同类型的图表 Qt 提供了不同的系列。系列除了负责存储、访问数据,应该还提供了数据的绘制方法,比如常用的折线图和曲线图分别对应 QLineSerie 和 QSPLineSerie。我们可以用不同的系列达到不同的展示目的。

常见的系列如下:

  • QBarSeries (柱状图)

  • QHorizontalBarSeries (水平柱状图)

  • QHorizontalPercentBarSeries (水平百分比柱状图)

  • QHorizontalStackedBarSeries (水平层叠图)

  • QPercentBarSeries (百分比柱状图)

  • QStackedBarSeries (层叠图/堆叠的条形图)

  • QAreaSeries (面积图)

  • QBoxPlotSeries (形图/盒须图)

  • QPieSeries (饼图)

  • QXYSeries (线性图、曲线图、散点图的基类)

  • QLineSeries (折线图)

  • QSplineSeries (曲线图)

  • QScatterSeries (散点图)

系列继承关系如下:

坐标轴-QAbstractAxis

图表中,一般都有 X、Y 坐标轴,复杂一些的还带有 Z 轴。对应到 Qt 的图表也有 X、Y 轴对象。坐标轴封装了刻度,标签,网格线,标题等属性。

坐标轴有以下几种:

  • QBarCategoryAxis
    类别坐标轴,用字符串作为坐标轴的可读,用于图表的非数值坐标轴
  • QDateTimeAxis
    时间坐标轴,用作时间数据的坐标轴
  • QLogValueAxis
    对数数值坐标轴,作为数值类数据的对数坐标轴
  • QValueAxis
    数值坐标轴,用作数值型数据的坐标轴
  • QCategoryAxis
    分组数值坐标轴,可以为数值范围设置标签

坐标轴继承关系如下:

图例-Legend

图例是对图表上系列的补充说明。我们可以设置序列颜色及其文字说明、并控制序列显示的位置。

此外,图例中还有一个 QLegendMarker 类,可为每个序列的图例生成一个类似QChrckedBox 的组件;单击序列的标记,可以控制序列是否显示。

下面是官方例程 chartthemes 的运行效果。

在这里插入图片描述
上述内容,建议通过官方例程配合类文档学习。

 创建GUI界面

需要在安装 Qt 时带上了 charts,否则后面工作无法开展。

  • 对于编译方式安装的 Qt,需要注意在 configure 时不要跳过 charts。
  • 对于安装包方式安装的 Qt,需要注意在安装时,确保 charts 组件被选中。

新建一个 Qt Widgets Application 项目。

在绘制 ui 窗体时,从 designer 的工具箱中选择一个 “Widget” 类型的控件,然后在它上面单击鼠标右键,选择 “提升为”。

在弹出的界面中,填写 "提升的类名称" 为: QChartView,头文件名称会自动生成,我们不用关心。然后单击“添加”按钮即可。

 界面布局

完整代码

在 .pro 文件添加

QT  += charts
QT  += serialport serialbus

在 .h 文件添加

#include <QtCharts>         // QChart 所需的头文件

#include <QModbusClient>    // 发送 Modbus 请求
#include <QModbusReply>     // 访问服务器后的回复
#include <QTimer>

// 使用串行总线与 Modbus 服务器进行通信的 Modbus 客户端
#include <QModbusRtuSerialMaster>

#include <QVariant>     // 这个类型充当着最常见的数据类型的联合
#include <QSerialPort>  // 串口IO操作
#include <QDebug>

QT_CHARTS_USE_NAMESPACE     //声明 Qt Charts 的命名空间

widget.cpp 文件

#include "widget.h"
#include "ui_widget.h"

const uint8_t xMax = 10;
const uint8_t yMax = 70;

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

    modbus_init();

    charts_init();

    my_time = new QTimer ();
    my_time->setInterval(1000);

    connect(my_time, &QTimer::timeout, this, &Widget::timeout);
}

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

void Widget::charts_init()      // 配置图表
{
    // 构建图表对象
    chart = new QChart ();
    chart->setTitle("温度曲线");   //图表的名字
    ui->widget->setChart(chart);    //将图表绑定到视图
    ui->widget->setRenderHint(QPainter::Antialiasing);//设置渲染:抗锯齿

    // 创建曲线系列
    mSeries = new QSplineSeries ();
//    mSeries->setColor(QColor(255, 0, 0));//曲线颜色
    mSeries->setName("温度"); //曲线名字
    chart->addSeries(mSeries);//将曲线系列添加到图表
    chart->setTheme(QtCharts::QChart::ChartThemeBrownSand);

    // 坐标轴
    mAxisY = new QValueAxis ();
    mAxisX = new QValueAxis ();

    mAxisX->setTitleText(QString(tr("时间(s)")));
    mAxisY->setTitleText(QString(tr("温度(℃)")));

    mAxisY->setRange(-20, yMax);// 坐标轴范围
    mAxisY->setTickCount(10);// 坐标轴分等份

    mAxisX->setRange(0, xMax);
    mAxisX->setTickCount(10);
    mAxisX->setLabelFormat("%.0f"); //坐标轴标签格式:每个单位保留几位小数
    mAxisX->setTitleFont(QFont("宋体"));
//    mAxisX->setLabelsVisible(false);    //隐藏坐标轴标签
//    mAxisX->setMinorTickCount(4); //每个单位之间绘制了多少虚网线
//    mAxisX->setGridLineVisible(false);  // 隐藏网格
//    mAxisX->setLabelsAngle(45);//字体倾斜角度
//    mAxisX->setLabelsColor(Qt::blue);
//    mAxisX->setLabelsEditable(true);

    // 图例
    QLegend *mlegeng = chart->legend();
    mlegeng->setAlignment(Qt::AlignBottom);// 图例在图表底部显示
    mlegeng->show();

    // 添加坐标轴(先添加轴到 Chart 上,再附加轴到 Series 上)
    chart->addAxis(mAxisX, Qt::AlignBottom);
    chart->addAxis(mAxisY, Qt::AlignLeft);
    mSeries->attachAxis(mAxisX);
    mSeries->attachAxis(mAxisY);

    // 隐藏背景
//    chart->setBackgroundVisible(false);
    // 设置外边界全都为 0
    chart->setContentsMargins(0, 0, 0, 0);
    // 设置内部边界全都为 0
    chart->setMargins(QMargins(0, 0, 0, 0));
    // 设置背景区域无圆角
    chart->setBackgroundRoundness(10);
    // 突出曲线上的点
    mSeries->setPointsVisible(true);
}

void Widget::modbus_init()      // 配置传输协议
{
    modbusClient = new QModbusRtuSerialMaster ();//创建一个ModbusRTU通信的主机对象

    //设置通信的端口号、波特率、奇偶校验位、数据位、停止位
    modbusClient->setConnectionParameter(QModbusDevice::SerialPortNameParameter, QVariant("/dev/ttySTM2"));
    modbusClient->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600);
    modbusClient->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);
    modbusClient->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
    modbusClient->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);

    modbusClient->setTimeout(1000);//如果在规定的超时内没有收到响应,则设置TimeoutError
    modbusClient->setNumberOfRetries(3);//设置客户端在请求失败前执行的重试次数

    bool ok = modbusClient->connectDevice();//连接设备
    if(ok){
        qDebug() << "Successful connect";
    }else{
        qDebug() << "Connect failure";
    }
}

void Widget::ready_read()   // 读取数据并输出到图表
{
    auto reply = qobject_cast<QModbusReply *>(sender());
    if(reply == nullptr){
        qDebug() << "No response";
        return;
    }

    if(reply->error() == QModbusDevice::NoError){
        const QModbusDataUnit rev_data = reply->result();

        quint16 tempartureDate = rev_data.value(0);
        quint16 tempar = tempartureDate;
        qreal temparture = 0;

        if((tempar & 0x8000) == 32768){ //温度为负数
            tempartureDate &= 0x7fff;
            temparture = tempartureDate/10.0;
            QString str1 = QString("-%1").arg(temparture);   //将任何类型的数据转化为字符型数据

            ui->wendu_label->setText(str1);     //读取数据到 ui 界面
        }else{
            temparture = tempartureDate/10.0;
            QString str1 = QString("%1").arg(temparture);   //将任何类型的数据转化为字符型数据

            ui->wendu_label->setText(str1);     //读取数据到 ui 界面
        }

        // 系列添加数值
        static int count = 0;
        if(count > mAxisX->max()){
            mSeries->removePoints(0, mSeries->count() - xMax);

            chart->axisX()->setMin(count - xMax);
            chart->axisX()->setMax(count);
        }
        mSeries->append(count, temparture);
        count++;

    }else if(reply->error() == QModbusDevice::ProtocolError){
        qDebug() << "Data read protocol error" << reply->errorString();
    }else{
        qDebug() << "Other error" << reply->errorString();
    }
}

void Widget::timeout()  // 定时器超时操作
{
    //0x04:要读取寄存器的起始地址; 2:表示读取几个寄存器
    QModbusDataUnit data(QModbusDataUnit::HoldingRegisters, 0x04, 2);
    //0x01:表示设备地址
    QModbusReply *reply = modbusClient->sendReadRequest(data, 0x01);

    if(reply == nullptr){
        qDebug() << "Read data failure: " << modbusClient->errorString();
    }
    if(!reply == isFullScreen()){
        connect(reply, &QModbusReply::finished, this, &Widget::ready_read);
    }
}


void Widget::on_time_btn_clicked()
{
    my_time->start();
}

void Widget::on_stop_btn_clicked()
{
    my_time->stop();
}

补充:

实现图表缩放、移动,点击鼠标右键返回

Widget_Chart.h

#ifndef WIDGET_CHART_H
#define WIDGET_CHART_H

#include <QWidget>
#include <QtCharts>
#include "chartview.h"
#include "dw_param.h"

namespace Ui {
class Widget_Chart;
}

class Widget_Chart : public QWidget
{
    Q_OBJECT

public:
    // 获取单实例对象
    static Widget_Chart *getInstance();
    // 填充图表数据
    void charts_data(int numFormal);

protected:
    void paintEvent(QPaintEvent *event)override;

private slots:
    void on_pushButton_widget_chart_clear_clicked();

private:
    explicit Widget_Chart(QWidget *parent = nullptr);
    ~Widget_Chart();
    // 禁止外部拷贝构造
    Widget_Chart(const Widget_Chart &widget_chart) = delete;
    // 禁止外部赋值操作
    const Widget_Chart &operator=(const Widget_Chart &widget_chart) = delete;

    void widget_chart_init();
    void charts_create(QChart *chart, QValueAxis *mAxX, QValueAxis *mAxY, \
                       QSplineSeries *mSeries_0, QSplineSeries *mSeries_1, QSplineSeries *mSeries_2);

    QChart *chart_1 = nullptr;
    QValueAxis *mAxX_1 = nullptr;
    QValueAxis *mAxY_1 = nullptr;
    QSplineSeries *mSeries_1_0 = nullptr;
    QSplineSeries *mSeries_1_1 = nullptr;
    QSplineSeries *mSeries_1_2 = nullptr;
    ChartView *chartview_1 = nullptr;

private:
    Ui::Widget_Chart *ui;
};

#endif // WIDGET_CHART_H

Widget_Chart.cpp

#include "widget_chart.h"
#include "ui_widget_chart.h"

const uint16_t xMax = 20;
const uint16_t yMax = 1100;
const uint16_t num = 3000;  //每条曲线保留 num 点的数据
static int count = 0;  //图表 X 轴坐标

Widget_Chart::Widget_Chart(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget_Chart)
{
    ui->setupUi(this);
    this->setWindowFlags(Qt::WindowMinimizeButtonHint|Qt::WindowCloseButtonHint);   //取消最大化按钮
    setFixedSize(this->width(), this->height());    //禁止调节窗口大小

    widget_chart_init();
}

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

Widget_Chart *Widget_Chart::getInstance()
{
    Widget_Chart *widget_chart = new Widget_Chart();
    return widget_chart;
}

void Widget_Chart::charts_data(int numFormal)
{
    for(int i = 0; i < numFormal; i++)
    {
        mSeries_1_0->append(count+i, dw_param.fwp[i]);
        mSeries_1_1->append(count+i, dw_param.bwp[i]);
    }

    count += numFormal;
    if(count > mAxX_1->max()){
        chart_1->axisX()->setMin(count-xMax);
        chart_1->axisX()->setMax(count);
    }
    if(count > num)
    {
        mSeries_1_0->removePoints(0, mSeries_1_0->count()-num);
        mSeries_1_1->removePoints(0, mSeries_1_1->count()-num);
    }
}

void Widget_Chart::paintEvent(QPaintEvent *event)
{
    chartview_1->resize(ui->widget_chart->width(), ui->widget_chart->height());
}

void Widget_Chart::widget_chart_init()
{
    //图表
    chart_1 = new QChart();
    //坐标轴
    mAxX_1 = new QValueAxis();
    mAxY_1 = new QValueAxis();
    //图例
    mSeries_1_0 = new QSplineSeries();
    mSeries_1_1 = new QSplineSeries();
    mSeries_1_2 = new QSplineSeries();

    charts_create(chart_1, mAxX_1, mAxY_1, mSeries_1_0, mSeries_1_1, mSeries_1_2);

    chartview_1 = new ChartView(chart_1, ui->widget_chart);

    chartview_1->setRenderHint(QPainter::Antialiasing);    //抗锯齿
    //注:这里需要设置大小
    chartview_1->resize(ui->widget_chart->width(), ui->widget_chart->height());
}

void Widget_Chart::charts_create(QChart *chart, QValueAxis *mAxX, QValueAxis *mAxY, QSplineSeries *mSeries_0, QSplineSeries *mSeries_1, QSplineSeries *mSeries_2)
{
    mSeries_0->setName("前向功率");
    mSeries_1->setName("反向功率");
    //mSeries_2->setName("PSU_V");

    mAxX->setRange(0, xMax);
    mAxX->setLabelFormat("%.0f");
    mAxX->setTickCount(20);
    //mAxX->setLineVisible(false);    //轴线显示
    mAxX->setLabelsVisible(false);  //轴刻度标签显示

    mAxY->setRange(0, yMax);
    mAxY->setLabelFormat("%d");
    mAxY->setTickCount(12);
    //mAxY->setLineVisible(false);
    mAxY->setLabelsVisible(true);

    QLegend *mlegend = chart->legend();
    mlegend->setAlignment(Qt::AlignBottom);
    //mlegend->hide();

    chart->addSeries(mSeries_0);
    chart->addSeries(mSeries_1);
    //chart->addSeries(mSeries_2);
    chart->setTheme(QtCharts::QChart::ChartThemeBrownSand); //图表主题

//    mAxX->setTitleText(name);
    mAxY->setTitleText("功率(W)");
    chart->addAxis(mAxX, Qt::AlignBottom);
    chart->addAxis(mAxY, Qt::AlignLeft);

    mSeries_0->attachAxis(mAxX);
    mSeries_0->attachAxis(mAxY);
    mSeries_1->attachAxis(mAxX);
    mSeries_1->attachAxis(mAxY);
//    mSeries_2->attachAxis(mAxX);
//    mSeries_2->attachAxis(mAxY);

    chart->setContentsMargins(0, 0, 0, 0);
    chart->setMargins(QMargins(0, 0, 0, 0));
    chart->setBackgroundRoundness(10);

    //ui->charts_widget->setChart(chart);
    //ui->charts_widget->setRenderHint(QPainter::Antialiasing);    //抗锯齿

    mSeries_0->setPen(QPen(QColor(0, 35, 245), 3));     //设置颜色和线条粗细
    mSeries_1->setPen(QPen(QColor(255, 0, 0), 3));
    //mSeries_2->setPen(QPen(QColor(251, 160, 251), 3));
}



void Widget_Chart::on_pushButton_widget_chart_clear_clicked()
{
    mSeries_1_0->clear();
    mSeries_1_1->clear();

    count = 0;
    chart_1->axisX()->setMin(count);
    chart_1->axisX()->setMax(xMax);
    chart_1->axisY()->setMin(0);
    chart_1->axisY()->setMax(yMax);
    chartview_1->saveAxisRange(false);
}

ChartView.h

#ifndef CHARTVIEW_H
#define CHARTVIEW_H

#include <QChartView>
#include <QMouseEvent>
#include <QGraphicsSimpleTextItem>
#include <QChart>

#if (QT_VERSION <= QT_VERSION_CHECK(6,0,0))
QT_CHARTS_USE_NAMESPACE
#endif

class ChartView : public QChartView
{
    Q_OBJECT

    using AxisRange = std::array<qreal, 2>;
public:
    explicit ChartView(QWidget* parent = nullptr);
    ChartView(QChart *chart, QChartView *parent = nullptr);
    ~ChartView();

    void saveAxisRange(bool save=true);   //保存缩放或移动前的坐标轴,以便重新设置
protected:
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void wheelEvent(QWheelEvent *event);
    void keyPressEvent(QKeyEvent *event);
    void keyReleaseEvent(QKeyEvent *event);

private:
    QPoint prevPoint_;
    bool leftButtonPressed_;
    bool ctrlPressed_;
    bool alreadySaveRange_;
    AxisRange xRange_;
    AxisRange yRange_;
    QGraphicsSimpleTextItem* coordItem_;
};

#endif // CHARTVIEW_H

ChartView.cpp

#include "chartview.h"

#include <QApplication>
#include <QValueAxis>
#include <QDebug>

ChartView::ChartView(QWidget* parent)
    : QChartView( parent )
    , prevPoint_{}
    , leftButtonPressed_{}
    , ctrlPressed_{}
    , alreadySaveRange_{}
    , xRange_{}
    , yRange_{}
    , coordItem_{}
{
    setDragMode(QGraphicsView::RubberBandDrag);
    setMouseTracking(false);
    setCursor(QCursor(Qt::PointingHandCursor));
}

ChartView::ChartView(QChart *chart, QChartView *parent)
    : ChartView(parent) // C++11 Delegating constructors
{
    setChart(chart);
}

ChartView::~ChartView()
{
}

void ChartView::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        prevPoint_ = event->pos();
        leftButtonPressed_ = true;
    }
}

void ChartView::mouseMoveEvent(QMouseEvent *event)
{
    //实时显示鼠标在界面上的坐标
    if (!coordItem_) {
        coordItem_ = new QGraphicsSimpleTextItem{ chart() };
        coordItem_->setZValue(5);
        coordItem_->setPos(100, 50);
        coordItem_->show();
    }
    const QPoint curPos{ event->pos() };
    const QPointF curVal{ chart()->mapToValue(QPointF(curPos)) };
    coordItem_->setText(QString("X = %1, Y = %2").arg(curVal.x()).arg(curVal.y()));

    if (leftButtonPressed_) {
        const auto offset = curPos - prevPoint_;
        prevPoint_ = curPos;
        if (!alreadySaveRange_) {
            saveAxisRange();
            alreadySaveRange_ = true;
        }
        chart()->scroll(-offset.x(), offset.y());
    }
}

void ChartView::mouseReleaseEvent(QMouseEvent *event)
{
    leftButtonPressed_ = false;
    if (event->button() == Qt::RightButton) {
        if (alreadySaveRange_) {
            chart()->axisX()->setRange(xRange_[0], xRange_[1]);
            chart()->axisY()->setRange(yRange_[0], yRange_[1]);
        }
        alreadySaveRange_ = false;
    }
}

void ChartView::wheelEvent(QWheelEvent *event)
{
#if (QT_VERSION <= QT_VERSION_CHECK(6,0,0))
    const auto pos  = QPointF(event->pos());
    const auto isZoomIn = event->delta() > 0;
#else
    const auto pos  = event->position();
    const auto isZoomIn = event->angleDelta().y() > 0;
#endif
    const QPointF curVal = chart()->mapToValue(pos);

    if (!alreadySaveRange_) {
        saveAxisRange();
        alreadySaveRange_ = true;
    }

    auto zoom = [](QValueAxis* axis, double centre, bool zoomIn) {
        constexpr auto scaling{ 1.5 };  //缩放倍率

        const double down = axis->min();
        const double up = axis->max();

        double downOffset{};
        double upOffset{};
        if (zoomIn) {
            downOffset = (centre - down) / scaling;
            upOffset = (up - centre) / scaling;
        }
        else {
            downOffset = (centre - down) * scaling;
            upOffset = (up - centre) * scaling;
        }

        axis->setRange(centre - downOffset, centre + upOffset);
    };

    if (ctrlPressed_) {
        auto axis = qobject_cast<QValueAxis*>(chart()->axisY());
        zoom(axis, curVal.y(), isZoomIn);
    } else {
        auto axis = qobject_cast<QValueAxis*>(chart()->axisX());
        zoom(axis, curVal.x(), isZoomIn);
    }
}

void ChartView::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Control)
        ctrlPressed_ = true;
}

void ChartView::keyReleaseEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Control)
        ctrlPressed_ = false;
}

void ChartView::saveAxisRange(bool save)
{
    alreadySaveRange_ = save;
    const auto axisX = qobject_cast<QValueAxis*>(chart()->axisX());
    xRange_ = { axisX->min(), axisX->max() };
    const auto axisY = qobject_cast<QValueAxis*>(chart()->axisY());
    yRange_ = { axisY->min(), axisY->max() };
}

参考鸣谢

Qt Chart之绘制折线图:图表以及坐标轴设置_qt绘制折线图 图标 坐标轴设置_好奇松鼠的博客-优快云博客

Qt - QTChart绘制图表_JYU_hsy的博客-优快云博客

https://www.cnblogs.com/ybqjymy/p/13665040.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值