Browse Source

add new page

KaysonCui 5 years ago
parent
commit
84cf99e579
35 changed files with 1328 additions and 440 deletions
  1. 6 24
      front/.eslintrc
  2. BIN
      front/project/www/assets/back_nomal.png
  3. BIN
      front/project/www/assets/backhome_nomal.png
  4. BIN
      front/project/www/assets/banner.png
  5. BIN
      front/project/www/assets/banner_1.png
  6. BIN
      front/project/www/assets/begin.png
  7. BIN
      front/project/www/assets/checkbox_normal.png
  8. BIN
      front/project/www/assets/checkbox_select.png
  9. BIN
      front/project/www/assets/continue.png
  10. BIN
      front/project/www/assets/drop_down_1.png
  11. BIN
      front/project/www/assets/drop_downbule.png
  12. BIN
      front/project/www/assets/lock.png
  13. BIN
      front/project/www/assets/report.png
  14. BIN
      front/project/www/assets/reset.png
  15. BIN
      front/project/www/assets/review_normal.png
  16. BIN
      front/project/www/assets/swich-off.png
  17. BIN
      front/project/www/assets/swich-on.png
  18. 9 0
      front/project/www/components/Button/index.less
  19. 2 2
      front/project/www/components/Division/index.js
  20. 11 0
      front/project/www/components/Division/index.less
  21. 17 12
      front/project/www/components/ListTable/index.js
  22. 5 0
      front/project/www/components/ListTable/index.less
  23. 88 9
      front/project/www/components/Panel/index.js
  24. 71 1
      front/project/www/components/Panel/index.less
  25. 25 0
      front/project/www/components/QAList/index.js
  26. 9 0
      front/project/www/components/QAList/index.less
  27. 2 2
      front/project/www/components/Tabs/index.js
  28. 17 0
      front/project/www/components/Tabs/index.less
  29. 308 195
      front/project/www/routes/exercise/main/page.js
  30. 12 1
      front/project/www/routes/page/demo/page.js
  31. 281 51
      front/project/www/routes/paper/process/base/index.js
  32. 196 0
      front/project/www/routes/paper/process/base/index.less
  33. 73 62
      front/project/www/routes/paper/process/page.js
  34. 46 0
      front/project/www/routes/sentence/read/index.less
  35. 150 81
      front/project/www/routes/sentence/read/page.js

+ 6 - 24
front/.eslintrc

@@ -1,30 +1,11 @@
 {
   "parser": "babel-eslint",
-  "extends": [
-    "standard-react",
-    "airbnb-base"
-  ],
-  "plugins": [
-    "react",
-    "import"
-  ],
+  "extends": ["standard-react", "airbnb-base"],
+  "plugins": ["react", "import"],
   "settings": {
     "import/resolver": {
       "alias": {
-        "map": [
-          [
-            "@src",
-            "./src"
-          ],
-          [
-            "@project",
-            "./project/admin"
-          ],
-          [
-            "@components",
-            "./components"
-          ]
-        ]
+        "map": [["@src", "./src"], ["@project", "./project/admin"], ["@components", "./components"]]
       }
     }
   },
@@ -77,6 +58,7 @@
     "react/prop-types": "off",
     "global-require": "off",
     "import/no-extraneous-dependencies": "off",
-    "import/no-named-as-default": "off"
+    "import/no-named-as-default": "off",
+    "operator-linebreak": "off"
   }
-}
+}

BIN
front/project/www/assets/back_nomal.png


BIN
front/project/www/assets/backhome_nomal.png


BIN
front/project/www/assets/banner.png


BIN
front/project/www/assets/banner_1.png


BIN
front/project/www/assets/begin.png


BIN
front/project/www/assets/checkbox_normal.png


BIN
front/project/www/assets/checkbox_select.png


BIN
front/project/www/assets/continue.png


BIN
front/project/www/assets/drop_down_1.png


BIN
front/project/www/assets/drop_downbule.png


BIN
front/project/www/assets/lock.png


BIN
front/project/www/assets/report.png


BIN
front/project/www/assets/reset.png


BIN
front/project/www/assets/review_normal.png


BIN
front/project/www/assets/swich-off.png


BIN
front/project/www/assets/swich-on.png


+ 9 - 0
front/project/www/components/Button/index.less

@@ -49,6 +49,11 @@
   color: #fff;
 }
 
+.button.theme.disabled {
+  background: #c8c8c8;
+  border: 1px solid #b8b8b8;
+}
+
 .button.default {
   background: #fff;
   color: @holder_color;
@@ -79,6 +84,10 @@
   background: @theme_color_hover;
 }
 
+.button.theme.disabled:hover {
+  background: darken(#c8c8c8, 5);
+}
+
 .button.border:hover {
   border-color: @theme_color;
 }

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

@@ -2,8 +2,8 @@ import React from 'react';
 import './index.less';
 
 function Division(props) {
-  const { children, col = 3 } = props;
-  return <div className={`division col-${col}`}>{children}</div>;
+  const { children, col = 3, type = 'default' } = props;
+  return <div className={`division ${type} col-${col}`}>{children}</div>;
 }
 Division.propTypes = {};
 export default Division;

+ 11 - 0
front/project/www/components/Division/index.less

@@ -25,4 +25,15 @@
     margin-right: 28px;
     margin-bottom: 56px;
   }
+}
+
+.division.col-3.compact {
+  margin: 0 -10px;
+
+  >div {
+    width: 320px;
+    margin-left: 10px;
+    margin-right: 10px;
+    margin-bottom: 25px;
+  }
 }

+ 17 - 12
front/project/www/components/ListTable/index.js

@@ -17,21 +17,26 @@ function getFilter(filter) {
 }
 
 function ListTable(props) {
-  const { style, position, title, filters = [], columns = [], data = [] } = props;
+  const { style, position, title, rightAction, filters = [], columns = [], data = [] } = props;
   return (
     <Module style={style} className="list-table">
-      {title && <div className="header">
-        <span className="title">{position}</span>
-        <span className="sub-title">{title}</span>
-      </div>}
-      {filters.length > 0 && <div className="filter">
-        <span className="text">筛选</span>
-        <div className="filter-list">
-          {filters.map(filter => {
-            return <div className="filter-item">{getFilter(filter)}</div>;
-          })}
+      {title && (
+        <div className="header">
+          <span className="title">{position}</span>
+          <span className="sub-title">{title}</span>
         </div>
-      </div>}
+      )}
+      {filters.length > 0 && (
+        <div className="filter">
+          <span className="text">筛选</span>
+          <div className="filter-list">
+            {filters.map(filter => {
+              return <div className="filter-item">{getFilter(filter)}</div>;
+            })}
+          </div>
+          <div className="right-action">{rightAction}</div>
+        </div>
+      )}
       {data && data.length > 0 && <Table columns={columns} data={data} />}
     </Module>
   );

+ 5 - 0
front/project/www/components/ListTable/index.less

@@ -36,6 +36,11 @@
         display: inline-block;
       }
     }
