You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

855 lines
20 KiB

  1. <template>
  2. <div class="home">
  3. <div class="top">
  4. <div @click="showConfirmDialog" class="ret-container">
  5. <img src="../assets/return.jpg" alt="返回" class="ret">
  6. </div>
  7. <h1>📈股票知识评测系统</h1>
  8. <p>全方面评估您的股票投资知识水平获取个性化学习建议</p>
  9. </div>
  10. <div class="confirm-dialog dialog-overlay" v-if="showDialog">
  11. <div class="dialog-content">
  12. <h3>确认提示</h3>
  13. <p>您还未提交确定要退出吗</p>
  14. <div class="dialog-buttons">
  15. <button class="cancel-btn" @click="closeDialog">取消</button>
  16. <button class="confirm-btn" @click="confirmExit">确定</button>
  17. </div>
  18. </div>
  19. </div>
  20. <div class="confirm-dialog dialog-overlay" v-if="showTeam">
  21. <div class="dialog-content">
  22. <h3>确认提示</h3>
  23. <p>您确定要提交吗</p>
  24. <div class="dialog-buttons">
  25. <button class="cancel-btn" @click="closeSubmit">取消</button>
  26. <button class="confirm-btn" @click="submitAnswers">确定</button>
  27. </div>
  28. </div>
  29. </div>
  30. <div class="clearfix">
  31. <div class="content">
  32. <div class="block">
  33. <div class="schedule" :style="{ width: progress + '%' }"></div>
  34. </div>
  35. <div class="questions-container">
  36. <div v-for="question in currentQuestions" :key="question.id" class="question-card">
  37. <div class="question-header">
  38. <div class="question">
  39. <span class="text-lg font-bold mr-2">{{ question.id }}</span>
  40. {{ question.stem }}
  41. </div>
  42. </div>
  43. <div class="options">
  44. <label
  45. class="option"
  46. :class="{ 'selected': getAnswer(question.id) === 'A' }"
  47. @click="setAnswer(question.id, 'A')"
  48. >
  49. <input
  50. type="radio"
  51. :name="'answer' + question.id"
  52. value="A"
  53. :checked="getAnswer(question.id) === 'A'"
  54. @change="setAnswer(question.id, 'A')"
  55. >
  56. <span class="label">A. {{ question.A }}</span>
  57. </label>
  58. <label
  59. class="option"
  60. :class="{ 'selected': getAnswer(question.id) === 'B' }"
  61. @click="setAnswer(question.id, 'B')"
  62. >
  63. <input
  64. type="radio"
  65. :name="'answer' + question.id"
  66. value="B"
  67. :checked="getAnswer(question.id) === 'B'"
  68. @change="setAnswer(question.id, 'B')"
  69. >
  70. <span class="label">B. {{ question.B }}</span>
  71. </label>
  72. <label
  73. class="option"
  74. :class="{ 'selected': getAnswer(question.id) === 'C' }"
  75. @click="setAnswer(question.id, 'C')"
  76. >
  77. <input
  78. type="radio"
  79. :name="'answer' + question.id"
  80. value="C"
  81. :checked="getAnswer(question.id) === 'C'"
  82. @change="setAnswer(question.id, 'C')"
  83. >
  84. <span class="label">C. {{ question.C }}</span>
  85. </label>
  86. <label
  87. class="option"
  88. :class="{ 'selected': getAnswer(question.id) === 'D' }"
  89. @click="setAnswer(question.id, 'D')"
  90. >
  91. <input
  92. type="radio"
  93. :name="'answer' + question.id"
  94. value="D"
  95. :checked="getAnswer(question.id) === 'D'"
  96. @change="setAnswer(question.id, 'D')"
  97. >
  98. <span class="label">D. {{ question.D }}</span>
  99. </label>
  100. </div>
  101. </div>
  102. </div>
  103. <div class="nav-buttons">
  104. <button
  105. class="nav-btn prev"
  106. @click="prevPage"
  107. :disabled="currentPage === 1"
  108. >
  109. 上一页
  110. </button>
  111. <span class="page-info"> {{ currentPage }} / {{ totalPages }} </span>
  112. <button
  113. class="nav-btn next"
  114. @click="nextPage"
  115. :disabled="currentPage === totalPages"
  116. >
  117. 下一页
  118. </button>
  119. </div>
  120. </div>
  121. <div class="right">
  122. <div class="time-module">
  123. <div class="countdown"> {{ countdown }}</div>
  124. </div>
  125. <div class="question-nav">
  126. <h3>📝 题目导航</h3>
  127. <div class="question-grid" v-show="page === 1">
  128. <div
  129. class="question-number"
  130. :class="getQuestionStatusClass(i)"
  131. @click="goToPageByQuestion(i)"
  132. v-for="i in 25"
  133. :key="i"
  134. >
  135. <span class="question-text">{{ i }}</span>
  136. </div>
  137. </div>
  138. <div class="question-grid" v-show="page === 2">
  139. <div
  140. class="question-number"
  141. :class="getQuestionStatusClass(i + 25)"
  142. @click="goToPageByQuestion(i + 25)"
  143. v-for="i in 25"
  144. :key="i + 25"
  145. >
  146. <span class="question-text">{{ i + 25 }}</span>
  147. </div>
  148. </div>
  149. <div class="pagination">
  150. <button
  151. class="pagination-btn"
  152. @click="changeNavPage(1)"
  153. :class="{ active: page === 1 }"
  154. :disabled="page === 1"
  155. >
  156. 上一页
  157. </button>
  158. <button
  159. class="pagination-btn"
  160. @click="changeNavPage(2)"
  161. :class="{ active: page === 2 }"
  162. :disabled="page === 2"
  163. >
  164. 下一页
  165. </button>
  166. </div>
  167. </div>
  168. <div class="statistics">
  169. <h3>📊 答题统计</h3>
  170. <div class="statistics-item">
  171. <span class="statistics-label">答题进度:</span>
  172. <span class="statistics-value">{{ answeredCount }}/{{ totalQuestions }}</span>
  173. </div>
  174. </div>
  175. <button class="submit-btn" @click="closeTeamPrompt">🚀 提交试卷</button>
  176. </div>
  177. </div>
  178. </div>
  179. </template>
  180. <script>
  181. import axios from 'axios';
  182. export default {
  183. name: 'TextView',
  184. data() {
  185. return {
  186. questions: [], // 从后端获取的题目
  187. currentPage: 1,
  188. page: 1,
  189. answers: {},
  190. startTime: new Date(),
  191. countdownMinutes: 30,
  192. countdown: '',
  193. timer: null,
  194. isSubmitted: false,
  195. totalQuestions: 50, // 后端固定50题
  196. questionStates: {},
  197. showDialog: false,
  198. showTeam: false,
  199. };
  200. },
  201. created() {
  202. for (let i = 1; i <= this.totalQuestions; i++) {
  203. this.$set(this.answers, i, 0)
  204. }
  205. },
  206. computed: {
  207. // 当前页显示的两个题目
  208. currentQuestions() {
  209. const startIndex = (this.currentPage - 1) * 2;
  210. return [
  211. this.questions[startIndex],
  212. this.questions[startIndex + 1]
  213. ];
  214. },
  215. totalPages() {
  216. return Math.ceil(this.questions.length / 2);
  217. },
  218. answeredCount() {
  219. return Object.keys(this.answers).filter(
  220. key => this.answers[key] !== 0
  221. ).length
  222. },
  223. completionRate() {
  224. return (this.answeredCount / this.totalQuestions) * 100;
  225. },
  226. progress() {
  227. return this.completionRate;
  228. },
  229. // 当前显示的题目ID
  230. currentQuestionIds() {
  231. return this.currentQuestions.map(q => q ? q.id : null).filter(Boolean);
  232. }
  233. },
  234. methods: {
  235. // 从后端获取题目
  236. async fetchQuestions() {
  237. try {
  238. const response = await axios.post('http://192.168.40.41:8000/api/knowledge/questions');
  239. this.questions = response.data.data.list;
  240. } catch (error) {
  241. console.error('获取题目失败:', error);
  242. }
  243. },
  244. // // 默认题目数据(备用)- 生成50个题目
  245. // getDefaultQuestions() {
  246. // // 这里创建一个示例数组,您可以根据实际需要替换为真实数据
  247. // const defaultQuestions = [];
  248. // for (let i = 1; i <= 50; i++) {
  249. // defaultQuestions.push({
  250. // id: i,
  251. // stem: `这是第${i}个股票知识测试题目`,
  252. // A: `选项A - 第${i}题`,
  253. // B: `选项B - 第${i}题`,
  254. // C: `选项C - 第${i}题`,
  255. // D: `选项D - 第${i}题`
  256. // });
  257. // }
  258. // return defaultQuestions;
  259. // },
  260. // 获取题目在当前试卷中的序号
  261. getQuestionIndex(questionId) {
  262. // 查找题目在questions数组中的位置并返回序号
  263. const index = this.questions.findIndex(q => q.id === questionId);
  264. return index !== -1 ? index + 1 : questionId;
  265. },
  266. // 显示确认弹框
  267. showConfirmDialog() {
  268. this.showDialog = true;
  269. },
  270. // 关闭弹框
  271. closeDialog() {
  272. this.showDialog = false;
  273. },
  274. // 确认退出
  275. confirmExit() {
  276. this.showDialog = false;
  277. // 跳转到首页
  278. this.$router.push('/');
  279. },
  280. //显示警告弹框
  281. closeTeamPrompt() {
  282. this.showTeam = true;
  283. },
  284. closeSubmit() {
  285. this.showTeam = false;
  286. },
  287. // submitAnswers() {
  288. // this.showTeam = false;
  289. // },
  290. // 获取题目的答案
  291. getAnswer(questionId) {
  292. return this.answers[questionId] || '';
  293. },
  294. // 设置题目的答案
  295. setAnswer(questionId, answer) {
  296. this.$set(this.answers, questionId, answer);
  297. },
  298. // 获取题目状态样式
  299. getQuestionStatusClass(questionNumber) {
  300. const isAnswered = !!this.answers[questionNumber];
  301. if (isAnswered) {
  302. return 'answered';
  303. } else {
  304. return 'unanswered';
  305. }
  306. },
  307. // 修改导航页面切换方法
  308. changeNavPage(newPage) {
  309. this.page = newPage;
  310. },
  311. // 修改题目页面切换方法
  312. changePage(newPage) {
  313. if (newPage >= 1 && newPage <= this.totalPages) {
  314. this.currentPage = newPage;
  315. // 自动更新导航页(每25题一页)
  316. const startQuestion = (newPage - 1) * 2 + 1;
  317. this.page = Math.ceil(startQuestion / 25);
  318. }
  319. },
  320. // 修改通过题目跳转的方法
  321. goToPageByQuestion(questionNumber) {
  322. const page = Math.ceil(questionNumber / 2);
  323. this.changePage(page);
  324. // 自动切换到对应的导航页
  325. this.page = Math.ceil(questionNumber / 25);
  326. },
  327. // 上一页
  328. prevPage() {
  329. if (this.currentPage > 1) {
  330. this.currentPage--;
  331. }
  332. if (this.currentPage < 13) {
  333. this.page=1;
  334. }
  335. },
  336. // 下一页
  337. nextPage() {
  338. if (this.currentPage < this.totalPages) {
  339. this.currentPage++;
  340. }
  341. if(this.currentPage > 13){
  342. this.page=2;
  343. }
  344. },
  345. // 更新倒计时
  346. updateCountdown() {
  347. const now = new Date();
  348. const elapsedMinutes = (now - this.startTime) / (1000 * 60);
  349. const remainingMinutes = Math.max(0, this.countdownMinutes - elapsedMinutes);
  350. const minutes = Math.floor(remainingMinutes).toString().padStart(2, '0');
  351. const seconds = Math.floor((remainingMinutes % 1) * 60).toString().padStart(2, '0');
  352. this.countdown = `${minutes}:${seconds}`;
  353. // 如果倒计时结束,自动提交试卷
  354. if (remainingMinutes <= 0 && !this.isSubmitted) {
  355. this.isSubmitted = true;
  356. this.submitAnswers();
  357. }
  358. },
  359. // 提交答案到后端
  360. async submitAnswers() {
  361. try {
  362. if (Object.keys(this.answers).length === 0) {
  363. this.showTeam=false;
  364. return;
  365. }
  366. // 将答案格式转换为要求的格式
  367. const formattedAnswers = Object.keys(this.answers).map(questionId => ({
  368. questionId: parseInt(questionId),
  369. userAnswer: this.answers[questionId]
  370. }));
  371. const savedData = JSON.parse(localStorage.getItem('submissionData'));
  372. console.log('第一个,存储的数据:', localStorage.getItem('submissionData'));
  373. const submission = {
  374. jwcode: savedData.jwcode,
  375. answers: formattedAnswers
  376. };
  377. console.log(submission);
  378. // 调用后端提交接口
  379. const response = await axios.post('http://192.168.40.41:8000/api/knowledge/submit', submission);
  380. console.log(submission);
  381. // 跳转到结果页面
  382. this.$router.push({
  383. name: 'ResultView',
  384. query: {
  385. score: response.data.data.score || 80,
  386. total: this.totalQuestions,
  387. timeUsed: this.getTimeUsed()
  388. }
  389. });
  390. } catch (error) {
  391. console.error('提交试卷失败:', error);
  392. alert('提交失败,请重试');
  393. this.isSubmitted = false;
  394. }
  395. },
  396. // 获取已用时间
  397. getTimeUsed() {
  398. const now = new Date();
  399. const elapsedMinutes = (now - this.startTime) / (1000 * 60);
  400. return Math.round(elapsedMinutes);
  401. }
  402. },
  403. async mounted() {
  404. // 从后端获取题目
  405. await this.fetchQuestions();
  406. // 设置定时器,每秒更新一次倒计时
  407. this.timer = setInterval(() => {
  408. this.updateCountdown();
  409. }, 1000);
  410. },
  411. beforeDestroy() {
  412. if (this.timer) {
  413. clearInterval(this.timer);
  414. }
  415. }
  416. };
  417. </script>
  418. <style scoped>
  419. .questions-container {
  420. display: flex;
  421. flex-direction: column;
  422. gap: 30px;
  423. }
  424. .ret{
  425. width: 30px;
  426. height: 50px;
  427. float: left;
  428. }
  429. .page-info {
  430. color: #e5e7eb;
  431. font-weight: bold;
  432. padding: 0 20px;
  433. }
  434. .question-grid {
  435. display: grid;
  436. grid-template-columns: repeat(5, 1fr);
  437. gap: 10px;
  438. margin: auto 30px;
  439. }
  440. .question-number {
  441. width: 60px;
  442. height: 50px;
  443. border-radius: 8px;
  444. display: flex;
  445. align-items: center;
  446. justify-content: center;
  447. font-weight: bold;
  448. cursor: pointer;
  449. position: relative;
  450. }
  451. .question-number.unanswered {
  452. background-color: #374151;
  453. border: 1px solid #4b5563;
  454. color: #e5e7eb;
  455. }
  456. .question-number.answered {
  457. background-color: #10b981;
  458. border: 1px solid #059669;
  459. color: white;
  460. }
  461. /* .question-number.current {
  462. background-color: #3b82f6;
  463. border: 2px solid #2563eb;
  464. color: white;
  465. transform: scale(1.1);
  466. } */
  467. .question-text {
  468. z-index: 1;
  469. }
  470. .pagination-btn.active {
  471. background-color: #2563eb;
  472. transform: scale(1.05);
  473. }
  474. .pagination-btn:disabled{
  475. background-color: #4b5563;
  476. color: #e5e7eb;
  477. }
  478. * {
  479. margin: 0;
  480. padding: 0;
  481. box-sizing: border-box;
  482. }
  483. body {
  484. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  485. background-color: #f5f7fa;
  486. }
  487. .home {
  488. min-height: 100vh;
  489. width: 100%;
  490. background-color: #24293c;
  491. overflow: auto;
  492. padding: 20px;
  493. }
  494. .top {
  495. background: linear-gradient(135deg, #0c4a6e 100%);
  496. color: white;
  497. padding: 30px;
  498. margin-bottom: 30px;
  499. border-radius: 10px;
  500. box-shadow: 0 10px 30px rgba(0,0,0,0.3);
  501. position: relative;
  502. overflow: hidden;
  503. }
  504. .top h1 {
  505. font-size: 2.2em;
  506. margin-bottom: 10px;
  507. background: none;
  508. }
  509. .top p {
  510. font-size: 1.1em;
  511. opacity: 0.9;
  512. background: none;
  513. }
  514. .block {
  515. background: rgba(139, 141, 145, 0.7);
  516. width: 90%;
  517. height: 10px;
  518. border-radius: 5px;
  519. margin: 0 auto 30px;
  520. overflow: hidden;
  521. position: relative;
  522. box-shadow: inset 0 0 10px rgba(0,0,0,0.3);
  523. }
  524. .schedule {
  525. background: linear-gradient(90deg, #0ea5e9 0%, #38bdf8 100%);
  526. height: 100%;
  527. width: 15%;
  528. transition: width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  529. position: relative;
  530. }
  531. .content {
  532. float: left;
  533. width: calc(65% - 20px);
  534. min-height: 920px;
  535. border: #274779 solid 2px;
  536. border-radius: 10px;
  537. color: #f1f5f9;
  538. padding: 20px 40px 50px;
  539. margin-right: 20px;
  540. background-color: #2a3147;
  541. }
  542. .question-card {
  543. border: #274779 solid 2px;
  544. border-radius: 10px;
  545. padding: 20px 40px;
  546. background-color: #2f374d;
  547. }
  548. .question-header {
  549. margin-bottom: 20px;
  550. }
  551. .question {
  552. font-size: 1.3em;
  553. line-height: 1.8;
  554. margin-bottom: 25px;
  555. color: #f1f5f9;
  556. font-weight: 500;
  557. }
  558. .options {
  559. display: flex;
  560. flex-direction: column;
  561. gap: 15px;
  562. }
  563. .option {
  564. border: #183954 solid 2px;
  565. border-radius: 10px;
  566. padding: 12px 15px;
  567. display: flex;
  568. align-items: center;
  569. cursor: pointer;
  570. transition: all 0.3s ease;
  571. background-color: #374151;
  572. }
  573. .option:hover {
  574. border-color: #3b82f6;
  575. background-color: #4b5563;
  576. }
  577. .option input {
  578. margin-right: 15px;
  579. width: 18px;
  580. height: 18px;
  581. }
  582. .option .label {
  583. font-size: 1.1em;
  584. color: #e5e7eb;
  585. }
  586. .option.selected {
  587. border-color: #3b82f6;
  588. background-color: rgba(59, 130, 246, 0.2);
  589. }
  590. .right {
  591. float: right;
  592. width: calc(35% - 20px);
  593. color: #f1f5f9;
  594. padding: 20px;
  595. }
  596. .time-module {
  597. border: #274779 solid 2px;
  598. border-radius: 30px;
  599. padding: 15px;
  600. margin-bottom: 20px;
  601. background-color: #2f374d;
  602. }
  603. .question-nav {
  604. border: #274779 solid 2px;
  605. border-radius: 30px;
  606. padding: 15px 0 15px 19px ;
  607. margin-bottom: 20px;
  608. background-color: #2f374d;
  609. }
  610. .statistics {
  611. height: 150px;
  612. border: #274779 solid 2px;
  613. border-radius: 30px;
  614. padding: 15px;
  615. margin-bottom: 20px;
  616. background-color: #2f374d;
  617. }
  618. .time-module h3, .question-nav h3, .statistics h3 {
  619. margin-bottom: 15px;
  620. color: #f1f5f9;
  621. font-size: 1.2em;
  622. border-bottom: 2px solid #274779;
  623. padding-bottom: 10px;
  624. }
  625. .countdown {
  626. font-size: 1.5em;
  627. color: #f59e0b;
  628. text-align: center;
  629. }
  630. .pagination {
  631. display: flex;
  632. /* justify-content: center; */
  633. margin: 30px 148px 5px;
  634. gap: 20px;
  635. }
  636. .pagination-btn {
  637. padding: 8px;
  638. border-radius: 8px;
  639. border: none;
  640. background-color: #3b82f6;
  641. color: white;
  642. font-weight: bold;
  643. cursor: pointer;
  644. transition: all 0.3s ease;
  645. margin: auto 20px;
  646. }
  647. .pagination-btn:hover {
  648. background-color: #2563eb;
  649. }
  650. .pagination-btn:disabled {
  651. background-color: #4b5563;
  652. }
  653. .nav-buttons {
  654. display: flex;
  655. justify-content: space-between;
  656. align-items: center;
  657. margin-top: 30px;
  658. }
  659. .nav-btn {
  660. padding: 10px 20px;
  661. border-radius: 8px;
  662. border: none;
  663. background-color: #3b82f6;
  664. color: white;
  665. font-weight: bold;
  666. display: flex;
  667. align-items: center;
  668. gap: 5px;
  669. }
  670. .nav-btn:hover {
  671. background-color: #2563eb;
  672. }
  673. .nav-btn.prev {
  674. background-color: #3b82f6;;
  675. }
  676. .nav-btn.prev:hover {
  677. background-color: #2563eb;
  678. }
  679. .nav-btn:disabled {
  680. background-color: #4b5563;
  681. cursor: not-allowed;
  682. }
  683. .statistics-item {
  684. display: flex;
  685. font-size: larger;
  686. justify-content: space-between;
  687. margin-top: 30px;
  688. padding-top: 10px;
  689. padding-bottom: 2px;
  690. border-bottom: 2px solid #274779;
  691. }
  692. .statistics-label {
  693. color: #e5e7eb;
  694. }
  695. .statistics-value {
  696. font-weight: bold;
  697. color: #3b82f6;
  698. }
  699. .submit-btn {
  700. width: 100%;
  701. padding: 15px;
  702. border-radius: 10px;
  703. border: none;
  704. background-color: #10b981;
  705. color: white;
  706. font-size: 1.2em;
  707. font-weight: bold;
  708. cursor: pointer;
  709. transition: all 0.3s ease;
  710. margin-top: 20px;
  711. display: block;
  712. text-align: center;
  713. text-decoration: none;
  714. }
  715. .submit-btn:hover {
  716. background-color: #059669;
  717. }
  718. .clearfix::after {
  719. content: "";
  720. clear: both;
  721. display: table;
  722. }
  723. .ret-container {
  724. float: left;
  725. cursor: pointer;
  726. transition: transform 0.3s ease;
  727. }
  728. .ret-container:hover {
  729. transform: scale(1.1);
  730. }
  731. .confirm-dialog {
  732. position: fixed;
  733. top: 0;
  734. left: 0;
  735. width: 100%;
  736. height: 100%;
  737. display: flex;
  738. justify-content: center;
  739. align-items: center;
  740. z-index: 1000;
  741. }
  742. .dialog-content {
  743. position: relative;
  744. background: white;
  745. padding: 30px;
  746. border-radius: 15px;
  747. box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
  748. max-width: 400px;
  749. width: 90%;
  750. text-align: center;
  751. z-index: 1001;
  752. }
  753. .dialog-overlay {
  754. position: absolute;
  755. top: 0;
  756. left: 0;
  757. width: 100%;
  758. height: 100%;
  759. background: rgba(0, 0, 0, 0.5);
  760. backdrop-filter: blur(5px);
  761. }
  762. .dialog-content h3 {
  763. color: #333;
  764. margin-bottom: 15px;
  765. font-size: 1.4em;
  766. }
  767. .dialog-content p {
  768. color: #666;
  769. margin-bottom: 25px;
  770. font-size: 1.1em;
  771. line-height: 1.5;
  772. }
  773. .dialog-buttons {
  774. display: flex;
  775. gap: 15px;
  776. justify-content: center;
  777. }
  778. .cancel-btn, .confirm-btn {
  779. padding: 12px 30px;
  780. border: none;
  781. border-radius: 8px;
  782. font-size: 1em;
  783. font-weight: bold;
  784. cursor: pointer;
  785. transition: all 0.3s ease;
  786. min-width: 100px;
  787. }
  788. .cancel-btn {
  789. background: #f1f1f1;
  790. color: #666;
  791. }
  792. .cancel-btn:hover {
  793. background: #e0e0e0;
  794. }
  795. .confirm-btn {
  796. background: #ff4757;
  797. color: white;
  798. }
  799. .confirm-btn:hover {
  800. background: #ff3742;
  801. transform: translateY(-2px);
  802. }
  803. </style>