Selaa lähdekoodia

V1.4
1、解决了报警文件录制的bug

Apple 1 viikko sitten
vanhempi
commit
3391eb6e75
27 muutettua tiedostoa jossa 595 lisäystä ja 160 poistoa
  1. 2 1
      SQL/ACAServer.sql
  2. 2 0
      Server/CMakeLists.txt
  3. 31 11
      Server/ThreadManager/ThreadWriteDBManager.cpp
  4. 3 2
      Server/ThreadManager/ThreadWriteDBManager.h
  5. 160 107
      Server/ThreadRecord/CreateRecordFileThread.cpp
  6. 10 1
      Server/ThreadRecord/CreateRecordFileThread.h
  7. 7 7
      Server/common/LHLog/LHLogInit.cpp
  8. 11 1
      Server/main.cpp
  9. 63 1
      SettingLibrary/Modules/AICompare/aicomparewidget.cpp
  10. 6 1
      SettingLibrary/Modules/AICompare/aicomparewidget.h
  11. 10 9
      SettingLibrary/Modules/Basic/TableDelegate/DelegateCheckBox.cpp
  12. 50 0
      SettingLibrary/Modules/Basic/basicwidget.cpp
  13. 5 0
      SettingLibrary/Modules/Basic/basicwidget.h
  14. 28 6
      SettingLibrary/Modules/Basic/compareitemdialog.cpp
  15. 7 0
      SettingLibrary/Modules/Basic/compareitemlistdialog.cpp
  16. 19 0
      SettingLibrary/Modules/Basic/compareitemlistdialog.h
  17. 14 3
      SettingLibrary/Modules/Basic/singlecompareroadwidget.cpp
  18. 2 0
      SettingLibrary/Modules/Basic/singlecompareroadwidget.h
  19. 69 5
      SettingLibrary/Modules/Noise/noisemonitorparamdialog.cpp
  20. 2 0
      SettingLibrary/Modules/Noise/noisemonitorparamdialog.h
  21. 42 2
      SettingLibrary/Modules/Noise/noisewidget.cpp
  22. 5 0
      SettingLibrary/Modules/Noise/noisewidget.h
  23. 11 0
      SettingLibrary/Resources/qss/white/aicomparewidget.qss
  24. 11 0
      SettingLibrary/Resources/qss/white/basicwidget.qss
  25. 12 0
      SettingLibrary/Resources/qss/white/noisewidget.qss
  26. 5 0
      SettingLibrary/Resources/qss/white/singlecompareroadwidget.qss
  27. 8 3
      show1/widget.cpp

+ 2 - 1
SQL/ACAServer.sql

@@ -37,7 +37,8 @@ ORDER BY PKID DESC;
 #录音文件信息
 SELECT *
 FROM tACARecordFile
-ORDER BY PKID DESC;
+ORDER BY PKID DESC
+LIMIT 100;
 
 
 #获取声卡信息

+ 2 - 0
Server/CMakeLists.txt

