diff --git a/.gitignore b/.gitignore index d4f367d..3430194 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ app/ ======= *.tmp >>>>>>> Stashed changes +/.qm/ +/.qtc_clangd/ +/debug/ +/release/ +/.idea/ +/.vscode/ \ No newline at end of file diff --git a/PayloadAPP.pro b/PayloadAPP.pro index 9373f84..f6b1c55 100644 --- a/PayloadAPP.pro +++ b/PayloadAPP.pro @@ -1,8 +1,10 @@ QT += core gui webenginewidgets QT += network +QT += quickwidgets qml greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++17 +# CONFIG += console QMAKE_PROJECT_DEPTH = 0 # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. @@ -24,7 +26,9 @@ SOURCES += \ homepagedlg.cpp \ main.cpp \ mainwindow.cpp \ - qweb.cpp + qweb.cpp \ + rescueload.cpp \ + rescueloadwidget.cpp HEADERS += \ DCFrameCkCmd.h \ @@ -40,7 +44,9 @@ HEADERS += \ global.h \ homepagedlg.h \ mainwindow.h \ - qweb.h + qweb.h \ + rescueload.h \ + rescueloadwidget.h FORMS += \ Src/GDDC/gddcCmdDlg.ui \ @@ -50,7 +56,8 @@ FORMS += \ Src/GDDC/gddcdlg.ui \ homepagedlg.ui \ mainwindow.ui \ - qweb.ui + qweb.ui \ + rescueloadwidget.ui TRANSLATIONS += \ PayloadAPP_zh_CN.ts @@ -63,9 +70,11 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target RESOURCES += \ - mainwindow.qrc + mainwindow.qrc \ + map/places_map.qrc DISTFILES += \ + config.ini \ res/Qss/qss.qss \ style.qss diff --git a/homepagedlg.ui b/homepagedlg.ui index d0a3d31..9b1dff6 100644 --- a/homepagedlg.ui +++ b/homepagedlg.ui @@ -22,10 +22,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -36,7 +36,7 @@ - + @@ -54,10 +54,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed diff --git a/mainwindow.cpp b/mainwindow.cpp index 624bf3b..9d79a04 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -33,7 +33,7 @@ MainWindow::MainWindow(QWidget *parent) QString style = QLatin1String(file.readAll()); qApp->setStyleSheet(style); - qDebug()<setPage(new CustomWebEnginePage()); + mWeb->load(QUrl("http://192.168.150.1")); // mWeb = new QWebEngineView(this); + // 设置栈窗口,多页面共享同一窗口 + ui->stackedWidget->addWidget(mWeb); + ui->stackedWidget->addWidget(m_GDDCdlg); + ui->stackedWidget->addWidget(m_HomePagedlg); + ui->stackedWidget->addWidget(m_rescueLoadWidget); - ui->SubPage->addWidget(mWeb); - ui->SubPage->addWidget(m_GDDCdlg); - ui->SubPage->addWidget(m_HomePagedlg); - - // mWeb->show(); - mWeb->hide(); - m_HomePagedlg->hide(); - m_HomePagedlg->show(); - + // 初始栈窗口显示主页 + ui->stackedWidget->setCurrentWidget(m_HomePagedlg); } void MainWindow::initButton() @@ -234,9 +235,7 @@ void MainWindow::toolButton_clicked() { changeBtnColor(1); //m_HomePagedlg->move(130,80); - m_HomePagedlg->show(); //显示窗口 - m_GDDCdlg->hide(); - mWeb->hide(); + ui->stackedWidget->setCurrentWidget(m_HomePagedlg); process->kill(); } //光电吊舱 @@ -245,27 +244,24 @@ void MainWindow::toolButton_2_clicked() changeBtnColor(2); //m_GDDCdlg->move(130,80); m_GDDCdlg->show(); //显示窗口 - m_HomePagedlg->hide(); - mWeb->hide(); + ui->stackedWidget->setCurrentWidget(m_GDDCdlg); } //L链 void MainWindow::toolButton_3_clicked() { changeBtnColor(3); - mWeb->show(); - m_HomePagedlg->hide(); - m_GDDCdlg->hide(); + // mWeb->load(QUrl("http://192.168.1.10")); + ui->stackedWidget->setCurrentWidget(mWeb); } //自组网 void MainWindow::toolButton_4_clicked() { changeBtnColor(4); - m_HomePagedlg->hide(); - m_GDDCdlg->hide(); - mWeb->load(QUrl("http://192.168.1.10")); - mWeb->show(); + // mWeb->load(QUrl("http://192.168.1.10")); + // mWeb->show(); + ui->stackedWidget->setCurrentWidget(mWeb); //process->kill(); } @@ -273,23 +269,18 @@ void MainWindow::toolButton_4_clicked() void MainWindow::toolButton_5_clicked() { changeBtnColor(5); - m_HomePagedlg->hide(); - m_GDDCdlg->hide(); - mWeb->load(QUrl("http://192.168.150.1")); - mWeb->show(); - + // mWeb->load(QUrl("http://192.168.150.1")); + // mWeb->show(); + ui->stackedWidget->setCurrentWidget(mWeb); } //Ku卫通 void MainWindow::toolButton_6_clicked() { changeBtnColor(6); - m_HomePagedlg->hide(); - m_GDDCdlg->hide(); - - mWeb->load(QUrl("http://192.168.0.2")); - mWeb->show(); - + // mWeb->load(QUrl("http://192.168.0.2")); + // mWeb->show(); + ui->stackedWidget->setCurrentWidget(mWeb); } @@ -297,21 +288,14 @@ void MainWindow::toolButton_6_clicked() void MainWindow::toolButton_7_clicked() { changeBtnColor(7); - m_HomePagedlg->hide(); - m_GDDCdlg->hide(); - // mWeb->stop(); - mWeb->load(QUrl("https://baidu.com")); - mWeb->show(); - - // + ui->stackedWidget->setCurrentWidget(m_rescueLoadWidget); } //三维建模 void MainWindow::toolButton_8_clicked() { changeBtnColor(8); - m_HomePagedlg->hide(); - m_GDDCdlg->hide(); + ui->stackedWidget->setCurrentWidget(mWeb); process->setWorkingDirectory("D:/QTdemo/playerApp/app"); // 设置工作目录 process->start("D:/QTdemo/playerApp/app/XCOM V2.0.exe"); // 启动exe并传递参数 } diff --git a/mainwindow.h b/mainwindow.h index 6ea101b..4c2257c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -4,7 +4,7 @@ #include #include "Src/GDDC/gddcdlg.h" #include "homepagedlg.h" - +#include "rescueloadwidget.h" #include #include #include @@ -52,8 +52,10 @@ private slots: public: GDDCdlg *m_GDDCdlg; HomePageDlg *m_HomePagedlg; + RescueLoadWidget *m_rescueLoadWidget; //QWeb *m_qWeb; QWebEngineView *mWeb; + private: QProcess *process; QString exeDirPathName = ""; diff --git a/mainwindow.ui b/mainwindow.ui index 920c05f..87d2b05 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -178,6 +178,30 @@ + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + diff --git a/map/marker.png b/map/marker.png new file mode 100644 index 0000000..2116dfd Binary files /dev/null and b/map/marker.png differ diff --git a/map/places_map.qml b/map/places_map.qml new file mode 100644 index 0000000..fd73c3f --- /dev/null +++ b/map/places_map.qml @@ -0,0 +1,201 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [Imports] +import QtQuick +import QtPositioning +import QtLocation + +//! [Imports] +MapView { + anchors.fill: parent + id: mapview + //! [Initialize Plugin] + map.plugin: Plugin { + id: myPlugin + name: "osm" + //specify plugin parameters if necessary + PluginParameter { + name: "osm.mapping.providersrepository.disabled" + value: "true" + } + + PluginParameter { + name: "osm.mapping.providersrepository.address" + value: "http://maps-redirect.qt.io/osm/5.6/" + } + + //PluginParameter {...} + //... + } + map.activeMapType: map.supportedMapTypes[map.supportedMapTypes.length - 2] + map.center: QtPositioning.coordinate(30.67, 120.07) + map.zoomLevel: 10 + // map.activeMapType: MapType.HybridMap + // map.onSupportedMapTypesChanged: { + // map.activeMapType = map.supportedMapTypes[map.supportedMapTypes.length - 1] + // } + // Component.onCompleted: {console.log(map.supportedMapTypes)} + + + // 搜救信息位置显示,与页面列表信息同步 + // signal locationClicked(string imsi) + signal indexClicked(int row) + + MapItemView { + id: itemview + parent: mapview.map + // model: testModel + model: imsiDataModel + z:2 + delegate: MapQuickItem { + id: location + // property bool isSelected: false // 标记是否选中 + property string imsi: model.imsi + property double latitude: model.latitude + property double longitude: model.longitude + coordinate: QtPositioning.coordinate(latitude, longitude) + //图像底部中心对齐坐标点 + anchorPoint.x: image.width * 0.5 + anchorPoint.y: image.height + + sourceItem: Item { + width: image.width + height: image.height + Image { + id: image + source: "marker" + property double imageWidth: 20 + property double imageHeight: 25 + width: itemMouse.containsMouse ? imageWidth*1.25 : imageWidth + height: itemMouse.containsMouse ? imageHeight*1.25: imageHeight + Text { + anchors.centerIn: parent + text: (model.row + 1) + font.pixelSize: image.width*0.5 + color: "black" + horizontalAlignment: Text.AlignRight // 设置水平对齐方式为右对齐 + verticalAlignment: Text.AlignBottom // 设置垂直对齐方式为底部对齐 + } + } + + MouseArea { + id: itemMouse + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true + cursorShape: Qt.PointingHandCursor + onClicked: { + indexClicked(model.row) + } + } + } + } + } + + + //点击外部列表时,地图上显示被选中的图标 + ListModel { + id: listSelectModel + } + + MapItemView { + parent: mapview.map + // model: testModel + model: listSelectModel + z:3 + delegate: MapQuickItem { // 当前选中的标记 + z: itemview.z + 1 + // property string imsi: model.imsi + property double latitude: model.latitude + property double longitude: model.longitude + property string imagesource: "selectlocation.png" + property bool selected: true + coordinate: QtPositioning.coordinate(latitude, longitude) + //图像底部中心对齐坐标点 + anchorPoint.x: selectimgae.width * 0.5 + anchorPoint.y: selectimgae.height + sourceItem: Item { + Image { + id: selectimgae + source: imagesource + property double imageWidth: 30 + property double imageHeight: 30 + width: imageWidth + height: imageHeight + } + } + } + } + // // 外部信号发射时创建图标 + Connections { + target: imsiSelectModel + function onSelectionChanged (selected, deselected) { + var indexs = imsiSelectModel.selectedIndexes + // if (indexs.length === 0) { + listSelectModel.clear() + // } + for (var i=0; i + + marker.png + places_map.qml + selectlocation.png + + + diff --git a/map/selectlocation.png b/map/selectlocation.png new file mode 100644 index 0000000..61bfedd Binary files /dev/null and b/map/selectlocation.png differ diff --git a/res/Qss/qss.qss b/res/Qss/qss.qss index 3d1a2e1..0e4698d 100644 --- a/res/Qss/qss.qss +++ b/res/Qss/qss.qss @@ -200,8 +200,7 @@ QFrame#frameGDDCPage QTableView { color: rgb(255, 255, 255); - font-family:"微软雅黑"; - font-size:9pt; + font: 12pt "Microsoft YaHei UI"; border:none; background-color: rgba(50, 50, 50, 50); /*rgba(150, 223, 155, 50)*/ selection-background-color: rgba(96, 96, 96, 0); /*选中区域的背景色,设置成透明96,96,96*/ @@ -215,7 +214,7 @@ QTableWidget::item { /*设置单元格背景颜色*/ QTableWidget::item:selected { - /*background-color:#CCFFE5;*/ + background-color:#CCFFE5; } /*设置表头背景颜色、角落颜色*/ QHeaderView::section,QTableCornerButton:section @@ -232,7 +231,7 @@ QHeaderView::section,QTableCornerButton:section } QHeaderView::section:checked { - background-color: rgb(96, 96, 96 ); + background-color: rgb(11, 11, 11); } /*设置表格中的水平线和垂直线*/ QTableWidget::horizontalHeader diff --git a/rescueload.cpp b/rescueload.cpp new file mode 100644 index 0000000..be57de2 --- /dev/null +++ b/rescueload.cpp @@ -0,0 +1,358 @@ +#include "rescueload.h" + +#include +#include +#include +#include +#include +#include +#include + +// ==================== ImsiData数据类 start ======================= +QHash ImsiData::operatorMap = { + {"00", "移动"}, {"02", "移动"}, {"04", "移动"}, {"07", "移动"}, {"08", "移动"}, {"13", "移动"}, + {"01", "联通"}, {"06", "联通"}, {"09", "联通"}, {"10", "联通"}, + {"03", "电信"}, {"05", "电信"}, {"11", "电信"}, {"12", "电信"}, + {"15", "广电"}, {"20","铁通"} +}; +ImsiData::ImsiData(const QJsonObject& json) +{ + if (json.contains("latitude")) + this->latitude = json.value("latitude").toString(); + if (json.contains("longitude")) + this->longitude = json.value("longitude").toString(); + if (json.contains("altitude")) + this->altitude = json.value("altitude").toInt(); + if (json.contains("create_date")) + this->createDate = json.value("create_date").toString(); + if (json.contains("rssi")) + this->rssi = json.value("rssi").toInt(); + if (json.contains("fcn")) + this->fcn = json.value("fcn").toString(); + if (json.contains("imsi")) + this->imsi = json.value("imsi").toString(); + this->operatorName = getOperatorNameByImsi(this->imsi); // 第4、5位 +} + +ImsiData::ImsiData(const ImsiData& from) +{ + this->latitude = from.latitude; + this->longitude = from.longitude; + this->altitude = from.altitude; + this->createDate = from.createDate; + this->rssi = from.rssi; + this->fcn = from.fcn; + this->imsi = from.imsi; + this->operatorName = from.operatorName; +} + +QString ImsiData::getOperatorNameByImsi(QString imsi) +{ + return operatorMap.value(imsi.mid(3,2)); +} + + +// ==================== ImsiData数据类 end ======================= + + +// ===================== ImsiTableModel start ================================== + +ImsiTableModel::ImsiTableModel(QObject *parent) + : QAbstractTableModel(parent) +{ + // Set names to the role name hash container (QHash) + // model.name, model.hue, model.saturation, model.brightness + m_roleNames[imsiRole] = "imsi"; + m_roleNames[latitudeRole] = "latitude"; + m_roleNames[longitudeRole] = "longitude"; + m_roleNames[dateRole] = "createDate"; + m_roleNames[altitudeRole] = "altitude"; + m_roleNames[rssiRole] = "rssi"; + m_roleNames[fcnRole] = "fcn"; + m_roleNames[operatorRole] = "operator"; + + m_horizontalHeader = {"IMSI", "运营商"}; + + m_defaultDir = QString("./搜救信息"); // 默认目录 + // 检查并创建默认目录 + QDir dir(m_defaultDir); + if (!dir.exists()) { + dir.mkpath("."); + } +} + +ImsiTableModel::~ImsiTableModel() +{ + QString timeStr = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss"); + QString defaultPath = QString("%1/搜救信息%2.csv").arg(getSaveDir(), timeStr); + saveDataListToCsv(defaultPath); +} + +QVariant ImsiTableModel::headerData(int section, Qt::Orientation orientation, int role) const + +{ + if(orientation == Qt::Horizontal) { //水平表头 + if(Qt::DisplayRole == role && section < m_horizontalHeader.count()) { + return m_horizontalHeader[section]; + } + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +int ImsiTableModel::rowCount(const QModelIndex &parent) const +{ + // For list models only the root node (an invalid parent) should return the list's size. For all + // other (valid) parents, rowCount() should return 0 so that it does not become a tree model. + Q_UNUSED(parent) + return m_dataMap.count(); +} + +int ImsiTableModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 2; +} + + +QVariant ImsiTableModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + int column = index.column(); + int row = index.row(); + + QString imsi = m_imsiList.at(row); + ImsiData data = m_dataMap.value(imsi); + switch (role) { + case Qt::DisplayRole: + if (column == 1) { + return data.operatorName; + } else if (column == 0) { + return data.imsi; + } + break; + + // 对齐处理 + case Qt::TextAlignmentRole: + return Qt::AlignCenter; + break; + case imsiRole: + return data.imsi; + case latitudeRole: + return data.latitude; + case longitudeRole: + return data.longitude; + case dateRole: + return data.createDate; + case altitudeRole: + return data.altitude; + case rssiRole: + return data.rssi; + case fcnRole: + return data.fcn; + default: + break; + } + return QVariant(); +} + +QModelIndex ImsiTableModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid() || row < 0 || column < 0 || row >= rowCount() || column >= columnCount()) + return QModelIndex(); + + // Create and return a QModelIndex for the given row and column + return createIndex(row, column); +} + +void ImsiTableModel::setDataMap(const QHash &dataMap) +{ + beginResetModel(); + this->m_dataMap = dataMap; + this->m_imsiList = dataMap.keys(); + for (int i = 0; i < m_imsiList.size(); ++i) { + m_imsiIndex.insert(m_imsiList[i], i); + } + endResetModel(); +} + +void ImsiTableModel::updateData(const ImsiData &data) +{ + QString imsi = data.imsi; + if (m_dataMap.contains(imsi)) { + // 更新现有数据 + m_dataMap[imsi] = data; + int row = m_imsiIndex.value(imsi); + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + } else { + // 插入新数据 + beginInsertRows(QModelIndex(), m_imsiList.size(), m_imsiList.size()); + m_imsiList.append(imsi); + m_dataMap.insert(imsi, data); + endInsertRows(); + } +} + +int ImsiTableModel::getRole(QString rolename) +{ + if (rolename == "latitude") + return latitudeRole; + else if (rolename == "longitude") + return longitudeRole; + else + return -1; +} + +QHash ImsiTableModel::roleNames() const +{ + return m_roleNames; +} + +void ImsiTableModel::saveDataListToCsv(const QString& filePath, const QString& delimiter) { + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning("Could not open file for writing."); + return; + } + QTextStream out(&file); + QStringList row; + row << "IMSI" << "运营商" << "经度" << "纬度" << "海拔高度" << "频点" << "强度" << "记录时间"; // CSV header + out << row.join(delimiter) << "\n"; + for (const QString& imsi : m_imsiList) { + row.clear(); + ImsiData data = m_dataMap.value(imsi); + row << data.imsi + << data.operatorName + << data.longitude + << data.latitude + << QString::number(data.altitude) + << data.fcn + << QString::number(data.rssi) + << data.createDate; + out << row.join(delimiter) << "\n"; + } + file.close(); +} +// ======================= ImsiTableModel end===================== + + +// ======================= RescuLoad start=========================== +RescueLoad::RescueLoad(QObject *parent) + :QObject(parent), remoteIp("10.10.10.50"), remotePort(8056) +{ + // remoteIp = "127.0.0.1"; +} + +RescueLoad::~RescueLoad() {} + + +void RescueLoad::startCommunication() +{ + udpSocket = new QUdpSocket(this); + connect(udpSocket, &QUdpSocket::readyRead, this, &RescueLoad::readData); +} + + +void RescueLoad::sendMessage(const QString &message) +{ + QByteArray datagram = message.toUtf8(); + udpSocket->writeDatagram(datagram, QHostAddress(remoteIp), remotePort); +} + +void RescueLoad::sendMessage(const QByteArray &datagram) +{ + udpSocket->writeDatagram(datagram, QHostAddress(remoteIp), remotePort); +} + +void RescueLoad::readData() +{ + while (udpSocket->hasPendingDatagrams()) { + QByteArray datagram; + datagram.resize(udpSocket->pendingDatagramSize()); + QHostAddress sender; + quint16 senderPort; + udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); + // qDebug() << "Received response from" << sender.toString() << ":" << senderPort; + // qDebug() << "Data:" << datagram; + handleMessage(datagram); + } +} +void RescueLoad::handleMessage(const QByteArray &datagram) { + + QJsonParseError json_error; + QJsonDocument jsonDoc(QJsonDocument::fromJson(datagram, &json_error)); + if(json_error.error != QJsonParseError::NoError || !jsonDoc.isObject()){ + qDebug() << "json error!"; + return; + } + + QJsonObject baseObj = jsonDoc.object(); + + QJsonValue vType = baseObj.value("type"); + QJsonValue vMsg = baseObj.value("msg"); + // if (!vType.isDouble() || !vMsg.isString()) return; + + int type = vType.toInt(); + QString msg = vMsg.toString(); + // if ("success" != msg) return; + + switch (type) { + case 1: // 4G心跳包 + // todo + sendMessage(QString("{\"cmd\": \"heartbeat\"}")); // 定时回心跳,或者单独开定时器 + break; + case 2: // 5G心跳包 + sendMessage(QString("{\"cmd\": \"heartbeat\"}")); + break; + case 3:{ // IMSI数据 + QJsonArray array = baseObj.value("data").toArray(); + + for (int i = 0; i < array.size(); i++){ + QJsonObject json = array.at(i).toObject(); + ImsiData data(json); + // ImsiData data; + emit dataUpdate(data); + } + break; + } + default: + break; + } +} + +void RescueLoad::syncTime() +{ + QString timeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); + QJsonObject timeObj; + timeObj.insert("datetime", timeStr); + + QJsonObject sendObj; + sendObj.insert("cmd", "syncTime"); + sendObj.insert("params", timeObj); + + sendMessage(QJsonDocument(sendObj).toJson(QJsonDocument::Compact)); +} + +/** + * @brief RescueLoad::sendNameList 设置黑白名单 + * @param action: 'reject'/'block'/'reject2lte'/'release2lte', #对其他imsi动作, + * @param type: 'ADD'/'SUB'/'SAVE' #名单配置模式,ADD:增加名单,SUB:删除名单,SAVE:名单替换 + * @param wnamelist: IMSI字符串列表 + * @param bnamelist: IMSI字符串列表 + */ +void RescueLoad::sendNameList(const QString action, const QString type, const QList &wnamelist, const QList &bnamelist) +{ + QJsonObject paramsObj; + paramsObj.insert("action", action); + paramsObj.insert("type", type); + paramsObj.insert("wnamelist", QJsonArray::fromStringList(wnamelist)); + paramsObj.insert("bnamelist", QJsonArray::fromStringList(bnamelist)); + + QJsonObject sendObj; + sendObj.insert("cmd", "msg_set_namelist"); + sendObj.insert("params", paramsObj); + + sendMessage(QJsonDocument(sendObj).toJson(QJsonDocument::Compact)); +} + +// ============================ RescueLoad end================================= diff --git a/rescueload.h b/rescueload.h new file mode 100644 index 0000000..97471b5 --- /dev/null +++ b/rescueload.h @@ -0,0 +1,122 @@ +#ifndef RESCUELOAD_H +#define RESCUELOAD_H + +#include "qjsonarray.h" +#include "qtmetamacros.h" +#include +#include +#include +#include +#include + +/** + * @brief 搜救载荷通信和数据处理 + * 包含数据元素类 ImsiData: 存储搜救信息单元 + * 数据模型类 ImsiTableModel: Imsi的数据容器,向表格和地图提供数据 + * 载荷通信类 RescueLoad: 发送和接收数据 + */ + +//搜救载荷移动身份识别码数据 +class ImsiData +{ +public: + static QHash operatorMap; + static QString getOperatorNameByImsi(QString imsi); +public: + ImsiData(){}; + ImsiData(const QJsonObject& json); + ImsiData(const ImsiData& from); + ~ImsiData(){}; +public: + QString imsi; + QString operatorName; + QString longitude; + QString latitude; + int altitude; // 海拔高度 + QString fcn; // 频点/BAND + int rssi; // 强度 + QString createDate; + +} /*ImsiData*/; + +class ImsiTableModel : public QAbstractTableModel +{ + Q_OBJECT +public: + enum RoleNames { + imsiRole = Qt::UserRole, + latitudeRole = Qt::UserRole+2, + longitudeRole = Qt::UserRole+3, + altitudeRole = Qt::UserRole+4, + dateRole = Qt::UserRole+5, + rssiRole = Qt::UserRole+6, + fcnRole = Qt::UserRole+7, + operatorRole = Qt::UserRole+8, + + }; +public: + explicit ImsiTableModel(QObject *parent = nullptr); + virtual ~ImsiTableModel(); + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_INVOKABLE QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; + + void setDataMap(const QHash& dataMap ); + void updateData(const ImsiData &data); + QHash& getDataMap(){ + return m_dataMap; + } + QString getSaveDir() {return m_defaultDir;} + Q_INVOKABLE int getRole(QString); + void saveDataListToCsv(const QString &filePath, const QString &delimiter = ","); +signals: + void detailUpdated(QString, double, double); +protected: + // return the roles mapping to be used by QML + virtual QHash roleNames() const override; +private: + QHash m_dataMap; // 用于存储数据,不需要排序时用QHash即可 + QList m_imsiList; // 用于同步TableView的行索引 + QHash m_imsiIndex; // 用于更新数据时获取行索引 + QHash m_roleNames; + QStringList m_horizontalHeader; + QString m_defaultDir; +}; + + +class RescueLoad: public QObject +{ + Q_OBJECT + +public: + RescueLoad(QObject *parent); + ~RescueLoad(); + + + // udp通信 +public: + void startCommunication(); // 在工作线程中运行 + void sendMessage(const QString &message); + void sendMessage(const QByteArray &datagram); + void syncTime(); + void sendNameList(const QString action, const QString type, const QList &wnamelist, const QList &bnamelist); + +signals: + void dataUpdate(ImsiData data); +private slots: + void readData(); + +public : + QString remoteIp; + int remotePort; + +private: + QUdpSocket* udpSocket; + void handleMessage(const QByteArray &datagram); + + +}; + +#endif // RESCUELOAD_H diff --git a/rescueloadwidget.cpp b/rescueloadwidget.cpp new file mode 100644 index 0000000..3563cf3 --- /dev/null +++ b/rescueloadwidget.cpp @@ -0,0 +1,363 @@ +#include "rescueloadwidget.h" +#include +#include "qjsonarray.h" +#include "ui_rescueloadwidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +RescueLoadWidget::RescueLoadWidget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::RescueLoadWidget) +{ + ui->setupUi(this); + initialModel(); // 初始化数据model + initialUi(); // 初始化表格样式 + + // 数据交互和视图设置 + rescueLoad = new RescueLoad(this); + rescueLoad->startCommunication(); // todo: 转移到子线程中运行 + // rescueLoad->sendMessage(QString("Hello")); + + connect(rescueLoad, &RescueLoad::dataUpdate, rescueLoad, [=](ImsiData data){ + imsiDataModel->updateData(data); + }); + + // 地图组件, 必须在RescueLoad和ImsiDataModel初始化后加载 + openQLocationMap(); +} + +RescueLoadWidget::~RescueLoadWidget() +{ + ui->deviceDetail->clearContents(); + delete protoItem; + delete standardItem; + delete ui; +} + +// 使用QLocation加载地图模块 (fixme: 能显示地图,但是无法向qml传递数据) +void RescueLoadWidget::openQLocationMap() { + QQuickView *qmlView = new QQuickView(); + qmlView->rootContext()->setContextProperty("imsiDataModel", imsiDataModel); // 注意,先绑定属性,再设置qml + qmlView->rootContext()->setContextProperty("imsiSelectModel", ui->deviceview->selectionModel()); // 注意,先绑定属性,再设置qml + qmlView->setSource(QUrl("qrc:/places_map.qml")); + QWidget *container = QWidget::createWindowContainer(qmlView, this); + ui->mapGroup->setLayout(new QVBoxLayout); + ui->mapGroup->layout()->addWidget(container); + qDebug()<<"搜救地图开启"<getDataMap().keys().size(); + // 接收QML的信号 + QObject* object = (QObject* )qmlView->rootObject(); + // connect(object, SIGNAL(locationClicked(QString)), this, SLOT(updateDetailTable(QString))); + QObject::connect(object, SIGNAL(indexClicked(int)), this, SLOT(tableIndexClicked(int))); +} + +void RescueLoadWidget::tableIndexClicked(int row){ + // qDebug()<deviceview->clicked(imsiDataModel->index(row)); +} + +void RescueLoadWidget::initialUi() +{ + // 设备imsi列表样式 + setTable(ui->deviceview); + setTable(ui->bnameview); + setTable(ui->wnameview); + // 单元格样式 + protoItem = new QTableWidgetItem; + protoItem->setTextAlignment(Qt::AlignCenter); + standardItem = new QStandardItem; + standardItem->setTextAlignment(Qt::AlignCenter); + QFont font = QFont("Microsoft YaHei UI", 12); + protoItem->setFont(font); + standardItem->setFont(font); + setDeviceDetail(ui->deviceDetail); + +} + +void RescueLoadWidget::setTable(QTableView* tableView) { + // 设置样式表 + tableView->setStyleSheet( + "QTableView::item {" + " background-color: transparent;" + " color: white;" + "}" + ); + QFont font = QFont("Microsoft YaHei UI", 12); + + tableView->horizontalHeader()->setFont(font); + tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); // 可拉伸填充 + tableView->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter); //表头信息显示居中 + tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);//对第2列单独设置固定宽度 + tableView->setColumnWidth(1, 100);//设置固定宽度 + + tableView->verticalHeader()->setFont(font); + tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + tableView->verticalHeader()->setDefaultAlignment(Qt::AlignCenter); + tableView->verticalHeader()->setFixedWidth(35); // 行表头(序号)设置固定宽度 + // tableWidget->verticalHeader()->hide(); //将默认序号隐藏 + tableView->setShowGrid(true); + tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); // 不可编辑 + + // 选中行更新详细信息 + tableView->setSelectionBehavior(QAbstractItemView::SelectRows); // 选中行 + + // 单击后选中并显示详情,再次点击取消选中 + connect(tableView, &QTableView::clicked, this, [=](QModelIndex index) { + static int preRow = -1; // 记录前一个点击事件所在行 + int curRow = index.row(); + QModelIndex imsiIndex = index.model()->index(curRow, 0); // note 第一列必须为imsi值 + QString imsi =tableView->model()->data(imsiIndex).toString(); + QItemSelectionModel* selectModel = tableView->selectionModel(); + if (curRow == preRow) { + // 取消选中状态 + if (selectModel->isSelected(index)) { + selectModel->select(index, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); + preRow = -1; //取消后重置,否则无法选中 + return; + } + } + selectModel->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); + preRow = curRow; + + if (tableView == ui->deviceview){ + updateDetailTable(imsi); + } + }); + + // 选中行更新名单编辑区 + connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, tableView, [=]() { + QModelIndexList selectedIndexes = tableView->selectionModel()->selectedIndexes(); + QAbstractItemModel* model = tableView->model(); + ui->namelistEdit->clear(); + + QString selectedText; + for (const QModelIndex &index : selectedIndexes) { + if (index.column() == 0) { + selectedText = model->data(index).toString(); + ui->namelistEdit->appendPlainText(selectedText.trimmed()); + } + } + ui->namelistEdit->show(); + // qDebug() << "选择状态变更: selcted: " << selectedIndexes; + }); +} + + + + +void RescueLoadWidget::initialModel() +{ + QStringList headers = QStringList()<<"IMSI"<<"运营商"; + wnamelistModel = new QStandardItemModel(this); + wnamelistModel->setHorizontalHeaderLabels(headers); + wnamelistModel->setColumnCount(2); + ui->wnameview->setModel(wnamelistModel); + + bnamelistModel = new QStandardItemModel(this); + bnamelistModel->setHorizontalHeaderLabels(headers); + bnamelistModel->setColumnCount(2); + ui->bnameview->setModel(bnamelistModel); + + imsiDataModel = new ImsiTableModel(this); + ui->deviceview->setModel(imsiDataModel); +} + + +void RescueLoadWidget::setDeviceDetail(QTableWidget* deviceDetail) { + // 设备详情表样式 + deviceDetail->setStyleSheet( + "QTableWidget::item {" + " background-color: transparent;" + " color: white;" + "}" + ); + deviceDetail->verticalHeader()->setFont(QFont("Microsoft YaHei UI", 12)); + deviceDetail->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + deviceDetail->verticalHeader()->setDefaultAlignment(Qt::AlignCenter); + deviceDetail->setEditTriggers(QAbstractItemView::NoEditTriggers); // 不可编辑 + deviceDetail->setShowGrid(true); + // 初始化 + for (int i=0; irowCount(); i++) { + QTableWidgetItem *item = protoItem->clone(); + deviceDetail->setItem(i, 0, item); + } + deviceDetail->show(); + qDebug() << "设备详细信息初始化完成"; +} + +// 更新指定imsi的详细信息 +void RescueLoadWidget::updateDetailTable(const QString imsi) +{ + const QHash& dataMap = imsiDataModel->getDataMap(); + if (!dataMap.contains(imsi)) return; + ImsiData data = imsiDataModel->getDataMap().value(imsi); + // qDebug()<<"更新详细信息start: " << imsi; + emit imsiDataModel->detailUpdated(imsi, data.longitude.toDouble(), data.latitude.toDouble()); + ui->deviceDetail->item(0, 0)->setText(data.imsi); + ui->deviceDetail->item(1, 0)->setText(data.operatorName); + ui->deviceDetail->item(2, 0)->setText(data.longitude); + ui->deviceDetail->item(3, 0)->setText(data.latitude); + ui->deviceDetail->item(4, 0)->setText(QString::number(data.altitude)); + ui->deviceDetail->item(5, 0)->setText(data.fcn); + ui->deviceDetail->item(6, 0)->setText(QString::number(data.rssi)); + ui->deviceDetail->item(7, 0)->setText(data.createDate); + ui->deviceDetail->show(); + + // qDebug()<<"更新详细信息end: " << imsi; +} + +slots void RescueLoadWidget::on_start4G_clicked() +{ + // 开启4g功放 + rescueLoad->sendMessage(QString("{\"cmd\": \"startCell\"}")); + rescueLoad->sendMessage(QString("{\"cmd\": \"heartbeat\"}")); + // 时间同步 + rescueLoad->syncTime(); + +} + + +slots void RescueLoadWidget::on_stop4G_clicked() +{ + rescueLoad->sendMessage(QString("{\"cmd\": \"stopCell\"}")); +} + + +slots void RescueLoadWidget::on_start5G_clicked() +{ + // 开启5g功放 + rescueLoad->sendMessage(QString("{\"cmd\": \"startCell_5g\"}")); + rescueLoad->sendMessage(QString("{\"cmd\": \"heartbeat\"}")); + // 时间同步 + rescueLoad->syncTime(); +} + + +slots void RescueLoadWidget::on_stop5G_clicked() +{ + rescueLoad->sendMessage(QString("{\"cmd\": \"stopCell_5g\"}")); +} + + +slots void RescueLoadWidget::on_addblistBtn_clicked() +{ + QList namelistToSend = getNameListEditList(); // 输入量,用于更新namelist + QString type = ui->namelistConfigType->currentText().toUpper(); + QString sendType; + if (type.startsWith("ADD")) { + sendType = "ADD"; + } else if (type.startsWith("SUB")) { + sendType = "SUB"; + } else if (type.startsWith("SAVE")) { + sendType = "SAVE"; + } else { + return; + } + rescueLoad->sendNameList("block", sendType, QList(), namelistToSend); + updateWBnamelist("B", sendType, namelistToSend); +} + +slots void RescueLoadWidget::on_addwlistBtn_clicked() +{ + QList namelistToSend = getNameListEditList(); // 输入量,用于更新namelist + QString type = ui->namelistConfigType->currentText().toUpper(); + QString sendType; + if (type.startsWith("ADD")) { + sendType = "ADD"; + } else if (type.startsWith("SUB")) { + sendType = "SUB"; + } else if (type.startsWith("SAVE")) { + sendType = "SAVE"; + } else { + return; + } + rescueLoad->sendNameList("block", sendType, namelistToSend, QList()); + updateWBnamelist("W", sendType, namelistToSend); +} + +/** 获取编辑区的imsi列表*/ +QList RescueLoadWidget::getNameListEditList(){ + + QTextBlock block = ui->namelistEdit->document()->firstBlock(); + QList namelist; + while (block.isValid()) { + QString tmp = block.text().trimmed(); + if (! tmp.isEmpty()) { + namelist.append(tmp); + } + block = block.next(); + } + return namelist; +} + +// 更新黑名单和白名单的数据与显示 +void RescueLoadWidget::updateWBnamelist(QString listType, QString sendType, QList &namelistToUpdate) +{ + QStandardItemModel *namelistModel = listType=="W"? wnamelistModel: bnamelistModel; + QSet &namelistToShow = listType=="W"? wnamelist : bnamelist; + + // 更新黑/白名单列表 + if (sendType == "ADD") { // 新增 + for(const QString &imsi: namelistToUpdate) { + namelistToShow.insert(imsi); + } + } else if (sendType == "SUB") { // 删除 + for(const QString &imsi: namelistToUpdate) { + namelistToShow.remove(imsi); + } + } else if (sendType == "SAVE") { // 替换现有在列表 + namelistToShow.clear(); + for(const QString &imsi: namelistToUpdate) { + namelistToShow.insert(imsi); + } + } + + // 列表显示更新 + namelistModel->clear(); + namelistModel->setColumnCount(2); + namelistModel->setRowCount(namelistToShow.size()); + namelistModel->setHorizontalHeaderLabels(QStringList()<<"IMSI"<<"运营商"); + int i = 0; + for (const QString &imsi : namelistToShow) { + QStandardItem *item1 =standardItem->clone(); + QStandardItem *item2 =standardItem->clone(); + item1->setText(imsi); + item2->setText(ImsiData::getOperatorNameByImsi(imsi)); + namelistModel->setItem(i, 0, item1); + namelistModel->setItem(i, 1, item2); + i ++ ; + } + qDebug() << "名单数据更新end"<< namelistModel->rowCount(); +} + + + +void RescueLoadWidget::on_ipportBtn_clicked() +{ + QString newIp = ui->ipEdit->text().trimmed(); + int newPort = ui->portEdit->text().trimmed().toInt(); + rescueLoad->remoteIp = newIp; + rescueLoad->remotePort = newPort; +} + + +void RescueLoadWidget::on_saveFileButton_clicked() +{ + QString timeStr = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss"); + QString defaultPath = QString("%1/搜救信息%2.csv").arg(imsiDataModel->getSaveDir(), timeStr); + QString filePath = QFileDialog::getSaveFileName(this, tr("保存文件"), defaultPath, tr("CSV 文件 (*.csv)")); + qDebug()<< filePath; + if (!filePath.isEmpty()) { + imsiDataModel->saveDataListToCsv(filePath); + } +} + diff --git a/rescueloadwidget.h b/rescueloadwidget.h new file mode 100644 index 0000000..b49a7f4 --- /dev/null +++ b/rescueloadwidget.h @@ -0,0 +1,71 @@ +#ifndef RESCUELOADWIDGET_H +#define RESCUELOADWIDGET_H + +#include "rescueload.h" + +#include +#include +#include + +namespace Ui { +class RescueLoadWidget; +} + +// 搜救页面 +class RescueLoadWidget : public QWidget +{ + Q_OBJECT + +public: + explicit RescueLoadWidget(QWidget *parent = nullptr); + ~RescueLoadWidget(); + +private: + Ui::RescueLoadWidget *ui; + QTableWidgetItem *protoItem; // 单元格模版 + QStandardItem *standardItem; // 单元格模版 + QSet wnamelist; + QSet bnamelist; + ImsiTableModel* imsiDataModel; + QStandardItemModel* wnamelistModel; + QStandardItemModel* bnamelistModel; +public: + RescueLoad* rescueLoad; + void initialUi(); + + void openQLocationMap(); + +private slots: + void on_start4G_clicked(); + + void on_stop4G_clicked(); + + void on_start5G_clicked(); + + void on_stop5G_clicked(); + + void on_addwlistBtn_clicked(); + + void on_addblistBtn_clicked(); + + void updateDetailTable(const QString imsi); + void tableIndexClicked(int row); + void on_ipportBtn_clicked(); + void on_saveFileButton_clicked(); + +private: + void setDeviceTable(QTableWidget* tableWidget); + void addNewData(); + void deleteData(); + void syncTime(); + + void setDeviceDetail(QTableWidget *); + QList getNameListEditList(); + void updateWBnamelist(QString, QString, QList&); + + void setTable(QTableView *tableWidget); + void initialModel(); +}; + + +#endif // RESCUELOADWIDGET_H diff --git a/rescueloadwidget.ui b/rescueloadwidget.ui new file mode 100644 index 0000000..ad46a28 --- /dev/null +++ b/rescueloadwidget.ui @@ -0,0 +1,726 @@ + + + RescueLoadWidget + + + + 0 + 0 + 1106 + 684 + + + + + 0 + 0 + + + + Form + + + + + + + 650 + 450 + + + + 搜救信息位置图 + + + + + + + + 0 + 0 + + + + + 330 + 250 + + + + + 0 + + + + + + 0 + 0 + + + + + 320 + 300 + + + + 0 + + + + 设备检索列表 + + + + + + + 0 + 0 + + + + 保存当前检索的设备列表到文件 + + + + + + + <html><head/><body><p>Ctrl + C 可复制单元格内容</p></body></html> + + + false + + + color:black; +background-color:transparent; + + + + false + + + false + + + + + + + + 黑名单 + + + + + + false + + + false + + + + + + + + 白名单 + + + + + + false + + + false + + + + + + + + + + + + 0 + 0 + + + + + 320 + 275 + + + + + 16777215 + 300 + + + + <html><head/><body><p><br/></p></body></html> + + + 设备详情 + + + + 10 + + + 5 + + + 6 + + + + + + 0 + 240 + + + + + 9 + + + + <html><head/><body><p>Ctrl + C 可复制单元格内容</p></body></html> + + + 1 + + + false + + + 40 + + + 160 + + + true + + + true + + + false + + + 25 + + + 30 + + + false + + + + 设备IMSI + + + + + 运营商 + + + + + 经度 + + + + + 纬度 + + + + + 海拔高度 + + + + + 频点 + + + + + 强度 + + + + + 时间 + + + + + + + + + + + + + + + + 310 + 180 + + + + + 400 + 16777215 + + + + 功能控制 + + + + + + + + Qt::Horizontal + + + + 13 + 20 + + + + + + + + 载荷IP: + + + Qt::AlignCenter + + + false + + + + + + + + 80 + 0 + + + + 10.10.10.50 + + + + + + + 端口: + + + Qt::AlignCenter + + + false + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + 8056 + + + + + + + + 50 + 16777215 + + + + 修改 + + + + + + + Qt::Horizontal + + + + 13 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 13 + 17 + + + + + + + + true + + + + 0 + 0 + + + + + 90 + 30 + + + + 开启4G功放 + + + false + + + false + + + + + + + Qt::Horizontal + + + + 18 + 17 + + + + + + + + + 90 + 30 + + + + 开启5G功放 + + + + + + + Qt::Horizontal + + + + 25 + 17 + + + + + + + + Qt::Horizontal + + + + 13 + 17 + + + + + + + + + 0 + 0 + + + + + 90 + 30 + + + + 停止4G功放 + + + + + + + Qt::Horizontal + + + + 18 + 17 + + + + + + + + + 90 + 30 + + + + 停止5G功放 + + + + + + + Qt::Horizontal + + + + 25 + 17 + + + + + + + + + + + + + true + + + + 370 + 210 + + + + + 16777215 + 220 + + + + 黑/白名单设置 + + + + 12 + + + + + + + Qt::Vertical + + + + 20 + 17 + + + + + + + + + 10 + + + + 名单配置模式 + + + Qt::AlignCenter + + + + + + + + 160 + 30 + + + + + ADD: 增加名单 + + + + + SUB: 删除名单 + + + + + SAVE: 名单替换 + + + + + + + + Qt::Vertical + + + + 137 + 13 + + + + + + + + + 0 + 30 + + + + + 10 + + + + 配置黑名单 + + + + + + + + 0 + 30 + + + + + 10 + + + + 配置白名单 + + + + + + + Qt::Vertical + + + + 20 + 13 + + + + + + + + + + + 12 + + + + <html><head/><body><p>点击输入IMSI,或选择右侧列表项导入</p></body></html> + + + color:white; +background-color:transparent; + + + + + + + + + + + + + + +