Bläddra i källkod

1.修复“归还设备”功能中的Bug;2.调整蓝牙部分功能;3.新增下拉框、时分秒选择等控件

xj 3 månader sedan
förälder
incheckning
5e17121462

+ 5 - 0
Loto.pro

@@ -11,3 +11,8 @@ CONFIG(debug, debug|release) {
 } else {
     DESTDIR_COMMON = $${BASE_DESTDIR}/Release
 }
+
+DISTFILES += \
+    src/qml/components/MComboBox.qml \
+    src/qml/components/MSelectDateTime.qml \
+    src/qml/components/MTimePicker.qml

+ 23 - 23
Loto.pro.user

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE QtCreatorProject>
-<!-- Written by QtCreator 4.14.2, 2026-02-06T09:03:15. -->
+<!-- Written by QtCreator 4.14.2, 2026-02-10T09:36:57. -->
 <qtcreator>
  <data>
   <variable>EnvironmentId</variable>
@@ -86,16 +86,16 @@
   <variable>ProjectExplorer.Project.Target.0</variable>
   <valuemap type="QVariantMap">
    <value type="QString" key="DeviceType">GenericLinuxOsType</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">rk3568</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">rk3568</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{4b7c7020-747d-4c4a-8e7b-c68e8d5127ef}</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">DaJiGui</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">DaJiGui</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{09bab3f2-c9c3-421a-aef1-57a08250e2d5}</value>
    <value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
    <value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
    <value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Debug</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Debug</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -134,8 +134,8 @@
     <value type="int" key="RunSystemFunction">0</value>
    </valuemap>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Release</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Release</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -176,8 +176,8 @@
    </valuemap>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Profile</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Profile</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -327,10 +327,10 @@
     <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes">
      <value type="QString">LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH</value>
      <value type="QString">QML2_IMPORT_PATH=$QTDIR/qml:$QML2_IMPORT_PATH</value>
-     <value type="QString">QTDIR=/opt/qt5</value>
+     <value type="QString">QTDIR=/home/teamhd/qt5</value>
      <value type="QString">QT_PLUGIN_PATH=$QTDIR/plugins:$QT_PLUGIN_PATH</value>
     </valuelist>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on rk3568)</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on DaJiGui)</value>
     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">RemoteLinuxRunConfiguration:/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
     <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
     <value type="int" key="RemoteLinux.EnvironmentAspect.Version">1</value>
@@ -347,16 +347,16 @@
   <variable>ProjectExplorer.Project.Target.1</variable>
   <valuemap type="QVariantMap">
    <value type="QString" key="DeviceType">GenericLinuxOsType</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">DaJiGui</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">DaJiGui</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{09bab3f2-c9c3-421a-aef1-57a08250e2d5}</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">rk3568</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">rk3568</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{4b7c7020-747d-4c4a-8e7b-c68e8d5127ef}</value>
    <value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
    <value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
    <value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Debug</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Debug</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -395,8 +395,8 @@
     <value type="int" key="RunSystemFunction">0</value>
    </valuemap>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Release</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Release</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -437,8 +437,8 @@
    </valuemap>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Profile</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Profile</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -588,10 +588,10 @@
     <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes">
      <value type="QString">LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH</value>
      <value type="QString">QML2_IMPORT_PATH=$QTDIR/qml:$QML2_IMPORT_PATH</value>
-     <value type="QString">QTDIR=/home/teamhd/qt5</value>
+     <value type="QString">QTDIR=/opt/qt5</value>
      <value type="QString">QT_PLUGIN_PATH=$QTDIR/plugins:$QT_PLUGIN_PATH</value>
     </valuelist>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on DaJiGui)</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on rk3568)</value>
     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">RemoteLinuxRunConfiguration:/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
     <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
     <value type="int" key="RemoteLinux.EnvironmentAspect.Version">1</value>

+ 40 - 82
src/interactive/InteractiveCAN.cpp

@@ -104,20 +104,12 @@ InteractiveCAN::InteractiveCAN()
             else if (WorkNodeFormModel::instance()->modelType() == QString("releaseIsolation")) {
                 createColockJobTicket();
             }
-            else {
-                // 归还设备
-                checkEKeyStatus();
-            }
         }
     });
     connect(m_bleClient, &BLEClient::signalSendJobTicketStatus, this, &InteractiveCAN::slotSendJobTicketStatus);
     connect(m_bleClient, &BLEClient::workTicketResultReceived, this, &InteractiveCAN::slotReceivedJobTicketResult, Qt::QueuedConnection);
     connect(m_bleClient, &BLEClient::signalFailConnect, this, &InteractiveCAN::slotReConnectBLE);
 
-    m_checkHasKeyTimer = new QTimer(this);
-    connect(m_checkHasKeyTimer, &QTimer::timeout, this, &InteractiveCAN::checkEKeyStatus);
-    connect(this, &InteractiveCAN::signalCheckedKey, this, &InteractiveCAN::getJobTicketInfo);
-
     connect(this, &InteractiveCAN::signalUpdateColockStatus, this, &InteractiveCAN::slotUpdateColockStatus);
     
     // ========== 连接CAN信号到归还钥匙和锁管理器 ==========
@@ -134,8 +126,10 @@ InteractiveCAN::InteractiveCAN()
     connect(m_bleClient, &BLEClient::workTicketResultReceived,
             ReturnKeyLockManager::instance(), &ReturnKeyLockManager::slotReceivedJobTicket,
             Qt::QueuedConnection);
-    connect(ReturnKeyLockManager::instance(), &ReturnKeyLockManager::isProcessingChanged,
-            this, &InteractiveCAN::slotReturnKeyAndLockProcessingChanged);
+    connect(ReturnKeyLockManager::instance(), &ReturnKeyLockManager::isVisibleChanged,
+            this, &InteractiveCAN::slotReturnKeyAndLockVisibleChanged);
+    connect(this, &InteractiveCAN::signalBackLock,
+            ReturnKeyLockManager::instance(), &ReturnKeyLockManager::slotBackLock);
     // ====================================================
 }
 
@@ -145,6 +139,7 @@ void InteractiveCAN::createJobTicket()
     if (isolationPoints.isEmpty()) return;
 
     m_isolationPoints = stringToIntList(isolationPoints);
+    m_pointInfo.clear();
 
     // 获取隔离点信息
     for (int i = 0; i < m_isolationPoints.length(); i++) {
@@ -434,61 +429,6 @@ void InteractiveCAN::httpRequestGetWorkTicketByNodeId()
     emit signalGetRequestData(timestampSeconds, url, jsonData, GetInteractiveData()->m_token);
 }
 
-void InteractiveCAN::checkEKeyStatus()
-{
-    // TODO: 暂时考虑只有两把钥匙的情况
-    QMap<quint8, CANKeyRFIDStatus> keyRFIDs = m_canClient->keyRFIDStatus();
-    quint8 nodeId = keyRFIDs.keys().first();
-    CANKeyRFIDStatus keyRFID = keyRFIDs[nodeId];
-
-    if (keyRFID.leftKeyRFID == m_keyNFC) {
-        QMap<quint8, CANKeyBaseStatus> keyBaseStatus = m_canClient->keyBaseStatus();
-        if (keyBaseStatus.find(nodeId) == keyBaseStatus.end()) {
-            return;
-        }
-        bool hasLeftKey = keyBaseStatus[nodeId].leftHasKey;
-
-        // 钥匙被放回,开始读取作业票详情, 后续可能是从后台获取
-        if (hasLeftKey) {
-            qDebug() << "[checkEKeyStatus]: start checking key";
-            m_checkHasKeyTimer->stop();
-            emit signalCheckedKey(nodeId, true);
-        }
-    }
-    else if (keyRFID.rightKeyRFID == m_keyNFC) {
-        QMap<quint8, CANKeyBaseStatus> keyBaseStatus = m_canClient->keyBaseStatus();
-        if (keyBaseStatus.find(nodeId) == keyBaseStatus.end()) {
-            return;
-        }
-        bool hasRightKey = keyBaseStatus[nodeId].rightHasKey;
-        if (hasRightKey) {
-            m_checkHasKeyTimer->stop();
-            emit signalCheckedKey(nodeId, false);
-        }
-    }
-}
-
-void InteractiveCAN::getJobTicketInfo(quint8 nodeId, bool isLeftKey)
-{
-    qDebug() << "[getJobTicketInfo]: " << m_checkedFlags;
-    if (m_checkedFlags) {
-        m_checkedFlags = false;
-        m_canClient->lockingEKey(nodeId, isLeftKey, !isLeftKey);
-
-//        m_bleClient->startGetWorkTicketResult();
-        if (m_bleClient) {
-            qDebug() << "[getJobTicketInfo]: " << m_bleClient->isConnected();
-            if (m_bleClient->isConnected()) {
-                m_bleClient->startGetWorkTicketResult();
-            }
-            else {
-                m_startGetWorkTicketResultFlag = true;
-                m_bleClient->apiConnectDevice(m_currentKeyMAC);
-            }
-        }
-    }
-}
-
 void InteractiveCAN::slotHttpResponseGetKeyMAC(QByteArray data)
 {
     QJsonParseError error;
@@ -605,9 +545,9 @@ void InteractiveCAN::slotHttpResponseUploadJobTicket(QByteArray data)
         if(codeValue == 200 || codeValue == 0) {
 //            m_okUnlockKey = true;
 //            qDebug() << "[slotHttpResponseUploadJobTicket]" << jsonDoc;
-            if (m_bleClient) {
+//            if (m_bleClient) {
 //                m_bleClient->disconnectDevice();
-            }
+//            }
         }
     }
 }
@@ -705,6 +645,9 @@ void InteractiveCAN::slotHttpResponseUpdateBackLock(QByteArray data)
         }
         if(codeValue == 200 || codeValue == 0) {
             m_getLockedRfidFlag = false;
+            if (rootObj.contains("data") && rootObj.value("data").isArray()) {
+                emit signalBackLock(0, rootObj.value("data").toArray());
+            }
         }
     }
 }
@@ -862,21 +805,27 @@ void InteractiveCAN::slotGetPopedDevices(const QString &nfcId, const QString &de
     }
     else if (deviceType == "key") {
         m_okUnlockKey = true;
+        if (m_bleClient) {
+            m_bleClient->disconnectDevice();
+        }
     }
-    qDebug() << "[slotGetPopedDevices] " << m_popedDevices.locksNfc;
-    qDebug() << "[slotGetPopedDevices] " << m_popedDevices.keyNfc;
 }
 
-void InteractiveCAN::slotReturnKeyAndLockProcessingChanged()
+void InteractiveCAN::slotReturnKeyAndLockVisibleChanged()
 {
-    if (ReturnKeyLockManager::instance()->isProcessing()) {
+    if (ReturnKeyLockManager::instance()->isVisible()) {
         connect(m_bleClient, &BLEClient::workTicketResultReceived,
                 ReturnKeyLockManager::instance(), &ReturnKeyLockManager::slotReceivedJobTicket,
                 Qt::QueuedConnection);
+        connect(this, &InteractiveCAN::signalJobTicketInfo,
+                ReturnKeyLockManager::instance(), &ReturnKeyLockManager::slotGetJobTicketResult,
+                Qt::QueuedConnection);
     }
     else {
         disconnect(m_bleClient, &BLEClient::workTicketResultReceived,
                    ReturnKeyLockManager::instance(), &ReturnKeyLockManager::slotReceivedJobTicket);
+        disconnect(this, &InteractiveCAN::signalJobTicketInfo,
+                ReturnKeyLockManager::instance(), &ReturnKeyLockManager::slotGetJobTicketResult);
     }
 }
 
@@ -1083,7 +1032,6 @@ void InteractiveCAN::getWorkTicketByNodeId()
 
 void InteractiveCAN::connectBLEDevice(const QString &keyNfc)
 {
-    qDebug() << keyNfc;
     if (!keyNfc.isEmpty()) {
         m_keyNFC = keyNfc;
         QJsonObject jsonRoot;
@@ -1097,6 +1045,17 @@ void InteractiveCAN::connectBLEDevice(const QString &keyNfc)
         QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);
 
         emit signalGetRequestData(timestampSeconds, url, jsonData, GetInteractiveData()->m_token);
+
+        if (m_bleClient) {
+            qDebug() << "[connectBLEDevice]: " << m_bleClient->isConnected();
+            if (m_bleClient->isConnected()) {
+                m_bleClient->startGetWorkTicketResult();
+            }
+            else {
+                m_startGetWorkTicketResultFlag = true;
+                m_bleClient->apiConnectDevice(m_currentKeyMAC);
+            }
+        }
     }
 }
 
@@ -1161,10 +1120,6 @@ void InteractiveCAN::slotUnlockLocks(bool success, const QList<QString>& lockRfi
                 m_finishedFlags = false;
                 getEKey();
                 m_finishedFlags = true;
-                // 等待钥匙取出
-                QTimer::singleShot(10000, [this]() {
-                    m_checkHasKeyTimer->start(1000);
-                });
             }
         }
 //        qDebug() << "[slotUnlockLocks]: " << lockRfids << WorkNodeFormModel::instance()->modelType();