@@ -31,6 +31,7 @@ file(GLOB LOCAL_SRC
     ${CMAKE_SOURCE_DIR}/common/DataManager/*.cpp
     ${CMAKE_SOURCE_DIR}/common/Network/*.cpp
     ${CMAKE_SOURCE_DIR}/common/GlobalInfo/*.cpp
+    ${CMAKE_SOURCE_DIR}/External/common/SingleApplication/*.cpp
 
     ${CMAKE_SOURCE_DIR}/External/common/Thread/*.cpp
 
@@ -82,6 +83,7 @@ target_include_directories(${this_exe} PRIVATE
     
     ${CMAKE_SOURCE_DIR}/External/common
     ${CMAKE_SOURCE_DIR}/External/common/Thread
+    ${CMAKE_SOURCE_DIR}/External/common/SingleApplication
     
     ${CMAKE_SOURCE_DIR}/External/module
     ${CMAKE_SOURCE_DIR}/External/module/ThreadPool

+ 31 - 11
Server/ThreadManager/ThreadWriteDBManager.cpp

@@ -87,8 +87,13 @@ void ThreadWriteDBManager::addAlarmInfo(const AlarmInfo_t& mainAlarm, const Alar
 void ThreadWriteDBManager::addRecordFileInfo(std::list<RecordFileInfo_t>& listRecordFile, bool isNewFile)
 {
     std::lock_guard<std::mutex> lock(m_mutexListRecordFile);
-    m_listRecordFile.insert(m_listRecordFile.end(), listRecordFile.begin(), listRecordFile.end());
-    m_isRecordFileInsert.store(isNewFile);
+    if(isNewFile)
+    {
+        m_listRecordFileInsert.insert(m_listRecordFileInsert.end(), listRecordFile.begin(), listRecordFile.end());
+    } else {
+        m_listRecordFileUpdate.insert(m_listRecordFileUpdate.end(), listRecordFile.begin(), listRecordFile.end());
+    }
+
 }
 
 
@@ -230,32 +235,47 @@ void ThreadWriteDBManager::consistencyAlarmInfoToDB()
 /* 录音文件写数据库 */
 void ThreadWriteDBManager::recordFileInfoToDB()
 {
-    std::list<RecordFileInfo_t> listRecordFile;
+    std::list<RecordFileInfo_t> listRecordFileInsert;
+    std::list<RecordFileInfo_t> listRecordFileUpdate;
     {
         std::lock_guard<std::mutex> lock(m_mutexListRecordFile);
-        if(m_listRecordFile.empty())
+        if(m_listRecordFileInsert.empty() && m_listRecordFileUpdate.empty())
         {
             return; // 没有录音文件信息
         }
-        listRecordFile = std::move(m_listRecordFile); // 移动录音文件信息列表
-        m_listRecordFile.clear();
+        /* 移动录音文件 */
+        if(!m_listRecordFileInsert.empty())
+        {
+            listRecordFileInsert = std::move(m_listRecordFileInsert); // 移动录音文件信息列表
+            m_listRecordFileInsert.clear();
+        }
+        if(!m_listRecordFileUpdate.empty())
+        {
+            listRecordFileUpdate = std::move(m_listRecordFileUpdate); // 移动录音文件信息列表
+            m_listRecordFileUpdate.clear();
+        }
     }
     
     /* 处理录音文件路径 */
-    for(auto& recordFile : listRecordFile)
+    for(auto& recordFile : listRecordFileInsert)
+    {
+        processFilePath(recordFile.FilePath);
+    }
+    for(auto& recordFile : listRecordFileUpdate)
     {
         processFilePath(recordFile.FilePath);
     }
 
-    if(m_isRecordFileInsert.load())
+    if(!listRecordFileInsert.empty())
     {
-        if(!m_fromWebAPI.insertRecordFileInfo(listRecordFile))
+        if(!m_fromWebAPI.insertRecordFileInfo(listRecordFileInsert))
         {
             SPDLOG_LOGGER_ERROR(m_logger, "写入录音文件信息失败");
         }
-    } else 
+    }
+    if(!listRecordFileUpdate.empty())
     {
-        if(!m_fromWebAPI.updateRecordFileInfo(listRecordFile))
+        if(!m_fromWebAPI.updateRecordFileInfo(listRecordFileUpdate))
         {
             SPDLOG_LOGGER_ERROR(m_logger, "更新录音文件信息失败");
         }

+ 3 - 2
Server/ThreadManager/ThreadWriteDBManager.h

@@ -128,8 +128,9 @@ private:
     /* ---------------------------- 录音文件信息 ---------------------------- */
     /* 存储录音文件信息 */
     std::mutex m_mutexListRecordFile;               /* 录音文件信息列表的互斥锁 */
-    std::list<RecordFileInfo_t> m_listRecordFile;
-    std::atomic_bool m_isRecordFileInsert = false;  /* 录音文件信息是否是插入数据库 */
+    std::list<RecordFileInfo_t> m_listRecordFileInsert; /* 录音文件写入列表 */
+    std::list<RecordFileInfo_t> m_listRecordFileUpdate; /* 录音文件更新列表 */
+    // std::atomic_bool m_isRecordFileInsert = false;  /* 录音文件信息是否是插入数据库 */
 
     /* ---------------------------- 数据库日志 ---------------------------- */
     QDateTime m_lastProcessLogTime;                 /* 上次处理日志的时间 */

+ 160 - 107
Server/ThreadRecord/CreateRecordFileThread.cpp

@@ -75,7 +75,7 @@ bool CreateRecordFileThread::setData(const AudioSrcData& srcData)
         return false;
     }
     /* 记录日期 */
-    if(m_bufferData.startTime.isNull() || m_bufferData.startTime.isValid())
+    if(m_bufferData.startTime.isNull() || !m_bufferData.startTime.isValid())
     {
         m_bufferData.startTime = srcData.startTime;
     }
@@ -141,28 +141,31 @@ bool CreateRecordFileThread::stopRecordLongFile(const OneCompareItemRoadInfo_t&
 /* 开启录制 */
 bool CreateRecordFileThread::startRecordAlarmFile(const AlarmInfo_t& alarmInfo)
 {
-    std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
-    /* 先检查是否已经在队列中了 */
-    AlarmKey_t key = {alarmInfo.CompareItemID, alarmInfo.RoadInfo.nCompareRoadNum, 
-                        alarmInfo.AlarmType, alarmInfo.StartTime};
-    if(m_mapAlarmFile.find(key) != m_mapAlarmFile.end())
-    {
-        SPDLOG_LOGGER_WARN(m_logger, "{} 对比项ID:{},{}, 通道:{}, 报警类型: {}, 已经在报警录制队列中,无法重复添加", m_logBase, 
-                                        alarmInfo.CompareItemID, alarmInfo.strCompareItemName.toStdString(), alarmInfo.RoadInfo.nCompareRoadNum,
-                                        static_cast<int>(alarmInfo.AlarmType));
-        return true;
-    }
+    {
+        std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
+        /* 先检查是否已经在队列中了 */
+        AlarmKey_t key = {alarmInfo.CompareItemID, alarmInfo.RoadInfo.nCompareRoadNum, 
+                            alarmInfo.AlarmType, alarmInfo.StartTime};
+        if(m_mapAlarmFile.find(key) != m_mapAlarmFile.end())
+        {
+            SPDLOG_LOGGER_WARN(m_logger, "{} 对比项ID:{},{}, 通道:{}, 报警类型: {}, 已经在报警录制队列中,无法重复添加", m_logBase, 
+                                            alarmInfo.CompareItemID, alarmInfo.strCompareItemName.toStdString(), alarmInfo.RoadInfo.nCompareRoadNum,
+                                            static_cast<int>(alarmInfo.AlarmType));
+            return true;
+        }
 
-    /* 将数据值插入map中 */
-    AlarmValue_t value;
-    value.alarmType = alarmInfo.AlarmType;
-    value.startTime = alarmInfo.StartTime;
-    value.endTime = alarmInfo.EndTime;
-    value.numConpareItemID = alarmInfo.CompareItemID;
-    value.strCompareItemName = alarmInfo.strCompareItemName;
-    value.itemRoadInfo = alarmInfo.RoadInfo;
+        /* 将数据值插入map中 */
+        AlarmValue_t value;
+        value.alarmType = alarmInfo.AlarmType;
+        value.startTime = alarmInfo.StartTime;
+        value.endTime = alarmInfo.EndTime;
+        value.numConpareItemID = alarmInfo.CompareItemID;
+        value.strCompareItemName = alarmInfo.strCompareItemName;
+        value.itemRoadInfo = alarmInfo.RoadInfo;
 
-    m_mapAlarmFile.insert({key, value});
+        m_mapAlarmFile.insert({key, value});
+    }
+    m_cond_var.notify_one();
 
     return true;
 }
@@ -214,8 +217,9 @@ void CreateRecordFileThread::task()
     
     while(m_isRunning)
     {
-        /* 线程休眠100ms */
-        std::this_thread::sleep_for(std::chrono::milliseconds(100));
+        /* 使用条件变量阻塞线程,加快开启报警时的响应速度 */
+        std::unique_lock<std::mutex> lock(m_mutexCondVar);
+        m_cond_var.wait_for(lock, std::chrono::milliseconds(500));
 
         /*--------------------------------------------------------------
         * 写入报警文件
@@ -321,8 +325,10 @@ bool CreateRecordFileThread::writeLongRecordFile()
     {
         /* 如果没有写入过数据,则是新文件 */
         isNewFile = true;
-        m_writtenStartTime = m_srcData.startTime; // 记录开始时间
-        m_writtenNowTime = m_writtenStartTime; // 记录当前时间
+        m_writtenStartTime = m_srcData.startTime;   // 记录开始时间
+        m_writtenNowTime = m_writtenStartTime;
+        // SPDLOG_LOGGER_WARN(m_logger, "新录音文件: {} - {}", m_writtenStartTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(),
+        //     m_writtenNowTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
     }
     /* 设置今日目录 */
     if(!setTodayPath(isNewFile))
@@ -353,7 +359,7 @@ bool CreateRecordFileThread::writeLongRecordFile()
     {
         std::lock_guard<std::mutex> lock(m_mutexBuffer);
         wSize = wavFile.write(m_srcData.pData, m_srcData.dataSize);
-        /* 更新结束时间 */
+        /* 记录当前结束时间 */
         m_writtenNowTime = m_srcData.endTime;
         /* 清空缓冲区 */
         m_srcData.clear();
@@ -368,8 +374,8 @@ bool CreateRecordFileThread::writeLongRecordFile()
         SPDLOG_LOGGER_TRACE(m_logger, "{} 写入WAV文件成功: {}, 大小: {} 字节", m_logBase, m_wavFileName.toStdString(), wSize);
     }
     wavFile.close();
-    // SPDLOG_LOGGER_DEBUG(m_logger, "{} 写入WAV文件完成: {}, 大小: {} 字节", 
-    //     m_logBase, m_wavFileName.toStdString(), wSize);
+    // SPDLOG_LOGGER_DEBUG(m_logger, "写入录音文件成功, 写入的时间段: {} - {}", 
+    //     m_writtenStartTime.toString("yyyy-MM-dd hh:mm:ss").toStdString(), m_writtenNowTime.toString("yyyy-MM-dd hh:mm:ss").toStdString());
 
     /*--------------------------------------------------------------
      * 对该文件进行其他操作,判断是否已经过了一个整点,修改其文件名称
@@ -398,16 +404,22 @@ bool CreateRecordFileThread::writeLongRecordFile()
         SPDLOG_LOGGER_INFO(m_logger, "{} 结束记录一个文件: {}, 已写入大小: {} 字节", 
             m_logBase, m_wavFileName.toStdString(), m_writtenSize);
         isRecordCompleted = true;
+    }
+
+    /* 更新文件信息到数据库 */
+    updateRecordFileInfoToDB(isNewFile, isRecordCompleted);
+
+    /* 清空已经结束的文件信息 */
+    if(isRecordCompleted)
+    {
         /* 重置已写入大小 */
         m_writtenSize = 0;
         m_writtenStartTime = QDateTime();       // 重新开始时间
-        m_writtenNowTime = m_writtenStartTime;  // 重新开始时间
+        m_writtenNowTime = QDateTime();         // 重新开始时间
         m_wavFileName.clear();                  // 清空文件名
         m_openFileErrorSize = 0;                // 重置错误次数
     }
 
-    /* 更新文件信息到数据库 */
-    updateRecordFileInfoToDB(isNewFile, isRecordCompleted);
 
     return true;
 }
@@ -530,18 +542,14 @@ bool CreateRecordFileThread::isOneHourPassed()
 {
     if(m_writtenSize >= m_oneHourSize)
     {
-        return true; // 已经写入的数据大小超过了一小时的大小
+        // 已经写入的数据大小超过了一小时的大小
+        return true;
     }
     /* 下面是判断刚启动的时候,到下一个整点不足1小时,也会保存文件 */
     int minute = m_writtenNowTime.time().minute();
     bool isPassed = false;
-    /* 如果当前时间的分钟数小于等于2,并且已经写入的大小超过两次写入的大小 */
-    if(minute <= 2 && m_writtenSize >= m_writeCriticalSize * 2)
-    {
-        isPassed = true;
-    }
-    /* 或者已经写入的数据大小超过了一小时的大小,则认为过了整点 */
-    if(m_writtenSize >= m_oneHourSize)
+    /* 如果当前时间的分钟数小于等于2,并且已经写入的大小超过三分钟大小 */
+    if( (minute <= 2) && (m_writtenSize > m_oneSecondSize * 60 * 3) )
     {
         isPassed = true;
     }
@@ -608,6 +616,7 @@ QString CreateRecordFileThread::generateAlarmFileName(const AlarmValue_t& value,
         
         /* 拼接文件夹路径 */
         retFileName = itemDir.filePath(fileName);
+        SPDLOG_LOGGER_DEBUG(m_logger, "{} 生成新的报警文件名: {}", m_logBase, retFileName.toStdString());
     }else 
     {
         /* 已有的文件,是报警结束的文件名 */
@@ -617,6 +626,8 @@ QString CreateRecordFileThread::generateAlarmFileName(const AlarmValue_t& value,
         QString endTimeStr = value.endTime.toString("yyyyMMdd_hhmmss");
         /* 替换掉原来的结束时间 */
         retFileName.replace("-.wav", QString("-%1.wav").arg(endTimeStr));
+
+        SPDLOG_LOGGER_DEBUG(m_logger, "{} 原文件名:{} 生成报警结束文件名: {}", m_logBase, value.fileName.toStdString(), retFileName.toStdString());
     }
 
     return retFileName;
@@ -626,32 +637,14 @@ QString CreateRecordFileThread::generateAlarmFileName(const AlarmValue_t& value,
 /* 写入报警文件 */
 void CreateRecordFileThread::writeAlarmFile()
 {
-    const int writeSeconds = 2;
-    const int newAlarmSeconds = 10;
     /* 新文件,从报警时间往前推10秒写入 */
     std::list<AudioSrcData*> newDataList;
-    /* 已经打开的文件写入数据 */
-    std::list<AudioSrcData*> dataList;
+    int writeSize = m_numNewAlarmSeconds > 10 ? m_numNewAlarmSeconds : 10;
     {
-        /* 先判断环形队列中数据是否足够的秒数,不一定是每一秒都会写的 */
         std::lock_guard<std::mutex> lock(m_ringQueue.mutex);
-        /* 目前暂定2秒写一次 */
-        if(m_numNewAlarmSeconds < writeSeconds)
-        {
-            /* 不够两秒时间,退出 */
-            return;
-        }
         /* 取出环形队列中的数据 */
         int size = m_ringQueue.QueueSize();
-        for(int i = size - writeSeconds; i < size; ++i)
-        {
-            AudioSrcData* data = m_ringQueue[i];
-            if(data != nullptr)
-            {
-                dataList.push_back(data);
-            }
-        }
-        int start = size - newAlarmSeconds;
+        int start = size - writeSize;
         if(start < 0)
         {
             start = 0; // 防止越界
@@ -667,52 +660,13 @@ void CreateRecordFileThread::writeAlarmFile()
         }
     }
 
-    /* 创建新文件并写入数据 */
-    std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
-    /* 将数据循环写入到文件中 */
-    for(auto& alarmInfo : m_mapAlarmFile)
-    {
-        /* 获取报警信息 */
-        // const AlarmKey_t& key = alarmInfo.first;
-        AlarmValue_t& value = alarmInfo.second;
-        /* 判断是新否是新录音的文件 */
-        if(value.state == eRecordState::eRS_Init)
-        {
-            /* 创建新的文件,并写入wav头文件 */
-            createNewAlarmFile(value, newDataList);
-        } else
-        {
-            /* 已经存在的文件,直接写入新录制的文件 */
-            /* 打开文件 */
-            QFile wavFile(value.fileName);
-            if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
-            {
-                SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
-                SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
-                continue;
-            }
-            /* 写入音频数据到文件 */
-            for(auto& audioData : dataList)
-            {
-                if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
-                {
-                    continue; // 跳过空数据
-                }
-                auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
-                if(writeSize < 0)
-                {
-                    SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
-                    SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
-                    break; // 写入失败,跳出循环
-                }else 
-                {
-                    value.writtenSize += writeSize; // 累加写入大小
-                }
-            }
-            wavFile.close();
-        }
-        
-    }
+
+    /* 写入已经创建的报警文件的数据,需要先写入已经创建的文件,再创建新的文件,防止
+        创建新的文件后再次写入重复的数据 */
+    writeExistingAlarmFileData(newDataList);
+    /* 写入新的报警文件 */
+    writeNewAlarmFileData(newDataList);
+    
 
     /* 处理已经结束的报警,修改文件名,并将其移除队列中 */
     for(auto it = m_mapAlarmFile.begin(); it != m_mapAlarmFile.end(); )
@@ -736,7 +690,6 @@ void CreateRecordFileThread::writeAlarmFile()
         }
     }
 
-
     /* 清空标志位 */
     m_numNewAlarmSeconds = 0;
     
@@ -748,7 +701,6 @@ void CreateRecordFileThread::createNewAlarmFile(AlarmValue_t& value, const std::
 {
     /* 新的报警录音,生成文件名 */
     value.fileName = generateAlarmFileName(value, true);
-    value.state = eRecordState::eRS_Recording; // 设置为录音状态
     SPDLOG_LOGGER_INFO(m_logger, "{} 开始写入报警文件: {}", m_logBase, value.fileName.toStdString());
     
     /* 计算出报警开始位置在报警文件中的偏移,这个偏移值在设置结束的时候取出 */
@@ -803,6 +755,107 @@ void CreateRecordFileThread::createNewAlarmFile(AlarmValue_t& value, const std::
 }
 
 
+/*  写入新文件数据 */
+void CreateRecordFileThread::writeNewAlarmFileData(std::list<AudioSrcData*> dataList)
+{
+    /* 判断是否有新的报警信息 */
+    bool isNewAlarm = false;
+    std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
+    for(const auto& pair : m_mapAlarmFile)
+    {
+        const AlarmValue_t& value = pair.second;
+        if(value.state == eRecordState::eRS_Init)
+        {
+            isNewAlarm = true;
+            break;
+        }
+    }
+    if(!isNewAlarm)
+    {
+        return; // 没有新的报警信息
+    }
+    
+    /* 创建新文件并写入数据 */
+    for(auto& alarmInfo : m_mapAlarmFile)
+    {
+        AlarmValue_t& value = alarmInfo.second;
+        /* 判断是新否是新录音的文件 */
+        if(value.state == eRecordState::eRS_Init)
+        {
+            /* 创建新的文件,并写入wav头文件 */
+            createNewAlarmFile(value, dataList);
+            value.state = eRecordState::eRS_Recording; // 设置为录音状态
+        }
+    }
+}
+
+/* 写入已经创建的报警文件数据 */
+void CreateRecordFileThread::writeExistingAlarmFileData(std::list<AudioSrcData*> newList)
+{
+    const int listSize = static_cast<int>(newList.size());
+    if(m_numNewAlarmSeconds > listSize)
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "{} 音频队列中数据小于需要写入的新的数据大小", m_logBase);
+        return;
+    }
+    /* 已经打开的文件写入数据 */
+    std::list<AudioSrcData*> dataList;
+    if(m_numNewAlarmSeconds > 0)
+    {
+        /* 取出最后 m_numNewAlarmSeconds 个数据 */
+        int i = newList.size() - m_numNewAlarmSeconds;
+        for(auto it = newList.end(); i < listSize; ++i)
+        {
+            --it;
+            dataList.push_back(*it);
+        }
+    }
+
+    /* 写入数据 */
+    std::lock_guard<std::mutex> lock(m_mutexAlarmFile);
+    /* 将数据循环写入到文件中 */
+    for(auto& alarmInfo : m_mapAlarmFile)
+    {
+        /* 获取报警信息 */
+        // const AlarmKey_t& key = alarmInfo.first;
+        AlarmValue_t& value = alarmInfo.second;
+        /* 判断是新否是新录音的文件 */
+        if(eRecordState::eRS_Recording == value.state)
+        {
+            /* 已经存在的文件,直接写入新录制的文件 */
+            /* 打开文件 */
+            QFile wavFile(value.fileName);
+            if(!wavFile.open(QIODevice::WriteOnly | QIODevice::Append))
+            {
+                SPDLOG_LOGGER_ERROR(m_logger, "{} 打开报警文件失败: {}", m_logBase, value.fileName.toStdString());
+                SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
+                continue;
+            }
+            /* 写入音频数据到文件 */
+            for(auto& audioData : dataList)
+            {
+                if(audioData == nullptr || audioData->pData == nullptr || audioData->dataSize == 0)
+                {
+                    continue; // 跳过空数据
+                }
+                auto writeSize = wavFile.write(reinterpret_cast<const char*>(audioData->pData), audioData->dataSize);
+                if(writeSize < 0)
+                {
+                    SPDLOG_LOGGER_ERROR(m_logger, "{} 写入报警文件失败: {}", m_logBase, value.fileName.toStdString());
+                    SPDLOG_LOGGER_ERROR(m_logger, "错误原因: {}", wavFile.errorString().toStdString());
+                    break; // 写入失败,跳出循环
+                }else 
+                {
+                    value.writtenSize += writeSize; // 累加写入大小
+                }
+            }
+            wavFile.close();
+        }
+    }
+}
+
+
+
 /* 设置今日报警文件夹 */
 bool CreateRecordFileThread::setTodayAlarmPath()
 {

+ 10 - 1
Server/ThreadRecord/CreateRecordFileThread.h

@@ -9,9 +9,10 @@
 
 #include <atomic>
 #include <mutex>
+#include <condition_variable>
 #include <QFile>
 #include <QDir>
-#include <qdatetime.h>
+#include <QDateTime>
 
 
 
@@ -149,6 +150,10 @@ private:
     void writeAlarmFile();
     /* 创建新的文件 */
     void createNewAlarmFile(AlarmValue_t& value, const std::list<AudioSrcData*>& dataList);
+    /*  写入新文件数据 */
+    void writeNewAlarmFileData(std::list<AudioSrcData*> newList);
+    /* 写入已经创建的报警文件数据 */
+    void writeExistingAlarmFileData(std::list<AudioSrcData*> newList);
     /* 生成报警文件名 */
     inline QString generateAlarmFileName(const AlarmValue_t& value, bool isNewFile);
     /* 设置今日报警文件夹 */
@@ -158,6 +163,10 @@ private:
     
 
 private:
+    /* 使用条件变量进行线程睡眠,是为了防止报警时间很短,还未写入报警文件,报警就结束了,导致没有报警文件的问题
+        使用条件变量,一有报警即会创建文件写入 */
+    std::condition_variable m_cond_var;     /* 条件变量,用于线程间同步 */
+    std::mutex m_mutexCondVar;              /* 条件变量的互斥锁 */
     /* ------------------------- 录音文件 ------------------------- */
     /* 对比项通道信息列表,记录当前对比项的通道信息,如果没有对比项信息就停止录音 */
     std::mutex m_mutexCompareItemRoadInfo;

+ 7 - 7
Server/common/LHLog/LHLogInit.cpp

@@ -48,17 +48,17 @@ void initLog(QString ModuleName, CLHQLogApi& lhQLog)
         auto sink_default = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
 
         /* 修改输出格式 */
-#if  defined (C_DEBUG)
+// #if  defined (C_DEBUG)
         sink_consolse->set_pattern("%^[%Y-%m-%d %H:%M:%S:%e] [%l] [%n] [%s %#] %v%$");
         // sink_file->set_pattern("[%Y-%m-%d %H:%M:%S] [%^%n%$] [%^%l%$] %s %#: %v");
         sink_custom->set_pattern("%v");
         sink_default->set_pattern("%^[%Y-%m-%d %H:%M:%S:%e] [%l] [%s %#] %v%$");
-#elif defined(C_RELEASE)
-        sink_consolse->set_pattern("%^[%Y-%m-%d %H:%M:%S:%e] [%l] [%n] %v%$");
-        // sink_file->set_pattern("[%Y-%m-%d %H:%M:%S] [%^%n%$] [%^%l%$] %s %#: %v");
-        sink_custom->set_pattern("%v");
-        sink_default->set_pattern("%^[%Y-%m-%d %H:%M:%S:%e] [%l] %v%$");
-#endif
+// #elif defined(C_RELEASE)
+//         sink_consolse->set_pattern("%^[%Y-%m-%d %H:%M:%S:%e] [%l] [%n] %v%$");
+//         // sink_file->set_pattern("[%Y-%m-%d %H:%M:%S] [%^%n%$] [%^%l%$] %s %#: %v");
+//         sink_custom->set_pattern("%v");
+//         sink_default->set_pattern("%^[%Y-%m-%d %H:%M:%S:%e] [%l] %v%$");
+// #endif
 
         /* 日志输出方向,终端和文件 */
         std::vector<spdlog::sink_ptr> sinks;

+ 11 - 1
Server/main.cpp

@@ -9,6 +9,7 @@
 #include "LHCompareAPI.h"
 #include "signalstats.h"
 #include "ACAServer.h"
+#include "singleapplication.h"
 
 #include <QDir>
 #include <QFile>
@@ -30,8 +31,17 @@ int main(int argc, char* argv[])
         fmt::print("main logger is nullptr");
         return -1;
     }
+
+    /* 检查是否已经有实例在运行 */
+    SingleApplication app(argc, argv);
+    if(app.isRunning())
+    {
+        SPDLOG_LOGGER_WARN(logger, "ACAServer 已经有一个实例在运行,无法启动新的实例");
+        return 0;
+    }
+
     SPDLOG_LOGGER_INFO(logger, "★  ★  ★  ★  ★  ★  ★  ☆  ACAServer  ☆  ★  ★  ★  ★  ★  ★  ★");
-    SPDLOG_LOGGER_INFO(logger, "ACServer Version: {}", "6.0.0.3");
+    SPDLOG_LOGGER_INFO(logger, "ACServer Version: {}", "6.0.0.4");
     /* 设置线程池最大线程个数 */
     CPPTP.setThreadMaxNum(1024);
 

+ 63 - 1
SettingLibrary/Modules/AICompare/aicomparewidget.cpp

@@ -1,11 +1,13 @@
 #include "aicomparewidget.h"
 #include "ui_aicomparewidget.h"
 
-#include <QIntValidator>
+#include <QStyle>
+
 #include "UIStyleManager.h"
 #include "SystemConfig.h"
 #include "FromWebAPI.h"
 #include "commonFunc.h"
+#include "tipwidget.h"
 
 
 
@@ -58,6 +60,38 @@ AICompareWidget::~AICompareWidget()
 /* 判断参数是否修改 */
 bool AICompareWidget::isDataChanged()
 {
+    bool isSuccess = true;
+    /* 判断数据的有效性 */
+    if(ui->lineEdit_length->text().isEmpty())
+    {
+        isSuccess = false;
+        setWarn(ui->lineEdit_length, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "AI对比持续时间不能为空", this);
+    }
+    if(ui->lineEdit_compareCount->text().isEmpty())
+    {
+        isSuccess = false;
+        setWarn(ui->lineEdit_compareCount, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "AI对比持续次数不能为空", this);
+    }
+    if(ui->lineEdit_notSimilarThrehold->text().isEmpty())
+    {
+        isSuccess = false;
+        setWarn(ui->lineEdit_notSimilarThrehold, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "AI对比不相似度阈值不能为空", this);
+    }
+    if(ui->lineEdit_similarThrehold->text().isEmpty())
+    {
+        isSuccess = false;
+        setWarn(ui->lineEdit_similarThrehold, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "AI对比相似度阈值不能为空", this);
+    }
+
+    if(isSuccess == false)
+    {
+        return false;
+    }
+
     m_aiCompareConfig.nAiComputeDuration = ui->lineEdit_length->text().toInt();
     m_aiCompareConfig.nAiCompareCount = ui->lineEdit_compareCount->text().toInt();
     m_aiCompareConfig.fSimilarThrehold = ui->lineEdit_similarThrehold->text().toFloat();
@@ -78,6 +112,8 @@ bool AICompareWidget::isDataChanged()
 /* 保存配置项 */
 bool AICompareWidget::saveConfig()
 {
+    cancelAllWarn();
+
     if(!isDataChanged())
     {
         return true; // 没有修改,直接返回成功
@@ -116,3 +152,29 @@ void AICompareWidget::do_pBtn_restore_clicked()
     ui->lineEdit_notSimilarThrehold->setText(QString::number(aiConfig.fNotSimilarThrehold, 'f', 3));
     ui->checkBox_noConsistencyOtherAlarm->setChecked(aiConfig.bNoConsistencyAlarmOtherAlarm);
 }
+
+
+/* 设置一个控件报警,边框显示红色
+ * 注意:这个功能需要在qss里设置属性[Warn=true],并实现相应的报警样式 */
+void AICompareWidget::setWarn(QWidget* widget, bool isWarn)
+{
+    if(widget == nullptr)
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "设置控件报警失败,widget为nullptr");
+        return;
+    }
+    widget->setProperty("Warn", isWarn);
+    widget->style()->unpolish(widget);
+    widget->style()->polish(widget);
+    widget->update();
+}
+
+/* 取消全部报警 */
+void AICompareWidget::cancelAllWarn()
+{
+    setWarn(ui->lineEdit_compareCount, false);
+    setWarn(ui->lineEdit_length, false);
+    setWarn(ui->lineEdit_notSimilarThrehold, false);
+    setWarn(ui->lineEdit_similarThrehold, false);
+}
+

+ 6 - 1
SettingLibrary/Modules/AICompare/aicomparewidget.h

@@ -30,7 +30,12 @@ public:
 private slots:
     /* 恢复配置项 */
     void do_pBtn_restore_clicked();
-    
+
+private:
+    /* 设置报警 */
+    void setWarn(QWidget* widget, bool isWarn);
+    /* 取消全部报警 */
+    void cancelAllWarn();
 
 private:
     Ui::AICompareWidget *ui;

+ 10 - 9
SettingLibrary/Modules/Basic/TableDelegate/DelegateCheckBox.cpp

@@ -7,6 +7,7 @@
 #include <QApplication>
 #include <QStyleOptionViewItem>
 #include <QAbstractItemView>
+#include <QMouseEvent>
 
 #include "spdlog/spdlog.h"
 
@@ -95,15 +96,15 @@ void DelegateCheckBox::paint(QPainter *painter, const QStyleOptionViewItem &opti
 
 bool DelegateCheckBox::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
 {
-    // if (option.state & QStyle::State_Enabled && event->type() == QEvent::MouseButtonRelease) 
-    // {
-    //     // 选中整行
-    //     QAbstractItemView *view = qobject_cast<QAbstractItemView *>(parent());
-    //     if (view) 
-    //     {
-    //         view->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
-    //     }
-    // }
+    // 禁用点击复选框区域
+    if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
+        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
+        QRect checkBoxRect(option.rect.left() + 12, option.rect.top() + (option.rect.height() - 20) / 2, 20, 20);
+        if (checkBoxRect.contains(mouseEvent->pos())) {
+            // 直接返回 false,不处理点击
+            return false;
+        }
+    }
     return QStyledItemDelegate::editorEvent(event, model, option, index);
 }
 

+ 50 - 0
SettingLibrary/Modules/Basic/basicwidget.cpp

@@ -95,6 +95,8 @@ bool BasicWidget::isDataChanged()
 /* 保存数据 */
 bool BasicWidget::saveBasicInfo()
 {
+    /* 取消所有的报警 */
+    cancelAllWarn();
     bool isSuccess = true;
     /* 保存基础信息 */
     if(!saveBasicSettingInfo())
@@ -176,6 +178,31 @@ bool BasicWidget::isDataChangedBasicInfo()
     m_baseConfig.isClearDirSystemOn = ui->checkBox_clearHistoryDir->isChecked();
     m_baseConfig.isUsingSoundCardName = ui->checkBox_enableSoundCardName->isChecked();
 
+    /* 检查必填项的有效性 */
+    bool isSuccess = true;
+    if(m_baseConfig.strServerIP.isEmpty())
+    {
+        setWarn(ui->lineEdit_serverIP, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "服务器IP不能为空", this);
+        isSuccess = false;
+    }
+    if(m_baseConfig.nRecordMode < 0)
+    {
+        setWarn(ui->comBox_recordMode, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "录音模式不能为空", this);
+        isSuccess = false;
+    }
+    if(m_baseConfig.strSoundCardName.isEmpty())
+    {
+        setWarn(ui->comBox_driverName, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "声卡驱动不能为空", this);
+        isSuccess = false;
+    }
+    if(isSuccess == false)
+    {
+        return false;
+    }
+
     // SPDLOG_LOGGER_DEBUG(m_logger, "修改后的基础配置信息: ServerIP: {}, RecordMode: {}, DriverName: {}, NotConsistency: {}, EnableMultiCore: {}, EnableDebugLog: {}, ClearHistoryDirOnStart: {}, UseSoundCardName: {}",
     //     baseConfig.strServerIP.toStdString(), baseConfig.nRecordMode, baseConfig.strDriverName.toStdString(),
     //     baseConfig.nNotConsistency, baseConfig.isEnableMultiCore, baseConfig.isEnableDebugLog,
@@ -446,3 +473,26 @@ bool BasicWidget::restoreCompareItemInfo()
 }
 
 
+/* 设置一个控件报警,边框显示红色
+ * 注意:这个功能需要在qss里设置属性[Warn=true],并实现相应的报警样式 */
+void BasicWidget::setWarn(QWidget* widget, bool isWarn)
+{
+    if(widget == nullptr)
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "设置控件报警失败,widget为nullptr");
+        return;
+    }
+    widget->setProperty("Warn", isWarn);
+    widget->style()->unpolish(widget);
+    widget->style()->polish(widget);
+    widget->update();
+}
+
+/* 取消全部报警 */
+void BasicWidget::cancelAllWarn()
+{
+    setWarn(ui->lineEdit_serverIP, false);
+    setWarn(ui->comBox_driverName, false);
+    setWarn(ui->comBox_recordMode, false);
+}
+

+ 5 - 0
SettingLibrary/Modules/Basic/basicwidget.h

@@ -65,6 +65,11 @@ private:
     /* 恢复对比项信息 */
     bool restoreCompareItemInfo();
 
+    /* 设置报警 */
+    void setWarn(QWidget* widget, bool isWarn);
+    /* 取消全部报警 */
+    void cancelAllWarn();
+
 private:
     Ui::BasicWidget *ui;
     std::shared_ptr<spdlog::logger> m_logger = nullptr;

+ 28 - 6
SettingLibrary/Modules/Basic/compareitemdialog.cpp

@@ -6,6 +6,7 @@
 #include "SoundCardData.h"
 #include "tipwidget.h"
 #include "commonFunc.h"
+#include "commonFunc.h"
 
 #include <QIntValidator>
 
@@ -95,6 +96,9 @@ void CompareItemDialog::setMultiEditMode(bool multiEdit)
         delete widget;
         widget = nullptr;
     }
