项目倒是挺有意思的
感谢无私开源的程序员,谢谢你们哒~
现在我们来分析代码
数据流部分是用的dva
在global.js中有一个分享文章功能
//umi-dva-antd-mobile\src\global.js
import { Toast, Modal } from 'antd-mobile';
import { setAuthority } from '@/utils/authority';
import initWx from '@/utils/wx';
import debug from '@/utils/debug';
import './global.less';
setAuthority('admin');
function isWeixn() {
const ua = navigator.userAgent.toLowerCase();
return ua.includes('micromessenger');
}
if (!isWeixn()) { // 需要在微信端运行的时候 开启下面的注释
// alert('请在微信客户端打开');
// window.location.replace('#/404');
} else {
debug().then(() => {
initWx({
title: '分享标题',
imgUrl: '', // 分享图标
isNeedLogin: true,
desc: '分享描述',
openid: process.env.NODE_ENV === 'development' ? 'oEgayjggrU06oORZJVeFUJ_KF1Mk' : undefined,
});
});
}
// Notify user if offline now
window.addEventListener('sw.offline', () => {
Toast.offline('当前处于离线状态');
});
// Pop up a prompt on the page asking the user if they want to use the latest version
window.addEventListener('sw.updated', e => {
console.log('sw.updated');
const reloadSW = async () => {
// Check if there is sw whose state is waiting in ServiceWorkerRegistration
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
const worker = e.detail && e.detail.waiting;
if (!worker) {
return Promise.resolve();
}
// Send skip-waiting event to waiting SW with MessageChannel
await new Promise((resolve, reject) => {
const channel = new MessageChannel();
channel.port1.onmessage = event => {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
});
// Refresh current page to use the updated HTML and other assets after SW has skiped waiting
window.location.reload(true);
return true;
};
Modal.alert('有新内容', '请点击“刷新”按钮或者手动刷新页面', [
{
text: '刷新',
onPress: () => {
reloadSW();
},
},
]);
});
这个页面还真的不知道
这种入口文件不明显啊
我们先找下面的吧
//umi-dva-antd-mobile\src\pages\entrance\index.tsx
import { Button, Icon, Modal, Picker } from 'antd-mobile';
import classNames from 'classnames';
import { connect } from 'dva';
import LS from 'parsec-ls';
import React from 'react';
import * as theme from '../../theme';
const styles = require('./index.less');
interface IChapterData {
parentId?: string | number;
value: string | number;
label: React.ReactNode;
children?: IChapterData[]
}
interface IEntranceState {
selected: number;
chapterValue: [];
chapterLabel: string;
visible: boolean;
}
interface IEntranceProps {
dispatch?: any;
loading?: boolean;
h5: {
chapterData: IChapterData[] | IChapterData[][]
}
}
@connect(({ h5, loading }) => ({
h5,
loading: loading.models.h5,
}))
class Index extends React.Component<IEntranceProps, IEntranceState> {
constructor(props) {
super(props);
this.state = {
selected: -1,
chapterValue: [],
chapterLabel: '请选择章节',
visible: false,
};
}
public render() {
const { selected, chapterValue, chapterLabel, visible } = this.state;
const { h5: { chapterData = [] } } = this.props;
const questionTotal = 100;
return (
<div className={styles.entranceBox}>
<div className={styles.title}>网约车从业资格证</div>
<div className={styles.subtitle}>考题练习及模拟考试</div>
<div
className={classNames(styles['select-item'], styles['opt-large'], {
[styles['opt-selected']]: selected === 1,
})}
onClick={() => {
this.setState({ selected: 1, chapterValue: [], chapterLabel: '请选择章节' }, () => {
this.getChapterData(1);
});
}}
>
全国公共科目考试题库
</div>
<div
className={classNames(styles['select-item'], styles['opt-large'], {
[styles['opt-selected']]: selected === 7,
})}
onClick={() => {
this.setState({ selected: 7, chapterValue: [], chapterLabel: '请选择章节' }, () => {
this.getChapterData(2);
});
}}
>
本地区域科目考试题库
</div>
<Picker
extra="请选择(可选)"
data={chapterData}
cols={1}
title="请选择章节"
value={chapterValue}
onOk={e => {
this.setState({
chapterValue: e,
chapterLabel: chapterData.filter(x => x.value === e[0])[0].label,
});
}}
>
<div className={styles['select-item']}>{chapterLabel}</div>
</Picker>
<div className={styles.bottom}>
<Button
type="primary"
onClick={() => {
this.getPaper(2);
}}
>
考题预习
</Button>
<Button
type="primary"
onClick={() => {
this.getPaper(3);
}}
>
模拟考试
</Button>
</div>
<Modal
visible={visible}
transparent={true}
wrapClassName={styles['modal-wrap']}
maskClosable={false}
onClose={() => {
this.setState({
visible: false,
});
}}
title={null}
footer={[
{
text: '开始考试',
onPress: () => {
this.startTheExam();
},
style: { background: theme.primaryColor, color: '#ffffff', fontSize: 14 },
},
]}
>
<div className={styles['modal-container']}>
<div className={styles['modal-header']}>
<div
className={styles.closeBtn}
onClick={() => {
Modal.alert('确认放弃本次考试吗?', '', [
{ text: '放弃考试', onPress: () => this.setState({ visible: false }) },
{ text: '继续考试' },
]);
}}
>
<Icon type="cross"/>
</div>
<div className={styles.avatar}>
<img src={require('../../assets/h5/avatar.png')} alt="avatar"/>
</div>
<div className={styles['name-wrap']}>
滴滴 <span>师傅</span>
</div>
</div>
<div className={styles['modal-body']}>
<div className={styles['desc-item']}>
<div className={styles['item-label']}>考试科目:</div>
<div className={styles['item-value']}>北京市出租汽车驾驶员从业资格模拟考试</div>
</div>
<div className={styles['desc-item']}>
<div className={styles['item-label']}>试题数量:</div>
<div className={styles['item-value']}>
<span>{questionTotal}</span> 题
</div>
</div>
<div className={styles['desc-item']}>
<div className={styles['item-label']}>考试时间:</div>
<div className={styles['item-value']}>
共 <span>80</span> 分钟 (公共科目
<span>50</span>
分钟,区域科目
<span>30</span>
分钟)
</div>
</div>
<div className={styles['desc-item']}>
<div className={styles['item-label']}>合格标准:</div>
<div className={styles['item-value']}>
<p>
公共科目
<span>40分</span>
及格,满分
<span>50分</span>;
</p>
<p>
区域科目-理论知识
<span>32分</span>
及格,满分
<span>40分</span>;
</p>
<p>
区域科目-运营实务
<span>8分</span>
及格,满分
<span>10分</span>;
</p>
<p>
每题
<span>1分</span>
,共计
<span>100题</span>
,总分
<span>100分</span>;
</p>
</div>
</div>
<div className={styles.notice} style={{ display: 'none' }}>
温馨提示:按照驾管部要求,考试不能修改答案,每做一题,系统自动计算错题数。
</div>
</div>
</div>
</Modal>
</div>
);
}
/**
* 获取试题
* @param step
*/
public getPaper = step => {
const { selected, chapterValue } = this.state;
const { dispatch } = this.props;
if (step === 2 && (selected === -1 || chapterValue.length === 0)) {
Modal.alert('请选择地区和章节', '');
return;
}
// @ts-ignore
const sectionId = chapterValue[0];
LS.setObj('exam-region', { chapterId: selected, sectionId });
if (step === 2) {
// @TODO 生成考题预习
dispatch({
type: 'h5/fetchQuestions',
payload: {
chapterID: selected,
sectionID: sectionId,
type: 'preview',
},
});
} else {
// @TODO 生成考试试卷
dispatch({
type: 'h5/fetchTestPapers',
callback: (response, modalVisible) => {
this.setState({
visible: modalVisible,
});
LS.setObj('exam-paper', response.paper);
},
});
}
};
/**
* 取得一级或二级章节列表
*/
private getChapterData = (parentID: number) => {
const { dispatch } = this.props;
dispatch({
type: 'h5/fetchChapter',
payload: { parentID },
});
};
/**
* 开始考试
*/
private startTheExam = () => {
const { dispatch } = this.props;
dispatch({
type: 'h5/startTheExam',
});
};
}
export default Index;
还有容错页的处理
//umi-dva-antd-mobile\src\pages\exception\500.js
import React from 'react';
import Link from 'umi/link';
import Exception from '@/components/Exception';
const Exception500 = () => (
<Exception type="500" desc="抱歉,服务器出错了" linkElement={Link} backText="返回首页" />
);
export default Exception500;
分析代码的话是有这个user页面的,不过咩有怎么处理
//umi-dva-antd-mobile\src\pages\result\index.tsx
import { Button } from 'antd-mobile';
import { connect } from 'dva';
import LS from 'parsec-ls';
import React from 'react';
import router from 'umi/router';
const styles = require('./index.less');
interface IResultState {
resultData: {
score: number;
time: string;
isPass: boolean;
}
}
@connect(({ h5, loading }) => ({
h5,
loading: loading.models.h5,
}))
class Result extends React.PureComponent<any, IResultState> {
constructor(props) {
super(props);
const { h5: { resultData: { score = 0, time = '00:00', isPass } } } = props;
this.state = {
resultData: {
score, time, isPass,
},
};
}
public componentDidMount() {
if (LS.getObj('exam-test-result') === null) {
router.push('/');
return;
}
const { result } = LS.getObj('exam-test-result');
const { resultData } = this.state;
if (result) {
this.setState({
resultData: {
...resultData,
...result,
},
});
}
}
public render() {
const { resultData: { score = 0, time = '00:00', isPass } } = this.state;
return (
<div className={styles.result}>
<div className={styles.card}>
<div className={styles['img-wrap']}>
{this.getTitleContent(isPass)}
<div className={styles.wording}>科目考试成绩</div>
<div className={styles.score}>{score || 0} 分</div>
<div className={styles.duration}>答题时长:{time || '00:00'}</div>
</div>
<div className={styles.btns}>
<Button
type='ghost'
onClick={() => {
this.goToWrongPage();
}}
>查看错题
</Button>
<Button
type="primary"
onClick={() => {
router.push('/entrance');
}}
>返回首页
</Button>
</div>
</div>
</div>
);
}
private getTitleContent = (isPass) => {
if (isPass) {
return (<>
<div className={styles.title}>恭喜您 <br/> 考试通过啦</div>
<img src={require('../../assets/h5/pass.jpg')} alt=""/>
</>);
}
return (<>
<div className={styles.title}>很遗憾 <br/> 您没有通过考试</div>
<img src={require('../../assets/h5/fail.jpg')} alt=""/>
</>);
};
/**
* 查看错题
*/
private goToWrongPage = () => {
const { dispatch } = this.props;
const examResult = LS.getObj('exam-test-result');
let data = [];
if (examResult !== null) {
const { result: { wrong_ids = [] }, questions = [], answers = [] } = examResult;
data = wrong_ids.map((id) => ({
...questions.filter(x => x.id === id)[0] || {},
userAnswer: this.getUserSelectAnswer(answers, id),
})).filter(x => x !== null);
}
// 先获取一遍数据 避免 js 报错
dispatch({
type: 'h5/savePaperData',
payload: {
questions: data,
paper: LS.getObj('exam-paper') || {},
},
});
router.push('/paper/wrong');
};
private getUserSelectAnswer = (answers, id) => {
if (answers && answers.length > 0) {
return answers.filter(x => x.questionId === id)[0].answer;
}
return [];
};
}
export default Result;
分析代码可知道这个是入口文件
import { Button } from 'antd-mobile';
import React from 'react';
import router from 'umi/router';
const styles = require('./index.less');
export default class extends React.Component<{}, {}, any> {
constructor(props) {
super(props);
}
public render = () => {
return (<div className={styles.home}>
<div
className={styles.index}
>
<div className={styles.title}>网约车从业资格证</div>
<div className={styles.subtitle}>考题练习及模拟考试</div>
<Button
type='primary'
className={styles['btn-enter']}
onClick={() => {
// this.setState({ step: 1 });
router.push('/entrance');
}}
>进入
</Button>
</div>
</div>);
};
}
其实这个项目让我百思不得其解的,因为不知道入口在那里,一般都会有router.js来定义的,所以啊,很有意思的项目啊~
运行于浏览器中的h5,比需要各种环境的react-native因为便捷更加能够激发人的创造性
//src\pages\home\index.tsx