KaysonCui 5 anni fa
parent
commit
f981507455

+ 17 - 0
front/project/h5/app.js

@@ -0,0 +1,17 @@
+import React, { Component } from 'react';
+import { LocaleProvider } from 'antd';
+import zhCN from 'antd/lib/locale-provider/zh_CN';
+import './app.less';
+
+export default class extends Component {
+  constructor(props) {
+    super(props);
+    const state = {};
+    this.state = state;
+  }
+
+  render() {
+    const { children } = this.props;
+    return <LocaleProvider locale={zhCN}>{children}</LocaleProvider>;
+  }
+}

+ 275 - 0
front/project/h5/app.less

@@ -0,0 +1,275 @@
+@charset "utf-8";
+@cornflower: #6865fd;
+@cornflower_bg: #e4eaf4;
+@dark-sky-blue: #4292f0;
+@night-blue: #050930;
+@slate-grey: #5e677b;
+@bluey-grey: #8897a8;
+@coral: #f36565;
+@butterscotch: #fcb750;
+@ice-blue: #f7fcff;
+
+@theme_color: @dark-sky-blue;
+@theme_holder_color: #7bb3f3;
+@theme_color_hover: darken(@dark-sky-blue, 10);
+@holder_color: @bluey-grey;
+@base_color: @slate-grey;
+@theme_bg_color: #e9eff8;
+@line_color: #c7d0d9;
+@base_size: 14px;
+@base_bg_color: #f4f4f4;
+@base_select_color: #f4f7fd;
+@content_width: 1000px;
+
+.theme,
+.theme a,
+.theme a:hover {
+  color: @theme_color;
+}
+
+.night {
+  color: @night-blue;
+}
+
+.link {
+  // text-decoration: underline;
+  // text-decoration-style: dashed;
+  // text-decoration-color: @theme_color;
+  border-bottom: 1px dashed @theme_color;
+}
+
+.f-s-16 {
+  font-size: 16px;
+}
+
+.f-s-12 {
+  font-size: 12px;
+}
+
+.t-d-l {
+  text-decoration: underline;
+}
+
+.f-w-b {
+  font-weight: bold;
+}
+
+.t-l {
+  text-align: left;
+}
+
+.t-r {
+  text-align: right;
+}
+
+.t-c {
+  text-align: center;
+}
+
+.f-l {
+  float: left;
+}
+
+.f-r {
+  float: right;
+}
+
+.p-a {
+  position: absolute;
+}
+
+.d-i-b {
+  display: inline-block;
+}
+
+.m-l-5 {
+  margin-left: 5px;
+}
+
+.m-l-1 {
+  margin-left: 10px;
+}
+
+.m-l-2 {
+  margin-left: 20px;
+}
+
+.m-r-5 {
+  margin-right: 5px;
+}
+
+.m-r-1 {
+  margin-right: 10px;
+}
+
+.m-r-2 {
+  margin-right: 20px;
+}
+
+.m-t-5 {
+  margin-top: 5px;
+}
+
+.m-t-1 {
+  margin-top: 10px;
+}
+
+.m-t-2 {
+  margin-top: 20px;
+}
+
+.m-b-5 {
+  margin-bottom: 5px;
+}
+
+.m-b-1 {
+  margin-bottom: 10px;
+}
+
+.m-b-2 {
+  margin-bottom: 20px;
+}
+
+.p-l-5 {
+  padding-left: 5px;
+}
+
+.p-l-1 {
+  padding-left: 10px;
+}
+
+.p-l-2 {
+  padding-left: 20px;
+}
+
+.p-r-5 {
+  padding-right: 5px;
+}
+
+.p-r-1 {
+  padding-right: 10px;
+}
+
+.p-r-2 {
+  padding-right: 10px;
+}
+
+.p-t-5 {
+  padding-top: 5px;
+}
+
+.p-t-1 {
+  padding-top: 10px;
+}
+
+.p-t-2 {
+  padding-top: 20px;
+}
+
+.p-b-5 {
+  padding-bottom: 5px;
+}
+
+.p-b-1 {
+  padding-bottom: 10px;
+}
+
+.p-b-2 {
+  padding-bottom: 20px;
+}
+
+.c-p {
+  cursor: pointer;
+}
+
+.w-1 {
+  width: 10%;
+}
+
+.w-2 {
+  width: 20%;
+}
+
+.w-3 {
+  width: 30%;
+}
+
+.w-4 {
+  width: 40%;
+}
+
+.w-5 {
+  width: 50%;
+}
+
+input,
+textarea {
+  outline: none;
+}
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+  color: @holder_color;
+}
+
+.d-i-b {
+  display: inline-block;
+  overflow: hidden;
+}
+
+.nowrap {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+body {
+  color: @base_color;
+  font-size: @base_size;
+  line-height: 1.7;
+  font-family: 'PingFang SC', 'Microsoft YaHei', '微软雅黑', 'Hiragino Sans GB', Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: subpixel-antialiased;
+  background: @base_bg_color;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  overflow: hidden;
+  overflow-y: auto;
+
+  #page {
+    height: 100%;
+  }
+
+  #full-page {
+    height: 100%;
+  }
+
+  .content {
+    width: @content_width;
+    margin: 0 auto;
+  }
+}
+
+#root {}
+
+body {
+  .ant-tooltip {
+
+    .ant-tooltip-arrow {
+      border-top-color: #fff;
+    }
+
+    .ant-tooltip-inner {
+      background: #fff;
+      color: @base_color;
+    }
+  }
+}

