前端具体项目实践·通过传统项目学习javascript的使用

116 阅读4分钟

概要

本文章结合了本人的实战项目,主要目的是介绍javascript在以html5,css,js为框架的传统项目中的具体应用方法

项目介绍

带UI的小初高数学学习软件

用户:

小学、初中和高中学生。

功能:

1、所有功能通过图形化界面操作,可以是桌面应用,可以是网站(编程语言和技术不限);

2、用户注册功能。用户提供邮箱,点击注册将收到一个注册码,用户可使用该注册码完成注册;

3、用户完成注册后,界面提示设置密码,用户输入两次密码匹配后设置密码成功。密码6-10位,必须含大小写字母和数字。用户在登录状态下可修改密码,输入正确的原密码,再输入两次相同的新密码后修改密码成功;

4、密码设置成功后,跳转到选择界面,界面显示小学、初中和高中三个选项,用户点击其中之一后,提示用户输入需要生成的题目数量;

5、用户输入题目数量后,生成一张试卷(同一张卷子不能有相同题目,题目全部为选择题),界面显示第一题的题干和四个选项,用户选择四个选项中的一个后提交,界面显示第二题,...,直至最后一题;

6、最后一题提交后,界面显示分数,分数根据答对的百分比计算;

7、用户在分数界面可选择退出或继续做题;

8、小初高数学题目要求见附表。

附表:小学、初中、高中题目要求

 小学初中高中  
难度要求+,-,*./平方,开根号sin,cos,tan  
备注只能有+,-,*./和()题目中至少有一个平方或开根号的运算符题目中至少有一个sin,cos或tan的运算符

实现效果

image.png

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

我们可以发现prevButtondisabled属性值在currentQuestionIndex === 0时会被设置为1,也就是实现了当currentQuestionIndex === 0prevButton不可见的效果。

同样的当currentQuestionIndex取到不同值的时候,nextButton中的显示内容和是否可见都会发生变化

再学习一下JS如何关联HTML

上面我们对prevButtonnextButton的属性做了设置,那么这两个量究竟关联HTML中的那个元素呢?

    const prevButton = document.getElementById('prevButton');
    const nextButton = document.getElementById('nextButton');

从上面的代码我们可以知道 常量prevButton关联HTML中属性id的值为prevButton的元素而常量nextButton关联HTML中属性id的值为nextButton的元素