Qt for WebAssembly显示QML实例

之前写过一篇文章win10安装配置Qt for WebAssembly,本文介绍一个Qt for WebAssembly实例,效果图如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
demo效果查看地址
代码下载路径

1.1. 技术栈如下:

**开发语言:C++
开发框架:Qt5.15
开发工具:Qt Creator 4.12.4 (Enterprise)
其他工具1:python3.7
**

1.2. 系统的详细开发过程

1.2.1. 用Qt Creator 4.12.4创建项目

在这里插入图片描述

在这里插入图片描述

项目创建完成后运行如下图:
在这里插入图片描述

1.2.2. 创建资源文件

由于项目中需要用到图片,我们创建一个资源文件resource.qrc,如图
在这里插入图片描述
把图片添加到resource.qrc里面,
在这里插入图片描述

1.2.3. 添加项目需要的模块

由于系统里面需要3D效果,并需要显示图表,所以在工程文件pro里面需要添加模块。创建好项目后自带模块,如图:
在这里插入图片描述
添加3D和图表模块,如图:
在这里插入图片描述
添加em++编译配置项,参考文章Qt for WebAssembly爬坑问题集锦

  • BINARYEN_TRAP_MODE=‘clamp’
  • TOTAL_MEMORY=20774912
    在这里插入图片描述

1.2.4. 创建项目文件和目录

在这里插入图片描述

项目目录结构如上图,文件和目录的说明如下:
(目前Qt for WebAssembly不支持多线程和数据库操作)
**
CDataclass:Qt类,数据异步处理类,由页面触发,处理完成后通过信号发送处理结果给页面
Common:Qt类,单例类,注册元类型和初始化mysql数据库
CWorker:Qt类,在线程中运行
main.cpp:项目入口文件
Login:QML文件,登录页面
MyProgressBar:QML文件,进度条
Models3DPage:QML文件,展示QtQuick3D功能
ChartsPage:QML文件,展示Qt画图功能
LogPage:QML文件,日志页面
UserPage:QML文件,用户页面,用TableView展示百万数量及表格
main:QML文件,登录成功后跳转到的主页面,展示功能页面
**

下面我们来创建上面的列表类文件

1.2.4.1. 创建Common类

Common类是一个单例类,有以下功能:

  • 负责初始化数据库
  • 获取数据库实例
  • 定义控件缩放函数
  • 定义系统常量和数据结构

头文件

//定义数据处理结果状态码
enum RET_CODE {
    RET_OK = 0, //成功
    RET_DBERR_OPEN, //数据库查询打开失败
    RET_DBERR_RUN, //SQL执行失败
    RET_PARAMERR, //参数错误
    RET_NOFUNC, //方法不存在
    RET_NOWORKTYPE //处理类型不存在
};

//定义数据处理结果状态码对应的信息
extern QStringList RET_MSG;

//定义命令参数数据结构
typedef struct _CmdData {
    QString func; //处理的函数名称
    QMap<QString, QString> params; //参数列表
} CmdData;

//定义命令处理结果数据结构
typedef struct _RstData {
    int retCode; //结果状态码
    QString func; //处理的函数名称
    QString msg; //结果信息
    QVector< QVector<QString> > result; //处理结果数据,二位数组
} RstData;

// MYSQL数据库信息
typedef struct _MysqlInfo
{
    int port;
    QString host, name, usr, pwd;
} MysqlInfo;

class MyCommon : public QWidget
{
    Q_OBJECT
public:
    explicit MyCommon(QWidget *parent = nullptr);
    ~MyCommon();

    static MyCommon *instance(); //定义单例类
    static void InitDataBase(const MysqlInfo &dbInfo); //初始化数据库

#ifdef MYSQL
    static QSqlDatabase GetNewDatabase()
    {
        QSqlDatabase newDb;
        if (QSqlDatabase::contains("mysql_1"))
        {
            int n = QSqlDatabase::connectionNames().size();
            newDb = QSqlDatabase::cloneDatabase(mDatabase, QString("mysql_%1").arg(n));
        }
        else
        {
            newDb = QSqlDatabase::cloneDatabase(mDatabase, "mysql_1");
        }

        return newDb;
    }
#endif

signals:

public slots:

private:
    static float xScal, yScal;
    static QRect mScreenRect;
    static MyCommon *self;//单例模式
#ifdef MYSQL
    static QSqlDatabase mDatabase;
#endif
    static MysqlInfo mDbInfo;
};

源文件

MyCommon *MyCommon::self = nullptr;
#ifdef MYSQL
QSqlDatabase MyCommon::mDatabase;
#endif
MysqlInfo MyCommon::mDbInfo;

