在使用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
文件。
导入
代码语言:javascript复制import React, { Component } from 'react';
import $ from 'jquery';
import './App.css';
题目描述部件
代码语言:javascript复制class DescriptionBar extends Component {
render() {
return <p>{this.props.description}</p>
}
}
答案选择部件
代码语言:javascript复制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>
)
}
}
提交部件
代码语言:javascript复制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>
)
}
}
问题部件
问题部件是题目描述和答案选择的父部件。
代码语言:javascript复制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部件
代码语言:javascript复制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
上的演示(题库数据量较大,加载大概需要十几秒钟)。