概要
本文章结合了本人的实战项目,主要目的是介绍javascript在以html5,css,js为框架的传统项目中的具体应用方法
项目介绍
带UI的小初高数学学习软件
用户:
小学、初中和高中学生。
功能:
1、所有功能通过图形化界面操作,可以是桌面应用,可以是网站(编程语言和技术不限);
2、用户注册功能。用户提供邮箱,点击注册将收到一个注册码,用户可使用该注册码完成注册;
3、用户完成注册后,界面提示设置密码,用户输入两次密码匹配后设置密码成功。密码6-10位,必须含大小写字母和数字。用户在登录状态下可修改密码,输入正确的原密码,再输入两次相同的新密码后修改密码成功;
4、密码设置成功后,跳转到选择界面,界面显示小学、初中和高中三个选项,用户点击其中之一后,提示用户输入需要生成的题目数量;
5、用户输入题目数量后,生成一张试卷(同一张卷子不能有相同题目,题目全部为选择题),界面显示第一题的题干和四个选项,用户选择四个选项中的一个后提交,界面显示第二题,...,直至最后一题;
6、最后一题提交后,界面显示分数,分数根据答对的百分比计算;
7、用户在分数界面可选择退出或继续做题;
8、小初高数学题目要求见附表。
附表:小学、初中、高中题目要求
| 小学 | 初中 | 高中 | |
|---|---|---|---|
| 难度要求 | +,-,*./ | 平方,开根号 | sin,cos,tan |
| 备注 | 只能有+,-,*./和() | 题目中至少有一个平方或开根号的运算符 | 题目中至少有一个sin,cos或tan的运算符 |
实现效果
Html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小初高数学学习软件</title>
<link rel="stylesheet" href="./question.css">
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
</head>
<body>
<div class="container" id="answerCard">
<div class="question" id="question">$$a * b / c * sin(d)$$</div>
<div class="progress" id="progress">当前题目: 1/5</div>
<div class="options">
<div class="option" id="option0">1</div>
<div class="option" id="option1">2</div>
<div class="option" id="option2">3</div>
<div class="option" id="option3">4</div>
</div>
<div class="buttons">
<button class="button" id="prevButton" disabled>上一题</button>
<button class="button" id="nextButton">下一题</button>
</div>
<div>
<div class="questionList" id="questionList"></div>
<!-- 题目序号将动态生成 -->
</div>
</div>
</div>
<script type="module" src="./question.js"></script>
</body>
</html>
JS代码
import { getquestion } from "../request/question.js";
var params = new URLSearchParams(window.location.search);
const username = params.get('username');
const grade = params.get('grade');
const quantity = params.get('quantity');
const questiondata = {
type: grade,
nums: quantity
};
var ChosenOptions = [];
var questions = [];
window.onload = function() {
const user = JSON.parse(localStorage.getItem(username)) || null;
if (!user||!user.data.token) {
alert('请先登录');
location.href = '../loginpage/login.html';
}
};
for (let i = 0; i < 50; i++) {
ChosenOptions.push({
choice: -1,
done: false
});
}
getquestion(questiondata)
.then((response) => {
if (response.data.success) {
console.log(response.data.data)
console.log('获取题目成功');
questions = Array.from(response.data.data.questions);
let preQuestionIndex = 0;
let currentQuestionIndex = 0;
let selectedOptionIndex = null;
const questionEl = document.getElementById('question');
const progressEl = document.getElementById('progress');
const optionEls = [...document.querySelectorAll('.option')];
const prevButton = document.getElementById('prevButton');
const nextButton = document.getElementById('nextButton');
generateAnswerCard();
loadQuestion(0);
prevButton.addEventListener('click', () => {
if (currentQuestionIndex > 0) {
preQuestionIndex= currentQuestionIndex;
currentQuestionIndex--;
updateAnswerCard(preQuestionIndex,currentQuestionIndex);
loadQuestion(currentQuestionIndex);
}
});
nextButton.addEventListener('click', () => {
if (currentQuestionIndex < questions.length - 1) {
preQuestionIndex= currentQuestionIndex;
currentQuestionIndex++;
updateAnswerCard(preQuestionIndex,currentQuestionIndex);
loadQuestion(currentQuestionIndex);
} else {
// 当前题目是最后一题时,提交答案
submitAnswers();
}
});
// 动态生成答题卡
function generateAnswerCard() {
const questionListEl = document.getElementById('questionList');
questionListEl.innerHTML = ''; // 清空现有的答题卡
for (let i = 0; i < quantity; i++) {
const questionIndexEl = document.createElement('div');
questionIndexEl.className = 'questionIndex';
questionIndexEl.textContent = i + 1;
questionIndexEl.onclick = function() { loadQuestion(i); };
questionListEl.appendChild(questionIndexEl);
}
}
// 更新答题卡状态
function updateAnswerCard(preQuestionIndex,currentQuestionIndex) {
const questionIndices = document.querySelectorAll('.questionIndex');
questionIndices.forEach((indexEl, index) => {
if (index === preQuestionIndex) {
indexEl.classList.remove('active');
}
if (index === currentQuestionIndex) {
indexEl.classList.add('active');
}
if (ChosenOptions[index].done) {
indexEl.classList.add('selected');
} else {
indexEl.classList.remove('selected');
}
});
}
// 修改 loadQuestion 函数
function loadQuestion(index) {
if (index >= 0 && index < questions.length) {
preQuestionIndex=currentQuestionIndex
currentQuestionIndex = index;
loadQuestionView();
updateAnswerCard(preQuestionIndex,currentQuestionIndex);
// 确保选中的题目序号在视窗中可见
document.querySelectorAll('.questionIndex')[currentQuestionIndex].scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
// 修改 loadQuestionView 函数
function loadQuestionView() {
const currentQuestion = questions[currentQuestionIndex];
var random = Math.floor(Math.random() * 100) + 1; // 生成一个1到100之间的随机
// 使用 MathJax 格式显示数学公式
var str = currentQuestion.content || '题目数据缺失';
questionEl.innerHTML = toMarkdown(str);
progressEl.textContent = `当前题目: ${currentQuestionIndex + 1}/${questions.length}`;
optionEls.forEach((optionEl, i) => {
if (currentQuestion.options && currentQuestion.options[i] && currentQuestion.options[i].content) {
optionEl.textContent = currentQuestion.options[i].content;
optionEl.classList.remove('selected');
if (ChosenOptions[currentQuestionIndex].choice === i && !ChosenOptions[currentQuestionIndex].done) {
optionEl.classList.add('selected');
selectedOptionIndex = i;
} else {
selectedOptionIndex = null;
}
} else {
optionEl.textContent = random.toString();
}
});
if (ChosenOptions[currentQuestionIndex].done) {
const selectedOptionEl = optionEls[ChosenOptions[currentQuestionIndex].choice];
if (selectedOptionEl) {
selectedOptionEl.classList.add('selected');
selectedOptionIndex = ChosenOptions[currentQuestionIndex].choice;
}
}
updateButtons();
MathJax.typeset();
}
function updateButtons() {
prevButton.disabled = currentQuestionIndex === 0;
if (currentQuestionIndex === questions.length - 1) {
nextButton.textContent = '交卷';
nextButton.disabled = false;
} else {
nextButton.textContent = '下一题';
nextButton.disabled = false;
}
}
function submitAnswers() {
// 提交答案的逻辑
console.log('提交答案');
var correctnum = 0;
for (let i = 0; i < questions.length; i++) {
if (ChosenOptions[i].done && questions[i].options[ChosenOptions[i].choice].isCorrect) {
correctnum++;
}
}
var score = (100 * correctnum / questions.length).toFixed(2);
location.href = "../scorepage/score.html?username=" + encodeURIComponent(username)
+ "&score=" + encodeURIComponent(score);
nextButton.disabled = true;
}
optionEls.forEach((optionEl, i) => {
optionEl.addEventListener('click', () => {
if (selectedOptionIndex !== null) {
optionEls[selectedOptionIndex].classList.remove('selected');
}
optionEl.classList.add('selected');
selectedOptionIndex = i;
ChosenOptions[currentQuestionIndex].done = true;
ChosenOptions[currentQuestionIndex].choice = i;
});
});
loadQuestion(currentQuestionIndex);
} else {
console.log('获取题目失败');
}
})
.catch(error => {
console.log(error);
});
function toMarkdown(question) {
// 替换除法符号为 \div
let expression = question.replace(/\//g, '\\div');
// 替换开方符号为 sqrt,并正确添加括号
expression = expression.replace(/\u221A(\d+)/g, '\\sqrt{$1}');
// 替换三角函数符号为 LaTeX 格式
expression = expression.replace(/sin/g, '\\sin');
expression = expression.replace(/cos/g, '\\cos');
expression = expression.replace(/tan/g, '\\tan');
// 添加适当的括号
expression = expression.replace(/\(([^()]+)\)/g, '\\left($1\\right)');
// 创建 Markdown 格式的表达式
return '$$' + expression + '$$';
}
着重介绍一下AXIOS请求的使用方法
我们可以看到代码中的请求部分是getquestion函数下多达上百行的代码,但其实请求的主体部分仅有
getquestion(questiondata)
.then((response) => {
if (response.data.success) {
console.log('获取题目失败');
//成功获取数据的逻辑
} else {
console.log('获取题目失败');
//获取数据失败的逻辑
}
})
.catch(error => {
console.log(error);
//请求发送失败的逻辑
});
其中getquestion是我封装的get请求函数其声明如下
import request from "./request.js";
export function getquestion(data){
return request({
method :'POST',
url :'/questions/question',
data,
})
}
其中request是我创建的axios请求实例,其声明如下
import axios from '../node_modules/axios/dist/esm/axios.js'
// 创建axios实例
const request = axios.create({
baseURL: 'http://localhost:8081', // API基础路径
});
export default request;
上面的封装方法也是使用三件套创建前端项目时常用的axios请求封装方法,可供大家学习。
从上面的代码不难发现,我们使用了axios向后端发送了POST请求,当后端收到我们的请求并发送反馈,这样的反馈数据被前端收到后,就能从反馈中拿到我们想要的数据并做下一步的处理。
JS代码对HTML元素属性的变动
从上面的这一段代码中
function updateButtons() {
prevButton.disabled = currentQuestionIndex === 0;
if (currentQuestionIndex === questions.length - 1) {
nextButton.textContent = '交卷';
nextButton.disabled = false;
} else {
nextButton.textContent = '下一题';
nextButton.disabled = false;
}
}
我们可以发现prevButton的disabled属性值在currentQuestionIndex === 0时会被设置为1,也就是实现了当currentQuestionIndex === 0时prevButton不可见的效果。
同样的当currentQuestionIndex取到不同值的时候,nextButton中的显示内容和是否可见都会发生变化
再学习一下JS如何关联HTML
上面我们对prevButton和nextButton的属性做了设置,那么这两个量究竟关联HTML中的那个元素呢?
const prevButton = document.getElementById('prevButton');
const nextButton = document.getElementById('nextButton');
从上面的代码我们可以知道
常量prevButton关联HTML中属性id的值为prevButton的元素而常量nextButton关联HTML中属性id的值为nextButton的元素