camera.vue 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501
  1. <template>
  2. <view class="camera-container">
  3. <!-- 顶部相机区域 - 固定位置 -->
  4. <view class="fixed-camera-container">
  5. <view class="user-avatar">
  6. <camera v-if="!digitalHumanUrl" device-position="front" flash="auto" class="camera"
  7. :mode="mode" @error="handleCameraError"></camera>
  8. <web-view v-else-if="digitalHumanUrl" :src="digitalHumanUrl" class="digital-human-webview"></web-view>
  9. <image v-else src="/static/avatar.png" mode="aspectFill" class="avatar-image"></image>
  10. </view>
  11. </view>
  12. <!-- 主要内容区域 - 修改为流式布局 -->
  13. <scroll-view class="content-scroll" scroll-y="true" :scroll-into-view="currentScrollId" :scroll-with-animation="true">
  14. <view class="content">
  15. <!-- 测试内容区域 - 修改为流式布局,每个问题都显示 -->
  16. <view v-for="(question, qIndex) in questions" :key="question.id" :id="'question-'+qIndex" class="interview-content">
  17. <!-- 问题编号和进度 -->
  18. <view class="question-number">
  19. <text>{{qIndex + 1}}/{{questions.length}}</text>
  20. </view>
  21. <!-- 问题和选项区域 -->
  22. <view class="question-area">
  23. <view class="question-importance">
  24. <!-- <text v-if="currentQuestion.isImportant">重点题</text> -->
  25. <text class="question-type">{{question.questionTypeName}}</text>
  26. {{question.text}}<text class="question-type" v-if="question.is_required_correct">(红线题)</text>
  27. </view>
  28. <!-- 添加图片显示区域,仅在 question_form 为 3 时显示 -->
  29. <view class="question-image-container" v-if="question.questionType === 3 && question.imageUrl">
  30. <image :src="question.imageUrl" mode="widthFix" class="question-image"></image>
  31. </view>
  32. <view class="options">
  33. <!-- 渲染非开放问题 (questionType: 1, 2 或 3) -->
  34. <view
  35. v-if="question.questionType !== 0"
  36. v-for="(option, index) in question.options"
  37. :key="index"
  38. class="option-item"
  39. :class="{
  40. 'option-selected': isOptionSelected(question, qIndex, index),
  41. 'option-disabled': qIndex !== currentQuestionIndex
  42. }"
  43. @click="qIndex === currentQuestionIndex && selectOption(index)">
  44. <text class="option-text">{{ option.option_text || (typeof option === 'string' ? option : JSON.stringify(option)) }}</text>
  45. </view>
  46. </view>
  47. <!-- 添加当前题目的按钮 - 只在多选题时显示 -->
  48. <view class="question-button-container" v-if="qIndex === currentQuestionIndex && question.questionType === 2">
  49. <button class="next-button" @click="nextQuestion()"
  50. :disabled="selectedOptions.length === 0">
  51. {{"提交答案"}}
  52. </button>
  53. </view>
  54. </view>
  55. </view>
  56. <!-- 添加底部空白区域,确保内容不被固定按钮遮挡 -->
  57. <view class="bottom-space"></view>
  58. </view>
  59. </scroll-view>
  60. <!-- 面试结束弹窗 -->
  61. <view class="interview-end-modal" v-if="showEndModal">
  62. <view class="modal-content">
  63. <view class="modal-title">测试已完成</view>
  64. <!-- <view class="score-display">{{score}}/{{totalQuestions}}</view>
  65. <view class="modal-message">您在本次测试中答了{{score}}道题目,共{{totalQuestions}}道题目。</view> -->
  66. <view class="modal-buttons">
  67. <!-- <button type="default" class="modal-button" @click="restartTest">重新测试</button> -->
  68. <button type="primary" class="modal-button" @click="navigateToInterview">进入面试</button>
  69. </view>
  70. </view>
  71. </view>
  72. <!-- AI面试结束页面 -->
  73. <view class="interview-complete-screen" v-if="interviewCompleted">
  74. <view class="digital-avatar-large">
  75. <image src="/static/avatar.png" mode="aspectFill" class="avatar-image-large"></image>
  76. </view>
  77. <view class="complete-message">AI面试已结束</view>
  78. <view class="complete-description">本次面试的全部环节已结束。非常感谢您的参与。</view>
  79. <button class="complete-button" @click="back">我知道了</button>
  80. </view>
  81. <!-- 在模板中添加一个测试按钮(开发时使用,发布前删除) -->
  82. <!-- <button @click="testEndScreen" style="position: absolute; top: 10px; right: 10px; z-index: 9999;">测试结束页</button> -->
  83. <!-- 添加加载状态 -->
  84. <view class="loading-container" v-if="loading">
  85. <uni-load-more status="loading" :contentText="{contentdown: '加载中...'}" />
  86. <text class="loading-text">正在加载面试题目...</text>
  87. </view>
  88. <!-- 添加加载错误提示 -->
  89. <view class="loading-container" v-if="!loading && loadError">
  90. <view class="error-container">
  91. <text class="error-message">加载面试题目失败</text>
  92. <button class="retry-button" @click="fetchInterviewData">重试</button>
  93. </view>
  94. </view>
  95. </view>
  96. </template>
  97. <script>
  98. import { getInterviewList, getInterviewDetail,getQuestions } from '@/api/user.js';
  99. import { apiBaseUrl } from '@/common/config.js';
  100. export default {
  101. data() {
  102. return {
  103. cameraContext: null,
  104. devicePosition: 'front',
  105. interviewStarted: true,
  106. showEndModal: false,
  107. currentQuestionIndex: 0,
  108. currentStep: 1,
  109. stepTexts: ['测试准备', '回答问题', '测试结束'],
  110. progressWidth: 50,
  111. remainingTime: '00:27',
  112. selectedOption: null,
  113. selectedOptions: [],
  114. showResult: false,
  115. isAnswerCorrect: false,
  116. questions: [], // 改为空数组,将通过API获取
  117. interviewId: null, // 存储当前面试ID
  118. useVideo: false,
  119. timerInterval: null,
  120. score: 0,
  121. totalQuestions: 0,
  122. interviewCompleted: false,
  123. digitalHumanUrl: '', // 数字人URL
  124. loading: true, // 添加加载状态
  125. loadError: false, // 添加加载错误状态
  126. errorMessage: '', // 添加错误消息
  127. answers: [], // 存储用户的所有答案
  128. currentQuestionDetail: null, // 当前题目详情
  129. isSubmitting: false, // 是否正在提交答案
  130. openQuestionAnswer: '', // 存储开放问题的答案
  131. currentAnswer: null, // 存储当前答案以便提交
  132. questionStartTime: null, // 存储问题开始时间
  133. mode: 'normal', // 相机模式
  134. currentScrollId: 'question-0', // 当前滚动到的问题ID
  135. }
  136. },
  137. computed: {
  138. currentQuestion() {
  139. console.log(this.questions[this.currentQuestionIndex]);
  140. return this.questions[this.currentQuestionIndex];
  141. }
  142. },
  143. onLoad(options) {
  144. // 从路由参数中获取面试ID
  145. if (options && options.id) {
  146. this.interviewId = options.id;
  147. this.fetchInterviewData();
  148. } else {
  149. // 如果没有ID,可以获取面试列表并使用第一个
  150. this.fetchInterviewList();
  151. }
  152. },
  153. onReady() {
  154. // 创建相机上下文
  155. this.cameraContext = uni.createCameraContext();
  156. // 创建AI视频上下文
  157. if (this.useVideo) {
  158. this.aiVideoContext = uni.createVideoContext('aiInterviewer');
  159. }
  160. // 初始化数字人
  161. this.initDigitalHuman();
  162. },
  163. methods: {
  164. // 获取面试列表
  165. async fetchInterviewList() {
  166. try {
  167. this.loading = true;
  168. const res = await getQuestions({
  169. position_id:JSON.parse(uni.getStorageSync('selectedJob')).id,
  170. page:1,
  171. limit:999,
  172. tenant_id:JSON.parse(uni.getStorageSync('userInfo')).tenant_id || 1
  173. });
  174. console.log(res.items);
  175. // 使用第一个面试
  176. this.interviewId = res.items//[0].id;
  177. this.fetchInterviewData(res.items);
  178. } catch (error) {
  179. console.error('获取面试列表失败:', error);
  180. this.handleLoadError('获取面试列表失败');
  181. }
  182. },
  183. // 获取面试详情数据
  184. async fetchInterviewData(data) {
  185. try {
  186. this.loading = true;
  187. // const res = await getInterviewDetail({ id: this.interviewId });
  188. // console.log('API返回数据:', res);
  189. // 如果返回的是问题列表
  190. if (data && Array.isArray(data)) {
  191. // 处理多个问题,并过滤掉 question_form 为 0 的问题
  192. this.questions = data
  193. .filter(q => q.question_form !== 0) // 过滤掉开放问题
  194. .map((q, index) => ({
  195. id: q.id || index + 1,
  196. text: q.question || '未知问题',
  197. options: q.options || [],
  198. correctAnswer: q.correctAnswer || 0,
  199. isImportant: q.is_system || false,
  200. explanation: q.explanation || '',
  201. questionType: q.question_form,
  202. questionTypeName: q.question_form_name || '单选题',
  203. is_required_correct: q.is_required_correct || false,
  204. correctAnswers: q.correct_answers || [],
  205. difficulty: q.difficulty || 1,
  206. difficultyName: q.difficulty_name || '初级',
  207. imageUrl: q.question_image_url || '' // 添加图片URL字段
  208. }));
  209. } else {
  210. // 处理单个问题
  211. this.processInterviewData(res);
  212. }
  213. console.log(this.questions);
  214. // 设置总题目数
  215. this.totalQuestions = this.questions.length;
  216. /* // 启动倒计时
  217. if (this.questions.length > 0) {
  218. this.startTimer();
  219. } */
  220. } catch (error) {
  221. console.error('获取面试详情失败:', error);
  222. this.handleLoadError('获取面试详情失败');
  223. } finally {
  224. this.loading = false;
  225. }
  226. },
  227. // 处理面试数据
  228. processInterviewData(data) {
  229. // 清空现有问题列表
  230. this.questions = [];
  231. // 检查数据格式并处理
  232. if (data && data.question_form !== 0) { // 只处理非开放问题
  233. // 创建一个格式化的问题对象
  234. const formattedQuestion = {
  235. id: data.id || 1,
  236. text: data.question || '未知问题',
  237. options: data.options || [],
  238. correctAnswer: data.correctAnswer || 0,
  239. isImportant: data.is_system || false,
  240. explanation: data.explanation || '',
  241. questionType: data.question_form, // 1-单选题,2-多选题,3-看图答题
  242. questionTypeName: data.question_form_name || '单选题',
  243. is_required_correct: data.is_required_correct || false,
  244. correctAnswers: data.correct_answers || [],
  245. difficulty: data.difficulty || 1,
  246. difficultyName: data.difficulty_name || '初级',
  247. imageUrl: data.question_image_url || '' // 添加图片URL字段
  248. };
  249. // 添加到问题列表
  250. this.questions.push(formattedQuestion);
  251. // 设置总题目数
  252. this.totalQuestions = this.questions.length;
  253. /* // 启动倒计时
  254. this.startTimer(); */
  255. } else {
  256. this.handleLoadError('没有可用的选择题');
  257. }
  258. },
  259. // 处理加载错误
  260. handleLoadError(message) {
  261. this.loadError = true;
  262. this.loading = false;
  263. this.errorMessage = message || '加载失败';
  264. uni.showToast({
  265. title: message || '加载失败',
  266. icon: 'none',
  267. duration: 2000
  268. });
  269. },
  270. startTimer() {
  271. // 确保问题已加载
  272. if (this.questions.length === 0) return;
  273. // 记录开始时间
  274. this.questionStartTime = new Date();
  275. // 设置倒计时时间(秒)
  276. let seconds = 60; // 每题60秒,可以根据需要调整
  277. this.remainingTime = `01:00`;
  278. this.timerInterval = setInterval(() => {
  279. seconds--;
  280. if (seconds <= 0) {
  281. clearInterval(this.timerInterval);
  282. // 时间到,自动提交答案并进入下一题
  283. if (!this.showResult) {
  284. this.checkAnswer();
  285. // 短暂延迟后自动进入下一题
  286. setTimeout(() => {
  287. if (this.currentQuestionIndex < this.questions.length - 1) {
  288. this.goToNextQuestion();
  289. } else {
  290. // 如果是最后一题,显示结束模态框或跳转
  291. uni.navigateTo({
  292. url: '/pages/interview-question/interview-question'
  293. });
  294. }
  295. }, 1500); // 1.5秒后自动进入下一题
  296. }
  297. }
  298. // 格式化显示时间 (不再显示,但仍然计算用于内部逻辑)
  299. const min = Math.floor(seconds / 60).toString().padStart(2, '0');
  300. const sec = (seconds % 60).toString().padStart(2, '0');
  301. this.remainingTime = `${min}:${sec}`;
  302. }, 1000);
  303. },
  304. resetTimer() {
  305. clearInterval(this.timerInterval);
  306. /* this.startTimer(); */
  307. },
  308. selectOption(index) {
  309. if (this.showResult) return; // 已显示结果时不能再选择
  310. console.log('selectOption',index);
  311. // 判断当前题目类型
  312. if (this.currentQuestion.questionType === 2) { // 多选题
  313. // 如果已经选中,则取消选中
  314. const optionIndex = this.selectedOptions.indexOf(index);
  315. if (optionIndex > -1) {
  316. this.selectedOptions.splice(optionIndex, 1);
  317. } else {
  318. // 否则添加到已选中数组
  319. this.selectedOptions.push(index);
  320. }
  321. // 多选题不自动提交,需要用户手动点击"进入下一题"按钮
  322. } else if (this.currentQuestion.questionType === 1 || this.currentQuestion.questionType === 3 || this.currentQuestion.questionType === 4) { // 单选题、看图答题或类型4题目
  323. this.selectedOption = index;
  324. // 自动提交答案并进入下一题
  325. this.playAiSpeaking();
  326. // 短暂延迟后提交答案,让用户看到选中效果
  327. setTimeout(() => {
  328. this.checkAnswer();
  329. this.saveAnswer();
  330. // 提交答案
  331. this.submitCurrentAnswer().then(() => {
  332. // 提交成功后,短暂延迟后自动进入下一题
  333. setTimeout(() => {
  334. // 如果是最后一题,跳转到面试页面
  335. if (this.currentQuestionIndex >= this.questions.length - 1) {
  336. uni.navigateTo({
  337. url: '/pages/interview-question/interview-question'
  338. });
  339. } else {
  340. // 否则进入下一题
  341. this.goToNextQuestion();
  342. }
  343. }, 800); // 0.8秒后自动进入下一题
  344. }).catch(error => {
  345. console.error('提交答案失败:', error);
  346. // 即使提交失败也继续下一题
  347. setTimeout(() => {
  348. if (this.currentQuestionIndex >= this.questions.length - 1) {
  349. uni.navigateTo({
  350. url: '/pages/interview-question/interview-question'
  351. });
  352. } else {
  353. this.goToNextQuestion();
  354. }
  355. }, 1000);
  356. });
  357. }, 500); // 0.5秒后提交答案
  358. }
  359. },
  360. checkAnswer() {
  361. // 如果已经显示结果,说明已经提交过答案,不再重复提交
  362. if (this.showResult) return;
  363. clearInterval(this.timerInterval); // 停止计时
  364. // 确保当前问题存在
  365. if (!this.currentQuestion) {
  366. console.error('当前问题不存在');
  367. return;
  368. }
  369. // 根据题目类型检查答案是否正确
  370. if (this.currentQuestion.questionType === 0) { // 开放问题
  371. // 开放问题没有标准答案,可以根据需要设置为正确或待评估
  372. this.isAnswerCorrect = true; // 或者设置为 null 表示待评估
  373. } else if (this.currentQuestion.questionType === 2) { // 多选题
  374. const sortedSelected = [...this.selectedOptions].sort();
  375. const sortedCorrect = [...this.currentQuestion.correctAnswers].sort();
  376. if (sortedSelected.length !== sortedCorrect.length) {
  377. this.isAnswerCorrect = false;
  378. } else {
  379. this.isAnswerCorrect = sortedSelected.every((value, index) => value === sortedCorrect[index]);
  380. }
  381. } else { // 单选题或看图答题 (questionType === 1 或 3)
  382. this.isAnswerCorrect = this.selectedOption === this.currentQuestion.correctAnswer;
  383. }
  384. // 显示结果
  385. this.showResult = true;
  386. // 保存当前题目的答案
  387. this.saveAnswer();
  388. },
  389. // 修改 nextQuestion 方法,只处理多选题
  390. nextQuestion() {
  391. // 确保当前是多选题
  392. if (this.currentQuestion.questionType !== 2) return;
  393. // 检查答案并提交
  394. this.checkAnswer();
  395. // 保存当前题目的答案
  396. this.saveAnswer();
  397. // 提交答案
  398. this.submitCurrentAnswer().then(() => {
  399. // 提交成功后,短暂延迟后自动进入下一题
  400. setTimeout(() => {
  401. // 如果是最后一题,跳转到面试页面
  402. if (this.currentQuestionIndex >= this.questions.length - 1) {
  403. uni.navigateTo({
  404. url: '/pages/interview-question/interview-question'
  405. });
  406. } else {
  407. // 否则进入下一题
  408. this.goToNextQuestion();
  409. }
  410. }, 500); // 0.5秒后自动进入下一题
  411. }).catch(error => {
  412. console.error('提交答案失败:', error);
  413. // 即使提交失败也继续下一题
  414. setTimeout(() => {
  415. if (this.currentQuestionIndex >= this.questions.length - 1) {
  416. uni.navigateTo({
  417. url: '/pages/interview-question/interview-question'
  418. });
  419. } else {
  420. this.goToNextQuestion();
  421. }
  422. }, 1000);
  423. });
  424. },
  425. // 保存当前题目的答案
  426. saveAnswer() {
  427. let answer;
  428. if (this.currentQuestion.questionType === 0) { // 开放问题
  429. answer = {
  430. questionId: this.currentQuestion.id,
  431. questionType: this.currentQuestion.questionType,
  432. answer: this.openQuestionAnswer,
  433. answerDuration: this.getAnswerDuration() // 添加答题时长
  434. };
  435. } else if (this.currentQuestion.questionType === 1 || this.currentQuestion.questionType === 3 || this.currentQuestion.questionType === 4) { // 单选题、看图答题或类型4题目
  436. answer = {
  437. questionId: this.currentQuestion.id,
  438. questionType: this.currentQuestion.questionType,
  439. answer: this.selectedOption,
  440. answerDuration: this.getAnswerDuration() // 添加答题时长
  441. };
  442. } else { // 多选题
  443. answer = {
  444. questionId: this.currentQuestion.id,
  445. questionType: this.currentQuestion.questionType,
  446. answer: this.selectedOptions,
  447. answerDuration: this.getAnswerDuration() // 添加答题时长
  448. };
  449. }
  450. // 检查是否已存在该题目的答案,如果存在则更新
  451. const existingIndex = this.answers.findIndex(a => a.questionId === answer.questionId);
  452. if (existingIndex > -1) {
  453. this.answers[existingIndex] = answer;
  454. } else {
  455. this.answers.push(answer);
  456. }
  457. // 保存当前答案以便提交
  458. this.currentAnswer = answer;
  459. console.log('已保存答案:', this.answers);
  460. },
  461. // 获取答题时长(秒)
  462. getAnswerDuration() {
  463. // 这里假设每题默认30秒,根据剩余时间计算用时
  464. // 您可以根据实际情况调整计算逻辑
  465. const remainingTimeArr = this.remainingTime.split(':');
  466. const remainingSeconds = parseInt(remainingTimeArr[0]) * 60 + parseInt(remainingTimeArr[1]);
  467. return 30 - remainingSeconds; // 假设每题30秒
  468. },
  469. // 提交当前答案
  470. async submitCurrentAnswer() {
  471. if (!this.currentAnswer || this.isSubmitting) return; // 如果正在提交或没有答案,则不执行
  472. try {
  473. // 设置提交状态为true
  474. this.isSubmitting = true;
  475. // 显示提交中提示
  476. uni.showLoading({
  477. title: '正在提交答案...'
  478. });
  479. // 构建提交数据
  480. let answerContent = '';
  481. let answerOptions = [];
  482. if (this.currentAnswer.questionType === 0) {
  483. // 开放题直接使用文本答案
  484. answerContent = this.currentAnswer.answer;
  485. answerOptions = [];
  486. } else if (this.currentAnswer.questionType === 1 || this.currentAnswer.questionType === 3 || this.currentAnswer.questionType === 4) {
  487. // 单选题或看图答题,获取选项ID而不是索引
  488. const selectedIndex = this.currentAnswer.answer;
  489. const selectedOption = this.currentQuestion.options[selectedIndex];
  490. console.log('selectedOption',selectedOption);
  491. const optionId = selectedOption.id ? selectedOption.id : selectedIndex;
  492. answerContent = optionId.toString();
  493. answerOptions = [optionId];
  494. } else if (this.currentAnswer.questionType === 2) {
  495. // 多选题,将选项ID数组转为逗号分隔的字符串
  496. const selectedIndices = this.currentAnswer.answer;
  497. const selectedOptionIds = selectedIndices.map(index => {
  498. const option = this.currentQuestion.options[index];
  499. // 使用选项的ID或其他唯一标识符
  500. return option.id ? option.id : index;
  501. });
  502. answerOptions = selectedOptionIds;
  503. }
  504. const submitData = {
  505. position_id: JSON.parse(uni.getStorageSync('selectedJob')).id,
  506. applicant_id: JSON.parse(uni.getStorageSync('userInfo')).id,
  507. job_position_question_id: this.currentAnswer.questionId,
  508. // answer_content: answerContent,
  509. answer_options: answerOptions,
  510. answer_duration: this.currentAnswer.answerDuration || 0,
  511. tenant_id:JSON.parse(uni.getStorageSync('userInfo')).tenant_id || 1
  512. };
  513. console.log('提交数据:', submitData);
  514. // 调用API提交答案
  515. const res = await this.$http.post(`${apiBaseUrl}/api/job/submit_answer`, submitData);
  516. console.log('提交答案响应:', res);
  517. return res;
  518. } catch (error) {
  519. console.error('提交答案失败:', error);
  520. // 显示错误提示
  521. uni.showToast({
  522. title: '提交答案失败,请重试',
  523. icon: 'none'
  524. });
  525. throw error;
  526. } finally {
  527. // 隐藏加载提示
  528. uni.hideLoading();
  529. // 重置提交状态
  530. this.isSubmitting = false;
  531. }
  532. },
  533. // 修改 goToNextQuestion 方法,在切换到下一题时重置当前选项状态
  534. async goToNextQuestion() {
  535. // 重置状态
  536. this.showResult = false;
  537. // 重置当前选择状态,但不影响已保存的答案
  538. this.selectedOption = null;
  539. this.selectedOptions = [];
  540. this.openQuestionAnswer = ''; // 重置开放问题答案
  541. // 前往下一题
  542. this.currentQuestionIndex++;
  543. // 更新滚动ID,滚动到下一题
  544. this.currentScrollId = 'question-' + this.currentQuestionIndex;
  545. // 如果已经有下一题的详情,直接使用
  546. if (this.questions[this.currentQuestionIndex]) {
  547. // 更新进度
  548. this.progressWidth = (this.currentQuestionIndex + 1) / this.questions.length * 100;
  549. // 重置计时器但不显示
  550. this.resetTimer();
  551. // 播放AI介绍下一题
  552. this.playAiSpeaking();
  553. setTimeout(() => {
  554. this.pauseAiSpeaking();
  555. }, 2000);
  556. return;
  557. }
  558. // 否则请求下一题详情
  559. try {
  560. this.loading = true;
  561. // 假设您有一个获取单个题目详情的API
  562. // const res = await getQuestionDetail({
  563. // interviewId: this.interviewId,
  564. // questionIndex: this.currentQuestionIndex
  565. // });
  566. // 模拟API调用
  567. await new Promise(resolve => setTimeout(resolve, 1000));
  568. const res = { /* 模拟的题目数据 */ };
  569. // 处理题目数据
  570. if (res) {
  571. const formattedQuestion = {
  572. id: res.id || this.currentQuestionIndex + 1,
  573. text: res.question || '未知问题',
  574. options: res.options || [],
  575. correctAnswer: res.correctAnswer || 0,
  576. isImportant: res.is_system || false,
  577. explanation: res.explanation || '',
  578. questionType: res.question_form || 1,
  579. questionTypeName: res.question_form_name || '单选题',
  580. is_required_correct: res.is_required_correct || false,
  581. correctAnswers: res.correct_answers || [],
  582. difficulty: res.difficulty || 1,
  583. difficultyName: res.difficulty_name || '初级'
  584. };
  585. // 添加到问题列表
  586. this.questions.push(formattedQuestion);
  587. }
  588. } catch (error) {
  589. console.error('获取题目详情失败:', error);
  590. uni.showToast({
  591. title: '获取题目失败,请重试',
  592. icon: 'none'
  593. });
  594. // 出错时回到上一题
  595. this.currentQuestionIndex--;
  596. } finally {
  597. this.loading = false;
  598. // 更新进度
  599. this.progressWidth = (this.currentQuestionIndex + 1) / this.questions.length * 100;
  600. // 重置计时器
  601. this.resetTimer();
  602. }
  603. },
  604. toggleSettings() {
  605. // 打开设置面板
  606. uni.showToast({
  607. title: '设置功能开发中',
  608. icon: 'none'
  609. });
  610. },
  611. back(target) {
  612. // 清除计时器
  613. if (this.timerInterval) {
  614. clearInterval(this.timerInterval);
  615. }
  616. // 如果指定了跳转目标
  617. if (target) {
  618. uni.navigateTo({
  619. url: target
  620. });
  621. return;
  622. }
  623. // 否则尝试返回上一页,如果失败则跳转到首页
  624. // try {
  625. // const pages = getCurrentPages();
  626. // if (pages.length > 1) {
  627. // uni.reLaunch({
  628. // url: '/pages/index/index'
  629. // });
  630. // } else {
  631. // // 如果当前是第一页,则跳转到首页
  632. // uni.reLaunch({
  633. // url: '/pages/index/index'
  634. // });
  635. // }
  636. // } catch (e) {
  637. // console.error('导航错误:', e);
  638. // // 出错时也跳转到首页
  639. // uni.reLaunch({
  640. // url: '/pages/index/index'
  641. // });
  642. // }
  643. },
  644. error(e) {
  645. // 相机错误事件
  646. console.error(e.detail);
  647. uni.showToast({
  648. title: '相机启动失败,请检查权限设置',
  649. icon: 'none'
  650. });
  651. },
  652. playAiSpeaking() {
  653. if (this.useVideo && this.aiVideoContext) {
  654. this.aiVideoContext.play();
  655. }
  656. // 触发数字人说话动画
  657. if (this.digitalHumanUrl) {
  658. // 假设当前问题的文本作为数字人要说的内容
  659. const speakText = this.currentQuestion ? this.currentQuestion.text : '';
  660. this.interactWithDigitalHuman(speakText);
  661. }
  662. },
  663. pauseAiSpeaking() {
  664. if (this.useVideo && this.aiVideoContext) {
  665. this.aiVideoContext.pause();
  666. }
  667. },
  668. // 修改 restartTest 方法,添加可选的跳转目标
  669. restartTest(target) {
  670. this.currentQuestionIndex = 0;
  671. this.score = 0;
  672. this.showEndModal = false;
  673. this.showResult = false;
  674. this.selectedOption = null;
  675. this.selectedOptions = [];
  676. this.resetTimer();
  677. // 如果指定了跳转目标
  678. if (target) {
  679. uni.navigateTo({
  680. url: target
  681. });
  682. }
  683. },
  684. // 在methods中添加测试方法
  685. testEndScreen() {
  686. this.interviewCompleted = true;
  687. this.showEndModal = false;
  688. },
  689. // 初始化数字人
  690. initDigitalHuman() {
  691. // 移除不可用的示例URL
  692. // this.digitalHumanUrl = 'https://your-digital-human-service.com/avatar?id=123';
  693. // 暂时不使用数字人服务,直接使用本地图片
  694. this.digitalHumanUrl = '';
  695. // 如果您有实际可用的数字人服务,可以在这里设置
  696. // 例如:this.digitalHumanUrl = 'https://your-actual-service.com/avatar';
  697. // 或者使用本地HTML文件(如果有的话)
  698. // this.digitalHumanUrl = '/hybrid/html/digital-human.html';
  699. },
  700. // 与数字人交互的方法
  701. interactWithDigitalHuman(message) {
  702. // 如果数字人是通过web-view加载的,可以使用postMessage与其通信
  703. const webview = this.$mp.page.$getAppWebview().children()[0];
  704. if (webview) {
  705. webview.evalJS(`receiveMessage('${message}')`);
  706. }
  707. },
  708. // 添加 navigateToInterview 方法
  709. navigateToInterview() {
  710. // 关闭模态框
  711. this.showEndModal = false;
  712. // 跳转到interview页面
  713. uni.navigateTo({
  714. url: '/pages/interview/interview',
  715. success: () => {
  716. console.log('成功跳转到interview页面');
  717. },
  718. fail: (err) => {
  719. console.error('跳转失败:', err);
  720. uni.showToast({
  721. title: '跳转失败,请手动返回首页',
  722. icon: 'none'
  723. });
  724. }
  725. });
  726. },
  727. // 添加 handleCameraError 方法
  728. handleCameraError(e) {
  729. console.error('相机错误:', e.detail);
  730. uni.showToast({
  731. title: '相机启动失败,请检查权限设置',
  732. icon: 'none'
  733. });
  734. },
  735. // 修改 isOptionSelected 方法,更精确地判断选项是否被选中
  736. isOptionSelected(question, qIndex, optionIndex) {
  737. // 获取该题的答案
  738. const answer = this.answers.find(a => a.questionId === question.id);
  739. // 如果是当前正在回答的题目
  740. if (qIndex === this.currentQuestionIndex) {
  741. if (question.questionType === 1 || question.questionType === 3 || question.questionType === 4) { // 单选题
  742. return this.selectedOption === optionIndex;
  743. } else if (question.questionType === 2) { // 多选题
  744. return this.selectedOptions.includes(optionIndex);
  745. }
  746. }
  747. // 如果是已回答过的题目,使用保存的答案
  748. else if (answer) {
  749. if (question.questionType === 1 || question.questionType === 3 || question.questionType === 4) { // 单选题
  750. return answer.answer === optionIndex;
  751. } else if (question.questionType === 2 && Array.isArray(answer.answer)) { // 多选题
  752. return answer.answer.includes(optionIndex);
  753. }
  754. }
  755. return false;
  756. },
  757. },
  758. // 添加生命周期钩子,确保在组件销毁时清除计时器
  759. beforeDestroy() {
  760. if (this.timerInterval) {
  761. clearInterval(this.timerInterval);
  762. }
  763. }
  764. }
  765. </script>
  766. <style>
  767. .camera-container {
  768. position: relative;
  769. width: 100%;
  770. height: 100vh;
  771. background-color: #ffffff;
  772. display: flex;
  773. flex-direction: column;
  774. overflow: hidden; /* 防止内容溢出 */
  775. }
  776. .header {
  777. height: 90rpx;
  778. /* background-color: #000000; */
  779. display: flex;
  780. align-items: center;
  781. padding: 0 30rpx;
  782. /* color: #ffffff; */
  783. }
  784. .back-button {
  785. width: 60rpx;
  786. height: 60rpx;
  787. display: flex;
  788. align-items: center;
  789. justify-content: center;
  790. }
  791. .title {
  792. flex: 1;
  793. text-align: center;
  794. font-size: 32rpx;
  795. font-weight: bold;
  796. }
  797. .controls {
  798. display: flex;
  799. gap: 30rpx;
  800. }
  801. .iconfont {
  802. font-size: 40rpx;
  803. }
  804. .content {
  805. flex: 1;
  806. display: flex;
  807. flex-direction: column;
  808. position: relative;
  809. padding: 50rpx 20rpx 0;
  810. }
  811. .progress-container {
  812. display: flex;
  813. flex-direction: column;
  814. justify-content: space-between;
  815. margin-bottom: 20rpx;
  816. }
  817. .progress-step {
  818. display: flex;
  819. align-items: center;
  820. margin-bottom: 10rpx;
  821. }
  822. .step-circle {
  823. width: 40rpx;
  824. height: 40rpx;
  825. border-radius: 50%;
  826. background-color: #e0e0e0;
  827. display: flex;
  828. align-items: center;
  829. justify-content: center;
  830. font-size: 24rpx;
  831. margin-right: 10rpx;
  832. }
  833. .step-circle.active {
  834. background-color: #ff9500;
  835. color: #ffffff;
  836. }
  837. .step-text {
  838. font-size: 24rpx;
  839. color: #666666;
  840. }
  841. .progress-bar {
  842. height: 10rpx;
  843. background-color: #e0e0e0;
  844. border-radius: 5rpx;
  845. overflow: hidden;
  846. margin-bottom: 20rpx;
  847. }
  848. .progress-fill {
  849. height: 100%;
  850. background-color: #00c853;
  851. transition: width 0.3s;
  852. }
  853. .interview-content {
  854. flex: 1;
  855. display: flex;
  856. flex-direction: column;
  857. margin-bottom: 40rpx;
  858. padding: 20rpx;
  859. border-bottom: 1px solid #eee;
  860. }
  861. .video-area {
  862. display: flex;
  863. /* flex-direction: column; */
  864. justify-content: space-between;
  865. height: auto;
  866. margin-bottom: 20rpx;
  867. }
  868. .video-camera {
  869. display: flex;
  870. flex-direction: row;
  871. align-items: center;
  872. justify-content: space-between;
  873. width: 65%;
  874. }
  875. .interviewer {
  876. background-color: #f5f5f5;
  877. display: flex;
  878. align-items: center;
  879. justify-content: center;
  880. position: relative;
  881. width: 48% !important;
  882. height: 300rpx;
  883. }
  884. .interviewer-video {
  885. width: 100%;
  886. height: 100%;
  887. object-fit: cover;
  888. }
  889. .interviewer-avatar {
  890. width: 80%;
  891. height: 80%;
  892. object-fit: contain;
  893. }
  894. .user-camera-container {
  895. width: 48%;
  896. height: 300rpx;
  897. border-radius: 10rpx;
  898. overflow: hidden;
  899. border: 4rpx solid #ffffff;
  900. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
  901. }
  902. .question-area {
  903. flex: 1;
  904. display: flex;
  905. flex-direction: column;
  906. }
  907. .question-number {
  908. font-size: 28rpx;
  909. color: #666;
  910. margin-bottom: 20rpx;
  911. }
  912. .question-importance {
  913. background-color: #f9f9f9;
  914. padding: 20rpx;
  915. border-radius: 10rpx;
  916. margin-bottom: 20rpx;
  917. font-size: 28rpx;
  918. color: #333;
  919. }
  920. .question-importance text {
  921. color: #ff0000;
  922. /* margin-right: 10rpx; */
  923. }
  924. .options {
  925. display: flex;
  926. flex-direction: column;
  927. gap: 20rpx;
  928. margin-bottom: 30rpx;
  929. }
  930. .option-item {
  931. padding: 20rpx;
  932. background-color: #f5f5f5;
  933. border-radius: 10rpx;
  934. font-size: 28rpx;
  935. transition: all 0.3s;
  936. border: 1px solid #e0e0e0;
  937. display: flex;
  938. align-items: center;
  939. cursor: pointer; /* 添加指针样式 */
  940. }
  941. .option-item:active {
  942. transform: scale(0.98); /* 点击时轻微缩小效果 */
  943. opacity: 0.9;
  944. }
  945. .option-selected {
  946. background-color: #e6f7ff;
  947. border: 1px solid #1890ff;
  948. }
  949. /* 添加选中后的动画效果 */
  950. @keyframes selectedPulse {
  951. 0% { box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.4); }
  952. 70% { box-shadow: 0 0 0 10px rgba(24, 144, 255, 0); }
  953. 100% { box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); }
  954. }
  955. .option-selected {
  956. animation: selectedPulse 1.5s ease-out;
  957. }
  958. .option-correct {
  959. background-color: #d4f7d4;
  960. border: 1px solid #52c41a;
  961. }
  962. .option-wrong {
  963. background-color: #fff1f0;
  964. border: 1px solid #ff4d4f;
  965. }
  966. .option-text {
  967. color: #333;
  968. }
  969. .timer-container {
  970. text-align: right;
  971. margin: 20rpx 0;
  972. }
  973. .timer-text {
  974. font-size: 24rpx;
  975. color: #666;
  976. background-color: #f0f0f0;
  977. padding: 6rpx 12rpx;
  978. border-radius: 6rpx;
  979. }
  980. .bottom-button-container {
  981. position: fixed;
  982. bottom: 50px;
  983. left: 5%;
  984. right: 0;
  985. padding: 20rpx;
  986. display: flex;
  987. justify-content: center;
  988. z-index: 10;
  989. }
  990. .bottom-button-wrapper {
  991. display: flex;
  992. width: 100%;
  993. justify-content: space-between;
  994. align-items: center;
  995. }
  996. .timer-display {
  997. font-size: 28rpx;
  998. color: #666;
  999. padding: 10rpx 20rpx;
  1000. border-radius: 6rpx;
  1001. }
  1002. .next-button {
  1003. background-color: #0039b3;
  1004. color: #ffffff;
  1005. border-radius: 10rpx;
  1006. font-size: 30rpx;
  1007. height: 80rpx;
  1008. line-height: 80rpx;
  1009. width: 90%;
  1010. text-align: center;
  1011. margin: 0;
  1012. }
  1013. .footer {
  1014. height: 100rpx;
  1015. background-color: #000000;
  1016. display: flex;
  1017. justify-content: space-between;
  1018. align-items: center;
  1019. padding: 0 30rpx;
  1020. color: #ffffff;
  1021. }
  1022. .timer {
  1023. font-size: 28rpx;
  1024. }
  1025. .next-step {
  1026. font-size: 28rpx;
  1027. }
  1028. .interview-end-modal {
  1029. position: fixed;
  1030. top: 0;
  1031. left: 0;
  1032. right: 0;
  1033. bottom: 0;
  1034. background-color: rgba(0, 0, 0, 0.5);
  1035. display: flex;
  1036. align-items: center;
  1037. justify-content: center;
  1038. z-index: 999;
  1039. }
  1040. .modal-content {
  1041. width: 80%;
  1042. background-color: #ffffff;
  1043. border-radius: 20rpx;
  1044. padding: 40rpx;
  1045. display: flex;
  1046. flex-direction: column;
  1047. align-items: center;
  1048. }
  1049. .modal-title {
  1050. font-size: 36rpx;
  1051. font-weight: bold;
  1052. margin-bottom: 30rpx;
  1053. }
  1054. .score-display {
  1055. font-size: 48rpx;
  1056. font-weight: bold;
  1057. color: #0039b3;
  1058. margin: 20rpx 0;
  1059. }
  1060. .modal-message {
  1061. font-size: 28rpx;
  1062. color: #666;
  1063. text-align: center;
  1064. margin-bottom: 40rpx;
  1065. }
  1066. .modal-buttons {
  1067. display: flex;
  1068. justify-content: space-between;
  1069. width: 100%;
  1070. margin-top: 20rpx;
  1071. }
  1072. .modal-button {
  1073. width: 45%;
  1074. }
  1075. @keyframes speaking {
  1076. 0% {
  1077. opacity: 0.8;
  1078. }
  1079. 50% {
  1080. opacity: 1;
  1081. }
  1082. 100% {
  1083. opacity: 0.8;
  1084. }
  1085. }
  1086. .speaking {
  1087. animation: speaking 1.5s infinite;
  1088. }
  1089. .digital-avatar {
  1090. display: flex;
  1091. flex-direction: row;
  1092. align-items: flex-start;
  1093. justify-content: space-between;
  1094. width: 100%;
  1095. margin-bottom: 20rpx;
  1096. }
  1097. .message-box {
  1098. width: 65%;
  1099. min-height: 120rpx;
  1100. background-color: #f5f5f5;
  1101. border-radius: 10rpx;
  1102. padding: 15rpx;
  1103. margin-right: 20rpx;
  1104. display: flex;
  1105. align-items: center;
  1106. }
  1107. .message-text {
  1108. font-size: 24rpx;
  1109. color: #333;
  1110. line-height: 1.5;
  1111. }
  1112. .user-avatar {
  1113. width: 110px;
  1114. height: 160px;
  1115. border-radius: 10rpx;
  1116. overflow: hidden;
  1117. }
  1118. .camera {
  1119. width: 100%;
  1120. height: 100%;
  1121. border-radius: 10rpx;
  1122. border: 2rpx solid #e0e0e0;
  1123. }
  1124. .avatar-image {
  1125. width: 100%;
  1126. height: 100%;
  1127. border-radius: 10rpx;
  1128. border: 2rpx solid #e0e0e0;
  1129. }
  1130. .digital-human-webview {
  1131. width: 100%;
  1132. height: 100%;
  1133. border-radius: 10rpx;
  1134. border: 2rpx solid #e0e0e0;
  1135. }
  1136. .interview-complete-screen {
  1137. position: fixed;
  1138. top: 0;
  1139. left: 0;
  1140. right: 0;
  1141. bottom: 0;
  1142. background-color: #f0f5ff;
  1143. display: flex;
  1144. flex-direction: column;
  1145. align-items: center;
  1146. justify-content: center;
  1147. z-index: 1000;
  1148. padding: 40rpx;
  1149. }
  1150. .digital-avatar-large {
  1151. width: 200rpx;
  1152. height: 200rpx;
  1153. margin-bottom: 60rpx;
  1154. }
  1155. .avatar-image-large {
  1156. width: 100%;
  1157. height: 100%;
  1158. border-radius: 30rpx;
  1159. }
  1160. .complete-message {
  1161. font-size: 40rpx;
  1162. font-weight: bold;
  1163. color: #333;
  1164. margin-bottom: 30rpx;
  1165. }
  1166. .complete-description {
  1167. font-size: 28rpx;
  1168. color: #666;
  1169. text-align: center;
  1170. margin-bottom: 80rpx;
  1171. line-height: 1.5;
  1172. }
  1173. .complete-button {
  1174. background-color: #0039b3;
  1175. color: #ffffff;
  1176. border-radius: 10rpx;
  1177. font-size: 32rpx;
  1178. padding: 20rpx 60rpx;
  1179. margin-top: 40rpx;
  1180. }
  1181. /* 添加加载状态样式 */
  1182. .loading-container {
  1183. position: absolute;
  1184. top: 0;
  1185. left: 0;
  1186. right: 0;
  1187. bottom: 0;
  1188. display: flex;
  1189. flex-direction: column;
  1190. align-items: center;
  1191. justify-content: center;
  1192. background-color: rgba(255, 255, 255, 0.9);
  1193. z-index: 100;
  1194. }
  1195. .loading-text {
  1196. margin-top: 20rpx;
  1197. font-size: 28rpx;
  1198. color: #666;
  1199. }
  1200. .error-container {
  1201. text-align: center;
  1202. }
  1203. .error-message {
  1204. font-size: 28rpx;
  1205. color: #ff4d4f;
  1206. margin-bottom: 30rpx;
  1207. }
  1208. .retry-button {
  1209. background-color: #0039b3;
  1210. color: #ffffff;
  1211. border-radius: 10rpx;
  1212. font-size: 28rpx;
  1213. padding: 10rpx 30rpx;
  1214. }
  1215. .question-type {
  1216. display: inline-block;
  1217. /* background-color: #0039b3; */
  1218. color: white;
  1219. font-size: 24rpx;
  1220. /* padding: 4rpx 5rpx; */
  1221. border-radius: 6rpx;
  1222. /* margin-right: 10rpx; */
  1223. }
  1224. /* 新增的开放问题样式 */
  1225. .open-question-container {
  1226. display: flex;
  1227. flex-direction: column;
  1228. width: 100%;
  1229. margin-bottom: 20rpx;
  1230. }
  1231. .open-question-input {
  1232. width: 100%;
  1233. height: 300rpx;
  1234. background-color: #f9f9f9;
  1235. border: 1px solid #e0e0e0;
  1236. border-radius: 10rpx;
  1237. padding: 20rpx;
  1238. font-size: 28rpx;
  1239. color: #333;
  1240. }
  1241. .word-count {
  1242. text-align: right;
  1243. font-size: 24rpx;
  1244. color: #999;
  1245. margin-top: 10rpx;
  1246. }
  1247. .option-prefix {
  1248. margin-right: 10rpx;
  1249. font-weight: bold;
  1250. color: #333;
  1251. min-width: 30rpx;
  1252. }
  1253. /* 新增的图片显示样式 */
  1254. .question-image-container {
  1255. width: 100%;
  1256. margin: 20rpx 0;
  1257. display: flex;
  1258. justify-content: center;
  1259. align-items: center;
  1260. }
  1261. .question-image {
  1262. width: 100%;
  1263. max-width: 600rpx;
  1264. border-radius: 10rpx;
  1265. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  1266. }
  1267. /* 添加滚动视图样式 */
  1268. .content-scroll {
  1269. flex: 1;
  1270. height: calc(100vh - 200px); /* 只减去顶部相机的高度 */
  1271. margin-top: 10px;
  1272. }
  1273. /* 添加底部空白区域,防止内容被固定按钮遮挡 */
  1274. .bottom-space {
  1275. height: 50rpx; /* 减小底部空白区域 */
  1276. }
  1277. /* 添加流式布局相关样式 */
  1278. .interview-content {
  1279. margin-bottom: 40rpx;
  1280. padding: 20rpx;
  1281. border-bottom: 1px solid #eee;
  1282. }
  1283. .option-disabled {
  1284. opacity: 0.7;
  1285. pointer-events: none;
  1286. }
  1287. .question-button-container {
  1288. margin-top: 20rpx;
  1289. display: flex;
  1290. justify-content: center;
  1291. }
  1292. .bottom-timer-container {
  1293. display: none;
  1294. }
  1295. /* 添加固定相机容器样式 */
  1296. .fixed-camera-container {
  1297. position: sticky;
  1298. top: 0;
  1299. width: 100%;
  1300. padding: 20rpx;
  1301. background-color: #fff;
  1302. z-index: 10;
  1303. display: flex;
  1304. justify-content: flex-start;
  1305. border-bottom: 1px solid #eee;
  1306. }
  1307. .user-avatar {
  1308. width: 110px;
  1309. height: 160px;
  1310. border-radius: 10rpx;
  1311. overflow: hidden;
  1312. }
  1313. </style>