FaceRecognition.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <template>
  2. <div>
  3. <video id="video" ref="video" :width="width" :height="height" autoplay>
  4. </video>
  5. <div v-if="showRecognizeButton" style="position: absolute; width: 400px; text-align: center; margin-top: -50px; color: #232323;">
  6. <button class="verify-button" @click="snap" :disabled="disableSnap">{{msg}}</button>
  7. </div>
  8. </div>
  9. </template>
  10. <script>
  11. import { mapState as globalMapState } from "vuex";
  12. import { createNamespacedHelpers } from "vuex";
  13. const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
  14. export default {
  15. name: "FaceRecognition",
  16. data() {
  17. return { disableSnap: false, msg: "开始识别" };
  18. },
  19. props: {
  20. width: String,
  21. height: String,
  22. showRecognizeButton: Boolean,
  23. closeCamera: Boolean // optional
  24. },
  25. async mounted() {
  26. this.openCamera();
  27. },
  28. watch: {
  29. snapNow(val) {
  30. if (val) {
  31. this.snapTimer();
  32. this.toggleSnapNow();
  33. }
  34. },
  35. closeCamera: function(newValue) {
  36. if (newValue) {
  37. console.log("关闭摄像头");
  38. this.$refs.video.srcObject.getTracks().forEach(function(track) {
  39. track.stop();
  40. });
  41. } else {
  42. this.openCamera();
  43. }
  44. }
  45. },
  46. beforeDestroy() {
  47. this.$refs.video.srcObject.getTracks().forEach(function(track) {
  48. track.stop();
  49. });
  50. },
  51. methods: {
  52. ...mapMutations(["toggleSnapNow", "decreaseSnapCount"]),
  53. async openCamera() {
  54. const video = this.$refs.video;
  55. if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  56. try {
  57. console.log("启动摄像头");
  58. const stream = await navigator.mediaDevices.getUserMedia({
  59. video: {
  60. facingMode: "user"
  61. // width: 400,
  62. // height: this.showRecognizeButton ? 300 : 250
  63. }
  64. });
  65. video.srcObject = stream;
  66. video.play();
  67. } catch (error) {
  68. console.log(error);
  69. this.$Message.error("无法启用摄像头");
  70. }
  71. } else {
  72. this.$Message.error("没有找到可用的摄像头");
  73. }
  74. },
  75. async snapTimer() {
  76. const captureBlob = await this.getSnapShot();
  77. const [fileName, captureFilePath] = await this.uploadToServer(
  78. captureBlob
  79. );
  80. this.decreaseSnapCount();
  81. await this.faceCompare(fileName, captureFilePath);
  82. const video = this.$refs.video;
  83. video && video.play();
  84. },
  85. async snap() {
  86. this.disableSnap = true;
  87. this.msg = "拍照中...";
  88. const captureBlob = await this.getSnapShot();
  89. this.msg = "上传照片中...";
  90. const [, captureFilePath] = await this.uploadToServer(captureBlob);
  91. this.msg = "人脸比对中...";
  92. await this.faceCompareSync(captureFilePath);
  93. const video = this.$refs.video;
  94. video && video.play();
  95. this.msg = "开始识别";
  96. this.disableSnap = false;
  97. },
  98. async getSnapShot() {
  99. return new Promise(resolve => {
  100. const video = this.$refs.video;
  101. video.pause();
  102. var canvas = document.createElement("canvas");
  103. canvas.width = 220;
  104. canvas.height = 165;
  105. var context = canvas.getContext("2d");
  106. context.drawImage(video, 0, 0, 220, 165);
  107. canvas.toBlob(resolve);
  108. });
  109. },
  110. async uploadToServer(captureBlob) {
  111. //保存抓拍照片到又拍云
  112. var fileName = new Date().getTime() + ".jpg";
  113. var fileUrl = "/capture_photo/" + this.user.userId + "/" + fileName;
  114. try {
  115. await this.$upyunhttp.put(fileUrl, captureBlob, {
  116. headers: {
  117. "Content-Type": "image/jpeg"
  118. }
  119. });
  120. } catch (e) {
  121. console.log(e);
  122. this.$Message.error(e.message);
  123. return;
  124. }
  125. const UPYUN_URL = (await this.$http.get("/api/ecs_oe_student_face/upyun"))
  126. .data.downloadPrefix;
  127. return [
  128. fileName,
  129. UPYUN_URL + "/capture_photo/" + this.user.userId + "/" + fileName
  130. ];
  131. },
  132. async faceCompareSync(captureFilePath) {
  133. try {
  134. const res = await this.$http.post(
  135. "/api/ecs_oe_student_face/examCaptureQueue/compareFaceSync?fileUrl=" +
  136. captureFilePath
  137. );
  138. // TODO: 识别成功、失败的通知或跳转
  139. this.$emit("on-recognize-result", {
  140. error: null,
  141. pass: res.data.isPass,
  142. stranger: res.data.isStranger
  143. });
  144. } catch (e) {
  145. console.log(e);
  146. this.$Message.error(e.message);
  147. return;
  148. }
  149. },
  150. async faceCompare(fileName, captureFilePath) {
  151. try {
  152. await this.$http.post(
  153. "/api/ecs_oe_student_face/examCaptureQueue/uploadExamCapture?fileUrl=" +
  154. captureFilePath +
  155. "&fileName=" +
  156. fileName +
  157. "&examRecordDataId=" +
  158. this.$route.params.examRecordDataId
  159. );
  160. const examRecordDataId = this.$route.params.examRecordDataId;
  161. await this.showSnapResult(fileName, examRecordDataId);
  162. } catch (e) {
  163. console.log(e);
  164. this.$Message.error(e.message);
  165. return;
  166. }
  167. },
  168. async showSnapResult(fileName, examRecordDataId) {
  169. try {
  170. // 获取抓拍结果
  171. const snapRes =
  172. (await this.$http.get(
  173. "/api/ecs_oe_student_face/examCaptureQueue/getExamCaptureResult?fileName=" +
  174. fileName +
  175. "&examRecordDataId=" +
  176. examRecordDataId
  177. )).data || {};
  178. if (this.$route.name !== "OnlineExamingHome") {
  179. // 非考试页,不显示结果,也不继续查询
  180. return;
  181. }
  182. if (snapRes.isCompleted) {
  183. if (snapRes.isStranger) {
  184. this.$Message.error("请独立考试");
  185. } else if (!snapRes.isPass) {
  186. this.$Message.error("请保持正确坐姿");
  187. }
  188. } else {
  189. setTimeout(
  190. this.showSnapResult.bind(this, fileName, examRecordDataId),
  191. 30 * 1000
  192. );
  193. }
  194. } catch (e) {
  195. console.log(e);
  196. this.$Message.error(e.message);
  197. }
  198. }
  199. },
  200. computed: {
  201. ...globalMapState(["user"]),
  202. ...mapState(["snapNow"])
  203. }
  204. };
  205. </script>
  206. <style scoped>
  207. .verify-button {
  208. font-size: 16px;
  209. background-color: #ffcc00;
  210. display: inline-block;
  211. padding: 6px 16px;
  212. border-radius: 6px;
  213. }
  214. .verify-button:hover {
  215. color: #444444;
  216. cursor: pointer;
  217. }
  218. </style>