浏览代码

Merge branch 'feature-20200316-fs' into master-t

Michael Wang 5 年之前
父节点
当前提交
8407917392
共有 39 个文件被更改,包括 677 次插入170 次删除
  1. 1 0
      .env.development
  2. 2 2
      .env.production
  3. 1 0
      .env.staging
  4. 1 0
      .env.stagingAli
  5. 二进制
      public/favicon.ico
  6. 二进制
      public/img/icons/android-chrome-192x192.png
  7. 二进制
      public/img/icons/android-chrome-384x384.png
  8. 二进制
      public/img/icons/android-chrome-512x512.png
  9. 二进制
      public/img/icons/apple-touch-icon-120x120.png
  10. 二进制
      public/img/icons/apple-touch-icon-152x152.png
  11. 二进制
      public/img/icons/apple-touch-icon-180x180.png
  12. 二进制
      public/img/icons/apple-touch-icon-60x60.png
  13. 二进制
      public/img/icons/apple-touch-icon-76x76.png
  14. 二进制
      public/img/icons/apple-touch-icon.png
  15. 二进制
      public/img/icons/favicon-16x16.png
  16. 二进制
      public/img/icons/favicon-32x32.png
  17. 二进制
      public/img/icons/favicon.ico
  18. 二进制
      public/img/icons/msapplication-icon-144x144.png
  19. 二进制
      public/img/icons/mstile-150x150.png
  20. 15 138
      public/img/icons/safari-pinned-tab.svg
  21. 6 6
      public/manifest.json
  22. 二进制
      src/assets/logo.png
  23. 27 3
      src/components/FaceRecognition/FaceRecognition.vue
  24. 30 9
      src/components/MainLayout/MainLayout.vue
  25. 4 1
      src/components/MainLayout/SiteMessagePopup.vue
  26. 二进制
      src/components/MainLayout/epcc-logo.png
  27. 4 1
      src/constants/constants.js
  28. 90 4
      src/features/Login/Login.vue
  29. 5 1
      src/features/OfflineExam/OfflineExamHome.vue
  30. 15 1
      src/features/OfflineExam/OfflineExamList.vue
  31. 406 0
      src/features/OfflineExam/OfflineExamUploadCug.vue
  32. 47 0
      src/features/OfflineExam/imageToPdf.js
  33. 2 0
      src/features/OnlineExam/Examing/ArrowNavView.vue
  34. 1 0
      src/features/OnlineExam/Examing/ExamingEnd.vue
  35. 1 0
      src/features/OnlineExam/Examing/QuestionNavView.vue
  36. 4 2
      src/features/OnlineExam/Examing/TextQuestionView.vue
  37. 1 0
      src/features/SiteMessage/SiteMessageHome.vue
  38. 5 1
      src/store.js
  39. 9 1
      src/utils/nativeExe.js

+ 1 - 0
.env.development

@@ -1,3 +1,4 @@
+VUE_APP_SKIP_CHECK_NATIVE=true
 VUE_APP_TK_SERVER_HTML_URL=http://192.168.10.38:5220
 VUE_APP_TK_SERVER_API_URL=http://192.168.10.38:8008
 VUE_APP_WK_SERVER_SOCKET=wss://wuhan-dev.qmth.com.cn:8878/api/ws/faceBiopsy

+ 2 - 2
.env.production

@@ -1,5 +1,5 @@
 VUE_APP_TK_SERVER_HTML_URL=http://ecs.qmth.com.cn
 VUE_APP_TK_SERVER_API_URL=http://ecs.qmth.com.cn
-VUE_APP_WK_SERVER_SOCKET=wss://ecs.qmth.com.cn:8878/oewebsocket/
-VUE_APP_WK_SERVER_SOCKET_FOR_AUDIO=wss://ecs.qmth.com.cn:8878/oewebsocket/fileAnswer/
+VUE_APP_WK_SERVER_SOCKET=wss://ecs.qmth.com.cn:8878/api/ws/faceBiopsy
+VUE_APP_WK_SERVER_SOCKET_FOR_AUDIO=wss://ecs.qmth.com.cn:8878/api/ws/fileAnswer
 VUE_APP_GIT_REPO_VERSION=TO_BE_OVERRIDED

+ 1 - 0
.env.staging

@@ -1,3 +1,4 @@
+VUE_APP_SKIP_CHECK_NATIVE=true
 VUE_APP_TK_SERVER_HTML_URL=http://192.168.10.39:5220
 VUE_APP_TK_SERVER_API_URL=http://192.168.10.39:8008
 VUE_APP_WK_SERVER_SOCKET=wss://wuhan-test.qmth.com.cn:8878/api/ws/faceBiopsy

+ 1 - 0
.env.stagingAli

@@ -1,3 +1,4 @@
+VUE_APP_SKIP_CHECK_NATIVE=true
 VUE_APP_TK_SERVER_HTML_URL=http://ecs-test.qmth.com.cn
 VUE_APP_TK_SERVER_API_URL=http://ecs-test.qmth.com.cn
 VUE_APP_WK_SERVER_SOCKET=wss://ecs-test.qmth.com.cn:8878/api/ws/faceBiopsy

二进制
public/favicon.ico


二进制
public/img/icons/android-chrome-192x192.png


二进制
public/img/icons/android-chrome-384x384.png


二进制
public/img/icons/android-chrome-512x512.png


二进制
public/img/icons/apple-touch-icon-120x120.png


二进制
public/img/icons/apple-touch-icon-152x152.png


二进制
public/img/icons/apple-touch-icon-180x180.png


二进制
public/img/icons/apple-touch-icon-60x60.png


二进制
public/img/icons/apple-touch-icon-76x76.png


二进制
public/img/icons/apple-touch-icon.png


二进制
public/img/icons/favicon-16x16.png


二进制
public/img/icons/favicon-32x32.png


二进制
public/img/icons/favicon.ico


二进制
public/img/icons/msapplication-icon-144x144.png


二进制
public/img/icons/mstile-150x150.png


+ 15 - 138
public/img/icons/safari-pinned-tab.svg

@@ -2,148 +2,25 @@
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
  "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
 <svg version="1.0" xmlns="http://www.w3.org/2000/svg"
- width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
+ width="500.000000pt" height="500.000000pt" viewBox="0 0 500.000000 500.000000"
  preserveAspectRatio="xMidYMid meet">
 <metadata>
 Created by potrace 1.11, written by Peter Selinger 2001-2013
 </metadata>
-<g transform="translate(0.000000,16.000000) scale(0.000320,-0.000320)"
+<g transform="translate(0.000000,500.000000) scale(0.100000,-0.100000)"
 fill="#000000" stroke="none">
