Commit a35f592c by zhanghaozhe

Merge branch 'ai-test-merge' into dev

# Conflicts:
#	src/router/router-config.js
parents ccd05e41 1d805d20
...@@ -4494,9 +4494,9 @@ ...@@ -4494,9 +4494,9 @@
} }
}, },
"date-fns": { "date-fns": {
"version": "1.30.1", "version": "2.14.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.14.0.tgz",
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" "integrity": "sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw=="
}, },
"date-now": { "date-now": {
"version": "0.1.4", "version": "0.1.4",
...@@ -13980,6 +13980,11 @@ ...@@ -13980,6 +13980,11 @@
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
}, },
"store2": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/store2/-/store2-2.11.2.tgz",
"integrity": "sha512-TQMKs+C6n9idtzLpxluikmDCYiDJrTbbIGn9LFxMg0BVTu+8JZKSlXTWYRpOFKlfKD5HlDWLVpJJyNGZ2e9l1A=="
},
"stream-browserify": { "stream-browserify": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
"case-sensitive-paths-webpack-plugin": "2.2.0", "case-sensitive-paths-webpack-plugin": "2.2.0",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"css-loader": "1.0.0", "css-loader": "1.0.0",
"date-fns": "^1.30.1", "date-fns": "^2.14.0",
"dotenv": "6.0.0", "dotenv": "6.0.0",
"dotenv-expand": "4.2.0", "dotenv-expand": "4.2.0",
"eslint": "5.12.0", "eslint": "5.12.0",
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"sass-resources-loader": "^2.0.0", "sass-resources-loader": "^2.0.0",
"socket.io": "^2.2.0", "socket.io": "^2.2.0",
"store2": "^2.11.2",
"style-loader": "0.23.1", "style-loader": "0.23.1",
"swiper": "^4.5.1", "swiper": "^4.5.1",
"terser-webpack-plugin": "1.2.2", "terser-webpack-plugin": "1.2.2",
......
import React, { Component } from 'react';
import './index.scss'
import { HeaderBar } from "@common/index"
import Question from "@components/ai-test/common/question"
import Navigation from "@components/ai-test/common/navigation"
import { html, http } from "@/utils"
import { Toast } from "antd-mobile";
import Recommends from '@/components/ai-test/common/recommends'
class Analysis extends Component {
state = {
questions: [],
activeIndex: 0,
userSelect: '',
rightAnswer: '',
userUnselect: false,
}
componentDidMount() {
this.getAnalysis()
}
getAnalysis = () => {
http.post(`${API.home}/sys/get_analysis`, {
record_id: this.props.match.params.recordId,
}).then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.setState({
questions: data,
}, () => {
this.getAnswerInfo()
});
} else {
Toast.fail(msg, 2, null, false)
}
})
}
getAnswerInfo = () => {
const {questions, activeIndex} = this.state
const question = questions[activeIndex]
const userAnswerIndex = question.options.findIndex(item => item.user_select)
const rightAnswerIndex = question.options.findIndex(item => item.is_ans)
this.setState({
userSelect: String.fromCharCode(65 + userAnswerIndex),
rightAnswer: String.fromCharCode(65 + rightAnswerIndex),
userUnselect: userAnswerIndex < 0,
});
}
componentDidUpdate(prevProps, prevState) {
if (prevState.activeIndex !== this.state.activeIndex) {
this.getAnswerInfo()
}
}
render() {
const {questions, activeIndex, userSelect, rightAnswer, userUnselect} = this.state
return (
<div className={'analysis-container'}>
<HeaderBar title={'AI水平测试'} arrow={true}/>
{
!!questions.length && <Question activeIndex={activeIndex} question={questions[activeIndex]}/>
}
<div style={{height: '8px', backgroundColor: '#f5f5f5'}}></div>
<div className="analysis">
{
userUnselect
? <div className={'info'}>您未作答</div>
: <div className="info">
您选择的是{userSelect},正确答案是{rightAnswer} 回答{userSelect === rightAnswer ? '正确' : '错误'}
</div>
}
<div className="content">
<div className="head">
<i className="icon"></i>
<span>解析</span>
</div>
{
!!questions.length &&
<div className="analysis-content" dangerouslySetInnerHTML={html(questions[activeIndex].analysis)}></div>
}
</div>
</div>
{
!!questions.length && questions[activeIndex].type_id && <Recommends typeId={questions[activeIndex].type_id}/>
}
<Navigation questions={questions} isAnalysis={true} handleClick={(index) => {
this.setState({
activeIndex: index,
});
}}/>
</div>
);
}
}
export default Analysis;
\ No newline at end of file
.analysis-container {
padding-bottom: 55px;
padding-top: 64px;
.detail-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #fff;
border-bottom: 1px solid #DDD;
}
.analysis {
padding: 15px 17px 0;
.info {
margin-bottom: 22px;
color: #333;
font-size: 14px;
}
.head {
display: flex;
align-items: center;
margin-bottom: 15px;
.icon {
display: block;
width: 16px;
height: 16px;
margin-right: 5px;
background: url("./analysis.png") no-repeat;
background-size: contain;
}
span {
font-size: 14px;
color: #09f;
}
}
&-content {
font-size: 14px;
color: #666;
}
}
}
\ No newline at end of file
import React, { Component } from 'react';
import './index.scss'
import { HeaderBar } from "@common/index"
import Question from "@components/ai-test/common/question"
import { html, http } from "@/utils"
import { Toast } from "antd-mobile";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import Recommends from "@components/ai-test/common/recommends"
import classnames from 'classnames'
class Assist extends Component {
state = {
question: null,
answer: {},
result: null,
rightAnswer: '',
userAnswer: '',
}
componentDidMount() {
this.getData()
}
getData = () => {
http.get(`${API.home}/sys/aitest/assist`)
.then(res => {
const {code, msg, data} = res.data
if (code === 200) {
const answer = {
[data.id]: undefined,
}
this.setState({
question: data,
answer,
});
} else {
Toast.fail(msg, 2, null, false)
}
})
}
selectAnswer = (question, optionId) => {
this.setState({
answer: {
[question.id]: optionId,
},
});
}
submit = () => {
const {history, user} = this.props
if (user.hasError) {
history.push('/passport')
return
}
const {question, answer} = this.state
if (!answer[question.id]) {
Toast.info('请选择后进提交', 2, null, false)
return
}
http.post(`${API.home}/sys/aitest/assistSubmit`, {
code: this.props.match.params.assistCode,
question_id: question.id,
answer_id: answer[question.id],
}).then(res => {
const {code, msg, data} = res.data
this.setAnswer(data.correct_answer)
if (code === 200) {
this.setState({
result: data,
});
} else {
Toast.fail(msg, 2, null, false)
}
})
}
setAnswer = (rightAnswerId) => {
this.setState(state => {
let rightAnswer = '', userAnswer = ''
const question = {
...state.question, ...{
options: state.question.options.map((item, index) => {
if (item.id === rightAnswerId) {
item.is_ans = 1
rightAnswer = String.fromCharCode(65 + index)
}
if (item.id === state.answer[state.question.id]) {
userAnswer = String.fromCharCode(65 + index)
item.user_select = 1
}
return item
}),
},
}
return {
question,
rightAnswer,
userAnswer,
}
});
}
render() {
const {question, answer, result, rightAnswer, userAnswer} = this.state
return (
<div className={'assist'}>
<HeaderBar title={'AI水平测试'} arrow={true}/>
{
question &&
<Question question={question} category={'机器学习'} answer={answer} selectAnswer={!result && this.selectAnswer}/>
}
<div style={{height: '8px', backgroundColor: '#f5f5f5'}}></div>
{
result && <div className="content">
<div class={'info'}>您的选择是{userAnswer},正确答案是{rightAnswer} 回答{userAnswer === rightAnswer ? '正确' : '错误'}</div>
<div className="head">
<i className="icon"></i>
<span>解析</span>
</div>
<div className="analysis-content" dangerouslySetInnerHTML={html(result.analysis)}></div>
</div>
}
{
result && <>
<Recommends typeId={question.type_id}/>
<div className={classnames(['status', {
end: result.status === 6,
success: result.status === 1,
error: result.status === 2 || result.status === 3 || result.status === 4 || result.status === 5,
}])}>
{result.desc}
</div>
</>
}
<div className="btns">
{
!result && <button className={'submit'} onClick={this.submit}>提交</button>
}
{
result && (result.status === 6
? <Link to={'/'} class={'home'}>返回首页</Link>
: <Link to={'/ai-test/scores'} class={'test'}>我也要测试</Link>)
}
</div>
</div>
);
}
}
export default connect(
state => state.user,
null,
)(Assist)
\ No newline at end of file
.assist {
padding-top: 59px;
padding-bottom: 60px;
.detail-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #fff;
border-bottom: 1px solid #DDD;
}
.btns {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
padding: 10px;
background-color: #fff;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.05);
.submit {
background-color: #09f;
color: #fff;
font-size: 18px;
width: 100%;
height: 100%;
border: 0;
outline: 0;
-webkit-appearance: none;
}
a {
display: block;
width: 100%;
height: 100%;
font-size: 18px;
line-height: 40px;
color: #fff;
text-align: center;
&.home {
background-color: #09f;
}
&.test {
background-color: #FFAE00;
}
}
}
.head {
display: flex;
align-items: center;
margin-bottom: 15px;
.icon {
display: block;
width: 16px;
height: 16px;
margin-right: 5px;
background: url("../analysis/analysis.png") no-repeat;
background-size: contain;
}
span {
font-size: 14px;
color: #09f;
}
}
.content {
padding: 15px 17px;
.info {
color: #333;
font-size: 14px;
margin-bottom: 15px;
}
.analysis-content{
font-size: 14px;
}
}
.status {
font-size: 14px;
text-align: center;
color: #333;
&.success {
color: #29C8A0;
}
&.error {
color: #FF5A5A;
}
&.end {
color: #666;
}
}
}
\ No newline at end of file
import React from 'react';
import './index.scss'
import classnames from 'classnames'
const Navigation = ({questions, answer, handleClick, isAnalysis}) => {
return (
<div className="navigation">
<ul>
{
!!questions.length && questions.map((item, index) => {
const userSelectIndex = item.options.findIndex(item => item.user_select)
const rightAnswerIndex = item.options.findIndex(item => item.is_ans)
return <li key={index} onClick={handleClick.bind(this, index)}
className={classnames({
active: answer && answer[item.id],
correct: isAnalysis && userSelectIndex === rightAnswerIndex,
error: isAnalysis && userSelectIndex >= 0 && userSelectIndex !== rightAnswerIndex,
unselect: isAnalysis && userSelectIndex < 0,
})}>{index + 1}</li>
})
}
</ul>
</div>
);
};
export default Navigation;
\ No newline at end of file
.navigation {
position: fixed;
bottom: 0;
left: 0;
width: 374px;
height: 55px;
padding: 0 15px;
background: #fff;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.05);
ul {
height: 55px;
padding-top: 11px;
overflow-x: auto;
white-space: nowrap;
}
li {
display: inline-block;
width: 34px;
height: 34px;
border: 1px solid #09f;
border-radius: 50%;
margin-right: 20px;
color: #09f;
font-size: 18px;
text-align: center;
line-height: 34px;
&.active {
color: #fff;
background-color: #09f;
}
&.correct {
background-color: #2CDBAF;
border-color: #2CDBAF;
color: #fff;
}
&.error {
border-color: #E64949;
color: #fff;
background-color: #E64949;
}
&.unselect {
border: 1px solid #E64949;
color: #E64949;
}
}
}
import React, { Component } from 'react';
import './index.scss'
import { html } from "@/utils"
import classnames from 'classnames'
class Question extends Component {
handleSelect = option => {
const {selectAnswer} = this.props
if (selectAnswer) {
selectAnswer(this.props.question, option.id)
this.setState({
selectedId: option.id,
})
}
}
render() {
const {question, answer, activeIndex, category} = this.props
return (
<div className={'question-container'}>
{
<div className="question">
{activeIndex !== undefined && `${activeIndex + 1}.`}
{category && <span className={'category'}>{category}</span>}&nbsp;
<span dangerouslySetInnerHTML={html(question.ques)}></span>
</div>
}
<ul className={'options'}>
{
!!question.options.length && question.options.map((item, index) => {
return <li key={item.id}
className={classnames({
active: answer && answer[question.id] === item.id,
error: item.user_select && !item.is_ans,
correct: item.is_ans,
})}
onClick={this.handleSelect.bind(this, item)}>
<div className={'letter'}>{String.fromCharCode(65 + index)}</div>
<div>{item.des}</div>
</li>
})
}
</ul>
</div>
);
}
}
export default Question;
\ No newline at end of file
.question-container {
padding-bottom: 20px;
.question {
font-size: 16px;
color: #222;
margin-bottom: 21px;
padding: 0 17px;
white-space: pre-line;
.category {
padding: 2px 5px 3px;
border: 1px solid #09f;
border-radius: 3px;
font-size: 12px;
color: #09f;
}
}
.options {
li {
display: flex;
min-height: 48px;
padding: 0 17px;
align-items: center;
font-size: 16px;
color: #333;
margin-bottom: 12px;
&.active {
background: #F8F8FB;
.letter {
color: #09f;
border-color: #09f;
}
}
&.correct {
.letter {
background-color: #29C8A0;
color: #fff;
border: 1px solid #29C8A0;
}
}
&.error {
.letter {
background-color: #FF5A5A;
color: #fff;
border: 1px solid #FF5A5A;
}
}
}
}
.letter {
flex: 0 0 auto;
width: 24px;
height: 24px;
margin-right: 13px;
border: 1px solid #999;
border-radius: 50%;
text-align: center;
line-height: 24px;
}
}
\ No newline at end of file
import React, { Component } from 'react';
import './index.scss'
import { http } from "@/utils"
import { Toast } from "antd-mobile";
import { Link } from "react-router-dom";
class Recommends extends Component {
state = {
recommends: [],
}
componentDidMount() {
this.props.typeId && this.getRecommends(this.props.typeId)
}
getRecommends = (typeId) => {
http.post(`${API.home}/sys/get_commend_course`, {
type_id: typeId,
}).then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.setState({
recommends: data,
})
} else {
Toast.fail(msg, 2, null, msg)
}
})
}
render() {
const {recommends} = this.state
return (
<div className="recommends">
<div>相关课程</div>
<ul>
{
!!recommends.length && recommends.map(item => {
return <li key={item.course_id}>
<Link to={`/detail?id=${item.course_id}`}><img src={item.image_name} alt=""/></Link>
</li>
})
}
</ul>
</div>
);
}
}
export default Recommends;
\ No newline at end of file
.recommends {
margin-top: 24px;
padding: 0 16px;
div:nth-child(1) {
font-size: 14px;
color: #09f;
margin-bottom: 15px;
}
ul {
display: flex;
flex-wrap: wrap;
}
li {
margin-right: 13px;
margin-bottom: 10px;
font-size: 0;
&:nth-child(2n) {
margin-right: 0;
}
}
img {
width: 165px;
height: 119px;
}
}
\ No newline at end of file
import React, { Component } from 'react';
import './index.scss'
import Question from '../common/question'
import { browser, getParam, http } from "@/utils"
import { Toast } from 'antd-mobile'
import storage from 'store2'
import Navigation from "@components/ai-test/common/navigation"
import { differenceInSeconds, differenceInMinutes, differenceInHours, differenceInDays, lightFormat } from "date-fns";
import { isEmpty } from 'lodash'
class Exam extends Component {
store = storage.namespace('aiTestExam')
timer = null
unlisten = null
state = {
questions: [],
activeQuestion: 0,
time: {
d: 0,
h: 0,
m: 0,
s: 0,
},
elapsed: 0,
answer: {},
recordId: undefined,
}
componentDidMount() {
if (this.store.get('submitted')) {
this.store.remove('submitted')
this.props.history.replace('/ai-test')
}
this.getQuestions()
this.unlisten = this.props.history.listen((location, action) => {
if (action === 'POP') {
this.store.clearAll()
}
this.unlisten()
})
}
componentWillUnmount() {
clearInterval(this.timer)
}
setCounter = () => {
this.timer = setInterval(() => {
this.setState(state => {
const s = state.elapsed + 1
return {
time: {
d: Math.floor(s / (60 * 60 * 24)),
h: Math.floor(s / (60 * 60)) % 24,
m: Math.floor(s / 60) % 60,
s: s % 60,
},
elapsed: s,
}
})
}, 1000)
}
goBack = () => {
const {state} = this.props.location
if (browser.isWeixin && getParam('code') && getParam('state')) {
window.history.go(-2)
}
if (state.records && state.records.length > 1) {
window.history.go(-1);
} else if (state.from && state.from.pathname) {
location.replace(`${state.from.pathname}${state.from.search}`)
} else {
window.location.href = window.location.origin
}
}
getQuestions = () => {
http.get(`${API.home}/sys/get_question`)
.then(res => {
const {code, msg, data} = res.data
if (code === 200) {
let answer = this.store.get('answer') || {}
if (isEmpty(answer)) {
data.forEach(item => {
answer[item.id] = 0
})
}
this.setState({
questions: data,
answer,
recordId: data[0].record_id,
})
this.getStartTime(data[0].create_time)
this.setCounter()
} else if (code === 23007) {
this.props.history.replace('/ai-test/scores')
} else {
Toast.fail(msg, 2, null, false)
}
})
}
getStartTime = (timestamp) => {
const now = new Date()
const createTime = new Date(timestamp * 1000)
this.setState({
time: {
d: differenceInDays(now, createTime),
h: differenceInHours(now, createTime) % 24,
m: differenceInMinutes(now, createTime) % 60,
s: differenceInSeconds(now, createTime) % 60,
},
elapsed: differenceInSeconds(now, createTime),
});
}
selectAnswer = (question, optionId) => {
this.setState(state => {
const answer = state.answer
answer[question.id] = optionId
return {
answer,
}
}, () => {
const {answer, questions} = this.state
const keys = Object.keys(answer)
const values = Object.values(answer)
if (keys.length === questions.length && values.every(item => item)) {
this.storeData()
}
})
}
storeData = () => {
const {answer, time, recordId, elapsed} = this.state
const {history} = this.props
clearInterval(this.timer)
this.store.setAll({time, answer, recordId, elapsed})
history.push('/ai-test/submit')
}
render() {
const {questions, activeQuestion, time, answer} = this.state
return (
<div className={'exam'}>
<header>
<div className="go-back">
<i className='iconfont iconiconfront-68' onClick={this.goBack}></i>
</div>
<div className="time">
<i className={'iconfont iconzhong'}></i>
<span>
{
!!time.d && time.d
}
{
!!time.h && <>{time.h && time.h.toString().padStart(2, '0')}:</>
}
{time.m.toString().padStart(2, '0')}:
{time.s.toString().padStart(2, '0')}
</span>
</div>
{/*
<div className="time">
<i className={'iconfont iconzhong'}></i>
<span>
{
!!time.d && time.d
}
{
!!time.h && <>{time.h && time.h.toString().padStart(2, '0')}:</>
}
{time.m.toString().padStart(2, '0')}:
{time.s.toString().padStart(2, '0')}
</span>
</div>
*/}
<div className="count"
onClick={this.storeData}>{Object.values(answer).filter(item => item).length}/{questions.length}</div>
</header>
<div className="banner">
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/ai-test/m/scores/exam-banner.png" alt=""/>
</div>
<div className="question-section">
{
!!questions.length &&
<Question activeIndex={activeQuestion} question={questions[activeQuestion]} selectAnswer={this.selectAnswer}
answer={answer}></Question>
}
</div>
<Navigation questions={questions} answer={answer} handleClick={(index) => {
this.setState({
activeQuestion: index,
});
}}/>
</div>
);
}
}
export default Exam;
\ No newline at end of file
html, body {
height: 100%;
}
body {
background-color: #f5f5f5;
}
.exam {
background-color: #fff;
padding-bottom: 55px;
header {
display: flex;
height: 49px;
padding: 0 12px;
align-items: center;
justify-content: space-between;
.iconfont {
font-size: 16px;
color: #222;
font-weight: 600;
}
.time {
display: flex;
align-items: center;
.iconfont {
margin-right: 8px;
}
span {
font-size: 18px;
}
}
.count {
font-size: 14px;
color: #09f;
}
}
.banner {
height: 94px;
margin-bottom: 18px;
img {
width: 100%;
height: 100%;
}
}
}
\ No newline at end of file
import React, { Component } from 'react';
import './index.scss'
import { Switch, Route } from 'react-router-dom'
import Scores from './scores'
import Exam from './exam'
import SubmitAnswer from "@components/ai-test/submit-answer"
import Analysis from "@components/ai-test/analysis"
import Assist from "@components/ai-test/assist"
import Help from '@components/ai-test/share'
import Report from '@components/ai-test/report'
class AiTest extends Component {
render() {
const {match} = this.props
return (
<Switch>
<Route path={`${match.path}/exam`} render={props => <Exam {...props}/>}/>
<Route path={`${match.path}/submit`} render={props => <SubmitAnswer {...props}/>}/>
<Route path={`${match.path}/analysis/:recordId`} render={props => <Analysis {...props}/>}/>
<Route path={`${match.path}/assist/:assistCode`} render={props => <Assist {...props}/>}/>
<Route path={`${match.path}/share`} render={props => <Help {...props}/>}/>
<Route path={`${match.path}/report`} render={props => <Report {...props}/>}/>
<Route render={(props) => <Scores {...props}/>}/>
</Switch>
);
}
}
export default AiTest;
\ No newline at end of file
.ai-test{
}
\ No newline at end of file
...@@ -8,6 +8,12 @@ import html2canvas from 'html2canvas' ...@@ -8,6 +8,12 @@ import html2canvas from 'html2canvas'
import scoreIconL from '@assets/image/score_icon-l.png' import scoreIconL from '@assets/image/score_icon-l.png'
import scoreIconR from '@assets/image/score_icon-r.png' import scoreIconR from '@assets/image/score_icon-r.png'
import scoreReportBg from '@assets/image/scoreReport_bg.png' import scoreReportBg from '@assets/image/scoreReport_bg.png'
import {connect} from "react-redux"
@connect(state => ({
user: state.user
}),
)
class scoreReport extends Component { class scoreReport extends Component {
...@@ -18,13 +24,20 @@ class scoreReport extends Component { ...@@ -18,13 +24,20 @@ class scoreReport extends Component {
cutIndex: 0, cutIndex: 0,
myRankList: '', myRankList: '',
imgUrl: '', imgUrl: '',
codeSrc: '' codeSrc: '',
avatar_file: ''
} }
} }
componentDidMount() { componentDidMount() {
this.handleFetchInfo(0) const {user, history} = this.props
if (user.hasError) {
history.push('/passport')
return
}
this.getCodeWe() this.getCodeWe()
this.handleFetchInfo(0)
} }
...@@ -35,14 +48,13 @@ class scoreReport extends Component { ...@@ -35,14 +48,13 @@ class scoreReport extends Component {
var w = parseInt(window.getComputedStyle(_canvas).width) var w = parseInt(window.getComputedStyle(_canvas).width)
var h = parseInt(window.getComputedStyle(_canvas).height) var h = parseInt(window.getComputedStyle(_canvas).height)
var scale = window.devicePixelRatio var scale = window.devicePixelRatio
//将canvas画布放大若干倍,然后盛放在较小的容器内,就显得不模糊了
canvas2.width = w * scale canvas2.width = w * scale
canvas2.height = h * scale canvas2.height = h * scale
var context = canvas2.getContext("2d") var context = canvas2.getContext("2d")
context.scale(1, 1) context.scale(1, 1)
html2canvas(document.getElementsByClassName('score-list')[0], {canvas: canvas2}).then(function (canvas) { html2canvas(document.getElementsByClassName('score-list')[0], {canvas: canvas2}).then(function (canvas) {
// document.body.appendChild(canvas); //document.body.appendChild(canvas);
let imgUrl = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream") let imgUrl = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream")
_this.setState({ _this.setState({
...@@ -54,7 +66,7 @@ class scoreReport extends Component { ...@@ -54,7 +66,7 @@ class scoreReport extends Component {
// 获取二维码 // 获取二维码
getCodeWe() { getCodeWe() {
let _this = this let _this = this
let qrCodeLink = 'www.julyedu.com' let qrCodeLink = '/ai-test'
return new Promise(resolve => { return new Promise(resolve => {
QRCode.toDataURL(qrCodeLink, {}, function (err, url) { QRCode.toDataURL(qrCodeLink, {}, function (err, url) {
_this.setState({ _this.setState({
...@@ -72,10 +84,49 @@ class scoreReport extends Component { ...@@ -72,10 +84,49 @@ class scoreReport extends Component {
this.setState({ this.setState({
myRankList: data myRankList: data
}) })
let avatar = this.props.user && this.props.user.data.avatar
this.getBase64(avatar)
setTimeout(()=> {
this.getCanvas() this.getCanvas()
},100)
}
})
}
getBase64Image = (img) => {
var canvas = document.createElement("canvas")
canvas.width = img.width
canvas.height = img.height
var ctx = canvas.getContext("2d")
ctx.drawImage(img, 0, 0, img.width, img.height)
var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase()
var dataURL = canvas.toDataURL("image/" + ext)
return dataURL
} }
getBase64 = (img) => {
let base64 = null
let image = new Image()
let timeStamp = +new Date()
image.setAttribute('crossOrigin', 'anonymous')
image.src = img + '?' + timeStamp
image.onload = () => {
base64 = this.getBase64Image(image)
this.setState({
avatar_file: base64
}) })
} }
image.onerror = () => {
console.log('onerror')
let timeStamp = +new Date()
this.getBase64(img + '?' + timeStamp)
}
}
change = (index) => { change = (index) => {
this.setState({ this.setState({
...@@ -86,15 +137,14 @@ class scoreReport extends Component { ...@@ -86,15 +137,14 @@ class scoreReport extends Component {
render() { render() {
const {tab, cutIndex, myRankList, codeSrc, imgUrl, isshow} = this.state const {tab, cutIndex, myRankList, codeSrc, imgUrl, avatar_file} = this.state
return ( return (
<> <div className={'score-content'}>
<HeaderBar <HeaderBar
title='成绩报告' title='成绩报告'
arrow={true} arrow={true}
home={false} home={false}
/> />
<div className={'score-report'}> <div className={'score-report'}>
<ul className={'tab-list'}> <ul className={'tab-list'}>
{ {
...@@ -147,6 +197,7 @@ class scoreReport extends Component { ...@@ -147,6 +197,7 @@ class scoreReport extends Component {
</table> </table>
<div className="comment text-overflow-4"> <div className="comment text-overflow-4">
<img className={'avatar_file'} src={avatar_file} alt=""/>
{myRankList && myRankList.title && myRankList.title.comment} {myRankList && myRankList.title && myRankList.title.comment}
</div> </div>
...@@ -163,7 +214,7 @@ class scoreReport extends Component { ...@@ -163,7 +214,7 @@ class scoreReport extends Component {
<div className="tip">长按图片分享给好友,或保存后分享到朋友圈</div> <div className="tip">长按图片分享给好友,或保存后分享到朋友圈</div>
</div> </div>
</> </div>
) )
} }
} }
......
html, body, #root { html, body, #root {
height: 100% !important; height: 100% !important;
} }
.score-content {
.score-report {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #09f; background-color: #09f;
padding: 30px 12px 70px 12px; }
.score-report {
width: 100%;
padding: 30px 12px 45px 12px;
color: #fff; color: #fff;
.tab-list { .tab-list {
...@@ -33,7 +35,7 @@ html, body, #root { ...@@ -33,7 +35,7 @@ html, body, #root {
.imgUrl { .imgUrl {
width: 100%; width: 100%;
height: 434px; height: 434px;
position:absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
} }
...@@ -50,6 +52,7 @@ html, body, #root { ...@@ -50,6 +52,7 @@ html, body, #root {
font-size: 22px; font-size: 22px;
color: #000; color: #000;
padding-top: 40px; padding-top: 40px;
img { img {
width: 16px; width: 16px;
height: 11px; height: 11px;
...@@ -104,13 +107,13 @@ html, body, #root { ...@@ -104,13 +107,13 @@ html, body, #root {
.has { .has {
color: #09f; color: #09f;
} }
.rank { .rank {
color: #FF2121; color: #FF2121;
} }
} }
.comment { .comment {
text-indent: 2em;
text-align: left; text-align: left;
color: #333; color: #333;
font-size: 14px; font-size: 14px;
...@@ -123,10 +126,12 @@ html, body, #root { ...@@ -123,10 +126,12 @@ html, body, #root {
position: absolute; position: absolute;
bottom: 40px; bottom: 40px;
right: 25px; right: 25px;
img { img {
width: 80px; width: 80px;
height: 80px; height: 80px;
} }
p { p {
color: #000; color: #000;
font-size: 12px; font-size: 12px;
...@@ -140,5 +145,13 @@ html, body, #root { ...@@ -140,5 +145,13 @@ html, body, #root {
font-size: 16px; font-size: 16px;
color: #fff; color: #fff;
} }
.avatar_file {
width: 49px;
height: 49px;
border-radius: 50%;
float: left;
margin-right: 10px;
}
} }
import React, { Component } from 'react';
import './index.scss'
import { Tabs, Toast } from "antd-mobile";
import { http } from "@/utils"
import storage from 'store2'
import { html } from '@/utils'
import { compareDesc } from "date-fns";
import { Link } from "react-router-dom";
class Scores extends Component {
store = storage.namespace('aiTestEntry')
state = {
tabs: [
{title: '当前成绩'},
{title: '今日最佳'},
{title: '本月最佳'},
],
rankList: [],
isExpandRankList: false,
type: 2,
icons: [
require('./rank-1.png'),
require('./rank-2.png'),
require('./rank-3.png'),
],
isShowRule: false,
isNeverShow: this.store.get('isNeverShow'),
pageState: {},
availableTestNum: 0,
userScore: {},
userAddress: {
name: '',
phone: '',
address: '',
},
isShowUserAddress: false,
}
componentDidMount() {
this.getInitialData()
this.getRankList()
this.getUserScores(0)
this.getUserAddress()
}
handleChange = (e) => {
const isNeverSHow = e.target.checked
this.setState({
isNeverSHow,
})
this.store.set('isNeverShow', isNeverSHow)
}
startTest = () => {
this.props.history.push('/ai-test/exam')
}
getInitialData = () => {
http.get(`${API.home}/sys/activity_data`)
.then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.setState({
pageState: data,
})
} else {
Toast.fail(msg, 2, null, false)
}
})
}
getRankList = () => {
http.get(`${API.home}/sys/at/ranks/${this.state.type}`)
.then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.setState({
rankList: data,
})
} else {
Toast.fail(msg, 2, null, false)
}
})
}
getUserScores = (type) => {
http.get(`${API.home}/sys/at/user_score/${type}`)
.then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.setState({
userScore: data,
})
} else {
Toast.fail(msg, 2, null, false)
}
})
}
getUserAddress = () => {
http.get(`${API.home}/sys/user_address_info`)
.then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.setState({
userAddress: data,
})
}
})
}
inputText = e => {
const key = e.target.name
const value = e.target.value
this.setState(state => {
return {
userAddress: {
...state.userAddress, ...{
[key]: value,
},
},
}
})
}
submitForm = (e) => {
e.preventDefault()
const {userAddress} = this.state
if (!Object.values(userAddress).every(item => !!item)) {
Toast.info('请填写完整')
return
}
http.post(`${API.home}/sys/update_address`, userAddress)
.then(res => {
const {code, msg, data} = res.data
if (code === 200) {
Toast.success('提交成功', 2, null, false)
this.setState({
isShowUserAddress: false,
})
} else {
Toast.fail(msg, 2, null, false)
}
})
}
render() {
const {
tabs,
rankList,
icons,
isExpandRankList,
isShowRule,
isNeverShow,
pageState,
userScore,
isShowUserAddress,
userAddress,
} = this.state
const _rankList = Array.isArray(rankList) ? isExpandRankList ? rankList : rankList.slice(0, 10) : []
return (
<div className={'scores'}>
<div className="banner">
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/ai-test/m/scores/banner.png" alt=""/>
</div>
<div className="info">
<span>已有{pageState.join_num}人参加测试</span>
<a href="javascript:void(0);" onClick={() => {
this.setState({
isShowRule: true,
})
}}>规则</a>
</div>
{
userScore.rank !== '-' &&
<div className="score-list">
<Tabs tabs={tabs} tabBarUnderlineStyle={{display: 'none'}} onChange={(tab, i) => {
this.getUserScores(i)
}}>
{
tabs.map((tab, index) => {
return <div className={'tab-content'} key={index}>
<table>
<thead>
<tr>
<th>分数</th>
<th>用时</th>
<th>最终排名</th>
</tr>
</thead>
<tbody>
<tr>
<td>{userScore.score} <Link to={`/ai-test/parse/${userScore.r_id}`}>解析</Link></td>
<td>{userScore.cost_time}</td>
<td>{userScore.rank}</td>
</tr>
</tbody>
</table>
</div>
})
}
</Tabs>
<div className="share">
<Link to={'/ai-test/report'}>分享</Link>
</div>
</div>
}
<div className="rank-list">
<div className="head">
<div>
测试排行榜
</div>
<div>
<span>仅显示前50</span>
<a href="javascript:void(0);" onClick={() => {
this.setState({
isShowUserAddress: true,
})
}}>收货地址</a>
</div>
</div>
<div className="list">
<table>
<thead>
<tr>
<th>名次</th>
<th>昵称</th>
<th>成绩</th>
<th>奖品</th>
</tr>
</thead>
<tbody>
{
!!_rankList.length && _rankList.map((item, index) => {
return <tr key={index}>
<td>
{
index < 3 ? <img src={icons[index]} alt=""/> : index + 1
}
</td>
<td>
<img src={item.avatar} className={'avatar'} alt=""/>
{item.user_name}
</td>
<td>
<span className={'score'}>{item.score}</span>/<span>{item.cost_time}</span>
</td>
<td>
{
item.prize_url ? <a href={item.prize_url}>{item.prize}</a> : item.prize
}
</td>
</tr>
})
}
</tbody>
</table>
{
!isExpandRankList ?
<div className="expand" onClick={() => {
this.setState({
isExpandRankList: true,
})
}}>
<span>
展开更多
<i className={'iconfont iconiconfront-69'}></i>
</span>
</div>
:
<div className="expand" onClick={() => {
this.setState({
isExpandRankList: false,
})
}}>
<span>
收起
<i className={'iconfont iconiconfront-71'}></i>
</span>
</div>
}
</div>
</div>
<div className="btn">
{
compareDesc(new Date(), pageState.stop_time * 1000) > 0 ?
pageState.daily_test_num > 0
? <button className={'available'} onClick={() => {
isNeverShow ? this.startTest() : this.setState({
isShowRule: true,
})
}}>开始测试<span>(今日可测试{pageState.daily_test_num}次)</span></button>
: <Link to={'/ai-test/share'}>
<button className={'get-chance'}>获取测试机会<span>(今日可测试0次)</span></button>
</Link>
: <button className={'unavailable'}>活动已结束</button>
}
</div>
{
isShowRule &&
<Rule rule={pageState.rule} startTest={this.startTest} neverShow={this.handleChange} isNeverShow={isNeverShow}
close={() => {
this.setState({
isShowRule: false,
})
}}/>
}
{
isShowUserAddress &&
<div className="user-address-wrapper">
<div className="user-address">
<div className="title">收货信息</div>
<div className="tip">获奖用户(以最终榜单为准)请及时填写收货信息</div>
<form action="" onSubmit={this.submitForm}>
<input type="text" placeholder={'收件人'} name={'name'} onChange={this.inputText}
value={userAddress.name}/>
<input type="tel" placeholder={'联系方式'} name={'phone'} onChange={this.inputText}
value={userAddress.phone}/>
<input type="text" placeholder={'收货地址'} name={'address'} onChange={this.inputText}
value={userAddress.address}/>
<button type={'submit'}
className={Object.values(userAddress).every(value => !!value) ? 'available' : ''}>提交
</button>
</form>
<i className={'close iconfont iconiconfront-2'} onClick={() => {
this.setState({
isShowUserAddress: false,
})
}}/>
</div>
</div>
}
</div>
);
}
}
function Rule({neverShow, isNeverShow, rule, close, startTest}) {
return <div className="rule-mask">
<div className="rule">
<div>测试规则</div>
<div dangerouslySetInnerHTML={html(rule)}></div>
<div className="option">
<input id={'never-show'} type="checkbox" onChange={neverShow} checked={isNeverShow}/>
<label htmlFor="never-show">不再提示</label>
</div>
<button onClick={startTest}>进入测试</button>
<i className={'close iconfont iconiconfront-2'} onClick={close}/>
</div>
</div>
}
export default Scores;
\ No newline at end of file
.scores {
$blue-bg: #e5f5ff;
background-color: #2e7ee9;
padding: 0 5px 60px;
.banner {
height: 171px;
margin: 0 -5px;
img {
width: 100%;
height: 100%;
}
}
.info {
position: relative;
height: 70px;
text-align: center;
line-height: 70px;
span {
color: $blue-bg;
font-size: 15px;
}
a {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
text-decoration: underline;
font-size: 14px;
color: #fff;
}
}
.score-list {
border-radius: 6px;
overflow: hidden;
margin-bottom: 30px;
.share {
height: 40px;
text-align: center;
font-size: 16px;
line-height: 40px;
color: #09f;
text-decoration: underline;
background-color: $blue-bg;
}
}
th {
font-weight: normal;
}
.am-tabs {
&-tar-bar-wrap {
border: 0;
}
&-default-bar {
padding: 10px 10px 0;
background-color: $blue-bg !important;
&-tab-active {
background-color: #fff;
color: #0D75E5;
}
&-tab {
width: 80px;
height: 40px;
border-radius: 4px 4px 0 0;
&::after {
display: none !important;
}
}
&-content {
padding-top: 2px;
line-height: 48px;
font-size: 15px;
color: #525B65;
}
}
.title {
display: flex;
justify-content: space-around;
height: 38px;
background-color: #FFD66A;
color: #333;
font-size: 14px;
line-height: 38px;
}
.tab-content {
box-sizing: border-box;
padding-top: 10px;
background-color: #fff;
}
table {
width: 100%;
margin: 0;
text-align: center;
border-collapse: collapse;
a {
color: #09f;
text-decoration: underline;
}
thead tr {
height: 38px;
background-color: #FFD66A;
color: #333;
font-size: 14px;
line-height: 38px;
}
tbody tr {
height: 50px;
line-height: 50px;
}
}
}
.rank-list {
border-radius: 6px;
overflow: hidden;
margin-bottom: 30px;
.head {
height: 68px;
padding-top: 12px;
background-color: $blue-bg;
text-align: center;
div:nth-child(1) {
font-size: 18px;
color: #0E75E6;
margin-bottom: 5px;
}
div:nth-child(2) {
position: relative;
span {
color: #666;
font-size: 12px;
}
a {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
text-decoration: underline;
font-size: 14px;
color: #077EE8;
}
}
}
.list {
background: #fff;
padding-top: 11px;
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
border: none;
text-align: center;
td, th {
margin-right: -1px;
padding: 0;
}
.avatar {
margin-right: 6px;
}
td:nth-of-type(1) {
width: 15%;
}
td:nth-of-type(2) {
width: 35%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: left;
//padding-left: 10%;
}
thead tr {
height: 38px;
line-height: 38px;
background-color: #FFD76A;
font-size: 14px;
color: #333;
}
tbody tr {
height: 49px;
line-height: 49px;
font-size: 12px;
color: #333;
td:nth-of-type(1) {
font-size: 16px;
img {
width: 14px;
height: 18px;
}
}
td:nth-of-type(2) {
img {
width: 18px;
height: 18px;
border-radius: 50%;
}
}
&:nth-child(even) {
background: #f6fbff;
}
img {
vertical-align: middle;
}
.score {
color: #09f;
}
}
}
}
.expand {
height: 49px;
line-height: 49px;
text-align: center;
background: #fff;
color: #525C65;
font-size: 14px;
.iconfont {
margin-left: 5px;
}
}
}
.btn {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
background-color: #fff;
padding: 10px;
color: #fff;
font-size: 0;
button {
width: 100%;
height: 100%;
font-size: 16px;
color: #fff;
border: none;
outline: 0;
-webkit-appearance: none;
&.get-chance {
background-color: #FFAE00;
}
&.available {
background: linear-gradient(-90deg, rgba(0, 153, 255, 1) 0%, rgba(61, 177, 255, 1) 100%);
}
&.unavailable {
background: #525C65;
}
}
span {
font-size: 12px;
}
}
.rule-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .8);
z-index: 100;
}
.rule {
position: absolute;
top: 19.7%;
left: 50%;
transform: translateX(-50%);
width: 300px;
height: 311px;
padding: 18px 25px;
background-color: #fff;
border-radius: 5px;
color: #525C65;
text-align: center;
div:nth-child(1) {
font-size: 16px;
margin-bottom: 15px;
}
div:nth-child(2) {
font-size: 14px;
margin-bottom: 16px;
}
label {
font-size: 14px;
color: #555;
opacity: .8;
}
.option {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 21px;
}
input {
position: relative;
width: 15px;
height: 15px;
margin-right: 8px;
border: 1px solid #3799ff;
border-radius: 2px;
-webkit-appearance: none;
&::after {
content: '';
position: absolute;
top: 2px;
left: 3px;
width: 10px;
height: 6px;
display: none;
border-bottom: 1px solid #3799ff;
border-left: 1px solid #3799ff;
transform-origin: 5px 4px;
transform: rotate(-45deg);
}
&:checked {
&::after {
display: block;
}
}
}
button {
width: 94px;
height: 30px;
background: #09f;
color: #fff;
font-size: 14px;
border-radius: 15px;
-webkit-appearance: none;
border: none;
outline: 0;
}
.close {
position: absolute;
bottom: -60px;
left: 50%;
transform: translateX(-50%);
color: #fff;
display: block;
font-size: 30px;
}
}
.user-address-wrapper {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, .6);
}
.user-address {
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: 300px;
height: 309px;
padding: 18px 26px 0;
background: #fff;
border-radius: 5px;
text-align: center;
.title {
font-size: 16px;
color: #525C65;
text-align: center;
margin-bottom: 17px;
}
.tip {
font-size: 12px;
color: #FFAE00;
margin-bottom: 15px;
text-align: left;
}
input {
width: 250px;
height: 40px;
padding-left: 10px;
border: 1px solid #DDD;
margin-bottom: 10px;
font-size: 13px;
&::placeholder {
color: #999;
}
}
button {
padding: 10px 45px;
font-size: 15px;
color: #fff;
background: rgba(82, 92, 101, .3);
border-radius: 17px;
border: none;
outline: 0;
-webkit-appearance: none;
&.available {
background-color: #09f;
}
}
.iconfont {
position: absolute;
bottom: -60px;
left: 50%;
transform: translateX(-50%);
font-size: 30px;
color: #fff;
}
}
}
\ No newline at end of file
import React, {Component} from 'react'
import {CopyToClipboard} from 'react-copy-to-clipboard'
import {browser, http, wxShare} from '@/utils'
import './index.scss'
import {Toast} from "antd-mobile"
class aiTestHelp extends Component {
constructor(props) {
super(props)
this.state = {
code: '',
total_num: '',
isshowYindao: false
}
}
componentDidMount() {
this.handleFetchInfo()
}
share = () => {
wxShare({
title: '全国AI水平测试开始了,看看你能答对几道题?',
desc: '',
link: `${API.m}/ai-test/assist/${this.state.code}`,
imgUrl: 'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/ai-test/m/share-img.png',
})
this.setState({
isshowYindao: true
})
}
copyToSuccess = () => {
Toast.info('链接已复制,快去发送给好友吧~')
}
handleFetchInfo = () => {
http.get(`${API.home}/sys/aitest/invitation`).then(res => {
const {code, data, msg} = res.data
if (code === 200) {
this.setState({
total_num: data.total_num,
code: data.code
})
} else if (code === 4030) {
this.props.history.push('/passport/login')
} else {
Toast.info(msg)
}
})
}
render() {
const {code, total_num, isshowYindao} = this.state
return (
<div className={'ai-test-help'}>
<div className="banner">
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/ai-test/m/text2.png" alt=""/>
<span>{total_num}</span>
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/ai-test/m/text1.png" alt=""/>
</div>
<p className={'tip-l'}>分享给好友</p>
<p className={'tip-s'}>每个好友只能助力1次哦~</p>
<p className={'tip-m'}>好友答题为你助力,答题正确你将获得</p>
<p className={'tip-num'}>一次测试机会</p>
{
!browser.isWeixin &&
<CopyToClipboard
text={`${API.m}/ai-test/assist/${code}`}
onCopy={this.copyToSuccess}
>
<div className={'share-btn'}>
邀请好友助力
</div>
</CopyToClipboard>
}
{
browser.isWeixin &&
<div className={'share-btn'} onClick={this.share}>
邀请好友助力
</div>
}
{
isshowYindao &&
<div className={'share'}>
<div className="content">
<p className="test">点击右上角,分享给好友</p>
<i className="iconfont"></i>
</div>
</div>
}
</div>
)
}
}
export default aiTestHelp
\ No newline at end of file
html, body, #root {
height: 100% !important;
}
.ai-test-help {
width: 100%;
height: 100%;
text-align: center;
letter-spacing: 1px;
.banner {
width: 100%;
height: 282px;
background-image: url("https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/ai-test/m/help-banner.png");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
padding-top: 60px;
img {
height: 18px;
}
span {
font-size: 25px;
font-weight: 400;
color: #FFCA28;
margin: 0 9px;
}
}
.tip-s {
margin-top: 10px;
font-weight: 400;
font-size: 12px;
color: #FF5F83;
}
.tip-m {
margin-top: 30px;
color: #525C65;
font-weight: 400;
font-size: 14px;
}
.tip-l {
margin-top: 30px;
color: #FF5F83;
font-weight: 500;
font-size: 18px;
}
.tip-num {
margin-top: 15px;
color: #09f;
font-weight: 600;
font-size: 20px;
&:before, &:after {
width: 13px;
height: 12px;
display: inline-block;
content: '';
background-repeat: no-repeat;
background-position: center;
background-size: contain;
vertical-align: text-top;
margin: 0 20px;
}
&:before {
background-image: url("https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/ai-test/m/mark_icon_l.png");
}
&:after {
background-image: url("https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/ai-test/m/mark_icon_r.png");
}
}
.share-btn {
height: 40px;
line-height: 40px;
border-radius: 5px;
position: fixed;
right: 10px;
left: 10px;
bottom: 5px;
background-color: #FFAE00;
color: #fff;
font-size: 18px;
font-weight: 400;
text-align: center;
}
.share {
position: fixed;
right: 0;
left: 0;
bottom: 0;
top: 0;
background: rgba(0, 0, 0, .6);
color: #fff;
.content {
position: relative;
height: 100px;
padding-top: 36px;
box-sizing: border-box;
font-size: 16px;
.iconfont {
position: absolute;
top: 10px;
right: 40px;
font-size: 38px;
color: #fff;
width: 50px;
height: 38px;
background: url('https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/pythonCourse/h5/share_arrow.png') center center no-repeat;
background-size: 100% 100%;
}
}
}
}
import React, { Component } from 'react';
import './index.scss'
import { HeaderBar } from "@common/index"
import storage from 'store2'
import { Toast } from "antd-mobile";
import { http } from "@/utils"
import classnames from 'classnames'
import { Link } from "react-router-dom";
import { isEmpty } from 'lodash'
class SubmitAnswer extends Component {
store = storage.namespace('aiTestExam')
timer = null
state = {
time: this.store.get('time') || {d: 0, h: 0, m: 0, s: 0},
answer: this.store.get('answer'),
recordId: this.store.get('recordId'),
analysis: [],
results: [],
elapsed: this.store.get('elapsed'),
}
componentDidMount() {
this.setCounter()
if (isEmpty(this.state.answer) || !this.state.recordId) {
this.props.history.replace('/ai-test')
}
}
componentWillUnmount() {
clearInterval(this.timer)
}
setCounter = () => {
this.timer = setInterval(() => {
this.setState(state => {
const s = state.elapsed + 1
return {
time: {
d: Math.floor(s / (60 * 60 * 24)),
h: Math.floor(s / (60 * 60)) % 24,
m: Math.floor(s / 60) % 60,
s: s % 60,
},
elapsed: s,
}
})
}, 1000)
}
submit = () => {
const {answer, recordId} = this.state
http.post(`${API.home}/sys/submit_answer`, {
answer: JSON.stringify(answer),
cost_time: this.state.elapsed,
record_id: recordId,
}).then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.store.clearAll()
this.store.set('submitted', true)
clearInterval(this.timer)
this.getAnalysis()
} else {
Toast.fail(msg, 2, null, false)
}
})
}
getAnalysis = () => {
http.post(`${API.home}/sys/get_analysis`, {
record_id: this.state.recordId,
}).then(res => {
const {code, msg, data} = res.data
if (code === 200) {
this.setState({
analysis: data,
});
} else {
Toast.fail(msg, 2, null, false)
}
})
}
render() {
const {time, answer, analysis, recordId} = this.state
return (
<div className={'submit-answer'}>
<HeaderBar title={'提交试卷'} arrow={true}/>
<div className="content">
<div className="cost">用时:
<span>
{
!!time.d && time.d
}
{
!!time.h && <>{time.h && time.h.toString().padStart(2, '0')}:</>
}
{time.m.toString().padStart(2, '0')}:
{time.s.toString().padStart(2, '0')}
</span>
</div>
{
analysis.length
? <>
<ul className={'answers'}>
{
analysis.map((item, index) => {
const userSelectIndex = item.options.findIndex(item => item.user_select)
const rightAnswerIndex = item.options.findIndex(item => item.is_ans)
return <li key={item.id}
className={classnames({
correct: userSelectIndex === rightAnswerIndex,
wrong: userSelectIndex >= 0 && userSelectIndex !== rightAnswerIndex,
unselect: userSelectIndex < 0,
})}>{index + 1}</li>
})
}
</ul>
{
!!analysis.length && <div className={'score'}>总分:{analysis[0].score}</div>
}
<Link to={`/ai-test/analysis/${recordId}`}>
<button>查看解析</button>
</Link>
</>
: <>
<ul className={'answers'}>
{
answer && !!Object.keys(answer).length && Object.keys(answer).map((item, index) => {
return <li key={item} className={answer[item] ? 'selected' : ''}>{index + 1}</li>
})
}
</ul>
<button onClick={this.submit}>提交</button>
</>
}
</div>
</div>
);
}
}
export default SubmitAnswer;
\ No newline at end of file
html, body {
height: 100%;
background-color: #fff;
}
.submit-answer {
.detail-header {
background-color: #fff;
border-bottom: 1px solid #DDD;
}
.content {
padding: 32px 16px;
}
.cost {
position: relative;
font-size: 18px;
color: #333;
text-align: center;
margin-bottom: 30px;
@mixin pseudo {
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
display: block;
width: 16px;
height: 11px;
background: url("./line.png") no-repeat;
background-size: contain;
}
$dis: 25%;
&::before {
@include pseudo;
left: $dis;
}
&::after {
@include pseudo;
transform: scale(-1, 1) translateY(-50%);
right: $dis;
}
}
.answers {
display: flex;
flex-wrap: wrap;
width: 80%;
margin: 0 auto 60px;
li {
$size: 34px;
width: $size;
height: $size;
margin-right: 25px;
margin-bottom: 16px;
line-height: $size;
border-radius: 50%;
border: 1px solid #09f;
color: #09f;
text-align: center;
font-size: 18px;
&:nth-of-type(5n) {
margin-right: 0;
}
&.selected {
background-color: #09f;
color: #fff;
}
&.wrong {
background-color: #E64949;
border-color: #E64949;
color: #fff;
}
&.unselect {
border: 1px solid #E64949;
color: #E64949;
}
&.correct {
background-color: #2CDBAF;
border-color: #2CDBAF;
color: #fff;
}
}
}
.score {
margin-bottom: 30px;
font-size: 21px;
color: #09f;
text-align: center;
}
button {
width: 343px;
height: 44px;
background: #09f;
border-radius: 22px;
-webkit-appearance: none;
outline: 0;
border: 0;
color: #fff;
font-size: 18px;
}
}
\ No newline at end of file
...@@ -40,188 +40,188 @@ export default [ ...@@ -40,188 +40,188 @@ export default [
{ {
path: '/', path: '/',
exact: true, exact: true,
component: Index component: Index,
}, },
{ {
path: '/vip/newvip', path: '/vip/newvip',
component: NewVip component: NewVip,
}, },
{ {
path: '/classify', path: '/classify',
component: Classify component: Classify,
}, },
{ {
path: '/study', path: '/study',
component: Study component: Study,
}, },
{ {
path: '/my', path: '/my',
component: My component: My,
}, },
{ {
path: '/myedit', path: '/myedit',
component: MyEdit, component: MyEdit,
isPrivate: true isPrivate: true,
}, },
{ {
path: '/courselist', path: '/courselist',
component: CourseList component: CourseList,
}, },
{ {
path: '/preferential', path: '/preferential',
component: Preferential component: Preferential,
}, },
{ {
path: '/search', path: '/search',
exact: true, exact: true,
component: Search component: Search,
}, },
{ {
path: '/search-result', path: '/search-result',
component: SearchResult component: SearchResult,
}, },
{ {
path: '/order', path: '/order',
component: Order, component: Order,
isPrivate: true isPrivate: true,
}, },
{ {
path: '/orderinfo', path: '/orderinfo',
component: Orderinfo component: Orderinfo,
}, },
{ {
path: '/detail', path: '/detail',
component: Detail component: Detail,
}, },
{ {
path: '/getDetail', path: '/getDetail',
component: Detail component: Detail,
}, },
{ {
path: '/examination', path: '/examination',
component: Examination component: Examination,
}, },
{ {
path: '/coupons', path: '/coupons',
component: Coupons, component: Coupons,
isPrivate: true isPrivate: true,
}, },
{ {
path: '/shopcart', path: '/shopcart',
component: ShopCart, component: ShopCart,
isPrivate: true isPrivate: true,
}, },
{ {
path: '/bargain-middle-page', path: '/bargain-middle-page',
component: BargainMiddlePage component: BargainMiddlePage,
}, },
{ {
path: '/passport', path: '/passport',
component: Passport component: Passport,
}, },
{ {
path: '/play', path: '/play',
component: Video, component: Video,
isPrivate: true isPrivate: true,
}, },
{ {
path: '/scholarship', path: '/scholarship',
component: Scholarship component: Scholarship,
}, },
{ {
path: '/document', path: '/document',
component: DrawDocument component: DrawDocument,
}, },
{ {
path: '/shareposter', path: '/shareposter',
component: sharePoster, component: sharePoster,
isPrivate: true isPrivate: true,
}, },
{ {
path: '/myorders', path: '/myorders',
component: myOrders, component: myOrders,
isPrivate: true isPrivate: true,
}, },
{ {
path: '/purchased', path: '/purchased',
component: Purchased, component: Purchased,
isPrivate: true isPrivate: true,
}, },
{ {
path: '/payOrder', path: '/payOrder',
component: PayOrder component: PayOrder,
}, },
{ {
path: '/campTest', path: '/campTest',
component: CampTest component: CampTest,
}, },
{ {
path: '/campResolve', path: '/campResolve',
component: CampResolve component: CampResolve,
}, },
// 分享领红包课程列表页 // 分享领红包课程列表页
{ {
path: '/ShareCourse', path: '/ShareCourse',
component: ShareCourse component: ShareCourse,
}, },
// 区号 // 区号
{ {
path: '/country', path: '/country',
component: Country component: Country,
}, },
{ {
path: '/togroup', path: '/togroup',
component: ToGroup component: ToGroup,
}, },
{ {
path: '/aist-share', path: '/aist-share',
component: loadable(() => import(/* webpackChunkName: 'aist-share'*/'@/components/share-page/aist-share')) component: loadable(() => import(/* webpackChunkName: 'aist-share'*/'@/components/share-page/aist-share')),
}, },
{ {
path: '/blessingRank', path: '/blessingRank',
component: loadable(() => import(/* webpackChunkName: 'blessing-rank' */'@/components/blessingRank/index')) component: loadable(() => import(/* webpackChunkName: 'blessing-rank' */'@/components/blessingRank/index')),
}, },
{ {
path: '/blessingPreheat', path: '/blessingPreheat',
component: loadable(() => import(/* webpackChunkName: 'blessing-preheat' */'@/components/blessingPreheat/index')) component: loadable(() => import(/* webpackChunkName: 'blessing-preheat' */'@/components/blessingPreheat/index')),
}, },
{ {
path: '/blessingGetPrize', path: '/blessingGetPrize',
component: loadable(() => import(/* webpackChunkName: 'blessing-getPrize' */'@/components/blessingGetPrize/index')) component: loadable(() => import(/* webpackChunkName: 'blessing-getPrize' */'@/components/blessingGetPrize/index')),
}, },
{ {
path: '/prize-winner-list', path: '/prize-winner-list',
component: loadable(() => import(/* webpackChunkName: 'prize-winner-list' */'@/components/activity/1111/prize-winner-list')) component: loadable(() => import(/* webpackChunkName: 'prize-winner-list' */'@/components/activity/1111/prize-winner-list')),
}, },
//定金订单页面 //定金订单页面
{ {
path: '/deposit-order', path: '/deposit-order',
component: loadable(() => import(/* webpackChunkName: 'deposit-order' */ '@components/order/deposit/deposit-order')) component: loadable(() => import(/* webpackChunkName: 'deposit-order' */ '@components/order/deposit/deposit-order')),
}, },
//定金支付页面 //定金支付页面
{ {
path: '/deposit-pay-order', path: '/deposit-pay-order',
component: loadable(() => import(/* webpackChunkName: 'deposit-pay-order' */ '@components/order/deposit/deposit-pay-order')) component: loadable(() => import(/* webpackChunkName: 'deposit-pay-order' */ '@components/order/deposit/deposit-pay-order')),
}, },
//尾款支付页面 //尾款支付页面
{ {
path: '/final-deposit-order', path: '/final-deposit-order',
component: loadable(() => import(/* webpackChunkName: 'deposit-pay-order' */ '@components/order/deposit/final-order')) component: loadable(() => import(/* webpackChunkName: 'deposit-pay-order' */ '@components/order/deposit/final-order')),
}, },
// 定金支付之后 // 定金支付之后
{ {
path: '/expand/callback', path: '/expand/callback',
component: ExpandCallback component: ExpandCallback,
}, },
// 定金-邀请好友助力 // 定金-邀请好友助力
{ {
path: '/expand/index', path: '/expand/index',
component: ExpandShare component: ExpandShare,
}, },
{ {
path: '/toAppDemo', path: '/toAppDemo',
component: loadable(() => import(/* webpackChunkName: 'aist-share'*/'@/components/blessingPreheat/toAppDemo')) component: loadable(() => import(/* webpackChunkName: 'aist-share'*/'@/components/blessingPreheat/toAppDemo')),
}, },
{ {
path: '/activity', path: '/activity',
...@@ -236,79 +236,79 @@ export default [ ...@@ -236,79 +236,79 @@ export default [
//双旦活动 //双旦活动
{ {
path: '/activity/newyear-2019/landing', path: '/activity/newyear-2019/landing',
component: loadable(() => import(/* webpackChunkName: 'newyear-2019-landing'*/ '@components/activity/newyear-2019/landing/index')) component: loadable(() => import(/* webpackChunkName: 'newyear-2019-landing'*/ '@components/activity/newyear-2019/landing/index')),
}, },
// 双旦活动预热页面 // 双旦活动预热页面
{ {
path: '/year/yearIndex', path: '/year/yearIndex',
component: loadable(() => import(/* webpackChunkName: 'newyear-yearIndex' */ '@components/activity/newyear-2019/preheat/index')) component: loadable(() => import(/* webpackChunkName: 'newyear-yearIndex' */ '@components/activity/newyear-2019/preheat/index')),
}, },
// 我的宝箱 // 我的宝箱
{ {
path: '/year/yearTreasure', path: '/year/yearTreasure',
component: loadable(() => import(/* webpackChunkName: 'newyear-yearIndex' */ '@components/activity/newyear-2019/myTreasure/index')) component: loadable(() => import(/* webpackChunkName: 'newyear-yearIndex' */ '@components/activity/newyear-2019/myTreasure/index')),
} }
, ,
// 双旦心愿单 // 双旦心愿单
{ {
path: '/year/yearWish', path: '/year/yearWish',
component: loadable(() => import(/* webpackChunkName: 'newyear-yearIndex' */ '@components/activity/newyear-2019/year-wish/index')) component: loadable(() => import(/* webpackChunkName: 'newyear-yearIndex' */ '@components/activity/newyear-2019/year-wish/index')),
}, },
// python 小课页面 // python 小课页面
{ {
path: '/python', path: '/python',
component: loadable(() => import(/* webpackChunkName: 'python-class'*/'@/components/python')) component: loadable(() => import(/* webpackChunkName: 'python-class'*/'@/components/python')),
}, },
{ {
path: '/pythonShare', path: '/pythonShare',
component: loadable(() => import('@/components/pythonShare')) component: loadable(() => import('@/components/pythonShare')),
}, },
{ {
path: '/pythonStudy', path: '/pythonStudy',
component: loadable(() => import('@/components/python/pythonStudy')) component: loadable(() => import('@/components/python/pythonStudy')),
}, },
// 赠一得一 // 赠一得一
{ {
path: '/active/givecourse', path: '/active/givecourse',
component: loadable(() => import(/* activity-give-courses */'@components/activity/give-courses/index')) component: loadable(() => import(/* activity-give-courses */'@components/activity/give-courses/index')),
}, },
{ {
path: '/active/assistance', path: '/active/assistance',
component: loadable(() => import(/* activity-give-courses-assistance */'@components/activity/give-courses/assistance/index')) component: loadable(() => import(/* activity-give-courses-assistance */'@components/activity/give-courses/assistance/index')),
}, },
{ {
path: '/active/to', path: '/active/to',
component: loadable(() => import(/* activity-give-courses-share */'@components/activity/give-courses/share-content/index')) component: loadable(() => import(/* activity-give-courses-share */'@components/activity/give-courses/share-content/index')),
}, },
{ {
path: '/wxerr', path: '/wxerr',
component: loadable(() => import(/* wx-err */ '@components/wxerr/index')) component: loadable(() => import(/* wx-err */ '@components/wxerr/index')),
}, },
// 助学计划落地页 // 助学计划落地页
{ {
path: '/college', path: '/college',
exact: true, exact: true,
component: loadable(() => import('@/components/college')) component: loadable(() => import('@/components/college')),
}, },
// 助学计划落地页 // 助学计划落地页
{ {
path: '/college/:id', path: '/college/:id',
exact: true, exact: true,
component: loadable(() => import('@/components/college/courseList')) component: loadable(() => import('@/components/college/courseList')),
}, },
//限时免费落地页 //限时免费落地页
{ {
path: '/free', path: '/free',
exact: true, exact: true,
component: loadable(() => import(/*limit-free*/'@/components/limit-free')) component: loadable(() => import(/*limit-free*/'@/components/limit-free')),
}, },
// 新的开宝箱活动-活动页 // 新的开宝箱活动-活动页
{ {
path: '/box/boxActive', path: '/box/boxActive',
component: loadable(() => import(/* webpackChunkName: 'treasure-box-home' */ '@components/activity/treasure-box/preheat/index')) component: loadable(() => import(/* webpackChunkName: 'treasure-box-home' */ '@components/activity/treasure-box/preheat/index')),
}, },
// 新的开宝箱活动-我的宝箱 // 新的开宝箱活动-我的宝箱
{ {
...@@ -318,25 +318,21 @@ export default [ ...@@ -318,25 +318,21 @@ export default [
// 新的开宝箱活动-扫码、分享页 // 新的开宝箱活动-扫码、分享页
{ {
path: '/box/landing', path: '/box/landing',
component: loadable(() => import(/* webpackChunkName: 'newyear-2019-landing'*/ '@components/activity/treasure-box/landing/index')) component: loadable(() => import(/* webpackChunkName: 'newyear-2019-landing'*/ '@components/activity/treasure-box/landing/index')),
}, },
//ML小课 //ML小课
{ {
path: '/ml', path: '/ml',
exact: true, exact: true,
component: loadable(() => import(/* ml */'@/components/ml')) component: loadable(() => import(/* ml */'@/components/ml')),
}, },
{ {
path: '/mlShare', path: '/mlShare',
component: loadable(() => import('@/components/mlShare')) component: loadable(() => import('@/components/mlShare')),
}, },
//ai水平测试
{ {
path: '/interactive-tutorial', path: '/ai-test',
component: loadable(() => import(/* webpackChunkName: 'interactive-tutorial' */'@/components/interactive-tutorial')) component: loadable(() => import('@/components/ai-test')),
}, },
// AI水平测试 成绩报告
{
path: '/scoreReport',
component: loadable(() => import('@/components/scoreReport'))
}
] ]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment