CheckComputer.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. <template>
  2. <div style="max-width: 800px; margin: 30px auto; ">
  3. <Steps :current="current" size="small">
  4. <Step title="网速"></Step>
  5. <Step title="时钟"></Step>
  6. <Step title="摄像头"></Step>
  7. <Step title="声音"></Step>
  8. <Step title="微信小程序"></Step>
  9. <Step title="检测结果"></Step>
  10. </Steps>
  11. <div v-if="current === 0" class="section">
  12. <div class="list">
  13. <table>
  14. <tbody class="list-row">
  15. <tr class="list-header qm-primary-strong-text">
  16. <td class="first-td">检查项</td>
  17. <td>值</td>
  18. <td>状态</td>
  19. </tr>
  20. <tr>
  21. <td>电脑当前下载速度</td>
  22. <td>{{ network.downlink }}Mb</td>
  23. <td>
  24. <div v-if="network.downlinkStatus">
  25. <Icon class="pass-check" type="md-checkmark" />
  26. </div>
  27. <div v-else>
  28. <Icon
  29. class="fail-cross"
  30. title="下载速度不佳"
  31. type="md-close"
  32. />
  33. </div>
  34. </td>
  35. </tr>
  36. <tr>
  37. <td>电脑当前网络延迟</td>
  38. <td>{{ network.rrt }}毫秒</td>
  39. <td>
  40. <div v-if="network.rrtStatus">
  41. <Icon class="pass-check" type="md-checkmark" />
  42. </div>
  43. <div v-else>
  44. <Icon
  45. class="fail-cross"
  46. title="网络延迟较大"
  47. type="md-close"
  48. />
  49. </div>
  50. </td>
  51. </tr>
  52. </tbody>
  53. </table>
  54. </div>
  55. </div>
  56. <div v-if="current === 1" class="section">
  57. <div class="list">
  58. <table>
  59. <tbody class="list-row">
  60. <tr class="list-header qm-primary-strong-text">
  61. <td class="first-td">检查项</td>
  62. <td>值</td>
  63. <td>状态</td>
  64. </tr>
  65. <!-- <tr>
  66. <td>电脑当前时间</td>
  67. <td>{{ timeCurrent }} (北京时间)</td>
  68. <td></td>
  69. </tr> -->
  70. <!-- <tr>
  71. <td>电脑是否时间准确</td>
  72. <td>{{ timeDifference }}</td>
  73. </tr> -->
  74. <tr>
  75. <td>电脑时区</td>
  76. <td>{{ time.currentTimeZone }}</td>
  77. <td>
  78. <div v-if="time.timeZoneStatus">
  79. <Icon class="pass-check" type="md-checkmark" />
  80. </div>
  81. <div v-else>
  82. <Icon
  83. class="fail-cross"
  84. title="请将电脑设置为北京时区"
  85. type="md-close"
  86. />
  87. </div>
  88. </td>
  89. </tr>
  90. <tr>
  91. <td>电脑时钟频率</td>
  92. <td>
  93. <div v-if="time.clockRateStateResolved">
  94. {{ time.clockRateDiff > 3 ? "时钟过慢" : "正常" }}
  95. </div>
  96. <div v-else>
  97. <PulseLoader />
  98. </div>
  99. </td>
  100. <td>
  101. <div v-if="time.clockRateStateResolved">
  102. <Icon
  103. class="pass-check"
  104. v-if="time.clockRateStatus"
  105. type="md-checkmark"
  106. />
  107. <Icon
  108. class="fail-cross"
  109. v-else
  110. title="请更换电脑"
  111. type="md-close"
  112. />
  113. </div>
  114. <div v-else>
  115. <PulseLoader />
  116. </div>
  117. </td>
  118. </tr>
  119. </tbody>
  120. </table>
  121. </div>
  122. </div>
  123. <div v-show="current === 2" class="section">
  124. <div>
  125. <div style="display: flex;">
  126. <video
  127. id="video"
  128. ref="video"
  129. width="400"
  130. height="300"
  131. autoplay
  132. ></video>
  133. <div style="margin-left: 50px; margin-top: 100px;">
  134. <Button
  135. type="warning"
  136. @click="
  137. camera.resolved = true;
  138. camera.status = false;
  139. "
  140. >
  141. 图像中不是电脑操作者本人
  142. </Button>
  143. <div style="width: 30px; height: 30px;"></div>
  144. <Button
  145. type="primary"
  146. @click="
  147. camera.resolved = true;
  148. camera.status = true;
  149. "
  150. >
  151. 图像中是电脑操作者本人&nbsp;&nbsp;&nbsp;&nbsp;
  152. </Button>
  153. </div>
  154. </div>
  155. </div>
  156. <div class="list">
  157. <table>
  158. <tbody class="list-row">
  159. <tr class="list-header qm-primary-strong-text">
  160. <td class="first-td">检查项</td>
  161. <td>值</td>
  162. <td>状态</td>
  163. </tr>
  164. <tr>
  165. <td>摄像头正常启用</td>
  166. <td>
  167. <div v-if="camera.resolved">
  168. {{ camera.status ? "正常" : "请检查摄像头" }}
  169. </div>
  170. <div v-else>
  171. <PulseLoader />
  172. </div>
  173. </td>
  174. <td>
  175. <div v-if="camera.resolved">
  176. <div v-if="camera.status">
  177. <Icon class="pass-check" type="md-checkmark" />
  178. </div>
  179. <div v-else>
  180. <Icon
  181. class="fail-cross"
  182. title="请检查摄像头"
  183. type="md-close"
  184. />
  185. </div>
  186. </div>
  187. <div v-else>
  188. <PulseLoader />
  189. </div>
  190. </td>
  191. </tr>
  192. </tbody>
  193. </table>
  194. </div>
  195. </div>
  196. <div v-show="current === 3" class="section" style="text-align: center">
  197. <div>
  198. <div style="display: flex; margin-bottom: 30px;">
  199. <audio
  200. src="https://ecs-static.qmth.com.cn/check-audio.mp3"
  201. controls
  202. nodownload
  203. @loadeddata="
  204. sound.downloadResolved = true;
  205. sound.downloadStatus = true;
  206. "
  207. @error="
  208. sound.downloadResolved = true;
  209. sound.downloadStatus = false;
  210. "
  211. />
  212. <div style="margin-left: 30px; display: flex; ">
  213. <Button
  214. type="warning"
  215. @click="
  216. sound.playedStatusResolved = true;
  217. sound.playedStatus = false;
  218. "
  219. title="或者听不到声音"
  220. >
  221. 不能播放声音
  222. </Button>
  223. <div style="width: 30px; height: 30px;"></div>
  224. <Button
  225. type="primary"
  226. @click="
  227. sound.playedStatusResolved = true;
  228. sound.playedStatus = true;
  229. "
  230. >
  231. 能够播放声音
  232. </Button>
  233. </div>
  234. </div>
  235. </div>
  236. <div class="list">
  237. <table>
  238. <tbody class="list-row">
  239. <tr class="list-header qm-primary-strong-text">
  240. <td class="first-td">检查项</td>
  241. <td>值</td>
  242. <td>状态</td>
  243. </tr>
  244. <tr>
  245. <td>文件下载</td>
  246. <td>
  247. <div v-if="sound.downloadResolved">
  248. {{ sound.downloadStatus ? "正常" : "出错" }}
  249. </div>
  250. <div v-else>
  251. <PulseLoader />
  252. </div>
  253. </td>
  254. <td>
  255. <div v-if="sound.downloadResolved">
  256. <div v-if="sound.downloadStatus">
  257. <Icon class="pass-check" type="md-checkmark" />
  258. </div>
  259. <div v-else>
  260. <Icon class="fail-cross" title="下载出错" type="md-close" />
  261. </div>
  262. </div>
  263. <div v-else>
  264. <PulseLoader />
  265. </div>
  266. </td>
  267. </tr>
  268. <tr>
  269. <td>声音播放</td>
  270. <td>
  271. <div v-if="sound.playedStatusResolved">
  272. {{ sound.playedStatus ? "正常" : "出错" }}
  273. </div>
  274. <div v-else>
  275. <PulseLoader />
  276. </div>
  277. </td>
  278. <td>
  279. <div v-if="sound.playedStatusResolved">
  280. <div v-if="sound.playedStatus">
  281. <Icon class="pass-check" type="md-checkmark" />
  282. </div>
  283. <div v-else>
  284. <Icon
  285. class="fail-cross"
  286. title="不能播放声音"
  287. type="md-close"
  288. />
  289. </div>
  290. </div>
  291. <div v-else>
  292. <PulseLoader />
  293. </div>
  294. </td>
  295. </tr>
  296. </tbody>
  297. </table>
  298. </div>
  299. </div>
  300. <div v-show="current === 4" class="section">
  301. <div>
  302. <div style="display: flex;">
  303. <div>
  304. <div v-if="wechat.qrValue" style="display: flex">
  305. <qrcode
  306. :value="wechat.qrValue"
  307. :options="{ width: 200 }"
  308. style="margin-left: -10px;"
  309. ></qrcode>
  310. <div style="margin-top: 10px;">
  311. <div style="font-size: 30px;">
  312. 请使用<span style="font-weight: 900; color: #1E90FF;"
  313. >微信</span
  314. >扫描二维码后,在微信小程序上录音,并上传文件。
  315. </div>
  316. <div
  317. v-if="wechat.qrScanned"
  318. style="margin-top: 30px; font-size: 30px;"
  319. >
  320. {{ this.wechat.studentAnswer ? "已上传" : "已扫描" }}
  321. <Icon type="md-checkmark" />
  322. </div>
  323. </div>
  324. </div>
  325. <div v-else>正在获取二维码...</div>
  326. </div>
  327. </div>
  328. <div
  329. class="audio-answer audio-answer-line-height"
  330. style="margin-top: 20px; text-align: left;"
  331. >
  332. <span class="audio-answer-line-height">上传文件:</span>
  333. <audio
  334. class="audio-answer-line-height"
  335. v-if="this.wechat.studentAnswer"
  336. controls
  337. controlsList="nodownload"
  338. :src="this.wechat.studentAnswer"
  339. />
  340. <span v-else class="audio-answer-line-height">未上传文件</span>
  341. </div>
  342. <div style=" margin-top: 30px; display: flex; margin-bottom: 30px;">
  343. <Button
  344. type="warning"
  345. @click="
  346. wechat.qrScannedResolved = true;
  347. wechat.qrScanned = false;
  348. "
  349. title="扫码不成功"
  350. >
  351. 不能正确扫描二维码
  352. </Button>
  353. <div style="width: 30px; height: 30px;"></div>
  354. <Button
  355. type="warning"
  356. @click="
  357. wechat.uploadResolved = true;
  358. wechat.uploadStatus = false;
  359. "
  360. title="上传不成功"
  361. >
  362. 上传不成功
  363. </Button>
  364. </div>
  365. </div>
  366. <div class="list">
  367. <table>
  368. <tbody class="list-row">
  369. <tr class="list-header qm-primary-strong-text">
  370. <td class="first-td">检查项</td>
  371. <td>值</td>
  372. <td>状态</td>
  373. </tr>
  374. <tr>
  375. <td>扫描二维码</td>
  376. <td>
  377. <div v-if="wechat.qrScannedResolved">
  378. {{ wechat.qrScanned ? "正常" : "出错" }}
  379. </div>
  380. <div v-else>
  381. <PulseLoader />
  382. </div>
  383. </td>
  384. <td>
  385. <div v-if="wechat.qrScannedResolved">
  386. <div v-if="wechat.qrScanned">
  387. <Icon class="pass-check" type="md-checkmark" />
  388. </div>
  389. <div v-else>
  390. <Icon class="fail-cross" title="扫描出错" type="md-close" />
  391. </div>
  392. </div>
  393. <div v-else>
  394. <PulseLoader />
  395. </div>
  396. </td>
  397. </tr>
  398. <tr>
  399. <td>上传录音</td>
  400. <td>
  401. <div v-if="wechat.uploadResolved">
  402. {{ wechat.uploadStatus ? "正常" : "出错" }}
  403. </div>
  404. <div v-else>
  405. <PulseLoader />
  406. </div>
  407. </td>
  408. <td>
  409. <div v-if="wechat.uploadResolved">
  410. <div v-if="wechat.uploadStatus">
  411. <Icon class="pass-check" type="md-checkmark" />
  412. </div>
  413. <div v-else>
  414. <Icon class="fail-cross" type="md-close" />
  415. </div>
  416. </div>
  417. <div v-else>
  418. <PulseLoader />
  419. </div>
  420. </td>
  421. </tr>
  422. </tbody>
  423. </table>
  424. </div>
  425. </div>
  426. <div v-show="current === 5" class="section">
  427. <div class="list">
  428. <table>
  429. <tbody class="list-row">
  430. <tr class="list-header qm-primary-strong-text">
  431. <td class="first-td">检查项</td>
  432. <td>结果</td>
  433. </tr>
  434. <tr>
  435. <td>网速</td>
  436. <td>
  437. <div v-if="step1Status">
  438. <Icon class="pass-check" type="md-checkmark" />
  439. </div>
  440. <div v-else>
  441. <Icon class="fail-cross" type="md-close" />
  442. </div>
  443. </td>
  444. </tr>
  445. <tr>
  446. <td>时钟</td>
  447. <td>
  448. <div v-if="step2StatusResolved">
  449. <div v-if="step2Status">
  450. <Icon class="pass-check" type="md-checkmark" />
  451. </div>
  452. <div v-else>
  453. <Icon class="fail-cross" type="md-close" />
  454. </div>
  455. </div>
  456. <div v-else>
  457. <PulseLoader />
  458. </div>
  459. </td>
  460. </tr>
  461. <tr>
  462. <td>摄像头</td>
  463. <td>
  464. <div v-if="step3StatusResolved">
  465. <div v-if="step3Status">
  466. <Icon class="pass-check" type="md-checkmark" />
  467. </div>
  468. <div v-else>
  469. <Icon class="fail-cross" type="md-close" />
  470. </div>
  471. </div>
  472. <div v-else class="fail-cross">
  473. 请在“摄像头”步骤进行人工确认!
  474. </div>
  475. </td>
  476. </tr>
  477. <tr>
  478. <td>声音</td>
  479. <td>
  480. <div v-if="step4StatusResolved">
  481. <div v-if="step4Status">
  482. <Icon class="pass-check" type="md-checkmark" />
  483. </div>
  484. <div v-else>
  485. <Icon class="fail-cross" type="md-close" />
  486. </div>
  487. </div>
  488. <div v-else class="fail-cross">
  489. 请在“声音”步骤进行人工确认!
  490. </div>
  491. </td>
  492. </tr>
  493. <tr>
  494. <td>微信小程序</td>
  495. <td>
  496. <div v-if="step5StatusResolved">
  497. <div v-if="step5Status">
  498. <Icon class="pass-check" type="md-checkmark" />
  499. </div>
  500. <div v-else>
  501. <Icon class="fail-cross" type="md-close" />
  502. </div>
  503. </div>
  504. <div v-else class="fail-cross">
  505. 请在“微信小程序”步骤进行人工确认!
  506. </div>
  507. </td>
  508. </tr>
  509. </tbody>
  510. </table>
  511. </div>
  512. <div style="color: red;">
  513. <div v-if="!step1Status">
  514. 检查网络是否连接,路由器是否正常工作。
  515. </div>
  516. <div v-if="step2StatusResolved && !step2Status">
  517. 请调整电脑时间和社区与北京时间一致。
  518. </div>
  519. <div v-if="step3StatusResolved && !step3Status">
  520. 请确认摄像头连接线正常,能正常工作,关闭杀毒软件、关闭摄像头滤镜软件;请确认您的电脑是否为双摄摄像头,启用的摄像头是否正确。
  521. </div>
  522. <div v-if="step4StatusResolved && !step4Status">
  523. 请确认音箱连接正常,调整音量开关及大小。
  524. </div>
  525. <div v-if="step5StatusResolved && !step5Status">
  526. 请确认微信已登录并连接网络。
  527. </div>
  528. <div
  529. v-if="
  530. !step1Status ||
  531. (step2StatusResolved && !step2Status) ||
  532. (step3StatusResolved && !step3Status) ||
  533. (step4StatusResolved && !step4Status) ||
  534. (step5StatusResolved && !step5Status)
  535. "
  536. >
  537. 请按提示检查并调试,调试后可再次进行环境检测,如仍有问题可致电
  538. 400-8585-755
  539. </div>
  540. </div>
  541. </div>
  542. <div style="margin-top: 30px; text-align: center;">
  543. <Button type="primary" @click="previous" :disabled="current === 0">
  544. 上一步
  545. </Button>
  546. <div style="width: 30px; height: 1px; display: inline-block;"></div>
  547. <Button type="primary" @click="next" :disabled="current === 5">
  548. 下一步
  549. </Button>
  550. <div style="width: 30px; height: 1px; display: inline-block;"></div>
  551. <Button
  552. type="primary"
  553. @click="() => this.$emit('on-close')"
  554. v-if="current === 5"
  555. >
  556. 进入考试
  557. </Button>
  558. </div>
  559. </div>
  560. </template>
  561. <script>
  562. import moment from "moment";
  563. // import "moment-duration-format";
  564. import VueQrcode from "@chenfengyuan/vue-qrcode";
  565. import PulseLoader from "vue-spinner/src/PulseLoader.vue";
  566. import {
  567. openWS,
  568. closeWsWithoutReconnect,
  569. getQRCode,
  570. } from "@/features/OnlineExam/Examing/ws";
  571. import { createNamespacedHelpers } from "vuex";
  572. const { mapState } = createNamespacedHelpers("examingHomeModule");
  573. const CLOCK_RATE_TIMEOUT = 10;
  574. // console.log(
  575. // moment.duration(-new Date().getTimezoneOffset(), "minutes").format("hh:mm")
  576. // );
  577. export default {
  578. name: "check-computer",
  579. data() {
  580. return {
  581. current: 0,
  582. // status: ["process", "wait", "wait", "wait", "wait"],
  583. network: {
  584. downlink: navigator.connection.downlink,
  585. downlinkStatus: navigator.connection.downlink > 0.5,
  586. rrt: navigator.connection.rtt,
  587. rrtStatus: navigator.connection.rtt < 1000,
  588. },
  589. time: {
  590. // nowDate: Date.now(),
  591. // timeDifference: 0,
  592. currentTimeZone: moment().format("Z"),
  593. timeZoneStatus: new Date().getTimezoneOffset() / 60 === -8,
  594. clockRateDiff: null,
  595. clockRateStateResolved: false,
  596. clockRateStatus: false,
  597. },
  598. camera: {
  599. status: false,
  600. resolved: false,
  601. },
  602. sound: {
  603. downloadResolved: false,
  604. downloadStatus: false,
  605. playedStatusResolved: false,
  606. playedStatus: false,
  607. },
  608. wechat: {
  609. qrValue: " ",
  610. qrScannedResolved: false,
  611. qrScanned: false,
  612. uploadResolved: false,
  613. uploadStatus: false,
  614. studentAnswer: null,
  615. examRecordDataId: null,
  616. },
  617. };
  618. },
  619. async created() {
  620. this.getNowInterval = setInterval(() => {
  621. this.nowDate = Date.now();
  622. }, 1000);
  623. openWS({ examRecordDataId: this.$store.state.user.id });
  624. if (!getQRCode(1, "AUDIO", { testEnv: true })) {
  625. setTimeout(() => {
  626. getQRCode(1, "AUDIO", { testEnv: true });
  627. }, 3000);
  628. }
  629. },
  630. async mounted() {
  631. let start, end;
  632. // const networkStart = performance.now();
  633. fetch("/oe/login", { Method: "HEAD" }).then(e => {
  634. start = moment(e.headers.get("date"));
  635. // const networkEnd = performance.now();
  636. // this.timeDifference =
  637. // Math.abs(moment().diff(start) - (networkEnd - networkStart) / 2) <
  638. // 60 * 1000
  639. // ? "OK"
  640. // : "与服务器时间差异超过60秒";
  641. });
  642. this.checkClockRateTimeout = setTimeout(() => {
  643. fetch("/oe/login", { Method: "HEAD" }).then(e => {
  644. end = moment(e.headers.get("date"));
  645. this.time.clockRateStateResolved = true;
  646. this.time.clockRateDiff =
  647. end.diff(start, "seconds") - CLOCK_RATE_TIMEOUT;
  648. this.time.clockRateStatus =
  649. end.diff(start, "seconds") < CLOCK_RATE_TIMEOUT + 2;
  650. });
  651. }, CLOCK_RATE_TIMEOUT * 1000);
  652. await this.openCamera();
  653. },
  654. beforeDestroy() {
  655. if (this.$refs.video.srcObject) {
  656. this.$refs.video.srcObject.getTracks().forEach(function(track) {
  657. track.stop();
  658. });
  659. }
  660. clearInterval(this.getNowInterval);
  661. clearTimeout(this.checkClockRateTimeout);
  662. closeWsWithoutReconnect();
  663. },
  664. methods: {
  665. previous() {
  666. if (this.current >= 0) {
  667. this.current -= 1;
  668. }
  669. },
  670. next() {
  671. if (this.current < 5) {
  672. this.current += 1;
  673. }
  674. if (this.current === 5) {
  675. window._hmt.push([
  676. "_trackEvent",
  677. "环境检测",
  678. `网络: ${this.step1Status}; 时间: ${this.step2Status}; 摄像头: ${this.step3Status}; 声音: ${this.step4Status}; 小程序: ${this.step5Status};`,
  679. ]);
  680. }
  681. },
  682. async openCamera() {
  683. const video = this.$refs.video;
  684. if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  685. try {
  686. console.log("启动摄像头");
  687. const stream = await navigator.mediaDevices.getUserMedia({
  688. video: {
  689. facingMode: "user",
  690. // width: 400,
  691. // height: this.showRecognizeButton ? 300 : 250
  692. },
  693. });
  694. if (stream) {
  695. video.srcObject = stream;
  696. try {
  697. await video.play();
  698. // this.camera = true;
  699. } catch (error) {
  700. console.log("摄像头没有正常启用", error);
  701. this.$Message.error({
  702. content: "摄像头没有正常启用",
  703. duration: 15,
  704. closable: true,
  705. });
  706. }
  707. } else {
  708. this.$Message.error({
  709. content: "没有可用的视频流",
  710. duration: 15,
  711. closable: true,
  712. });
  713. window._hmt.push([
  714. "_trackEvent",
  715. "摄像头框-环境检测",
  716. "摄像头状态",
  717. "没有可用的视频流",
  718. ]);
  719. }
  720. } catch (error) {
  721. console.log("无法启用摄像头", error);
  722. this.$Message.error({
  723. content: "无法启用摄像头",
  724. duration: 15,
  725. closable: true,
  726. });
  727. window._hmt.push([
  728. "_trackEvent",
  729. "摄像头框-环境检测",
  730. "摄像头状态",
  731. "无法启用摄像头",
  732. ]);
  733. }
  734. } else {
  735. this.$Message.error({
  736. content: "没有找到可用的摄像头",
  737. duration: 15,
  738. closable: true,
  739. });
  740. window._hmt.push([
  741. "_trackEvent",
  742. "摄像头框-环境检测",
  743. "摄像头状态",
  744. "没有找到可用的摄像头",
  745. ]);
  746. }
  747. },
  748. },
  749. computed: {
  750. ...mapState([
  751. "questionQrCode",
  752. "questionQrCodeScanned",
  753. "questionAnswerFileUrl",
  754. ]),
  755. timeCurrent() {
  756. return moment(this.nowDate)
  757. .utcOffset("+08:00")
  758. .format("YYYY-MM-DD HH:mm:ssZZ");
  759. },
  760. step1Status() {
  761. return this.network.downlinkStatus && this.network.rrtStatus;
  762. },
  763. step2StatusResolved() {
  764. return this.time.clockRateStateResolved;
  765. },
  766. step2Status() {
  767. return this.time.timeZoneStatus && this.time.clockRateStatus;
  768. },
  769. step3StatusResolved() {
  770. return this.camera.cameraResolved;
  771. },
  772. step3Status() {
  773. return this.camera.status;
  774. },
  775. step4StatusResolved() {
  776. return this.sound.downloadResolved && this.sound.playedStatusResolved;
  777. },
  778. step4Status() {
  779. return this.sound.downloadStatus && this.sound.playedStatus;
  780. },
  781. step5StatusResolved() {
  782. return this.wechat.qrScannedResolved && this.wechat.uploadResolved;
  783. },
  784. step5Status() {
  785. return this.wechat.qrScanned && this.wechat.uploadStatus;
  786. },
  787. },
  788. watch: {
  789. questionQrCode(value) {
  790. // console.log(this.examQuestion.studentAnswer);
  791. // console.log("watch", value);
  792. this.wechat.qrValue = value.qrCode;
  793. this.wechat.examRecordDataId = decodeURIComponent(value.qrCode).match(
  794. /&examRecordDataId=(\d+)&/
  795. )[1];
  796. },
  797. questionQrCodeScanned() {
  798. // console.log(this.examQuestion.studentAnswer);
  799. // console.log("watch", value);
  800. this.wechat.qrScanned = true;
  801. this.wechat.qrScannedResolved = true;
  802. },
  803. questionAnswerFileUrl(value) {
  804. // console.log(this.examQuestion.studentAnswer);
  805. // console.log("watch", value);
  806. const examRecordDataId = this.wechat.examRecordDataId;
  807. for (const q of value) {
  808. if (!q.saved) {
  809. let acknowledgeStatus = "CONFIRMED";
  810. this.$http
  811. .post(
  812. "/api/ecs_oe_student/examControl/saveUploadedFileAcknowledgeStatus",
  813. {
  814. examRecordDataId,
  815. filePath: q.fileUrl,
  816. order: q.order,
  817. acknowledgeStatus,
  818. }
  819. )
  820. .then(() => {
  821. this.wechat.studentAnswer = q.fileUrl;
  822. this.wechat.uploadResolved = true;
  823. this.wechat.uploadStatus = true;
  824. q.saved = true;
  825. if (acknowledgeStatus === "CONFIRMED")
  826. this.$Message.info({
  827. content: "小程序作答已更新",
  828. duration: 5,
  829. closable: true,
  830. });
  831. })
  832. .catch(() => {
  833. this.$Message.error({
  834. content: "更新小程序答案失败!",
  835. duration: 15,
  836. closable: true,
  837. });
  838. });
  839. }
  840. }
  841. },
  842. },
  843. components: {
  844. qrcode: VueQrcode,
  845. PulseLoader,
  846. },
  847. };
  848. </script>
  849. <style scoped>
  850. .section {
  851. margin-top: 10px;
  852. }
  853. .list {
  854. border: 1px solid #eeeeee;
  855. border-radius: 6px;
  856. text-align: center;
  857. }
  858. .list table {
  859. width: 100%;
  860. border-collapse: collapse !important;
  861. border-spacing: 0;
  862. }
  863. .list td {
  864. border: 1px solid #eeeeee;
  865. border-radius: 6px;
  866. border-collapse: separate !important;
  867. padding: 10px;
  868. }
  869. .list .first-td {
  870. width: 50%;
  871. }
  872. .pass-check {
  873. font-size: 20px;
  874. color: green;
  875. }
  876. .fail-cross {
  877. font-size: 20px;
  878. color: red;
  879. }
  880. </style>