-<path d="M18 46618 c45 -75 122 -207 122 -211 0 -2 25 -45 55 -95 30 -50 55
--96 55 -102 0 -5 5 -10 10 -10 6 0 10 -4 10 -9 0 -5 73 -135 161 -288 89 -153
-173 -298 187 -323 14 -25 32 -57 41 -72 88 -149 187 -324 189 -335 2 -7 8 -13
-13 -13 5 0 9 -4 9 -10 0 -5 46 -89 103 -187 175 -302 490 -846 507 -876 8 -16
-20 -36 25 -45 28 -46 290 -498 339 -585 13 -23 74 -129 136 -236 61 -107 123
--215 137 -240 14 -25 29 -50 33 -56 5 -5 23 -37 40 -70 18 -33 38 -67 44 -75
-11 -16 21 -33 63 -109 14 -25 29 -50 33 -56 4 -5 21 -35 38 -65 55 -100 261
--455 269 -465 4 -5 14 -21 20 -35 15 -29 41 -75 103 -180 24 -41 52 -88 60
--105 9 -16 57 -100 107 -185 112 -193 362 -626 380 -660 8 -14 23 -38 33 -55
-11 -16 23 -37 27 -45 4 -8 26 -46 48 -85 23 -38 53 -90 67 -115 46 -81 64
--113 178 -310 62 -107 121 -210 132 -227 37 -67 56 -99 85 -148 16 -27 32 -57
-36 -65 4 -8 15 -27 25 -42 9 -15 53 -89 96 -165 44 -76 177 -307 296 -513 120
--206 268 -463 330 -570 131 -227 117 -203 200 -348 36 -62 73 -125 82 -140 10
--15 21 -34 25 -42 4 -8 20 -37 36 -65 17 -27 38 -65 48 -82 49 -85 64 -111 87
--153 13 -25 28 -49 32 -55 4 -5 78 -134 165 -285 87 -151 166 -288 176 -305
-10 -16 26 -43 35 -59 9 -17 125 -217 257 -445 132 -229 253 -441 270 -471 17
--30 45 -79 64 -108 18 -29 33 -54 33 -57 0 -2 20 -37 44 -77 24 -40 123 -212
-221 -383 97 -170 190 -330 205 -355 16 -25 39 -65 53 -90 13 -25 81 -144 152
--265 70 -121 137 -238 150 -260 12 -22 37 -65 55 -95 18 -30 43 -73 55 -95 12
--22 48 -85 80 -140 77 -132 163 -280 190 -330 13 -22 71 -123 130 -225 59
--102 116 -199 126 -217 10 -17 29 -50 43 -72 15 -22 26 -43 26 -45 0 -2 27
--50 60 -106 33 -56 60 -103 60 -105 0 -2 55 -98 90 -155 8 -14 182 -316 239
--414 13 -22 45 -79 72 -124 27 -46 49 -86 49 -89 0 -2 14 -24 30 -48 16 -24
-30 -46 30 -49 0 -5 74 -135 100 -176 5 -8 24 -42 43 -75 50 -88 58 -101 262
--455 104 -179 199 -345 213 -370 14 -25 28 -49 32 -55 4 -5 17 -26 28 -45 10
--19 62 -109 114 -200 114 -197 133 -230 170 -295 16 -27 33 -57 38 -65 17 -28
-96 -165 103 -180 4 -8 16 -28 26 -45 10 -16 77 -131 148 -255 72 -124 181
--313 243 -420 62 -107 121 -209 131 -227 35 -62 323 -560 392 -678 38 -66 83
--145 100 -175 16 -30 33 -59 37 -65 4 -5 17 -27 29 -47 34 -61 56 -100 90
--156 17 -29 31 -55 31 -57 0 -2 17 -32 39 -67 21 -35 134 -229 251 -433 117
--203 235 -407 261 -451 27 -45 49 -85 49 -88 0 -4 8 -19 19 -34 15 -21 200
--341 309 -533 10 -19 33 -58 51 -87 17 -29 31 -54 31 -56 0 -2 25 -44 55 -94
-30 -50 55 -95 55 -98 0 -4 6 -15 14 -23 7 -9 27 -41 43 -71 17 -30 170 -297
-342 -594 171 -296 311 -542 311 -547 0 -5 5 -9 10 -9 6 0 10 -4 10 -10 0 -5
-22 -47 49 -92 27 -46 58 -99 68 -118 24 -43 81 -140 93 -160 5 -8 66 -114 135
--235 69 -121 130 -227 135 -235 12 -21 259 -447 283 -490 10 -19 28 -47 38
--62 11 -14 19 -29 19 -32 0 -3 37 -69 83 -148 99 -170 305 -526 337 -583 13
--22 31 -53 41 -70 11 -16 22 -37 26 -45 7 -14 82 -146 103 -180 14 -24 181
--311 205 -355 13 -22 46 -80 75 -130 29 -49 64 -110 78 -135 14 -25 51 -88 82
--140 31 -52 59 -102 63 -110 4 -8 18 -33 31 -55 205 -353 284 -489 309 -535
-17 -30 45 -78 62 -106 18 -28 36 -60 39 -72 4 -12 12 -22 17 -22 5 0 9 -4 9
--10 0 -5 109 -197 241 -427 133 -230 250 -431 259 -448 51 -90 222 -385 280
--485 37 -63 78 -135 92 -160 14 -25 67 -117 118 -205 51 -88 101 -175 111
--193 34 -58 55 -95 149 -257 51 -88 101 -173 110 -190 9 -16 76 -131 147 -255
-72 -124 140 -241 151 -260 61 -108 281 -489 355 -615 38 -66 77 -133 87 -150
-35 -63 91 -161 100 -175 14 -23 99 -169 128 -220 54 -97 135 -235 142 -245 4
--5 20 -32 35 -60 26 -48 238 -416 276 -480 10 -16 26 -46 37 -65 30 -53 382
--661 403 -695 10 -16 22 -37 26 -45 4 -8 26 -48 50 -88 24 -41 43 -75 43 -77
-0 -2 22 -40 50 -85 27 -45 50 -84 50 -86 0 -3 38 -69 83 -147 84 -142 302
--520 340 -587 10 -19 34 -60 52 -90 18 -30 44 -75 57 -100 14 -25 45 -79 70
--120 25 -41 56 -96 70 -121 14 -25 77 -133 138 -240 62 -107 122 -210 132
--229 25 -43 310 -535 337 -581 11 -19 26 -45 34 -59 17 -32 238 -414 266 -460
-11 -19 24 -41 28 -49 3 -7 75 -133 160 -278 84 -146 153 -269 153 -274 0 -5 5
--9 10 -9 6 0 10 -4 10 -10 0 -5 82 -150 181 -322 182 -314 201 -346 240 -415
-12 -21 80 -139 152 -263 71 -124 141 -245 155 -270 14 -25 28 -49 32 -55 6 -8
-145 -248 220 -380 37 -66 209 -362 229 -395 11 -19 24 -42 28 -49 4 -8 67
--118 140 -243 73 -125 133 -230 133 -233 0 -2 15 -28 33 -57 19 -29 47 -78 64
--108 17 -30 53 -93 79 -139 53 -90 82 -141 157 -272 82 -142 115 -199 381
--659 142 -245 268 -463 281 -485 12 -22 71 -125 132 -230 60 -104 172 -298
-248 -430 76 -132 146 -253 156 -270 11 -16 22 -36 26 -44 3 -8 30 -54 60 -103
-29 -49 53 -91 53 -93 0 -3 18 -34 40 -70 22 -36 40 -67 40 -69 0 -2 37 -66 81
--142 45 -77 98 -168 119 -204 20 -36 47 -81 58 -100 12 -19 27 -47 33 -62 6
--16 15 -28 20 -28 5 0 9 -4 9 -9 0 -6 63 -118 140 -251 77 -133 140 -243 140
--245 0 -2 18 -33 41 -70 22 -37 49 -83 60 -101 10 -19 29 -51 40 -71 25 -45
-109 -189 126 -218 7 -11 17 -29 22 -40 6 -11 22 -38 35 -60 14 -22 37 -62 52
--90 14 -27 35 -62 45 -77 11 -14 19 -29 19 -32 0 -3 18 -35 40 -71 22 -36 40
--67 40 -69 0 -2 19 -35 42 -72 23 -38 55 -94 72 -124 26 -47 139 -244 171
--298 6 -9 21 -36 34 -60 28 -48 37 -51 51 -19 6 12 19 36 29 52 10 17 27 46
-38 65 11 19 104 181 208 360 103 179 199 345 213 370 14 25 42 74 64 109 21
-34 38 65 38 67 0 2 18 33 40 69 22 36 40 67 40 69 0 3 177 310 199 346 16 26
-136 234 140 244 2 5 25 44 52 88 27 44 49 81 49 84 0 2 18 34 40 70 22 36 40
-67 40 69 0 2 20 36 43 77 35 58 169 289 297 513 9 17 50 86 90 155 40 69 86
-150 103 180 16 30 35 62 41 70 6 8 16 24 22 35 35 64 72 129 167 293 59 100
-116 199 127 220 11 20 30 53 41 72 43 72 1070 1850 1121 1940 14 25 65 113
-113 195 48 83 96 166 107 185 10 19 28 50 38 68 11 18 73 124 137 235 64 111
-175 303 246 427 71 124 173 299 225 390 52 91 116 202 143 248 27 45 49 85 49
-89 0 4 6 14 14 22 7 9 28 43 46 76 26 47 251 436 378 655 11 19 29 51 40 70
-11 19 101 176 201 348 99 172 181 317 181 323 0 5 5 9 10 9 6 0 10 5 10 11 0
-6 8 23 18 37 11 15 32 52 49 82 16 30 130 228 253 440 122 212 234 405 248
-430 13 25 39 70 57 100 39 65 69 117 130 225 25 44 50 87 55 95 12 19 78 134
-220 380 61 107 129 224 150 260 161 277 222 382 246 425 15 28 47 83 71 123
-24 41 43 78 43 83 0 5 4 9 8 9 4 0 13 12 19 28 7 15 23 45 36 67 66 110 277
-478 277 483 0 3 6 13 14 21 7 9 27 41 43 71 17 30 45 80 63 110 34 57 375 649
-394 685 6 11 16 27 22 35 6 8 26 42 44 75 18 33 41 74 51 90 10 17 24 41 32
-55 54 97 72 128 88 152 11 14 19 28 19 30 0 3 79 141 175 308 96 167 175 305
-175 308 0 3 6 13 14 21 7 9 26 39 41 66 33 60 276 483 338 587 24 40 46 80 50
-88 4 8 13 24 20 35 14 23 95 163 125 215 11 19 52 91 92 160 40 69 80 139 90
-155 9 17 103 179 207 360 105 182 200 346 211 365 103 181 463 802 489 845 7
-11 15 27 19 35 4 8 29 51 55 95 64 110 828 1433 848 1470 9 17 24 41 33 55 9
-14 29 48 45 77 15 28 52 93 82 145 30 51 62 107 71 123 17 30 231 398 400 690
-51 88 103 179 115 202 12 23 26 48 32 55 6 7 24 38 40 68 17 30 61 107 98 170
-37 63 84 144 103 180 19 36 41 72 48 81 8 8 14 18 14 21 0 4 27 51 59 106 32
-55 72 124 89 154 16 29 71 125 122 213 51 88 104 180 118 205 13 25 28 50 32
-55 4 6 17 26 28 45 11 19 45 80 77 135 31 55 66 116 77 135 11 19 88 152 171
-295 401 694 620 1072 650 1125 11 19 87 152 170 295 83 143 158 273 166 288 9
-16 21 36 26 45 6 9 31 52 55 96 25 43 54 94 66 115 11 20 95 164 186 321 91
-157 173 299 182 315 9 17 26 46 37 65 12 19 66 114 121 210 56 96 108 186 117
-200 8 14 24 40 34 59 24 45 383 664 412 713 5 9 17 29 26 45 15 28 120 210
-241 419 36 61 68 117 72 125 4 8 12 23 19 34 35 57 245 420 262 453 11 20 35
-61 53 90 17 29 32 54 32 56 0 3 28 51 62 108 33 57 70 119 80 138 10 19 23 42
-28 50 5 8 32 53 59 100 27 47 149 258 271 470 122 212 234 405 248 430 30 53
-62 108 80 135 6 11 15 27 19 35 4 8 85 150 181 315 96 165 187 323 202 350 31
-56 116 202 130 225 5 8 25 42 43 75 19 33 92 159 162 280 149 257 157 271 202
-350 19 33 38 67 43 75 9 14 228 392 275 475 12 22 55 96 95 165 40 69 80 139
-90 155 24 42 202 350 221 383 9 15 27 47 41 72 14 25 75 131 136 236 61 106
-121 210 134 232 99 172 271 470 279 482 5 8 23 40 40 70 18 30 81 141 142 245
-60 105 121 210 135 235 14 25 71 124 127 220 56 96 143 247 194 335 51 88 96
-167 102 175 14 24 180 311 204 355 23 43 340 590 356 615 5 8 50 87 101 175
-171 301 517 898 582 1008 25 43 46 81 46 83 0 2 12 23 27 47 14 23 40 67 56
-97 16 30 35 62 42 70 7 8 15 22 18 30 4 8 20 38 37 65 16 28 33 57 37 65 6 12
-111 196 143 250 5 8 55 95 112 193 57 98 113 195 126 215 12 20 27 46 32 57 6
-11 14 27 20 35 5 8 76 130 156 270 80 140 165 287 187 325 23 39 52 90 66 115
-13 25 30 52 37 61 8 8 14 18 14 21 0 4 41 77 92 165 50 87 175 302 276 478
-101 176 208 360 236 408 28 49 67 117 86 152 19 35 41 70 48 77 6 6 12 15 12
-19 0 7 124 224 167 291 12 21 23 40 23 42 0 2 21 40 46 83 26 43 55 92 64 109
-54 95 327 568 354 614 19 30 45 75 59 100 71 128 82 145 89 148 4 2 8 8 8 13
-0 5 42 82 94 172 311 538 496 858 518 897 14 25 40 70 58 100 18 30 42 71 53
-90 10 19 79 139 152 265 73 127 142 246 153 265 10 19 43 76 72 125 29 50 63
-108 75 130 65 116 80 140 87 143 4 2 8 8 8 12 0 8 114 212 140 250 6 8 14 24
-20 35 5 11 54 97 108 190 l100 170 -9611 3 c-5286 1 -9614 -1 -9618 -5 -5 -6
--419 -719 -619 -1068 -89 -155 -267 -463 -323 -560 -38 -66 -81 -140 -95 -165
--31 -56 -263 -457 -526 -910 -110 -190 -224 -388 -254 -440 -29 -52 -61 -109
--71 -125 -23 -39 -243 -420 -268 -465 -11 -19 -204 -352 -428 -740 -224 -388
--477 -826 -563 -975 -85 -148 -185 -322 -222 -385 -37 -63 -120 -207 -185
--320 -65 -113 -177 -306 -248 -430 -72 -124 -172 -297 -222 -385 -51 -88 -142
--245 -202 -350 -131 -226 -247 -427 -408 -705 -65 -113 -249 -432 -410 -710
--160 -278 -388 -673 -506 -877 -118 -205 -216 -373 -219 -373 -3 0 -52 82
--109 183 -58 100 -144 250 -192 332 -95 164 -402 696 -647 1120 -85 149 -228
-396 -317 550 -212 365 -982 1700 -1008 1745 -10 19 -43 76 -72 125 -29 50 -64
-110 -77 135 -14 25 -63 110 -110 190 -47 80 -96 165 -110 190 -14 25 -99 171
--188 325 -89 154 -174 300 -188 325 -13 25 -64 113 -112 195 -48 83 -140 242
--205 355 -65 113 -183 317 -263 454 -79 137 -152 264 -163 282 -50 89 -335
-583 -354 614 -12 19 -34 58 -50 85 -15 28 -129 226 -253 440 -124 215 -235
-408 -247 430 -12 22 -69 121 -127 220 -58 99 -226 389 -373 645 -148 256 -324
-561 -392 678 -67 117 -134 232 -147 255 -13 23 -33 59 -46 80 l-22 37 -9615 0
--9615 0 20 -32z"/>
+<path d="M2299 4996 c-2 -2 -33 -6 -69 -10 -36 -3 -76 -8 -90 -10 -14 -3 -41
+-7 -60 -10 -491 -73 -1015 -350 -1377 -727 -95 -99 -122 -132 -199 -234 -41
+-55 -78 -104 -82 -110 -8 -11 -95 -156 -129 -215 -107 -190 -222 -527 -259
+-760 -3 -19 -7 -46 -10 -60 -26 -146 -26 -590 0 -715 2 -11 7 -40 10 -65 16
+-117 86 -369 143 -509 187 -464 519 -874 931 -1149 80 -54 118 -77 243 -145
+38 -20 179 -83 264 -117 78 -31 229 -77 315 -95 8 -2 35 -8 60 -14 67 -15 137
+-26 265 -41 70 -8 488 -6 535 3 14 3 47 8 73 12 27 4 59 8 70 11 12 2 34 6 49
+9 219 40 540 156 711 257 26 15 50 28 53 28 18 0 291 191 363 254 360 316 577
+628 745 1066 21 55 69 221 81 278 2 9 8 38 14 65 13 56 17 80 35 217 14 105
+15 525 2 580 -3 14 -8 46 -11 71 -3 25 -8 56 -10 70 -3 13 -8 38 -11 54 -15
+86 -95 366 -115 405 -4 8 -17 40 -29 70 -36 90 -123 259 -184 357 -307 497
+-786 874 -1341 1058 -61 20 -123 38 -175 51 -80 19 -99 24 -125 28 -16 3 -39
+8 -50 10 -11 2 -43 7 -70 10 -28 4 -64 10 -80 12 -34 6 -481 15 -486 10z"/>
 </g>
 </svg>

