Эх сурвалжийг харах

1、刷卡登录,首页,登录页都需要支持刷卡登录
2、虚拟键盘
3、页面表单和样式错乱\字体问题
4、提交表单校验,提交后页面自动刷新,提交后再点击 表单只读显示
5、页面缺少任务名称,负责人,时间等问题,页面之间跳转问题,各操作提示问题
6、还钥匙还锁页面、can通讯优化
7、改动很多,遇到问题,及时找我。可能我改动的会引起其他bug
8、日志规范整理加入时间等

我演示:登录、主要是刷卡登录
第一个任务,审核
第二个任务,勘察确认
第三个任务,取钥匙/锁
第四个任务,共锁
这些一定要完整,可用,其他功能尽量完整。
(我本地测试,创建了虚拟can0,你到时候自己改回物理can设备。触发换钥匙还锁,用cansend can0
581#43206000AABBCCDD 发送指令即可)

lishuangbao 3 сар өмнө
parent
commit
0e295d8ec5
44 өөрчлөгдсөн 4782 нэмэгдсэн , 1401 устгасан
  1. 82 216
      Loto.pro.user
  2. 53 0
      scripts/setup_can_usb.sh
  3. 48 9
      src/httpclient/HttpCardLogin.cpp
  4. 655 648
      src/httpclient/HttpClient.cpp
  5. 3 1
      src/httpclient/HttpGetJobTickets.cpp
  6. 25 0
      src/httpclient/HttpGetWorkNodeDetail.cpp
  7. 31 0
      src/httpclient/JobTicketsModel.cpp
  8. 7 0
      src/httpclient/JobTicketsModel.h
  9. 4 0
      src/httpclient/WorkNodeFormModel.cpp
  10. 14 0
      src/httpclient/WorkNodeFormModel.h
  11. BIN
      src/icon.png
  12. 26 0
      src/interactive/InteractiveCAN.cpp
  13. 427 0
      src/interactive/ReturnKeyLockManager.cpp
  14. 201 0
      src/interactive/ReturnKeyLockManager.h
  15. 18 9
      src/main.cpp
  16. 2 0
      src/qml.qrc
  17. 68 70
      src/qml/JobTicketPage.qml
  18. 174 58
      src/qml/Login.qml
  19. 41 1
      src/qml/SettingPage.qml
  20. 312 78
      src/qml/WorkingPage.qml
  21. 71 5
      src/qml/components/AlertDialog.qml
  22. 303 0
      src/qml/components/CustomVirtualKeyboard.qml
  23. 105 46
      src/qml/components/FormCard.qml
  24. 15 25
      src/qml/components/IconText.qml
  25. 6 0
      src/qml/components/JobTicketCard.qml
  26. 16 17
      src/qml/components/JobTicketColockProcess.qml
  27. 14 14
      src/qml/components/JobTicketProcess.qml
  28. 2 2
      src/qml/components/JobTicketSubProcess.qml
  29. 88 0
      src/qml/components/LoadingDialog.qml
  30. 1 1
      src/qml/components/MDatePicker.qml
  31. 19 3
      src/qml/components/MInput.qml
  32. 9 2
      src/qml/components/MRadioButton.qml
  33. 9 2
      src/qml/components/MSwitchButton.qml
  34. 41 15
      src/qml/components/MTextArea.qml
  35. 99 99
      src/qml/components/NoJobTicketDialog.qml
  36. 604 46
      src/qml/components/ReturnKeyLockProcess.qml
  37. 1 1
      src/qml/components/SettingFormCard.qml
  38. 4 4
      src/qml/components/UpdatePasswordDialog.qml
  39. 907 6
      src/qml/main.qml
  40. 1 0
      src/resources.qrc
  41. 11 2
      src/src.pro
  42. 250 21
      src/usr/CANClient.cpp
  43. 11 0
      src/usr/CANClient.h
  44. 4 0
      src/usr/LotoQmlPlugin.cpp

+ 82 - 216
Loto.pro.user

@@ -1,20 +1,20 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE QtCreatorProject>
-<!-- Written by QtCreator 4.14.2, 2026-01-23T09:05:23. -->
+<!-- Written by QtCreator 18.0.2, 2026-01-28T22:18:58. -->
 <qtcreator>
  <data>
   <variable>EnvironmentId</variable>
-  <value type="QByteArray">{a22e3eca-8c48-4235-becd-e90a9241f53a}</value>
+  <value type="QByteArray">{a5587603-485e-486a-a1a9-dc3454e0b3d3}</value>
  </data>
  <data>
   <variable>ProjectExplorer.Project.ActiveTarget</variable>
-  <value type="int">0</value>
+  <value type="qlonglong">0</value>
  </data>
  <data>
   <variable>ProjectExplorer.Project.EditorSettings</variable>
   <valuemap type="QVariantMap">
+   <value type="bool" key="EditorConfiguration.AutoDetect">true</value>
    <value type="bool" key="EditorConfiguration.AutoIndent">true</value>
-   <value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
    <value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
    <valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
     <value type="QString" key="language">Cpp</value>
@@ -28,23 +28,27 @@
      <value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
     </valuemap>
    </valuemap>
-   <value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
+   <value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
    <value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
    <value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
    <value type="int" key="EditorConfiguration.IndentSize">4</value>
    <value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
+   <value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
    <value type="int" key="EditorConfiguration.MarginColumn">80</value>
    <value type="bool" key="EditorConfiguration.MouseHiding">true</value>
    <value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
    <value type="int" key="EditorConfiguration.PaddingMode">1</value>
+   <value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
+   <value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
    <value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
    <value type="bool" key="EditorConfiguration.ShowMargin">false</value>
-   <value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
+   <value type="int" key="EditorConfiguration.SmartBackspaceBehavior">2</value>
    <value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
    <value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
    <value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
    <value type="int" key="EditorConfiguration.TabSize">8</value>
    <value type="bool" key="EditorConfiguration.UseGlobal">true</value>
+   <value type="bool" key="EditorConfiguration.UseIndenter">false</value>
    <value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
    <value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
    <value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
@@ -52,6 +56,7 @@
    <value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
    <value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
    <value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
+   <value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
   </valuemap>
  </data>
  <data>
@@ -59,48 +64,50 @@
   <valuemap type="QVariantMap">
    <valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
     <value type="bool" key="AutoTest.Framework.Boost">true</value>
+    <value type="bool" key="AutoTest.Framework.CTest">false</value>
     <value type="bool" key="AutoTest.Framework.Catch">true</value>
     <value type="bool" key="AutoTest.Framework.GTest">true</value>
     <value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
     <value type="bool" key="AutoTest.Framework.QtTest">true</value>
    </valuemap>
+   <value type="bool" key="AutoTest.ApplyFilter">false</value>
    <valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
+   <valuelist type="QVariantList" key="AutoTest.PathFilters"/>
    <value type="int" key="AutoTest.RunAfterBuild">0</value>
    <value type="bool" key="AutoTest.UseGlobal">true</value>
-   <valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
-   <value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
-   <value type="QString" key="ClangCodeModel.WarningConfigId">Builtin.Questionable</value>
    <valuemap type="QVariantMap" key="ClangTools">
     <value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
     <value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
     <value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
-    <value type="int" key="ClangTools.ParallelJobs">4</value>
+    <value type="int" key="ClangTools.ParallelJobs">8</value>
+    <value type="bool" key="ClangTools.PreferConfigFile">true</value>
     <valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
     <valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
     <valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
     <value type="bool" key="ClangTools.UseGlobalSettings">true</value>
    </valuemap>
+   <value type="int" key="RcSync">0</value>
   </valuemap>
  </data>
  <data>
   <variable>ProjectExplorer.Project.Target.0</variable>
   <valuemap type="QVariantMap">
-   <value type="QString" key="DeviceType">GenericLinuxOsType</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">rk3568</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">rk3568</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{4b7c7020-747d-4c4a-8e7b-c68e8d5127ef}</value>
-   <value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
-   <value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
-   <value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
+   <value type="QString" key="DeviceType">Desktop</value>
+   <value type="bool" key="HasPerBcDcs">true</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.15.2 GCC 64bit</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.15.2 GCC 64bit</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.5152.gcc_64_kit</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build/build-Loto-rk3568-Debug</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build/build-Loto-rk3568-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/fffe/ISCS_LOTO_Linuxqt5/build/Desktop_Qt_5_15_2_GCC_64bit-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/fffe/ISCS_LOTO_Linuxqt5/build/Desktop_Qt_5_15_2_GCC_64bit-Debug</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
-      <value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
       <value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
       <valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
      </valuemap>
@@ -108,7 +115,7 @@
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
      </valuemap>
-     <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
+     <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
@@ -119,7 +126,7 @@
       <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
       <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
      </valuemap>
-     <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
+     <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
@@ -127,133 +134,52 @@
     <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
     <value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
     <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
+    <value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
     <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value>
     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
-    <value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value>
-    <value type="int" key="RunSystemFunction">0</value>
-   </valuemap>
-   <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
-    <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
-      <value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
-      <value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
-      <valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
+    <value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
+    <value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
+    <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
+     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
+      <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
+      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
+      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
+      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
      </valuemap>
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
-     </valuemap>
-     <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
+     <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
+     <valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
+     <value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
     </valuemap>
-    <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
-      <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
-     </valuemap>
-     <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
+    <value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
+    <valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
+     <value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
+     <value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
+     <value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
+     <value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
+     <valuelist type="QVariantList" key="CustomOutputParsers"/>
+     <value type="int" key="PE.EnvironmentAspect.Base">2</value>
+     <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
+     <value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
+     <value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:</value>
+     <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/fffe/ISCS_LOTO_Linuxqt5/src/src.pro</value>
+     <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
+     <value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
+     <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
+     <value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
+     <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
+     <value type="QString" key="RunConfiguration.WorkingDirectory.default">%{RunConfig:Executable:Path}</value>
     </valuemap>
-    <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
-    <value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
-    <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
-    <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
-    <value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
-    <value type="int" key="QtQuickCompiler">0</value>
-    <value type="int" key="RunSystemFunction">0</value>
-   </valuemap>
-   <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
-    <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
-    <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
-      <value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
-      <value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
-      <valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
-     </valuemap>
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
-     </valuemap>
-     <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
-    </valuemap>
-    <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
-      <value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
-     </valuemap>
-     <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
-     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
-    </valuemap>
-    <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
-    <value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
-    <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
-    <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
-    <value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
-    <value type="int" key="QtQuickCompiler">0</value>
-    <value type="int" key="RunSystemFunction">0</value>
-    <value type="int" key="SeparateDebugInfo">0</value>
+    <value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
+    <value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value>
    </valuemap>
-   <value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">RemoteLinux.CheckForFreeDiskSpaceStep</value>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedFiles"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedHosts"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedRemotePaths"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedSysroots"/>
-      <value type="QString" key="RemoteLinux.CheckForFreeDiskSpaceStep.PathToCheck">/</value>
-      <value type="qlonglong" key="RemoteLinux.CheckForFreeDiskSpaceStep.RequiredSpace">5242880</value>
-      <valuelist type="QVariantList" key="RemoteLinux.LastDeployedLocalTimes"/>
-      <valuelist type="QVariantList" key="RemoteLinux.LastDeployedRemoteTimes"/>
-     </valuemap>
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">RemoteLinux.KillAppStep</value>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedFiles"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedHosts"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedRemotePaths"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedSysroots"/>
-      <valuelist type="QVariantList" key="RemoteLinux.LastDeployedLocalTimes"/>
-      <valuelist type="QVariantList" key="RemoteLinux.LastDeployedRemoteTimes"/>
-     </valuemap>
-     <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.2">
-      <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
-      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">RemoteLinux.RsyncDeployStep</value>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedFiles"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedHosts"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedRemotePaths"/>
-      <valuelist type="QVariantList" key="ProjectExplorer.RunConfiguration.LastDeployedSysroots"/>
-      <valuelist type="QVariantList" key="RemoteLinux.LastDeployedLocalTimes"/>
-      <valuelist type="QVariantList" key="RemoteLinux.LastDeployedRemoteTimes"/>
-      <value type="QString" key="RemoteLinux.RsyncDeployStep.Flags">-av</value>
-     </valuemap>
-     <value type="int" key="ProjectExplorer.BuildStepList.StepsCount">3</value>
+     <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
      <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
@@ -261,95 +187,35 @@
     <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
     <value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">DeployToGenericLinux</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
    </valuemap>
-   <value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
-    <value type="QString" key="Analyzer.Perf.CallgraphMode">dwarf</value>
-    <valuelist type="QVariantList" key="Analyzer.Perf.Events">
-     <value type="QString">cpu-cycles</value>
-    </valuelist>
-    <valuelist type="QVariantList" key="Analyzer.Perf.ExtraArguments"/>
-    <value type="int" key="Analyzer.Perf.Frequency">250</value>
-    <valuelist type="QVariantList" key="Analyzer.Perf.RecordArguments">
-     <value type="QString">-e</value>
-     <value type="QString">cpu-cycles</value>
-     <value type="QString">--call-graph</value>
-     <value type="QString">dwarf,4096</value>
-     <value type="QString">-F</value>
-     <value type="QString">250</value>
-    </valuelist>
-    <value type="QString" key="Analyzer.Perf.SampleMode">-F</value>
     <value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
-    <value type="int" key="Analyzer.Perf.StackSize">4096</value>
-    <value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value>
-    <value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value>
-    <value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value>
-    <value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value>
     <value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
-    <valuelist type="QVariantList" key="Analyzer.Valgrind.AddedSuppressionFiles"/>
-    <value type="bool" key="Analyzer.Valgrind.Callgrind.CollectBusEvents">false</value>
-    <value type="bool" key="Analyzer.Valgrind.Callgrind.CollectSystime">false</value>
-    <value type="bool" key="Analyzer.Valgrind.Callgrind.EnableBranchSim">false</value>
-    <value type="bool" key="Analyzer.Valgrind.Callgrind.EnableCacheSim">false</value>
-    <value type="bool" key="Analyzer.Valgrind.Callgrind.EnableEventToolTips">true</value>
-    <value type="double" key="Analyzer.Valgrind.Callgrind.MinimumCostRatio">0.01</value>
-    <value type="double" key="Analyzer.Valgrind.Callgrind.VisualisationMinimumCostRatio">10</value>
-    <value type="bool" key="Analyzer.Valgrind.FilterExternalIssues">true</value>
-    <value type="QString" key="Analyzer.Valgrind.KCachegrindExecutable">kcachegrind</value>
-    <value type="int" key="Analyzer.Valgrind.LeakCheckOnFinish">1</value>
-    <value type="int" key="Analyzer.Valgrind.NumCallers">25</value>
-    <valuelist type="QVariantList" key="Analyzer.Valgrind.RemovedSuppressionFiles"/>
-    <value type="int" key="Analyzer.Valgrind.SelfModifyingCodeDetection">1</value>
+    <value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
     <value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
-    <value type="bool" key="Analyzer.Valgrind.ShowReachable">false</value>
-    <value type="bool" key="Analyzer.Valgrind.TrackOrigins">true</value>
-    <value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">valgrind</value>
-    <valuelist type="QVariantList" key="Analyzer.Valgrind.VisibleErrorKinds">
-     <value type="int">0</value>
-     <value type="int">1</value>
-     <value type="int">2</value>
-     <value type="int">3</value>
-     <value type="int">4</value>
-     <value type="int">5</value>
-     <value type="int">6</value>
-     <value type="int">7</value>
-     <value type="int">8</value>
-     <value type="int">9</value>
-     <value type="int">10</value>
-     <value type="int">11</value>
-     <value type="int">12</value>
-     <value type="int">13</value>
-     <value type="int">14</value>
-    </valuelist>
     <valuelist type="QVariantList" key="CustomOutputParsers"/>
-    <value type="int" key="PE.EnvironmentAspect.Base">1</value>
-    <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes">
-     <value type="QString">LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH</value>
-     <value type="QString">QML2_IMPORT_PATH=$QTDIR/qml:$QML2_IMPORT_PATH</value>
-     <value type="QString">QTDIR=/opt/qt5</value>
-     <value type="QString">QT_PLUGIN_PATH=$QTDIR/plugins:$QT_PLUGIN_PATH</value>
-    </valuelist>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on rk3568)</value>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">RemoteLinuxRunConfiguration:/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
-    <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
-    <value type="int" key="RemoteLinux.EnvironmentAspect.Version">1</value>
-    <value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
+    <value type="int" key="PE.EnvironmentAspect.Base">2</value>
+    <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
+    <value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
+    <value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:</value>
+    <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/fffe/ISCS_LOTO_Linuxqt5/src/src.pro</value>
+    <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
+    <value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
     <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
-    <value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
+    <value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
     <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
-    <value type="QString" key="RunConfiguration.X11Forwarding">:0</value>
+    <value type="QString" key="RunConfiguration.WorkingDirectory.default">%{RunConfig:Executable:Path}</value>
    </valuemap>
-   <value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
   </valuemap>
  </data>
  <data>
   <variable>ProjectExplorer.Project.TargetCount</variable>
-  <value type="int">1</value>
- </data>
- <data>
-  <variable>ProjectExplorer.Project.Updater.FileVersion</variable>
-  <value type="int">22</value>
+  <value type="qlonglong">1</value>
  </data>
  <data>
   <variable>Version</variable>

+ 53 - 0
scripts/setup_can_usb.sh

@@ -0,0 +1,53 @@
+#!/bin/bash
+# CAN-USB适配器快速配置脚本
+# 使用方法: sudo ./setup_can_usb.sh [设备路径]
+# 示例: sudo ./setup_can_usb.sh /dev/ttyACM0
+
+DEVICE=${1:-/dev/ttyACM0}
+INTERFACE="can0"
+BITRATE="s8"  # s8 = 1Mbps
+
+echo "=========================================="
+echo "CAN-USB适配器配置脚本"
+echo "=========================================="
+
+# 检查设备是否存在
+if [ ! -e "$DEVICE" ]; then
+    echo "[错误] 设备 $DEVICE 不存在!"
+    echo "可用的串口设备:"
+    ls -la /dev/ttyACM* /dev/ttyUSB* 2>/dev/null || echo "  未找到串口设备"
+    echo ""
+    echo "可用的CAN接口:"
+    ip link show type can 2>/dev/null || echo "  未找到CAN接口"
+    exit 1
+fi
+
+# 关闭已有的slcand进程
+echo "[1/4] 关闭已有的slcand进程..."
+killall slcand 2>/dev/null
+
+# 关闭已有的can0接口
+echo "[2/4] 关闭已有的can0接口..."
+ip link set $INTERFACE down 2>/dev/null
+
+# 启动slcand
+echo "[3/4] 启动slcand (设备: $DEVICE, 波特率: 1Mbps)..."
+slcand -o -c -$BITRATE $DEVICE $INTERFACE
+sleep 1
+
+# 启动CAN接口
+echo "[4/4] 启动CAN接口..."
+ip link set $INTERFACE up
+
+# 验证
+echo ""
+echo "=========================================="
+echo "配置完成! 接口状态:"
+echo "=========================================="
+ip link show $INTERFACE
+
+echo ""
+echo "测试命令:"
+echo "  监听: candump $INTERFACE"
+echo "  发送: cansend $INTERFACE 123#DEADBEEF"
+echo ""

+ 48 - 9
src/httpclient/HttpCardLogin.cpp

@@ -18,8 +18,12 @@ HttpCardLogin::HttpCardLogin(QObject *parent)
 
 void HttpCardLogin::run()
 {
+    qDebug() << "[HttpCardLogin] 线程启动,cardID:" << m_cardID;
     if (m_cardID.length() > 0 && cardIDToNfc(m_cardID)) {
+        qDebug() << "[HttpCardLogin] 卡号转换成功,cardNfc:" << m_cardNfc;
         httpRequestCardLogin();
+    } else {
+        qDebug() << "[HttpCardLogin] 卡号为空或转换失败,cardID:" << m_cardID;
     }
 }
 
@@ -34,7 +38,11 @@ void HttpCardLogin::httpRequestCardLogin()
 
     QJsonDocument jsonDoc(jsonRoot);
     QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);
-    qDebug() << "json=" << QString::fromUtf8(jsonData);
+    
+    qDebug() << "[HttpCardLogin] 发送登录请求:";
+    qDebug() << "[HttpCardLogin]   URL:" << url;
+    qDebug() << "[HttpCardLogin]   请求数据:" << QString::fromUtf8(jsonData);
+    qDebug() << "[HttpCardLogin]   时间戳:" << timestampSeconds;
 
     emit signalPostRequestData(timestampSeconds, url, jsonData, NULL, m_accessToken);
     m_cardID = "";
@@ -55,32 +63,47 @@ bool HttpCardLogin::cardIDToNfc(const QString &cardId)
 
 void HttpCardLogin::slotHttpResponseCardLogin(QByteArray data)
 {
+    qDebug() << "[HttpCardLogin] 收到登录响应,数据长度:" << data.length();
+    qDebug() << "[HttpCardLogin] 响应数据:" << QString::fromUtf8(data);
+
     QJsonParseError error;
     QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
-    if (error.error == QJsonParseError::NoError) {
-        emit signalLoginReturnStat(-3, "登录失败");
+    
+    // 修复:原逻辑判断条件错误,应该是解析失败时返回错误
+    if (error.error != QJsonParseError::NoError) {
+        qDebug() << "[HttpCardLogin] JSON解析失败:" << error.errorString();
+        emit signalLoginReturnStat(-3, "JSON解析失败: " + error.errorString());
         return;
     }
-    if(!(jsonDoc.isNull() || jsonDoc.isEmpty())) {
-        emit signalLoginReturnStat(-3, "登录失败");
+    
+    // 修复:原逻辑判断条件错误,应该是文档为空时返回错误
+    if (jsonDoc.isNull() || jsonDoc.isEmpty()) {
+        qDebug() << "[HttpCardLogin] JSON文档为空";
+        emit signalLoginReturnStat(-3, "服务器返回数据为空");
         return;
     }
 
     QJsonObject rootObj = jsonDoc.object();
+    qDebug() << "[HttpCardLogin] 解析JSON对象成功";
+    
     if(rootObj.contains("code"))
     {
         int codeValue = rootObj.value("code").toInt();
+        qDebug() << "[HttpCardLogin] 响应code:" << codeValue;
+        
         if(codeValue == 200 || codeValue == 0)
         {
             if(rootObj.contains("data"))
             {
                 QJsonObject vDataObj = rootObj.value("data").toObject();
                 if (vDataObj.empty()) {
-                    emit signalLoginReturnStat(-3, "登录失败");
+                    qDebug() << "[HttpCardLogin] data对象为空";
+                    emit signalLoginReturnStat(-3, "登录失败:返回数据为空");
                     return;
                 }
                 if (!vDataObj.contains("accessToken")) {
-                    emit signalLoginReturnStat(-3, "登录失败");
+                    qDebug() << "[HttpCardLogin] 缺少accessToken字段";
+                    emit signalLoginReturnStat(-3, "登录失败:缺少accessToken");
                     return;
                 }
                 QJsonValue value = vDataObj.value("accessToken");
@@ -118,17 +141,33 @@ void HttpCardLogin::slotHttpResponseCardLogin(QByteArray data)
                         }
                     }
                     Config()->username = username;
+                    qDebug() << "[HttpCardLogin] 登录成功,用户名:" << username;
                     emit signalLoginReturnStat(0, "登录成功");
                     emit signalLoginReturnParam(username, 0);
                     m_threadstatus = false;
                 }
                 else{
-                    emit signalLoginReturnStat(-3, "登录失败");
+                    qDebug() << "[HttpCardLogin] accessToken类型不是字符串";
+                    emit signalLoginReturnStat(-3, "登录失败:accessToken格式错误");
                 }
             }
+            else{
+                qDebug() << "[HttpCardLogin] 响应中缺少data字段";
+                emit signalLoginReturnStat(-3, "登录失败:响应缺少data字段");
+            }
         }
         else{
-            emit signalLoginReturnStat(-2, "用户不存在");
+            // 获取服务器返回的错误信息
+            QString errorMsg = "用户不存在";
+            if (rootObj.contains("msg") && rootObj.value("msg").type() == QJsonValue::String) {
+                errorMsg = rootObj.value("msg").toString();
+            }
+            qDebug() << "[HttpCardLogin] 登录失败,code:" << codeValue << ",msg:" << errorMsg;
+            emit signalLoginReturnStat(-2, errorMsg);
         }
     }
+    else{
+        qDebug() << "[HttpCardLogin] 响应中缺少code字段";
+        emit signalLoginReturnStat(-3, "登录失败:响应格式错误");
+    }
 }

+ 655 - 648
src/httpclient/HttpClient.cpp

