camera.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. "use strict";
  2. const common_vendor = require("../../common/vendor.js");
  3. const api_user = require("../../api/user.js");
  4. const common_assets = require("../../common/assets.js");
  5. const _sfc_main = {
  6. data() {
  7. return {
  8. cameraContext: null,
  9. devicePosition: "front",
  10. interviewStarted: true,
  11. showEndModal: false,
  12. currentQuestionIndex: 0,
  13. currentStep: 1,
  14. stepTexts: ["测试准备", "回答问题", "测试结束"],
  15. progressWidth: 50,
  16. remainingTime: "00:27",
  17. selectedOption: null,
  18. selectedOptions: [],
  19. showResult: false,
  20. isAnswerCorrect: false,
  21. questions: [],
  22. // 改为空数组,将通过API获取
  23. interviewId: null,
  24. // 存储当前面试ID
  25. useVideo: false,
  26. timerInterval: null,
  27. score: 0,
  28. totalQuestions: 0,
  29. interviewCompleted: false,
  30. digitalHumanUrl: "",
  31. // 数字人URL
  32. loading: true,
  33. // 添加加载状态
  34. loadError: false,
  35. // 添加加载错误状态
  36. errorMessage: "",
  37. // 添加错误消息
  38. answers: [],
  39. // 存储用户的所有答案
  40. currentQuestionDetail: null,
  41. // 当前题目详情
  42. isSubmitting: false,
  43. // 是否正在提交答案
  44. openQuestionAnswer: ""
  45. // 存储开放问题的答案
  46. };
  47. },
  48. computed: {
  49. currentQuestion() {
  50. console.log(this.questions[this.currentQuestionIndex]);
  51. return this.questions[this.currentQuestionIndex];
  52. }
  53. },
  54. onLoad(options) {
  55. if (options && options.id) {
  56. this.interviewId = options.id;
  57. this.fetchInterviewData();
  58. } else {
  59. this.fetchInterviewList();
  60. }
  61. },
  62. onReady() {
  63. this.cameraContext = common_vendor.index.createCameraContext();
  64. if (this.useVideo) {
  65. this.aiVideoContext = common_vendor.index.createVideoContext("aiInterviewer");
  66. }
  67. this.initDigitalHuman();
  68. },
  69. methods: {
  70. // 获取面试列表
  71. async fetchInterviewList() {
  72. try {
  73. this.loading = true;
  74. const res2 = await api_user.getInterviewList();
  75. console.log(res2);
  76. this.interviewId = res2.items;
  77. this.fetchInterviewData(res2.items);
  78. } catch (error) {
  79. console.error("获取面试列表失败:", error);
  80. this.handleLoadError("获取面试列表失败");
  81. }
  82. },
  83. // 获取面试详情数据
  84. async fetchInterviewData(data) {
  85. try {
  86. this.loading = true;
  87. if (data && Array.isArray(data)) {
  88. this.questions = data.map((q, index) => ({
  89. id: q.id || index + 1,
  90. text: q.question || "未知问题",
  91. options: q.options || [],
  92. correctAnswer: q.correctAnswer || 0,
  93. isImportant: q.is_system || false,
  94. explanation: q.explanation || "",
  95. questionType: q.question_form,
  96. questionTypeName: q.question_form_name || "单选题",
  97. correctAnswers: q.correct_answers || [],
  98. difficulty: q.difficulty || 1,
  99. difficultyName: q.difficulty_name || "初级"
  100. }));
  101. } else {
  102. this.processInterviewData(res);
  103. }
  104. console.log(this.questions);
  105. this.totalQuestions = this.questions.length;
  106. if (this.questions.length > 0) {
  107. this.startTimer();
  108. }
  109. } catch (error) {
  110. console.error("获取面试详情失败:", error);
  111. this.handleLoadError("获取面试详情失败");
  112. } finally {
  113. this.loading = false;
  114. }
  115. },
  116. // 处理面试数据
  117. processInterviewData(data) {
  118. this.questions = [];
  119. if (data) {
  120. const formattedQuestion = {
  121. id: data.id || 1,
  122. text: data.question || "未知问题",
  123. options: data.options || [],
  124. correctAnswer: data.correctAnswer || 0,
  125. isImportant: data.is_system || false,
  126. explanation: data.explanation || "",
  127. questionType: data.question_form,
  128. // 1-单选题,2-多选题
  129. questionTypeName: data.question_form_name || "单选题",
  130. correctAnswers: data.correct_answers || [],
  131. difficulty: data.difficulty || 1,
  132. difficultyName: data.difficulty_name || "初级"
  133. };
  134. this.questions.push(formattedQuestion);
  135. this.totalQuestions = this.questions.length;
  136. this.startTimer();
  137. } else {
  138. this.handleLoadError("面试中没有问题");
  139. }
  140. },
  141. // 处理加载错误
  142. handleLoadError(message) {
  143. this.loadError = true;
  144. this.loading = false;
  145. this.errorMessage = message || "加载失败";
  146. common_vendor.index.showToast({
  147. title: message || "加载失败",
  148. icon: "none",
  149. duration: 2e3
  150. });
  151. },
  152. startTimer() {
  153. if (this.questions.length === 0)
  154. return;
  155. let seconds = 30;
  156. this.timerInterval = setInterval(() => {
  157. seconds--;
  158. if (seconds <= 0) {
  159. clearInterval(this.timerInterval);
  160. if (!this.showResult) {
  161. this.checkAnswer();
  162. }
  163. }
  164. const min = Math.floor(seconds / 60).toString().padStart(2, "0");
  165. const sec = (seconds % 60).toString().padStart(2, "0");
  166. this.remainingTime = `${min}:${sec}`;
  167. }, 1e3);
  168. },
  169. resetTimer() {
  170. clearInterval(this.timerInterval);
  171. this.startTimer();
  172. },
  173. selectOption(index) {
  174. if (this.showResult)
  175. return;
  176. if (this.currentQuestion.questionType === 2) {
  177. const optionIndex = this.selectedOptions.indexOf(index);
  178. if (optionIndex > -1) {
  179. this.selectedOptions.splice(optionIndex, 1);
  180. } else {
  181. this.selectedOptions.push(index);
  182. }
  183. } else if (this.currentQuestion.questionType === 1) {
  184. this.selectedOption = index;
  185. }
  186. this.playAiSpeaking();
  187. },
  188. checkAnswer() {
  189. clearInterval(this.timerInterval);
  190. if (!this.currentQuestion) {
  191. console.error("当前问题不存在");
  192. return;
  193. }
  194. if (this.currentQuestion.questionType === 0) {
  195. this.isAnswerCorrect = true;
  196. } else if (this.currentQuestion.questionType === 2) {
  197. const sortedSelected = [...this.selectedOptions].sort();
  198. const sortedCorrect = [...this.currentQuestion.correctAnswers].sort();
  199. if (sortedSelected.length !== sortedCorrect.length) {
  200. this.isAnswerCorrect = false;
  201. } else {
  202. this.isAnswerCorrect = sortedSelected.every((value, index) => value === sortedCorrect[index]);
  203. }
  204. } else {
  205. this.isAnswerCorrect = this.selectedOption === this.currentQuestion.correctAnswer;
  206. }
  207. this.showResult = true;
  208. },
  209. nextQuestion() {
  210. if (!this.showResult) {
  211. this.checkAnswer();
  212. return;
  213. }
  214. this.saveAnswer();
  215. if (this.currentQuestionIndex >= this.questions.length - 1) {
  216. this.showEndModal = true;
  217. return;
  218. }
  219. this.goToNextQuestion();
  220. },
  221. // 保存当前题目的答案
  222. saveAnswer() {
  223. let answer;
  224. if (this.currentQuestion.questionType === 0) {
  225. answer = {
  226. questionId: this.currentQuestion.id,
  227. questionType: this.currentQuestion.questionType,
  228. answer: this.openQuestionAnswer
  229. };
  230. } else {
  231. answer = {
  232. questionId: this.currentQuestion.id,
  233. questionType: this.currentQuestion.questionType,
  234. answer: this.currentQuestion.questionType === 1 ? this.selectedOption : this.selectedOptions
  235. };
  236. }
  237. const existingIndex = this.answers.findIndex((a) => a.questionId === answer.questionId);
  238. if (existingIndex > -1) {
  239. this.answers[existingIndex] = answer;
  240. } else {
  241. this.answers.push(answer);
  242. }
  243. console.log("已保存答案:", this.answers);
  244. },
  245. // 提交所有答案
  246. async submitAllAnswers() {
  247. if (this.isSubmitting)
  248. return;
  249. try {
  250. this.isSubmitting = true;
  251. common_vendor.index.showLoading({
  252. title: "正在提交答案..."
  253. });
  254. const submitData = {
  255. interviewId: this.interviewId,
  256. answers: this.answers
  257. };
  258. await new Promise((resolve) => setTimeout(resolve, 1e3));
  259. common_vendor.index.hideLoading();
  260. common_vendor.index.showToast({
  261. title: "提交成功",
  262. icon: "success"
  263. });
  264. setTimeout(() => {
  265. this.back();
  266. }, 1500);
  267. } catch (error) {
  268. console.error("提交答案失败:", error);
  269. common_vendor.index.showToast({
  270. title: "提交答案失败,请重试",
  271. icon: "none"
  272. });
  273. } finally {
  274. this.isSubmitting = false;
  275. }
  276. },
  277. // 修改 goToNextQuestion 方法,添加 async 关键字
  278. async goToNextQuestion() {
  279. this.showResult = false;
  280. this.selectedOption = null;
  281. this.selectedOptions = [];
  282. this.openQuestionAnswer = "";
  283. this.currentQuestionIndex++;
  284. if (this.questions[this.currentQuestionIndex]) {
  285. this.progressWidth = (this.currentQuestionIndex + 1) / this.questions.length * 100;
  286. this.resetTimer();
  287. this.playAiSpeaking();
  288. setTimeout(() => {
  289. this.pauseAiSpeaking();
  290. }, 2e3);
  291. return;
  292. }
  293. try {
  294. this.loading = true;
  295. await new Promise((resolve) => setTimeout(resolve, 1e3));
  296. const res2 = {
  297. /* 模拟的题目数据 */
  298. };
  299. if (res2) {
  300. const formattedQuestion = {
  301. id: res2.id || this.currentQuestionIndex + 1,
  302. text: res2.question || "未知问题",
  303. options: res2.options || [],
  304. correctAnswer: res2.correctAnswer || 0,
  305. isImportant: res2.is_system || false,
  306. explanation: res2.explanation || "",
  307. questionType: res2.question_form || 1,
  308. questionTypeName: res2.question_form_name || "单选题",
  309. correctAnswers: res2.correct_answers || [],
  310. difficulty: res2.difficulty || 1,
  311. difficultyName: res2.difficulty_name || "初级"
  312. };
  313. this.questions.push(formattedQuestion);
  314. }
  315. } catch (error) {
  316. console.error("获取题目详情失败:", error);
  317. common_vendor.index.showToast({
  318. title: "获取题目失败,请重试",
  319. icon: "none"
  320. });
  321. this.currentQuestionIndex--;
  322. } finally {
  323. this.loading = false;
  324. this.progressWidth = (this.currentQuestionIndex + 1) / this.questions.length * 100;
  325. this.resetTimer();
  326. }
  327. },
  328. toggleSettings() {
  329. common_vendor.index.showToast({
  330. title: "设置功能开发中",
  331. icon: "none"
  332. });
  333. },
  334. back() {
  335. if (this.timerInterval) {
  336. clearInterval(this.timerInterval);
  337. }
  338. try {
  339. const pages = getCurrentPages();
  340. if (pages.length > 1) {
  341. common_vendor.index.reLaunch({
  342. url: "/pages/index/index"
  343. });
  344. } else {
  345. common_vendor.index.reLaunch({
  346. url: "/pages/index/index"
  347. });
  348. }
  349. } catch (e) {
  350. console.error("导航错误:", e);
  351. common_vendor.index.reLaunch({
  352. url: "/pages/index/index"
  353. });
  354. }
  355. },
  356. error(e) {
  357. console.error(e.detail);
  358. common_vendor.index.showToast({
  359. title: "相机启动失败,请检查权限设置",
  360. icon: "none"
  361. });
  362. },
  363. playAiSpeaking() {
  364. if (this.useVideo && this.aiVideoContext) {
  365. this.aiVideoContext.play();
  366. }
  367. if (this.digitalHumanUrl) {
  368. const speakText = this.currentQuestion ? this.currentQuestion.text : "";
  369. this.interactWithDigitalHuman(speakText);
  370. }
  371. },
  372. pauseAiSpeaking() {
  373. if (this.useVideo && this.aiVideoContext) {
  374. this.aiVideoContext.pause();
  375. }
  376. },
  377. // 重新开始测试
  378. restartTest() {
  379. this.currentQuestionIndex = 0;
  380. this.score = 0;
  381. this.showEndModal = false;
  382. this.showResult = false;
  383. this.selectedOption = null;
  384. this.selectedOptions = [];
  385. this.resetTimer();
  386. },
  387. // 在methods中添加测试方法
  388. testEndScreen() {
  389. this.interviewCompleted = true;
  390. this.showEndModal = false;
  391. },
  392. // 初始化数字人
  393. initDigitalHuman() {
  394. this.digitalHumanUrl = "";
  395. },
  396. // 与数字人交互的方法
  397. interactWithDigitalHuman(message) {
  398. const webview = this.$mp.page.$getAppWebview().children()[0];
  399. if (webview) {
  400. webview.evalJS(`receiveMessage('${message}')`);
  401. }
  402. }
  403. },
  404. // 添加生命周期钩子,确保在组件销毁时清除计时器
  405. beforeDestroy() {
  406. if (this.timerInterval) {
  407. clearInterval(this.timerInterval);
  408. }
  409. }
  410. };
  411. if (!Array) {
  412. const _component_uni_load_more = common_vendor.resolveComponent("uni-load-more");
  413. _component_uni_load_more();
  414. }
  415. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  416. return common_vendor.e({
  417. a: $data.digitalHumanUrl
  418. }, $data.digitalHumanUrl ? {
  419. b: $data.digitalHumanUrl
  420. } : {
  421. c: common_assets._imports_0
  422. }, {
  423. d: common_vendor.t($data.currentQuestionIndex + 1),
  424. e: common_vendor.t($options.currentQuestion.id),
  425. f: common_vendor.t($data.questions.length),
  426. g: $options.currentQuestion.isImportant
  427. }, $options.currentQuestion.isImportant ? {} : {}, {
  428. h: common_vendor.t($options.currentQuestion.questionTypeName),
  429. i: common_vendor.t($options.currentQuestion.text),
  430. j: $options.currentQuestion.questionType == 0
  431. }, $options.currentQuestion.questionType == 0 ? {
  432. k: $data.openQuestionAnswer,
  433. l: common_vendor.o(($event) => $data.openQuestionAnswer = $event.detail.value),
  434. m: common_vendor.t($data.openQuestionAnswer.length)
  435. } : {
  436. n: common_vendor.f($options.currentQuestion.options, (option, index, i0) => {
  437. return {
  438. a: common_vendor.t(option.option_text || (typeof option === "string" ? option : JSON.stringify(option))),
  439. b: index,
  440. c: ($options.currentQuestion.questionType === 1 ? $data.selectedOption === index : $data.selectedOptions.includes(index)) ? 1 : "",
  441. d: $data.showResult && ($options.currentQuestion.questionType === 1 ? index === $options.currentQuestion.correctAnswer : $options.currentQuestion.correctAnswers.includes(index)) ? 1 : "",
  442. e: $data.showResult && ($options.currentQuestion.questionType === 1 ? $data.selectedOption === index && index !== $options.currentQuestion.correctAnswer : $data.selectedOptions.includes(index) && !$options.currentQuestion.correctAnswers.includes(index)) ? 1 : "",
  443. f: common_vendor.o(($event) => $options.selectOption(index), index)
  444. };
  445. }),
  446. o: common_vendor.t($options.currentQuestion.questionType === 1 ? "●" : "☐")
  447. }, {
  448. p: common_vendor.t($data.showResult ? "下一题" : "提交答案"),
  449. q: common_vendor.o((...args) => $options.nextQuestion && $options.nextQuestion(...args)),
  450. r: $options.currentQuestion.questionType === 0 && $data.openQuestionAnswer.trim() === "" || $options.currentQuestion.questionType === 1 && $data.selectedOption === null || $options.currentQuestion.questionType === 2 && $data.selectedOptions.length === 0,
  451. s: $data.showEndModal
  452. }, $data.showEndModal ? {
  453. t: common_vendor.o((...args) => $options.restartTest && $options.restartTest(...args)),
  454. v: common_vendor.o((...args) => $options.back && $options.back(...args))
  455. } : {}, {
  456. w: $data.interviewCompleted
  457. }, $data.interviewCompleted ? {
  458. x: common_assets._imports_0,
  459. y: common_vendor.o((...args) => $options.back && $options.back(...args))
  460. } : {}, {
  461. z: $data.loading
  462. }, $data.loading ? {
  463. A: common_vendor.p({
  464. status: "loading",
  465. contentText: {
  466. contentdown: "加载中..."
  467. }
  468. })
  469. } : {}, {
  470. B: !$data.loading && $data.loadError
  471. }, !$data.loading && $data.loadError ? {
  472. C: common_vendor.o((...args) => $options.fetchInterviewData && $options.fetchInterviewData(...args))
  473. } : {});
  474. }
  475. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
  476. wx.createPage(MiniProgramPage);