+    /* 清空其他自定义选项 */
+    ui->lineEdit_compareItmName->clear();
+
     m_listOtherRoadWgt.clear();
     m_listOtherRoadWgt.push_back(ui->widget_mainRoad);
     m_listOtherRoadWgt.push_back(ui->widget_secondRoad);
@@ -376,14 +380,15 @@ void CompareItemDialog::setDetectParamInputOnlyNumber()
     // 静音参数,只允许输入1~1000的整数
     // QRegularExpression rx1("-[4-8][0-9]");
     // ui->lineEditMuteThreshold->setValidator(new QRegularExpressionValidator(rx1, this));
-    ui->lineEditMuteThreshold->setValidator(new QIntValidator(m_silenceThresholdLow, m_silenceThresholdHigh, this));
-    ui->lineEditMuteLen->setValidator(new QIntValidator(m_silenceDurationLow, m_silenceDurationHigh, this));
-    ui->lineEditMuteSensibility->setValidator(new QIntValidator(m_silenceSensitivityLow, m_silenceSensitivityHigh, this));
+    // StrictDoubleValidator
+    ui->lineEditMuteThreshold->setValidator(new StrictIntValidator(m_silenceThresholdLow, m_silenceThresholdHigh, this));
+    ui->lineEditMuteLen->setValidator(new StrictIntValidator(m_silenceDurationLow, m_silenceDurationHigh, this));
+    ui->lineEditMuteSensibility->setValidator(new StrictIntValidator(m_silenceSensitivityLow, m_silenceSensitivityHigh, this));
 
     // 过载参数
