Browse Source

feat(front): 登录处理

Go 4 years ago
parent
commit
d8672988f5

+ 8 - 0
front/project/admin/stores/system.js

@@ -157,6 +157,14 @@ export default class SystemStore extends BaseStore {
     return this.apiPut('/setting/tips', params);
   }
 
+  getSentenceInfo() {
+    return this.apiGet('/setting/sentence_info');
+  }
+
+  setTSentenceInfo(params) {
+    return this.apiPut('/setting/sentence_info', params);
+  }
+
   listRank(params) {
     return this.apiGet('/setting/rank/list', params);
   }

+ 20 - 6
front/project/h5/stores/user.js

@@ -10,6 +10,11 @@ export default class UserStore extends BaseStore {
     return { login: false };
   }
 
+  infoHandle(result) {
+    this.setToken(result.token);
+    this.setState({ login: true, needLogin: false, info: result, username: result.username });
+  }
+
   originInviteCode(inviteCode) {
     this.setState({
       inviteCode,
@@ -19,8 +24,14 @@ export default class UserStore extends BaseStore {
   /**
    * 验证token
    */
-  token() {
-    return this.apiPost('/auth/token');
+  refreshToken() {
+    return this.apiPost('/auth/token')
+      .then(result => {
+        this.infoHandle(result);
+      })
+      .catch(() => {
+        this.logout(false);
+      });
   }
 
   /**
@@ -34,13 +45,16 @@ export default class UserStore extends BaseStore {
     if (!inviteCode) {
       ({ inviteCode } = this.state);
     }
-    return this.apiPost('/auth/login', { area, mobile, mobileVerifyCode, inviteCode });
+    return this.apiPost('/auth/login', { area, mobile, mobileVerifyCode, inviteCode }).then(result => {
+      this.infoHandle(result);
+      return result;
+    });
   }
 
   loginWechat(code) {
-    return this.apiGet('/auth/wechat', { code }).then((info) => {
-      this.setState({ login: true, info });
-      return info;
+    return this.apiGet('/auth/wechat', { code }).then((result) => {
+      this.infoHandle(result);
+      return result;
     });
   }
 

+ 198 - 46
front/project/www/components/Login/index.js

@@ -2,12 +2,15 @@ import React, { Component } from 'react';
 import './index.less';
 import { Modal, Icon, Button, Tooltip } from 'antd';
 import Assets from '@src/components/Assets';
+import { asyncSMessage } from '@src/services/AsyncTools';
 import { Icon as GIcon } from '../Icon';
 import { Button as GButton } from '../Button';
 import { User } from '../../stores/user';
+import { My } from '../../stores/my';
+import { Common } from '../../stores/common';
+import { MobileArea } from '../../../Constant';
 
 const LOGIN_PHONE = 'LOGIN_PHONE';
-const REGISTER_PHONE = 'REGISTER_PHONE';
 const LOGIN_WX = 'LOGIN_WX';
 const BIND_PHONE = 'BIND_PHONE';
 const BIND_WX = 'BIND_WX';
@@ -23,23 +26,142 @@ export default class Login extends Component {
     User.closeLogin();
   }
 
+  login() {
+    const { data, needEmail, mobileError, validError } = this.state;
+    const { area, mobile, mobileVerifyCode, email } = data;
+    if (mobileError !== '' || validError !== '') return;
+    if (area === '' || mobile === '' || mobileVerifyCode === '') return;
+    if (needEmail && email === '') return;
+    User.login(area, mobile, mobileVerifyCode).then(() => {
+      let handler = null;
+      if (needEmail) {
+        handler = My.bindEmail(email);
+      } else {
+        handler = Promise.resolve();
+      }
+      handler.then((result) => {
+        if (result.bindWechat) {
+          this.close();
+        } else {
+          this.setState({ type: BIND_WX });
+        }
+      });
+    })
+      .catch(err => {
+        if (err.message.indexOf('验证码') >= 0) {
+          this.setState({ validError: err.message });
+        } else {
+          this.setState({ mobileError: err.message });
+        }
+      });
+  }
+
+  bind() {
+    const { data, needEmail, mobileError, validError } = this.state;
+    const { area, mobile, mobileVerifyCode, email } = data;
+    if (mobileError !== '' || validError !== '') return;
+    if (area === '' || mobile === '' || mobileVerifyCode === '') return;
+    if (needEmail && email === '') return;
+    User.bind(area, mobile, mobileVerifyCode).then(() => {
+      let handler = null;
+      if (needEmail) {
+        handler = My.bindEmail(email);
+      } else {
+        handler = Promise.resolve();
+      }
+      handler.then(() => {
+        this.close();
+      });
+    })
+      .catch(err => {
+        if (err.message.indexOf('验证码') >= 0) {
+          this.setState({ validError: err.message });
+        } else {
+          this.setState({ mobileError: err.message });
+        }
+      });
+  }
+
+  scanLogin() {
+    User.loginWechat('').then((result) => {
+      if (result.id) {
+        this.close();
+      } else {
+        this.setState({ type: BIND_PHONE });
+      }
+    });
+  }
+
+  scanBind() {
+    User.loginWechat('').then(() => {
+      this.close();
+    }).catch(err => {
+      this.setState({ type: BIND_WX_ERROR, wechatError: err.message });
+    });
+  }
+
+  changeData(field, value) {
+    let { data } = this.state;
+    data = data || {};
+    data[field] = value;
+    this.setState({ data });
+  }
+
+  validMobile() {
+    const { data } = this.state;
+    const { area, mobile } = data;
+    if (area === '' || mobile === '') return;
+    User.validWechat(area, mobile)
+      .then(result => {
+        if (result) {
+          this.setState({ mobileError: '' });
+          return User.validMobile(area, mobile)
+            .then(r => {
+              this.setState({ needEmail: r });
+            });
+        }
+        this.setState({ needEmail: false });
+        return Promise.reject(new Error('该手机已绑定其他账号,请更换手机号码'));
+      })
+      .catch(err => {
+        this.setState({ mobileError: err.message });
+      });
+  }
+
+  sendValid() {
+    const { data, mobileError } = this.state;
+    const { area, mobile } = data;
+    if (area === '' || mobile === '' || mobileError !== '') return Promise.reject();
+    return Common.sendSms(area, mobile)
+      .then((result) => {
+        if (result) {
+          asyncSMessage('发送成功');
+          this.setState({ mobileError: '', validError: '' });
+        } else {
+          throw new Error('发送失败');
+        }
+      })
+      .catch(err => {
+        this.setState({ mobileError: err.message });
+        throw err;
+      });
+  }
+
   render() {
     const { type } = this.state;
     const { user } = this.props;
     return (
       <Modal wrapClassName={`login-modal ${type}`} visible={user.needLogin} footer={null} closable={false} width={470}>
         {this.renderBody(type)}
-        <GIcon name="close" onClick={() => this.close()} />
+        <GIcon name="close" onClick={() => {
+          this.close();
+        }} />
       </Modal>
     );
   }
 
   renderBody(type) {
     switch (type) {
-      case LOGIN_PHONE:
-        return this.renderLoginPhone();
-      case REGISTER_PHONE:
-        return this.renderRegisterPhone();
       case LOGIN_WX:
         return this.renderLoginWx();
       case BIND_PHONE:
@@ -48,45 +170,42 @@ export default class Login extends Component {
         return this.renderBindWx();
       case BIND_WX_ERROR:
         return this.renderBindWxError();
+      case LOGIN_PHONE:
       default:
-        return this.LOGIN_PHONE();
+        return this.renderLoginPhone();
     }
   }
 
   renderLoginPhone() {
+    const { needEmail } = this.state;
     return (
       <div className="body">
         <div className="title">手机号登录</div>
-        <SelectInput placeholder="请输入手机号" />
-        <VerificationInput placeholder="请输入验证码" />
-        <Button type="primary" size="large" block>
-          登录
-        </Button>
-        <Tooltip overlayClassName="gray" placement="left" title="微信扫码登录">
-          <a className="other">
-            <Assets name="code" />
-          </a>
-        </Tooltip>
-      </div>
-    );
-  }
-
-  renderRegisterPhone() {
-    return (
-      <div className="body">
-        <div className="title">手机号登录</div>
-        <div className="tip">
+        <div className="tip" hidden={!needEmail}>
           <Assets name="notice" />
           该手机号尚未注册,将自动为您注册账户
         </div>
-        <SelectInput placeholder="请输入手机号" />
-        <VerificationInput placeholder="请输入验证码" />
-        <Input placeholder="请输入邮箱" />
-        <Button type="primary" size="large" block>
+        <SelectInput placeholder="请输入手机号" selectValue={this.state.data.area} select={MobileArea} value={this.state.data.mobile} error={this.state.mobileError} onSelect={(value) => {
+          this.changeData('area', value);
+        }} onChange={(e) => {
+          this.changeData('mobile', e.target.value);
+          this.validMobile(e.target.value);
+        }} />
+        <VerificationInput placeholder="请输入验证码" value={this.state.data.mobileVerifyCode} error={this.state.validError} onSend={() => {
+          return this.sendValid();
+        }} />
+        {needEmail && <Input placeholder="请输入邮箱" value={this.state.data.email} onChange={(e) => {
+          this.changeData('email', e.target.value);
+        }} />}
+        <Button type="primary" size="large" block onClick={() => {
+          this.login();
+        }}>
           登录
         </Button>
         <Tooltip overlayClassName="gray" placement="left" title="微信扫码登录">
-          <a className="other">
+          <a className="other" onClick={() => {
+            this.setState({ type: LOGIN_WX });
+          }}>
             <Assets name="code" />
           </a>
         </Tooltip>
@@ -103,7 +222,9 @@ export default class Login extends Component {
           <div className="text">请使用微信扫描二维码登录</div>
         </div>
         <Tooltip overlayClassName="gray" placement="left" title="手机号登录">
-          <a className="other">
+          <a className="other" onClick={() => {
+            this.setState({ type: LOGIN_PHONE });
+          }}>
             <Assets name="phone" />
           </a>
         </Tooltip>
@@ -112,6 +233,7 @@ export default class Login extends Component {
   }
 
   renderBindPhone() {
+    const { needEmail } = this.state;
     return (
       <div className="body">
         <div className="title">绑定手机号</div>
@@ -119,10 +241,21 @@ export default class Login extends Component {
           <Assets name="notice" />
           微信登录成功!为更好的使用服务,请您绑定手机号和邮箱。
         </div>
-        <SelectInput placeholder="请输入手机号" />
-        <VerificationInput placeholder="请输入验证码" />
-        <Input placeholder="请输入邮箱" />
-        <Button type="primary" size="large" block>
+        <SelectInput placeholder="请输入手机号" selectValue={this.state.data.area} select={MobileArea} value={this.state.data.mobile} error={this.state.mobileError} onSelect={(value) => {
+          this.changeData('area', value);
+        }} onChange={(e) => {
+          this.changeData('mobile', e.target.value);
+          this.validMobile(e.target.value);
+        }} />
+        <VerificationInput placeholder="请输入验证码" value={this.state.data.mobileVerifyCode} error={this.state.validError} onSend={() => {
+          return this.sendValid();
+        }} />
+        {needEmail && <Input placeholder="请输入邮箱" value={this.state.data.email} onChange={(e) => {
+          this.changeData('email', e.target.value);
+        }} />}
+        <Button type="primary" size="large" block onClick={() => {
+          this.bind();
+        }}>
           绑定
         </Button>
       </div>
@@ -140,7 +273,9 @@ export default class Login extends Component {
         <div className="qr-code">
           <Assets name="qrcode" />
           <div className="text">请使用微信扫描二维码登录</div>
-          <div className="jump">跳过</div>
+          <div className="jump" onClick={() => {
+            this.close();
+          }}>跳过</div>
         </div>
       </div>
     );
@@ -152,7 +287,9 @@ export default class Login extends Component {
         <div className="title">绑定失败</div>
         <div className="text">该微信账户已绑定其他手机号,您可直接使用微信登入。</div>
         <div className="btn">
-          <GButton radius>Ok</GButton>
+          <GButton radius onClick={() => {
+            this.close();
+          }}>Ok</GButton>
         </div>
       </div>
     );
@@ -161,12 +298,12 @@ export default class Login extends Component {
 
 class Input extends Component {
   render() {
-    const { className = '', onChange, placeholder, error, left, right } = this.props;
+    const { className = '', onChange, placeholder, value, error, left, right } = this.props;
     return (
       <div className={`g-input-container ${className}`}>
         <div className={`g-input-wrapper ${error ? 'error' : ''}`}>
           {left && <div className="g-input-left">{left}</div>}
-          <input className="g-input" placeholder={placeholder} onChange={data => onChange && onChange(data)} />
+          <input className="g-input" placeholder={placeholder} value={value} onChange={data => onChange && onChange(data)} />
           {right && <div className="g-input-right">{right}</div>}
         </div>
         <div hidden={!error} className="g-input-error">
@@ -178,26 +315,39 @@ class Input extends Component {
 }
 
 class SelectInput extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { showSelect: false };
+  }
+
   render() {
-    const { className = '', onChange, placeholder, value, selectValue, onSelect } = this.props;
+    const { showSelect } = this.state;
+    const { className = '', onChange, placeholder, value, error, selectValue, select, onSelect } = this.props;
     return (
       <Input
         className={className}
         left={
-          <span className="g-input-left-select" onClick={() => onSelect && onSelect()}>
+          <span className="g-input-left-select" onClick={() => this.setState({ showSelect: !showSelect })}>
             {selectValue}
-            <Icon type="down" />
+            <Icon type={showSelect ? 'up' : 'down'} />
+            {showSelect && <ul className="select-list">{select.map((row) => {
+              return <li onClick={() => {
+                this.setState({ showSelect: false });
+                if (onSelect) onSelect(row.value);
+              }}>{row.label}</li>;
+            })}</ul>}
           </span>
         }
         value={value}
         placeholder={placeholder}
         onChange={data => onChange && onChange(data)}
+        error={error}
       />
     );
   }
 }
 
-class VerificationInput extends Component {
+export class VerificationInput extends Component {
   constructor(props) {
     super(props);
     this.timeKey = null;
@@ -211,9 +361,11 @@ class VerificationInput extends Component {
   onSend() {
     const { onSend, time = 60 } = this.props;
     if (onSend) {
-      onSend();
+      onSend()
+        .then(() => {
+          this.setTime(time);
+        });
     }
-    this.setTime(time);
   }
 
   setTime(time) {

+ 2 - 2
front/project/www/routes/paper/process/page.js

@@ -291,9 +291,9 @@ export default class extends Page {
     if (!paper.id || !scene) return null;
     switch (paper.paperModule) {
       case 'sentence':
-        return <Sentence key={userQuestion.id} {...this.state} flow={this} />;
+        return <Sentence key={userQuestion.id} {...this.state} flow={this} mode='process' />;
       default:
-        return <Base key={userQuestion.id} {...this.state} flow={this} />;
+        return <Base key={userQuestion.id} {...this.state} flow={this} mode='process' />;
     }
   }
 }

+ 62 - 19
front/project/www/routes/paper/process/sentence/index.js

@@ -1,9 +1,10 @@
 import React, { Component } from 'react';
 import './index.less';
 import Assets from '@src/components/Assets';
-import { formatSecond, formatPercent } from '@src/services/Tools';
+import { formatSecond, formatPercent, formatSeconds } from '@src/services/Tools';
 import Icon from '../../../../components/Icon';
 import Button from '../../../../components/Button';
+import Switch from '../../../../components/Switch';
 import Tabs from '../../../../components/Tabs';
 import Progress from '../../../../components/Progress';
 import HardInput from '../../../../components/HardInput';
@@ -113,20 +114,58 @@ export default class extends Component {
     return stem;
   }
 
+  renderHeader() {
+    const { mode } = this.props;
+    switch (mode) {
+      case 'process':
+        return this.renderProcessHeader();
+      default:
+        return this.renderQuestionHeader();
+    }
+  }
+
+  renderProcessHeader() {
+    const { userQuestion, singleTime, paper, flow } = this.props;
+    return <div className="layout-header">
+      <div className="left">
+        <div className="title">{paper.title}</div>
+      </div>
+      <div className="right">
+        <div className="text"><Assets name='timecost_icon' />Time cost {formatSecond(userQuestion.userTime || singleTime)}</div>
+        <Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
+      </div>
+    </div>;
+  }
+
+  renderQuestionHeader() {
+    const { userQuestion, questionNo, paper, flow } = this.props;
+    return <div className="layout-header">
+      <div className="left">
+        <div className="title">{paper.title}</div>
+      </div>
+      <div className="right">
+        <span className="b">
+          用时:<span dangerouslySetInnerHTML={{ __html: formatSeconds(userQuestion.userTime).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
+          {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
+        </span>
+        <span className="b">
+          全站:<span dangerouslySetInnerHTML={{ __html: formatSeconds(questionNo.totalTime / questionNo.totalNumber).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
+          {/* 全站:<span className="s">1</span>m<span className="s">39</span>s */}
+        </span>
+        <span className="b">
+          <span className="s">{formatPercent(questionNo.totalCorrect, questionNo.totalNumber)}</span>%
+        </span>
+        <Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
+      </div>
+    </div>;
+  }
+
   render() {
-    const { flow, paper, userQuestion, singleTime } = this.props;
+    const { flow, paper, userQuestion } = this.props;
     return (
       <div id='paper-process-sentence'>
         <div className="layout">
-          <div className="layout-header">
-            <div className="left">
-              <div className="title">{paper.title}</div>
-            </div>
-            <div className="right">
-              <div className="text"><Assets name='timecost_icon' />Time cost {formatSecond(userQuestion.userTime || singleTime)}</div>
-              <Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
-            </div>
-          </div>
+          {this.renderHeader()}
           {this.renderBody()}
           <div className="layout-footer">
             <div className="left">
@@ -222,16 +261,20 @@ export default class extends Component {
   }
 
   renderAnswer() {
-    const { analysisTab, question, userQuestion, stem } = this.state;
+    const { mode } = this.props;
+    const { analysisTab, question, userQuestion, stem, showAnswer } = this.state;
     const { userAnswer = {} } = userQuestion;
     const { answer } = question;
     return <div className="layout-body">
-      <div className="title"><Icon name="question" />请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。</div>
+      {mode === 'question' ? <Switch checked={showAnswer} onChange={(value) => {
+        this.setState({ showAnswer: value });
+      }}>{showAnswer ? '显示答案' : '关闭答案'}</Switch> : <div className="title"><Icon name="question" />请分别找出句子中的主语,谓语和宾语,并做出逻辑关系判断。</div>}
+
       <div className="desc" dangerouslySetInnerHTML={{ __html: stem }} />
       <div className="label">主语</div>
       <div className="input">
         <HardInput
-          show
+          show={showAnswer}
           list={userAnswer.subject || []}
           answer={answer.subject}
         />
@@ -239,7 +282,7 @@ export default class extends Component {
       <div className="label">谓语</div>
       <div className="input">
         <HardInput
-          show
+          show={showAnswer}
           list={userAnswer.predicate || []}
           answer={answer.predicate}
         />
@@ -247,16 +290,16 @@ export default class extends Component {
       <div className="label">宾语</div>
       <div className="input">
         <HardInput
-          show
+          show={showAnswer}
           list={userAnswer.object || []}
           answer={answer.object}
         />
       </div>
       <div className="select">
         <div className="select-title">本句存在以下哪种逻辑关系?(可多选)</div>
-        <AnswerCheckbox show list={SentenceOption} selected={userAnswer.options} answer={answer.options} />
+        <AnswerCheckbox show={showAnswer} list={SentenceOption} selected={userAnswer.options} answer={answer.options} />
       </div>
-      <div className="analysis">
+      {showAnswer && <div className="analysis">
         <Tabs
           type="division"
           active={analysisTab}
@@ -266,7 +309,7 @@ export default class extends Component {
           }}
         />
         {this.renderText()}
-      </div>
+      </div>}
     </div>;
   }
 

+ 12 - 0
front/project/www/routes/paper/process/sentence/index.less

@@ -41,6 +41,18 @@
             margin-right: 5px;
           }
         }
+
+        .b {
+          margin-left: 30px;
+
+          .s {
+            color: #4299FF;
+          }
+        }
+
+        .icon {
+          margin-left: 10px;
+        }
       }
     }
   }

+ 56 - 38
front/project/www/routes/paper/question/page.js

@@ -210,51 +210,69 @@ export default class extends Page {
     const { report = {} } = this.state;
     switch (report.paperModule) {
       case 'sentence':
-        return <Sentence {...this.state} flow={this} scene='answer' />;
+        return <Sentence {...this.state} flow={this} scene='answer' mode='question' />;
       default:
         return <div className='base'>{this.renderBase()}</div>;
     }
   }
 
-  renderBase() {
-    const { questionStatus, userQuestion = {}, questionNo = {}, paper = {}, showIds, questionNos = [] } = this.state;
+  renderHeader() {
+    const { report = {} } = this.state;
+    switch (report.paperModule) {
+      case 'examination':
+        return this.renderExaminationHeader();
+      default:
+        return this.renderExerciseHeader();
+    }
+  }
+
+  renderExaminationHeader() {
 
+  }
+
+  renderExerciseHeader() {
+    const { userQuestion = {}, questionNo = {}, paper = {}, showIds, questionNos = [] } = this.state;
+    return <div className="layout-header">
+      <div className="left">
+        <div className="no">No.{userQuestion.stageNo || userQuestion.no}</div>
+        <div className="title"><Assets name='book' />{paper.title}</div>
+      </div>
+      <div className="center">
+        <div className="menu-wrap">
+          ID:{questionNo.title}
+          {questionNos && questionNos.length > 0 && <Icon name="more" onClick={() => {
+            this.setState({ showIds: true });
+          }} />}
+          {showIds && <div className='menu-content'>
+            <p>题源汇总</p>
+            {(questionNos || []).map((row) => <p>ID:{row.title}</p>)}
+          </div>}
+        </div>
+      </div>
+      <div className="right">
+        <span className="b">
+          用时:<span dangerouslySetInnerHTML={{ __html: formatSeconds(userQuestion.userTime).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
+          {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
+        </span>
+        <span className="b">
+          全站:<span dangerouslySetInnerHTML={{ __html: formatSeconds(questionNo.totalTime / questionNo.totalNumber).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
+          {/* 全站:<span className="s">1</span>m<span className="s">39</span>s */}
+        </span>
+        <span className="b">
+          <span className="s">{formatPercent(questionNo.totalCorrect, questionNo.totalNumber)}</span>%
+        </span>
+        <Icon name="question" />
+        <Icon name="star" active={userQuestion.collect} onClick={() => this.toggleCollect()} />
+      </div>
+    </div>;
+  }
+
+  renderBase() {
+    const { questionStatus, userQuestion = {}, showIds } = this.state;
     return <div className="layout" onClick={() => {
       if (showIds) this.setState({ showIds: false });
     }}>
-      <div className="layout-header">
-        <div className="left">
-          <div className="no">No.{userQuestion.no}</div>
-          <div className="title"><Assets name='book' />{paper.title}13</div>
-        </div>
-        <div className="center">
-          <div className="menu-wrap">
-            ID:{questionNo.title}
-            {questionNos && questionNos.length > 0 && <Icon name="more" onClick={() => {
-              this.setState({ showIds: true });
-            }} />}
-            {showIds && <div className='menu-content'>
-              <p>题源汇总</p>
-              {(questionNos || []).map((row) => <p>ID:{row.title}</p>)}
-            </div>}
-          </div>
-        </div>
-        <div className="right">
-          <span className="b">
-            用时:<span dangerouslySetInnerHTML={{ __html: formatSeconds(userQuestion.userTime).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
-            {/* 用时:<span className="s">1</span>m<span className="s">39</span>s */}
-          </span>
-          <span className="b">
-            全站:<span dangerouslySetInnerHTML={{ __html: formatSeconds(questionNo.totalTime / questionNo.totalNumber).replace(/([0-9]+)([msh])/g, '<span class="s">$1</span>$2') }} />
-            {/* 全站:<span className="s">1</span>m<span className="s">39</span>s */}
-          </span>
-          <span className="b">
-            <span className="s">{formatPercent(questionNo.totalCorrect, questionNo.totalNumber)}</span>%
-          </span>
-          <Icon name="question" />
-          <Icon name="star" active={userQuestion.collect} onClick={() => this.toggleCollect()} />
-        </div>
-      </div>
+      {this.renderHeader()}
       <div className="layout-body">{this.renderBody()}</div>
       <div className="layout-footer">
         <div className="left">
@@ -276,8 +294,8 @@ export default class extends Page {
           <AnswerButton className="item" onClick={() => this.setState({ feedbackModal: true })}>纠错</AnswerButton>
         </div>
         <div className="right">
-          <Icon name="prev" onClick={() => this.prevQuestion()} />
-          <Icon name="next" onClick={() => this.nextQuestion()} />
+          {userQuestion.no !== 1 && <Icon name="prev" onClick={() => this.prevQuestion()} />}
+          {userQuestion.questionNumber !== userQuestion.no && <Icon name="next" onClick={() => this.nextQuestion()} />}
         </div>
       </div>
       {this.state.askModal && this.renderAsk()}

+ 1 - 0
front/project/www/routes/paper/report/index.js

@@ -3,6 +3,7 @@ export default {
   key: 'paper-report',
   title: '报告',
   needLogin: true,
+  hideHeader: true,
   component() {
     return import('./page');
   },

+ 46 - 16
front/project/www/routes/paper/report/page.js

@@ -210,8 +210,13 @@ const pieOption = {
 };
 
 export default class extends Page {
+  initState() {
+    return { report: { paperModule: 'exercise' } };
+  }
+
   initData() {
     const { id } = this.params;
+    const { page = '' } = this.state.search;
     Question.detailReport(id).then(result => {
       switch (result.paperModule) {
         case 'sentence':
@@ -230,22 +235,49 @@ export default class extends Page {
       }
       this.setState({ report: result });
     });
+    switch (page) {
+      case 'question':
+        // 题目回顾列表
+        Question.questionReport(id).then(result => {
+          this.setState({ list: result });
+        });
+        break;
+      default:
+        break;
+    }
   }
 
   refreshSentence() {
-
+    const { page = '' } = this.state.search;
+    switch (page) {
+      case 'question':
+        break;
+      default:
+    }
   }
 
   refreshTextbook() {
-
+    this.refreshExercise();
   }
 
   refreshExamination() {
-
+    const { page = '' } = this.state.search;
+    switch (page) {
+      case 'total':
+        break;
+      case 'question':
+        break;
+      default:
+    }
   }
 
   refreshExercise() {
-
+    const { page = '' } = this.state.search;
+    switch (page) {
+      case 'question':
+        break;
+      default:
+    }
   }
 
   renderView() {
@@ -269,22 +301,20 @@ export default class extends Page {
   }
 
   renderTextbook() {
-    return <div />;
+    return this.renderExercise();
   }
 
   renderExercise() {
-    return (
-      <div>
-        <div className="content">
-          <LineChart option={lineOption} />
-          <BarChart option={bar1Option} />
-          <BarChart option={bar2Option} />
-          <BarChart option={bar3Option} />
-          <BarChart option={barOption} />
-          <PieChart option={pieOption} />
-        </div>
+    return <div>
+      <div className="content">
+        <LineChart option={lineOption} />
+        <BarChart option={bar1Option} />
+        <BarChart option={bar2Option} />
+        <BarChart option={bar3Option} />
+        <BarChart option={barOption} />
+        <PieChart option={pieOption} />
       </div>
-    );
+    </div>;
   }
 
   renderExamination() {

+ 0 - 10
front/project/www/routes/sentence/process/index.js

@@ -1,10 +0,0 @@
-export default {
-  path: '/sentence/process/:id',
-  key: 'sentence-process',
-  title: '长难句',
-  needLogin: true,
-  hideHeader: true,
-  component() {
-    return import('./page');
-  },
-};

+ 0 - 0
front/project/www/routes/sentence/process/page.js


+ 29 - 26
front/project/www/routes/sentence/read/page.js

@@ -6,6 +6,8 @@ import Icon from '../../../components/Icon';
 import Progress from '../../../components/Progress';
 import Assets from '../../../../../src/components/Assets';
 import { Sentence } from '../../../stores/sentence';
+import { Main } from '../../../stores/main';
+import { formatMoney } from '../../../../../src/services/Tools';
 
 export default class extends Page {
   constructor(props) {
@@ -40,6 +42,9 @@ export default class extends Page {
       }
       this.jumpPage(page);
     });
+    Main.getSentence().then(result => {
+      this.setState({ info: result });
+    });
   }
 
   refreshSentence() {
@@ -204,7 +209,7 @@ export default class extends Page {
   }
 
   renderBody() {
-    const { showMenu, article, index, chapterMap = {} } = this.state;
+    const { showMenu, article, index, chapterMap = {}, info = {} } = this.state;
     return article ? (
       <div className="layout-body">
         <div className="crumb">千行长难句解析 >> {(chapterMap[article.chapter] || {}).title}</div>
@@ -214,20 +219,20 @@ export default class extends Page {
         </div>
         {showMenu && this.renderMenu()}
       </div>
-    ) : (
-      <div className="layout-body">
-        <div className="free-over">
-          <div className="free-over-title">试读已结束,购买后可继续阅读。</div>
-          <div className="free-over-btn">¥ 20.00 | 立即购买</div>
-          <div className="free-over-desc">
-            <div className="free-over-desc-title">张小爱笑笑笑 2019-07-13</div>
-            <div className="free-over-desc-content">
-              韩瑞祥/文 移民文学(Migrations
-              literatur)成为当今德国文坛上一个备受关注的文学现象,一批又一批脱颖而出的移民文学作家为当今德国文学的发展不断地
-            </div>
+    ) : (<div className="layout-body">
+      <div className="free-over">
+        <div className="free-over-title">试读已结束,购买后可继续阅读。</div>
+        <div className="free-over-btn" onClick={() => {
+          window.location.href = info.link;
+        }}>{formatMoney(info.price)} | 立即购买</div>
+        <div className="free-over-desc">
+          <div className="free-over-desc-title">{info.title}</div>
+          <div className="free-over-desc-content">
+            {info.description}
           </div>
         </div>
       </div>
+    </div>
     );
   }
 
@@ -264,13 +269,12 @@ export default class extends Page {
                 {chapter.title}
                 <div className="page">{chapter.startPage}</div>
               </div>
-            ) : (
-              <Tooltip title={message}>
-                <div className={'chapter-item trail'}>
-                  {chapter.title}
-                  <div className="page">{chapter.startPage}</div>
-                </div>
-              </Tooltip>
+            ) : (<Tooltip title={message}>
+              <div className={'chapter-item trail'}>
+                {chapter.title}
+                <div className="page">{chapter.startPage}</div>
+              </div>
+            </Tooltip>
             );
             const list = [_item];
             if (chapter.value) {
@@ -287,13 +291,12 @@ export default class extends Page {
                     {article.title}
                     <div className="page">{article.startPage}</div>
                   </div>
-                ) : (
-                  <Tooltip title={message}>
-                    <div className={'part-item trail'}>
-                      {article.title}
-                      <div className="page">{article.startPage}</div>
-                    </div>
-                  </Tooltip>
+                ) : (<Tooltip title={message}>
+                  <div className={'part-item trail'}>
+                    {article.title}
+                    <div className="page">{article.startPage}</div>
+                  </div>
+                </Tooltip>
                 );
                 list.push(item);
               });

+ 14 - 0
front/project/www/stores/main.js

@@ -24,6 +24,20 @@ export default class MainStore extends BaseStore {
   }
 
   /**
+   * 获取长难句信息
+   */
+  getSentence() {
+    return this.apiGet('/base/sentence');
+  }
+
+  /**
+   * 获取心经信息
+   */
+  getExperience() {
+    return this.apiGet('/base/experience');
+  }
+
+  /**
    * 获取考分排行信息
    */
   getScore(total, quant) {

+ 14 - 3
front/project/www/stores/question.js

@@ -2,15 +2,18 @@ import BaseStore from '@src/stores/base';
 
 export default class QuestionStore extends BaseStore {
   startLink(type, item) {
-    linkTo(`/paper/process/${type}/${item.id}`);
+    const w = window.open('about:blank');
+    w.location.href = `/paper/process/${type}/${item.id}`;
   }
 
   continueLink(type, item) {
-    linkTo(`/paper/process/${type}/${item.id}?r=${item.report.id}`);
+    const w = window.open('about:blank');
+    w.location.href = `/paper/process/${type}/${item.id}?r=${item.report.id}`;
   }
 
   reportLink(item) {
-    linkTo(`/paper/report/${item.report.id}`);
+    const w = window.open('about:blank');
+    w.location.href = `/paper/report/${item.report.id}`;
   }
 
   /**
@@ -135,6 +138,14 @@ export default class QuestionStore extends BaseStore {
   }
 
   /**
+   * 获取做题题目记录
+   * @param {*} userReportId
+   */
+  questionReport(userReportId) {
+    return this.apiGet('/question/report/detail', { userReportId });
+  }
+
+  /**
    * 开始考试
    * @param {*} type
    * @param {*} paperId

+ 22 - 20
front/project/www/stores/user.js

@@ -30,17 +30,25 @@ export default class UserStore extends BaseStore {
     if (this.state.login) {
       return Promise.resolve();
     }
-    return new Promise(resolve => {
-      this.loginCB = resolve;
+    return new Promise((resolve, reject) => {
+      this.successCB = resolve;
+      this.failCB = reject;
       this.setState({ needLogin: true });
     });
   }
 
-  closeLogin() {
-    this.setState({ needLogin: false });
+  closeLogin(err) {
+    this.setState({ needLogin: !!err });
+    if (err) {
+      if (this.failCB) this.failCB();
+    } else if (this.loginCB) this.loginCB();
     this.loginCB = null;
+    this.failCB = null;
   }
 
+  /**
+   * 验证token
+   */
   refreshToken() {
     return this.apiPost('/auth/token')
       .then(result => {
@@ -54,8 +62,6 @@ export default class UserStore extends BaseStore {
   infoHandle(result) {
     this.setToken(result.token);
     this.setState({ login: true, needLogin: false, info: result, username: result.username });
-    if (this.loginCB) this.loginCB();
-    this.loginCB = null;
   }
 
   originInviteCode(inviteCode) {
@@ -79,35 +85,31 @@ export default class UserStore extends BaseStore {
   }
 
   /**
-   * 验证token
-   */
-  token() {
-    return this.apiPost('/auth/token');
-  }
-
-  /**
    * 登陆
    * @param {*} mobile 手机号
    * @param {*} mobileVerifyCode 手机验证码
    * @param {*} inviteCode 邀请人手机/邀请码
    */
-  login(mobile, mobileVerifyCode, inviteCode) {
+  login(area, mobile, mobileVerifyCode, inviteCode) {
     if (!inviteCode) {
       ({ inviteCode } = this.state);
     }
-    return this.apiPost('/auth/login', { mobile, mobileVerifyCode, inviteCode });
+    return this.apiPost('/auth/login', { area, mobile, mobileVerifyCode, inviteCode }).then(result => {
+      this.infoHandle(result);
+      return result;
+    });
   }
 
   loginWechat(code) {
-    return this.apiGet('/auth/wechat_pc', { code }).then(() => {
-      this.setState({ login: true });
+    return this.apiGet('/auth/wechat_pc', { code }).then((result) => {
+      this.infoHandle(result);
+      return result;
     });
   }
 
   /**
    * 登出
    */
-
   logout(login = true) {
     return Promise.resolve()
       .then(() => {
@@ -130,8 +132,8 @@ export default class UserStore extends BaseStore {
    * @param {*} mobileVerifyCode 手机验证码
    * @param {*} inviteCode 邀请人手机/邀请码
    */
-  bind(mobile, mobileVerifyCode, inviteCode) {
-    return this.apiPost('/auth/bind', { mobile, mobileVerifyCode, inviteCode });
+  bind(area, mobile, mobileVerifyCode, inviteCode) {
+    return this.apiPost('/auth/bind', { area, mobile, mobileVerifyCode, inviteCode });
   }
 
   /**

+ 1 - 0
server/data/src/main/java/com/qxgmat/data/constants/enums/SettingKey.java

@@ -28,6 +28,7 @@ public enum SettingKey {
     COURSE_INDEX("course_index"), // 课程首页设置
     PROMOTE("course_promote"), // 促销
     EXPERIENCE_INFO("experience_info"), // 心经信息
+    SENTENCE_INFO("sentence_info"), // 长难句信息
 
     TIPS("tips"); // 页面提示信息
 

+ 17 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/admin/SettingController.java

@@ -348,6 +348,23 @@ public class SettingController {
         return ResponseHelp.success(entity.getValue());
     }
 
+    @RequestMapping(value = "/sentence_info", method = RequestMethod.PUT)
+    @ApiOperation(value = "修改长难句信息", httpMethod = "PUT")
+    private Response<Boolean> editSentenceInfo(@RequestBody @Validated JSONObject dto){
+        Setting entity = settingService.getByKey(SettingKey.SENTENCE_INFO);
+        entity.setValue(dto);
+        settingService.edit(entity);
+        return ResponseHelp.success(true);
+    }
+
+    @RequestMapping(value = "/sentence_info", method = RequestMethod.GET)
+    @ApiOperation(value = "获取长难句信息", httpMethod = "GET")
+    private Response<JSONObject> getSentenceInfo(){
+        Setting entity = settingService.getByKey(SettingKey.SENTENCE_INFO);
+
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/tips", method = RequestMethod.PUT)
     @ApiOperation(value = "修改结构说明", httpMethod = "PUT")
     private Response<Boolean> editTips(@RequestBody @Validated JSONObject dto){

+ 14 - 0
server/gateway-api/src/main/java/com/qxgmat/controller/api/BaseController.java

@@ -87,6 +87,20 @@ public class BaseController {
         return ResponseHelp.success(map);
     }
 
+    @RequestMapping(value = "/sentence", method = RequestMethod.GET)
+    @ApiOperation(value = "获取长难句信息", notes = "获取长难句信息", httpMethod = "GET")
+    public Response<JSONObject> sentence()  {
+        Setting entity = settingService.getByKey(SettingKey.SENTENCE_INFO);
+        return ResponseHelp.success(entity.getValue());
+    }
+
+    @RequestMapping(value = "/experience", method = RequestMethod.GET)
+    @ApiOperation(value = "获取心经信息", notes = "获取心经信息", httpMethod = "GET")
+    public Response<JSONObject> experience()  {
+        Setting entity = settingService.getByKey(SettingKey.EXPERIENCE_INFO);
+        return ResponseHelp.success(entity.getValue());
+    }
+
     @RequestMapping(value = "/score", method = RequestMethod.GET)
     @ApiOperation(value = "考分计算", notes = "获取考分排行信息", httpMethod = "GET")
     public Response<Rank> score(

+ 0 - 5
server/gateway-api/src/main/java/com/qxgmat/controller/api/WechatController.java

@@ -1,5 +0,0 @@
-package com.qxgmat.controller.api;
-
-public class WechatController {
-
-}

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/MyDto.java

@@ -7,6 +7,7 @@ import io.swagger.annotations.ApiModelProperty;
  * Created by GaoJie on 2017/11/1.
  */
 public class MyDto extends UserDto {
+    private Integer id;
 
     private String nickname;
 
@@ -130,4 +131,13 @@ public class MyDto extends UserDto {
     public void setVip(Boolean vip) {
         this.vip = vip;
     }
+
+    @Override
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
 }