之前写过一篇文章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,编译步骤如下:
- 打开dos进入项目文件目录Qt5_11QMLDemo,在该目录下创建out目录,进入out目录
- 运行D:\emsdk-master\emsdk_env.bat设置emsdk环境
- 运行D:\Qt5.15\5.15.0\Src\qtbase\bin\qmake.exe …\Qt5_11QMLDemo.pro生成Makefile
- 运行d:\Qt5.15\Tools\mingw810_64\bin\mingw32-make.exe -j8编译
- 编译完成(要经漫长等待)后查看目录有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服务。部署操作如下:
- 把生成的5个文件复制到一个目录下(如:D:\webasm)
- 把Qt5_11QMLDemo.html改成index.html
- 进入D:\webasm目录创建http服务,python -m http.server
- 在浏览器地址栏输入:http://127.0.0.1:8000查看运行结果
1.5. 源码文件
后台源码:https://download.csdn.net/download/yyt593891927/12704767
默认用户名:admin
默认密码:admin