-    ui->lineEditOverloadThreshold->setValidator(new QIntValidator(m_overloadThresholdLow, m_overloadThresholdHigh, this));
-    ui->lineEditOverloadLen->setValidator(new QIntValidator(m_overloadDurationLow, m_overloadDurationHigh, this));
-    ui->lineEditOverloadSensibility->setValidator(new QIntValidator(m_overloadSensitivityLow, m_overloadSensitivityHigh, this));
+    ui->lineEditOverloadThreshold->setValidator(new StrictIntValidator(m_overloadThresholdLow, m_overloadThresholdHigh, this));
+    ui->lineEditOverloadLen->setValidator(new StrictIntValidator(m_overloadDurationLow, m_overloadDurationHigh, this));
+    ui->lineEditOverloadSensibility->setValidator(new StrictIntValidator(m_overloadSensitivityLow, m_overloadSensitivityHigh, this));
 
     // 反相参数,阈值为浮点数,范围0.0~0.99
     ui->lineEditPhaseThreshold->setValidator(new StrictDoubleValidator(m_phaseThresholdLow, m_phaseThresholdHigh, 3, this));
@@ -490,6 +495,22 @@ bool CompareItemDialog::getCompareRoadInfo(CompareItemInfo_t& compareItemInfo)
         return false;
     }
 