+ 6 - 6
public/manifest.json

@@ -1,6 +1,6 @@
 {
-  "name": "ecs-student",
-  "short_name": "ecs-student",
+  "name": "网考移动端",
+  "short_name": "网考移动端",
   "icons": [
     {
       "src": "/img/icons/android-chrome-192x192.png",
@@ -8,13 +8,13 @@
       "type": "image/png"
     },
     {
-      "src": "/img/icons/android-chrome-512x512.png",
-      "sizes": "512x512",
+      "src": "/img/icons/android-chrome-384x384.png",
+      "sizes": "384x384",
       "type": "image/png"
     }
   ],
   "start_url": "/index.html",
   "display": "standalone",
-  "background_color": "#000000",
-  "theme_color": "#4DBA87"
+  "theme_color": "#ffffff",
+  "background_color": "#ffffff"
 }

二进制
src/assets/logo.png


+ 27 - 3
src/components/FaceRecognition/FaceRecognition.vue

@@ -424,12 +424,16 @@ export default {
         // console.log(buffer);
         // var view1 = new Uint8Array(buffer);
         // console.log(buffer[0], buffer[1], buffer[429721]);
-        const fileMd5 = MD5(buffer);
-        console.log(fileMd5);
+        // const fileMd5 = MD5(buffer);
+        const fileMd5Base64 = window.btoa(
+          String.fromCharCode(...MD5.digest(buffer))
+        );
+        // console.log(fileMd5);
+        // console.log(fileMd5, fileMd5Base64);
 
         const params = new URLSearchParams();
         params.append("fileSuffix", "png");
-        params.append("fileMd5", fileMd5);
+        // params.append("fileMd5", fileMd5);
         const res = await this.$http.get(
           "/api/ecs_oe_student/examControl/getCapturePhotoYunSign?" + params
         );
@@ -448,15 +452,35 @@ export default {
           myFormData.append(k, v);
         }
         myFormData.append("file", captureBlob);
+        // myFormData.toBlob();
+        // let myHeaders = new Headers();
+        // myHeaders.append("Content-MD5", fileMd5Base64);
 
         try {
           const res2 = await fetch(res.data.formUrl, {
             method: "POST",
+            // headers: myHeaders,
             body: myFormData,
           });
+          console.log(res2, {
+            "res2.headers": res2.headers,
+            'res2.headers.get("Content-MD5")': res2.headers.get("Content-MD5"),
+            'res2.headers.get("Content-Type")': res2.headers.get(
+              "Content-Type"
+            ),
+          });
+          // throw "eee";
           if (!res2.ok) {
             throw res2.status;
           }
+          if (res2.headers.get("Content-MD5") != fileMd5Base64) {
+            window._hmt.push([
+              "_trackEvent",
+              "摄像头框",
+              "抓拍照片保存失败--alioss content-md5 mismatch",
+            ]);
+            throw "图片校验失败";
+          }
         } catch (error) {
           window._hmt.push([
             "_trackEvent",

+ 30 - 9
src/components/MainLayout/MainLayout.vue

@@ -77,34 +77,54 @@
       <img :src="getLogo" class="qm-logo" />
       <ul>
         <li v-if="menus.includes('STU_ONLINE_EXAM')">
-          <router-link class="link" to="/online-exam">
+          <router-link
+            class="link"
+            to="/online-exam"
+            ondragstart="return false;"
+          >
             在线考试
           </router-link>
         </li>
         <li v-if="menus.includes('STU_ONLINE_HOMEWORK')">
-          <router-link class="link" to="/online-homework">
+          <router-link
+            class="link"
+            to="/online-homework"
+            ondragstart="return false;"
+          >
             在线作业
           </router-link>
         </li>
         <li v-if="menus.includes('STU_ONLINE_PRACTICE')">
-          <router-link class="link" to="/online-practice">
+          <router-link
+            class="link"
+            to="/online-practice"
+            ondragstart="return false;"
+          >
             在线练习
           </router-link>
         </li>
         <li v-if="menus.includes('STU_OFFLINE_EXAM')">
-          <router-link class="link" to="/offline-exam">
+          <router-link
+            class="link"
+            to="/offline-exam"
+            ondragstart="return false;"
+          >
             离线考试
           </router-link>
         </li>
         <li v-if="menus.includes('STU_NOTICE')">
-          <router-link class="link" to="/site-message">
+          <router-link
+            class="link"
+            to="/site-message"
+            ondragstart="return false;"
+          >
             <Badge :count="messageUnread" :offset="[20, -20]">
               公告通知
             </Badge>
           </router-link>
         </li>
-        <li v-if="menus.includes('STU_MODIFY_PWD')">
-          <router-link class="link" to="/password">
+        <li v-if="!isEpcc">
+          <router-link class="link" to="/password" ondragstart="return false;">
             修改密码
           </router-link>
         </li>
@@ -136,7 +156,7 @@ export default {
   },
   computed: {
     ...mapState(["user", "siteMessages", "QECSConfig"]),
-    ...mapGetters(["isEpcc"]),
+    ...mapGetters(["isEpcc", "isCug"]),
     messageUnread() {
       return this.siteMessages.filter(v => v.hasRead === false).length;
     },
@@ -268,7 +288,8 @@ export default {
   background-image: url(./link.png);
   background-repeat: no-repeat;
   background-position: 30px 50%;
-  /* margin-left: -40px; */
+  padding-left: 45px;
+  text-align: left;
   padding-right: 20px;
   line-height: 40px;
   width: 100%;

+ 4 - 1
src/components/MainLayout/SiteMessagePopup.vue

@@ -22,7 +22,10 @@
         {{ unreadMessageContent }}
       </p>
       <div style="text-align: left; margin-left: 20px; margin-bottom: 10px;">
-        <router-link :to="'/site-message/' + unreadMessage.id">
+        <router-link
+          :to="'/site-message/' + unreadMessage.id"
+          ondragstart="return false;"
+        >
           详情 >>>
         </router-link>
         <span

二进制
src/components/MainLayout/epcc-logo.png


+ 4 - 1
src/constants/constants.js

@@ -6,4 +6,7 @@ export const VUE_APP_WK_SERVER_SOCKET = process.env.VUE_APP_WK_SERVER_SOCKET;
 export const VUE_APP_WK_SERVER_SOCKET_FOR_AUDIO =
   process.env.VUE_APP_WK_SERVER_SOCKET_FOR_AUDIO;
 export const FACE_API_MODEL_PATH = "/models/20190620/";
-export const EPCC_DOMAIN = "iepcc-ps.ecs.qmth.com.cn";
+// export const EPCC_DOMAIN = "iepcc-ps.ecs.qmth.com.cn";
+export const EPCC_DOMAIN = "bds.ecs.qmth.com.cn";
+export const CUG_DOMAIN = "cug.ecs.qmth.com.cn";
+// export const CUG_DOMAIN = "ecs-test.qmth.com.cn";

+ 90 - 4
src/features/Login/Login.vue

@@ -191,15 +191,15 @@ export default {
       disableLoginBtnBecauseVCam: true,
       disableLoginBtnBecauseAppVersionChecker: false,
       disableLoginBtnBecauseRefreshServiceWorker: false,
+      disableLoginBtnBecauseNotAllowedNative: true,
       newVersionAvailable: false,
       VUE_APP_GIT_REPO_VERSION: process.env.VUE_APP_GIT_REPO_VERSION,
     };
   },
   computed: {
     logoPath() {
-      return this.QECSConfig.LOGO_FILE_URL
-        ? this.QECSConfig.LOGO_FILE_URL + "!/progressive/true/format/webp"
-        : "";
+      // "!/progressive/true/format/webp"
+      return this.QECSConfig.LOGO_FILE_URL ? this.QECSConfig.LOGO_FILE_URL : "";
     },
     productName() {
       return this.QECSConfig.OE_STUDENT_SYS_NAME || "远程教育网络考试";
@@ -250,7 +250,8 @@ export default {
           (this.disableLoginBtnBecauseRemoteApp ||
             this.disableLoginBtnBecauseVCam)) ||
         this.disableLoginBtnBecauseAppVersionChecker ||
-        this.disableLoginBtnBecauseRefreshServiceWorker
+        this.disableLoginBtnBecauseRefreshServiceWorker ||
+        this.disableLoginBtnBecauseNotAllowedNative
       );
     },
   },
@@ -415,6 +416,7 @@ export default {
 
     await this.checkElectronConfig();
     await this.checkVCam();
+    await this.checkAllowedClient();
 
     if (
       this.allowLoginType.length === 1 &&
@@ -687,6 +689,7 @@ export default {
           const fs = window.nodeRequire("fs");
           applicationNames = fs.readFileSync("remoteApplication.txt", "utf-8");
         } catch (error) {
+          console.log(error);
           window._hmt.push([
             "_trackEvent",
             "登录页面",
@@ -764,6 +767,7 @@ export default {
           const fs = window.nodeRequire("fs");
           applicationNames = fs.readFileSync("CameraInfo.txt", "utf-8");
         } catch (error) {
+          console.log(error);
           window._hmt.push([
             "_trackEvent",
             "登录页面",
@@ -801,6 +805,88 @@ export default {
         this.disableLoginBtnBecauseVCam = false;
       }
     },
+    async checkAllowedClient() {
+      if (process.env.VUE_APP_SKIP_CHECK_NATIVE === "true") {
+        console.log(
+          "环境检查: process.env.VUE_APP_SKIP_CHECK_NATIVE === " +
+            process.env.VUE_APP_SKIP_CHECK_NATIVE,
+          " 允许浏览器访问"
+        );
+        this.disableLoginBtnBecauseNotAllowedNative = false;
+        return;
+      }
+      // if (!this.FE_FEATURE_ALLOW_CHECK) {
+      //   this.disableLoginBtnBecauseNotAllowedNative = false;
+      //   return;
+      // }
+      /**
+       * 本应用仅处理学生端的请求。不做跳转,不管浏览器类型。
+       * 允许NATIVE/允许BROWSER,在以下场景中能访问:学生端/移动端浏览器
+       */
+      // if (
+      //   this.QECSConfig.LOGIN_SUPPORT.includes("BROWSER") &&
+      //   this.QECSConfig.LOGIN_SUPPORT.includes("NATIVE")
+      // ) {
+      //   // FIXME: 暂时允许选择浏览器的可以在浏览器中访问
+      //   this.disableLoginBtnBecauseNotAllowedNative = false;
+      //   return;
+      // } else
+      if (this.QECSConfig.LOGIN_SUPPORT.includes("NATIVE")) {
+        // 检测是否是学生端。 检测是否有node。检测是否能node调用。
+        const hasShell = UA.getBrowser().name === "electron-exam-shell";
+        const hasNode = typeof nodeRequire !== "undefined";
+        let hasFs = false;
+        let hasReadFileSync = false;
+        if (hasShell && hasNode) {
+          const fs = window.nodeRequire("fs");
+          if (fs && typeof fs.readFileSync === "function") {
+            hasFs = true;
+            hasReadFileSync = true;
+            this.disableLoginBtnBecauseNotAllowedNative = false;
+          }
+        }
+        if (this.disableLoginBtnBecauseNotAllowedNative) {
+          window._hmt.push([
+            "_trackEvent",
+            "登录页面",
+            "不允许访问-" +
+              JSON.stringify({
+                hasShell,
+                hasNode,
+                hasFs,
+                hasReadFileSync,
+              }),
+          ]);
+          this.$Message.error({
+            content: "请与学校申请最新的客户端,进行考试!",
+            duration: 2 * 24 * 60 * 60,
+          });
+        }
+        // } else if (this.QECSConfig.LOGIN_SUPPORT.includes("BROWSER")) {
+        //   const hasShell = UA.getBrowser().name === "electron-exam-shell";
+        //   if (hasShell) {
+        //     if (this.disableLoginBtnBecauseNotAllowedNative) {
+        //       window._hmt.push([
+        //         "_trackEvent",
+        //         "登录页面",
+        //         "不允许访问-不允许在考生端中访问只设定移动端浏览器访问的设置",
+        //       ]);
+        //       this.$Message.error({
+        //         content: "本考试不支持在考生端中进行!",
+        //         duration: 2 * 24 * 60 * 60,
+        //       });
+        //     }
+        //   }
+        //   // TODO: redirect to wap site
+        //   // if(chromeUA.major < "66") { } // 到移动端去判断
+        //   return;
+      } else {
+        this.$Message.error({
+          content: "本考试不支持在考生端中进行!",
+          duration: 2 * 24 * 60 * 60,
+        });
+      }
+    },
     closeApp() {
       console.log("关闭应用");
       window.close();

+ 5 - 1
src/features/OfflineExam/OfflineExamHome.vue

@@ -5,7 +5,7 @@
                        line-height: 40px; background-color: #fafafa;"
     >
       当前所在位置:
-      <BreadcrumbItem>离线考试</BreadcrumbItem>
+      <BreadcrumbItem>{{ isCug ? "考查课考核" : "离线考试" }}</BreadcrumbItem>
     </Breadcrumb>
 
     <div class="home">
@@ -18,6 +18,7 @@
 </template>
 
 <script>
+import { mapGetters } from "vuex";
 import EcsOfflineList from "./OfflineExamList.vue";
 
 export default {
@@ -30,6 +31,9 @@ export default {
       courses: [],
     };
   },
+  computed: {
+    ...mapGetters(["isCug"]),
+  },
   async mounted() {
     try {
       window._hmt.push(["_trackEvent", "离线考试页面", "进入列表", ""]);

+ 15 - 1
src/features/OfflineExam/OfflineExamList.vue

@@ -23,6 +23,7 @@
               <a
                 :href="course.offlineFileUrl"
                 download
+                ondragstart="return false;"
                 @click="() => downloadOfflineFile(course.offlineFileUrl)"
               >
                 <i-icon type="ios-cloud-download"></i-icon>下载作答
@@ -44,6 +45,7 @@
                   class="qm-primary-button"
                   href="#"
                   download
+                  ondragstart="return false;"
                   @click="() => tempDisableBtnAndDownloadPaper(course)"
                 >
                   下载试卷
@@ -61,7 +63,13 @@
                     download
                   >下载答题卡</a>
                 </i-button> -->
+                <ecs-offline-exam-upload-cug
+                  v-if="isCug"
+                  :course="course"
+                  @reloadList="$emit('reloadList')"
+                ></ecs-offline-exam-upload-cug>
                 <ecs-offline-exam-upload
+                  v-else
                   :course="course"
                   @reloadList="$emit('reloadList')"
                 ></ecs-offline-exam-upload>
@@ -100,17 +108,19 @@
 </template>
 
 <script>
-import { mapState as globalMapState } from "vuex";
+import { mapState as globalMapState, mapGetters } from "vuex";
 import {
   TK_SERVER_HTML_URL,
   TK_SERVER_API_URL,
 } from "@/constants/constants.js";
 import OfflineExamUpload from "./OfflineExamUpload.vue";
+import OfflineExamUploadCug from "./OfflineExamUploadCug.vue";
 
 export default {
   name: "EcsOfflineList",
   components: {
     "ecs-offline-exam-upload": OfflineExamUpload,
+    "ecs-offline-exam-upload-cug": OfflineExamUploadCug,
   },
   props: {
     courses: {
@@ -127,6 +137,10 @@ export default {
   },
   computed: {
     ...globalMapState(["user", "timeDifference"]),
+    ...mapGetters(["isCug"]),
+    // enableFeFeature() {
+    //   return !!!this.FE_FEATURE_IMAGE_PDF;
+    // },
   },
   methods: {
     async enterExam(course) {

+ 406 - 0
src/features/OfflineExam/OfflineExamUploadCug.vue

@@ -0,0 +1,406 @@
+<template>
+  <div>
+    <Upload
+      ref="uploadComp"
+      :headers="headers"
+      :data="{ fileType: fileType }"
+      :before-upload="handleBeforeUpload"
+      :action="
+        '/api/ecs_oe_admin/offlineExam/submitPaper?examRecordDataId=' +
+          course.examRecordDataId
+      "
+      :max-size="1024 * 30"
+      :format="uploadFileFormat"
+      :accept="uploadFileAccept"
+      :on-format-error="handleFormatError"
+      :on-exceeded-size="handleMaxSize"
+      :on-success="handleSuccess"
+      :on-error="handleError"
+      :show-upload-list="false"
+    >
+      <i-button
+        icon="ios-cloud-upload-outline"
+        class="qm-primary-button"
+        style="width: 100%;"
+      >
+        上传作答
+      </i-button>
+    </Upload>
+    <div v-if="file !== null && loadingStatus">
+      待上传文件: {{ file.name }}
+      <i-button :loading="loadingStatus">
+        {{ loadingStatus ? "上传中..." : "上传" }}
+      </i-button>
+    </div>
+    <Modal v-model="showPreview" fullscreen footer-hide :closable="false">
+      <!-- <div slot="header"></div> -->
+      <img id="previewId" class="preview-image" />
+    </Modal>
+  </div>
+</template>
+
+<script>
+import { printCurrentPage } from "./imageToPdf";
+
+export default {
+  name: "EcsOfflineUploadCug",
+  props: {
+    course: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      headers: {
+        token: window.sessionStorage.getItem("token"),
+        key: window.localStorage.getItem("key"),
+      },
+      file: null,
+      fileType: null,
+      loadingStatus: false,
+      uploadFileFormat: [],
+      uploadFileAccept: "",
+      // imageSrc
+      previewImage: null,
+      showPreview: false,
+    };
+  },
+  async created() {
+    const res = await this.$http.get(
+      "/api/ecs_exam_work/exam/getExamPropertyFromCacheByStudentSession/" +
+        this.course.examId +
+        `/OFFLINE_UPLOAD_FILE_TYPE`
+    );
+
+    this.uploadFileFormat =
+      (res.data.OFFLINE_UPLOAD_FILE_TYPE &&
+        JSON.parse(res.data.OFFLINE_UPLOAD_FILE_TYPE)) ||
+      [];
+    this.uploadFileAccept =
+      "image/jpeg," +
+      this.uploadFileFormat.map(v => "application/" + v.toLowerCase()).join();
+    if (this.uploadFileAccept.includes("zip")) {
+      this.uploadFileAccept = ".zip," + this.uploadFileAccept;
+    }
+    this.uploadFileFormat.push(...["jpeg", "jpg"]);
+    this.uploadFileFormat = this.uploadFileFormat.map(v => v.toLowerCase());
+  },
+  methods: {
+    fileFormatCheck(file, resolve, reject) {
+      function getMimetype(signature) {
+        switch (signature) {
+          case "89504E47":
+            return "image/png";
+          case "47494638":
+            return "image/gif";
+          case "25504446":
+            return "application/pdf";
+          case "FFD8FFDB":
+          case "FFD8FFE0":
+          case "FFD8FFE1":
+            return "image/jpeg";
+          case "504B0304":
+            return "application/zip";
+          case "504B34":
+            return "application/zip";
+          default:
+            return "Unknown filetype";
+        }
+      }
+      console.log(file);
+
+      const filereader = new FileReader();
+      let uploads = [];
+      filereader.onloadend = evt => {
+        if (evt.target.readyState === FileReader.DONE) {
+          const uint = new Uint8Array(evt.target.result);
+          let bytes = [];
+          uint.forEach(byte => {
+            bytes.push(byte.toString(16));
+          });
+          const hex = bytes.join("").toUpperCase();
+          uploads.push({
+            filename: file.name,
+            filetype: file.type ? file.type : "Unknown/Extension missing",
+            binaryFileType: getMimetype(hex),
+            hex: hex,
+          });
+
+          if (["application/pdf"].includes(getMimetype(hex))) {
+            if (!file.name.endsWith(".pdf")) {
+              this.loadingStatus = false;
+              this.$Notice.warning({
+                title: "文件内容与文件的后缀不一致",
+                // desc: file.name + " 文件是pdf文档,但文件名后缀不是.pdf!"
+              });
+              this.file = null;
+              reject("文件内容与文件的后缀不一致,请确认文件是pdf文档!");
+            } else {
+              resolve();
+            }
+          } else if (["application/zip"].includes(getMimetype(hex))) {
+            if (!file.name.endsWith(".zip")) {
+              this.loadingStatus = false;
+              // this.$refs.uploadComp.fileList.splice(0);
+              // this.$refs.uploadComp.fileList = [];
+              this.$Notice.warning({
+                title: "文件内容与文件的后缀不一致",
+                // desc: file.name + " 文件是zip压缩包,但文件名后缀不是.zip!"
+              });
+              this.file = null;
+              reject("文件内容与文件的后缀不一致,请确认文件是zip压缩包!");
+            } else {
+              resolve();
+            }
+          } else if (["image/jpeg"].includes(getMimetype(hex))) {
+            if (file.name.endsWith(".jpeg") || file.name.endsWith(".jpg")) {
+              printCurrentPage()
+                .then(filename => {
+                  // const fs = nodeRequire("fs");
+                  const path = window.nodeRequire("path");
+                  const fileNew = {
+                    name: path.basename(filename),
+                    path: filename,
+                    type: "application/pdf",
+                    // lastModified
+                    // lastModifiedDate
+                    // size
+                  };
+                  this.file = fileNew;
+                  file = fileNew;
+                  // console.log(this.$refs.uploadComp.fileList);
+                  // this.$refs.uploadComp.fileList = [file];
+                  // console.log(this.$refs.uploadComp.fileList);
+                  resolve();
+                })
+                .catch(() => {
+                  reject();
+                });
+            } else {
+              this.loadingStatus = false;
+              // this.$refs.uploadComp.fileList.splice(0);
+              // this.$refs.uploadComp.fileList = [];
+              this.$Notice.warning({
+                title: "文件内容与文件的后缀不一致",
+              });
+              this.file = null;
+              reject("文件内容与文件的后缀不一致,请确认文件是jpg文件!");
+            }
+          } else {
+            console.log("binary file type check: not zip or pdf");
+            window._hmt.push([
+              "_trackEvent",
+              "离线考试页面",
+              "上传作答",
+              "文件格式非zip或pdf",
+            ]);
+            this.$Notice.warning({
+              title: "作答文件损坏",
+              desc:
+                file.name +
+                " 文件无法以 " +
+                this.uploadFileFormat.join(" 或 ") +
+                " 格式读取。",
+            });
+            this.file = null;
+            this.loadingStatus = false;
+            reject("作答文件损坏");
+          }
+        }
+      };
+      const blob = file.slice(0, 4);
+      filereader.readAsArrayBuffer(blob);
+    },
+    handleSuccess() {
+      window._hmt.push(["_trackEvent", "离线考试页面", "上传作答", "上传成功"]);
+      this.file = null;
+      this.loadingStatus = false;
+      this.$Message.success({
+        content: "上传成功",
+        duration: 5,
+        closable: true,
+      });
+      this.$emit("reloadList");
+      this.showPreview = false;
+    },
+    handleError(error, file) {
+      window._hmt.push(["_trackEvent", "离线考试页面", "上传作答", "上传失败"]);
+      this.file = null;
+      this.loadingStatus = false;
+      console.log(error);
+      this.$Message.error({
+        content: (file && file.desc) || "上传失败",
+        duration: 15,
+        closable: true,
+      });
+      this.showPreview = false;
+    },
+    handleFormatError(file) {
+      this.file = null;
+      this.loadingStatus = false;
+      this.$Notice.warning({
+        title: "作答文件格式不对",
+        desc:
+          file.name +
+          " 文件格式不对,请选择 " +
+          this.uploadFileFormat.join(" 或 ") +
+          "  文件。",
+      });
+      this.showPreview = false;
+    },
+    handleMaxSize(file) {
+      this.file = null;
+      this.loadingStatus = false;
+      this.$Notice.warning({
+        title: "超出文件大小限制",
+        desc: file.name + " 太大,作答文件不能超过30M.",
+      });
+      this.showPreview = false;
+    },
+    async handleBeforeUpload(file) {
+      const suffix = file.name.split(".").pop();
+      if (suffix.toLowerCase() !== suffix) {
+        this.$Notice.error({
+          title: "文件名后缀必须是小写",
+          desc: file.name + " 文件名后缀必须是小写。",
+        });
+        return Promise.reject("file suffix should be lower case");
+      }
+      if (file.name.endsWith(".jpeg") || file.name.endsWith(".jpg")) {
+        new Promise((resolve, reject) => {
+          if (this.course.offlineFileUrl) {
+            this.$Modal.confirm({
+              title: "已有作答附件,是否覆盖?",
+              onCancel: () => reject(-1),
+              onOk: () => resolve(),
+            });
+          } else {
+            resolve();
+          }
+        })
+          .then(() => {
+            this.showPreview = true;
+            this.$Message.info({
+              content: "正在准备将图片转为PDF...",
+              duration: 1.5,
+              closable: true,
+            });
+
+            return new Promise(resolve => {
+              var reader = new FileReader();
+              reader.onload = function(e) {
+                document
+                  .getElementById("previewId")
+                  .setAttribute("src", e.target.result);
+
+                setTimeout(resolve, 2500);
+                // resolve();
+              };
+              //Imagepath.files[0] is blob type
+              reader.readAsDataURL(file);
+            });
+          })
+          .then(() => {
+            // return;
+            return printCurrentPage()
+              .then(filename => {
+                const fs = window.nodeRequire("fs");
+                const path = window.nodeRequire("path");
+                // const fileNew = {
+                //   name: path.basename(filename),
+                //   // filename: path.basename(filename),
+                //   uri: "file://" + filename,
+                //   // path: filename,
+                //   type: "application/pdf",
+                // };
+                const fileNew = new File(
+                  [fs.readFileSync(filename)],
+                  path.basename(filename),
+                  { type: "application/pdf" } // what I upload is image.
+                );
+                this.file = fileNew;
+
+                this.$Message.info({
+                  content: "图片转换成功,正在上传...",
+                  duration: 2,
+                  closable: true,
+                });
+
+                var stats = fs.statSync(filename);
+                var fileSizeInBytes = stats["size"];
+                if (fileSizeInBytes > 1024 * 30 * 1024) {
+                  this.handleMaxSize();
+                  throw "exceed max size";
+                }
+
+                const formData = new FormData();
+                formData.append("fileType", "pdf");
+                formData.append("file", fileNew);
+
+                return this.$http
+                  .post(
+                    "/api/ecs_oe_admin/offlineExam/submitPaper?examRecordDataId=" +
+                      this.course.examRecordDataId,
+                    formData
+                  )
+                  .then(() => {
+                    this.handleSuccess();
+                  })
+                  .catch(() => {
+                    this.handleError();
+                  });
+              })
+              .catch(error => {
+                console.log(error);
+                this.handleError();
+              });
+          });
+        return Promise.reject("图片另外路径上传");
+      }
+
+      return new Promise((resolve, reject) => {
+        if (this.course.offlineFileUrl) {
+          this.$Modal.confirm({
+            title: "已有作答附件,是否覆盖?",
+            onCancel: () => reject(-1),
+            onOk: () => resolve(),
+          });
+        } else {
+          resolve();
+        }
+      }).then(() => {
+        if (
+          file.type.includes("/pdf")
+          //  ||
+          // file.type.includes("/jpeg") ||
+          // file.type.includes("/jpg")
+        ) {
+          this.fileType = "pdf";
+        } else if (file.type.includes("/zip")) {
+          this.fileType = "zip";
+        }
+        this.file = file;
+        this.loadingStatus = true;
+        return new Promise((resolve, reject) =>
+          this.fileFormatCheck(file, resolve, reject)
+        );
+      });
+    },
+  },
+};
+</script>
+
+<style scoped>
+.list .ivu-upload-select {
+  width: 100%;
+}
+.preview-image {
+  max-width: 100vw;
+  max-height: 100vh;
+  margin: -16px;
+  object-fit: contain;
+}
+</style>

+ 47 - 0
src/features/OfflineExam/imageToPdf.js

@@ -0,0 +1,47 @@
+export function printCurrentPage() {
+  return new Promise((resolve, reject) => {
+    let remote = window.nodeRequire("electron").remote;
+    document.querySelector("#app").style = "display: none";
+    remote.getCurrentWindow().webContents.printToPDF(
+      {
+        marginsType: 1,
+        pageSize: "A4",
+        printBackground: false,
+        landscape: false,
+      },
+      (error, data) => {
+        document.querySelector("#app").style = "display: block";
+        if (error) {
+          reject(error);
+        }
+
+        // this.isPrint = false;
+
+        //Synchronous
+        // let filename = dialog.showSaveDialog(WIN, options);
+        // // console.log(filename);
+        // if (!filename) return;
+        const tmpFolder = window.nodeRequire("os").tmpdir();
+        const filename = tmpFolder + "/提交答案-" + Date.now() + ".pdf";
+
+        const fs = window.nodeRequire("fs");
+        // TODO: choose pdf file name
+        // 按数据模型文档,此处没有file name,所以暂时只能是固定的名称
+        fs.writeFile(filename, data, error => {
+          if (error) {
+            // this.$alert(error, "失败", {
+            //   confirmButtonText: "确定",
+            // });
+            // throw error;
+            reject(error);
+          }
+          console.log("Write PDF successfully." + filename);
+          resolve(filename);
+          // this.$alert("导出PDF成功", "成功", {
+          //   confirmButtonText: "确定",
+          // });
+        });
+      }
+    );
+  });
+}

+ 2 - 0
src/features/OnlineExam/Examing/ArrowNavView.vue

@@ -7,6 +7,7 @@
           :to="{
             path: `/online-exam/exam/${$route.params.examId}/examRecordData/${$route.params.examRecordDataId}/order/${previousQuestionOrder}`,
           }"
+          ondragstart="return false;"
         >
           上一题
         </router-link>
@@ -21,6 +22,7 @@
       <template v-if="nextQuestionOrder">
         <router-link
           class="qm-primary-button"
+          ondragstart="return false;"
           :to="{
             path: `/online-exam/exam/${$route.params.examId}/examRecordData/${$route.params.examRecordDataId}/order/${nextQuestionOrder}`,
           }"

+ 1 - 0
src/features/OnlineExam/Examing/ExamingEnd.vue

@@ -60,6 +60,7 @@
         class="qm-primary-button"
         :to="backTo"
         style="display: inline-block; width: 100%;"
+        ondragstart="return false;"
       >
         返回主页
       </router-link>

+ 1 - 0
src/features/OnlineExam/Examing/QuestionNavView.vue

@@ -17,6 +17,7 @@
               <router-link
                 :key="index2"
                 :class="itemClass(section, index2)"
+                ondragstart="return false;"
                 :to="{
                   path: `/online-exam/exam/${
                     $route.params.examId

+ 4 - 2
src/features/OnlineExam/Examing/TextQuestionView.vue

@@ -221,13 +221,15 @@ export default {
         ele.innerHTML = this.studentAnswer;
         const imgs = ele.querySelectorAll(".photo-answer");
         // if()
-        return [...imgs].map(e => e.src.replace("!/both/200x200", ""));
+        return [...imgs].map(e =>
+          e.src.replace("?x-oss-process=image/resize,m_lfit,h_200,w_200", "")
+        );
       },
       set(pSrcs) {
         let imageStr = pSrcs.map(
           v =>
             `<a href='${v}' target='_blank' ><img class='photo-answer' src='${v +
-              "!/both/200x200"}' /></a>`
+              "?x-oss-process=image/resize,m_lfit,h_200,w_200"}' /></a>`
         );
         const ele = document.createElement("div");
         ele.innerHTML = this.studentAnswer || "";

+ 1 - 0
src/features/SiteMessage/SiteMessageHome.vue

@@ -60,6 +60,7 @@ export default {
               <router-link
                 to={"/site-message/" + params.row.id + "?fromPage=" + this.page}
                 style="display: flex"
+                ondragstart="return false;"
               >
                 <img
                   class={

+ 5 - 1
src/store.js

@@ -1,6 +1,6 @@
 import Vue from "vue";
 import Vuex from "vuex";
-import { EPCC_DOMAIN } from "@/constants/constants";
+import { EPCC_DOMAIN, CUG_DOMAIN } from "@/constants/constants";
 
 Vue.use(Vuex);
 
@@ -238,5 +238,9 @@ export default new Vuex.Store({
     isEpcc(state) {
       return state.user.schoolDomain === EPCC_DOMAIN;
     },
+    isCug(state) {
+      const FE_FEATURE_IMAGE_PDF = true;
+      return state.user.schoolDomain === CUG_DOMAIN && FE_FEATURE_IMAGE_PDF;
+    },
   },
 });

+ 9 - 1
src/utils/nativeExe.js

@@ -1,9 +1,17 @@
 export default function async(exeName, cb) {
   if (typeof nodeRequire == "undefined") {
+    console.log("nodeRequire failed");
+    window._hmt.push([
+      "_trackEvent",
+      window.location.pathname.replace(/\d+/g, ""),
+      "不在Electron中,调用 " + exeName + " 失败",
+    ]);
     throw "不在Electron中,调用 " + exeName + " 失败";
   }
   return new Promise(resolve => {
-    window.nodeRequire("node-cmd").get(exeName, async () => {
+    window.nodeRequire("node-cmd").get(exeName, async (err, data, stderr) => {
+      console.log(err, data, stderr); // 未免过多日志,此处后续可以关闭
+      await new Promise(resolve2 => setTimeout(() => resolve2(), 1000));
       await cb();
       resolve();
     });