@@ -1,648 +1,655 @@
-#include "HttpClient.h"
-
-#include "../usr/config.h"
-
-#include <QNetworkAccessManager>
-#include <QNetworkRequest>
-#include <QNetworkReply>
-#include <QEventLoop>
-#include <QTimer>
-#include <QHttpPart>
-#include <QBuffer>
-#include <QJsonDocument>
-#include <QJsonObject>
-
-QString HttpClient::sToken = QString();
-
-HttpClient::HttpClient(QObject *parent)
-    : QThread(parent)
-{
-
-}
-
-bool HttpClient::getRequest(const QString &url, const QString &token, const QByteArray &inData, QByteArray &outData)
-{
-    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
-
-    QString strUrl = url;
-    if(!inData.isNull())
-    {
-        strUrl = strUrl + "?" + inData;
-    }
-    // 设置头信息
-    QNetworkRequest requestInfo;
-    requestInfo.setUrl(QUrl(strUrl));
-    requestInfo.setRawHeader("Authorization", token.toUtf8());
-    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
-    // 发送 post 请求
-    QNetworkReply* reply = pHttpMgr->get(requestInfo);
-
-    // 添加超时处理,1ms 超时
-    QEventLoop eventLoop;
-    connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
-
-    // 比如设置 1ms 内完成请求,否则就认为是超时
-    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventLoop, &QEventLoop::quit);
-    eventLoop.exec();
-
-    QByteArray array;
-    if(reply->isFinished()){
-        if (reply->error() == QNetworkReply::NoError) {
-            // 正常结束,读取响应数据
-            QByteArray result = reply->readAll();
-            QString strResult = QString::fromUtf8(result);
-            reply->deleteLater();
-            outData = strResult.toUtf8();
-            return true;
-        }
-        else {
-            // 异常结束
-            // 请求失败
-            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
-
-            outData = QString("{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}").toUtf8();
-            reply->deleteLater();
-            return false;
-        }
-    }
-    else {
-        // 请求超时
-        disconnect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
-        reply->abort();
-
-    }
-
-    reply->deleteLater();
-
-    outData =  "{\"code\":400,\"result\":\"failure\"}";
-    return false;
-}
-
-bool HttpClient::postRequest(const QString &url, const QString &token, const QByteArray &inData, QByteArray &outData)
-{
-    // 获取当前日期和时间
-    QDateTime currentDateTime = QDateTime::currentDateTime();
-    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
-
-    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
-    QString strUrl = url;
-    // 设置头信息
-    QNetworkRequest requestInfo;
-    requestInfo.setUrl(QUrl(strUrl));
-    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
-    requestInfo.setRawHeader("Module", "Android_Material");
-    requestInfo.setRawHeader("Authorization", token.toUtf8());
-    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
-
-    // 发送 post 请求
-    QNetworkReply* reply = pHttpMgr->post(requestInfo, inData);
-
-    // 添加超时处理,1ms 超时
-    QEventLoop eventLoop;
-    connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
-
-    // 比如设置 1ms 内完成请求,否则就认为是超时
-    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventLoop, &QEventLoop::quit);
-    eventLoop.exec();
-
-    if(reply->isFinished()){
-        if (reply->error() == QNetworkReply::NoError) {
-            // 正常结束,读取响应数据
-            QByteArray result = reply->readAll();
-            outData = result;
-            reply->deleteLater();
-            return true;
-        }
-        else {
-            // 异常结束
-            // 请求失败
-            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
-
-            outData = QString("{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}").toUtf8();
-            reply->deleteLater();
-            return false;
-        }
-    }
-    else {
-        // 请求超时
-        disconnect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
-        reply->abort();
-    }
-
-    reply->deleteLater();
-
-    outData = "{\"code\":400,\"result\":\"failure\"}";
-    return false;
-}
-
-bool HttpClient::putRequest(const QString &url, const QString &token, const QByteArray &inData, QByteArray &outData)
-{
-    // 获取当前日期和时间
-    QDateTime currentDateTime = QDateTime::currentDateTime();
-    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
-
-    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
-    QString strUrl = url;
-    // 设置头信息
-    QNetworkRequest requestInfo;
-    requestInfo.setUrl(QUrl(strUrl));
-    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
-    requestInfo.setRawHeader("Module", "Android_Material");
-    requestInfo.setRawHeader("Authorization", token.toUtf8());
-    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
-
-    // 发送 post 请求
-    QNetworkReply* reply = pHttpMgr->put(requestInfo, inData);
-
-    // 添加超时处理,1ms 超时
-    QEventLoop eventLoop;
-    connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
-
-    // 比如设置 1ms 内完成请求,否则就认为是超时
-    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventLoop, &QEventLoop::quit);
-    eventLoop.exec();
-
-    if(reply->isFinished()){
-        if (reply->error() == QNetworkReply::NoError) {
-            // 正常结束,读取响应数据
-            QByteArray result = reply->readAll();
-            outData = result;
-            reply->deleteLater();
-            return true;
-        }
-        else {
-            // 异常结束
-            // 请求失败
-            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
-
-            outData = QString("{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}").toUtf8();
-            reply->deleteLater();
-            return false;
-        }
-    }
-    else {
-        // 请求超时
-        disconnect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
-        reply->abort();
-    }
-
-    reply->deleteLater();
-
-    outData = "{\"code\":400,\"result\":\"failure\"}";
-    return false;
-}
-
-void HttpClient::run()
-{
-    while(this->m_threadstatus)
-    {
-        if(m_workStat != httpWorkStat::httpWorkSleep)
-        {
-            QString res = "";
-            switch(m_workStat)
-            {
-            case httpWorkStat::httpWorkJson:res = postJsonRequest();break;
-            case httpWorkStat::httpWorkFormdata:res = postFormdataRequest();break;
-            case httpWorkStat::httpWorkGet:res = getRequest();break;
-            case httpWorkStat::httpWorkSleep: break;
-            }
-
-            QByteArray bb = res.toUtf8();
-            m_mutex.unlock();
-            m_workStat = httpWorkStat::httpWorkSleep;
-            //emit signalResponseData(m_id, m_postUrl, bb);
-        }
-
-        msleep(10);
-    }
-    qDebug() << "httpclient thread exit!";
-}
-
-QString HttpClient::postJsonRequest()
-{
-    // 获取当前日期和时间
-    QDateTime currentDateTime = QDateTime::currentDateTime();
-    // qint64 timestampSeconds = currentDateTime.toSecsSinceEpoch();
-    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
-
-    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
-    QString url = "http://";
-    url = url + Config()->httpHost + m_postUrl;
-    // qDebug() << url;
-    // 设置头信息
-    QNetworkRequest requestInfo;
-    requestInfo.setUrl(QUrl(url));
-    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
-    requestInfo.setRawHeader("Module", "Android_Material");
-    QString auth = m_token;
-    requestInfo.setRawHeader("Authorization", auth.toUtf8());
-    requestInfo.setRawHeader("tenant-id", Config()->tenant_id.toUtf8());
-
-    // QString strInfo = requestInfo.url().toString();
-
-    // 发送 post 请求
-    QNetworkReply* reply = pHttpMgr->post(requestInfo, m_httpData);
-
-    // 添加超时处理,1ms 超时
-    QEventLoop eventloop;
-    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
-
-    // 比如设置 1ms 内完成请求,否则就认为是超时
-    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventloop, &QEventLoop::quit);
-    eventloop.exec();
-
-    QByteArray array;
-    if(reply->isFinished()){
-        if (reply->error() == QNetworkReply::NoError) {
-            // 正常结束,读取响应数据
-            QByteArray result = reply->readAll();
-            QString strRes = QString::fromUtf8(result);
-
-            // qDebug() << "http post success post response=" << strRes;
-            reply->deleteLater();
-            return strRes;
-        }
-        else {
-            // 异常结束
-            // 请求失败
-            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
-
-            // qDebug() << "http post failed, error code = " << statusCode.toString() << " error info: " << reply->errorString();
-            QString strRes = "{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}";
-            reply->deleteLater();
-            return strRes;
-        }
-    }
-    else {
-        // 请求超时
-        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
-        reply->abort();
-        // qDebug() << "http post timeout";
-    }
-
-    reply->deleteLater();
-
-    return "{\"code\":400,\"result\":\"failure\"}";
-}
-
-QString HttpClient::postFormdataRequest()
-{
-    // 获取当前日期和时间
-    QDateTime currentDateTime = QDateTime::currentDateTime();
-    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
-
-    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
-    QString url = "http://";
-    url = url + Config()->httpHost + m_postUrl;
-
-    // 创建multipart表单
-    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
-
-    if(false == m_httpData.isNull())
-    {
-        // 添加JSON数据部分
-        QHttpPart jsonPart;
-        jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"userName\""));
-        jsonPart.setBody(m_httpData);
-        multiPart->append(jsonPart);
-    }
-
-    // 添加文件部分
-    QHttpPart filePart;
-    filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/bmp"));
-    filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
-                       QVariant(QString("form-data; name=\"file\"; filename=\"finger.bmp\"")));
-
-
-    QBuffer *buffer = new QBuffer(&m_httpFile);
-    if (!buffer->open(QIODevice::ReadOnly)) {
-        qDebug() << "无法打开缓冲区";
-        delete pHttpMgr;
-        delete buffer;
-        delete multiPart;
-        return "{\"code\":400,\"result\":\"无法打开缓冲区\"}";
-    }
-    filePart.setBodyDevice(buffer);
-    buffer->setParent(multiPart); // 设置父对象以便自动清理
-
-    multiPart->append(filePart);
-
-
-    // 设置头信息
-    QNetworkRequest requestInfo;
-    requestInfo.setUrl(QUrl(url));
-    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + multiPart->boundary());
-    requestInfo.setRawHeader("Module", "Android_Material");
-    QString auth = m_token;
-    requestInfo.setRawHeader("Authorization", auth.toUtf8());
-    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
-
-    // 发送 post 请求
-    QNetworkReply* reply = pHttpMgr->post(requestInfo, multiPart);
-    multiPart->setParent(reply); // reply删除时会自动删除multiPart
-
-    // 添加超时处理,1ms 超时
-    QEventLoop eventloop;
-    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
-
-    // 比如设置 1ms 内完成请求,否则就认为是超时
-    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventloop, &QEventLoop::quit);
-    eventloop.exec();
-
-    QByteArray array;
-    if(reply->isFinished()){
-        if (reply->error() == QNetworkReply::NoError) {
-            // 正常结束,读取响应数据
-            QByteArray result = reply->readAll();
-            QString strRes = QString::fromUtf8(result);
-
-            reply->deleteLater();
-            return strRes;
-        }
-        else {
-            // 异常结束
-            // 请求失败
-            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
-
-            QString strRes = "{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}";
-            reply->deleteLater();
-            return strRes;
-        }
-    }
-    else {
-        // 请求超时
-        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
-        reply->abort();
-    }
-
-    reply->deleteLater();
-
-    return "{\"code\":400,\"result\":\"failure\"}";
-}
-
-QString HttpClient::getRequest()
-{
-    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
-    QString url = "http://" + Config()->httpHost;
-    url = url + m_postUrl;
-    QString getJsonStr = "";
-    if(m_httpData != "{}")
-    {
-        // QString getJsonStr = "";
-        QJsonDocument jsonDoc = QJsonDocument::fromJson(m_httpData);
-        if (!jsonDoc.isNull() && !jsonDoc.isEmpty() && jsonDoc.isObject()) {
-            QJsonObject jsonObj = jsonDoc.object();
-            for (const QString& key : jsonObj.keys()) {
-                QString value;
-                if (jsonObj.value(key).isString()) {
-                    value = jsonObj.value(key).toString();
-                }
-                else if (jsonObj.value(key).isDouble()) {
-                    value = QString::number(jsonObj.value(key).toDouble());
-                }
-                else {
-                    value = QString::number(jsonObj.value(key).toInt());
-                }
-
-                if (getJsonStr.isEmpty()) {
-                    getJsonStr = key + "=" + value;
-                }
-                else {
-                    getJsonStr = getJsonStr + "&" + key + "=" + value;
-                }
-            }
-        }
-        url = url + "?" + getJsonStr;
-    }
-    // 设置头信息
-    QNetworkRequest requestInfo;
-    requestInfo.setUrl(QUrl(url));
-    QString auth = m_token;
-    requestInfo.setRawHeader("Authorization", auth.toUtf8());
-    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
-    // 发送 get 请求
-    QNetworkReply* reply = pHttpMgr->get(requestInfo);
-
-    // 添加超时处理,1ms 超时
-    QEventLoop eventloop;
-    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
-
-    // 比如设置 1ms 内完成请求,否则就认为是超时
-    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventloop, &QEventLoop::quit);
-    eventloop.exec();
-
-    QByteArray array;
-    if(reply->isFinished()){
-        if (reply->error() == QNetworkReply::NoError) {
-            // 正常结束,读取响应数据
-            QByteArray result = reply->readAll();
-            QString strRes = QString::fromUtf8(result);
-            QStringList urlSplit = m_postUrl.split("/");
-
-            reply->deleteLater();
-            return strRes;
-        }
-        else {
-            // 异常结束
-            // 请求失败
-            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
-
-            QString strRes = "{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}";
-            reply->deleteLater();
-            return strRes;
-        }
-    }
-    else {
-        // 请求超时
-        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
-        reply->abort();
-    }
-
-    reply->deleteLater();
-
-    return "{\"code\":400,\"result\":\"failure\"}";
-}
-
-QString HttpClient::putJsonRequest()
-{
-    // 获取当前日期和时间
-    QDateTime currentDateTime = QDateTime::currentDateTime();
-    // qint64 timestampSeconds = currentDateTime.toSecsSinceEpoch();
-    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
-
-    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
-    QString url = "http://";
-    url = url + Config()->httpHost + m_postUrl;
-    // qDebug() << url;
-    // 设置头信息
-    QNetworkRequest requestInfo;
-    requestInfo.setUrl(QUrl(url));
-    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
-    requestInfo.setRawHeader("Module", "Android_Material");
-    QString auth = m_token;
-    requestInfo.setRawHeader("Authorization", auth.toUtf8());
-    requestInfo.setRawHeader("tenant-id", Config()->tenant_id.toUtf8());
-
-    // 发送 put 请求
-    QNetworkReply* reply = pHttpMgr->put(requestInfo, m_httpData);
-
-    // 添加超时处理,1ms 超时
-    QEventLoop eventloop;
-    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
-
-    // 比如设置 1ms 内完成请求,否则就认为是超时
-    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventloop, &QEventLoop::quit);
-    eventloop.exec();
-
-    QByteArray array;
-    if(reply->isFinished()){
-        if (reply->error() == QNetworkReply::NoError) {
-            // 正常结束,读取响应数据
-            QByteArray result = reply->readAll();
-            QString strRes = QString::fromUtf8(result);
-
-            // qDebug() << "http post success post response=" << strRes;
-            reply->deleteLater();
-            return strRes;
-        }
-        else {
-            // 异常结束
-            // 请求失败
-            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
-
-            // qDebug() << "http post failed, error code = " << statusCode.toString() << " error info: " << reply->errorString();
-            QString strRes = "{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}";
-            reply->deleteLater();
-            return strRes;
-        }
-    }
-    else {
-        // 请求超时
-        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
-        reply->abort();
-        // qDebug() << "http post timeout";
-    }
-
-    reply->deleteLater();
-
-    return "{\"code\":400,\"result\":\"failure\"}";
-}
-
-void HttpClient::slotPostRequestData(quint64 id, QString postUrl, QByteArray data, QByteArray file, QString token)
-{
-    m_mutex.lock();
-    this->m_id = id;
-    this->m_postUrl = postUrl;
-    this->m_httpData = data;
-    this->m_httpFile = file;
-    this->m_token = token;
-    HttpClient::sToken = token;
-    if(!this->m_httpData.isNull() && !this->m_httpFile.isNull())
-    {
-        m_workStat = httpWorkStat::httpWorkFormdata;
-    }
-    else if(!this->m_httpData.isNull())
-    {
-        QString res = postJsonRequest();
-        QByteArray bb = res.toUtf8();
-        m_mutex.unlock();
-
-        if(m_postUrl == Config()->usernameLogin_url) {
-            emit signalResponsePasswordLoginData(bb);
-        }
-        else if (m_postUrl == Config()->cardLogin_url) {
-            emit signalResponseCardLoginData(bb);
-        }
-        else if (m_postUrl == Config()->updateNodeApprovalUrl) {
-            emit signalResponseUpdateNodeApprovalUrl(bb);
-        }
-        else if (m_postUrl == Config()->uploadJobTicketUrl) {
-            emit signalResponseUploadJobTicket(bb);
-        }
-        else if (m_postUrl == Config()->uploadPositionInfoUrl) {
-            emit signalResponseUploadPositionInfo(bb);
-        }
-        else if (m_postUrl == Config()->updateUncolockUrl) {
-            emit signalResponseUpdateUncolock(bb);
-        }
-        else if (m_postUrl == Config()->updateColockUrl) {
-            emit signalResponseUpdateColock(bb);
-        }
-        else if (m_postUrl == Config()->updatePointUnlock) {
-            emit signalResponseUpdatePointUnlock(bb);
-        }
-        else if (m_postUrl == Config()->updateBackLock) {
-            emit signalResponseUpdateBackLock(bb);
-        }
-    }
-    else if(!this->m_httpFile.isNull())
-    {
-        m_workStat = httpWorkStat::httpWorkFormdata;
-    }
-}
-
-void HttpClient::slotGetRequestData(quint64 id, QString postUrl, QByteArray data, QString token)
-{
-    m_mutex.lock();
-    this->m_id = id;
-    this->m_postUrl = postUrl;
-    this->m_httpData = data;
-    this->m_token = token;
-    HttpClient::sToken = token;
-
-    QString res = getRequest();
-    QByteArray bb = res.toUtf8();
-    m_mutex.unlock();
-
-    if (m_postUrl == Config()->jobTicketsUrl) {
-        emit signalResponseGetJobTickets(bb);
-    }
-    else if (m_postUrl == Config()->workNodeDetail) {
-        emit signalResponseGetWorkNodeDetail(bb);
-    }
-    else if (m_postUrl == Config()->workNodeDetailForm) {
-        emit signalResponseGetFormById(bb);
-    }
-    else if (m_postUrl == Config()->userInfoUrl) {
-        emit signalResponseGetUserInfoUrl(bb);
-    }
-    else if (m_postUrl == Config()->keyMACByNFC) {
-        emit signalResponseGetKeyMAC(bb);
-    }
-    else if (m_postUrl == Config()->isolationPointById) {
-        emit signalResponseGetIsolationPointInfo(bb);
-    }
-}
-
-void HttpClient::slotPutRequestData(quint64 id, QString postUrl, QByteArray data, QByteArray file, QString token)
-{
-    m_mutex.lock();
-    this->m_id = id;
-    this->m_postUrl = postUrl;
-    this->m_httpData = data;
-    this->m_httpFile = file;
-    this->m_token = token;
-    HttpClient::sToken = token;
-    if(!this->m_httpData.isNull() && !this->m_httpFile.isNull())
-    {
-        m_workStat = httpWorkStat::httpWorkFormdata;
-    }
-    else if(!this->m_httpData.isNull())
-    {
-        QString res = putJsonRequest();
-        QByteArray bb = res.toUtf8();
-        m_mutex.unlock();
-
-        if(m_postUrl == Config()->updateUserInfoUrl) {
-            emit signalReponseUpdateUserInfoUrl(bb);
-        }
-        else if (m_postUrl == Config()->updatePasswordUrl) {
-            emit signalReponseUpdateUserPasswordUrl(bb);
-        }
-    }
-}
-
-void HttpClient::slotSetThreadStop()
-{
-    this->m_threadstatus = false;
-}
+#include "HttpClient.h"
+
+#include "../usr/config.h"
+
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QEventLoop>
+#include <QTimer>
+#include <QHttpPart>
+#include <QBuffer>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+QString HttpClient::sToken = QString();
+
+HttpClient::HttpClient(QObject *parent)
+    : QThread(parent)
+{
+
+}
+
+bool HttpClient::getRequest(const QString &url, const QString &token, const QByteArray &inData, QByteArray &outData)
+{
+    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
+
+    QString strUrl = url;
+    if(!inData.isNull())
+    {
+        strUrl = strUrl + "?" + inData;
+    }
+    // 设置头信息
+    QNetworkRequest requestInfo;
+    requestInfo.setUrl(QUrl(strUrl));
+    requestInfo.setRawHeader("Authorization", token.toUtf8());
+    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
+    // 发送 post 请求
+    QNetworkReply* reply = pHttpMgr->get(requestInfo);
+
+    // 添加超时处理,1ms 超时
+    QEventLoop eventLoop;
+    connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
+
+    // 比如设置 1ms 内完成请求,否则就认为是超时
+    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventLoop, &QEventLoop::quit);
+    eventLoop.exec();
+
+    QByteArray array;
+    if(reply->isFinished()){
+        if (reply->error() == QNetworkReply::NoError) {
+            // 正常结束,读取响应数据
+            QByteArray result = reply->readAll();
+            QString strResult = QString::fromUtf8(result);
+            reply->deleteLater();
+            outData = strResult.toUtf8();
+            return true;
+        }
+        else {
+            // 异常结束
+            // 请求失败
+            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+            outData = QString("{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}").toUtf8();
+            reply->deleteLater();
+            return false;
+        }
+    }
+    else {
+        // 请求超时
+        disconnect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
+        reply->abort();
+
+    }
+
+    reply->deleteLater();
+
+    outData =  "{\"code\":400,\"result\":\"failure\"}";
+    return false;
+}
+
+bool HttpClient::postRequest(const QString &url, const QString &token, const QByteArray &inData, QByteArray &outData)
+{
+    // 获取当前日期和时间
+    QDateTime currentDateTime = QDateTime::currentDateTime();
+    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
+
+    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
+    QString strUrl = url;
+    // 设置头信息
+    QNetworkRequest requestInfo;
+    requestInfo.setUrl(QUrl(strUrl));
+    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
+    requestInfo.setRawHeader("Module", "Android_Material");
+    requestInfo.setRawHeader("Authorization", token.toUtf8());
+    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
+
+    // 发送 post 请求
+    QNetworkReply* reply = pHttpMgr->post(requestInfo, inData);
+
+    // 添加超时处理,1ms 超时
+    QEventLoop eventLoop;
+    connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
+
+    // 比如设置 1ms 内完成请求,否则就认为是超时
+    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventLoop, &QEventLoop::quit);
+    eventLoop.exec();
+
+    if(reply->isFinished()){
+        if (reply->error() == QNetworkReply::NoError) {
+            // 正常结束,读取响应数据
+            QByteArray result = reply->readAll();
+            outData = result;
+            reply->deleteLater();
+            return true;
+        }
+        else {
+            // 异常结束
+            // 请求失败
+            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+            outData = QString("{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}").toUtf8();
+            reply->deleteLater();
+            return false;
+        }
+    }
+    else {
+        // 请求超时
+        disconnect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
+        reply->abort();
+    }
+
+    reply->deleteLater();
+
+    outData = "{\"code\":400,\"result\":\"failure\"}";
+    return false;
+}
+
+bool HttpClient::putRequest(const QString &url, const QString &token, const QByteArray &inData, QByteArray &outData)
+{
+    // 获取当前日期和时间
+    QDateTime currentDateTime = QDateTime::currentDateTime();
+    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
+
+    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
+    QString strUrl = url;
+    // 设置头信息
+    QNetworkRequest requestInfo;
+    requestInfo.setUrl(QUrl(strUrl));
+    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
+    requestInfo.setRawHeader("Module", "Android_Material");
+    requestInfo.setRawHeader("Authorization", token.toUtf8());
+    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
+
+    // 发送 post 请求
+    QNetworkReply* reply = pHttpMgr->put(requestInfo, inData);
+
+    // 添加超时处理,1ms 超时
+    QEventLoop eventLoop;
+    connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
+
+    // 比如设置 1ms 内完成请求,否则就认为是超时
+    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventLoop, &QEventLoop::quit);
+    eventLoop.exec();
+
+    if(reply->isFinished()){
+        if (reply->error() == QNetworkReply::NoError) {
+            // 正常结束,读取响应数据
+            QByteArray result = reply->readAll();
+            outData = result;
+            reply->deleteLater();
+            return true;
+        }
+        else {
+            // 异常结束
+            // 请求失败
+            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+            outData = QString("{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}").toUtf8();
+            reply->deleteLater();
+            return false;
+        }
+    }
+    else {
+        // 请求超时
+        disconnect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
+        reply->abort();
+    }
+
+    reply->deleteLater();
+
+    outData = "{\"code\":400,\"result\":\"failure\"}";
+    return false;
+}
+
+void HttpClient::run()
+{
+    while(this->m_threadstatus)
+    {
+        if(m_workStat != httpWorkStat::httpWorkSleep)
+        {
+            QString res = "";
+            switch(m_workStat)
+            {
+            case httpWorkStat::httpWorkJson:res = postJsonRequest();break;
+            case httpWorkStat::httpWorkFormdata:res = postFormdataRequest();break;
+            case httpWorkStat::httpWorkGet:res = getRequest();break;
+            case httpWorkStat::httpWorkSleep: break;
+            }
+
+            QByteArray bb = res.toUtf8();
+            m_mutex.unlock();
+            m_workStat = httpWorkStat::httpWorkSleep;
+            //emit signalResponseData(m_id, m_postUrl, bb);
+        }
+
+        msleep(10);
+    }
+    qDebug() << "httpclient thread exit!";
+}
+
+QString HttpClient::postJsonRequest()
+{
+    // 获取当前日期和时间
+    QDateTime currentDateTime = QDateTime::currentDateTime();
+    // qint64 timestampSeconds = currentDateTime.toSecsSinceEpoch();
+    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
+
+    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
+    QString url = "http://";
+    url = url + Config()->httpHost + m_postUrl;
+    // qDebug() << url;
+    // 设置头信息
+    QNetworkRequest requestInfo;
+    requestInfo.setUrl(QUrl(url));
+    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
+    requestInfo.setRawHeader("Module", "Android_Material");
+    QString auth = m_token;
+    requestInfo.setRawHeader("Authorization", auth.toUtf8());
+    requestInfo.setRawHeader("tenant-id", Config()->tenant_id.toUtf8());
+
+    // QString strInfo = requestInfo.url().toString();
+
+    // 发送 post 请求
+    QNetworkReply* reply = pHttpMgr->post(requestInfo, m_httpData);
+
+    // 添加超时处理,1ms 超时
+    QEventLoop eventloop;
+    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
+
+    // 比如设置 1ms 内完成请求,否则就认为是超时
+    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventloop, &QEventLoop::quit);
+    eventloop.exec();
+
+    QByteArray array;
+    if(reply->isFinished()){
+        if (reply->error() == QNetworkReply::NoError) {
+            // 正常结束,读取响应数据
+            QByteArray result = reply->readAll();
+            QString strRes = QString::fromUtf8(result);
+
+            // qDebug() << "http post success post response=" << strRes;
+            reply->deleteLater();
+            return strRes;
+        }
+        else {
+            // 异常结束
+            // 请求失败
+            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+            // qDebug() << "http post failed, error code = " << statusCode.toString() << " error info: " << reply->errorString();
+            QString strRes = "{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}";
+            reply->deleteLater();
+            return strRes;
+        }
+    }
+    else {
+        // 请求超时
+        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
+        reply->abort();
+        // qDebug() << "http post timeout";
+    }
+
+    reply->deleteLater();
+
+    return "{\"code\":400,\"result\":\"failure\"}";
+}
+
+QString HttpClient::postFormdataRequest()
+{
+    // 获取当前日期和时间
+    QDateTime currentDateTime = QDateTime::currentDateTime();
+    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
+
+    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
+    QString url = "http://";
+    url = url + Config()->httpHost + m_postUrl;
+
+    // 创建multipart表单
+    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
+
+    if(false == m_httpData.isNull())
+    {
+        // 添加JSON数据部分
+        QHttpPart jsonPart;
+        jsonPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"userName\""));
+        jsonPart.setBody(m_httpData);
+        multiPart->append(jsonPart);
+    }
+
+    // 添加文件部分
+    QHttpPart filePart;
+    filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/bmp"));
+    filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
+                       QVariant(QString("form-data; name=\"file\"; filename=\"finger.bmp\"")));
+
+
+    QBuffer *buffer = new QBuffer(&m_httpFile);
+    if (!buffer->open(QIODevice::ReadOnly)) {
+        qDebug() << "无法打开缓冲区";
+        delete pHttpMgr;
+        delete buffer;
+        delete multiPart;
+        return "{\"code\":400,\"result\":\"无法打开缓冲区\"}";
+    }
+    filePart.setBodyDevice(buffer);
+    buffer->setParent(multiPart); // 设置父对象以便自动清理
+
+    multiPart->append(filePart);
+
+
+    // 设置头信息
+    QNetworkRequest requestInfo;
+    requestInfo.setUrl(QUrl(url));
+    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + multiPart->boundary());
+    requestInfo.setRawHeader("Module", "Android_Material");
+    QString auth = m_token;
+    requestInfo.setRawHeader("Authorization", auth.toUtf8());
+    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
+
+    // 发送 post 请求
+    QNetworkReply* reply = pHttpMgr->post(requestInfo, multiPart);
+    multiPart->setParent(reply); // reply删除时会自动删除multiPart
+
+    // 添加超时处理,1ms 超时
+    QEventLoop eventloop;
+    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
+
+    // 比如设置 1ms 内完成请求,否则就认为是超时
+    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventloop, &QEventLoop::quit);
+    eventloop.exec();
+
+    QByteArray array;
+    if(reply->isFinished()){
+        if (reply->error() == QNetworkReply::NoError) {
+            // 正常结束,读取响应数据
+            QByteArray result = reply->readAll();
+            QString strRes = QString::fromUtf8(result);
+
+            reply->deleteLater();
+            return strRes;
+        }
+        else {
+            // 异常结束
+            // 请求失败
+            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+            QString strRes = "{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}";
+            reply->deleteLater();
+            return strRes;
+        }
+    }
+    else {
+        // 请求超时
+        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
+        reply->abort();
+    }
+
+    reply->deleteLater();
+
+    return "{\"code\":400,\"result\":\"failure\"}";
+}
+
+QString HttpClient::getRequest()
+{
+    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
+    QString url = "http://" + Config()->httpHost;
+    url = url + m_postUrl;
+    QString getJsonStr = "";
+    if(m_httpData != "{}")
+    {
+        // QString getJsonStr = "";
+        QJsonDocument jsonDoc = QJsonDocument::fromJson(m_httpData);
+        if (!jsonDoc.isNull() && !jsonDoc.isEmpty() && jsonDoc.isObject()) {
+            QJsonObject jsonObj = jsonDoc.object();
+            for (const QString& key : jsonObj.keys()) {
+                QString value;
+                if (jsonObj.value(key).isString()) {
+                    value = jsonObj.value(key).toString();
+                }
+                else if (jsonObj.value(key).isDouble()) {
+                    value = QString::number(jsonObj.value(key).toDouble());
+                }
+                else {
+                    value = QString::number(jsonObj.value(key).toInt());
+                }
+
+                if (getJsonStr.isEmpty()) {
+                    getJsonStr = key + "=" + value;
+                }
+                else {
+                    getJsonStr = getJsonStr + "&" + key + "=" + value;
+                }
+            }
+        }
+        url = url + "?" + getJsonStr;
+    }
+    // 设置头信息
+    QNetworkRequest requestInfo;
+    requestInfo.setUrl(QUrl(url));
+    QString auth = m_token;
+    requestInfo.setRawHeader("Authorization", auth.toUtf8());
+    requestInfo.setRawHeader("tenant_id", Config()->tenant_id.toUtf8());
+    // 发送 get 请求
+    QNetworkReply* reply = pHttpMgr->get(requestInfo);
+
+    // 添加超时处理,1ms 超时
+    QEventLoop eventloop;
+    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
+
+    // 比如设置 1ms 内完成请求,否则就认为是超时
+    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventloop, &QEventLoop::quit);
+    eventloop.exec();
+
+    QByteArray array;
+    if(reply->isFinished()){
+        if (reply->error() == QNetworkReply::NoError) {
+            // 正常结束,读取响应数据
+            QByteArray result = reply->readAll();
+            QString strRes = QString::fromUtf8(result);
+            QStringList urlSplit = m_postUrl.split("/");
+
+            reply->deleteLater();
+            return strRes;
+        }
+        else {
+            // 异常结束
+            // 请求失败
+            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+            QString strRes = "{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}";
+            reply->deleteLater();
+            return strRes;
+        }
+    }
+    else {
+        // 请求超时
+        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
+        reply->abort();
+    }
+
+    reply->deleteLater();
+
+    return "{\"code\":400,\"result\":\"failure\"}";
+}
+
+QString HttpClient::putJsonRequest()
+{
+    // 获取当前日期和时间
+    QDateTime currentDateTime = QDateTime::currentDateTime();
+    // qint64 timestampSeconds = currentDateTime.toSecsSinceEpoch();
+    QString timetamp_str = currentDateTime.toString("yyyy-MM-ddTHH:mm:ssZ");
+
+    QNetworkAccessManager* pHttpMgr = new QNetworkAccessManager();
+    QString url = "http://";
+    url = url + Config()->httpHost + m_postUrl;
+    // qDebug() << url;
+    // 设置头信息
+    QNetworkRequest requestInfo;
+    requestInfo.setUrl(QUrl(url));
+    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
+    requestInfo.setRawHeader("Module", "Android_Material");
+    QString auth = m_token;
+    requestInfo.setRawHeader("Authorization", auth.toUtf8());
+    requestInfo.setRawHeader("tenant-id", Config()->tenant_id.toUtf8());
+
+    // 发送 put 请求
+    QNetworkReply* reply = pHttpMgr->put(requestInfo, m_httpData);
+
+    // 添加超时处理,1ms 超时
+    QEventLoop eventloop;
+    connect(reply, SIGNAL(finished()), &eventloop, SLOT(quit()));
+
+    // 比如设置 1ms 内完成请求,否则就认为是超时
+    QTimer::singleShot(HTTP_REQUEST_TIMEOUT, &eventloop, &QEventLoop::quit);
+    eventloop.exec();
+
+    QByteArray array;
+    if(reply->isFinished()){
+        if (reply->error() == QNetworkReply::NoError) {
+            // 正常结束,读取响应数据
+            QByteArray result = reply->readAll();
+            QString strRes = QString::fromUtf8(result);
+
+            // qDebug() << "http post success post response=" << strRes;
+            reply->deleteLater();
+            return strRes;
+        }
+        else {
+            // 异常结束
+            // 请求失败
+            QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+            // qDebug() << "http post failed, error code = " << statusCode.toString() << " error info: " << reply->errorString();
+            QString strRes = "{\"code\":" + statusCode.toString() + ",\"result\":\"" + reply->errorString() + "\"}";
+            reply->deleteLater();
+            return strRes;
+        }
+    }
+    else {
+        // 请求超时
+        disconnect(reply, &QNetworkReply::finished, &eventloop, &QEventLoop::quit);
+        reply->abort();
+        // qDebug() << "http post timeout";
+    }
+
+    reply->deleteLater();
+
+    return "{\"code\":400,\"result\":\"failure\"}";
+}
+
+void HttpClient::slotPostRequestData(quint64 id, QString postUrl, QByteArray data, QByteArray file, QString token)
+{
+    qDebug() << "[HttpClient] slotPostRequestData 收到POST请求";
+    qDebug() << "[HttpClient]   URL:" << postUrl;
+    qDebug() << "[HttpClient]   数据:" << QString::fromUtf8(data);
+    
+    m_mutex.lock();
+    this->m_id = id;
+    this->m_postUrl = postUrl;
+    this->m_httpData = data;
+    this->m_httpFile = file;
+    this->m_token = token;
+    HttpClient::sToken = token;
+    if(!this->m_httpData.isNull() && !this->m_httpFile.isNull())
+    {
+        m_workStat = httpWorkStat::httpWorkFormdata;
+    }
+    else if(!this->m_httpData.isNull())
+    {
+        qDebug() << "[HttpClient] 开始执行postJsonRequest...";
+        QString res = postJsonRequest();
+        qDebug() << "[HttpClient] postJsonRequest完成,响应:" << res.left(200);
+        QByteArray bb = res.toUtf8();
+        m_mutex.unlock();
+
+        if(m_postUrl == Config()->usernameLogin_url) {
+            emit signalResponsePasswordLoginData(bb);
+        }
+        else if (m_postUrl == Config()->cardLogin_url) {
+            qDebug() << "[HttpClient] 发射signalResponseCardLoginData信号";
+            emit signalResponseCardLoginData(bb);
+        }
+        else if (m_postUrl == Config()->updateNodeApprovalUrl) {
+            emit signalResponseUpdateNodeApprovalUrl(bb);
+        }
+        else if (m_postUrl == Config()->uploadJobTicketUrl) {
+            emit signalResponseUploadJobTicket(bb);
+        }
+        else if (m_postUrl == Config()->uploadPositionInfoUrl) {
+            emit signalResponseUploadPositionInfo(bb);
+        }
+        else if (m_postUrl == Config()->updateUncolockUrl) {
+            emit signalResponseUpdateUncolock(bb);
+        }
+        else if (m_postUrl == Config()->updateColockUrl) {
+            emit signalResponseUpdateColock(bb);
+        }
+        else if (m_postUrl == Config()->updatePointUnlock) {
+            emit signalResponseUpdatePointUnlock(bb);
+        }
+        else if (m_postUrl == Config()->updateBackLock) {
+            emit signalResponseUpdateBackLock(bb);
+        }
+    }
+    else if(!this->m_httpFile.isNull())
+    {
+        m_workStat = httpWorkStat::httpWorkFormdata;
+    }
+}
+
+void HttpClient::slotGetRequestData(quint64 id, QString postUrl, QByteArray data, QString token)
+{
+    m_mutex.lock();
+    this->m_id = id;
+    this->m_postUrl = postUrl;
+    this->m_httpData = data;
+    this->m_token = token;
+    HttpClient::sToken = token;
+
+    QString res = getRequest();
+    QByteArray bb = res.toUtf8();
+    m_mutex.unlock();
+
+    if (m_postUrl == Config()->jobTicketsUrl) {
+        emit signalResponseGetJobTickets(bb);
+    }
+    else if (m_postUrl == Config()->workNodeDetail) {
+        emit signalResponseGetWorkNodeDetail(bb);
+    }
+    else if (m_postUrl == Config()->workNodeDetailForm) {
+        emit signalResponseGetFormById(bb);
+    }
+    else if (m_postUrl == Config()->userInfoUrl) {
+        emit signalResponseGetUserInfoUrl(bb);
+    }
+    else if (m_postUrl == Config()->keyMACByNFC) {
+        emit signalResponseGetKeyMAC(bb);
+    }
+    else if (m_postUrl == Config()->isolationPointById) {
+        emit signalResponseGetIsolationPointInfo(bb);
+    }
+}
+
+void HttpClient::slotPutRequestData(quint64 id, QString postUrl, QByteArray data, QByteArray file, QString token)
+{
+    m_mutex.lock();
+    this->m_id = id;
+    this->m_postUrl = postUrl;
+    this->m_httpData = data;
+    this->m_httpFile = file;
+    this->m_token = token;
+    HttpClient::sToken = token;
+    if(!this->m_httpData.isNull() && !this->m_httpFile.isNull())
+    {
+        m_workStat = httpWorkStat::httpWorkFormdata;
+    }
+    else if(!this->m_httpData.isNull())
+    {
+        QString res = putJsonRequest();
+        QByteArray bb = res.toUtf8();
+        m_mutex.unlock();
+
+        if(m_postUrl == Config()->updateUserInfoUrl) {
+            emit signalReponseUpdateUserInfoUrl(bb);
+        }
+        else if (m_postUrl == Config()->updatePasswordUrl) {
+            emit signalReponseUpdateUserPasswordUrl(bb);
+        }
+    }
+}
+
+void HttpClient::slotSetThreadStop()
+{
+    this->m_threadstatus = false;
+}