+    /* 判断是否选择声卡通道信息 */
+    bool isSelectSoundCard = true;
+    for(auto& widget : m_listOtherRoadWgt)
+    {
+        if(widget->getCurrentPCMName().isEmpty())
+        {
+            widget->setSoundCardWarn(true);
+            TipWidget::display(TipWidget::OPERATOR_WARN, QString("请选择%1通道的声卡录音通道").arg(widget->getIndex()), this);
+            isSelectSoundCard = false;
+        }
+    }
+    if(isSelectSoundCard == false)
+    {
+        return false;
+    }
+
     return true;
 }
 
@@ -598,6 +619,7 @@ void CompareItemDialog::cancelAllWarn()
     for(SingleCompareRoadWidget* roadWgt : m_listOtherRoadWgt)
     {
         roadWgt->setRoadNameWarn(false);
+        roadWgt->setSoundCardWarn(false);
     }
 
     /* 取消检测参数的报警红框 */

+ 7 - 0
SettingLibrary/Modules/Basic/compareitemlistdialog.cpp

@@ -80,6 +80,12 @@ CompareItemListDialog::CompareItemListDialog(QWidget *parent) :
     connect(ui->pBtn_enableCPItem, &QPushButton::clicked, this, &CompareItemListDialog::do_pBtn_enable_Clicked);
     connect(ui->pBtn_disableCPItem, &QPushButton::clicked, this, &CompareItemListDialog::do_pBtn_disable_Clicked);
 