QStringList RET_MSG = QStringList() << "成功" << "数据库查询打开失败" << "SQL执行失败" << "参数错误" << "方法不存在" << "处理类型不存在";

MyCommon::MyCommon(QObject *parent) : QObject(parent)
{
    //注册元类型:主要是在定义信号槽的时候,传递的参数类型不一定是QT所识别的
    qRegisterMetaType<CmdData>("CmdData");
    qRegisterMetaType<RstData>("RstData");

    mDbInfo.port = 3306;
    mDbInfo.host = "127.0.0.1";
    mDbInfo.name = "test_db";
    mDbInfo.usr = "root";
    mDbInfo.pwd = "123456";
    InitDataBase(mDbInfo);
}

MyCommon::~MyCommon()
{
    if (self != nullptr)
    {
        delete self;
    }
}

void MyCommon::InitDataBase(const MysqlInfo &dbInfo)
{
#ifdef MYSQL
    mDatabase = QSqlDatabase::addDatabase("QMYSQL");
    mDatabase.setHostName(dbInfo.host);//设置主机地址
    mDatabase.setPort(dbInfo.port);  //设置端口
    mDatabase.setDatabaseName(dbInfo.name);  //设置数据库名称
    mDatabase.setUserName(dbInfo.usr);  //设置用户名
    mDatabase.setPassword(dbInfo.pwd);  //设置密码
#endif
}
1.2.4.2. 创建CDataClass类

CDataClass是Qt类,有以下功能:

  • 定义多线程,把数据处理移到线程里面处理
  • 定义命令处理函数,供页面调用
  • 定义发送命令信号,把从页面接受的命令发送到线程里面
  • 定义接收处理结果槽函数
  • 定义发送处理结果信号,把从数据返回到页面

头文件

class CDataClass;
typedef void (CDataClass::*PTRFUN)(const CmdData &argcs); //函数指针,用于分发命令

class CDataClass : public QObject
{
    Q_OBJECT
public:
    explicit CDataClass(QObject *parent = nullptr);
    ~CDataClass()
    {
        mWorkerThread.quit();
        mWorkerThread.wait();
    }

    Q_INVOKABLE void handleCmdDataQML(const QString &func, const QStringList &keys,
                                      const QStringList &values); //供页面调用的命令函数,分发到具体的处理函数
    Q_INVOKABLE void checkUserPwd(const CmdData &argcs); //验证输入的用户名和密码
    Q_INVOKABLE void getUsersData(const CmdData &argcs); //查询用户信息
    Q_INVOKABLE void addUsersData(const CmdData &argcs); //增加用户信息
    Q_INVOKABLE void editUsersData(const CmdData &argcs); //编辑用户信息
    Q_INVOKABLE void getLogsData(const CmdData &argcs); //查询日志信息

signals:
	//把页面接受的命令,发送到线程里面的槽函数
    void operate(const int type, const QString &func, const QString &cmd);
    //把线程里面的处理结果返回给页面
    void operateResult(const RstData &rstData);
 	//登录结果信号
    void signalLoginResult(const bool &result);
    //消息显示信号
    void signalMeaasge(const QString &msg);

public slots:
     void handleResults(const RstData &rstData); //接受线程里面处理结果

private:
    QThread mWorkerThread; //定义处理线程
    QMap<QString, PTRFUN> mFuncMap; //定义命令处理函数映射关系
};

源文件

CDataClass::CDataClass(QObject *parent) : QObject(parent)
{
    Worker *worker = new Worker; //定义数据处理类
    worker->moveToThread(&mWorkerThread); //把数据处理类移到线程
    connect(&mWorkerThread, &QThread::finished, worker, &QObject::deleteLater);
    //定义信号槽,把命令发送到线程里面的槽函数
    connect(this, &CDataClass::operate, worker, &Worker::doWork);
    //定义信号槽,接收线程里面发送的结果
    connect(worker, &Worker::resultReady, this, &CDataClass::handleResults);
    mWorkerThread.start(); //开启线程

	//初始化命令处理函数映射关系
    mFuncMap["checkUserPwd"] = &CDataClass::checkUserPwd;
    mFuncMap["getUsersData"] = &CDataClass::getUsersData;
    mFuncMap["addUsersData"] = &CDataClass::addUsersData;
    mFuncMap["editUsersData"] = &CDataClass::editUsersData;
    mFuncMap["getLogsData"] = &CDataClass::getLogsData;
}

void CDataClass::handleResults(const RstData &rstData)
{
    if (rstData.func == "checkUserPwd")
    {
        if (rstData.result.size() > 0)
        {
            emit signalLoginResult(true);
        }
        else
        {
            emit signalLoginResult(false);
        }
    }
    else
    {
        emit operateResult(rstData);
    }
}

