camera.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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. };
  39. },
  40. computed: {
  41. currentQuestion() {
  42. console.log(this.questions[this.currentQuestionIndex]);
  43. return this.questions[this.currentQuestionIndex];
  44. }
  45. },
  46. onLoad(options) {
  47. if (options && options.id) {
  48. this.interviewId = options.id;
  49. this.fetchInterviewData();
  50. } else {
  51. this.fetchInterviewList();
  52. }
  53. },
  54. onReady() {
  55. this.cameraContext = common_vendor.index.createCameraContext();
  56. if (this.useVideo) {
  57. this.aiVideoContext = common_vendor.index.createVideoContext("aiInterviewer");
  58. }
  59. this.initDigitalHuman();
  60. },
  61. methods: {
  62. // 获取面试列表
  63. async fetchInterviewList() {
  64. try {
  65. this.loading = true;
  66. const res = await api_user.getInterviewList();
  67. console.log(res);
  68. this.interviewId = res.items[0].id;
  69. this.fetchInterviewData();
  70. } catch (error) {
  71. console.error("获取面试列表失败:", error);
  72. this.handleLoadError("获取面试列表失败");
  73. }
  74. },
  75. // 获取面试详情数据
  76. async fetchInterviewData() {
  77. try {
  78. this.loading = true;
  79. const res = await api_user.getInterviewDetail({ id: this.interviewId });
  80. console.log("API返回数据:", res);
  81. if (res && Array.isArray(res.items)) {
  82. this.questions = res.items.map((q, index) => ({
  83. id: q.id || index + 1,
  84. text: q.question || "未知问题",
  85. options: q.options || [],
  86. correctAnswer: q.correctAnswer || 0,
  87. isImportant: q.is_system || false,
  88. explanation: q.explanation || "",
  89. questionType: q.question_form || 1,
  90. questionTypeName: q.question_form_name || "单选题",
  91. correctAnswers: q.correct_answers || [],
  92. difficulty: q.difficulty || 1,
  93. difficultyName: q.difficulty_name || "初级"
  94. }));
  95. } else {
  96. this.processInterviewData(res);
  97. }
  98. console.log(this.questions);
  99. this.totalQuestions = this.questions.length;
  100. if (this.questions.length > 0) {
  101. this.startTimer();
  102. }
  103. } catch (error) {
  104. console.error("获取面试详情失败:", error);
  105. this.handleLoadError("获取面试详情失败");
  106. } finally {
  107. this.loading = false;
  108. }
  109. },
  110. // 处理面试数据
  111. processInterviewData(data) {
  112. this.questions = [];
  113. if (data) {
  114. const formattedQuestion = {
  115. id: data.id || 1,
  116. text: data.question || "未知问题",
  117. options: data.options || [],
  118. correctAnswer: data.correctAnswer || 0,
  119. isImportant: data.is_system || false,
  120. explanation: data.explanation || "",
  121. questionType: data.question_form || 1,
  122. // 1-单选题,2-多选题
  123. questionTypeName: data.question_form_name || "单选题",
  124. correctAnswers: data.correct_answers || [],
  125. difficulty: data.difficulty || 1,
  126. difficultyName: data.difficulty_name || "初级"
  127. };
  128. this.questions.push(formattedQuestion);
  129. this.totalQuestions = this.questions.length;
  130. this.startTimer();
  131. } else {
  132. this.handleLoadError("面试中没有问题");
  133. }
  134. },
  135. // 处理加载错误
  136. handleLoadError(message) {
  137. this.loadError = true;
  138. this.loading = false;
  139. this.errorMessage = message || "加载失败";
  140. common_vendor.index.showToast({
  141. title: message || "加载失败",
  142. icon: "none",
  143. duration: 2e3
  144. });
  145. },
  146. startTimer() {
  147. if (this.questions.length === 0)
  148. return;
  149. let seconds = 30;
  150. this.timerInterval = setInterval(() => {
  151. seconds--;
  152. if (seconds <= 0) {
  153. clearInterval(this.timerInterval);
  154. if (!this.showResult) {
  155. this.checkAnswer();
  156. }
  157. }
  158. const min = Math.floor(seconds / 60).toString().padStart(2, "0");
  159. const sec = (seconds % 60).toString().padStart(2, "0");
  160. this.remainingTime = `${min}:${sec}`;
  161. }, 1e3);
  162. },
  163. resetTimer() {
  164. clearInterval(this.timerInterval);
  165. this.startTimer();
  166. },
  167. selectOption(index) {
  168. if (this.showResult)
  169. return;
  170. if (this.currentQuestion.questionType === 2) {
  171. const optionIndex = this.selectedOptions.indexOf(index);
  172. if (optionIndex > -1) {
  173. this.selectedOptions.splice(optionIndex, 1);
  174. } else {
  175. this.selectedOptions.push(index);
  176. }
  177. } else {
  178. this.selectedOption = index;
  179. }
  180. this.playAiSpeaking();
  181. },
  182. checkAnswer() {
  183. clearInterval(this.timerInterval);
  184. if (!this.currentQuestion) {
  185. console.error("当前问题不存在");
  186. return;
  187. }
  188. if (this.currentQuestion.questionType === 2) {
  189. const sortedSelected = [...this.selectedOptions].sort();
  190. const sortedCorrect = [...this.currentQuestion.correctAnswers].sort();
  191. if (sortedSelected.length !== sortedCorrect.length) {
  192. this.isAnswerCorrect = false;
  193. } else {
  194. this.isAnswerCorrect = sortedSelected.every((value, index) => value === sortedCorrect[index]);
  195. }
  196. } else {
  197. this.isAnswerCorrect = this.selectedOption === this.currentQuestion.correctAnswer;
  198. }
  199. if (this.isAnswerCorrect) {
  200. this.score++;
  201. }
  202. this.showResult = true;
  203. if (this.currentQuestionIndex === this.questions.length - 1) {
  204. setTimeout(() => {
  205. this.interviewCompleted = false;
  206. }, 1500);
  207. return;
  208. }
  209. setTimeout(() => {
  210. this.goToNextQuestion();
  211. }, 1500);
  212. },
  213. nextQuestion() {
  214. if (this.currentQuestion.questionType === 2) {
  215. if (this.selectedOptions.length > 0 && !this.showResult) {
  216. this.checkAnswer();
  217. }
  218. } else {
  219. if (this.selectedOption !== null && !this.showResult) {
  220. this.checkAnswer();
  221. } else if (this.showResult) {
  222. this.goToNextQuestion();
  223. }
  224. }
  225. },
  226. // 新增方法,处理进入下一题的逻辑
  227. goToNextQuestion() {
  228. this.showResult = false;
  229. this.selectedOption = null;
  230. this.selectedOptions = [];
  231. this.currentQuestionIndex++;
  232. if (this.currentQuestionIndex >= this.questions.length) {
  233. this.showEndModal = true;
  234. this.interviewCompleted = false;
  235. if (this.timerInterval) {
  236. clearInterval(this.timerInterval);
  237. }
  238. return;
  239. }
  240. this.resetTimer();
  241. this.progressWidth = (this.currentQuestionIndex + 1) / this.questions.length * 100;
  242. this.playAiSpeaking();
  243. setTimeout(() => {
  244. this.pauseAiSpeaking();
  245. }, 2e3);
  246. },
  247. toggleSettings() {
  248. common_vendor.index.showToast({
  249. title: "设置功能开发中",
  250. icon: "none"
  251. });
  252. },
  253. back() {
  254. if (this.timerInterval) {
  255. clearInterval(this.timerInterval);
  256. }
  257. try {
  258. const pages = getCurrentPages();
  259. if (pages.length > 1) {
  260. common_vendor.index.reLaunch({
  261. url: "/pages/index/index"
  262. });
  263. } else {
  264. common_vendor.index.reLaunch({
  265. url: "/pages/index/index"
  266. });
  267. }
  268. } catch (e) {
  269. console.error("导航错误:", e);
  270. common_vendor.index.reLaunch({
  271. url: "/pages/index/index"
  272. });
  273. }
  274. },
  275. error(e) {
  276. console.error(e.detail);
  277. common_vendor.index.showToast({
  278. title: "相机启动失败,请检查权限设置",
  279. icon: "none"
  280. });
  281. },
  282. playAiSpeaking() {
  283. if (this.useVideo && this.aiVideoContext) {
  284. this.aiVideoContext.play();
  285. }
  286. if (this.digitalHumanUrl) {
  287. const speakText = this.currentQuestion ? this.currentQuestion.text : "";
  288. this.interactWithDigitalHuman(speakText);
  289. }
  290. },
  291. pauseAiSpeaking() {
  292. if (this.useVideo && this.aiVideoContext) {
  293. this.aiVideoContext.pause();
  294. }
  295. },
  296. // 重新开始测试
  297. restartTest() {
  298. this.currentQuestionIndex = 0;
  299. this.score = 0;
  300. this.showEndModal = false;
  301. this.showResult = false;
  302. this.selectedOption = null;
  303. this.selectedOptions = [];
  304. this.resetTimer();
  305. },
  306. // 在methods中添加测试方法
  307. testEndScreen() {
  308. this.interviewCompleted = true;
  309. this.showEndModal = false;
  310. },
  311. // 初始化数字人
  312. initDigitalHuman() {
  313. this.digitalHumanUrl = "";
  314. },
  315. // 与数字人交互的方法
  316. interactWithDigitalHuman(message) {
  317. const webview = this.$mp.page.$getAppWebview().children()[0];
  318. if (webview) {
  319. webview.evalJS(`receiveMessage('${message}')`);
  320. }
  321. }
  322. },
  323. // 添加生命周期钩子,确保在组件销毁时清除计时器
  324. beforeDestroy() {
  325. if (this.timerInterval) {
  326. clearInterval(this.timerInterval);
  327. }
  328. }
  329. };
  330. if (!Array) {
  331. const _component_uni_load_more = common_vendor.resolveComponent("uni-load-more");
  332. _component_uni_load_more();
  333. }
  334. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  335. return common_vendor.e({
  336. a: $data.digitalHumanUrl
  337. }, $data.digitalHumanUrl ? {
  338. b: $data.digitalHumanUrl
  339. } : {
  340. c: common_assets._imports_0
  341. }, {
  342. d: common_vendor.t($data.currentQuestionIndex + 1),
  343. e: common_vendor.t($options.currentQuestion.id),
  344. f: common_vendor.t($data.questions.length),
  345. g: $options.currentQuestion.isImportant
  346. }, $options.currentQuestion.isImportant ? {} : {}, {
  347. h: common_vendor.t($options.currentQuestion.questionTypeName),
  348. i: common_vendor.t($options.currentQuestion.text),
  349. j: common_vendor.f($options.currentQuestion.options, (option, index, i0) => {
  350. return {
  351. a: common_vendor.t(option.option_text || (typeof option === "string" ? option : JSON.stringify(option))),
  352. b: index,
  353. c: ($options.currentQuestion.questionType === 1 ? $data.selectedOption === index : $data.selectedOptions.includes(index)) ? 1 : "",
  354. d: $data.showResult && ($options.currentQuestion.questionType === 1 ? index === $options.currentQuestion.correctAnswer : $options.currentQuestion.correctAnswers.includes(index)) ? 1 : "",
  355. e: $data.showResult && ($options.currentQuestion.questionType === 1 ? $data.selectedOption === index && index !== $options.currentQuestion.correctAnswer : $data.selectedOptions.includes(index) && !$options.currentQuestion.correctAnswers.includes(index)) ? 1 : "",
  356. f: common_vendor.o(($event) => $options.selectOption(index), index)
  357. };
  358. }),
  359. k: common_vendor.t($data.remainingTime),
  360. l: common_vendor.t($data.showResult ? "下一题" : "提交答案"),
  361. m: common_vendor.o((...args) => $options.nextQuestion && $options.nextQuestion(...args)),
  362. n: $options.currentQuestion.questionType === 1 ? $data.selectedOption === null : $data.selectedOptions.length === 0,
  363. o: $data.showEndModal
  364. }, $data.showEndModal ? {
  365. p: common_vendor.t($data.score),
  366. q: common_vendor.t($data.totalQuestions),
  367. r: common_vendor.t($data.score),
  368. s: common_vendor.t($data.totalQuestions),
  369. t: common_vendor.o((...args) => $options.restartTest && $options.restartTest(...args)),
  370. v: common_vendor.o((...args) => $options.back && $options.back(...args))
  371. } : {}, {
  372. w: $data.interviewCompleted
  373. }, $data.interviewCompleted ? {
  374. x: common_assets._imports_0,
  375. y: common_vendor.o((...args) => $options.back && $options.back(...args))
  376. } : {}, {
  377. z: $data.loading
  378. }, $data.loading ? {
  379. A: common_vendor.p({
  380. status: "loading",
  381. contentText: {
  382. contentdown: "加载中..."
  383. }
  384. })
  385. } : {}, {
  386. B: !$data.loading && $data.loadError
  387. }, !$data.loading && $data.loadError ? {
  388. C: common_vendor.o((...args) => $options.fetchInterviewData && $options.fetchInterviewData(...args))
  389. } : {});
  390. }
  391. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
  392. wx.createPage(MiniProgramPage);