+    /* 设置TableView */
+    ui->tableView->setParent(nullptr); /* 先移除父对象 */
+    ui->tableView->deleteLater(); /* 延迟删除 */
+    ui->tableView = new CTableView(ui->widgetContent);
+    auto layout = ui->widgetContent->layout();
+    layout->addWidget(ui->tableView);
 
     /* 初始化表格 */
     initTable();
@@ -550,6 +556,7 @@ void CompareItemListDialog::initTable()
 
     /* 设置第一列代理 */
     auto checkBoxDelegate = new DelegateCheckBox(ui->tableView);
+    
     /* 设置多种选择状态 */
     ui->tableView->setItemDelegateForColumn(0, checkBoxDelegate);
 

+ 19 - 0
SettingLibrary/Modules/Basic/compareitemlistdialog.h

@@ -3,6 +3,7 @@
 
 #include <QStandardItemModel>
 #include <QSortFilterProxyModel>
+#include <QTableView>
 
 #include "DialogBase.h"
 #include "GlobalVariable.h"
@@ -26,6 +27,24 @@ protected:
 };
 
 
+/* 点击空白处不会取消选中的QTableView */
+class CTableView : public QTableView
+{
+public:
+    using QTableView::QTableView;
+protected:
+    void mousePressEvent(QMouseEvent *event) override
+    {
+        QModelIndex idx = indexAt(event->pos());
+        if (!idx.isValid()) {
+            // 点击空白处,什么都不做
+            return;
+        }
+        QTableView::mousePressEvent(event);
+    }
+};
+
+
 
 
 /**

+ 14 - 3
SettingLibrary/Modules/Basic/singlecompareroadwidget.cpp

@@ -53,8 +53,8 @@ void SingleCompareRoadWidget::setSoundCardRoadList(const SoundCardPCMInfo_t& sou
         QString strData = QString::fromStdString(it.strPCMName);
         ui->comboBox_soundCardNum->addItem(strItem, strData);
     }
-    /* 默认选择第一个 */
-    ui->comboBox_soundCardNum->setCurrentIndex(0);
+    /* 默认为空 */
+    ui->comboBox_soundCardNum->setCurrentIndex(-1);
 }
 
 /* 设置默认参数,设置之前需要先设置完成声卡可选通道列表 */
