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.

856 lines
20 KiB

<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>