有个朋友要考2024年上海三类人员安管考试B证,他手里是一个word试题文档。需要打印出来在背,很麻烦。但是由于是太专业太新的试题,在网上没有类似的小程序。所以我利用两天时间在现有小程序(AI识图生诗词文案)基础上增加了一个刷题页面。
先给自己的小程序曝光一下,欢迎各位大佬光临!扫码或搜索AI识图生诗词文案
书接上文,上一篇写了django实现刷题题库及访问视图的功能,前端给后端传递题号,后端给前端返回试题、选项、答案等信息。
小程序页功能有显示题目、显示选项、用户回答完后显示答案、显示正确或错误的图标、记录用户正确题数量和错误题数量、翻页和滑动翻页、跳转题目、记录用户上次访问的题号等功能。
先上图片看看效果
代码写的如果有问题,也请各位大佬指正。谢谢!
下面写小程序端如何实现(我使用vscode和uniapp写的小程序)。
<template>
<view class="page-content" @touchstart="onTouchStart" @touchend="onTouchEnd" style="height: 100%;">
<!-- 显示题目 -->
<uni-card :is-shadow="false" style="background-color: transparent;">
<text class="uni-body">{{ type }}.第{{ questionid }}题,共{{ maxquestions }}题</text>
</uni-card>
<uni-card class="question">
<text>{{ question }}</text>
</uni-card>
<!-- 正确或错误的邮戳 -->
<view class="question">
<view v-if="hasAnswered" class="stamp-container">
<img :src="stampImageUrl" class="stamp-image" />
</view>
</view>
<!-- 显示选项,选项前加上字母 -->
<uni-card>
<uni-data-checkbox multiple mode="list" v-model="selectedOptions" :localdata="formattedOptionsWithLetters"
:disabled="hasAnswered"></uni-data-checkbox>
</uni-card>
<!-- 提交按钮 -->
<view class="submit">
<button @click="submitAnswer" :disabled="hasAnswered">提交</button>
</view>
<!-- 显示我的答案和正确答案 -->
<view class="answer" v-if="hasAnswered">
<uni-row class="demo-uni-row" :width="nvueWidth">
<uni-col :span="12">
<view class="demo-uni-col correct">正确答案:{{ correctAnswersLetters }}</view>
</uni-col>
<uni-col :span="12">
<view class="demo-uni-col">我的答案:{{ selectedOptionsLetters.join(', ') }}</view>
</uni-col>
</uni-row>
</view>
<!-- 显示正确题目和错误题目的数量 -->
<view class="answer">
<uni-row class="demo-uni-row" :width="nvueWidth">
<uni-col :span="12">
<view class="demo-uni-col dark">正确题目:{{ correctCount }}</view>
</uni-col>
<uni-col :span="12">
<view class="demo-uni-col light">错误题目:{{ wrongCount }}</view>
</uni-col>
</uni-row>
</view>
<!-- 添加左右翻页按钮 -->
<view class="fixed-pagination">
<button @click="changeQuestion(-1)">上一题</button>
<input type="number" v-model="inputQuestionId" placeholder="输入页码" />
<button @click="goToQuestion">跳转</button>
<button @click="changeQuestion(1)">下一题</button>
</view>
</view>
</template>
<script>
import {
mapState, mapMutations
} from 'vuex'
export default {
data() {
return {
questionid: 1,
type: '',
question: '',
options: [],
answer: [],
maxquestions: 800,//最大题目数量
selectedOptions: [], // 用户选择的选项
answerkey: '', // 正确答案的索引
selectedOptionLetter: '', // 用户选择的选项字母
correctCount: 0, // 正确题目数量
wrongCount: 0, // 错误题目数量
touchStartX: 0, // 记录触摸开始的X坐标
touchEndX: 0, // 记录触摸结束的X坐标
inputQuestionId: '', // 用户输入的题目编号
hasAnswered: false, // 用户是否已经回答了当前题目
isCorrect: false, // 用户的回答是否正确
stampImageUrl: '', // 用于显示邮戳的图片路径
}
},
computed: {
...mapState(['isAuth',]),
// 格式化选项以符合uni-data-checkbox的数据格式,并添加字母
formattedOptionsWithLetters() {
return this.options.map((option, index) => ({ value: index, text: `${this.getOptionLetter(index)}: ${option}` }));
},
// 获取用户选择的选项的字母表示
selectedOptionsLetters() {
return this.selectedOptions.map(index => this.getOptionLetter(index));
},
// 获取正确答案的字母表示
correctAnswersLetters() {
if (!this.answer || this.answer.length === 0) {
return ''; // 如果答案为空,返回空字符串
}
// 将正确答案的文本转换为索引
const correctAnswerIndexes = this.answer.map(answerText =>
this.options.findIndex(option => option === answerText)
);
// 过滤掉未找到的索引(即 findIndex 返回 -1 的情况)
const filteredCorrectAnswerIndexes = correctAnswerIndexes.filter(index => index !== -1);
// 将索引转换为字母表示
return filteredCorrectAnswerIndexes.map(index => this.getOptionLetter(index)).join(', ');
},
},
mounted() {
this.getQuestion();
},
methods: {
...mapMutations(['saveLoginStatus', 'saveUserInfo']),
// 首先获取上次访问的试题ID
getLastVisitedQuestion() {
const token = uni.getStorageSync('token');
const header = {
'Authorization': `Bearer ${token}`,
};
this.$api.create.getlastvisitedquestion({}, { header }).then(response => {
// 成功获取后,取得singlequestion的questionid,更新this.questionid
this.questionid = parseInt(response.data.multiplequestion.questionid);
// 请求新的试题
this.getQuestion();
}).catch(error => {
// 如果获取失败,可能是用户第一次使用,直接请求第一题
console.error(error);
this.questionid = 1;
this.getQuestion();
});
},
// 获取试题
getQuestion() {
const token = uni.getStorageSync('token');
const data = {
questionid: this.questionid,
};
const header = {
'Authorization': `Bearer ${token}`,
};
this.$api.create.getmultipleQuestions(data, { header }).then(response => {
this.question = response.data.question;
this.options = response.data.options;
this.answer = response.data.answer; // 确保 answer 是字符串
this.type = response.data.type;
this.maxquestions = response.data.total_questions;
// 成功获取试题后,保存新的questionid到后端
this.saveLastVisitedQuestion(this.questionid);
// 重置用户选择和显示答案的状态
this.selectedOptions = [];
this.isCorrect = false;
this.hasAnswered = false;
this.stampImageUrl = ''; // 重置邮戳图片路径
}).catch(error => {
// 处理错误
console.error(error);
uni.showToast({
title: '没有这道题哦!',
icon: 'none',
duration: 2000
});
});
},
// 保存最新访问的试题ID到后端
saveLastVisitedQuestion(questionId) {
const token = uni.getStorageSync('token');
const data = {
type: 'multiplequestion', // 根据你的需求调整
questionid: questionId,
};
const header = {
'Authorization': `Bearer ${token}`,
};
this.$api.create.savelastvisitedquestion(data, { header }).then(response => {
// console.log('题号保存成功', response);
}).catch(error => {
console.error('题号保存失败', error);
});
},
// 获取选项的字母
getOptionLetter(key) {
const letters = ['A', 'B', 'C', 'D', 'E'];
return letters[key];
},
// 提交答案的逻辑
submitAnswer() {
this.hasAnswered = true;
// 将正确答案的文本转换为索引
const correctAnswerIndexes = this.answer.map(answerText =>
this.options.findIndex(option => option === answerText)
);
// 转换用户答案和正确答案为基于索引的字符串
const userAnswerSorted = this.selectedOptions.map(Number).sort().join(',');
const correctAnswerSorted = correctAnswerIndexes.sort().join(',');
if (userAnswerSorted === correctAnswerSorted) {
this.correctCount++;
this.isCorrect = true; // 设置回答正确
this.stampImageUrl = '/static/zhengque.svg';
} else {
this.wrongCount++;
//添加错题
this.addwrongBook(this.questionid);
this.isCorrect = false; // 设置回答错误
this.stampImageUrl = '/static/cuowu.svg';
}
},
// 添加错题,其他题型需要修改type
addwrongBook(questionid, type) {
const token = uni.getStorageSync('token');
const data = {
questionid: questionid,
type: 'multiplequestion',
};
const header = {
'Authorization': `Bearer ${token}`,
};
this.$api.create.addwrongBook(data, { header }).then(response => {
// console.log(response.data);
}).catch(error => {
console.error(error);
});
},
// 登录
async wechatLogin() {
try {
const loginRes = await uni.login({ provider: 'weixin' });
const userInfoRes = await uni.getUserInfo({ provider: 'weixin' });
const userInfo = userInfoRes.userInfo;
this.code = loginRes[1].code;
this.doLogin();
} catch (err) {
console.log('微信登录失败', err);
}
},
async doLogin() {
try {
const response = await this.$api.user.login({ code: this.code });
if (response.statusCode === 200) {
console.log('登录成功!');
// 保存用户信息和token
this.saveLoginStatus(response.data);
}
} catch (error) {
console.log('登录失败', error);
}
},
// 触摸开始事件
onTouchStart(event) {
this.touchStartX = event.changedTouches[0].clientX;
},
// 触摸结束事件
onTouchEnd(event) {
this.touchEndX = event.changedTouches[0].clientX;
this.handleSwipe();
},
// 处理滑动事件
handleSwipe() {
const threshold = 50; // 设定滑动阈值
const deltaX = this.touchEndX - this.touchStartX;
if (deltaX > threshold) {
// 向右滑动,显示上一题
this.changeQuestion(-1);
} else if (deltaX < -threshold) {
// 向左滑动,显示下一题
this.changeQuestion(1);
}
},
// 修改后的changeQuestion方法
changeQuestion(direction) {
const newQuestionId = this.questionid + direction;
const maxquestions = this.maxquestions;
if (newQuestionId < 1) {
uni.showToast({
title: '已经是第一题了',
icon: 'none'
});
return;
}
if (newQuestionId > maxquestions) {
uni.showToast({
title: '已经是最后一题了',
icon: 'none'
});
return;
}
// 更新题目编号
this.questionid = newQuestionId;
// 获取新题目
this.getQuestion();
},
// 跳转到指定页面
goToQuestion() {
const questionId = parseInt(this.inputQuestionId, 10);
const maxquestions = this.maxquestions;
if (isNaN(questionId) || questionId < 1) {
uni.showToast({
title: '请输入有效的题目编号',
icon: 'none'
});
return;
}
if (questionId > maxquestions) {
uni.showToast({
title: `超出范围,共${maxquestions}题`,
icon: 'none'
});
return;
}
this.questionid = questionId; // 更新题目编号
this.selectedOption = null;
this.hasAnswered = false;
this.isCorrect = false;
this.stampImageUrl = ''; // 重置邮戳图片路径
this.getQuestion(); // 获取新题目
this.inputQuestionId = ''; // 清空输入框
},
},
//加载页面时,执行getData函数
onLoad() {
this.getLastVisitedQuestion()
},
}
</script>