+ 3 - 1
src/httpclient/HttpGetJobTickets.cpp

@@ -14,7 +14,9 @@ HttpGetJobTickets::HttpGetJobTickets(QObject *parent)
 
 HttpGetJobTickets::~HttpGetJobTickets()
 {
-    cleanJobTicketsModel();
+    // 注意:不要在析构函数中清空 JobTicketModel 数据
+    // 因为可能有多个 HttpGetJobTickets 实例,一个实例销毁时不应影响共享的数据模型
+    // cleanJobTicketsModel();
 }
 
 void HttpGetJobTickets::run()

+ 25 - 0
src/httpclient/HttpGetWorkNodeDetail.cpp

@@ -66,6 +66,16 @@ bool parseJsonToFormControl(const QJsonObject& jsonObj, FormControl& formControl
     if (jsonObj.contains("gridColumns")) {
         formControl.m_gridColumns = jsonObj.value("gridColumns").toInt();
     }
+    // 表单回显值
+    if (jsonObj.contains("value")) {
+        if (jsonObj.value("value").isString()) {
+            formControl.m_value = jsonObj.value("value").toString();
+        } else if (jsonObj.value("value").isBool()) {
+            formControl.m_value = jsonObj.value("value").toBool() ? "true" : "false";
+        } else if (jsonObj.value("value").isDouble()) {
+            formControl.m_value = QString::number(jsonObj.value("value").toDouble());
+        }
+    }
     return true;
 }
 
@@ -181,6 +191,14 @@ void HttpGetWorkNodeDetail::slotHttpResponseGetWorkNodeDetail(QByteArray data)
 
                 QJsonObject formData = parseJsonString(vDataObj.value("formData").toString());
                 WorkNodeFormModel::instance()->setFormJsonInfo(vDataObj.value("formData").toString());
+                
+                // 解析表单名称(当前任务名称)
+                if (formData.contains("name") && formData.value("name").isString()) {
+                    WorkNodeFormModel::instance()->setFormName(formData.value("name").toString());
+                } else {
+                    WorkNodeFormModel::instance()->setFormName("");
+                }
+                
                 // 解析单个页面表单信息
                 int gridColumns = 1;
                 if (formData.contains("conf")) {
@@ -323,6 +341,13 @@ void HttpGetWorkNodeDetail::slotHttpResponseGetFormById(QByteArray data)
                     formControl.m_gridIndex = gridIndex;
                     WorkNodeFormModel::instance()->append(formControl);
                 }
+                // 解析表单名称(当前任务名称)
+                if (vDataObj.contains("name") && vDataObj.value("name").isString()) {
+                    WorkNodeFormModel::instance()->setFormName(vDataObj.value("name").toString());
+                } else {
+                    WorkNodeFormModel::instance()->setFormName("");
+                }
+                
                 QJsonDocument formDoc(vDataObj);
                 WorkNodeFormModel::instance()->setFormJsonInfo(formDoc.toJson(QJsonDocument::Compact));
                 emit signalWorkNodeDetailReturnStat(0, "解析表单页完成");

+ 31 - 0
src/httpclient/JobTicketsModel.cpp

@@ -155,3 +155,34 @@ QVariant JobTicketModel::data(const QModelIndex &index, int role) const
 
     return QVariant();
 }
+
+int JobTicketModel::runningCount() const
+{
+    int count = 0;
+    for (const JobTicket& ticket : m_dataList) {
+        if (ticket.m_approvalStatus == "进行中") {
+            count++;
+        }
+    }
+    return count;
+}
+
+int JobTicketModel::getFirstRunningNodeId() const
+{
+    for (const JobTicket& ticket : m_dataList) {
+        if (ticket.m_approvalStatus == "进行中") {
+            return ticket.m_nodeId;
+        }
+    }
+    return -1;
+}
+
+int JobTicketModel::getFirstRunningIndex() const
+{
+    for (int i = 0; i < m_dataList.count(); i++) {
+        if (m_dataList[i].m_approvalStatus == "进行中") {
+            return i;
+        }
+    }
+    return -1;
+}

+ 7 - 0
src/httpclient/JobTicketsModel.h

@@ -78,6 +78,13 @@ public:
     void clear();
 
     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    
+    // 获取"进行中"状态的作业数量
+    Q_INVOKABLE int runningCount() const;
+    // 获取第一个"进行中"作业的nodeId,如果没有返回-1
+    Q_INVOKABLE int getFirstRunningNodeId() const;
+    // 获取第一个"进行中"作业的索引,如果没有返回-1
+    Q_INVOKABLE int getFirstRunningIndex() const;
     virtual QHash<int, QByteArray> roleNames() const override;
     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
 private:

+ 4 - 0
src/httpclient/WorkNodeFormModel.cpp

@@ -63,6 +63,7 @@ QHash<int, QByteArray> WorkNodeFormModel::roleNames() const
     roles[Required] = "required";
     roles[RequiredMessage] = "requiredMessage";
     roles[Type] = "type";
+    roles[Value] = "value";
     roles[GridColumns] = "gridColumns";
     roles[GroupName] = "gridGroupName";
     roles[GridCount] = "gridCount";
@@ -103,6 +104,9 @@ QVariant WorkNodeFormModel::data(const QModelIndex &index, int role) const
     else if (role == Type) {
         return formControl.m_type;
     }
+    else if (role == Value) {
+        return formControl.m_value;
+    }
     else if (role == GroupName) {
         return formControl.m_gridGroupName;
     }

+ 14 - 0
src/httpclient/WorkNodeFormModel.h

@@ -19,6 +19,7 @@ public:
     bool m_required;
     QString m_requiredMsg;
     QString m_type;
+    QString m_value;        // 表单回显值
 
     QVariant m_result;
 
@@ -39,6 +40,7 @@ class WorkNodeFormModel : public QAbstractListModel
     Q_PROPERTY(QString formJsonInfo READ formJsonInfo WRITE setFormJsonInfo NOTIFY formJsonInfoChanged)
     Q_PROPERTY(QJsonArray nodeUserList READ nodeUserList WRITE setNodeUserList NOTIFY nodeUserListChanged)
     Q_PROPERTY(int workId READ workId WRITE setWorkId NOTIFY workIdChanged)
+    Q_PROPERTY(QString formName READ formName WRITE setFormName NOTIFY formNameChanged)
 private:
     explicit WorkNodeFormModel(QObject *parent=nullptr);
     explicit WorkNodeFormModel() = default;
@@ -53,6 +55,7 @@ public:
         Required,
         RequiredMessage,
         Type,
+        Value,          // 表单回显值
 
         GroupName,
         GridColumns,
@@ -121,10 +124,19 @@ public:
     void setWorkId(int id) {
         m_workId = id;
     }
+
+    QString formName() const {
+        return m_formName;
+    }
+    void setFormName(const QString& name) {
+        m_formName = name;
+        emit formNameChanged();
+    }
 signals:
     void formJsonInfoChanged();
     void nodeUserListChanged();
     void workIdChanged();
+    void formNameChanged();
 private:
     QList<FormControl> m_formList;
 
@@ -139,6 +151,8 @@ private:
     QJsonArray m_pointsList;
 
     int m_workId;
+
+    QString m_formName;
 };
 
 #endif // WORKNODEFORMMODEL_H

BIN
src/icon.png


+ 26 - 0
src/interactive/InteractiveCAN.cpp

@@ -1,7 +1,23 @@
 #include "InteractiveCAN.h"
 #include "InteractiveData.h"
+#include "ReturnKeyLockManager.h"
 #include "../usr/config.h"
 #include "../httpclient/WorkNodeFormModel.h"
+#include <QDebug>
+
+// ============ 临时禁用CAN通讯日志 (可撤回) ============
+#define CAN_LOG_DISABLED
+#ifdef CAN_LOG_DISABLED
+#undef qDebug
+#undef qInfo
+#undef qWarning
+#undef qCritical
+#define qDebug() QNoDebug()
+#define qInfo() QNoDebug()
+#define qWarning() QNoDebug()
+#define qCritical() QNoDebug()
+#endif
+// ====================================================
 
 InteractiveCAN* InteractiveCAN::pInstance = nullptr;
 
@@ -92,6 +108,16 @@ InteractiveCAN::InteractiveCAN()
     connect(this, &InteractiveCAN::signalCheckedKey, this, &InteractiveCAN::getJobTicketInfo);
 
     connect(this, &InteractiveCAN::signalUpdateColockStatus, this, &InteractiveCAN::slotUpdateColockStatus);
+    
+    // ========== 连接CAN信号到归还钥匙和锁管理器 ==========
+    // 必须使用 Qt::QueuedConnection 因为CAN回调在子线程中
+    connect(m_canClient, &CANClient::nfcDeviceDetected, 
+            ReturnKeyLockManager::instance(), &ReturnKeyLockManager::onNfcDetected,
+            Qt::QueuedConnection);
+    connect(m_canClient, &CANClient::nfcDeviceError, 
+            ReturnKeyLockManager::instance(), &ReturnKeyLockManager::onDeviceError,
+            Qt::QueuedConnection);
+    // ====================================================
 }
 
 void InteractiveCAN::createJobTicket()

+ 427 - 0
src/interactive/ReturnKeyLockManager.cpp

@@ -0,0 +1,427 @@
+#include "ReturnKeyLockManager.h"
+#include <QDebug>
+#include <QDateTime>
+#include <QRandomGenerator>
+
+ReturnKeyLockManager* ReturnKeyLockManager::m_instance = nullptr;
+
+ReturnKeyLockManager::ReturnKeyLockManager(QObject* parent)
+    : QObject(parent)
+    , m_isProcessing(false)
+    , m_isVisible(false)
+    , m_totalKeys(0)
+    , m_successKeys(0)
+    , m_failedKeys(0)
+    , m_totalLocks(0)
+    , m_successLocks(0)
+    , m_failedLocks(0)
+    , m_confirmCountdown(20)
+{
+    m_currentReadingStatus = "正在读取锁/钥匙状态......";
+    
+    // 处理队列定时器
+    m_processTimer = new QTimer(this);
+    m_processTimer->setInterval(500);  // 500ms处理一个
+    connect(m_processTimer, &QTimer::timeout, this, &ReturnKeyLockManager::processNextInQueue);
+    
+    // 确认倒计时定时器
+    m_confirmTimer = new QTimer(this);
+    m_confirmTimer->setInterval(1000);
+    connect(m_confirmTimer, &QTimer::timeout, this, &ReturnKeyLockManager::onConfirmCountdownTick);
+}
+
+ReturnKeyLockManager::~ReturnKeyLockManager()
+{
+    m_processTimer->stop();
+    m_confirmTimer->stop();
+}
+
+ReturnKeyLockManager* ReturnKeyLockManager::instance()
+{
+    if (!m_instance) {
+        m_instance = new ReturnKeyLockManager();
+    }
+    return m_instance;
+}
+
+ReturnKeyLockManager* ReturnKeyLockManager::create(QQmlEngine*, QJSEngine*)
+{
+    return instance();
+}
+
+void ReturnKeyLockManager::setIsVisible(bool visible)
+{
+    qDebug() << "[ReturnKeyLockManager] setIsVisible:" << visible << "当前:" << m_isVisible;
+    if (m_isVisible != visible) {
+        m_isVisible = visible;
+        qDebug() << "[ReturnKeyLockManager] isVisible 已更新为:" << m_isVisible << ", 发出 isVisibleChanged 信号";
+        emit isVisibleChanged();
+        
+        if (visible) {
+            startConfirmCountdown();
+        } else {
+            stopConfirmCountdown();
+        }
+    }
+}
+
+void ReturnKeyLockManager::onNfcDetected(const QString& nfcId, const QString& deviceType, int slotIndex)
+{
+    QMutexLocker locker(&m_mutex);
+    
+    qDebug() << "[ReturnKeyLockManager] NFC检测到:" << nfcId << "类型:" << deviceType << "插槽:" << slotIndex;
+    
+    // 去重检查
+    if (m_nfcIdSet.contains(nfcId)) {
+        qDebug() << "[ReturnKeyLockManager] NFC已存在,跳过:" << nfcId;
+        return;
+    }
+    
+    // 添加到集合
+    m_nfcIdSet.insert(nfcId);
+    
+    // 创建设备信息
+    ReturnDeviceInfo info;
+    info.nfcId = nfcId;
+    info.deviceType = deviceType;
+    info.slotIndex = slotIndex;
+    info.status = "pending";
+    info.statusMessage = "等待处理";
+    info.returnTime = getCurrentTimeString();
+    info.hasTask = false;
+    info.isDeviceError = false;
+    
+    // 加入待处理队列
+    m_pendingQueue.enqueue(info);
+    m_deviceMap[nfcId] = info;
+    
+    // 更新统计
+    if (deviceType == "key") {
+        m_totalKeys++;
+    } else {
+        m_totalLocks++;
+    }
+    emit statisticsChanged();
+    
+    // 显示页面(如果还没显示)
+    if (!m_isVisible) {
+        emit requestShowPage();
+    }
+    
+    // 启动处理
+    if (!m_processTimer->isActive()) {
+        m_isProcessing = true;
+        emit isProcessingChanged();
+        m_processTimer->start();
+    }
+    
+    emit dataUpdated();
+}
+
+void ReturnKeyLockManager::onDeviceError(const QString& deviceType, int slotIndex)
+{
+    QMutexLocker locker(&m_mutex);
+    
+    qDebug() << "[ReturnKeyLockManager] 设备异常:" << deviceType << "插槽:" << slotIndex;
+    
+    // 创建异常设备信息
+    ReturnDeviceInfo info;
+    info.nfcId = QString("ERROR_%1_%2").arg(deviceType).arg(slotIndex);
+    info.deviceType = deviceType;
+    info.slotIndex = slotIndex;
+    info.status = "error";
+    info.statusMessage = "设备读取异常";
+    info.returnTime = getCurrentTimeString();
+    info.hasTask = false;
+    info.isDeviceError = true;
+    
+    // 去重检查
+    if (m_nfcIdSet.contains(info.nfcId)) {
+        return;
+    }
+    m_nfcIdSet.insert(info.nfcId);
+    
+    // 添加到异常列表
+    m_errorDevices.append(info);
+    m_deviceMap[info.nfcId] = info;
+    
+    // 显示页面
+    if (!m_isVisible) {
+        emit requestShowPage();
+    }
+    
+    emit dataUpdated();
+}
+
+void ReturnKeyLockManager::onApiResponse(const QString& nfcId, bool success, const QVariantMap& data)
+{
+    QMutexLocker locker(&m_mutex);
+    
+    if (!m_deviceMap.contains(nfcId)) {
+        qWarning() << "[ReturnKeyLockManager] 未知NFC响应:" << nfcId;
+        return;
+    }
+    
+    ReturnDeviceInfo& info = m_deviceMap[nfcId];
+    
+    if (success) {
+        info.status = data.value("returnStatus", "success").toString();
+        info.statusMessage = data.value("statusMessage", "归还成功").toString();
+        info.taskId = data.value("taskId", "").toString();
+        info.taskName = data.value("taskName", "").toString();
+        info.taskOrderNo = data.value("orderNo", "").toString();
+        info.taskWorkshop = data.value("workshop", "").toString();
+        info.taskWorker = data.value("worker", "").toString();
+        info.hasTask = !info.taskId.isEmpty();
+        
+        // 更新统计
+        if (info.status == "success") {
+            if (info.deviceType == "key") {
+                m_successKeys++;
+            } else {
+                m_successLocks++;
+            }
+        } else {
+            if (info.deviceType == "key") {
+                m_failedKeys++;
+            } else {
+                m_failedLocks++;
+            }
+        }
+    } else {
+        info.status = "failed";
+        info.statusMessage = data.value("errorMessage", "归还失败").toString();
+        info.hasTask = false;
+        
+        if (info.deviceType == "key") {
+            m_failedKeys++;
+        } else {
+            m_failedLocks++;
+        }
+    }
+    
+    // 分类到对应组
+    if (info.isDeviceError) {
+        // 已在异常列表中
+    } else if (!info.hasTask) {
+        m_noTaskDevices.append(info);
+    } else {
+        if (!m_taskGroups.contains(info.taskId)) {
+            m_taskGroups[info.taskId] = QList<ReturnDeviceInfo>();
+        }
+        m_taskGroups[info.taskId].append(info);
+    }
+    
+    emit statisticsChanged();
+    emit dataUpdated();
+}
+
+void ReturnKeyLockManager::processNextInQueue()
+{
+    QMutexLocker locker(&m_mutex);
+    
+    if (m_pendingQueue.isEmpty()) {
+        m_processTimer->stop();
+        m_isProcessing = false;
+        m_currentReadingStatus = "读取完成";
+        emit isProcessingChanged();
+        emit currentReadingStatusChanged();
+        return;
+    }
+    
+    ReturnDeviceInfo info = m_pendingQueue.dequeue();
+    m_currentReadingStatus = QString("正在读取%1状态...").arg(info.deviceType == "key" ? "钥匙" : "锁仓");
+    emit currentReadingStatusChanged();
+    
+    // TODO: 调用API查询设备状态
+    // 这里需要发出信号,由HttpClient处理
+    // emit signalQueryDeviceStatus(timestamp, url, jsonData, token);
+    
+    // 临时:模拟API响应(实际使用时删除这段,改为真实API调用)
+    QVariantMap mockData;
+    mockData["returnStatus"] = "success";
+    mockData["statusMessage"] = "归还成功";
+    mockData["taskId"] = QString("task_%1").arg(QRandomGenerator::global()->bounded(3));
+    mockData["taskName"] = QString("作业任务%1").arg(QRandomGenerator::global()->bounded(3) + 1);
+    mockData["orderNo"] = QString("WO-2024-%1").arg(QRandomGenerator::global()->bounded(100), 3, 10, QChar('0'));
+    mockData["workshop"] = "车间A区";
+    mockData["worker"] = "张工";
+    
+    locker.unlock();
+    onApiResponse(info.nfcId, true, mockData);
+}
+
+void ReturnKeyLockManager::confirmReturn()
+{
+    qDebug() << "[ReturnKeyLockManager] 确认归还";
+    stopConfirmCountdown();
+    closePage();
+}
+
+void ReturnKeyLockManager::retrieveFailedDevices()
+{
+    qDebug() << "[ReturnKeyLockManager] 取出归还失败的设备";
+    
+    // TODO: 发出信号通知CAN解锁失败的设备
+    // 这里需要遍历失败的设备,调用CAN解锁
+    
+    // 重置倒计时
+    m_confirmCountdown = 20;
+    emit confirmCountdownChanged();
+}
+
+void ReturnKeyLockManager::closePage()
+{
+    QMutexLocker locker(&m_mutex);
+    
+    // 清空所有数据
+    m_nfcIdSet.clear();
+    m_pendingQueue.clear();
+    m_deviceMap.clear();
+    m_taskGroups.clear();
+    m_errorDevices.clear();
+    m_noTaskDevices.clear();
+    
+    m_totalKeys = 0;
+    m_successKeys = 0;
+    m_failedKeys = 0;
+    m_totalLocks = 0;
+    m_successLocks = 0;
+    m_failedLocks = 0;
+    
+    m_isProcessing = false;
+    m_currentReadingStatus = "正在读取锁/钥匙状态......";
+    
+    emit statisticsChanged();
+    emit isProcessingChanged();
+    emit currentReadingStatusChanged();
+    
+    setIsVisible(false);
+}
+
+QVariantList ReturnKeyLockManager::getTaskGroups()
+{
+    QMutexLocker locker(&m_mutex);
+    QVariantList result;
+    
+    for (auto it = m_taskGroups.begin(); it != m_taskGroups.end(); ++it) {
+        QVariantMap group;
+        group["taskId"] = it.key();
+        
+        if (!it.value().isEmpty()) {
+            const ReturnDeviceInfo& first = it.value().first();
+            group["taskName"] = first.taskName;
+            group["orderNo"] = first.taskOrderNo;
+            group["workshop"] = first.taskWorkshop;
+            group["worker"] = first.taskWorker;
+        }
+        
+        // 计算该任务的成功/失败数
+        int success = 0, failed = 0;
+        QVariantList devices;
+        for (const ReturnDeviceInfo& info : it.value()) {
+            QVariantMap device;
+            device["nfcId"] = info.nfcId;
+            device["deviceType"] = info.deviceType;
+            device["slotIndex"] = info.slotIndex;
+            device["status"] = info.status;
+            device["statusMessage"] = info.statusMessage;
+            device["returnTime"] = info.returnTime;
+            devices.append(device);
+            
+            if (info.status == "success") success++;
+            else failed++;
+        }
+        group["devices"] = devices;
+        group["hasFailure"] = (failed > 0);
+        group["statusText"] = (failed > 0) ? "有失败" : "已完成";
+        
+        result.append(group);
+    }
+    
+    return result;
+}
+
+QVariantList ReturnKeyLockManager::getErrorDevices()
+{
+    QMutexLocker locker(&m_mutex);
+    QVariantList result;
+    
+    for (const ReturnDeviceInfo& info : m_errorDevices) {
+        QVariantMap device;
+        device["nfcId"] = info.nfcId;
+        device["deviceType"] = info.deviceType;
+        device["slotIndex"] = info.slotIndex;
+        device["status"] = info.status;
+        device["statusMessage"] = info.statusMessage;
+        device["returnTime"] = info.returnTime;
+        result.append(device);
+    }
+    
+    return result;
+}
+
+QVariantList ReturnKeyLockManager::getNoTaskDevices()
+{
+    QMutexLocker locker(&m_mutex);
+    QVariantList result;
+    
+    for (const ReturnDeviceInfo& info : m_noTaskDevices) {
+        QVariantMap device;
+        device["nfcId"] = info.nfcId;
+        device["deviceType"] = info.deviceType;
+        device["slotIndex"] = info.slotIndex;
+        device["status"] = info.status;
+        device["statusMessage"] = info.statusMessage;
+        device["returnTime"] = info.returnTime;
+        result.append(device);
+    }
+    
+    return result;
+}
+
+void ReturnKeyLockManager::testTrigger(const QString& deviceType, int slotIndex)
+{
+    // 生成测试NFC卡号
+    QString testNfcId = QString("TEST%1%2%3")
+        .arg(deviceType.toUpper())
+        .arg(slotIndex)
+        .arg(QRandomGenerator::global()->bounded(10000), 4, 10, QChar('0'));
+    
+    qInfo() << "[ReturnKeyLockManager] 测试触发:" << deviceType << "插槽:" << slotIndex << "NFC:" << testNfcId;
+    
+    // 调用正常的NFC检测流程
+    onNfcDetected(testNfcId, deviceType, slotIndex);
+}
+
+void ReturnKeyLockManager::startConfirmCountdown()
+{
+    m_confirmCountdown = 20;
+    emit confirmCountdownChanged();
+    m_confirmTimer->start();
+}
+
+void ReturnKeyLockManager::stopConfirmCountdown()
+{
+    m_confirmTimer->stop();
+}
+
+void ReturnKeyLockManager::onConfirmCountdownTick()
+{
+    m_confirmCountdown--;
+    emit confirmCountdownChanged();
+    
+    if (m_confirmCountdown <= 0) {
+        confirmReturn();
+    }
+}
+
+void ReturnKeyLockManager::updateStatistics()
+{
+    emit statisticsChanged();
+}
+
+QString ReturnKeyLockManager::getCurrentTimeString()
+{
+    return QDateTime::currentDateTime().toString("hh:mm:ss");
+}

+ 201 - 0
src/interactive/ReturnKeyLockManager.h

@@ -0,0 +1,201 @@
+#ifndef RETURNKEYLOCKMANAGER_H
+#define RETURNKEYLOCKMANAGER_H
+
+#include <QObject>
+#include <QTimer>
+#include <QMutex>
+#include <QSet>
+#include <QQueue>
+#include <QVariantMap>
+#include <QVariantList>
+#include <QtQml/qqml.h>
+
+/**
+ * @brief 归还设备信息结构
+ */
+struct ReturnDeviceInfo {
+    QString nfcId;              // NFC卡号
+    QString deviceType;         // 设备类型: "key" 或 "lock"
+    int slotIndex;              // 插槽索引 (钥匙: 0=左, 1=右; 锁: 0-4)
+    QString status;             // 状态: "pending", "success", "failed", "error"
+    QString statusMessage;      // 状态描述
+    QString taskId;             // 所属作业任务ID
+    QString taskName;           // 作业任务名称
+    QString taskOrderNo;        // 工单号
+    QString taskWorkshop;       // 车间
+    QString taskWorker;         // 负责人
+    QString returnTime;         // 归还时间
+    bool hasTask;               // 是否有关联作业任务
+    bool isDeviceError;         // 是否设备异常(无法读取NFC)
+};
+
+/**
+ * @brief 归还钥匙和锁管理器
+ * 
+ * 功能:
+ * 1. 监听CAN通讯,接收NFC卡号
+ * 2. NFC卡号去重并存入队列
+ * 3. 逐个调用API获取设备状态
+ * 4. 按作业任务分类设备
+ * 5. 提供统计数据
+ */
+class ReturnKeyLockManager : public QObject
+{
+    Q_OBJECT
+    QML_SINGLETON
+    QML_NAMED_ELEMENT(ReturnKeyLockManager)
+
+    // QML属性
+    Q_PROPERTY(bool isProcessing READ isProcessing NOTIFY isProcessingChanged)
+    Q_PROPERTY(bool isVisible READ isVisible WRITE setIsVisible NOTIFY isVisibleChanged)
+    Q_PROPERTY(int totalTasks READ totalTasks NOTIFY statisticsChanged)
+    Q_PROPERTY(int totalKeys READ totalKeys NOTIFY statisticsChanged)
+    Q_PROPERTY(int successKeys READ successKeys NOTIFY statisticsChanged)
+    Q_PROPERTY(int failedKeys READ failedKeys NOTIFY statisticsChanged)
+    Q_PROPERTY(int totalLocks READ totalLocks NOTIFY statisticsChanged)
+    Q_PROPERTY(int successLocks READ successLocks NOTIFY statisticsChanged)
+    Q_PROPERTY(int failedLocks READ failedLocks NOTIFY statisticsChanged)
+    Q_PROPERTY(int confirmCountdown READ confirmCountdown NOTIFY confirmCountdownChanged)
+    Q_PROPERTY(QString currentReadingStatus READ currentReadingStatus NOTIFY currentReadingStatusChanged)
+
+public:
+    static ReturnKeyLockManager* instance();
+    static ReturnKeyLockManager* create(QQmlEngine*, QJSEngine*);
+    ~ReturnKeyLockManager();
+
+    // 属性访问器
+    bool isProcessing() const { return m_isProcessing; }
+    bool isVisible() const { return m_isVisible; }
+    void setIsVisible(bool visible);
+    
+    int totalTasks() const { return m_taskGroups.count(); }
+    int totalKeys() const { return m_totalKeys; }
+    int successKeys() const { return m_successKeys; }
+    int failedKeys() const { return m_failedKeys; }
+    int totalLocks() const { return m_totalLocks; }
+    int successLocks() const { return m_successLocks; }
+    int failedLocks() const { return m_failedLocks; }
+    int confirmCountdown() const { return m_confirmCountdown; }
+    QString currentReadingStatus() const { return m_currentReadingStatus; }
+
+public slots:
+    /**
+     * @brief 接收CAN发送的NFC卡号(由CANClient调用)
+     * @param nfcId NFC卡号
+     * @param deviceType 设备类型 "key" 或 "lock"
+     * @param slotIndex 插槽索引
+     */
+    void onNfcDetected(const QString& nfcId, const QString& deviceType, int slotIndex);
+
+    /**
+     * @brief 接收设备异常信号(无法读取NFC)
+     * @param deviceType 设备类型
+     * @param slotIndex 插槽索引
+     */
+    void onDeviceError(const QString& deviceType, int slotIndex);
+
+    /**
+     * @brief API响应处理(由HttpClient调用)
+     * @param nfcId NFC卡号
+     * @param success 是否成功
+     * @param data 返回数据
+     */
+    void onApiResponse(const QString& nfcId, bool success, const QVariantMap& data);
+
+    /**
+     * @brief 确认归还
+     */
+    Q_INVOKABLE void confirmReturn();
+
+    /**
+     * @brief 取出归还失败的设备
+     */
+    Q_INVOKABLE void retrieveFailedDevices();
+
+    /**
+     * @brief 清空所有数据并关闭页面
+     */
+    Q_INVOKABLE void closePage();
+
+    /**
+     * @brief 获取按作业任务分组的数据(供QML使用)
+     */
+    Q_INVOKABLE QVariantList getTaskGroups();
+
+    /**
+     * @brief 获取设备异常列表(供QML使用)
+     */
+    Q_INVOKABLE QVariantList getErrorDevices();
+
+    /**
+     * @brief 获取无作业任务列表(供QML使用)
+     */
+    Q_INVOKABLE QVariantList getNoTaskDevices();
+
+    /**
+     * @brief 测试触发(用于开发调试)
+     * @param deviceType 设备类型 "key" 或 "lock"
+     * @param slotIndex 插槽索引
+     */
+    Q_INVOKABLE void testTrigger(const QString& deviceType = "key", int slotIndex = 0);
+
+signals:
+    void isProcessingChanged();
+    void isVisibleChanged();
+    void statisticsChanged();
+    void confirmCountdownChanged();
+    void currentReadingStatusChanged();
+    
+    // 请求显示页面(CAN检测到设备插入时发出)
+    void requestShowPage();
+    
+    // 数据更新信号(通知QML刷新界面)
+    void dataUpdated();
+    
+    // API请求信号(连接到HttpClient)
+    void signalQueryDeviceStatus(quint64 id, const QString& url, const QByteArray& data, const QString& token);
+
+private slots:
+    void processNextInQueue();
+    void onConfirmCountdownTick();
+
+private:
+    explicit ReturnKeyLockManager(QObject* parent = nullptr);
+    
+    void updateStatistics();
+    void startConfirmCountdown();
+    void stopConfirmCountdown();
+    QString getCurrentTimeString();
+
+private:
+    static ReturnKeyLockManager* m_instance;
+    
+    QMutex m_mutex;
+    QSet<QString> m_nfcIdSet;                   // NFC去重集合
+    QQueue<ReturnDeviceInfo> m_pendingQueue;    // 待处理队列
+    QMap<QString, ReturnDeviceInfo> m_deviceMap;// 所有设备信息 (key=nfcId)
+    
+    // 按作业任务分组 (key=taskId, value=设备列表)
+    QMap<QString, QList<ReturnDeviceInfo>> m_taskGroups;
+    QList<ReturnDeviceInfo> m_errorDevices;     // 设备异常列表
+    QList<ReturnDeviceInfo> m_noTaskDevices;    // 无作业任务列表
+    
+    bool m_isProcessing;
+    bool m_isVisible;
+    QString m_currentReadingStatus;
+    
+    // 统计数据
+    int m_totalKeys;
+    int m_successKeys;
+    int m_failedKeys;
+    int m_totalLocks;
+    int m_successLocks;
+    int m_failedLocks;
+    
+    // 确认倒计时
+    int m_confirmCountdown;
+    QTimer* m_confirmTimer;
+    QTimer* m_processTimer;
+};
+
+#endif // RETURNKEYLOCKMANAGER_H

+ 18 - 9
src/main.cpp

@@ -1,12 +1,17 @@
 #include <QGuiApplication>
-#include <QQmlApplicationEngine>
 #include <QInputMethod>
+#include <QQmlApplicationEngine>
+#include <QDateTime>
 
-#include "./usr/LotoQmlPlugin.h"
 #include "./interactive/InteractiveCAN.h"
