camera.js 33 KB

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