|
|
<template> <div class="home"> <div class="top"> <div @click="showConfirmDialog" class="ret-container"> <img src="../assets/return.jpg" alt="返回" class="ret"> </div> <h1>📈股票知识评测系统</h1> <p>全方面评估您的股票投资知识水平,获取个性化学习建议</p> </div> <div class="confirm-dialog dialog-overlay" v-if="showDialog"> <div class="dialog-content"> <h3>确认提示</h3> <p>您还未提交,确定要退出吗?</p> <div class="dialog-buttons"> <button class="cancel-btn" @click="closeDialog">取消</button> <button class="confirm-btn" @click="confirmExit">确定</button> </div> </div> </div> <div class="confirm-dialog dialog-overlay" v-if="showTeam"> <div class="dialog-content"> <h3>确认提示</h3> <p>您确定要提交吗?</p> <div class="dialog-buttons"> <button class="cancel-btn" @click="closeSubmit">取消</button> <button class="confirm-btn" @click="submitAnswers">确定</button> </div> </div> </div> <div class="clearfix"> <div class="content"> <div class="block"> <div class="schedule" :style="{ width: progress + '%' }"></div> </div> <div class="questions-container"> <div v-for="question in currentQuestions" :key="question.id" class="question-card"> <div class="question-header"> <div class="question"> <span class="text-lg font-bold mr-2">{{ question.id }}、</span> {{ question.stem }} </div> </div> <div class="options"> <label class="option" :class="{ 'selected': getAnswer(question.id) === 'A' }" @click="setAnswer(question.id, 'A')" > <input type="radio" :name="'answer' + question.id" value="A" :checked="getAnswer(question.id) === 'A'" @change="setAnswer(question.id, 'A')" > <span class="label">A. {{ question.A }}</span> </label> <label class="option" :class="{ 'selected': getAnswer(question.id) === 'B' }" @click="setAnswer(question.id, 'B')" > <input type="radio" :name="'answer' + question.id" value="B" :checked="getAnswer(question.id) === 'B'" @change="setAnswer(question.id, 'B')" > <span class="label">B. {{ question.B }}</span> </label> <label class="option" :class="{ 'selected': getAnswer(question.id) === 'C' }" @click="setAnswer(question.id, 'C')" > <input type="radio" :name="'answer' + question.id" value="C" :checked="getAnswer(question.id) === 'C'" @change="setAnswer(question.id, 'C')" > <span class="label">C. {{ question.C }}</span> </label> <label class="option" :class="{ 'selected': getAnswer(question.id) === 'D' }" @click="setAnswer(question.id, 'D')" > <input type="radio" :name="'answer' + question.id" value="D" :checked="getAnswer(question.id) === 'D'" @change="setAnswer(question.id, 'D')" > <span class="label">D. {{ question.D }}</span> </label> </div> </div> </div> <div class="nav-buttons"> <button class="nav-btn prev" @click="prevPage" :disabled="currentPage === 1" > ← 上一页 </button> <span class="page-info">第 {{ currentPage }} 页 / 共 {{ totalPages }} 页</span> <button class="nav-btn next" @click="nextPage" :disabled="currentPage === totalPages" > 下一页 → </button> </div> </div> <div class="right"> <div class="time-module"> <div class="countdown">⏰ {{ countdown }}</div> </div> <div class="question-nav"> <h3>📝 题目导航</h3> <div class="question-grid" v-show="page === 1"> <div class="question-number" :class="getQuestionStatusClass(i)" @click="goToPageByQuestion(i)" v-for="i in 25" :key="i" > <span class="question-text">{{ i }}</span> </div> </div> <div class="question-grid" v-show="page === 2"> <div class="question-number" :class="getQuestionStatusClass(i + 25)" @click="goToPageByQuestion(i + 25)" v-for="i in 25" :key="i + 25" > <span class="question-text">{{ i + 25 }}</span> </div> </div> <div class="pagination"> <button class="pagination-btn" @click="changeNavPage(1)" :class="{ active: page === 1 }" :disabled="page === 1" > ← 上一页 </button> <button class="pagination-btn" @click="changeNavPage(2)" :class="{ active: page === 2 }" :disabled="page === 2" > 下一页 → </button> </div> </div> <div class="statistics"> <h3>📊 答题统计</h3> <div class="statistics-item"> <span class="statistics-label">答题进度:</span> <span class="statistics-value">{{ answeredCount }}/{{ totalQuestions }}</span> </div> </div> <button class="submit-btn" @click="closeTeamPrompt">🚀 提交试卷</button> </div> </div> </div></template>
<script>import axios from 'axios';export default { name: 'TextView', data() { return { questions: [], // 从后端获取的题目
currentPage: 1, page: 1, answers: {}, startTime: new Date(), countdownMinutes: 30, countdown: '', timer: null, isSubmitted: false, totalQuestions: 50, // 后端固定50题
questionStates: {}, showDialog: false, showTeam: false, }; }, created() { for (let i = 1; i <= this.totalQuestions; i++) { this.$set(this.answers, i, 0) } }, computed: { // 当前页显示的两个题目
currentQuestions() { const startIndex = (this.currentPage - 1) * 2; return [ this.questions[startIndex], this.questions[startIndex + 1] ]; }, totalPages() { return Math.ceil(this.questions.length / 2); }, answeredCount() { return Object.keys(this.answers).filter( key => this.answers[key] !== 0 ).length }, completionRate() { return (this.answeredCount / this.totalQuestions) * 100; }, progress() { return this.completionRate; }, // 当前显示的题目ID
currentQuestionIds() { return this.currentQuestions.map(q => q ? q.id : null).filter(Boolean); } }, methods: { // 从后端获取题目
async fetchQuestions() { try { const response = await axios.post('http://192.168.40.41:8000/api/knowledge/questions'); this.questions = response.data.data.list; } catch (error) { console.error('获取题目失败:', error); } }, // // 默认题目数据(备用)- 生成50个题目
// getDefaultQuestions() {
// // 这里创建一个示例数组,您可以根据实际需要替换为真实数据
// const defaultQuestions = [];
// for (let i = 1; i <= 50; i++) {
// defaultQuestions.push({
// id: i,
// stem: `这是第${i}个股票知识测试题目`,
// A: `选项A - 第${i}题`,
// B: `选项B - 第${i}题`,
// C: `选项C - 第${i}题`,
// D: `选项D - 第${i}题`
// });
// }
// return defaultQuestions;
// },
// 获取题目在当前试卷中的序号
getQuestionIndex(questionId) { // 查找题目在questions数组中的位置并返回序号
const index = this.questions.findIndex(q => q.id === questionId); return index !== -1 ? index + 1 : questionId; }, // 显示确认弹框
showConfirmDialog() { this.showDialog = true; }, // 关闭弹框
closeDialog() { this.showDialog = false; }, // 确认退出
confirmExit() { this.showDialog = false; // 跳转到首页
this.$router.push('/'); }, //显示警告弹框
closeTeamPrompt() { this.showTeam = true; }, closeSubmit() { this.showTeam = false; }, // submitAnswers() {
// this.showTeam = false;
// },
// 获取题目的答案
getAnswer(questionId) { return this.answers[questionId] || ''; }, // 设置题目的答案
setAnswer(questionId, answer) { this.$set(this.answers, questionId, answer); }, // 获取题目状态样式
getQuestionStatusClass(questionNumber) { const isAnswered = !!this.answers[questionNumber]; if (isAnswered) { return 'answered'; } else { return 'unanswered'; } }, // 修改导航页面切换方法
changeNavPage(newPage) { this.page = newPage; }, // 修改题目页面切换方法
changePage(newPage) { if (newPage >= 1 && newPage <= this.totalPages) { this.currentPage = newPage; // 自动更新导航页(每25题一页)
const startQuestion = (newPage - 1) * 2 + 1; this.page = Math.ceil(startQuestion / 25); } }, // 修改通过题目跳转的方法
goToPageByQuestion(questionNumber) { const page = Math.ceil(questionNumber / 2); this.changePage(page); // 自动切换到对应的导航页
this.page = Math.ceil(questionNumber / 25); }, // 上一页
prevPage() { if (this.currentPage > 1) { this.currentPage--; } if (this.currentPage < 13) { this.page=1; } }, // 下一页
nextPage() { if (this.currentPage < this.totalPages) { this.currentPage++; } if(this.currentPage > 13){ this.page=2; } }, // 更新倒计时
updateCountdown() { const now = new Date(); const elapsedMinutes = (now - this.startTime) / (1000 * 60); const remainingMinutes = Math.max(0, this.countdownMinutes - elapsedMinutes); const minutes = Math.floor(remainingMinutes).toString().padStart(2, '0'); const seconds = Math.floor((remainingMinutes % 1) * 60).toString().padStart(2, '0'); this.countdown = `${minutes}:${seconds}`; // 如果倒计时结束,自动提交试卷
if (remainingMinutes <= 0 && !this.isSubmitted) { this.isSubmitted = true; this.submitAnswers(); } }, // 提交答案到后端
async submitAnswers() { try { if (Object.keys(this.answers).length === 0) { this.showTeam=false; return; } // 将答案格式转换为要求的格式
const formattedAnswers = Object.keys(this.answers).map(questionId => ({ questionId: parseInt(questionId), userAnswer: this.answers[questionId] })); const savedData = JSON.parse(localStorage.getItem('submissionData')); console.log('第一个,存储的数据:', localStorage.getItem('submissionData')); const submission = { jwcode: savedData.jwcode, answers: formattedAnswers }; console.log(submission); // 调用后端提交接口
const response = await axios.post('http://192.168.40.41:8000/api/knowledge/submit', submission); console.log(submission); // 跳转到结果页面
this.$router.push({ name: 'ResultView', query: { score: response.data.data.score || 80, total: this.totalQuestions, timeUsed: this.getTimeUsed() } }); } catch (error) { console.error('提交试卷失败:', error); alert('提交失败,请重试'); this.isSubmitted = false; } }, // 获取已用时间
getTimeUsed() { const now = new Date(); const elapsedMinutes = (now - this.startTime) / (1000 * 60); return Math.round(elapsedMinutes); } }, async mounted() { // 从后端获取题目
await this.fetchQuestions(); // 设置定时器,每秒更新一次倒计时
this.timer = setInterval(() => { this.updateCountdown(); }, 1000); }, beforeDestroy() { if (this.timer) { clearInterval(this.timer); } }};</script>
<style scoped>.questions-container { display: flex; flex-direction: column; gap: 30px;}.ret{ width: 30px; height: 50px; float: left;}.page-info { color: #e5e7eb; font-weight: bold; padding: 0 20px;}
.question-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin: auto 30px;}
.question-number { width: 60px; height: 50px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-weight: bold; cursor: pointer; position: relative;}
.question-number.unanswered { background-color: #374151; border: 1px solid #4b5563; color: #e5e7eb;}
.question-number.answered { background-color: #10b981; border: 1px solid #059669; color: white;}
/* .question-number.current { background-color: #3b82f6; border: 2px solid #2563eb; color: white; transform: scale(1.1);} */
.question-text { z-index: 1;}
.pagination-btn.active { background-color: #2563eb; transform: scale(1.05);}.pagination-btn:disabled{ background-color: #4b5563; color: #e5e7eb;}
* { margin: 0; padding: 0; box-sizing: border-box;}body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f7fa;}.home { min-height: 100vh; width: 100%; background-color: #24293c; overflow: auto; padding: 20px;}.top { background: linear-gradient(135deg, #0c4a6e 100%); color: white; padding: 30px; margin-bottom: 30px; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); position: relative; overflow: hidden;}.top h1 { font-size: 2.2em; margin-bottom: 10px; background: none;}.top p { font-size: 1.1em; opacity: 0.9; background: none;}.block { background: rgba(139, 141, 145, 0.7); width: 90%; height: 10px; border-radius: 5px; margin: 0 auto 30px; overflow: hidden; position: relative; box-shadow: inset 0 0 10px rgba(0,0,0,0.3);}.schedule { background: linear-gradient(90deg, #0ea5e9 0%, #38bdf8 100%); height: 100%; width: 15%; transition: width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); position: relative;}.content { float: left; width: calc(65% - 20px); min-height: 920px; border: #274779 solid 2px; border-radius: 10px; color: #f1f5f9; padding: 20px 40px 50px; margin-right: 20px; background-color: #2a3147;}.question-card { border: #274779 solid 2px; border-radius: 10px; padding: 20px 40px; background-color: #2f374d;}.question-header { margin-bottom: 20px;}.question { font-size: 1.3em; line-height: 1.8; margin-bottom: 25px; color: #f1f5f9; font-weight: 500;}.options { display: flex; flex-direction: column; gap: 15px;}.option { border: #183954 solid 2px; border-radius: 10px; padding: 12px 15px; display: flex; align-items: center; cursor: pointer; transition: all 0.3s ease; background-color: #374151;}.option:hover { border-color: #3b82f6; background-color: #4b5563;}.option input { margin-right: 15px; width: 18px; height: 18px;}.option .label { font-size: 1.1em; color: #e5e7eb;}.option.selected { border-color: #3b82f6; background-color: rgba(59, 130, 246, 0.2);}.right { float: right; width: calc(35% - 20px); color: #f1f5f9; padding: 20px;}.time-module { border: #274779 solid 2px; border-radius: 30px; padding: 15px; margin-bottom: 20px; background-color: #2f374d;}.question-nav { border: #274779 solid 2px; border-radius: 30px; padding: 15px 0 15px 19px ; margin-bottom: 20px; background-color: #2f374d;}.statistics { height: 150px; border: #274779 solid 2px; border-radius: 30px; padding: 15px; margin-bottom: 20px; background-color: #2f374d;}.time-module h3, .question-nav h3, .statistics h3 { margin-bottom: 15px; color: #f1f5f9; font-size: 1.2em; border-bottom: 2px solid #274779; padding-bottom: 10px;}.countdown { font-size: 1.5em; color: #f59e0b; text-align: center;}.pagination { display: flex; /* justify-content: center; */ margin: 30px 148px 5px; gap: 20px;}.pagination-btn { padding: 8px; border-radius: 8px; border: none; background-color: #3b82f6; color: white; font-weight: bold; cursor: pointer; transition: all 0.3s ease; margin: auto 20px;}.pagination-btn:hover { background-color: #2563eb;}.pagination-btn:disabled { background-color: #4b5563;}.nav-buttons { display: flex; justify-content: space-between; align-items: center; margin-top: 30px;}.nav-btn { padding: 10px 20px; border-radius: 8px; border: none; background-color: #3b82f6; color: white; font-weight: bold; display: flex; align-items: center; gap: 5px;}.nav-btn:hover { background-color: #2563eb;}.nav-btn.prev { background-color: #3b82f6;;}.nav-btn.prev:hover { background-color: #2563eb;}.nav-btn:disabled { background-color: #4b5563; cursor: not-allowed;}.statistics-item { display: flex; font-size: larger; justify-content: space-between; margin-top: 30px; padding-top: 10px; padding-bottom: 2px; border-bottom: 2px solid #274779;}.statistics-label { color: #e5e7eb;}.statistics-value { font-weight: bold; color: #3b82f6;}.submit-btn { width: 100%; padding: 15px; border-radius: 10px; border: none; background-color: #10b981; color: white; font-size: 1.2em; font-weight: bold; cursor: pointer; transition: all 0.3s ease; margin-top: 20px; display: block; text-align: center; text-decoration: none;}.submit-btn:hover { background-color: #059669;}.clearfix::after { content: ""; clear: both; display: table;}
.ret-container { float: left; cursor: pointer; transition: transform 0.3s ease;}
.ret-container:hover { transform: scale(1.1);}.confirm-dialog { position: fixed; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; z-index: 1000;}
.dialog-content { position: relative; background: white; padding: 30px; border-radius: 15px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); max-width: 400px; width: 90%; text-align: center; z-index: 1001;}.dialog-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(5px);}
.dialog-content h3 { color: #333; margin-bottom: 15px; font-size: 1.4em;}
.dialog-content p { color: #666; margin-bottom: 25px; font-size: 1.1em; line-height: 1.5;}
.dialog-buttons { display: flex; gap: 15px; justify-content: center;}
.cancel-btn, .confirm-btn { padding: 12px 30px; border: none; border-radius: 8px; font-size: 1em; font-weight: bold; cursor: pointer; transition: all 0.3s ease; min-width: 100px;}
.cancel-btn { background: #f1f1f1; color: #666;}
.cancel-btn:hover { background: #e0e0e0;}
.confirm-btn { background: #ff4757; color: white;}
.confirm-btn:hover { background: #ff3742; transform: translateY(-2px);}</style>
|