+#include "./usr/LotoQmlPlugin.h"
+
 
-int main(int argc, char** argv)
+int main(int argc, char **argv)
 {
+    // 设置日志格式:时间 [类型] 消息
+    qSetMessagePattern("[%{time yyyy-MM-dd hh:mm:ss.zzz}] [%{type}] %{message}");
+    
     QGuiApplication app(argc, argv);
 
     qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
@@ -15,15 +20,19 @@ int main(int argc, char** argv)
 
     QQmlApplicationEngine engine;
 
-    // 注册C++类/对象到QML
+    // 注册C++类/对象到QML123
     LotoQmlTypes::registerTypes();
 
     const QUrl url = QUrl(QStringLiteral("qrc:/qml/main.qml"));
-    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
-                     &app, [url](QObject* obj, const QUrl& objUrl) {
-                         if (!obj && url == objUrl)
-                             QCoreApplication::exit(-1);
-                     }, Qt::QueuedConnection);
+    QObject::connect(
+        &engine,
+        &QQmlApplicationEngine::objectCreated,
+        &app,
+        [url](QObject *obj, const QUrl &objUrl) {
+            if (!obj && url == objUrl)
+                QCoreApplication::exit(-1);
+        },
+        Qt::QueuedConnection);
 
     engine.load(url);
 

+ 2 - 0
src/qml.qrc

@@ -7,6 +7,7 @@
         <file>qml/SettingPage.qml</file>
         <file>qml/WorkingPage.qml</file>
         <file>qml/components/AlertDialog.qml</file>
+        <file>qml/components/LoadingDialog.qml</file>
         <file>qml/components/AvatarCard.qml</file>
         <file>qml/components/FormCard.qml</file>
         <file>qml/components/IconText.qml</file>
@@ -34,6 +35,7 @@
         <file>qml/components/UserInfoCard.qml</file>
         <file>qml/main.qml</file>
         <file>qml/components/MVirtualKeyboard.qml</file>
+        <file>qml/components/CustomVirtualKeyboard.qml</file>
         <file>qml/components/JobTicketColockProcess.qml</file>
         <file>qml/components/ReturnKeyLockProcess.qml</file>
     </qresource>

+ 68 - 70
src/qml/JobTicketPage.qml

@@ -12,6 +12,9 @@ Rectangle {
     property string pageTitleText: "我的作业"
 
     property int jobCurrentIndex: 1
+    
+    // 是否自动跳转到详情页(登录后第一次进入时为 true,从详情页返回时为 false)
+    property bool autoNavigateToDetail: true
 
     color: "transparent"
 
@@ -82,7 +85,14 @@ Rectangle {
 
                 iconCharacter: "\uf129"
                 iconSize: 16
-                text: qsTr("您当前有" + JobTicketModel.rowCount().toString() + "个作业任务,请点击任一任务进行操作")
+                text: {
+                    var runningCount = JobTicketModel.runningCount();
+                    if (runningCount === 0) {
+                        return qsTr("您当前没有进行中的作业任务");
+                    } else {
+                        return qsTr("您当前有" + runningCount.toString() + "个作业任务,请选择作业任务进行操作");
+                    }
+                }
                 textColor: "white"
             }
         }
@@ -141,9 +151,13 @@ Rectangle {
                             jobNodeId: nodeId
 
                             Component.onCompleted: {
-                                if (JobTicketModel.rowCount() === 1) {
+                                // 如果只有一个"进行中"的作业,且当前卡片就是那个进行中的作业,自动跳转详情
+                                // 但如果 autoNavigateToDetail 为 false(从详情页返回),则不自动跳转
+                                var runningCount = JobTicketModel.runningCount();
+                                if (control.autoNavigateToDetail && runningCount === 1 && approvalStatus === "进行中") {
                                     ticketCard.signalShowFormCard();
                                 }
+                                // 如果没有进行中的作业,停留在列表页,不自动跳转
                             }
                         }
                     }