void CDataClass::handleCmdDataQML(const QString &func, const QStringList &keys,
                                  const QStringList &values)
{
    qDebug() << "[CDataClass::handleCmdDataQML]" << func << keys << values;
    CmdData argcs;
    argcs.func = func;
    if (keys.size() != values.size())
    {
        return;
    }
    for (int var = 0; var < keys.size(); ++var)
    {
        argcs.params[keys[var]] = values[var];
    }
    handleCmdData(argcs);
}

void CDataClass::handleCmdData(const CmdData &argcs)
{
    RstData rstData;
    if (!mFuncMap.contains(argcs.func))
    {
        rstData.retCode = RET_NOFUNC;
        rstData.msg = RET_MSG[rstData.retCode];
        rstData.func = argcs.func;
        emit signalMeaasge(rstData.msg);
        return;
    }

    //(this->*mFuncMap[argcs.func])(argcs);return;
    // 因为Qt for WebAssembly现在不支持多线程和数据库访问,所以这里注释掉,下面是模拟生成返回数据代码
    rstData.retCode = RET_OK;
    rstData.msg = RET_MSG[rstData.retCode];
    rstData.func = argcs.func;
    for (int i = 0; i < 100000; ++i)
    {
        QVector<QString> tmp(10);
        tmp[0] = QString::number(i+1);
        tmp[1] = QString("test%1").arg(i+1);
        tmp[2] = tmp[1];
        tmp[7] = "1";
        tmp[8] = "";
        tmp[9] = "";
        rstData.result.push_back(tmp);
    }
    handleResults(rstData);
}
1.2.4.3. 创建CWorker类

CWorker是Qt类,有以下功能:

  • 定义mysql操作函数
  • 定义接收页面命令槽函数
  • 定义发送处理结果信号,把从数据返回到页面
//定义线程里面支持的处理数据的操作
enum WORK_TYPE {
    WORK_DB_QUERY = 0, //数据库查询
    WORK_DB_RUN //数据库更新(增、删、改)
};

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr);
    
	void testAddData1(); //测试函数

signals:
    void resultReady(const RstData &rstData); //返回处理结果信号

public slots:
    void doWork(const int type, const QString &func, const QString &cmd); //接收页面命令槽函数

private:
#ifdef MYSQL
    QSqlDatabase mDatabase;
#endif

    int RunSql(const QString &sqlStr); //执行sql语句,写入接口
    int RunSql(const QString &prepare, const QMap<QString, QVariant> &values);
    int RunSqlColRow(const QString &sqlStr, QVector< QVector<QString> > &result); //执行sql语句,查询接口, 返回二维数组[列][行]
    int RunSqlRowCol(const QString &sqlStr, QVector< QVector<QString> > &result); //执行sql语句,查询接口, 返回二维数组[行][列]
};
1.2.4.4. 创建main.cpp

main.cpp是项目的入口文件,有以下功能:

  • 注册数据类型,供信号槽传输
  • 加载全局字体,支持中文(没有就会显示中文异常)
  • 初始化单例类
  • 在qml中设置属性,用于绑定信号槽,便于qml和c++通讯
  • 加载入口qml文件

源文件

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QApplication app(argc, argv);
    qmlRegisterType<CDataClass>("dataclass", 1, 0, "CDataClass"); // 注册类,Q_INVOKABLE方式调用c++里面函数
    qmlRegisterType<MenuTreeModel>("MenuTreeModel", 0, 1, "MenuTreeModel");
    qmlRegisterType<UserTableModel>("UserTableModel", 0, 1, "UserTableModel");
    qmlRegisterType<LogTableModel>("LogTableModel", 0, 1, "LogTableModel");
    qmlRegisterType<MyTreeModel>("MyTreeModel", 0, 1, "MyTreeModel");

    MyCommon::instance(); //初始化单例类

	//加载全局字体,支持中文显示
    int fontId = QFontDatabase::addApplicationFont(QStringLiteral(":/qml/font/SIMYOU.TTF"));
    QStringList fontFamilies = QFontDatabase::applicationFontFamilies(fontId);
    qDebug()<<"fontfamilies:"<<fontFamilies;
    if (fontFamilies.size() > 0)
    {
        QFont font;
        font.setFamily(fontFamilies[0]);//设置全局字体
        app.setFont(font);
    }

    QQmlApplicationEngine engine;
    CDataClass dataClass;
    engine.rootContext()->setContextProperty("datacls", &dataClass); // 设置属性,接收c++对象信号

    engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

1.2.4.5. 创建Login.qml页面
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 1.4