@@ -1201,6 +1156,7 @@ void InteractiveCAN::slotDeviceDetected(const QString &nfcId)
 void InteractiveCAN::slotSearchBLE()
 {
     QMutexLocker locker(&m_mutex);
+//    qDebug() << ">>> " << m_currentKeyMAC << m_searchedBLE;
     if (m_bleClient && !m_currentKeyMAC.isEmpty() && !m_searchedBLE) {
         m_searchedBLE = true;
         emit signalOkSearchedBLE(true);
@@ -1211,7 +1167,9 @@ void InteractiveCAN::slotOkSearchedBLE(bool success)
 {
     if (success) {
         if (m_bleClient->isConnected()) {
-            createColockJobTicket();
+            if (!m_okSendJobTicket) {
+                createColockJobTicket();
+            }
         }
         else {
             m_bleClient->apiConnectDevice(m_currentKeyMAC);
@@ -1230,12 +1188,12 @@ void InteractiveCAN::slotSendJobTicketStatus(bool success)
         }
         else {
             m_finishedFlags = false;
-            getEKey();
+            qDebug() << "[slotSendJobTicketStatus] " << m_okUnlockKey;
+            if (!m_okUnlockKey) {
+                getEKey();
+            }
+
             m_finishedFlags = true;
-            // 等待钥匙取出
-            QTimer::singleShot(10000, [this]() {
-                m_checkHasKeyTimer->start(1000);
-            });
         }
     }
 }

+ 2 - 6
src/interactive/InteractiveCAN.h

@@ -107,8 +107,6 @@ public slots:
 
     void slotSendJobTicketStatus(bool success);
     void slotReceivedJobTicketResult(const BLERWorkTicketResult& result);
-
-    void checkEKeyStatus();
 private:
     explicit InteractiveCAN();
 
@@ -125,8 +123,6 @@ public:
     void httpRequestPostUpdateKeyBack(const QString& keyNfc, int target, const QJsonArray& lockList);
     void httpRequestGetWorkTicketByNodeId();
 public slots:
-    void getJobTicketInfo(quint8 nodeId, bool isLeftKey);
-
     void slotHttpResponseGetKeyMAC(QByteArray data);
 
     void slotHttpResponseGetIsolationPointInfo(QByteArray data);
@@ -146,7 +142,7 @@ public slots:
     void slotReConnectBLE();
 
     void slotGetPopedDevices(const QString& nfcId, const QString& deviceType, int slotIndex);
-    void slotReturnKeyAndLockProcessingChanged();
+    void slotReturnKeyAndLockVisibleChanged();
 signals:
     void signalReturnDevicesInfo(const QByteArray& info);
     void signalOkSearchedBLE(bool success);
@@ -167,6 +163,7 @@ signals:
 
     void signalJobTicketResult(const QString& jsonResult, const QVariantMap& pointInfo);
     void signalJobTicketInfo(int stat, const QJsonObject& dataObj);
+    void signalBackLock(int stat, const QJsonArray& dataObj);
 
     void signalPostRequestData(quint64 id, QString postUrl, QByteArray data, QByteArray file, QString token);
     void signalUploadJobTicketReturnStat(int stat, QString str);
@@ -186,7 +183,6 @@ private:
     QString m_currentKeyMAC;
 
     QTimer* m_bleConnectTimer;
-    QTimer* m_checkHasKeyTimer;      // 检查是否有钥匙放入
 
     QJsonDocument m_ticketDoc;
     QList<int> m_isolationPoints;

+ 58 - 32
src/interactive/ReturnKeyLockManager.cpp

@@ -257,26 +257,15 @@ void ReturnKeyLockManager::onApiResponse(const QString& nfcId, bool success, con
         }
     }
 
-    if (WorkNodeFormModel::instance()->modelType() == "releaseIsolation" && m_taskGroups.contains(m_currTask.taskCode)) {
-        m_taskGroups[m_currTask.taskCode].append(info);
-
-        QList<QString> lockRfids;
-        lockRfids << nfcId;
-
-        InteractiveCAN::instance()->setTaskCode(m_currTask.taskCode.toInt());
-//        InteractiveCAN::instance()->httpRequestPostUpdateBackLock(lockRfids);
+    // 分类到对应组
+    if (!info.hasTask) {
+        m_noTaskDevices.append(info);
     }
     else {
-        // 分类到对应组
-        if (!info.hasTask) {
-            m_noTaskDevices.append(info);
-        }
-        else {
-            if (!m_taskGroups.contains(info.taskId)) {
-                m_taskGroups[info.taskId] = QList<ReturnDeviceInfo>();
-            }
-            m_taskGroups[info.taskId].append(info);
+        if (!m_taskGroups.contains(info.taskId)) {
+            m_taskGroups[info.taskId] = QList<ReturnDeviceInfo>();
         }
+        m_taskGroups[info.taskId].append(info);
     }
     
     emit statisticsChanged();
@@ -326,10 +315,10 @@ void ReturnKeyLockManager::processNextInQueue()
     
     if (m_pendingQueue.isEmpty()) {
         m_processTimer->stop();
-        m_isProcessing = false;
-        m_currentReadingStatus = "读取完成";
-        emit isProcessingChanged();
-        emit currentReadingStatusChanged();
+//        m_isProcessing = false;
+//        m_currentReadingStatus = "读取完成";
+//        emit isProcessingChanged();
+//        emit currentReadingStatusChanged();
         return;
     }
     
@@ -359,7 +348,13 @@ void ReturnKeyLockManager::processNextInQueue()
         lockRfids << info.nfcId;
 
         InteractiveCAN::instance()->httpRequestPostUpdateBackLock(lockRfids);
-//        onApiResponse(info.nfcId, true, mockData);
+
+        mockData["taskId"] = WorkNodeFormModel::instance()->workId();
+        mockData["taskName"] = WorkNodeFormModel::instance()->nodeName();
+        mockData["orderNo"] = WorkNodeFormModel::instance()->orderNo();
+        mockData["worker"] = WorkNodeFormModel::instance()->workerName();
+
+        onApiResponse(info.nfcId, true, mockData);
     }
 }
 
@@ -444,6 +439,8 @@ QVariantList ReturnKeyLockManager::getTaskGroups()
             device["statusMessage"] = info.statusMessage;
             device["returnTime"] = info.returnTime;
             devices.append(device);
+
+            qDebug() << ">>>>> " << it.key() << info.deviceType << info.taskName;
             
             if (info.status == "success") success++;
             else failed++;
@@ -522,6 +519,12 @@ void ReturnKeyLockManager::slotGetJobTicketResult(int stat, const QJsonObject& d
     QString nfcId = m_currTask.keyNfc;
 
     ReturnDeviceInfo& info = m_deviceMap[nfcId];
+    if (info.hasTask) return;
+
+    m_isProcessing = false;
+    m_currentReadingStatus = "读取完成";
+    emit isProcessingChanged();
+    emit currentReadingStatusChanged();
 
     if (stat != 0){
         info.status = "failed";
@@ -548,15 +551,25 @@ void ReturnKeyLockManager::slotGetJobTicketResult(int stat, const QJsonObject& d
             info.statusMessage = "归还成功";
         }
         else {
-            info.status = "failed";
-            info.statusMessage = "作业任务未完成";
+            info.status = "success";
+            if (ticketStatus == 1) {
+                info.statusMessage = "待上锁";
+            }
+            else if (ticketStatus == 2) {
+                info.statusMessage = "作业任务进行中";
+            }
+            else {
+                info.statusMessage = "待解锁";
+            }
         }
 
-        info.taskId = WorkNodeFormModel::instance()->workId();
+        info.taskId = QString::number(WorkNodeFormModel::instance()->workId());
         info.taskName = WorkNodeFormModel::instance()->nodeName();
         info.taskOrderNo = WorkNodeFormModel::instance()->orderNo();
         info.taskWorker = WorkNodeFormModel::instance()->workerName();
+        info.deviceType = "key";
         info.hasTask = !info.taskId.isEmpty();
+        qDebug() << ">> " << info.taskId << info.taskName << info.taskOrderNo << info.taskWorker;
 
         // 更新统计
         if (info.status == "success") {
@@ -658,19 +671,32 @@ void ReturnKeyLockManager::slotGetJobTicketResult(int stat, const QJsonObject& d
             }
         }
     }
+//    if (WorkNodeFormModel::instance()->modelType() == "releaseIsolation" || WorkNodeFormModel::instance()->modelType() == "isolation") {
+//        qDebug() << "[slotGetJobTicketResult] " << m_taskGroups.keys();
+//        QList<ReturnDeviceInfo> releaseIsolationInfoList = m_taskGroups[m_currTask.taskCode];
+//        m_taskGroups.clear();
+//        m_taskGroups[m_currTask.taskCode] = releaseIsolationInfoList;
 
-    if (WorkNodeFormModel::instance()->modelType() == "releaseIsolation") {
-        QList<ReturnDeviceInfo> releaseIsolationInfoList = m_taskGroups[m_currTask.taskCode];
-        m_taskGroups.clear();
-        m_taskGroups[m_currTask.taskCode] = releaseIsolationInfoList;
-
-        setIsVisible(true);
-    }
+//        setIsVisible(true);
+//    }
 
     emit statisticsChanged();
     emit dataUpdated();
 }
 
+void ReturnKeyLockManager::slotBackLock(int stat, const QJsonArray &dataObj)
+{
+//    QMutexLocker locker(&m_mutex);
+
+    m_isProcessing = false;
+    m_currentReadingStatus = "读取完成";
+    emit isProcessingChanged();
+    emit currentReadingStatusChanged();
+
+//    emit statisticsChanged();
+//    emit dataUpdated();
+}
+
 void ReturnKeyLockManager::startConfirmCountdown()
 {
     m_confirmCountdown = 20;

+ 2 - 0
src/interactive/ReturnKeyLockManager.h

@@ -155,6 +155,8 @@ public slots:
     Q_INVOKABLE void testTrigger(const QString& deviceType = "key", int slotIndex = 0);
 
     void slotGetJobTicketResult(int stat, const QJsonObject& dataObj);
+
+    void slotBackLock(int stat, const QJsonArray& dataObj);
 public:
     Q_INVOKABLE void startConfirmCountdown();
     Q_INVOKABLE void stopConfirmCountdown();

+ 3 - 0
src/qml.qrc

@@ -38,5 +38,8 @@
         <file>qml/components/CustomVirtualKeyboard.qml</file>
         <file>qml/components/JobTicketColockProcess.qml</file>
         <file>qml/components/ReturnKeyLockProcess.qml</file>
+        <file>qml/components/MComboBox.qml</file>
+        <file>qml/components/MSelectDateTime.qml</file>
+        <file>qml/components/MTimePicker.qml</file>
     </qresource>
 </RCC>

+ 3 - 1
src/qml/Login.qml

@@ -1278,7 +1278,9 @@ Rectangle {
 
         Text {
             id: iconLabel
-            x: 320
+//            x: 320
+            anchors.left: tipLabel.right
+            anchors.leftMargin: 40
             y: 6
             text: "\uf0a5"
             color: "orange"

+ 77 - 16
src/qml/components/FormCard.qml

@@ -107,7 +107,7 @@ Rectangle {
                         // 横向一行:标题 + 选项(radio/switch 用开关/分段形式)
                         Row {
                             id: rowLayout
-                            visible: type === "radio" || type === "switch"
+                            visible: type === "input" || type === "radio" || type === "switch" || type === "select" || type === "date" || type === "datetime" || type === "timepicker"
                             anchors.fill: parent
                             spacing: 16
                             Row {
@@ -144,14 +144,14 @@ Rectangle {
                                 width: parent.width - labelRow.width - 16
                                 height: 48
                                 anchors.verticalCenter: parent.verticalCenter
-                                visible: type === "radio" || type === "switch"
+                                visible: type === "input" || type === "radio" || type === "switch" || type === "select" || type === "date" || type === "datetime" || type === "timepicker"
                             }
                         }
 
                         // 纵向:标题在上、控件在下(input/textarea 等)
                         Column {
                             id: columnLayout
-                            visible: type !== "radio" && type !== "switch"
+                            visible: type !== "input" && type !== "radio" && type !== "switch" && type !== "select" && type !== "date" && type !== "datetime" && type !== "timepicker"
                             anchors.fill: parent
                             spacing: 12
                             Row {
@@ -183,14 +183,14 @@ Rectangle {
                                 id: formControlColumn
                                 width: parent.width
                                 height: parent.height - labelRow2.height - 12
-                                visible: type !== "radio" && type !== "switch"
+                                visible: type !== "input" && type !== "radio" && type !== "switch" && type !== "select" && type !== "date" && type !== "datetime" && type !== "timepicker"
                             }
                         }
                     }
                 }
 
                 Component.onCompleted: {
-                    // console.log(id, ">>", type, ">>", label, ">>", options, "<<", options.length, "<<", groupIndex, ">>", gridColumns, ">>", gridCount);
+                     console.log(id, ">>", type, ">>", label, ">>", options, "<<", options.length, "<<", groupIndex, ">>", gridColumns, ">>", gridCount);
                     var columns = gridColumns === 0 ? 1 : gridColumns;
 
                     formArea.x = gridColumns === 0 ? 0 : (groupIndex % columns) * (contentItem.width/columns);
@@ -261,22 +261,40 @@ Rectangle {
                         // 连接虚拟键盘
                         formControlColumn.item.signalInputClicked.connect(control.showVirtualKeyboard);
                         control.signalSubmit.connect(formControlColumn.item.slotShowRequiredMsg);
+                    } else if (type === "date") {
+                        formControlRow.source = "MDatePicker.qml";
+                        formControlRow.item.controlId = id;
+                        formControlRow.item.modelIndex = groupIndex;
+                        formControlRow.item.placeholderText = placeholder;
+                        formControlRow.item.required = required;
+                        formControlRow.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
+                        formControlRow.item.height = 48;
+                        formControlRow.item.enabled = !control.readOnly;
+                        // 回显值
+                        if (value !== undefined && value !== "") {
+                            formControlRow.item.text = value;
+                        }
+
+                        formControlRow.item.signalTextChanged.connect(control.slotCollectInputInfo);
+                        control.signalSubmit.connect(formControlRow.item.slotShowRequiredMsg);
+                    } else if (type === "datetime") {
+
                     } else if (type === "timepicker") {
-                        formControlColumn.source = "MDatePicker.qml";
-                        formControlColumn.item.controlId = id;
-                        formControlColumn.item.modelIndex = groupIndex;
-                        formControlColumn.item.placeholderText = placeholder;
-                        formControlColumn.item.required = required;
-                        formControlColumn.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
-                        formControlColumn.item.height = 48;
-                        formControlColumn.item.enabled = !control.readOnly;
+                        formControlRow.source = "MTimePicker.qml";
+                        formControlRow.item.controlId = id;
+                        formControlRow.item.modelIndex = groupIndex;
+                        formControlRow.item.placeholderText = placeholder;
+                        formControlRow.item.required = required;
+                        formControlRow.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
+                        formControlRow.item.height = 48;
+                        formControlRow.item.enabled = !control.readOnly;
                         // 回显值
                         if (value !== undefined && value !== "") {
-                            formControlColumn.item.text = value;
+                            formControlRow.item.text = value;
                         }
 
-                        formControlColumn.item.signalTextChanged.connect(control.slotCollectInputInfo);
-                        control.signalSubmit.connect(formControlColumn.item.slotShowRequiredMsg);
+                        formControlRow.item.signalTextChanged.connect(control.slotCollectInputInfo);
+                        control.signalSubmit.connect(formControlRow.item.slotShowRequiredMsg);
                     } else if (type === "radio") {
                         formControlRow.source = "MRadioButton.qml";
                         formControlRow.item.controlId = id;
@@ -327,6 +345,34 @@ Rectangle {
                         formControlColumn.item.signalInputClicked.connect(control.showVirtualKeyboard);
 
                         control.signalSubmit.connect(formControlColumn.item.slotShowRequiredMsg);
+                    } else if (type == "select") {
+                        formControlRow.source = "MComboBox.qml";
+                        formControlRow.item.controlId = id;
+                        formControlRow.item.modelIndex = groupIndex;
+                        formControlRow.item.required = required;
+                        formControlRow.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
+                        formControlRow.item.enabled = !control.readOnly;
+                        formControlRow.item.fontSize = 15;
+                        formControlRow.item.backgroundVisible = false;
+                        var comboBoxOptions = [];
+                        var selectedIdx = -1;
+                        for (var i = 0; i < options.length; i++) {
+                            var optParts = options[i].split(",");
+                            comboBoxOptions.push({text: optParts[0]});
+                            // 查找回显值对应的索引
+                            if (value !== undefined && value !== "" && optParts.length > 1 && optParts[1] === value) {
+                                selectedIdx = i;
+                            }
+                        }
+                        formControlRow.item.model = comboBoxOptions;
+                        formControlRow.item.height = 48;
+                        // 回显选中状态
+                        if (selectedIdx >= 0) {
+                            formControlRow.item.selectedIndices = [selectedIdx];
+                        }
+
+                        formControlRow.item.signalSelectionChanged.connect(control.slotCollectComboBoxInfo);
+                        control.signalSubmit.connect(formControlRow.item.slotShowRequiredMsg);
                     } else {
                         return;
                     }
@@ -400,6 +446,21 @@ Rectangle {
         }
     }
 
+    function slotCollectComboBoxInfo(index, id, selectedIndices, selectedDataItems) {
+        var formInfo = control.returnFormInfo["fields"][index];
+        if (typeof(formInfo) === "string") {
+            var formInfoObj = JSON.parse(control.returnFormInfo["fields"][index]);
+            if (formInfoObj["id"] === id) {
+                formInfoObj["value"] = formInfoObj["options"][selectedIndices[0]]["value"];
+                control.returnFormInfo["fields"][index] = JSON.stringify(formInfoObj);
+            }
+        } else {
+            if (control.returnFormInfo["fields"][index]["id"] === id) {
+               control.returnFormInfo["fields"][index]["value"] = control.returnFormInfo["fields"][index]["options"][selectedIndices[0]]["value"];
+            }
+        }
+    }
+
     function collectFormInputInfo() {
         signalSubmit(true);
         return JSON.stringify(control.returnFormInfo);

+ 344 - 0
src/qml/components/MComboBox.qml

@@ -0,0 +1,344 @@
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Controls 2.12
+
+Rectangle {
+    id: control
+
+    // === 接口属性 ===
+    property string controlId
+    property int modelIndex
+    property var model: []                     // [{ text: string }]
+    property var selectedIndices: []           // 单选时数组最多一个元素
+    signal selectionChanged(var selectedIndices, var selectedData)
+    signal signalSelectionChanged(int index, string id, var selectedIndices, var selectedData)
+
+    property bool required: false
+    property string requiredMsg
+    property bool showRequiredMsg: false
+
+    // === 状态属性 ===
+    property bool enabled: true
+    property bool expanded: false              // 下拉框是否展开
+    property int dropDownMaxHeight: 200        // 下拉列表最大高度(超过则滚动)
+
+    // === 样式属性 ===
+    property bool backgroundVisible: true
+    property real radius: 12
+    property int fontSize: 15
+    property color buttonColor: "transparent"
+    property color hoverColor: Qt.darker(buttonColor, 1.2)
+    property color textColor: "white"
+    property color checkmarkColor: "#5a8fc4"
+    property color borderColor: "#355a80"
+    property color selectedBgColor: "#1a3d2e"   // 选中项背景(偏绿与绿色勾一致)
+    property color selectedBorderColor: "#4CAF50"
+    property color checkmarkGreen: "#4CAF50"   // 选中时的绿色勾
+    property real pressedScale: 0.98
+    property bool shadowEnabled: true
+    property color shadowColor: theme.shadowColor
+
+    // 布局尺寸(复用原组件,新增下拉列表尺寸)
+    property int verticalPadding: 12
+    property int iconSize: 24
+    property int spacingBetweenIconAndText: 10
+    property int verticalSpacingBetweenButtons: 10
+    property int buttonHeight: 48
+    property int dropDownItemHeight: buttonHeight  // 下拉选项高度与头部一致
+
+    // === 隐藏文本用于测量最大宽度 ===
+    Text {
+        id: measureText
+        visible: false
+        font.pixelSize: control.fontSize
+    }
+
+    property real maxTextWidth: 0
+    function updateMaxTextWidth() {
+        var maxWidth = 0
+        for (var i = 0; i < model.length; i++) {
+            measureText.text = model[i].text
+            if (measureText.width > maxWidth)
+                maxWidth = measureText.width
+        }
+        maxTextWidth = maxWidth
+    }
+    Component.onCompleted: updateMaxTextWidth()
+    onModelChanged: updateMaxTextWidth()
+
+    // === 尺寸计算(下拉框头部尺寸) ===
+    implicitWidth: maxTextWidth + verticalPadding * 2 + iconSize * 2 + spacingBetweenIconAndText
+    implicitHeight: buttonHeight + 24
+
+    width: implicitWidth
+    height: expanded ? implicitHeight + dropDownContent.height + 8 : implicitHeight
+    color: "transparent"
+
+    // === 下拉框头部(点击展开/收起) ===
+    Rectangle {
+        id: header
+        anchors.top: parent.top
+        anchors.left: parent.left
+        anchors.right: parent.right
+        height: buttonHeight
+        radius: control.radius
+        border.width: 1.5
+        border.color: control.borderColor
+        color: control.backgroundVisible ? control.buttonColor : "transparent"
+        opacity: control.enabled ? 1.0 : 0.6
+
+        Behavior on border.color { ColorAnimation { duration: 150 } }
+        Behavior on color { ColorAnimation { duration: 150 } }
+        Behavior on opacity { NumberAnimation { duration: 100 } }
+
+        // 头部阴影
+        layer.enabled: control.shadowEnabled && control.backgroundVisible
+
+        // === 头部内容:选中文本 + 下拉箭头 ===
+        RowLayout {
+            anchors.fill: parent
+            anchors.leftMargin: verticalPadding
+            anchors.rightMargin: verticalPadding
+            spacing: spacingBetweenIconAndText
+            Layout.alignment: Qt.AlignVCenter
+
+            // 选中项文本
+            Text {
+                id: selectedText
+                text: control.selectedIndices.length > 0 ? control.model[control.selectedIndices[0]].text : qsTr("请选择")
+                color: control.textColor
+                font.pixelSize: control.fontSize
+                font.bold: control.selectedIndices.length > 0
+                elide: Text.ElideRight
+                Layout.fillWidth: true
+                Layout.alignment: Qt.AlignVCenter
+            }
+
+            // 下拉箭头(展开/收起动画)
+            Text {
+                id: arrowIcon
+                text: "\uf0d7"
+                color: control.checkmarkColor
+                font.pixelSize: iconSize - 6
+                font.family: iconFont.name
+                Layout.alignment: Qt.AlignVCenter
+                rotation: expanded ? 180 : 0
+                Behavior on rotation { NumberAnimation { duration: 200 } }
+            }
+        }
+
+        // 头部交互(点击展开/收起)
+        MouseArea {
+            anchors.fill: parent
+            hoverEnabled: true
+            cursorShape: control.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+            enabled: control.enabled
+
+            onEntered: {
+                header.border.color = control.checkmarkColor
+                header.color = control.hoverColor
+            }
+            onExited: {
+                header.border.color = control.borderColor
+                header.color = control.buttonColor
+            }
+            onPressed: {
+                header.scale = control.pressedScale
+                header.opacity = 0.85
+            }
+            onReleased: {
+                header.scale = 1.0
+                header.opacity = 1.0
+                control.expanded = !control.expanded // 切换展开状态
+            }
+            onCanceled: {
+                header.scale = 1.0
+                header.opacity = 1.0
+            }
+        }
+    }
+
+    // === 下拉列表容器 ===
+    Rectangle {
+        id: dropDownContent
+        anchors.top: header.bottom
+        anchors.topMargin: 8
+        anchors.left: header.left
+        anchors.right: header.right
+        height: Math.min(model.length * (dropDownItemHeight + verticalSpacingBetweenButtons) - verticalSpacingBetweenButtons, dropDownMaxHeight)
+        radius: control.radius
+        border.width: 1.5
+        border.color: control.borderColor
+        color: control.backgroundVisible ? control.buttonColor : "transparent"
+        visible: control.expanded && model.length > 0
+
+        Behavior on height { NumberAnimation { duration: 200 } }
+        Behavior on opacity { NumberAnimation { duration: 100 } }
+
+        // === 下拉列表滚动容器 ===
+        Flickable {
+            anchors.fill: parent
+            anchors.margins: 4
+            clip: true
+            flickableDirection: Flickable.VerticalFlick
+            contentWidth: parent.width - 8
+            contentHeight: model.length * (dropDownItemHeight + verticalSpacingBetweenButtons) - verticalSpacingBetweenButtons
+
+            // === 下拉选项 Repeater ===
+            ColumnLayout {
+                id: dropDownItems
+                spacing: verticalSpacingBetweenButtons
+                width: parent.width
+
+                Repeater {
+                    model: control.model
+                    delegate: Rectangle {
+                        id: dropDownItem
+                        width: parent.width
+                        height: dropDownItemHeight
+                        radius: 8
+//                        border.width: checked ? 2.5 : 1.5
+//                        border.color: checked ? control.selectedBorderColor : control.checkmarkColor
+                        color: control.backgroundVisible ? (checked ? control.selectedBgColor : (hovered ? control.hoverColor : control.buttonColor)) : "transparent"
+                        opacity: control.enabled ? 1.0 : 0.6
+
+                        // 状态属性
+                        property bool hovered: false
+                        property bool checked: control.selectedIndices.indexOf(index) !== -1
+
+                        Behavior on color { ColorAnimation { duration: 150 } }
+                        Behavior on border.color { ColorAnimation { duration: 120 } }
+                        Behavior on border.width { NumberAnimation { duration: 120 } }
+                        Behavior on opacity { NumberAnimation { duration: 100 } }
+
+                        // === 选项内容:勾选框 + 文本 ===
+                        RowLayout {
+                            anchors.fill: parent
+                            anchors.leftMargin: verticalPadding
+                            anchors.rightMargin: verticalPadding
+                            spacing: spacingBetweenIconAndText
+                            Layout.alignment: Qt.AlignVCenter
+
+                            // 勾选框
+                            Rectangle {
+                                id: checkBox
+                                width: iconSize
+                                height: iconSize
+                                radius: 4
+                                border.width: checked ? 2 : 1
+                                border.color: checked ? control.checkmarkGreen : control.checkmarkColor
+                                color: checked ? "#404CAF50" : "transparent"
+                                Layout.alignment: Qt.AlignVCenter
+
+                                Behavior on border.color { ColorAnimation { duration: 120 } }
+                                Behavior on color { ColorAnimation { duration: 120 } }
+
+                                Text {
+                                    anchors.centerIn: parent
+                                    text: "\uf00c"
+                                    color: control.checkmarkGreen
+                                    font.pixelSize: iconSize - 6
+                                    font.family: iconFont.name
+                                    visible: checked
+                                    opacity: checked ? 1 : 0
+                                    Behavior on opacity { NumberAnimation { duration: 120 } }
+                                }
+                            }
+
+                            // 选项文本
+                            Text {
+                                id: itemLabel
+                                text: modelData.text
+                                color: control.textColor
+                                font.pixelSize: control.fontSize
+                                font.bold: checked
+                                elide: Text.ElideRight
+                                Layout.fillWidth: true
+                                Layout.alignment: Qt.AlignVCenter
+                            }
+                        }
+
+                        // === 选项交互逻辑 ===
+                        MouseArea {
+                            anchors.fill: parent
+                            hoverEnabled: true
+                            cursorShape: control.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+                            enabled: control.enabled
+
+                            onEntered: dropDownItem.hovered = true
+                            onExited: dropDownItem.hovered = false
+
+                            onPressed: {
+                                dropDownItem.scale = control.pressedScale
+                                dropDownItem.opacity = 0.85
+                            }
+
+                            onReleased: {
+                                dropDownItem.scale = 1.0
+                                dropDownItem.opacity = 1.0
+
+                                // 单选逻辑
+                                var newSelection = []
+                                if (control.selectedIndices.length === 0 || control.selectedIndices[0] !== index) {
+                                    newSelection.push(index)
+                                }
+                                control.selectedIndices = newSelection
+
+                                // 获取选中数据
+                                var selectedDataItems = []
+                                var selectedDataValue = []
+                                for (var i = 0; i < control.selectedIndices.length; i++) {
+                                    selectedDataValue.push(control.model[control.selectedIndices[i]].text)
+                                    selectedDataItems.push(control.model[control.selectedIndices[i]])
+                                }
+
+                                // 触发信号
+                                control.selectionChanged(control.selectedIndices, selectedDataItems)
+                                control.signalSelectionChanged(control.modelIndex, control.controlId, control.selectedIndices, selectedDataValue)
+
+                                // 选中后收起下拉框
+                                control.expanded = false
+                            }
+
+                            onCanceled: {
+                                dropDownItem.scale = 1.0
+                                dropDownItem.opacity = 1.0
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // === 必填提示文本 ===
+    Text {
+        id: textRequiredMsg
+        anchors.top: expanded ? dropDownContent.bottom : header.bottom
+        anchors.topMargin: 15
+        anchors.left: parent.left
+        visible: control.required && control.showRequiredMsg
+        text: qsTr(requiredMsg)
+        color: "red"
+        font.pixelSize: control.fontSize
+        font.bold: true
+        verticalAlignment: Text.AlignVCenter
+    }
+
+    // === 复用原组件的必填提示方法 ===
+    function slotShowRequiredMsg(submit) {
+        if (submit && control.selectedIndices.length === 0) {
+            control.showRequiredMsg = true;
+        }
+    }
+
+    // === 点击外部收起下拉框 ===
+    MouseArea {
+        anchors.fill: parent
+        z: -1
+        enabled: control.expanded
+        onClicked: {
+            control.expanded = false;
+        }
+    }
+}

+ 1 - 1
src/qml/components/MDatePicker.qml

@@ -22,7 +22,7 @@ Item {
     // === 样式属性 ===
     property int fontSize: 16
     property real radius: 10
-    property string showDatePopupSymbo: "📆"
+    property string showDatePopupSymbo: "\uf073"
     property color textColor: "white"
 
     // === 状态属性 ===

+ 281 - 0
src/qml/components/MSelectDateTime.qml

@@ -0,0 +1,281 @@
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+
+Item {
+    id: control
+
+    // ==== 外部接口====
+    property bool backgroundVisible: true
+    property real radius: 20
+    property int padding: 15
+    property bool shadowEnabled: true
+
+    // 选中的时分秒
+    property var selectedTime: {
+        const now = new Date();
+        return {
+            hour: now.getHours() || 0,
+            minute: now.getMinutes() || 0,
+            second: now.getSeconds() || 0
+        };
+    }
+    signal timeChanged(var newTime)  // 时间变更信号
+
+    // 滑动滚轮配置
+    property int wheelItemHeight: 40  // 每个数值项高度
+    property int wheelVisibleCount: 5 // 滚轮可见项数(奇数,确保中间项居中)
+    property real wheelWidth: 80      // 单个滚轮宽度
+
+    property real wheelVisibleHeight: wheelItemHeight * wheelVisibleCount
+    property real centerOffset: wheelVisibleHeight / 2
+    implicitWidth: wheelWidth * 3 + padding * 2 + 20
+    implicitHeight: wheelVisibleHeight + padding * 2 + 20 // 预留标签高度
+
+    function padZero(num) {
+        const n = Number(num) || 0;
+        return n < 10 ? "0" + n : n.toString();
+    }
+
+    function calculateContentY(selectedIndex, totalCount) {
+        let contentY = (selectedIndex * wheelItemHeight) + (wheelItemHeight/2) - control.centerOffset;
+        // 最大值居中的临界值
+        const maxContentY = ((totalCount - 1) * wheelItemHeight) + (wheelItemHeight/2) - control.centerOffset;
+        // 边界校验:确保contentY在合法范围
+        contentY = Math.max(0, Math.min(contentY, maxContentY));
+        return contentY;
+    }
+
+    function snapToItem(flickable, totalCount) {
+        const currentIndex = Math.round((flickable.contentY + control.centerOffset) / wheelItemHeight);
+        let targetIndex = (currentIndex % totalCount + totalCount) % totalCount;
+        // 计算精准的居中位置
+        const targetContentY = calculateContentY(targetIndex, totalCount);
+
+        flickable.contentY = Qt.binding(function() {
+            return targetContentY;
+        });
+        flickable.flickDeceleration = 2000; // 优化滑动手感
+
+        if (flickable === hourWheel) {
+            control.selectedTime.hour = targetIndex;
+        } else if (flickable === minuteWheel) {
+            control.selectedTime.minute = targetIndex;
+        } else if (flickable === secondWheel) {
+            control.selectedTime.second = targetIndex;
+        }
+        // 触发时间变更信号
+        control.timeChanged(control.selectedTime);
+    }
+
+    // ==== 视觉背景====
+    Rectangle {
+        id: background
+        visible: control.backgroundVisible
+        anchors.fill: parent
+        radius: control.radius
+        color: theme.secondaryColor || "#f0f0f0"
+        layer.enabled: control.shadowEnabled && control.backgroundVisible
+    }
+
+    // ==== 中间选中线 ====
+    Rectangle {
+        id: centerLine
+        anchors.verticalCenter: parent.verticalCenter
+        x: padding + 10
+        width: wheelWidth * 3 + 20
+        height: 2
+        color: "transparent"
+        border.width: 1
+        border.color: "#5a8fc4"
+        z: 10
+    }
+
+    // ==== 主体布局:时/分/秒滚轮并排 ====
+    RowLayout {
+        id: wheelsContainer
+        anchors.centerIn: parent
+        anchors.topMargin: 10
+        spacing: 10
+        width: wheelWidth * 3 + 20
+
+        // ==== 小时滚轮(含顶部标签)====
+        ColumnLayout {
+            spacing: 2
+
+            Text {
+                id: hourLabel
+                Layout.alignment: Qt.AlignHCenter
+                text: "时"
+                font.pixelSize: 14
+                color: "#000000" // 标签固定黑色
+                font.bold: true
+            }
+
+            Flickable {
+                id: hourWheel
+                width: wheelWidth
+                height: wheelVisibleHeight // 改用计算后的可见高度
+                clip: true
+                flickableDirection: Flickable.VerticalFlick
+                boundsBehavior: Flickable.StopAtBounds // 防止滚动越界
+                contentWidth: wheelWidth
+                contentHeight: 24 * wheelItemHeight
+                contentY: calculateContentY(control.selectedTime.hour, 24)
+
+                // 滑动结束后平滑对齐
+                onFlickEnded: snapToItem(hourWheel, 24)
+                onContentYChanged: {
+                    const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
+                    const safeIndex = (previewIndex % 24 + 24) % 24;
+                    console.log(padZero(safeIndex), safeIndex)
+                    console.log(control.selectedTime.hour, control.selectedTime.hour === 11);
+                }
+
+                Column {
+                    id: hourColumn
+                    width: wheelWidth
+
+                    Repeater {
+                        model: 24
+                        delegate: Text {
+                            width: wheelWidth
+                            height: wheelItemHeight
+                            text: padZero(modelData)
+                            font.pixelSize: 18
+                            font.bold: modelData === control.selectedTime.hour
+                            color: modelData === control.selectedTime.hour
+                                ? "#5a8fc4"
+                                : "#000000"
+                            horizontalAlignment: Text.AlignHCenter
+                            verticalAlignment: Text.AlignVCenter
+                        }
+                    }
+                }
+            }
+        }
+
+        // ==== 分钟滚轮(含顶部标签)====
+        ColumnLayout {
+            spacing: 2
+
+            Text {
+                id: minuteLabel
+                Layout.alignment: Qt.AlignHCenter
+                text: "分"
+                font.pixelSize: 14
+                color: "#000000" // 标签固定黑色
+                font.bold: true
+            }
+
+            Flickable {
+                id: minuteWheel
+                width: wheelWidth
+                height: wheelVisibleHeight
+                clip: true
+                flickableDirection: Flickable.VerticalFlick
+                boundsBehavior: Flickable.StopAtBounds
+                contentWidth: wheelWidth
+                contentHeight: 60 * wheelItemHeight
+                contentY: calculateContentY(control.selectedTime.minute, 60)
+
+                onFlickEnded: snapToItem(minuteWheel, 60)
+                onContentYChanged: {
+                    const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
+                    const safeIndex = (previewIndex % 60 + 60) % 60;
+                }
+
+                Column {
+                    id: minuteColumn
+                    width: wheelWidth
+
+                    Repeater {
+                        model: 60
+                        delegate: Text {
+                            width: wheelWidth
+                            height: wheelItemHeight
+                            text: padZero(modelData)
+                            font.pixelSize: 18
+                            font.bold: modelData === control.selectedTime.minute
+                            color: modelData === control.selectedTime.minute
+                                ? "#5a8fc4"
+                                : "#000000"
+                            horizontalAlignment: Text.AlignHCenter
+                            verticalAlignment: Text.AlignVCenter
+                        }
+                    }
+                }
+            }
+        }
+
+        // ==== 秒滚轮 ====
+        ColumnLayout {
+            spacing: 2
+
+            Text {
+                id: secondLabel
+                Layout.alignment: Qt.AlignHCenter
+                text: "秒"
+                font.pixelSize: 14
+                color: "#000000" // 标签固定黑色
+                font.bold: true
+            }
+
+            Flickable {
+                id: secondWheel
+                width: wheelWidth
+                height: wheelVisibleHeight
+                clip: true
+                flickableDirection: Flickable.VerticalFlick
+                boundsBehavior: Flickable.StopAtBounds
+                contentWidth: wheelWidth
+                contentHeight: 60 * wheelItemHeight
+                contentY: calculateContentY(control.selectedTime.second, 60)
+
+                onFlickEnded: snapToItem(secondWheel, 60)
+                onContentYChanged: {
+                    const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
+                    const safeIndex = (previewIndex % 60 + 60) % 60;
+                }
+
+                Column {
+                    id: secondColumn
+                    width: wheelWidth
+
+                    Repeater {
+                        model: 60
+                        delegate: Text {
+                            width: wheelWidth
+                            height: wheelItemHeight
+                            text: padZero(modelData)
+                            font.pixelSize: 18
+                            font.bold: modelData === control.selectedTime.second
+                            color: modelData === control.selectedTime.second
+                                ? "#5a8fc4"
+                                : "#000000"
+                            horizontalAlignment: Text.AlignHCenter
+                            verticalAlignment: Text.AlignVCenter
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // ==== 生命周期:初始化居中 ====
+    Component.onCompleted: {
+        Qt.callLater(() => {
+            hourWheel.contentY = calculateContentY(control.selectedTime.hour, 24);
+            minuteWheel.contentY = calculateContentY(control.selectedTime.minute, 60);
+            secondWheel.contentY = calculateContentY(control.selectedTime.second, 60);
+        });
+    }
+
+    // ==== 监听外部修改selectedTime,同步更新滚轮位置 ====
+    onSelectedTimeChanged: {
+        Qt.callLater(() => {
+            hourWheel.contentY = calculateContentY(Number(control.selectedTime.hour) || 0, 24);
+            minuteWheel.contentY = calculateContentY(Number(control.selectedTime.minute) || 0, 60);
+            secondWheel.contentY = calculateContentY(Number(control.selectedTime.second) || 0, 60);
+        });
+    }
+}

+ 153 - 0
src/qml/components/MTimePicker.qml

@@ -0,0 +1,153 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+
+Item {
+    id: control
+    width: 240
+    height: 40
+
+    // === 接口属性 & 信号 ===
+    property string controlId
+    property int modelIndex         // 特殊标记,可以省略循环WorkNodeFormModel的时间
+    property alias text: textField.text
+    property alias placeholderText: textField.placeholderText
+    signal accepted()  // 输入回车触发
+    signal signalTextChanged(int index, string id, string data);
+
+    property bool required: false
+    property string requiredMsg
+    property bool showRequiredMsg: false
+
+    // === 样式属性 ===
+    property int fontSize: 16
+    property real radius: 10
+    property string showDatePopupSymbo: "\uf073"
+    property color textColor: "white"
+
+    // === 状态属性 ===
+    property bool enabled: true
+    property bool backgroundVisible: true  // 背景显示控制
+
+    // === 背景与阴影 ===
+//    MultiEffect {
+//        source: background
+//        anchors.fill: background
+//        shadowEnabled: true
+//        shadowColor: theme.shadowColor
+//        shadowBlur: theme.shadowBlur
+//        shadowHorizontalOffset: theme.shadowXOffset
+//        shadowVerticalOffset: theme.shadowYOffset
+//    }
+
+    Rectangle {
+        id: background
+        anchors.fill: parent
+        radius: control.radius
+        // 无背景时:选中用主题高亮色,未选中用次级色;有背景时沿用主题边框色
+        // border.color: control.backgroundVisible
+        //                ? theme.getBorderColor(textField.activeFocus)
+        //                : (textField.activeFocus ? theme.focusColor : "#40A9FF")
+        border.color: "#40A9FF"
+        border.width: 1
+        // color: control.backgroundVisible ? theme.secondaryColor : "transparent"
+        color: "transparent"
+        opacity: control.enabled ? 1.0 : 0.6
+    }
+
+    // === 内容布局 ===
+    RowLayout {
+        id: layout
+        anchors.fill: parent
+        // anchors.margins: 8
+        spacing: 6
+
+        // === 输入框主体 ===
+        TextField {
+            id: textField
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            Layout.alignment: Qt.AlignVCenter
+
+            font.pixelSize: control.fontSize
+            color: control.textColor
+            placeholderTextColor: control.textColor
+            enabled: control.enabled
+            verticalAlignment: Text.AlignVCenter
+            echoMode: TextInput.Normal
+            background: null
+            onAccepted: control.accepted()
+
+            onFocusChanged: {
+                if (!focus) {
+                    forceActiveFocus()
+                }
+            }
+
+            onTextChanged: signalTextChanged(control.modelIndex, control.controlId, textField.text);
+        }
+
+        Text {
+            id: popupSymbo
+            Layout.rightMargin: 8
+            text: showDatePopupSymbo
+            color: "#666"
+            font.pixelSize: 16
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHCenter
+
+            MouseArea {
+                anchors.fill: parent
+                enabled: control.enabled
+                cursorShape: Qt.PointingHandCursor
+                onClicked: {
+                    // TODO: 显示Calendar控件
+                    // console.log("显示Calendar控件");
+                    calendarPopup.open();
+                }
+            }
+        }
+    }
+    Popup {
+        id: calendarPopup
+        x: layout.width - width
+        y: -__calendar.height/2 - textField.height - 10
+        width: 200
+        height: 100
+        modal: true  // 设置是否为模态窗口
+        // focus: visible  // 获取焦点
+        closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside  // 设置关闭策略
+
+        MSelectDateTime {
+            id: __calendar
+            anchors.centerIn: parent
+        }
+
+        Connections {
+            target: __calendar
+            function onTimeChanged(clickedDate) {
+                textField.text = Qt.formatDate(clickedDate, "HH-mm-SS");
+            }
+        }
+    }
+
+    Text {
+        id: textRequiredMsg
+        anchors.top: layout.bottom
+        anchors.topMargin: 15
+        anchors.left: layout.left
+        visible: control.required && control.showRequiredMsg
+        text: qsTr(requiredMsg)
+        color: "red"
+        font.pixelSize: control.fontSize
+        // 普通文字不使用图标字体
+        font.bold: true
+        verticalAlignment: Text.AlignVCenter
+    }
+
+    function slotShowRequiredMsg(submit) {
+        if (submit && textField.text === "") {
+            control.showRequiredMsg = true;
+        }
+    }
+}

+ 4 - 0
src/qml/components/ReturnKeyLockProcess.qml

@@ -32,6 +32,10 @@ Rectangle {
         errorDevices = ReturnKeyLockManager.getErrorDevices();
         noTaskDevices = ReturnKeyLockManager.getNoTaskDevices();
         console.log("[qml] taskGroups: ", taskGroups);
+        for (var i = 0; i < taskGroups.length; i++) {
+            var info = taskGroups[i];
+            console.log("[qml] taskGroups: ", info.taskName, info.orderNo, info.worker);
+        }
     }
 
     // 连接数据更新信号

+ 2 - 2
src/qml/main.qml

@@ -13,8 +13,8 @@ ApplicationWindow {
     width: 1920
     height: 1080
 
-    visibility: Window.FullScreen       // 设置窗口为全屏
-    //visibility: Window.Windowed          // 窗口模式,非全屏
+    //visibility: Window.FullScreen       // 设置窗口为全屏
+    visibility: Window.Windowed          // 窗口模式,非全屏
     flags: Qt.FramelessWindowHint       // 设置窗口无边框
 
     property int defaultLogoutSeconds: 120 + 1

+ 8 - 6
src/usr/BluetoothClient.cpp

@@ -63,7 +63,7 @@ BLEClient::~BLEClient()
 
 void BLEClient::startDeviceDiscovery()
 {
-//    QMutexLocker locker(&m_mutex);
+    QMutexLocker locker(&m_mutex);
 
     // 前置校验:避免重复启动/代理为空
     if (m_isSearching || !m_discoveryAgent) {
@@ -153,8 +153,8 @@ void BLEClient::onDiscoveryFinished()
 void BLEClient::connectDevice(const QString& macAddress, const QString& name)
 {
     Q_UNUSED(name);
-    QMutexLocker locker(&m_mutex);
-    if (m_leController && m_leController->state() == QLowEnergyController::UnconnectedState) {
+//    QMutexLocker locker(&m_mutex);
+    if (m_leController && m_leController->state() != QLowEnergyController::UnconnectedState) {
         emit connectStateChanged(QLowEnergyController::UnconnectedState, "当前非断开状态,无法连接");
         return;
     }
@@ -276,9 +276,10 @@ void BLEClient::connectDevice(const QString& macAddress, const QString& name)
         m_leController = nullptr;
     }
     if (m_targetService) {
-//        delete m_targetService;
+        delete m_targetService;
         m_targetService = nullptr;
     }
+    m_writeChar = QLowEnergyCharacteristic(); // 清空写特征值
 
     // 初始化蓝牙适配器
     QBluetoothLocalDevice localDev;
@@ -452,8 +453,9 @@ bool BLEClient::isConnected()
 
 void BLEClient::apiConnectDevice(const QString &macAddress)
 {
+    if (isConnected()) return;
     m_selectedMAC = macAddress;
-    m_discoveryAgent->start();
+    startDeviceDiscovery();
 }
 
 void BLEClient::run()
@@ -691,7 +693,7 @@ quint16 BLEClient::calculateCRC16(const QByteArray& data)
 void BLEClient::sendCmdWithRetry(const QByteArray& frame, const QString& cmdName, int retryCount)
 {
 //    QMutexLocker locker(&m_mutex);
-    qDebug() << "[sendCmdWithRetry]: " << !m_writeChar.isValid() << m_leController->state() << !m_targetService;
+//    qDebug() << "[sendCmdWithRetry]: " << !m_writeChar.isValid() << m_leController->state() << !m_targetService;
     if (!m_writeChar.isValid() || (m_leController && m_leController->state() != QLowEnergyController::DiscoveredState) || !m_targetService) {
         emit cmdSendResult(false, cmdName, "蓝牙未连接或写特征值/服务无效");
         return;

+ 158 - 267
src/usr/CANClient.cpp

@@ -23,8 +23,6 @@
 #endif
 // ========================================
 
-#define CAN_SLEEP_TIME 50
-
 // 十六进制转字符串
 std::string hexToString(uint8_t hex) {
     return QString("%1").arg(hex, 2, 16, QChar('0')).toUpper().toStdString();
@@ -70,12 +68,12 @@ CANClient::CANClient(QObject *parent)
 #elif defined(CAN_MODE_PHYSICAL)
     qInfo() << "[CAN] 模式: 物理CAN (生产环境)";
     // 物理CAN:需要完整配置
-    system("ifconfig can0 down");
-    system("ip link set up can0 type can bitrate 1000000 restart-ms 100");
+    system("sudo ifconfig can0 down");
+    system("sudo ip link set up can0 type can bitrate 1000000 restart-ms 100");
 #else
     qWarning() << "[CAN] 警告: 未选择CAN模式,使用默认物理CAN配置";
-    system("ifconfig can0 down");
-    system("ip link set up can0 type can bitrate 1000000 restart-ms 100");
+    system("sudo ifconfig can0 down");
+    system("sudo ip link set up can0 type can bitrate 1000000 restart-ms 100");
 #endif
 
     // 初始化插件
@@ -85,30 +83,6 @@ CANClient::CANClient(QObject *parent)
 
     connectDevice();
 
-//    m_readControlTimer = new QTimer(this);
-//    m_readControlTimer->setInterval(300);
-//    connect(m_readControlTimer, &QTimer::timeout, this, [this]() {
-//        readKeyBaseControlStatusAsync();
-//        msleep(CAN_SLEEP_TIME);
-//        readLockControlStatusAsync();
-//        msleep(CAN_SLEEP_TIME);
-//    });
-//    m_readControlTimer->start();
-
-//    m_readStatusTimer = new QTimer(this);
-//    m_readStatusTimer->setInterval(200);
-//    connect(m_readStatusTimer, &QTimer::timeout, this, [this]() {
-//        readKeyRFIDStatusAsync();
-//        msleep(CAN_SLEEP_TIME);
-//        readLockRFIDStatusAsync();
-//        msleep(CAN_SLEEP_TIME);
-//        readKeyBaseStatusAsync();
-//        msleep(CAN_SLEEP_TIME);
-//        readLockStatusAsync();
-//        msleep(CAN_SLEEP_TIME);
-//    });
-//    m_readStatusTimer->start();
-
     connect(this, &CANClient::workingKeyChanged, this, &CANClient::slotWorkingKeyChanged);
     connect(this, &CANClient::workingLocksChanged, this, &CANClient::slotWorkingLocksChanged);
 
@@ -177,23 +151,23 @@ void CANClient::run()
     qInfo() << "CAN监听线程启动!";
 
     while (m_threadstatus) {
-        for (int i = 0; i < 10; i++) {
+        for (int i = 0; i < 20; i++) {
             readDeviceInfoAsync(i);
             msleep(CAN_SLEEP_TIME);
         }
-        readKeyBaseStatusAsync();
+        readAllKeyBaseStatusAsync();
         msleep(CAN_SLEEP_TIME);
-        readLockStatusAsync();
+        readAllLocksStatusAsync();
         msleep(CAN_SLEEP_TIME);
 
-        readKeyBaseControlStatusAsync();
+        readAllKeyBaseControlStatusAsync();
         msleep(CAN_SLEEP_TIME);
-        readLockControlStatusAsync();
+        readAllLocksControlStatusAsync();
         msleep(CAN_SLEEP_TIME);
 
-        readKeyRFIDStatusAsync();
+        readAllKeyRFIDStatusAsync();
         msleep(CAN_SLEEP_TIME);
-        readLockRFIDStatusAsync();
+        readAllLocksRFIDStatusAsync();
         msleep(CAN_SLEEP_TIME);
     }
     qInfo() << "CAN监听线程退出!";
@@ -416,6 +390,7 @@ void CANClient::slotUpdateKeyBaseControl(quint8 nodeId, const CANKeyBaseControlS
 
     bool isWrite = false;
     CANKeyBaseChargeStatus writeStatus;
+    writeStatus.nodeId = nodeId;
 
     if (m_keyBaseStatus[nodeId].leftHasKey && !status.leftLocked) {
         writeStatus.leftLocked = true;
@@ -430,13 +405,13 @@ void CANClient::slotUpdateKeyBaseControl(quint8 nodeId, const CANKeyBaseControlS
             emit signalReturnKey();
         }
     }
-//    else if (!m_keyBaseStatus[nodeId].leftHasKey && status.leftLocked) {
-//        writeStatus.leftLocked = false;
-//        writeStatus.leftWorking = true;
-//        writeStatus.leftChargeEnable = false;
-//        writeStatus.leftChargeState = ChargeState::Charging;
-//        isWrite = true;
-//    }
+    else if (!m_keyBaseStatus[nodeId].leftHasKey && status.leftLocked) {
+        writeStatus.leftLocked = false;
+        writeStatus.leftWorking = true;
+        writeStatus.leftChargeEnable = false;
+        writeStatus.leftChargeState = ChargeState::Charging;
+        isWrite = true;
+    }
     if (m_keyBaseStatus[nodeId].rightHasKey && !status.rightLocked) {
         writeStatus.rightLocked = true;
         writeStatus.rightWorking = true;
@@ -450,13 +425,13 @@ void CANClient::slotUpdateKeyBaseControl(quint8 nodeId, const CANKeyBaseControlS
             emit signalReturnKey();
         }
     }
-//    else if (!m_keyBaseStatus[nodeId].rightHasKey && status.rightLocked) {
-//        writeStatus.rightLocked = false;
-//        writeStatus.rightWorking = true;
-//        writeStatus.rightChargeEnable = false;
-//        writeStatus.rightChargeState = ChargeState::Charging;
-//        isWrite = true;
-//    }
+    else if (!m_keyBaseStatus[nodeId].rightHasKey && status.rightLocked) {
+        writeStatus.rightLocked = false;
+        writeStatus.rightWorking = true;
+        writeStatus.rightChargeEnable = false;
+        writeStatus.rightChargeState = ChargeState::Charging;
+        isWrite = true;
+    }
 
     if (isWrite) {
         writeKeyBaseChargeControlAsync(writeStatus);
@@ -474,6 +449,8 @@ void CANClient::slotUpdateLockedStatus(quint8 nodeId, const CANLocksControl &sta
 
     bool isWrite = false;
     CANLocksControl writeStatus;
+    writeStatus.nodeId = nodeId;
+
     for (int i = 0; i < status.lockNums.length(); i++) {
         int lockNum = status.lockNums[i];
         QMap<int, bool> lockHasKeyMap = m_locksControlStatus[nodeId].lockHasKeyMap;
@@ -493,10 +470,17 @@ void CANClient::slotUpdateLockedStatus(quint8 nodeId, const CANLocksControl &sta
                 }
             }
         }
-//        else if ((lockHasKeyMap.find(lockNum) != lockHasKeyMap.end() && !lockHasKeyMap[lockNum]) &&
-//                 (lockLockedMap.find(lockNum) != lockLockedMap.end() && lockLockedMap[lockNum])) {
+        else if ((lockHasKeyMap.find(lockNum) != lockHasKeyMap.end() && !lockHasKeyMap[lockNum]) &&
+                 (lockLockedMap.find(lockNum) != lockLockedMap.end() && lockLockedMap[lockNum])) {
+            writeStatus.lockNums.append(lockNum);
+            writeStatus.lockLockedMap.insert(lockNum, false);
+            writeStatus.lockWorkMap.insert(lockNum, true);
+            isWrite = true;
+        }
+//        if ((lockHasKeyMap.find(lockNum) != lockHasKeyMap.end() && !lockHasKeyMap[lockNum]) &&
+//                 (lockLockedMap.find(lockNum) != lockLockedMap.end() && !lockLockedMap[lockNum])) {
 //            writeStatus.lockNums.append(lockNum);
-//            writeStatus.lockLockedMap.insert(lockNum, false);
+//            writeStatus.lockLockedMap.insert(lockNum, true);
 //            writeStatus.lockWorkMap.insert(lockNum, true);
 //            isWrite = true;
 //        }
@@ -682,20 +666,36 @@ QString CANClient::parseRFIDCard(const QByteArray& data)
     return cardStr;
 }
 
-bool CANClient::verifyNodeID(quint8 deviceNodeId, quint8& returnNodeId)
+//bool CANClient::verifyNodeID(quint8 deviceNodeId, quint8& returnNodeId)
+//{
+//    if (!m_deviceOfNodeId.values().contains(deviceNodeId)) {
+//        return false;
+//    }
+
+//    quint8 nodeId = 0;
+//    for (auto iter = m_deviceOfNodeId.begin(); iter != m_deviceOfNodeId.end(); ++iter) {
+//        if (iter.value() == deviceNodeId) {
+//            nodeId = iter.key();
+//            break;
+//        }
+//    }
+//    returnNodeId = nodeId;
+//    return true;
+//}
+
+bool CANClient::verifyAllNodeID(quint8 deviceNodeId, QList<quint8> &returnNodeId)
 {
     if (!m_deviceOfNodeId.values().contains(deviceNodeId)) {
         return false;
     }
 
-    quint8 nodeId = 0;
+    QList<quint8> nodeIds;
     for (auto iter = m_deviceOfNodeId.begin(); iter != m_deviceOfNodeId.end(); ++iter) {
         if (iter.value() == deviceNodeId) {
-            nodeId = iter.key();
-            break;
+            nodeIds.append(iter.key());
         }
     }
-    returnNodeId = nodeId;
+    returnNodeId = nodeIds;
     return true;
 }
 
@@ -789,16 +789,23 @@ void CANClient::readDeviceInfoAsync(quint8 nodeId)
     });
 }
 
-void CANClient::readKeyBaseStatusAsync()
+void CANClient::readAllKeyBaseStatusAsync()
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_EKEY_NODEID, nodeId)) {
+    QList<quint8> nodeIds;
+    if (!verifyAllNodeID(CAN_DEVICE_EKEY_NODEID, nodeIds)) {
         CANKeyBaseStatus emptyStatus;
-        emit keyBaseStatusRead(nodeId, emptyStatus);
+        emit keyBaseStatusRead(-1, emptyStatus);
         return;
     }
 
+    for (int i = 0; i < nodeIds.count(); i++) {
+        readKeyBaseStatusAsync(nodeIds[i]);
+        msleep(CAN_SLEEP_TIME);
+    }
+}
+
+void CANClient::readKeyBaseStatusAsync(quint8 nodeId)
+{
     QCanBusFrame frame = buildReadFrame(nodeId, CAN_EKEY_STATUS_INDEX, CAN_EKEY_STATUS_SUBINDEX);
 //    qDebug() << "发送readKeyBaseStatusAsync帧 FrameId: 0x" << QString::number(frame.frameId(), 16) << "Data:" << frame.payload().toHex().toUpper();
     sendFrameAsync(frame, [this, nodeId](const QCanBusFrame& respFrame) {
@@ -828,43 +835,6 @@ void CANClient::readKeyBaseStatusAsync()
 //                    << ",左充电:" << (status.leftCharging ? "充电中" : "未充电")
 //                    << "; 右钥匙:" << (status.rightHasKey ? "有" : "无")
 //                    << ",右充电:" << (status.rightCharging ? "充电中" : "未充电");
-            QMutexLocker locker(&m_insertDataMutex);
-
-//            bool prevLeftHasKey = false, prevRightHasKey = false;
-//            if (m_keyBaseStatus.find(nodeId) != m_keyBaseStatus.end()) {
-//                prevLeftHasKey = m_keyBaseStatus[nodeId].leftHasKey;
-//                prevRightHasKey = m_keyBaseStatus[nodeId].rightHasKey;
-//            }
-
-//            if (!m_keyBaseStatus.isEmpty()) {
-//                if (!prevLeftHasKey && status.leftHasKey) {
-//                    qInfo() << "[归还检测] 检测到左钥匙插入!";
-
-//                    if (m_keyRFIDStatus.find(nodeId) != m_keyRFIDStatus.end() &&
-//                        !m_keyRFIDStatus[nodeId].leftKeyRFID.isEmpty() &&
-//                        m_keyRFIDStatus[nodeId].leftKeyRFID != "00000000") {
-//                        qInfo() << "[归还检测] 左钥匙:NFC卡号:" << m_keyRFIDStatus[nodeId].leftKeyRFID;
-
-////                        emit nfcDeviceDetected(m_keyRFIDStatus[nodeId].leftKeyRFID, "key", 0);
-//                    } else {
-//                        qWarning() << "[归还检测] 左钥匙存在但无法读取NFC";
-//                        emit nfcDeviceError("key", 0);
-//                    }
-//                }
-//                else if (prevLeftHasKey && !status.leftHasKey) {
-//                    qInfo() << "[取出检测] 检测到左钥匙取出!";
-//                    if (m_keyRFIDStatus.find(nodeId) != m_keyRFIDStatus.end() &&
-//                        !m_keyRFIDStatus[nodeId].leftKeyRFID.isEmpty() &&
-//                        m_keyRFIDStatus[nodeId].leftKeyRFID != "00000000") {
-//                        qInfo() << "[取出检测] 左钥匙:NFC卡号:" << m_keyRFIDStatus[nodeId].leftKeyRFID;
-
-//                        emit signalPopDevices(m_keyRFIDStatus[nodeId].leftKeyRFID, "key", 0);
-//                    } else {
-//                        qWarning() << "[取出检测] 左钥匙存在但无法读取NFC";
-//                        emit nfcDeviceError("key", 0);
-//                    }
-//                }
-//            }
         }
         else {
             qWarning() << "[KeyBase] 读取状态失败";
@@ -875,16 +845,23 @@ void CANClient::readKeyBaseStatusAsync()
     });
 }
 
-void CANClient::readKeyBaseControlStatusAsync()
+void CANClient::readAllKeyBaseControlStatusAsync()
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_EKEY_NODEID, nodeId)) {
+    QList<quint8> nodeIds;
+    if (!verifyAllNodeID(CAN_DEVICE_EKEY_NODEID, nodeIds)) {
         CANKeyBaseControlStatus emptyStatus;
-        emit keyBaseControlStatusRead(nodeId, emptyStatus);
+        emit keyBaseControlStatusRead(-1, emptyStatus);
         return;
     }
 
+    for (int i = 0; i < nodeIds.count(); i++) {
+        readKeyBaseControlStatusAsync(nodeIds[i]);
+        msleep(CAN_SLEEP_TIME);
+    }
+}
+
+void CANClient::readKeyBaseControlStatusAsync(quint8 nodeId)
+{
     QCanBusFrame frame = buildReadFrame(nodeId, CAN_FASTENER_CONTROL_INDEX, CAN_FASTENER_CONTROL_SUBINDEX);
     sendFrameAsync(frame, [this, nodeId](const QCanBusFrame& respFrame) {
         CANKeyBaseControlStatus status;
@@ -914,29 +891,6 @@ void CANClient::readKeyBaseControlStatusAsync()
 
             status.success = true;
 
-//            bool prevLeftHasKey = false, prevRightHasKey = false;
-//            if (m_keyBaseControlStatus.find(nodeId) != m_keyBaseControlStatus.end()) {
-//                prevLeftHasKey = m_keyBaseControlStatus[nodeId].leftLocked;
-//                prevRightHasKey = m_keyBaseControlStatus[nodeId].rightLocked;
-//            }
-
-//            if (!m_keyBaseControlStatus.isEmpty()) {
-//                if (!prevLeftHasKey && status.leftLocked) {
-//                    qInfo() << "[归还检测] 检测到左钥匙插入!";
-
-//                    if (m_keyRFIDStatus.find(nodeId) != m_keyRFIDStatus.end() &&
-//                        !m_keyRFIDStatus[nodeId].leftKeyRFID.isEmpty() &&
-//                        m_keyRFIDStatus[nodeId].leftKeyRFID != "00000000") {
-//                        qInfo() << "[归还检测] 左钥匙:NFC卡号:" << m_keyRFIDStatus[nodeId].leftKeyRFID;
-
-//                        emit nfcDeviceDetected(m_keyRFIDStatus[nodeId].leftKeyRFID, "key", 0);
-//                    } else {
-//                        qWarning() << "[归还检测] 左钥匙存在但无法读取NFC";
-//                        emit nfcDeviceError("key", 0);
-//                    }
-//                }
-//            }
-
 //            qInfo() << "[KeyBase] 左钥匙:" << (status.leftLocked ? "锁住" : "未锁")
 //                    << ",左充电:" << (status.leftCharging ? "充电中" : "未充电") << (status.leftBuckleFault ? "故障" : "正常")
 //                    << "; 右钥匙:" << (status.rightLocked ? "锁住" : "未锁")
@@ -951,15 +905,23 @@ void CANClient::readKeyBaseControlStatusAsync()
     });
 }
 
-void CANClient::readLockStatusAsync()
+void CANClient::readAllLocksStatusAsync()
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_LOCK_NODEID, nodeId)) {
-        emit lockStatusRead(nodeId, CANLockControlStatus());
+    QList<quint8> nodeIds;
+    if (!verifyAllNodeID(CAN_DEVICE_LOCK_NODEID, nodeIds)) {
+        CANLockControlStatus emptyStatus;
+        emit lockStatusRead(-1, emptyStatus);
         return;
     }
 
+    for (int i = 0; i < nodeIds.count(); i++) {
+        readLockStatusAsync(nodeIds[i]);
+        msleep(CAN_SLEEP_TIME);
+    }
+}
+
+void CANClient::readLockStatusAsync(quint8 nodeId)
+{
     // 构建SDO读帧:索引0x6010(锁状态),子索引0x00,1字节数据
     QCanBusFrame frame = buildReadFrame(nodeId, CAN_LOCK_STATUS_INDEX, CAN_LOCK_STATUS_SUBINDEX);
 
@@ -991,55 +953,7 @@ void CANClient::readLockStatusAsync()
             }
 //            qDebug() << "FrameId: 0x" << QString::number(respFrame.frameId(), 16) << "Data:" << respFrame.payload().toHex().toUpper();
             status.success = true;
-            
-//            // ========== 检测锁插入(用于归还流程) ==========
-//            QMutexLocker locker(&m_insertDataMutex);
-            
-//            // 获取之前的锁状态
-//            QMap<int, bool> prevLockHasKeyMap;
-//            if (m_locksControlStatus.find(nodeId) != m_locksControlStatus.end()) {
-//                prevLockHasKeyMap = m_locksControlStatus[nodeId].lockHasKeyMap;
-//            }
-//            if (!m_locksControlStatus.isEmpty()) {
-//                // 检测每个锁位的插入(从无到有)
-//                for (int i = 0; i < 5; i++) {
-//                    bool prevHasLock = prevLockHasKeyMap.contains(i) ? prevLockHasKeyMap[i] : false;
-//                    bool currHasLock = status.lockHasKeyMap.contains(i) ? status.lockHasKeyMap[i] : false;
-
-//                    if (!prevHasLock && currHasLock) {
-//                        qInfo() << "[归还检测] 检测到" << (i+1) << "号锁插入!";
-//                        // 锁插入,获取NFC卡号
-//                        if (m_locksRFIDStatus.find(nodeId) != m_locksRFIDStatus.end() &&
-//                            m_locksRFIDStatus[nodeId].lockRFIDMap.contains(i) &&
-//                            !m_locksRFIDStatus[nodeId].lockRFIDMap[i].isEmpty() &&
-//                            m_locksRFIDStatus[nodeId].lockRFIDMap[i] != "00000000") {
-//                            qInfo() << "[归还检测]" << (i+1) << "号锁NFC卡号:" << m_locksRFIDStatus[nodeId].lockRFIDMap[i];
-
-//                            emit nfcDeviceDetected(m_locksRFIDStatus[nodeId].lockRFIDMap[i], "lock", i);
-//                            emit signalLockNfcDeviceDetected(m_locksRFIDStatus[nodeId].lockRFIDMap[i]);
-//                        } else {
-//                            qWarning() << "[归还检测]" << (i+1) << "号锁存在但无法读取NFC";
-//                            emit nfcDeviceError("lock", i);
-//                        }
-//                    }
-//                    else if (prevHasLock && !currHasLock) {
-//                        qInfo() << "[取出检测] 检测到"  << (i+1) << "号锁取出!";
-//                        qDebug() << m_locksRFIDStatus[nodeId].lockRFIDMap[i];
-//                        if (m_locksRFIDStatus.find(nodeId) != m_locksRFIDStatus.end() &&
-//                            m_locksRFIDStatus[nodeId].lockRFIDMap.contains(i) &&
-//                            !m_locksRFIDStatus[nodeId].lockRFIDMap[i].isEmpty() &&
-//                            m_locksRFIDStatus[nodeId].lockRFIDMap[i] != "00000000") {
-//                            qInfo() << "[取出检测]" << (i+1) << "号锁NFC卡号:" << m_locksRFIDStatus[nodeId].lockRFIDMap[i];
-
-//                            emit signalPopDevices(m_locksRFIDStatus[nodeId].lockRFIDMap[i], "lock", i);
-//                        } else {
-//                            qWarning() << "[取出检测]" << (i+1) << "号锁存在但无法读取NFC";
-//                            emit nfcDeviceError("lock", i);
-//                        }
-//                    }
-//                }
-//            }
-//            // ====================================================
+            status.nodeId = nodeId;
             
             if (m_locksControlStatus.find(nodeId) == m_locksControlStatus.end()) {
                 m_locksControlStatus.insert(nodeId, status);
@@ -1057,15 +971,23 @@ void CANClient::readLockStatusAsync()
     });
 }
 
-void CANClient::readLockControlStatusAsync()
+void CANClient::readAllLocksControlStatusAsync()
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_LOCK_NODEID, nodeId)) {
-        emit lockControlStatusRead(nodeId, CANLocksControl());
+    QList<quint8> nodeIds;
+    if (!verifyAllNodeID(CAN_DEVICE_LOCK_NODEID, nodeIds)) {
+        CANLocksControl emptyStatus;
+        emit lockControlStatusRead(-1, emptyStatus);
         return;
     }
 
+    for (int i = 0; i < nodeIds.count(); i++) {
+        readLockControlStatusAsync(nodeIds[i]);
+        msleep(CAN_SLEEP_TIME * 2);
+    }
+}
+
+void CANClient::readLockControlStatusAsync(quint8 nodeId)
+{
     QCanBusFrame frame = buildReadFrame(nodeId, CAN_LOCK_CONTROL_INDEX, CAN_LOCK_CONTROL_SUBINDEX);
     sendFrameAsync(frame, [this, nodeId](const QCanBusFrame& respFrame) {
         CANLocksControl status;
@@ -1103,11 +1025,7 @@ void CANClient::readLockControlStatusAsync()
 
 void CANClient::writeSingleLockBuckleAsync(const CANSingleLockStatus& status)
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_LOCK_NODEID, nodeId)) {
-        return;
-    }
+    quint8 nodeId = status.nodeId;
 
     if (!checkLockNumValid(status.lockNum)) {
         emit lockBuckleWrite(false, status.lockNum, status.isLocked);
@@ -1148,11 +1066,7 @@ void CANClient::writeSingleLockBuckleAsync(const CANSingleLockStatus& status)
 
 void CANClient::writeLockBuckleAsync(const CANLocksControl &status)
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_LOCK_NODEID, nodeId)) {
-        return;
-    }
+    quint8 nodeId = status.nodeId;
 
     uint8_t dataByte = 0x00;
     uint8_t workByte = 0x00;
@@ -1208,14 +1122,8 @@ void CANClient::writeLockBuckleAsync(const CANLocksControl &status)
     });
 }
 
-void CANClient::readTemperatureAsync()
+void CANClient::readTemperatureAsync(quint8 nodeId)
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_EKEY_NODEID, nodeId)) {
-        return;
-    }
-
     QCanBusFrame frame = buildReadFrame(nodeId, CAN_EKEY_TEMPERATURE_INDEX, CAN_EKEY_TEMPERATURE_SUBINDEX);
     sendFrameAsync(frame, [this, nodeId](const QCanBusFrame& respFrame) {
         double leftTemp = 0.0, rightTemp = 0.0;
@@ -1248,16 +1156,24 @@ void CANClient::readTemperatureAsync()
     });
 }
 
-void CANClient::readKeyRFIDStatusAsync()
+void CANClient::readAllKeyRFIDStatusAsync()
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_EKEY_NODEID, nodeId)) {
+    QList<quint8> nodeIds;
+    if (!verifyAllNodeID(CAN_DEVICE_EKEY_NODEID, nodeIds)) {
         return;
     }
 
+    for (int i = 0; i < nodeIds.count(); i++) {
+        readKeyRFIDStatusAsync(nodeIds[i]);
+        msleep(CAN_SLEEP_TIME);
+    }
+}
+
+void CANClient::readKeyRFIDStatusAsync(quint8 nodeId)
+{
     // 初始化钥匙RFID状态
     CANKeyRFIDStatus keyStatus;
+    keyStatus.nodeId = nodeId;
     keyStatus.leftKeyRFID = "";
     keyStatus.rightKeyRFID = "";
     keyStatus.leftKeyReadSuccess = false;
@@ -1299,7 +1215,7 @@ void CANClient::readKeyRFIDStatusAsync()
                    emit nfcDeviceDetected(currHasLock, "key", 0);
                 }
                 else if (prevLeftHasKey != currHasLock && (currHasLock.isEmpty() || currHasLock == "00000000")) {
-                    qInfo() << "[取出检测] 左钥匙:NFC卡号:" << currHasLock;
+                    qInfo() << "[取出检测] 左钥匙:NFC卡号:" << prevLeftHasKey;
 
                     emit signalPopDevices(prevLeftHasKey, "key", 0);
                 }
@@ -1388,14 +1304,21 @@ void CANClient::readKeyRFIDStatusAsync()
     msleep(CAN_SLEEP_TIME);
 }
 
-void CANClient::readSingleKeyRFIDAsync(bool isLeftKey)
+void CANClient::readAllLocksRFIDStatusAsync()
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_EKEY_NODEID, nodeId)) {
+    QList<quint8> nodeIds;
+    if (!verifyAllNodeID(CAN_DEVICE_LOCK_NODEID, nodeIds)) {
         return;
     }
 
+    for (int i = 0; i < nodeIds.count(); i++) {
+        readLockRFIDStatusAsync(nodeIds[i]);
+        msleep(CAN_SLEEP_TIME);
+    }
+}
+
+void CANClient::readSingleKeyRFIDAsync(quint8 nodeId, bool isLeftKey)
+{
     quint16 rfidIndex = isLeftKey ? CAN_LEFT_EKEY_CARD_INDEX : CAN_RIGHT_EKEY_CARD_INDEX;
     quint8 rfidSubIndex = isLeftKey ? CAN_LEFT_EKEY_CARD_SUBINDEX: CAN_RIGHT_EKEY_CARD_SUBINDEX;
     QCanBusFrame frame = buildReadFrame(nodeId, rfidIndex, rfidSubIndex);
@@ -1426,16 +1349,11 @@ void CANClient::readSingleKeyRFIDAsync(bool isLeftKey)
     });
 }
 
-void CANClient::readLockRFIDStatusAsync()
+void CANClient::readLockRFIDStatusAsync(quint8 nodeId)
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_LOCK_NODEID, nodeId)) {
-        return;
-    }
-
     // 初始化锁RFID状态
     CANLockRFIDStatus lockStatus;
+    lockStatus.nodeId = nodeId;
     for (int i = 0; i < 5; i++) {
         lockStatus.lockRFIDMap.insert(i, "");
         lockStatus.lockReadSuccessMap.insert(i, false);
@@ -1494,7 +1412,7 @@ void CANClient::readLockRFIDStatusAsync()
                     }
                     else if (prevHasLock != currHasLock && (currHasLock.isEmpty() || currHasLock == "00000000")) {
                         qInfo() << "[取出检测] 检测到" << (lockNum+1) << "号锁被取出!";
-                        qInfo() << "[取出检测]" << (lockNum+1) << "号锁NFC卡号:" << currHasLock;
+                        qInfo() << "[取出检测]" << (lockNum+1) << "号锁NFC卡号:" << prevHasLock;
                         emit signalPopDevices(prevHasLock, "lock", lockNum);
                     }
                 }
@@ -1529,14 +1447,8 @@ void CANClient::readLockRFIDStatusAsync()
     }
 }
 
