wangyaojie 2 years ago
parent
commit
c0ae3ca918
100 changed files with 4226 additions and 1385 deletions
  1. 453 162
      client/answerWidget.cpp
  2. 27 6
      client/answerWidget.h
  3. 109 69
      client/awBackground.cpp
  4. 9 0
      client/awBackground.h
  5. 36 4
      client/awBackground.ui
  6. 73 16
      client/awExamScore.cpp
  7. 3 0
      client/awExamScore.h
  8. 3 0
      client/awExamScore.ui
  9. 39 3
      client/awMsgBox.cpp
  10. 11 2
      client/awMsgBox.h
  11. 67 17
      client/awQuestionNavigate.cpp
  12. 3 1
      client/awQuestionNavigate.h
  13. 80 0
      client/awTimeLeftTips.cpp
  14. 35 0
      client/awTimeLeftTips.h
  15. 78 0
      client/awTimeLeftTips.ui
  16. 8 6
      client/awWaitExam.cpp
  17. 1 0
      client/awWaitExam.h
  18. 8 4
      client/awqn_item.cpp
  19. 1 1
      client/awqn_item.h
  20. 10 8
      client/clEditPassword.cpp
  21. 13 3
      client/clNoticeDetail.cpp
  22. 134 13
      client/clNoticeList.cpp
  23. 13 0
      client/clNoticeList.h
  24. 69 1
      client/clNoticePopWidget.cpp
  25. 13 1
      client/clNoticePopWidget.h
  26. 10 3
      client/clObjectiveScore.cpp
  27. 122 92
      client/clOfflineExam.cpp
  28. 2 0
      client/clOfflineExam.h
  29. 61 17
      client/clOnlineExam.cpp
  30. 8 1
      client/clOnlineExam.h
  31. 34 9
      client/clOnlineHomework.cpp
  32. 4 3
      client/clOnlineHomework.h
  33. 73 18
      client/clOnlinePractice.cpp
  34. 9 1
      client/clOnlinePractice.h
  35. 1 1
      client/clStudentInfo.cpp
  36. 25 3
      client/client.pro
  37. 63 16
      client/client.qss
  38. 7 6
      client/cloeUploadFile.cpp
  39. 18 2
      client/cloeViewPaper.cpp
  40. 3 0
      client/cloeViewPaper.h
  41. 22 13
      client/clopPaperDetail.cpp
  42. 3 2
      client/clopPaperReport.cpp
  43. 1 0
      client/clopPaperReport.h
  44. 12 0
      client/clopReport.cpp
  45. 3 3
      client/clopResultList.cpp
  46. 238 61
      client/courseList.cpp
  47. 9 3
      client/courseList.h
  48. 2 1
      client/environmentalTest.cpp
  49. 139 58
      client/etCameraTest.cpp
  50. 11 4
      client/etCameraTest.h
  51. 6 6
      client/etCameraTest.ui
  52. 14 6
      client/etMobileTest.cpp
  53. 8 0
      client/etNetworkTest.cpp
  54. 3 0
      client/etNetworkTest.h
  55. 114 27
      client/etVoiceTest.cpp
  56. 10 1
      client/etVoiceTest.h
  57. 32 3
      client/etVoiceTest.ui
  58. BIN
      client/images/btn-etvt-mute.png
  59. BIN
      client/images/btn-play_disable.png
  60. BIN
      client/images/btn-prev-photo.png
  61. BIN
      client/images/icon-aw-close.png
  62. BIN
      client/images/icon-aw-minisize.png
  63. BIN
      client/images/icon-um-info.png
  64. BIN
      client/images/qm-logo.png
  65. 151 25
      client/login.cpp
  66. 8 0
      client/login.h
  67. 37 5
      client/login.ui
  68. 50 5
      client/main.cpp
  69. 65 19
      client/privacyWidget.cpp
  70. 6 0
      client/rc.qrc
  71. 27 4
      client/welcomeWidget.cpp
  72. 2 0
      common/CAppInfo.cpp
  73. 5 0
      common/CAppInfo.h
  74. 9 4
      common/CAudioPlayerProc.cpp
  75. 1 0
      common/CAudioPlayerProc.h
  76. 442 88
      common/CCommonTools.cpp
  77. 4 1
      common/CCommonTools.h
  78. 126 10
      common/CHttpBll.cpp
  79. 6 1
      common/CHttpBll.h
  80. 12 12
      common/CHttpClient.cpp
  81. 1 1
      common/CHttpClient.h
  82. 5 0
      common/CHttpInterceptor.cpp
  83. 2 1
      common/CHttpInterceptor.h
  84. 61 0
      common/CMultiMonitorEnumerator.h
  85. 95 14
      common/httpDataDef.h
  86. 5 5
      common/logproc.h
  87. 30 4
      common/popMsgBox.cpp
  88. 14 4
      common/popMsgBox.h
  89. 2 2
      common/popMsgBox.ui
  90. 7 3
      component/CKeyBoardHook.cpp
  91. 2 2
      component/CKeyBoardHook.h
  92. 3 0
      component/CWebsocketProc.cpp
  93. 13 3
      component/clOperation.cpp
  94. 1 0
      component/clOperation.h
  95. 33 3
      face/CFaceRecProc.cpp
  96. 94 65
      face/faceCompare.cpp
  97. 11 4
      face/faceCompare.h
  98. 512 413
      face/faceLiveness.cpp
  99. 14 9
      face/faceLiveness.h
  100. 7 1
      face/faceLiveness.ui

File diff suppressed because it is too large
+ 453 - 162
client/answerWidget.cpp


+ 27 - 6
client/answerWidget.h

@@ -9,13 +9,16 @@
 #include "awHandinPaper.h"
 #include "awResumeExam.h"
 #include "faceLiveness.h"
+#include "awTimeLeftTips.h"
 #include <opencv2/opencv.hpp>
+#include "CLiveViodeProc.h"
+#include "CMultiMonitorEnumerator.h"
 
 namespace Ui {
 class answerWidget;
 }
 
-class answerWidget : public QWidget
+class answerWidget : public QWidget , ITRTCVideoRenderCallback
 {
     Q_OBJECT
 
@@ -23,6 +26,7 @@ signals:
     void showExamScore();
     void showPracticeInfo();
     void gobackCourseList();
+    void gobackLogin();
 public:
     explicit answerWidget(QWidget *parent = nullptr);
     ~answerWidget();
@@ -72,6 +76,8 @@ private:
 	bool checkAnswer();
     void startCapture();
     void stopCapture();
+    void onRenderVideoFrame(const char* userId, TRTCVideoStreamType streamType, TRTCVideoFrame* frame);
+	void checkRemoteBreach();
 
     Ui::answerWidget *ui;
 	
@@ -89,13 +95,28 @@ private:
     std::shared_ptr<awResumeExam> m_pResumeExam;
 	std::shared_ptr<QTimer> m_pHandinPaperTimer;
     std::shared_ptr<faceLiveness> m_pFaceLiveness;
-    int m_nLeftSeconds;
-	bool bShowLivenessTip;
+    std::shared_ptr<awTimeLeftTips> m_ptimeLeftTips;
+	std::shared_ptr<QTimer> m_pNetWorkErrorTimerPtr;
+    int m_nLeftSeconds = 0;
+    int m_nEnterExamTime = 0;
+    int m_nUsedExamSeconds = 0;
+	int m_nEnterLivenessFaceSeconds = 0;
+	bool bShowLivenessTip;//活体检测前提示
+	bool bShowLeftHint;
+	int m_nHeartBreatFailedCount;
+	const int m_nHeartBreatFailedExitCount = 10;
 
     std::shared_ptr<QTimer> m_pVideoTimer;
-    std::shared_ptr<QTimer> m_pinitTimer;
-    cv::VideoCapture m_cam;
-	int m_nLastCaptureSecond;
+//    std::shared_ptr<QTimer> m_pinitTimer;
+//    cv::VideoCapture m_cam;
+    cv::Mat m_nCurImage;
+    std::mutex m_imageMutex;
+    __int64 m_lastFaceTime = 0;
+    int m_nLastCaptureSecond = 0;
+    int m_nNextFaceCountSecond = 0;
+	
+	QStringList m_sRemoteList;
+	QStringList m_sRemoteTipList;
 };
 
 #endif // ANSWERWIDGET_H

+ 109 - 69
client/awBackground.cpp

@@ -17,15 +17,32 @@ awBackground::awBackground(AW_WIDGET_TYPE wt, QWidget *parent) :
     initUI();
     g_appInfoPtr->m_pAnsBgWidget = this;
 
+	if (!g_appInfoPtr->m_oExamInfo.sCourseCode.isEmpty() &&
+		!g_appInfoPtr->m_oExamInfo.sCourseName.isEmpty())
+	{
+		ui->label_awbg_exam->setText(QString::fromLocal8Bit("%1(%2)")
+			.arg(g_appInfoPtr->m_oExamInfo.sCourseName)
+			.arg(g_appInfoPtr->m_oExamInfo.sCourseCode));
+	}
+
     qRegisterMetaType<CGetCourseInfo>("CGetCourseInfo");
     connect(g_httpBllPtr.get(), &CHttpBll::sgnGetCourseInfo, this, &awBackground::onGetCourseInfo);
 
     
+	m_pServerTime = std::make_shared<QTimer>();
+	m_pServerTime->setInterval(1000);
+	connect(m_pServerTime.get(), &QTimer::timeout, this, [&]() {
+        ui->label_serverTime->adjustSize();
+		ui->label_serverTime->setText(QString::fromLocal8Bit("服务器时间:%1")
+			.arg(QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime()).toString("yyyy-MM-dd hh:mm:ss")));
+	});
+	m_pServerTime->start();
 }
 
 awBackground::~awBackground()
 {
     g_appInfoPtr->m_pAnsBgWidget = nullptr;
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -34,7 +51,7 @@ void awBackground::initUI()
 	//ui->label_awbg_exam->setText(QString::fromLocal8Bit("%1(%2)").arg().arg());
 	ui->label_awbg_student->setText(QString::fromLocal8Bit("%1 | %2 | %3 | %4")
 		.arg(g_appInfoPtr->m_sStudentName).arg(g_appInfoPtr->m_sStudentCode)
-		.arg(g_appInfoPtr->m_nVersionId).arg(g_appInfoPtr->m_sOrgName));
+        .arg(g_appInfoPtr->m_sStudentIdentityNumber).arg(g_appInfoPtr->m_sOrgName));
     QDesktopWidget *dekwiget = QApplication::desktop();
     setGeometry(0, 0, dekwiget->width(), dekwiget->height());
     ui->widget_awbg_BG->setGeometry(0, 0, width(), height());
@@ -45,18 +62,37 @@ void awBackground::initUI()
     ui->label_awbg_exam->setGeometry(ui->label_awbg_examIcon->x() + ui->label_awbg_examIcon->width() + g_appInfoPtr->m_fRate*10,
                                      ui->label_awbg_examIcon->y() + (ui->label_awbg_examIcon->height() - ui->label_awbg_exam->height())/2,
                                      ui->label_awbg_exam->width(), ui->label_awbg_exam->height());
-    ui->label_awbg_student->adjustSize();
-    ui->label_awbg_student->setGeometry(ui->widget_awbg_top->width() - g_appInfoPtr->m_fRate*30 - ui->label_awbg_student->width(),
+    ui->btn_aw_close->setVisible(!g_appInfoPtr->m_bFullScreenTop);
+    ui->btn_aw_minisize->setVisible(!g_appInfoPtr->m_bFullScreenTop);
+	ui->label_awbg_student->adjustSize();
+	int nStudentLeft = ui->widget_awbg_top->width() - g_appInfoPtr->m_fRate*30 - ui->label_awbg_student->width();
+    
+	if(!g_appInfoPtr->m_bFullScreenTop)
+    {
+        ui->btn_aw_close->setGeometry(ui->widget_awbg_top->width() - g_appInfoPtr->m_fRate*(30 + 24),
+                                      ui->label_awbg_exam->y(), g_appInfoPtr->m_fRate * 24, g_appInfoPtr->m_fRate * 24);
+        ui->btn_aw_minisize->setGeometry(ui->btn_aw_close->x() - g_appInfoPtr->m_fRate*6 - ui->btn_aw_close->width(),
+                                      ui->btn_aw_close->y(), ui->btn_aw_close->width(), ui->btn_aw_close->height());
+        nStudentLeft = ui->btn_aw_minisize->x() - g_appInfoPtr->m_fRate*20 - ui->label_awbg_student->width();
+    }
+    
+    ui->label_awbg_student->setGeometry(nStudentLeft,
                                         ui->label_awbg_exam->y(), ui->label_awbg_student->width(), ui->label_awbg_student->height());
     ui->label_awbg_studentIcon->setGeometry(ui->label_awbg_student->x() - g_appInfoPtr->m_fRate*(10 + 16),
                                             ui->label_awbg_examIcon->y(), ui->label_awbg_examIcon->width(), ui->label_awbg_examIcon->height());
     ui->label_cl_company->adjustSize();
     ui->label_cl_company->setGeometry(g_appInfoPtr->m_fRate*30, ui->widget_awbg_BG->height() - g_appInfoPtr->m_fRate*10 - ui->label_cl_company->height(),
                                       ui->label_cl_company->width(), ui->label_cl_company->height());
+    if(!g_appInfoPtr->m_sVersionCode.isEmpty())
+    {
+        ui->label_cl_version->setText(QString("V%1").arg(g_appInfoPtr->m_sVersionCode));
+    }
     ui->label_cl_version->adjustSize();
     ui->label_cl_version->setGeometry(ui->widget_awbg_BG->width() - g_appInfoPtr->m_fRate*30 - ui->label_cl_version->width(),
                                       ui->label_cl_company->y(), ui->label_cl_version->width(), ui->label_cl_version->height());
-    ui->label_serverTime->adjustSize();
+	ui->label_serverTime->setText(QString::fromLocal8Bit("服务器时间:%1")
+		.arg(QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime()).toString("yyyy-MM-dd hh:mm:ss")));
+	ui->label_serverTime->adjustSize();
     ui->label_serverTime->setGeometry(ui->label_cl_version->x() - g_appInfoPtr->m_fRate*20 - ui->label_serverTime->width(),
                                       ui->label_cl_company->y(), ui->label_serverTime->width(), ui->label_serverTime->height());
 
@@ -65,6 +101,11 @@ void awBackground::initUI()
         if(m_pWaitExam == nullptr)
         {
             m_pWaitExam = std::make_shared<awWaitExam>(this);
+            connect(m_pWaitExam.get(), &awWaitExam::exitEaxm, this, [&](){
+                m_pWaitExam.reset();
+                emit closeWidget();
+            });
+
             connect(m_pWaitExam.get(), &awWaitExam::enterExam, this, [&](){
                 m_pWaitExam.reset();
 
@@ -76,38 +117,7 @@ void awBackground::initUI()
 				g_httpBllPtr->post(hrp);
 
 				m_wt = AW_WIDGET_TYPE::awwt_answerWidget;
-				if (m_pAnswerWidget == nullptr)
-				{
-					m_pAnswerWidget = std::make_shared<answerWidget>(this);
-                    connect(m_pAnswerWidget.get(), &answerWidget::showPracticeInfo, this, [&](){
-                        m_pAnswerWidget.reset();
-                        emit showPracticeInfo();
-                    });
-
-                    connect(m_pAnswerWidget.get(), &answerWidget::gobackCourseList, this, [&](){
-                        m_pAnswerWidget.reset();
-                        emit closeWidget();
-                    });
-
-                    connect(m_pAnswerWidget.get(), &answerWidget::showExamScore, this, [&](){
-                        m_pAnswerWidget.reset();
-
-                        m_wt = AW_WIDGET_TYPE::awwt_examScore;
-                        if(m_pExamScore == nullptr)
-                        {
-                            m_pExamScore = std::make_shared<awExamScore>(this);
-                            connect(m_pExamScore.get(), &awExamScore::goHomePage, this, [&](){
-                               emit closeWidget();
-                            });
-                        }
-                        m_pExamScore->setUI(g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 56, width() - g_appInfoPtr->m_fRate * 20 * 2,
-                            height() - g_appInfoPtr->m_fRate*(56 + 38));
-                        m_pExamScore->show();
-                    });
-				}
-				m_pAnswerWidget->setUI(g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 56, width() - g_appInfoPtr->m_fRate * 20 * 2,
-					height() - g_appInfoPtr->m_fRate*(56 + 38));
-				m_pAnswerWidget->show();
+
             });
         }
 		m_pWaitExam->setUI(g_appInfoPtr->m_fRate*20, g_appInfoPtr->m_fRate*56, width() - g_appInfoPtr->m_fRate*20*2,
@@ -122,44 +132,23 @@ void awBackground::initUI()
 		hrp.nRequestType = RequestType::rtGetCourseInfo;
 		hrp.eParamType = HttpParamType::hptUrl;
 		g_httpBllPtr->post(hrp);
-        if(m_pAnswerWidget == nullptr)
-        {
-            m_pAnswerWidget = std::make_shared<answerWidget>(this);
-            connect(m_pAnswerWidget.get(), &answerWidget::showPracticeInfo, this, [&](){
-                m_pAnswerWidget.reset();
-                emit showPracticeInfo();
-            });
 
-            connect(m_pAnswerWidget.get(), &answerWidget::gobackCourseList, this, [&](){
-                m_pAnswerWidget.reset();
-                emit closeWidget();
-            });
-
-            connect(m_pAnswerWidget.get(), &answerWidget::showExamScore, this, [&](){
-                m_pAnswerWidget.reset();
-
-                m_wt = AW_WIDGET_TYPE::awwt_examScore;
-                if(m_pExamScore == nullptr)
-                {
-                    m_pExamScore = std::make_shared<awExamScore>(this);
-                    connect(m_pExamScore.get(), &awExamScore::goHomePage, this, [&](){
-                       emit closeWidget();
-                    });
-                }
-                m_pExamScore->setUI(g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 56, width() - g_appInfoPtr->m_fRate * 20 * 2,
-                    height() - g_appInfoPtr->m_fRate*(56 + 38));
-                m_pExamScore->show();
-            });
-        }
-		m_pAnswerWidget->setUI(g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 56, width() - g_appInfoPtr->m_fRate * 20 * 2,
-			height() - g_appInfoPtr->m_fRate*(56 + 38));
-		m_pAnswerWidget->show();
 	}	
 	else if (m_wt == AW_WIDGET_TYPE::awwt_examScore)
 	{
+        CHttpRequestPackage hrp;
+        hrp.sUri = QString("/api/ecs_oe_student/client/exam/process/getCourseInfo/%1").arg(g_appInfoPtr->m_oExamInfo.nExamRecordDataId);
+        hrp.sParamList.push_back(QString("examRecordDataId,%1").arg(g_appInfoPtr->m_oExamInfo.nExamRecordDataId));
+        hrp.nRequestType = RequestType::rtGetCourseInfo;
+        hrp.eParamType = HttpParamType::hptUrl;
+        g_httpBllPtr->post(hrp);
+
         if(m_pExamScore == nullptr)
         {
             m_pExamScore = std::make_shared<awExamScore>(this);
+            connect(m_pExamScore.get(), &awExamScore::goHomePage, this, [&](){
+               emit closeWidget();
+            });
         }
 		m_pExamScore->setUI(g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 56, width() - g_appInfoPtr->m_fRate * 20 * 2,
 			height() - g_appInfoPtr->m_fRate*(56 + 38));
@@ -167,6 +156,43 @@ void awBackground::initUI()
 	}
 }
 
+void awBackground::showExamAnswer()
+{
+    if(m_pAnswerWidget == nullptr)
+    {
+        m_pAnswerWidget = std::make_shared<answerWidget>(this);
+        connect(m_pAnswerWidget.get(), &answerWidget::gobackLogin, this, &awBackground::gobackLogin);
+        connect(m_pAnswerWidget.get(), &answerWidget::showPracticeInfo, this, [&](){
+            m_pAnswerWidget.reset();
+            emit showPracticeInfo();
+        });
+
+        connect(m_pAnswerWidget.get(), &answerWidget::gobackCourseList, this, [&](){
+            m_pAnswerWidget.reset();
+            emit closeWidget();
+        });
+
+        connect(m_pAnswerWidget.get(), &answerWidget::showExamScore, this, [&](){
+            m_pAnswerWidget.reset();
+
+            m_wt = AW_WIDGET_TYPE::awwt_examScore;
+            if(m_pExamScore == nullptr)
+            {
+                m_pExamScore = std::make_shared<awExamScore>(this);
+                connect(m_pExamScore.get(), &awExamScore::goHomePage, this, [&](){
+                   emit closeWidget();
+                });
+            }
+            m_pExamScore->setUI(g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 56, width() - g_appInfoPtr->m_fRate * 20 * 2,
+                height() - g_appInfoPtr->m_fRate*(56 + 38));
+            m_pExamScore->show();
+        });
+    }
+    m_pAnswerWidget->setUI(g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 56, width() - g_appInfoPtr->m_fRate * 20 * 2,
+        height() - g_appInfoPtr->m_fRate*(56 + 38));
+    m_pAnswerWidget->show();
+}
+
 void awBackground::onGetCourseInfo(CGetCourseInfo getCourseInfo)
 {
     if (getCourseInfo.nCode == 200)
@@ -175,17 +201,31 @@ void awBackground::onGetCourseInfo(CGetCourseInfo getCourseInfo)
 
         g_appInfoPtr->m_oExamInfo.sCourseCode = getCourseInfo.sCourseCode;
         g_appInfoPtr->m_oExamInfo.sCourseName = getCourseInfo.sCourseName;
+        if(m_wt == AW_WIDGET_TYPE::awwt_answerWidget)
+        {
+            showExamAnswer();
+        }
 	}
 	else
 	{
         if(getCourseInfo.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取科目信息失败"), g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(QString::fromLocal8Bit("获取科目信息失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getCourseInfo.sMessage, g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(getCourseInfo.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
 
+
+void awBackground::on_btn_aw_close_clicked()
+{
+    QApplication::quit();
+}
+
+void awBackground::on_btn_aw_minisize_clicked()
+{
+    emit minisize();
+}

+ 9 - 0
client/awBackground.h

@@ -5,6 +5,7 @@
 #include "awWaitExam.h"
 #include "answerWidget.h"
 #include "awExamScore.h"
+#include <QTimer>
 
 namespace Ui {
 class awBackground;
@@ -23,16 +24,24 @@ class awBackground : public QWidget
 signals:
     void showPracticeInfo();
     void closeWidget();
+    void gobackLogin();
+    void minisize();
 public:
     explicit awBackground(AW_WIDGET_TYPE wt, QWidget *parent = nullptr);
     ~awBackground();
 private slots:
     void onGetCourseInfo(CGetCourseInfo getCourseInfo);
+    void on_btn_aw_close_clicked();
+
+    void on_btn_aw_minisize_clicked();
+
 private:
     void initUI();
+    void showExamAnswer();
 
     Ui::awBackground *ui;
 
+    std::shared_ptr<QTimer> m_pServerTime;
     std::shared_ptr<awWaitExam> m_pWaitExam;
     std::shared_ptr<answerWidget> m_pAnswerWidget;    
     std::shared_ptr<awExamScore> m_pExamScore;

+ 36 - 4
client/awBackground.ui

@@ -76,16 +76,48 @@
     <widget class="QLabel" name="label_awbg_student">
      <property name="geometry">
       <rect>
-       <x>500</x>
-       <y>20</y>
-       <width>54</width>
-       <height>12</height>
+       <x>350</x>
+       <y>30</y>
+       <width>341</width>
+       <height>16</height>
       </rect>
      </property>
      <property name="text">
       <string>文闹闹  |  T20190123  |  402000201010202827  |  测试中心</string>
      </property>
     </widget>
+    <widget class="QPushButton" name="btn_aw_close">
+     <property name="geometry">
+      <rect>
+       <x>740</x>
+       <y>30</y>
+       <width>31</width>
+       <height>23</height>
+      </rect>
+     </property>
+     <property name="focusPolicy">
+      <enum>Qt::NoFocus</enum>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+    <widget class="QPushButton" name="btn_aw_minisize">
+     <property name="geometry">
+      <rect>
+       <x>700</x>
+       <y>30</y>
+       <width>31</width>
+       <height>23</height>
+      </rect>
+     </property>
+     <property name="focusPolicy">
+      <enum>Qt::NoFocus</enum>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
    </widget>
    <widget class="QLabel" name="label_serverTime">
     <property name="geometry">

+ 73 - 16
client/awExamScore.cpp

@@ -14,17 +14,20 @@ awExamScore::awExamScore(QWidget *parent) :
 
 	qRegisterMetaType<CGetExamProperty>("CGetExamProperty");
 	connect(g_httpBllPtr.get(), &CHttpBll::sgnGetExamProperty, this, &awExamScore::onGetExamProperty);
+	qRegisterMetaType<CGetEndExamInfo>("CGetEndExamInfo");
+	connect(g_httpBllPtr.get(), &CHttpBll::sgnGetEndExamInfo, this, &awExamScore::onGetEndExamInfo);
 
 	CHttpRequestPackage hrp;
 	hrp.sUri = QString("/api/ecs_exam_work/exam/getExamPropertyFromCacheByStudentSession/%1/%2")
-		.arg(g_appInfoPtr->m_oExamInfo.nExamId).arg("SNAPSHOT_INTERVAL,PRACTICE_TYPE,FREEZE_TIME");
+        .arg(g_appInfoPtr->m_oExamInfo.nExamId).arg("AFTER_EXAM_REMARK,IS_OBJ_SCORE_VIEW,SHOW_CHEATING_REMARK,CHEATING_REMARK");
 	hrp.nRequestType = RequestType::rtGetExamProperty;
 	hrp.sCommonStr = "AFTER_EXAM_REMARK,IS_OBJ_SCORE_VIEW,SHOW_CHEATING_REMARK,CHEATING_REMARK";
-	g_httpBllPtr->get(hrp);
+	g_httpBllPtr->get(hrp);	
 }
 
 awExamScore::~awExamScore()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -52,7 +55,7 @@ void awExamScore::setUI(const int nLeft, const int nTop, const int nWidth, const
     ui->txtb_awes_breachNotice->setGeometry(ui->txtb_awes_postNotice->x() + ui->txtb_awes_postNotice->width() + g_appInfoPtr->m_fRate*20,
                                             ui->txtb_awes_postNotice->y(), ui->txtb_awes_postNotice->width(), ui->txtb_awes_postNotice->height());
     ui->btn_awes_goback->setGeometry((ui->widget_awes_BG->width() - g_appInfoPtr->m_fRate*200)/2, ui->txtb_awes_postNotice->y() + ui->txtb_awes_postNotice->height() + g_appInfoPtr->m_fRate*30,
-                                     g_appInfoPtr->m_fRate*200, g_appInfoPtr->m_fRate*40);
+                                     g_appInfoPtr->m_fRate*200, g_appInfoPtr->m_fRate*40);	
 }
 
 void awExamScore::onGetExamProperty(CGetExamProperty getExamProperty)
@@ -68,21 +71,43 @@ void awExamScore::onGetExamProperty(CGetExamProperty getExamProperty)
 					(ui->widget_awes_BG->width() - g_appInfoPtr->m_fRate * 40 * 2 - g_appInfoPtr->m_fRate*20)/2, g_appInfoPtr->m_fRate * 164);
 				ui->txtb_awes_breachNotice->setGeometry(ui->txtb_awes_postNotice->x() + ui->txtb_awes_postNotice->width() + g_appInfoPtr->m_fRate * 20,
 					ui->txtb_awes_postNotice->y(), ui->txtb_awes_postNotice->width(), ui->txtb_awes_postNotice->height());
-				ui->txtb_awes_breachNotice->insertHtml(getExamProperty.sCheatingRemark);
+                QTextCursor cursor = ui->txtb_awes_breachNotice->textCursor();
+				QTextBlockFormat textStyleFormat = cursor.blockFormat();				
+				textStyleFormat.setLineHeight(g_appInfoPtr->m_fRate * 5, QTextBlockFormat::LineDistanceHeight);
+				cursor.setBlockFormat(textStyleFormat);
+				QTextCharFormat fmt;
+                QFont font;
+                font.setFamily("Microsoft YaHei");
+                font.setPixelSize(g_appInfoPtr->m_fRate*16);
+                font.setWeight(QFont::Bold);
+				fmt.setFont(font);
+                cursor.insertText(QString::fromLocal8Bit("违纪说明:\n"), fmt);				
+				cursor.insertHtml(getExamProperty.sCheatingRemark);
+				ui->txtb_awes_breachNotice->setTextCursor(cursor);
 			}
 
-			ui->txtb_awes_postNotice->insertHtml(getExamProperty.sAfterExamRemark);
+			{
+				QTextCursor cursor = ui->txtb_awes_postNotice->textCursor();
+				QTextBlockFormat textStyleFormat = cursor.blockFormat();
+				textStyleFormat.setLineHeight(g_appInfoPtr->m_fRate * 5, QTextBlockFormat::LineDistanceHeight);
+				cursor.setBlockFormat(textStyleFormat);
+				QTextCharFormat fmt;
+				QFont font;
+				font.setFamily("Microsoft YaHei");
+				font.setPixelSize(g_appInfoPtr->m_fRate * 16);
+				font.setWeight(QFont::DemiBold);
+				fmt.setFont(font);
+				cursor.insertText(QString::fromLocal8Bit("考后说明:\n"), fmt);
+				cursor.insertHtml(getExamProperty.sAfterExamRemark);
+				ui->txtb_awes_postNotice->setTextCursor(cursor);
+			}
 
 			if (getExamProperty.bIsObjScoreView)
 			{
 				//获取客观分
 				ui->label_awes_result->setText(QString::fromLocal8Bit("考试结果计算中..."));
-				CHttpRequestPackage hrp;
-				hrp.sUri = QString("/api/ecs_oe_student/client/exam/process/getEndExamInfo");
-				hrp.sParamList.push_back(QString("examRecordDataId,%1").arg(g_appInfoPtr->m_oExamInfo.nExamRecordDataId));
-				hrp.nRequestType = RequestType::rtGetEndExamInfo;				
-				hrp.eParamType = HttpParamType::hptUrl;
-				g_httpBllPtr->post(hrp);
+				m_getScoreEndTime = g_appInfoPtr->serverMTime() + +2 * 60 * 1000;
+				getObjectiveScore();
 			}
 			else
 			{
@@ -95,30 +120,62 @@ void awExamScore::onGetExamProperty(CGetExamProperty getExamProperty)
 	{
         if(getExamProperty.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取考试信息失败"), g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(QString::fromLocal8Bit("获取考试信息失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getExamProperty.sMessage, g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(getExamProperty.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
 
+void awExamScore::getObjectiveScore()
+{
+	CHttpRequestPackage hrp;
+	hrp.sUri = QString("/api/ecs_oe_student/client/exam/process/getEndExamInfo");
+	hrp.sParamList.push_back(QString("examRecordDataId,%1").arg(g_appInfoPtr->m_oExamInfo.nExamRecordDataId));
+	hrp.nRequestType = RequestType::rtGetEndExamInfo;
+	hrp.eParamType = HttpParamType::hptUrl;
+	g_httpBllPtr->post(hrp);
+}
+
 void awExamScore::onGetEndExamInfo(CGetEndExamInfo getEndExamInfo)
 {
 	if (getEndExamInfo.nCode == 200)
 	{
-		ui->label_awes_result->setText(QString::fromLocal8Bit("客观题得分:%1").arg(getEndExamInfo.fObjectiveScore));
+		if (getEndExamInfo.bIsCalculate)
+		{
+			ui->label_awes_result->setText(QString::fromLocal8Bit("考试结果计算中…"));
+			if (m_getScoreEndTime > g_appInfoPtr->serverMTime())
+			{
+				QTimer::singleShot(3 * 1000, [&]() {
+					getObjectiveScore();
+				});
+			}
+			else
+			{
+
+			}
+		}
+		else if (getEndExamInfo.bIsWarn)
+		{
+			ui->label_awes_result->setText(QString::fromLocal8Bit("成绩待审核"));
+		}
+		else
+		{
+			ui->label_awes_result->setText(QString::fromLocal8Bit("客观题得分:%1").arg(getEndExamInfo.fObjectiveScore));
+		}
+		
 	}
 	else
 	{
         if(getEndExamInfo.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取考试信息失败"), g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(QString::fromLocal8Bit("获取考试信息失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getEndExamInfo.sMessage, g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(getEndExamInfo.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }

+ 3 - 0
client/awExamScore.h

@@ -25,7 +25,10 @@ private slots:
     void on_btn_awes_goback_clicked();
 
 private:
+	void getObjectiveScore();
+
     Ui::awExamScore *ui;
+	__int64 m_getScoreEndTime;
 };
 
 #endif // AWEXAMSCORE_H

+ 3 - 0
client/awExamScore.ui

@@ -76,6 +76,9 @@
       <height>192</height>
      </rect>
     </property>
+    <property name="placeholderText">
+     <string/>
+    </property>
    </widget>
    <widget class="QTextBrowser" name="txtb_awes_breachNotice">
     <property name="geometry">

+ 39 - 3
client/awMsgBox.cpp

@@ -54,8 +54,42 @@ awMsgBox::~awMsgBox()
     delete ui;
 }
 
-void awMsgBox::initUI()
+void awMsgBox::clear(QWidget *parent)
 {
+    std::scoped_lock lock(msgMutex);
+
+    for (std::list<std::shared_ptr<awMsgBox>>::iterator it = vMsgList.begin(); it != vMsgList.end(); )
+    {
+        if((*it)->parent() == parent || parent == nullptr)
+        {
+            it = vMsgList.erase(it);
+        }
+		else
+		{
+			++it;
+		}
+    }
+	
+}
+
+void awMsgBox::initUI(MSG_ICON_TYPE type)
+{
+    QString sStyle = "";
+    if(type == MSG_ICON_TYPE::mit_information)
+    {
+        sStyle = "border-image:url(:/images/icon-um-info.png);";
+    }
+    else if(type == MSG_ICON_TYPE::mit_succeed)
+    {
+        sStyle = "border-image:url(:/images/icon-env-test-pass.png);";
+    }
+    else if(type == MSG_ICON_TYPE::mit_error)
+    {
+        sStyle = "border-image:url(:/images/icon-um.png);";
+    }
+
+    ui->label_awmb_icon->setStyleSheet(sStyle);
+
     ui->label_awmb_content->adjustSize();
     int nCW = g_appInfoPtr->m_fRate*40 + g_appInfoPtr->m_fRate*40 + g_appInfoPtr->m_fRate*(28 + 10) + ui->label_awmb_content->width();
     QDesktopWidget *dekwiget = QApplication::desktop();
@@ -78,11 +112,13 @@ void awMsgBox::initUI()
     ui->widget_awmb_BG->setGraphicsEffect(effect);
 }
 
-void ShowMsg(QString sMsg, QWidget *parent)
+void ShowMsg(QString sMsg, QWidget *parent, MSG_ICON_TYPE type)
 {
 	std::shared_ptr<awMsgBox> msg = std::make_shared<awMsgBox>(parent);
+
+
     msg->setMsg(sMsg);
-    msg->initUI();
+    msg->initUI(type);
     msg->show();
 	std::scoped_lock lock(awMsgBox::msgMutex);
 	awMsgBox::vMsgList.push_back(msg);

+ 11 - 2
client/awMsgBox.h

@@ -10,6 +10,13 @@ class awMsgBox;
 
 #include <QTimer>
 
+enum class MSG_ICON_TYPE
+{
+    mit_information = 1,
+    mit_succeed,
+    mit_error
+};
+
 class awMsgBox : public QDialog
 {
     Q_OBJECT
@@ -18,8 +25,10 @@ public:
     explicit awMsgBox(QWidget *parent = nullptr);
     ~awMsgBox();
 
-    void initUI();
+    void initUI(MSG_ICON_TYPE type);
     void setMsg(QString sMsg);
+
+    static void clear(QWidget *parent = nullptr);
 	static std::list< std::shared_ptr<awMsgBox>> vMsgList;
 	static std::mutex msgMutex;
 private slots:
@@ -31,6 +40,6 @@ private:
 
 };
 
-extern void ShowMsg(QString sMsg, QWidget *parent = nullptr);
+extern void ShowMsg(QString sMsg, QWidget *parent = nullptr, MSG_ICON_TYPE type = MSG_ICON_TYPE::mit_information);
 
 #endif // AWMSGBOX_H

+ 67 - 17
client/awQuestionNavigate.cpp

@@ -23,19 +23,32 @@ awQuestionNavigate::~awQuestionNavigate()
 
 int awQuestionNavigate::setUI(const int nWidth)
 {
-	ui->label_questionName->setText(QString::fromLocal8Bit("%1%2").arg(CCommonTools::Arab2Sinogram(m_pgs.nNumber)).arg(m_pgs.sGroupName));
+	ui->label_questionName->setText(QString::fromLocal8Bit("%1 %2").arg(CCommonTools::Arab2Sinogram(m_pgs.nNumber)).arg(m_pgs.sGroupName));
+    ui->label_questionName->setToolTip(ui->label_questionName->text());
 	ui->label_questionScore->setText(QString::fromLocal8Bit("%1分").arg(m_pgs.fGroupScore));
-	ui->widget_qn_groupInfo->setGeometry(0, 0, nWidth, g_appInfoPtr->m_fRate*50);
-    ui->label_questionName->adjustSize();
-    ui->label_questionName->setGeometry(0, g_appInfoPtr->m_fRate*20, ui->label_questionName->width(), ui->label_questionName->height());
+
     ui->label_questionScore->adjustSize();
-    ui->label_questionScore->setGeometry(ui->widget_qn_groupInfo->width() - ui->label_questionScore->width(), ui->label_questionName->y() + (ui->label_questionName->height() - ui->label_questionScore->height())/2,
+    ui->label_questionScore->setGeometry(nWidth - ui->label_questionScore->width(), ui->label_questionName->y() + (ui->label_questionName->height() - ui->label_questionScore->height())/2,
                                          ui->label_questionScore->width(), ui->label_questionScore->height());
-	QGridLayout *layout = new QGridLayout;
-	layout->setHorizontalSpacing(g_appInfoPtr->m_fRate*10);
-	layout->setVerticalSpacing(g_appInfoPtr->m_fRate*10);
-	layout->setAlignment(Qt::AlignLeft);
-	layout->setContentsMargins(0, 0, 0, 0);
+
+    ui->label_questionName->setGeometry(0, g_appInfoPtr->m_fRate*20, ui->label_questionScore->x() - g_appInfoPtr->m_fRate*10, ui->label_questionName->height());
+    ui->label_questionName->adjustSize();
+    int nNameWidth = ui->label_questionScore->x() - ui->label_questionName->x() - g_appInfoPtr->m_fRate*10;
+    int nRowCount = (ui->label_questionName->width() + nNameWidth - 1)/nNameWidth;
+    if(nRowCount > 1)
+    {
+         ui->label_questionName->setWordWrap(true);
+         ui->label_questionName->setGeometry(0, g_appInfoPtr->m_fRate*20,
+                                               nNameWidth, nRowCount*ui->label_questionName->height());
+    }
+
+
+    ui->widget_qn_groupInfo->setGeometry(0, 0, nWidth, g_appInfoPtr->m_fRate*30 + ui->label_questionName->height());
+//	QGridLayout *layout = new QGridLayout;
+//	layout->setHorizontalSpacing(g_appInfoPtr->m_fRate*10);
+//	layout->setVerticalSpacing(g_appInfoPtr->m_fRate*10);
+//	layout->setAlignment(Qt::AlignLeft);
+//	layout->setContentsMargins(0, 0, 0, 0);
 	
     m_vItemList.clear();
     int nTotal = 0;
@@ -47,13 +60,19 @@ int awQuestionNavigate::setUI(const int nWidth)
         for(int j = 0; j < nSubQuestionSize; ++j)
         {
             CSubQuestionStruct &sqs = qs.vSubQuestionStruct[j];
-            awqn_item *item = new awqn_item(sqs);
+            awqn_item *item = new awqn_item(sqs, ui->widget_qn_questionList);
             connect(item, &awqn_item::showSubQuestion, this, [&](int nOrder) {
                     emit showSubQuestion(qs.sQuestionId, nOrder, qs.bHasContent);
 			});
-			item->setFixedSize(g_appInfoPtr->m_fRate * 30, g_appInfoPtr->m_fRate * 30);
-			layout->addWidget(item, (sqs.nShowNumber + 4)/5 - 1, (sqs.nShowNumber - 1)%5, 1, 1);
-			item->setUI();
+
+            int nCol = nTotal%5;
+            int nRow = (nTotal + 1 + 4)/5 - 1;
+            int nY = g_appInfoPtr->m_fRate*nRow * 40;
+            int nX = g_appInfoPtr->m_fRate*nCol * 40;
+
+            item->setUI(nX, nY, (g_appInfoPtr->m_fRate*30), (30*g_appInfoPtr->m_fRate));
+
+
             m_vItemList.push_back(item);
             nTotal++;
         }
@@ -61,11 +80,36 @@ int awQuestionNavigate::setUI(const int nWidth)
     int nRow = (nTotal + 4)/5;
     ui->widget_qn_questionList->setGeometry(0, ui->widget_qn_groupInfo->y() + ui->widget_qn_groupInfo->height(),
                                             nWidth, nRow*40*g_appInfoPtr->m_fRate);
-	ui->widget_qn_questionList->setLayout(layout);	
+//	ui->widget_qn_questionList->setLayout(layout);
     setGeometry(0, 0, nWidth, ui->widget_qn_questionList->height() + ui->widget_qn_groupInfo->height());
     return height();
 }
 
+int awQuestionNavigate::showItems(std::vector<awqn_item*> vItemList)
+{
+    int nTotal = 0;
+    int nQuestionSize = vItemList.size();
+    for(int i = 0; i < nQuestionSize; ++i)
+    {
+        awqn_item *item = vItemList[i];
+
+        int nCol = nTotal%5;
+        int nRow = (nTotal + 1 + 4)/5 - 1;
+        int nY = g_appInfoPtr->m_fRate*nRow * 40;
+        int nX = g_appInfoPtr->m_fRate*nCol * 40;
+        item->setUI(nX, nY, g_appInfoPtr->m_fRate*30, g_appInfoPtr->m_fRate*30);
+
+        ++nTotal;
+    }
+    int nRow = (nTotal + 4)/5;
+    int nWidth = width();
+    ui->widget_qn_questionList->setGeometry(0, ui->widget_qn_groupInfo->y() + ui->widget_qn_groupInfo->height(),
+                                            nWidth, nRow*40*g_appInfoPtr->m_fRate);
+
+    setFixedHeight(ui->widget_qn_questionList->height() + ui->widget_qn_groupInfo->height());
+    return height();
+}
+
 void awQuestionNavigate::refreshStatus()
 {
     int nSize = m_vItemList.size();
@@ -79,9 +123,9 @@ void awQuestionNavigate::refreshStatus()
     }
 }
 
-void awQuestionNavigate::setShowType(ITEM_SHOW_TYPE type)
+int awQuestionNavigate::setShowType(ITEM_SHOW_TYPE type)
 {
-    int nIndex = 1;
+    std::vector<awqn_item*> vItemList;
     int nSize = m_vItemList.size();
     for(int i = 0; i < nSize; ++i)
     {
@@ -91,12 +135,14 @@ void awQuestionNavigate::setShowType(ITEM_SHOW_TYPE type)
             if(type == ITEM_SHOW_TYPE::ist_showAll)
             {
                 item->setVisible(true);
+                vItemList.push_back(item);
             }
             else if(type == ITEM_SHOW_TYPE::ist_showMarked)
             {
                 if(item->isMarked())
                 {
                     item->setVisible(true);
+                    vItemList.push_back(item);
                 }
                 else
                 {
@@ -108,6 +154,7 @@ void awQuestionNavigate::setShowType(ITEM_SHOW_TYPE type)
                 if(item->isAnswered())
                 {
                     item->setVisible(true);
+                    vItemList.push_back(item);
                 }
                 else
                 {
@@ -119,6 +166,7 @@ void awQuestionNavigate::setShowType(ITEM_SHOW_TYPE type)
                 if(!item->isAnswered())
                 {
                     item->setVisible(true);
+                    vItemList.push_back(item);
                 }
                 else
                 {
@@ -127,4 +175,6 @@ void awQuestionNavigate::setShowType(ITEM_SHOW_TYPE type)
             }
         }
     }
+
+    return showItems(vItemList);
 }

+ 3 - 1
client/awQuestionNavigate.h

@@ -28,8 +28,10 @@ public:
 
     int setUI(const int nWidth);
     void refreshStatus();
-    void setShowType(ITEM_SHOW_TYPE type);
+    int setShowType(ITEM_SHOW_TYPE type);
 private:
+    int showItems(std::vector<awqn_item*> vItemList);
+
     Ui::awQuestionNavigate *ui;
 
     CPaperGroupStruct &m_pgs;

+ 80 - 0
client/awTimeLeftTips.cpp

@@ -0,0 +1,80 @@
+#include "awTimeLeftTips.h"
+#include "ui_awTimeLeftTips.h"
+
+#include "CAppInfo.h"
+
+#include <QDesktopWidget>
+
+awTimeLeftTips::awTimeLeftTips(MASK_POP_WIDGET_TYPE type, QWidget *parent) :
+    QWidget(parent),
+    ui(new Ui::awTimeLeftTips)
+{
+    ui->setupUi(this);
+
+    setStyleSheet(g_appInfoPtr->m_sQssStr);
+
+    if(type == MASK_POP_WIDGET_TYPE::wt_timeLeft)
+    {
+        ui->label_awtlt_content->setText(QString::fromLocal8Bit(R"(<html>
+                                                <style>
+                                                    .normal
+                                                    {
+                                                        font-size:%1px;
+                                                        font-family:"Microsoft YaHei";
+                                                        font-weight:400;
+                                                        color:rgba(102,102,102,1);
+                                                    }
+                                                    .hover
+                                                    {
+                                                        font-size:%2px;
+                                                        font-family:"Microsoft YaHei";
+                                                        font-weight:600;
+                                                        color:rgba(255,0,0,1);
+                                                    }
+                                                </style>
+                                                <body>
+                                                    <span class="normal">还有</span><span class="hover">五</span><span class="normal">分钟即将结束本场考试,请合理分配时间!</span>
+                                                </body>
+                                            </html>)").arg((int)(g_appInfoPtr->m_fRate * 14)).arg((int)(g_appInfoPtr->m_fRate * 16)));
+    }
+    else
+    {
+        ui->label_awtlt_title->setText(QString::fromLocal8Bit("网络连接异常"));
+        ui->label_awtlt_content->setText(QString::fromLocal8Bit("退出考试"));
+    }
+
+    initUI();
+}
+
+awTimeLeftTips::~awTimeLeftTips()
+{
+    delete ui;
+}
+
+void awTimeLeftTips::setText(QString sTitle, QString sContent)
+{
+	ui->label_awtlt_title->setText(sTitle);
+	ui->label_awtlt_content->setText(sContent);
+}
+
+void awTimeLeftTips::initUI()
+{
+    QDesktopWidget *dekwiget = QApplication::desktop();
+    setGeometry(0, 0, dekwiget->width(), dekwiget->height());
+	ui->widget_mask->setGeometry(0, 0, width(), height());
+	ui->widget_awtlt_bg->setGeometry((width() - g_appInfoPtr->m_fRate*420)/2,
+		g_appInfoPtr->m_fRate * 190, g_appInfoPtr->m_fRate*420, g_appInfoPtr->m_fRate*190);
+	ui->label_awtlt_title->adjustSize();
+	ui->label_awtlt_title->setGeometry(g_appInfoPtr->m_fRate *20, g_appInfoPtr->m_fRate *20,
+		ui->label_awtlt_title->width(), ui->label_awtlt_title->height());	
+	ui->label_awtlt_content->adjustSize();
+	ui->label_awtlt_content->setGeometry(g_appInfoPtr->m_fRate * 40, ui->label_awtlt_title->y() + ui->label_awtlt_title->height() +  g_appInfoPtr->m_fRate * 40,
+		ui->label_awtlt_content->width(), ui->label_awtlt_content->height());
+	ui->btn_awtlt_confirm->setGeometry(ui->widget_awtlt_bg->width() - g_appInfoPtr->m_fRate *(20 + 60),
+		ui->widget_awtlt_bg->height() - g_appInfoPtr->m_fRate *(20 + 40), 60, 40);
+}
+
+void awTimeLeftTips::on_btn_awtlt_confirm_clicked()
+{
+    emit timeLeftConfirm();
+}

+ 35 - 0
client/awTimeLeftTips.h

@@ -0,0 +1,35 @@
+#ifndef AWTIMELEFTTIPS_H
+#define AWTIMELEFTTIPS_H
+
+#include <QWidget>
+namespace Ui {
+class awTimeLeftTips;
+}
+
+enum class MASK_POP_WIDGET_TYPE
+{
+    wt_timeLeft=1,
+    wt_exitExam
+};
+class awTimeLeftTips : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit awTimeLeftTips(MASK_POP_WIDGET_TYPE type, QWidget *parent = nullptr);
+    ~awTimeLeftTips();
+
+	void setText(QString sTitle, QString sContent);
+signals:
+    void timeLeftConfirm();
+private slots:
+    void on_btn_awtlt_confirm_clicked();
+
+private:
+    void initUI();
+	
+
+    Ui::awTimeLeftTips *ui;
+};
+
+#endif // AWTIMELEFTTIPS_H

+ 78 - 0
client/awTimeLeftTips.ui

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>awTimeLeftTips</class>
+ <widget class="QWidget" name="awTimeLeftTips">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <widget class="QWidget" name="widget_mask" native="true">
+   <property name="geometry">
+    <rect>
+     <x>10</x>
+     <y>10</y>
+     <width>381</width>
+     <height>281</height>
+    </rect>
+   </property>
+   <widget class="QWidget" name="widget_awtlt_bg" native="true">
+    <property name="geometry">
+     <rect>
+      <x>30</x>
+      <y>40</y>
+      <width>321</width>
+      <height>121</height>
+     </rect>
+    </property>
+    <widget class="QLabel" name="label_awtlt_title">
+     <property name="geometry">
+      <rect>
+       <x>10</x>
+       <y>20</y>
+       <width>54</width>
+       <height>12</height>
+      </rect>
+     </property>
+     <property name="text">
+      <string>温馨提示</string>
+     </property>
+    </widget>
+    <widget class="QLabel" name="label_awtlt_content">
+     <property name="geometry">
+      <rect>
+       <x>50</x>
+       <y>50</y>
+       <width>54</width>
+       <height>12</height>
+      </rect>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+    <widget class="QPushButton" name="btn_awtlt_confirm">
+     <property name="geometry">
+      <rect>
+       <x>220</x>
+       <y>90</y>
+       <width>75</width>
+       <height>23</height>
+      </rect>
+     </property>
+     <property name="text">
+      <string>确定</string>
+     </property>
+    </widget>
+   </widget>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 8 - 6
client/awWaitExam.cpp

@@ -75,6 +75,7 @@ awWaitExam::awWaitExam(QWidget *parent) :
 
 awWaitExam::~awWaitExam()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -124,12 +125,13 @@ void awWaitExam::onStartExam(CStartExam startExam)
 	{
         if(startExam.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("开始考试失败"), g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(QString::fromLocal8Bit("开始考试失败"), (QWidget*)g_appInfoPtr->m_pAnsBgWidget->parent(), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(startExam.sMessage, g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(startExam.sMessage, (QWidget*)g_appInfoPtr->m_pAnsBgWidget->parent(), MSG_ICON_TYPE::mit_error);
         }
+        QTimer::singleShot(2000, this, [&](){emit exitEaxm();});
 	}
 }
 
@@ -186,11 +188,11 @@ void awWaitExam::onGetExamRecordPaperStruct(CGetExamRecordPaperStruct getExamRec
 	{
         if(getExamRecordPaperStruct.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取试卷结构失败"), g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(QString::fromLocal8Bit("获取试卷结构失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getExamRecordPaperStruct.sMessage, g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(getExamRecordPaperStruct.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -208,11 +210,11 @@ void awWaitExam::onGetExamProperty(CGetExamProperty getExamProperty)
 	{
         if(getExamProperty.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取考试信息失败"), g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(QString::fromLocal8Bit("获取考试信息失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getExamProperty.sMessage, g_appInfoPtr->m_pAnsBgWidget);
+            ShowMsg(getExamProperty.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }

+ 1 - 0
client/awWaitExam.h

@@ -15,6 +15,7 @@ class awWaitExam : public QWidget
 
 signals:
     void enterExam();
+    void exitEaxm();
 public:
     explicit awWaitExam(QWidget *parent = nullptr);
     ~awWaitExam();

+ 8 - 4
client/awqn_item.cpp

@@ -2,6 +2,7 @@
 #include "ui_awqn_item.h"
 
 #include "CAppInfo.h"
+#include "logproc.h"
 
 awqn_item::awqn_item(CSubQuestionStruct &sqs, QWidget *parent) :
     QWidget(parent),
@@ -20,8 +21,9 @@ awqn_item::~awqn_item()
     delete ui;
 }
 
-void awqn_item::setUI()
-{
+void awqn_item::setUI(int nX, int nY, int nWidth, int nHeight)
+{    
+    setGeometry(nX, nY, nWidth, nHeight);
 	ui->btn_awqn_quesion->setGeometry(0, 0, width(), height());
 
 }
@@ -55,6 +57,8 @@ void awqn_item::refreshStatus()
             border:0px;
             border-radius:%2px;
         })").arg((int)(g_appInfoPtr->m_fRate*12)).arg((int)(g_appInfoPtr->m_fRate*15));
+
+
     QString sMarkedStyle = QString(R"(QPushButton
         {
             outline:none;
@@ -66,7 +70,7 @@ void awqn_item::refreshStatus()
             border:%2px solid rgba(255,159,67,1);
             border-radius:%3px;
         })").arg((int)(g_appInfoPtr->m_fRate*12))
-            .arg((int)(g_appInfoPtr->m_fRate*1))
+            .arg((int)(g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1))
             .arg((int)(g_appInfoPtr->m_fRate*15));
     QString sAnsweredStyle = QString(R"(QPushButton
         {
@@ -79,7 +83,7 @@ void awqn_item::refreshStatus()
             border:%2px solid rgba(131,172,247,1);
             border-radius:%3px;
         })").arg((int)(g_appInfoPtr->m_fRate*12))
-            .arg((int)(g_appInfoPtr->m_fRate*1))
+            .arg((int)(g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1))
             .arg((int)(g_appInfoPtr->m_fRate*15));
     QString sCurrentStyle = QString(R"(QPushButton
         {

+ 1 - 1
client/awqn_item.h

@@ -16,7 +16,7 @@ signals:
 public:
     explicit awqn_item(CSubQuestionStruct &sqs, QWidget *parent = nullptr);
     ~awqn_item();
-	void setUI();
+    void setUI(int nX, int nY, int nWidth, int nHeight);
     void refreshStatus();
     bool isMarked();
     bool isAnswered();

+ 10 - 8
client/clEditPassword.cpp

@@ -5,7 +5,8 @@
 #include "CAppInfo.h"
 #include "logproc.h"
 #include "awMsgBox.h"
-
+#include <QRegExpValidator>
+#include <QRegExp>
 clEditPassword::clEditPassword(QWidget *parent) :
     QWidget(parent),
     ui(new Ui::clEditPassword)
@@ -20,6 +21,7 @@ clEditPassword::clEditPassword(QWidget *parent) :
 
 clEditPassword::~clEditPassword()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -27,7 +29,7 @@ clEditPassword::~clEditPassword()
 void clEditPassword::setUI(const int nWidth, const int nHeight)
 {
     setGeometry(0, 0, nWidth, nHeight);
-	QRegExp regx("[a-zA-Z0-9]+$");	
+    QRegExp regx("[a-zA-Z0-9]{6,18}");
     ui->edt_oldPassword->setGeometry(g_appInfoPtr->m_fRate*30, g_appInfoPtr->m_fRate*30,
                                      g_appInfoPtr->m_fRate*360, g_appInfoPtr->m_fRate*40);
 	QValidator *validator = new QRegExpValidator(regx, ui->edt_newPassword);
@@ -46,20 +48,20 @@ void clEditPassword::on_btn_savePassword_clicked()
 {
     if (ui->edt_oldPassword->text().isEmpty())
 	{
-        ShowMsg(QString::fromLocal8Bit("请先输入原始密码"), this);
+        ShowMsg(QString::fromLocal8Bit("请先输入原始密码"), this, MSG_ICON_TYPE::mit_error);
         return;
 	}
 
     if (ui->edt_newPassword->text() != ui->edt_newPassword1->text())
     {
-        ShowMsg(QString::fromLocal8Bit("两次输入密码不一致"), this);
+        ShowMsg(QString::fromLocal8Bit("两次输入密码不一致"), this, MSG_ICON_TYPE::mit_error);
         return;
     }
 
     if(ui->edt_newPassword->text().length() < 6 ||
             ui->edt_newPassword->text().length() > 18)
     {
-        ShowMsg(QString::fromLocal8Bit("请输入6到18位数字或字母"), this);
+        ShowMsg(QString::fromLocal8Bit("请输入6到18位数字或字母"), this, MSG_ICON_TYPE::mit_error);
         return;
     }
 
@@ -76,17 +78,17 @@ void clEditPassword::onEditPassword(CEditPassword editPassword)
 {
 	if (editPassword.nCode == 200)
 	{
-		ShowMsg(QString::fromLocal8Bit("保存密码成功"), this);
+        ShowMsg(QString::fromLocal8Bit("保存密码成功"), this, MSG_ICON_TYPE::mit_succeed);
 	}
 	else
 	{
 		if (editPassword.sMessage != "")
 		{
-			ShowMsg(editPassword.sMessage, this);
+            ShowMsg(editPassword.sMessage, this, MSG_ICON_TYPE::mit_error);
 		}
 		else
 		{
-			ShowMsg(QString::fromLocal8Bit("保存密码失败"), this);
+            ShowMsg(QString::fromLocal8Bit("保存密码失败"), this, MSG_ICON_TYPE::mit_error);
 		}
 	}
 }

+ 13 - 3
client/clNoticeDetail.cpp

@@ -19,6 +19,16 @@ clNoticeDetail::~clNoticeDetail()
 
 void clNoticeDetail::setUI(const int nLeft, const int nTop, const int nWidth, const int nHeight)
 {
+	if (m_ni.bHasRecalled)
+	{
+		ui->label_clnd_title->setText(QString::fromLocal8Bit("发送者已撤回消息:%1").arg(m_ni.sTitle));		
+		ui->txtb_noticeContent->insertPlainText(QString::fromLocal8Bit("该消息已经被发送者撤回。"));
+	}
+	else
+	{
+		ui->label_clnd_title->setText(m_ni.sTitle);
+		ui->txtb_noticeContent->insertHtml(m_ni.sContent);
+	}
     setGeometry(nLeft, nTop, nWidth, nHeight);
     ui->widget_offlineExam->setGeometry(0, 0, width(), height());
     ui->widget_op_top->setGeometry(0, 0, width(), g_appInfoPtr->m_fRate*72);
@@ -29,7 +39,7 @@ void clNoticeDetail::setUI(const int nLeft, const int nTop, const int nWidth, co
     ui->btn_gobackList->setIcon(QIcon(":/images/icon-goback-list.png"));
     ui->widget_clnd_client->setGeometry(0, ui->widget_op_top->height(), width(), height() - ui->widget_op_top->height());
 	
-	ui->label_clnd_title->setText(m_ni.sTitle);
+	
 	ui->label_clnd_title->adjustSize();
     ui->label_clnd_title->setGeometry((ui->widget_clnd_client->width() - ui->label_clnd_title->width())/2,
                                       g_appInfoPtr->m_fRate*30, ui->label_clnd_title->width(), ui->label_clnd_title->height());
@@ -38,8 +48,8 @@ void clNoticeDetail::setUI(const int nLeft, const int nTop, const int nWidth, co
     ui->label_clnd_time->setGeometry((ui->widget_clnd_client->width() - ui->label_clnd_time->width())/2,
                                      ui->label_clnd_title->y() + ui->label_clnd_title->height() + g_appInfoPtr->m_fRate*10, ui->label_clnd_time->width(), ui->label_clnd_time->height());
     ui->label_HLine->setGeometry(g_appInfoPtr->m_fRate*30, ui->label_clnd_time->y() + ui->label_clnd_time->height() + g_appInfoPtr->m_fRate*30,
-                                 ui->widget_clnd_client->width() - g_appInfoPtr->m_fRate*30*2, g_appInfoPtr->m_fRate*1);
-	ui->txtb_noticeContent->insertHtml(m_ni.sContent);
+                                 ui->widget_clnd_client->width() - g_appInfoPtr->m_fRate*30*2, g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
+	
 	ui->txtb_noticeContent->setGeometry(ui->label_HLine->x(), ui->label_HLine->y() + ui->label_HLine->height() + g_appInfoPtr->m_fRate*16,
                                         ui->label_HLine->width(), ui->widget_clnd_client->height() - ui->label_HLine->y() - g_appInfoPtr->m_fRate*(16 + 16 + 1));
 }

+ 134 - 13
client/clNoticeList.cpp

@@ -1,14 +1,15 @@
 #include "clNoticeList.h"
 #include "ui_clNoticeList.h"
 
+#include <QLabel>
+#include <QScrollBar>
+#include <QHBoxLayout>
 
 #include "CAppInfo.h"
 #include "clOperation.h"
 #include "CheckHeaderView.h"
-#include <QLabel>
-#include <QScrollBar>
-#include <QHBoxLayout>
 #include "logproc.h"
+#include "awMsgBox.h"
 
 clNoticeList::clNoticeList(QWidget *parent) :
     QWidget(parent),
@@ -17,6 +18,10 @@ clNoticeList::clNoticeList(QWidget *parent) :
     ui->setupUi(this);
 
     setStyleSheet(g_appInfoPtr->m_sQssStr);
+
+    qRegisterMetaType<CUpdateNoticeReadStatus>("CUpdateNoticeReadStatus");
+    connect(g_httpBllPtr.get(), &CHttpBll::sgnUpdateNoticeReadStatus, this, &clNoticeList::onUpdateNoticeReadStatus);
+
 }
 
 clNoticeList::~clNoticeList()
@@ -122,7 +127,7 @@ void clNoticeList::setNotice(std::vector<CNoticeInfo> vNL)
                                       .arg((int)(g_appInfoPtr->m_fRate*14))
                                       .arg((int)(g_appInfoPtr->m_fRate*14))
                                       .arg((int)(g_appInfoPtr->m_fRate*14))
-                                      .arg((int)(g_appInfoPtr->m_fRate*1))
+                                      .arg((int)(g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1))
                                       .arg((int)(g_appInfoPtr->m_fRate*3));
 	vNoticeList.clear();
 	vNoticeList.assign(vNL.begin(), vNL.end());
@@ -138,6 +143,7 @@ void clNoticeList::setNotice(std::vector<CNoticeInfo> vNL)
 		QHBoxLayout *layout = new QHBoxLayout;
 		widget->setLayout(layout);
 		QLabel *icon = new QLabel;
+		icon->setObjectName("icon");
 		icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 16, g_appInfoPtr->m_fRate * 12));
 		QString sIconFile = "";
 		if (ni.bHasRead)
@@ -152,7 +158,14 @@ void clNoticeList::setNotice(std::vector<CNoticeInfo> vNL)
 		layout->addWidget(icon);
 		QMyLabel *text = new QMyLabel(i);
 		connect(text, &QMyLabel::sgnClick, this, &clNoticeList::on_tablewt_examList_cellClicked);
-		text->setText(ni.sContent);
+		if (ni.bHasRecalled)
+		{
+			text->setText(QString::fromLocal8Bit("发送者已撤回消息:%1").arg(ni.sTitle));
+		}
+		else
+		{
+			text->setText(ni.sTitle);
+		}
 		text->setFixedWidth(900);
 		text->setStyleSheet(QString(R"(QLabel
                                     {
@@ -178,16 +191,124 @@ void clNoticeList::on_tablewt_examList_cellClicked(int row, int column)
 		return;
 	}
 
-	if (m_pNoticeDetail == nullptr)
+    showNotice(vNoticeList[row]);
+}
+
+
+void clNoticeList::viewNotice(__int64 nId)
+{
+    for(CNoticeInfo ni : vNoticeList)
+    {
+        if(ni.nId == nId)
+        {
+            showNotice(ni);
+        }
+    }
+}
+
+void clNoticeList::showNotice(CNoticeInfo ni)
+{
+    if (m_pNoticeDetail == nullptr)
+    {
+        m_pNoticeDetail = std::make_shared<clNoticeDetail>(ni, this);
+        m_pNoticeDetail->setUI(0, 0, width(), height());
+        connect(m_pNoticeDetail.get(), &clNoticeDetail::gobackNoticeList, this, [&]() {
+            if (m_pNoticeDetail != nullptr)
+            {
+                m_pNoticeDetail.reset();
+            }
+        });
+    }
+    m_pNoticeDetail->show();
+    setNoticeRead(QString::number(ni.nId));
+}
+
+//noticeId  通知ID,多个以逗号分隔
+void clNoticeList::setNoticeRead(QString sIds)
+{
+    CHttpRequestPackage hrp;
+    hrp.sUri = "/api/ecs_exam_work/notice/updateNoticeReadStatus";
+    hrp.sParamList.push_back(QString("noticeId,%1").arg(sIds));
+    hrp.nRequestType = RequestType::rtUpdateNoticeReadStatus;
+    hrp.sCommonStr = sIds;
+    g_httpBllPtr->post(hrp);
+
+}
+
+void clNoticeList::on_btn_setRead_clicked()
+{
+    QStringList list;
+    for(int i = 0; i < ui->tablewt_examList->rowCount(); i++)
+    {
+        QCheckBox* chk = dynamic_cast<QCheckBox*>(ui->tablewt_examList->cellWidget(i, 0));
+        if(chk && chk->isChecked())
+        {
+            list<<QString::number(vNoticeList[i].nId);
+        }
+    }
+
+    if(list.count() > 0)
+    {
+        QString sIds = list.join(",");
+        setNoticeRead(sIds);
+    }
+}
+
+void clNoticeList::onUpdateNoticeReadStatus(CUpdateNoticeReadStatus unrs)
+{
+    if(unrs.nCode == 200)
+    {
+        QStringList list = unrs.sIds.split(",");
+        for(QString sId : list)
+        {
+            updateReadStatus(sId.toLongLong());
+        }
+		int unReadCount = 0;
+		int nSize = vNoticeList.size();
+		for (int i = 0; i < nSize; ++i)
+		{
+			CNoticeInfo &ni = vNoticeList[i];
+			if(!ni.bHasRead)
+			{
+				++unReadCount;
+			 }
+		}
+		emit unReadNoticeCount(unReadCount);
+    }
+    else
+    {
+        if(unrs.sMessage.isEmpty())
+        {
+            ShowMsg(QString::fromLocal8Bit("更新通知状态失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
+        }
+        else
+        {
+            ShowMsg(unrs.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
+        }
+    }
+}
+
+void clNoticeList::updateReadStatus(__int64 nId)
+{
+	int nSize = vNoticeList.size();
+	for (int i = 0; i < nSize; ++i)
 	{
-		m_pNoticeDetail = std::make_shared<clNoticeDetail>(vNoticeList[row], this);
-		m_pNoticeDetail->setUI(0, 0, width(), height());
-		connect(m_pNoticeDetail.get(), &clNoticeDetail::gobackNoticeList, this, [&]() {
-			if (m_pNoticeDetail != nullptr)
+		CNoticeInfo &ni = vNoticeList[i];
+		if (ni.nId == nId)
+		{
+			ni.bHasRead = true;
+			QString sIconFile = ":/images/icon-notice-read.png";
+			QWidget *widget = ui->tablewt_examList->cellWidget(i, 1);
+			if (widget)
 			{
-				m_pNoticeDetail.reset();
+				QLabel *icon = widget->findChild<QLabel*>("icon");
+				if (icon)
+				{
+					icon->setPixmap(QPixmap(sIconFile).scaled(g_appInfoPtr->m_fRate * 16, g_appInfoPtr->m_fRate * 12));
+				}
 			}
-		});
+		}
 	}
-	m_pNoticeDetail->show();
+
+	emit readNotice(nId);
 }

+ 13 - 0
client/clNoticeList.h

@@ -20,6 +20,7 @@ private:
 	void mouseReleaseEvent(QMouseEvent *ev) {emit sgnClick(m_nRow, 1); }
 	int m_nRow;
 };
+
 class clNoticeList : public QWidget
 {
     Q_OBJECT
@@ -31,14 +32,26 @@ public:
     void setUI(const int nWidth, const int nHeight);
 
 	void setNotice(std::vector<CNoticeInfo> vNL);
+    void viewNotice(__int64 nId);
+signals:
+	void unReadNoticeCount(int nCount);
+	void readNotice(__int64 nId);
 private slots:
     void on_tablewt_examList_cellClicked(int row, int column);
 
+    void on_btn_setRead_clicked();
+
+    void onUpdateNoticeReadStatus(CUpdateNoticeReadStatus unrs);
 private:
     Ui::clNoticeList *ui;
 
 	std::vector<CNoticeInfo> vNoticeList;
 	std::shared_ptr<clNoticeDetail> m_pNoticeDetail;
+
+    void showNotice(CNoticeInfo ni);
+    void setNoticeRead(QString sIds);
+    void updateReadStatus(__int64 nId);
 };
 
+
 #endif // CLNOTICELIST_H

+ 69 - 1
client/clNoticePopWidget.cpp

@@ -46,10 +46,78 @@ void clNoticePopWidget::setUI(const int nLeft, const int nTop, const int nWidth,
 
 void clNoticePopWidget::addNotice(const CNoticeInfo ni)
 {
+	std::vector<CNoticeInfo>::iterator it = 
+		std::find_if(vIgnoreNL.begin(), vIgnoreNL.end(), [&ni](const CNoticeInfo &temp) {
+			return temp.nId == ni.nId;
+		});
+	if (it != vIgnoreNL.end())
+	{
+		return;
+	}
+
 	vNL.push_back(ni);
+	if (vNL.size() == 1)
+	{
+		showNotice(ni);
+	}	
+}
+
+void clNoticePopWidget::setNoticeRead(__int64 nId)
+{
+	std::vector<CNoticeInfo>::iterator it =
+		std::find_if(vNL.begin(), vNL.end(), [&nId](const CNoticeInfo &temp) {
+		return temp.nId == nId;
+	});
+
+	if (it != vNL.end())
+	{
+		vNL.erase(it);
+		if (vNL.size() <= 0)
+		{
+			hide();
+		}
+		else
+		{
+			showNotice(*vNL.begin());
+		}
+		
+	}
+}
+
+void clNoticePopWidget::showNotice(const CNoticeInfo ni)
+{
+	if (ni.bHasRecalled)
+	{
+		ui->label_noticeTitle->setText(QString::fromLocal8Bit("发送者已撤回消息:%1").arg(ni.sTitle));
+		ui->label_noticeTitle->adjustSize();
+		ui->txtb_noticeContent->insertPlainText(QString::fromLocal8Bit("该消息已经被发送者撤回。"));
+	}
+	else
+	{
+		ui->label_noticeTitle->setText(ni.sTitle);
+		ui->label_noticeTitle->adjustSize();
+		ui->txtb_noticeContent->insertHtml(ni.sContent);
+	}
+	
 }
 
 int clNoticePopWidget::noticeCount()
 {
 	return vNL.size();
-}
+}
+
+void clNoticePopWidget::on_btn_detail_clicked()
+{
+    emit showNoticeDetail(vNL.begin()->nId);
+    on_btn_ignore_clicked();
+}
+
+void clNoticePopWidget::on_btn_ignore_clicked()
+{
+	vIgnoreNL.push_back(*vNL.begin());
+    vNL.erase(vNL.begin());
+    if(vNL.size() <= 0)
+    {
+        hide();
+    }
+}

+ 13 - 1
client/clNoticePopWidget.h

@@ -20,9 +20,21 @@ public:
 
 	void addNotice(const CNoticeInfo ni);
 	int noticeCount();
+
+	void setNoticeRead(__int64 nId);
+signals:
+    void showNoticeDetail(__int64 nId);
+private slots:
+    void on_btn_detail_clicked();
+
+    void on_btn_ignore_clicked();
+
 private:
+	void showNotice(const CNoticeInfo ni);
+
     Ui::clNoticePopWidget *ui;
-	std::vector<CNoticeInfo> vNL;
+	std::vector<CNoticeInfo> vNL;	
+	std::vector<CNoticeInfo> vIgnoreNL;
 };
 
 #endif // CLNOTICEPOPWIDGET_H

+ 10 - 3
client/clObjectiveScore.cpp

@@ -32,6 +32,7 @@ clObjectiveScore::clObjectiveScore(int nRow, QString sExamStudentId, QWidget *pa
 
 clObjectiveScore::~clObjectiveScore()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -41,7 +42,7 @@ void clObjectiveScore::setUI(const int nLeft, const int nTop, const int nWidth,
     ui->widget_objevtiveScore->setGeometry(g_appInfoPtr->m_fRate*5, g_appInfoPtr->m_fRate*5, width()-g_appInfoPtr->m_fRate*10, height()-g_appInfoPtr->m_fRate*10);
 
     ui->lw_objectiveScore->setGeometry(g_appInfoPtr->m_fRate*20, g_appInfoPtr->m_fRate*10,
-                                       ui->widget_objevtiveScore->width() - g_appInfoPtr->m_fRate*20*2,
+                                       ui->widget_objevtiveScore->width() - g_appInfoPtr->m_fRate*20,
                                        ui->widget_objevtiveScore->height() - g_appInfoPtr->m_fRate*10*2);
    /*
 	QListWidgetItem *pItem = new QListWidgetItem;
@@ -145,8 +146,13 @@ void clObjectiveScore::onQueryObjectiveScoreList(CQueryObjectiveScoreList queryO
             ui->lw_objectiveScore->setItemWidget(pItem, w);
 
             int nSzie = queryObjectiveScoreList.vObjectiveScore.size();
+
             for (int i = nSzie - 1; i >= 0; --i)
             {
+                if(i == nSzie - 11)
+                {
+                    break;
+                }
                 CObjectiveScore os = queryObjectiveScoreList.vObjectiveScore[i];
                 QListWidgetItem *pItem = new QListWidgetItem;
                 QSize size = pItem->sizeHint();
@@ -209,6 +215,7 @@ void clObjectiveScore::onQueryObjectiveScoreList(CQueryObjectiveScoreList queryO
                 w->setLayout(l);
                 ui->lw_objectiveScore->setItemWidget(pItem, w);
             }
+			nSzie = nSzie > 10 ? 10 : nSzie;
             int nHeight = g_appInfoPtr->m_fRate * 46*(nSzie+1) + g_appInfoPtr->m_fRate * (10 + 5) + g_appInfoPtr->m_fRate * (20 + 5);
             emit objectiveScoreHeight(m_nRow, nHeight);
         }
@@ -217,11 +224,11 @@ void clObjectiveScore::onQueryObjectiveScoreList(CQueryObjectiveScoreList queryO
 	{
         if(queryObjectiveScoreList.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取客观题成绩失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取客观题成绩失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(queryObjectiveScoreList.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(queryObjectiveScoreList.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }

+ 122 - 92
client/clOfflineExam.cpp

@@ -95,15 +95,20 @@ void clOfflineExam::setUI(const int nWidth, const int nHeight)
 
 void clOfflineExam::showEvent(QShowEvent *)
 {
-	CHttpRequestPackage hrp1;
-	hrp1.sUri = "/api/ecs_core/org/getAnswersUrl/" + g_appInfoPtr->m_sRootOrgId;
-	hrp1.nRequestType = RequestType::rtGetOffLineExamAnswerSheet;
-	g_httpBllPtr->get(hrp1);
-
-	CHttpRequestPackage hrp;
-	hrp.sUri = "/api/ecs_oe_admin/client/exam/process/getOfflineCourse";
-	hrp.nRequestType = RequestType::rtGetOfflineCourse;
-	g_httpBllPtr->post(hrp);	
+    refreshCourse();
+}
+
+void clOfflineExam::refreshCourse()
+{
+    CHttpRequestPackage hrp1;
+    hrp1.sUri = "/api/ecs_core/org/getAnswersUrl/" + g_appInfoPtr->m_sRootOrgId;
+    hrp1.nRequestType = RequestType::rtGetOffLineExamAnswerSheet;
+    g_httpBllPtr->get(hrp1);
+
+    CHttpRequestPackage hrp;
+    hrp.sUri = "/api/ecs_oe_admin/client/exam/process/getOfflineCourse";
+    hrp.nRequestType = RequestType::rtGetOfflineCourse;
+    g_httpBllPtr->post(hrp);
 }
 
 void clOfflineExam::onGetOfflineCourse(CGetOfflineCourse getOfflineCourse)
@@ -111,6 +116,7 @@ void clOfflineExam::onGetOfflineCourse(CGetOfflineCourse getOfflineCourse)
 	if (getOfflineCourse.nCode == 200)
 	{
 		int nSize = getOfflineCourse.vOfflineCourseInfo.size();
+        ui->tablewt_examList->setRowCount(nSize);
 		for (int i = 0; i < nSize; ++i)
 		{
 			COfflineCourseInfo oci = getOfflineCourse.vOfflineCourseInfo[i];
@@ -144,7 +150,7 @@ void clOfflineExam::onGetOfflineCourse(CGetOfflineCourse getOfflineCourse)
                     sExportDir += "/";
                 }
 
-                COfflineCourseInfo oci = m_vOfflineCourseInfo[nRow];
+                COfflineCourseInfo &oci = m_vOfflineCourseInfo[nRow];
                 for (COfflineFileInfo &fileInfo : oci.vOfflineFileInfo)
                 {
                     QString sFileName = fileInfo.sOfflineFileUrl.right(fileInfo.sOfflineFileUrl.length() - fileInfo.sOfflineFileUrl.lastIndexOf("/") - 1);
@@ -152,10 +158,11 @@ void clOfflineExam::onGetOfflineCourse(CGetOfflineCourse getOfflineCourse)
                     fileInfo.sLocalFileName = sAnswerFile;
                     CHttpRequestPackage hrp;
                     hrp.sUri = fileInfo.sOfflineFileUrl;
-                    hrp.sCommonStr = QString("%1,%2").arg(sAnswerFile).arg(QString::number(nRow));
+                    hrp.sCommonStr = sAnswerFile;
+                    hrp.sAdditionStr = QString::number(nRow);
                     hrp.sCommonStr1 = __FILE__;
                     hrp.nRequestType = RequestType::rtDownLoadFile;
-
+                    hrp.nRetryCount = 3;
                     g_httpBllPtr->downLoad(hrp);
                 }
             });
@@ -190,22 +197,33 @@ void clOfflineExam::onGetOfflineCourse(CGetOfflineCourse getOfflineCourse)
             connect(clo, &clOperation::downloadOfflinePaper, this, [&](int nRow){
                 COfflineCourseInfo oci = m_vOfflineCourseInfo[nRow];
 								
-				QString sExportDir = QFileDialog::getExistingDirectory(
-                    this, QString::fromLocal8Bit("选择下载目录"),
-					"/");
+//				QString sExportDir = QFileDialog::getExistingDirectory(
+//                    this, QString::fromLocal8Bit("选择下载目录"),
+//					"/");
 
-				if (sExportDir.isEmpty())
-				{
-					return;
-				}
+//				if (sExportDir.isEmpty())
+//				{
+//					return;
+//				}
 
-				//导出
-				if (sExportDir.right(1) != "/")
-				{
-					sExportDir += "/";
-				}
+//				//导出
+//				if (sExportDir.right(1) != "/")
+//				{
+//					sExportDir += "/";
+//				}
+
+                QString sPaperFile = oci.sCourseCode + "_" + oci.sCourseName + ".zip";
+
+                sPaperFile = QFileDialog::getSaveFileName(
+                        this, oci.sPaperId, sPaperFile,
+                        "All Files(*.*)");
+
+                if(sPaperFile.isEmpty())
+                {
+                    return;
+                }
 				
-				QString sPaperFile = sExportDir + oci.sCourseCode + "_" + oci.sCourseName + ".zip";
+
 				if (!QFile::exists(sPaperFile))
 				{
 					CHttpRequestPackage hrp;
@@ -237,11 +255,11 @@ void clOfflineExam::onGetOfflineCourse(CGetOfflineCourse getOfflineCourse)
 	{
         if(getOfflineCourse.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取离线考试信息失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取离线考试信息失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getOfflineCourse.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getOfflineCourse.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -249,40 +267,36 @@ void clOfflineExam::onGetOfflineCourse(CGetOfflineCourse getOfflineCourse)
 void clOfflineExam::onDownLoadFile(CDownLoadFileInfo downLoadFileInfo)
 {
     if (downLoadFileInfo.sModuleName == __FILE__)
-    {
-        QStringList list = downLoadFileInfo.sFileName.split(",");
-        if(list.count() == 2)
+    {                        
+        if(downLoadFileInfo.nCode == 200)
         {
-            if(downLoadFileInfo.nCode == 200)
+            if(downLoadFileInfo.sAdditionStr == "downloadAnswerSheet")
             {
-                if(list[1] == "downloadAnswerSheet")
-                {
-                    ShowMsg(QString::fromLocal8Bit("下载完成"), (QWidget*)(this->parent()));
-                }
-                else
-                {
-                    int nRow = list[1].toInt();
-                    COfflineCourseInfo oci = m_vOfflineCourseInfo[nRow];
-                    QString sLocalFileName = oci.vOfflineFileInfo[oci.vOfflineFileInfo.size() - 1].sLocalFileName;
-
-                    if(sLocalFileName == list[0])
-                    {
-                        ShowMsg(QString::fromLocal8Bit("下载完成"), (QWidget*)(this->parent()));
-                    }
-                }
+                ShowMsg(QString::fromLocal8Bit("下载完成"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_succeed);
             }
             else
             {
-                if(downLoadFileInfo.sMessage.isEmpty())
-                {
-                    ShowMsg(QString::fromLocal8Bit("下载失败"), g_appInfoPtr->m_pAnsBgWidget);
-                }
-                else
+                int nRow = downLoadFileInfo.sAdditionStr.toInt();
+                COfflineCourseInfo oci = m_vOfflineCourseInfo[nRow];
+                QString sLocalFileName = oci.vOfflineFileInfo[oci.vOfflineFileInfo.size() - 1].sLocalFileName;
+
+                if(sLocalFileName == downLoadFileInfo.sFileName)
                 {
-                    ShowMsg(downLoadFileInfo.sMessage, this);
+                    ShowMsg(QString::fromLocal8Bit("下载完成"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_succeed);
                 }
             }
         }
+        else
+        {
+            if(downLoadFileInfo.sMessage.isEmpty())
+            {
+                ShowMsg(QString::fromLocal8Bit("下载失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
+            }
+            else
+            {
+                ShowMsg(downLoadFileInfo.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
+            }
+        }
     }
 
 //    downLoadFileInfo
@@ -320,11 +334,11 @@ void clOfflineExam::onGetOffLineExamAnswerSheet(CGetOffLineExamAnswerSheet getOf
 	{
         if(getOffLineExamAnswerSheet.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取离线考试信息失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取离线考试信息失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getOffLineExamAnswerSheet.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getOffLineExamAnswerSheet.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -333,31 +347,32 @@ void clOfflineExam::onStartOfflineExam(CStartOfflineExam startOfflineExam)
 {
 	if (startOfflineExam.nCode == 200)
 	{
-		int nRow = startOfflineExam.nRow;
-		COfflineCourseInfo &oci = m_vOfflineCourseInfo[nRow];
-		oci.sStatus = "EXAM_ING";
+        refreshCourse();
+//		int nRow = startOfflineExam.nRow;
+//		COfflineCourseInfo &oci = m_vOfflineCourseInfo[nRow];
+//		oci.sStatus = "EXAM_ING";
 		
-		clOperation *op = dynamic_cast<clOperation*>(ui->tablewt_examList->cellWidget(nRow, 4));
-		if (op)
-		{
-			op->setOperationType(CL_OPERATION_TYPE::cot_offline_exam);
-		}
-
-		CHttpRequestPackage hrp;
-		hrp.sUri = QString("/api/branch_ecs_ques/paper/export/%1/PAPER/offLine").arg(oci.sPaperId);
-		hrp.nRequestType = RequestType::rtGetOffLineExamPaper;
-		hrp.sCommonStr = QString::number(nRow);
-		g_httpBllPtr->get(hrp);
+//		clOperation *op = dynamic_cast<clOperation*>(ui->tablewt_examList->cellWidget(nRow, 4));
+//		if (op)
+//		{
+//			op->setOperationType(CL_OPERATION_TYPE::cot_offline_exam);
+//		}
+
+//		CHttpRequestPackage hrp;
+//		hrp.sUri = QString("/api/branch_ecs_ques/paper/export/%1/PAPER/offLine").arg(oci.sPaperId);
+//		hrp.nRequestType = RequestType::rtGetOffLineExamPaper;
+//		hrp.sCommonStr = QString::number(nRow);
+//		g_httpBllPtr->get(hrp);
 	}
 	else
 	{
         if(startOfflineExam.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("离线考试开考失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("离线考试开考失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(startOfflineExam.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(startOfflineExam.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -366,17 +381,17 @@ void clOfflineExam::onGetOffLineExamPaper(CGetOffLineExamPaper getOffLineExamPap
 {
 	if (getOffLineExamPaper.nCode == 200)
 	{
-		ShowMsg(QString::fromLocal8Bit("下载试卷完成"), this);
+        ShowMsg(QString::fromLocal8Bit("下载试卷完成"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_succeed);
 	}
 	else
 	{
         if(getOffLineExamPaper.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("离线考试下载试卷失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("离线考试下载试卷失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getOffLineExamPaper.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getOffLineExamPaper.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -385,6 +400,7 @@ void clOfflineExam::onPreviewOffLineExamPaper(CPreviewOffLineExamPaper previewOf
 {
 	if (previewOffLineExamPaper.nCode == 200)
 	{
+		int nRow = previewOffLineExamPaper.nRow;		 
 		if (m_pViewPaper == nullptr)
 		{
 			m_pViewPaper = std::make_shared<cloeViewPaper>(previewOffLineExamPaper.sUrl, (QWidget*)this->parent()->parent());
@@ -392,17 +408,22 @@ void clOfflineExam::onPreviewOffLineExamPaper(CPreviewOffLineExamPaper previewOf
                m_pViewPaper.reset();
             });
 		}
+		if(nRow >= 0 && nRow < m_vOfflineCourseInfo.size())
+		{
+			COfflineCourseInfo oci = m_vOfflineCourseInfo[nRow];
+			m_pViewPaper->setCourseCodeAndName(oci.sCourseCode, oci.sCourseName);
+		}		
 		m_pViewPaper->show();
 	}
 	else
 	{
         if(previewOffLineExamPaper.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("离线考试下载试卷失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("离线考试下载试卷失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(previewOffLineExamPaper.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(previewOffLineExamPaper.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -441,11 +462,11 @@ void clOfflineExam::onGetExamProperty(CGetExamProperty getExamProperty)
     {
         if(getExamProperty.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取离线考试信息失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取离线考试信息失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getExamProperty.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getExamProperty.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
         }
     }
 }
@@ -453,29 +474,38 @@ void clOfflineExam::onGetExamProperty(CGetExamProperty getExamProperty)
 
 void clOfflineExam::on_btn_downloadAnswerSheet_clicked()
 {
-    QString sExportDir = QFileDialog::getExistingDirectory(
-        this, QString::fromLocal8Bit("选择下载目录"),
-        "/");
+//    QString sExportDir = QFileDialog::getExistingDirectory(
+//        this, QString::fromLocal8Bit("选择下载目录"),
+//        "/");
 
-    if (sExportDir.isEmpty())
-    {
-        return;
-    }
+//    if (sExportDir.isEmpty())
+//    {
+//        return;
+//    }
 
-    //导出
-    if (sExportDir.right(1) != "/")
-    {
-        sExportDir += "/";
-    }
+//    //导出
+//    if (sExportDir.right(1) != "/")
+//    {
+//        sExportDir += "/";
+//    }
 
     QString sFileName = m_sAnswerSheetUrl.right(m_sAnswerSheetUrl.length() - m_sAnswerSheetUrl.lastIndexOf("/") - 1);
-    QString sAnswerFile = sExportDir + sFileName;
+//    QString sAnswerFile = sExportDir + sFileName;
+    QString sAnswerFile = QFileDialog::getSaveFileName(
+            this, m_sAnswerSheetUrl, QFileInfo(sFileName).fileName(),
+            "All Files(*.*)");
+
+    if(sAnswerFile.isEmpty())
+    {
+        return;
+    }
 
     CHttpRequestPackage hrp;
     hrp.sUri = m_sAnswerSheetUrl;
-    hrp.sCommonStr = QString("%1,%2").arg(sAnswerFile).arg("downloadAnswerSheet");
+    hrp.sCommonStr = sAnswerFile;
+    hrp.sAdditionStr = "downloadAnswerSheet";
     hrp.sCommonStr1 = __FILE__;
     hrp.nRequestType = RequestType::rtDownLoadFile;
-
+    hrp.nRetryCount = 3;
     g_httpBllPtr->downLoad(hrp);
 }

+ 2 - 0
client/clOfflineExam.h

@@ -33,6 +33,8 @@ private slots:
 private:
 	void showEvent(QShowEvent *);
 
+    void refreshCourse();
+
     Ui::clOfflineExam *ui;
     std::vector<COfflineCourseInfo> m_vOfflineCourseInfo;
 	std::shared_ptr<cloeViewPaper> m_pViewPaper;

+ 61 - 17
client/clOnlineExam.cpp

@@ -125,39 +125,59 @@ void clOnlineExam::setUI(const int nWidth, const int nHeight)
 
 void clOnlineExam::showEvent(QShowEvent *)
 {
-	CHttpRequestPackage hrp;
-	hrp.sUri = "/api/ecs_oe_admin/client/exam/process/queryExamList";
-	hrp.nRequestType = RequestType::rtQueryExamList;
-	g_httpBllPtr->post(hrp);
-
-	CHttpRequestPackage hrp1;
-	hrp1.sUri = "/api/ecs_oe_admin/client/exam/process/queryExamEndList";
-	hrp1.nRequestType = RequestType::rtQueryExamEndList;
-	g_httpBllPtr->post(hrp1);
+    refreshExam();
 }
 
+void clOnlineExam::refreshExam()
+{
+    CHttpRequestPackage hrp;
+    hrp.sUri = "/api/ecs_oe_admin/client/exam/process/queryExamList";
+    hrp.nRequestType = RequestType::rtQueryExamList;
+    g_httpBllPtr->post(hrp);
+
+    CHttpRequestPackage hrp1;
+    hrp1.sUri = "/api/ecs_oe_admin/client/exam/process/queryExamEndList";
+    hrp1.nRequestType = RequestType::rtQueryExamEndList;
+    g_httpBllPtr->post(hrp1);
+}
+
+
+
 void clOnlineExam::onQueryExamList(CQueryExamList queryExamList)
 {
 	if (queryExamList.nCode == 200)
 	{
 		qDebug() << "onQueryExamList";
 		int nSize = queryExamList.vOnlieExamList.size();
+        ui->tablewt_examList->setRowCount(nSize);
 		for (int i = 0; i < nSize; ++i)
 		{
-			CExamCourseInfo eci = queryExamList.vOnlieExamList[i];
+			CExamCourseInfo eci = queryExamList.vOnlieExamList[i];			
 			ui->tablewt_examList->setItem(i, 0, new QTableWidgetItem(eci.sCourseName));
 			ui->tablewt_examList->setItem(i, 1, new QTableWidgetItem(eci.sCourseLevel));
 			ui->tablewt_examList->setItem(i, 2, new QTableWidgetItem(eci.sSpecialtyName));
 			ui->tablewt_examList->setItem(i, 3, new QTableWidgetItem(QString::fromLocal8Bit("%1 ~ %2").arg(eci.sStartTime).arg(eci.sEndTime)));
-			ui->tablewt_examList->setItem(i, 4, new QTableWidgetItem(QString::fromLocal8Bit("")));
+            QString sWeekCycle = CWeekTransform::getWeekCycles(eci.vExamCycleWeek);
+            QString sTimeRange = eci.sExamCycleTimeRange.join(QString::fromLocal8Bit(","));
+			QDateTime dtCurrent = QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime());			
+			QDateTime dtStartTime = QDateTime::fromString(eci.sStartTime, "yyyy-MM-dd hh:mm:ss");
+			QDateTime dtEndTime = QDateTime::fromString(eci.sEndTime, "yyyy-MM-dd hh:mm:ss");
+			bool bIsInExamTime = dtCurrent > dtStartTime && dtCurrent < dtEndTime;
+			QString sWeek = dtCurrent.toString("ddd");
+			bool bIsInWeek = sWeekCycle.size() == 0 || sWeekCycle.indexOf(sWeek) >= 0;
+			QTime tCurrent = QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime()).time();
+			bool bIsInTimeRange = eci.sExamCycleTimeRange.size() == 0 || CWeekTransform::isInTimeRange(tCurrent, eci.sExamCycleTimeRange);
+            ui->tablewt_examList->setItem(i, 4, new QTableWidgetItem(QString::fromLocal8Bit("%1 %2").arg(sWeekCycle).arg(sTimeRange)));
 			ui->tablewt_examList->setItem(i, 5, new QTableWidgetItem(QString::number(eci.nAllowExamCount)));
 			clOperation *clo = new clOperation(i, ui->tablewt_examList);
 			clo->setUI(ui->tablewt_examList->columnWidth(6), ui->tablewt_examList->rowHeight(i), CL_OPERATION_TYPE::cot_online_exam);
+            clo->setBtn1Enable(bIsInWeek && bIsInTimeRange && bIsInExamTime && eci.nAllowExamCount>0);
 			ui->tablewt_examList->setCellWidget(i, 6, clo);
             connect(clo, &clOperation::enterExam, this, [&](CL_OPERATION_TYPE cot, int nRow){
                 if (cot == CL_OPERATION_TYPE::cot_online_exam )
                 {
-                    emit enterExam(cot, m_vOnlieExamList[nRow].nExamId, m_vOnlieExamList[nRow].nExamStudentId);
+                    emit enterExam(cot, m_vOnlieExamList[nRow].nExamId, m_vOnlieExamList[nRow].nExamStudentId,
+                                   m_vOnlieExamList[nRow].sCourseCode, m_vOnlieExamList[nRow].sCourseName);
                 }
             });
 			connect(clo, &clOperation::showObjectiveScore, this, [&](CL_OPERATION_TYPE cot, int nRow) {
@@ -177,7 +197,12 @@ void clOnlineExam::onQueryExamList(CQueryExamList queryExamList)
 						connect(m_pObjectiveScore.get(), &clObjectiveScore::objectiveScoreHeight, this, [&](int nRow, int nHeight) {
 							int ntableTop =  ui->tabw_onlineExam->y() + g_appInfoPtr->m_fRate*70;
 							int nTop = ntableTop + (nRow + 2)* ui->tablewt_examList->verticalHeader()->defaultSectionSize() - nHeight / 2;
-							if (nTop < ui->tabw_onlineExam->y())
+                            if (nHeight > ui->tabw_onlineExam->height())
+                            {
+                                nHeight = ui->tabw_onlineExam->height();
+                            }
+
+                            if (nTop < ui->tabw_onlineExam->y())
 							{
 								nTop = ui->tabw_onlineExam->y();
 							}
@@ -185,6 +210,8 @@ void clOnlineExam::onQueryExamList(CQueryExamList queryExamList)
 							{
 								nTop = ntableTop + ui->tablewt_examList->height() - nHeight;
 							}
+
+
 							m_pObjectiveScore->setUI(ui->tablewt_examList->x() + ui->tablewt_examList->width() - ui->tablewt_examList->columnWidth(6) - g_appInfoPtr->m_fRate * 530,
 								nTop, g_appInfoPtr->m_fRate * 530, nHeight);
 							m_pObjectiveScore->show();
@@ -208,11 +235,11 @@ void clOnlineExam::onQueryExamList(CQueryExamList queryExamList)
 	{
         if(queryExamList.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取线考试信息失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取线考试信息失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(queryExamList.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(queryExamList.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -222,6 +249,7 @@ void clOnlineExam::onQueryExamEndList(CQueryExamEndList queryExamEndList)
 	if (queryExamEndList.nCode == 200)
 	{
 		int nSize = queryExamEndList.vOnlieEndExamList.size();
+        ui->tablew_finishExamList->setRowCount(nSize);
 		for (int i = 0; i < nSize; ++i)
 		{
 			CExamCourseInfo eci = queryExamEndList.vOnlieEndExamList[i];
@@ -229,10 +257,14 @@ void clOnlineExam::onQueryExamEndList(CQueryExamEndList queryExamEndList)
 			ui->tablew_finishExamList->setItem(i, 1, new QTableWidgetItem(eci.sCourseLevel));
 			ui->tablew_finishExamList->setItem(i, 2, new QTableWidgetItem(eci.sSpecialtyName));
 			ui->tablew_finishExamList->setItem(i, 3, new QTableWidgetItem(QString::fromLocal8Bit("%1 ~ %2").arg(eci.sStartTime).arg(eci.sEndTime)));
-			ui->tablew_finishExamList->setItem(i, 4, new QTableWidgetItem(QString::fromLocal8Bit("")));
+			QString sWeekCycle = CWeekTransform::getWeekCycles(eci.vExamCycleWeek);
+			QString sTimeRange = eci.sExamCycleTimeRange.join(",");
+			
+			ui->tablew_finishExamList->setItem(i, 4, new QTableWidgetItem(QString::fromLocal8Bit("%1 %2").arg(sWeekCycle).arg(sTimeRange)));
 			ui->tablew_finishExamList->setItem(i, 5, new QTableWidgetItem(QString::number(eci.nAllowExamCount)));
 			clOperation *clo = new clOperation(i, ui->tablew_finishExamList);
 			clo->setUI(ui->tablew_finishExamList->columnWidth(6), ui->tablew_finishExamList->rowHeight(i), CL_OPERATION_TYPE::cot_online_finish_exam);
+			
 			ui->tablew_finishExamList->setCellWidget(i, 6, clo);
 
             connect(clo, &clOperation::showObjectiveScore, this, [&](CL_OPERATION_TYPE cot, int nRow) {
@@ -252,6 +284,11 @@ void clOnlineExam::onQueryExamEndList(CQueryExamEndList queryExamEndList)
                         connect(m_pObjectiveScore.get(), &clObjectiveScore::objectiveScoreHeight, this, [&](int nRow, int nHeight) {
                             int ntableTop =  ui->tablew_finishExamList->y() + g_appInfoPtr->m_fRate*70;
                             int nTop = ntableTop + (nRow + 2)* ui->tablew_finishExamList->verticalHeader()->defaultSectionSize() - nHeight / 2;
+                            if (nHeight > ui->tabw_onlineExam->height())
+                            {
+                                nHeight = ui->tabw_onlineExam->height();
+                            }
+
                             if (nTop < ui->tablew_finishExamList->y())
                             {
                                 nTop = ui->tablew_finishExamList->y();
@@ -281,6 +318,13 @@ void clOnlineExam::onQueryExamEndList(CQueryExamEndList queryExamEndList)
 	}
 	else
 	{
-	
+        if(queryExamEndList.sMessage.isEmpty())
+        {
+            ShowMsg(QString::fromLocal8Bit("获取在线考试信息失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
+        }
+        else
+        {
+            ShowMsg(queryExamEndList.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
+        }
 	}
 }

+ 8 - 1
client/clOnlineExam.h

@@ -7,6 +7,7 @@
 #include "CHttpBll.h"
 #include "clOperation.h"
 
+
 namespace Ui {
 class clOnlineExam;
 }
@@ -15,24 +16,30 @@ class clOnlineExam : public QWidget
 {
     Q_OBJECT
 signals:
-    void enterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId);
+    void enterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId,
+                   QString sCourseCode, QString sCourseName);
 public:
     explicit clOnlineExam(QWidget *parent = nullptr);
     ~clOnlineExam();
 
     void setUI(const int nWidth, const int nHeight);
+    void refreshExam();
 private slots:
 	void onQueryExamList(CQueryExamList queryExamList);
 	void onQueryExamEndList(CQueryExamEndList queryExamEndList);	
 private:
 	void showEvent(QShowEvent *event);
 
+    QString getWeekCycles(std::vector<int> vWeek);
+
     Ui::clOnlineExam *ui;
 
 	std::shared_ptr<QTimer> m_pObjectiveScoreTimer;
     std::shared_ptr<clObjectiveScore> m_pObjectiveScore;
 	std::vector<CExamCourseInfo> m_vOnlieExamList;
     std::vector<CExamCourseInfo> m_vOnlieEndExamList;
+
+    
 };
 
 #endif // CLONLINEEXAM_H

+ 34 - 9
client/clOnlineHomework.cpp

@@ -3,9 +3,11 @@
 
 #include "CAppInfo.h"
 #include "clOperation.h"
+#include "awMsgBox.h"
 
 #include <QScrollBar>
-#include "awMsgBox.h"
+#include <QDateTime>
+
 
 clOnlineHomework::clOnlineHomework(QWidget *parent) :
     QWidget(parent),
@@ -125,18 +127,23 @@ void clOnlineHomework::setUI(const int nWidth, const int nHeight)
 
 void clOnlineHomework::showEvent(QShowEvent *)
 {
-	CHttpRequestPackage hrp;
-	hrp.sUri = "/api/ecs_oe_admin/client/exam/process/queryHomeworkList";
-	hrp.nRequestType = RequestType::rtQueryHomeworkList;
-	g_httpBllPtr->post(hrp);
+    refreshExam();
 }
 
+void clOnlineHomework::refreshExam()
+{
+    CHttpRequestPackage hrp;
+    hrp.sUri = "/api/ecs_oe_admin/client/exam/process/queryHomeworkList";
+    hrp.nRequestType = RequestType::rtQueryHomeworkList;
+    g_httpBllPtr->post(hrp);
+}
 
 void clOnlineHomework::onQueryHomeworkList(CQueryHomeworkList queryHomeworkList)
 {
 	if (queryHomeworkList.nCode == 200)
 	{
 		int nSize = queryHomeworkList.vHomeworkExamList.size();
+        ui->tablewt_examList->setRowCount(nSize);
 		for (int i = 0; i < nSize; ++i)
 		{
 			CExamCourseInfo eci = queryHomeworkList.vHomeworkExamList[i];
@@ -144,13 +151,25 @@ void clOnlineHomework::onQueryHomeworkList(CQueryHomeworkList queryHomeworkList)
 			ui->tablewt_examList->setItem(i, 1, new QTableWidgetItem(eci.sCourseLevel));
 			ui->tablewt_examList->setItem(i, 2, new QTableWidgetItem(eci.sSpecialtyName));
 			ui->tablewt_examList->setItem(i, 3, new QTableWidgetItem(QString::fromLocal8Bit("%1 ~ %2").arg(eci.sStartTime).arg(eci.sEndTime)));
-			ui->tablewt_examList->setItem(i, 4, new QTableWidgetItem(QString::fromLocal8Bit("")));
+			QString sWeekCycle = CWeekTransform::getWeekCycles(eci.vExamCycleWeek);
+			QString sTimeRange = eci.sExamCycleTimeRange.join(",");
+			QDateTime dtCurrent = QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime());
+			QDateTime dtStartTime = QDateTime::fromString(eci.sStartTime, "yyyy-MM-dd hh:mm:ss");
+			QDateTime dtEndTime = QDateTime::fromString(eci.sEndTime, "yyyy-MM-dd hh:mm:ss");
+			bool bIsInExamTime = dtCurrent > dtStartTime && dtCurrent < dtEndTime;
+			QString sWeek = dtCurrent.toString("ddd");
+			bool bIsInWeek = sWeekCycle.size() == 0 || sWeekCycle.indexOf(sWeek) >= 0;
+			QTime tCurrent = QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime()).time();
+			bool bIsInTimeRange = eci.sExamCycleTimeRange.size() == 0 || CWeekTransform::isInTimeRange(tCurrent, eci.sExamCycleTimeRange);
+			ui->tablewt_examList->setItem(i, 4, new QTableWidgetItem(QString::fromLocal8Bit("%1 %2").arg(sWeekCycle).arg(sTimeRange)));
 			ui->tablewt_examList->setItem(i, 5, new QTableWidgetItem(QString::number(eci.nAllowExamCount)));
 			clOperation *clo = new clOperation(i, ui->tablewt_examList);
+			
             connect(clo, &clOperation::enterExam, this, [&](CL_OPERATION_TYPE cot, int nRow){
                 if (cot == CL_OPERATION_TYPE::cot_online_homework )
                 {
-                    emit enterExam(cot, m_vHomeworkExamList[nRow].nExamId, m_vHomeworkExamList[nRow].nExamStudentId);
+                    emit enterExam(cot, m_vHomeworkExamList[nRow].nExamId, m_vHomeworkExamList[nRow].nExamStudentId,
+                                   m_vHomeworkExamList[nRow].sCourseCode, m_vHomeworkExamList[nRow].sCourseName);
                 }
             });
 
@@ -171,6 +190,11 @@ void clOnlineHomework::onQueryHomeworkList(CQueryHomeworkList queryHomeworkList)
                         connect(m_pObjectiveScore.get(), &clObjectiveScore::objectiveScoreHeight, this, [&](int nRow, int nHeight) {
                             int ntableTop =  ui->tabw_onlineExam->y() + g_appInfoPtr->m_fRate*70;
                             int nTop = ntableTop + (nRow + 2)* ui->tablewt_examList->verticalHeader()->defaultSectionSize() - nHeight / 2;
+                            if (nHeight > ui->tabw_onlineExam->height())
+                            {
+                                nHeight = ui->tabw_onlineExam->height();
+                            }
+
                             if (nTop < ui->tabw_onlineExam->y())
                             {
                                 nTop = ui->tabw_onlineExam->y();
@@ -197,6 +221,7 @@ void clOnlineHomework::onQueryHomeworkList(CQueryHomeworkList queryHomeworkList)
             });
 
 			clo->setUI(ui->tablewt_examList->columnWidth(6), ui->tablewt_examList->rowHeight(i), CL_OPERATION_TYPE::cot_online_homework);
+			clo->setBtn1Enable(bIsInWeek && bIsInTimeRange && bIsInExamTime);
 			ui->tablewt_examList->setCellWidget(i, 6, clo);
 		}
         m_vHomeworkExamList.swap(queryHomeworkList.vHomeworkExamList);
@@ -205,11 +230,11 @@ void clOnlineHomework::onQueryHomeworkList(CQueryHomeworkList queryHomeworkList)
 	{
         if(queryHomeworkList.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取在线作业列表失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取在线作业列表失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(queryHomeworkList.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(queryHomeworkList.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }

+ 4 - 3
client/clOnlineHomework.h

@@ -15,17 +15,18 @@ class clOnlineHomework : public QWidget
 {
     Q_OBJECT
 signals:
-    void enterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId);
+    void enterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId,
+                   QString sCourseCode, QString sCourseName);
 public:
     explicit clOnlineHomework(QWidget *parent = nullptr);
     ~clOnlineHomework();
 
     void setUI(const int nWidth, const int nHeight);
-
+    void refreshExam();
 private slots:
 	void onQueryHomeworkList(CQueryHomeworkList queryHomeworkList);
 private:
-	void clOnlineHomework::showEvent(QShowEvent *);
+    void showEvent(QShowEvent *);
 
     Ui::clOnlineHomework *ui;
     std::vector<CExamCourseInfo> m_vHomeworkExamList;

+ 73 - 18
client/clOnlinePractice.cpp

@@ -6,7 +6,9 @@
 
 #include <QScrollBar>
 #include <QListView>
+#include <QDateTime>
 #include "awMsgBox.h"
+#include "logproc.h"
 
 clOnlinePractice::clOnlinePractice(QWidget *parent) :
     QWidget(parent),
@@ -16,6 +18,8 @@ clOnlinePractice::clOnlinePractice(QWidget *parent) :
 
     setStyleSheet(g_appInfoPtr->m_sQssStr);
 
+    m_nCurExamId = 0;
+
 	qRegisterMetaType<CQueryBatchList>("CQueryBatchList");
 	connect(g_httpBllPtr.get(), &CHttpBll::sgnQueryBatchList, this, &clOnlinePractice::onQueryBatchList);
 	qRegisterMetaType<CQueryPracticeCourseList>("CQueryPracticeCourseList");
@@ -94,27 +98,60 @@ void clOnlinePractice::onQueryBatchList(CQueryBatchList queryBatchList)
 {
 	if (queryBatchList.nCode == 200)
 	{
-		ui->cbo_batch->clear();
-		for (CBatchInfo bi : queryBatchList.vBatchList)
+        __int64 nCurExamId = m_nCurExamId;
+		ui->cbo_batch->clear();        
+        int nIndex = 0;
+        for (size_t i = 0; i < queryBatchList.vBatchList.size(); ++i)
 		{
+            CBatchInfo bi = queryBatchList.vBatchList[i];
 			ui->cbo_batch->addItem(bi.sName, bi.nId);
+
+            if(nCurExamId == bi.nId)
+            {
+                nIndex = i;
+            }
+		}
+
+		if(queryBatchList.vBatchList.size() > 0 )
+		{
+			m_nCurExamId = queryBatchList.vBatchList[nIndex].nId;
+			ui->cbo_batch->setCurrentIndex(nIndex);
+			myDebug() << "m_nCurExamId" << m_nCurExamId;
 		}
-		ui->cbo_batch->setCurrentIndex(0);
+        
 	}
 	else
 	{
         if(queryBatchList.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取批次信息失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取批次信息失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(queryBatchList.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(queryBatchList.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }
 
+void clOnlinePractice::releaseClopResult()
+{
+    if(m_pResultList)
+    {
+        m_pResultList.reset();
+    }
+}
+
 void clOnlinePractice::showEvent(QShowEvent *)
+{    	   
+	refreshExam();
+}
+
+void clOnlinePractice::refreshExamInfo()
+{
+    refreshExam();
+}
+
+void clOnlinePractice::refreshExam()
 {
 	CHttpRequestPackage hrp;
 	hrp.sUri = "/api/ecs_exam_work/exam/queryByNameLike";
@@ -128,24 +165,36 @@ void clOnlinePractice::showEvent(QShowEvent *)
 void clOnlinePractice::onQueyPracticeCourseList(CQueryPracticeCourseList queryPracticeCourseList)
 {
 	if (queryPracticeCourseList.nCode == 200)
-	{
+	{        
 		int nSize = queryPracticeCourseList.vPracticeCourseInfo.size();
+        ui->tablewt_examList->setRowCount(nSize);
 		for (int i = 0; i < nSize; ++i)
 		{
 			CPracticeCourseInfo pci = queryPracticeCourseList.vPracticeCourseInfo[i];
 			ui->tablewt_examList->setItem(i, 0, new QTableWidgetItem(pci.sCourseName));
 			ui->tablewt_examList->setItem(i, 1, new QTableWidgetItem(QString::fromLocal8Bit("%1 ~ %2").arg(pci.sStartTime).arg(pci.sEndTime)));
-			ui->tablewt_examList->setItem(i, 2, new QTableWidgetItem(QString::fromLocal8Bit("")));
+			QString sWeekCycle = CWeekTransform::getWeekCycles(pci.vExamCycleWeek);
+			QString sTimeRange = pci.sExamCycleTimeRange.join(",");
+			QDateTime dtCurrent = QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime());
+			QDateTime dtStartTime = QDateTime::fromString(pci.sStartTime, "yyyy-MM-dd hh:mm:ss");
+			QDateTime dtEndTime = QDateTime::fromString(pci.sEndTime, "yyyy-MM-dd hh:mm:ss");
+			bool bIsInExamTime = dtCurrent > dtStartTime && dtCurrent < dtEndTime;
+			QString sWeek = dtCurrent.toString("ddd");
+			bool bIsInWeek = sWeekCycle.size() == 0 || sWeekCycle.indexOf(sWeek) >= 0;
+			QTime tCurrent = QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime()).time();
+			bool bIsInTimeRange = pci.sExamCycleTimeRange.size() == 0 || CWeekTransform::isInTimeRange(tCurrent, pci.sExamCycleTimeRange);
+			ui->tablewt_examList->setItem(i, 2, new QTableWidgetItem(QString::fromLocal8Bit("%1 %2").arg(sWeekCycle).arg(sTimeRange)));
 			ui->tablewt_examList->setItem(i, 3, new QTableWidgetItem(QString::number(pci.nPracticeCount)));
 			ui->tablewt_examList->setItem(i, 4, new QTableWidgetItem(QString::fromLocal8Bit("%1%").arg(pci.fRecentObjectiveAccuracy)));
 			ui->tablewt_examList->setItem(i, 5, new QTableWidgetItem(QString::fromLocal8Bit("%1%").arg(pci.fAveObjectiveAccuracy)));
 			ui->tablewt_examList->setItem(i, 6, new QTableWidgetItem(QString::fromLocal8Bit("%1%").arg(pci.fMaxObjectiveAccuracy)));
 			clOperation *clo = new clOperation(i, ui->tablewt_examList);
-
+			
             connect(clo, &clOperation::enterExam, this, [&](CL_OPERATION_TYPE cot, int nRow){
                 if (cot == CL_OPERATION_TYPE::cot_online_practice )
                 {
-                    emit enterExam(cot, m_vPracticeCourseInfo[nRow].nExamId, m_vPracticeCourseInfo[nRow].nExamStudentId);
+                    emit enterExam(cot, m_vPracticeCourseInfo[nRow].nExamId, m_vPracticeCourseInfo[nRow].nExamStudentId,
+                                   m_vPracticeCourseInfo[nRow].sCourseCode, m_vPracticeCourseInfo[nRow].sCourseName);					
                 }
             });
             connect(clo, &clOperation::viewPracticeDetail, this, [&](int nRow){
@@ -161,6 +210,7 @@ void clOnlinePractice::onQueyPracticeCourseList(CQueryPracticeCourseList queryPr
                 m_pResultList->show();
             });
 			clo->setUI(ui->tablewt_examList->columnWidth(7), ui->tablewt_examList->rowHeight(i), CL_OPERATION_TYPE::cot_online_practice);
+			clo->setBtn1Enable(bIsInWeek && bIsInTimeRange && bIsInExamTime );
 			ui->tablewt_examList->setCellWidget(i, 7, clo);
 		}
         m_vPracticeCourseInfo.swap(queryPracticeCourseList.vPracticeCourseInfo);
@@ -169,22 +219,27 @@ void clOnlinePractice::onQueyPracticeCourseList(CQueryPracticeCourseList queryPr
 	{
         if(queryPracticeCourseList.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取练习列表失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取练习列表失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(queryPracticeCourseList.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(queryPracticeCourseList.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }
 
 void clOnlinePractice::on_cbo_batch_currentIndexChanged(int index)
 {
-    QString sExamId = ui->cbo_batch->currentData().toString();
-	CHttpRequestPackage hrp;
-	hrp.sUri = "/api/ecs_oe_admin/client/exam/process/queryPracticeCourseList";
-	hrp.sParamList.push_back(QString("examId,%1").arg(sExamId));
-	hrp.nRequestType = RequestType::rtQueryPracticeCourseList;
-	hrp.eParamType = HttpParamType::hptUrl;
-	g_httpBllPtr->post(hrp);
+    QString sExamId = ui->cbo_batch->currentData().toString();    
+    if(!sExamId.isEmpty())
+    {
+        m_nCurExamId = sExamId.toULongLong();
+		myDebug() << "on_cbo_batch_currentIndexChanged"<<sExamId;        
+        CHttpRequestPackage hrp;
+        hrp.sUri = "/api/ecs_oe_admin/client/exam/process/queryPracticeCourseList";
+        hrp.sParamList.push_back(QString("examId,%1").arg(sExamId));
+        hrp.nRequestType = RequestType::rtQueryPracticeCourseList;
+        hrp.eParamType = HttpParamType::hptUrl;
+        g_httpBllPtr->post(hrp);
+    }
 }

+ 9 - 1
client/clOnlinePractice.h

@@ -13,12 +13,15 @@ class clOnlinePractice : public QWidget
 {
     Q_OBJECT
 signals:
-    void enterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId);
+    void enterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId,
+                   QString sCourseCode, QString sCourseName);
 public:
     explicit clOnlinePractice(QWidget *parent = nullptr);
     ~clOnlinePractice();
 
     void setUI(const int nWidth, const int nHeight);
+    void releaseClopResult();
+    void refreshExamInfo();
 private slots:
 	void onQueryBatchList(CQueryBatchList queryBatchList);
 	void onQueyPracticeCourseList(CQueryPracticeCourseList queryPracticeCourseList);
@@ -26,11 +29,16 @@ private slots:
 
 private:
 	void showEvent(QShowEvent *);
+	
+	void refreshExam();
+
     Ui::clOnlinePractice *ui;
 
     std::vector<CPracticeCourseInfo> m_vPracticeCourseInfo;
 
     std::shared_ptr<clopResultList> m_pResultList;
+
+    __int64 m_nCurExamId;
 };
 
 #endif // CLONLINEPRACTICE_H

+ 1 - 1
client/clStudentInfo.cpp

@@ -57,7 +57,7 @@ void clStudentInfo::setUI(const int nLeft, const int nTop, const int nWidth, con
     ui->label_clsi_orgName->setGeometry(ui->label_clsi_studentCode->x(), ui->label_clsi_orgNameHint->y(),
                                         ui->label_clsi_orgName->width(), ui->label_clsi_orgName->height());
     ui->label_HLine->setGeometry(ui->label_clsi_studentName->x(), ui->label_clsi_orgNameHint->y() + ui->label_clsi_orgNameHint->height() + g_appInfoPtr->m_fRate*20,
-                                 ui->widget_npw_BG->width() - g_appInfoPtr->m_fRate*20*2,g_appInfoPtr->m_fRate*1);
+                                 ui->widget_npw_BG->width() - g_appInfoPtr->m_fRate*20*2,g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
     ui->btn_clsi_editPassword->setGeometry(ui->label_clsi_studentName->x(), ui->label_HLine->y() + ui->label_HLine->height() + g_appInfoPtr->m_fRate*20,
                                       g_appInfoPtr->m_fRate*135, g_appInfoPtr->m_fRate*32);
     ui->btn_clsi_exitLogin->setGeometry(ui->widget_npw_BG->width() - g_appInfoPtr->m_fRate*20 - ui->btn_clsi_editPassword->width(),

+ 25 - 3
client/client.pro

@@ -1,4 +1,4 @@
-QT       += core gui webenginewidgets multimedia
+QT       += core gui webenginewidgets multimedia websockets network
 
 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 
@@ -10,6 +10,9 @@ CONFIG += c++17
 # deprecated API in order to know how to port your code away from it.
 DEFINES += QT_DEPRECATED_WARNINGS
 
+QMAKE_CXXFLAGS_RELEASE += -O2 -GL
+QMAKE_CXXFLAGS_DEBUG += -Zi
+
 # You can also make your code fail to compile if it uses deprecated APIs.
 # In order to do so, uncomment the following line.
 # You can also select to disable deprecated APIs only up to a certain version of Qt.
@@ -23,6 +26,7 @@ SOURCES += \
     awMsgBox.cpp \
     awQuestionNavigate.cpp \
     awResumeExam.cpp \
+    awTimeLeftTips.cpp \
     awWaitExam.cpp \
     awqn_item.cpp \
     clEditPassword.cpp \
@@ -54,7 +58,8 @@ SOURCES += \
     main.cpp \
     login.cpp \
     privacyWidget.cpp \
-    welcomeWidget.cpp
+    welcomeWidget.cpp \
+    CLiveViodeProc.cpp \
 
 HEADERS += \
     answerWidget.h \
@@ -64,6 +69,7 @@ HEADERS += \
     awMsgBox.h \
     awQuestionNavigate.h \
     awResumeExam.h \
+    awTimeLeftTips.h \
     awWaitExam.h \
     awqn_item.h \
     clEditPassword.h \
@@ -94,7 +100,8 @@ HEADERS += \
     etWhetherEnvTest.h \
     login.h \
     privacyWidget.h \
-    welcomeWidget.h
+    welcomeWidget.h \
+    CLiveViodeProc.h \
 
 FORMS += \
     answerWidget.ui \
@@ -104,6 +111,7 @@ FORMS += \
     awMsgBox.ui \
     awQuestionNavigate.ui \
     awResumeExam.ui \
+    awTimeLeftTips.ui \
     awWaitExam.ui \
     awqn_item.ui \
     clEditPassword.ui \
@@ -141,15 +149,29 @@ RCC_DIR += ./tmp_client
 MOC_DIR += ./tmp_client
 OBJECTS_DIR += ./tmp_client
 
+RC_ICONS = images/coe.ico
+
 include(../common/common.pri)
 include(../component/component.pri)
 include(../face/face.pri)
 include(../question/question.pri)
 
+BREAKPAD_PATH = C:/project/qtPlugins/breakpad/src
 INCLUDEPATH += ../common/ \
     ../component/ \
     ../face/ \
     ../question/ \
+    ../3rdPart/TX_TRTC_SDK/Win32/include/  \
+    ../3rdPart/TX_TRTC_SDK/Win32/include/Live/ \
+    ../3rdPart/TX_TRTC_SDK/Win32/include/TRTC/ \
+    ../3rdPart/TX_TRTC_SDK/Win32/include/Vod/ \
+    $$BREAKPAD_PATH \
+
+LIBS +=-LC:/project/workspace/oline-exam-cloud/3rdPart/TX_TRTC_SDK/Win32/lib -lliteav \
+
+CONFIG(release, debug|release): LIBS += -L$$BREAKPAD_PATH/client/windows/Release/lib -lcommon -lcrash_generation_client -lcrash_generation_server -lexception_handler
+CONFIG(debug, debug|release): LIBS +=  -L$$BREAKPAD_PATH/client/windows/Debug/lib -lcommon -lcrash_generation_client -lcrash_generation_server -lexception_handler
+
 
 # Default rules for deployment.
 qnx: target.path = /tmp/$${TARGET}/bin

+ 63 - 16
client/client.qss

@@ -51,6 +51,7 @@ QPushButton#btn_studentCodeLogin
     color:rgba(153,153,153,1);
     background:rgba(239,240,245,1);
     border:0px;
+    text-align : center;
 }
 
 QPushButton#btn_identityLogin
@@ -62,6 +63,7 @@ QPushButton#btn_identityLogin
     color:rgba(153,153,153,1);
     background:rgba(239,240,245,1);
     border-top-right-radius:RATE_BASE_SIZE20px;
+    text-align : center;
 }
 
 QLabel#label_loginTitle
@@ -129,7 +131,7 @@ QLabel#label_pmb_title
     color:rgba(51,51,51,1);
 }
 
-QLabel#label_pmb_content
+QLabel#label_pmb_content,#label_awtlt_content
 {
     font-size:RATE_BASE_SIZE14px;
     font-family:"Microsoft YaHei";
@@ -396,6 +398,18 @@ QPushButton#btn1:hover,#btn2:hover,#btn3:hover
     color:rgba(255,255,255,1);
 }
 
+QPushButton#btn1:disabled,#btn2:disabled,#btn3:disabled
+{
+    outline:none;
+    border:1px solid rgba(231,235,241,1);
+    border-radius:RATE_BASE_SIZE6px;
+    background:rgba(244, 243, 247,1);
+    font-size:RATE_BASE_SIZE12px;
+    font-family:"Microsoft YaHei";
+    font-weight:400;
+    color:rgba(153,153,153,1);
+}
+
 QWidget#widget_op_top
 {
     background:rgba(239,240,245,1);
@@ -905,32 +919,32 @@ QToolButton#btn_etvt_canotPlay
     padding-right:RATE_BASE_SIZE20px;
 }
 
-QSlider#hslider_etvt_voice::groove:horizontal
+QSlider#hslider_etvt_voice::groove:horizontal,#hslider_etvt_volume::groove:horizontal
 {
-    height:RATE_BASE_SIZE2px;
+    height:RATE_BASE_SIZE4px;
     background:rgba(102,102,102,1);
-    border-radius:RATE_BASE_SIZE1px;
+    border-radius:RATE_BASE_SIZE2px;
 }
 
-QSlider#hslider_etvt_voice::handle:horizontal
+QSlider#hslider_etvt_voice::handle:horizontal,#hslider_etvt_volume::handle:horizontal
 {
     background:rgba(19,187,138,1);
-    border-radius:RATE_BASE_SIZE6px;
-    width:RATE_BASE_SIZE12px;
-    height:RATE_BASE_SIZE12px;
-    margin-top:-RATE_BASE_SIZE5px;
-    margin-bottom:-RATE_BASE_SIZE5px;
+    border-radius:RATE_BASE_SIZE8px;
+    width:RATE_BASE_SIZE16px;
+    height:RATE_BASE_SIZE16px;
+    margin-top:-RATE_BASE_SIZE6px;
+    margin-bottom:-RATE_BASE_SIZE6px;
     margin-left:0px;
 }
 
-QSlider#hslider_etvt_voice::sub-page:horizontal
+QSlider#hslider_etvt_voice::sub-page:horizontal,#hslider_etvt_volume::sub-page:horizontal
 {
     border-radius:RATE_BASE_SIZE1px;
     height:RATE_BASE_SIZE2px;
     background:rgba(19,187,138,1);
 }
 
-QLabel#label_etmt_status
+QLabel#label_etmt_status,#label_etvt_time
 {
     font-size:RATE_BASE_SIZE12px;
     font-family:"Microsoft YaHei";
@@ -999,6 +1013,17 @@ QPushButton#btn_fc_compare
     border-radius:RATE_BASE_SIZE10px;
 }
 
+QPushButton#btn_fc_compare:disabled
+{
+    outline:none;
+    font-size:RATE_BASE_SIZE16px;
+    font-family:"Microsoft YaHei";
+    font-weight:500;
+    color:rgba(153,153,153,1);
+    background:rgba(244, 243, 247,1);
+    border-radius:RATE_BASE_SIZE10px;
+}
+
 QPushButton#btn_fc_close
 {
     border-image:url(:/images/btn-fc-close.png);
@@ -1095,6 +1120,7 @@ QListWidget#lw_objectiveScore::item
     border:0px;
     margin-top:RATE_BASE_SIZE10px;
     min-height:RATE_BASE_SIZE48px;
+    margin-right:RATE_BASE_SIZE20px;
 }
 
 QWidget#widget_awbg_BG
@@ -1329,7 +1355,7 @@ QLabel#label_awhp_answered,#label_awhp_unanswered,#label_awhp_marked
     color:rgba(102,102,102,1);
 }
 
-QPushButton#btn_awhp_comfirm
+QPushButton#btn_awhp_comfirm,#btn_awtlt_confirm
 {
     outline:none;
     background:rgba(19,186,140,1);
@@ -1387,6 +1413,7 @@ QTextBrowser#txtb_awes_postNotice,#txtb_awes_breachNotice
     font-family:"Microsoft YaHei";
     font-weight:400;
     color:rgba(102,102,102,1);
+    padding:RATE_BASE_SIZE30 RATE_BASE_SIZE30 RATE_BASE_SIZE30 RATE_BASE_SIZE30px;
 }
 
 QPushButton#btn_awes_goback
@@ -1401,7 +1428,7 @@ QPushButton#btn_awes_goback
     color:rgba(255,255,255,1);
 }
 
-QPushButton#btn_qaab_mark,#btn_qaab_cut,#btn_qaab_paste,#btn_qaab_superscript,#btn_qaab_subscript,#btn_qaab_cancelSub,#btn_qaab_cancelSup
+QPushButton#btn_qaab_copy,#btn_qaab_cut,#btn_qaab_paste,#btn_qaab_superscript,#btn_qaab_subscript,#btn_qaab_cancelSub,#btn_qaab_cancelSup
 {
     outline:none;
     background:rgba(19,187,138,1);
@@ -1579,7 +1606,7 @@ QTextBrowser#tb_questionBody
 QTextBrowser#tb_option
 {
     outline:none;
-    border:none;
+    border:0px solid rgba(102,102,102,1);
     background:rgba(255,255,255,1);
     padding:0 0 0 0px;
 
@@ -1680,7 +1707,7 @@ QWidget#widget_audioPlay
     background:rgba(239,240,245,1);
 }
 
-QWidget#widget_cloe_viewpaper
+QWidget#widget_cloe_viewpaper,#widget_awtlt_bg
 {
     border-radius:RATE_BASE_SIZE10px;
     background:rgba(255,255,255,1);
@@ -1955,3 +1982,23 @@ QLabel#label_fl_hint
     font-weight:600;
     color:rgba(51,51,51,1);
 }
+
+
+QLabel#label_awtlt_title
+{
+    font-size:RATE_BASE_SIZE18px;
+    font-family:"Microsoft YaHei";
+    font-weight:600;
+    color:rgba(51,51,51,1);
+}
+
+
+QPushButton#btn_aw_close
+{
+    border-image:url(:/images/icon-aw-close.png);
+}
+
+QPushButton#btn_aw_minisize
+{
+    border-image:url(:/images/icon-aw-minisize.png);
+}

+ 7 - 6
client/cloeUploadFile.cpp

@@ -45,6 +45,7 @@ cloeUploadFile::cloeUploadFile(__int64 nExamRecordDataId, std::vector<QString> v
 
 cloeUploadFile::~cloeUploadFile()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -61,7 +62,7 @@ void cloeUploadFile::initUI()
     ui->btn_fc_close->setGeometry(ui->widget_cloe_BG->width() - g_appInfoPtr->m_fRate*(20 + 16),
                                   g_appInfoPtr->m_fRate*16, g_appInfoPtr->m_fRate*16, g_appInfoPtr->m_fRate*16);
     ui->label_HLine->setGeometry(0, ui->label_fc_title->y() + ui->label_fc_title->height() + g_appInfoPtr->m_fRate*16,
-                                 ui->widget_cloe_BG->width(), g_appInfoPtr->m_fRate*1);
+                                 ui->widget_cloe_BG->width(), g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
     ui->label_cloe_selFileType->adjustSize();
     ui->label_cloe_selFileType->setGeometry(g_appInfoPtr->m_fRate*30, ui->label_HLine->y() + ui->label_HLine->height() + g_appInfoPtr->m_fRate*30,
                                             ui->label_cloe_selFileType->width(), ui->label_cloe_selFileType->height());
@@ -135,7 +136,7 @@ void cloeUploadFile::on_btn_cloe_selFile_clicked()
     }
 	else
 	{		
-		ShowMsg(QString::fromLocal8Bit("请先选择文件类型"), this);
+        ShowMsg(QString::fromLocal8Bit("请先选择文件类型"), this, MSG_ICON_TYPE::mit_error);
 		return;
 	}
 	
@@ -167,7 +168,7 @@ void cloeUploadFile::on_btn_comfirmUpload_clicked()
 {
 	if (ui->edt_cloe_file->text().isEmpty())
 	{
-		ShowMsg(QString::fromLocal8Bit("请先选择文件,再上传"), this);
+        ShowMsg(QString::fromLocal8Bit("请先选择文件,再上传"), this, MSG_ICON_TYPE::mit_error);
 		return;
 	}
 	
@@ -205,18 +206,18 @@ void cloeUploadFile::onSubmitOfflinePaper(CSubmitOfflinePaper submitOfflinePaper
 {
 	if (submitOfflinePaper.nCode == 200)
 	{
-		ShowMsg(QString::fromLocal8Bit("上传答案完成"), this);
+        ShowMsg(QString::fromLocal8Bit("上传答案完成"), (QWidget*)this->parent(), MSG_ICON_TYPE::mit_succeed);
 		emit uploadSucceed();
 	}
 	else
 	{
         if(submitOfflinePaper.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("上传答案失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("上传答案失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(submitOfflinePaper.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(submitOfflinePaper.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }

+ 18 - 2
client/cloeViewPaper.cpp

@@ -5,7 +5,7 @@
 #include "awMsgBox.h"
 #include "json/json.h"
 #include <QDesktopWidget>
-
+#include <QDateTime>
 
 cloeViewPaper::cloeViewPaper(QString sPaper, QWidget *parent) :
     QWidget(parent),
@@ -16,10 +16,20 @@ cloeViewPaper::cloeViewPaper(QString sPaper, QWidget *parent) :
 	initPaper(sPaper);
 
 	initUI();		
+
+    m_pServerTime = std::make_shared<QTimer>();
+    m_pServerTime->setInterval(1000);
+    connect(m_pServerTime.get(), &QTimer::timeout, this, [&]() {
+        ui->label_serverTime->adjustSize();
+        ui->label_serverTime->setText(QString::fromLocal8Bit("服务器时间:%1")
+            .arg(QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime()).toString("yyyy-MM-dd hh:mm:ss")));
+    });
+    m_pServerTime->start();
 }
 
 cloeViewPaper::~cloeViewPaper()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -64,13 +74,19 @@ void cloeViewPaper::initUI()
 
 }
 
+void cloeViewPaper::setCourseCodeAndName(QString sCode, QString sName)
+{
+	ui->label_awbg_exam->setText(QString::fromLocal8Bit("课程代码:%1 - 课程名称:%2").arg(sCode).arg(sName));
+	ui->label_awbg_exam->adjustSize();
+}
+
 void cloeViewPaper::initPaper(QString sPaper)
 {
 	Json::Reader reader;
 	Json::Value jPaper = Json::Value::null;
 	if (!reader.parse(sPaper.toStdString(), jPaper))
 	{
-		ShowMsg(QString::fromLocal8Bit("解析试卷出错!"), this);		
+        ShowMsg(QString::fromLocal8Bit("解析试卷出错!"), this, MSG_ICON_TYPE::mit_error);
 		return;
 	}
 

+ 3 - 0
client/cloeViewPaper.h

@@ -16,6 +16,7 @@ public:
     explicit cloeViewPaper(QString sPaper, QWidget *parent = nullptr);
     ~cloeViewPaper();
 
+	void setCourseCodeAndName(QString sCode, QString sName);
 private slots:
     void on_btn_clop_goback_clicked();
 
@@ -23,6 +24,8 @@ private:
     void initUI();
 	void initPaper(QString sPaper);
 
+    std::shared_ptr<QTimer> m_pServerTime;
+
     Ui::cloeViewPaper *ui;    
 };
 

+ 22 - 13
client/clopPaperDetail.cpp

@@ -88,11 +88,11 @@ void clopPaperDetail::onFindExamRecordDataEntity(CFindExamRecordDataEntity findE
 	{
         if(findExamRecordDataEntity.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取考试记录失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取考试记录失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(findExamRecordDataEntity.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(findExamRecordDataEntity.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -121,11 +121,11 @@ void clopPaperDetail::onGetExamRecordQuestions(CGetExamRecordQuestions getExamRe
 	{
         if(getExamRecordQuestions.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取题目信息失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取题目信息失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getExamRecordQuestions.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getExamRecordQuestions.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -158,11 +158,11 @@ void clopPaperDetail::onGetExamRecordPaperStruct(CGetExamRecordPaperStruct getEx
 	{
         if(getExamRecordPaperStruct.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取试卷结构失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取试卷结构失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getExamRecordPaperStruct.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getExamRecordPaperStruct.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -197,11 +197,11 @@ void clopPaperDetail::onGetQuestion(CGetQuestion getQuestion)
 	{
         if(getQuestion.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取试题失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取试题失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getQuestion.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getQuestion.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -365,6 +365,7 @@ void clopPaperDetail::on_btn_expandPaper_clicked()
                                         }
                                         else
                                         {
+											QString sAns = jBlocks["blocks"][nb]["value"].asString().c_str();
                                             sRightAnswer += jBlocks["blocks"][nb]["value"].asString() == "true" ? QString::fromLocal8Bit("正确") : QString::fromLocal8Bit("错误");
                                         }
                                     }
@@ -418,11 +419,19 @@ void clopPaperDetail::on_btn_expandPaper_clicked()
                     if (sqs.sQuestionType == QUESTION_TYPE::SingleChoice ||
                         sqs.sQuestionType == QUESTION_TYPE::MultipleChoice ||
                         sqs.sQuestionType == QUESTION_TYPE::TrueOrFalse)
-                    {
-                        for (int isal = 0; isal < sqs.sStudentAnswer.length(); ++isal)
-                        {
-                            sStudentAnswer += 'A' + QString(sqs.sStudentAnswer[isal]).toInt();
-                        }
+                    {                        
+						if (sqs.sQuestionType != QUESTION_TYPE::TrueOrFalse)
+						{
+							for (int isal = 0; isal < sqs.sStudentAnswer.length(); ++isal)
+							{
+								sStudentAnswer += 'A' + QString(sqs.sStudentAnswer[isal]).toInt();
+							}
+						}
+						else
+						{
+							QString  sAns = sqs.sStudentAnswer;
+                            sStudentAnswer = sqs.sStudentAnswer == "true" ? QString::fromLocal8Bit("正确") : sqs.sStudentAnswer == "false" ? QString::fromLocal8Bit("错误") : "";
+						}                                                    
                     }
                     else if (sqs.sQuestionType == QUESTION_TYPE::FillUp ||
                         sqs.sQuestionType == QUESTION_TYPE::Essay)

+ 3 - 2
client/clopPaperReport.cpp

@@ -119,6 +119,7 @@ void clopPaperReport::onGetPracticeDetailInfo(CGetPracticeDetailInfo getPractice
     if(getPracticeDetailInfo.nCode == 200)
     {        
         int nSize = getPracticeDetailInfo.vPracticePaperStructList.size();
+        emit objectiveAccuracy(getPracticeDetailInfo.fObjectiveAccuracy);
 		ui->tablewt_examList->setRowCount(nSize);
         for(int i = 0; i < nSize; i++)
         {
@@ -139,11 +140,11 @@ void clopPaperReport::onGetPracticeDetailInfo(CGetPracticeDetailInfo getPractice
     {
         if(getPracticeDetailInfo.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取练习信息失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取练习信息失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getPracticeDetailInfo.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getPracticeDetailInfo.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
     }
 }

+ 1 - 0
client/clopPaperReport.h

@@ -13,6 +13,7 @@ class clopPaperReport : public QWidget
     Q_OBJECT
 signals:
     void heightChange(int nHeight);
+    void objectiveAccuracy(double fAccuracy);
 public:
     explicit clopPaperReport(__int64 nExamRecordDataId, bool bFromCache = false, QWidget *parent = nullptr);
     ~clopPaperReport();

+ 12 - 0
client/clopReport.cpp

@@ -64,6 +64,18 @@ void clopReport::setUI(const int nLeft, const int nTop, const int nWidth, const
 			}
 		});
 
+        connect(paperReport, &clopPaperReport::objectiveAccuracy, this, [&](double fAccuracy) {
+            ui->label_clop_correctRate->setText(QString::fromLocal8Bit("%1%").arg(fAccuracy));
+            ui->label_clop_correctRate->adjustSize();
+            ui->label_clop_correctRate->setGeometry(ui->btn_clop_goback->x() - g_appInfoPtr->m_fRate*30 - ui->label_clop_correctRate->width(),
+                                                    (ui->widget_clop_top->height() - ui->label_clop_correctRate->height())/2,
+                                                    ui->label_clop_correctRate->width(), ui->label_clop_correctRate->height());
+            ui->label_clop_correctRateHint->adjustSize();
+            ui->label_clop_correctRateHint->setGeometry(ui->label_clop_correctRate->x() - ui->label_clop_correctRateHint->width() - g_appInfoPtr->m_fRate*10,
+                                                        (ui->widget_clop_top->height() - ui->label_clop_correctRateHint->height())/2,
+                                                        ui->label_clop_correctRateHint->width(), ui->label_clop_correctRateHint->height());
+        });
+
         QListWidgetItem *pItem = new QListWidgetItem;
         ui->lw_reportInfo->addItem(pItem);
         ui->lw_reportInfo->setItemWidget(pItem, paperReport);

+ 3 - 3
client/clopResultList.cpp

@@ -112,7 +112,7 @@ void clopResultList::setUI(const int nLeft, const int nTop, const int nWidth, co
 void clopResultList::onQueryPracticeRecordList(CQueryPracticeRecordList queryPracticeRecordList)
 {
     if(queryPracticeRecordList.nCode == 200)
-    {
+    {        
         int nSize = queryPracticeRecordList.vPracticeRecordList.size();
         ui->tablewt_examList->setRowCount(nSize);
         for(int i = 0; i < nSize; ++i)
@@ -152,11 +152,11 @@ void clopResultList::onQueryPracticeRecordList(CQueryPracticeRecordList queryPra
     {
         if(queryPracticeRecordList.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取练习列表信息失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取练习列表信息失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(queryPracticeRecordList.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(queryPracticeRecordList.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
     }
 }

+ 238 - 61
client/courseList.cpp

@@ -42,7 +42,8 @@ courseList::courseList(QWidget *parent) :
 	connect(g_httpBllPtr.get(), &CHttpBll::sgnFaceCheckEnabled, this, &courseList::onFaceCheckEnabled);
 	qRegisterMetaType<CLivenessEnabled>("CLivenessEnabled");
 	connect(g_httpBllPtr.get(), &CHttpBll::sgnLivenessEnabled, this, &courseList::onLivenessEnabled);
-
+    qRegisterMetaType<CEndExam>("CEndExam");
+    connect(g_httpBllPtr.get(), &CHttpBll::sgnEndExam, this, &courseList::onEndExam);
 	
 
 	sqlite3_stmt *stmt;
@@ -50,7 +51,7 @@ courseList::courseList(QWidget *parent) :
 		.arg(g_appInfoPtr->m_nStudentId);
 	if (!g_sysDBProcPtr->QuerySql(sSql.toStdString(), &stmt))
 	{
-		ShowMsg(QString::fromLocal8Bit("加载隐私协议信息失败!"), this);		
+        ShowMsg(QString::fromLocal8Bit("加载隐私协议信息失败!"), this, MSG_ICON_TYPE::mit_error);
 	}
 
 	bool bAgreement = false;
@@ -99,15 +100,28 @@ courseList::courseList(QWidget *parent) :
     m_pStudentInfoBtnTimer->setInterval(500);
     connect(m_pStudentInfoBtnTimer.get(), &QTimer::timeout, this, &courseList::hideStudentInfoBtn);
 
-	m_pRefreshTimer = std::make_shared<QTimer>();
-	m_pRefreshTimer->setInterval(1000*60*3);
-	connect(m_pRefreshTimer.get(), &QTimer::timeout, this, []() {
+	m_pServerTime = std::make_shared<QTimer>();
+	m_pServerTime->setInterval(1000);
+	connect(m_pServerTime.get(), &QTimer::timeout, this, [&]() {
+        ui->label_serverTime->adjustSize();
+		ui->label_serverTime->setText(QString::fromLocal8Bit("服务器时间:%1")
+			.arg(QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime()).toString("yyyy-MM-dd hh:mm:ss")));
+	});
+	m_pServerTime->start();
+
+	m_pRefreshMenuTimer = std::make_shared<QTimer>();
+	m_pRefreshMenuTimer->setInterval(1000 * 60 * 3);
+	connect(m_pRefreshMenuTimer.get(), &QTimer::timeout, this, []() {
 		CHttpRequestPackage hrp;
 		hrp.sUri = "/api/ecs_core/rolePrivilege/getStudentClientMenu";
 		hrp.nRequestType = RequestType::rtGetStudentClientMenu;
 		hrp.sParamList.push_back(QString::fromLocal8Bit("rootOrgId,%1").arg(g_appInfoPtr->m_sRootOrgId));
 		g_httpBllPtr->get(hrp);
+	});
 
+	m_pOnlineTimer = std::make_shared<QTimer>();
+	m_pOnlineTimer->setInterval(1000*60*3);
+	connect(m_pOnlineTimer.get(), &QTimer::timeout, this, []() {	
 		CHttpRequestPackage hrp1;
 		hrp1.sUri = "/api/ecs_core/student/online_signal/" + QString::number(g_appInfoPtr->m_nStudentId); 
 		hrp1.nRequestType = RequestType::rtOnlineSignal;		
@@ -116,10 +130,33 @@ courseList::courseList(QWidget *parent) :
 
     ui->btn_mobileLogin->installEventFilter(this);
     ui->btn_studentInfo->installEventFilter(this);
+
+	m_pOnlineTimer->start();
+	m_pRefreshMenuTimer->start();
+
+    if (!g_appInfoPtr->m_sMenuLogoUrl.isEmpty())
+    {
+        QString sFileName = g_appInfoPtr->m_sMenuLogoUrl.right(g_appInfoPtr->m_sMenuLogoUrl.length() - g_appInfoPtr->m_sMenuLogoUrl.lastIndexOf("/") - 1);
+        sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+        CHttpRequestPackage hrp;
+        hrp.sUri = g_appInfoPtr->m_sMenuLogoUrl;
+        hrp.sCommonStr = sFileName;
+        hrp.sCommonStr1 = __FILE__;
+        hrp.sAdditionStr = "MenuLogoUrl";
+        hrp.nRequestType = RequestType::rtDownLoadFile;
+        hrp.nRetryCount = 3;
+        g_httpBllPtr->downLoad(hrp);
+    }
+    else
+    {
+        ui->label_cl_org_icon->setPixmap(QPixmap(":/images/qm-logo.png").scaled(ui->label_cl_org_icon->width(), ui->label_cl_org_icon->height(),
+                                                                   Qt::KeepAspectRatio, Qt::SmoothTransformation));
+    }
 }
 
 courseList::~courseList()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -160,7 +197,7 @@ void courseList::initUI()
 	ui->stw_courseList->setCurrentIndex(0);
 	*/
     
-    ui->label_cl_org_icon->setGeometry(g_appInfoPtr->m_fRate*30, g_appInfoPtr->m_fRate*30, g_appInfoPtr->m_fRate*36, g_appInfoPtr->m_fRate*36);
+    ui->label_cl_org_icon->setGeometry(g_appInfoPtr->m_fRate*15, g_appInfoPtr->m_fRate*15, g_appInfoPtr->m_fRate*66, g_appInfoPtr->m_fRate*66);
     ui->btn_onlineExam->setGeometry(g_appInfoPtr->m_fRate*20, g_appInfoPtr->m_fRate*103, g_appInfoPtr->m_fRate*56, g_appInfoPtr->m_fRate*66);
     ui->btn_onlineHomework->setGeometry(ui->btn_onlineExam->x(), ui->btn_onlineExam->y() + ui->btn_onlineExam->height() + g_appInfoPtr->m_fRate*17,
                                         ui->btn_onlineExam->width(), ui->btn_onlineExam->height());
@@ -185,7 +222,7 @@ void courseList::initUI()
     ui->btn_minisize->setGeometry(ui->btn_cl_close->x() - g_appInfoPtr->m_fRate*6 - ui->btn_cl_close->width(), ui->btn_cl_close->y(),
                                   ui->btn_cl_close->width(), ui->btn_cl_close->height());
     ui->label_VLine->setGeometry(ui->btn_minisize->x() - g_appInfoPtr->m_fRate*21, ui->btn_cl_close->y(),
-                                 g_appInfoPtr->m_fRate*1, g_appInfoPtr->m_fRate*24);
+                                 g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1, g_appInfoPtr->m_fRate*24);
     ui->btn_exit->setGeometry(ui->label_VLine->x() - g_appInfoPtr->m_fRate*(20+24), ui->btn_cl_close->y(),
                               ui->btn_cl_close->width(), ui->btn_cl_close->height());
     ui->btn_studentInfo->setGeometry(ui->btn_exit->x() - g_appInfoPtr->m_fRate*(10+90), ui->btn_cl_close->y(),
@@ -206,7 +243,11 @@ void courseList::initUI()
     ui->label_cl_version->adjustSize();
     ui->label_cl_version->setGeometry(ui->widget_bottom->width() - g_appInfoPtr->m_fRate*30 - ui->label_cl_version->width(),
                                       ui->label_cl_company->y(), ui->label_cl_version->width(), ui->label_cl_version->height());
-    ui->label_serverTime->adjustSize();
+	
+	ui->label_serverTime->setText(QString::fromLocal8Bit("服务器时间:%1")
+			.arg(QDateTime::fromMSecsSinceEpoch(g_appInfoPtr->serverMTime()).toString("yyyy-MM-dd hh:mm:ss")));
+	
+	ui->label_serverTime->adjustSize();
     ui->label_serverTime->setGeometry(ui->label_cl_version->x() - g_appInfoPtr->m_fRate*20 - ui->label_serverTime->width(), ui->label_cl_company->y(),
                                       ui->label_serverTime->width(), ui->label_serverTime->height());
 
@@ -267,8 +308,9 @@ void courseList::on_btn_minisize_clicked()
 
 void courseList::on_btn_cl_close_clicked()
 {
-    close();
-	logout();
+//    logout();
+//    close();
+    QCoreApplication::quit();
 }
 
 void courseList::setCheck(COURSE_MENU_BTN_TYPE cmbt)
@@ -310,6 +352,12 @@ void courseList::setCheck(COURSE_MENU_BTN_TYPE cmbt)
 	if (m_pOnlinePractice != nullptr)
 	{
 		m_pOnlinePractice->hide();
+        m_pOnlinePractice->releaseClopResult();
+	}
+
+	if (m_pClopReport != nullptr)
+	{
+		m_pClopReport->hide();		
 	}
 	
 	if (m_pOfflineExam != nullptr)
@@ -407,7 +455,7 @@ void courseList::setCheck(COURSE_MENU_BTN_TYPE cmbt)
     ui->label_currrentPlace->setText(sCurrentPlace);
 }
 
-void courseList::onEnterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId)
+void courseList::onEnterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId, QString sCourseCode, QString sCourseName)
 {
     if(cot == CL_OPERATION_TYPE::cot_online_exam)
     {
@@ -415,7 +463,10 @@ void courseList::onEnterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nEx
     }
     g_appInfoPtr->m_oExamInfo.nExamId = nExamId;
 	g_appInfoPtr->m_oExamInfo.nExamStudentId = nExamStudentId;
+    g_appInfoPtr->m_oExamInfo.sCourseCode = sCourseCode;
+    g_appInfoPtr->m_oExamInfo.sCourseName = sCourseName;
     //开考限流
+    m_nRetryCount = 0;
     CHttpRequestPackage hrp;
     hrp.sUri = QString("https://tcc.qmth.com.cn/rate_limit/prod/startExam/%1").arg(100);
     hrp.nRequestType = RequestType::rtStartExamLimit;
@@ -426,6 +477,7 @@ void courseList::onStartExamLimit(CStartExamLimit startExamLimit)
 {
 	if (startExamLimit.nCode == 200)
 	{
+        ++m_nRetryCount;
 		if (startExamLimit.bPass)
 		{
             CHttpRequestPackage hrp;
@@ -436,11 +488,24 @@ void courseList::onStartExamLimit(CStartExamLimit startExamLimit)
 		else
 		{
 			//重试
+            if(m_nRetryCount >= 3)
+            {
+                ShowMsg(QString::fromLocal8Bit("系统繁忙,请稍后重试"), this, MSG_ICON_TYPE::mit_error);
+            }
+            else
+            {
+                QTimer::singleShot(3*1000, this, [](){
+                    CHttpRequestPackage hrp;
+                    hrp.sUri = QString("https://tcc.qmth.com.cn/rate_limit/prod/startExam/%1").arg(100);
+                    hrp.nRequestType = RequestType::rtStartExamLimit;
+                    g_httpBllPtr->getUrl(hrp);
+                });
+            }
 		}
 	}
 	else
 	{
-		ShowMsg(QString::fromLocal8Bit("开考失败"), this);
+        ShowMsg(QString::fromLocal8Bit("开考失败"), this, MSG_ICON_TYPE::mit_error);
 	}
 }
 
@@ -460,12 +525,12 @@ void courseList::onIpLimit(CIpLimit ipLimit)
 		}
 		else
 		{
-			ShowMsg(QString::fromLocal8Bit("当前ip限制参加考试"), this);
+            ShowMsg(QString::fromLocal8Bit("当前ip限制参加考试"), this, MSG_ICON_TYPE::mit_error);
 		}
 	}
 	else
 	{
-		ShowMsg(QString::fromLocal8Bit("开考失败"), this);
+        ShowMsg(QString::fromLocal8Bit("开考失败"), this, MSG_ICON_TYPE::mit_error);
 	}
 }
 
@@ -533,7 +598,7 @@ void courseList::onGetExamProperty(CGetExamProperty getExamProperty)
 	{
         if(getExamProperty.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取考试信息失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取考试信息失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
@@ -549,7 +614,12 @@ void courseList::onFaceCheckEnabled(CFaceCheckEnabled faceCheckEnabled)
         //人脸检测
         g_appInfoPtr->m_oExamInfo.bFaceCheck = faceCheckEnabled.bEnabled;
         if (faceCheckEnabled.bEnabled && !g_appInfoPtr->m_oExamInfo.bIsExamInProgress)
-		{			         
+		{			 
+			if (g_appInfoPtr->m_sStudentPhotoPath.isEmpty())
+			{
+                ShowMsg(QString::fromLocal8Bit("本场考试需要进行人脸检测,但是您没有上传底照,请联系老师"), this, MSG_ICON_TYPE::mit_error);
+				return;
+			}
 			if (m_pFaceCompare == nullptr)
 			{
 				m_pFaceCompare = std::make_shared<faceCompare>(this);
@@ -583,11 +653,11 @@ void courseList::onFaceCheckEnabled(CFaceCheckEnabled faceCheckEnabled)
 	{
         if(faceCheckEnabled.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取人脸识别信息失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取人脸识别信息失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(faceCheckEnabled.sMessage, this);
+            ShowMsg(faceCheckEnabled.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -605,11 +675,11 @@ void courseList::onLivenessEnabled(CLivenessEnabled livenessEnabled)
 	{
         if(livenessEnabled.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取活体信息失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取活体信息失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(livenessEnabled.sMessage, this);
+            ShowMsg(livenessEnabled.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -629,9 +699,26 @@ void courseList::enterWaitExam()
 			m_pBackground = std::make_shared<awBackground>(AW_WIDGET_TYPE::awwt_waitExam, this);
 
 		}
-		
+
+        connect(m_pBackground.get(), &awBackground::minisize, this, &courseList::minisize);
         connect(m_pBackground.get(), &awBackground::closeWidget, this, [&](){
             m_pBackground.reset();
+			m_pRefreshMenuTimer->start();
+            if(m_pOnlineExam)
+            {
+                m_pOnlineExam->refreshExam();
+            }
+
+            if(m_pOnlineHomework)
+            {
+                m_pOnlineHomework->refreshExam();
+            }
+        });
+
+        connect(m_pBackground.get(), &awBackground::gobackLogin, this, [&](){
+            m_pBackground.reset();
+            logout();
+            close();
         });
         connect(m_pBackground.get(), &awBackground::showPracticeInfo, this, [&](){
             m_pBackground.reset();
@@ -644,13 +731,19 @@ void courseList::enterWaitExam()
                 m_pClopReport = std::make_shared<clopReport>(pr, true, ui->stw_courseList);
                 connect(m_pClopReport.get(), &clopReport::goback, this, [&](){
                     m_pClopReport.reset();
+                    if(m_pOnlinePractice)
+                    {
+                        m_pOnlinePractice->refreshExamInfo();
+                    }
                 });
                 m_pClopReport->setUI(0, 0, ui->stw_courseList->width(), ui->stw_courseList->height());
             }
             m_pClopReport->show();
+			m_pRefreshMenuTimer->start();
         });
 	}
 	m_pBackground->show();
+	m_pRefreshMenuTimer->stop();
 }
 
 void courseList::on_btn_onlineExam_clicked()
@@ -690,9 +783,9 @@ void courseList::on_btn_editPassword_clicked()
 }
 
 void courseList::on_btn_exit_clicked()
-{
-    close();
+{    
 	logout();
+    close();
 }
 
 void courseList::logout()
@@ -834,8 +927,10 @@ void courseList::onGetStudentInfoBySession(CGetStudentInfoBySession getStudentIn
 			CHttpRequestPackage hrp;
 			hrp.sUri = g_appInfoPtr->m_sStudentPhotoPath;
 			hrp.sCommonStr = sFileName;
+            hrp.sCommonStr1 = __FILE__;
+            hrp.sAdditionStr = "StudentPhotoPath";
 			hrp.nRequestType = RequestType::rtDownLoadFile;
-
+            hrp.nRetryCount = 3;
 			g_httpBllPtr->downLoad(hrp);
 		}
 
@@ -851,11 +946,11 @@ void courseList::onGetStudentInfoBySession(CGetStudentInfoBySession getStudentIn
 	{
         if(getStudentInfoBySession.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取考生信息失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取考生信息失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getStudentInfoBySession.sMessage, this);
+            ShowMsg(getStudentInfoBySession.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -870,37 +965,41 @@ void courseList::onAppDownLoadUrl(CAppDownLoadUrl appDownLoadUrl)
     {
         if(appDownLoadUrl.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取APP下载地址失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取APP下载地址失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(appDownLoadUrl.sMessage, this);
+            ShowMsg(appDownLoadUrl.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
     }
 }
 
 void courseList::onDownLoadFile(CDownLoadFileInfo downLoadFileInfo)
 {
-	QString sFileName = g_appInfoPtr->m_sStudentPhotoPath.right(g_appInfoPtr->m_sStudentPhotoPath.length() - g_appInfoPtr->m_sStudentPhotoPath.lastIndexOf("/") - 1);
-	sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
-	if (downLoadFileInfo.sFileName == sFileName)
-	{
-		if (downLoadFileInfo.nCode == 200)
-		{
-		
-		}
-		else
-		{
+    if(downLoadFileInfo.sModuleName == __FILE__)
+    {
+        if (downLoadFileInfo.nCode == 200)
+        {
+            QString sFileName = g_appInfoPtr->m_sStudentPhotoPath.right(g_appInfoPtr->m_sStudentPhotoPath.length() - g_appInfoPtr->m_sStudentPhotoPath.lastIndexOf("/") - 1);
+            sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+            if (downLoadFileInfo.sAdditionStr == "MenuLogoUrl")
+            {
+                ui->label_cl_org_icon->setPixmap(QPixmap(sFileName).scaled(ui->label_cl_org_icon->width(), ui->label_cl_org_icon->height(),
+                                                                           Qt::KeepAspectRatio, Qt::SmoothTransformation));
+            }
+        }
+        else
+        {
             if(downLoadFileInfo.sMessage.isEmpty())
             {
-                ShowMsg(QString::fromLocal8Bit("下载失败"), this);
+                ShowMsg(QString::fromLocal8Bit("下载失败"), this, MSG_ICON_TYPE::mit_error);
             }
             else
             {
-                ShowMsg(downLoadFileInfo.sMessage, this);
+                ShowMsg(downLoadFileInfo.sMessage, this, MSG_ICON_TYPE::mit_error);
             }
-		}
-	}
+        }
+    }
 }
 
 void courseList::onSpecialtyNameList(CSpecialtyNameList specialtyNameList)
@@ -918,11 +1017,11 @@ void courseList::onSpecialtyNameList(CSpecialtyNameList specialtyNameList)
 	{
         if(specialtyNameList.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取专业信息失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取专业信息失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(specialtyNameList.sMessage, this);
+            ShowMsg(specialtyNameList.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -936,7 +1035,14 @@ void courseList::onCheckExamInProgress(CCheckExamInProgress checkExamInProgress)
 			if (checkExamInProgress.bIsExceed)
 			{
 				//超过断点时长交卷
-				
+                g_appInfoPtr->m_oExamInfo.nExamRecordDataId = checkExamInProgress.nExamRecordDataId;
+                g_appInfoPtr->m_oExamInfo.nExamId = checkExamInProgress.nExamId;
+
+                CHttpRequestPackage hrp;
+                hrp.sUri = QString("/api/ecs_oe_student/client/exam/process/endExam");
+                hrp.sCommonStr = __FILE__;
+                hrp.nRequestType = RequestType::rtEndExam;
+                g_httpBllPtr->post(hrp);
 				
 			}
 			else
@@ -965,6 +1071,7 @@ void courseList::onCheckExamInProgress(CCheckExamInProgress checkExamInProgress)
 		}
 		else
 		{
+            g_appInfoPtr->m_oExamInfo.bIsExamInProgress = false;
 			if (checkExamInProgress.sEnterType == EXAM_INPROGRESS_ENTER_TYPE::EIET_COURSE_LIST)
 			{
 				CHttpRequestPackage hrp;
@@ -999,15 +1106,60 @@ void courseList::onCheckExamInProgress(CCheckExamInProgress checkExamInProgress)
 	{
         if(checkExamInProgress.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取断点信息失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取断点信息失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(checkExamInProgress.sMessage, this);
+            ShowMsg(checkExamInProgress.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
 
+void courseList::onEndExam(CEndExam endExam)
+{
+    if(endExam.sModuleName == __FILE__)
+    {
+        if (endExam.nCode == 200)
+        {
+            m_pRefreshMenuTimer->stop();
+
+            if(m_pBackground != nullptr)
+            {
+                m_pBackground.reset();
+            }
+            m_pBackground = std::make_shared<awBackground>(AW_WIDGET_TYPE::awwt_examScore, this);
+
+            connect(m_pBackground.get(), &awBackground::minisize, this, &courseList::minisize);
+            connect(m_pBackground.get(), &awBackground::closeWidget, this, [&](){
+                m_pBackground.reset();
+                m_pRefreshMenuTimer->start();
+                if(m_pOnlineExam)
+                {
+                    m_pOnlineExam->refreshExam();
+                }
+
+                if(m_pOnlineHomework)
+                {
+                    m_pOnlineHomework->refreshExam();
+                }
+            });
+
+            m_pBackground->show();
+        }
+        else
+        {
+            if(endExam.sMessage.isEmpty())
+            {
+                ShowMsg(QString::fromLocal8Bit("交卷失败"), g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
+            }
+            else
+            {
+                ShowMsg(endExam.sMessage, g_appInfoPtr->m_pAnsBgWidget, MSG_ICON_TYPE::mit_error);
+            }
+        }
+    }
+}
+
 void courseList::onGetStudentClientMenu(CGetStudentClientMenu getStudentClientMenu)
 {
 	if (getStudentClientMenu.nCode == 200)
@@ -1020,33 +1172,42 @@ void courseList::onGetStudentClientMenu(CGetStudentClientMenu getStudentClientMe
 		ui->btn_editPassword->setVisible(false);
 		for (CStudentClientMenu scm : getStudentClientMenu.vMenus)
 		{
-			if (scm.sCode == "stu_online_exam_0")
+            QString sMenuName = scm.sName;
+            sMenuName.insert(sMenuName.length()/2, '\n');
+			QString sMenuCode = scm.sCode.left(scm.sCode.lastIndexOf("_"));
+			if (sMenuCode == "stu_online_exam")
 			{
 				ui->btn_onlineExam->setVisible(true);
+                ui->btn_onlineExam->setText(sMenuName);
 			}
-			else if (scm.sCode == "stu_online_homework_0")
+			else if (sMenuCode == "stu_online_homework")
 			{
 				ui->btn_onlineHomework->setVisible(true);
+                ui->btn_onlineHomework->setText(sMenuName);
 			}
-			else if (scm.sCode == "stu_online_practice_0")
+			else if (sMenuCode == "stu_online_practice")
 			{
 				ui->btn_onlinePractice->setVisible(true);
+                ui->btn_onlinePractice->setText(sMenuName);
 			}
-			else if (scm.sCode == "stu_offline_exam_0")
+			else if (sMenuCode == "stu_offline_exam")
 			{
 				ui->btn_offlineExam->setVisible(true);
+                ui->btn_offlineExam->setText(sMenuName);
 			}
-			else if (scm.sCode == "stu_notice_0")
+			else if (sMenuCode == "stu_notice")
 			{
 				ui->btn_notice->setVisible(true);
+                ui->btn_notice->setText(sMenuName);
 				CHttpRequestPackage hrp;
 				hrp.sUri = "/api/ecs_exam_work/notice/getUserNoticeList";
 				hrp.nRequestType = RequestType::rtGetUserNoticeList;				
 				g_httpBllPtr->get(hrp);
 			}		
-			else if (scm.sCode == "stu_modify_pwd_0")
+			else if (sMenuCode == "stu_modify_pwd")
 			{
 				ui->btn_editPassword->setVisible(true);
+                ui->btn_editPassword->setText(sMenuName);
 			}
 		}
 		menuBtnRePosistion();
@@ -1055,11 +1216,11 @@ void courseList::onGetStudentClientMenu(CGetStudentClientMenu getStudentClientMe
 	{
         if(getStudentClientMenu.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取菜单失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取菜单失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getStudentClientMenu.sMessage, this);
+            ShowMsg(getStudentClientMenu.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -1072,6 +1233,16 @@ void courseList::onGetUserNoticeList(CGetUserNoticeList getUserNoticeList)
 		{
 			m_pNoticeList = std::make_shared<clNoticeList>(ui->stw_courseList);
 			m_pNoticeList->setUI(ui->stw_courseList->width(), ui->stw_courseList->height());
+			connect(m_pNoticeList.get(), &clNoticeList::unReadNoticeCount, this, [&](int nCount) {
+				ui->btn_noticeNum->setText(QString::number(nCount));
+				ui->btn_noticeNum->setVisible(nCount > 0);
+			
+			});
+
+			connect(m_pNoticeList.get(), &clNoticeList::readNotice, this, [&](__int64 nId) {
+				m_pNoticePopWidget->setNoticeRead(nId);
+
+			});
 		}
 		m_pNoticeList->setNotice(getUserNoticeList.vNoticeList);
 		
@@ -1079,21 +1250,27 @@ void courseList::onGetUserNoticeList(CGetUserNoticeList getUserNoticeList)
 		{
 			m_pNoticePopWidget = std::make_shared<clNoticePopWidget>(this);
 			m_pNoticePopWidget->setUI(g_appInfoPtr->m_fRate * 960, g_appInfoPtr->m_fRate * 558, g_appInfoPtr->m_fRate * 310, g_appInfoPtr->m_fRate * 200);
+            connect(m_pNoticePopWidget.get(), &clNoticePopWidget::showNoticeDetail, this, [&](__int64 nId){
+                setCheck(COURSE_MENU_BTN_TYPE::cmbt_notice);
+                m_pNoticeList->viewNotice(nId);
+            });
 		}
 		
+		int nUnReadCount = 0;
 		for (CNoticeInfo ni : getUserNoticeList.vNoticeList)
 		{
 			if (!ni.bHasRead)
 			{
 				m_pNoticePopWidget->addNotice(ni);
+				++nUnReadCount;
 			}
 		}
 		
+		ui->btn_noticeNum->setText(QString::number(nUnReadCount));		
+		ui->btn_noticeNum->setVisible(nUnReadCount > 0);
+					
 		if(m_pNoticePopWidget->noticeCount() > 0)
-		{
-			ui->btn_noticeNum->setVisible(true);
-			ui->btn_noticeNum->setText(QString::number(m_pNoticePopWidget->noticeCount()));
-			
+		{						
 			m_pNoticePopWidget->show();
 		}		
 	}
@@ -1101,11 +1278,11 @@ void courseList::onGetUserNoticeList(CGetUserNoticeList getUserNoticeList)
 	{
         if(getUserNoticeList.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取通知信息失败"), this);
+            ShowMsg(QString::fromLocal8Bit("获取通知信息失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getUserNoticeList.sMessage, this);
+            ShowMsg(getUserNoticeList.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
 	}
 }

+ 9 - 3
client/courseList.h

@@ -62,7 +62,8 @@ private slots:
     void hideStudentInfoBtn();
     void hideMobileLoginBtn();
     //进入考试
-    void onEnterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId);
+    void onEnterExam(CL_OPERATION_TYPE cot, __int64 nExamId, __int64 nExamStudentId, 
+		QString sCourseCode, QString sCourseName);
 
 	void onAppDownLoadUrl(CAppDownLoadUrl appDownLoadUrl);
 	void onDownLoadFile(CDownLoadFileInfo downLoadFileInfo);
@@ -76,6 +77,8 @@ private slots:
 	void onGetExamProperty(CGetExamProperty getExamProperty);
 	void onFaceCheckEnabled(CFaceCheckEnabled faceCheckEnabled);
 	void onLivenessEnabled(CLivenessEnabled livenessEnabled);
+
+    void onEndExam(CEndExam endExam);
 private:
     void initUI();
     void setCheck(COURSE_MENU_BTN_TYPE cmbt);
@@ -89,7 +92,9 @@ private:
 
 	std::shared_ptr<QTimer> m_pStudentInfoBtnTimer;
 	std::shared_ptr<QTimer> m_pMobileLoginBtnTimer;
-	std::shared_ptr<QTimer> m_pRefreshTimer;
+	std::shared_ptr<QTimer> m_pServerTime;
+	std::shared_ptr<QTimer> m_pRefreshMenuTimer;
+	std::shared_ptr<QTimer> m_pOnlineTimer;
     std::shared_ptr<clNoticePopWidget> m_pNoticePopWidget;
     std::shared_ptr<clMobileLogin> m_pMobileLogin;
     std::shared_ptr<clStudentInfo> m_pStudentInfo;
@@ -107,9 +112,10 @@ private:
     std::shared_ptr<etWhetherEnvTest> m_pWhetherEnvTest;
     std::shared_ptr<environmentalTest> m_pEnvironmentalTest;
     std::shared_ptr<faceCompare> m_pFaceCompare;
-    std::shared_ptr<cloeUploadFile> m_pCloeUploadFile;
+//    std::shared_ptr<cloeUploadFile> m_pCloeUploadFile;
     std::shared_ptr<awBackground> m_pBackground;
     std::shared_ptr<awResumeExam> m_pResumeExam;
+    int m_nRetryCount = 0;
 };
 
 #endif // COURSELIST_H

+ 2 - 1
client/environmentalTest.cpp

@@ -17,6 +17,7 @@ environmentalTest::environmentalTest(QWidget *parent) :
     setStyleSheet(g_appInfoPtr->m_sQssStr);
 
     m_curentStep = ENV_TEST_STEP::ett_network;
+
     initUI();
 }
 
@@ -43,7 +44,7 @@ void environmentalTest::initUI()
                                       ui->label_et_networkIcon->y() + ui->label_et_networkIcon->height() + g_appInfoPtr->m_fRate*6,
                                       ui->label_et_network->width(), ui->label_et_network->height());
     ui->label_et_HLine1->setGeometry(ui->label_et_networkIcon->x() + ui->label_et_networkIcon->width()/2, ui->label_et_networkIcon->y() + (ui->label_et_networkIcon->height() - g_appInfoPtr->m_fRate*1)/2,
-                                     g_appInfoPtr->m_fRate*109 + ui->label_et_networkIcon->width(), g_appInfoPtr->m_fRate*1);
+                                     g_appInfoPtr->m_fRate*109 + ui->label_et_networkIcon->width(), g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
     ui->label_et_timeIcon->setGeometry(ui->label_et_networkIcon->x() + ui->label_et_HLine1->width(), ui->label_et_networkIcon->y(),
                                        ui->label_et_networkIcon->width(), ui->label_et_networkIcon->height());
     ui->label_et_time->adjustSize();

+ 139 - 58
client/etCameraTest.cpp

@@ -19,56 +19,89 @@ etCameraTest::etCameraTest(QWidget *parent) :
 
     setStyleSheet(g_appInfoPtr->m_sQssStr);
 
-	m_nCameraOpenStatus = 2;
-
-    m_pinitTimer = std::make_shared<QTimer>();
-    m_pinitTimer->setInterval(1000);
-    connect(m_pinitTimer.get(), &QTimer::timeout, this, [&](){
-        m_pinitTimer->stop();
-		bool bOpened = true;
-        if(!m_cam.open(0))
-        {
-            ShowMsg(QString::fromLocal8Bit("打开摄像头失败"), this);
-			bOpened = false;
-        }
-		
-        int inWidth = ui->widget_etct_camera->width();
-        m_cam.set(CV_CAP_PROP_FRAME_WIDTH, inWidth);
-        int inHeight = ui->widget_etct_camera->height();
-        m_cam.set(CV_CAP_PROP_FRAME_HEIGHT, inHeight);
-        m_cam.set(CV_CAP_PROP_FPS, 5);
-        m_pVideoTimer->start();
-
-		ui->tablewt_envTest->item(0, 0)->setText(QString::fromLocal8Bit("摄像头正常启用"));
-		QWidget *widget = new QWidget;
-		QHBoxLayout *layout = new QHBoxLayout;
-		widget->setLayout(layout);
-		QLabel *icon = new QLabel;
-
-		if(bOpened)
-		{ 
-			ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("正常"));
-            icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 12, g_appInfoPtr->m_fRate * 9));
-			icon->setPixmap(QPixmap(":/images/icon-et-item-pass.png").scaled(g_appInfoPtr->m_fRate * 12, g_appInfoPtr->m_fRate * 9));
-		}
-		else
-		{
-			ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("异常"));
-            icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
-            icon->setPixmap(QPixmap(":/images/icon-et-iten-unpass.png").scaled(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
-		}
+
+
+    m_nCameraOpenStatus = 2;
+
+ //   m_cam.set(CV_CAP_PROP_FOURCC, CV_FOURCC('F', 'M', 'P', '4'));
+//    int inWidth = ui->widget_etct_camera->width();
+//    m_cam.set(CV_CAP_PROP_FRAME_WIDTH, inWidth);
+//    int inHeight = ui->widget_etct_camera->height();
+//    m_cam.set(CV_CAP_PROP_FRAME_HEIGHT, inHeight);
+//    m_cam.set(CV_CAP_PROP_FPS, 5);
+//	m_cam.set(CV_CAP_PROP_BRIGHTNESS, 60);//亮度
+//	m_cam.set(CV_CAP_PROP_CONTRAST, 30);//对比度 40
+//	m_cam.set(CV_CAP_PROP_HUE, 50);//色调 50
+//	m_cam.set(CV_CAP_PROP_EXPOSURE, 60);//曝光 50 获取摄像头参数
+
+//    m_pinitTimer = std::make_shared<QTimer>();
+//    m_pinitTimer->setInterval(1000);
+//    connect(m_pinitTimer.get(), &QTimer::timeout, this, [&](){
+//        m_pinitTimer->stop();
+//		bool bOpened = true;
+//        if(!m_cam.open(0))
+//        {
+//            ShowMsg(QString::fromLocal8Bit("打开摄像头失败"), this, MSG_ICON_TYPE::mit_error);
+//			bOpened = false;
+//        }
+
+//        m_pVideoTimer->start();
+
+//		ui->tablewt_envTest->item(0, 0)->setText(QString::fromLocal8Bit("摄像头正常启用"));
+//		QWidget *widget = new QWidget;
+//		QHBoxLayout *layout = new QHBoxLayout;
+//		widget->setLayout(layout);
+//		QLabel *icon = new QLabel;
+
+//		if(bOpened)
+//		{
+//			ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("正常"));
+//            icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 12, g_appInfoPtr->m_fRate * 9));
+//			icon->setPixmap(QPixmap(":/images/icon-et-item-pass.png").scaled(g_appInfoPtr->m_fRate * 12, g_appInfoPtr->m_fRate * 9));
+//		}
+//		else
+//		{
+//			ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("异常"));
+//            icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
+//            icon->setPixmap(QPixmap(":/images/icon-et-iten-unpass.png").scaled(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
+//		}
 				
-		layout->addWidget(icon);
-		layout->addStretch();
-		ui->tablewt_envTest->setCellWidget(0, 2, widget);
-    });
+//		layout->addWidget(icon);
+//		layout->addStretch();
+//		ui->tablewt_envTest->setCellWidget(0, 2, widget);
+//    });
+
+    //ui->tablewt_envTest->item(0, 0)->setText(QString::fromLocal8Bit("摄像头正常启用"));
+
+    //		if(bOpened)
+    //		{
+    //			ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("正常"));
+    //            icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 12, g_appInfoPtr->m_fRate * 9));
+    //			icon->setPixmap(QPixmap(":/images/icon-et-item-pass.png").scaled(g_appInfoPtr->m_fRate * 12, g_appInfoPtr->m_fRate * 9));
+    //		}
+    //		else
+    //		{
+    //			ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("异常"));
+    //            icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
+    //            icon->setPixmap(QPixmap(":/images/icon-et-iten-unpass.png").scaled(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
+    //		}
+    
 
     m_pVideoTimer = std::make_shared<QTimer>();
     m_pVideoTimer->setInterval(200);
+	
     connect(m_pVideoTimer.get(), &QTimer::timeout, this, [&](){
 
+		if (!m_bSetCameraStaus)
+		{
+			m_bSetCameraStaus = true;
+			setCameraStaus(true);
+		}
         cv::Mat frame;
-        m_cam>>frame;
+        {
+            std::scoped_lock lock(m_imageMutex);
+            frame = m_nCurImage;
+        }
 
         if(frame.empty())
         {
@@ -83,16 +116,69 @@ etCameraTest::etCameraTest(QWidget *parent) :
         ui->widget_etct_camera->setPalette(palette);
     });
 
-	
+    
 }
 
 etCameraTest::~etCameraTest()
 {
     m_pVideoTimer->stop();
-	m_cam.release();
+    g_clientVideoProcPtr->stopTest(true);
+
+    awMsgBox::clear(this);
     delete ui;
 }
 
+void etCameraTest::setCameraStaus(bool bOpened)
+{
+    QWidget *widget = new QWidget;
+    QHBoxLayout *layout = new QHBoxLayout;
+    widget->setLayout(layout);
+    QLabel *icon = new QLabel;
+
+	ui->tablewt_envTest->item(0, 0)->setText(QString::fromLocal8Bit("摄像头正常启用"));
+    ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("异常"));
+    icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
+    icon->setPixmap(QPixmap(":/images/icon-et-iten-unpass.png").scaled(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
+    if(bOpened)
+    {
+        ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("正常"));
+        icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 12, g_appInfoPtr->m_fRate * 9));
+        icon->setPixmap(QPixmap(":/images/icon-et-item-pass.png").scaled(g_appInfoPtr->m_fRate * 12, g_appInfoPtr->m_fRate * 9));
+    }
+    else
+    {
+        ui->tablewt_envTest->item(0, 1)->setText(QString::fromLocal8Bit("异常"));
+        icon->setFixedSize(QSize(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
+        icon->setPixmap(QPixmap(":/images/icon-et-iten-unpass.png").scaled(g_appInfoPtr->m_fRate * 8, g_appInfoPtr->m_fRate * 8));
+    }
+
+    layout->addWidget(icon);
+    layout->addStretch();
+    ui->tablewt_envTest->setCellWidget(0, 2, widget);
+
+}
+
+void etCameraTest::onRenderVideoFrame(const char* userId, TRTCVideoStreamType streamType, TRTCVideoFrame* frame)
+{
+    if(g_clientVideoProcPtr->isCameraTest())
+    {        
+        __int64 nServerTime = g_appInfoPtr->serverMTime();
+        if(nServerTime - m_lastFaceTime >= 100)
+        {
+            m_lastFaceTime = nServerTime;
+            cv::Mat matImg;
+            cv::cvtColor(cv::Mat(frame->height, frame->width, CV_8UC4, frame->data), matImg, CV_RGBA2RGB);
+            if(matImg.empty())
+            {
+                qDebug()<<"widgetCameraTest frame is empty";
+                return;
+            }
+            std::scoped_lock lock(m_imageMutex);
+            m_nCurImage = matImg;
+        }
+    }
+}
+
 int etCameraTest::setUI(const int nLeft, const int nTop, const int nWidth)
 {
     setGeometry(nLeft, nTop, nWidth, g_appInfoPtr->m_fRate*507);
@@ -107,7 +193,7 @@ int etCameraTest::setUI(const int nLeft, const int nTop, const int nWidth)
     ui->btn_IsSelf->setIconSize(QSize(g_appInfoPtr->m_fRate*32, g_appInfoPtr->m_fRate*32));
     ui->btn_IsSelf->setIcon(QIcon(":/images/icon-is-self.png"));
     ui->label_HLine->setGeometry(ui->widget_etct_camera->x(), ui->widget_etct_camera->y() + ui->widget_etct_camera->height() + g_appInfoPtr->m_fRate*30,
-                                 width() - g_appInfoPtr->m_fRate*30*2, g_appInfoPtr->m_fRate*1);
+                                 width() - g_appInfoPtr->m_fRate*30*2, g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
 
     ui->tablewt_envTest->setGeometry(ui->widget_etct_camera->x(), ui->label_HLine->y() + ui->label_HLine->height(),
                                      width() - g_appInfoPtr->m_fRate*30*2, height() - ui->label_HLine->y() - ui->label_HLine->height());
@@ -149,18 +235,13 @@ int etCameraTest::setUI(const int nLeft, const int nTop, const int nWidth)
     for(int i = 0; i < 2; i++)
     {
         ui->tablewt_envTest->setItem(i, 0, new QTableWidgetItem(QString::fromLocal8Bit("")));
-        ui->tablewt_envTest->setItem(i, 1, new QTableWidgetItem(QString::fromLocal8Bit("")));
-   /*     QWidget *widget = new QWidget;
-        QHBoxLayout *layout = new QHBoxLayout;
-        widget->setLayout(layout);
-        QLabel *icon = new QLabel;
-        icon->setFixedSize(QSize(g_appInfoPtr->m_fRate*12, g_appInfoPtr->m_fRate*9));
-        icon->setPixmap(QPixmap(":/images/icon-et-item-pass.png").scaled(g_appInfoPtr->m_fRate*12, g_appInfoPtr->m_fRate*9));
-        layout->addWidget(icon);
-        layout->addStretch();
-        ui->tablewt_envTest->setCellWidget(i, 2, widget);*/
-    }
-	m_pinitTimer->start();
+        ui->tablewt_envTest->setItem(i, 1, new QTableWidgetItem(QString::fromLocal8Bit(""))); 
+    }	
+
+	setCameraStaus(false);
+
+	g_clientVideoProcPtr->startTest(this, true);
+	m_pVideoTimer->start();
     return height();
 }
 

+ 11 - 4
client/etCameraTest.h

@@ -3,14 +3,15 @@
 
 #include <QWidget>
 #include <opencv2/opencv.hpp>
-
+#include <mutex>
 #include <QTimer>
+#include "CLiveViodeProc.h"
 
 namespace Ui {
 class etCameraTest;
 }
 
-class etCameraTest : public QWidget
+class etCameraTest : public QWidget, ITRTCVideoRenderCallback
 {
     Q_OBJECT
 
@@ -26,11 +27,17 @@ private slots:
     void on_btn_IsNotSelf_clicked();
 
 private:
+    void onRenderVideoFrame(const char* userId, TRTCVideoStreamType streamType, TRTCVideoFrame* frame);
+    void setCameraStaus(bool bOpened);
     Ui::etCameraTest *ui;
     std::shared_ptr<QTimer> m_pVideoTimer;
-    std::shared_ptr<QTimer> m_pinitTimer;
-    cv::VideoCapture m_cam;
+//    std::shared_ptr<QTimer> m_pinitTimer;
+//    cv::VideoCapture m_cam;
+    cv::Mat m_nCurImage;
+    std::mutex m_imageMutex;
+    __int64 m_lastFaceTime=0;
 	int m_nCameraOpenStatus;
+	bool m_bSetCameraStaus = false;
 };
 
 #endif // ETCAMERATEST_H

+ 6 - 6
client/etCameraTest.ui

@@ -39,14 +39,14 @@
   <widget class="QToolButton" name="btn_IsSelf">
    <property name="geometry">
     <rect>
-     <x>380</x>
-     <y>30</y>
+     <x>340</x>
+     <y>170</y>
      <width>191</width>
      <height>121</height>
     </rect>
    </property>
    <property name="text">
-    <string>图像中是操作者本人</string>
+    <string>图像中是操作者本人</string>
    </property>
    <property name="toolButtonStyle">
     <enum>Qt::ToolButtonTextUnderIcon</enum>
@@ -55,14 +55,14 @@
   <widget class="QToolButton" name="btn_IsNotSelf">
    <property name="geometry">
     <rect>
-     <x>380</x>
-     <y>170</y>
+     <x>340</x>
+     <y>20</y>
      <width>191</width>
      <height>131</height>
     </rect>
    </property>
    <property name="text">
-    <string>图像中是操作者本人</string>
+    <string>图像中是操作者本人</string>
    </property>
    <property name="toolButtonStyle">
     <enum>Qt::ToolButtonTextUnderIcon</enum>

+ 14 - 6
client/etMobileTest.cpp

@@ -51,6 +51,7 @@ etMobileTest::etMobileTest(QWidget *parent) :
 
 etMobileTest::~etMobileTest()
 {
+    awMsgBox::clear(this);
 	g_websocketPtr->close();
     delete ui;
 }
@@ -85,7 +86,7 @@ int etMobileTest::setUI(const int nLeft, const int nTop, const int nWidth)
                                          </body>
                                          </html>)").arg((int)(g_appInfoPtr->m_fRate*24)).arg((int)(g_appInfoPtr->m_fRate*24)));
     ui->label_HLine->setGeometry(ui->label_etmt_tips->x(), ui->label_etmt_tips->y() + ui->label_etmt_tips->height() + g_appInfoPtr->m_fRate*20,
-                                 g_appInfoPtr->m_fRate*490, g_appInfoPtr->m_fRate*1);
+                                 g_appInfoPtr->m_fRate*490, g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
     ui->label_etmt_status->adjustSize();
     ui->label_etmt_status->setGeometry(ui->label_etmt_tips->x(), ui->label_HLine->y() + ui->label_HLine->height() + 20,
                                        g_appInfoPtr->m_fRate*490, ui->label_etmt_status->height());
@@ -177,17 +178,19 @@ void etMobileTest::onGetWXQrCode(CGetWXQrCode getWXQrCode)
 {
 	if (getWXQrCode.nCode == 200)
 	{
-		ui->label_etmv_qrcode->setPixmap(CQREncode::GenerateQRcode(getWXQrCode.sUrl, ui->label_etmv_qrcode->width()));
-	}
+        ui->label_etmv_qrcode->setPixmap(CQREncode::GenerateQRcode(QString("%1%2")
+                                                                     .arg(getWXQrCode.sUrl)
+                                                                     .arg(QByteArray(("&apiServer=" + g_httpBllPtr->getHttpUrl()).toLocal8Bit()).toPercentEncoding().data()), ui->label_etmv_qrcode->width()));
+    }
 	else
 	{
         if(getWXQrCode.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("获取二维码失败"), (QWidget*)(this->parent()));
+            ShowMsg(QString::fromLocal8Bit("获取二维码失败"), (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(getWXQrCode.sMessage, (QWidget*)(this->parent()));
+            ShowMsg(getWXQrCode.sMessage, (QWidget*)(this->parent()), MSG_ICON_TYPE::mit_error);
         }
 	}
 }
@@ -208,7 +211,9 @@ void etMobileTest::onMobileStatus(__int64 nExamRecordDataId, int nOrder, QString
 		icon->setPixmap(QPixmap(":/images/icon-et-item-pass.png").scaled(g_appInfoPtr->m_fRate*12, g_appInfoPtr->m_fRate*9));
 		layout->addWidget(icon);
 		layout->addStretch();
-		ui->tablewt_envTest->setCellWidget(i, 2, widget);
+        ui->tablewt_envTest->setCellWidget(i, 2, widget);
+
+        ui->label_etmt_status->setText(QString::fromLocal8Bit("已扫码"));
 	}
 }
 
@@ -244,6 +249,8 @@ void etMobileTest::onSaveUploadedFileAcknowledgeStatus(CSaveUploadedFileAcknowle
 		layout->addStretch();
 		ui->tablewt_envTest->setCellWidget(i, 2, widget);
         m_nUploadStatus = 1;
+        ui->label_etmt_status->setText(QString::fromLocal8Bit("已上传"));
+        ShowMsg(QString::fromLocal8Bit("小程序作答已更新"), this, MSG_ICON_TYPE::mit_information);
 	}
 	else
 	{
@@ -259,6 +266,7 @@ void etMobileTest::onSaveUploadedFileAcknowledgeStatus(CSaveUploadedFileAcknowle
 		layout->addWidget(icon);
 		layout->addStretch();
 		ui->tablewt_envTest->setCellWidget(i, 2, widget);
+        ui->label_etmt_status->setText(QString::fromLocal8Bit("上传出错"));
 	}
 }
 

+ 8 - 0
client/etNetworkTest.cpp

@@ -106,6 +106,14 @@ void etNetworkTest::threadProc()
     DWORD	dwBandOut = 0;
     while(m_bIsRun)
     {
+        QString sFileName = sAudioUrl.right(sAudioUrl.length() - sAudioUrl.lastIndexOf("/") - 1);
+        sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+        CHttpRequestPackage hrp;
+        hrp.sUri = sAudioUrl;
+        hrp.sCommonStr = sFileName;
+        hrp.nRequestType = RequestType::rtDownLoadFile;
+        g_httpBllPtr->downLoad(hrp);
+
         GetIfTable(m_pTable, &m_dwAdapters, TRUE);
         DWORD	dwInOctets = 0;
         DWORD	dwOutOctets = 0;

+ 3 - 0
client/etNetworkTest.h

@@ -4,6 +4,7 @@
 #include <QWidget>
 #include <thread>
 #include <mutex>
+#include "CHttpBll.h"
 
 namespace Ui {
 class etNetworkTest;
@@ -30,6 +31,8 @@ private:
 
 	bool bSpeedPass;
 	bool bDelayPass;
+
+    const QString sAudioUrl = "https://ecs-static.qmth.com.cn/check-audio.mp3";
 };
 
 #endif // ETNETWORKTEST_H

+ 114 - 27
client/etVoiceTest.cpp

@@ -10,6 +10,7 @@
 #include <QFile>
 #include <QFileInfo>
 #include <QSettings>
+#include <QFileDialog>
 
 
 etVoiceTest::etVoiceTest(QWidget *parent) :
@@ -21,17 +22,19 @@ etVoiceTest::etVoiceTest(QWidget *parent) :
     setStyleSheet(g_appInfoPtr->m_sQssStr);
 
     m_nVoiceStatus = 2;
+	m_nAudioSecord = 0;
 
 	qRegisterMetaType<CDownLoadFileInfo>("CDownLoadFileInfo");
 	connect(g_httpBllPtr.get(), &CHttpBll::sgnDownLoadFile, this, &etVoiceTest::onDownLoadFile);
 
 	QString sFileName = sAudioUrl.right(sAudioUrl.length() - sAudioUrl.lastIndexOf("/") - 1);
 	sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+	m_sDownLoadAudioFile = sFileName;
 	CHttpRequestPackage hrp;
 	hrp.sUri = sAudioUrl;
 	hrp.sCommonStr = sFileName;
 	hrp.nRequestType = RequestType::rtDownLoadFile;
-
+    hrp.nRetryCount = 3;
 	g_httpBllPtr->downLoad(hrp);
 
 	QFileInfo file("coe.cfgi");
@@ -39,6 +42,8 @@ etVoiceTest::etVoiceTest(QWidget *parent) :
 	QSettings set(sFilePath, QSettings::IniFormat);
 
 	m_nPlayVol = set.value("config/playVol", 100).toInt();
+	
+	ui->hslider_etvt_volume->setValue(m_nPlayVol);
 
 	if (g_audioPalyerPtr == nullptr)
 	{
@@ -49,8 +54,14 @@ etVoiceTest::etVoiceTest(QWidget *parent) :
 	m_pTimer->setInterval(1000);
 	connect(m_pTimer.get(), &QTimer::timeout, this, [&]() {
 		ui->hslider_etvt_voice->setValue(ui->hslider_etvt_voice->value() + 1);
+		ui->label_etvt_time->setText(QString("%1:%2 / %3:%4").arg((int)(ui->hslider_etvt_voice->value() / 60), 2, 10, QChar('0'))
+			.arg((int)(ui->hslider_etvt_voice->value() % 60), 2, 10, QChar('0'))
+			.arg((int)(m_nAudioSecord / 60), 2, 10, QChar('0'))
+			.arg((int)(m_nAudioSecord % 60), 2, 10, QChar('0')));
+			
 		if (ui->hslider_etvt_voice->value() >= m_nAudioSecord)
 		{
+            ui->btn_etvt_play->setStyleSheet("border-image:url(:/images/btn-etvt-play.png);");
 			m_pTimer->stop();
 			g_audioPalyerPtr->stopPlay();
 		}
@@ -61,12 +72,12 @@ etVoiceTest::etVoiceTest(QWidget *parent) :
 			{
 				m_pTimer->stop();
 				QString sErrorStr = g_audioPalyerPtr->getErrMsg();
-				ShowMsg(sErrorStr, this);
+                ShowMsg(sErrorStr, this, MSG_ICON_TYPE::mit_error);
 			}
 
 			if (sDeciceName != m_sRecordDevice)
 			{
-				ShowMsg(QString::fromLocal8Bit("播放设备拔出,请检查设备!"), this);
+                ShowMsg(QString::fromLocal8Bit("播放设备拔出,请检查设备!"), this, MSG_ICON_TYPE::mit_error);
 				//            myDebug()<<QString::fromLocal8Bit("播放设备拔出,请检查设备!");
 			}
 		}
@@ -77,6 +88,7 @@ etVoiceTest::etVoiceTest(QWidget *parent) :
 
 etVoiceTest::~etVoiceTest()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -87,15 +99,21 @@ int etVoiceTest::setUI(const int nLeft, const int nTop, const int nWidth)
                                   g_appInfoPtr->m_fRate*423, g_appInfoPtr->m_fRate*40);
     ui->btn_etvt_play->setGeometry(g_appInfoPtr->m_fRate*8, (ui->widget_etvt_voice->height() - g_appInfoPtr->m_fRate*24)/2,
                                    g_appInfoPtr->m_fRate*24, g_appInfoPtr->m_fRate*24);
-    ui->hslider_etvt_voice->setGeometry(ui->btn_etvt_play->x() + ui->btn_etvt_play->width() + g_appInfoPtr->m_fRate*21,
+	ui->label_etvt_time->adjustSize();
+	ui->label_etvt_time->setGeometry(ui->btn_etvt_play->x() + ui->btn_etvt_play->width() + g_appInfoPtr->m_fRate * 10,
+		(ui->widget_etvt_voice->height() - ui->label_etvt_time->height()) / 2,
+		ui->label_etvt_time->width(), ui->label_etvt_time->height());
+    ui->hslider_etvt_voice->setGeometry(ui->label_etvt_time->x() + ui->label_etvt_time->width() + g_appInfoPtr->m_fRate*10,
                                         (ui->widget_etvt_voice->height() - g_appInfoPtr->m_fRate*12)/2,
-                                        g_appInfoPtr->m_fRate*284, g_appInfoPtr->m_fRate*12);
+                                        g_appInfoPtr->m_fRate*150, g_appInfoPtr->m_fRate*16);
     ui->btn_etvt_volume->setGeometry(ui->hslider_etvt_voice->x() + ui->hslider_etvt_voice->width() + g_appInfoPtr->m_fRate*20,
                                      ui->btn_etvt_play->y(), ui->btn_etvt_play->width(), ui->btn_etvt_play->height());
-    ui->btn_etvt_download->setGeometry(ui->btn_etvt_volume->x() + ui->btn_etvt_volume->width() + g_appInfoPtr->m_fRate*10,
+	ui->hslider_etvt_volume->setGeometry(ui->btn_etvt_volume->x() + ui->btn_etvt_volume->width() + g_appInfoPtr->m_fRate * 10, ui->hslider_etvt_voice->y(),
+		g_appInfoPtr->m_fRate*50, ui->hslider_etvt_voice->height());
+    ui->btn_etvt_download->setGeometry(ui->hslider_etvt_volume->x() + ui->hslider_etvt_volume->width() + g_appInfoPtr->m_fRate*10,
                                        ui->btn_etvt_play->y(), ui->btn_etvt_play->width(), ui->btn_etvt_play->height());
     ui->btn_etvt_canotPlay->setGeometry(ui->widget_etvt_voice->x() + ui->widget_etvt_voice->width() + g_appInfoPtr->m_fRate*20,
-                                        ui->widget_etvt_voice->y(), 141, g_appInfoPtr->m_fRate*40);
+                                        ui->widget_etvt_voice->y(), g_appInfoPtr->m_fRate*141, g_appInfoPtr->m_fRate*40);
     ui->btn_etvt_canotPlay->setIconSize(QSize(g_appInfoPtr->m_fRate*13, g_appInfoPtr->m_fRate*13));
     ui->btn_etvt_canotPlay->setIcon(QIcon(":/images/btn-can-not-play"));
     ui->btn_etvt_canPlay->setGeometry(ui->btn_etvt_canotPlay->x() + ui->btn_etvt_canotPlay->width() + g_appInfoPtr->m_fRate*10,
@@ -103,7 +121,7 @@ int etVoiceTest::setUI(const int nLeft, const int nTop, const int nWidth)
     ui->btn_etvt_canPlay->setIconSize(QSize(g_appInfoPtr->m_fRate*18, g_appInfoPtr->m_fRate*12));
     ui->btn_etvt_canPlay->setIcon(QIcon(":/images/btn_can-play.png"));
     ui->label_HLine->setGeometry(ui->widget_etvt_voice->x(), ui->widget_etvt_voice->y() + ui->widget_etvt_voice->height() + g_appInfoPtr->m_fRate*30,
-                                 width() - g_appInfoPtr->m_fRate*30*2, g_appInfoPtr->m_fRate*1);
+                                 width() - g_appInfoPtr->m_fRate*30*2, g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
 
     ui->tablewt_envTest->setGeometry(ui->widget_etvt_voice->x(), ui->label_HLine->y() + ui->label_HLine->height(),
                                      width() - g_appInfoPtr->m_fRate*30*2, height() - ui->label_HLine->y() - ui->label_HLine->height());
@@ -167,12 +185,13 @@ int etVoiceTest::widgetHeight()
 
 void etVoiceTest::onDownLoadFile(CDownLoadFileInfo downLoadFileInfo)
 {
-	QString sFileName = sAudioUrl.right(sAudioUrl.length() - sAudioUrl.lastIndexOf("/") - 1);
-	sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
-	if (downLoadFileInfo.sFileName == sFileName)
+	//QString sFileName = sAudioUrl.right(sAudioUrl.length() - sAudioUrl.lastIndexOf("/") - 1);
+	//sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+	if (downLoadFileInfo.sFileName == m_sDownLoadAudioFile)
 	{
 		if (downLoadFileInfo.nCode == 200)
 		{
+			initAudioDuration();
 			ui->tablewt_envTest->setItem(0, 0, new QTableWidgetItem(QString::fromLocal8Bit("文件下载")));
 			ui->tablewt_envTest->setItem(0, 1, new QTableWidgetItem(QString::fromLocal8Bit("正常")));
 			QWidget *widget = new QWidget;
@@ -233,39 +252,76 @@ void etVoiceTest::on_btn_etvt_canPlay_clicked()
     m_nVoiceStatus = 1;
 }
 
-void etVoiceTest::on_btn_etvt_play_clicked()
+void etVoiceTest::initAudioDuration()
 {
 	QString sFileName = sAudioUrl.right(sAudioUrl.length() - sAudioUrl.lastIndexOf("/") - 1);
 	sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
-	if (QFile::exists(sFileName))
-	{
-		m_nAudioSecord = g_audioPalyerPtr->GetMediaDuration(sFileName);
-		ui->hslider_etvt_voice->setMaximum(m_nAudioSecord);
-		ui->hslider_etvt_voice->setValue(0);
-		g_audioPalyerPtr->setVol(m_nPlayVol);
+	m_nAudioSecord = g_audioPalyerPtr->GetMediaDuration(sFileName);
+	ui->label_etvt_time->setText(QString("%1:%2 / %3:%4").arg(0, 2, 10, QChar('0'))
+		.arg(0, 2, 10, QChar('0'))
+		.arg((int)(m_nAudioSecord / 60), 2, 10, QChar('0'))
+		.arg((int)(m_nAudioSecord % 60), 2, 10, QChar('0')));
+	ui->hslider_etvt_voice->setMaximum(m_nAudioSecord);
+	ui->hslider_etvt_voice->setValue(0);
+}
 
-		if (!g_audioPalyerPtr->getDeviceinfo(m_sRecordDevice))
+void etVoiceTest::on_btn_etvt_play_clicked()
+{
+	if (m_pTimer->isActive())
+	{			
+		m_pTimer->stop();
+		g_audioPalyerPtr->pausePlay();
+		ui->btn_etvt_play->setStyleSheet("border-image:url(:/images/btn-etvt-play.png);");
+	}
+	else
+	{ 
+		if (m_nAudioSecord == 0 || ui->hslider_etvt_voice->value() == 0
+			|| ui->hslider_etvt_voice->value() >= m_nAudioSecord)
 		{
-			m_pTimer->stop();
-			QString sErrorStr = g_audioPalyerPtr->getErrMsg();
-			ShowMsg(sErrorStr, this);
-		}
+			QString sFileName = sAudioUrl.right(sAudioUrl.length() - sAudioUrl.lastIndexOf("/") - 1);
+			sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+			if (QFile::exists(sFileName))
+			{
+				ui->btn_etvt_play->setStyleSheet("border-image:url(:/images/btn-etvt-stop.png);");
+				initAudioDuration();
+				g_audioPalyerPtr->setVol(m_nPlayVol);
 
-		g_audioPalyerPtr->playAudio(sFileName);
-		m_pTimer->start();
+				if (!g_audioPalyerPtr->getDeviceinfo(m_sRecordDevice))
+				{
+					m_pTimer->stop();
+					QString sErrorStr = g_audioPalyerPtr->getErrMsg();
+					ShowMsg(sErrorStr, this);
+				}
 
+				g_audioPalyerPtr->playAudio(sFileName);
+				m_pTimer->start();
+
+			}
+		}
+		else
+		{
+			ui->btn_etvt_play->setStyleSheet("border-image:url(:/images/btn-etvt-stop.png);");
+			g_audioPalyerPtr->play();
+			m_pTimer->start();
+		}
 	}
+    
+
+
 }
 
 void etVoiceTest::on_btn_etvt_download_clicked()
 {
 	QString sFileName = sAudioUrl.right(sAudioUrl.length() - sAudioUrl.lastIndexOf("/") - 1);
-	sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+	sFileName = QFileDialog::getSaveFileName(
+		this, sAudioUrl, QFileInfo(sFileName).fileName(),
+		"All Files(*.*)");
+	m_sDownLoadAudioFile = sFileName;
 	CHttpRequestPackage hrp;
 	hrp.sUri = sAudioUrl;
 	hrp.sCommonStr = sFileName;
 	hrp.nRequestType = RequestType::rtDownLoadFile;
-
+    hrp.nRetryCount = 3;
 	g_httpBllPtr->downLoad(hrp);
 }
 
@@ -274,3 +330,34 @@ int etVoiceTest::getCheckStatus()
 {
     return m_nVoiceStatus;
 }
+
+void etVoiceTest::on_hslider_etvt_volume_valueChanged(int value)
+{    
+	QFileInfo file("coe.cfgi");
+	QString sFilePath = file.absoluteFilePath();
+	QSettings set(sFilePath, QSettings::IniFormat);
+
+	m_nPlayVol = value;
+	set.setValue("config/playVol", value);
+	if (m_pTimer && m_pTimer->isActive())
+	{
+		g_audioPalyerPtr->setVol(m_nPlayVol);
+	}
+    
+}
+
+void etVoiceTest::on_btn_etvt_volume_clicked()
+{
+    if(m_bMute)
+    {
+        ui->btn_etvt_volume->setStyleSheet("border-image:url(:/images/btn-etvt-volume.png);");
+        ui->hslider_etvt_volume->setValue(m_nLastVol);
+    }
+    else
+    {
+        ui->btn_etvt_volume->setStyleSheet("border-image:url(:/images/btn-etvt-mute.png);");
+        m_nLastVol = ui->hslider_etvt_volume->value();
+        ui->hslider_etvt_volume->setValue(0);
+    }
+    m_bMute = !m_bMute;
+}

+ 10 - 1
client/etVoiceTest.h

@@ -30,17 +30,26 @@ private slots:
 
     void on_btn_etvt_download_clicked();
 
+    void on_hslider_etvt_volume_valueChanged(int value);
+
+    void on_btn_etvt_volume_clicked();
+
 private:
+	void initAudioDuration();
+
     Ui::etVoiceTest *ui;
 
 	const QString sAudioUrl = "https://ecs-static.qmth.com.cn/check-audio.mp3";
 	int m_nPlayVol;
+    int m_nLastVol = 0;
+	QString m_sDownLoadAudioFile;
 
 	std::shared_ptr<QTimer> m_pTimer;
 	int m_nAudioSecord;
 	std::string m_sRecordDevice;
 
-    int m_nVoiceStatus;
+    int m_nVoiceStatus;    
+    bool m_bMute = false;
 };
 
 #endif // ETVOICETEST_H

+ 32 - 3
client/etVoiceTest.ui

@@ -38,9 +38,9 @@
    <widget class="QSlider" name="hslider_etvt_voice">
     <property name="geometry">
      <rect>
-      <x>60</x>
+      <x>120</x>
       <y>30</y>
-      <width>160</width>
+      <width>71</width>
       <height>22</height>
      </rect>
     </property>
@@ -51,7 +51,7 @@
    <widget class="QPushButton" name="btn_etvt_volume">
     <property name="geometry">
      <rect>
-      <x>240</x>
+      <x>200</x>
       <y>30</y>
       <width>31</width>
       <height>23</height>
@@ -74,6 +74,35 @@
      <string/>
     </property>
    </widget>
+   <widget class="QSlider" name="hslider_etvt_volume">
+    <property name="geometry">
+     <rect>
+      <x>240</x>
+      <y>30</y>
+      <width>51</width>
+      <height>22</height>
+     </rect>
+    </property>
+    <property name="maximum">
+     <number>100</number>
+    </property>
+    <property name="orientation">
+     <enum>Qt::Horizontal</enum>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label_etvt_time">
+    <property name="geometry">
+     <rect>
+      <x>50</x>
+      <y>30</y>
+      <width>71</width>
+      <height>16</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>00:00 / 00:00</string>
+    </property>
+   </widget>
   </widget>
   <widget class="QToolButton" name="btn_etvt_canotPlay">
    <property name="geometry">

BIN
client/images/btn-etvt-mute.png


BIN
client/images/btn-play_disable.png


BIN
client/images/btn-prev-photo.png


BIN
client/images/icon-aw-close.png


BIN
client/images/icon-aw-minisize.png


BIN
client/images/icon-um-info.png


BIN
client/images/qm-logo.png


+ 151 - 25
client/login.cpp

@@ -11,7 +11,7 @@
 #include "logproc.h"
 #include "awMsgBox.h"
 #include "CCommonTools.h"
-
+#include "CLiveViodeProc.h"
 #include <QDateTime>
 #include "CKeyBoardHook.h"
 
@@ -43,7 +43,7 @@ login::login(QWidget *parent)
     qssFile.open(QFile::ReadOnly);
     QString qss;
     qss = qssFile.readAll();
-    int RATE_BASE_SIZE1 = fRate;
+    int RATE_BASE_SIZE1 = fRate < 1? 1 : fRate;
     int RATE_BASE_SIZE2 = 2*fRate;
     int RATE_BASE_SIZE3 = 3*fRate;
     int RATE_BASE_SIZE4 = 4*fRate;
@@ -99,7 +99,7 @@ login::login(QWidget *parent)
     qss.replace("RATE_BASE_SIZE1", QString::number(RATE_BASE_SIZE1));
     g_appInfoPtr->m_sQssStr = qss;
     setStyleSheet(qss);
-
+    qDebug()<<qss;
 	ui->widget_BG->setVisible(false);
 	setGeometry(0, 0, dekwiget->width(), dekwiget->height());
 	m_pCourseList = nullptr;
@@ -114,17 +114,22 @@ login::login(QWidget *parent)
 	connect(g_httpBllPtr.get(), &CHttpBll::sgnLoginInfo, this, &login::onLoginInfo);
 	qRegisterMetaType<CLogout>("CLogout");
 	connect(g_httpBllPtr.get(), &CHttpBll::sgnLogout, this, &login::onLogout);
-
+	qRegisterMetaType<CDownLoadFileInfo>("CDownLoadFileInfo");
+	connect(g_httpBllPtr.get(), &CHttpBll::sgnDownLoadFile, this, &login::onDownLoadFile);
+    connect(g_httpBllPtr.get(), &CHttpBll::sgnTokenExpired, this, &login::onTokenExpired);
 
 	CHttpRequestPackage hrp;
-	hrp.sUri = QString("https://ecs-test-static.qmth.com.cn/org_properties/byOrgDomain/%1.ecs.qmth.com.cn/studentClientConfig.json").arg(g_appInfoPtr->m_sOrgPrefix);
+    hrp.sUri = QString("https://%1/org_properties/byOrgDomain/%2.ecs.qmth.com.cn/studentClientConfig.json")
+            .arg(g_appInfoPtr->m_sEscDomain).arg(g_appInfoPtr->m_sOrgPrefix);
 	hrp.nRequestType = RequestType::rtStudentClientConfig;
-	g_httpBllPtr->getUrl(hrp);
-		
+    g_httpBllPtr->getUrl(hrp);
+
+    g_clientVideoProcPtr = std::make_shared<CLiveViodeProc>();
 }
 
 login::~login()
 {
+    awMsgBox::clear();
     delete ui;
 }
 
@@ -135,7 +140,7 @@ void login::initParam()
     g_sysDBProcPtr = std::make_shared<CSqlite3DBProc>();
     if(!g_sysDBProcPtr->OpenDB("data/sys.db"))
     {
-        ShowMsg(QString::fromLocal8Bit("打开数据库失败!"), this);
+        ShowMsg(QString::fromLocal8Bit("打开数据库失败!"), this, MSG_ICON_TYPE::mit_error);
 //        myDebug()<<QString::fromLocal8Bit("打开数据库失败!");
     }
 
@@ -143,7 +148,7 @@ void login::initParam()
     QString sSql = QString("select name,ifnull(value,'') from t_param");
     if(!g_sysDBProcPtr->QuerySql(sSql.toStdString(), &stmt))
     {
-        ShowMsg(QString::fromLocal8Bit("加载参数信息失败!"), this);
+        ShowMsg(QString::fromLocal8Bit("加载参数信息失败!"), this, MSG_ICON_TYPE::mit_error);
 //        myDebug()<<QString::fromLocal8Bit("查询参数信息失败!");
         return;
     }
@@ -215,7 +220,7 @@ void login::initParam()
     }
     else
     {
-//        g_logPtr = std::make_shared<CLogTrack>(sProjectName, sEndpoint, sLogstore);
+        g_logPtr = std::make_shared<CLogTrack>(sProjectName, sEndpoint, sLogstore);
     }
 
     g_appInfoPtr->m_sHttpServer = sHttpServer;
@@ -233,7 +238,7 @@ void login::initParam()
 	sSql = QString("select version_id,version_code,ifnull(machine_id,''),ifnull(update_time,'') from t_sysinfo");
 	if (!g_sysDBProcPtr->QuerySql(sSql.toStdString(), &stmt))
 	{
-		ShowMsg(QString::fromLocal8Bit("加载参数信息失败!"), this);
+        ShowMsg(QString::fromLocal8Bit("加载参数信息失败!"), this, MSG_ICON_TYPE::mit_error);
 		//        myDebug()<<QString::fromLocal8Bit("查询参数信息失败!");
 		return;
 	}
@@ -268,7 +273,7 @@ void login::initParam()
 		sSql = QString("update t_sysinfo set machine_id = '%1'").arg(g_appInfoPtr->m_sMachineId);
 		if (!g_sysDBProcPtr->ExcuteSql(sSql.toStdString()))
 		{
-			ShowMsg(QString::fromLocal8Bit("保存机器信息失败!"), this);
+            ShowMsg(QString::fromLocal8Bit("保存机器信息失败!"), this, MSG_ICON_TYPE::mit_error);
 			//myDebug() << QString::fromLocal8Bit("保存机器信息失败!");
 			return;
 		}
@@ -282,7 +287,9 @@ void login::initUI()
     ui->widget_BG->setGeometry(0, 0, dekwiget->width(), dekwiget->height());
     ui->btn_close->setGeometry(width() - g_appInfoPtr->m_fRate*24, 0,
                                g_appInfoPtr->m_fRate*24, g_appInfoPtr->m_fRate*24);
-    ui->widget_loginBG->setGeometry((width() - g_appInfoPtr->m_fRate*800)/2, (height() - g_appInfoPtr->m_fRate*480)/2,
+	ui->label_org_logo->setGeometry(g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 20, g_appInfoPtr->m_fRate * 80, g_appInfoPtr->m_fRate * 80);
+	ui->label_org_bg->setGeometry(0, g_appInfoPtr->m_fRate * (100+10), ui->widget_BG->width(), ui->widget_BG->height() - g_appInfoPtr->m_fRate*(100 + 10));
+	ui->widget_loginBG->setGeometry((width() - g_appInfoPtr->m_fRate*800)/2, (height() - g_appInfoPtr->m_fRate*480)/2,
                                     g_appInfoPtr->m_fRate*800, g_appInfoPtr->m_fRate*480);
     ui->widget_orgInfo->setGeometry(0,0, g_appInfoPtr->m_fRate*300, ui->widget_loginBG->height());
     ui->label_orgTitle->adjustSize();
@@ -294,6 +301,10 @@ void login::initUI()
     ui->label_complay->adjustSize();
     ui->label_complay->setGeometry(g_appInfoPtr->m_fRate*42, ui->widget_orgInfo->height() - g_appInfoPtr->m_fRate*42 - ui->label_complay->height(),
                                    ui->label_complay->width(), ui->label_complay->height());
+    if(!g_appInfoPtr->m_sVersionCode.isEmpty())
+    {
+        ui->label_version->setText(QString("V%1").arg(g_appInfoPtr->m_sVersionCode));
+    }
     ui->label_version->adjustSize();
     ui->label_version->setGeometry(ui->label_complay->x(), ui->label_complay->y() - g_appInfoPtr->m_fRate*5 - ui->label_version->height(),
                                    ui->label_version->width(), ui->label_version->height());
@@ -307,6 +318,18 @@ void login::initUI()
 			ui->btn_studentCodeLogin->width(), ui->btn_studentCodeLogin->height());
 	}
 
+    if(m_nloginTypeCount < 2)
+    {
+        if(m_loginType == LOGIN_TYPE::lt_studentCode)
+        {
+            ui->btn_studentCodeLogin->setGeometry(0, 0, ui->widget_login->width(), g_appInfoPtr->m_fRate * 44);
+        }
+
+        if(m_loginType == LOGIN_TYPE::lt_identity)
+        {
+            ui->btn_identityLogin->setGeometry(0, 0, ui->widget_login->width(), g_appInfoPtr->m_fRate * 44);
+        }
+    }
 	
     ui->label_loginTitle->adjustSize();
     ui->label_loginTitle->setGeometry((ui->widget_login->width() - ui->label_loginTitle->width())/2,
@@ -391,6 +414,7 @@ void login::onStudentClientConfig(CStudentClientConfig studentClientConfig)
 		ui->btn_identityLogin->setVisible(false);
 		if (studentClientConfig.bStudentCodeLogin )
 		{
+            ++m_nloginTypeCount;
 			ui->btn_studentCodeLogin->setVisible(true);
 			ui->btn_studentCodeLogin->setText(studentClientConfig.sStudentCodeLoginAlias);
 			m_loginType = LOGIN_TYPE::lt_studentCode;
@@ -402,6 +426,7 @@ void login::onStudentClientConfig(CStudentClientConfig studentClientConfig)
 
 		if (studentClientConfig.bIdentifyNumberLogin)
 		{
+            ++m_nloginTypeCount;
 			ui->btn_identityLogin->setVisible(true);
 			ui->btn_identityLogin->setText(studentClientConfig.sIdentityNumberLoginAlias);
 		}
@@ -414,7 +439,46 @@ void login::onStudentClientConfig(CStudentClientConfig studentClientConfig)
 		g_appInfoPtr->m_sRootOrgId = studentClientConfig.sRootOrgId;
 		g_appInfoPtr->m_bShowQmthLogo = studentClientConfig.bShowQmthLogo;
 		g_appInfoPtr->m_bShowStudentClientAppQrcode = studentClientConfig.bShowStudentClientAppQrcode;
-       
+        if(studentClientConfig.bIsCustomMenuLogo)
+        {
+            g_appInfoPtr->m_sMenuLogoUrl = studentClientConfig.sCusMenuLogoFileUrl;
+        }  
+
+		initUI();
+
+		if (!studentClientConfig.sStudentClientBgPictureUrl.isEmpty())
+		{
+			m_sStudentClientBgPictureUrl = studentClientConfig.sStudentClientBgPictureUrl;
+			QString sFileName = m_sStudentClientBgPictureUrl.right(m_sStudentClientBgPictureUrl.length() - m_sStudentClientBgPictureUrl.lastIndexOf("/") - 1);
+			sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+			CHttpRequestPackage hrp;
+			hrp.sUri = studentClientConfig.sStudentClientBgPictureUrl;
+			hrp.sCommonStr = sFileName;
+			hrp.sAdditionStr = "downloadAnswerSheet";
+			hrp.sCommonStr1 = __FILE__;
+			hrp.nRequestType = RequestType::rtDownLoadFile;
+			hrp.nRetryCount = 3;
+			g_httpBllPtr->downLoad(hrp);
+		}
+
+		if (!studentClientConfig.sLogoFileUrl.isEmpty())
+		{
+			m_sLogoFileUrl = studentClientConfig.sLogoFileUrl;
+			QString sFileName = m_sLogoFileUrl.right(m_sLogoFileUrl.length() - m_sLogoFileUrl.lastIndexOf("/") - 1);
+			sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+			CHttpRequestPackage hrp;
+			hrp.sUri = studentClientConfig.sLogoFileUrl;
+			hrp.sCommonStr = sFileName;			
+			hrp.sCommonStr1 = __FILE__;
+			hrp.nRequestType = RequestType::rtDownLoadFile;
+			hrp.nRetryCount = 3;
+			g_httpBllPtr->downLoad(hrp);
+		}
+		
+#ifdef _DEBUG
+		g_appInfoPtr->m_bFullScreenTop = false;
+#endif // _DEBUG
+
         if(g_appInfoPtr->m_bFullScreenTop)
         {
             CCommonTools::topMostSlot(this, true);
@@ -422,33 +486,36 @@ void login::onStudentClientConfig(CStudentClientConfig studentClientConfig)
             g_keyBoardHookPtr = std::make_shared<CKeyBoardHook>();
             g_keyBoardHookPtr->Hotkey_Install(0);
         }
-                
+  
         
-		initUI();
+		
 	}
 	else
 	{
-		ShowMsg(QString::fromLocal8Bit("获取系统参数失败,请检查网络连接!"), this);
+        ShowMsg(QString::fromLocal8Bit("获取系统参数失败,请检查网络连接!"), this, MSG_ICON_TYPE::mit_error);
 		if (m_pLoading != nullptr)
 		{
 			m_pLoading.reset();
 		}
+        QTimer::singleShot(3000, this, [&](){close();});
 	}
 }
 
 void login::on_btn_login_clicked()
-{    
+{
 	if (ui->btn_studentCodeLogin->text().isEmpty() ||
 		ui->btn_identityLogin->text().isEmpty())
 	{
-		ShowMsg(QString::fromLocal8Bit("请填写账号和密码"), this);
+        ShowMsg(QString::fromLocal8Bit("请填写账号和密码"), this, MSG_ICON_TYPE::mit_error);
 		return;
 	}
 
+    ui->btn_login->setEnabled(false);
+    m_nRetryCount = 0;
 	//登录限流
 	CHttpRequestPackage hrp;
 	hrp.sUri = QString("https://tcc.qmth.com.cn/rate_limit/prod/login/%1").arg(500);
-	hrp.nRequestType = RequestType::rtLoginLimit;
+	hrp.nRequestType = RequestType::rtLoginLimit;    
 	g_httpBllPtr->getUrl(hrp);
    	
 }
@@ -457,6 +524,7 @@ void login::onLoginLimit(CLoginLimit loginLimit)
 {
 	if (loginLimit.nCode == 200)
 	{
+        ++m_nRetryCount;
 		if (loginLimit.bPass)
 		{
 			//登录
@@ -473,23 +541,56 @@ void login::onLoginLimit(CLoginLimit loginLimit)
 		else
 		{
 			//等待重试
+            if(m_nRetryCount >= 3)
+            {
+                ShowMsg(QString::fromLocal8Bit("系统繁忙,请稍后重试"), this, MSG_ICON_TYPE::mit_error);
+                ui->btn_login->setEnabled(true);
+            }
+            else
+            {
+                QTimer::singleShot(3000, this, [](){
+                    //登录限流
+                    CHttpRequestPackage hrp;
+                    hrp.sUri = QString("https://tcc.qmth.com.cn/rate_limit/prod/login/%1").arg(500);
+                    hrp.nRequestType = RequestType::rtLoginLimit;
+                    g_httpBllPtr->getUrl(hrp);
+                });
+            }
 		}
 	}
 	else
     {
         if(loginLimit.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("登录失败"), this);
+            ShowMsg(QString::fromLocal8Bit("登录失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(loginLimit.sMessage, this);
+            ShowMsg(loginLimit.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
+        ui->btn_login->setEnabled(true);
 	}
 }
 
+void login::onTokenExpired()
+{
+   if(m_pCourseList != nullptr)
+   {
+       m_pCourseList.reset();
+   }
+
+   if (m_pLoading != nullptr)
+   {
+       m_pLoading.reset();
+   }
+
+   ShowMsg(QString::fromLocal8Bit("登录失效,请重新登录"), this, MSG_ICON_TYPE::mit_error);
+}
+
 void login::onLoginInfo(CLoginInfo loginInfo)
 {
+    ui->btn_login->setEnabled(true);
+
 	if (loginInfo.nCode == 200)
 	{
 		g_appInfoPtr->m_nStudentId = loginInfo.nUserId;
@@ -500,21 +601,23 @@ void login::onLoginInfo(CLoginInfo loginInfo)
 		}
 
 		m_pCourseList->show();
+		ui->edt_account->setText("");
+		ui->edt_password->setText("");
 	}
 	else
 	{
 		if (loginInfo.sMessage != "")
 		{
-			ShowMsg(loginInfo.sMessage, this);
+            ShowMsg(loginInfo.sMessage, this, MSG_ICON_TYPE::mit_error);
 		}
 		else
 		{
-			ShowMsg(QString::fromLocal8Bit("登录失败"), this);
+            ShowMsg(QString::fromLocal8Bit("登录失败"), this, MSG_ICON_TYPE::mit_error);
 		}
 	}
 }
 
-void login::onLogout(CLogout logout)
+void login::onLogout(CLogout )
 {
 	if (m_pCourseList !=nullptr)
 	{
@@ -533,3 +636,26 @@ void login::on_btn_identityLogin_clicked()
     m_loginType = LOGIN_TYPE::lt_identity;
     setBtnStyle();
 }
+
+void login::onDownLoadFile(CDownLoadFileInfo downLoadFileInfo)
+{
+	if (downLoadFileInfo.sModuleName == __FILE__)
+	{
+		if (downLoadFileInfo.nCode == 200)
+		{
+			QString sLogoFileName = m_sLogoFileUrl.right(m_sLogoFileUrl.length() - m_sLogoFileUrl.lastIndexOf("/") - 1);
+			sLogoFileName = g_appInfoPtr->m_sCacheFileDir + sLogoFileName;
+			QString sClientBgPictureFileName = m_sStudentClientBgPictureUrl.right(m_sStudentClientBgPictureUrl.length() - m_sStudentClientBgPictureUrl.lastIndexOf("/") - 1);
+			sClientBgPictureFileName = g_appInfoPtr->m_sCacheFileDir + sClientBgPictureFileName;
+			if (sLogoFileName == downLoadFileInfo.sFileName)
+			{
+				ui->label_org_logo->setPixmap(QPixmap(sLogoFileName).scaled(ui->label_org_logo->width(), ui->label_org_logo->height(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
+			}
+
+			if (sClientBgPictureFileName == downLoadFileInfo.sFileName)
+			{
+				ui->label_org_bg->setPixmap(QPixmap(sClientBgPictureFileName).scaled(ui->label_org_bg->width(), ui->label_org_bg->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+			}
+		}
+	}
+}

+ 8 - 0
client/login.h

@@ -38,6 +38,8 @@ private slots:
 	void onLoginLimit(CLoginLimit loginLimit); 
 	void onLoginInfo(CLoginInfo loginInfo);
 	void onLogout(CLogout logout);
+	void onDownLoadFile(CDownLoadFileInfo downLoadFileInfo);
+    void onTokenExpired();
 private:
     void initUI();
     void initParam();
@@ -46,8 +48,14 @@ private:
     Ui::login *ui;
 	std::shared_ptr<QTimer> m_pUiTimer; 
 	LOGIN_TYPE m_loginType;
+    int m_nloginTypeCount = 0;
 
     std::shared_ptr<courseList> m_pCourseList;
 	std::shared_ptr<awResumeExam> m_pLoading;
+
+    int m_nRetryCount;
+	QString m_sStudentClientBgPictureUrl;
+	QString m_sLogoFileUrl;
+
 };
 #endif // LOGIN_H

+ 37 - 5
client/login.ui

@@ -31,6 +31,35 @@
       <height>23</height>
      </rect>
     </property>
+    <property name="focusPolicy">
+     <enum>Qt::NoFocus</enum>
+    </property>
+    <property name="text">
+     <string/>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label_org_logo">
+    <property name="geometry">
+     <rect>
+      <x>60</x>
+      <y>20</y>
+      <width>251</width>
+      <height>91</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string/>
+    </property>
+   </widget>
+   <widget class="QLabel" name="label_org_bg">
+    <property name="geometry">
+     <rect>
+      <x>0</x>
+      <y>130</y>
+      <width>791</width>
+      <height>461</height>
+     </rect>
+    </property>
     <property name="text">
      <string/>
     </property>
@@ -38,8 +67,8 @@
    <widget class="QWidget" name="widget_loginBG" native="true">
     <property name="geometry">
      <rect>
-      <x>160</x>
-      <y>170</y>
+      <x>150</x>
+      <y>150</y>
       <width>481</width>
       <height>361</height>
      </rect>
@@ -58,12 +87,12 @@
        <rect>
         <x>30</x>
         <y>40</y>
-        <width>54</width>
-        <height>12</height>
+        <width>111</width>
+        <height>16</height>
        </rect>
       </property>
       <property name="text">
-       <string/>
+       <string>网考学生端</string>
       </property>
      </widget>
      <widget class="QLabel" name="label_version">
@@ -140,6 +169,9 @@
       <property name="text">
        <string>在线考试平台</string>
       </property>
+      <property name="wordWrap">
+       <bool>true</bool>
+      </property>
      </widget>
      <widget class="QLineEdit" name="edt_account">
       <property name="geometry">

+ 50 - 5
client/main.cpp

@@ -1,11 +1,14 @@
 #include "login.h"
 
 #include <QApplication>
+#include <QFileInfo>
+#include <QSettings>
 #include "logproc.h"
 #include <windows.h>
 #include <DbgHelp.h>
 #include "CLogTrack.h"
-
+#include "client/windows/handler/exception_handler.h"
+/*
 long  __stdcall CrashInfocallback(_EXCEPTION_POINTERS *pexcp)
 {
     //创建 Dump 文件
@@ -56,25 +59,67 @@ long  __stdcall CrashInfocallback(_EXCEPTION_POINTERS *pexcp)
 
     return 0;
 }
+*/
+bool callback(const wchar_t *dump_path, const wchar_t *id,
+	void *context, EXCEPTION_POINTERS *exinfo,
+	MDRawAssertionInfo *assertion,
+	bool succeeded)
+{
+	EXCEPTION_RECORD* record = exinfo->ExceptionRecord;
+	QString errCode(QString::number(record->ExceptionCode, 16)),
+		errAdr(QString::number((uint)record->ExceptionAddress, 16));
+
+	HWND task = nullptr;
+	task = FindWindow(L"Shell_TrayWnd", nullptr);
+	if (task)
+	{
+		ShowWindow(task, SW_SHOW);//隐藏任务栏
+	}
+
+	QMessageBox::critical(NULL, QString::fromLocal8Bit("程序崩溃"),
+		QString::fromLocal8Bit("<FONT size=4><div><b>对于发生的错误,表示诚挚的歉意</b><br/></div>") +
+		QString::fromLocal8Bit("<div>错误代码:%1</div><div>错误地址:%2</div></FONT>").arg(errCode).arg(errAdr),
+		QMessageBox::Ok);
+	myDebug() << QString::fromLocal8Bit("程序崩溃,错误代码:%1,错误地址:%2").arg(errCode).arg(errAdr);
+
+	return succeeded;
+}
+/*
+int mydiv(int x, int y)
+{
+	int *z=0;
+	*z = x / y;
+	return *z;
+}
+*/
 
 int main(int argc, char *argv[])
 {    
 	QApplication a(argc, argv);
 
 	
-	::SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)CrashInfocallback);
+	//::SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)CrashInfocallback);
+
+	
 
 	g_appInfoPtr = std::make_shared<CAppInfo>();	
 
-/*	QFileInfo file("coe.cfgi");
+    QFileInfo file("coe.cfgi");
 	QString sFilePath = file.absoluteFilePath();
 	QSettings set(sFilePath, QSettings::IniFormat);
 
-	g_appInfoPtr->m_nShowDebugInfo = set.value("config/debug", false).toBool();
-	*/
+    g_appInfoPtr->m_bShowDebugInfo = set.value("config/debug", false).toBool();
+
 
 	qInstallMessageHandler(CustomOutputMessage);
 
+	google_breakpad::ExceptionHandler eh(
+		L".", NULL, callback, NULL,
+		google_breakpad::ExceptionHandler::HANDLER_ALL);
+		
+//	printf("9/3=%d\n", mydiv(9, 3));
+	//printf("9/0=%d\n", mydiv(9, 0));  //程序将在此崩溃
+	
 	login w;
 	w.show();
 

+ 65 - 19
client/privacyWidget.cpp

@@ -10,7 +10,7 @@
 #include "logproc.h"
 #include "CSqlite3DBProc.h"
 
-
+#include "CCommonTools.h"
 privacyWidget::privacyWidget(QWidget *parent) :
     QWidget(parent),
     ui(new Ui::privacyWidget)
@@ -25,7 +25,7 @@ privacyWidget::privacyWidget(QWidget *parent) :
 	connect(g_httpBllPtr.get(), &CHttpBll::sgnAgreement, this, &privacyWidget::onAgreement);
 
 	CHttpRequestPackage hrp;
-	hrp.sUri = "https://ecs-test-static.qmth.com.cn/oe_client/pc/agreement.json";
+    hrp.sUri = QString("https://%1/oe_client/pc/agreement.json").arg(g_appInfoPtr->m_sEscDomain);
 	hrp.nRequestType = RequestType::rtAgreement;
 	g_httpBllPtr->getUrl(hrp);
 
@@ -37,6 +37,19 @@ privacyWidget::privacyWidget(QWidget *parent) :
 		{
 			m_pTimer->stop();
 			ui->btn_pw_agree->setText(QString::fromLocal8Bit("同意"));
+            QString sStyle = QString(R"(QPushButton#btn_pw_agree
+            {
+                outline:none;
+                background:rgba(19,186,140,1);
+                border-radius:%1px;
+                font-size:%2px;
+                font-family:"Microsoft YaHei";
+                font-weight:400;
+                color:rgba(255,255,255,1);
+            })").arg((int)(g_appInfoPtr->m_fRate*6))
+                    .arg((int)(g_appInfoPtr->m_fRate*14));
+            ui->btn_pw_agree->setStyleSheet(sStyle);
+
 		}
 		else
 		{
@@ -48,6 +61,7 @@ privacyWidget::privacyWidget(QWidget *parent) :
 
 privacyWidget::~privacyWidget()
 {
+    awMsgBox::clear(this);
     delete ui;
 }
 
@@ -108,7 +122,7 @@ void privacyWidget::initAgreement(QString sContent)
 	Json::Value jsonRoot = Json::Value::null;
 	if (!reader.parse(sContent.toStdString(), jsonRoot))
 	{
-		ShowMsg(QString::fromLocal8Bit("加载隐私协议出错"), this);
+        ShowMsg(QString::fromLocal8Bit("加载隐私协议出错"), this, MSG_ICON_TYPE::mit_error);
 		qDebug() << sContent;
 	}
 	QTextCursor cursor = ui->txtb_pw_privacy->textCursor();
@@ -173,19 +187,51 @@ void privacyWidget::initAgreement(QString sContent)
 			}
 			else if (blocks["blocks"][j]["type"].asString() == "image")
 			{
-				QString strHtml = "";
-				if (blocks["blocks"][j]["param"].isMember("width"))
-				{
-					strHtml = QString("<img src=\"%1\" height=\"%2\" width=\"%3\" align=\"bottom\" />")
-						.arg(blocks["blocks"][j]["value"].asString().c_str())
-						.arg(blocks["blocks"][j]["param"]["height"].asString().c_str())
-						.arg(blocks["blocks"][j]["param"]["width"].asString().c_str());
-				}
-				else
-				{
-					strHtml = "<img src=\"" + QString::fromLocal8Bit(blocks["blocks"][j]["value"].asString().c_str()) + "\"  align=\"bottom\" />";
-				}
-				cursor.insertHtml(strHtml);
+                QString sImage = blocks["blocks"][j]["value"].asString().c_str();
+                if(!(sImage.startsWith("http") || sImage.startsWith("data:image")))
+                {
+                    sImage = QString("temp/paper/attachment/")  + sImage;
+                }
+
+                if(sImage.startsWith("data:image"))
+                {
+                    QImage img;
+                    img = QImage::fromData(QByteArray::fromBase64(CCommonTools::getImageRawBase64Str(sImage).toLatin1()));
+                    if(blocks["blocks"][j]["param"].isMember("width"))
+                    {
+                        img = img.scaled(QString(blocks["blocks"][j]["param"]["width"].asString().c_str()).toInt(),
+                                QString(blocks["blocks"][j]["param"]["height"].asString().c_str()).toInt(),
+                                Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+
+                    }
+                    cursor.insertImage(img);
+                }
+                else
+                {
+                    QImage img(sImage);
+                    if(blocks["blocks"][j]["param"].isMember("width"))
+                    {
+                        img = img.scaled(QString(blocks["blocks"][j]["param"]["width"].asString().c_str()).toInt(),
+                                QString(blocks["blocks"][j]["param"]["height"].asString().c_str()).toInt(),
+                                Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+
+                    }
+                    cursor.insertImage(img);
+                }
+
+//				QString strHtml = "";
+//				if (blocks["blocks"][j]["param"].isMember("width"))
+//				{
+//					strHtml = QString("<img src=\"%1\" height=\"%2\" width=\"%3\" align=\"bottom\" />")
+//						.arg(blocks["blocks"][j]["value"].asString().c_str())
+//						.arg(blocks["blocks"][j]["param"]["height"].asString().c_str())
+//						.arg(blocks["blocks"][j]["param"]["width"].asString().c_str());
+//				}
+//				else
+//				{
+//					strHtml = "<img src=\"" + QString::fromLocal8Bit(blocks["blocks"][j]["value"].asString().c_str()) + "\"  align=\"bottom\" />";
+//				}
+//				cursor.insertHtml(strHtml);
 			}
 		}
 		if (i < nSize - 1)
@@ -210,11 +256,11 @@ void privacyWidget::initUI()
                                     ui->label_pw_title->width(), ui->label_pw_title->height());
     ui->btn_pw_close->setGeometry(ui->widget_pw_BG->width() - g_appInfoPtr->m_fRate*(20 + 16), g_appInfoPtr->m_fRate*19,
                                   g_appInfoPtr->m_fRate*16, g_appInfoPtr->m_fRate*16);
-    ui->label_HLine_top->setGeometry(0, g_appInfoPtr->m_fRate*54, ui->widget_pw_BG->width(), g_appInfoPtr->m_fRate*1);
+    ui->label_HLine_top->setGeometry(0, g_appInfoPtr->m_fRate*54, ui->widget_pw_BG->width(), g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
     ui->txtb_pw_privacy->setGeometry(0, ui->label_HLine_top->y() + ui->label_HLine_top->height() + g_appInfoPtr->m_fRate*20,
                                      ui->widget_pw_BG->width(), g_appInfoPtr->m_fRate*432);
     ui->label_HLine_bottom->setGeometry(0, ui->txtb_pw_privacy->y() + ui->txtb_pw_privacy->height() + g_appInfoPtr->m_fRate*19,
-                                        ui->widget_pw_BG->width(), g_appInfoPtr->m_fRate*1);
+                                        ui->widget_pw_BG->width(), g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
     ui->btn_pw_agree->setGeometry(g_appInfoPtr->m_fRate*185, ui->label_HLine_bottom->y() + ui->label_HLine_bottom->height() + g_appInfoPtr->m_fRate*12,
                                   g_appInfoPtr->m_fRate*160, g_appInfoPtr->m_fRate*40);
     ui->btn_pw_disagree->setGeometry(ui->btn_pw_agree->x() + ui->btn_pw_agree->width() + g_appInfoPtr->m_fRate*10, ui->btn_pw_agree->y(),
@@ -248,7 +294,7 @@ void privacyWidget::on_btn_pw_agree_clicked()
 	QString sSql = QString("insert or replace into t_agreement (student_id,agreement) values(%1,1)").arg(g_appInfoPtr->m_nStudentId);
 	if (!g_sysDBProcPtr->ExcuteSql(sSql.toStdString()))
 	{
-		ShowMsg(QString::fromLocal8Bit("保存隐私协议信息失败!"), this);
+        ShowMsg(QString::fromLocal8Bit("保存隐私协议信息失败!"), this, MSG_ICON_TYPE::mit_error);
 	}
     emit agreement();
 }

+ 6 - 0
client/rc.qrc

@@ -63,5 +63,11 @@
         <file>images/img-fl-action.png</file>
         <file>images/img-fl-face.png</file>
         <file>images/icon-liveness-faild.png</file>
+        <file>images/qm-logo.png</file>
+        <file>images/btn-play_disable.png</file>
+        <file>images/icon-um-info.png</file>
+        <file>images/icon-aw-close.png</file>
+        <file>images/icon-aw-minisize.png</file>
+        <file>images/btn-etvt-mute.png</file>
     </qresource>
 </RCC>

+ 27 - 4
client/welcomeWidget.cpp

@@ -32,8 +32,8 @@ void welcomeWidget::initUI()
     QDesktopWidget *dekwiget = QApplication::desktop();
     setGeometry(0, 0, dekwiget->width(), dekwiget->height());
     ui->widget_mask->setGeometry(0, 0, dekwiget->width(), dekwiget->height());
-    ui->widget_wcw_BG->setGeometry((width() - g_appInfoPtr->m_fRate*420)/2, (height() - g_appInfoPtr->m_fRate*212)/2,
-                                   g_appInfoPtr->m_fRate*420, g_appInfoPtr->m_fRate*212);
+    ui->widget_wcw_BG->setGeometry((width() - g_appInfoPtr->m_fRate*100)/2, (height() - g_appInfoPtr->m_fRate*212)/2,
+                                   g_appInfoPtr->m_fRate*100, g_appInfoPtr->m_fRate*212);
     ui->label_wcw_icon->setGeometry(g_appInfoPtr->m_fRate*30, g_appInfoPtr->m_fRate*30,
                                     g_appInfoPtr->m_fRate*40, g_appInfoPtr->m_fRate*40);
     ui->label_wcw_tips->adjustSize();
@@ -45,8 +45,31 @@ void welcomeWidget::initUI()
     ui->label_wcw_specialty->adjustSize();
     ui->label_wcw_specialty->setGeometry(ui->label_wcw_tips->x(), ui->label_wcw_studentName->y() + ui->label_wcw_studentName->height() + g_appInfoPtr->m_fRate*10,
                                          ui->label_wcw_specialty->width(), ui->label_wcw_specialty->height());
-    ui->btn_wcw_comfirm->setGeometry(ui->widget_wcw_BG->width() - g_appInfoPtr->m_fRate*(20 + 80), ui->widget_wcw_BG->height() - g_appInfoPtr->m_fRate*(20 + 30),
-                                     g_appInfoPtr->m_fRate*80, g_appInfoPtr->m_fRate*30);
+    
+    int nWidth = ui->widget_wcw_BG->width();
+	if (nWidth - g_appInfoPtr->m_fRate * 30 < ui->label_wcw_tips->x() + ui->label_wcw_tips->width())
+	{
+		nWidth = ui->label_wcw_tips->x() + ui->label_wcw_tips->width() + g_appInfoPtr->m_fRate * 30;
+	}
+
+    if(nWidth - g_appInfoPtr->m_fRate*30 < ui->label_wcw_studentName->x() + ui->label_wcw_studentName->width())
+    {
+        nWidth = ui->label_wcw_studentName->x() + ui->label_wcw_studentName->width() + g_appInfoPtr->m_fRate*30;
+    }
+
+    if(nWidth - g_appInfoPtr->m_fRate*30 < ui->label_wcw_specialty->x() + ui->label_wcw_specialty->width())
+    {
+        nWidth = ui->label_wcw_specialty->x() + ui->label_wcw_specialty->width() + g_appInfoPtr->m_fRate*30;
+    }
+
+    if(nWidth > ui->widget_wcw_BG->width())
+    {
+        ui->widget_wcw_BG->setGeometry((width() - nWidth)/2, (height() - g_appInfoPtr->m_fRate*212)/2,
+                                       nWidth, g_appInfoPtr->m_fRate*212);
+    }
+
+	ui->btn_wcw_comfirm->setGeometry(ui->widget_wcw_BG->width() - g_appInfoPtr->m_fRate*(20 + 80), ui->widget_wcw_BG->height() - g_appInfoPtr->m_fRate*(20 + 30),
+		g_appInfoPtr->m_fRate * 80, g_appInfoPtr->m_fRate * 30);
 }
 
 void welcomeWidget::on_btn_wcw_comfirm_clicked()

+ 2 - 0
common/CAppInfo.cpp

@@ -20,6 +20,7 @@ CAppInfo::CAppInfo()
 	m_bDisableVirtualCamera = false;
 	m_sRootOrgId = "";
 	m_bShowQmthLogo = false;
+    m_sMenuLogoUrl = "";
 	m_bShowStudentClientAppQrcode = false;
 	m_bWeiXinAnswerEnabled = false;
 	m_jAudioPlayedCount = Json::Value::null;
@@ -41,6 +42,7 @@ CAppInfo::CAppInfo()
 	m_sMachineId = "";	
     m_sUpgradeUrl = "";
 	m_sCacheFileDir = "temp/";
+    m_bShowDebugInfo = false;
 
     m_pAnsBgWidget = nullptr;
 }

+ 5 - 0
common/CAppInfo.h

@@ -92,6 +92,7 @@ public:
 	bool m_bDisableVirtualCamera;//虚拟摄像头
 	QString m_sRootOrgId;
 	bool m_bShowQmthLogo;
+    QString m_sMenuLogoUrl;
 	bool m_bShowStudentClientAppQrcode;
 	bool m_bWeiXinAnswerEnabled;//是否开启微信作答
 	Json::Value m_jAudioPlayedCount;
@@ -120,7 +121,11 @@ public:
 
 	QString m_sCacheFileDir;
     __int64 m_nRecordId;
+
+    bool m_bShowDebugInfo;//是否显示debug日志
     __int64 serverMTime();
+
+    std::map<QString, QString> m_FileCacheMap;
 };
 
 extern std::shared_ptr<CAppInfo> g_appInfoPtr;

+ 9 - 4
common/CAudioPlayerProc.cpp

@@ -67,8 +67,8 @@ void CAudioPlayerProc::onMetaDataAvailableChanged(bool available)
             qDebug()<<str<<"   :"<<m_pAudioPlayer->metaData(str).toString().toUtf8().data();
         }
         qint64 time = m_pAudioPlayer->metaData("Duration").toInt();
-        QString str = QString("%1").arg((time+500)/1000);
-        int m_nAudioSecond = (time+500)/1000;
+        QString str = QString("%1").arg((time+999)/1000);
+        int m_nAudioSecond = (time+999)/1000;
         emit durationChange(m_nAudioSecond);
     }
 }
@@ -79,7 +79,7 @@ int CAudioPlayerProc::GetMediaDuration(QString sAudio)
     MediaInfoDLL::MediaInfo MI;
     QString filepath = fileInfo.absoluteFilePath();
     MI.Open(filepath.toStdWString());
-    int nDuration = QString::fromStdWString((std::basic_string<wchar_t>)MI.Get(MediaInfoDLL::stream_t::Stream_Audio, 0, __T("Duration"))).toLong()/1000;
+    int nDuration = (QString::fromStdWString((std::basic_string<wchar_t>)MI.Get(MediaInfoDLL::stream_t::Stream_Audio, 0, __T("Duration"))).toLong()+999)/1000;
     MI.Close();
     return nDuration;
 }
@@ -106,7 +106,7 @@ int CAudioPlayerProc::playAudio(QString sAudio, bool IsRepeat)
         m_pAudioPlayer->play();
     }
     qDebug()<<m_pAudioPlayer->duration();
-    return m_pAudioPlayer->duration()/1000;
+    return (m_pAudioPlayer->duration()+999)/1000;
 }
 
 void CAudioPlayerProc::stopPlay()
@@ -119,6 +119,11 @@ void CAudioPlayerProc::pausePlay()
     m_pAudioPlayer->pause();
 }
 
+void CAudioPlayerProc::play()
+{
+	m_pAudioPlayer->play();
+}
+
 void CAudioPlayerProc::setVol(int vol)
 {
     if(vol > 100)

+ 1 - 0
common/CAudioPlayerProc.h

@@ -43,6 +43,7 @@ public:
     int playAudio(QString sAudio, bool IsRepeat = false);
     void stopPlay();
     void pausePlay();
+	void play();
     void setVol(int vol);
 
     void setMicVol(int nVol);

+ 442 - 88
common/CCommonTools.cpp

@@ -6,7 +6,8 @@
 #include <QCryptographicHash>
 #include  "logproc.h"
 #include <random>
-
+#include <QFileInfo>
+#include <QDir>
 #include <windows.h>
 #include <tlhelp32.h>// for CreateToolhelp32Snapshot
 #include <Psapi.h>   // for GetModuleFileNameEx
@@ -57,7 +58,7 @@ namespace CCommonTools
     }
 
     int getDisplayNumber()
-    {
+    {		
         int iNumber = 0;
         bool bFlag = true;
 
@@ -196,6 +197,25 @@ namespace CCommonTools
 		return output;
 	}
 
+    void createDir(QString sDir)
+    {
+        QFileInfo fileInfo = sDir;
+        QDir dir;
+        QString sExt = fileInfo.suffix();
+        if(sExt.isEmpty())
+        {
+            dir = QDir(fileInfo.absoluteFilePath());
+        }
+        else
+        {
+            dir = QDir(fileInfo.absolutePath());
+        }
+
+        if(!dir.exists())
+        {
+           dir.mkpath(dir.absolutePath());
+        }
+    }
 
     QString getUuid()
     {
@@ -266,7 +286,7 @@ namespace CCommonTools
             QString exePath = GetPathByProcessID( pe32.th32ProcessID );
             exePath = FORMAT_PATH( exePath );
             if(!exePath.isEmpty() )
-            {
+            {                
                 for(int i = 0; i < sCheckApps.count(); ++i)
                 {
                     if(exeName.toLower().indexOf(sCheckApps[i].toLower()) == 0)
@@ -284,62 +304,75 @@ namespace CCommonTools
         CloseHandle(hProcessSnap);
     }
 
-    int listCameraDevices(std::vector<CameraInfo>& list)
+    int listCameraDevices(std::vector<CameraInfo> &list)
     {
-        ICreateDevEnum *pDevEnum = NULL;
-        IEnumMoniker *pEnum = NULL;
         int deviceCounter = 0;
-        CoInitialize(NULL);
-
-        HRESULT hr = CoCreateInstance(
-            CLSID_SystemDeviceEnum,
-            NULL,
-            CLSCTX_INPROC_SERVER,
-            IID_ICreateDevEnum,
-            reinterpret_cast<void**>(&pDevEnum)
-        );
-
+        HRESULT hr = CoInitialize(NULL);
         if (SUCCEEDED(hr))
         {
-            hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEnum, 0);
-            if (hr == S_OK)
+            IEnumMoniker *pEnum;
+
+            ICreateDevEnum *pDevEnum;
+            HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
+                CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
+
+            if (SUCCEEDED(hr))
+            {
+                // Create an enumerator for the category.
+                hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
+                if (hr == S_FALSE)
+                {
+                    hr = VFW_E_NOT_FOUND;  // The category is empty. Treat as an error.
+                }
+                pDevEnum->Release();
+            }
+
+
+            if (SUCCEEDED(hr))
             {
-    //            int nIdx = 0;
                 IMoniker *pMoniker = NULL;
+
                 while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
                 {
                     IPropertyBag *pPropBag;
-                    hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
-                    (void**)(&pPropBag));
-
-                    if (FAILED(hr)) {
-                          pMoniker->Release();
-                          continue; // Skip this one, maybe the next one will work.
+                    HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
+                    if (FAILED(hr))
+                    {
+                        pMoniker->Release();
+                        continue;
                     }
 
                     CameraInfo cam;
-                    LPOLESTR pwszName;
-
-                    pMoniker->GetDisplayName(0,NULL,&pwszName);
-                    std::wstring strInfo = pwszName;
-                     std::wstring subStr = L"";
-                     std::wstring strVid = L"";
-                     std::wstring strPid = L"";
-                    cam.detail = strInfo;
-                    myDebug()<<strInfo;
-                    //硬件厂商id Ven 或者vid int
-                    size_t nPos = strInfo.find(L"vid_", 0);
-                    if(nPos !=  std::wstring::npos)
+
+                    VARIANT var;
+                    VariantInit(&var);
+
+                    // Get description or friendly name.
+                    hr = pPropBag->Read(L"Description", &var, 0);
+                    if (FAILED(hr))
                     {
-                        subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
-                        size_t nSubPos = subStr.find(L"&", 0);
-                        strVid = subStr.substr(0, nSubPos);
-                        cam.vid = strVid;
+                        hr = pPropBag->Read(L"FriendlyName", &var, 0);
                     }
-                    else
+
+                    if (SUCCEEDED(hr))
                     {
-                        size_t nPos = strInfo.find(L"ven_", 0);
-                        if(nPos != std::wstring::npos)
+                        cam.name = var.bstrVal;
+
+                        VariantClear(&var);
+                    }
+
+                    hr = pPropBag->Read(L"DevicePath", &var, 0);
+                    if (SUCCEEDED(hr))
+                    {
+                        std::wstring strInfo = var.bstrVal;
+                        std::wstring subStr = L"";
+                        std::wstring strVid = L"";
+                        std::wstring strPid = L"";
+                        cam.detail = strInfo;
+                        myDebug()<<strInfo;
+                        //硬件厂商id Ven 或者vid int
+                        size_t nPos = strInfo.find(L"vid_", 0);
+                        if(nPos !=  std::wstring::npos)
                         {
                             subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
                             size_t nSubPos = subStr.find(L"&", 0);
@@ -348,29 +381,28 @@ namespace CCommonTools
                         }
                         else
                         {
-                            size_t nPos = strInfo.find(L"int", 0);
+                            size_t nPos = strInfo.find(L"ven_", 0);
                             if(nPos != std::wstring::npos)
                             {
-                                subStr = strInfo.substr(nPos+3,strInfo.length() - nPos-3);
-                                size_t nSubPos = subStr.find(L"#", 0);
+                                subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+                                size_t nSubPos = subStr.find(L"&", 0);
                                 strVid = subStr.substr(0, nSubPos);
                                 cam.vid = strVid;
                             }
+                            else
+                            {
+                                size_t nPos = strInfo.find(L"int", 0);
+                                if(nPos != std::wstring::npos)
+                                {
+                                    subStr = strInfo.substr(nPos+3,strInfo.length() - nPos-3);
+                                    size_t nSubPos = subStr.find(L"#", 0);
+                                    strVid = subStr.substr(0, nSubPos);
+                                    cam.vid = strVid;
+                                }
+                            }
                         }
-                    }
-                    //代表产品编号DEV pid uid
-                    nPos = strInfo.find(L"pid_", 0);
-                    subStr = L"";
-                    if(nPos != std::wstring::npos)
-                    {
-                        subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
-                        size_t nSubPos = subStr.find(L"&", 0);
-                        strPid = subStr.substr(0, nSubPos);
-                        cam.pid = strPid;
-                    }
-                    else
-                    {
-                        nPos = strInfo.find(L"dev_", 0);
+                        //代表产品编号DEV pid uid
+                        nPos = strInfo.find(L"pid_", 0);
                         subStr = L"";
                         if(nPos != std::wstring::npos)
                         {
@@ -381,61 +413,373 @@ namespace CCommonTools
                         }
                         else
                         {
-                            nPos = strInfo.find(L"uid", 0);
+                            nPos = strInfo.find(L"dev_", 0);
                             subStr = L"";
                             if(nPos != std::wstring::npos)
                             {
-                                subStr = strInfo.substr(nPos+3,strInfo.length() - nPos-3);
-                                size_t nSubPos = subStr.find(L"#", 0);
+                                subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+                                size_t nSubPos = subStr.find(L"&", 0);
                                 strPid = subStr.substr(0, nSubPos);
                                 cam.pid = strPid;
                             }
+                            else
+                            {
+                                nPos = strInfo.find(L"uid", 0);
+                                subStr = L"";
+                                if(nPos != std::wstring::npos)
+                                {
+                                    subStr = strInfo.substr(nPos+3,strInfo.length() - nPos-3);
+                                    size_t nSubPos = subStr.find(L"#", 0);
+                                    strPid = subStr.substr(0, nSubPos);
+                                    cam.pid = strPid;
+                                }
+                            }
                         }
+
+
+                        VariantClear(&var);
                     }
 
+                    pPropBag->Release();
+                    pMoniker->Release();
+                    list.push_back(cam);
+                    ++deviceCounter;
+                }
+                pEnum->Release();
+            }
+
+            CoUninitialize();
+        }
+
+        return deviceCounter;
+
+    }
+
+    int listCameraDevices(std::vector<CameraInfo> &list, QStringList &CameraList, QStringList checkCameraLiist)
+    {
+        int deviceCounter = 0;
+        HRESULT hr = CoInitialize(NULL);
+        if (SUCCEEDED(hr))
+        {
+            IEnumMoniker *pEnum;
+
+            ICreateDevEnum *pDevEnum;
+            HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
+                CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
+
+            if (SUCCEEDED(hr))
+            {
+                // Create an enumerator for the category.
+                hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
+                if (hr == S_FALSE)
+                {
+                    hr = VFW_E_NOT_FOUND;  // The category is empty. Treat as an error.
+                }
+                pDevEnum->Release();
+            }
+
 
-                    VARIANT varName;
-                    VariantInit(&varName);
-                    hr = pPropBag->Read(L"Description", &varName, 0);
+            if (SUCCEEDED(hr))
+            {
+                IMoniker *pMoniker = NULL;
+
+                while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
+                {
+                    IPropertyBag *pPropBag;
+                    HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
+                    if (FAILED(hr))
+                    {
+                        pMoniker->Release();
+                        continue;
+                    }
+
+                    CameraInfo cam;
+
+                    VARIANT var;
+                    VariantInit(&var);
+
+                    // Get description or friendly name.
+                    hr = pPropBag->Read(L"Description", &var, 0);
                     if (FAILED(hr))
                     {
-                          hr = pPropBag->Read(L"FriendlyName", &varName, 0);
+                        hr = pPropBag->Read(L"FriendlyName", &var, 0);
+                    }
+
+                    if (SUCCEEDED(hr))
+                    {
+                        cam.name = var.bstrVal;
+                        QString sCameraName = QString::fromStdWString(cam.name);
+                        for(int i = 0; i < checkCameraLiist.count(); ++i)
+                        {
+                            if(sCameraName.toLower().indexOf(checkCameraLiist[i].toLower()) == 0)
+                            {
+                                if(CameraList.indexOf(sCameraName) < 0)
+                                {
+                                    CameraList.push_back(sCameraName);
+                                }
 
+                            }
+                        }
+
+                        VariantClear(&var);
                     }
 
+                    hr = pPropBag->Read(L"DevicePath", &var, 0);
                     if (SUCCEEDED(hr))
                     {
-                        hr = pPropBag->Read(L"FriendlyName", &varName, 0);
-                        int count = 0;
-                        wchar_t wtmp[255] = { 0 };
-                        char tmp[255] = { 0 };
-                        memset(tmp, 0, 255);
-                        while (varName.bstrVal[count] != 0x00 && count < 255)
+                        std::wstring strInfo = var.bstrVal;
+                        std::wstring subStr = L"";
+                        std::wstring strVid = L"";
+                        std::wstring strPid = L"";
+                        cam.detail = strInfo;
+                        myDebug()<<strInfo;
+                        //硬件厂商id Ven 或者vid int
+                        size_t nPos = strInfo.find(L"vid_", 0);
+                        if(nPos !=  std::wstring::npos)
+                        {
+                            subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+                            size_t nSubPos = subStr.find(L"&", 0);
+                            strVid = subStr.substr(0, nSubPos);
+                            cam.vid = strVid;
+                        }
+                        else
+                        {
+                            size_t nPos = strInfo.find(L"ven_", 0);
+                            if(nPos != std::wstring::npos)
+                            {
+                                subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+                                size_t nSubPos = subStr.find(L"&", 0);
+                                strVid = subStr.substr(0, nSubPos);
+                                cam.vid = strVid;
+                            }
+                            else
+                            {
+                                size_t nPos = strInfo.find(L"int", 0);
+                                if(nPos != std::wstring::npos)
+                                {
+                                    subStr = strInfo.substr(nPos+3,strInfo.length() - nPos-3);
+                                    size_t nSubPos = subStr.find(L"#", 0);
+                                    strVid = subStr.substr(0, nSubPos);
+                                    cam.vid = strVid;
+                                }
+                            }
+                        }
+                        //代表产品编号DEV pid uid
+                        nPos = strInfo.find(L"pid_", 0);
+                        subStr = L"";
+                        if(nPos != std::wstring::npos)
+                        {
+                            subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+                            size_t nSubPos = subStr.find(L"&", 0);
+                            strPid = subStr.substr(0, nSubPos);
+                            cam.pid = strPid;
+                        }
+                        else
                         {
-                            wtmp[count] = (wchar_t)varName.bstrVal[count];
-                            count++;
+                            nPos = strInfo.find(L"dev_", 0);
+                            subStr = L"";
+                            if(nPos != std::wstring::npos)
+                            {
+                                subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+                                size_t nSubPos = subStr.find(L"&", 0);
+                                strPid = subStr.substr(0, nSubPos);
+                                cam.pid = strPid;
+                            }
+                            else
+                            {
+                                nPos = strInfo.find(L"uid", 0);
+                                subStr = L"";
+                                if(nPos != std::wstring::npos)
+                                {
+                                    subStr = strInfo.substr(nPos+3,strInfo.length() - nPos-3);
+                                    size_t nSubPos = subStr.find(L"#", 0);
+                                    strPid = subStr.substr(0, nSubPos);
+                                    cam.pid = strPid;
+                                }
+                            }
                         }
 
-                        cam.name = wtmp;
-                        list.push_back(cam);
+
+                        VariantClear(&var);
                     }
 
                     pPropBag->Release();
-                    pPropBag = NULL;
                     pMoniker->Release();
-                    pMoniker = NULL;
-
-                    deviceCounter++;
+                    list.push_back(cam);
+                    ++deviceCounter;
                 }
-
-                pDevEnum->Release();
-                pDevEnum = NULL;
                 pEnum->Release();
-                pEnum = NULL;
             }
+
+            CoUninitialize();
         }
-        CoUninitialize();
+
         return deviceCounter;
+
+
+
+
+
+//        ICreateDevEnum *pDevEnum = NULL;
+//        IEnumMoniker *pEnum = NULL;
+//        int deviceCounter = 0;
+//        CoInitialize(NULL);
+
+//        HRESULT hr = CoCreateInstance(
+//            CLSID_SystemDeviceEnum,
+//            NULL,
+//            CLSCTX_INPROC_SERVER,
+//            IID_ICreateDevEnum,
+//            reinterpret_cast<void**>(&pDevEnum)
+//        );
+
+//        if (SUCCEEDED(hr))
+//        {
+//            hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEnum, 0);
+//            if (hr == S_OK)
+//            {
+//    //            int nIdx = 0;
+//                IMoniker *pMoniker = NULL;
+//                while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
+//                {
+//                    IPropertyBag *pPropBag;
+//                    hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
+//                    (void**)(&pPropBag));
+
+//                    if (FAILED(hr)) {
+//                          pMoniker->Release();
+//                          continue; // Skip this one, maybe the next one will work.
+
+//                    }
+
+//                    VARIANT var;
+//                    VariantInit(&var);
+
+//                    hr = pPropBag->Read(L"Description", &var, 0);
+//                    if (FAILED(hr))
+//                    {
+//                        hr = pPropBag->Read(L"FriendlyName", &var, 0);
+//                    }
+
+//                    CameraInfo cam;
+//                    LPOLESTR pwszName;
+
+//                    pMoniker->GetDisplayName(0,NULL,&pwszName);
+//                    std::wstring strInfo = pwszName;
+//                     std::wstring subStr = L"";
+//                     std::wstring strVid = L"";
+//                     std::wstring strPid = L"";
+//                    cam.detail = strInfo;
+//                    myDebug()<<strInfo;
+//                    //硬件厂商id Ven 或者vid int
+//                    size_t nPos = strInfo.find(L"vid_", 0);
+//                    if(nPos !=  std::wstring::npos)
+//                    {
+//                        subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+//                        size_t nSubPos = subStr.find(L"&", 0);
+//                        strVid = subStr.substr(0, nSubPos);
+//                        cam.vid = strVid;
+//                    }
+//                    else
+//                    {
+//                        size_t nPos = strInfo.find(L"ven_", 0);
+//                        if(nPos != std::wstring::npos)
+//                        {
+//                            subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+//                            size_t nSubPos = subStr.find(L"&", 0);
+//                            strVid = subStr.substr(0, nSubPos);
+//                            cam.vid = strVid;
+//                        }
+//                        else
+//                        {
+//                            size_t nPos = strInfo.find(L"int", 0);
+//                            if(nPos != std::wstring::npos)
+//                            {
+//                                subStr = strInfo.substr(nPos+3,strInfo.length() - nPos-3);
+//                                size_t nSubPos = subStr.find(L"#", 0);
+//                                strVid = subStr.substr(0, nSubPos);
+//                                cam.vid = strVid;
+//                            }
+//                        }
+//                    }
+//                    //代表产品编号DEV pid uid
+//                    nPos = strInfo.find(L"pid_", 0);
+//                    subStr = L"";
+//                    if(nPos != std::wstring::npos)
+//                    {
+//                        subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+//                        size_t nSubPos = subStr.find(L"&", 0);
+//                        strPid = subStr.substr(0, nSubPos);
+//                        cam.pid = strPid;
+//                    }
+//                    else
+//                    {
+//                        nPos = strInfo.find(L"dev_", 0);
+//                        subStr = L"";
+//                        if(nPos != std::wstring::npos)
+//                        {
+//                            subStr = strInfo.substr(nPos+4,strInfo.length() - nPos-4);
+//                            size_t nSubPos = subStr.find(L"&", 0);
+//                            strPid = subStr.substr(0, nSubPos);
+//                            cam.pid = strPid;
+//                        }
+//                        else
+//                        {
+//                            nPos = strInfo.find(L"uid", 0);
+//                            subStr = L"";
+//                            if(nPos != std::wstring::npos)
+//                            {
+//                                subStr = strInfo.substr(nPos+3,strInfo.length() - nPos-3);
+//                                size_t nSubPos = subStr.find(L"#", 0);
+//                                strPid = subStr.substr(0, nSubPos);
+//                                cam.pid = strPid;
+//                            }
+//                        }
+//                    }
+
+
+//                    VARIANT varName;
+//                    VariantInit(&varName);
+//                    hr = pPropBag->Read(L"Description", &varName, 0);
+//                    if (FAILED(hr))
+//                    {
+//                          hr = pPropBag->Read(L"FriendlyName", &varName, 0);
+
+//                    }
+
+//                    if (SUCCEEDED(hr))
+//                    {
+//                        hr = pPropBag->Read(L"FriendlyName", &varName, 0);
+//                        int count = 0;
+//                        wchar_t wtmp[255] = { 0 };
+//                        char tmp[255] = { 0 };
+//                        memset(tmp, 0, 255);
+//                        while (varName.bstrVal[count] != 0x00 && count < 255)
+//                        {
+//                            wtmp[count] = (wchar_t)varName.bstrVal[count];
+//                            count++;
+//                        }
+
+//                        cam.name = wtmp;
+//                        list.push_back(cam);
+//                    }
+
+//                    pPropBag->Release();
+//                    pPropBag = NULL;
+//                    pMoniker->Release();
+//                    pMoniker = NULL;
+
+//                    deviceCounter++;
+//                }
+
+//                pDevEnum->Release();
+//                pDevEnum = NULL;
+//                pEnum->Release();
+//                pEnum = NULL;
+//            }
+//        }
+//        CoUninitialize();
+//        return deviceCounter;
     }
 
     QString fileMd5(QString sFileName)//获取文件MD5值
@@ -554,5 +898,15 @@ namespace CCommonTools
         std::mt19937 generator(rand_dev());
         std::uniform_int_distribution<int>  distr(nForm, nTo);
         return distr(generator);
-    }        
+    }
+
+    QString getImageRawBase64Str(QString sBase64Image)
+    {
+        int nIndex = sBase64Image.indexOf("base64,");
+        if(nIndex >= 0)
+        {
+            return sBase64Image.right(sBase64Image.length() - nIndex - 7);
+        }
+        return sBase64Image;
+    }
 }

+ 4 - 1
common/CCommonTools.h

@@ -28,15 +28,18 @@ namespace CCommonTools
 	QString Arab2Sinogram(int num);
     QString getUuid();
     qint64 getTimeStamp();
+    void createDir(QString sDir);
     bool isUseRDP();//是否使用远程桌面
     void getAllAppNameList(QStringList &appList, QStringList sCheckApps, QStringList sCheckNames); //获取进程列表
-    int listCameraDevices(std::vector<CameraInfo>& list);//获取摄像头信息
+    int listCameraDevices(std::vector<CameraInfo> &list);
+    int listCameraDevices(std::vector<CameraInfo>& list, QStringList &CameraList, QStringList checkCameraLiist);//获取摄像头信息
     QString fileMd5(QString sFileName);//获取文件MD5值
     void topMostSlot(QWidget *widget, bool bTopMost);
     QImage Mat2QImage(const cv::Mat& mat);
     bool checkMat(cv::Mat& mat); //检查摄像头是否断开 true:正常 false:断开
     void genRandomNumber(QList<int> &listRandom, int nSize);//生成1-size随机数
     int genRandomNumber(int nForm, int nTo);//生成范围类随机数
+    QString getImageRawBase64Str(QString sBase64Image);
 }
 
 #endif // CCOMMONTOOLS_H

+ 126 - 10
common/CHttpBll.cpp

@@ -45,6 +45,8 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 //    requestPkg.sHeadList.push_back(QString::fromLocal8Bit("platform,%1").arg("WEB"));
 //    requestPkg.sHeadList.push_back(QString::fromLocal8Bit("deviceId,%1").arg(g_AppInfo->sMachineId));
 //    requestPkg.sHeadList.push_back(QString::fromLocal8Bit("time,%1").arg(sTimeStamp));
+	requestPkg.sHeadList.push_back(QString::fromLocal8Bit("Referer,%1").arg(requestPkg.sUri));
+	requestPkg.sHeadList.push_back(QString::fromLocal8Bit("User-Agent,%1").arg("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) electron-exam-shell/1.9.1 Chrome/58.0.3029.110 Electron/1.7.16 Safari/537.36"));
     if(sToken != "")
     {
 		requestPkg.sHeadList.push_back(QString::fromLocal8Bit("key,%1").arg(sKey));
@@ -60,10 +62,17 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 	int nCode = 0;
 	if (requestPkg.nHttpType == HttpType::htDownload)
 	{
-		requestPkg.sUri = requestPkg.sUri.replace("\\", "/");
+		requestPkg.sUri = requestPkg.sUri.replace("\\", "/");	
 		if (!downLoadFile(requestPkg.sUri.toLocal8Bit().data(), requestPkg.sCommonStr.toLocal8Bit().data(), nCode))
 		{
-			sErrorMsg = QString::fromLocal8Bit("接口%1调用失败!").arg(requestPkg.sUri);
+            --requestPkg.nRetryCount;
+            if(requestPkg.nRetryCount > 0)
+            {
+                downLoad(requestPkg);
+                return;
+            }
+
+            sErrorMsg = QString::fromLocal8Bit("%1下载失败!").arg(requestPkg.sUri);
 		}	
 	}
     if(requestPkg.nHttpType == HttpType::htPost)
@@ -88,7 +97,7 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 		}
 	}
     __int64 nEndTime = g_appInfoPtr->serverMTime();
-    myServerLog()<< requestPkg.sUri << ":resCode," << nCode << "requestTime(ms):" << nEndTime - nStartTime;
+    myServerLog()<< requestPkg.sUri << ":resCode," << nCode<<"response:"<<sResponse.c_str() << "requestTime(ms):" << nEndTime - nStartTime;
     myDebug() << requestPkg.sUri << ":"<< sResponse.c_str();
 
 	if (nCode != 200)
@@ -104,6 +113,12 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 		}
 	}
 
+	if (nCode == 403)
+	{		
+        emit sgnTokenExpired();
+        return;
+	}
+
     switch(requestPkg.nRequestType)
     {
         case RequestType::rtUpgrade://客户端版本升级接口
@@ -136,6 +151,22 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
             }
             emit sgnSysNotice(sn);
         }
+        break;
+
+        case RequestType::rtGetSoftwareConfig:
+        {
+            CGetSoftwareConfig gsc;
+            gsc.nCode = nCode;
+            if(sErrorMsg != "" || nCode !=200)
+            {
+                gsc.sMessage = sErrorMsg;
+            }
+            else
+            {
+                genSoftwareConfig(&gsc, sResponse);
+            }
+            emit sgnGetSoftwareConfig(gsc);
+        }
         break;
 
 		case RequestType::rtDownLoadFile:
@@ -143,6 +174,7 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 			CDownLoadFileInfo dfi;
 			dfi.sFileName = requestPkg.sCommonStr;
 			dfi.sModuleName = requestPkg.sCommonStr1;
+			dfi.sAdditionStr = requestPkg.sAdditionStr;
 			dfi.nCode = nCode;
 			if (sErrorMsg != "" || nCode != 200)
 			{
@@ -329,6 +361,20 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 		}
 		break;
 
+        case RequestType::rtUpdateNoticeReadStatus:
+        {
+            CUpdateNoticeReadStatus unrs;
+            unrs.nCode = nCode;
+            unrs.sIds = requestPkg.sCommonStr;
+            if (sErrorMsg != "" || nCode != 200)
+            {
+                unrs.sMessage = sErrorMsg;
+            }
+
+            emit sgnUpdateNoticeReadStatus(unrs);
+        }
+        break;
+
 		case RequestType::rtQueryExamList:
 		{
 			CQueryExamList qel;
@@ -588,6 +634,7 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 		{
 			CPreviewOffLineExamPaper poep;
 			poep.nCode = nCode;
+			poep.nRow = requestPkg.sCommonStr.toInt();
 			if (sErrorMsg != "" || nCode != 200)
 			{
 				poep.sMessage = sErrorMsg;
@@ -916,6 +963,7 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 		{
 			CEndExam ee;
 			ee.nCode = nCode;
+            ee.sModuleName = requestPkg.sCommonStr;
 			if (sErrorMsg != "" || nCode != 200)
 			{
 				ee.sMessage = sErrorMsg;
@@ -1016,6 +1064,22 @@ void CHttpBll::requestProc(CHttpRequestPackage requestPkg)
 		}
 		break;
 
+		case RequestType::rtGetEndExamInfo:
+		{
+			CGetEndExamInfo geei;
+			geei.nCode = nCode;
+			if (sErrorMsg != "" || nCode != 200)
+			{
+				geei.sMessage = sErrorMsg;
+			}
+			else
+			{
+				genGetEndExamInfo(&geei, sResponse);
+			}
+			emit sgnGetEndExamInfo(geei);
+		}
+		break;
+
         default:
         break;
     }
@@ -1088,6 +1152,7 @@ void CHttpBll::genStudentClientConfig(CStudentClientConfig *pStudentClientConfig
 	}
 	
 	pStudentClientConfig->sLogoFileUrl = jsonRoot["LOGO_FILE_URL"].asString().c_str();
+	pStudentClientConfig->sStudentClientBgPictureUrl = jsonRoot["STUDENT_CLIENT_BG_PICTURE_URL"].asString().c_str();
 	pStudentClientConfig->sOeStudentSysName = jsonRoot["OE_STUDENT_SYS_NAME"].asString().c_str();
 	QString preventCheating = jsonRoot["PREVENT_CHEATING_CONFIG"].asString().c_str();
 	QStringList pcList = preventCheating.split(",");
@@ -1136,6 +1201,11 @@ void CHttpBll::genSysNotice(CSysNotice *pSysNotice, const std::string &sResponse
 	pSysNotice->bEnable = jsonRoot["enable"].asBool();	
 }
 
+void CHttpBll::genSoftwareConfig(CGetSoftwareConfig *pSoftwareConfig, const std::string &sResponse)
+{
+    pSoftwareConfig->sSoftwareConfig = std::string(sResponse.rbegin()+32, sResponse.rend()).c_str();
+}
+
 void CHttpBll::genLoginLimit(CLoginLimit *pLoginLimit, const std::string &sResponse)
 {
 	Json::Reader reader;
@@ -1289,7 +1359,7 @@ void CHttpBll::genGetUserNoticeList(CGetUserNoticeList *pGetUserNoticeList, cons
 		ni.sPublisher = jNoticeItem["publisher"].asString().c_str();
 		ni.sPublishTime = jNoticeItem["publishTime"].asString().c_str();
 		ni.bHasRead = jNoticeItem["hasRead"].asBool();
-		//ni.bHasRecalled = jNoticeItem["hasRecalled"].asBool();
+        ni.bHasRecalled = jNoticeItem["hasRecalled"].isNull() ? false : jNoticeItem["hasRecalled"].asBool();
 		pGetUserNoticeList->vNoticeList.push_back(ni);
 	}
 
@@ -1388,22 +1458,22 @@ void CHttpBll::genGetExamProperty(CGetExamProperty *pGetExamProperty, const std:
 
 	if (jsonRoot.isMember("AFTER_EXAM_REMARK"))
 	{
-		pGetExamProperty->sAfterExamRemark = QVariant(jsonRoot["AFTER_EXAM_REMARK"].asString().c_str()).toInt();
+		pGetExamProperty->sAfterExamRemark = jsonRoot["AFTER_EXAM_REMARK"].asString().c_str();
 	}
 
 	if (jsonRoot.isMember("IS_OBJ_SCORE_VIEW"))
 	{
-		pGetExamProperty->bIsObjScoreView = QVariant(jsonRoot["IS_OBJ_SCORE_VIEW"].asString().c_str()).toInt();
+		pGetExamProperty->bIsObjScoreView = QVariant(jsonRoot["IS_OBJ_SCORE_VIEW"].asString().c_str()).toBool();
 	}
 
 	if (jsonRoot.isMember("SHOW_CHEATING_REMARK"))
 	{
-		pGetExamProperty->bShowCheatingRemark = QVariant(jsonRoot["SHOW_CHEATING_REMARK"].asString().c_str()).toInt();
+		pGetExamProperty->bShowCheatingRemark = QVariant(jsonRoot["SHOW_CHEATING_REMARK"].asString().c_str()).toBool();
 	}
 
 	if (jsonRoot.isMember("CHEATING_REMARK"))
 	{
-		pGetExamProperty->sCheatingRemark = QVariant(jsonRoot["CHEATING_REMARK"].asString().c_str()).toInt();
+		pGetExamProperty->sCheatingRemark = jsonRoot["CHEATING_REMARK"].asString().c_str();
 	}
 
 	if (jsonRoot.isMember("OFFLINE_UPLOAD_FILE_TYPE"))
@@ -1517,6 +1587,7 @@ void CHttpBll::genStartAnswer(CStartAnswer *pStartAnswer, const std::string &sRe
 	}
 	pStartAnswer->nDuration = jsonRoot["duration"].asInt64();
 	pStartAnswer->nExamRecordDataId = jsonRoot["examRecordDataId"].asInt64();
+    pStartAnswer->nUsedExamSeconds = jsonRoot["usedExamSeconds"].asInt64();
 }
 
 void CHttpBll::genGetQuestion(CGetQuestion *pGetQuestion, const std::string &sResponse)
@@ -1614,6 +1685,24 @@ void CHttpBll::genExamCourseInfo(const Json::Value &jExamItem, CExamCourseInfo &
 	eci.sCourseName = jExamItem["courseName"].asString().c_str();
 	eci.sEndTime = jExamItem["endTime"].asString().c_str();
 	eci.bExamCycleEnabled = jExamItem["examCycleEnabled"].asBool();
+    int nWeekSize = jExamItem["examCycleWeek"].size();
+    for(int i =0; i < nWeekSize; ++i)
+    {
+        eci.vExamCycleWeek.push_back(jExamItem["examCycleWeek"][i].asInt());
+    }
+
+    int nCycleTimeRangeSize = jExamItem["examCycleTimeRange"].size();
+    for(int i = 0; i < nCycleTimeRangeSize; ++i)
+    {
+        Json::Value jTimeRange = jExamItem["examCycleTimeRange"][i]["timeRange"];
+        int nTimeRangeSize = jTimeRange.size();
+        QStringList sTimeRangeList;
+        for(int j = 0; j < nTimeRangeSize; ++j)
+        {
+            sTimeRangeList<<jTimeRange[j].asString().c_str();
+        }
+        eci.sExamCycleTimeRange<<sTimeRangeList.join("~");
+    }
 	//eci.sCourseCode = jExamItem["examCycleTimeRange"].asString().c_str();
 	//eci.sCourseCode = jExamItem["examCycleWeek"].asString().c_str();
 	eci.nExamId = jExamItem["examId"].asInt64();
@@ -1745,6 +1834,25 @@ void CHttpBll::genQueryPracticeCourseList(CQueryPracticeCourseList *pQueryPracti
 		pci.bExamCycleEnabled = jExamItem["examCycleEnabled"].asBool();
 		//pci.sExamCycleTimeRange = jExamItem["examCycleTimeRange"].asDouble();
 		//pci.sExamCycleWeek = jExamItem["examCycleWeek"].asDouble();
+        int nWeekSize = jExamItem["examCycleWeek"].size();
+        for(int i = 0; i < nWeekSize; ++i)
+        {
+            pci.vExamCycleWeek.push_back(jExamItem["examCycleWeek"][i].asInt());
+        }
+
+        int nCycleTimeRangeSize = jExamItem["examCycleTimeRange"].size();
+        for(int i = 0; i < nCycleTimeRangeSize; ++i)
+        {
+            Json::Value jTimeRange = jExamItem["examCycleTimeRange"][i]["timeRange"];
+            int nTimeRangeSize = jTimeRange.size();
+            QStringList sTimeRangeList;
+            for(int j = 0; j < nTimeRangeSize; ++j)
+            {
+                sTimeRangeList<<jTimeRange[j].asString().c_str();
+            }
+            pci.sExamCycleTimeRange<<sTimeRangeList.join("~");
+        }
+
 		pci.nExamId = jExamItem["examId"].asInt64();
 		pci.sExamName = jExamItem["examName"].asString().c_str();
 		pci.nExamStudentId = jExamItem["examStudentId"].asInt64();
@@ -2090,6 +2198,12 @@ void CHttpBll::genGetExamRecordPaperStruct(CGetExamRecordPaperStruct *pGetExamRe
 
 void CHttpBll::genGetEndExamInfo(CGetEndExamInfo *pGetEndExamInfo, const std::string &sResponse)
 {
+	if (sResponse == "")
+	{
+		pGetEndExamInfo->bIsCalculate = true;
+		return;
+	}
+
 	Json::Reader reader;
 	Json::Value jsonRoot = Json::Value::null;
 	if (!reader.parse(sResponse, jsonRoot))
@@ -2097,11 +2211,13 @@ void CHttpBll::genGetEndExamInfo(CGetEndExamInfo *pGetEndExamInfo, const std::st
 		pGetEndExamInfo->sMessage = QString::fromLocal8Bit("解析后台返回环境信息json异常!");
 		myDebug() << pGetEndExamInfo->sMessage << sResponse.c_str();
 	}
-
+		
 	pGetEndExamInfo->nExamRecordDataId = jsonRoot["examRecordDataId"].asInt64();
 	pGetEndExamInfo->bIsWarn = jsonRoot["isWarn"].asBool();
-	pGetEndExamInfo->fObjectiveAccuracy = jsonRoot["objectiveAccuracy"].asInt64();
+	pGetEndExamInfo->fObjectiveAccuracy = jsonRoot["objectiveAccuracy"].asDouble();
 	pGetEndExamInfo->fObjectiveScore = jsonRoot["objectiveScore"].asInt64();
+	
+	
 }
 
 void CHttpBll::genGetWXQrCode(CGetWXQrCode *pGetWXQrCode, const std::string &sResponse)

+ 6 - 1
common/CHttpBll.h

@@ -10,9 +10,11 @@ class CHttpBll :public QObject , public CHttpInterceptor
 {
     Q_OBJECT
 signals:
+    void sgnTokenExpired();
     void sgnUpgrade(CUpgrade upgrade);
 	void sgnDownLoadFile(CDownLoadFileInfo downLoadFileInfo);
     void sgnAgreement(CAgreement agreement);
+    void sgnGetSoftwareConfig(CGetSoftwareConfig getSoftwareConfig);
 	void sgnStudentClientConfig(CStudentClientConfig studentClientConfig);
 	void sgnSysNotice(CSysNotice sysNotice);
 	void sgnLoginLimit(CLoginLimit loginLimit);
@@ -22,6 +24,7 @@ signals:
 	void sgnGetStudentClientMenu(CGetStudentClientMenu getStudentClientMenu);
 	void sgnGetStudentInfoBySession(CGetStudentInfoBySession getStudentInfoBySession);
 	void sgnSpecialtyNameList(CSpecialtyNameList specialtyNameList);
+    void sgnUpdateNoticeReadStatus(CUpdateNoticeReadStatus updateNoticeReadStatus);
 	void sgnGetUserNoticeList(CGetUserNoticeList getUserNoticeList);
 	void sgnAppDownLoadUrl(CAppDownLoadUrl appDownLoadUrl);
 	void sgnAppEnabled(CAppEnabled appEnabled);
@@ -79,8 +82,9 @@ public:
 private:
     void genUpgrade(CUpgrade *pUpgrade, const std::string &sResponse);
     void genAgreement(CAgreement *pAgreement, const std::string &sResponse);
+    void genSoftwareConfig(CGetSoftwareConfig *pSoftwareConfig, const std::string &sResponse);
 	void genStudentClientConfig(CStudentClientConfig *pStudentClientConfig, const std::string &sResponse);
-	void genSysNotice(CSysNotice *pSysNotice, const std::string &sResponse);
+	void genSysNotice(CSysNotice *pSysNotice, const std::string &sResponse);    
 	void genLoginLimit(CLoginLimit *pLoginLimit, const std::string &sResponse);
 	void genLoginInfo(CLoginInfo *pLoginInfo, const std::string &sResponse);
 	void genLogout(CLogout *pLogout, const std::string &sResponse);
@@ -88,6 +92,7 @@ private:
 	void genGetStudentClientMenu(CGetStudentClientMenu *pGetStudentClientMenu, const std::string &sResponse);
 	void genGetStudentInfoBySession(CGetStudentInfoBySession *pGetStudentInfoBySession, const std::string &sResponse);
 	void genSpecialtyNameList(CSpecialtyNameList *pSpecialtyNameList, const std::string &sResponse);
+
 	void genGetUserNoticeList(CGetUserNoticeList *pGetUserNoticeList, const std::string &sResponse);
 	void genAppDownLoadUrl(CAppDownLoadUrl *pAppDownLoadUrl, const std::string &sResponse);
 	void genAppEnabled(CAppEnabled *pAppEnabled, const std::string &sResponse);

+ 12 - 12
common/CHttpClient.cpp

@@ -362,6 +362,7 @@ bool CHttpClient::postFormData(std::string uri, std::string &sResponse, int &nRe
 
         curl_slist_free_all(m_pList);
         curl_formfree(m_formpost);
+        m_formpost = NULL;
         m_lastptr = NULL;
         m_pList = NULL;
         m_sParmStr = "";
@@ -492,15 +493,14 @@ CURLcode CHttpClient::sslctx_function(CURL *curl, void *sslctx, void *parm)
 
 void CHttpClient::sslVerify()
 {
+	curl_easy_setopt(m_pCurl, CURLOPT_SSL_VERIFYPEER, false);
+	curl_easy_setopt(m_pCurl, CURLOPT_SSL_VERIFYHOST, false);
+	/*
 	curl_easy_setopt(m_pCurl, CURLOPT_SSL_VERIFYPEER, 0);
 	curl_easy_setopt(m_pCurl, CURLOPT_SSL_VERIFYHOST, 2);
 	curl_easy_setopt(m_pCurl, CURLOPT_SSLCERTTYPE, "PEM");
 	curl_easy_setopt(m_pCurl, CURLOPT_SSLKEYTYPE, "PEM");
 	curl_easy_setopt(m_pCurl, CURLOPT_SSL_CTX_FUNCTION, *sslctx_function);
-	/*	curl_easy_setopt(m_pCurl, CURLOPT_CAINFO, "cacert.pem");
-	curl_easy_setopt(m_pCurl, CURLOPT_SSLCERT, "client.pem");
-	curl_easy_setopt(m_pCurl, CURLOPT_SSLKEY, "key.pem");
-	curl_easy_setopt(m_pCurl, CURLOPT_KEYPASSWD, "Qmth$5220");
 	*/
 }
 
@@ -637,13 +637,13 @@ bool CHttpClient::download(std::string uri, std::string sFileName, int &nResCode
 //    m_pList = curl_slist_append(m_pList,"Accept-Language:zh-CN,zh;q=0.8");
 //    curl_easy_setopt(m_pCurl, CURLOPT_HTTPHEADER, m_pList);
 
-//    std::string strUrl = "";
+    std::string strUrl = "";
 
-//    int nPos = uri.find_last_of(":");
-//    strUrl = uri.substr(0, nPos+1);
-//    strUrl += encodeURL(uri.substr(nPos + 1, uri.length() - nPos - 1));
-//    myDebug()<<strUrl.c_str();
-    curl_easy_setopt(m_pCurl, CURLOPT_URL, uri.c_str() ); //提交表单的URL地址    
+    int nPos = uri.find_last_of(":");
+    strUrl = uri.substr(0, nPos+1);
+    strUrl += encodeURL(uri.substr(nPos + 1, uri.length() - nPos - 1));
+    //myDebug()<<strUrl.c_str();
+    curl_easy_setopt(m_pCurl, CURLOPT_URL, strUrl.c_str() ); //提交表单的URL地址    
 
     curl_easy_setopt(m_pCurl, CURLOPT_TIMEOUT,m_nTimeOutSecord); //设置超时时长
 
@@ -725,8 +725,8 @@ std::string CHttpClient::encodeStr(std::string sStr)
 
 std::string CHttpClient::encodeURL(std::string sUri)
 {
-    std::string retUri = "";
-    QString strUri = sUri.c_str();
+    std::string retUri = "";	
+    QString strUri = QString::fromLocal8Bit(sUri.c_str());
     QStringList uriList = strUri.split("/");
     for(int i = 0; i < uriList.count(); i++)
     {

+ 1 - 1
common/CHttpClient.h

@@ -2,7 +2,7 @@
 #define CHTTPCLIENT_H
 
 #include <QString>
-#include <WinSock2.h>
+
 #include <curl/curl.h>
 #include <vector>
 

+ 5 - 0
common/CHttpInterceptor.cpp

@@ -64,6 +64,11 @@ void CHttpInterceptor::get(CHttpRequestPackage requestPkg)
     m_mReqPkgList.push_back(std::move(requestPkg));
 }
 
+QString CHttpInterceptor::getHttpUrl() const
+{
+    return m_sUrl;
+}
+
 void CHttpInterceptor::getUrl(CHttpRequestPackage requestPkg)
 {
     std::scoped_lock lock(m_mReqPkgMutex);

+ 2 - 1
common/CHttpInterceptor.h

@@ -19,13 +19,14 @@ public:
     ~CHttpInterceptor();
 
     void init(QString sIp, QString sPort, bool bUseHttps = false);
+    QString getHttpUrl() const;
 
     void post(CHttpRequestPackage requestPkg);
     void get(CHttpRequestPackage requestPkg);
     void getUrl(CHttpRequestPackage requestPkg);
 	void put(CHttpRequestPackage requestPkg);
 	void downLoad(CHttpRequestPackage requestPkg);
-    virtual void requestProc(const CHttpRequestPackage requestPkg)=0;
+    virtual void requestProc(const CHttpRequestPackage requestPkg)=0;    
 protected:
 
     void initHeads(const CHttpRequestPackage &requestPkg);

+ 61 - 0
common/CMultiMonitorEnumerator.h

@@ -0,0 +1,61 @@
+#ifndef CMULTIMONITORENUMERATOR_H
+#define CMULTIMONITORENUMERATOR_H
+
+#include <windows.h>
+#include <vector>
+
+class CMultiMonitorEnumerator
+{
+public:
+    CMultiMonitorEnumerator()
+    {
+        m_iMontorNumber = 0;
+        EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM)this);
+    }
+
+    int getMontorNumber()
+    {
+        return m_iMontorNumber;
+    }
+protected:
+  //枚举显示器回调函数
+  static BOOL CALLBACK MonitorEnumProc(
+    HMONITOR hMonitor,  // handle to display monitor
+    HDC hdcMonitor,     // handle to monitor-appropriate device context
+    LPRECT lprcMonitor, // pointer to monitor intersection rectangle
+    LPARAM dwData       // data passed from EnumDisplayMonitors
+    )
+    {
+        CMultiMonitorEnumerator *pThis = (CMultiMonitorEnumerator *)dwData;
+
+        //GetMonitorInfo 获取显示器信息
+        MONITORINFOEX infoEx;
+        memset(&infoEx, 0,  sizeof(infoEx));
+        infoEx.cbSize = sizeof(infoEx);
+        if(GetMonitorInfo(hMonitor, &infoEx))
+        {
+            DISPLAYCONFIG_TOPOLOGY_ID currentTopologyId;
+            UINT32 requiredPaths, requiredModes;
+            GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, &requiredModes);
+            std::vector<DISPLAYCONFIG_PATH_INFO> paths(requiredPaths);
+            std::vector<DISPLAYCONFIG_MODE_INFO> modes(requiredModes);
+            QueryDisplayConfig(QDC_DATABASE_CURRENT, &requiredPaths, paths.data(), &requiredModes, modes.data(), &currentTopologyId);
+
+            if(currentTopologyId == DISPLAYCONFIG_TOPOLOGY_CLONE)
+            {//复制屏
+                pThis->m_iMontorNumber += paths.size();
+            }
+            else
+            {
+                pThis->m_iMontorNumber++;
+            }
+            return true; //continue the enumeration
+        }
+
+        return false; //stop the enumeration
+  }
+private:
+    int m_iMontorNumber;
+};
+
+#endif // CMULTIMONITORENUMERATOR_H

+ 95 - 14
common/httpDataDef.h

@@ -3,7 +3,8 @@
 
 #include <QString>
 #include <QStringList>
-
+#include <map>
+#include <QDateTime>
 enum class HttpParamType
 {
     hptUrl = 1, //放到url后面
@@ -25,6 +26,7 @@ enum class RequestType
 {
 	rtUndefine = 0,
 	rtUpgrade,//客户端版本升级接口
+    rtGetSoftwareConfig,//获取检测黑名单
 	rtAgreement,//获取隐私协议接口
 	rtStudentClientConfig, //获取当前机构的客户端环境信息接口
 	rtSysNotice,//平台通知接口
@@ -94,9 +96,11 @@ public:
     QString sUri;
     QString sCommonStr;
     QString sCommonStr1;
+	QString sAdditionStr;
     HttpParamType eParamType;
     QStringList sParamList;
     QStringList sHeadList;
+    int nRetryCount;
     CHttpRequestPackage()
     {
         nRequestType = RequestType::rtUndefine;
@@ -105,6 +109,8 @@ public:
         sUri = "";
         sCommonStr = "";
         sCommonStr1 = "";
+		sAdditionStr = "";
+        nRetryCount = 0;
     }
 };
 
@@ -120,6 +126,16 @@ public:
     }
 };
 
+class CGetSoftwareConfig : public CBaseResponsePackage
+{
+public:
+    QString sSoftwareConfig;
+    CGetSoftwareConfig()
+    {
+        sSoftwareConfig = "";
+    }
+};
+
 class CUpgrade : public CBaseResponsePackage
 {
 public:
@@ -161,6 +177,7 @@ public:
 	bool bStudentCodeLogin;
 	bool bIdentifyNumberLogin;	
 	QString sLogoFileUrl;//" : "https://ecs-test-static.qmth.com.cn/org_logo/0/1613800689888.png",
+	QString sStudentClientBgPictureUrl;
 	QString sOeStudentSysName;//" : "网考 - 33d34sss3d",
 	bool bDisableMutiScreen;
 	bool bDisableRemoteAssistance;
@@ -178,11 +195,12 @@ public:
 		bStudentCodeLogin  = false;
 		bIdentifyNumberLogin = false;
 		sLogoFileUrl = "";
+		sStudentClientBgPictureUrl = "";
 		sOeStudentSysName = "";
-		bDisableMutiScreen = true;
-		bDisableRemoteAssistance = true;
-		bFullScreenTop = true;
-		bDisableVirtualCamera = true;
+        bDisableMutiScreen = false;
+        bDisableRemoteAssistance = false;
+        bFullScreenTop = false;
+        bDisableVirtualCamera = false;
 		sRootOrgId = "";// " : "0",
 		bShowQmthLogo = false;// " : "false",
 		bShowStudentClientAppQrcode = false;// " : "true",				
@@ -308,10 +326,12 @@ class CDownLoadFileInfo : public CBaseResponsePackage
 public:
     QString sFileName;
 	QString sModuleName;
+    QString sAdditionStr;
     CDownLoadFileInfo()
     {
         sFileName = "";
-		sModuleName == "";
+        sModuleName = "";
+        sAdditionStr = "";
     }
 };
 
@@ -353,6 +373,16 @@ public:
 	std::vector<CNoticeInfo> vNoticeList;
 };
 
+class CUpdateNoticeReadStatus : public CBaseResponsePackage
+{
+public:
+    QString sIds;
+    CUpdateNoticeReadStatus()
+    {
+        sIds = "";
+    }
+};
+
 class CAppDownLoadUrl : public CBaseResponsePackage
 {
 public:
@@ -545,10 +575,12 @@ class CStartAnswer : public CBaseResponsePackage
 public:
 	__int64 nDuration;
 	__int64 nExamRecordDataId;
+    __int64 nUsedExamSeconds;
 	CStartAnswer()
 	{
 		nDuration = 0;
 		nExamRecordDataId = 0;
+        nUsedExamSeconds = 0;
 	}
 };
 
@@ -693,6 +725,49 @@ public:
 	}
 };
 
+class CWeekTransform
+{
+public:
+	static QString getWeekCycles(std::vector<int> vWeek)
+	{
+		QStringList weekList;
+		for (int nWeek : vWeek)
+		{
+			weekList << mWeekCycle.at(nWeek);
+		}
+		return weekList.join(QString::fromLocal8Bit(","));
+	}
+
+	static bool isInTimeRange(QTime tCurrent, QStringList sExamCycleTimeRange)
+	{
+		bool bRet = false;		
+		for (QString sTimeRange : sExamCycleTimeRange)
+		{						
+			QStringList list = sTimeRange.split("~");
+			if(list.count() == 2)
+			{
+				QTime sStart = QTime::fromString(list[0], "hh:mm");
+				QTime sEnd = QTime::fromString(list[1], "hh:mm");
+				if (sStart < tCurrent && tCurrent < sEnd)
+				{
+					bRet = true;					
+				}				
+			}						
+		}
+		return bRet;
+	}
+private:
+	inline static const std::map<int, QString> mWeekCycle = {
+		{1, QString::fromLocal8Bit("周一")},
+		{2, QString::fromLocal8Bit("周二")},
+		{3, QString::fromLocal8Bit("周三")},
+		{4, QString::fromLocal8Bit("周四")},
+		{5, QString::fromLocal8Bit("周五")},
+		{6, QString::fromLocal8Bit("周六")},
+		{7, QString::fromLocal8Bit("周日")},
+	};
+};
+
 class CExamCourseInfo
 {
 public:
@@ -704,8 +779,8 @@ public:
 	QString sCourseName;// ,
 	QString sEndTime;// ,
 	bool bExamCycleEnabled;// true,
-	QString sExamCycleTimeRange;// [],
-	QString sExamCycleWeek;// [],
+    QStringList sExamCycleTimeRange;// [],
+    std::vector<int> vExamCycleWeek;// [],
 	__int64 nExamId;// 0,
 	QString sExamName;// ,
 	QString sExamStatus;// ,
@@ -735,8 +810,7 @@ public:
 		sCourseName = "";// ,
 		sEndTime = "";// ,
 		bExamCycleEnabled = false;// true,
-		sExamCycleTimeRange = "";// [],
-		sExamCycleWeek = "";// [],
+
 		nExamId = 0;// 0,
 		sExamName = "";// ,
 		sExamStatus = "";// ,
@@ -819,8 +893,8 @@ public:
 	QString sCourseName;// ,
 	QString sEndTime;// ,
 	bool bExamCycleEnabled;// true,
-	QString sExamCycleTimeRange;// [],
-	QString sExamCycleWeek;// [],
+    QStringList sExamCycleTimeRange;// [],
+    std::vector<int> vExamCycleWeek;// [],
 	__int64 nExamId;// 0,
 	QString sExamName;// ,
 	__int64 nExamStudentId;// 0,
@@ -838,8 +912,6 @@ public:
 		sCourseName = "";// ,
 		sEndTime = "";// ,
 		bExamCycleEnabled = false;
-		sExamCycleTimeRange = "";// [],
-		sExamCycleWeek = "";// [],
 		nExamId = 0;// 0,
 		sExamName = "";// ,
 		nExamStudentId = 0;// 0,
@@ -1038,9 +1110,11 @@ class CPreviewOffLineExamPaper : public CBaseResponsePackage
 {
 public:
 	QString sUrl;
+	int nRow;
 	CPreviewOffLineExamPaper()
 	{
 		sUrl = "";
+		nRow = -1;
 	}
 };
 
@@ -1107,6 +1181,11 @@ public:
 class CEndExam : public CBaseResponsePackage
 {
 public:
+    QString sModuleName;
+    CEndExam()
+    {
+        sModuleName = "";
+    }
 };
 
 class CGetEndExamInfo : public CBaseResponsePackage
@@ -1116,12 +1195,14 @@ public:
 	bool bIsWarn;
 	double fObjectiveAccuracy;
 	double fObjectiveScore;
+	bool bIsCalculate;
 	CGetEndExamInfo()
 	{
 		nExamRecordDataId = 0;
 		bIsWarn = false;
 		fObjectiveAccuracy = 0;
 		fObjectiveScore = 0;
+		bIsCalculate = false;
 	}
 };
 

+ 5 - 5
common/logproc.h

@@ -10,8 +10,8 @@
 
 #include "CLogTrack.h"
 
-#define myDebug()      (qDebug())
-#define myServerLog() (qInfo())
+#define myDebug()      (qDebug()<<__FILE__ << __LINE__<<",")
+#define myServerLog() (qInfo()<<__FILE__ << __LINE__<<",")
 
 static void CustomOutputMessage(QtMsgType type,
                          const QMessageLogContext &context,
@@ -31,7 +31,7 @@ static void CustomOutputMessage(QtMsgType type,
                               arg(msg);
             file.write(message.toLocal8Bit());
             file.close();
-            /*
+
             if(g_logPtr)
             {
                 CLogData ld;
@@ -43,13 +43,13 @@ static void CustomOutputMessage(QtMsgType type,
                 ld.pushLog("clientTime", current_date);
                 g_logPtr->putLog(ld);
             }
-			*/
+
         }
         break;
 
         case QtDebugMsg:
         {
-//            if(g_appInfoPtr->m_nShowDebugInfo)
+            if(g_appInfoPtr->m_bShowDebugInfo)
             {
                 QFile file("coe.cfgl");
                 file.open(QIODevice::WriteOnly | QIODevice::Append);

+ 30 - 4
common/popMsgBox.cpp

@@ -28,7 +28,7 @@ void popMsgBox::setMsg(QString sMsg, QString sTitle)
     ui->label_pmb_title->setText(sTitle);
 }
 
-void popMsgBox::initUI()
+void popMsgBox::initUI(POP_MSG_BTN btn)
 {
     int nCW = g_appInfoPtr->m_fRate*340;
     QDesktopWidget *dekwiget = QApplication::desktop();
@@ -42,28 +42,54 @@ void popMsgBox::initUI()
     ui->label_pmb_content->setGeometry(ui->label_pmb_icon->x() + ui->label_pmb_icon->width() + g_appInfoPtr->m_fRate*16,
                                        ui->label_pmb_title->y() + ui->label_pmb_title->height() + g_appInfoPtr->m_fRate*8,
                                    224, 80);
-    ui->btn_yes->setGeometry(ui->widget_pmb_bg->width() - g_appInfoPtr->m_fRate*(20 + 80),
+	if (btn == POP_MSG_BTN::pmb_yes)
+	{
+		ui->btn_no->setVisible(false);
+	}
+	ui->btn_yes->setGeometry(ui->widget_pmb_bg->width() - g_appInfoPtr->m_fRate*(20 + 80),
                              ui->widget_pmb_bg->height() - g_appInfoPtr->m_fRate*(20 + 30),
                              g_appInfoPtr->m_fRate*80, g_appInfoPtr->m_fRate*30);
     ui->btn_no->setGeometry(ui->btn_yes->x() - g_appInfoPtr->m_fRate*15 - ui->btn_yes->width(),
                             ui->btn_yes->y(), ui->btn_yes->width(), ui->btn_yes->height());
 }
 
-int popMsg(QString sMsg, QString sTitle, QWidget *parent)
+int popMsg(QString sMsg, QString sTitle, QWidget *parent, POP_MSG_BTN btn, int nStayTime)
 {
     popMsgBox msgDlg(parent);
 
     msgDlg.setMsg(sMsg, sTitle);
-    msgDlg.initUI();
+    msgDlg.initUI(btn);
+	if (nStayTime != 0)
+	{
+		msgDlg.setStayTime(nStayTime);
+	}
     return msgDlg.exec();
 }
 
+void popMsgBox::setStayTime(int nStayTime)
+{
+	m_timePtr = std::make_shared<QTimer>();
+	m_timePtr->setInterval(nStayTime * 1000);
+	connect(m_timePtr.get(), &QTimer::timeout, this, [&](){
+		accept();
+	});
+	m_timePtr->start();
+}
+
 void popMsgBox::on_btn_yes_clicked()
 {
+	if (m_timePtr != nullptr)
+	{
+		m_timePtr->stop();
+	}
     accept();
 }
 
 void popMsgBox::on_btn_no_clicked()
 {
+	if (m_timePtr != nullptr)
+	{
+		m_timePtr->stop();
+	}
     reject();
 }

+ 14 - 4
common/popMsgBox.h

@@ -2,11 +2,18 @@
 #define POPMSGBOX_H
 
 #include <QDialog>
+#include <QTimer>
 
 namespace Ui {
 class popMsgBox;
 }
 
+enum class POP_MSG_BTN
+{
+	pmb_yes = 1,
+	pmb_yse_no
+};
+
 class popMsgBox : public QDialog
 {
     Q_OBJECT
@@ -16,16 +23,19 @@ public:
     ~popMsgBox();
 
     void setMsg(QString sMsg, QString sTitle);
-    void initUI();
+    void initUI(POP_MSG_BTN btn);
+	
+	void setStayTime(int nStayTime);
 private slots:
     void on_btn_yes_clicked();
 
-    void on_btn_no_clicked();
+    void on_btn_no_clicked();	
 
 private:
-    Ui::popMsgBox *ui;
+    Ui::popMsgBox *ui;	
+	std::shared_ptr<QTimer> m_timePtr;
 };
 
-extern int popMsg(QString sMsg, QString sTitle, QWidget *parent);
+extern int popMsg(QString sMsg, QString sTitle, QWidget *parent, POP_MSG_BTN btn = POP_MSG_BTN::pmb_yse_no, int nStayTime = 0);
 
 #endif // POPMSGBOX_H

+ 2 - 2
common/popMsgBox.ui

@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>400</width>
-    <height>300</height>
+    <width>465</width>
+    <height>341</height>
    </rect>
   </property>
   <property name="windowTitle">

+ 7 - 3
component/CKeyBoardHook.cpp

@@ -55,9 +55,6 @@ LRESULT CALLBACK CKeyBoardHook::Hotkey_Filter(int nCode, WPARAM wParam, LPARAM l
 
         if (WM_KEYDOWN == wParam || wParam == WM_SYSKEYDOWN)  //如果按键为按下状态
         {
-//			_WRITELOGW(("WM_SYSKEYDOWN vkCode:" + IntToStr((int)Key_Info->vkCode) + " scanCode:"
-//				+ IntToStr((int)Key_Info->scanCode) + " flags:" +
-//				IntToStr((int)Key_Info->flags)).c_str());
             if(g_appInfoPtr->m_bFullScreenTop)
             {
                 if (Key_Info->vkCode == VK_RWIN || Key_Info->vkCode == VK_LWIN) //屏敝 WIN
@@ -114,6 +111,13 @@ LRESULT CALLBACK CKeyBoardHook::Hotkey_Filter(int nCode, WPARAM wParam, LPARAM l
                 {
                     return 1;
                 }
+
+                if(bCtrlKeyDown && (Key_Info->vkCode == 0x58 ||
+                                    Key_Info->vkCode == 0x56 ||
+                                    Key_Info->vkCode == 0x43))
+                {
+                    return 1;
+                }
             }
 
             if ((Key_Info->vkCode == VK_F11) && bCtrlKeyDown

+ 2 - 2
component/CKeyBoardHook.h

@@ -2,7 +2,7 @@
 #define CKEYBOARDHOOK_H
 
 #include <windows.h>
-#include <boost/shared_ptr.hpp>
+#include <memory>
 
 
 class CKeyBoardHook
@@ -23,6 +23,6 @@ private:
     HHOOK g_HotKey;
 };
 
-extern boost::shared_ptr<CKeyBoardHook> g_keyBoardHookPtr;
+extern std::shared_ptr<CKeyBoardHook> g_keyBoardHookPtr;
 
 #endif // CKEYBOARDHOOK_H

+ 3 - 0
component/CWebsocketProc.cpp

@@ -63,6 +63,7 @@ void CWebsocketProc::open()
         qDebug()<<url.url();
         m_pWebsocket->open(url);
         m_pTime->start(m_nTimeOutSecord*1000);
+        myServerLog()<<url.url();
     }
 
 }
@@ -119,6 +120,7 @@ void CWebsocketProc::onDisconnected()
         emit networkWeak();
     }    
     qDebug()<<"Websocket Disconnected";
+    myServerLog() <<"Websocket Disconnected";
 }
 
 //连接成功会触发这个槽函数
@@ -129,6 +131,7 @@ void CWebsocketProc::onConnected()
     m_pTime->stop();
     emit connected();
     qDebug()<<"Websocket Connected";
+    myServerLog()<<"Websocket Connected";
 }
 
 //收到服务发来的消息会触发这个槽函数

+ 13 - 3
component/clOperation.cpp

@@ -113,7 +113,7 @@ void clOperation::setOperationType(CL_OPERATION_TYPE cot)
         ui->btn4->setStyleSheet(QString(R"(QPushButton
                                         {
                                             outline:none;
-                                            border:%1px solid rgba(148,219,197,1);
+                                            border:1px solid rgba(148,219,197,1);
                                             border-radius:%2px;
                                             background:rgba(255,255,255,1);
                                             font-size:%3px;
@@ -132,12 +132,12 @@ void clOperation::setOperationType(CL_OPERATION_TYPE cot)
                                             font-family:"Microsoft YaHei";
                                             font-weight:500;
                                             color:rgba(255,255,255,1);
-                                        })").arg((int)(g_appInfoPtr->m_fRate*1))
+                                        })")//.arg((int)(g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1))
                                             .arg((int)(g_appInfoPtr->m_fRate*6))
                                             .arg((int)(g_appInfoPtr->m_fRate*12))
                                             .arg((int)(g_appInfoPtr->m_fRate*6))
                                             .arg((int)(g_appInfoPtr->m_fRate*12)));
-}
+    }
 }
 
 bool clOperation::eventFilter(QObject *obj, QEvent *ev)
@@ -182,6 +182,16 @@ bool clOperation::eventFilter(QObject *obj, QEvent *ev)
     return QWidget::eventFilter(obj, ev);
 }
 
+void clOperation::setBtn1Enable(bool bEnable)
+{
+	if (m_operationType == CL_OPERATION_TYPE::cot_online_exam
+		|| m_operationType == CL_OPERATION_TYPE::cot_online_practice
+		|| m_operationType == CL_OPERATION_TYPE::cot_online_homework)
+	{
+		ui->btn1->setEnabled(bEnable);
+	}
+}
+
 void clOperation::on_btn1_clicked()
 {
     if(m_operationType == CL_OPERATION_TYPE::cot_online_exam

+ 1 - 0
component/clOperation.h

@@ -40,6 +40,7 @@ public:
 
     void setUI(const int nWidth, const int nHeight, CL_OPERATION_TYPE cot);
 	void setOperationType(CL_OPERATION_TYPE cot);
+	void setBtn1Enable(bool bEnable);
 private slots:
     void on_btn1_clicked();
 

+ 33 - 3
face/CFaceRecProc.cpp

@@ -5,7 +5,7 @@
 #include "CAppInfo.h"
 #include "logproc.h"
 
-
+#include <QTextCodec>
 
 std::shared_ptr<CFaceRecProc> g_faceRecProcPtr = nullptr;
 
@@ -144,6 +144,34 @@ void CFaceRecProc::destroyHandle()
     }
 }
 
+bool CFaceRecProc::getFaceCount(cv::Mat faceMat, int &nFaceCount)
+{
+    try
+    {
+        SeetaImageData simg;
+        simg.height = faceMat.rows;
+        simg.width = faceMat.cols;
+        simg.channels = faceMat.channels();
+        simg.data = faceMat.data;
+
+        SeetaFaceInfoArray faces = m_pFaceDetector->detect(simg);
+        nFaceCount = faces.size;
+
+        return true;
+    }
+    catch (const std::exception &e)
+    {
+        m_sErrMsg = QString::fromLocal8Bit("人脸比对失败,%1").arg(e.what());
+//        myServerLog()<<m_sErrMsg;
+        return false;
+    }
+}
+
+bool CFaceRecProc::getFaceCount(QString sFaceImage, int &nFaceCount)
+{
+    cv::Mat matFaceImg = cv::imread(sFaceImage.toLocal8Bit().data());
+    return getFaceCount(matFaceImg, nFaceCount);
+}
 
 bool CFaceRecProc::faceCompare(cv::Mat faceMat, cv::Mat baseMat, int &nfaceCount, float &fScore)
 {
@@ -406,6 +434,7 @@ bool CFaceRecProc::setBaseImage(cv::Mat baseMat)
         simg.data = baseMat.data;
 
         SeetaFaceInfoArray faces = m_pFaceDetector->detect(simg);
+        myDebug()<<"detect";
         if(faces.size <= 0)
         {
             m_sErrMsg = QString::fromLocal8Bit("未检测到底照人脸");
@@ -421,8 +450,9 @@ bool CFaceRecProc::setBaseImage(cv::Mat baseMat)
 
         SeetaPointF points[5];
         m_pFaceLandmarker->mark(simg, faces.data[0].pos, points);
+        myDebug()<<"mark";
         m_pFaceRecognizer->Extract(simg, points, m_baseFeaturePtr.get());
-
+        myDebug()<<"Extract";
         return true;
     }
     catch (const std::exception &e)
@@ -434,7 +464,7 @@ bool CFaceRecProc::setBaseImage(cv::Mat baseMat)
 
 bool CFaceRecProc::setBaseImage(QString sBaseImge)
 {
-    cv::Mat matBaseImg = cv::imread(sBaseImge.toStdString());
+    cv::Mat matBaseImg = cv::imread(sBaseImge.toLocal8Bit().toStdString());
     return setBaseImage(matBaseImg);
 }
 

+ 94 - 65
face/faceCompare.cpp

@@ -37,46 +37,17 @@ faceCompare::faceCompare(QWidget *parent) :
         CHttpRequestPackage hrp;
         hrp.sUri = g_appInfoPtr->m_sStudentPhotoPath;
         hrp.sCommonStr = sFileName;
+        hrp.sCommonStr1 = __FILE__;
         hrp.nRequestType = RequestType::rtDownLoadFile;
-
+        hrp.nRetryCount = 3;
         g_httpBllPtr->downLoad(hrp);
     }
 
-    m_pinitTimer = std::make_shared<QTimer>();
-    m_pinitTimer->setInterval(1000);
-    connect(m_pinitTimer.get(), &QTimer::timeout, this, [&](){
-        m_pinitTimer->stop();
-        if(!m_cam.open(0))
-        {
-            ShowMsg(QString::fromLocal8Bit("打开摄像头失败"), this);
-			return;
-        }
-
-        int inWidth = ui->widget_fc_camera->width();
-        m_cam.set(CV_CAP_PROP_FRAME_WIDTH, inWidth);
-        int inHeight = ui->widget_fc_camera->height();
-        m_cam.set(CV_CAP_PROP_FRAME_HEIGHT, inHeight);
-        m_cam.set(CV_CAP_PROP_FPS, 5);
-        m_pVideoTimer->start();
-
-
-    });
-
     m_pVideoTimer = std::make_shared<QTimer>();
     m_pVideoTimer->setInterval(200);
     connect(m_pVideoTimer.get(), &QTimer::timeout, this, [&](){
-
-        cv::Mat frame;
-        m_cam>>frame;
-
-        if(frame.empty())
-        {
-            qDebug()<<"widgetCameraTest frame is empty";
-            return;
-        }
-
-        m_nCurImage = frame;
-        QImage img = CCommonTools::Mat2QImage(frame);
+        std::scoped_lock lock(m_imageMutex);
+        QImage img = CCommonTools::Mat2QImage(m_nCurImage);
 
         ui->widget_fc_camera->setAutoFillBackground(true);
         QPalette palette;
@@ -84,27 +55,71 @@ faceCompare::faceCompare(QWidget *parent) :
         ui->widget_fc_camera->setPalette(palette);
     });
 
-	m_pinitTimer->start();
+    g_clientVideoProcPtr->startTest(this);
+
+    m_pVideoTimer->start();
 
 	connect(this, &faceCompare::compareFailed, this, [&](QString sErrorMsg) {
-		ShowMsg(sErrorMsg, this);
+        ShowMsg(sErrorMsg, this, MSG_ICON_TYPE::mit_error);
 		ui->btn_fc_compare->setEnabled(true);
 	});
-
+	
     m_bIsRun = true;
-    m_thread = std::thread(std::bind(&faceCompare::threadProc, this));
+    m_thread = std::thread(std::bind(&faceCompare::threadProc, this));	
 }
 
 faceCompare::~faceCompare()
 {
     m_pVideoTimer->stop();
+    g_clientVideoProcPtr->stopTest();
 
     m_bIsRun = false;
     m_thread.join();
-
+    awMsgBox::clear(this);
     delete ui;
 }
 
+void faceCompare::onRenderVideoFrame(const char* userId, TRTCVideoStreamType streamType, TRTCVideoFrame* frame)
+{
+    if(g_clientVideoProcPtr->isCameraTest())
+    {
+        __int64 nServerTime = g_appInfoPtr->serverMTime();
+        if(nServerTime - m_lastFaceTime >= 100)
+        {
+            m_lastFaceTime = nServerTime;
+            cv::Mat matImg;
+            cv::cvtColor(cv::Mat(frame->height, frame->width, CV_8UC4, frame->data), matImg, CV_RGBA2RGB);
+            std::scoped_lock lock(m_imageMutex);
+            m_nCurImage = matImg.clone();
+        }
+    }
+}
+
+void faceCompare::onDownLoadFile(CDownLoadFileInfo downLoadFileInfo)
+{
+    if(downLoadFileInfo.sModuleName == __FILE__)
+    {
+        if (downLoadFileInfo.nCode == 200)
+        {
+            QString sFileName = g_appInfoPtr->m_sStudentPhotoPath.right(g_appInfoPtr->m_sStudentPhotoPath.length() - g_appInfoPtr->m_sStudentPhotoPath.lastIndexOf("/") - 1);
+            sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
+            ui->label_fc_basePhoto->setPixmap(QPixmap(sFileName).scaled(ui->label_fc_basePhoto->width(), ui->label_fc_basePhoto->height(),
+                Qt::IgnoreAspectRatio));
+        }
+        else
+        {
+            if(downLoadFileInfo.sMessage.isEmpty())
+            {
+                ShowMsg(QString::fromLocal8Bit("下载失败"), this, MSG_ICON_TYPE::mit_error);
+            }
+            else
+            {
+                ShowMsg(downLoadFileInfo.sMessage, this, MSG_ICON_TYPE::mit_error);
+            }
+        }
+    }
+}
+
 void faceCompare::initUI()
 {
     QDesktopWidget *dekwiget = QApplication::desktop();
@@ -118,7 +133,7 @@ void faceCompare::initUI()
     ui->btn_fc_close->setGeometry(ui->widget_fc_BG->width() - g_appInfoPtr->m_fRate*(20 + 16),
                                   g_appInfoPtr->m_fRate*16, g_appInfoPtr->m_fRate*16, g_appInfoPtr->m_fRate*16);
     ui->label_HLine->setGeometry(0, ui->label_fc_title->y() + ui->label_fc_title->height() + g_appInfoPtr->m_fRate*16,
-                                 ui->widget_fc_BG->width(), g_appInfoPtr->m_fRate*1);
+                                 ui->widget_fc_BG->width(), g_appInfoPtr->m_fRate*1 < 1 ? 1 : g_appInfoPtr->m_fRate*1);
     ui->label_fc_basePhoto->setGeometry(g_appInfoPtr->m_fRate*30, ui->label_HLine->y() + ui->label_HLine->height() + g_appInfoPtr->m_fRate*20,
                                         g_appInfoPtr->m_fRate*140, g_appInfoPtr->m_fRate*180);
 	QString sFileName = g_appInfoPtr->m_sStudentPhotoPath.right(g_appInfoPtr->m_sStudentPhotoPath.length() - g_appInfoPtr->m_sStudentPhotoPath.lastIndexOf("/") - 1);
@@ -140,6 +155,7 @@ void faceCompare::initUI()
                                    g_appInfoPtr->m_fRate*580, g_appInfoPtr->m_fRate*100);
 }
 
+
 void faceCompare::on_btn_fc_compare_clicked()
 {
     if(g_faceRecProcPtr == nullptr)
@@ -152,16 +168,21 @@ void faceCompare::on_btn_fc_compare_clicked()
             sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
             if(!QFile::exists(sFileName))
             {
-                ShowMsg(QString::fromLocal8Bit("底照下载失败,请检查网络"), this);
+                ShowMsg(QString::fromLocal8Bit("底照下载失败,请检查网络"), this, MSG_ICON_TYPE::mit_error);
                 return;
             }
 			QFileInfo filePath(sFileName);
 			
-            g_faceRecProcPtr->setBaseImage(filePath.absoluteFilePath());
+            if(!g_faceRecProcPtr->setBaseImage(filePath.absoluteFilePath()))
+            {
+                ShowMsg(g_faceRecProcPtr->errorMsg(), this, MSG_ICON_TYPE::mit_error);
+                return;
+            }
         }
         else
         {
-            ShowMsg(QString::fromLocal8Bit("当前考试未底照"), this);
+            ShowMsg(QString::fromLocal8Bit("本场考试需要进行人脸检测,但是您没有上传底照,请联系老师"), this, MSG_ICON_TYPE::mit_error);
+			QTimer::singleShot(3000, this, [&]() { emit exitFaceCompare(); });
             return;
         }
     }
@@ -171,6 +192,7 @@ void faceCompare::on_btn_fc_compare_clicked()
 
 void faceCompare::on_btn_fc_close_clicked()
 {
+    m_bIsRun = false;
     emit exitFaceCompare();
 }
 
@@ -208,6 +230,9 @@ void faceCompare::threadProc()
                     }
                     else
                     {
+#ifdef _DEBUG
+						g_appInfoPtr->m_oExamInfo.nWarnThreshold = 1;
+#endif // _DEBUG
                         if(fScore*100 > g_appInfoPtr->m_oExamInfo.nWarnThreshold)
                         {
 							m_fScore = fScore;
@@ -221,6 +246,7 @@ void faceCompare::threadProc()
                             CHttpRequestPackage hrp;
                             hrp.sUri = "/api/ecs_oe_student/client/exam/process/upload";
                             hrp.nRequestType = RequestType::rtProcessUpload;
+                            hrp.sCommonStr = __FILE__;
                             hrp.sParamList.push_back(QString("formdataFileType,file,%1").arg(sFileName));
                             hrp.sParamList.push_back(QString("md5,%1").arg(CCommonTools::fileMd5(sFileName)));
 
@@ -247,31 +273,34 @@ void faceCompare::threadProc()
 //文件上传
 void faceCompare::onProcessUpload(CProcessUpload processUpload)
 {
-    if(processUpload.nCode == 200)
+    if(processUpload.sCommonStr == __FILE__)
     {
-		CHttpRequestPackage hrp;
-		hrp.sUri = "/api/ecs_oe_student/client/exam/process/saveFaceCompareResult";
-		hrp.nRequestType = RequestType::rtSaveFaceCompareResult;		
-		Json::Value jBody = Json::Value::null;
-		jBody["faceCompareResult"] = QString::number(m_fScore).toStdString();
-		jBody["fileUrl"] = processUpload.sFileUrl.toStdString();
-		jBody["pass"] = true;
-		jBody["processTime"] = m_nTime;
-		jBody["stranger"] = m_nFaceCount > 1;
-		hrp.sParamList.push_back(QString("CustomBody,%1").arg(jBody.toStyledString().c_str()));
-		hrp.eParamType = HttpParamType::hptCustomBody;
-	
-		g_httpBllPtr->post(hrp);
-    }
-    else
-    {
-        if(processUpload.sMessage.isEmpty())
+        if(processUpload.nCode == 200)
         {
-            ShowMsg(QString::fromLocal8Bit("上传照片失败"), this);
+            CHttpRequestPackage hrp;
+            hrp.sUri = "/api/ecs_oe_student/client/exam/process/saveFaceCompareResult";
+            hrp.nRequestType = RequestType::rtSaveFaceCompareResult;
+            Json::Value jBody = Json::Value::null;
+            jBody["faceCompareResult"] = QString::number(m_fScore).toStdString();
+            jBody["fileUrl"] = processUpload.sFileUrl.toStdString();
+            jBody["pass"] = true;
+            jBody["processTime"] = m_nTime;
+            jBody["stranger"] = m_nFaceCount > 1;
+            hrp.sParamList.push_back(QString("CustomBody,%1").arg(jBody.toStyledString().c_str()));
+            hrp.eParamType = HttpParamType::hptCustomBody;
+
+            g_httpBllPtr->post(hrp);
         }
         else
         {
-            ShowMsg(processUpload.sMessage, this);
+            if(processUpload.sMessage.isEmpty())
+            {
+                ShowMsg(QString::fromLocal8Bit("上传照片失败"), this, MSG_ICON_TYPE::mit_error);
+            }
+            else
+            {
+                ShowMsg(processUpload.sMessage, this, MSG_ICON_TYPE::mit_error);
+            }
         }
     }
 }
@@ -281,7 +310,7 @@ void faceCompare::onSaveFaceCompareResult(CBaseResponsePackage res)
 {
     if(res.nCode == 200)
     {
-        ShowMsg(QString::fromLocal8Bit("人脸比对成功"), this);
+        ShowMsg(QString::fromLocal8Bit("人脸比对成功"), this, MSG_ICON_TYPE::mit_succeed);
         QTimer::singleShot(3000, this, [&](){
             emit faceComparePass();
         });
@@ -291,11 +320,11 @@ void faceCompare::onSaveFaceCompareResult(CBaseResponsePackage res)
     {
         if(res.sMessage.isEmpty())
         {
-            ShowMsg(QString::fromLocal8Bit("保存人脸识别信息失败"), this);
+            ShowMsg(QString::fromLocal8Bit("保存人脸识别信息失败"), this, MSG_ICON_TYPE::mit_error);
         }
         else
         {
-            ShowMsg(res.sMessage, this);
+            ShowMsg(res.sMessage, this, MSG_ICON_TYPE::mit_error);
         }
     }
 }

+ 11 - 4
face/faceCompare.h

@@ -8,12 +8,13 @@
 #include <thread>
 #include <mutex>
 #include "CHttpBll.h"
+#include "CLiveViodeProc.h"
 
 namespace Ui {
 class faceCompare;
 }
 
-class faceCompare : public QWidget
+class faceCompare : public QWidget, ITRTCVideoRenderCallback
 {
     Q_OBJECT
 
@@ -33,20 +34,26 @@ private slots:
 
     void onProcessUpload(CProcessUpload processUpload); //文件上传
     void onSaveFaceCompareResult(CBaseResponsePackage res);//保存人脸识别比对验证结果
+
+    void onDownLoadFile(CDownLoadFileInfo downLoadFileInfo);
 private:
+    void onRenderVideoFrame(const char* userId, TRTCVideoStreamType streamType, TRTCVideoFrame* frame);
     void initUI();
     Ui::faceCompare *ui;
 
-    std::shared_ptr<QTimer> m_pVideoTimer;
-    std::shared_ptr<QTimer> m_pinitTimer;
-    cv::VideoCapture m_cam;
+	
+	std::shared_ptr<QTimer> m_pVideoTimer;
+
 
     void threadProc();
 
+    __int64 m_lastFaceTime = 0;
+
     std::thread m_thread;
     bool m_bIsRun;
     bool m_bStartCompare;
     cv::Mat m_nCurImage;
+    std::mutex m_imageMutex;
 	float m_fScore;
 	int m_nFaceCount;
 	int m_nTime;

+ 512 - 413
face/faceLiveness.cpp

@@ -36,44 +36,39 @@ faceLiveness::faceLiveness(QWidget *parent) :
         hrp.sUri = g_appInfoPtr->m_sStudentPhotoPath;
         hrp.sCommonStr = sFileName;
         hrp.nRequestType = RequestType::rtDownLoadFile;
-
+        hrp.nRetryCount = 3;
         g_httpBllPtr->downLoad(hrp);
     }
 
-    m_pinitTimer = std::make_shared<QTimer>();
-    m_pinitTimer->setInterval(1000);
-    connect(m_pinitTimer.get(), &QTimer::timeout, this, [&](){
-        m_pinitTimer->stop();
-        if(!m_cam.open(0))
-        {
-            ShowMsg(QString::fromLocal8Bit("打开摄像头失败"), this);
-            return;
-        }
-
-        int inWidth = ui->widget_fc_camera->width();
-        m_cam.set(CV_CAP_PROP_FRAME_WIDTH, inWidth);
-        int inHeight = ui->widget_fc_camera->height();
-        m_cam.set(CV_CAP_PROP_FRAME_HEIGHT, g_appInfoPtr->m_fRate*431);
-        m_cam.set(CV_CAP_PROP_FPS, 5);
-        m_pVideoTimer->start();
-
-
-    });
+//    m_cam.set(CV_CAP_PROP_FOURCC, CV_FOURCC('M', 'J', 'P', 'G'));
+//    int inWidth = ui->widget_fc_camera->width();
+//    m_cam.set(CV_CAP_PROP_FRAME_WIDTH, inWidth);
+//    int inHeight = ui->widget_fc_camera->height();
+//    m_cam.set(CV_CAP_PROP_FRAME_HEIGHT, inHeight);
+//    m_cam.set(CV_CAP_PROP_FPS, 5);
+
+//    m_pinitTimer = std::make_shared<QTimer>();
+//    m_pinitTimer->setInterval(1000);
+//    connect(m_pinitTimer.get(), &QTimer::timeout, this, [&](){
+//        m_pinitTimer->stop();
+//        if(!m_cam.open(0))
+//        {
+//            ShowMsg(QString::fromLocal8Bit("打开摄像头失败"), this, MSG_ICON_TYPE::mit_error);
+//            return;
+//        }
+
+//        m_pVideoTimer->start();
+//    });
 
     m_pVideoTimer = std::make_shared<QTimer>();
     m_pVideoTimer->setInterval(200);
     connect(m_pVideoTimer.get(), &QTimer::timeout, this, [&](){
-
         cv::Mat frame;
-        m_cam>>frame;
-
-        if(frame.empty())
         {
-            qDebug()<<"widgetCameraTest frame is empty";
-            return;
+            std::scoped_lock sl(m_imageMutex);
+            frame = m_nCurImage;
         }
 
-
         QImage img = CCommonTools::Mat2QImage(frame);
 
         ui->widget_fc_camera->setAutoFillBackground(true);
@@ -88,13 +83,14 @@ faceLiveness::faceLiveness(QWidget *parent) :
         }
     });
 
-    m_pinitTimer->start();
+    g_clientVideoProcPtr->startTest(this);
+    m_pVideoTimer->start();
 
     //生成动作列表
     QStringList sActionList = g_appInfoPtr->m_oExamInfo.sActionOptions.split(",");
     if(sActionList.count() != g_appInfoPtr->m_oExamInfo.nActionNum)
     {
-        ShowMsg(QString::fromLocal8Bit("初始化活体动作失败"), this);
+        ShowMsg(QString::fromLocal8Bit("初始化活体动作失败"), this, MSG_ICON_TYPE::mit_error);
         emit faceLivenessFaild();
         return;
     }
@@ -105,7 +101,7 @@ faceLiveness::faceLiveness(QWidget *parent) :
     m_livenessList.push_back(lvif);
 	m_currentVerifyInfo = lvif;
     if(g_appInfoPtr->m_oExamInfo.sActionOrder == "FIXED")
-    {
+    {		
         for(QString sAction : sActionList)
         {
             LivenessVerifyInfo lvi;
@@ -121,7 +117,7 @@ faceLiveness::faceLiveness(QWidget *parent) :
         for(int i = 0; i < g_appInfoPtr->m_oExamInfo.nActionNum; i++)
         {
             LivenessVerifyInfo lvi;
-            lvi.sActionType = sActionList[list[i]];
+            lvi.sActionType = sActionList[list[i]-1];
             lvi.nActionLeftSceonds = g_appInfoPtr->m_oExamInfo.nActionDuration;
             m_livenessList.push_back(lvi);
         }
@@ -144,15 +140,39 @@ faceLiveness::faceLiveness(QWidget *parent) :
 
 faceLiveness::~faceLiveness()
 {
-    m_ActionTimer->stop();
     m_pVideoTimer->stop();
+    m_ActionTimer->stop();
+
+    g_clientVideoProcPtr->stopTest();
 
     m_bIsRun = false;
     m_thread.join();
-
+    awMsgBox::clear(this);
     delete ui;
 }
 
+void faceLiveness::onRenderVideoFrame(const char* userId, TRTCVideoStreamType streamType, TRTCVideoFrame* frame)
+{
+    if(g_clientVideoProcPtr->isCameraTest())
+    {
+        __int64 nServerTime = g_appInfoPtr->serverMTime();
+        if(nServerTime - m_lastFaceTime >= 100)
+        {
+            m_lastFaceTime = nServerTime;
+            cv::Mat matImg;
+            cv::cvtColor(cv::Mat(frame->height, frame->width, CV_8UC4, frame->data), matImg, CV_RGBA2RGB);
+            if(matImg.empty())
+            {
+                qDebug()<<"widgetCameraTest frame is empty";
+                return;
+            }
+
+            std::scoped_lock sl(m_imageMutex);
+            m_nCurImage = matImg;
+        }
+    }
+}
+
 void faceLiveness::initUI()
 {
     QDesktopWidget *dekwiget = QApplication::desktop();
@@ -203,177 +223,191 @@ void faceLiveness::initUI()
 
 void faceLiveness::threadProc()
 {
-    while(m_bIsRun)
-    {
-        if(m_bStartCompare)
-        {
-            if(m_imgList.begin() != m_imgList.end())
-            {
-                myDebug()<<"m_imgList:"<<m_imgList.size();
-
-                cv::Mat matImage;
-                {
-                    std::scoped_lock lock(m_imgMutex);
-                    if(m_imgList.begin() != m_imgList.end())
-                    {
-                        matImage = (*m_imgList.begin()).clone();
-                        m_imgList.erase(m_imgList.begin());
-                    }
-                    else
-                    {
-                        continue;
-                    }
-                }
-
-                if(matImage.empty())
-                {
-                    myDebug()<<"matImage.empty";
-                    continue;
-                }
-
-                if(!m_currentVerifyInfo.bIsVerify)
-                {
-                    verifyAction(matImage);
-                }
-            }
-        }
-        else
-        {
-            Sleep(100);
-        }
-    }
+	try
+	{
+		while (m_bIsRun)
+		{
+			if (m_bStartCompare)
+			{
+				if (m_imgList.begin() != m_imgList.end())
+				{
+					myDebug() << "m_imgList:" << m_imgList.size();
+
+					cv::Mat matImage;
+					{
+						std::scoped_lock lock(m_imgMutex);
+						if (m_imgList.begin() != m_imgList.end())
+						{
+							matImage = (*m_imgList.begin()).clone();
+							m_imgList.erase(m_imgList.begin());
+						}
+						else
+						{
+							continue;
+						}
+					}
+
+					if (matImage.empty())
+					{
+						myDebug() << "matImage.empty";
+						continue;
+					}
+
+					if (!m_currentVerifyInfo.bIsVerify)
+					{
+						verifyAction(matImage);
+					}
+				}
+			}
+			else
+			{
+				Sleep(100);
+			}
+		}
+	}
+	catch (const std::exception &e)
+	{
+		myDebug()<<QString::fromLocal8Bit("人脸比对失败,%1").arg(e.what());		
+	}
 }
 
 void faceLiveness::verifyAction(cv::Mat matImage)
 {
-    bool bHasStatus = false;
-    int nFaceCount = 0;
-    float fScore = 0;
-
-    if(m_currentVerifyInfo.bIsVerify)
-    {
-        return;
-    }
-
-    m_currentVerifyInfo.matImage = matImage;
-
-    if(m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_FACE_DETECT)
-    {
-        SeetaRect rt;
-        if(!g_faceRecProcPtr->compareWithBase(matImage, nFaceCount, fScore, rt))
-        {
-            m_currentVerifyInfo.sErrorMsg = g_faceRecProcPtr->errorMsg();
-            myServerLog()<<g_faceRecProcPtr->errorMsg();
-            return;
-        }
-
-        m_currentVerifyInfo.nFaceCount = nFaceCount;
-        m_currentVerifyInfo.fSimilarity = fScore;
-
-        if(nFaceCount != 1)
-        {
-            myServerLog()<< QString::fromLocal8Bit("活体检测人脸数量异常,")  << nFaceCount;
-            m_currentVerifyInfo.sErrorMsg = QString::fromLocal8Bit("活体检测人脸数量异常,").arg(nFaceCount);
-            return;
-        }
-
-        if(fScore*100 > g_appInfoPtr->m_oExamInfo.nWarnThreshold)
-        {
-            bool bRealness = false;
-            if(!g_faceRecProcPtr->faceRealness(matImage, bRealness))
-            {
-                myServerLog()<<g_faceRecProcPtr->errorMsg();
-                m_currentVerifyInfo.sErrorMsg = g_faceRecProcPtr->errorMsg();
-                return;
-            }
-            //验证成功
-            if(bRealness)
-            {
-                m_currentVerifyInfo.nEndTime = g_appInfoPtr->serverMTime();
-                m_currentVerifyInfo.nRealness = 1;
-                m_currentVerifyInfo.bPass = true;
-                m_currentVerifyInfo.bIsVerify = true;
-
-                myServerLog()<<m_currentVerifyInfo.sActionType << ":" << fScore;
-
-                return;
-            }
-            else
-            {
-                m_currentVerifyInfo.nRealness = 0;
-            }
-        }
-    }
-    if(m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_BLINK ||
-            m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_NOD)
-    {
-        int nFaceStatus = 0;
-        if(m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_BLINK)
-        {
-            nFaceStatus = SL_EYE_CLOSE;
-        }
-        else
-        {
-            nFaceStatus = SL_HEAD_DOWN;
-        }
-
-        if(!g_faceRecProcPtr->getFaceAttribute(matImage, nFaceStatus, bHasStatus, nFaceCount))
-        {
-            myServerLog()<<g_faceRecProcPtr->errorMsg();
-            m_currentVerifyInfo.sErrorMsg = g_faceRecProcPtr->errorMsg();
-            return;
-        }
-
-        m_currentVerifyInfo.nFaceCount = nFaceCount;
-
-        if(nFaceCount != 1)
-        {
-            myServerLog()<< QString::fromLocal8Bit("活体检测人脸数量异常,")  << nFaceCount;
-            m_currentVerifyInfo.sErrorMsg = QString::fromLocal8Bit("活体检测人脸数量异常,").arg(nFaceCount);
-            return;
-        }
-
-        if(bHasStatus)
-        {
-            //验证成功
-            m_currentVerifyInfo.nEndTime = g_appInfoPtr->serverMTime();
-            m_currentVerifyInfo.nRealness = 1;
-            m_currentVerifyInfo.bPass = true;
-            m_currentVerifyInfo.bIsVerify = true;
-        }
-    }
-    else if(m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_SHAKE)
-    {
-        float fYaw = 0;
-        float fPitch = 0;
-        float fRoll = 0;
-        int nFaceCount = 0;
-        if(!g_faceRecProcPtr->getFaceAttribute(matImage, fYaw, fPitch, fRoll, nFaceCount))
-        {
-            myServerLog()<<g_faceRecProcPtr->errorMsg();
-            m_currentVerifyInfo.sErrorMsg = g_faceRecProcPtr->errorMsg();
-            return;
-        }
-         m_currentVerifyInfo.nFaceCount = nFaceCount;
-        if(m_fMinYaw > fYaw)
-        {
-            m_fMinYaw = fYaw;
-        }
-
-        if(m_fMaxYaw < fYaw)
-        {
-            m_fMaxYaw = fYaw;
-        }
-
-        if(m_fMaxYaw - m_fMinYaw > 30)
-        {
-            //验证成功
-            m_currentVerifyInfo.nEndTime = g_appInfoPtr->serverMTime();
-            m_currentVerifyInfo.nRealness = 1;
-            m_currentVerifyInfo.bPass = true;
-            m_currentVerifyInfo.bIsVerify = true;
-        }
-    }
+	try
+	{
+		bool bHasStatus = false;
+		int nFaceCount = 0;
+		float fScore = 0;
+
+		if (m_currentVerifyInfo.bIsVerify)
+		{
+			return;
+		}
+
+		m_currentVerifyInfo.matImage = matImage;
+
+		if (m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_FACE_DETECT)
+		{
+			SeetaRect rt;
+			if (!g_faceRecProcPtr->compareWithBase(matImage, nFaceCount, fScore, rt))
+			{
+				m_currentVerifyInfo.sErrorMsg = g_faceRecProcPtr->errorMsg();
+				myServerLog() << g_faceRecProcPtr->errorMsg();
+				return;
+			}
+
+			m_currentVerifyInfo.nFaceCount = nFaceCount;
+			m_currentVerifyInfo.fSimilarity = fScore;
+
+			if (nFaceCount != 1)
+			{
+				myServerLog() << QString::fromLocal8Bit("活体检测人脸数量异常,") << nFaceCount;
+				m_currentVerifyInfo.sErrorMsg = QString::fromLocal8Bit("活体检测人脸数量异常,").arg(nFaceCount);
+				return;
+			}
+
+			if (fScore * 100 > g_appInfoPtr->m_oExamInfo.nWarnThreshold)
+			{
+				bool bRealness = false;
+				if (!g_faceRecProcPtr->faceRealness(matImage, bRealness))
+				{
+					myServerLog() << g_faceRecProcPtr->errorMsg();
+					m_currentVerifyInfo.sErrorMsg = g_faceRecProcPtr->errorMsg();
+					return;
+				}
+				//验证成功
+				if (bRealness)
+				{
+					m_currentVerifyInfo.nEndTime = g_appInfoPtr->serverMTime();
+					m_currentVerifyInfo.nRealness = 1;
+					m_currentVerifyInfo.bPass = true;
+					m_currentVerifyInfo.bIsVerify = true;
+
+					myServerLog() << m_currentVerifyInfo.sActionType << ":" << fScore;
+
+					return;
+				}
+				else
+				{
+					m_currentVerifyInfo.nRealness = 0;
+				}
+			}
+		}
+		if (m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_BLINK ||
+			m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_NOD)
+		{
+			int nFaceStatus = 0;
+			if (m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_BLINK)
+			{
+				nFaceStatus = SL_EYE_CLOSE;
+			}
+			else
+			{
+				nFaceStatus = SL_HEAD_DOWN;
+			}
+
+			if (!g_faceRecProcPtr->getFaceAttribute(matImage, nFaceStatus, bHasStatus, nFaceCount))
+			{
+				myServerLog() << g_faceRecProcPtr->errorMsg();
+				m_currentVerifyInfo.sErrorMsg = g_faceRecProcPtr->errorMsg();
+				return;
+			}
+
+			m_currentVerifyInfo.nFaceCount = nFaceCount;
+
+			if (nFaceCount != 1)
+			{
+				myServerLog() << QString::fromLocal8Bit("活体检测人脸数量异常,") << nFaceCount;
+				m_currentVerifyInfo.sErrorMsg = QString::fromLocal8Bit("活体检测人脸数量异常,").arg(nFaceCount);
+				return;
+			}
+
+			if (bHasStatus)
+			{
+				//验证成功
+				m_currentVerifyInfo.nEndTime = g_appInfoPtr->serverMTime();
+				m_currentVerifyInfo.nRealness = 1;
+				m_currentVerifyInfo.bPass = true;
+				m_currentVerifyInfo.bIsVerify = true;
+			}
+		}
+		else if (m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_SHAKE)
+		{
+			float fYaw = 0;
+			float fPitch = 0;
+			float fRoll = 0;
+			int nFaceCount = 0;
+			if (!g_faceRecProcPtr->getFaceAttribute(matImage, fYaw, fPitch, fRoll, nFaceCount))
+			{
+				myServerLog() << g_faceRecProcPtr->errorMsg();
+				m_currentVerifyInfo.sErrorMsg = g_faceRecProcPtr->errorMsg();
+				return;
+			}
+			m_currentVerifyInfo.nFaceCount = nFaceCount;
+			if (m_fMinYaw > fYaw)
+			{
+				m_fMinYaw = fYaw;
+			}
+
+			if (m_fMaxYaw < fYaw)
+			{
+				m_fMaxYaw = fYaw;
+			}
+
+			if (m_fMaxYaw - m_fMinYaw > 30)
+			{
+				//验证成功
+				m_currentVerifyInfo.nEndTime = g_appInfoPtr->serverMTime();
+				m_currentVerifyInfo.nRealness = 1;
+				m_currentVerifyInfo.bPass = true;
+				m_currentVerifyInfo.bIsVerify = true;
+			}
+		}
+	}
+	catch (const std::exception &e)
+	{
+		myDebug() << QString::fromLocal8Bit("人脸比对失败,%1").arg(e.what());
+	}
 }
 
 void faceLiveness::on_btn_fc_close_clicked()
@@ -393,15 +427,19 @@ void faceLiveness::on_btn_fl_startVerify_clicked()
             sFileName = g_appInfoPtr->m_sCacheFileDir + sFileName;
             if(!QFile::exists(sFileName))
             {
-                ShowMsg(QString::fromLocal8Bit("底照下载失败,请检查网络"), this);
+                ShowMsg(QString::fromLocal8Bit("底照下载失败,请检查网络"), this, MSG_ICON_TYPE::mit_error);
                 return;
             }
 
-            g_faceRecProcPtr->setBaseImage(sFileName);
+            if(!g_faceRecProcPtr->setBaseImage(sFileName))
+            {
+                ShowMsg(g_faceRecProcPtr->errorMsg(), this, MSG_ICON_TYPE::mit_error);
+                return;
+            }
         }
         else
         {
-            ShowMsg(QString::fromLocal8Bit("当前考试未底照"), this);
+            ShowMsg(QString::fromLocal8Bit("当前考试未底照"), this, MSG_ICON_TYPE::mit_error);
             return;
         }
     }
@@ -426,26 +464,27 @@ void faceLiveness::on_btn_fl_startVerify_clicked()
 void faceLiveness::initAcionIcon()
 {
     QMovie *movie;
+	ui->btn_fl_time->setVisible(true);
     ui->btn_fl_time->setText(QString("%1s").arg(m_currentVerifyInfo.nActionLeftSceonds));
     if(m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_FACE_DETECT)
     {
         movie = new QMovie(":/images/img-fl-face.png");
         ui->label_fl_actionTips->setText(QString::fromLocal8Bit("请让我看到您的正脸"));
-        ui->btn_fl_time->setText("");
+        ui->btn_fl_time->setVisible(false);
     }
     else if(m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_BLINK)
     {
-        movie = new QMovie(":/images/gif-close-eyes.png");
+        movie = new QMovie(":/images/gif-close-eyes.gif");
         ui->label_fl_actionTips->setText(QString::fromLocal8Bit("请闭眼"));
     }
     else if(m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_SHAKE)
     {
-        movie = new QMovie(":/images/gif-turn-head.png");
+        movie = new QMovie(":/images/gif-turn-head.gif");
         ui->label_fl_actionTips->setText(QString::fromLocal8Bit("请摇头"));
     }
     else if(m_currentVerifyInfo.sActionType == ACTION_TYPE::AT_NOD)
     {
-        movie = new QMovie(":/images/gif-head-down.png");
+        movie = new QMovie(":/images/gif-head-down.gif");
         ui->label_fl_actionTips->setText(QString::fromLocal8Bit("请点头"));
     }
     ui->label_fl_actionMoive->setMovie(movie);
@@ -454,233 +493,293 @@ void faceLiveness::initAcionIcon()
 
 void faceLiveness::saveLivenessResult()
 {
-    bool bSubmit = true;
-    for(int i = 0; i < m_livenessList.count(); ++i)
-    {
-        if(!m_livenessList[i].matImage.empty() &&
-                m_livenessList[i].sUrl.isEmpty())
-        {
-            bSubmit = false;
-            m_nCurIndex = i;
-            m_currentVerifyInfo = m_livenessList[i];
-            QString sImageFile = QString("temp/photo/%1.png").arg(CCommonTools::getUuid());
-            QImage img = CCommonTools::Mat2QImage(m_currentVerifyInfo.matImage);
-            img.save(sImageFile, "PNG");
-            //上传图片
-            CHttpRequestPackage hrp;
-            hrp.sUri = "/api/ecs_oe_student/client/exam/process/upload";
-            hrp.nRequestType = RequestType::rtProcessUpload;
-            hrp.sParamList.push_back(QString("formdataFileType,file,%1").arg(sImageFile));
-            hrp.sParamList.push_back(QString("md5,%1").arg(CCommonTools::fileMd5(sImageFile)));
-
-            hrp.eParamType = HttpParamType::hptFormdata;
-            g_httpBllPtr->post(hrp);
-            break;
-        }
-    }
-
-    if(bSubmit)
-    {
-        bool bSucceed = true;
-        Json::Value jLiveness = Json::Value::null;
-        jLiveness["status"] = m_sLivenessStatus.toStdString();
-        jLiveness["faceLiveVerifyId"] = g_appInfoPtr->m_oExamInfo.nFaceLiveVerifyId;
-        jLiveness["processTime"] = m_nEndTime - m_nStartTime;
-        for(int i = 0; i < m_livenessList.count(); ++i)
-        {
-            if(m_livenessList[i].sActionType == ACTION_TYPE::AT_FACE_DETECT)
-            {
-                jLiveness["examRecordDataId"] = g_appInfoPtr->m_oExamInfo.nExamRecordDataId;
-                jLiveness["faceCount"] = m_livenessList[i].nFaceCount;                
-                jLiveness["realness"] = m_livenessList[i].nRealness;
-                jLiveness["similarity"] = m_livenessList[i].fSimilarity;
-                if(m_livenessList[i].nFaceCount == 0)
-                {
-                    jLiveness["status"] = STATUS_TYPE::ST_NOT_ONESELF.toStdString();
-                }
-
-            }
-            else
-            {
-				Json::Value jAction = Json::Value::null;
-                if(!m_livenessList[i].bPass)
-                {
-                    bSucceed = false;
-					jAction["errorMsg"] = m_livenessList[i].sErrorMsg.toStdString();
-                }
-				jAction["fileUrl"] = m_livenessList[i].sUrl.toStdString();
-				jAction["pass"] = m_livenessList[i].bPass;
-				jAction["processTime"] = m_livenessList[i].nEndTime - m_livenessList[i].nStartTime;
-				jAction["retry"] = 0;
-				jAction["type"] = m_livenessList[i].sActionType.toStdString();
-				jLiveness["actions"].append(jAction);
-            }
-
-        }
-        qDebug()<<jLiveness.toStyledString().c_str();
-        CHttpRequestPackage hrp;
-        hrp.sUri = QString("/api/ecs_oe_student/client/exam/process/saveFaceLiveVerifyResult");
-        hrp.nRequestType = RequestType::rtSaveFaceLiveVerifyResult;
-        hrp.eParamType = HttpParamType::hptCustomBody;
-        hrp.sParamList.push_back(QString("CustomBody,%1").arg(jLiveness.toStyledString().c_str()));
-
-        g_httpBllPtr->post(hrp);
-
-    }
+	try
+	{
+		bool bSubmit = true;
+		for (int i = 0; i < m_livenessList.count(); ++i)
+		{
+			if (!m_livenessList[i].matImage.empty() &&
+				m_livenessList[i].sUrl.isEmpty())
+			{
+				bSubmit = false;
+				m_nCurIndex = i;
+				m_currentVerifyInfo = m_livenessList[i];
+				QString sImageFile = QString("temp/photo/%1.png").arg(CCommonTools::getUuid());
+				QImage img = CCommonTools::Mat2QImage(m_currentVerifyInfo.matImage);
+				img.save(sImageFile, "PNG");
+				//上传图片
+				CHttpRequestPackage hrp;
+				hrp.sUri = "/api/ecs_oe_student/client/exam/process/upload";
+				hrp.nRequestType = RequestType::rtProcessUpload;
+                hrp.sCommonStr = __FILE__;
+				hrp.sParamList.push_back(QString("formdataFileType,file,%1").arg(sImageFile));
+				hrp.sParamList.push_back(QString("md5,%1").arg(CCommonTools::fileMd5(sImageFile)));
+
+				hrp.eParamType = HttpParamType::hptFormdata;
+				g_httpBllPtr->post(hrp);
+				break;
+			}
+		}
+
+		if (bSubmit)
+		{
+			bool bSucceed = true;
+			Json::Value jLiveness = Json::Value::null;
+			jLiveness["status"] = m_sLivenessStatus.toStdString();
+			jLiveness["faceLiveVerifyId"] = g_appInfoPtr->m_oExamInfo.nFaceLiveVerifyId;
+			jLiveness["processTime"] = m_nEndTime - m_nStartTime;
+			for (int i = 0; i < m_livenessList.count(); ++i)
+			{
+				if (m_livenessList[i].sActionType == ACTION_TYPE::AT_FACE_DETECT)
+				{
+					jLiveness["examRecordDataId"] = g_appInfoPtr->m_oExamInfo.nExamRecordDataId;
+					jLiveness["faceCount"] = m_livenessList[i].nFaceCount;
+					jLiveness["realness"] = m_livenessList[i].nRealness;
+					jLiveness["similarity"] = m_livenessList[i].fSimilarity;
+					if (m_livenessList[i].nFaceCount == 0)
+					{
+						jLiveness["status"] = STATUS_TYPE::ST_NOT_ONESELF.toStdString();
+					}
+
+				}
+				else
+				{
+					Json::Value jAction = Json::Value::null;
+					if (!m_livenessList[i].bPass)
+					{
+						bSucceed = false;
+						jAction["errorMsg"] = m_livenessList[i].sErrorMsg.toStdString();
+					}
+					jAction["fileUrl"] = m_livenessList[i].sUrl.toStdString();
+					jAction["pass"] = m_livenessList[i].bPass;
+					jAction["processTime"] = m_livenessList[i].nEndTime - m_livenessList[i].nStartTime;
+					jAction["retry"] = 0;
+					jAction["type"] = m_livenessList[i].sActionType.toStdString();
+					jLiveness["actions"].append(jAction);
+				}
+
+			}
+			qDebug() << jLiveness.toStyledString().c_str();
+			CHttpRequestPackage hrp;
+			hrp.sUri = QString("/api/ecs_oe_student/client/exam/process/saveFaceLiveVerifyResult");
+			hrp.nRequestType = RequestType::rtSaveFaceLiveVerifyResult;
+			hrp.eParamType = HttpParamType::hptCustomBody;
+			hrp.sParamList.push_back(QString("CustomBody,%1").arg(jLiveness.toStyledString().c_str()));
+
+			g_httpBllPtr->post(hrp);
+
+		}
+	}
+	catch (const std::exception &e)
+	{
+		myDebug() << QString::fromLocal8Bit("人脸比对失败,%1").arg(e.what());
+	}
 }
 
 void faceLiveness::countdownTimer()
 {
-    --m_nMaxSeconds;
-    ui->label_fl_time->setText(QString("(%1)").arg(m_nMaxSeconds));
-    if(m_nMaxSeconds <= 0)
-    {
-        //整体超时
-        m_bStartCompare = false;
-        m_ActionTimer->stop();
-		m_countdownTimer->stop();
-        {
-            std::scoped_lock sl(m_livenessListMutex);
-            m_livenessList[m_nCurIndex] = m_currentVerifyInfo;
-        }
-        m_sLivenessStatus = STATUS_TYPE::ST_TIME_OUT;
-        saveLivenessResult();
-    }
+	try
+	{
+		--m_nMaxSeconds;
+		ui->label_fl_time->setText(QString("(%1)").arg(m_nMaxSeconds));
+		if (m_nMaxSeconds <= 0)
+		{
+			//整体超时
+			m_bStartCompare = false;
+			m_ActionTimer->stop();
+			m_countdownTimer->stop();
+			{
+				std::scoped_lock sl(m_livenessListMutex);
+				m_livenessList[m_nCurIndex] = m_currentVerifyInfo;
+			}
+			m_sLivenessStatus = STATUS_TYPE::ST_TIME_OUT;
+			saveLivenessResult();
+		}
+	}
+	catch (const std::exception &e)
+	{
+		myDebug() << QString::fromLocal8Bit("人脸比对失败,%1").arg(e.what());
+	}
 }
 
 void faceLiveness::actionTimer()
 {        
-    if(m_currentVerifyInfo.bIsVerify)
-    {
-        //当前比对完成
-        {
-            std::scoped_lock sl(m_livenessListMutex);
-            m_livenessList[m_nCurIndex] = m_currentVerifyInfo;
-        }
-
-        //下一个动作
-        ++m_nCurIndex;
-        if(m_nCurIndex < m_livenessList.count())
-        {
-            std::scoped_lock sl(m_livenessListMutex);
-            m_currentVerifyInfo = m_livenessList[m_nCurIndex];
-            initAcionIcon();
-            {
-                std::scoped_lock sl(m_imgMutex);
-                m_imgList.clear();
-            }
-        }
-        else
-        {
-            //整体比对完成
-            m_sLivenessStatus = STATUS_TYPE::ST_SUCCESS;
-            m_bStartCompare = false;
-            m_ActionTimer->stop();   
-			m_countdownTimer->stop();
-            saveLivenessResult();
-            return;
-        }
-
-    }
-    else
-    {
-        if(m_currentVerifyInfo.nActionLeftSceonds <= 0)
-        {
-            //单个超时
-            m_bStartCompare = false;
-            m_currentVerifyInfo.sErrorMsg = QString::fromLocal8Bit("action timeout");
-            m_ActionTimer->stop();
-			m_countdownTimer->stop();
-            {
-                std::scoped_lock sl(m_livenessListMutex);
-                m_livenessList[m_nCurIndex] = m_currentVerifyInfo;
-            }
-            m_sLivenessStatus = STATUS_TYPE::ST_ACTION_FAILED;
-            saveLivenessResult();
-            return;
-        }
-        else
-        {
-            --m_currentVerifyInfo.nActionLeftSceonds;
-            ui->btn_fl_time->setText(QString("%1s").arg(m_currentVerifyInfo.nActionLeftSceonds));
-        }
-    }    
+	try
+	{
+		if (m_currentVerifyInfo.bIsVerify)
+		{
+			//当前比对完成
+			{
+				std::scoped_lock sl(m_livenessListMutex);
+				m_livenessList[m_nCurIndex] = m_currentVerifyInfo;
+			}
+
+			//下一个动作
+			++m_nCurIndex;
+			if (m_nCurIndex < m_livenessList.count())
+			{
+				std::scoped_lock sl(m_livenessListMutex);
+				m_currentVerifyInfo = m_livenessList[m_nCurIndex];
+				initAcionIcon();
+				{
+					std::scoped_lock sl(m_imgMutex);
+					m_imgList.clear();
+				}
+			}
+			else
+			{
+				//整体比对完成
+				m_sLivenessStatus = STATUS_TYPE::ST_SUCCESS;
+				m_bStartCompare = false;
+				m_ActionTimer->stop();
+				m_countdownTimer->stop();
+				saveLivenessResult();
+				return;
+			}
+
+		}
+		else
+		{
+			if (m_currentVerifyInfo.nActionLeftSceonds <= 0)
+			{
+				//单个超时
+				m_bStartCompare = false;
+				m_currentVerifyInfo.sErrorMsg = QString::fromLocal8Bit("action timeout");
+				m_ActionTimer->stop();
+				m_countdownTimer->stop();
+				{
+					std::scoped_lock sl(m_livenessListMutex);
+					m_livenessList[m_nCurIndex] = m_currentVerifyInfo;
+				}
+				m_sLivenessStatus = STATUS_TYPE::ST_ACTION_FAILED;
+				saveLivenessResult();
+				return;
+			}
+			else
+			{
+				--m_currentVerifyInfo.nActionLeftSceonds;
+				ui->btn_fl_time->setText(QString("%1s").arg(m_currentVerifyInfo.nActionLeftSceonds));
+			}
+		}
+	}
+	catch (const std::exception &e)
+	{
+		myDebug() << QString::fromLocal8Bit("人脸比对失败,%1").arg(e.what());
+	}
 }
 
 //文件上传
 void faceLiveness::onProcessUpload(CProcessUpload processUpload)
 {
-    if(processUpload.nCode == 200)
-    {
-        m_livenessList[m_nCurIndex].sUrl = processUpload.sFileUrl;
-        saveLivenessResult();
-    }
-    else
-    {
-        if(processUpload.sMessage.isEmpty())
-        {
-            ShowMsg(QString::fromLocal8Bit("上传照片失败"), this);
-        }
-        else
+	try
+	{
+        if(processUpload.sCommonStr == __FILE__)
         {
-            ShowMsg(processUpload.sMessage, this);
-        }
-    }
-}
+            if (processUpload.nCode == 200)
+            {
+                m_nRetryCount = 0;
+                m_livenessList[m_nCurIndex].sUrl = processUpload.sFileUrl;
+                saveLivenessResult();
+            }
+            else
+            {
+                if (processUpload.sMessage.isEmpty())
+                {
+                    ShowMsg(QString::fromLocal8Bit("上传照片失败"), this, MSG_ICON_TYPE::mit_error);
+                }
+                else
+                {
+                    ShowMsg(processUpload.sMessage, this, MSG_ICON_TYPE::mit_error);
+                }
 
-//保存人脸活体验证结果
-void faceLiveness::onSaveFaceLiveVerifyResult(CBaseResponsePackage res)
-{
-    if(res.nCode == 200)
-    {
-        if(m_pCloseTimer == nullptr)
-        {
-            m_pCloseTimer = std::make_shared<QTimer>();
-            m_pCloseTimer->setInterval(2000);
-            connect(m_pCloseTimer.get(), &QTimer::timeout, this, [&](){
-                if(m_sLivenessStatus == STATUS_TYPE::ST_SUCCESS)
+                if(m_nRetryCount < 4)
                 {
-                    emit faceLivenessSucceed();
+                    saveLivenessResult();
+                    m_nRetryCount++;
                 }
                 else
                 {
                     emit faceLivenessFaild();
                 }
-            });
-        }
-
-        m_pCloseTimer->start();
-        if(m_sLivenessStatus == STATUS_TYPE::ST_SUCCESS)
-        {
-            ui->label_fl_hint->setText(QString::fromLocal8Bit("恭喜您完成检测!"));
-            ui->label_fl_icon->setPixmap(QPixmap(":/images/icon-welcom.png"));
-        }
-        else
-        {
-            ui->label_fl_hint->setText(QString::fromLocal8Bit("检测失败:单个动作超时"));
-            ui->label_fl_icon->setPixmap(QPixmap(":/images/icon-liveness-faild.png"));
+            }
         }
-        ui->label_fl_hint->adjustSize();
-        ui->widget_fc_BG->setVisible(false);
-        ui->widget_fl_hint->setVisible(true);
-        ui->widget_fl_hint->setGeometry((width() - ui->label_fl_hint->width() -  g_appInfoPtr->m_fRate*180)/2, (height() - g_appInfoPtr->m_fRate*180)/2,
-                                        ui->label_fl_hint->width() +  g_appInfoPtr->m_fRate*180, g_appInfoPtr->m_fRate*180);
-        ui->label_fl_icon->setGeometry(g_appInfoPtr->m_fRate*75, g_appInfoPtr->m_fRate*70, g_appInfoPtr->m_fRate*40, g_appInfoPtr->m_fRate*40);
-
-        ui->label_fl_hint->setGeometry(ui->label_fl_icon->x() + ui->label_fl_icon->width() + g_appInfoPtr->m_fRate*20,
-                                       ui->label_fl_icon->y() + (ui->label_fl_icon->height() - ui->label_fl_hint->height())/2,
-                                       ui->label_fl_hint->width(), ui->label_fl_hint->height());
+	}
+	catch (const std::exception &e)
+	{
+		myDebug() << QString::fromLocal8Bit("人脸比对失败,%1").arg(e.what());
+	}
+}
 
-    }
-    else
-    {
-        if(res.sMessage.isEmpty())
-        {
-            ShowMsg(QString::fromLocal8Bit("活体验证结果失败"), this);
-        }
-        else
-        {
-            ShowMsg(res.sMessage, this);
-        }
-    }
+//保存人脸活体验证结果
+void faceLiveness::onSaveFaceLiveVerifyResult(CBaseResponsePackage res)
+{
+	try
+	{
+		if (res.nCode == 200)
+		{
+			if (m_pCloseTimer == nullptr)
+			{
+				m_pCloseTimer = std::make_shared<QTimer>();
+				m_pCloseTimer->setInterval(2000);
+				connect(m_pCloseTimer.get(), &QTimer::timeout, this, [&]() {
+					if (m_sLivenessStatus == STATUS_TYPE::ST_SUCCESS)
+					{
+						emit faceLivenessSucceed();
+					}
+					else
+					{
+						emit faceLivenessFaild();
+					}
+				});
+			}
+
+			m_pCloseTimer->start();
+			if (m_sLivenessStatus == STATUS_TYPE::ST_SUCCESS)
+			{
+				ui->label_fl_hint->setText(QString::fromLocal8Bit("恭喜您完成检测!"));
+				ui->label_fl_icon->setPixmap(QPixmap(":/images/icon-welcom.png"));
+			}
+			else
+			{
+				ui->label_fl_hint->setText(QString::fromLocal8Bit("检测失败:单个动作超时"));
+				ui->label_fl_icon->setPixmap(QPixmap(":/images/icon-liveness-faild.png"));
+			}
+			ui->label_fl_hint->adjustSize();
+			ui->widget_fc_BG->setVisible(false);
+			ui->widget_fl_hint->setVisible(true);
+			ui->widget_fl_hint->setGeometry((width() - ui->label_fl_hint->width() - g_appInfoPtr->m_fRate * 180) / 2, (height() - g_appInfoPtr->m_fRate * 180) / 2,
+				ui->label_fl_hint->width() + g_appInfoPtr->m_fRate * 180, g_appInfoPtr->m_fRate * 180);
+			ui->label_fl_icon->setGeometry(g_appInfoPtr->m_fRate * 75, g_appInfoPtr->m_fRate * 70, g_appInfoPtr->m_fRate * 40, g_appInfoPtr->m_fRate * 40);
+
+			ui->label_fl_hint->setGeometry(ui->label_fl_icon->x() + ui->label_fl_icon->width() + g_appInfoPtr->m_fRate * 20,
+				ui->label_fl_icon->y() + (ui->label_fl_icon->height() - ui->label_fl_hint->height()) / 2,
+				ui->label_fl_hint->width(), ui->label_fl_hint->height());
+
+		}
+		else
+		{
+			if (res.sMessage.isEmpty())
+			{
+                ShowMsg(QString::fromLocal8Bit("活体验证结果失败"), this, MSG_ICON_TYPE::mit_error);
+			}
+			else
+			{
+                ShowMsg(res.sMessage, this, MSG_ICON_TYPE::mit_error);
+			}
+
+            if(m_nRetryCount < 4)
+            {
+                saveLivenessResult();
+                m_nRetryCount++;
+            }
+            else
+            {
+                emit faceLivenessFaild();
+            }
+		}
+	}
+	catch (const std::exception &e)
+	{
+		myDebug() << QString::fromLocal8Bit("人脸比对失败,%1").arg(e.what());
+	}
 }

+ 14 - 9
face/faceLiveness.h

@@ -7,7 +7,7 @@
 #include <thread>
 #include <mutex>
 #include <opencv2/opencv.hpp>
-
+#include "CLiveViodeProc.h"
 #include "CHttpBll.h"
 
 //SHAKE,BLINK,NOD
@@ -64,7 +64,7 @@ namespace Ui {
 class faceLiveness;
 }
 
-class faceLiveness : public QWidget
+class faceLiveness : public QWidget, ITRTCVideoRenderCallback
 {
     Q_OBJECT
 signals:
@@ -89,21 +89,22 @@ private:
     void initAcionIcon();
     void saveLivenessResult();
     void verifyAction(cv::Mat matImage);
-
+    void onRenderVideoFrame(const char* userId, TRTCVideoStreamType streamType, TRTCVideoFrame* frame);
 
     Ui::faceLiveness *ui;
 
     std::shared_ptr<QTimer> m_pVideoTimer;
-    std::shared_ptr<QTimer> m_pinitTimer;
-    cv::VideoCapture m_cam;
+//    std::shared_ptr<QTimer> m_pinitTimer;
+//    cv::VideoCapture m_cam;
 
     std::shared_ptr<QTimer> m_pCloseTimer;
 
     void threadProc();
 
-    __int64 m_nStartTime;
-    __int64 m_nEndTime;
-    int m_nMaxSeconds;
+    __int64 m_lastFaceTime = 0;
+    __int64 m_nStartTime = 0;
+    __int64 m_nEndTime = 0;
+    int m_nMaxSeconds = 0;
 
     std::shared_ptr<QTimer> m_ActionTimer;
     std::shared_ptr<QTimer> m_countdownTimer;
@@ -111,6 +112,9 @@ private:
     std::mutex m_imgMutex;
     std::list<cv::Mat> m_imgList;
 
+    cv::Mat m_nCurImage;
+    std::mutex m_imageMutex;
+
     QList<LivenessVerifyInfo> m_livenessList;
     int m_nCurIndex;
     std::mutex m_livenessListMutex;
@@ -122,7 +126,8 @@ private:
     std::thread m_thread;
     bool m_bIsRun;
 
-    bool m_bStartCompare;
+	bool m_bStartCompare = false;
+    int m_nRetryCount = 0;
 };
 
 #endif // FACELIVENESS_H

+ 7 - 1
face/faceLiveness.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>673</width>
-    <height>492</height>
+    <height>531</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -53,6 +53,9 @@
        <height>23</height>
       </rect>
      </property>
+     <property name="focusPolicy">
+      <enum>Qt::NoFocus</enum>
+     </property>
      <property name="text">
       <string/>
      </property>
@@ -138,6 +141,9 @@
        <height>23</height>
       </rect>
      </property>
+     <property name="focusPolicy">
+      <enum>Qt::NoFocus</enum>
+     </property>
      <property name="text">
       <string>开始检测</string>
      </property>

Some files were not shown because too many files changed in this diff