Rectangle {
    id: loginpage
    visible: true
    width: 960
    height: 540
    z: 1000  //设置层级,盖住下层页面
    property string username: 'admin'
    property string password: 'admin'

    signal signalStartProgressBar()
    signal signalShowMsg(string title, string text)

    Rectangle {
        id: rectangle
        x: 230
        y: 100
        width: 500
        height: 340
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
        border.color: "lightgray"

        Text {
            id: text1
            x: 201
            y: 8
            width: 99
            height: 29
            text: qsTr("登录页面")
            anchors.horizontalCenter: parent.horizontalCenter
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
            font.pixelSize: 22
        }

        Text {
            id: text2
            x: 78
            y: 68
            width: 67
            height: 27
            text: qsTr("账号:")
            horizontalAlignment: Text.AlignRight
            verticalAlignment: Text.AlignVCenter
            font.pixelSize: 16
        }

        Text {
            id: text3
            x: 78
            y: 107
            width: 67
            height: 27
            text: qsTr("密码:")
            horizontalAlignment: Text.AlignRight
            font.pixelSize: 16
            verticalAlignment: Text.AlignVCenter
        }

        TextInput {
            id: textInputUser
            x: 158
            y: 68
            width: 271
            height: 27
            font.pixelSize: 24
            passwordCharacter: ""
            readOnly: false
            horizontalAlignment: Text.AlignLeft
            cursorVisible: false
            echoMode: TextInput.Normal
            text: loginpage.username
            BorderImage {
                anchors.rightMargin: 0
                anchors.leftMargin: -3
                anchors.bottomMargin: 0
                anchors.topMargin: 0
                anchors.fill: parent
                source: "qrc:/qml/images/blackborder.png"
                border.left: 1; border.top: 1
                border.right: 1; border.bottom: 1
            }
        }

        TextInput {
            id: textInputPwd
            x: 158
            y: 107
            width: 271
            height: 27
            font.pixelSize: 24
            passwordCharacter: ""
            echoMode: TextInput.Password
            text: loginpage.password
            cursorVisible: false
            horizontalAlignment: Text.AlignLeft
            readOnly: false

            BorderImage {
                anchors.leftMargin: -3
                anchors.fill: parent
                border.top: 1
                border.left: 1
                source: "qrc:/qml/images/blackborder.png"
                border.right: 1
                border.bottom: 1
            }
        }

        Button {
            id: button
            x: 174
            y: 196
            width: 152
            height: 34
            text: qsTr("登录")
            anchors.horizontalCenter: parent.horizontalCenter

            MouseArea {
                id: mouseArea
                anchors.fill: parent
                onClicked: {
                    loginpage.username = textInputUser.text;
                    loginpage.password = textInputPwd.text;
                    if (loginpage.username == '' || loginpage.password == '') {

                        loginpage.signalShowMsg('提示', '请输入账号和密码!')
                        return;
                    }

                    datacls.handleCmdDataQML('checkUserPwd', ['user','pwd'], [loginpage.username, loginpage.password]);
                    loginpage.signalStartProgressBar();
                }
            }
        }

    }

    Component.onCompleted: {

    }
}
1.2.4.6. 创建MyProgressBar.qml进度条
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.0

Rectangle {
    id: rectangle
    visible: true
    width: 960
    height: 540
    color: "#00000000"
    opacity: 1

    function setProgressValue(value) {
        progressBar.value = value;
    }

    function start() {
        progressBar.value = 0;
        timeid.start()
    }

    ProgressBar {
        id: progressBar
        width: 325
        height: 23
        opacity: 1
        //indeterminate: true
        maximumValue: 100
        value: 0
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter

        Timer {
            id: timeid
            interval: 1000;
            repeat: true;
            running: false;
            onTriggered: {console.log(progressBar.value)
                if (progressBar.value < 100) {
                    progressBar.value += 1;
                } else if (progressBar.value >= 100) {
                    progressBar.value = 100;
                    rectangle.visible = false;
                    timeid.stop();
                }
            }
        }
    }

    MouseArea {
        id: mouseArea
        anchors.fill: parent
    }

}
1.2.4.7. 创建Models3DPage.qml

Models3DPage页面展示QtQuick3D功能

import QtQuick 2.15
import QtQuick3D 1.15