-void CANClient::readSingleLockRFIDAsync(int lockNum)
+void CANClient::readSingleLockRFIDAsync(quint8 nodeId, int lockNum)
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_LOCK_NODEID, nodeId)) {
-        return;
-    }
-
     if (!checkLockNumValid(lockNum)) {
         emit singleLockRFIDRead(lockNum, false, "");
         return;
@@ -1571,15 +1483,10 @@ void CANClient::readSingleLockRFIDAsync(int lockNum)
     });
 }
 
-void CANClient::readKeyBaseChargeStatusAsync()
+void CANClient::readKeyBaseChargeStatusAsync(quint8 nodeId)
 {
-    quint8 nodeId = 0;
-
-    if (!verifyNodeID(CAN_DEVICE_EKEY_NODEID, nodeId)) {
-        return;
-    }
-
     CANKeyBaseChargeStatus chargeStatus;
+    chargeStatus.nodeId = nodeId;
     chargeStatus.leftChargeEnable = false;
     chargeStatus.rightChargeEnable = false;
     chargeStatus.leftChargeState = ChargeState::Uncharged;
@@ -1625,11 +1532,13 @@ bool CANClient::openEKey(QString& keyNFC)
 
     for (auto iter = m_keyBaseStatus.begin(); iter != m_keyBaseStatus.end(); ++iter) {
         if (iter->leftHasKey) {
+            chargeStatus.nodeId = iter.key();
             chargeStatus.leftLocked = true;
             chargeStatus.leftWorking = true;
             chargeStatus.leftChargeEnable = true;
             chargeStatus.leftChargeState = ChargeState::Charging;
 
+            unlockStatus.nodeId = iter.key();
             unlockStatus.leftLocked = true;
             unlockStatus.leftWorking = true;
             unlockStatus.leftChargeEnable = false;
@@ -1641,11 +1550,13 @@ bool CANClient::openEKey(QString& keyNFC)
             break;
         }
         if (iter->rightHasKey) {
+            chargeStatus.nodeId = iter.key();
             chargeStatus.rightLocked = true;
             chargeStatus.rightWorking = true;
             chargeStatus.rightChargeEnable = true;
             chargeStatus.rightChargeState = ChargeState::Charging;
 
+            unlockStatus.nodeId = iter.key();
             unlockStatus.rightLocked = true;
             unlockStatus.rightWorking = true;
             unlockStatus.rightChargeEnable = false;
@@ -1668,6 +1579,7 @@ bool CANClient::unlockEKey()
 {
     CANKeyBaseChargeStatus chargeStatus;
     for (auto iter = m_keyBaseStatus.begin(); iter != m_keyBaseStatus.end(); ++iter) {
+        chargeStatus.nodeId = iter.key();
         if (iter->leftHasKey) {
             chargeStatus.leftLocked = false;
             chargeStatus.leftWorking = true;
@@ -1734,6 +1646,7 @@ bool CANClient::unlockLocks(const QList<int>& lockNums)
     for (auto iter = m_locksControlStatus.begin(); iter != m_locksControlStatus.end(); ++iter) {
         for (auto hasKeyIter = iter->lockHasKeyMap.begin(); hasKeyIter != iter->lockHasKeyMap.end(); ++hasKeyIter) {
             if (hasKeyIter.value() && lockNums.contains(hasKeyIter.key())) {
+                lockStatus.nodeId = iter.key();
                 lockStatus.lockNums.append(hasKeyIter.key());
                 lockStatus.lockLockedMap.insert(hasKeyIter.key(), false);
                 lockStatus.lockWorkMap.insert(hasKeyIter.key(), true);
@@ -1755,35 +1668,12 @@ bool CANClient::unlockLocks(const QList<int>& lockNums)
     return true;
 }
 
-bool CANClient::lockingEKey(quint8 nodeId, bool leftKey, bool rightKey)
-{
-    Q_UNUSED(nodeId);
-    CANKeyBaseChargeStatus chargeStatus;
-
-    if (leftKey) {
-        chargeStatus.leftLocked = true;
-        chargeStatus.leftWorking = true;
-        chargeStatus.leftChargeEnable = true;
-        chargeStatus.leftChargeState = ChargeState::Charging;
-
-    }
-    if (rightKey) {
-        chargeStatus.rightLocked = true;
-        chargeStatus.rightWorking = true;
-        chargeStatus.rightChargeEnable = true;
-        chargeStatus.rightChargeState = ChargeState::Charging;
-    }
-    writeKeyBaseChargeControlAsync(chargeStatus);
-    msleep(CAN_SLEEP_TIME);
-    return true;
-}
-
 void CANClient::unlockEKey(const QString &keyNFC)
 {
     CANKeyBaseChargeStatus chargeStatus;
 
     for (auto iter = m_keyRFIDStatus.begin(); iter != m_keyRFIDStatus.end(); ++iter) {
-//        int nodeId = iter.key();
+        chargeStatus.nodeId = iter.key();
         if (keyNFC == iter->leftKeyRFID) {
             chargeStatus.leftLocked = false;
             chargeStatus.leftWorking = true;
@@ -1810,10 +1700,11 @@ void CANClient::unlockEKey(const QString &keyNFC)
 void CANClient::unlockLock(const QString &lockNFC)
 {
     CANLocksControl lockStatus;
-    // 暂时只处理一个5路锁
+
     for (auto iter = m_locksRFIDStatus.begin(); iter != m_locksRFIDStatus.end(); ++iter) {
         for (auto hasKeyIter = iter->lockRFIDMap.begin(); hasKeyIter != iter->lockRFIDMap.end(); ++hasKeyIter) {
             if (hasKeyIter.value() == lockNFC) {
+                lockStatus.nodeId = iter.key();
                 lockStatus.lockNums.append(hasKeyIter.key());
                 lockStatus.lockLockedMap.insert(hasKeyIter.key(), false);
                 lockStatus.lockWorkMap.insert(hasKeyIter.key(), true);
@@ -1828,11 +1719,11 @@ void CANClient::unlockLock(const QString &lockNFC)
 
 void CANClient::writeKeyBaseChargeControlAsync(CANKeyBaseChargeStatus& chargeStatus)
 {
-    quint8 nodeId = 0;
+    quint8 nodeId = chargeStatus.nodeId;
 
-    if (!verifyNodeID(CAN_DEVICE_EKEY_NODEID, nodeId)) {
-        return;
-    }
+//    if (!verifyNodeID(CAN_DEVICE_EKEY_NODEID, nodeId)) {
+//        return;
+//    }
 
     // ========== 写入卡扣充电使能 ==========
     uint8_t statusByte = 0x00;
@@ -1953,8 +1844,8 @@ QString CANClient::getLockRFID(int lockNum)
             return itemRFID.lockRFIDMap[lockNum];
         }
     }
-    readLockRFIDStatusAsync();
-    msleep(CAN_SLEEP_TIME);
+//    readLockRFIDStatusAsync();
+//    msleep(CAN_SLEEP_TIME);
     return QString();
 }
 

+ 29 - 16
src/usr/CANClient.h

@@ -13,11 +13,14 @@
 
 #include "define.h"
 
+#define CAN_SLEEP_TIME 50
+
 // 响应回调函数类型定义
 using CANResponseCallback = std::function<void(const QCanBusFrame& respFrame)>;
 
 typedef struct {
     bool success = false;           // 读取是否成功
+    quint8 nodeId = 0;
     bool leftHasKey = false;        // 左钥匙是否存在
     bool leftCharging = false;      // 左钥匙是否充电中
     bool rightHasKey = false;       // 右钥匙是否存在
@@ -27,6 +30,7 @@ Q_DECLARE_METATYPE(CANKeyBaseStatus)
 
 typedef struct {
     bool success = false;           // 读取是否成功
+    quint8 nodeId = 0;
     bool leftLocked = false;        // 左钥匙是否锁住
     bool leftCharging = false;      // 左钥匙是否充电中
     bool rightLocked = false;       // 右钥匙是否锁住
@@ -39,6 +43,7 @@ Q_DECLARE_METATYPE(CANKeyBaseControlStatus)
 
 typedef struct {
     bool success = false;         // 读取是否成功
+    quint8 nodeId = 0;
     QList<int> lockNums;
     QMap<int, bool> lockHasKeyMap;   // 1-5号锁有锁/无锁(索引0=1号锁)
     QMap<int, bool> lockFaultMap; // 锁故障状态(key=锁号)
@@ -47,6 +52,7 @@ Q_DECLARE_METATYPE(CANLockControlStatus)
 
 typedef struct {
     int lockNum = 0;              // 锁号1-5
+    quint8 nodeId = 0;
     bool isWorking = false;       // 设置工作状态
     bool isLocked = false;        // 设置锁定状态
 } CANSingleLockStatus;
@@ -54,6 +60,7 @@ Q_DECLARE_METATYPE(CANSingleLockStatus)
 
 typedef struct {
     bool success = false;
+    quint8 nodeId = 0;
     QList<int> lockNums;
     QMap<int, bool> lockWorkMap;    // 设置工作状态(key=锁号)
     QMap<int, bool> lockLockedMap;  // 设置锁定状态(key=锁号)
@@ -63,6 +70,7 @@ Q_DECLARE_METATYPE(CANLocksControl)
 
 typedef struct {
     bool success = false;               // 读取是否成功
+    quint8 nodeId = 0;
     QString leftKeyRFID;                // 左钥匙卡号
     QString rightKeyRFID;               // 右钥匙卡号
     bool leftKeyReadSuccess = false;    // 左钥匙单独读取成功标记
@@ -72,6 +80,7 @@ Q_DECLARE_METATYPE(CANKeyRFIDStatus)
 
 typedef struct {
     bool success = false;                   // 读取是否成功
+    quint8 nodeId = 0;
     QMap<int, QString> lockRFIDMap;         // 各锁卡号(key=锁号)
     QMap<int, bool> lockReadSuccessMap;     // 各锁读取成功标记(key=锁号)
 } CANLockRFIDStatus;
@@ -84,6 +93,7 @@ enum class ChargeState {
 
 typedef struct {
     bool success = false;         // 读取/写入是否成功
+    quint8 nodeId = 0;
 
     // 可写控制项(仅充电使能)
     bool leftChargeEnable = false;// 左卡扣充电使能(1=开启,0=关闭)
@@ -245,7 +255,8 @@ private:
     // 解析RFID卡号(小端4字节)
     QString parseRFIDCard(const QByteArray& data);
 
-    bool verifyNodeID(quint8 deviceNodeId, quint8& returnNodeId);
+//    bool verifyNodeID(quint8 deviceNodeId, quint8& returnNodeId);
+    bool verifyAllNodeID(quint8 deviceNodeId, QList<quint8>& returnNodeId);
 
     ChargeState bitToChargeState(uint8_t bitValue) {
         switch (bitValue) {
@@ -272,37 +283,42 @@ private:
     //
     void readDeviceInfoAsync(quint8 nodeId);
     // 电子钥匙底座
-    void readKeyBaseStatusAsync();
-    void readKeyBaseControlStatusAsync();
+    void readAllKeyBaseStatusAsync();
+    void readKeyBaseStatusAsync(quint8 nodeId);
+    void readAllKeyBaseControlStatusAsync();
+    void readKeyBaseControlStatusAsync(quint8 nodeId);
     // 5路挂锁底座
-    void readLockStatusAsync();
-    void readLockControlStatusAsync();
+    void readAllLocksStatusAsync();
+    void readLockStatusAsync(quint8 nodeId);
+    void readAllLocksControlStatusAsync();
+    void readLockControlStatusAsync(quint8 nodeId);
+
     void writeSingleLockBuckleAsync(const CANSingleLockStatus& status);
     void writeLockBuckleAsync(const CANLocksControl& status);
     // 温湿度
-    void readTemperatureAsync();
+    void readTemperatureAsync(quint8 nodeId);
 
     // 批量读取:仅左+右钥匙
-    void readKeyRFIDStatusAsync();
+    void readAllKeyRFIDStatusAsync();
+    void readKeyRFIDStatusAsync(quint8 nodeId);
     // 批量读取:仅1-5号锁
-    void readLockRFIDStatusAsync();
+    void readAllLocksRFIDStatusAsync();
+    void readLockRFIDStatusAsync(quint8 nodeId);
     // 单个读取:仅左/右钥匙
-    void readSingleKeyRFIDAsync(bool isLeftKey);
+    void readSingleKeyRFIDAsync(quint8 nodeId, bool isLeftKey);
     // 单个读取:仅某一个锁
-    void readSingleLockRFIDAsync(int lockNum);
+    void readSingleLockRFIDAsync(quint8 nodeId, int lockNum);
 
     // 左+右卡扣充电使能
     void writeKeyBaseChargeControlAsync(CANKeyBaseChargeStatus& chargeStatus);
     // 读取卡扣当前充电状态
-    void readKeyBaseChargeStatusAsync();
+    void readKeyBaseChargeStatusAsync(quint8 nodeId);
 public:
     bool openEKey(QString& keyNFC);
     bool unlockEKey();
     bool getLockRfids(int lockCount, QMap<int, QString>& rfids);
     bool unlockLocks(const QList<int>& lockNums);
 
-    bool lockingEKey(quint8 nodeId, bool leftKey, bool rightKey);
-
     void unlockEKey(const QString& keyNFC);
     void unlockLock(const QString& lockNFC);
 public:
@@ -326,9 +342,6 @@ private:
     QMap<QString, CANResponseCallback> m_requestMap;
     QMutex m_requestMutex;
 
-    QTimer* m_readControlTimer;
-    QTimer* m_readStatusTimer;
-
     QMutex m_insertDataMutex;
 
     QMap<quint8, CANKeyBaseStatus> m_keyBaseStatus;

+ 2 - 2
src/usr/config.h

@@ -73,8 +73,8 @@ public:
 
     QString m_systemMACAddr;
 
-//    QString httpHost = "120.27.232.27:9292";
-    QString httpHost = "192.168.0.10:48080";
+    QString httpHost = "120.27.232.27:9292";
+//    QString httpHost = "192.168.0.10:48080";
     QString tenant_id = "149";
 
     QString userInfoUrl = "/admin-api/system/user/profile/get";                                             // 用户中心