@@ -176,51 +190,39 @@ Rectangle {
 
             Row {
                 anchors.centerIn: parent
-                spacing: 10
+                spacing: 20
 
                 Rectangle {
                     id: previousPage
-                    width: 78
-                    height: 34
+                    width: 90
+                    height: 36
                     border.color: "#40A9FF"
                     color: "#1A40A9FF"
-                    radius: 5
+                    radius: 6
 
                     opacity: jobCurrentIndex > 1 ? 1 : 0.3
 
-                    Text {
-                        id: previousPageIconLabel
-                        anchors.left: parent.left
-                        anchors.leftMargin: 10
-                        anchors.verticalCenter: parent.verticalCenter
-                        text: "\uf104"
-                        color: "white"
-                        font.pixelSize: 24
-                        font.family: iconFont.name
-                        verticalAlignment: Text.AlignVCenter
-                        horizontalAlignment: Text.AlignHCenter
-                    }
-
-                    Item {
-                        id: previousPageLabelWrap
-                        anchors.left: previousPageIconLabel.right
-                        anchors.leftMargin: 5
-                        anchors.verticalCenter: parent.verticalCenter
-                        width: previousPageIconLabel.implicitWidth
-                        height: previousPageIconLabel.implicitHeight
-                        Layout.preferredWidth: width
-                        Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
+                    Row {
+                        anchors.centerIn: parent
+                        spacing: 6
+                        
+                        Text {
+                            id: previousPageIconLabel
+                            anchors.verticalCenter: parent.verticalCenter
+                            text: "\uf104"
+                            color: "white"
+                            font.pixelSize: 16
+                            font.family: iconFont.name
+                            verticalAlignment: Text.AlignVCenter
+                        }
 
                         Text {
                             id: previousPageLabel
                             anchors.verticalCenter: parent.verticalCenter
                             text: qsTr("上一页")
-                            opacity: 1
                             color: "white"
                             font.pixelSize: 14
-                            font.bold: true
                             verticalAlignment: Text.AlignVCenter
-                            Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
                         }
                     }
 
@@ -234,61 +236,57 @@ Rectangle {
                         }
                     }
                 }
-                Text {
-                    id: pageCount
-                    y: 10
-
-                    font.pixelSize: 14
-                    font.family: iconFont.name
-                    verticalAlignment: Text.AlignVCenter
-                    horizontalAlignment: Text.AlignHCenter
-
-                    text: "第" + jobCurrentIndex.toString() + "页,共" + (Math.ceil(JobTicketModel.rowCount()/4)).toString() + "页"
-                    color: "white"
+                
+                // 页码显示
+                Rectangle {
+                    id: pageCountBg
+                    width: pageCount.implicitWidth + 24
+                    height: 36
+                    color: "transparent"
+                    
+                    Text {
+                        id: pageCount
+                        anchors.centerIn: parent
+                        font.pixelSize: 14
+                        verticalAlignment: Text.AlignVCenter
+                        horizontalAlignment: Text.AlignHCenter
+                        text: "第" + jobCurrentIndex.toString() + "页,共" + (Math.ceil(JobTicketModel.rowCount()/4)).toString() + "页"
+                        color: "white"
+                    }
                 }
+                
                 Rectangle {
                     id: nextPage
-                    width: 78
-                    height: 34
+                    width: 90
+                    height: 36
                     color: "#1A40A9FF"
                     border.color: "#40A9FF"
-                    radius: 5
+                    radius: 6
 
                     opacity: jobCurrentIndex < Math.ceil(JobTicketModel.rowCount()/4) ? 1 : 0.3
 
-                    Item {
-                        id: nextPageLabelWrap
-                        anchors.left: parent.left
-                        anchors.leftMargin: 10
-                        anchors.verticalCenter: parent.verticalCenter
-                        width: previousPageIconLabel.implicitWidth
-                        height: previousPageIconLabel.implicitHeight
-                        Layout.preferredWidth: width
-                        Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
-
+                    Row {
+                        anchors.centerIn: parent
+                        spacing: 6
+                        
                         Text {
                             id: nextPageLabel
                             anchors.verticalCenter: parent.verticalCenter
                             text: qsTr("下一页")
-                            opacity: 1
                             color: "white"
                             font.pixelSize: 14
-                            font.bold: true
                             verticalAlignment: Text.AlignVCenter
-                            Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
                         }
-                    }
-                    Text {
-                        id: nextPageIconLabel
-                        anchors.right: parent.right
-                        anchors.rightMargin: 10
-                        anchors.verticalCenter: parent.verticalCenter
-                        text: "\uf105"
-                        color: "white"
-                        font.pixelSize: 24
-                        font.family: iconFont.name
-                        verticalAlignment: Text.AlignVCenter
-                        horizontalAlignment: Text.AlignHCenter
+                        
+                        Text {
+                            id: nextPageIconLabel
+                            anchors.verticalCenter: parent.verticalCenter
+                            text: "\uf105"
+                            color: "white"
+                            font.pixelSize: 16
+                            font.family: iconFont.name
+                            verticalAlignment: Text.AlignVCenter
+                        }
                     }
 
                     MouseArea {

+ 174 - 58
src/qml/Login.qml

@@ -30,8 +30,59 @@ Rectangle {
 
     property int faceAreaSize: 500
 
+    // 刷卡登录状态
+    property bool cardLoginLoading: false           // 刷卡登录loading状态
+    property bool showCardLoginError: false         // 是否显示刷卡登录错误提示
+    property string cardLoginErrorText: ""          // 刷卡登录错误提示内容
+    property var cardLoginErrorDialog: null         // 错误提示对话框对象
+
     Component.onCompleted: {
         isCardInput = true;
+        // 确保页面加载时获取焦点,以便接收键盘事件
+        control.forceActiveFocus();
+        console.log("[Login.qml] 页面加载完成,已获取焦点");
+    }
+
+    // 关闭刷卡登录错误提示对话框
+    function closeCardLoginErrorDialog() {
+        console.log("[Login.qml] 关闭错误提示对话框");
+        if (cardLoginErrorDialog !== null) {
+            cardLoginErrorDialog.visible = false;
+            cardLoginErrorDialog.destroy();
+            cardLoginErrorDialog = null;
+        }
+        showCardLoginError = false;
+        cardLoginErrorAutoCloseTimer.stop();
+    }
+
+    // 显示刷卡登录错误提示对话框
+    function showCardLoginErrorDialog(errorMsg) {
+        console.log("[Login.qml] 显示错误提示对话框,内容:", errorMsg);
+        // 先关闭之前的对话框
+        closeCardLoginErrorDialog();
+
+        cardLoginErrorText = errorMsg;
+        showCardLoginError = true;
+
+        var component = Qt.createComponent("components/AlertDialog.qml");
+        if (component.status === Component.Ready) {
+            cardLoginErrorDialog = component.createObject(appAlertDialog, {
+                messageTypeName: "登录失败",
+                messageValue: errorMsg,
+                messageIconCharacter: "\uf071"
+            });
+            cardLoginErrorDialog.parent = appAlertDialog;
+            // 点击确认按钮关闭对话框
+            cardLoginErrorDialog.confirm.connect(function () {
+                console.log("[Login.qml] 用户点击确认按钮");
+                closeCardLoginErrorDialog();
+            });
+            // 启动3秒自动关闭定时器
+            cardLoginErrorAutoCloseTimer.restart();
+            console.log("[Login.qml] 启动3秒自动关闭定时器");
+        } else {
+            console.log("[Login.qml] 加载AlertDialog组件失败:", component.errorString());
+        }
     }
 
     HttpPasswordLogin {
@@ -78,16 +129,25 @@ Rectangle {
     Connections {
         target: httpCardLogin
         function onSignalLoginReturnStat(stat, data) {
+            console.log("[Login.qml] 收到登录响应信号,stat:", stat, ",data:", data);
+            // 隐藏loading
+            cardLoginLoading = false;
+            console.log("[Login.qml] 隐藏loading");
+            
             if(stat === 0) {
+                console.log("[Login.qml] 登录成功!");
                 loginSuccess = true
             } else {
-                showErrorLogin = true;
-                errorNoticeTimeout = 3;
-                errorNoticText = data;
+                console.log("[Login.qml] 登录失败,显示错误提示");
+                // 使用AlertDialog显示错误提示
+                showCardLoginErrorDialog(data);
             }
         }
     }
 
+    // 作业票请求状态标志
+    property bool isRequestingJobTickets: false
+
     // 作业票
     HttpGetJobTickets {
         id: httpGetJobTickets
@@ -106,6 +166,14 @@ Rectangle {
     Connections {
         target: httpGetJobTickets
         function onSignalJobTicketsReturnStat(stat, msg) {
+            // 只有在本页面发起请求时才处理
+            if (!isRequestingJobTickets) {
+                console.log("[Login.qml] 忽略非本页面发起的作业票响应");
+                return;
+            }
+            isRequestingJobTickets = false;
+            console.log("[Login.qml] 作业票返回: stat=" + stat + ", msg=" + msg);
+            
             if (stat !== 0 || JobTicketModel.rowCount() === 0) {
                 appStackView.push("components/NoJobTicketDialog.qml");
             } else {
@@ -140,17 +208,26 @@ Rectangle {
         case Qt.Key_F: httpCardLogin.cardID = httpCardLogin.cardID + "F";break;
         case Qt.Key_Return: {
             if (httpCardLogin.cardID.length > 0) {
-                console.log("card:" + httpCardLogin.cardID);
+                console.log("[Login.qml] 刷卡回车,卡号:", httpCardLogin.cardID);
+                // 关闭之前可能存在的错误提示对话框
+                closeCardLoginErrorDialog();
+                // 显示loading
+                console.log("[Login.qml] 显示loading");
+                cardLoginLoading = true;
+                console.log("[Login.qml] 启动登录线程");
                 httpCardLogin.start();
+            } else {
+                console.log("[Login.qml] 卡号为空,忽略回车");
             }
             return
         }
         }
     }
 
-    Keys.onReleased: function(event) {
+    Keys.onPressed: function(event) {
         if (isCardInput) {
             setcardIDByKey(event.key);
+            event.accepted = true;  // 防止事件继续传递
         }
     }
 
@@ -212,6 +289,9 @@ Rectangle {
         id: passwordInput
         anchors.fill: parent
 
+        // 当前聚焦的输入框
+        property Item currentFocusedInput: null
+
         MBlurCard {
             id: __blurCard
             anchors.fill: parent
@@ -225,13 +305,18 @@ Rectangle {
 
             Rectangle {
                 id: passwordInputArea
-                anchors.centerIn: parent
+                anchors.horizontalCenter: parent.horizontalCenter
+                y: virtualKeyboard.keyboardVisible ? 30 : (parent.height - height) / 2
                 width: 497
                 height: 438
 
                 color: "#980e57ea"
                 radius: 20
 
+                Behavior on y {
+                    NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
+                }
+
                 Column {
                     id: __passwordColumn
                     x: 0
@@ -247,19 +332,23 @@ Rectangle {
                         width: 353
                         height: 68
                         color: "#00ffffff"
-                        border.color: "#a3ffffff"
-                        border.width: 3
+                        border.color: usernameText.activeFocus ? "#1890FF" : "#a3ffffff"
+                        border.width: usernameText.activeFocus ? 4 : 3
                         radius: 10
 
+                        Behavior on border.color {
+                            ColorAnimation { duration: 150 }
+                        }
+
                         TextField {
                             id: usernameText
                             anchors.fill: parent
                             anchors.margins: 2
                             font.pointSize: 15
                             focus: false
-                            color: "#a3ffffff"
+                            color: "#ffffff"
                             background: Rectangle {
-                                color: "transparent" // 使TextField背景透明
+                                color: "transparent"
                             }
                             placeholderText: qsTr("请输入用户名")
                             font.pixelSize: 30
@@ -274,18 +363,16 @@ Rectangle {
                             leftPadding: 20
                             renderType: Text.QtRendering
                             font.styleName: "Regular"
-
-                            onActiveFocusChanged: {
-//                                if (activeFocus) {
-//                                    globalKeyboard.inputContainer = passwordInputArea;
-//                                    globalKeyboard.inputTopLeftGlobal = usernameText.mapToGlobal(Qt.point(0, 0));
-//                                    globalKeyboard.show(usernameText);
-//                                } else {
-//                                    globalKeyboard.inputTopLeftGlobal = Qt.point(0, 0);
-//                                    if (!passwordText.activeFocus && !usernameText.activeFocus) {
-//                                        globalKeyboard.hide();
-//                                    }
-//                                }
+                            // 禁用系统输入法
+                            inputMethodHints: Qt.ImhNoPredictiveText
+
+                            MouseArea {
+                                anchors.fill: parent
+                                onClicked: {
+                                    usernameText.forceActiveFocus();
+                                    passwordInput.currentFocusedInput = usernameText;
+                                    virtualKeyboard.show(usernameText);
+                                }
                             }
                         }
                     }
@@ -295,22 +382,26 @@ Rectangle {
                         width: 353
                         height: 68
                         color: "transparent"
-                        border.color: "#a3ffffff"
-                        border.width: 3
+                        border.color: passwordText.activeFocus ? "#1890FF" : "#a3ffffff"
+                        border.width: passwordText.activeFocus ? 4 : 3
                         radius: 10
 
+                        Behavior on border.color {
+                            ColorAnimation { duration: 150 }
+                        }
+
                         TextField {
                             id: passwordText
                             anchors.fill: parent
                             anchors.margins: 2
                             font.pointSize: 15
                             focus: false
-                            color: "#a3ffffff"
+                            color: "#ffffff"
                             background: Rectangle {
-                                color: "transparent" // 使TextField背景透明
+                                color: "transparent"
                             }
                             placeholderText: qsTr("请输入密码")
-                            echoMode: TextInput.Password // 设置echoMode为Password
+                            echoMode: TextInput.Password
                             font.pixelSize: 30
                             selectionColor: "#a300aaff"
                             horizontalAlignment: Text.AlignLeft
@@ -323,18 +414,16 @@ Rectangle {
                             leftPadding: 20
                             renderType: Text.QtRendering
                             font.styleName: "Regular"
-
-                            onActiveFocusChanged: {
-//                                if (activeFocus) {
-//                                    globalKeyboard.inputContainer = passwordInputArea;
-//                                    globalKeyboard.inputTopLeftGlobal = passwordText.mapToGlobal(Qt.point(0, 0));
-//                                    globalKeyboard.show(passwordText);
-//                                } else {
-//                                    globalKeyboard.inputTopLeftGlobal = Qt.point(0, 0);
-//                                    if (!passwordText.activeFocus && !usernameText.activeFocus) {
-//                                        globalKeyboard.hide();
-//                                    }
-//                                }
+                            // 禁用系统输入法
+                            inputMethodHints: Qt.ImhNoPredictiveText
+
+                            MouseArea {
+                                anchors.fill: parent
+                                onClicked: {
+                                    passwordText.forceActiveFocus();
+                                    passwordInput.currentFocusedInput = passwordText;
+                                    virtualKeyboard.show(passwordText);
+                                }
                             }
                         }
                     }
@@ -347,16 +436,15 @@ Rectangle {
                         text: qsTr("登录")
 
                         btnRadius: 10
-                        buttonColor: "#a300aaff"
-                        textColor: "#a3ffffff"
+                        buttonColor: "#1890FF"
+                        textColor: "#ffffff"
                         contentScale: 0.5
 
                         onClicked:  {
-                            // TODO: 验证登录是否成功
+                            virtualKeyboard.hide();
                             httpPasswordLogin.username = usernameText.text;
                             httpPasswordLogin.password = passwordText.text;
                             httpPasswordLogin.start();
-                            // loginSuccess = true;
                         }
                     }
 
@@ -373,20 +461,24 @@ Rectangle {
                         contentScale: 0.5
 
                         onClicked: {
+                            virtualKeyboard.hide();
                             loginInput.visible = false;
                             loginInput.sourceComponent = null;
-                            control.isCardInput = true;     // 不使用用户名密码输入后,开启工卡登录对象监听键盘事件
+                            control.isCardInput = true;
+                            control.forceActiveFocus();
                         }
                     }
-
-//                    MVirtualKeyboard {
-//                        id: globalKeyboard
-//                        keyboardHeight: 220
-//                        parentWindow: passwordInputArea
-//                    }
                 }
             }
 
+            // 自定义虚拟键盘
+            CustomVirtualKeyboard {
+                id: virtualKeyboard
+                anchors.bottom: parent.bottom
+                anchors.horizontalCenter: parent.horizontalCenter
+                width: parent.width * 0.9
+            }
+
             Rectangle {
                 id: errorLogin
                 anchors.centerIn: parent
@@ -447,12 +539,12 @@ Rectangle {
         MouseArea {
             id: __iInputArea
             anchors.fill: parent
-            // 防鼠标事件穿透
-            onClicked: function(mouse) { mouse.accepted=true; }
-            onPressed: function(mouse) { mouse.accepted=true; }
-            onReleased: function(mouse) { mouse.accepted=true; }
-            onWheel: function(mouse) { mouse.accepted=true; }
-            onDoubleClicked: function(mouse) { mouse.accepted=true; }
+            z: -1  // 放到最底层,不阻挡其他交互
+            // 点击空白区域关闭键盘
+            onClicked: function(mouse) {
+                virtualKeyboard.hide();
+                mouse.accepted = true;
+            }
         }
     }
 
@@ -465,7 +557,7 @@ Rectangle {
         text: appTitle
         color: loginTextColor
         font.pixelSize: 56
-        font.family: iconFont.name
+        // 普通文字不使用图标字体
         verticalAlignment: Text.AlignVCenter
         horizontalAlignment: Text.AlignHCenter
     }
@@ -555,7 +647,7 @@ Rectangle {
             text: qsTr("您可以通过刷卡直接进行登录")
             color: loginTextColor
             font.pixelSize: 20
-            font.family: iconFont.name
+            // 普通文字不使用图标字体
             font.bold: true
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHCenter
@@ -591,13 +683,37 @@ Rectangle {
     // 登录成功
     onLoginSuccessChanged: {
         if (loginSuccess) {
+            // 关闭可能存在的loading和错误提示
+            cardLoginLoading = false;
+            closeCardLoginErrorDialog();
+            
             loginInput.visible = false;
             appShowLogout = true
             loginInput.sourceComponent = null;
             // TODO: 登录成功跳入作业页面
             // appStackView.push("WorkingPage.qml")
+            isRequestingJobTickets = true;
             httpGetJobTickets.start();
             //appStackView.push("components/NoJobTicketDialog.qml")
         }
     }
+
+    // 刷卡登录错误提示自动关闭定时器(3秒)
+    Timer {
+        id: cardLoginErrorAutoCloseTimer
+        interval: 3000
+        running: false
+        repeat: false
+        onTriggered: {
+            console.log("[Login.qml] 3秒超时,自动关闭错误提示");
+            closeCardLoginErrorDialog();
+        }
+    }
+
+    // 刷卡登录Loading组件
+    LoadingDialog {
+        id: cardLoginLoadingDialog
+        loadingText: "登录中,请稍后..."
+        showLoading: cardLoginLoading
+    }
 }

+ 41 - 1
src/qml/SettingPage.qml

@@ -69,6 +69,46 @@ Rectangle {
     function opsitiveAlertDialogCallback(componentObj) {
         negativeAlertDialogCallback(componentObj);
     }
+    
+    // 显示操作成功确认对话框(继续操作/退出系统)
+    function showSuccessConfirmDialog(msgText) {
+        var component = Qt.createComponent("components/AlertDialog.qml");
+        if (component.status === Component.Ready) {
+            var alertDialogComp = component.createObject(appAlertDialog, {
+                messageTypeName: msgText,
+                messageValue: "",
+                messageIconCharacter: "\uf058",
+                showCancelBtn: true,
+                confirmBtnText: "继续操作",
+                cancelBtnText: "退出系统"
+            });
+            alertDialogComp.parent = appAlertDialog;
+            // 继续操作 - 返回作业列表页
+            alertDialogComp.confirm.connect(function () { successContinueCallback(alertDialogComp); });
+            // 退出系统 - 返回首页
+            alertDialogComp.cancel.connect(function () { successExitCallback(alertDialogComp); });
+        } else {
+            console.log("Error loading component:", component.errorString());
+        }
+    }
+    
+    // 成功后继续操作 - 返回作业列表页
+    function successContinueCallback(componentObj) {
+        componentObj.visible = false;
+        appAlertDialog.sourceComponent = null;
+        componentObj.destroy();
+        // 返回作业列表页
+        appStackView.pop();
+    }
+    
+    // 成功后退出系统 - 返回首页
+    function successExitCallback(componentObj) {
+        componentObj.visible = false;
+        appAlertDialog.sourceComponent = null;
+        componentObj.destroy();
+        // 返回首页
+        appShowHome = true;
+    }
 
     HttpUpdateUserPassword {
         id: httpUpdateUserPassword
@@ -90,7 +130,7 @@ Rectangle {
             if (stat !== 0) {
                 showAlertDialog("提示", msg, "\uf071", negativeAlertDialogCallback);
             } else {
-                showAlertDialog("成功", msg, "\uf058", opsitiveAlertDialogCallback);
+                showSuccessConfirmDialog("修改成功,继续操作?");
             }
         }
     }

+ 312 - 78
src/qml/WorkingPage.qml

@@ -63,10 +63,56 @@ Rectangle {
             if (stat !== 0) {
                 showAlertDialog("提示", msg, "\uf071", negativeAlertDialogCallback);
             } else {
-                showAlertDialog("成功", msg, "\uf058", opsitiveAlertDialogCallback);
+                // 提交成功统一提示
+                showSuccessConfirmDialog("提交成功,继续操作?");
             }
         }
     }
+    
+    // 用于刷新列表数据
+    HttpGetJobTickets {
+        id: httpRefreshJobTickets
+    }
+    
+    // 刷新数据状态
+    property bool isRefreshingData: false
+    
+    Connections {
+        target: httpRefreshJobTickets
+        function onSignalJobTicketsReturnStat(stat, msg) {
+            console.log("[WorkingPage.qml] 刷新数据返回: stat=" + stat);
+            if (isRefreshingData) {
+                isRefreshingData = false;
+                // 断开信号
+                try {
+                    httpRefreshJobTickets.signalGetRequestData.disconnect(httpClientThread.slotGetRequestData);
+                } catch(e) {}
+                try {
+                    httpClientThread.signalResponseGetJobTickets.disconnect(httpRefreshJobTickets.slotHttpResponseGetJobTickets);
+                } catch(e) {}
+                // 跳转到列表页
+                appStackView.replace("JobTicketPage.qml", { autoNavigateToDetail: false });
+            }
+        }
+    }
+    
+    // 刷新数据并跳转到列表页
+    function refreshDataAndGoToList() {
+        console.log("[WorkingPage.qml] 开始刷新数据");
+        isRefreshingData = true;
+        // 连接信号
+        try {
+            httpRefreshJobTickets.signalGetRequestData.disconnect(httpClientThread.slotGetRequestData);
+        } catch(e) {}
+        try {
+            httpClientThread.signalResponseGetJobTickets.disconnect(httpRefreshJobTickets.slotHttpResponseGetJobTickets);
+        } catch(e) {}
+        httpRefreshJobTickets.signalGetRequestData.connect(httpClientThread.slotGetRequestData);
+        httpClientThread.signalResponseGetJobTickets.connect(httpRefreshJobTickets.slotHttpResponseGetJobTickets);
+        // 发起请求
+        httpRefreshJobTickets.start();
+    }
+    
 
     Rectangle {
         id: __container
@@ -84,91 +130,154 @@ Rectangle {
         Rectangle {
             id: __workingInfoArea
             x: 46
-            y: 36
-            width: parent.width-x*2
-            height: 87
+            y: 30
+            width: parent.width - x * 2
+            height: 80
 
             color: "transparent"
 
+            // 底部分隔线
             Canvas {
                 id: __workingInfoCanvas
                 anchors.fill: parent
                 onPaint: {
                     var ctx = getContext("2d");
                     ctx.beginPath();
-                    ctx.lineWidth = 1;                              // 边框宽度
-                    ctx.moveTo(0, parent.height);                   // 移动到起始点
-                    ctx.lineTo(parent.width, parent.height);        // 画线到另一个点
-                    ctx.strokeStyle = "#40A9FF";                    // 边框颜色
+                    ctx.lineWidth = 1;
+                    ctx.moveTo(0, parent.height);
+                    ctx.lineTo(parent.width, parent.height);
+                    ctx.strokeStyle = "#40A9FF";
                     ctx.stroke();
                 }
             }
 
-            RowLayout {
-                id: workingInfoRow
-                x: -titleIconCharacterSize/2
-                anchors.verticalCenter: parent.verticalCenter
-
-                spacing: 5
-
-                IconText {
-                    id: workingTitleBtn
-                    visible: titleText !== ""
-
-                    // iconCharacter: titleIconCharacter
-                    iconSize: titleIconCharacterSize
+            // 标题行
+            Row {
+                id: titleRow
+                anchors.left: parent.left
+                anchors.top: parent.top
+                anchors.topMargin: 10
+                spacing: 20
+
+                // 作业名称(大标题)
+                Text {
+                    id: workingTitleText
                     text: titleText
-                    textColor: "white"
+                    color: "white"
+                    font.pixelSize: 28
+                    // 标题不使用图标字体
+                    font.bold: true
+                    verticalAlignment: Text.AlignVCenter
                 }
 
-                IconText {
-                    id: workingSopBtn
-                    visible: sopInfoText !== ""
-
-                    iconCharacter: sopIconCharacter
-                    iconSize: 14
-                    text: sopInfoText
-                    textColor: "white"
+                // 当前任务(副标题)
+                Row {
+                    anchors.verticalCenter: parent.verticalCenter
+                    spacing: 6
+                    visible: WorkNodeFormModel.formName !== ""
+                    
+                    Text {
+                        text: "当前任务:"
+                        color: "#888888"
+                        font.pixelSize: 16
+                        // 普通文字不使用图标字体
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
+                    Text {
+                        text: WorkNodeFormModel.formName
+                        color: "#40A9FF"
+                        font.pixelSize: 16
+                        // 普通文字不使用图标字体
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
                 }
 
-                // IconText {
-                //     id: workingPositonBtn
-                //     visible: positionText !== ""
-
-                //     iconCharacter: positionIconCharacter
-                //     iconSize: 14
-                //     text: positionText
-                //     textColor: "white"
-                // }
+                // 作业编号
+                Row {
+                    anchors.verticalCenter: parent.verticalCenter
+                    spacing: 8
+                    visible: sopInfoText !== ""
+                    
+                    Text {
+                        text: sopIconCharacter
+                        color: "#40A9FF"
+                        font.pixelSize: 16
+                        font.family: iconFont.name
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
+                    Text {
+                        text: sopInfoText
+                        color: "#a0a0a0"
+                        font.pixelSize: 16
+                        // 普通文字不使用图标字体
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
+                }
 
-                IconText {
-                    id: workingUserBtn
+                // 执行人
+                Row {
+                    anchors.verticalCenter: parent.verticalCenter
+                    spacing: 8
                     visible: userName !== ""
-
-                    iconCharacter: userIconCharacter
-                    iconSize: 14
-                    text: userName
-                    textColor: "white"
+                    
+                    Text {
+                        text: userIconCharacter
+                        color: "#40A9FF"
+                        font.pixelSize: 16
+                        font.family: iconFont.name
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
+                    Text {
+                        text: userName
+                        color: "#a0a0a0"
+                        font.pixelSize: 16
+                        // 普通文字不使用图标字体
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
                 }
 
-                IconText {
-                    id: workingStateBtn
+                // 状态
+                Row {
+                    anchors.verticalCenter: parent.verticalCenter
+                    spacing: 8
                     visible: workingStateText !== ""
-
-                    iconCharacter: workingStateCharacter
-                    iconSize: 14
-                    text: workingStateText
-                    textColor: "white"
+                    
+                    Text {
+                        text: workingStateCharacter
+                        color: "#52c41a"
+                        font.pixelSize: 16
+                        font.family: iconFont.name
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
+                    Text {
+                        text: workingStateText
+                        color: "#52c41a"
+                        font.pixelSize: 16
+                        // 普通文字不使用图标字体
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
                 }
 
-                IconText {
-                    id: workingTimeBtn
+                // 时间
+                Row {
+                    anchors.verticalCenter: parent.verticalCenter
+                    spacing: 8
                     visible: workingTimeText !== ""
-
-                    iconCharacter: workingTimeCharacter
-                    iconSize: 14
-                    text: workingTimeText
-                    textColor: "white"
+                    
+                    Text {
+                        text: workingTimeCharacter
+                        color: "#40A9FF"
+                        font.pixelSize: 16
+                        font.family: iconFont.name
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
+                    Text {
+                        text: workingTimeText
+                        color: "#a0a0a0"
+                        font.pixelSize: 16
+                        // 时间文字不使用图标字体,避免冒号显示异常
+                        anchors.verticalCenter: parent.verticalCenter
+                    }
                 }
             }
         }
@@ -177,8 +286,9 @@ Rectangle {
             id: formContent
             x: 46
             width: parent.width-x*2
-            height: showNegativeBtn || showOpsitiveBtn ? parent.height-__workingInfoArea.height -__btnArea.height - 70:
-                                                         parent.height-__workingInfoArea.height - 70
+            height: __btnArea.visible 
+                ? parent.height - __workingInfoArea.height - __btnArea.height - 70
+                : parent.height - __workingInfoArea.height - 70
             anchors.top: __workingInfoArea.bottom
             anchors.topMargin: 30
             anchors.bottomMargin: 5
@@ -206,6 +316,8 @@ Rectangle {
                 visible: control.showFormCard
                 iconSize: 24
                 textColor: "white"
+                readOnly: control.workingStateText === "已完成"  // 已完成状态表单不可编辑
+                virtualKeyboard: workingPageKeyboard  // 传递虚拟键盘引用
 
                 returnFormInfo: WorkNodeFormModel.formJsonInfo !== "" ? JSON.parse(WorkNodeFormModel.formJsonInfo) : {}
             }
@@ -225,32 +337,59 @@ Rectangle {
         Rectangle {
             id: __btnArea
             x: 46
-            width: parent.width-x*2
+            width: parent.width - x * 2
             height: 70
             anchors.bottom: parent.bottom
-            anchors.bottomMargin: 0
-            visible: showNegativeBtn || showOpsitiveBtn
+            anchors.bottomMargin: 10
+            // 只有当有按钮需要显示时才显示按钮区域
+            visible: showNegativeBtn || showOpsitiveBtn || control.workingStateText === "已完成"
 
             color: "transparent"
 
+            // 顶部分隔线
             Canvas {
                 id: __btnAreaCanvas
                 anchors.fill: parent
                 onPaint: {
                     var ctx = getContext("2d");
                     ctx.beginPath();
-                    ctx.lineWidth = 1;                  // 边框宽度
-                    ctx.moveTo(0, 0);                   // 移动到起始点
-                    ctx.lineTo(parent.width, 0);        // 画线到另一个点
-                    ctx.strokeStyle = "#40A9FF";        // 边框颜色
+                    ctx.lineWidth = 1;
+                    ctx.moveTo(0, 0);
+                    ctx.lineTo(parent.width, 0);
+                    ctx.strokeStyle = "#40A9FF";
                     ctx.stroke();
                 }
             }
 
-            RowLayout {
+            Row {
                 id: __btnsRow
                 anchors.right: parent.right
                 anchors.verticalCenter: parent.verticalCenter
+                spacing: 16
+
+                // 返回按钮(仅"已完成"状态时显示)
+                MButton {
+                    id: __btnBack
+                    visible: control.workingStateText === "已完成"
+                    text: qsTr("返回")
+                    iconCharacter: "\uf060"
+                    iconColor: "white"
+                    textColor: "white"
+                    width: 140
+                    height: 44
+
+                    buttonColor: "#1A40A9FF"  // 半透明蓝色,与整体风格一致
+                    fontSize: 16
+                    iconSize: 18
+                    btnRadius: 8
+
+                    MouseArea {
+                        anchors.fill: parent
+                        onClicked: {
+                            appStackView.pop();
+                        }
+                    }
+                }
 
                 MButton {
                     id: __btnNegative
@@ -258,11 +397,12 @@ Rectangle {
                     text: qsTr(textNegativeBtn)
                     iconCharacter: "\uf057"
                     iconColor: "white"
-                    height: 40
+                    width: 140
+                    height: 44
 
                     buttonColor: colorNegativeBtn
                     fontSize: 16
-                    iconSize: 16
+                    iconSize: 18
                     btnRadius: 8
 
                     MouseArea {
@@ -270,21 +410,30 @@ Rectangle {
                         anchors.fill: parent
 
                         onClicked: {
-                            // 收集FormCard中的信息
-                            showAlertDialog("提示", "退出表单页面", "\uf071", negativeAlertDialogCallback);
+                            // 审核不通过 - 也需要表单校验
+                            var formInfo = formCard.collectFormInputInfo();
+                            if (validateFormInfo(formInfo)) {
+                                httpUpdateNodeApproval.nodeId = control.currentNodeId;
+                                httpUpdateNodeApproval.approvalStatus = "rejected";
+                                httpUpdateNodeApproval.formData = formInfo;
+                                httpUpdateNodeApproval.start();
+                            }
                         }
                     }
                 }
+
                 MButton {
+                    id: __btnOpsitive
                     visible: showOpsitiveBtn
                     text: qsTr(textOpsitiveBtn)
                     iconCharacter: "\uf058"
                     iconColor: "white"
-                    height: 40
+                    width: 140
+                    height: 44
 
                     buttonColor: colorOpsitiveBtn
                     fontSize: 16
-                    iconSize: 16
+                    iconSize: 18
                     btnRadius: 8
 
                     MouseArea {
@@ -292,9 +441,7 @@ Rectangle {
                         anchors.fill: parent
 
                         onClicked: {
-                            // showAlertDialog("审核成功", "当前作业任务已完成", "\uf058", opsitiveAlertDialogCallback);
                             var formInfo = formCard.collectFormInputInfo();
-                            // 验证formInfo中, required为true时,value是否为空
                             if (validateFormInfo(formInfo)) {
                                 httpUpdateNodeApproval.nodeId = control.currentNodeId;
                                 httpUpdateNodeApproval.approvalStatus = "approved";
@@ -307,6 +454,15 @@ Rectangle {
             }
         }
     }
+    
+    // 虚拟键盘
+    CustomVirtualKeyboard {
+        id: workingPageKeyboard
+        anchors.bottom: parent.bottom
+        anchors.horizontalCenter: parent.horizontalCenter
+        width: parent.width * 0.9
+        z: 1000
+    }
 
     function showAlertDialog(msgTypeName, msgValue, msgIconChar, callbackFunc) {
         var component = Qt.createComponent("components/AlertDialog.qml");
@@ -325,6 +481,68 @@ Rectangle {
         }
     }
 
+    // 显示确认对话框(双按钮模式)
+    function showConfirmDialog(msgTypeName, msgValue, msgIconChar, confirmCallback, cancelCallback) {
+        var component = Qt.createComponent("components/AlertDialog.qml");
+        if (component.status === Component.Ready) {
+            var alertDialogComp = component.createObject(appAlertDialog, {
+                messageTypeName: msgTypeName,
+                messageValue: msgValue,
+                messageIconCharacter: msgIconChar,
+                showCancelBtn: true,
+                confirmBtnText: "确认",
+                cancelBtnText: "返回"
+            });
+            alertDialogComp.parent = appAlertDialog;
+            // 处理确认按钮
+            alertDialogComp.confirm.connect(function () { confirmCallback(alertDialogComp); });
+            // 处理取消/返回按钮
+            alertDialogComp.cancel.connect(function () { cancelCallback(alertDialogComp); });
+        } else {
+            console.log("Error loading component:", component.errorString());
+        }
+    }
+    
+    // 显示操作成功确认对话框(继续操作/退出系统)
+    function showSuccessConfirmDialog(msgText) {
+        var component = Qt.createComponent("components/AlertDialog.qml");
+        if (component.status === Component.Ready) {
+            var alertDialogComp = component.createObject(appAlertDialog, {
+                messageTypeName: msgText,
+                messageValue: "",
+                messageIconCharacter: "\uf058",
+                showCancelBtn: true,
+                confirmBtnText: "继续操作",
+                cancelBtnText: "退出系统"
+            });
+            alertDialogComp.parent = appAlertDialog;
+            // 继续操作 - 返回作业列表页
+            alertDialogComp.confirm.connect(function () { successContinueCallback(alertDialogComp); });
+            // 退出系统 - 返回首页
+            alertDialogComp.cancel.connect(function () { successExitCallback(alertDialogComp); });
+        } else {
+            console.log("Error loading component:", component.errorString());
+        }
+    }
+    
+    // 成功后继续操作 - 刷新数据后返回作业列表页
+    function successContinueCallback(componentObj) {
+        componentObj.visible = false;
+        appAlertDialog.sourceComponent = null;
+        componentObj.destroy();
+        // 刷新数据后跳转到列表页
+        refreshDataAndGoToList();
+    }
+    
+    // 成功后退出系统 - 返回首页
+    function successExitCallback(componentObj) {
+        componentObj.visible = false;
+        appAlertDialog.sourceComponent = null;
+        componentObj.destroy();
+        // 返回首页
+        appShowHome = true;
+    }
+
     function negativeAlertDialogCallback(componentObj) {
         componentObj.visible = false;
         appAlertDialog.sourceComponent = null;
@@ -336,6 +554,22 @@ Rectangle {
         appShowHome = true;
     }
 
+    // 确认取消操作 - 返回列表页
+    function cancelConfirmCallback(componentObj) {
+        componentObj.visible = false;
+        appAlertDialog.sourceComponent = null;
+        componentObj.destroy();
+        // 返回列表页
+        appStackView.pop();
+    }
+
+    // 取消操作 - 关闭对话框,留在当前页面
+    function cancelDismissCallback(componentObj) {
+        componentObj.visible = false;
+        appAlertDialog.sourceComponent = null;
+        componentObj.destroy();
+    }
+
     function validateFormInfo(formInfo) {
         var formJsonObj = JSON.parse(formInfo);
         var fields = formJsonObj["fields"];

+ 71 - 5
src/qml/components/AlertDialog.qml

@@ -12,6 +12,13 @@ Rectangle {
     property string messageIconCharacter: ""
 
     property bool dismissOnOverlay: true
+    
+    // 是否显示取消按钮(双按钮模式)
+    property bool showCancelBtn: false
+    property string confirmBtnText: "确认"
+    property string cancelBtnText: "返回"
+    property color confirmBtnColor: "#1890FF"
+    property color cancelBtnColor: "#666666"
 
     signal confirm()
     signal cancel()
@@ -58,7 +65,7 @@ Rectangle {
                 color: "white"
 
                 font.pixelSize: 24
-                font.family: iconFont.name
+                // 对话框标题不使用图标字体
                 verticalAlignment: Text.AlignVCenter
                 horizontalAlignment: Text.AlignHCenter
             }
@@ -71,18 +78,21 @@ Rectangle {
                 color: "white"
 
                 font.pixelSize: 18
-                font.family: iconFont.name
+                // 对话框内容不使用图标字体
                 verticalAlignment: Text.AlignVCenter
                 horizontalAlignment: Text.AlignHCenter
             }
 
+            // 单按钮模式
             Rectangle {
+                id: singleBtn
                 y: 232
                 width: 136
                 height: 49
                 anchors.horizontalCenter: parent.horizontalCenter
+                visible: !control.showCancelBtn
 
-                color: "#1890FF"
+                color: control.confirmBtnColor
                 radius: 12
 
                 IconText {
@@ -90,18 +100,74 @@ Rectangle {
                     iconCharacter: "\uf00c"
                     iconSize: 18
                     iconColor: "white"
-                    text: qsTr("确认")
+                    text: qsTr(control.confirmBtnText)
                     textColor: "white"
                 }
 
                 MouseArea {
                     anchors.fill: parent
-
                     onClicked: {
                         control.confirm();
                     }
                 }
             }
+
+            // 双按钮模式
+            Row {
+                id: doubleBtns
+                y: 232
+                anchors.horizontalCenter: parent.horizontalCenter
+                spacing: 20
+                visible: control.showCancelBtn
+
+                // 取消/返回按钮
+                Rectangle {
+                    width: 120
+                    height: 49
+                    color: control.cancelBtnColor
+                    radius: 12
+
+                    IconText {
+                        anchors.centerIn: parent
+                        iconCharacter: "\uf060"
+                        iconSize: 18
+                        iconColor: "white"
+                        text: qsTr(control.cancelBtnText)
+                        textColor: "white"
+                    }
+
+                    MouseArea {
+                        anchors.fill: parent
+                        onClicked: {
+                            control.cancel();
+                        }
+                    }
+                }
+
+                // 确认按钮
+                Rectangle {
+                    width: 120
+                    height: 49
+                    color: control.confirmBtnColor
+                    radius: 12
+
+                    IconText {
+                        anchors.centerIn: parent
+                        iconCharacter: "\uf00c"
+                        iconSize: 18
+                        iconColor: "white"
+                        text: qsTr(control.confirmBtnText)
+                        textColor: "white"
+                    }
+
+                    MouseArea {
+                        anchors.fill: parent
+                        onClicked: {
+                            control.confirm();
+                        }
+                    }
+                }
+            }
         }
     }
     MouseArea {

+ 303 - 0
src/qml/components/CustomVirtualKeyboard.qml

@@ -0,0 +1,303 @@
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+
+// 自定义虚拟键盘组件 - 适用于无实体键盘的锁控柜设备
+Rectangle {
+    id: keyboard
+    
+    // 绑定的输入框
+    property Item targetInput: null
+    // 键盘是否可见
+    property bool keyboardVisible: false
+    // 是否大写模式
+    property bool isUpperCase: false
+    // 是否数字/符号模式
+    property bool isSymbolMode: false
+    
+    // 键盘样式配置
+    property color keyboardBgColor: "#1a1a2e"
+    property color keyColor: "#16213e"
+    property color keyPressedColor: "#0f3460"
+    property color keyTextColor: "#ffffff"
+    property color specialKeyColor: "#0f3460"
+    property color confirmKeyColor: "#1890FF"
+    property int keySpacing: 6
+    property int keyRadius: 8
+    property int keyHeight: 52
+    property int fontSize: 20
+    
+    width: parent ? parent.width : 800
+    height: 300
+    color: keyboardBgColor
+    visible: keyboardVisible
+    z: 10000
+    
+    // 字母键盘布局
+    property var letterRows: [
+        ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
+        ["a", "s", "d", "f", "g", "h", "j", "k", "l"],
+        ["z", "x", "c", "v", "b", "n", "m"]
+    ]
+    
+    // 数字/符号键盘布局
+    property var symbolRows: [
+        ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
+        ["-", "/", ":", ";", "(", ")", "$", "&", "@"],
+        [".", ",", "?", "!", "'", "#", "%"]
+    ]
+    
+    // 显示键盘
+    function show(inputItem) {
+        targetInput = inputItem;
+        keyboardVisible = true;
+        if (targetInput) {
+            targetInput.forceActiveFocus();
+        }
+    }
+    
+    // 隐藏键盘
+    function hide() {
+        keyboardVisible = false;
+    }
+    
+    // 输入字符
+    function inputChar(ch) {
+        if (targetInput) {
+            var displayChar = isUpperCase && !isSymbolMode ? ch.toUpperCase() : ch;
+            targetInput.insert(targetInput.cursorPosition, displayChar);
+        }
+    }
+    
+    // 退格删除
+    function backspace() {
+        if (targetInput && targetInput.cursorPosition > 0) {
+            var pos = targetInput.cursorPosition;
+            targetInput.remove(pos - 1, pos);
+        }
+    }
+    
+    // 输入空格
+    function inputSpace() {
+        if (targetInput) {
+            targetInput.insert(targetInput.cursorPosition, " ");
+        }
+    }
+    
+    // 切换大小写
+    function toggleCase() {
+        isUpperCase = !isUpperCase;
+    }
+    
+    // 切换数字/符号模式
+    function toggleSymbolMode() {
+        isSymbolMode = !isSymbolMode;
+    }
+    
+    // 获取显示文本
+    function getDisplayText(ch) {
+        if (isSymbolMode) return ch;
+        return isUpperCase ? ch.toUpperCase() : ch;
+    }
+    
+    // 处理按键点击
+    function handleKeyPress(keyChar) {
+        if (keyChar === "BACK") {
+            backspace();
+        } else if (keyChar === "SHIFT") {
+            toggleCase();
+        } else if (keyChar === "SYM") {
+            toggleSymbolMode();
+        } else if (keyChar === "SPACE") {
+            inputSpace();
+        } else if (keyChar === "OK") {
+            hide();
+        } else {
+            inputChar(keyChar);
+        }
+    }
+    
+    // 阻止点击穿透
+    MouseArea {
+        anchors.fill: parent
+        onClicked: function(mouse) { mouse.accepted = true; }
+        onPressed: function(mouse) { mouse.accepted = true; }
+        onReleased: function(mouse) { mouse.accepted = true; }
+    }
+    
+    // 通用按键组件
+    Component {
+        id: keyButtonComponent
+        
+        Rectangle {
+            id: keyBtn
+            property string keyChar: ""
+            property string display: keyChar
+            property bool isSpecial: false
+            property bool isConfirm: false
+            property real widthMultiplier: 1.0
+            
+            width: (keyboard.width - keyboard.keySpacing * 12) / 10 * widthMultiplier - keyboard.keySpacing
+            height: keyboard.keyHeight
+            radius: keyboard.keyRadius
+            color: keyBtnMouse.pressed ? keyboard.keyPressedColor : (isConfirm ? keyboard.confirmKeyColor : (isSpecial ? keyboard.specialKeyColor : keyboard.keyColor))
+            
+            Text {
+                anchors.centerIn: parent
+                text: display
+                color: keyboard.keyTextColor
+                font.pixelSize: isSpecial ? keyboard.fontSize - 2 : keyboard.fontSize
+                font.bold: isConfirm
+            }
+            
+            MouseArea {
+                id: keyBtnMouse
+                anchors.fill: parent
+                onClicked: {
+                    keyboard.handleKeyPress(keyBtn.keyChar);
+                }
+            }
+            
+            Behavior on color {
+                ColorAnimation { duration: 80 }
+            }
+        }
+    }
+    
+    Column {
+        anchors.fill: parent
+        anchors.margins: keySpacing
+        spacing: keySpacing
+        
+        // 第一行:数字
+        Row {
+            anchors.horizontalCenter: parent.horizontalCenter
+            spacing: keyboard.keySpacing
+            
+            Repeater {
+                model: keyboard.isSymbolMode ? keyboard.symbolRows[0] : ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
+                
+                Loader {
+                    sourceComponent: keyButtonComponent
+                    onLoaded: {
+                        item.keyChar = modelData;
+                        item.display = modelData;
+                    }
+                }
+            }
+        }
+        
+        // 第二行
+        Row {
+            anchors.horizontalCenter: parent.horizontalCenter
+            spacing: keyboard.keySpacing
+            
+            Repeater {
+                model: keyboard.isSymbolMode ? keyboard.symbolRows[1] : keyboard.letterRows[0]
+                
+                Loader {
+                    sourceComponent: keyButtonComponent
+                    onLoaded: {
+                        item.keyChar = modelData;
+                        item.display = Qt.binding(function() { return keyboard.getDisplayText(modelData); });
+                    }
+                }
+            }
+        }
+        
+        // 第三行
+        Row {
+            anchors.horizontalCenter: parent.horizontalCenter
+            spacing: keyboard.keySpacing
+            
+            Repeater {
+                model: keyboard.isSymbolMode ? keyboard.symbolRows[2] : keyboard.letterRows[1]
+                
+                Loader {
+                    sourceComponent: keyButtonComponent
+                    onLoaded: {
+                        item.keyChar = modelData;
+                        item.display = Qt.binding(function() { return keyboard.getDisplayText(modelData); });
+                    }
+                }
+            }
+        }
+        
+        // 第四行:Shift + 字母 + Backspace
+        Row {
+            anchors.horizontalCenter: parent.horizontalCenter
+            spacing: keyboard.keySpacing
+            
+            // Shift 键
+            Loader {
+                sourceComponent: keyButtonComponent
+                onLoaded: {
+                    item.keyChar = "SHIFT";
+                    item.display = Qt.binding(function() { return keyboard.isUpperCase ? "ABC" : "abc"; });
+                    item.isSpecial = true;
+                    item.widthMultiplier = 1.3;
+                }
+            }
+            
+            Repeater {
+                model: keyboard.isSymbolMode ? keyboard.symbolRows[2] : keyboard.letterRows[2]
+                
+                Loader {
+                    sourceComponent: keyButtonComponent
+                    onLoaded: {
+                        item.keyChar = modelData;
+                        item.display = Qt.binding(function() { return keyboard.getDisplayText(modelData); });
+                    }
+                }
+            }
+            
+            // Backspace 键
+            Loader {
+                sourceComponent: keyButtonComponent
+                onLoaded: {
+                    item.keyChar = "BACK";
+                    item.display = "←";
+                    item.isSpecial = true;
+                    item.widthMultiplier = 1.8;
+                }
+            }
+        }
+        
+        // 第五行:符号切换 + 空格 + 确定
+        Row {
+            anchors.horizontalCenter: parent.horizontalCenter
+            spacing: keyboard.keySpacing
+            
+            // 符号切换键
+            Loader {
+                sourceComponent: keyButtonComponent
+                onLoaded: {
+                    item.keyChar = "SYM";
+                    item.display = Qt.binding(function() { return keyboard.isSymbolMode ? "ABC" : "?123"; });
+                    item.isSpecial = true;
+                    item.widthMultiplier = 1.5;
+                }
+            }
+            
+            // 空格键
+            Loader {
+                sourceComponent: keyButtonComponent
+                onLoaded: {
+                    item.keyChar = "SPACE";
+                    item.display = "空格";
+                    item.widthMultiplier = 5.0;
+                }
+            }
+            
+            // 确定键
+            Loader {
+                sourceComponent: keyButtonComponent
+                onLoaded: {
+                    item.keyChar = "OK";
+                    item.display = "确定";
+                    item.isConfirm = true;
+                    item.widthMultiplier = 1.5;
+                }
+            }
+        }
+    }
+}

+ 105 - 46
src/qml/components/FormCard.qml

@@ -20,27 +20,35 @@ Rectangle {
     property int iconSize: 14
     property color textColor: "white"
 
-    property int padding: 10    // 边距
-    property int spacing: 16    // 间隔
+    property int padding: 20    // 边距
+    property int spacing: 28    // 间隔
     property int itemWidth: 300
-    // property int itemHeight: 144
-    property int itemHeight: 74
+    property int itemHeight: 90
     property int delegateWidth: itemWidth + spacing
     property int delegateHeight: itemHeight + spacing
 
     property int textAreaHeight: 160
     property var returnFormInfo
+    
+    // 只读模式(已完成状态时表单不可编辑)
+    property bool readOnly: false
+    
+    // 虚拟键盘引用
+    property var virtualKeyboard: null
+    property Item currentFocusedInput: null
 
     Flickable {
         id: contentItem
-        width: parent.width-45
+        width: parent.width - 60
         anchors.fill: parent
-        anchors.leftMargin: 45
-        anchors.topMargin: 35
+        anchors.leftMargin: 30
+        anchors.rightMargin: 30
+        anchors.topMargin: 30
+        anchors.bottomMargin: 20
 
-        clip: false
+        clip: true
         contentWidth: width
-        contentHeight: delegateHeight*WorkNodeFormModel.rowCount()
+        contentHeight: delegateHeight * WorkNodeFormModel.rowCount() + 40
 
         ListView {
             id: list_view
@@ -69,44 +77,43 @@ Rectangle {
                 Rectangle {
                     id: formArea
                     color: "transparent"
-                    // border.color: "#40A9FF"
                     radius: 12
 
-                    Text {
-                        id: formRequiredLabel
-                        // x: control.padding
-                        width: 7
-                        height: 20
-                        // anchors.verticalCenter: parent.verticalCenter
-                        visible: required
-                        text: "*"
-                        color: "red"
-                        font.pixelSize: 15
-                        font.family: iconFont.name
-                        font.bold: true
-                        verticalAlignment: Text.AlignVCenter
-                    }
+                    // 标签行(必填标识 + 标签文字)
+                    Row {
+                        id: labelRow
+                        spacing: 4
+                        
+                        Text {
+                            id: formRequiredLabel
+                            width: required ? 12 : 0
+                            height: 22
+                            visible: required
+                            text: "*"
+                            color: "#ff4d4f"
+                            font.pixelSize: 16
+                            font.family: iconFont.name
+                            font.bold: true
+                            verticalAlignment: Text.AlignVCenter
+                        }
 
-                    Text {
-                        id: formLabel
-                        x: formRequiredLabel.width+3
-                        width: 90
-                        height: 20
-                        // anchors.verticalCenter: parent.verticalCenter
-                        text: ""
-                        color: "white"
-                        font.pixelSize: 15
-                        font.family: iconFont.name
-                        font.bold: true
-                        verticalAlignment: Text.AlignVCenter
+                        Text {
+                            id: formLabel
+                            height: 22
+                            text: ""
+                            color: "#e0e0e0"
+                            font.pixelSize: 15
+                            // 表单标签不使用图标字体
+                            verticalAlignment: Text.AlignVCenter
+                        }
                     }
 
+                    // 控件区域
                     Loader {
                         id: formControl
-                        anchors.top: formLabel.bottom
-                        anchors.topMargin: 8
+                        anchors.top: labelRow.bottom
+                        anchors.topMargin: 10
                         width: formArea.width
-                        anchors.verticalCenter: parent.verticalCenter
                     }
                 }
 
@@ -133,9 +140,17 @@ Rectangle {
                         formControl.item.placeholderText = placeholder;
                         formControl.item.required = required;
                         formControl.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
-                        formControl.item.height = 40;
+                        formControl.item.height = 48;
+                        formControl.item.readOnly = control.readOnly;
+                        formControl.item.enabled = !control.readOnly;
+                        // 回显值
+                        if (value !== undefined && value !== "") {
+                            formControl.item.text = value;
+                        }
 
                         formControl.item.signalTextChanged.connect(control.slotCollectInputInfo);
+                        // 连接虚拟键盘
+                        formControl.item.signalInputClicked.connect(control.showVirtualKeyboard);
                     } else if (type === "switch") {
                         formControl.source = "MSwitchButton.qml";
                         formControl.item.text = "";
@@ -145,7 +160,12 @@ Rectangle {
                         formControl.item.modelIndex = groupIndex;
                         formControl.item.required = required;
                         formControl.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
-                        formControl.item.height = 40;
+                        formControl.item.height = 48;
+                        formControl.item.enabled = !control.readOnly;
+                        // 回显值
+                        if (value !== undefined && value !== "") {
+                            formControl.item.checked = (value === "true" || value === "1");
+                        }
 
                         formControl.item.signalToggled.connect(control.slotCollectSwitchChecked);
                     } else if (type === "daterange") {
@@ -155,9 +175,17 @@ Rectangle {
                         formControl.item.placeholderText = placeholder;
                         formControl.item.required = required;
                         formControl.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
-                        formControl.item.height = 40;
+                        formControl.item.height = 48;
+                        formControl.item.readOnly = control.readOnly;
+                        formControl.item.enabled = !control.readOnly;
+                        // 回显值
+                        if (value !== undefined && value !== "") {
+                            formControl.item.text = value;
+                        }
 
                         formControl.item.signalTextChanged.connect(control.slotCollectInputInfo);
+                        // 连接虚拟键盘
+                        formControl.item.signalInputClicked.connect(control.showVirtualKeyboard);
                     } else if (type === "timepicker") {
                         formControl.source = "MDatePicker.qml";
                         formControl.item.controlId = id;
@@ -165,7 +193,12 @@ Rectangle {
                         formControl.item.placeholderText = placeholder;
                         formControl.item.required = required;
                         formControl.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
-                        formControl.item.height = 40;
+                        formControl.item.height = 48;
+                        formControl.item.enabled = !control.readOnly;
+                        // 回显值
+                        if (value !== undefined && value !== "") {
+                            formControl.item.text = value;
+                        }
 
                         formControl.item.signalTextChanged.connect(control.slotCollectInputInfo);
                     } else if (type === "radio") {
@@ -174,14 +207,24 @@ Rectangle {
                         formControl.item.modelIndex = groupIndex;
                         formControl.item.required = required;
                         formControl.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
+                        formControl.item.enabled = !control.readOnly;
                         var radioOptions = [];
+                        var selectedIdx = -1;
                         for (var i = 0; i < options.length; i++) {
-                            radioOptions.push({text: options[i].split(",")[0]});
+                            var optParts = options[i].split(",");
+                            radioOptions.push({text: optParts[0]});
+                            // 查找回显值对应的索引
+                            if (value !== undefined && value !== "" && optParts.length > 1 && optParts[1] === value) {
+                                selectedIdx = i;
+                            }
                         }
 
                         formControl.item.model = radioOptions;
-                        // formControl.item.backgroundVisible = false;
-                        formControl.item.height = 40;
+                        formControl.item.height = 48;
+                        // 回显选中状态
+                        if (selectedIdx >= 0) {
+                            formControl.item.selectedIndices = [selectedIdx];
+                        }
 
                         formControl.item.signalSelectionChanged.connect(control.slotCollectRadioBtnInfo);
                     } else if (type === "textarea") {
@@ -191,10 +234,18 @@ Rectangle {
                         formControl.item.placeholderText = placeholder;
                         formControl.item.required = required;
                         formControl.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
+                        formControl.item.readOnly = control.readOnly;
+                        formControl.item.enabled = !control.readOnly;
 
                         formArea.height = control.textAreaHeight;
+                        // 回显值
+                        if (value !== undefined && value !== "") {
+                            formControl.item.text = value;
+                        }
 
                         formControl.item.signalTextAreaChanged.connect(control.slotCollectInputInfo);
+                        // 连接虚拟键盘
+                        formControl.item.signalInputClicked.connect(control.showVirtualKeyboard);
                     } else {
                         return;
                     }
@@ -284,4 +335,12 @@ Rectangle {
         signalSubmit(true);
         return JSON.stringify(control.returnFormInfo);
     }
+    
+    // 显示虚拟键盘
+    function showVirtualKeyboard(inputField) {
+        if (control.virtualKeyboard && !control.readOnly) {
+            control.currentFocusedInput = inputField;
+            control.virtualKeyboard.show(inputField);
+        }
+    }
 }

+ 15 - 25
src/qml/components/IconText.qml

@@ -30,18 +30,18 @@ Rectangle {
     property color shadowColor: theme.shadowColor
 
     // 卡片尺寸根据内容自适应
-    implicitWidth: contentLayout.implicitWidth + padding * 2
-    implicitHeight: contentLayout.implicitHeight + padding * 2
+    implicitWidth: contentLayout.width + padding * 2
+    implicitHeight: Math.max(iconLabel.height, label.height) + padding * 2
 
     radius: bgRadius
     color: "transparent"
 
     // === 内容布局 ===
-    // 使用ColumnLayout自动垂直排列内容, 并通过anchors.margins实现内边距
-    RowLayout {
+    // 使用 Row 布局并确保垂直居中对齐
+    Row {
         id: contentLayout
         anchors.centerIn: parent
-        anchors.margins: control.padding
+        spacing: 8
 
         Text {
             id: iconLabel
@@ -50,10 +50,10 @@ Rectangle {
             color: iconColor
             font.pixelSize: iconSize
             font.family: control.iconFontFamily
+            anchors.verticalCenter: parent.verticalCenter
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHCenter
 
-
             transform: Rotation {
                 id: iconRotation
                 origin.x: iconLabel.width / 2
@@ -81,25 +81,15 @@ Rectangle {
             }
         }
 
-        Item {
-            id: labelWrap
-            width: control.textShown ? label.implicitWidth : 0
-            height: label.implicitHeight
-            Layout.preferredWidth: width
-            Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
-            clip: true
-
-            Text {
-                id: label
-                anchors.verticalCenter: parent.verticalCenter
-                text: qsTr(control.text)
-                opacity: control.textShown ? 1 : 0
-                color: textColor
-                font.pixelSize: fontSize
-                font.bold: true
-                verticalAlignment: Text.AlignVCenter
-                Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } }
-            }
+        Text {
+            id: label
+            visible: control.textShown
+            text: qsTr(control.text)
+            color: textColor
+            font.pixelSize: fontSize
+            font.bold: true
+            anchors.verticalCenter: parent.verticalCenter
+            verticalAlignment: Text.AlignVCenter
         }
     }
 }

+ 6 - 0
src/qml/components/JobTicketCard.qml

@@ -125,6 +125,12 @@ Rectangle {
                 }
             }
 
+            // 如果状态是"已完成",隐藏操作按钮(仅查看模式)
+            if (workingStateText === "已完成") {
+                isShowNegativeBtn = false;
+                isShowOpsitiveBtn = false;
+            }
+
             appStackView.push("../WorkingPage.qml", {
                                     titleIconCharacter: "\uf023",
                                     titleText: jobTitleText,

+ 16 - 17
src/qml/components/JobTicketColockProcess.qml

@@ -401,7 +401,7 @@ Rectangle {
                 text: qsTr("请点击按钮进行操作")
                 color: "white"
                 font.pixelSize: 16
-                font.family: iconFont.name
+                // 普通文字不使用图标字体
                 verticalAlignment: Text.AlignVCenter
                 horizontalAlignment: Text.AlignHCenter
             }
@@ -635,7 +635,7 @@ Rectangle {
 
     Connections {
         target: control.colockModel
-        function onDataChanged() {
+        onDataChanged: {
             var allColocked = true;
             for (var i = 0; i < control.colockModel.count; i++) {
                 if (!control.colockModel.get(i).colocked) {
@@ -653,25 +653,25 @@ Rectangle {
 
     Connections {
         target: InteractiveCAN
-        function onSignalJobTicketResult(jsonResult, pointInfo) {
-            let lockInfo = JSON.parse(jsonResult);
+        onSignalJobTicketResult: {
+            var lockInfo = JSON.parse(jsonResult);
             if (!lockInfo.hasOwnProperty("data") || !Array.isArray(lockInfo.data)) {
                 return;
             }
-            for (let i = 0; i < lockInfo.data.length; i++) {
-                let dataItem = lockInfo.data[i];
+            for (var i = 0; i < lockInfo.data.length; i++) {
+                var dataItem = lockInfo.data[i];
                 if (dataItem.hasOwnProperty("taskCode")) {
                     if (dataItem.taskCode === WorkNodeFormModel.workId.toString() && dataItem.hasOwnProperty("dataList")) {
                         control.lockModel.clear();
-                        let dataList = dataItem.dataList;
-                        for (let j = 0; j < dataList.length; j++) {
-                            let position = dataList[j].dataId.toString();
-                            let name = pointInfo[position];
-                            let lockStatus = "";
-                            let locked = false;
-                            let status = dataList[j].status;
-                            let lockStatusIcon = "\uf058";
-                            let lockNfcinfo = dataList[j].infoRfidNo;
+                        var dataList = dataItem.dataList;
+                        for (var j = 0; j < dataList.length; j++) {
+                            var position = dataList[j].dataId.toString();
+                            var name = pointInfo[position];
+                            var lockStatus = "";
+                            var locked = false;
+                            var status = dataList[j].status;
+                            var lockStatusIcon = "\uf058";
+                            var lockNfcinfo = dataList[j].infoRfidNo;
 
                             if (status === 0) {
                                 lockStatus = "挂锁";
@@ -700,9 +700,8 @@ Rectangle {
             }
         }
         
-        function onSignalUpdateBackLockStatus(lockRfidList) {
+        onSignalUpdateBackLockStatus: {
             for (var i = 0; i < control.lockModel.count; i++) {
-
                 for (var j = 0; j < lockRfidList.length; j++) {
                     if (control.lockModel.get(i).lockNfc === lockRfidList[j]) {
                         control.lockModel.get(i).lockStatus = "已还锁";

+ 14 - 14
src/qml/components/JobTicketProcess.qml

@@ -190,7 +190,7 @@ Rectangle {
                 text: qsTr("请点击按钮进行操作")
                 color: "white"
                 font.pixelSize: 16
-                font.family: iconFont.name
+                // 普通文字不使用图标字体
                 verticalAlignment: Text.AlignVCenter
                 horizontalAlignment: Text.AlignHCenter
             }
@@ -609,7 +609,7 @@ Rectangle {
 
     Connections {
         target: control.colockModel
-        function onDataChanged() {
+        onDataChanged: {
             var allColocked = true;
             for (var i = 0; i < control.colockModel.count; i++) {
                 if (!control.colockModel.get(i).colocked) {
@@ -627,24 +627,24 @@ Rectangle {
 
     Connections {
         target: InteractiveCAN
-        function onSignalJobTicketResult(jsonResult, pointInfo) {
-            let lockInfo = JSON.parse(jsonResult);
+        onSignalJobTicketResult: {
+            var lockInfo = JSON.parse(jsonResult);
             if (!lockInfo.hasOwnProperty("data") || !Array.isArray(lockInfo.data)) {
                 return;
             }
-            for (let i = 0; i < lockInfo.data.length; i++) {
-                let dataItem = lockInfo.data[i];
+            for (var i = 0; i < lockInfo.data.length; i++) {
+                var dataItem = lockInfo.data[i];
                 if (dataItem.hasOwnProperty("taskCode")) {
                     if (dataItem.taskCode === WorkNodeFormModel.workId.toString() && dataItem.hasOwnProperty("dataList")) {
                         control.lockModel.clear();
-                        let dataList = dataItem.dataList;
-                        for (let j = 0; j < dataList.length; j++) {
-                            let position = dataList[j].dataId.toString();
-                            let name = pointInfo[position];
-                            let lockStatus = "";
-                            let locked = false;
-                            let status = dataList[j].status;
-                            let lockStatusIcon = "\uf09c";
+                        var dataList = dataItem.dataList;
+                        for (var j = 0; j < dataList.length; j++) {
+                            var position = dataList[j].dataId.toString();
+                            var name = pointInfo[position];
+                            var lockStatus = "";
+                            var locked = false;
+                            var status = dataList[j].status;
+                            var lockStatusIcon = "\uf09c";
                             if (status === 0) {
                                 lockStatus = "挂锁";
                                 locked = true;

+ 2 - 2
src/qml/components/JobTicketSubProcess.qml

@@ -59,7 +59,7 @@ Rectangle {
             visible: jobTicketStatusTypeName !== ""
             color: processingColor ? showSuccessfulColor ? "green" : "#40a9ff" : iconColor
             font.pixelSize: 18
-            font.family: iconFont.name
+            // 普通文字不使用图标字体
             font.bold: true
 //            font.weight: 800
             verticalAlignment: Text.AlignVCenter
@@ -75,7 +75,7 @@ Rectangle {
             font.pixelSize: 16
             font.bold: true
 //            font.weight: 800
-            font.family: iconFont.name
+            // 普通文字不使用图标字体
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHCenter
         }

+ 88 - 0
src/qml/components/LoadingDialog.qml

@@ -0,0 +1,88 @@
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+
+Rectangle {
+    id: control
+
+    anchors.fill: parent
+
+    // API
+    property string loadingText: "加载中,请稍后..."
+    property bool showLoading: false
+
+    visible: showLoading
+    color: "transparent"
+
+    MBlurCard {
+        id: __blurCard
+        anchors.fill: parent
+        blurSource: appBlurItem
+        blurAmount: 1.2
+        blurMax: 32
+        z: 1000
+
+        opacity: 1
+        scale: 1.0
+
+        Rectangle {
+            width: 300
+            height: 200
+            anchors.centerIn: parent
+
+            color: "#0A1929"
+            opacity: 0.85
+            radius: 16
+            border.color: "#4D40a9ff"
+            border.width: 2
+
+            Column {
+                anchors.centerIn: parent
+                spacing: 20
+
+                // 旋转的loading图标
+                Item {
+                    width: 64
+                    height: 64
+                    anchors.horizontalCenter: parent.horizontalCenter
+
+                    Text {
+                        id: loadingIcon
+                        anchors.centerIn: parent
+                        text: "\uf110"  // spinner图标
+                        color: "#1890FF"
+                        font.pixelSize: 48
+                        font.family: iconFont.name
+
+                        RotationAnimation on rotation {
+                            from: 0
+                            to: 360
+                            duration: 1000
+                            loops: Animation.Infinite
+                            running: control.showLoading
+                        }
+                    }
+                }
+
+                Text {
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    text: qsTr(loadingText)
+                    color: "white"
+                    font.pixelSize: 20
+                    // 普通文字不使用图标字体
+                    horizontalAlignment: Text.AlignHCenter
+                }
+            }
+        }
+    }
+
+    MouseArea {
+        id: __dialogArea
+        anchors.fill: parent
+        // 防鼠标事件穿透
+        onClicked: function(mouse) { mouse.accepted=true; }
+        onPressed: function(mouse) { mouse.accepted=true; }
+        onReleased: function(mouse) { mouse.accepted=true; }
+        onWheel: function(mouse) { mouse.accepted=true; }
+        onDoubleClicked: function(mouse) { mouse.accepted=true; }
+    }
+}

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

@@ -140,7 +140,7 @@ Item {
         text: qsTr(requiredMsg)
         color: "red"
         font.pixelSize: control.fontSize
-        font.family: iconFont.name
+        // 普通文字不使用图标字体
         font.bold: true
         verticalAlignment: Text.AlignVCenter
     }

+ 19 - 3
src/qml/components/MInput.qml

@@ -5,7 +5,7 @@ import QtQuick.Layouts 1.12
 Item {
     id: control
     width: 240
-    height: 40
+    height: 48
 
     // === 接口属性 & 信号 ===
     property string controlId
@@ -17,8 +17,10 @@ Item {
     property bool readOnly: false
     property bool passwordField: false
     property bool passwordVisible: false
+    property alias textField: textField  // 暴露 TextField 给外部访问
     signal accepted()  // 输入回车触发
     signal signalTextChanged(int index, string id, string data)
+    signal signalInputClicked(Item inputField)  // 点击输入框时触发,用于显示虚拟键盘
 
     // === 样式属性 ===
     property int fontSize: 16
@@ -63,7 +65,10 @@ Item {
     RowLayout {
         id: layout
         anchors.fill: parent
-        anchors.margins: 8
+        anchors.leftMargin: 12
+        anchors.rightMargin: 12
+        anchors.topMargin: 4
+        anchors.bottomMargin: 4
         spacing: 6
 
         // === 输入框主体 ===
@@ -83,6 +88,7 @@ Item {
                       ? (control.passwordVisible ? TextInput.Normal : TextInput.Password)
                       : TextInput.Normal
             background: null
+            inputMethodHints: Qt.ImhNoPredictiveText  // 禁用系统预测输入
             onAccepted: control.accepted()
 
             onActiveFocusChanged: {
@@ -90,6 +96,16 @@ Item {
             }
 
             onTextChanged: signalTextChanged(control.modelIndex, control.controlId, textField.text);
+            
+            // 点击输入框时触发信号
+            MouseArea {
+                anchors.fill: parent
+                enabled: control.enabled && !control.readOnly
+                onClicked: {
+                    textField.forceActiveFocus();
+                    control.signalInputClicked(textField);
+                }
+            }
         }
 
         // === 密码显示切换按钮 ===
@@ -120,7 +136,7 @@ Item {
         text: qsTr(control.requiredMsg)
         color: "red"
         font.pixelSize: control.fontSize
-        font.family: iconFont.name
+        // 普通文字不使用图标字体
         font.bold: true
         verticalAlignment: Text.AlignVCenter
     }

+ 9 - 2
src/qml/components/MRadioButton.qml

@@ -17,6 +17,9 @@ Rectangle {
     property string requiredMsg
     property bool showRequiredMsg: false
 
+    // === 状态属性 ===
+    property bool enabled: true
+
     // === 样式属性 ===
     property bool backgroundVisible: true
     property real radius: 10
@@ -178,7 +181,8 @@ Rectangle {
                 id: mouseArea
                 anchors.fill: parent
                 hoverEnabled: true
-                cursorShape: Qt.PointingHandCursor
+                cursorShape: control.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+                enabled: control.enabled
 
                 onEntered: btn.hovered = true
                 onExited: btn.hovered = false
@@ -220,6 +224,9 @@ Rectangle {
             }
         }
     }
+    
+    // 禁用时降低透明度
+    opacity: control.enabled ? 1.0 : 0.6
 
     Text {
         id: textRequiredMsg
@@ -230,7 +237,7 @@ Rectangle {
         text: qsTr(requiredMsg)
         color: "red"
         font.pixelSize: control.fontSize
-        font.family: iconFont.name
+        // 普通文字不使用图标字体
         font.bold: true
         verticalAlignment: Text.AlignVCenter
     }

+ 9 - 2
src/qml/components/MSwitchButton.qml

@@ -18,6 +18,9 @@ Rectangle {
     property string requiredMsg
     property bool showRequiredMsg: false
 
+    // === 状态属性 ===
+    property bool enabled: true
+
     // === 样式属性 ===
     property bool backgroundVisible: true
     property real radius: 10
@@ -130,7 +133,8 @@ Rectangle {
         id: mouseArea
         anchors.fill: parent
         hoverEnabled: true
-        cursorShape: Qt.PointingHandCursor
+        cursorShape: control.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
+        enabled: control.enabled
 
         onPressed: {
             scale.xScale = control.pressedScale
@@ -152,6 +156,9 @@ Rectangle {
             background.opacity = 1.0
         }
     }
+    
+    // 禁用时降低透明度
+    opacity: control.enabled ? 1.0 : 0.6
 
     Text {
         id: textRequiredMsg
@@ -162,7 +169,7 @@ Rectangle {
         text: qsTr(requiredMsg)
         color: "red"
         font.pixelSize: control.fontSize
-        font.family: iconFont.name
+        // 普通文字不使用图标字体
         font.bold: true
         verticalAlignment: Text.AlignVCenter
     }

+ 41 - 15
src/qml/components/MTextArea.qml

@@ -9,6 +9,7 @@ Item {
     property string controlId
     property int modelIndex
     property alias placeholderText: textArea.placeholderText
+    property alias text: textArea.text
     property bool backgroundVisible: true
     property color cardColor: theme.secondaryColor
     property real radius: 20
@@ -16,11 +17,17 @@ Item {
     property int fontSize: 16
 
     signal signalTextAreaChanged(int index, string id, string data)
-
+    signal signalInputClicked(Item inputField)  // 点击输入框时触发,用于显示虚拟键盘
+    
+    property alias textArea: textArea  // 暴露 TextArea 给外部访问
     property bool required: false
     property string requiredMsg
     property bool showRequiredMsg: false
     property color textColor: "white"
+    
+    // === 状态属性 ===
+    property bool readOnly: false
+    property bool enabled: true
 
     // 阴影属性
     property bool shadowEnabled: true
@@ -50,43 +57,49 @@ Item {
     Rectangle {
         id: background
         anchors.fill: parent
-        radius: control.radius
-        // color: control.cardColor
+        radius: 12
         color: "transparent"
         visible: control.backgroundVisible
-        // border.color: control.backgroundVisible
-        //                ? theme.getBorderColor(textArea.activeFocus)
-        //                : (textArea.activeFocus ? theme.focusColor : "#40A9FF")
-        border.color: "#40A9FF"
+        border.color: textArea.activeFocus ? "#1890FF" : "#40A9FF"
+        border.width: textArea.activeFocus ? 2 : 1
+        
+        Behavior on border.color {
+            ColorAnimation { duration: 150 }
+        }
     }
 
     // === 内容布局 ===
     ColumnLayout {
         id: contentLayout
         anchors.fill: parent
-        // anchors.margins: control.padding
-        spacing: 10
+        anchors.margins: 12
+        spacing: 0
 
         Flickable {
             id: textScroll
             Layout.fillWidth: true
-            Layout.preferredHeight: parent.height - control.padding*2  // 固定高度填充卡片内部
+            Layout.fillHeight: true
             clip: true
             contentWidth: textArea.width
-            contentHeight: textArea.paintedHeight
+            contentHeight: textArea.paintedHeight + 20
             boundsBehavior: Flickable.StopAtBounds
             interactive: true
 
             TextArea {
                 id: textArea
                 width: textScroll.width
-                height: textScroll.height
+                height: Math.max(textScroll.height, paintedHeight + 20)
                 wrapMode: Text.Wrap
                 font.pixelSize: control.fontSize
                 placeholderText: "请输入内容"
-//                palette.placeholderText: control.textColor
+                placeholderTextColor: "#606060"
                 background: null
                 color: control.textColor
+                leftPadding: 4
+                topPadding: 4
+                readOnly: control.readOnly
+                enabled: control.enabled
+                inputMethodHints: Qt.ImhNoPredictiveText  // 禁用系统预测输入
 
                 onActiveFocusChanged: {
                     appKeyboardVisible = activeFocus;
@@ -94,9 +107,22 @@ Item {
 
                 onTextChanged: {
                     textScroll.contentY = Math.max(0, textScroll.contentHeight - textScroll.height)
-
                     control.signalTextAreaChanged(control.modelIndex, control.controlId, textArea.text);
                 }
+                
+                // 点击输入框时触发信号
+                MouseArea {
+                    anchors.fill: parent
+                    enabled: control.enabled && !control.readOnly
+                    propagateComposedEvents: true
+                    onClicked: {
+                        textArea.forceActiveFocus();
+                        control.signalInputClicked(textArea);
+                        mouse.accepted = false;  // 允许事件继续传递以支持光标定位
+                    }
+                    onPressed: mouse.accepted = false
+                    onReleased: mouse.accepted = false
+                }
             }
         }
     }
@@ -110,7 +136,7 @@ Item {
         text: qsTr(requiredMsg)
         color: "red"
         font.pixelSize: control.fontSize
-        font.family: iconFont.name
+        // 普通文字不使用图标字体
         font.bold: true
         verticalAlignment: Text.AlignVCenter
     }

+ 99 - 99
src/qml/components/NoJobTicketDialog.qml

@@ -1,99 +1,99 @@
-import QtQuick 2.12
-import QtQuick.Layouts 1.12
-
-Item {
-    id: control
-
-    property bool showLogoutSeconds: false
-
-    Rectangle {
-        id: __dialogArea
-        x: 551
-        y: 237
-        width: 818
-        height: 507
-
-        color: "#0A1929"
-        opacity: 0.85
-        radius: 20
-
-        Rectangle {
-            id: __iconBackground
-            x: 349
-            y: 81
-            width: 120
-            height: 120
-            radius: width / 2
-            color: "#C8C8C8"
-            opacity: 0.2
-
-            Text {
-                id: __iconLabel
-                anchors.centerIn: parent
-                text: "\uf05a"
-                color: "#1890FF"
-                opacity: 0.8
-                font.pixelSize: 60
-                font.family: iconFont.name
-                verticalAlignment: Text.AlignVCenter
-                horizontalAlignment: Text.AlignHCenter
-            }
-        }
-
-        Text {
-            x: 121
-            y: 241
-            width: 576
-            height: 58
-
-            text: qsTr("您没有正在执行的作业任务,请确认")
-            color: "white"
-
-            font.pixelSize: 36
-            font.family: iconFont.name
-            verticalAlignment: Text.AlignVCenter
-            horizontalAlignment: Text.AlignHCenter
-        }
-
-        Rectangle {
-            x: 121
-            y: 349
-            width: 576
-            height: 87
-            color: "transparent"
-
-            MButton {
-                x: 204
-                y: 10
-                width: 168
-                height: 77
-
-                buttonColor: "#1890FF"
-                btnRadius: 12
-
-                text: qsTr("退出")
-                textColor: "white"
-
-                onClicked: {
-                    // 退出返回首页
-                    appShowHome = true;
-                }
-            }
-
-            IconText {
-                id: __logoutSeconds
-                x: 382
-                y: 28
-                width: 85
-                height: 30
-                visible: appLogoutSeconds % defaultLogoutSeconds !== 0
-
-                text: appLogoutSeconds % defaultLogoutSeconds === 0 ? "" : qsTr("(" + appLogoutSeconds.toString() + "秒)")
-                textColor: "red"
-                iconCharacter: appLogoutSeconds % defaultLogoutSeconds === 0 ? "" : "\uf1da"
-                iconSize: 22
-                iconColor: "red"
-            }
-        }
-    }
-}
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+
+Item {
+    id: control
+
+    property bool showLogoutSeconds: false
+
+    Rectangle {
+        id: __dialogArea
+        x: 551
+        y: 237
+        width: 818
+        height: 507
+
+        color: "#0A1929"
+        opacity: 0.85
+        radius: 20
+
+        Rectangle {
+            id: __iconBackground
+            x: 349
+            y: 81
+            width: 120
+            height: 120
+            radius: width / 2
+            color: "#C8C8C8"
+            opacity: 0.2
+
+            Text {
+                id: __iconLabel
+                anchors.centerIn: parent
+                text: "\uf05a"
+                color: "#1890FF"
+                opacity: 0.8
+                font.pixelSize: 60
+                font.family: iconFont.name
+                verticalAlignment: Text.AlignVCenter
+                horizontalAlignment: Text.AlignHCenter
+            }
+        }
+
+        Text {
+            x: 121
+            y: 241
+            width: 576
+            height: 58
+
+            text: qsTr("您没有正在执行的作业任务,请确认")
+            color: "white"
+
+            font.pixelSize: 36
+            // 普通文字不使用图标字体
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHCenter
+        }
+
+        Rectangle {
+            x: 121
+            y: 349
+            width: 576
+            height: 87
+            color: "transparent"
+
+            MButton {
+                x: 204
+                y: 10
+                width: 168
+                height: 77
+
+                buttonColor: "#1890FF"
+                btnRadius: 12
+
+                text: qsTr("退出")
+                textColor: "white"
+
+                onClicked: {
+                    // 退出返回首页
+                    appShowHome = true;
+                }
+            }
+
+            IconText {
+                id: __logoutSeconds
+                x: 382
+                y: 28
+                width: 85
+                height: 30
+                visible: appLogoutSeconds % defaultLogoutSeconds !== 0
+
+                text: appLogoutSeconds % defaultLogoutSeconds === 0 ? "" : qsTr("(" + appLogoutSeconds.toString() + "秒)")
+                textColor: "red"
+                iconCharacter: appLogoutSeconds % defaultLogoutSeconds === 0 ? "" : "\uf1da"
+                iconSize: 22
+                iconColor: "red"
+            }
+        }
+    }
+}

+ 604 - 46
src/qml/components/ReturnKeyLockProcess.qml

@@ -1,72 +1,630 @@
 import QtQuick 2.12
+import QtQuick.Controls 2.12
+import Loto 1.0
 
+/**
+ * 归还钥匙和锁处理页面
+ */
 Rectangle {
     id: control
+    width: parent ? parent.width : 1920
+    height: parent ? parent.height : 1080
+    color: "transparent"
+    visible: ReturnKeyLockManager.isVisible
 
-    anchors.fill: parent
+    // 数据
+    property var taskGroups: []
+    property var errorDevices: []
+    property var noTaskDevices: []
 
-    MBlurCard {
-        id: __blurCard
+    Component.onCompleted: {
+        console.log("[ReturnKeyLockProcess] ====== 组件加载完成 ======");
+        console.log("[ReturnKeyLockProcess] visible:", visible);
+        console.log("[ReturnKeyLockProcess] isVisible:", ReturnKeyLockManager.isVisible);
+        refreshData();
+    }
+    
+    onVisibleChanged: {
+        console.log("[ReturnKeyLockProcess] visible 变化:", visible);
+    }
+
+    function refreshData() {
+        taskGroups = ReturnKeyLockManager.getTaskGroups();
+        errorDevices = ReturnKeyLockManager.getErrorDevices();
+        noTaskDevices = ReturnKeyLockManager.getNoTaskDevices();
+    }
+
+    // 连接数据更新信号
+    Connections {
+        target: ReturnKeyLockManager
+        onDataUpdated: refreshData()
+    }
+
+    // 背景遮罩
+    Rectangle {
         anchors.fill: parent
-        blurSource: appBlurItem
-        blurAmount: 1.2
-        blurMax: 32
-        z: 1000
+        color: "#FF0000"
+        opacity: 0.8
+        
+        Text {
+            anchors.centerIn: parent
+            text: "背景遮罩测试 - 如果看到这个说明组件显示了"
+            color: "white"
+            font.pixelSize: 32
+        }
+    }
 
-        opacity: 1
-        scale: 1.0
+    // 主容器
+    Rectangle {
+        id: __container
+        x: 235
+        y: 156
+        width: 1450
+        height: 868
+
+        color: "#0A1929"
+        radius: 16
+        opacity: 0.95
+        border.color: "#40A9FF"
+        border.width: 1
+
+        // 标题
+        Text {
+            id: __dialogTitle
+            y: 30
+            anchors.horizontalCenter: parent.horizontalCenter
+            text: qsTr("归还钥匙和锁处理")
+            color: "#40A9FF"
+            font.pixelSize: 36
+            font.bold: true
+        }
 
+        // 分割线
         Rectangle {
-            anchors.fill: parent
+            x: 40
+            y: 80
+            width: parent.width - 80
+            height: 1
+            color: "#40A9FF"
+            opacity: 0.5
+        }
 
-            color: "#0A1929"
-            opacity: 0.85
-            radius: 16
-            border.color: "orange"
+        // 内容区域
+        Flickable {
+            id: __contentFlickable
+            x: 40
+            y: 95
+            width: parent.width - 80
+            height: 580
+            clip: true
+            contentHeight: __contentColumn.height + 20
+            flickableDirection: Flickable.VerticalFlick
 
-            Text {
-                id: __dialogTitle
-                y: 42
-                anchors.horizontalCenter: parent.horizontalCenter
+            ScrollBar.vertical: ScrollBar {
+                active: true
+                policy: ScrollBar.AsNeeded
+            }
 
-                text: qsTr("归还钥匙和锁处理")
-                color: "#1890FF"
-                opacity: 1
-                font.pixelSize: 64
-                font.family: iconFont.name
-                verticalAlignment: Text.AlignVCenter
-                horizontalAlignment: Text.AlignHCenter
+            Column {
+                id: __contentColumn
+                width: parent.width
+                spacing: 15
+
+                // ========== 归还状态标题 ==========
+                Text {
+                    text: "归还状态(按作业任务分组)"
+                    color: "#FF6B6B"
+                    font.pixelSize: 18
+                    font.bold: true
+                }
+
+                // ========== 正在读取状态 ==========
+                Rectangle {
+                    width: parent.width
+                    height: 50
+                    color: "#1A40A9FF"
+                    radius: 8
+                    visible: ReturnKeyLockManager.isProcessing
+
+                    Row {
+                        anchors.left: parent.left
+                        anchors.leftMargin: 20
+                        anchors.verticalCenter: parent.verticalCenter
+                        spacing: 15
+
+                        // 加载图标
+                        Rectangle {
+                            width: 24
+                            height: 24
+                            radius: 12
+                            color: "transparent"
+                            border.color: "#40A9FF"
+                            border.width: 2
+
+                            Rectangle {
+                                width: 6
+                                height: 6
+                                radius: 3
+                                color: "#40A9FF"
+                                anchors.horizontalCenter: parent.horizontalCenter
+                                y: 2
+
+                                RotationAnimator on rotation {
+                                    from: 0
+                                    to: 360
+                                    duration: 1000
+                                    loops: Animation.Infinite
+                                    running: ReturnKeyLockManager.isProcessing
+                                }
+                            }
+                        }
+
+                        Text {
+                            text: ReturnKeyLockManager.currentReadingStatus
+                            color: "white"
+                            font.pixelSize: 16
+                        }
+                    }
+
+                    Text {
+                        anchors.right: parent.right
+                        anchors.rightMargin: 20
+                        anchors.verticalCenter: parent.verticalCenter
+                        text: Qt.formatTime(new Date(), "hh:mm:ss")
+                        color: "#888888"
+                        font.pixelSize: 14
+                    }
+                }
+
+                // ========== 作业任务分组 ==========
+                Repeater {
+                    model: taskGroups
+
+                    delegate: Rectangle {
+                        width: __contentColumn.width
+                        height: taskCol.height + 30
+                        color: (modelData.hasFailure === true) ? "#1AFF4D4F" : "#1A40A9FF"
+                        radius: 8
+                        border.color: (modelData.hasFailure === true) ? "#FF4D4F" : "#40A9FF"
+                        border.width: 1
+
+                        Column {
+                            id: taskCol
+                            x: 20
+                            y: 15
+                            width: parent.width - 40
+                            spacing: 12
+
+                            // 任务标题行
+                            Row {
+                                width: parent.width
+                                spacing: 20
+
+                                Text {
+                                    text: modelData.taskName || "未知任务"
+                                    color: "#40A9FF"
+                                    font.pixelSize: 18
+                                    font.bold: true
+                                }
+
+                                Text {
+                                    text: "■ " + (modelData.orderNo || "")
+                                    color: "#888888"
+                                    font.pixelSize: 14
+                                }
+
+                                Text {
+                                    text: "📍 " + (modelData.workshop || "")
+                                    color: "#FF6B6B"
+                                    font.pixelSize: 14
+                                }
+
+                                Text {
+                                    text: "👤 " + (modelData.worker || "")
+                                    color: "white"
+                                    font.pixelSize: 14
+                                }
+
+                                Item { width: 20; height: 1 }
+
+                                // 状态标签
+                                Rectangle {
+                                    width: 60
+                                    height: 24
+                                    radius: 4
+                                    color: (modelData.hasFailure === true) ? "#FF4D4F" : "#52C41A"
+
+                                    Text {
+                                        anchors.centerIn: parent
+                                        text: (modelData.hasFailure === true) ? "有失败" : "已完成"
+                                        color: "white"
+                                        font.pixelSize: 12
+                                    }
+                                }
+                            }
+
+                            // 设备列表
+                            Repeater {
+                                model: modelData.devices || []
+
+                                delegate: Row {
+                                    spacing: 15
+                                    height: 40
+
+                                    // 状态图标
+                                    Rectangle {
+                                        width: 28
+                                        height: 28
+                                        radius: 14
+                                        color: modelData.status === "success" ? "#52C41A" : 
+                                               modelData.status === "failed" ? "#FF4D4F" : "#1890FF"
+                                        anchors.verticalCenter: parent.verticalCenter
+
+                                        Text {
+                                            anchors.centerIn: parent
+                                            text: modelData.status === "success" ? "✓" : 
+                                                  modelData.status === "failed" ? "✗" : "○"
+                                            color: "white"
+                                            font.pixelSize: 14
+                                            font.bold: true
+                                        }
+                                    }
+
+                                    // 设备名称
+                                    Text {
+                                        text: modelData.deviceType === "key" ? "钥匙" : ("锁仓 " + ((modelData.slotIndex || 0) + 1))
+                                        color: "white"
+                                        font.pixelSize: 16
+                                        font.bold: true
+                                        anchors.verticalCenter: parent.verticalCenter
+                                        width: 80
+                                    }
+
+                                    // 状态描述
+                                    Text {
+                                        text: modelData.statusMessage || ""
+                                        color: modelData.status === "success" ? "#52C41A" : 
+                                               modelData.status === "failed" ? "#FF4D4F" : "#888888"
+                                        font.pixelSize: 14
+                                        anchors.verticalCenter: parent.verticalCenter
+                                    }
+
+                                    // 时间
+                                    Text {
+                                        text: modelData.returnTime || ""
+                                        color: "#666666"
+                                        font.pixelSize: 14
+                                        anchors.verticalCenter: parent.verticalCenter
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+
+                // ========== 设备异常区域 ==========
+                Rectangle {
+                    width: parent.width
+                    height: errorCol.height + 30
+                    color: "#1AFF6B6B"
+                    radius: 8
+                    border.color: "#FF6B6B"
+                    border.width: 1
+                    visible: errorDevices.length > 0
+
+                    Column {
+                        id: errorCol
+                        x: 20
+                        y: 15
+                        width: parent.width - 40
+                        spacing: 10
+
+                        Row {
+                            spacing: 15
+
+                            Text {
+                                text: "设备异常"
+                                color: "#FF6B6B"
+                                font.pixelSize: 16
+                                font.bold: true
+                            }
+
+                            Rectangle {
+                                width: 50
+                                height: 22
+                                radius: 4
+                                color: "#FF6B6B"
+
+                                Text {
+                                    anchors.centerIn: parent
+                                    text: "异常"
+                                    color: "white"
+                                    font.pixelSize: 11
+                                }
+                            }
+                        }
+
+                        Text {
+                            text: "▲ 设备读取异常"
+                            color: "#FFAA00"
+                            font.pixelSize: 14
+                        }
+
+                        Repeater {
+                            model: errorDevices
+
+                            delegate: Row {
+                                spacing: 15
+                                height: 40
+
+                                Rectangle {
+                                    width: 28
+                                    height: 28
+                                    radius: 14
+                                    color: "#FFAA00"
+                                    anchors.verticalCenter: parent.verticalCenter
+
+                                    Text {
+                                        anchors.centerIn: parent
+                                        text: "⚠"
+                                        color: "white"
+                                        font.pixelSize: 14
+                                    }
+                                }
+
+                                Text {
+                                    text: modelData.deviceType === "key" ? "钥匙" : ("锁仓 " + ((modelData.slotIndex || 0) + 1))
+                                    color: "white"
+                                    font.pixelSize: 16
+                                    font.bold: true
+                                    anchors.verticalCenter: parent.verticalCenter
+                                    width: 80
+                                }
+
+                                Text {
+                                    text: "锁具设备异常(设备读取异常)"
+                                    color: "#FFAA00"
+                                    font.pixelSize: 14
+                                    anchors.verticalCenter: parent.verticalCenter
+                                }
+
+                                Text {
+                                    text: modelData.returnTime || ""
+                                    color: "#666666"
+                                    font.pixelSize: 14
+                                    anchors.verticalCenter: parent.verticalCenter
+                                }
+                            }
+                        }
+                    }
+                }
+
+                // ========== 无作业任务区域 ==========
+                Rectangle {
+                    width: parent.width
+                    height: noTaskCol.height + 30
+                    color: "#1A52C41A"
+                    radius: 8
+                    border.color: "#52C41A"
+                    border.width: 1
+                    visible: noTaskDevices.length > 0
+
+                    Column {
+                        id: noTaskCol
+                        x: 20
+                        y: 15
+                        width: parent.width - 40
+                        spacing: 10
+
+                        Row {
+                            spacing: 15
+
+                            Text {
+                                text: "无作业任务"
+                                color: "#52C41A"
+                                font.pixelSize: 16
+                                font.bold: true
+                            }
+
+                            Rectangle {
+                                width: 60
+                                height: 22
+                                radius: 4
+                                color: "#52C41A"
+
+                                Text {
+                                    anchors.centerIn: parent
+                                    text: "已完成"
+                                    color: "white"
+                                    font.pixelSize: 11
+                                }
+                            }
+                        }
+
+                        Text {
+                            text: "■ 无关联作业"
+                            color: "#888888"
+                            font.pixelSize: 14
+                        }
+
+                        Repeater {
+                            model: noTaskDevices
+
+                            delegate: Row {
+                                spacing: 15
+                                height: 40
+
+                                Rectangle {
+                                    width: 28
+                                    height: 28
+                                    radius: 14
+                                    color: modelData.status === "success" ? "#52C41A" : "#FF4D4F"
+                                    anchors.verticalCenter: parent.verticalCenter
+
+                                    Text {
+                                        anchors.centerIn: parent
+                                        text: modelData.status === "success" ? "✓" : "✗"
+                                        color: "white"
+                                        font.pixelSize: 14
+                                        font.bold: true
+                                    }
+                                }
+
+                                Text {
+                                    text: modelData.deviceType === "key" ? "钥匙" : ("锁仓 " + ((modelData.slotIndex || 0) + 1))
+                                    color: "white"
+                                    font.pixelSize: 16
+                                    font.bold: true
+                                    anchors.verticalCenter: parent.verticalCenter
+                                    width: 80
+                                }
+
+                                Text {
+                                    text: modelData.statusMessage || "归还成功(无作业任务)"
+                                    color: modelData.status === "success" ? "#52C41A" : "#FF4D4F"
+                                    font.pixelSize: 14
+                                    anchors.verticalCenter: parent.verticalCenter
+                                }
+
+                                Text {
+                                    text: modelData.returnTime || ""
+                                    color: "#666666"
+                                    font.pixelSize: 14
+                                    anchors.verticalCenter: parent.verticalCenter
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // ========== 分割线 ==========
+        Rectangle {
+            x: 40
+            y: 685
+            width: parent.width - 80
+            height: 1
+            color: "#40A9FF"
+            opacity: 0.3
+        }
+
+        // ========== 统计区域 ==========
+        Row {
+            x: 40
+            y: 700
+            width: parent.width - 80
+            spacing: 50
+
+            Repeater {
+                model: [
+                    { label: "作业任务", value: ReturnKeyLockManager.totalTasks, color: "white" },
+                    { label: "钥匙总数", value: ReturnKeyLockManager.totalKeys, color: "white" },
+                    { label: "钥匙成功", value: ReturnKeyLockManager.successKeys, color: "#52C41A" },
+                    { label: "钥匙失败", value: ReturnKeyLockManager.failedKeys, color: "#FF4D4F" },
+                    { label: "锁总数", value: ReturnKeyLockManager.totalLocks, color: "white" },
+                    { label: "锁成功", value: ReturnKeyLockManager.successLocks, color: "#52C41A" },
+                    { label: "锁失败", value: ReturnKeyLockManager.failedLocks, color: "#FF4D4F" }
+                ]
+
+                delegate: Column {
+                    spacing: 5
+                    Text { 
+                        text: modelData.label
+                        color: "#888888"
+                        font.pixelSize: 12
+                        anchors.horizontalCenter: parent.horizontalCenter
+                    }
+                    Text { 
+                        text: modelData.value.toString()
+                        color: modelData.color
+                        font.pixelSize: 28
+                        font.bold: true
+                        anchors.horizontalCenter: parent.horizontalCenter
+                    }
+                }
+            }
+        }
+
+        // ========== 底部按钮区域 ==========
+        Row {
+            anchors.bottom: parent.bottom
+            anchors.bottomMargin: 25
+            anchors.horizontalCenter: parent.horizontalCenter
+            spacing: 30
+
+            // 取出归还失败的钥匙/锁
+            Rectangle {
+                width: 280
+                height: 50
+                radius: 8
+                color: "transparent"
+                border.color: "#FF4D4F"
+                border.width: 2
+                visible: (ReturnKeyLockManager.failedKeys + ReturnKeyLockManager.failedLocks) > 0
+
+                Text {
+                    anchors.centerIn: parent
+                    text: "取出归还失败的钥匙/锁"
+                    color: "#FF4D4F"
+                    font.pixelSize: 18
+                }
+
+                MouseArea {
+                    anchors.fill: parent
+                    cursorShape: Qt.PointingHandCursor
+                    onClicked: ReturnKeyLockManager.retrieveFailedDevices()
+                }
             }
 
-            Flickable {
-                id: __colockView
-                anchors.horizontalCenter: parent.horizontalCenter
-                anchors.top: __dialogTitle.bottom
-                anchors.topMargin: 35
-                width: __taskColumn.width
-                height: __taskColumn.height + 30
+            // 确认归还按钮
+            Rectangle {
+                width: 200
+                height: 50
+                radius: 8
+                color: "#1890FF"
+                opacity: ReturnKeyLockManager.isProcessing ? 0.5 : 1
+
+                Row {
+                    anchors.centerIn: parent
+                    spacing: 10
 
-                clip: true
-                flickableDirection: Flickable.VerticalFlick
+                    Text {
+                        text: "确认归还"
+                        color: "white"
+                        font.pixelSize: 18
+                    }
 
-                contentWidth: __taskColumn.width
-                contentHeight: height
+                    Rectangle {
+                        width: 45
+                        height: 28
+                        radius: 14
+                        color: "#FF6B6B"
+                        visible: !ReturnKeyLockManager.isProcessing
 
-                Column {
-                    id: __taskColumn
-                    spacing: 15
+                        Text {
+                            anchors.centerIn: parent
+                            text: ReturnKeyLockManager.confirmCountdown + "秒"
+                            color: "white"
+                            font.pixelSize: 12
+                        }
+                    }
+                }
+
+                MouseArea {
+                    anchors.fill: parent
+                    cursorShape: Qt.PointingHandCursor
+                    enabled: !ReturnKeyLockManager.isProcessing
+                    onClicked: ReturnKeyLockManager.confirmReturn()
                 }
             }
         }
     }
+
+    // 阻止点击穿透
     MouseArea {
-        id: __mouseArea
         anchors.fill: parent
-        // 防鼠标事件穿透
-        onClicked: function(mouse) { mouse.accepted=true; }
-        onPressed: function(mouse) { mouse.accepted=true; }
-        onReleased: function(mouse) { mouse.accepted=true; }
-        onWheel: function(mouse) { mouse.accepted=true; }
-        onDoubleClicked: function(mouse) { mouse.accepted=true; }
+        onClicked: { }
+        onPressed: { }
     }
 }

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

@@ -71,7 +71,7 @@ Rectangle {
             color: "white"
             font.pixelSize: 18
             font.bold: true
-            font.family: iconFont.name
+            // 普通文字不使用图标字体
             verticalAlignment: Text.AlignVCenter
             horizontalAlignment: Text.AlignHCenter
         }

+ 4 - 4
src/qml/components/UpdatePasswordDialog.qml

@@ -96,7 +96,7 @@ Rectangle {
                     text: qsTr("当前密码")
                     color: "#B3C1D1"
                     font.pixelSize: 18
-                    font.family: iconFont.name
+                    // 普通文字不使用图标字体
                     font.bold: true
                     verticalAlignment: Text.AlignVCenter
                     horizontalAlignment: Text.AlignHCenter
@@ -145,7 +145,7 @@ Rectangle {
                     text: qsTr("新密码")
                     color: "#B3C1D1"
                     font.pixelSize: 18
-                    font.family: iconFont.name
+                    // 普通文字不使用图标字体
                     font.bold: true
                     verticalAlignment: Text.AlignVCenter
                     horizontalAlignment: Text.AlignHCenter
@@ -194,7 +194,7 @@ Rectangle {
                     text: qsTr("确认新密码")
                     color: "#B3C1D1"
                     font.pixelSize: 18
-                    font.family: iconFont.name
+                    // 普通文字不使用图标字体
                     font.bold: true
                     verticalAlignment: Text.AlignVCenter
                     horizontalAlignment: Text.AlignHCenter
@@ -299,7 +299,7 @@ Rectangle {
 
                     font.pixelSize: 18
                     font.bold: true
-                    font.family: iconFont.name
+                    // 普通文字不使用图标字体
 
                     color: "red"
 

+ 907 - 6
src/qml/main.qml

@@ -38,6 +38,82 @@ ApplicationWindow {
     property int currentIndex: 0
     property bool showHome: true
 
+    // ============ 首页刷卡登录相关属性 ============
+    property bool homeCardLoginLoading: false           // 首页刷卡登录loading状态
+    property bool homeCardLoginSuccess: false           // 首页刷卡登录成功标志
+    property var homeCardLoginErrorDialog: null         // 错误提示对话框对象
+    property bool homeCardLoginSkipLoginPage: false     // 首页刷卡登录成功后跳过登录页
+    
+    // ============ 导航栏刷新作业列表相关属性 ============
+    property bool navBarRefreshJobTickets: false        // 从导航栏刷新作业列表标志
+
+    // 首页刷卡登录:关闭错误提示对话框
+    function closeHomeCardLoginErrorDialog() {
+        console.log("[main.qml] 关闭首页刷卡登录错误提示");
+        if (homeCardLoginErrorDialog !== null) {
+            homeCardLoginErrorDialog.visible = false;
+            homeCardLoginErrorDialog.destroy();
+            homeCardLoginErrorDialog = null;
+        }
+        homeCardLoginErrorAutoCloseTimer.stop();
+    }
+
+    // 首页刷卡登录:显示错误提示对话框
+    function showHomeCardLoginErrorDialog(errorMsg) {
+        console.log("[main.qml] 显示首页刷卡登录错误提示:", errorMsg);
+        closeHomeCardLoginErrorDialog();
+
+        var component = Qt.createComponent("components/AlertDialog.qml");
+        if (component.status === Component.Ready) {
+            homeCardLoginErrorDialog = component.createObject(appAlertDialog, {
+                messageTypeName: "登录失败",
+                messageValue: errorMsg,
+                messageIconCharacter: "\uf071"
+            });
+            homeCardLoginErrorDialog.parent = appAlertDialog;
+            homeCardLoginErrorDialog.confirm.connect(function () {
+                console.log("[main.qml] 用户点击确认按钮");
+                closeHomeCardLoginErrorDialog();
+            });
+            homeCardLoginErrorAutoCloseTimer.restart();
+            console.log("[main.qml] 启动3秒自动关闭定时器");
+        }
+    }
+
+    // 首页刷卡登录:处理键盘输入
+    function homeSetCardIDByKey(keyValue) {
+        switch(keyValue) {
+        case Qt.Key_0: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "0"; break;
+        case Qt.Key_1: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "1"; break;
+        case Qt.Key_2: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "2"; break;
+        case Qt.Key_3: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "3"; break;
+        case Qt.Key_4: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "4"; break;
+        case Qt.Key_5: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "5"; break;
+        case Qt.Key_6: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "6"; break;
+        case Qt.Key_7: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "7"; break;
+        case Qt.Key_8: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "8"; break;
+        case Qt.Key_9: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "9"; break;
+        case Qt.Key_A: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "A"; break;
+        case Qt.Key_B: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "B"; break;
+        case Qt.Key_C: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "C"; break;
+        case Qt.Key_D: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "D"; break;
+        case Qt.Key_E: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "E"; break;
+        case Qt.Key_F: homeHttpCardLogin.cardID = homeHttpCardLogin.cardID + "F"; break;
+        case Qt.Key_Return: {
+            if (homeHttpCardLogin.cardID.length > 0) {
+                console.log("[main.qml] 首页刷卡回车,卡号:", homeHttpCardLogin.cardID);
+                closeHomeCardLoginErrorDialog();
+                console.log("[main.qml] 显示loading");
+                homeCardLoginLoading = true;
+                console.log("[main.qml] 启动登录线程");
+                homeHttpCardLogin.start();
+            }
+            return;
+        }
+        }
+    }
+    // ============================================
+
     MTheme {
         id: theme
     }
@@ -56,6 +132,152 @@ ApplicationWindow {
         }
     }
 
+    // ============ 首页刷卡登录HTTP组件 ============
+    HttpCardLogin {
+        id: homeHttpCardLogin
+
+        Component.onCompleted: {
+            // 首页刷卡登录信号连接(只在首页时生效)
+            connectHomeCardLoginSignals();
+        }
+
+        Component.onDestruction: {
+            disconnectHomeCardLoginSignals();
+        }
+    }
+
+    // 连接首页刷卡登录信号
+    function connectHomeCardLoginSignals() {
+        // 先断开再连接,避免重复连接
+        disconnectHomeCardLoginSignals();
+        homeHttpCardLogin.signalPostRequestData.connect(httpClientThread.slotPostRequestData);
+        httpClientThread.signalResponseCardLoginData.connect(homeHttpCardLogin.slotHttpResponseCardLogin);
+        console.log("[main.qml] 首页刷卡登录信号已连接");
+    }
+
+    // 断开首页刷卡登录信号
+    function disconnectHomeCardLoginSignals() {
+        try {
+            homeHttpCardLogin.signalPostRequestData.disconnect(httpClientThread.slotPostRequestData);
+        } catch(e) {}
+        try {
+            httpClientThread.signalResponseCardLoginData.disconnect(homeHttpCardLogin.slotHttpResponseCardLogin);
+        } catch(e) {}
+        console.log("[main.qml] 首页刷卡登录信号已断开");
+    }
+
+    Connections {
+        target: homeHttpCardLogin
+        function onSignalLoginReturnStat(stat, data) {
+            console.log("[main.qml] 首页刷卡登录响应,stat:", stat, ",data:", data);
+            homeCardLoginLoading = false;
+            console.log("[main.qml] 隐藏loading");
+
+            if (stat === 0) {
+                console.log("[main.qml] 首页刷卡登录成功!");
+                homeCardLoginSuccess = true;
+            } else {
+                console.log("[main.qml] 首页刷卡登录失败,显示错误提示");
+                showHomeCardLoginErrorDialog(data);
+            }
+        }
+    }
+
+    // 首页刷卡登录成功后获取作业票
+    HttpGetJobTickets {
+        id: homeHttpGetJobTickets
+
+        Component.onCompleted: {
+            // 首页获取作业票信号连接(只在首页刷卡登录成功时使用)
+            connectHomeGetJobTicketsSignals();
+        }
+
+        Component.onDestruction: {
+            disconnectHomeGetJobTicketsSignals();
+        }
+    }
+
+    // 连接首页获取作业票信号
+    function connectHomeGetJobTicketsSignals() {
+        // 先断开再连接,避免重复连接
+        disconnectHomeGetJobTicketsSignals();
+        homeHttpGetJobTickets.signalGetRequestData.connect(httpClientThread.slotGetRequestData);
+        httpClientThread.signalResponseGetJobTickets.connect(homeHttpGetJobTickets.slotHttpResponseGetJobTickets);
+        console.log("[main.qml] 首页获取作业票信号已连接");
+    }
+
+    // 断开首页获取作业票信号
+    function disconnectHomeGetJobTicketsSignals() {
+        try {
+            homeHttpGetJobTickets.signalGetRequestData.disconnect(httpClientThread.slotGetRequestData);
+        } catch(e) {}
+        try {
+            httpClientThread.signalResponseGetJobTickets.disconnect(homeHttpGetJobTickets.slotHttpResponseGetJobTickets);
+        } catch(e) {}
+        console.log("[main.qml] 首页获取作业票信号已断开");
+    }
+
+    Connections {
+        target: homeHttpGetJobTickets
+        function onSignalJobTicketsReturnStat(stat, msg) {
+            console.log("[main.qml] homeHttpGetJobTickets 返回: stat=" + stat + ", msg=" + msg + ", navBarRefresh=" + navBarRefreshJobTickets);
+            
+            // 从导航栏刷新的情况
+            if (navBarRefreshJobTickets) {
+                navBarRefreshJobTickets = false;
+                // 断开信号
+                disconnectHomeGetJobTicketsSignals();
+                
+                // stackView 结构:[广告页(0)] -> [Login.qml(1)] -> [JobTicketPage.qml(2)] -> [WorkingPage.qml(3)]
+                // 我们需要保留 [广告页] 和 [Login.qml],pop 掉后面的页面
+                // 使用 pop(item) 直接 pop 到 Login.qml(索引 1)
+                if (stackView.depth > 2) {
+                    // 获取 Login.qml 页面(索引 1),pop 到该页面
+                    var loginPage = stackView.get(1);
+                    if (loginPage) {
+                        stackView.pop(loginPage);
+                    }
+                }
+                
+                // 然后 push 列表页
+                if (stat !== 0 || JobTicketModel.rowCount() === 0) {
+                    stackView.push("components/NoJobTicketDialog.qml");
+                } else {
+                    stackView.push("JobTicketPage.qml");
+                }
+                return;
+            }
+            
+            // 首页刷卡登录的情况
+            if (stat !== 0 || JobTicketModel.rowCount() === 0) {
+                stackView.push("components/NoJobTicketDialog.qml");
+            } else {
+                stackView.push("JobTicketPage.qml");
+            }
+        }
+    }
+
+    // 首页刷卡登录成功处理
+    onHomeCardLoginSuccessChanged: {
+        if (homeCardLoginSuccess) {
+            console.log("[main.qml] 处理首页刷卡登录成功");
+            homeCardLoginLoading = false;
+            closeHomeCardLoginErrorDialog();
+            
+            // 设置标志:跳过登录页面
+            homeCardLoginSkipLoginPage = true;
+            
+            // 关闭首页,进入作业流程
+            showHome = false;
+            homeMouseArea.visible = false;
+            appShowLogout = true;
+            
+            // 重置标志
+            homeCardLoginSuccess = false;
+        }
+    }
+    // ============================================
+
     Component {
         id: adPage
 
@@ -201,7 +423,13 @@ ApplicationWindow {
                 iconColor: "#40a9ff"
 
                 onClicked: {
-                    stackView.push("JobTicketPage.qml");
+                    console.log("[main.qml] 导航栏点击作业按钮,跳转到列表页");
+                    // 直接替换当前页面为列表页,不重新请求数据,禁用自动跳转详情
+                    if (JobTicketModel.rowCount() === 0) {
+                        stackView.replace("components/NoJobTicketDialog.qml");
+                    } else {
+                        stackView.replace("JobTicketPage.qml", { autoNavigateToDetail: false });
+                    }
                 }
             }
             MButton {
@@ -303,6 +531,8 @@ ApplicationWindow {
             anchors.fill: parent
 
             onClicked: {
+                // 点击进入登录页面前,清空可能已输入的卡号
+                homeHttpCardLogin.cardID = "";
                 showHome = false;
                 homeMouseArea.visible = false;
             }
@@ -324,13 +554,610 @@ ApplicationWindow {
         // anchors.horizontalCenter: parent.horizontalCenter
     }
     
-    Loader {
+    // ========== 归还钥匙和锁处理页面(纯静态测试)==========
+    property bool showReturnPage: false
+    
+    Rectangle {
         id: __returnKeyLockProcess
-        visible: false
+        x: 0
+        y: 0
+        width: 1920
+        height: 1080
+        z: 99999
+        color: "#CC000000"
+        visible: showReturnPage
         
-        anchors.fill: parent
-        source: "components/ReturnKeyLockProcess.qml"
+        // 主容器
+        Rectangle {
+            x: 235
+            y: 106
+            width: 1450
+            height: 868
+            color: "#0D1B2A"
+            radius: 12
+            border.color: "#1E90FF"
+            border.width: 2
+            
+            // 标题栏
+            Rectangle {
+                x: 0
+                y: 0
+                width: parent.width
+                height: 60
+                color: "transparent"
+                
+                Text {
+                    anchors.centerIn: parent
+                    text: "归还锁具设备"
+                    color: "#1E90FF"
+                    font.pixelSize: 28
+                    font.bold: true
+                }
+                
+                // 关闭按钮
+                Rectangle {
+                    x: parent.width - 50
+                    y: 10
+                    width: 40
+                    height: 40
+                    radius: 8
+                    color: "#FF4D4F"
+                    Text {
+                        anchors.centerIn: parent
+                        text: "×"
+                        color: "white"
+                        font.pixelSize: 24
+                        font.bold: true
+                    }
+                    MouseArea {
+                        anchors.fill: parent
+                        onClicked: showReturnPage = false
+                    }
+                }
+            }
+            
+            // 标题分割线
+            Rectangle {
+                x: 20
+                y: 60
+                width: parent.width - 40
+                height: 1
+                color: "#1E90FF"
+                opacity: 0.5
+            }
+            
+            // 内容区域 - 可滚动
+            Flickable {
+                x: 20
+                y: 70
+                width: parent.width - 40
+                height: 580
+                clip: true
+                contentHeight: contentCol.height
+                flickableDirection: Flickable.VerticalFlick
+                
+                Column {
+                    id: contentCol
+                    width: parent.width
+                    spacing: 15
+                    
+                    // 小标题
+                    Text {
+                        text: "归还状态(按作业任务分组)"
+                        color: "#FF6B6B"
+                        font.pixelSize: 16
+                        font.bold: true
+                    }
+                    
+                    // ===== 作业任务卡片1 - 进行中(蓝色) =====
+                    Rectangle {
+                        width: parent.width
+                        height: 130
+                        color: "#1A1E90FF"
+                        radius: 8
+                        border.color: "#1E90FF"
+                        border.width: 1
+                        
+                        Column {
+                            x: 15
+                            y: 12
+                            width: parent.width - 30
+                            spacing: 10
+                            
+                            // 标题行:作业名称(主标题) + 任务名称(副标题) + 作业编号 + 发起人 + 发起时间
+                            Row {
+                                width: parent.width
+                                spacing: 20
+                                
+                                // 作业名称 - 主标题
+                                Text {
+                                    text: "1号产线停机维修作业"
+                                    color: "#1E90FF"
+                                    font.pixelSize: 16
+                                    font.bold: true
+                                }
+                                
+                                // 任务名称 - 副标题
+                                Text {
+                                    text: "电机隔离"
+                                    color: "#AAAAAA"
+                                    font.pixelSize: 14
+                                }
+                                
+                                // 作业编号 - 小号字体
+                                Text {
+                                    text: "ZY-2024-001234"
+                                    color: "#888888"
+                                    font.pixelSize: 12
+                                }
+                                
+                                // 发起人 - 小号字体
+                                Text {
+                                    text: "张三"
+                                    color: "#888888"
+                                    font.pixelSize: 12
+                                }
+                                
+                                // 发起时间 - 小号字体
+                                Text {
+                                    text: "2024-01-28 10:00"
+                                    color: "#888888"
+                                    font.pixelSize: 12
+                                }
+                                
+                                // 状态标签
+                                Rectangle {
+                                    width: 55
+                                    height: 20
+                                    radius: 3
+                                    color: "#1E90FF"
+                                    Text {
+                                        anchors.centerIn: parent
+                                        text: "进行中"
+                                        color: "white"
+                                        font.pixelSize: 11
+                                    }
+                                }
+                            }
+                            
+                            // 设备列表 - 横向排列
+                            Row {
+                                width: parent.width
+                                spacing: 25
+                                y: 5
+                                
+                                // 钥匙1
+                                Row {
+                                    spacing: 8
+                                    Rectangle {
+                                        width: 22; height: 22; radius: 11; color: "#FF4D4F"
+                                        Text { anchors.centerIn: parent; text: "✗"; color: "white"; font.pixelSize: 12; font.bold: true }
+                                    }
+                                    Text { text: "钥匙"; color: "white"; font.pixelSize: 13; font.bold: true }
+                                    Text { text: "归还失败"; color: "#FF4D4F"; font.pixelSize: 12 }
+                                }
+                                
+                                // 锁1
+                                Row {
+                                    spacing: 8
+                                    Rectangle {
+                                        width: 22; height: 22; radius: 11; color: "#FF4D4F"
+                                        Text { anchors.centerIn: parent; text: "✗"; color: "white"; font.pixelSize: 12; font.bold: true }
+                                    }
+                                    Text { text: "锁1"; color: "white"; font.pixelSize: 13; font.bold: true }
+                                    Text { text: "归还失败"; color: "#FF4D4F"; font.pixelSize: 12 }
+                                }
+                                
+                                // 锁2
+                                Row {
+                                    spacing: 8
+                                    Rectangle {
+                                        width: 22; height: 22; radius: 11; color: "#FF4D4F"
+                                        Text { anchors.centerIn: parent; text: "✗"; color: "white"; font.pixelSize: 12; font.bold: true }
+                                    }
+                                    Text { text: "锁2"; color: "white"; font.pixelSize: 13; font.bold: true }
+                                    Text { text: "归还失败"; color: "#FF4D4F"; font.pixelSize: 12 }
+                                }
+                            }
+                        }
+                    }
+                    
+                    // ===== 作业任务卡片2 - 已完成(绿色) =====
+                    Rectangle {
+                        width: parent.width
+                        height: 130
+                        color: "#1A52C41A"
+                        radius: 8
+                        border.color: "#52C41A"
+                        border.width: 1
+                        
+                        Column {
+                            x: 15
+                            y: 12
+                            width: parent.width - 30
+                            spacing: 10
+                            
+                            // 标题行
+                            Row {
+                                width: parent.width
+                                spacing: 20
+                                
+                                Text {
+                                    text: "2号产线设备维护作业"
+                                    color: "#52C41A"
+                                    font.pixelSize: 16
+                                    font.bold: true
+                                }
+                                
+                                Text {
+                                    text: "开关柜检修"
+                                    color: "#AAAAAA"
+                                    font.pixelSize: 14
+                                }
+                                
+                                Text {
+                                    text: "ZY-2024-005678"
+                                    color: "#888888"
+                                    font.pixelSize: 12
+                                }
+                                
+                                Text {
+                                    text: "李四"
+                                    color: "#888888"
+                                    font.pixelSize: 12
+                                }
+                                
+                                Text {
+                                    text: "2024-01-28 09:30"
+                                    color: "#888888"
+                                    font.pixelSize: 12
+                                }
+                                
+                                Rectangle {
+                                    width: 55
+                                    height: 20
+                                    radius: 3
+                                    color: "#52C41A"
+                                    Text {
+                                        anchors.centerIn: parent
+                                        text: "已完成"
+                                        color: "white"
+                                        font.pixelSize: 11
+                                    }
+                                }
+                            }
+                            
+                            Row {
+                                width: parent.width
+                                spacing: 25
+                                y: 5
+                                
+                                Row {
+                                    spacing: 8
+                                    Rectangle {
+                                        width: 22; height: 22; radius: 11; color: "#52C41A"
+                                        Text { anchors.centerIn: parent; text: "✓"; color: "white"; font.pixelSize: 12; font.bold: true }
+                                    }
+                                    Text { text: "钥匙"; color: "white"; font.pixelSize: 13; font.bold: true }
+                                    Text { text: "归还成功"; color: "#52C41A"; font.pixelSize: 12 }
+                                }
+                                
+                                Row {
+                                    spacing: 8
+                                    Rectangle {
+                                        width: 22; height: 22; radius: 11; color: "#52C41A"
+                                        Text { anchors.centerIn: parent; text: "✓"; color: "white"; font.pixelSize: 12; font.bold: true }
+                                    }
+                                    Text { text: "锁1"; color: "white"; font.pixelSize: 13; font.bold: true }
+                                    Text { text: "归还成功"; color: "#52C41A"; font.pixelSize: 12 }
+                                }
+                            }
+                        }
+                    }
+                    
+                    // ===== 设备异常卡片 (橙色) =====
+                    Rectangle {
+                        width: parent.width
+                        height: 100
+                        color: "#1AFF9800"
+                        radius: 8
+                        border.color: "#FF9800"
+                        border.width: 1
+                        
+                        Column {
+                            x: 15
+                            y: 12
+                            width: parent.width - 30
+                            spacing: 10
+                            
+                            Row {
+                                width: parent.width
+                                spacing: 20
+                                
+                                Text {
+                                    text: "设备异常"
+                                    color: "#FF9800"
+                                    font.pixelSize: 16
+                                    font.bold: true
+                                }
+                                
+                                Text {
+                                    text: "无法读取NFC信息"
+                                    color: "#AAAAAA"
+                                    font.pixelSize: 14
+                                }
+                                
+                                Rectangle {
+                                    width: 70
+                                    height: 20
+                                    radius: 3
+                                    color: "#FF9800"
+                                    Text {
+                                        anchors.centerIn: parent
+                                        text: "需处理"
+                                        color: "white"
+                                        font.pixelSize: 11
+                                    }
+                                }
+                            }
+                            
+                            Row {
+                                width: parent.width
+                                spacing: 25
+                                y: 5
+                                
+                                Row {
+                                    spacing: 8
+                                    Rectangle {
+                                        width: 22; height: 22; radius: 11; color: "#FF9800"
+                                        Text { anchors.centerIn: parent; text: "!"; color: "white"; font.pixelSize: 12; font.bold: true }
+                                    }
+                                    Text { text: "插槽3"; color: "white"; font.pixelSize: 13; font.bold: true }
+                                    Text { text: "归还失败"; color: "#FF9800"; font.pixelSize: 12 }
+                                }
+                            }
+                        }
+                    }
+                    
+                    // ===== 无作业任务卡片 (灰色) =====
+                    Rectangle {
+                        width: parent.width
+                        height: 100
+                        color: "#1A888888"
+                        radius: 8
+                        border.color: "#888888"
+                        border.width: 1
+                        
+                        Column {
+                            x: 15
+                            y: 12
+                            width: parent.width - 30
+                            spacing: 10
+                            
+                            Row {
+                                width: parent.width
+                                spacing: 20
+                                
+                                Text {
+                                    text: "无作业任务"
+                                    color: "#888888"
+                                    font.pixelSize: 16
+                                    font.bold: true
+                                }
+                                
+                                Text {
+                                    text: "未关联到任何作业"
+                                    color: "#AAAAAA"
+                                    font.pixelSize: 14
+                                }
+                                
+                                Rectangle {
+                                    width: 70
+                                    height: 20
+                                    radius: 3
+                                    color: "#888888"
+                                    Text {
+                                        anchors.centerIn: parent
+                                        text: "无关联"
+                                        color: "white"
+                                        font.pixelSize: 11
+                                    }
+                                }
+                            }
+                            
+                            Row {
+                                width: parent.width
+                                spacing: 25
+                                y: 5
+                                
+                                Row {
+                                    spacing: 8
+                                    Rectangle {
+                                        width: 22; height: 22; radius: 11; color: "#52C41A"
+                                        Text { anchors.centerIn: parent; text: "✓"; color: "white"; font.pixelSize: 12; font.bold: true }
+                                    }
+                                    Text { text: "钥匙"; color: "white"; font.pixelSize: 13; font.bold: true }
+                                    Text { text: "归还成功"; color: "#52C41A"; font.pixelSize: 12 }
+                                }
+                                
+                                Row {
+                                    spacing: 8
+                                    Rectangle {
+                                        width: 22; height: 22; radius: 11; color: "#52C41A"
+                                        Text { anchors.centerIn: parent; text: "✓"; color: "white"; font.pixelSize: 12; font.bold: true }
+                                    }
+                                    Text { text: "锁1"; color: "white"; font.pixelSize: 13; font.bold: true }
+                                    Text { text: "归还成功"; color: "#52C41A"; font.pixelSize: 12 }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            
+            // 底部分割线
+            Rectangle {
+                x: 20
+                y: 660
+                width: parent.width - 40
+                height: 1
+                color: "#1E90FF"
+                opacity: 0.3
+            }
+            
+            // 底部统计
+            Row {
+                x: 30
+                y: 675
+                spacing: 50
+                
+                Column {
+                    Text { text: "作业任务"; color: "#888888"; font.pixelSize: 12 }
+                    Text { text: "2"; color: "white"; font.pixelSize: 26; font.bold: true }
+                }
+                Column {
+                    Text { text: "钥匙总数"; color: "#888888"; font.pixelSize: 12 }
+                    Text { text: "3"; color: "white"; font.pixelSize: 26; font.bold: true }
+                }
+                Column {
+                    Text { text: "钥匙成功"; color: "#888888"; font.pixelSize: 12 }
+                    Text { text: "3"; color: "#52C41A"; font.pixelSize: 26; font.bold: true }
+                }
+                Column {
+                    Text { text: "钥匙失败"; color: "#888888"; font.pixelSize: 12 }
+                    Text { text: "0"; color: "#FF4D4F"; font.pixelSize: 26; font.bold: true }
+                }
+                Column {
+                    Text { text: "锁总数"; color: "#888888"; font.pixelSize: 12 }
+                    Text { text: "4"; color: "white"; font.pixelSize: 26; font.bold: true }
+                }
+                Column {
+                    Text { text: "锁成功"; color: "#888888"; font.pixelSize: 12 }
+                    Text { text: "3"; color: "#52C41A"; font.pixelSize: 26; font.bold: true }
+                }
+                Column {
+                    Text { text: "锁失败"; color: "#888888"; font.pixelSize: 12 }
+                    Text { text: "1"; color: "#FF4D4F"; font.pixelSize: 26; font.bold: true }
+                }
+                Column {
+                    Text { text: "设备异常"; color: "#888888"; font.pixelSize: 12 }
+                    Text { text: "1"; color: "#FF9800"; font.pixelSize: 26; font.bold: true }
+                }
+            }
+            
+            // 底部按钮
+            Row {
+                anchors.bottom: parent.bottom
+                anchors.bottomMargin: 20
+                anchors.horizontalCenter: parent.horizontalCenter
+                spacing: 25
+                
+                // 取出失败设备按钮
+                Rectangle {
+                    width: 240
+                    height: 45
+                    radius: 6
+                    color: "transparent"
+                    border.color: "#FF4D4F"
+                    border.width: 2
+                    
+                    Text {
+                        anchors.centerIn: parent
+                        text: "取出归还失败的设备"
+                        color: "#FF4D4F"
+                        font.pixelSize: 16
+                    }
+                    
+                    MouseArea {
+                        anchors.fill: parent
+                        onClicked: console.log("取出失败设备")
+                    }
+                }
+                
+                // 确认归还按钮
+                Rectangle {
+                    width: 180
+                    height: 45
+                    radius: 6
+                    color: "#1890FF"
+                    
+                    Row {
+                        anchors.centerIn: parent
+                        spacing: 8
+                        
+                        Text {
+                            text: "确认归还"
+                            color: "white"
+                            font.pixelSize: 16
+                        }
+                        
+                        Rectangle {
+                            width: 40
+                            height: 24
+                            radius: 12
+                            color: "#FF6B6B"
+                            
+                            Text {
+                                anchors.centerIn: parent
+                                text: "10s"
+                                color: "white"
+                                font.pixelSize: 11
+                            }
+                        }
+                    }
+                    
+                    MouseArea {
+                        anchors.fill: parent
+                        onClicked: showReturnPage = false
+                    }
+                }
+            }
+        }
+        
+        // 背景点击阻止穿透
+        MouseArea {
+            anchors.fill: parent
+            z: -1
+            onClicked: {}
+        }
+    }
+    
+    // ESC关闭弹窗
+    Shortcut {
+        sequence: "Escape"
+        onActivated: {
+            if (showReturnPage) {
+                console.log("[main.qml] ESC关闭静态弹窗");
+                showReturnPage = false;
+            }
+        }
+    }
+    
+    // F1 显示静态页面
+    Shortcut {
+        sequence: "F1"
+        onActivated: {
+            console.log("[main.qml] F1 显示静态归还页面");
+            showReturnPage = true;
+        }
+    }
+    // F2 也显示
+    Shortcut {
+        sequence: "F2"
+        onActivated: {
+            console.log("[main.qml] F2 显示静态归还页面");
+            showReturnPage = true;
+        }
     }
+    // F3 关闭
+    Shortcut {
+        sequence: "F3"
+        onActivated: {
+            console.log("[main.qml] F3 关闭静态归还页面");
+            showReturnPage = false;
+        }
+    }
+    // ============================================
 
     Timer {
         id: swipeTimer
@@ -345,10 +1172,32 @@ ApplicationWindow {
 
     onShowHomeChanged: {
         if (!showHome) {
-            stackView.push("Login.qml");
             swipeTimer.stop();
             appControlLogoutSecondsFlag = false
             __startLogoutTimer.restart();
+            // 离开首页时,重置首页刷卡登录状态
+            homeHttpCardLogin.cardID = "";
+            homeCardLoginLoading = false;
+            closeHomeCardLoginErrorDialog();
+            
+            // 检查是否是首页刷卡登录成功导致的页面切换
+            if (homeCardLoginSkipLoginPage) {
+                console.log("[main.qml] 首页刷卡登录成功,跳过登录页,直接获取作业票");
+                homeCardLoginSkipLoginPage = false;
+                // 首页刷卡登录成功,不 push Login.qml(避免信号冲突)
+                // 直接获取作业票
+                homeHttpGetJobTickets.start();
+            } else {
+                // 正常点击屏幕进入登录页,断开首页信号避免冲突
+                disconnectHomeCardLoginSignals();
+                disconnectHomeGetJobTicketsSignals();
+                // 进入登录页
+                stackView.push("Login.qml");
+                // 确保Login页面获取焦点
+                if (stackView.currentItem) {
+                    stackView.currentItem.forceActiveFocus();
+                }
+            }
         } else {
             stackView.clear();
             stackView.push(adPage.createObject(appWindow, {adImageSource: advertisementImageModel[0]}));
@@ -358,6 +1207,16 @@ ApplicationWindow {
             // 回到首页登录倒计时停止
             __startLogoutTimer.stop();
             __logoutCountTimer.stop();
+            // 回到首页时,重置首页刷卡登录状态并让键盘监听获取焦点
+            homeHttpCardLogin.cardID = "";
+            homeCardLoginLoading = false;
+            homeCardLoginSuccess = false;
+            homeCardLoginSkipLoginPage = false;
+            closeHomeCardLoginErrorDialog();
+            // 重新连接首页信号
+            connectHomeCardLoginSignals();
+            connectHomeGetJobTicketsSignals();
+            homeKeyboardListener.forceActiveFocus();
         }
     }
 
@@ -421,4 +1280,46 @@ ApplicationWindow {
             appLogoutSeconds--;
         }
     }
+
+    // ============ 首页刷卡登录:键盘监听 ============
+    // 让窗口获取焦点并监听键盘事件
+    Item {
+        id: homeKeyboardListener
+        focus: showHome  // 首页时获取焦点
+        Keys.enabled: showHome
+
+        Keys.onPressed: function(event) {
+            if (showHome) {
+                homeSetCardIDByKey(event.key);
+                event.accepted = true;  // 防止事件继续传递
+            }
+        }
+
+        Component.onCompleted: {
+            // 启动时让键盘监听获取焦点
+            if (showHome) {
+                forceActiveFocus();
+            }
+        }
+    }
+
+    // 首页刷卡登录错误提示自动关闭定时器(3秒)
+    Timer {
+        id: homeCardLoginErrorAutoCloseTimer
+        interval: 3000
+        running: false
+        repeat: false
+        onTriggered: {
+            console.log("[main.qml] 3秒超时,自动关闭错误提示");
+            closeHomeCardLoginErrorDialog();
+        }
+    }
+
+    // 首页刷卡登录Loading组件
+    LoadingDialog {
+        id: homeCardLoginLoadingDialog
+        loadingText: "登录中,请稍后..."
+        showLoading: homeCardLoginLoading
+    }
+    // ============================================
 }

+ 1 - 0
src/resources.qrc

@@ -1,5 +1,6 @@
 <RCC>
     <qresource prefix="/">
+        <file>icon.png</file>
         <file>resources/fonts/fontawesome-free-5.15.4-desktop/otfs/FontAwesome5Free-Solid-900.otf</file>
         <file>resources/png/a.jpeg</file>
         <file>resources/png/allsel.png</file>

+ 11 - 2
src/src.pro

@@ -66,14 +66,16 @@ HEADERS += \
     interactive/InteractiveData.h \
     interactive/InteractiveHttp.h \
     interactive/InteractiveFace.h \
-    interactive/InteractiveCAN.h
+    interactive/InteractiveCAN.h \
+    interactive/ReturnKeyLockManager.h
 
 SOURCES += \
     interactive/InteractiveTask.cpp \
     interactive/InteractiveData.cpp \
     interactive/InteractiveHttp.cpp \
     interactive/InteractiveFace.cpp \
-    interactive/InteractiveCAN.cpp
+    interactive/InteractiveCAN.cpp \
+    interactive/ReturnKeyLockManager.cpp
 
 # -------------------- usr 目录 --------------------
 HEADERS += \
@@ -104,10 +106,17 @@ qmldir.path = $${DESTDIR}/$${TARGET}Plugins
 COPIES += qmldir
 
 # ========== 安装配置 ==========
+# 远程设备部署路径配置
 qnx: target.path = /tmp/$${TARGET}/bin
 else: unix:!android: target.path = /opt/$${TARGET}/bin
 !isEmpty(target.path): INSTALLS += target
 
+# 远程部署额外配置(用于Qt Creator远程部署)
+DEPLOYMENT_DEPLOY_ROOT = /opt/$${TARGET}
+
+# 确保CAN权限(远程设备需要root权限操作CAN)
+# 部署后需在远程设备执行: sudo setcap cap_net_admin+eip /opt/Loto/bin/Loto
+
 ## QML插件安装:复制到目标目录的plugins子目录
 #plugin_install.files = $${DESTDIR}/plugins/liblotoplugin$${PLUGIN_SUFFIX}.so
 #plugin_install.path = $${target.path}/plugins

+ 250 - 21
src/usr/CANClient.cpp

@@ -8,6 +8,21 @@
 #include <QUuid>
 #include <QEventLoop>
 
+// ============ CAN通讯日志开关 ============
+// 取消下面的注释可以禁用CAN日志
+// #define CAN_LOG_DISABLED
+#ifdef CAN_LOG_DISABLED
+#undef qDebug
+#undef qInfo
+#undef qWarning
+#undef qCritical
+#define qDebug() QNoDebug()
+#define qInfo() QNoDebug()
+#define qWarning() QNoDebug()
+#define qCritical() QNoDebug()
+#endif
+// ========================================
+
 #define CAN_SLEEP_TIME 50
 
 // 十六进制转字符串
@@ -20,11 +35,47 @@ std::string hexToString(uint16_t hex) {
     return QString("%1").arg(hex, 4, 16, QChar('0')).toUpper().toStdString();
 }
 
+// ============ CAN接口配置模式选择 ============
+// 根据你的环境选择一种模式(取消注释对应的define)
+//
+// 模式1: CAN-USB适配器(本地开发,如CANable、PEAK-USB等)
+//        使用前需在终端执行:
+//        SLCAN类型: sudo slcand -o -c -s8 /dev/ttyACM0 can0 && sudo ip link set up can0
+//        原生类型:  sudo ip link set can0 type can bitrate 1000000 && sudo ip link set up can0
+// #define CAN_MODE_USB_ADAPTER
+
+// 模式2: 虚拟CAN(无硬件纯软件测试)
+//        使用前需执行: sudo modprobe vcan && sudo ip link add dev can0 type vcan && sudo ip link set up can0
+#define CAN_MODE_VIRTUAL
+
+// 模式3: 物理CAN(生产环境,板载CAN控制器)
+// #define CAN_MODE_PHYSICAL
+// ============================================
+
 CANClient::CANClient(QObject *parent)
     : QThread(parent)
 {
+    qWarning() << "========== CANClient 构造函数开始 ==========";
+    
+#if defined(CAN_MODE_VIRTUAL)
+    qInfo() << "[CAN] 模式: 虚拟CAN (软件测试)";
+    // 虚拟CAN不需要额外配置,假设已在终端创建好vcan接口
+#elif defined(CAN_MODE_USB_ADAPTER)
+    qInfo() << "[CAN] 模式: CAN-USB适配器 (本地开发)";
+    // CAN-USB适配器:假设已通过slcand或驱动配置好can0接口
+    // 仅尝试启动接口(如果尚未启动)
+    system("ip link set can0 up 2>/dev/null");
+#elif defined(CAN_MODE_PHYSICAL)
+    qInfo() << "[CAN] 模式: 物理CAN (生产环境)";
+    // 物理CAN:需要完整配置
     system("ifconfig can0 down");
     system("ip link set up can0 type can bitrate 1000000 restart-ms 100");
+#else
+    qWarning() << "[CAN] 警告: 未选择CAN模式,使用默认物理CAN配置";
+    system("ifconfig can0 down");
+    system("ip link set up can0 type can bitrate 1000000 restart-ms 100");
+#endif
+
     // 初始化插件
     pluginItemInit();
     // 初始化比特率和设备
@@ -81,21 +132,24 @@ CANClient::~CANClient()
 
 void CANClient::connectDevice()
 {
+    qInfo() << "[CAN] connectDevice() 开始连接...";
+    qInfo() << "[CAN] 插件:" << m_pluginsName << "接口:" << m_canInterfaceName;
+    
     QMutexLocker locker(&m_mutex);
     if (!m_CanDevice) {
-        qCritical() << "CAN设备实例为空,无法执行连接/断开操作!";
+        qCritical() << "[CAN] 设备实例为空,无法执行连接/断开操作!";
         return;
     }
 
     if (!m_canBusStatus) {
         // 连接CAN设备
         if (!m_CanDevice->connectDevice()) {
-            qCritical() << "打开CAN设备失败!原因:" << m_CanDevice->errorString();
+            qCritical() << "[CAN] 打开CAN设备失败!原因:" << m_CanDevice->errorString();
             emit signalCanBusStatusChanged(false);
             return;
         }
 
-        qInfo() << "CAN设备初始化完成!设备名:" << m_canInterfaceName;
+        qInfo() << "[CAN] ✓ 设备连接成功!接口:" << m_canInterfaceName;
 
         // 连接信号槽(避免重复连接)
         disconnect(m_CanDevice, &QCanBusDevice::framesReceived, this, &CANClient::receivedFrames);
@@ -159,12 +213,15 @@ void CANClient::pluginItemInit()
 
 void CANClient::bitrateItemInit()
 {
+    qWarning() << "[CAN] bitrateItemInit() 开始";
+    
     // 选择第一个可用的CAN接口,无则使用默认can0
     if (!m_canBusInterfaces.isEmpty()) {
         m_canInterfaceName = m_canBusInterfaces.first().name();
+        qWarning() << "[CAN] 使用检测到的接口:" << m_canInterfaceName;
     }
     else {
-        qWarning() << "无可用CAN设备接口,使用默认接口:" << m_canInterfaceName;
+        qWarning() << "[CAN] 无可用CAN设备接口,使用默认接口:" << m_canInterfaceName;
     }
 
     // 创建CAN设备实例
@@ -172,36 +229,58 @@ void CANClient::bitrateItemInit()
         delete m_CanDevice;
         m_CanDevice = nullptr;
     }
-    m_CanDevice = QCanBus::instance()->createDevice(m_pluginsName, m_canInterfaceName);
+    
+    qInfo() << "[CAN] 创建设备实例,插件:" << m_pluginsName << "接口:" << m_canInterfaceName;
+    
+    QString errorString;
+    m_CanDevice = QCanBus::instance()->createDevice(m_pluginsName, m_canInterfaceName, &errorString);
     if (!m_CanDevice) {
-        qCritical() << "创建CAN设备实例失败!插件:" << m_pluginsName
-                    << ",接口:" << m_canInterfaceName;
+        qCritical() << "[CAN] 创建设备实例失败!插件:" << m_pluginsName
+                    << ",接口:" << m_canInterfaceName
+                    << ",错误:" << errorString;
         return;
     }
+    
+    qInfo() << "[CAN] ✓ 设备实例创建成功";
 
     // 配置CAN设备参数
+#if defined(CAN_MODE_VIRTUAL)
+    // 虚拟CAN不需要设置比特率
+    qInfo() << "[CAN] 虚拟CAN模式,跳过比特率设置";
+#else
+    // 物理CAN需要设置比特率
     m_CanDevice->setConfigurationParameter(QCanBusDevice::BitRateKey, m_bitrate);
+#endif
     m_CanDevice->setConfigurationParameter(QCanBusDevice::LoopbackKey, false);       // 关闭回环
     m_CanDevice->setConfigurationParameter(QCanBusDevice::ReceiveOwnKey, false);    // 不接收自己发送的帧
-    // 配置错误过滤:仅监听关键错误
-    QFlags<QCanBusDevice::CanBusError> criticalErrors;
-//    criticalErrors |= QCanBusDevice::BusError;          // 总线错误
-    criticalErrors |= QCanBusDevice::ConnectionError;   // 连接错误
-    criticalErrors |= QCanBusDevice::ConfigurationError;// 配置错误
-    criticalErrors |= QCanBusDevice::OperationError;   // 控制器错误
-//    m_CanDevice->setConfigurationParameter(QCanBusDevice::ErrorFilterKey, criticalErrors);
 }
 
 void CANClient::receivedFrames()
 {
-    QMutexLocker locker(&m_mutex);
-    if (!m_CanDevice || m_CanDevice->state() == QCanBusDevice::UnconnectedState) {
-        return;
-    }
+    qDebug() << "[CAN] receivedFrames() 被调用";
+    
+    // 先收集所有帧(在锁内)
+    QList<QCanBusFrame> frames;
+    {
+        QMutexLocker locker(&m_mutex);
+        if (!m_CanDevice || m_CanDevice->state() == QCanBusDevice::UnconnectedState) {
+            qWarning() << "[CAN] 设备未连接,忽略帧";
+            return;
+        }
 
-    // 异步读取所有帧,无阻塞
-    while (m_CanDevice && m_CanDevice->framesAvailable()) {
-        QCanBusFrame respFrame = m_CanDevice->readFrame();
+        while (m_CanDevice && m_CanDevice->framesAvailable()) {
+            frames.append(m_CanDevice->readFrame());
+        }
+    }
+    
+    // 处理帧(在锁外,避免死锁)
+    for (const QCanBusFrame& respFrame : frames) {
+        qDebug() << "[CAN] 读取到帧";
+        
+        // ========== 检测设备主动上报的NFC数据(归还流程) ==========
+        // 当CAN设备检测到钥匙/锁插入时,会主动发送包含NFC卡号的帧
+        handleDeviceInsertionFrame(respFrame);
+        // =========================================================
 
         // 匹配请求并执行回调
         QMutexLocker reqLocker(&m_requestMutex);
@@ -242,11 +321,130 @@ void CANClient::canDeviceErrors(QCanBusDevice::CanBusError error) const
     }
 }
 
+void CANClient::handleDeviceInsertionFrame(const QCanBusFrame& frame)
+{
+    // ========== 检测设备主动上报的NFC数据 ==========
+    // 
+    // TODO: 根据实际CAN协议调整帧ID和数据格式
+    // 当前假设设备会发送包含NFC卡号的PDO帧
+    //
+    // 常见的帧格式(需根据实际设备协议确认):
+    // - 帧ID: 0x180 + NodeID (TPDO1) 或自定义ID
+    // - 数据: [设备类型, 插槽, NFC卡号(4字节)]
+    //
+    // 调试时可以打开下面的日志查看收到的帧
+    // ================================================
+    
+    if (!frame.isValid()) return;
+    
+    quint32 frameId = frame.frameId();
+    QByteArray payload = frame.payload();
+    
+    // 调试日志:显示所有收到的CAN帧
+    qDebug() << "[CAN收帧] ID:" << QString("0x%1").arg(frameId, 3, 16, QChar('0'))
+             << "Data:" << payload.toHex().toUpper();
+    
+    // ========== 方案1: 检测SDO响应中的NFC数据 ==========
+    // 当收到包含NFC索引的SDO响应时触发
+    if (frameId >= 0x581 && frameId <= 0x5FF && payload.length() >= 8) {
+        quint8 cmd = static_cast<quint8>(payload[0]);
+        quint16 index = static_cast<quint16>(static_cast<uint8_t>(payload[1])) |
+                        (static_cast<quint16>(static_cast<uint8_t>(payload[2])) << 8);
+        quint8 subIndex = static_cast<quint8>(payload[3]);
+        
+        qDebug() << "[CAN解析] cmd:" << QString("0x%1").arg(cmd, 2, 16, QChar('0'))
+                 << "index:" << QString("0x%1").arg(index, 4, 16, QChar('0'))
+                 << "subIndex:" << subIndex;
+        
+        // 检测钥匙NFC数据 (索引 0x6020=左钥匙, 0x6024=右钥匙)
+        if ((index == 0x6020 || index == 0x6024) && (cmd == 0x43 || cmd == 0x4B || cmd == 0x4F)) {
+            QByteArray nfcData = payload.mid(4, 4);
+            QString nfcId = parseRFIDCard(nfcData);
+            
+            if (!nfcId.isEmpty() && nfcId != "00000000") {
+                int slotIndex = (index == 0x6020) ? 0 : 1;  // 左=0, 右=1
+                qInfo() << "[归还检测-CAN帧] 检测到钥匙NFC:" << nfcId << "插槽:" << slotIndex;
+                emit nfcDeviceDetected(nfcId, "key", slotIndex);
+            }
+        }
+        
+        // 检测锁NFC数据 (索引 0x60F0-0x60F4 或根据实际协议)
+        if (index >= 0x60F0 && index <= 0x60F4 && (cmd == 0x43 || cmd == 0x4B || cmd == 0x4F)) {
+            QByteArray nfcData = payload.mid(4, 4);
+            QString nfcId = parseRFIDCard(nfcData);
+            
+            if (!nfcId.isEmpty() && nfcId != "00000000") {
+                int slotIndex = index - 0x60F0;  // 0-4号锁
+                qInfo() << "[归还检测-CAN帧] 检测到锁NFC:" << nfcId << "插槽:" << slotIndex;
+                emit nfcDeviceDetected(nfcId, "lock", slotIndex);
+            }
+        }
+    }
+    
+    // ========== 方案2: 检测自定义PDO帧(根据实际协议调整) ==========
+    // 如果设备使用自定义帧ID上报,在这里添加检测逻辑
+    // 例如: 帧ID = 0x200 表示钥匙插入事件
+    //
+    // if (frameId == 0x200 && payload.length() >= 5) {
+    //     quint8 deviceType = static_cast<quint8>(payload[0]);  // 0=钥匙, 1=锁
+    //     quint8 slotIndex = static_cast<quint8>(payload[1]);
+    //     QByteArray nfcData = payload.mid(2, 4);
+    //     QString nfcId = parseRFIDCard(nfcData);
+    //     
+    //     if (!nfcId.isEmpty() && nfcId != "00000000") {
+    //         QString type = (deviceType == 0) ? "key" : "lock";
+    //         qInfo() << "[归还检测-PDO] 设备插入:" << type << "插槽:" << slotIndex << "NFC:" << nfcId;
+    //         emit nfcDeviceDetected(nfcId, type, slotIndex);
+    //     }
+    // }
+}
+
 void CANClient::slotUpdateKeyBaseStatus(quint8 nodeId, const CANKeyBaseStatus &status)
 {
     if (!status.success) return;
 
     QMutexLocker locker(&m_insertDataMutex);
+    
+    // ========== 检测钥匙插入(用于归还流程) ==========
+    bool prevLeftHasKey = false;
+    bool prevRightHasKey = false;
+    
+    if (m_keyBaseStatus.find(nodeId) != m_keyBaseStatus.end()) {
+        prevLeftHasKey = m_keyBaseStatus[nodeId].leftHasKey;
+        prevRightHasKey = m_keyBaseStatus[nodeId].rightHasKey;
+    }
+    
+    // 检测左钥匙插入(从无到有)
+    if (!prevLeftHasKey && status.leftHasKey) {
+        qInfo() << "[归还检测] 检测到左钥匙插入!";
+        // 获取左钥匙NFC卡号
+        if (m_keyRFIDStatus.find(nodeId) != m_keyRFIDStatus.end() && 
+            !m_keyRFIDStatus[nodeId].leftKeyRFID.isEmpty() &&
+            m_keyRFIDStatus[nodeId].leftKeyRFID != "00000000") {
+            qInfo() << "[归还检测] 左钥匙NFC卡号:" << m_keyRFIDStatus[nodeId].leftKeyRFID;
+            emit nfcDeviceDetected(m_keyRFIDStatus[nodeId].leftKeyRFID, "key", 0);
+        } else {
+            qWarning() << "[归还检测] 左钥匙存在但无法读取NFC";
+            emit nfcDeviceError("key", 0);
+        }
+    }
+    
+    // 检测右钥匙插入(从无到有)
+    if (!prevRightHasKey && status.rightHasKey) {
+        qInfo() << "[归还检测] 检测到右钥匙插入!";
+        // 获取右钥匙NFC卡号
+        if (m_keyRFIDStatus.find(nodeId) != m_keyRFIDStatus.end() && 
+            !m_keyRFIDStatus[nodeId].rightKeyRFID.isEmpty() &&
+            m_keyRFIDStatus[nodeId].rightKeyRFID != "00000000") {
+            qInfo() << "[归还检测] 右钥匙NFC卡号:" << m_keyRFIDStatus[nodeId].rightKeyRFID;
+            emit nfcDeviceDetected(m_keyRFIDStatus[nodeId].rightKeyRFID, "key", 1);
+        } else {
+            qWarning() << "[归还检测] 右钥匙存在但无法读取NFC";
+            emit nfcDeviceError("key", 1);
+        }
+    }
+    // ====================================================
+    
     if (m_keyBaseStatus.find(nodeId) == m_keyBaseStatus.end()) {
         m_keyBaseStatus.insert(nodeId, status);
     }
@@ -741,7 +939,38 @@ void CANClient::readLockStatusAsync()
             }
 //            qDebug() << "FrameId: 0x" << QString::number(respFrame.frameId(), 16) << "Data:" << respFrame.payload().toHex().toUpper();
             status.success = true;
+            
+            // ========== 检测锁插入(用于归还流程) ==========
             QMutexLocker locker(&m_insertDataMutex);
+            
+            // 获取之前的锁状态
+            QMap<int, bool> prevLockHasKeyMap;
+            if (m_locksControlStatus.find(nodeId) != m_locksControlStatus.end()) {
+                prevLockHasKeyMap = m_locksControlStatus[nodeId].lockHasKeyMap;
+            }
+            
+            // 检测每个锁位的插入(从无到有)
+            for (int i = 0; i < 5; i++) {
+                bool prevHasLock = prevLockHasKeyMap.contains(i) ? prevLockHasKeyMap[i] : false;
+                bool currHasLock = status.lockHasKeyMap.contains(i) ? status.lockHasKeyMap[i] : false;
+                
+                if (!prevHasLock && currHasLock) {
+                    qInfo() << "[归还检测] 检测到" << (i+1) << "号锁插入!";
+                    // 锁插入,获取NFC卡号
+                    if (m_locksRFIDStatus.find(nodeId) != m_locksRFIDStatus.end() &&
+                        m_locksRFIDStatus[nodeId].lockRFIDMap.contains(i) &&
+                        !m_locksRFIDStatus[nodeId].lockRFIDMap[i].isEmpty() &&
+                        m_locksRFIDStatus[nodeId].lockRFIDMap[i] != "00000000") {
+                        qInfo() << "[归还检测]" << (i+1) << "号锁NFC卡号:" << m_locksRFIDStatus[nodeId].lockRFIDMap[i];
+                        emit nfcDeviceDetected(m_locksRFIDStatus[nodeId].lockRFIDMap[i], "lock", i);
+                    } else {
+                        qWarning() << "[归还检测]" << (i+1) << "号锁存在但无法读取NFC";
+                        emit nfcDeviceError("lock", i);
+                    }
+                }
+            }
+            // ====================================================
+            
             if (m_locksControlStatus.find(nodeId) == m_locksControlStatus.end()) {
                 m_locksControlStatus.insert(nodeId, status);
             }

+ 11 - 0
src/usr/CANClient.h

@@ -201,7 +201,18 @@ signals:
 
     void workingKeyChanged();
     void workingLocksChanged();
+    
+    // ========== 归还钥匙和锁检测信号 ==========
+    // 当检测到NFC设备插入时发出(用于归还流程)
+    void nfcDeviceDetected(const QString& nfcId, const QString& deviceType, int slotIndex);
+    // 当检测到设备异常时发出(无法读取NFC)
+    void nfcDeviceError(const QString& deviceType, int slotIndex);
+    // ==========================================
+    
 private:
+    // 处理设备主动上报的插入帧(归还流程)
+    void handleDeviceInsertionFrame(const QCanBusFrame& frame);
+    
     // 构造SDO读报文
     QCanBusFrame buildReadFrame(quint8 nodeId, quint16 index, quint8 subIndex);
 

+ 4 - 0
src/usr/LotoQmlPlugin.cpp

@@ -20,6 +20,7 @@
 #include "../interactive/InteractiveHttp.h"
 #include "../interactive/InteractiveTask.h"
 #include "../interactive/InteractiveCAN.h"
+#include "../interactive/ReturnKeyLockManager.h"
 
 void LotoQmlPlugin::registerTypes(const char *uri)
 {
@@ -91,4 +92,7 @@ void LotoQmlTypes::registerTypes()
     qmlRegisterSingletonType<WorkNodeFormModel>(uri, majorVersion, minorVersion, "WorkNodeFormModel", &WorkNodeFormModel::create);
 
     qmlRegisterSingletonType<InteractiveCAN>(uri, majorVersion, minorVersion, "InteractiveCAN", &InteractiveCAN::create);
+    
+    // 注册归还钥匙和锁管理器
+    qmlRegisterSingletonType<ReturnKeyLockManager>(uri, majorVersion, minorVersion, "ReturnKeyLockManager", &ReturnKeyLockManager::create);
 }