Rectangle {
    id: model3d
    visible: true
    width: 960
    height: 540
    border.color: mainwld.rectBdColor
    anchors.top: parent.top
    anchors.left: parent.left
    anchors.margins: 10
    color: "black"

    Rectangle {
        id: qt_logo
        width: 230
        height: 230
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.margins: 10
        color: "black"

        //! [offscreenSurface]
        layer.enabled: true
        //! [offscreenSurface]

        Rectangle {
            anchors.fill: parent
            color: "black"

            //! [2d]
            Image {
                anchors.fill: parent
                source: "images/qt_logo.png"
            }
            Text {
                id: text
                anchors.bottom: parent.bottom
                anchors.left: parent.left
                color: "white"
                font.pixelSize: 17
                text: qsTr("The Future is Written with Qt")
            }
            //! [2d]

            //! [2danimation]
            transform: Rotation {
                id: rotation
                origin.x: qt_logo.width / 2
                origin.y: qt_logo.height / 2
                axis { x: 1; y: 0; z: 0 }
            }

            PropertyAnimation {
                id: flip1
                target: rotation
                property: "angle"
                duration: 600
                to: 180
                from: 0
            }
            PropertyAnimation {
                id: flip2
                target: rotation
                property: "angle"
                duration: 600
                to: 360
                from: 180
            }
            //! [2danimation]
        }
    }

    View3D {
        id: view
        anchors.fill: parent
        camera: camera
        renderMode: View3D.Overlay

        PerspectiveCamera {
            id: camera
            position: Qt.vector3d(0, 200, 300)
            eulerRotation.x: -30
        }

        DirectionalLight {
            eulerRotation.x: -30
        }

        Model {
            //! [3dcube]
            id: cube
            visible: true
            position: Qt.vector3d(0, 0, 0)
            source: "#Cube"
            materials: [ DefaultMaterial {
                    diffuseMap: Texture {
                        id: texture
                        sourceItem: qt_logo
                    }
                }
            ]
            eulerRotation.y: 90
            //! [3dcube]

            SequentialAnimation on eulerRotation {
                loops: Animation.Infinite
                PropertyAnimation {
                    duration: 5000
                    from: Qt.vector3d(0, 0, 0)
                    to: Qt.vector3d(360, 0, 360)
                }
            }
        }
    }

    MouseArea {
        id: mouseArea
        anchors.fill: qt_logo

        Text {
            id: clickme
            anchors.top: mouseArea.top
            anchors.horizontalCenter: mouseArea.horizontalCenter
            font.pixelSize: 17
            text: "Click me!"
            color: "white"

            SequentialAnimation on color {
                loops: Animation.Infinite
                ColorAnimation { duration: 400; from: "white"; to: "black" }
                ColorAnimation { duration: 400; from: "black"; to: "white" }
            }

            states: [
                State {
                    name: "flipped";
                    AnchorChanges {
                        target: clickme
                        anchors.top: undefined
                        anchors.bottom: mouseArea.bottom
                    }
                }
            ]
        }

        onClicked: {
            if (clickme.state == "flipped") {
                clickme.state = "";
                flip2.start();
            } else {
                clickme.state = "flipped";
                flip1.start();
            }
        }
    }
}

1.2.4.8. 创建UserPage.qml页面

UserPage是QML文件,该文件展示QML表格功能,用TableView和自定义模型实现

class UserTableModel : public QAbstractTableModel
{
    Q_OBJECT

public:
    UserTableModel(QObject *parent = nullptr)
    {
        mRoleList = QStringList() << "Id" << "Name" << "Nick" << "Mobile"
                                  << "Email" << "Depart" << "Post" << "Status";
        mHandle = new CDataClass(parent);
        connect(mHandle, &CDataClass::operateResult, this, &UserTableModel::handleResults);
    }

    ~UserTableModel() override
    {
        if (mHandle != nullptr)
        {
            delete mHandle;
        }
    }

    int rowCount(const QModelIndex & = QModelIndex()) const override
    {
        return mRow;
    }

    int columnCount(const QModelIndex & = QModelIndex()) const override
    {
        return mColumn;
    }

    QVariant data(const QModelIndex &index, int role) const override
    {

        if (mResult.size() > index.row())
        {
            if (mResult[0].size() > role)
            {
                return mResult[index.row()][role];
            }
        }

        qDebug() << role << index.column() << index.row();
        return "";
    }

    QHash<int, QByteArray> roleNames() const override
    {
        QHash<int, QByteArray> roleHash;
        for (int var = 0; var < mRoleList.size(); ++var)
        {
            roleHash[var] = mRoleList[var].toUtf8();
        }
        qDebug() << roleHash;
        return roleHash;
    }

    Q_INVOKABLE void clearData()
    {
        mResult.clear();
        mRow = 0;
        mColumn = 0;
    }