+
+    .right-action {
+      float: right;
+      margin-right: 24px;
+    }
   }
 
   .table {

+ 88 - 9
front/project/www/components/Panel/index.js

@@ -4,6 +4,7 @@ import './index.less';
 import Assets from '@src/components/Assets';
 import Module from '../Module';
 import ProgressButton from '../ProgressButton';
+import Button from '../Button';
 
 function Panel(props) {
   const { style, message, data = {}, col = 3, title, onClick } = props;
@@ -11,28 +12,37 @@ function Panel(props) {
     <Module style={style} className="panel">
       <div className="header">
         <span>{title}</span>
-        <Tooltip title={message} trigger='click'><Assets name="QA" svg /></Tooltip>
+        <Tooltip title={message} trigger="click">
+          <Assets className="qa" name="QA" svg />
+        </Tooltip>
       </div>
       <div className="body">
         <div className="chart-info">
           <div className="chart" />
           <div className="info">
             {(data.info || []).map(row => {
-              return <div className="item">
-                <div className="title">{row.title}</div>
-                <div className="data">
-                  <span className="text">{row.number}</span>{row.unit}
+              return (
+                <div className="item">
+                  <div className="title">{row.title}</div>
+                  <div className="data">
+                    <span className="text">{row.number}</span>
+                    {row.unit}
+                  </div>
                 </div>
-              </div>;
+              );
             })}
           </div>
         </div>
         <div className={`list col-${col}`}>
           {(data.children || []).map(item => {
             return (
-              <ProgressButton className="item" progress={item.progress} onClick={() => {
-                if (onClick) onClick(item);
-              }}>
+              <ProgressButton
+                className="item"
+                progress={item.progress}
+                onClick={() => {
+                  if (onClick) onClick(item);
+                }}
+              >
                 {item.title}
               </ProgressButton>
             );
@@ -44,3 +54,72 @@ function Panel(props) {
 }
 Panel.propTypes = {};
 export default Panel;
+
+export function TotalPanel(props) {
+  const { style, title, lock, data = {} } = props;
+  return (
+    <Module style={style} className="panel total-panel">
+      <div className="header">
+        <span>{title}</span>
+        {lock && <Assets name="lock" />}
+      </div>
+      <div className="body">
+        <div className="chart-info">
+          <div className="chart" />
+          <div className="info">
+            {(data.info || []).map(row => {
+              return (
+                <div className="item">
+                  <div className="title">{row.title}</div>
+                  <div className="data">
+                    <span className="text">{row.number}</span>
+                    {row.unit}
+                  </div>
+                </div>
+              );
+            })}
+            <div className="date">有效期至:2019-11-13</div>
+          </div>
+        </div>
+      </div>
+    </Module>
+  );
+}
+
+export function WaitPanel(props) {
+  const { style, title } = props;
+  return (
+    <Module style={style} className="panel wait-panel">
+      <div className="header">
+        <span>{title}</span>
+        <Assets name="lock" />
+      </div>
+      <div className="body">
+        <div className="title">请于20190-07-05前开通</div>
+        <div className="btn">
+          <Button size="lager" width={120} radius>
+            立即开通
+          </Button>
+        </div>
+      </div>
+    </Module>
+  );
+}
+
+export function BuyPanel(props) {
+  const { style, title } = props;
+  return (
+    <Module style={style} className="panel buy-panel">
+      <div className="header">
+        <span>{title}</span>
+        <Assets name="lock" />
+      </div>
+      <div className="body">
+        <Assets name="banner_1" />
+        <Button radius size="small" width={80}>
+          立即购买
+        </Button>
+      </div>
+    </Module>
+  );
+}

+ 71 - 1
front/project/www/components/Panel/index.less

@@ -11,10 +11,13 @@
     .assets {
       transform: translateY(-1px);
       margin-left: 5px;
+    }
+
+    .qa {
       cursor: pointer;
     }
 
-    .assets:hover {
+    .qa:hover {
       color: @theme_color;
     }
   }
@@ -91,4 +94,71 @@
       }
     }
   }
+}
+
+.module.total-panel {
+  .body {
+    min-height: 166px;
+    padding: 20px 30px 0;
+    overflow: hidden;
+
+    .chart-info {
+      .info {
+        .item {
+          margin-right: 20px;
+        }
+
+        .date {
+          padding-top: 20px;
+          font-size: 12px;
+          color: #8897A8;
+        }
+      }
+    }
+  }
+}
+
+.module.wait-panel {
+  .body {
+    min-height: 166px;
+    padding: 20px 30px;
+
+    .title {
+      margin-top: 15px;
+      margin-bottom: 10px;
+      text-align: center;
+      font-size: 16px;
+      color: #5E677B;
+    }
+
+    .btn {
+      text-align: center;
+
+      .button {
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 24px;
+      }
+    }
+  }
+}
+
+.module.buy-panel {
+  .body {
+    min-height: 166px;
+    padding: 10px 10px 20px;
+    position: relative;
+
+    .assets {
+      width: 100%;
+      height: 100%;
+    }
+
+    .button {
+      position: absolute;
+      right: 25px;
+      top: 60px;
+    }
+
+  }
 }

+ 25 - 0
front/project/www/components/QAList/index.js

@@ -0,0 +1,25 @@
+import React from 'react';
+import './index.less';
+import Module from '../Module';
+import Tabs from '../Tabs';
+import OtherAnswer from '../OtherAnswer';
+
+function QAList(props) {
+  const { style, data = [] } = props;
+  return (
+    <Module style={style} className="qa-list">
+      <Tabs
+        type="division"
+        theme="theme"
+        space={2.5}
+        width={100}
+        tabs={[{ key: 'qx', name: '解析详情' }, { key: 'chinese', name: '中文语意' }]}
+      />
+      {data.map(item => {
+        return <OtherAnswer data={item} />;
+      })}
+    </Module>
+  );
+}
+QAList.propTypes = {};
+export default QAList;

+ 9 - 0
front/project/www/components/QAList/index.less

@@ -0,0 +1,9 @@
+@import '../../app.less';
+
+.module.qa-list {
+  padding: 30px;
+
+  .tabs {
+    border-bottom: 1px solid #eee;
+  }
+}

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

@@ -16,9 +16,9 @@ function getItem(props, item, onChange) {
 }
 
 function Tabs(props) {
-  const { tabs = [], type = 'line', border, onChange } = props;
+  const { tabs = [], type = 'line', theme = 'default', border, onChange } = props;
   return (
-    <div className={`tabs ${type} ${border ? 'border' : ''}`}>
+    <div className={`tabs ${type} ${theme} ${border ? 'border' : ''}`}>
       {tabs.map(item => {
         return item.path ? <Link to={item.path}>{getItem(props, item)}</Link> : getItem(props, item, onChange);
       })}

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

@@ -172,4 +172,21 @@
   .tab.active::after {
     width: 100%;
   }
+}
+
+.tabs.division.theme {
+  .tab {
+    color: #A7A7B7;
+    background: #ECEDEE;
+  }
+
+  .tab::after {
+    display: none;
+  }
+
+  .tab:hover,
+  .tab.active {
+    color: #fff;
+    background: @theme_color;
+  }
 }

+ 308 - 195
front/project/www/routes/exercise/main/page.js

@@ -1,6 +1,6 @@
 import React from 'react';
 import './index.less';
-import { Modal } from 'antd';
+import { Modal, Tooltip } from 'antd';
 import { Link } from 'react-router-dom';
 import Page from '@src/containers/Page';
 import { asyncConfirm } from '@src/services/AsyncTools';
@@ -96,9 +96,7 @@ const exerciseColumns = [
     render: item => {
       return (
         <div className="table-row p-t-1">
-          {!item.report && (
-            <IconButton type="start" tip="Start" onClick={() => Question.startLink('preview', item)} />
-          )}
+          {!item.report && <IconButton type="start" tip="Start" onClick={() => Question.startLink('preview', item)} />}
           {item.report.id && !item.report.isFinish && (
             <IconButton
               className="m-r-2"
@@ -107,9 +105,7 @@ const exerciseColumns = [
               onClick={() => Question.continueLink('preview', item)}
             />
           )}
-          {item.report.id && (
-            <IconButton type="restart" tip="Restart" onClick={() => this.restart('preview', item)} />
-          )}
+          {item.report.id && <IconButton type="restart" tip="Restart" onClick={() => this.restart('preview', item)} />}
         </div>
       );
     },
@@ -136,7 +132,7 @@ export default class extends Page {
         title: '练习册',
         width: 250,
         align: 'left',
-        render: (record) => {
+        render: record => {
           let progress = 0;
           if (record.report) {
             progress = formatPercent(record.report.userNumber, record.report.questionNumber);
@@ -155,7 +151,7 @@ export default class extends Page {
         title: '正确率',
         width: 150,
         align: 'left',
-        render: (record) => {
+        render: record => {
           let correct = '--';
           if (record.report) {
             correct = formatPercent(record.report.userCorrect, record.report.userNumber, false);
@@ -163,7 +159,9 @@ export default class extends Page {
           return (
             <div className="table-row">
               <div className="night f-s-16 f-w-b">{correct}</div>
-              <div className="f-s-12">全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}</div>
+              <div className="f-s-12">
+                全站{formatPercent(record.stat.totalCorrect, record.stat.totalNumber, false)}
+              </div>
             </div>
           );
         },
@@ -172,7 +170,7 @@ export default class extends Page {
         title: '全站用时',
         width: 150,
         align: 'left',
-        render: (record) => {
+        render: record => {
           let time = '--';
           if (record.report) {
             time = formatSeconds(record.report.userTime / record.report.userNumber);
@@ -189,7 +187,7 @@ export default class extends Page {
         title: '最近做题',
         width: 150,
         align: 'left',
-        render: (record) => {
+        render: record => {
           const time = record.report ? record.report.updateTime : record.paper ? record.paper.latestTime : null;
           if (!time) return null;
           return (
@@ -204,18 +202,37 @@ export default class extends Page {
         title: '操作',
         width: 180,
         align: 'left',
-        render: (record) => {
+        render: record => {
           return (
             <div className="table-row p-t-1">
-              {!record.report && <IconButton type="start" tip="Start" onClick={() => {
-                Question.startLink('sentence', record);
-              }} />}
-              {(record.report && !record.report.isFinish) && <IconButton className="m-r-2" type="continue" tip="Continue" onClick={() => {
-                Question.continueLink('sentence', record);
-              }} />}
-              {(record.report && !!record.report.isFinish) && <IconButton type="restart" tip="Restart" onClick={() => {
-                this.restart(record);
-              }} />}
+              {!record.report && (
+                <IconButton
+                  type="start"
+                  tip="Start"
+                  onClick={() => {
+                    Question.startLink('sentence', record);
+                  }}
+                />
+              )}
+              {record.report && !record.report.isFinish && (
+                <IconButton
+                  className="m-r-2"
+                  type="continue"
+                  tip="Continue"
+                  onClick={() => {
+                    Question.continueLink('sentence', record);
+                  }}
+                />
+              )}
+              {record.report && !!record.report.isFinish && (
+                <IconButton
+                  type="restart"
+                  tip="Restart"
+                  onClick={() => {
+                    this.restart(record);
+                  }}
+                />
+              )}
             </div>
           );
         },
@@ -224,13 +241,17 @@ export default class extends Page {
         title: '报告',
         width: 30,
         align: 'right',
-        render: (record) => {
+        render: record => {
           if (!record.report || !record.report.isFinish) return null;
           return (
             <div className="table-row p-t-1">
-              <IconButton type="report" tip="Report" onClick={() => {
-                Question.reportLink(record);
-              }} />
+              <IconButton
+                type="report"
+                tip="Report"
+                onClick={() => {
+                  Question.reportLink(record);
+                }}
+              />
             </div>
           );
         },
@@ -255,7 +276,7 @@ export default class extends Page {
 
   init() {
     Main.getExercise().then(result => {
-      const list = result.map((row) => {
+      const list = result.map(row => {
         row.title = `${row.titleZh}${row.titleEn}`;
         row.key = row.extend;
         return row;
@@ -272,7 +293,7 @@ export default class extends Page {
     const { info = {} } = this.props.user;
     if (info.latestExercise) {
       // 获取最后一次做题记录
-      Question.baseReport(info.latestExercise).then((result) => {
+      Question.baseReport(info.latestExercise).then(result => {
         this.setState({ latest: result });
       });
     }
@@ -299,12 +320,13 @@ export default class extends Page {
     const { sentence } = this.state;
     if (!sentence) {
       User.clearSentenceTrail();
-      Sentence.getInfo().then(result => {
-        // result.code = '123123';
-        // result.trailPages = 20;
-        this.setState({ sentence: result });
-        return result;
-      })
+      Sentence.getInfo()
+        .then(result => {
+          // result.code = '123123';
+          // result.trailPages = 20;
+          this.setState({ sentence: result });
+          return result;
+        })
         .then(({ code, trailPages, chapters }) => {
           return Sentence.listArticle().then(result => {
             const chapterSteps = [];
@@ -322,7 +344,7 @@ export default class extends Page {
               if (row.exercise) exerciseChapter = row;
             });
 
-            result.forEach((article) => {
+            result.forEach(article => {
               if (article.chapter === 0) introduction = article;
               if (!map[article.chapter]) {
                 map[article.chapter] = [];
@@ -417,22 +439,26 @@ export default class extends Page {
       this.setState({ tab2 });
     }
     const [type] = subject.children.filter(row => row.key === tab2);
-    Question.getExerciseProgress(type.id).then((result => {
+    Question.getExerciseProgress(type.id).then(result => {
       // const exerciseProgress = getMap(r, 'id');
       result = result.map(row => {
-        row.info = [{
-          title: '已做',
-          number: row.userNumber || 0,
-          unit: '题',
-        }, {
-          title: '剩余',
-          number: row.questionNumber - row.userNumber || 0,
-          unit: '题',
-        }, {
-          title: '总计',
-          number: row.questionNumber || 0,
-          unit: '题',
-        }];
+        row.info = [
+          {
+            title: '已做',
+            number: row.userNumber || 0,
+            unit: '题',
+          },
+          {
+            title: '剩余',
+            number: row.questionNumber - row.userNumber || 0,
+            unit: '题',
+          },
+          {
+            title: '总计',
+            number: row.questionNumber || 0,
+            unit: '题',
+          },
+        ];
         if (row.userStat) {
           row.correct = formatPercent(row.userStat.userCorrect, row.userStat.userNumber, false);
         } else {
@@ -441,7 +467,7 @@ export default class extends Page {
         row.progress = formatPercent(row.questionNumber - row.userNumber || 0, row.questionNumber);
         row.totalCorrect = formatPercent(row.stat.totalCorrect, row.stat.totalNumber, false);
 
-        row.children = row.children.map((r) => {
+        row.children = row.children.map(r => {
           r.title = r.title || r.titleZh;
           r.progress = formatPercent(r.userNumber, r.questionNumber);
           return r;
@@ -449,7 +475,7 @@ export default class extends Page {
         return row;
       });
       this.setState({ exerciseProgress: result });
-    }));
+    });
   }
 
   onChangePreviewType(type) {
@@ -547,26 +573,36 @@ export default class extends Page {
     const children = subject ? subject.children : [];
     return (
       <div>
-        {latest && <Continue
-          data={latest}
-          onClose={() => {
-            this.clearExercise();
-          }}
-          onContinue={() => {
-            Question.continueLink('exercise', latest);
-          }}
-          onRestart={() => {
-            this.restart(latest);
-          }}
-          onNext={() => {
-            Question.continueLink('exercise', latest);
-          }} />}
+        {latest && (
+          <Continue
+            data={latest}
+            onClose={() => {
+              this.clearExercise();
+            }}
+            onContinue={() => {
+              Question.continueLink('exercise', latest);
+            }}
+            onRestart={() => {
+              this.restart(latest);
+            }}
+            onNext={() => {
+              Question.continueLink('exercise', latest);
+            }}
+          />
+        )}
         <div className="content">
           <Module className="m-t-2">
-            <Tabs type="card" active={tab1} tabs={tabs} onChange={key => {
-              this.onChangeTab(1, key);
-            }} />
-            {children.length > 1 && <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />}
+            <Tabs
+              type="card"
+              active={tab1}
+              tabs={tabs}
+              onChange={key => {
+                this.onChangeTab(1, key);
+              }}
+            />
+            {children && children.length > 1 && (
+              <Tabs active={tab2} tabs={children} onChange={key => this.onChangeTab(2, key)} />
+            )}
           </Module>
           {tab1 !== SENTENCE && tab1 !== PREVIEW && this.renderExercise()}
           {tab1 === SENTENCE && this.renderSentence()}
@@ -632,6 +668,18 @@ export default class extends Page {
             },
             { type: 'select', checked: 'all', list: [{ key: 'all', title: '全部' }] },
           ]}
+          rightAction={
+            <div>
+              有效期至:2019-11-13{' '}
+              <Tooltip overlayClassName="gray" placement="top" title="全部模考做完才可重置">
+                <a>
+                  <Button size="small" disabled radius>
+                    Reset
+                  </Button>
+                </a>
+              </Tooltip>
+            </div>
+          }
           data={previews}
           columns={this.columns}
         />
@@ -649,7 +697,19 @@ export default class extends Page {
   }
 
   renderSentenceArticle() {
-    const { sentence = {}, introduction, chapterSteps, chapterStep = 1, exerciseChapter = {}, chapterMap = {}, articleMap = {}, trailArticles = [], paperFilterList = [], paperList = [], paperChecked } = this.state;
+    const {
+      sentence = {},
+      introduction,
+      chapterSteps,
+      chapterStep = 1,
+      exerciseChapter = {},
+      chapterMap = {},
+      articleMap = {},
+      trailArticles = [],
+      paperFilterList = [],
+      paperList = [],
+      paperChecked,
+    } = this.state;
     const { sentenceTrail } = this.props.user;
     let maxStep = 0;
     if (sentenceTrail) {
@@ -664,73 +724,97 @@ export default class extends Page {
     if (chapterInfo && chapterInfo.exercise) {
       isExercise = true;
     }
-    return <div>
-      {sentence.code && <div className='sentence-code'>CODE: {sentence.code}</div>}
-      {sentenceTrail && <div className='sentence-code'>CODE: <Link to=''>去获取</Link><a onClick={() => {
-        this.setState({ sentenceModel: true });
-      }}>输入</a></div>}
-      <Module>
-        <Step
-          list={chapterSteps}
-          step={chapterStep}
-          onClick={(step) => {
-            this.setState({ chapterStep: step });
-          }}
-          message='请购买后访问'
-          maxStep={maxStep}
-        />
-      </Module>
-      {/* 正常前言 */}
-      {sentence.code && chapter === 0 && <List
-        // title={chapterInfo.title}
-        list={[introduction]}
-        onClick={() => {
-          this.sentenceRead();
-        }}
-      />}
-      {/* 正常文章 */}
-      {sentence.code && chapter > 0 && !isExercise && <List
-        position={`Chapter${chapter}`}
-        title={chapterInfo.title}
-        list={articleMap[chapter]}
-        onClick={(part) => {
-          this.sentenceRead(part);
-        }}
-      />}
-      {/* 正常练习 */}
-      {sentence.code && isExercise && <ListTable
-        position={`Chapter${chapter}`}
-        title={chapterInfo.title}
-        filters={[{
-          type: 'radio',
-          checked: paperChecked,
-          list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
-          onChange: (item) => {
-            this.sentenceFilter(item.key);
-          },
-        }]}
-        data={paperFilterList}
-        columns={this.sentenceColums}
-      />}
-      {/* 试读文章 */}
-      {sentenceTrail && trailArticles.map((info) => {
-        return <List
-          position={info.value ? `Chapter${info.value}` : null}
-          title={info.title}
-          list={info.articles}
-          onClick={(part) => {
-            this.sentenceRead(part);
-          }}
-        />;
-      })}
-      {/* 试练 */}
-      {sentenceTrail && <ListTable
-        position={`Chapter${exerciseChapter.value}`}
-        title={exerciseChapter.title}
-        data={paperList}
-        columns={this.sentenceColums}
-      />}
-    </div>;
+    return (
+      <div>
+        {sentence.code && <div className="sentence-code">CODE: {sentence.code}</div>}
+        {sentenceTrail && (
+          <div className="sentence-code">
+            CODE: <Link to="">去获取</Link>
+            <a
+              onClick={() => {
+                this.setState({ sentenceModel: true });
+              }}
+            >
+              输入
+            </a>
+          </div>
+        )}
+        <Module>
+          <Step
+            list={chapterSteps}
+            step={chapterStep}
+            onClick={step => {
+              this.setState({ chapterStep: step });
+            }}
+            message="请购买后访问"
+            maxStep={maxStep}
+          />
+        </Module>
+        {/* 正常前言 */}
+        {sentence.code && chapter === 0 && (
+          <List
+            // title={chapterInfo.title}
+            list={[introduction]}
+            onClick={() => {
+              this.sentenceRead();
+            }}
+          />
+        )}
+        {/* 正常文章 */}
+        {sentence.code && chapter > 0 && !isExercise && (
+          <List
+            position={`Chapter${chapter}`}
+            title={chapterInfo.title}
+            list={articleMap[chapter]}
+            onClick={part => {
+              this.sentenceRead(part);
+            }}
+          />
+        )}
+        {/* 正常练习 */}
+        {sentence.code && isExercise && (
+          <ListTable
+            position={`Chapter${chapter}`}
+            title={chapterInfo.title}
+            filters={[
+              {
+                type: 'radio',
+                checked: paperChecked,
+                list: [{ key: 0, title: '未完成' }, { key: 1, title: '已完成' }],
+                onChange: item => {
+                  this.sentenceFilter(item.key);
+                },
+              },
+            ]}
+            data={paperFilterList}
+            columns={this.sentenceColums}
+          />
+        )}
+        {/* 试读文章 */}
+        {sentenceTrail &&
+          trailArticles.map(info => {
+            return (
+              <List
+                position={info.value ? `Chapter${info.value}` : null}
+                title={info.title}
+                list={info.articles}
+                onClick={part => {
+                  this.sentenceRead(part);
+                }}
+              />
+            );
+          })}
+        {/* 试练 */}
+        {sentenceTrail && (
+          <ListTable
+            position={`Chapter${exerciseChapter.value}`}
+            title={exerciseChapter.title}
+            data={paperList}
+            columns={this.sentenceColums}
+          />
+        )}
+      </div>
+    );
   }
 
   renderInputCode() {
@@ -739,13 +823,22 @@ export default class extends Page {
       <Module className="code-module">
         <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
         <div className="input-block">
-          <Input size="lager" placeholder="请输入CODE" onChange={(value) => {
-            this.code = value;
-          }} />
-          <Button size="lager" onClick={() => {
-            this.activeSentence();
-          }}>解锁</Button>
-          {sentenceError && <div className='error'>{sentenceError}</div>}
+          <Input
+            size="lager"
+            placeholder="请输入CODE"
+            onChange={value => {
+              this.code = value;
+            }}
+          />
+          <Button
+            size="lager"
+            onClick={() => {
+              this.activeSentence();
+            }}
+          >
+            解锁
+          </Button>
+          {sentenceError && <div className="error">{sentenceError}</div>}
         </div>
         <div className="tip">
           <Link to="/" className="left link">
@@ -755,9 +848,12 @@ export default class extends Page {
           <Link to="/" className="link">
             去获取 >>
           </Link>
-          <a onClick={() => {
-            this.trailSentence();
-          }} className="right link">
+          <a
+            onClick={() => {
+              this.trailSentence();
+            }}
+            className="right link"
+          >
             试用 >>
           </a>
         </div>
@@ -767,59 +863,76 @@ export default class extends Page {
 
   renderInputCodeModel() {
     const { sentenceError } = this.state;
-    return <Modal visible closable={false} footer={false} title={false}>
-      <div className="code-module-modal">
-        <div className="title">请输入CODE</div>
-        <div className="desc">
-          <Input onChange={(value) => {
-            this.code = value;
-          }} />
-          {sentenceError && <div className='error'>{sentenceError}</div>}
-          <div className='tip'>
-            <Link to="/" className="right link">
-              什么是CODE?
-          </Link>
+    return (
+      <Modal visible closable={false} footer={false} title={false}>
+        <div className="code-module-modal">
+          <div className="title">请输入CODE</div>
+          <div className="desc">
+            <Input
+              onChange={value => {
+                this.code = value;
+              }}
+            />
+            {sentenceError && <div className="error">{sentenceError}</div>}
+            <div className="tip">
+              <Link to="/" className="right link">
+                什么是CODE?
+              </Link>
+            </div>
+          </div>
+          <div className="btn-list">
+            <AnswerButton size="lager" theme="confirm" width={150} onClick={() => this.activeSentence()}>
+              确认
+            </AnswerButton>
+            <AnswerButton
+              size="lager"
+              theme="cancel"
+              width={150}
+              onClick={() => this.setState({ sentenceModel: false })}
+            >
+              取消
+            </AnswerButton>
           </div>
         </div>
-        <div className="btn-list">
-          <AnswerButton size="lager" theme="confirm" width={150} onClick={() => this.activeSentence()}>确认</AnswerButton>
-          <AnswerButton size="lager" theme="cancel" width={150} onClick={() => this.setState({ sentenceModel: false })}>取消</AnswerButton>
-        </div>
-      </div>
-    </Modal>;
+      </Modal>
+    );
   }
 
   renderExercise() {
     const { exerciseProgress = [] } = this.state;
-    return <div >
-      <Division col="2">
-        {(exerciseProgress || []).map((struct) => {
-          const [first] = struct.children;
-          let col = 3;
-          if (first && first.type === 'paper') {
-            col = 5;
-          }
-          return <Panel
-            title={struct.titleEn}
-            message={struct.description}
-            data={struct}
-            col={col}
-            onClick={(item) => {
-              if (item.type === 'paper') {
-                if (item.progress === 0) {
-                  Question.startLink('exercise', item);
-                } else if (item.progress === 100) {
-                  Question.startLink('exercise', item);
-                } else {
-                  Question.continueLink('exercise', item);
-                }
-              } else {
-                this.exerciseList(item);
-              }
-            }}
-          />;
-        })}
-      </Division>
-    </div>;
+    return (
+      <div>
+        <Division col="2">
+          {(exerciseProgress || []).map(struct => {
+            const [first] = struct.children;
+            let col = 3;
+            if (first && first.type === 'paper') {
+              col = 5;
+            }
+            return (
+              <Panel
+                title={struct.titleEn}
+                message={struct.description}
+                data={struct}
+                col={col}
+                onClick={item => {
+                  if (item.type === 'paper') {
+                    if (item.progress === 0) {
+                      Question.startLink('exercise', item);
+                    } else if (item.progress === 100) {
+                      Question.startLink('exercise', item);
+                    } else {
+                      Question.continueLink('exercise', item);
+                    }
+                  } else {
+                    this.exerciseList(item);
+                  }
+                }}
+              />
+            );
+          })}
+        </Division>
+      </div>
+    );
   }
 }

+ 12 - 1
front/project/www/routes/page/demo/page.js

@@ -5,7 +5,7 @@ import Continue from '../../../components/Continue';
 import Tabs from '../../../components/Tabs';
 import Module from '../../../components/Module';
 import Division from '../../../components/Division';
-import Panel from '../../../components/Panel';
+import Panel, { TotalPanel, WaitPanel, BuyPanel } from '../../../components/Panel';
 import Card from '../../../components/Card';
 import List from '../../../components/List';
 import ListTable from '../../../components/ListTable';
@@ -13,6 +13,7 @@ import ProgressText from '../../../components/ProgressText';
 import IconButton from '../../../components/IconButton';
 import Step from '../../../components/Step';
 import OtherAnswer from '../../../components/OtherAnswer';
+import QAList from '../../../components/QAList';
 
 export default class extends Page {
   renderView() {
@@ -20,6 +21,16 @@ export default class extends Page {
       <div>
         <Continue date={'2019-04-29 16:30'} data={{}} />
         <div className="content">
+          <Division col="3" type="compact">
+            <TotalPanel
+              title="千行 CAT"
+              lock
+              data={{ info: [{ title: '123', number: '123', unit: '1' }, { title: '123', number: '123', unit: '1' }] }}
+            />
+            <WaitPanel title="千行 CAT" />
+            <BuyPanel title="千行 CAT" />
+          </Division>
+          <QAList data={[]} />
           <Module className="m-t-2">
             <Tabs
               type="card"

+ 281 - 51
front/project/www/routes/paper/process/base/index.js

@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import ReactDOM from 'react-dom';
 import './index.less';
-import { Checkbox } from 'antd';
+import { Checkbox, Icon as AntDIcon } from 'antd';
 import Assets from '@src/components/Assets';
 import { formatSeconds, formatSecond, getMap } from '@src/services/Tools';
 import Icon from '../../../../components/Icon';
@@ -113,10 +113,9 @@ export default class extends Component {
     const { flow } = this.props;
     const { answer } = this.state;
     if (this.checkAnswer()) {
-      flow.submit(answer)
-        .then(() => {
-          flow.next();
-        });
+      flow.submit(answer).then(() => {
+        flow.next();
+      });
     }
   }
 
@@ -135,10 +134,12 @@ export default class extends Component {
         content = this.renderDetail();
         break;
     }
-    return <div id='paper-process-base'>
-      {content}
-      {modal ? this.renderModal() : ''}
-    </div>;
+    return (
+      <div id="paper-process-base">
+        {content}
+        {modal ? this.renderModal() : ''}
+      </div>
+    );
   }
 
   renderContent() {
@@ -147,8 +148,18 @@ export default class extends Component {
     const { steps = [] } = question.content;
     return (
       <div className="block block-content">
-        {steps.length > 0 && <Navigation theme='process' list={question.content.steps} active={step} onChange={(v) => this.setState({ step: v })} />}
-        <div className="text" dangerouslySetInnerHTML={{ __html: this.formatStrem(steps.length > 0 ? steps[step].stem : question.stem) }} />
+        {steps.length > 0 && (
+          <Navigation
+            theme="process"
+            list={question.content.steps}
+            active={step}
+            onChange={v => this.setState({ step: v })}
+          />
+        )}
+        <div
+          className="text"
+          dangerouslySetInnerHTML={{ __html: this.formatStrem(steps.length > 0 ? steps[step].stem : question.stem) }}
+        />
       </div>
     );
   }
@@ -188,30 +199,40 @@ export default class extends Component {
       <div className="layout">
         <div className="fixed">
           {QuestionTypeMap[question.questionType].long}
-          {question.questionType === 'ir' && <Assets
-            className="calculator-icon"
-            name="calculator_icon"
-            onClick={() => this.setState({ showCalculator: !showCalculator })}
-          />}
+          {question.questionType === 'ir' && (
+            <Assets
+              className="calculator-icon"
+              name="calculator_icon"
+              onClick={() => this.setState({ showCalculator: !showCalculator })}
+            />
+          )}
           {/* <Assets className="collect-icon" name="collect_icon" onClick={() => {
             flow.toggleCollect();
           }} /> */}
-          <div className="collect-icon"><Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} /></div>
+          <div className="collect-icon">
+            <Icon name="star" active={userQuestion.collect} onClick={() => flow.toggleCollect()} />
+          </div>
         </div>
         <Calculator show={showCalculator} />
         <div className="layout-header">
           <div className="title">{paper.title}</div>
           <div className="right">
-            <div className="block" onClick={() => {
-              this.setState({ showTime: !showTime });
-            }}>
+            <div
+              className="block"
+              onClick={() => {
+                this.setState({ showTime: !showTime });
+              }}
+            >
               <Assets name="timeleft_icon" />
               {showTime && stageTime && `Time left ${formatSecond(stageTime)}`}
               {showTime && singleTime && `Time cost ${formatSecond(singleTime)}`}
             </div>
-            <div className="block" onClick={() => {
-              this.setState({ showNo: !showNo });
-            }}>
+            <div
+              className="block"
+              onClick={() => {
+                this.setState({ showNo: !showNo });
+              }}
+            >
               <Assets name="subjectnumber_icon" />
               {showNo && `${userQuestion.no} of ${paper.questionNumber}`}
             </div>
@@ -279,45 +300,252 @@ export default class extends Component {
   }
 
   renderExerciseStart() {
-    const { disorder } = this.state;
-    const { paper, flow } = this.props;
+    const { paper, userQuestion, singleTime, stageTime, flow } = this.props;
+    const { showTime, showNo } = this.state;
     return (
-      <div className="start">
-        <div className="bg" />
-        <div className="fixed-content">
+      <div className="layout">
+        <div className="layout-header">
           <div className="title">{paper.title}</div>
-          <div className="desc">
-            <div className="block">
-              <div className="desc-title">
-                <Assets name="subject_icon" />
-                题目总数
+          <div className="right">
+            <div
+              className="block"
+              onClick={() => {
+                this.setState({ showTime: !showTime });
+              }}
+            >
+              <Assets name="timeleft_icon" />
+              {showTime && stageTime && `Time left ${formatSecond(stageTime)}`}
+              {showTime && singleTime && `Time cost ${formatSecond(singleTime)}`}
+            </div>
+            <div
+              className="block"
+              onClick={() => {
+                this.setState({ showNo: !showNo });
+              }}
+            >
+              <Assets name="subjectnumber_icon" />
+              {showNo && `${userQuestion.no} of ${paper.questionNumber}`}
+            </div>
+          </div>
+        </div>
+        <div className={'layout-body'}>{this.renderExerciseStartCAT()}</div>
+        <div className="layout-footer">
+          <div className="help">
+            <Assets name="help_icon" />
+            Help
+          </div>
+          <div className="full">
+            <Assets name="fullscreen_icon" onClick={() => flow.toggleFullscreen()} />
+          </div>
+          <div className="next" onClick={() => this.next()}>
+            Next
+            <Assets name="next_icon" />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderExerciseStartDefault() {
+    return (
+      <div className="exercise-start default">
+        <div className="title">Section Ordering</div>
+        <div className="desc">Select the order in which the exam sections are to be administered.</div>
+        <div className="desc tip">
+          NOTE: You have 1 minutes to make your selection. If you do not make your selection within 1 minutes, the first
+          option listed will be selected and you will view the exam in the following order: Analytical Writing
+          Assessment, Integrated Reasoning, Quantitative, Verbal.
+        </div>
+        <div className="desc">
+          Once you select your section order, you must view ALL questions in each section, in the order you have
+          selected, before moving on to the next section. You will NOT be able to return to this screen.
+        </div>
+        <div className="block-list">
+          <div className="block-item">
+            <div className="block-title">
+              <div className="block-title-border">
+                <AntDIcon type="check" />
+                <span>ARQV</span>
               </div>
-              <div className="desc-info">{paper.questionNumber}</div>
             </div>
-            <div className="block">
-              <div className="desc-title">
-                <Assets name="time_icon" />
-                建议用时
+            <div className="block-text">1.Analytical Writing Analysis </div>
+            <div className="block-text">2.Integrated Reasoning </div>
+            <div className="block-text">3.Quantitative </div>
+            <div className="block-text">4.Verbal </div>
+          </div>
+          <div className="block-item">
+            <div className="block-title">
+              <div className="block-title-border">
+                <AntDIcon type="check" />
+                <span>VQRA </span>
               </div>
-              <div className="desc-info">{formatSeconds(paper.time)}</div>
             </div>
+            <div className="block-text">1.Verbal </div>
+            <div className="block-text">2.Quantitative </div>
+            <div className="block-text">3.Integrated Reasoning </div>
+            <div className="block-text">4.Analytical Writing Analysis </div>
           </div>
-          <div className="tip">
-            <Checkbox className="m-r-1" checked={disorder} onChange={() => this.setState({ disorder: !disorder })} />
-            题目选项乱序显示
+          <div className="block-item">
+            <div className="block-title">
+              <div className="block-title-border">
+                <AntDIcon type="check" />
+                <span>QVRA</span>
+              </div>
+            </div>
+            <div className="block-text">1.Quantitative</div>
+            <div className="block-text">2.Verbal </div>
+            <div className="block-text">3.Integrated Reasoning </div>
+            <div className="block-text">4.Analytical Writing Analysis </div>
           </div>
-          <div className="submit">
-            <Button size="lager" radius onClick={() => flow.start({ disorder })}>
-              开始练习
-            </Button>
+        </div>
+        <div className="bottom">
+          <div className="text">
+            <Checkbox checked /> 题目选项乱序显示
+          </div>
+          <div className="text">
+            Click{' '}
+            <div className="next" onClick={() => this.next()}>
+              Next
+              <Assets name="next_icon" />
+            </div>{' '}
+            button to start the exam. You will begin the GMAT exam on the next screen.{' '}
           </div>
         </div>
       </div>
     );
   }
 
+  renderExerciseStartCAT() {
+    return (
+      <div className="exercise-start cat">
+        <div className="title">Section Ordering</div>
+        <div className="block-list">
+          <div className="block-item">
+            <div className="block-item-body active">
+              <AntDIcon type="check" style={{ color: '#fff' }} />
+              <div className="block-text">
+                <Checkbox checked /> Analytical Writing Analysis{' '}
+              </div>
+              <div className="block-text">
+                <Checkbox checked /> Integrated Reasoning{' '}
+              </div>
+              <div className="block-text">
+                <Checkbox checked /> Quantitative{' '}
+              </div>
+              <div className="block-text">
+                <Checkbox checked /> Verbal{' '}
+              </div>
+            </div>
+          </div>
+          <div className="block-item">
+            <div className="block-item-body">
+              <div className="block-text">
+                <Checkbox /> Quantitative
+              </div>
+              <div className="block-text">
+                <Checkbox /> Verbal{' '}
+              </div>
+              <div className="block-text">
+                <Checkbox /> Integrated Reasoning{' '}
+              </div>
+              <div className="block-text">
+                <Checkbox /> Analytical Writing Analysis{' '}
+              </div>
+            </div>
+          </div>
+          <div className="block-item">
+            <div className="block-item-body">
+              <div className="block-text">
+                <Checkbox /> Verbal{' '}
+              </div>
+              <div className="block-text">
+                <Checkbox /> Quantitative{' '}
+              </div>
+              <div className="block-text">
+                <Checkbox /> Integrated Reasoning{' '}
+              </div>
+              <div className="block-text">
+                <Checkbox /> Analytical Writing Analysis{' '}
+              </div>
+            </div>
+          </div>
+        </div>
+        <div className="bottom">
+          <div className="text">
+            <Checkbox checked /> 题目选项乱序显示
+          </div>
+          <div className="text">
+            Click{' '}
+            <div className="next" onClick={() => this.next()}>
+              Next
+              <Assets name="next_icon" />
+            </div>{' '}
+            button to start the exam. You will begin the GMAT exam on the next screen.{' '}
+          </div>
+          <div className="text tip">*实战可选择考试顺序但无法选择考试内容。</div>
+        </div>
+      </div>
+    );
+  }
+
   renderRelax() {
-    return <div />;
+    const { paper, userQuestion, singleTime, stageTime, flow } = this.props;
+    const { showTime, showNo } = this.state;
+    return (
+      <div className="layout">
+        <div className="layout-header">
+          <div className="title">{paper.title}</div>
+          <div className="right">
+            <div
+              className="block"
+              onClick={() => {
+                this.setState({ showTime: !showTime });
+              }}
+            >
+              <Assets name="timeleft_icon" />
+              {showTime && stageTime && `Time left ${formatSecond(stageTime)}`}
+              {showTime && singleTime && `Time cost ${formatSecond(singleTime)}`}
+            </div>
+            <div
+              className="block"
+              onClick={() => {
+                this.setState({ showNo: !showNo });
+              }}
+            >
+              <Assets name="subjectnumber_icon" />
+              {showNo && `${userQuestion.no} of ${paper.questionNumber}`}
+            </div>
+          </div>
+        </div>
+        <div className={'layout-body'}>
+          <div className="relax">
+            <div className="title">
+              Optional Break <Icon name="question" />
+            </div>
+            <div className="time">
+              <div className="block">0</div>
+              <div className="block">1</div>
+              <div className="div">:</div>
+              <div className="block">2</div>
+              <div className="block">3</div>
+            </div>
+          </div>
+        </div>
+        <div className="layout-footer">
+          <div className="help">
+            <Assets name="help_icon" />
+            Help
+          </div>
+          <div className="full">
+            <Assets name="fullscreen_icon" onClick={() => flow.toggleFullscreen()} />
+          </div>
+          <div className="next" onClick={() => this.next()}>
+            Next
+            <Assets name="next_icon" />
+          </div>
+        </div>
+      </div>
+    );
   }
 
   renderModal() {
@@ -337,11 +565,13 @@ export default class extends Component {
                 <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 className="btn-list">
+              <div className="btn" onClick={() => this.hideModal(true)}>
+                <span className="t-d-l">O</span>k
               </div>
-          </div>)}
+            </div>
+          )}
         </div>
       </div>
     );

+ 196 - 0
front/project/www/routes/paper/process/base/index.less

@@ -219,6 +219,202 @@
         border-left: 4px solid #006DAA;
       }
     }
+
+    .layout-body .relax {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      text-align: center;
+
+      .title {
+        color: #000000;
+        font-size: 32px;
+        margin-bottom: 40px;
+
+        .assets {
+          margin-left: 10px;
+        }
+      }
+
+      .time {
+        .block {
+          display: inline-block;
+          width: 110px;
+          height: 160px;
+          line-height: 160px;
+          background: #F2F3F5;
+          text-align: center;
+          color: #383838;
+          font-size: 100px;
+          padding: 0;
+          margin: 0 10px;
+        }
+
+        .div {
+          display: inline-block;
+          color: #383838;
+          font-size: 100px;
+          line-height: 160px;
+        }
+      }
+    }
+
+    .layout-body .exercise-start {
+      padding-top: 60px;
+      padding-left: 40px;
+      padding-right: 40px;
+      padding-bottom: 20px;
+
+      .title {
+        color: #000000;
+        font-size: 32px;
+        text-align: center;
+        margin-bottom: 10px;
+      }
+
+      .desc {
+        font-size: 16px;
+        margin-bottom: 25px;
+      }
+
+      .tip {
+        color: red;
+      }
+
+      .bottom {
+        .text {
+          color: #000000;
+          font-size: 16px;
+          margin-bottom: 5px;
+        }
+
+        .tip {
+          color: red;
+        }
+
+        .next {
+          display: inline-block;
+          background: #066da9;
+          color: #fff;
+          line-height: 32px;
+          padding: 0 5px;
+          cursor: pointer;
+          box-sizing: border-box;
+          height: 32px;
+
+          .assets {
+            margin-left: 10px;
+          }
+        }
+
+        .next:hover {
+          background: darken(#006DAA, 10);
+        }
+      }
+    }
+
+    .layout-body .exercise-start.default {
+      .block-list {
+        margin-bottom: 40px;
+
+        .block-item {
+          display: inline-block;
+          width: 33.33%;
+
+          .block-title {
+            margin-bottom: 10px;
+
+            .block-title-border {
+              height: 30px;
+              line-height: 20px;
+              display: inline-block;
+              background: rgba(255, 243, 46, 1);
+              border-radius: 4px;
+              border: 1px solid rgba(25, 25, 25, 1);
+              padding: 5px 10px 5px 5px;
+              position: relative;
+              cursor: pointer;
+
+              i {
+                position: absolute;
+                left: 7px;
+                top: 9px;
+              }
+
+              span {
+                display: inline-block;
+                vertical-align: top;
+              }
+            }
+
+            .block-title-border::before {
+              content: '';
+              display: inline-block;
+              width: 20px;
+              height: 20px;
+              background: #fff;
+              border: 1px solid #000;
+              border-radius: 10px;
+              margin-right: 5px;
+            }
+          }
+
+          .block-text {
+            margin-bottom: 20px;
+            color: #686872;
+            font-size: 16px;
+          }
+        }
+      }
+    }
+
+    .layout-body .exercise-start.cat {
+      .title {
+        margin-bottom: 40px;
+      }
+
+      .block-list {
+        margin-bottom: 70px;
+
+        .block-item {
+          display: inline-block;
+          width: 33.33%;
+          padding: 0 5px;
+
+          .block-item-body {
+            border-radius: 6px;
+            padding: 60px 20px;
+            border: 2px solid #CDCDCD;
+            position: relative;
+
+            .block-text {
+              margin-bottom: 30px;
+            }
+          }
+
+          .block-item-body.active {
+            border: 2px solid #4292f0;
+
+            i {
+              position: absolute;
+              top: 5px;
+              left: 5px;
+            }
+          }
+
+          .block-item-body.active::before {
+            content: '';
+            display: inline-block;
+            position: absolute;
+            top: 0;
+            left: 0;
+            border: 20px solid;
+            border-color: #4292f0 transparent transparent #4292f0;
+          }
+        }
+      }
+    }
   }
 
   .modal {

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

@@ -30,6 +30,7 @@ export default class extends Page {
       question: {},
       userQuestion: {},
       paper: {},
+      scene: null,
     };
   }
 
@@ -42,8 +43,8 @@ export default class extends Page {
       let handler = null;
       if (paper.paperModule === 'examination') {
         // 模考获取配置信息
-        handler = Main.getExaminationNumber().then((result) => {
-          Object.keys(result).forEach((key) => {
+        handler = Main.getExaminationNumber().then(result => {
+          Object.keys(result).forEach(key => {
             result[key].time = Number(result[key].time);
             result[key].number = Number(result[key].number);
           });
@@ -89,20 +90,22 @@ export default class extends Page {
   }
 
   continue(reportId) {
-    return Question.continue(reportId).then(report => {
-      if (report.isFinish) {
-        throw new Error('做题结束,请先重置');
-      }
-      this.setState({ report, scene: 'questionn' });
-      // 更新当前做题进度
-      if (report.paperModule === 'examination') {
-        const { stage, time, number } = report.setting;
-        this.initStage(stage, time[stage], number[stage]);
-      }
-      return this.next();
-    }).catch(() => {
-      Question.reportLink({ report: { id: reportId } });
-    });
+    return Question.continue(reportId)
+      .then(report => {
+        if (report.isFinish) {
+          throw new Error('做题结束,请先重置');
+        }
+        this.setState({ report, scene: 'questionn' });
+        // 更新当前做题进度
+        if (report.paperModule === 'examination') {
+          const { stage, time, number } = report.setting;
+          this.initStage(stage, time[stage], number[stage]);
+        }
+        return this.next();
+      })
+      .catch(() => {
+        Question.reportLink({ report: { id: reportId } });
+      });
   }
 
   next() {
@@ -111,35 +114,37 @@ export default class extends Page {
     if (scene === 'relax') {
       this.nextStage();
     }
-    return Question.next(report.id).then(userQuestion => {
-      const questionSetting = {};
-      if (setting.disorder) {
-        const { questions } = userQuestion.question.content;
-        if (questions) {
-          // 乱序显示选项
-          questionSetting.questions = [];
-          questions.forEach((q) => {
-            const order = randomList(q.select.length);
-            q.select = sortListWithOrder(q.select, order);
-            questionSetting.questions.push(order);
-          });
+    return Question.next(report.id)
+      .then(userQuestion => {
+        const questionSetting = {};
+        if (setting.disorder) {
+          const { questions } = userQuestion.question.content;
+          if (questions) {
+            // 乱序显示选项
+            questionSetting.questions = [];
+            questions.forEach(q => {
+              const order = randomList(q.select.length);
+              q.select = sortListWithOrder(q.select, order);
+              questionSetting.questions.push(order);
+            });
+          }
         }
-      }
-      this.setState({
-        userQuestion,
-        question: userQuestion.question,
-        questionSetting,
-        scene: 'question',
+        this.setState({
+          userQuestion,
+          question: userQuestion.question,
+          questionSetting,
+          scene: 'question',
+        });
+        this.singleQuestionTime();
+        return true;
+      })
+      .catch(err => {
+        if (err.message === 'finish') {
+          // 考试结束
+          return this.finish();
+        }
+        return false;
       });
-      this.singleQuestionTime();
-      return true;
-    }).catch((err) => {
-      if (err.message === 'finish') {
-        // 考试结束
-        return this.finish();
-      }
-      return false;
-    });
   }
 
   submit(answer) {
@@ -151,7 +156,7 @@ export default class extends Page {
         // 还原乱序选项
         questions.forEach((q, index) => {
           const order = questionSetting.questions[index];
-          Object.keys(q).forEach((k) => {
+          Object.keys(q).forEach(k => {
             if (q[k]) q[k] = resortListWithOrder(q[k], order);
           });
         });
@@ -172,22 +177,26 @@ export default class extends Page {
 
   stage() {
     const { report } = this.state;
-    return Question.stage(report.id).then(() => {
-      return this.next();
-    }).then(() => {
-      this.stageQuestionTime();
-    });
+    return Question.stage(report.id)
+      .then(() => {
+        return this.next();
+      })
+      .then(() => {
+        this.stageQuestionTime();
+      });
   }
 
   finish() {
     const { report } = this.state;
-    return Question.finish(report.id).then(() => {
-      // 跳转到报告页
-      Question.reportLink({ report });
-      // this.setState({ scene: 'finish' });
-    }).catch(() => {
-      Question.reportLink({ report });
-    });
+    return Question.finish(report.id)
+      .then(() => {
+        // 跳转到报告页
+        Question.reportLink({ report });
+        // this.setState({ scene: 'finish' });
+      })
+      .catch(() => {
+        Question.reportLink({ report });
+      });
   }
 
   singleQuestionTime(stop) {
@@ -267,12 +276,14 @@ export default class extends Page {
   }
 
   renderView() {
-    return <Fullscreen
-      enabled={this.state.isFullscreenEnabled}
-      onChange={isFullscreenEnabled => this.setState({ isFullscreenEnabled })}
-    >
-      {this.renderDetail()}
-    </Fullscreen>;
+    return (
+      <Fullscreen
+        enabled={this.state.isFullscreenEnabled}
+        onChange={isFullscreenEnabled => this.setState({ isFullscreenEnabled })}
+      >
+        {this.renderDetail()}
+      </Fullscreen>
+    );
   }
 
   renderDetail() {

+ 46 - 0
front/project/www/routes/sentence/read/index.less

@@ -46,6 +46,52 @@
         }
       }
 
+      .free-over {
+        width: 560px;
+        margin: 240px auto;
+        text-align: center;
+
+        .free-over-title {
+          color: #333;
+          font-size: 16px;
+          margin-bottom: 10px;
+        }
+
+        .free-over-btn {
+          display: inline-block;
+          background: #4292F0;
+          border-radius: 25px;
+          padding: 5px 20px;
+          margin-bottom: 40px;
+          color: #fff;
+          font-size: 20px;
+          cursor: pointer;
+        }
+
+        .free-over-btn:hover {
+          background: darken(#4292F0, 5)
+        }
+
+        .free-over-desc {
+          width: 560px;
+          background: rgba(245, 245, 245, 1);
+          border-radius: 4px;
+          padding: 20px;
+
+          .free-over-desc-title {
+            font-size: 12px;
+            color: #989898;
+            text-align: left;
+            margin-bottom: 5px;
+          }
+
+          .free-over-desc-content {
+            color: #5E677B;
+            text-align: left;
+          }
+        }
+      }
+
     }
 
     .layout-menu {

+ 150 - 81
front/project/www/routes/sentence/read/page.js

@@ -44,43 +44,46 @@ export default class extends Page {
 
   refreshSentence() {
     if (this.inited) return Promise.resolve();
-    return Sentence.listArticle().then(result => {
-      const articleMap = {};
-      let totalPage = 0;
-      let maxChapter = 0;
-      let introduction = null;
-      result.forEach((article) => {
-        if (article.chapter === 0) introduction = article;
-        if (!articleMap[article.chapter]) {
-          articleMap[article.chapter] = [];
-          if (article.chapter > maxChapter) maxChapter = article.chapter;
-        }
-        article.startPage = totalPage + 1;
-        article.endPage = totalPage + article.pages;
-        totalPage += article.pages;
-        articleMap[article.chapter].push(article);
-      });
-      this.setState({ articleMap, totalPage, maxChapter });
-      return introduction;
-    }).then((introduction) => {
-      return Sentence.getInfo().then(result => {
-        const map = {};
-        // 添加前言
-        if (introduction) {
-          result.chapters.unshift({
-            title: introduction.title,
-            value: 0,
-          });
-        }
+    return Sentence.listArticle()
+      .then(result => {
+        const articleMap = {};
+        let totalPage = 0;
+        let maxChapter = 0;
+        let introduction = null;
+        result.forEach(article => {
+          if (article.chapter === 0) introduction = article;
+          if (!articleMap[article.chapter]) {
+            articleMap[article.chapter] = [];
+            if (article.chapter > maxChapter) maxChapter = article.chapter;
+          }
+          article.startPage = totalPage + 1;
+          article.endPage = totalPage + article.pages;
+          totalPage += article.pages;
+          articleMap[article.chapter].push(article);
+        });
+        this.setState({ articleMap, totalPage, maxChapter });
+        return introduction;
+      })
+      .then(introduction => {
+        return Sentence.getInfo().then(result => {
+          const map = {};
+          // 添加前言
+          if (introduction) {
+            result.chapters.unshift({
+              title: introduction.title,
+              value: 0,
+            });
+          }
 
-        result.chapters.forEach((row) => {
-          map[row.value] = row;
+          result.chapters.forEach(row => {
+            map[row.value] = row;
+          });
+          this.setState({ sentence: result, chapterMap: map });
         });
-        this.setState({ sentence: result, chapterMap: map });
+      })
+      .then(() => {
+        this.inited = true;
       });
-    }).then(() => {
-      this.inited = true;
-    });
   }
 
   refreshArticle(articleId) {
@@ -116,7 +119,7 @@ export default class extends Page {
     this.setState({ inputPage: targetPage });
     const { article = {} } = this.state;
     this.updateProgress(target, index, article);
-    this.refreshArticle(target.id).then((row) => {
+    this.refreshArticle(target.id).then(row => {
       this.setState({ article: row, index, currentPage: targetPage });
     });
   }
@@ -181,7 +184,7 @@ export default class extends Page {
     const now = new Date();
     const time = (now.getTime() - this.lastTime.getTime()) / 1000;
     this.lastTime = now;
-    const progress = (index + 1) * 100 / target.pages;
+    const progress = ((index + 1) * 100) / target.pages;
     Sentence.updateProgress(target.chapter, target.part, progress, time, current.chapter, current.page);
     this.timeout = setTimeout(() => {
       // 最长5分钟阅读时间
@@ -202,7 +205,7 @@ export default class extends Page {
 
   renderBody() {
     const { showMenu, article, index, chapterMap = {} } = this.state;
-    return article && (
+    return article ? (
       <div className="layout-body">
         <div className="crumb">千行长难句解析 >> {(chapterMap[article.chapter] || {}).title}</div>
         {article.chapter > 0 && <div className="title">{article.title}</div>}
@@ -211,6 +214,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>
+        </div>
+      </div>
     );
   }
 
@@ -223,35 +240,61 @@ export default class extends Page {
     return (
       <div className="layout-menu">
         <div className="title">目录</div>
-        <div className="close" onClick={() => {
-          this.setState({ showMenu: false });
-        }}>x</div>
+        <div
+          className="close"
+          onClick={() => {
+            this.setState({ showMenu: false });
+          }}
+        >
+          x
+        </div>
         <div className="chapter">
           {chapters.map(chapter => {
             if (chapter.exercise) {
-              return [<div className={'chapter-item trail'}>
-                {chapter.title}
-              </div>];
+              return [<div className={'chapter-item trail'}>{chapter.title}</div>];
             }
             chapter.startPage = page;
-            const list = code ? [<div className={'chapter-item'} onClick={() => {
-              this.jumpPage(chapter.startPage);
-            }}>
-              {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>];
+            const _item = code ? (
+              <div
+                className={'chapter-item'}
+                onClick={() => {
+                  this.jumpPage(chapter.startPage);
+                }}
+              >
+                {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>
+            );
+            const list = [_item];
             if (chapter.value) {
-              (articleMap[chapter.value] || []).forEach((article) => {
+              (articleMap[chapter.value] || []).forEach(article => {
                 // 得到下一章节page
                 page += article.pages;
-                const item = code ? <div className={'part-item'} onClick={() => {
-                  if (code) this.jumpPage(article.startPage);
-                }}>
-                  {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>;
+                const item = code ? (
+                  <div
+                    className={'part-item'}
+                    onClick={() => {
+                      if (code) this.jumpPage(article.startPage);
+                    }}
+                  >
+                    {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>
+                );
                 list.push(item);
               });
             }
@@ -265,19 +308,28 @@ export default class extends Page {
   renderRight() {
     return (
       <div className="layout-right">
-        <div className="m-b-1" onClick={() => {
-          this.setState({ showMenu: true });
-        }}>
+        <div
+          className="m-b-1"
+          onClick={() => {
+            this.setState({ showMenu: true });
+          }}
+        >
           <Icon name="menu" />
         </div>
-        <div className="m-b-1" onClick={() => {
-          this.prevPage();
-        }}>
+        <div
+          className="m-b-1"
+          onClick={() => {
+            this.prevPage();
+          }}
+        >
           <Icon name="down_turn" />
         </div>
-        <div className="m-b-1" onClick={() => {
-          this.nextPage();
-        }}>
+        <div
+          className="m-b-1"
+          onClick={() => {
+            this.nextPage();
+          }}
+        >
           <Icon name="up_turn" />
         </div>
       </div>
@@ -288,22 +340,34 @@ export default class extends Page {
     const { showJump, currentPage, totalPage } = this.state;
     return (
       <div className="layout-bottom">
-        <span className="per">{totalPage ? parseInt(currentPage * 100 / totalPage, 10) : 0}%</span>
-        <span className="num">{currentPage}/{totalPage}</span>
+        <span className="per">{totalPage ? parseInt((currentPage * 100) / totalPage, 10) : 0}%</span>
+        <span className="num">
+          {currentPage}/{totalPage}
+        </span>
         <span className="btn">
-          <Assets name={showJump ? 'unfold_icon_down' : 'unfold_icon_up'} onClick={() => {
-            this.setState({ showJump: !showJump });
-          }} />
+          <Assets
+            name={showJump ? 'unfold_icon_down' : 'unfold_icon_up'}
+            onClick={() => {
+              this.setState({ showJump: !showJump });
+            }}
+          />
           <div hidden={!showJump} className="jump">
             <span className="text">当前页</span>
-            <input className="input" value={this.state.inputPage} onChange={(e) => {
-              this.page = Number(e.target.value);
-              this.setState({ inputPage: e.target.value });
-            }} />
-            <Assets name="yes_icon" onClick={() => {
-              if (this.page < 1 || this.page > totalPage) return;
-              this.jumpPage(this.page);
-            }} />
+            <input
+              className="input"
+              value={this.state.inputPage}
+              onChange={e => {
+                this.page = Number(e.target.value);
+                this.setState({ inputPage: e.target.value });
+              }}
+            />
+            <Assets
+              name="yes_icon"
+              onClick={() => {
+                if (this.page < 1 || this.page > totalPage) return;
+                this.jumpPage(this.page);
+              }}
+            />
           </div>
         </span>
       </div>
@@ -314,7 +378,12 @@ export default class extends Page {
     const { currentPage, totalPage } = this.state;
     return (
       <div className="layout-progress">
-        <Progress size="small" theme="theme" radius={false} progress={totalPage ? parseInt(currentPage * 100 / totalPage, 10) : 0} />
+        <Progress
+          size="small"
+          theme="theme"
+          radius={false}
+          progress={totalPage ? parseInt((currentPage * 100) / totalPage, 10) : 0}
+        />
       </div>
     );
   }