- 因为这个小项目是按照开源项目实现的,所以只了解React和Redux实现方式就好,相关的引入框架和后台数据库相关暂时先不解释。(主要是没时间了,肝deadline。。。)
文章目录
1.安装框架和依赖
用的是蚂蚁ant框架
npm install antd-mobile --save
然后导入样式
import { Button } from 'antd-mobile';
import 'antd-mobile/dist/antd-mobile.css';
还要安装插件
npm install babel-plugin-import --save
相关配置
npm run eject
package.json
"babel": {
"presets": [
"react-app"
],
"plugins": [
["import", { "libraryName": "antd-mobile", "style": "css" }]
]
}
项目数据
- 数据导入:
Quizzes.json文件导入数据库。首先创建数据库,右键点击表,选择导入文件,文件导入json文件,后面一键跳过就行,最后一步点击完成。 - 创建服务器:
npm init
cnpm install express --save
cnpm install mysql --save
创建index.js文件
- 前端依赖
需要安装的内容:axios,react-router-dom,redux,react-redux,
相关目录树
2.数据库配置
- sql.js
设置连接,新建连接对象,let con = mysql.createConnection(connection);
并定义promise对象查询的方法,返回为promise对象。
const mysql = require('mysql');
//配置连接
//这个自己设置哈
const connection = {
host: 'localhost',
post: '3306',
user: 'root',
password: 'root',
database: 'timu'
};
//创建连接对象
//let con = mysql.createConnection(connection);
//连接
// con.connect(err => {
// if (err) {
// console.log('数据库连接失败');
// } else {
// console.log('数据库连接成功');
// }
// });
//创建promise对象查询方法
function queryFn(sqlStr, arr) {
//创建连接对象
let con = mysql.createConnection(connection);
return new Promise((resolve, reject) => {
//找到了就返回并断开连接,没找到就拒绝连接
con.query(sqlStr, arr, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
con.end()
}
});
});
}
module.exports = queryFn;
3.express实现数据获取
- 通过express()前端页面的路由监听进行对应后端数据库端口的数据获取
- 创建express对象并绑定前端页面端口,为了传输数据
- 端口监听事件绑定过后就要进行页面URL的路由绑定,比如对这个路由
'/api/rtimu/'
进行数据的获取 - 这里通过异步请求进行获取,因为获取时是跨域的页面是“8080”端口但是服务器端是“3306”所以要进行跨域请求设置
res.append("Access-Control-Allow-Origin","*") res.append("Access-Control-Allow-Content-Type","*")
- 通过sql语句进行数据库操作,这里sqlQuery的promise对象建立时就已经连接上数据库啦
- 然后数据转成json就可以给前端的store用啦
网页服务端的index.js
var express = require('express')
var app = express()
var sqlQuery = require('./sql')
app.get('/',(req,res)=>{
res.send("这是答题API服务器")
})
//如果要获取第几页的数据 就 /:page
app.get('/api/rtimu/',async (req,res)=>{
//随机获取10个题目;
//console.log(req.query)
//跨域请求
res.append("Access-Control-Allow-Origin","*")
res.append("Access-Control-Allow-Content-Type","*")
//判断存不存在 存在就是page不存在就默认设为2
let page = req.query.page?req.query.page:2;
let strSql = `select * from quizzes limit ${page*10},10`;
//这里要用到异步
let result = await sqlQuery(strSql)
//console.log(result)
//Array.from(result)从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
res.json(Array.from(result));
})
//后端监听事件
//有关app.listen的解释
/*
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
//this就是下面
var app = function(req,res,next){}
*/
app.listen(8080,()=>{
console.log(
"server Start",
"http://localhost:8080/"
)
})
4.Store配置
ajax数据请求
- store/asyncMethods.js
- 通过随机生成page的值进行随机获取值
import axios from 'axios';
const host = 'http://localhost:8080'
let fns = {
async TmList(){
let page = parseInt(Math.random()*1600);
let httpUrl = `${host}/api/rtimu/?page=${page}`
let res = await axios.get(httpUrl);
return res.data
//console.log(res.dat)
}
}
export default fns;
Redux的state操作函数设置
- method.js
- 目的是为了在此对store中的state进行修改,也就是后面要调用到的dispatch
let methods = {
add:function(state,action){
state.num++
return state
},
addNum:function(state,action){
state.num = state.num + action.num;
return state
},
setTimu:function(state,action){
state.timuList = action.content;
return state;
}
}
export default methods
Redux的state设置
let state = {
timuList:[],
}
export default state
Redux的store相关操作(state初始化 reducer创建)
import {createStore} from 'redux';
import methods from './methods';
import state from './state';
let data = state;
//初始化state
//创建出reducer函数
let ActionFnObj=methods;
function reducer(state=data,action){
if(action.type.indexOf('redux')===-1){
state = ActionFnObj[action.type](state,action)
return {...state}
}else{
return state;
}
}
const store = createStore(reducer)
export default store
5.store(state和函数)到props的映射
- 建立映射函数(store.state和store.(dispatch函数))
- 然后通过connect方法进行组件类和store的绑定
我们通过这个思想来进行这个百万答题项目的页面渲染
- 首先是页面导航页面:App.js
- 通过映射获取state,但是本页面就直接跳转页面了,所以没有相关的store操作
import React from 'react';
import {connect} from 'react-redux'
import {Button} from 'antd-mobile'
//将state映射到props函数
function mapStateToProps(state){
return {...state}
}
//将修改state数据的方法,映射到props,默认会传入store里的dispach方法
function mapDispatchToProps(dispatch){
return {
onAddClick:()=>{dispatch({type:'add'})},
}
}
class Counter extends React.Component{
render(){
return (
<div>
<Button onClick={this.goDatiPage}>随机答题</Button>
<Button onClick={this.props.onAddClick5}>闯关答题</Button>
<Button onClick={this.props.onAddClick5}>抽奖答题</Button>
</div>
)
}
goDatiPage=()=>{
//console.log(this.props)
this.props.history.push("/dati")
}
}
//将上面的这2个方法,将数据仓库的state和修改state的方法映射到组件上,形成新的组件。
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
export default App
- 然后是答题页面Dati.js
- 首先是state和state操作函数的props映射,这里通过传入dispatch的值进行store中的方法匹配
- 然后进行react的题目DOM页面渲染,题目就直接从state获取就行了
- 然后题目的选项上面要绑定一个判断正确与错误的事件,也是通过对state里面的数据进行获取匹配实现的
- 最后答完题还要进行一个页面push约等于刷新的事件,进行下一题的操作,这里有个小技巧就是通过对state的currentnum进行修改然后就能够重新唤起渲染事件进行页面刷新,并且如果>10还能跳转result界面。
import React from 'react';
import {connect} from 'react-redux'
// import {Button} from 'antd-mobile'
import fns from '../store/asyncMethods'
import loadingImg from '../assets/img/loading.gif'
//将state映射到props函数
function mapStateToProps(state){
return {...state}
}
//将修改state数据的方法,映射到props,默认会传入store里的dispach方法
function mapDispatchToProps(dispatch){
return {
onAddClick:()=>{dispatch({type:'add'})},
getTimu:async ()=>{
let list = await fns.TmList()
dispatch({
type:"setTimu",
content:list
})
console.log(list)
}
}
}
class DatiCom extends React.Component{
constructor(props){
super(props)
this.state = {
currentTimu:0,
optionsStyle:['optionItem','optionItem','optionItem','optionItem'],
isChoose:false,
score:0
}
}
componentDidMount(){
this.props.getTimu()
}
render(){
console.log(this.props)
console.log(this.state.currentTimu)
let timuArr = this.props.timuList;
let currentNum = this.state.currentTimu;
let oStyle = this.state.optionsStyle;
//如果数据没有加载进来,就设置为loading
if(timuArr.length>0){
let options = JSON.parse(timuArr[currentNum].options)
return (
<div className="datiPage">
<h2>
{currentNum+1}-{timuArr[currentNum].quiz}
</h2>
<div className="options">
{
options.map((item,index)=>{
return (
<div key={index} className={oStyle[index]} onClick={()=>this.answerEvent(index)}>
{index+1}: {item}
</div>
)
})
}
</div>
</div>
)
}else{
return (
<div>
<img alt="img" src={loadingImg} />
</div>
)
}
}
goDatiPage=()=>{
//console.log(this.props)
this.props.history.push("/dati")
}
answerEvent=(index)=>{
if(this.state.isChoose){
return true;
}
console.log(index)
let currentAnswer = this.props.timuList[this.state.currentTimu].answer;
console.log(currentAnswer)
let score = this.state.score;
if((index+1)===Number(currentAnswer)){
let optionsStyle = this.state.optionsStyle;
optionsStyle[index] = "optionItem correct";
this.setState({
optionsStyle:optionsStyle,
isChoose:true,
score:score+10
})
}else{
let optionsStyle = this.state.optionsStyle;
optionsStyle[index] = "optionItem error";
optionsStyle[(Number(currentAnswer)-1)] = "optionItem correct"
this.setState({
optionsStyle:optionsStyle,
isChoose:true
})
}
//2秒跳转至下一题
setTimeout(() => {
let currentNum = this.state.currentTimu
currentNum++
if(currentNum===10){
this.props.history.push('/result',{score:this.state.score})
}else{
this.setState({
currentTimu:currentNum,
optionsStyle:['optionItem','optionItem','optionItem','optionItem'],
isChoose:false
})
}
}, 2000);
}
}
//将上面的这2个方法,将数据仓库的state和修改state的方法映射到组件上,形成新的组件。
const Dati = connect(
mapStateToProps,
mapDispatchToProps
)(DatiCom)
export default Dati
- result.js
- 实现获取props的state数据进行分数统计
import React from 'react';
import {connect} from 'react-redux'
import {Button} from 'antd-mobile'
//将state映射到props函数
function mapStateToProps(state){
return {...state}
}
//将修改state数据的方法,映射到props,默认会传入store里的dispach方法
function mapDispatchToProps(dispatch){
return {
onAddClick:()=>{dispatch({type:'add'})},
}
}
class Counter extends React.Component{
render(){
console.log(this.props)
return (
<div>
<h1>恭喜您获得{this.props.location.state.score}分</h1>
<Button onClick={this.goDatiPage}>回到首页</Button>
</div>
)
}
goDatiPage=()=>{
//console.log(this.props)
this.props.history.push("/")
}
}
//将上面的这2个方法,将数据仓库的state和修改state的方法映射到组件上,形成新的组件。
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
export default App
- index.js 主渲染文件
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'
import {BrowserRouter as Router,Route} from 'react-router-dom';
import store from './store/data'
import App from './view/App'
import './assets/css/style.css'
import Dati from './view/Dati'
import Result from './view/Result'
ReactDOM.render(
<Provider store={store}>
<Router>
<Route path="/" exact component={App}></Route>
<Route path="/dati" component={Dati}></Route>
<Route path="/result" component={Result}></Route>
</Router>
</Provider>,
document.querySelector("#root")
)
最后再来点css特效
/* .datiPage{
} */
.datiPage h2{
width: 90%;
margin: 10px auto;
}
.options{
width: 90%;
display: flex;
flex-direction: column;
height: 400px;
margin: 20px auto;
border-radius: 10px;
background-color: #efefef;
justify-content: space-around;
align-items: center;
}
.optionItem{
width: 90%;
height: 60px;
line-height: 60px;
padding: 0 10px;
background-color: lightblue;
border-radius: 10px;
}
.optionItem.correct{
background-color: lightgreen;
color: #fff;
}
.optionItem.error{
background-color: orangered;
color: #fff;
}
谢谢阅读,over!