    Q_INVOKABLE void getData(const QStringList &keys, const QStringList &values)
    {
        qDebug() << "[UserTableModel::getData]" << keys << values;
        CmdData argcs;
        argcs.func = "getUsersData";
        if (keys.size() != values.size())
        {
            return;
        }
        for (int var = 0; var < keys.size(); ++var)
        {
            argcs.params[keys[var]] = values[var];
        }
        mHandle->handleCmdData(argcs);
    }

signals:
    void signalRecvhandleResult();

public slots:
    void handleResults(const RstData &rstData)
    {
        emit signalRecvhandleResult();
        beginResetModel(); // 开始刷新模型
        if (rstData.retCode == 0)
        {
            mResult = rstData.result;
            mRow = mResult.size();
            qDebug() << "mRow = " << mRow;
            mColumn = mRow > 0 ? mResult[0].size() : 0;
            qDebug() << "mColumn = " << mColumn;

        }
        else
        {
            clearData();
        }
        endResetModel(); // 结束刷新模型
    }

private:
    int mRow;
    int mColumn;
    QStringList mRoleList;
    QVector< QVector<QString> > mResult;
    CDataClass *mHandle;

};

QML文件

import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.0
import UserTableModel 0.1

Rectangle {
    id: rectangle
    visible: true
    width: 960
    height: 540
    border.color: mainwld.rectBdColor

    function clearTableData() {
        tablemodelid.clearData();
    }

    Column {
        id: column
        spacing: mainwld.space
        anchors.fill: parent
        anchors.rightMargin: mainwld.space
        anchors.leftMargin: mainwld.space
        anchors.bottomMargin: mainwld.space
        anchors.topMargin: mainwld.space

        Rectangle {
            id: recthead
            //border.color: mainwld.rectBdColor
            width: parent.width
            height: 40

            Rectangle {
                border.color: mainwld.rectBdColor
                width: 180
                height: 30
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter

                TextInput {
                    id: textInput
                    font.pointSize: 20
                    anchors.fill: parent

                    onEditingFinished: {
                        if (textInput.focus) {
                            tablemodelid.getData(['search'], [textInput.text]);
                            progressBar.visible = true;
                            progressBar.start();
                        }
                        textInput.focus = false;
                    }
                }
            }

        }

        Rectangle {
            id: rectcontent
            y: recthead.height
            width: parent.width
            height: parent.height-recthead.height-mainwld.space
            //border.color: mainwld.rectBdColor

            TableView {
                id: tableView
                visible: true
                anchors.fill: parent

                TableViewColumn {
                    title: "序号"
                    role: "Id"
                    width: tableView.viewport.width*2/24
                }
                TableViewColumn {
                    title: "账号"
                    role: "Name"
                    width: tableView.viewport.width*3/24
                }
                TableViewColumn {
                    title: "员工姓名"
                    role: "Nick"
                    width: tableView.viewport.width*3/24
                }
                TableViewColumn {
                    title: "手机号码"
                    role: "Mobile"
                    width: tableView.viewport.width*3/24
                }
                TableViewColumn {
                    title: "邮箱"
                    role: "Email"
                    width: tableView.viewport.width*3/24
                }
                TableViewColumn {
                    title: "部门"
                    role: "Depart"
                    width: tableView.viewport.width*2/24
                }
                TableViewColumn {
                    title: "岗位"
                    role: "Post"
                    width: tableView.viewport.width*2/24
                }
                TableViewColumn {
                    title: "账号状态"
                    role: "Status"
                    width: tableView.viewport.width*2/24
                }
                TableViewColumn {
                    title: "操作"
                    role: "Operate1"
                    width: tableView.viewport.width*2/24
                }
                TableViewColumn {
                    title: "操作"
                    role: "Operate2"
                    width: tableView.viewport.width*2/24
                }

                model: UserTableModel {
                    id: tablemodelid

                    onSignalRecvhandleResult: {
                        progressBar.setProgressValue(100);
                    }
                }

                Component.onCompleted: {
                    tablemodelid.getData(['index','count'], ['0', '1000000']);
                    progressBar.visible = true;
                    progressBar.start();
                }
            }
        }
    }
}
1.2.4.9. 创建main.qml主页面

main是用户登录成功后跳转的主页面,有以下功能:

  • 显示菜单导航栏
  • 根据菜单显示不同页面
  • 退出登录
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Dialogs 1.3
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.0
import dataclass 1.0 //自定义的类,用作传数据,给qml使用
import MenuTreeModel 0.1
import MyTreeModel 0.1
import QtQml.Models 2.12