+ 8 - 0
front/project/h5/index.js

@@ -0,0 +1,8 @@
+export default {
+  mode: () => import('./app'),
+  apiToken: 'token',
+  loginAuth(route, { user }) {
+    if (route.needLogin && !user.login) return true;
+    return true;
+  },
+};

+ 18 - 0
front/project/h5/local.json

@@ -0,0 +1,18 @@
+{
+  "development": {
+    "scripts": [],
+    "proxy": [
+      {
+        "target": "http://qianxing.nuliji.com",
+        "from": "/api",
+        "to": "/api"
+      }
+    ]
+  },
+  "test": {
+    "scripts": []
+  },
+  "production": {
+    "scripts": []
+  }
+}

+ 5 - 0
front/project/h5/routes/index.js

@@ -0,0 +1,5 @@
+// We only need to import the modules necessary for initial render
+
+import Page from './page';
+
+export default [...Page];

+ 9 - 0
front/project/h5/routes/page/home/index.js

@@ -0,0 +1,9 @@
+export default {
+  path: '/',
+  key: 'index',
+  title: '首页',
+  needLogin: true,
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/h5/routes/page/home/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#index {}

+ 9 - 0
front/project/h5/routes/page/home/page.js

@@ -0,0 +1,9 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+
+export default class extends Page {
+  renderView() {
+    return <div />;
+  }
+}

+ 4 - 0
front/project/h5/routes/page/index.js

@@ -0,0 +1,4 @@
+import home from './home';
+import login from './login';
+
+export default [home, login];

+ 9 - 0
front/project/h5/routes/page/login/index.js

@@ -0,0 +1,9 @@
+export default {
+  path: '/login',
+  key: 'login',
+  title: '登录',
+  needLogin: false,
+  component() {
+    return import('./page');
+  },
+};

+ 3 - 0
front/project/h5/routes/page/login/index.less

@@ -0,0 +1,3 @@
+@charset "utf-8";
+
+#login {}

+ 24 - 0
front/project/h5/routes/page/login/page.js

@@ -0,0 +1,24 @@
+import React from 'react';
+import './index.less';
+import Page from '@src/containers/Page';
+import { User } from '../../../stores/user';
+
+export default class extends Page {
+  init() {
+    const { code } = this.props.core.query;
+    if (code) {
+      User.loginWechat(code).then(() => {
+        replaceLink('/');
+      });
+    } else {
+      const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=1&redirect_uri=${encodeURIComponent(
+        '/login',
+      )}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`;
+      window.location.href = url;
+    }
+  }
+
+  renderView() {
+    return <div />;
+  }
+}

+ 3 - 0
front/project/h5/stores/index.js

@@ -0,0 +1,3 @@
+import { User } from './user';
+
+export default [User];

+ 69 - 0
front/project/h5/stores/user.js

@@ -0,0 +1,69 @@
+import BaseStore from '@src/stores/base';
+// import * as querystring from 'querystring';
+
+export default class UserStore extends BaseStore {
+  initState() {
+    // const { code } = querystring.parse(window.location.search.replace('?', ''));
+    // if (code) {
+    //   this.loginWechat(code);
+    // }
+    return { login: false };
+  }
+
+  /**
+   * 验证token
+   */
+  token() {
+    return this.apiPost('/auth/token');
+  }
+
+  /**
+   * 登陆
+   * @param {*} mobile 手机号
+   * @param {*} mobileVerifyCode 手机验证码
+   * @param {*} inviteCode 邀请人手机/邀请码
+   */
+  login(mobile, mobileVerifyCode, inviteCode) {
+    return this.apiPost('/auth/login', { mobile, mobileVerifyCode, inviteCode });
+  }
+
+  loginWechat(code) {
+    return this.apiGet('/auth/wechat', { code }).then(() => {
+      this.setState({ login: true });
+    });
+  }
+
+  /**
+   * 登出
+   */
+  logout() {
+    return this.apiPost('/auth/logout');
+  }
+
+  /**
+   * 绑定手机
+   * @param {*} mobile 手机号
+   * @param {*} mobileVerifyCode 手机验证码
+   * @param {*} inviteCode 邀请人手机/邀请码
+   */
+  bind(mobile, mobileVerifyCode, inviteCode) {
+    return this.apiPost('/auth/bind', { mobile, mobileVerifyCode, inviteCode });
+  }
+
+  /**
+   * 查询邀请码对应账号
+   * @param {*} code 邀请码
+   */
+  validInviteCode(code) {
+    return this.apiGet('/auth/valid/invite_code', { code });
+  }
+
+  /**
+   * 查询手机对应账号
+   */
+  validMobile(mobile) {
+    return this.apiGet('/auth/valid/mobile', { mobile });
+  }
+}
+
+export const User = new UserStore({ key: 'user', local: true });

+ 7 - 0
front/project/www/app.less

@@ -32,6 +32,9 @@
 }
 
 .link {
+  // text-decoration: underline;
+  // text-decoration-style: dashed;
+  // text-decoration-color: @theme_color;
   border-bottom: 1px dashed @theme_color;
 }
 
@@ -43,6 +46,10 @@
   font-size: 12px;
 }
 
+.t-d-l {
+  text-decoration: underline;
+}
+
 .f-w-b {
   font-weight: bold;
 }

+ 6 - 1
front/project/www/components/Card/index.js

@@ -104,7 +104,12 @@ export default class Card extends Component {
     const { style, data = {}, process = {} } = this.props;
 
     return (
-      <Module style={style} className={`card ${data.status}`}>
+      <Module
+        style={style}
+        className={`card ${!process.payed && !process.startTime ? 'buy' : ''} ${process.payed ? 'open' : ''}  ${
+          process.startTime ? 'ing' : ''
+        }`}
+      >
         <div className="header">
           {`${data.titleZh} ${data.titleEn}`}
           {!process.payed && !process.startTime && <span className="sub-title">未购买</span>}

+ 2 - 2
front/project/www/components/RadioButton/index.js

@@ -3,12 +3,12 @@ import './index.less';
 import Button from '../Button';
 
 function RadioButton(props) {
-  const { list, value } = props;
+  const { list, checked } = props;
   return (
     <div className="radio-button">
       {list.map(item => {
         return (
-          <Button theme={item.key === value ? 'theme' : 'default'} size="small" radius>
+          <Button theme={item.key === checked ? 'theme' : 'default'} size="small" radius>
             {item.title}
           </Button>
         );

+ 1 - 1
front/project/www/components/Select/index.js

@@ -36,7 +36,7 @@ export default class Select extends Component {
         <div hidden={!selecting} className="mask" onClick={() => this.close()} />
         <div className="select-warp">
           <Button size={size} theme={theme} radius onClick={() => this.open()}>
-            {title}
+            {title} <i className="right-arrow" />
           </Button>
           <div className={`select-body ${selecting ? 'select' : ''}`}>
             {list.map(item => {

+ 21 - 0
front/project/www/components/Select/index.less

@@ -61,4 +61,25 @@
       border-color: @line_color;
     }
   }
+
+  .right-arrow {
+    display: inline-block;
+    position: relative;
+    width: 16px;
+    height: 16px;
+  }
+
+  .right-arrow::after {
+    display: inline-block;
+    content: " ";
+    height: 8px;
+    width: 8px;
+    border-width: 2px 2px 0 0;
+    border-color: #fff;
+    border-style: solid;
+    transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
+    position: absolute;
+    top: 50%;
+    margin-top: -1px;
+  }
 }

+ 3 - 0
front/project/www/components/Tabs/index.less

@@ -17,6 +17,7 @@
     color: @base_color;
     width: 120px;
     margin: 0 30px;
+    cursor: pointer;
   }
 
   .tab::after {
@@ -60,6 +61,7 @@
     position: relative;
     text-align: center;
     color: @base_color;
+    cursor: pointer;
   }
 
   .tab::after {
@@ -97,6 +99,7 @@
     text-align: center;
     color: @base_color;
     margin: 0 13px;
+    cursor: pointer;
   }
 
   .tab::after {

+ 10 - 0
front/project/www/routes/page/login/page.js

@@ -1,8 +1,18 @@
 import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
+import { User } from '../../../stores/user';
 
 export default class extends Page {
+  init() {
+    const { code } = this.props.core.query;
+    if (code) {
+      User.loginWechat(code).then(() => {
+        replaceLink('/');
+      });
+    }
+  }
+
   showWx() {
     setTimeout(() => {
       const wx = new WxLogin({

+ 17 - 0
front/project/www/routes/page/practise/index.less

@@ -39,4 +39,21 @@
       }
     }
   }
+
+  .work-body {
+    .work-nav {
+      margin-bottom: 20px;
+
+      .left {
+        display: inline-block;
+        padding-left: 5px;
+        font-size: 16px;
+        font-weight: 600;
+      }
+
+      .right {
+        float: right;
+      }
+    }
+  }
 }

+ 139 - 111
front/project/www/routes/page/practise/page.js

@@ -20,8 +20,106 @@ const PREVIEW = 'PREVIEW';
 const PREVIEW_CLASS = 'PREVIEW_CLASS';
 const PREVIEW_TASK = 'PREVIEW_TASK';
 
+const columns = [
+  {
+    title: '练习册',
+    width: 250,
+    align: 'left',
+    render: item => {
+      return (
+        <div className="table-row">
+          <div className="night f-s-16">{item.title}</div>
+          <div>
+            <ProgressText
+              progress={item.report.id ? item.repport.userNumber / item.report.questionNumber : 0}
+              size="small"
+            />
+          </div>
+        </div>
+      );
+    },
+  },
+  {
+    title: '正确率',
+    width: 150,
+    align: 'left',
+    render: item => {
+      return (
+        <div className="table-row">
+          <div className="night f-s-16 f-w-b">--</div>
+          <div className="f-s-12">{item.stat.totalCorrect / item.stat.totalNumber}</div>
+        </div>
+      );
+    },
+  },
+  {
+    title: '全站用时',
+    width: 150,
+    align: 'left',
+    render: item => {
+      return (
+        <div className="table-row">
+          <div className="night f-s-16 f-w-b">--</div>
+          <div className="f-s-12">全站{item.stat.totalTime / item.stat.totalNumber}s</div>
+        </div>
+      );
+    },
+  },
+  {
+    title: '最近做题',
+    width: 150,
+    align: 'left',
+    render: () => {
+      return (
+        <div className="table-row">
+          <div>2019-04-28</div>
+          <div>07:30</div>
+        </div>
+      );
+    },
+  },
+  {
+    title: '操作',
+    width: 180,
+    align: 'left',
+    render: item => {
+      return (
+        <div className="table-row p-t-1">
+          {!item.repport.id && (
+            <IconButton type="start" tip="Start" onClick={() => this.previewAction('start', item)} />
+          )}
+          {item.repport.id && (
+            <IconButton
+              className="m-r-2"
+              type="continue"
+              tip="Continue"
+              onClick={() => this.previewAction('continue', item)}
+            />
+          )}
+          {item.repport.id && (
+            <IconButton type="restart" tip="Restart" onClick={() => this.previewAction('restart', item)} />
+          )}
+        </div>
+      );
+    },
+  },
+  {
+    title: '报告',
+    width: 30,
+    align: 'right',
+    render: item => {
+      return (
+        <div className="table-row p-t-1">
+          {item.report.userNumber === item.report.questionNumber && <IconButton type="report" tip="Report" />}
+        </div>
+      );
+    },
+  },
+];
+
 export default class extends Page {
   initState() {
+    this.columns = columns;
     return {
       level1Tab: PREVIEW,
       level2Tab: '',
@@ -65,7 +163,7 @@ export default class extends Page {
       this.refreshClassProcess();
     }
     if (previewType === PREVIEW_TASK) {
-      this.refreshPreview();
+      this.refreshListPreview();
     }
   }
 
@@ -136,11 +234,11 @@ export default class extends Page {
         <div className="content">
           <Module className="m-t-2">
             <Tabs type="card" active={level1Tab} tabs={tabs} onChange={key => this.onChangeTab(1, key)} />
-            {(level1Tab !== HARD || level1Tab !== PREVIEW) && (
+            {level1Tab !== HARD && level1Tab !== PREVIEW && (
               <Tabs active={level2Tab} tabs={map[level2Tab]} onChange={key => this.onChangeTab(2, key)} />
             )}
           </Module>
-          {(level1Tab !== HARD || level1Tab !== PREVIEW) && this.renderType()}
+          {level1Tab !== HARD && level1Tab !== PREVIEW && this.renderType()}
           {level1Tab === HARD && this.renderHard()}
           {level1Tab === PREVIEW && this.renderWork()}
         </div>
@@ -161,122 +259,52 @@ export default class extends Page {
   }
 
   renderAllClass() {
-    const { allClass, classProcess } = this.props;
+    const { allClass, classProcess } = this.state;
     return (
-      <Division col="3">
-        {allClass.map(item => {
-          return <Card data={item} process={classProcess[item.id]} previewAction={this.previewAction} />;
-        })}
-      </Division>
+      <div className="work-body">
+        <div className="work-nav">
+          <div className="left">完成情况</div>
+          <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_TASK)}>
+            全部作业 >
+          </div>
+        </div>
+        <Division col="3">
+          {allClass.map(item => {
+            return <Card data={item} process={classProcess[item.id]} previewAction={this.previewAction} />;
+          })}
+        </Division>
+      </div>
     );
   }
 
   renderAllTask() {
-    const { previews } = this.props;
+    const { previews } = this.state;
     return (
-      <ListTable
-        filters={[
-          { type: 'radio', checked: 'first', list: [{ key: 'first', title: 123 }, { key: 'two', title: 321 }] },
-          { type: 'select', checked: 'first', list: [{ key: 'first', title: 123 }, { key: 'two', title: 321 }] },
-        ]}
-        data={previews}
-        columns={[
-          {
-            title: '练习册',
-            width: 250,
-            align: 'left',
-            render: item => {
-              return (
-                <div className="table-row">
-                  <div className="night f-s-16">{item.title}</div>
-                  <div>
-                    <ProgressText
-                      progress={item.report.id ? item.repport.userNumber / item.report.questionNumber : 0}
-                      size="small"
-                    />
-                  </div>
-                </div>
-              );
-            },
-          },
-          {
-            title: '正确率',
-            width: 150,
-            align: 'left',
-            render: item => {
-              return (
-                <div className="table-row">
-                  <div className="night f-s-16 f-w-b">--</div>
-                  <div className="f-s-12">{item.stat.totalCorrect / item.stat.totalNumber}</div>
-                </div>
-              );
-            },
-          },
-          {
-            title: '全站用时',
-            width: 150,
-            align: 'left',
-            render: item => {
-              return (
-                <div className="table-row">
-                  <div className="night f-s-16 f-w-b">--</div>
-                  <div className="f-s-12">全站{item.stat.totalTime / item.stat.totalNumber}s</div>
-                </div>
-              );
-            },
-          },
-          {
-            title: '最近做题',
-            width: 150,
-            align: 'left',
-            render: () => {
-              return (
-                <div className="table-row">
-                  <div>2019-04-28</div>
-                  <div>07:30</div>
-                </div>
-              );
-            },
-          },
-          {
-            title: '操作',
-            width: 180,
-            align: 'left',
-            render: item => {
-              return (
-                <div className="table-row p-t-1">
-                  {!item.repport.id && (
-                    <IconButton type="start" tip="Start" onClick={() => this.previewAction('start', item)} />
-                  )}
-                  {item.repport.id && (
-                    <IconButton
-                      className="m-r-2"
-                      type="continue"
-                      tip="Continue"
-                      onClick={() => this.previewAction('continue', item)}
-                    />
-                  )}
-                  {item.repport.id && (
-                    <IconButton type="restart" tip="Restart" onClick={() => this.previewAction('restart', item)} />
-                  )}
-                </div>
-              );
+      <div className="work-body">
+        <div className="work-nav">
+          <div className="left">全部作业</div>
+          <div className="right theme c-p" onClick={() => this.onChangePreviewType(PREVIEW_CLASS)}>
+            我的课程 >
+          </div>
+        </div>
+        <ListTable
+          filters={[
+            {
+              type: 'radio',
+              checked: 'today',
+              list: [{ key: 'today', title: '今日需完成' }, { key: 'tomorrow', title: '明日需完成' }],
             },
-          },
-          {
-            title: '报告',
-            width: 30,
-            align: 'right',
-            render: item => {
-              return (
-                <div className="table-row p-t-1">
-                  {item.report.userNumber === item.report.questionNumber && <IconButton type="report" tip="Report" />}
-                </div>
-              );
+            {
+              type: 'radio',
+              checked: 'unfinish',
+              list: [{ key: 'unfinish', title: '未完成' }, { key: 'finish', title: '已完成' }],
             },
-          },
-        ]}
-      />
+            { type: 'select', checked: 'all', list: [{ key: 'all', title: '全部' }] },
+          ]}
+          data={previews}
+          columns={this.columns}
+        />
+      </div>
     );
   }
 

+ 52 - 0
front/project/www/routes/page/start/index.less

@@ -208,4 +208,56 @@
       }
     }
   }
+
+  .modal {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+
+    .body {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      background: #006DAA;
+      width: 400px;
+      color: #fff;
+
+      .title {
+        height: 38px;
+        line-height: 38px;
+        font-size: 18px;
+        padding-left: 25px;
+        border-bottom: 1px solid #fff;
+      }
+
+      .desc {
+        text-align: center;
+        margin-top: 20px;
+        margin-bottom: 20px;
+
+      }
+
+      .btn-list {
+        text-align: center;
+        margin-bottom: 15px;
+
+        .btn {
+          display: inline-block;
+          width: 70px;
+          line-height: 35px;
+          height: 35px;
+          border: 1px solid #fff;
+          background: #006DAA;
+          cursor: pointer;
+        }
+
+        .btn:hover {
+          background: darken(#006DAA, 5);
+        }
+      }
+    }
+  }
 }

+ 70 - 9
front/project/www/routes/page/start/page.js

@@ -26,6 +26,7 @@ export default class extends Page {
       reportInfo: {},
       questionInfo: {},
       answer: {},
+      modal: null,
     };
   }
 
@@ -80,6 +81,7 @@ export default class extends Page {
 
   submit() {
     const { question, answer } = this.state;
+    if (!this.checkAnswer()) return;
     Question.submit(question.questionNoId, answer).then(() => {
       this.next();
     });
@@ -87,7 +89,39 @@ export default class extends Page {
 
   finish() {
     const { reportInfo } = this.state;
-    Question.finish(reportInfo.id).then(() => {});
+    Question.finish(reportInfo.id).then(() => {
+      this.showToast(null, 'Complete!', () => {
+        goBack();
+      });
+    });
+  }
+
+  showConfirm(title, desc, cb) {
+    this.showModal('confirm', title, desc, cb);
+  }
+
+  showToast(title, desc, cb) {
+    this.showModal('toast', title, desc, cb);
+  }
+
+  showModal(type, title, desc, cb) {
+    this.setState({ modal: { type, title, desc, cb } });
+  }
+
+  checkAnswer() {
+    const { question, answer } = this.state;
+    let result = null;
+    if (question.type === 'awa' && !answer.awa) result = 'Please answer the question first.';
+    if (result) return this.showToast(null, result);
+    return true;
+  }
+
+  hideModal(b) {
+    if (b) {
+      const { modal = {} } = this.state;
+      if (modal.cb) modal.cb();
+    }
+    this.setState({ modal: null });
   }
 
   formatStrem(text) {
@@ -106,10 +140,7 @@ export default class extends Page {
         );
       }
       for (let i = 0; i < tableList.length; i += 1) {
-        ReactDOM.render(
-          <AnswerTable list={table.header || []} columns={table.header} data={table.data} />,
-          tableList[i],
-        );
+        ReactDOM.render(<AnswerTable list={table.header} columns={table.header} data={table.data} />, tableList[i]);
       }
     }, 1);
     return text;
@@ -117,7 +148,7 @@ export default class extends Page {
 
   renderView() {
     const { start } = this.state;
-    if (!start) return this.renderStart();
+    if (start) return this.renderStart();
     return this.renderDetail();
   }
 
@@ -138,7 +169,6 @@ export default class extends Page {
     if (question.type === 'inline') return '';
     return (
       <div className="block block-answer">
-        <Editor onChange={v => this.onChangeAwa(v)} />
         {question.type === 'awa' && <Editor onChange={v => this.onChangeAwa(v)} />}
         {questions.map((item, index) => {
           return (
@@ -158,7 +188,7 @@ export default class extends Page {
   }
 
   renderDetail() {
-    const { showCalculator, info, question = { content: {} } } = this.state;
+    const { modal, showCalculator, info, question = { content: {} } } = this.state;
     const { typeset = 'one' } = question.content;
     return (
       <div className="layout">
@@ -202,12 +232,13 @@ export default class extends Page {
             <Assets name="next_icon" />
           </div>
         </div>
+        {modal ? this.renderModal() : ''}
       </div>
     );
   }
 
   renderStart() {
-    const { info, disorder } = this.state;
+    const { info, disorder, modal } = this.state;
     return (
       <div className="start">
         <div className="bg" />
@@ -239,6 +270,36 @@ export default class extends Page {
             </Button>
           </div>
         </div>
+        {modal ? this.renderModal() : ''}
+      </div>
+    );
+  }
+
+  renderModal() {
+    const { modal } = this.state;
+    return (
+      <div className="modal">
+        <div className="mask" />
+        <div className="body">
+          <div className="title">{modal.title}</div>
+          <div className="desc">{modal.desc}</div>
+          {modal.type === 'confirm' ? (
+            <div className="btn-list">
+              <div className="btn" onClick={() => this.hideModal(true)}>
+                <span className="t-d-l">Y</span>es
+              </div>
+              <div className="btn" onClick={() => this.hideModal(false)}>
+                <span className="t-d-l">N</span>o
+              </div>
+            </div>
+          ) : (
+            <div className="btn-list">
+              <div className="btn" onClick={() => this.hideModal(true)}>
+                <span className="t-d-l">O</span>k
+              </div>
+            </div>
+          )}
+        </div>
       </div>
     );
   }

+ 2 - 2
front/project/www/stores/main.js

@@ -27,7 +27,7 @@ export default class MainStore extends BaseStore {
    */
   getExercise() {
     return this.getApiCache('API:main:getExercise', () => {
-      return this.apiGet('/exercise/struct');
+      return this.apiGet('/base/exercise/struct');
     });
   }
 
@@ -44,7 +44,7 @@ export default class MainStore extends BaseStore {
    * 所有模考层级
    */
   getExamination() {
-    return this.apiGet('/examination/struct');
+    return this.apiGet('/base/examination/struct');
   }
 }
 

+ 6 - 6
front/project/www/stores/user.js

@@ -4,7 +4,9 @@ import * as querystring from 'querystring';
 export default class UserStore extends BaseStore {
   initState() {
     const { token } = querystring.parse(window.location.search.replace('?', ''));
-    this.setToken(token);
+    if (token) {
+      this.setToken(token);
+    }
     return { login: !!token };
   }
 
@@ -26,11 +28,9 @@ export default class UserStore extends BaseStore {
   }
 
   loginWechat(code) {
-    return this.apiGet('/auth/wechat', { code });
-  }
-
-  loginWechatPC(code) {
-    return this.apiGet('/auth/wechat_pc', { code });
+    return this.apiGet('/auth/wechat_pc', { code }).then(() => {
+      this.setState({ login: true });
+    });
   }
 
   /**