React.js

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

2018-01-31  本文已影响69人  fanzhh

使用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上的演示(题库数据量较大,加载大概需要十几秒钟)。

上一篇下一篇

猜你喜欢

热点阅读