Window {
    id: mainwld
    visible: true
    opacity: 1
    width: 960
    height: 540
    minimumWidth: 960
    minimumHeight: 540
    title: windowTitle

    property string windowTitle: ''
    property string rectBdColor: 'lightgray'
    property real space: 5
    property var modelpage: null
    property var chartpage: null
    property var userpage: null
    property var logpage: null

    Login {
        id: loginpage
        anchors.fill: parent
        visible: true

        Component.onCompleted: {
            loginpage.signalShowMsg.connect(showMsg)
        }
    }

    CDataClass {
        id: dataclass_id
    }

    function showMsg(title, text) {
        messageDialog.title = title;
        messageDialog.text = text;
        messageDialog.visible = true;
    }

    function handleStartProgressBar() {
        progressBar.visible = true;
        progressBar.start();
    }

    function handleSignalLoginResult(result)
    {
        progressBar.setProgressValue(100);
        if (result)
        {
            loginpage.visible = false;
            mainwld.windowTitle = 'QML演示系统';
        }
        else
        {
            showMsg('提示', '账号或密码不正确!');
        }
    }

    function handleSignalMeaasge(msg)
    {
        progressBar.setProgressValue(100);
        showMsg('提示', msg);
    }

    Connections
    {
        //qml 连接 c++ 信号
        target: datacls
//        onSignalLoginResult: handleSignalLoginResult(result)
//        onSignalMeaasge: handleSignalMeaasge(msg)
        function onSignalLoginResult(result) {
            handleSignalLoginResult(result);
        }

        function onSignalMeaasge(msg) {
            handleSignalMeaasge(msg)
        }
    }

    Timer {
        id: timeUpdate
        interval: 1000; running: true; repeat: true
        onTriggered: {
            //console.log(Qt.formatDateTime(new Date(), "dddd hh:mm:ss"), new Date().getDay());
            labelTime.text = Qt.formatDateTime(new Date(), "dddd hh:mm:ss");
        }
    }

    Rectangle {
        id: rectangle
        visible: true
        //border.color: mainwld.rectBdColor
        anchors.fill: parent
        anchors.rightMargin: mainwld.space
        anchors.leftMargin: mainwld.space
        anchors.bottomMargin: mainwld.space
        anchors.topMargin: mainwld.space

        Row {
            anchors.fill: parent
            spacing: mainwld.space
            Rectangle {
                id: rectLeft
                border.color: mainwld.rectBdColor
                width: 200
                height: parent.height

                Rectangle {
                    id: rectAvatar
                    width: parent.width
                    height: rectHead.height * 2 + mainwld.space
                    border.color: mainwld.rectBdColor

                    BorderImage {
                        y: 11
                        width: 52
                        height: 53
                        anchors.horizontalCenterOffset: 0
                        anchors.horizontalCenter: parent.horizontalCenter
                        source: "images/head.png"
                    }

                    Label {
                        id: labelTime
                        x: 39
                        y: 79
                        width: 151
                        height: 22
                        text: qsTr("星期二 14:17:15")
                        anchors.horizontalCenterOffset: 0
                        font.pointSize: 12
                        verticalAlignment: Text.AlignVCenter
                        horizontalAlignment: Text.AlignHCenter
                        anchors.horizontalCenter: parent.horizontalCenter
                    }

                }

                Rectangle {
                    id: rectMenu
                    y: rectAvatar.height
                    width: parent.width
                    height: parent.height-rectAvatar.height
                    border.color: mainwld.rectBdColor

                    TreeView {
                        id: treeView
                        frameVisible: false
                        headerVisible: false
                        anchors.fill: parent
                        anchors.rightMargin: mainwld.space
                        anchors.leftMargin: mainwld.space
                        anchors.bottomMargin: mainwld.space
                        anchors.topMargin: mainwld.space

                        //model: MenuTreeModel {}
                        model: MyTreeModel {}

                        itemDelegate: Item {
                            Text {
                                anchors.verticalCenter: parent.verticalCenter
                                color: styleData.textColor
                                elide: styleData.elideMode
                                text: styleData.value
                            }
                        }

                        TableViewColumn {
                            title: "title"
                            role: "title"
                            width: treeView.width-2
                        }

                        onClicked: {
                            console.log('-----------', index, index.parent.row, index.row, index.value);
                            modelpage1.visible = false;
                            chartpage1.visible = false;
                            userpage1.visible = false;
                            logpage1.visible = false;
                            if (index.parent.row === -1) {
                                if (index.row === 0) {

                                }
                            } else if (index.parent.row === 1) {
                                if (index.row === 0) {
                                    modelpage1.visible = true;
                                } else if (index.row === 1) {
                                    chartpage1.visible = true;
                                } else if (index.row === 2) {

                                }
                            } else if (index.parent.row === 2) {
                                if (index.row === 0) {
                                    userpage1.visible = true;
                                } else if (index.row === 1) {
                                    logpage1.visible = true;
                                }
                            }

                        }

                    }

                }
            }

            Column {
                spacing: mainwld.space
                width: parent.width-rectLeft.width-mainwld.space
                height: parent.height

                Rectangle {
                    id: rectHead
                    border.color: mainwld.rectBdColor
                    width: parent.width
                    height: 55

                    Button {
                        id: btnModifyPwd
                        width: 75
                        height: 33
                        text: qsTr("修改密码")
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.right: btnExit.left
                        anchors.rightMargin: mainwld.space

                        MouseArea {
                            anchors.fill: parent
                            onClicked: {

                            }
                        }
                    }
                    Button {
                        id: btnExit
                        width: 75
                        height: 33
                        text: qsTr("退出登录")
                        anchors.verticalCenter: parent.verticalCenter
                        anchors.right: parent.right
                        anchors.rightMargin: mainwld.space

                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                loginpage.visible = true;
                                mainwld.windowTitle = '登录页面';
                                modelpage1.visible = false;
                                chartpage1.visible = false;
                                userpage1.visible = false;
                                logpage1.visible = false;
                            }
                        }
                    }
                }

                Rectangle {
                    id: rectContent
                    border.color: mainwld.rectBdColor
                    width: parent.width
                    height: parent.height-rectHead.height-mainwld.space

                    Models3DPage {
                        id: modelpage1
                        visible: false
                        anchors.fill: parent
                    }

                    ChartsPage {
                        id: chartpage1
                        visible: false
                        anchors.fill: parent
                    }

                    UserPage {
                        id: userpage1
                        visible: false
                        anchors.fill: parent
                    }

                    LogPage {
                        id: logpage1
                        visible: false
                        anchors.fill: parent
                    }

                }
            }
        }
    }

    MessageDialog {
        id: messageDialog
        title: ""
        text: ""
        onAccepted: {
            console.log("And of course you could only agree.")
            visible = false
        }
        Component.onCompleted: visible = false
    }

    MyProgressBar {
        id: progressBar
        z: 2000
        visible: false

        Component.onCompleted: {
            loginpage.signalStartProgressBar.connect(handleStartProgressBar)
        }
    }

    function createComponentMenu(parent, url, name) {
        for (var i in parent.children) {
            var item = parent.children[i];
            //console.log(i, name, item, item.objectName, item.children);
            item.destroy();
        }

        return Qt.createComponent(url).createObject(parent, {
                                                        'objectName': name,
                                                        'visible': true,
                                                        'anchors.fill': parent
                                                    });
    }

    Component.onCompleted: {
        mainwld.windowTitle = '登录页面';
    }
}