@@ -71,7 +71,7 @@ void SingleCompareRoadWidget::setDefaultParams(const CompareItemRoadInfo_t& road
         ui->comboBox_soundCardNum->setCurrentIndex(numIndex);
     } else
     {
-        ui->comboBox_soundCardNum->setCurrentIndex(0); // 如果没有找到,默认选择第一个
+        ui->comboBox_soundCardNum->setCurrentIndex(-1); // 如果没有找到,默认为空
     }
     // for(int i = 0; i < ui->comboBox_soundCardNum->count(); ++i)
     // {
@@ -147,6 +147,15 @@ void SingleCompareRoadWidget::setRoadNameWarn(bool isWarn)
     ui->lineEditRoadName->update();
 }
 
+/* 设置声卡通道名称报警 */
+void SingleCompareRoadWidget::setSoundCardWarn(bool isWarn)
+{
+    ui->comboBox_soundCardNum->setProperty("Warn", isWarn);
+    ui->comboBox_soundCardNum->style()->unpolish(ui->comboBox_soundCardNum);
+    ui->comboBox_soundCardNum->style()->polish(ui->comboBox_soundCardNum);
+    ui->comboBox_soundCardNum->update();
+}
+
 /* 设置当前录音通道名称 */
 void SingleCompareRoadWidget::clearCurrentSelectRecordName()
 {
@@ -174,6 +183,8 @@ void SingleCompareRoadWidget::setMultiEditMode(bool multiEdit)
     /* 设置通道名称和声卡选择框不可编辑 */
     ui->lineEditRoadName->setEnabled(false);
     ui->comboBox_soundCardNum->setEnabled(false);
+    /* 清空声卡选择 */
+    ui->comboBox_soundCardNum->setCurrentIndex(-1);
 }
 
 

+ 2 - 0
SettingLibrary/Modules/Basic/singlecompareroadwidget.h

@@ -44,6 +44,8 @@ public:
 
     /* 设置通道名称报警 */
     void setRoadNameWarn(bool isWarn);
+    /* 设置声卡通道名称报警 */
+    void setSoundCardWarn(bool isWarn);
 
     /* 设置当前录音通道名称 */
     void clearCurrentSelectRecordName();

+ 69 - 5
SettingLibrary/Modules/Noise/noisemonitorparamdialog.cpp

@@ -128,9 +128,65 @@ bool NoiseMonitorParamDialog::isOKClicked()
     return true;
 }
 
+/* 判断是否有空的值 */
+bool NoiseMonitorParamDialog::isAnyValueEmpty()
+{
+    bool isNoEmpty = true;
+    if(ui->lineEdit_noiseOneDetectDuration->text().isEmpty())
+    {
+        setWarn(ui->lineEdit_noiseOneDetectDuration, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "请输入必填项", this);
+        isNoEmpty = false;
+    }
+    if(ui->lineEdit_noiseDetectContinueCount->text().isEmpty())
+    {
+        setWarn(ui->lineEdit_noiseDetectContinueCount, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "请输入必填项", this);
+        isNoEmpty = false;
+    }
+    if(ui->lineEdit_noiseContinueCountIsWarn->text().isEmpty())
+    {
+        setWarn(ui->lineEdit_noiseContinueCountIsWarn, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "请输入必填项", this);
+        isNoEmpty = false;
+    }
+    if(ui->lineEdit_noiseContinueCountPercent->text().isEmpty())
+    {
+        setWarn(ui->lineEdit_noiseContinueCountPercent, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "请输入必填项", this);
+        isNoEmpty = false;
+    }
+    if(ui->lineEdit_thresholdSlient->text().isEmpty())
+    {
+        setWarn(ui->lineEdit_thresholdSlient, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "请输入必填项", this);
+        isNoEmpty = false;
+    }
+    if(ui->lineEdit_thresholdDB->text().isEmpty())
+    {
+        setWarn(ui->lineEdit_thresholdDB, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "请输入必填项", this);
+        isNoEmpty = false;
+    }
+    if(ui->lineEdit_thresholdCV->text().isEmpty())
+    {
+        setWarn(ui->lineEdit_thresholdCV, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "请输入必填项", this);
+        isNoEmpty = false;
+    }
+
+    return isNoEmpty;
+}
+
+
 /* 更新噪音检测参数 */
 bool NoiseMonitorParamDialog::updateNoiseDetectParam()
 {
+    /* 先判断是否有空的值 */
+    if(isAnyValueEmpty() == false)
+    {
+        return false;
+    }
     /* 获取所有的值 */
     int noiseOneDetectDuration = ui->lineEdit_noiseOneDetectDuration->text().toInt();
     int noiseDetectContinueCount = ui->lineEdit_noiseDetectContinueCount->text().toInt();
@@ -157,11 +213,11 @@ bool NoiseMonitorParamDialog::updateNoiseDetectParam()
         setWarn(ui->lineEdit_noiseDetectContinueCount, true);
         isOverRange = true;
     }
-    if(noiseContinueCountIsWarn < m_noiseContinueCountIsWarnLow || noiseContinueCountIsWarn > m_noiseContinueCountIsWarnHigh)
-    {
-        setWarn(ui->lineEdit_noiseContinueCountIsWarn, true);
-        isOverRange = true;
-    }
+    // if(noiseContinueCountIsWarn < m_noiseContinueCountIsWarnLow || noiseContinueCountIsWarn > m_noiseContinueCountIsWarnHigh)
+    // {
+    //     setWarn(ui->lineEdit_noiseContinueCountIsWarn, true);
+    //     isOverRange = true;
+    // }
     if(noiseContinueCountPercent < m_noiseContinueCountPercentLow || noiseContinueCountPercent > m_noiseContinueCountPercentHigh)
     {
         setWarn(ui->lineEdit_noiseContinueCountPercent, true);
@@ -206,6 +262,14 @@ bool NoiseMonitorParamDialog::updateNoiseDetectParam()
         return false;
     }
 
