美文网首页React.js
使用Sqlite3+Express.js+React实现在线答题

使用Sqlite3+Express.js+React实现在线答题

作者: fanzhh | 来源:发表于2018-01-31 14:43 被阅读69次

    使用Sqlite3+Express.js+React实现在线答题(上)中,我们将题目数据从word文件转为txt格式并导入到sqlite3中,使用Express.js建立了json数据API接口。本篇文章我们将使用ReactJS建立前端。

    建立React项目

    首先安装create-react-app,如果你已安装,请略过。

    $ npm install -g create-react-app
    

    然后新建项目,我们项目的名字为frontend

    $ create-react-app frontend
    

    安装过程需要几分钟:


    2018-01-31 13-49-01屏幕截图.png

    从服务器获取json数据我们需要用jquery,安装

    npm install query
    

    界面设计

    我们预想的操作界面是这样的(原谅我粗狂的画风^^):


    sketch-1517379201030.png

    每道题在一个<div>中,上面是题目描述部分,下面是选择框。

    修改App.js

    修改frontend/src/App.js文件。

    导入

    import React, { Component } from 'react';
    import $ from 'jquery';
    import './App.css';
    

    题目描述部件

    class DescriptionBar extends Component {
      render() {
        return <p>{this.props.description}</p>
      }
    }
    

    答案选择部件

    class SelectionsBar extends Component {
      constructor(props) {
        super(props);
        this.handleChange=this.handleChange.bind(this); // 为事件绑定this
      }
      handleChange(event) {
        this.props.onChange(event) // 答案选择触发事件传递给props中的onChange
      }
      render() {
        var selection_type = this.props.remark === '1' ? 'checkbox' : 'radio'; // 多选题使用checkbox,单选题使用radio,注意判断题也是单选
        var selection_name = this.props.reamrk === '1' ? 'choose_mul' : 'choose_one'
        return (
          <form>
            <fieldset>
              <input name={selection_name} type={selection_type} id={this.props.id+'_A'} value='A' onChange={this.handleChange} /><label htmlFor={this.props.id+'_A'}>{this.props.answerA}</label><br /> // 每道题至少两个选择项A和B
              <input name={selection_name}  type={selection_type} id={this.props.id+'_B'} value='B'  onChange={this.handleChange} /><label htmlFor={this.props.id+'_B'}>{this.props.answerB}</label><br /> // 控件ID设为题目的ID+该控件的符号(A?B?C?...)
              {this.props.answerC === '' ? '' : (<span><input name={selection_name}  type={selection_type} id={this.props.id+'_C'} value='C'  onChange={this.handleChange} /><label htmlFor={this.props.id+'_C'}>{this.props.answerC}</label><br /></span>)} // C以下根据内容不为空则显示
              {this.props.answerD === '' ? '' : (<span><input name={selection_name}  type={selection_type} id={this.props.id+'_D'} value='D'  onChange={this.handleChange} /><label htmlFor={this.props.id+'_D'}>{this.props.answerD}</label><br /></span>)}
              {this.props.answerE === '' ? '' : (<span><input name={selection_name}  type={selection_type} id={this.props.id+'_E'} value='E'  onChange={this.handleChange} /><label htmlFor={this.props.id+'_E'}>{this.props.answerE}</label><br /></span>)}
            </fieldset>
          </form>
        )
      }
    }
    

    提交部件

    class SubmitBar extends Component {
      constructor(props) {
        super(props);
        this.onClick=this.onClick.bind(this);
      }
      onClick(event) {
        this.props.onClick(event) // 提交事件传递给父部件
      }
      render() {
        return(
          <form>
            <button type="submit" onClick={this.onClick} >{this.props.answered?'再做一遍错题':'检查'}</button> // 根据父控件状态判断现在是检查之前还是之后,相应改变按钮文字
          </form>
        )
      }
    }
    

    问题部件

    问题部件是题目描述和答案选择的父部件。

    class QuestionBar extends Component {
      render() {
        return (
          <div>
            <DescriptionBar description={this.props.question.description} /> // 题目描述部件
            <SelectionsBar // 选择部件
                id={this.props.question.id} // 传递属性值
                answer={this.props.question.answer}
                answerA={this.props.question.A}
                answerB={this.props.question.B}
                answerC={this.props.question.C}
                answerD={this.props.question.D}
                answerE={this.props.question.E}
                remark={this.props.question.remark}
                onChange={this.props.onChange}
              />
              {this.props.answered ? (this.props.question.answer===this.props.answer.answer? ('') : (<p  style={{"color":"red"}}>正确答案:{this.props.question.answer}</p>) ) : ('')} // 如果当前已经检查,且回答与正确答案不符,则以红色显示正确的答案。
          </div>
          )
      }
    }
    

    整体App部件

    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          questions: [], // 初始题目集
          current_questions: [], // 当前题目集,加载页面后与初始题目集相同,检查后则只保留错题
          answered: false, // 当前答题状态
          answers: [], // 答案集
        }
        this.handleChange = this.handleChange.bind(this);
        this.handleCheckClick = this.handleCheckClick.bind(this);
      }
      componentDidMount() { // 部件加载后获取数据
        var that = this;
        const url = 'http://localhost:3000/data/';
        $(function(){$.ajax({ // 这里很关键不要写错,$(function(){}困扰了我N天,:-<
          headers: {
            'Content-Type': 'application/json',
          },
          url: url,
          type: "GET",
          dataType: "json",
          data: {},
          success: function(result) {
            that.setState({questions:result,current_questions:result,});
            var answers = [];
            result.forEach((r)=>{
              answers.push({'id':r.id,'answer':''})
            });
            that.setState({answers:answers,});
          },
          error: function(xhr, status, err) {
            console.log(err.Message);
          },
        })})
      }
      handleChange(event) { // 选择控件的相应事件
        const id = parseInt(event.target.id.split('_')[0]); // 由控件ID获得题目ID和所作选择
        const selection = event.target.id.split('_')[1];
        const type = event.target.type;
        var answers = this.state.answers;
        if (type==='radio') { // 单选题直接给答案赋值
          answers.find(answer=>answer.id===id).answer = selection;
        } else {
          if (event.target.checked) { // 多选题,如果勾选
            if (!answers.find(answer=>answer.id===id).answer.includes(selection)){
              var tmp = answers.find(answer=>answer.id===id).answer + selection;
              tmp = tmp.split('').sort().join(''); // 赋值前排序,考虑到用户奇怪的操作方式,想想:ABC===ACB吗?
              answers.find(answer=>answer.id===id).answer = tmp;
            }
          } else { // 如果去掉勾选,答案中也要相应删除
            if (answers.find(answer=>answer.id===id).answer.includes(selection)){
              answers.find(answer=>answer.id===id).answer = answers.find(answer=>answer.id===id).answer.replace(selection,'')
            }
          }
        }
        this.setState({answers:answers,})
      }
      handleCheckClick(event) { // 检查按钮的相应事件
        event.preventDefault();
        if (event.target.innerHTML==='检查') {
          this.setState({answered:true,});
        } else { // 若是再做一遍错题,则需要根据正确与否更新错题库
          var current_questions = [];
          var answers = [];
          this.state.current_questions.forEach((question)=> {
            if (this.state.answers.find(answer=>answer.id===question.id).answer!==question.answer) {
              current_questions.push(question);
              answers.push({'id':question.id,'answer':''})
            }
          });
          this.setState({current_questions:current_questions,answers:answers,answered:false,})
        }
      }
      render() {
        var questions = [];
        this.state.current_questions.forEach((question)=>{
          questions.push(<div className="box effect2"><QuestionBar key={question.id} question={question} answer={this.state.answers.find(answer=>answer.id===question.id)} answered={this.state.answered} onChange={this.handleChange} /></div>)
        })
        return <div><div>{questions}</div><div className="box effect2"><SubmitBar answered={this.state.answered} onClick={this.handleCheckClick} /></div></div>
      }
    }
    
    export default App;
    

    OK。

    Github

    这个项目我放在github上了,地址在这儿

    演示地址

    点击这儿可以查看heroku上的演示(题库数据量较大,加载大概需要十几秒钟)。

    相关文章

      网友评论

      • 4e5f67d086be:数据果然大 等了10几秒 。。:smile:
        fanzhh:heroku免费平台,已经很不错啦,:joy:

      本文标题:使用Sqlite3+Express.js+React实现在线答题

      本文链接:https://www.haomeiwen.com/subject/qyikzxtx.html