1.3. 编译Demo

先配置好编译环境,参考文章win10安装配置Qt for WebAssembly,编译步骤如下:

  1. 打开dos进入项目文件目录Qt5_11QMLDemo,在该目录下创建out目录,进入out目录
    在这里插入图片描述
  2. 运行D:\emsdk-master\emsdk_env.bat设置emsdk环境
    在这里插入图片描述
  3. 运行D:\Qt5.15\5.15.0\Src\qtbase\bin\qmake.exe …\Qt5_11QMLDemo.pro生成Makefile
    在这里插入图片描述
  4. 运行d:\Qt5.15\Tools\mingw810_64\bin\mingw32-make.exe -j8编译
    在这里插入图片描述
  5. 编译完成(要经漫长等待)后查看目录有Qt5_11QMLDemo.js、Qt5_11QMLDemo.wasm、Qt5_11QMLDemo.html、qtloader.js、qtlogo.svg五个文件生成。
    在这里插入图片描述
    在这里插入图片描述
    为了编译操作简单,可以在项目目录Qt5_11QMLDemo下面创建一个build.bat脚本,把上面的命令写入脚本里面,编译时只需要运行脚本即可,脚本内容如下:
start cmd /k "cd /d .\out && d:\emsdk-master\emsdk_env.bat && d:\Qt5.15\5.15.0\Src\qtbase\bin\qmake.exe ..\TestWebAsmQml.pro && d:\Qt5.15\Tools\mingw810_64\bin\mingw32-make.exe -j8 clean && d:\Qt5.15\Tools\mingw810_64\bin\mingw32-make.exe -j8 && python -m http.server"

1.4. 运行部署

demo编译生成了Qt5_11QMLDemo.js、Qt5_11QMLDemo.wasm、Qt5_11QMLDemo.html、qtloader.js、qtlogo.svg五个文件,要运行就需要创建http服务。部署操作如下:

  1. 把生成的5个文件复制到一个目录下(如:D:\webasm)
  2. 把Qt5_11QMLDemo.html改成index.html
  3. 进入D:\webasm目录创建http服务,python -m http.server
  4. 在浏览器地址栏输入:http://127.0.0.1:8000查看运行结果
    在这里插入图片描述
    在这里插入图片描述

1.5. 源码文件

后台源码:https://download.csdn.net/download/yyt593891927/12704767
默认用户名:admin
默认密码:admin

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值