+    /* 判断条件是否满足,主要是噪音预警次数不能大于持续检测次数 */
+    if(noiseContinueCountIsWarn > noiseDetectContinueCount)
+    {
+        setWarn(ui->lineEdit_noiseContinueCountIsWarn, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, "噪音预警次数不能大于持续检测次数", this);
+        return false;
+    }
+
     m_noiseDetectParam.nNoiseOneDetectDuration = noiseOneDetectDuration;
     m_noiseDetectParam.nNoiseDetectContinueCount = noiseDetectContinueCount;
     m_noiseDetectParam.nNoiseContinueCountIsWarn = noiseContinueCountIsWarn;

+ 2 - 0
SettingLibrary/Modules/Noise/noisemonitorparamdialog.h

@@ -33,6 +33,8 @@ private:
     void setQss();
     /* 点击了OK */
     bool isOKClicked() override;
+    /* 判断是否有空的值 */
+    bool isAnyValueEmpty();
     /* 更新噪音检测参数 */
     bool updateNoiseDetectParam();
     /* 设置限制参数范围 */

+ 42 - 2
SettingLibrary/Modules/Noise/noisewidget.cpp

@@ -2,11 +2,13 @@
 #include "ui_noisewidget.h"
 #include "noisemonitorparamdialog.h"
 
-#include <QIntValidator>
+#include <QStyle>
 
 #include "UIStyleManager.h"
 #include "SystemConfig.h"
 #include "FromWebAPI.h"
+#include "commonFunc.h"
+#include "tipwidget.h"
 
 
 
@@ -35,7 +37,7 @@ NoiseWidget::NoiseWidget(QWidget *parent) :
     ui->label_3->hide();
 
     /* 设置输入数字的栏只能输入数字,并限制为整数0-60 */
-    ui->lineEdit_detectInternal->setValidator(new QIntValidator(0, 60, this));
+    ui->lineEdit_detectInternal->setValidator(new StrictIntValidator(0, 60, this));
     /* 设置输入栏的弱提示符 */
     ui->lineEdit_detectInternal->setPlaceholderText("请输入, 限制0-60秒");
 
@@ -93,6 +95,7 @@ void NoiseWidget::do_pBtn_restore_clicked()
 /* 保存参数 */
 bool NoiseWidget::saveConfig()
 {
+    cancelAllWarn();
     bool isSuccess = true;
     /* 先保存基础设置 */
     if(!saveBaseConfig())
@@ -126,6 +129,14 @@ bool NoiseWidget::isDataChanged()
 /* 判断基础数据是否修改了 */
 bool NoiseWidget::isDataChangedBaseConfig()
 {
+    /* 先检查有效性 */
+    if(ui->lineEdit_detectInternal->text().isEmpty())
+    {
+        setWarn(ui->lineEdit_detectInternal, true);
+        TipWidget::display(TipWidget::OPERATOR_WARN, QString("检测间隔不能为空"), this);
+        return false;
+    }
+
     m_noiseDetectBaseConfig.strNoiseServerAddr = ui->lineEdit_serverAddr->text();
     m_noiseDetectBaseConfig.strNoiseDetectDir = ui->lineEdit_noiseFileDir->text();
     m_noiseDetectBaseConfig.strServerPath = ui->lineEdit_serverPath->text();
@@ -215,3 +226,32 @@ bool NoiseWidget::saveNoiseDetectParam()
 }
 
 
+
+
+/* 设置一个控件报警,边框显示红色
+ * 注意:这个功能需要在qss里设置属性[Warn=true],并实现相应的报警样式 */
+void NoiseWidget::setWarn(QWidget* widget, bool isWarn)
+{
+    if(widget == nullptr)
+    {
+        SPDLOG_LOGGER_ERROR(m_logger, "设置控件报警失败,widget为nullptr");
+        return;
+    }
+    widget->setProperty("Warn", isWarn);
+    widget->style()->unpolish(widget);
+    widget->style()->polish(widget);
+    widget->update();
+}
+
+/* 取消全部报警 */
+void NoiseWidget::cancelAllWarn()
+{
+    setWarn(ui->lineEdit_detectInternal, false);
+}
+
+
+
+
+
+
+

+ 5 - 0
SettingLibrary/Modules/Noise/noisewidget.h

@@ -44,6 +44,11 @@ private:
     /* 保存噪音检测参数 */
     bool saveNoiseDetectParam();
 
+    /* 设置报警 */
+    void setWarn(QWidget* widget, bool isWarn);
+    /* 取消全部报警 */
+    void cancelAllWarn();
+
 private:
     Ui::NoiseWidget *ui;
     std::shared_ptr<spdlog::logger> m_logger = nullptr;

+ 11 - 0
SettingLibrary/Resources/qss/white/aicomparewidget.qss

@@ -44,6 +44,17 @@ QLineEdit:hover,QLineEdit:focus
 }
 
 
+QLineEdit:enabled[Warn=true]
+{
+    border: 1px solid #D21F21;
+}
+
+QLineEdit:!enabled
+{
+	background: #F5F5F5;
+	color:rgba(58,63,99,0.65);
+}
+
 /* ==========================================================
  *  QPushButton
  * ========================================================== */

+ 11 - 0
SettingLibrary/Resources/qss/white/basicwidget.qss

@@ -41,6 +41,17 @@ QLineEdit:hover,QLineEdit:focus
 	border: 1px solid #4458FE;
 }
 
+QLineEdit:enabled[Warn=true]
+{
+    border: 1px solid #D21F21;
+}
+
+QLineEdit:!enabled
+{
+	background: #F5F5F5;
+	color:rgba(58,63,99,0.65);
+}
+
 
 /* ==========================================================
  *  QPushButton

+ 12 - 0
SettingLibrary/Resources/qss/white/noisewidget.qss

@@ -36,6 +36,18 @@ QLineEdit:hover,QLineEdit:focus
 	border: 1px solid #4458FE;
 }
 
+
+QLineEdit:enabled[Warn=true]
+{
+    border: 1px solid #D21F21;
+}
+
+QLineEdit:!enabled
+{
+	background: #F5F5F5;
+	color:rgba(58,63,99,0.65);
+}
+
 QCheckBox
 {
 	font-weight: 400;

+ 5 - 0
SettingLibrary/Resources/qss/white/singlecompareroadwidget.qss

@@ -82,6 +82,11 @@ QComboBox:hover
     background:transparent;
 }
 
+QComboBox:enabled[Warn=true]
+{
+    border: 1px solid #D21F21;
+}
+
 /* 下拉箭头所在的位置方框 */
 QComboBox::drop-down
 {

+ 8 - 3
show1/widget.cpp

@@ -33,8 +33,8 @@ Widget::Widget(QWidget *parent) :
     initData.strWebAddr = "http://192.1.3.133:31000/v6/";
 
     // initData.strDBID = "cf6b57fa3d9841e22c3c897e6b8e66b8";       /* 达梦数据库 */
-    // initData.strDBID = "3b8889a0d58b8d71affc04bc27d14e42";          /* GBase */
-    initData.strDBID = "a75e46623562077b76334a2e02616747";       /* MySQL */
+    initData.strDBID = "3b8889a0d58b8d71affc04bc27d14e42";          /* GBase */
+    // initData.strDBID = "a75e46623562077b76334a2e02616747";       /* MySQL */
 
     DoInit(&initData);
     // 创建窗口
@@ -51,7 +51,12 @@ Widget::~Widget()
 
 void Widget::on_pBtn_save_clicked()
 {
-    DoSave(1);
+    if(DoSave(1) == 0)
+    {
+        SPDLOG_INFO("保存设置成功!");
+    }else {
+        SPDLOG_ERROR("保存设置失败!");
+    }
 }
 
 void Widget::on_pBtn_cancel_clicked()