camera.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  1. "use strict";
  2. const common_vendor = require("../../common/vendor.js");
  3. const api_user = require("../../api/user.js");
  4. const common_config = require("../../common/config.js");
  5. const common_assets = require("../../common/assets.js");
  6. const _sfc_main = {
  7. data() {
  8. return {
  9. cameraContext: null,
  10. devicePosition: "front",
  11. interviewStarted: true,
  12. showEndModal: false,
  13. currentQuestionIndex: 0,
  14. currentStep: 1,
  15. stepTexts: ["测试准备", "回答问题", "测试结束"],
  16. progressWidth: 50,
  17. remainingTime: "00:27",
  18. selectedOption: null,
  19. selectedOptions: [],
  20. showResult: false,
  21. isAnswerCorrect: false,
  22. questions: [],
  23. // 改为空数组,将通过API获取
  24. interviewId: null,
  25. // 存储当前面试ID
  26. useVideo: false,
  27. timerInterval: null,
  28. score: 0,
  29. totalQuestions: 0,
  30. interviewCompleted: false,
  31. digitalHumanUrl: "",
  32. // 数字人URL
  33. loading: true,
  34. // 添加加载状态
  35. loadError: false,
  36. // 添加加载错误状态
  37. errorMessage: "",
  38. // 添加错误消息
  39. answers: [],
  40. // 存储用户的所有答案
  41. currentQuestionDetail: null,
  42. // 当前题目详情
  43. isSubmitting: false,
  44. // 是否正在提交答案
  45. openQuestionAnswer: "",
  46. // 存储开放问题的答案
  47. currentAnswer: null,
  48. // 存储当前答案以便提交
  49. questionStartTime: null,
  50. // 存储问题开始时间
  51. mode: "normal",
  52. // 相机模式
  53. currentScrollId: "question-0",
  54. // 当前滚动到的问题ID
  55. questionGroups: [],
  56. // 存储分组后的题目
  57. currentGroupIndex: 0,
  58. // 当前显示的组索引
  59. showContinueButton: false,
  60. // 是否显示继续按钮
  61. scrollToBottom: false,
  62. // 新增:是否需要滚动到底部
  63. scrollTop: 0,
  64. // 控制 scroll-view 的滚动位置
  65. _clickTimer: null,
  66. _isClicking: false,
  67. showPromptModal: false,
  68. // 添加新的数据属性
  69. personDetectionSocket: null,
  70. // WebSocket对象
  71. personDetectionInterval: null,
  72. // 定时器对象
  73. showCameraWarning: false,
  74. // 添加新的数据属性
  75. showPageWarning: false,
  76. // 添加新的数据属性
  77. statusBarHeight: 0
  78. };
  79. },
  80. computed: {
  81. currentQuestion() {
  82. console.log(this.questions[this.currentQuestionIndex]);
  83. return this.questions[this.currentQuestionIndex];
  84. }
  85. },
  86. onLoad(options) {
  87. if (options && options.id) {
  88. this.interviewId = options.id;
  89. this.fetchInterviewData();
  90. } else {
  91. this.fetchInterviewList();
  92. }
  93. this.statusBarHeight = common_vendor.index.getSystemInfoSync().statusBarHeight || 0;
  94. },
  95. onReady() {
  96. this.cameraContext = common_vendor.index.createCameraContext();
  97. if (this.useVideo) {
  98. this.aiVideoContext = common_vendor.index.createVideoContext("aiInterviewer");
  99. }
  100. this.initDigitalHuman();
  101. },
  102. mounted() {
  103. common_vendor.index.onUserCaptureScreen(() => {
  104. console.log("User captured screen");
  105. this.screenCaptureCount++;
  106. if (this.screenCaptureCount === 1) {
  107. common_vendor.index.showModal({
  108. title: "Warning",
  109. content: "检测到屏幕截图。如果你再次捕捉屏幕,你的面试将无效.",
  110. showCancel: false,
  111. confirmText: "OK"
  112. });
  113. } else if (this.screenCaptureCount >= 2) {
  114. common_vendor.index.showModal({
  115. title: "Interview Invalid",
  116. content: "由于多次屏幕截图,您的面试已无效。请联系招聘人员寻求帮助.",
  117. showCancel: false,
  118. confirmText: "OK",
  119. success: () => {
  120. this.invalidateInterview();
  121. }
  122. });
  123. }
  124. });
  125. },
  126. beforeDestroy() {
  127. this.cleanupPersonDetectionWebSocket();
  128. },
  129. methods: {
  130. initPersonDetectionWebSocket() {
  131. if (this.personDetectionSocket) {
  132. this.cleanupPersonDetectionWebSocket();
  133. }
  134. try {
  135. this.personDetectionSocket = common_vendor.index.connectSocket({
  136. url: `${common_config.personDetectionWsUrl}/ws/interview-room/room_${common_vendor.index.getStorageSync("appId")}/${common_vendor.index.getStorageSync("appId")}/`,
  137. success: () => {
  138. console.log("WebSocket connection initiated");
  139. },
  140. fail: (error) => {
  141. console.error("WebSocket connection failed:", error);
  142. }
  143. });
  144. this.personDetectionSocket.onOpen(() => {
  145. console.log("WebSocket connection opened");
  146. this.startPersonDetectionInterval();
  147. });
  148. this.personDetectionSocket.onError((error) => {
  149. console.error("WebSocket error:", error);
  150. this.cleanupPersonDetectionWebSocket();
  151. });
  152. this.personDetectionSocket.onClose(() => {
  153. console.log("WebSocket connection closed");
  154. this.cleanupPersonDetectionWebSocket();
  155. });
  156. this.personDetectionSocket.onMessage((res2) => {
  157. try {
  158. const data = JSON.parse(res2.data);
  159. console.log(data);
  160. if (data.type === "person_detection_result") {
  161. this.handlePersonDetectionResult(data);
  162. }
  163. } catch (error) {
  164. console.error("Error parsing WebSocket message:", error);
  165. }
  166. });
  167. } catch (error) {
  168. console.error("Error initializing WebSocket:", error);
  169. this.cleanupPersonDetectionWebSocket();
  170. }
  171. },
  172. startPersonDetectionInterval() {
  173. if (this.personDetectionInterval) {
  174. clearInterval(this.personDetectionInterval);
  175. }
  176. this.personDetectionInterval = setInterval(() => {
  177. if (this.personDetectionSocket && this.cameraContext) {
  178. this.cameraContext.takePhoto({
  179. quality: "low",
  180. success: (res2) => {
  181. const tempFilePath = res2.tempImagePath;
  182. common_vendor.index.getFileSystemManager().readFile({
  183. filePath: tempFilePath,
  184. encoding: "base64",
  185. success: (res3) => {
  186. const base64Image = res3.data;
  187. this.personDetectionSocket.send({
  188. data: JSON.stringify({
  189. type: "person_detection",
  190. image_data: base64Image
  191. })
  192. });
  193. },
  194. fail: (error) => {
  195. console.error("Error reading image file:", error);
  196. }
  197. });
  198. },
  199. fail: (error) => {
  200. console.error("Error taking photo:", error);
  201. }
  202. });
  203. }
  204. }, 5e3);
  205. },
  206. cleanupPersonDetectionWebSocket() {
  207. if (this.personDetectionInterval) {
  208. clearInterval(this.personDetectionInterval);
  209. this.personDetectionInterval = null;
  210. }
  211. if (this.personDetectionSocket) {
  212. try {
  213. this.personDetectionSocket.close();
  214. } catch (error) {
  215. console.error("Error closing WebSocket:", error);
  216. }
  217. this.personDetectionSocket = null;
  218. }
  219. },
  220. handlePersonDetectionResult(data) {
  221. console.log(data.data.detection.has_person);
  222. console.log(data.data.identity.status);
  223. if (data.data.identity.status !== "identity_verified") {
  224. this.showPageWarning = true;
  225. common_vendor.index.showToast({
  226. title: data.data.identity.message,
  227. icon: "none",
  228. duration: 3e3
  229. });
  230. common_vendor.index.vibrateLong({
  231. success: function() {
  232. console.log("Vibration successful");
  233. },
  234. fail: function(err) {
  235. console.error("Vibration failed:", err);
  236. }
  237. });
  238. setTimeout(() => {
  239. this.showPageWarning = false;
  240. }, 3e3);
  241. }
  242. },
  243. // 添加作废面试的方法
  244. invalidateInterview() {
  245. if (this.isRecording) {
  246. this.stopRecordingAnswer();
  247. }
  248. if (this.recordingTimer) {
  249. clearInterval(this.recordingTimer);
  250. }
  251. this.isRecording = false;
  252. this.showStopRecordingButton = false;
  253. this.showStartRecordingButton = false;
  254. setTimeout(() => {
  255. common_vendor.index.switchTab({
  256. url: "/pages/index/index"
  257. });
  258. }, 500);
  259. const systemInfo = common_vendor.index.getSystemInfoSync();
  260. const isMiniProgram = systemInfo.uniPlatform && systemInfo.uniPlatform.startsWith("mp-");
  261. if (isMiniProgram) {
  262. this.initPersonDetectionWebSocket();
  263. }
  264. },
  265. // 获取面试列表
  266. async fetchInterviewList() {
  267. try {
  268. this.loading = true;
  269. const res2 = await api_user.getQuestions({
  270. position_id: JSON.parse(common_vendor.index.getStorageSync("selectedJob")).id,
  271. page: 1,
  272. limit: 999,
  273. tenant_id: JSON.parse(common_vendor.index.getStorageSync("userInfo")).tenant_id || 1
  274. });
  275. console.log(res2.items);
  276. this.interviewId = res2.items;
  277. this.fetchInterviewData(res2.items);
  278. } catch (error) {
  279. console.error("获取面试列表失败:", error);
  280. this.handleLoadError("获取面试列表失败");
  281. }
  282. },
  283. // 获取面试详情数据
  284. async fetchInterviewData(data) {
  285. try {
  286. this.loading = true;
  287. if (data && Array.isArray(data)) {
  288. const formattedQuestions = data.filter((q) => q.question_form !== 0).map((q, index) => ({
  289. id: q.id || index + 1,
  290. text: q.question || "未知问题",
  291. options: q.options || [],
  292. correctAnswer: q.correctAnswer || 0,
  293. isImportant: q.is_system || false,
  294. explanation: q.explanation || "",
  295. questionType: q.question_form,
  296. questionTypeName: q.question_form_name || "单选题",
  297. is_required_correct: q.is_required_correct || false,
  298. correctAnswers: q.correct_answers || [],
  299. difficulty: q.difficulty || 1,
  300. difficultyName: q.difficulty_name || "初级",
  301. imageUrl: q.question_image_url || "",
  302. // 添加图片URL字段
  303. category: q.category || 0
  304. // 添加 category 字段
  305. }));
  306. this.processQuestionGroups(formattedQuestions);
  307. } else {
  308. this.processInterviewData(res);
  309. }
  310. console.log(this.questions);
  311. this.totalQuestions = this.questions.length;
  312. } catch (error) {
  313. console.error("获取面试详情失败:", error);
  314. this.handleLoadError("获取面试详情失败");
  315. } finally {
  316. this.loading = false;
  317. }
  318. },
  319. // 处理面试数据
  320. processInterviewData(data) {
  321. this.questions = [];
  322. if (data && data.question_form !== 0) {
  323. const formattedQuestion = {
  324. id: data.id || 1,
  325. text: data.question || "未知问题",
  326. options: data.options || [],
  327. correctAnswer: data.correctAnswer || 0,
  328. isImportant: data.is_system || false,
  329. explanation: data.explanation || "",
  330. questionType: data.question_form,
  331. // 1-单选题,2-多选题,3-看图答题
  332. questionTypeName: data.question_form_name || "单选题",
  333. is_required_correct: data.is_required_correct || false,
  334. correctAnswers: data.correct_answers || [],
  335. difficulty: data.difficulty || 1,
  336. difficultyName: data.difficulty_name || "初级",
  337. imageUrl: data.question_image_url || ""
  338. // 添加图片URL字段
  339. };
  340. this.questions.push(formattedQuestion);
  341. this.totalQuestions = this.questions.length;
  342. } else {
  343. this.handleLoadError("没有可用的选择题");
  344. }
  345. },
  346. // 处理加载错误
  347. handleLoadError(message) {
  348. this.loadError = true;
  349. this.loading = false;
  350. this.errorMessage = message || "加载失败";
  351. common_vendor.index.showToast({
  352. title: message || "加载失败",
  353. icon: "none",
  354. duration: 2e3
  355. });
  356. },
  357. startTimer() {
  358. if (this.questions.length === 0)
  359. return;
  360. this.questionStartTime = /* @__PURE__ */ new Date();
  361. let seconds = 60;
  362. this.remainingTime = `01:00`;
  363. this.timerInterval = setInterval(() => {
  364. seconds--;
  365. if (seconds <= 0) {
  366. clearInterval(this.timerInterval);
  367. if (!this.showResult) {
  368. this.checkAnswer();
  369. setTimeout(() => {
  370. if (this.currentQuestionIndex < this.questions.length - 1) {
  371. this.goToNextQuestion();
  372. } else {
  373. common_vendor.index.navigateTo({
  374. url: "/pages/posture-guide/posture-guide"
  375. //'/pages/interview-question/interview-question'
  376. });
  377. }
  378. }, 1500);
  379. }
  380. }
  381. const min = Math.floor(seconds / 60).toString().padStart(2, "0");
  382. const sec = (seconds % 60).toString().padStart(2, "0");
  383. this.remainingTime = `${min}:${sec}`;
  384. }, 1e3);
  385. },
  386. resetTimer() {
  387. clearInterval(this.timerInterval);
  388. },
  389. selectOption(index) {
  390. if (this.currentQuestionIndex === this.questions.length - 1 && this.showContinueButton) {
  391. common_vendor.index.showToast({
  392. title: "该题目已完成,请继续下一部分",
  393. icon: "none",
  394. duration: 2e3
  395. });
  396. return;
  397. }
  398. if (this.showResult) {
  399. this.showResult = false;
  400. }
  401. console.log("selectOption", index);
  402. if (this.currentQuestion.questionType === 2) {
  403. const optionIndex = this.selectedOptions.indexOf(index);
  404. if (optionIndex > -1) {
  405. this.selectedOptions.splice(optionIndex, 1);
  406. } else {
  407. this.selectedOptions.push(index);
  408. }
  409. } else if (this.currentQuestion.questionType === 1 || this.currentQuestion.questionType === 3 || this.currentQuestion.questionType === 4) {
  410. this.selectedOption = index;
  411. this.playAiSpeaking();
  412. setTimeout(() => {
  413. this.checkAnswer();
  414. this.saveAnswer();
  415. this.submitCurrentAnswer().then(() => {
  416. setTimeout(() => {
  417. if (this.currentQuestionIndex < this.answers.length - 1) {
  418. this.currentQuestionIndex++;
  419. this.currentScrollId = "question-" + this.currentQuestionIndex;
  420. } else {
  421. this.goToNextQuestion();
  422. }
  423. }, 800);
  424. }).catch((error) => {
  425. console.error("提交答案失败:", error);
  426. setTimeout(() => {
  427. if (this.currentQuestionIndex < this.answers.length - 1) {
  428. this.currentQuestionIndex++;
  429. this.currentScrollId = "question-" + this.currentQuestionIndex;
  430. } else {
  431. this.goToNextQuestion();
  432. }
  433. }, 1e3);
  434. });
  435. }, 500);
  436. } else {
  437. this.selectedOption = index;
  438. const answer = {
  439. questionId: this.currentQuestion.id,
  440. questionType: this.currentQuestion.questionType,
  441. answer: index
  442. };
  443. const existingIndex = this.answers.findIndex((a) => a.questionId === answer.questionId);
  444. if (existingIndex > -1) {
  445. this.answers[existingIndex] = answer;
  446. } else {
  447. this.answers.push(answer);
  448. }
  449. this.playAiSpeaking();
  450. setTimeout(() => {
  451. this.checkAnswer();
  452. this.saveAnswer();
  453. this.submitCurrentAnswer().then(() => {
  454. setTimeout(() => {
  455. if (this.currentQuestionIndex < this.answers.length - 1) {
  456. this.currentQuestionIndex++;
  457. this.currentScrollId = "question-" + this.currentQuestionIndex;
  458. } else {
  459. this.goToNextQuestion();
  460. }
  461. }, 800);
  462. }).catch((error) => {
  463. console.error("提交答案失败:", error);
  464. setTimeout(() => {
  465. if (this.currentQuestionIndex < this.answers.length - 1) {
  466. this.currentQuestionIndex++;
  467. this.currentScrollId = "question-" + this.currentQuestionIndex;
  468. } else {
  469. this.goToNextQuestion();
  470. }
  471. }, 1e3);
  472. });
  473. }, 500);
  474. }
  475. },
  476. checkAnswer() {
  477. if (this.showResult)
  478. return;
  479. clearInterval(this.timerInterval);
  480. if (!this.currentQuestion) {
  481. console.error("当前问题不存在");
  482. return;
  483. }
  484. if (this.currentQuestion.questionType === 0) {
  485. this.isAnswerCorrect = true;
  486. } else if (this.currentQuestion.questionType === 2) {
  487. const sortedSelected = [...this.selectedOptions].sort();
  488. const sortedCorrect = [...this.currentQuestion.correctAnswers].sort();
  489. if (sortedSelected.length !== sortedCorrect.length) {
  490. this.isAnswerCorrect = false;
  491. } else {
  492. this.isAnswerCorrect = sortedSelected.every((value, index) => value === sortedCorrect[index]);
  493. }
  494. } else {
  495. this.isAnswerCorrect = this.selectedOption === this.currentQuestion.correctAnswer;
  496. }
  497. this.showResult = true;
  498. this.saveAnswer();
  499. if (this.currentQuestionIndex === this.questions.length - 1) {
  500. this.$nextTick(() => {
  501. this.$forceUpdate();
  502. });
  503. }
  504. },
  505. // 修改 nextQuestion 方法,只处理多选题
  506. nextQuestion() {
  507. if (this.currentQuestion.questionType !== 2)
  508. return;
  509. this.checkAnswer();
  510. this.saveAnswer();
  511. this.submitCurrentAnswer().then(() => {
  512. setTimeout(() => {
  513. if (this.currentQuestionIndex >= this.questions.length - 1) {
  514. if (this.currentGroupIndex < this.questionGroups.length - 1) {
  515. this.showContinueButton = true;
  516. setTimeout(() => {
  517. this.scrollTop = 0;
  518. this.$nextTick(() => {
  519. setTimeout(() => {
  520. this.scrollTop = 1e4;
  521. }, 100);
  522. });
  523. }, 200);
  524. } else {
  525. common_vendor.index.navigateTo({
  526. url: "/pages/posture-guide/posture-guide"
  527. //'/pages/interview-question/interview-question'
  528. });
  529. }
  530. } else {
  531. this.goToNextQuestion();
  532. }
  533. }, 500);
  534. }).catch((error) => {
  535. console.error("提交答案失败:", error);
  536. setTimeout(() => {
  537. if (this.currentQuestionIndex >= this.questions.length - 1) {
  538. if (this.currentGroupIndex < this.questionGroups.length - 1) {
  539. this.showContinueButton = true;
  540. setTimeout(() => {
  541. this.scrollTop = 1e4;
  542. }, 200);
  543. } else {
  544. common_vendor.index.navigateTo({
  545. url: "/pages/posture-guide/posture-guide"
  546. //'/pages/interview-question/interview-question'
  547. });
  548. }
  549. } else {
  550. this.goToNextQuestion();
  551. }
  552. }, 1e3);
  553. });
  554. },
  555. toggleSettings() {
  556. common_vendor.index.showToast({
  557. title: "设置功能开发中",
  558. icon: "none"
  559. });
  560. },
  561. back(target) {
  562. if (this.timerInterval) {
  563. clearInterval(this.timerInterval);
  564. }
  565. if (target) {
  566. common_vendor.index.navigateTo({
  567. url: target
  568. });
  569. return;
  570. }
  571. },
  572. error(e) {
  573. console.error(e.detail);
  574. common_vendor.index.showToast({
  575. title: "相机启动失败,请检查权限设置",
  576. icon: "none"
  577. });
  578. },
  579. playAiSpeaking() {
  580. if (this.useVideo && this.aiVideoContext) {
  581. this.aiVideoContext.play();
  582. }
  583. if (this.digitalHumanUrl) {
  584. const speakText = this.currentQuestion ? this.currentQuestion.text : "";
  585. this.interactWithDigitalHuman(speakText);
  586. }
  587. },
  588. pauseAiSpeaking() {
  589. if (this.useVideo && this.aiVideoContext) {
  590. this.aiVideoContext.pause();
  591. }
  592. },
  593. // 修改 restartTest 方法,添加可选的跳转目标
  594. restartTest(target) {
  595. this.currentQuestionIndex = 0;
  596. this.score = 0;
  597. this.showEndModal = false;
  598. this.showResult = false;
  599. this.selectedOption = null;
  600. this.selectedOptions = [];
  601. this.resetTimer();
  602. if (target) {
  603. common_vendor.index.navigateTo({
  604. url: target
  605. });
  606. }
  607. },
  608. // 在methods中添加测试方法
  609. testEndScreen() {
  610. this.interviewCompleted = true;
  611. this.showEndModal = false;
  612. },
  613. // 初始化数字人
  614. initDigitalHuman() {
  615. this.digitalHumanUrl = "";
  616. },
  617. // 与数字人交互的方法
  618. interactWithDigitalHuman(message) {
  619. const webview = this.$mp.page.$getAppWebview().children()[0];
  620. if (webview) {
  621. webview.evalJS(`receiveMessage('${message}')`);
  622. }
  623. },
  624. // 添加 navigateToInterview 方法
  625. navigateToInterview() {
  626. this.showEndModal = false;
  627. common_vendor.index.navigateTo({
  628. url: "/pages/interview/interview",
  629. success: () => {
  630. console.log("成功跳转到interview页面");
  631. },
  632. fail: (err) => {
  633. console.error("跳转失败:", err);
  634. common_vendor.index.showToast({
  635. title: "跳转失败,请手动返回首页",
  636. icon: "none"
  637. });
  638. }
  639. });
  640. },
  641. // 添加 handleCameraError 方法
  642. handleCameraError(e) {
  643. console.error("相机错误:", e.detail);
  644. common_vendor.index.showToast({
  645. title: "相机启动失败,请检查权限设置",
  646. icon: "none"
  647. });
  648. },
  649. // 修改 isOptionSelected 方法
  650. isOptionSelected(question, qIndex, optionIndex) {
  651. const answer = this.answers.find((a) => a.questionId === question.id);
  652. if (qIndex === this.currentQuestionIndex) {
  653. if (question.questionType === 1 || question.questionType === 3 || question.questionType === 4) {
  654. return this.selectedOption === optionIndex;
  655. } else if (question.questionType === 2) {
  656. return this.selectedOptions.includes(optionIndex);
  657. }
  658. } else if (answer) {
  659. if (question.questionType === 1 || question.questionType === 3 || question.questionType === 4) {
  660. return answer.answer === optionIndex;
  661. } else if (question.questionType === 2 && Array.isArray(answer.answer)) {
  662. return answer.answer.includes(optionIndex);
  663. }
  664. }
  665. return false;
  666. },
  667. // 修改 handleOptionClick 方法
  668. handleOptionClick(qIndex, index) {
  669. if (qIndex < this.currentQuestionIndex - 1) {
  670. return;
  671. }
  672. if (qIndex === this.questions.length - 1 && this.showContinueButton) {
  673. common_vendor.index.showToast({
  674. title: "该题目已完成,请继续下一部分",
  675. icon: "none",
  676. duration: 2e3
  677. });
  678. return;
  679. }
  680. if (qIndex > this.currentQuestionIndex) {
  681. const firstUnansweredIndex = this.currentQuestionIndex;
  682. common_vendor.index.showToast({
  683. title: `请先完成第 ${firstUnansweredIndex + 1} 题`,
  684. icon: "none",
  685. duration: 2e3
  686. });
  687. this.currentScrollId = "question-" + firstUnansweredIndex;
  688. return;
  689. }
  690. if (this._isClicking) {
  691. return;
  692. }
  693. this._isClicking = true;
  694. if (this._clickTimer) {
  695. clearTimeout(this._clickTimer);
  696. }
  697. try {
  698. if (qIndex === this.currentQuestionIndex - 1) {
  699. this.currentQuestionIndex = qIndex;
  700. this.currentScrollId = "question-" + qIndex;
  701. this.showResult = false;
  702. this.selectedOption = null;
  703. this.selectedOptions = [];
  704. }
  705. this.selectOption(index);
  706. this._clickTimer = setTimeout(() => {
  707. this._isClicking = false;
  708. }, 1e3);
  709. } catch (error) {
  710. console.error("处理选项点击失败:", error);
  711. common_vendor.index.showToast({
  712. title: "操作失败,请重试",
  713. icon: "none"
  714. });
  715. this._isClicking = false;
  716. }
  717. },
  718. // 处理题目分组
  719. processQuestionGroups(questions) {
  720. const group1 = [];
  721. const group2 = [];
  722. questions.forEach((q) => {
  723. if (q.category === 5) {
  724. group1.push(q);
  725. } else {
  726. group2.push(q);
  727. }
  728. });
  729. this.questionGroups = [group2, group1].filter((group) => group.length > 0);
  730. this.questions = this.questionGroups[0] || [];
  731. },
  732. // 处理继续按钮点击
  733. handleContinue() {
  734. if (this.currentGroupIndex < this.questionGroups.length - 1) {
  735. if (this.currentGroupIndex === 0) {
  736. this.showPromptModal = true;
  737. } else {
  738. this.proceedToNextGroup();
  739. }
  740. } else {
  741. common_vendor.index.navigateTo({
  742. url: "/pages/posture-guide/posture-guide"
  743. //'/pages/interview-question/interview-question'
  744. });
  745. }
  746. },
  747. // 添加新的方法处理弹窗确认
  748. handlePromptConfirm() {
  749. this.showPromptModal = false;
  750. this.proceedToNextGroup();
  751. },
  752. // 添加新的方法处理组切换逻辑
  753. proceedToNextGroup() {
  754. this.currentGroupIndex++;
  755. this.questions = this.questionGroups[this.currentGroupIndex];
  756. this.currentQuestionIndex = 0;
  757. this.showContinueButton = false;
  758. this.currentScrollId = "question-0";
  759. this.scrollTop = 0;
  760. this.selectedOption = null;
  761. this.selectedOptions = [];
  762. this.showResult = false;
  763. },
  764. // 保存当前题目的答案
  765. saveAnswer() {
  766. let answer;
  767. if (this.currentQuestion.questionType === 0) {
  768. answer = {
  769. questionId: this.currentQuestion.id,
  770. questionType: this.currentQuestion.questionType,
  771. answer: this.openQuestionAnswer,
  772. answerDuration: this.getAnswerDuration()
  773. // 添加答题时长
  774. };
  775. } else if (this.currentQuestion.questionType === 1 || this.currentQuestion.questionType === 3 || this.currentQuestion.questionType === 4) {
  776. answer = {
  777. questionId: this.currentQuestion.id,
  778. questionType: this.currentQuestion.questionType,
  779. answer: this.selectedOption,
  780. answerDuration: this.getAnswerDuration()
  781. // 添加答题时长
  782. };
  783. } else {
  784. answer = {
  785. questionId: this.currentQuestion.id,
  786. questionType: this.currentQuestion.questionType,
  787. answer: this.selectedOptions,
  788. answerDuration: this.getAnswerDuration()
  789. // 添加答题时长
  790. };
  791. }
  792. const existingIndex = this.answers.findIndex((a) => a.questionId === answer.questionId);
  793. if (existingIndex > -1) {
  794. this.answers[existingIndex] = answer;
  795. } else {
  796. this.answers.push(answer);
  797. }
  798. this.currentAnswer = answer;
  799. console.log("已保存答案:", this.answers);
  800. },
  801. // 获取答题时长(秒)
  802. getAnswerDuration() {
  803. const remainingTimeArr = this.remainingTime.split(":");
  804. const remainingSeconds = parseInt(remainingTimeArr[0]) * 60 + parseInt(remainingTimeArr[1]);
  805. return 30 - remainingSeconds;
  806. },
  807. // 提交当前答案
  808. async submitCurrentAnswer() {
  809. if (!this.currentAnswer || this.isSubmitting)
  810. return;
  811. try {
  812. this.isSubmitting = true;
  813. let answerContent = "";
  814. let answerOptions = [];
  815. if (this.currentAnswer.questionType === 0) {
  816. answerContent = this.currentAnswer.answer;
  817. answerOptions = [];
  818. } else if (this.currentAnswer.questionType === 1 || this.currentAnswer.questionType === 3 || this.currentAnswer.questionType === 4) {
  819. const selectedIndex = this.currentAnswer.answer;
  820. const selectedOption = this.currentQuestion.options[selectedIndex];
  821. console.log("selectedOption", selectedOption);
  822. const optionId = selectedOption.id ? selectedOption.id : selectedIndex;
  823. answerContent = optionId.toString();
  824. answerOptions = [optionId];
  825. } else if (this.currentAnswer.questionType === 2) {
  826. const selectedIndices = this.currentAnswer.answer;
  827. const selectedOptionIds = selectedIndices.map((index) => {
  828. const option = this.currentQuestion.options[index];
  829. return option.id ? option.id : index;
  830. });
  831. answerOptions = selectedOptionIds;
  832. }
  833. const submitData = {
  834. position_id: JSON.parse(common_vendor.index.getStorageSync("selectedJob")).id,
  835. applicant_id: JSON.parse(common_vendor.index.getStorageSync("userInfo")).id,
  836. job_position_question_id: this.currentAnswer.questionId,
  837. // answer_content: answerContent,
  838. answer_options: answerOptions,
  839. answer_duration: this.currentAnswer.answerDuration || 0,
  840. tenant_id: JSON.parse(common_vendor.index.getStorageSync("userInfo")).tenant_id || 1
  841. };
  842. console.log("提交数据:", submitData);
  843. const res2 = await this.$http.post(`${common_config.apiBaseUrl}/api/job/submit_answer`, submitData);
  844. console.log("提交答案响应:", res2);
  845. return res2;
  846. } catch (error) {
  847. console.error("提交答案失败:", error);
  848. common_vendor.index.showToast({
  849. title: "提交答案失败,请重试",
  850. icon: "none"
  851. });
  852. throw error;
  853. } finally {
  854. common_vendor.index.hideLoading();
  855. this.isSubmitting = false;
  856. }
  857. },
  858. // 修改 goToNextQuestion 方法
  859. async goToNextQuestion() {
  860. if (this.currentQuestionIndex >= this.questions.length - 1) {
  861. this.showContinueButton = true;
  862. setTimeout(() => {
  863. this.scrollTop = 0;
  864. this.$nextTick(() => {
  865. setTimeout(() => {
  866. this.scrollTop = 1e4;
  867. }, 100);
  868. });
  869. }, 200);
  870. return;
  871. }
  872. this.currentQuestionIndex++;
  873. this.currentScrollId = "question-" + this.currentQuestionIndex;
  874. this.showResult = false;
  875. this.selectedOption = null;
  876. this.selectedOptions = [];
  877. this.openQuestionAnswer = "";
  878. if (this.questions[this.currentQuestionIndex]) {
  879. this.progressWidth = (this.currentQuestionIndex + 1) / this.questions.length * 100;
  880. this.resetTimer();
  881. this.playAiSpeaking();
  882. setTimeout(() => {
  883. this.pauseAiSpeaking();
  884. }, 2e3);
  885. }
  886. }
  887. },
  888. // 添加生命周期钩子,确保在组件销毁时清除计时器
  889. beforeDestroy() {
  890. if (this.timerInterval) {
  891. clearInterval(this.timerInterval);
  892. }
  893. }
  894. };
  895. if (!Array) {
  896. const _component_uni_load_more = common_vendor.resolveComponent("uni-load-more");
  897. _component_uni_load_more();
  898. }
  899. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  900. return common_vendor.e({
  901. a: !$data.digitalHumanUrl
  902. }, !$data.digitalHumanUrl ? {
  903. b: $data.mode,
  904. c: common_vendor.o((...args) => $options.handleCameraError && $options.handleCameraError(...args))
  905. } : $data.digitalHumanUrl ? {
  906. e: $data.digitalHumanUrl
  907. } : {
  908. f: common_assets._imports_0
  909. }, {
  910. d: $data.digitalHumanUrl,
  911. g: $data.showPageWarning ? 1 : "",
  912. h: common_vendor.f($data.questions, (question, qIndex, i0) => {
  913. return common_vendor.e({
  914. a: common_vendor.t(qIndex + 1),
  915. b: common_vendor.t(question.questionTypeName),
  916. c: common_vendor.t(question.text),
  917. d: question.is_required_correct
  918. }, question.is_required_correct ? {} : {}, {
  919. e: question.questionType === 3 && question.imageUrl
  920. }, question.questionType === 3 && question.imageUrl ? {
  921. f: question.imageUrl
  922. } : {}, {
  923. g: question.questionType !== 0
  924. }, question.questionType !== 0 ? {
  925. h: common_vendor.f(question.options, (option, index, i1) => {
  926. return {
  927. a: common_vendor.t(option.option_text || (typeof option === "string" ? option : JSON.stringify(option))),
  928. b: index,
  929. c: $options.isOptionSelected(question, qIndex, index) ? 1 : "",
  930. d: common_vendor.o(($event) => $options.handleOptionClick(qIndex, index), index)
  931. };
  932. }),
  933. i: qIndex !== $data.currentQuestionIndex ? 1 : ""
  934. } : {}, {
  935. j: qIndex === $data.currentQuestionIndex && question.questionType === 2
  936. }, qIndex === $data.currentQuestionIndex && question.questionType === 2 ? {
  937. k: common_vendor.t("下一题"),
  938. l: common_vendor.o(($event) => $options.nextQuestion(), question.id),
  939. m: $data.selectedOptions.length === 0
  940. } : {}, {
  941. n: question.id,
  942. o: "question-" + qIndex
  943. });
  944. }),
  945. i: common_vendor.t($data.questions.length),
  946. j: $data.showContinueButton
  947. }, $data.showContinueButton ? {
  948. k: common_vendor.t($data.currentGroupIndex < $data.questionGroups.length - 1 ? "继续下一部分" : "完成测试"),
  949. l: common_vendor.o((...args) => $options.handleContinue && $options.handleContinue(...args))
  950. } : {}, {
  951. m: $data.currentScrollId,
  952. n: $data.scrollTop,
  953. o: $data.showEndModal
  954. }, $data.showEndModal ? {
  955. p: common_vendor.o((...args) => $options.navigateToInterview && $options.navigateToInterview(...args))
  956. } : {}, {
  957. q: $data.interviewCompleted
  958. }, $data.interviewCompleted ? {
  959. r: common_assets._imports_0,
  960. s: common_vendor.o((...args) => $options.back && $options.back(...args))
  961. } : {}, {
  962. t: $data.loading
  963. }, $data.loading ? {
  964. v: common_vendor.p({
  965. status: "loading",
  966. contentText: {
  967. contentdown: "加载中..."
  968. }
  969. })
  970. } : {}, {
  971. w: !$data.loading && $data.loadError
  972. }, !$data.loading && $data.loadError ? {
  973. x: common_vendor.o((...args) => $options.fetchInterviewData && $options.fetchInterviewData(...args))
  974. } : {}, {
  975. y: $data.showPromptModal
  976. }, $data.showPromptModal ? {
  977. z: common_vendor.o((...args) => $options.handlePromptConfirm && $options.handlePromptConfirm(...args))
  978. } : {});
  979. }
  980. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
  981. wx.createPage(MiniProgramPage);