Procházet zdrojové kódy

Merge branch 'master' of git.proginn.com:zaixianjiaoyu/sourcecode

Go před 6 roky
rodič
revize
3256338781
100 změnil soubory, kde provedl 6729 přidání a 253 odebrání
  1. 6 24
      front/.eslintrc
  2. 17 0
      front/project/h5/app.js
  3. 275 0
      front/project/h5/app.less
  4. 8 0
      front/project/h5/index.js
  5. 18 0
      front/project/h5/local.json
  6. 5 0
      front/project/h5/routes/index.js
  7. 9 0
      front/project/h5/routes/page/home/index.js
  8. 3 0
      front/project/h5/routes/page/home/index.less
  9. 9 0
      front/project/h5/routes/page/home/page.js
  10. 4 0
      front/project/h5/routes/page/index.js
  11. 9 0
      front/project/h5/routes/page/login/index.js
  12. 3 0
      front/project/h5/routes/page/login/index.less
  13. 24 0
      front/project/h5/routes/page/login/page.js
  14. 3 0
      front/project/h5/stores/index.js
  15. 69 0
      front/project/h5/stores/user.js
  16. 7 0
      front/project/www/app.less
  17. 77 6
      front/project/www/components/Answer/index.js
  18. 16 6
      front/project/www/components/AnswerSelect/index.js
  19. 2 2
      front/project/www/components/Button/index.less
  20. 104 98
      front/project/www/components/Card/index.js
  21. 23 0
      front/project/www/components/Editor/index.js
  22. 7 0
      front/project/www/components/Editor/index.less
  23. 1 1
      front/project/www/components/ListTable/index.js
  24. 1 1
      front/project/www/components/Navigation/index.js
  25. 2 2
      front/project/www/components/RadioButton/index.js
  26. 1 1
      front/project/www/components/Select/index.js
  27. 21 0
      front/project/www/components/Select/index.less
  28. 1 8
      front/project/www/components/Table/index.js
  29. 15 11
      front/project/www/components/Tabs/index.js
  30. 3 0
      front/project/www/components/Tabs/index.less
  31. 5 11
      front/project/www/local.json
  32. 10 0
      front/project/www/routes/page/login/page.js
  33. 17 0
      front/project/www/routes/page/practise/index.less
  34. 321 31
      front/project/www/routes/page/practise/page.js
  35. 1 1
      front/project/www/routes/page/start/index.js
  36. 52 0
      front/project/www/routes/page/start/index.less
  37. 200 50
      front/project/www/routes/page/start/page.js
  38. 1703 0
      front/project/www/static/ckeditor/CHANGES.md
  39. 1420 0
      front/project/www/static/ckeditor/LICENSE.md
  40. 39 0
      front/project/www/static/ckeditor/README.md
  41. 10 0
      front/project/www/static/ckeditor/adapters/jquery.js
  42. 97 0
      front/project/www/static/ckeditor/build-config.js
  43. 1237 0
      front/project/www/static/ckeditor/ckeditor.js
  44. 38 0
      front/project/www/static/ckeditor/config.js
  45. 208 0
      front/project/www/static/ckeditor/contents.css
  46. 5 0
      front/project/www/static/ckeditor/lang/en.js
  47. 10 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js
  48. 25 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/_translationstatus.txt
  49. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/af.js
  50. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ar.js
  51. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/az.js
  52. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/bg.js
  53. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ca.js
  54. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/cs.js
  55. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/cy.js
  56. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/da.js
  57. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/de-ch.js
  58. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/de.js
  59. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/el.js
  60. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/en-au.js
  61. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/en-gb.js
  62. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/en.js
  63. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/eo.js
  64. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/es-mx.js
  65. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/es.js
  66. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/et.js
  67. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/eu.js
  68. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fa.js
  69. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fi.js
  70. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fo.js
  71. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fr-ca.js
  72. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fr.js
  73. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/gl.js
  74. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/gu.js
  75. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/he.js
  76. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/hi.js
  77. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/hr.js
  78. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/hu.js
  79. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/id.js
  80. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/it.js
  81. 9 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ja.js
  82. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/km.js
  83. 10 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ko.js
  84. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ku.js
  85. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/lt.js
  86. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/lv.js
  87. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/mk.js
  88. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/mn.js
  89. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/nb.js
  90. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/nl.js
  91. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/no.js
  92. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/oc.js
  93. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/pl.js
  94. 13 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/pt-br.js
  95. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/pt.js
  96. 12 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ro.js
  97. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ru.js
  98. 10 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/si.js
  99. 11 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/sk.js
  100. 0 0
      front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/sl.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"]]
       }
     }
   },
@@ -48,7 +29,8 @@
     "goBack": false,
     "toLink": false,
     "openLink": false,
-    "WxLogin": false
+    "WxLogin": false,
+    "CKEDITOR": false
   },
   "rules": {
     "camelcase": "off",
@@ -73,4 +55,4 @@
     "global-require": "off",
     "import/no-extraneous-dependencies": "off"
   }
-}
+}

+ 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;
 }

+ 77 - 6
front/project/www/components/Answer/index.js

@@ -3,18 +3,88 @@ import './index.less';
 import RadioItem from '../RadioItem';
 
 export default class Answer extends Component {
+  constructor(props) {
+    super(props);
+    const single = [];
+    const double = [];
+    switch (props.type) {
+      case 'single':
+        for (let i = 0; i < props.list.length; i += 1) {
+          single.push(false);
+        }
+        break;
+      case 'double':
+        for (let i = 0; i < props.list.length; i += 1) {
+          double.push([false, false]);
+        }
+        break;
+      default:
+        break;
+    }
+    this.state = { single, double };
+  }
+
+  onChangeSingle(index) {
+    const { single } = this.state;
+    for (let i = 0; i < single.length; i += 1) {
+      single[i] = false;
+    }
+    single[index] = true;
+    this.setState({ single });
+    this.onChange(single);
+  }
+
+  onChangeDouble(index, column) {
+    const { direction } = this.props;
+    let { double } = this.state;
+    switch (direction) {
+      case 'landscape':
+        double[index] = double[index].map(() => false);
+        break;
+      case 'portrait':
+        double = double.map(row => {
+          row[index] = false;
+          return row;
+        });
+        break;
+      default:
+        break;
+    }
+    double[index][column] = true;
+    this.setState({ double });
+    this.onChange(double);
+  }
+
+  onChange(value) {
+    const { onChange } = this.props;
+    if (onChange) onChange(value);
+  }
+
   render() {
-    return <div className="answer">{this.renderTable()}</div>;
+    return <div className="answer">{this.renderDetail()}</div>;
+  }
+
+  renderDetail() {
+    const { type } = this.props;
+    switch (type) {
+      case 'single':
+        return this.renderList();
+      case 'double':
+        return this.renderTable();
+      default:
+        return <div />;
+    }
   }
 
   renderList() {
     const { list = [] } = this.props;
+    const { single = [] } = this.state;
     return (
       <div className="list">
-        {list.map(item => {
+        {list.map((item, index) => {
           return (
             <div className="item">
-              <RadioItem checked onClick={() => {}} />
+              <RadioItem checked={single[index]} onClick={() => this.onChangeSingle(index)} />
               <div className="text">{item}</div>
             </div>
           );
@@ -25,6 +95,7 @@ export default class Answer extends Component {
 
   renderTable() {
     const { list = [] } = this.props;
+    const { double = [] } = this.state;
     return (
       <table border="1" bordercolor="#d8d8d8">
         <thead>
@@ -39,14 +110,14 @@ export default class Answer extends Component {
           </tr>
         </thead>
         <tbody>
-          {list.map(item => {
+          {list.map((item, index) => {
             return (
               <tr>
                 <td align="center" className="bg">
-                  <RadioItem checked onClick={() => {}} />
+                  <RadioItem checked={double[index][0]} onClick={() => this.onChangeDouble(index, 0)} />
                 </td>
                 <td align="center" className="bg">
-                  <RadioItem checked onClick={() => {}} />
+                  <RadioItem checked={double[index][1]} onClick={() => this.onChangeDouble(index, 1)} />
                 </td>
                 <td>{item}</td>
               </tr>

+ 16 - 6
front/project/www/components/AnswerSelect/index.js

@@ -5,12 +5,22 @@ import Assets from '@src/components/Assets';
 export default class AnswerSelect extends Component {
   constructor(props) {
     super(props);
-    this.state = { selecting: false };
+    const select = [];
+    for (let i = 0; i < props.list.length; i += 1) {
+      select[i] = false;
+    }
+    this.state = { selecting: false, select };
   }
 
-  componentWillMount() {}
-
-  componentWillUnmount() {}
+  onChange(index) {
+    const { onChange } = this.props;
+    const { select } = this.state;
+    for (let i = 0; i < select.length; i += 1) {
+      select[i] = false;
+    }
+    select[index] = true;
+    if (onChange) onChange(select);
+  }
 
   open() {
     this.setState({ selecting: true });
@@ -30,7 +40,7 @@ export default class AnswerSelect extends Component {
         break;
       }
     }
-    const title = list.length > 0 ? list[index].title : '';
+    const title = list.length > 0 ? list[index] : '';
     return (
       <div className="answer-select">
         <div hidden={!selecting} className="mask" onClick={() => this.close()} />
@@ -43,7 +53,7 @@ export default class AnswerSelect extends Component {
             {list.map(item => {
               return (
                 <div className="select-option" onClick={() => this.close()}>
-                  {item.title}
+                  {item}
                 </div>
               );
             })}

+ 2 - 2
front/project/www/components/Button/index.less

@@ -51,7 +51,7 @@
 
 .button.default {
   background: #fff;
-  color: @base_color;
+  color: @holder_color;
   border: 1px solid @line_color;
 }
 
@@ -69,5 +69,5 @@
 }
 
 .button.default:hover {
-  border-color: @base_color;
+  border-color: @holder_color;
 }

+ 104 - 98
front/project/www/components/Card/index.js

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { Component } from 'react';
 import './index.less';
 import { Link } from 'react-router-dom';
 import { Checkbox } from 'antd';
@@ -7,110 +7,116 @@ import Progress from '../Progress';
 import IconButton from '../IconButton';
 import Button from '../Button';
 
-function getBuyBody(data) {
-  const desc = data.desc || [];
-  return (
-    <div className="body">
-      <div className="desc">
-        {desc.map(item => {
-          return <div className="item">{item}</div>;
-        })}
-      </div>
-      <div className="btn">
-        <Button size="lager" radius>
-          立即购买
-        </Button>
-      </div>
-    </div>
-  );
-}
-function getOpenBody() {
-  return (
-    <div className="body">
-      <div className="title">请开通预习作业</div>
-      <div className="text">
-        <Checkbox />
-        <span>
-          我已阅读并同意<Link to="">《千行 GMAT - Sentence Corretion 课程协议》</Link>
-        </span>
-      </div>
-      <div className="btn">
-        <Button size="lager" radius>
-          立即开通
-        </Button>
+export default class Card extends Component {
+  getBuyBody() {
+    const { data } = this.props;
+    return (
+      <div className="body">
+        <div className="desc" dangerouslySetInnerHTML={{ __html: data.description }} />
+        <div className="btn">
+          <Button size="lager" radius>
+            立即购买
+          </Button>
+        </div>
       </div>
-    </div>
-  );
-}
-function getIngBody(data) {
-  const list = data.list || [];
-  return (
-    <div className="body">
-      <div className="title">近期待完成</div>
-      {list.length === 0 ? (
+    );
+  }
+
+  getOpenBody() {
+    return (
+      <div className="body">
+        <div className="title">请开通预习作业</div>
         <div className="text">
-          <div>好棒!</div>
-          <div>近期的作业都完成啦</div>
+          <Checkbox />
+          <span>
+            我已阅读并同意<Link to="">《千行 GMAT - Sentence Corretion 课程协议》</Link>
+          </span>
+        </div>
+        <div className="btn">
+          <Button size="lager" radius>
+            立即开通
+          </Button>
         </div>
-      ) : (
-        <div className="list">
-          {list.map(item => {
-            return (
-              <div className="item">
-                <div className="top">
-                  <div className="date">{item.date}</div>
-                  <div className="action">
-                    {item.status === 'start' && <IconButton type="start" tip="Start" />}
-                    {item.status === 'ing' && <IconButton type="continue" tip="Continue" />}
-                    {item.status === 'ing' && <IconButton type="restart" tip="Restart" />}
+      </div>
+    );
+  }
+
+  getIngBody() {
+    const { process, previewAction } = this.props;
+    return (
+      <div className="body">
+        <div className="title">近期待完成</div>
+        {process.previews.length === 0 ? (
+          <div className="text">
+            <div>好棒!</div>
+            <div>近期的作业都完成啦</div>
+          </div>
+        ) : (
+          <div className="list">
+            {process.previews.map(item => {
+              return (
+                <div className="item">
+                  <div className="top">
+                    <div className="date">{item.time}</div>
+                    <div className="action">
+                      {!item.repport.id && (
+                        <IconButton
+                          type="start"
+                          tip="Start"
+                          onClick={() => previewAction && previewAction('start', item)}
+                        />
+                      )}
+                      {item.repport.id && (
+                        <IconButton
+                          type="continue"
+                          onClick={() => previewAction && previewAction('continue', item)}
+                          tip="Continue"
+                        />
+                      )}
+                      {item.repport.id && (
+                        <IconButton
+                          type="restart"
+                          onClick={() => previewAction && previewAction('restart', item)}
+                          tip="Restart"
+                        />
+                      )}
+                    </div>
                   </div>
+                  <Progress progress={item.report.id ? item.repport.userNumber / item.report.questionNumber : 0} />
                 </div>
-                <Progress progress={item.progress} />
-              </div>
-            );
-          })}
-        </div>
-      )}
-    </div>
-  );
-}
-function getEndBody() {
-  return (
-    <div className="body">
-      <div className="title">课程已经结束啦</div>
-    </div>
-  );
-}
+              );
+            })}
+          </div>
+        )}
+      </div>
+    );
+  }
 
-function getBody(data) {
-  switch (data.status) {
-    case 'buy':
-      return getBuyBody(data);
-    case 'open':
-      return getOpenBody(data);
-    case 'ing':
-      return getIngBody(data);
-    case 'end':
-      return getEndBody(data);
-    default:
-      return '';
+  getBody() {
+    const { process = {} } = this.props;
+    if (!process.payed && !process.startTime) return this.getBuyBody();
+    if (process.payed) return this.getOpenBody();
+    if (process.startTime) return this.getIngBody();
+    return <div />;
   }
-}
 
-function Card(props) {
-  const { style, data = {}, title } = props;
+  render() {
+    const { style, data = {}, process = {} } = this.props;
 
-  return (
-    <Module style={style} className={`card ${data.status}`}>
-      <div className="header">
-        {title}
-        {data.status === 'buy' && <span className="sub-title">未购买</span>}
-        {data.status === 'open' && <span className="sub-title">已购买</span>}
-      </div>
-      {getBody(data)}
-    </Module>
-  );
+    return (
+      <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>}
+          {(process.payed || process.startTime) && <span className="sub-title">已购买</span>}
+        </div>
+        {this.getBody()}
+      </Module>
+    );
+  }
 }
-
-Card.propTypes = {};
-export default Card;

+ 23 - 0
front/project/www/components/Editor/index.js

@@ -0,0 +1,23 @@
+import React, { Component } from 'react';
+import './index.less';
+
+export default class Editor extends Component {
+  componentWillMount() {
+    const { onChange } = this.props;
+    setTimeout(() => {
+      const editor = CKEDITOR.replace('editor', {
+        skin: 'moono',
+        toolbarGroups: [{ name: 'clipboard', groups: ['undo', 'clipboard'] }],
+        removePlugins: 'pastetext,pastefromword',
+        language: 'en',
+      });
+      editor.on('change', () => {
+        if (onChange) onChange(editor.getData());
+      });
+    }, 1);
+  }
+
+  render() {
+    return <textarea name="editor" />;
+  }
+}

+ 7 - 0
front/project/www/components/Editor/index.less

@@ -0,0 +1,7 @@
+.cke_button__cut_label,
+.cke_button__copy_label,
+.cke_button__paste_label,
+.cke_button__undo_label,
+.cke_button__redo_label {
+  display: block !important;
+}

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

@@ -20,7 +20,7 @@ function ListTable(props) {
   const { style, title, subTitle, filters = [], columns = [], data = [] } = props;
   return (
     <Module style={style} className="list-table">
-      <div className="header">
+      <div hidden={!title && !subTitle} className="header">
         <span className="title">{title}</span>
         <span className="sub-title">{subTitle}</span>
       </div>

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

@@ -8,7 +8,7 @@ function Navigation(props) {
       {list.map((item, index) => {
         return (
           <div className={`item ${index === active ? 'active' : ''}`}>
-            <span className="text">{item}</span>
+            <span className="text">{item.title}</span>
           </div>
         );
       })}

+ 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;
+  }
 }

+ 1 - 8
front/project/www/components/Table/index.js

@@ -19,16 +19,9 @@ function Table(props) {
         return (
           <div className="tr">
             {columns.map(column => {
-              if (column.render) {
-                return (
-                  <div style={{ width: column.width, textAlign: column.align }} className={`td ${column.className}`}>
-                    {column.render(row[column.key], row)}
-                  </div>
-                );
-              }
               return (
                 <div style={{ width: column.width, textAlign: column.align }} className={`td ${column.className}`}>
-                  {row[column.key]}
+                  {column.render ? column.render(row, row[column.key]) : row[column.key]}
                 </div>
               );
             })}

+ 15 - 11
front/project/www/components/Tabs/index.js

@@ -2,21 +2,25 @@ import React from 'react';
 import { Link } from 'react-router-dom';
 import './index.less';
 
+function getItem(props, item, onChange) {
+  const { width, space, active } = props;
+  return (
+    <div
+      onClick={() => active !== item.key && onChange && onChange(item.key)}
+      style={{ width: width || '', marginLeft: space || '', marginRight: space || '' }}
+      className={`tab ${active === item.key ? 'active' : ''}`}
+    >
+      {item.name}
+    </div>
+  );
+}
+
 function Tabs(props) {
-  const { tabs = [], type = 'line', width, space, border, active } = props;
+  const { tabs = [], type = 'line', border, onChange } = props;
   return (
     <div className={`tabs ${type} ${border ? 'border' : ''}`}>
       {tabs.map(item => {
-        return (
-          <Link to={item.path}>
-            <div
-              style={{ width: width || '', marginLeft: space || '', marginRight: space || '' }}
-              className={`tab ${active === item.key ? 'active' : ''}`}
-            >
-              {item.name}
-            </div>
-          </Link>
-        );
+        return item.path ? <Link to={item.path}>{getItem(props, item)}</Link> : getItem(props, item, onChange);
       })}
     </div>
   );

+ 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 {

+ 5 - 11
front/project/www/local.json

@@ -1,24 +1,18 @@
 {
   "development": {
-    "scripts": [
-      "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"
-    ],
+    "scripts": ["/ckeditor/ckeditor.js", "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"],
     "proxy": [
       {
-        "target": "http://127.0.0.1:8888",
+        "target": "http://qianxing.nuliji.com",
         "from": "/api",
         "to": "/api"
       }
     ]
   },
   "test": {
-    "scripts": [
-      "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"
-    ]
+    "scripts": ["http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"]
   },
   "production": {
-    "scripts": [
-      "http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"
-    ]
+    "scripts": ["http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"]
   }
-}
+}

+ 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;
+      }
+    }
+  }
 }

+ 321 - 31
front/project/www/routes/page/practise/page.js

@@ -2,51 +2,341 @@ import React from 'react';
 import './index.less';
 import { Link } from 'react-router-dom';
 import Page from '@src/containers/Page';
+import { asyncConfirm } from '@src/services/AsyncTools';
 import Tabs from '../../../components/Tabs';
 import Module from '../../../components/Module';
 import Input from '../../../components/Input';
 import Button from '../../../components/Button';
+import Division from '../../../components/Division';
+import Card from '../../../components/Card';
+import ListTable from '../../../components/ListTable';
+import ProgressText from '../../../components/ProgressText';
+import IconButton from '../../../components/IconButton';
+import { Main } from '../../../stores/main';
+import { Question } from '../../../stores/question';
+
+const HARD = 'HARD';
+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: '',
+      previewType: PREVIEW_CLASS,
+      tabs: [],
+      allClass: [],
+      classProcess: {},
+    };
+  }
+
+  initData() {
+    Main.getExercise().then(result => {
+      const list = result;
+      const map = {};
+      for (let i = 0; i < result.length; i += 1) {
+        const item = result[i];
+        if (!map[item.parentId]) map[item.parentId] = [];
+        map[item.parentId].push(item);
+      }
+      const tabs = [];
+      let allClass = [];
+      tabs.push({ key: HARD, name: '长难句' });
+      if (map[0]) {
+        for (let i = 0; i < map[0].length; i += 1) {
+          const item = map[0][i];
+          tabs.push({ key: item.id, name: `${item.titleZh} ${item.titleEn}` });
+          if (map[item.id]) {
+            allClass = allClass.concat(map[item.id]);
+          }
+        }
+      }
+      tabs.push({ key: PREVIEW, name: '预习作业' });
+      this.setState({ tabs, allClass, list, map });
+    });
+    this.refreshClassProcess();
+  }
+
+  refreshPreview() {
+    const { previewType } = this.state;
+    if (previewType === PREVIEW_CLASS) {
+      this.refreshClassProcess();
+    }
+    if (previewType === PREVIEW_TASK) {
+      this.refreshListPreview();
+    }
+  }
+
+  onChangePreviewType(type) {
+    this.setState({ previewType: type });
+    this.refreshPreview();
+  }
+
+  refreshClassProcess() {
+    Question.getClassProcess().then(result => {
+      const classProcess = {};
+      for (let i = 0; i < result.length; i += 1) {
+        const item = result[i];
+        classProcess[item.category].push(item);
+      }
+      this.setState({ classProcess });
+    });
+  }
+
+  refreshListPreview() {
+    Question.listPreview().then(result => {
+      this.setState({ previews: result });
+    });
+  }
+
+  onChangeTab(level, tab) {
+    const state = {};
+    state[`level${level}Tab`] = tab;
+    this.setState(state);
+  }
+
+  previewAction(type, item) {
+    switch (type) {
+      case 'start':
+        this.startPreview(item);
+        break;
+      case 'restart':
+        this.restartPreview(item);
+        break;
+      case 'continue':
+        this.continuePreview(item);
+        break;
+      default:
+        break;
+    }
+  }
+
+  startPreview(item) {
+    linkTo(`/start/${item.id}?type=preview`);
+  }
+
+  restartPreview(item) {
+    asyncConfirm('提示', '是否重置', () => {
+      Question.restart(item.report.id).then(() => {
+        this.refreshPreview();
+      });
+    });
+  }
+
+  continuePreview(item) {
+    linkTo(`/start/${item.id}?type=preview&r=${item.report.id}`);
+  }
+
   renderView() {
+    const { level1Tab, level2Tab, tabs, map } = this.state;
     return (
       <div>
         <div className="content">
           <Module className="m-t-2">
-            <Tabs
-              type="card"
-              active="main"
-              tabs={[
-                { key: 'main', name: '首页', path: '/' },
-                { key: 'ready', name: 'GetReady', path: '/' },
-                { key: 'exercise', name: '练习', path: '/' },
-                { key: 'cat', name: 'CAT模考', path: '/' },
-                { key: 'item', name: '题库', path: '/' },
-                { key: 'machine', name: '换库&机经', path: '/' },
-              ]}
-            />
-          </Module>
-          <Module className="code-module">
-            <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
-            <div className="input-block">
-              <Input size="lager" placeholder="请输入CODE" />
-              <Button size="lager">解锁</Button>
-            </div>
-            <div className="tip">
-              <Link to="/" className="left link">
-                什么是CODE?
-              </Link>
-              <span>没有 CODE?</span>
-              <Link to="/" className="link">
-                去获取 >>
-              </Link>
-              <Link to="/" className="right link">
-                试用 >>
-              </Link>
-            </div>
+            <Tabs type="card" active={level1Tab} tabs={tabs} onChange={key => this.onChangeTab(1, key)} />
+            {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 && this.renderHard()}
+          {level1Tab === PREVIEW && this.renderWork()}
         </div>
       </div>
     );
   }
+
+  renderWork() {
+    const { previewType } = this.state;
+    switch (previewType) {
+      case PREVIEW_CLASS:
+        return this.renderAllClass();
+      case PREVIEW_TASK:
+        return this.renderAllTask();
+      default:
+        return <div />;
+    }
+  }
+
+  renderAllClass() {
+    const { allClass, classProcess } = this.state;
+    return (
+      <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.state;
+    return (
+      <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: '明日需完成' }],
+            },
+            {
+              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>
+    );
+  }
+
+  renderHard() {
+    return this.renderInputCode();
+  }
+
+  renderType() {
+    return <div />;
+  }
+
+  renderInputCode() {
+    return (
+      <Module className="code-module">
+        <div className="title">输入《千行GMAT长难句》专属 Code,解锁在线练习功能。</div>
+        <div className="input-block">
+          <Input size="lager" placeholder="请输入CODE" />
+          <Button size="lager">解锁</Button>
+        </div>
+        <div className="tip">
+          <Link to="/" className="left link">
+            什么是CODE?
+          </Link>
+          <span>没有 CODE?</span>
+          <Link to="/" className="link">
+            去获取 >>
+          </Link>
+          <Link to="/" className="right link">
+            试用 >>
+          </Link>
+        </div>
+      </Module>
+    );
+  }
 }

+ 1 - 1
front/project/www/routes/page/start/index.js

@@ -1,5 +1,5 @@
 export default {
-  path: '/start',
+  path: '/start/:id',
   key: 'start',
   title: '开始考试',
   needLogin: true,

+ 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);
+        }
+      }
+    }
+  }
 }

+ 200 - 50
front/project/www/routes/page/start/page.js

@@ -1,6 +1,7 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
 import './index.less';
+import { Checkbox } from 'antd';
 import Assets from '@src/components/Assets';
 import Page from '@src/containers/Page';
 import Button from '../../../components/Button';
@@ -9,73 +10,186 @@ import Answer from '../../../components/Answer';
 import Calculator from '../../../components/Calculator';
 import AnswerSelect from '../../../components/AnswerSelect';
 import AnswerTable from '../../../components/AnswerTable';
+import { Question } from '../../../stores/question';
+import Editor from '../../../components/Editor';
 
 export default class extends Page {
   initState() {
-    return { showCalculator: false };
+    return {
+      showCalculator: false,
+      start: !this.props.core.query.r,
+      reportId: this.props.core.query.r,
+      type: this.props.core.query.type,
+      disorder: false,
+      step: 0,
+      info: {},
+      reportInfo: {},
+      questionInfo: {},
+      answer: {},
+      modal: null,
+    };
   }
 
-  renderView() {
-    return this.renderDetail();
+  initData() {
+    const { start } = this.state;
+    Question.getPaper(this.params.id).then(result => {
+      this.setState({ info: result });
+    });
+    if (!start) {
+      this.continue();
+    }
   }
 
-  initData() {
+  onChangeQuestion(index, value) {
+    const { question = {}, answer = {} } = this.state;
+    answer.questions[index] = { [question.type]: value };
+    this.setState({ answer });
+  }
+
+  onChangeAwa(value) {
+    const { answer = {} } = this.state;
+    answer.awa = value;
+    this.setState({ answer });
+  }
+
+  start() {
+    const { type, info, disorder } = this.state;
+    Question.start(type, info.id, disorder).then(result => {
+      this.setState({ reportInfo: result });
+      this.next();
+    });
+  }
+
+  continue() {
+    const { reportId } = this.state;
+    Question.continue(reportId).then(result => {
+      this.setState({ reportInfo: result });
+      this.next();
+    });
+  }
+
+  next() {
+    const { reportInfo } = this.state;
+    Question.next(reportInfo.id).then(result => {
+      this.setState({
+        questionInfo: result,
+        question: result.question,
+        answer: { questions: [], subject: [], predicate: [], object: [], options: [], awa: '' },
+      });
+    });
+  }
+
+  submit() {
+    const { question, answer } = this.state;
+    if (!this.checkAnswer()) return;
+    Question.submit(question.questionNoId, answer).then(() => {
+      this.next();
+    });
+  }
+
+  finish() {
+    const { reportInfo } = this.state;
+    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) {
+    if (!text) return '';
+    const { question = { content: {} } } = this.state;
+    const { table = {}, questions = [] } = question.content;
+    text = text.replace(/#select#/g, "<span class='#select#' />");
+    text = text.replace(/#table#/g, "<span class='#table#' />");
     setTimeout(() => {
-      ReactDOM.render(
-        <AnswerSelect list={[{ title: '123' }, { title: '321' }]} />,
-        document.getElementById('#select#'),
-      );
-      ReactDOM.render(
-        <AnswerTable
-          list={[{ title: '123' }, { title: '321' }]}
-          columns={[{ key: 'one', title: 'one' }, { key: 'two', title: 'two' }]}
-          data={[{ one: '123', two: '321' }]}
-        />,
-        document.getElementById('#table#'),
-      );
+      const selectList = document.getElementsByClassName('#select#');
+      const tableList = document.getElementsByClassName('#table#');
+      for (let i = 0; i < selectList.length; i += 1) {
+        ReactDOM.render(
+          <AnswerSelect list={questions[i].select} onChange={v => this.onChangeQuestion(i, v)} />,
+          selectList[i],
+        );
+      }
+      for (let i = 0; i < tableList.length; i += 1) {
+        ReactDOM.render(<AnswerTable list={table.header} columns={table.header} data={table.data} />, tableList[i]);
+      }
     }, 1);
+    return text;
+  }
+
+  renderView() {
+    const { start } = this.state;
+    if (start) return this.renderStart();
+    return this.renderDetail();
   }
 
   renderCotent() {
+    const { question = { content: {} }, step } = this.state;
+    const { steps = [] } = question.content;
     return (
       <div className="block block-content">
-        <Navigation list={['Sports Association', 'News Orgnization', 'News Orgnization']} onChange={() => {}} />
-        <div className="text">
-          For each of the following statements, select Both accept if, based on the information provided, it can be
-          inferred that both the sports association and the news organizations would likely accept that the statement is
-          true. If not, select Otherwise.
-          <span id="#table#" />
-          For each of the following statements, select Both accept if, based on the information provided, it can be
-          inferred <span id="#select#" />
-          that both the sports association and the news organizations would likely accept that the statement is true. If
-          not, select Otherwise.
-        </div>
+        {steps.length > 0 && <Navigation list={question.content.steps} active={step} onChange={() => {}} />}
+        <div className="text">{this.formatStrem(steps.length > 0 ? steps[step].stem : question.stem)}</div>
       </div>
     );
   }
 
   renderAnswer() {
+    const { question = { content: {} } } = this.state;
+    const { questions = [] } = question.content;
+    if (question.type === 'inline') return '';
     return (
       <div className="block block-answer">
-        <div className="text m-b-2">
-          For each of the following statements, select Both accept if, based on the information provided, it can be
-          inferred that both the sports association and the news organizations would likely accept that the statement is
-          true. If not, select Otherwise.{' '}
-        </div>
-        <Answer
-          list={[
-            'Neuroscientists, having amassed a wealth of knowledge',
-            'the past twenty years about the brain and its development from birth to adulthood',
-            'Neuroscientists, having amassed a wealth of knowledge',
-            'the past twenty years about the brain and its development from birth to adulthood, ) the past twenty years about the brain and its development from birth to adulthood',
-          ]}
-        />
+        {question.type === 'awa' && <Editor onChange={v => this.onChangeAwa(v)} />}
+        {questions.map((item, index) => {
+          return (
+            <div>
+              <div className="text m-b-2">{item.description}</div>
+              <Answer
+                list={item.select}
+                type={question.type}
+                direction={question.direction}
+                onChange={v => this.onChangeQuestion(index, v)}
+              />
+            </div>
+          );
+        })}
       </div>
     );
   }
 
   renderDetail() {
-    const { showCalculator } = this.state;
+    const { modal, showCalculator, info, question = { content: {} } } = this.state;
+    const { typeset = 'one' } = question.content;
     return (
       <div className="layout">
         <div className="fixed">
@@ -89,18 +203,19 @@ export default class extends Page {
         </div>
         <Calculator show={showCalculator} />
         <div className="layout-header">
-          <div className="title">OG18:1-20</div>
+          <div className="title">{info.title}</div>
           <div className="right">
             <div className="block">
               <Assets name="timeleft_icon" />
               Time left 00:02
             </div>
             <div className="block">
-              <Assets name="subjectnumber_icon" />1 of 20
+              <Assets name="subjectnumber_icon" />
+              {question.no} of {info.questionNumer}
             </div>
           </div>
         </div>
-        <div className="layout-body two">
+        <div className={`layout-body ${typeset}`}>
           {this.renderCotent()}
           {this.renderAnswer()}
         </div>
@@ -112,44 +227,79 @@ export default class extends Page {
           <div className="full">
             <Assets name="fullscreen_icon" />
           </div>
-          <div className="next">
+          <div className="next" onClick={() => this.next()}>
             Next
             <Assets name="next_icon" />
           </div>
         </div>
+        {modal ? this.renderModal() : ''}
       </div>
     );
   }
 
   renderStart() {
+    const { info, disorder, modal } = this.state;
     return (
       <div className="start">
         <div className="bg" />
         <div className="fixed-content">
-          <div className="title">「练习」OG18 综合:第1-20题</div>
+          <div className="title">{info.title}</div>
           <div className="desc">
             <div className="block">
               <div className="desc-title">
                 <Assets name="subject_icon" />
                 题目总数
               </div>
-              <div className="desc-info">20</div>
+              <div className="desc-info">{info.questionNumer}</div>
             </div>
             <div className="block">
               <div className="desc-title">
                 <Assets name="time_icon" />
                 建议用时
               </div>
-              <div className="desc-info">20 min</div>
+              <div className="desc-info">{info.time}</div>
             </div>
           </div>
-          <div className="tip">题目选项乱序显示</div>
+          <div className="tip">
+            <Checkbox className="m-r-1" checked={disorder} onChange={() => this.setState({ disorder: !disorder })} />
+            题目选项乱序显示
+          </div>
           <div className="submit">
-            <Button size="lager" radius>
+            <Button size="lager" radius onClick={() => this.start()}>
               开始练习
             </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>
     );
   }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1703 - 0
front/project/www/static/ckeditor/CHANGES.md


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1420 - 0
front/project/www/static/ckeditor/LICENSE.md


+ 39 - 0
front/project/www/static/ckeditor/README.md

@@ -0,0 +1,39 @@
+CKEditor 4
+==========
+
+Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+http://ckeditor.com - See LICENSE.md for license information.
+
+CKEditor is a text editor to be used inside web pages. It's not a replacement
+for desktop text editors like Word or OpenOffice, but a component to be used as
+part of web applications and websites.
+
+## Documentation
+
+The full editor documentation is available online at the following address:
+http://docs.ckeditor.com
+
+## Installation
+
+Installing CKEditor is an easy task. Just follow these simple steps:
+
+ 1. **Download** the latest version from the CKEditor website:
+    http://ckeditor.com. You should have already completed this step, but be
+    sure you have the very latest version.
+ 2. **Extract** (decompress) the downloaded file into the root of your website.
+
+**Note:** CKEditor is by default installed in the `ckeditor` folder. You can
+place the files in whichever you want though.
+
+## Checking Your Installation
+
+The editor comes with a few sample pages that can be used to verify that
+installation proceeded properly. Take a look at the `samples` directory.
+
+To test your installation, just call the following page at your website:
+
+	http://<your site>/<CKEditor installation path>/samples/index.html
+
+For example:
+
+	http://www.example.com/ckeditor/samples/index.html

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 10 - 0
front/project/www/static/ckeditor/adapters/jquery.js


+ 97 - 0
front/project/www/static/ckeditor/build-config.js

@@ -0,0 +1,97 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/license
+ */
+
+/**
+ * This file was added automatically by CKEditor builder.
+ * You may re-use it at any time to build CKEditor again.
+ *
+ * If you would like to build CKEditor online again
+ * (for example to upgrade), visit one the following links:
+ *
+ * (1) https://ckeditor.com/cke4/builder
+ *     Visit online builder to build CKEditor from scratch.
+ *
+ * (2) https://ckeditor.com/cke4/builder/4f63382572985d622b9d98e28e170aeb
+ *     Visit online builder to build CKEditor, starting with the same setup as before.
+ *
+ * (3) https://ckeditor.com/cke4/builder/download/4f63382572985d622b9d98e28e170aeb
+ *     Straight download link to the latest version of CKEditor (Optimized) with the same setup as before.
+ *
+ * NOTE:
+ *    This file is not used by CKEditor, you may remove it.
+ *    Changing this file will not change your CKEditor configuration.
+ */
+
+var CKBUILDER_CONFIG = {
+	skin: 'moono',
+	preset: 'standard',
+	ignore: [
+		'.DS_Store',
+		'.bender',
+		'.editorconfig',
+		'.gitattributes',
+		'.gitignore',
+		'.idea',
+		'.jscsrc',
+		'.jshintignore',
+		'.jshintrc',
+		'.mailmap',
+		'.npm',
+		'.travis.yml',
+		'bender-err.log',
+		'bender-out.log',
+		'bender.ci.js',
+		'bender.js',
+		'dev',
+		'gruntfile.js',
+		'less',
+		'node_modules',
+		'package.json',
+		'tests'
+	],
+	plugins : {
+		'a11yhelp' : 1,
+		'about' : 1,
+		'basicstyles' : 1,
+		'blockquote' : 1,
+		'clipboard' : 1,
+		'contextmenu' : 1,
+		'elementspath' : 1,
+		'enterkey' : 1,
+		'entities' : 1,
+		'filebrowser' : 1,
+		'floatingspace' : 1,
+		'format' : 1,
+		'horizontalrule' : 1,
+		'htmlwriter' : 1,
+		'image' : 1,
+		'indentlist' : 1,
+		'link' : 1,
+		'list' : 1,
+		'magicline' : 1,
+		'maximize' : 1,
+		'pastefromword' : 1,
+		'pastetext' : 1,
+		'removeformat' : 1,
+		'resize' : 1,
+		'scayt' : 1,
+		'showborders' : 1,
+		'sourcearea' : 1,
+		'specialchar' : 1,
+		'stylescombo' : 1,
+		'tab' : 1,
+		'table' : 1,
+		'tableselection' : 1,
+		'tabletools' : 1,
+		'toolbar' : 1,
+		'undo' : 1,
+		'uploadimage' : 1,
+		'wsc' : 1,
+		'wysiwygarea' : 1
+	},
+	languages : {
+		'en' : 1
+	}
+};

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1237 - 0
front/project/www/static/ckeditor/ckeditor.js


+ 38 - 0
front/project/www/static/ckeditor/config.js

@@ -0,0 +1,38 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+CKEDITOR.editorConfig = function( config ) {
+	// Define changes to default configuration here.
+	// For complete reference see:
+	// https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html
+
+	// The toolbar groups arrangement, optimized for two toolbar rows.
+	config.toolbarGroups = [
+		{ name: 'clipboard',   groups: [ 'clipboard', 'undo' ] },
+		{ name: 'editing',     groups: [ 'find', 'selection', 'spellchecker' ] },
+		{ name: 'links' },
+		{ name: 'insert' },
+		{ name: 'forms' },
+		{ name: 'tools' },
+		{ name: 'document',	   groups: [ 'mode', 'document', 'doctools' ] },
+		{ name: 'others' },
+		'/',
+		{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+		{ name: 'paragraph',   groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
+		{ name: 'styles' },
+		{ name: 'colors' },
+		{ name: 'about' }
+	];
+
+	// Remove some buttons provided by the standard plugins, which are
+	// not needed in the Standard(s) toolbar.
+	config.removeButtons = 'Underline,Subscript,Superscript';
+
+	// Set the most common block elements.
+	config.format_tags = 'p;h1;h2;h3;pre';
+
+	// Simplify the dialog windows.
+	config.removeDialogTabs = 'image:advanced;link:advanced';
+};

+ 208 - 0
front/project/www/static/ckeditor/contents.css

@@ -0,0 +1,208 @@
+/*
+Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+*/
+
+body
+{
+	/* Font */
+	/* Emoji fonts are added to visualise them nicely in Internet Explorer. */
+	font-family: sans-serif, Arial, Verdana, "Trebuchet MS", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+	font-size: 12px;
+
+	/* Text color */
+	color: #333;
+
+	/* Remove the background color to make it transparent. */
+	background-color: #fff;
+
+	margin: 20px;
+}
+
+.cke_editable
+{
+	font-size: 13px;
+	line-height: 1.6;
+
+	/* Fix for missing scrollbars with RTL texts. (#10488) */
+	word-wrap: break-word;
+}
+
+blockquote
+{
+	font-style: italic;
+	font-family: Georgia, Times, "Times New Roman", serif;
+	padding: 2px 0;
+	border-style: solid;
+	border-color: #ccc;
+	border-width: 0;
+}
+
+.cke_contents_ltr blockquote
+{
+	padding-left: 20px;
+	padding-right: 8px;
+	border-left-width: 5px;
+}
+
+.cke_contents_rtl blockquote
+{
+	padding-left: 8px;
+	padding-right: 20px;
+	border-right-width: 5px;
+}
+
+a
+{
+	color: #0782C1;
+}
+
+ol,ul,dl
+{
+	/* IE7: reset rtl list margin. (#7334) */
+	*margin-right: 0px;
+	/* Preserved spaces for list items with text direction different than the list. (#6249,#8049)*/
+	padding: 0 40px;
+}
+
+h1,h2,h3,h4,h5,h6
+{
+	font-weight: normal;
+	line-height: 1.2;
+}
+
+hr
+{
+	border: 0px;
+	border-top: 1px solid #ccc;
+}
+
+img.right
+{
+	border: 1px solid #ccc;
+	float: right;
+	margin-left: 15px;
+	padding: 5px;
+}
+
+img.left
+{
+	border: 1px solid #ccc;
+	float: left;
+	margin-right: 15px;
+	padding: 5px;
+}
+
+pre
+{
+	white-space: pre-wrap; /* CSS 2.1 */
+	word-wrap: break-word; /* IE7 */
+	-moz-tab-size: 4;
+	tab-size: 4;
+}
+
+.marker
+{
+	background-color: Yellow;
+}
+
+span[lang]
+{
+	font-style: italic;
+}
+
+figure
+{
+	text-align: center;
+	outline: solid 1px #ccc;
+	background: rgba(0,0,0,0.05);
+	padding: 10px;
+	margin: 10px 20px;
+	display: inline-block;
+}
+
+figure > figcaption
+{
+	text-align: center;
+	display: block; /* For IE8 */
+}
+
+a > img {
+	padding: 1px;
+	margin: 1px;
+	border: none;
+	outline: 1px solid #0782C1;
+}
+
+/* Widget Styles */
+.code-featured
+{
+	border: 5px solid red;
+}
+
+.math-featured
+{
+	padding: 20px;
+	box-shadow: 0 0 2px rgba(200, 0, 0, 1);
+	background-color: rgba(255, 0, 0, 0.05);
+	margin: 10px;
+}
+
+.image-clean
+{
+	border: 0;
+	background: none;
+	padding: 0;
+}
+
+.image-clean > figcaption
+{
+	font-size: .9em;
+	text-align: right;
+}
+
+.image-grayscale
+{
+	background-color: white;
+	color: #666;
+}
+
+.image-grayscale img, img.image-grayscale
+{
+	filter: grayscale(100%);
+}
+
+.embed-240p
+{
+	max-width: 426px;
+	max-height: 240px;
+	margin:0 auto;
+}
+
+.embed-360p
+{
+	max-width: 640px;
+	max-height: 360px;
+	margin:0 auto;
+}
+
+.embed-480p
+{
+	max-width: 854px;
+	max-height: 480px;
+	margin:0 auto;
+}
+
+.embed-720p
+{
+	max-width: 1280px;
+	max-height: 720px;
+	margin:0 auto;
+}
+
+.embed-1080p
+{
+	max-width: 1920px;
+	max-height: 1080px;
+	margin:0 auto;
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 5 - 0
front/project/www/static/ckeditor/lang/en.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 10 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js


+ 25 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/_translationstatus.txt

@@ -0,0 +1,25 @@
+Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+
+cs.js      Found: 30 Missing: 0
+cy.js      Found: 30 Missing: 0
+da.js      Found: 12 Missing: 18
+de.js      Found: 30 Missing: 0
+el.js      Found: 25 Missing: 5
+eo.js      Found: 30 Missing: 0
+fa.js      Found: 30 Missing: 0
+fi.js      Found: 30 Missing: 0
+fr.js      Found: 30 Missing: 0
+gu.js      Found: 12 Missing: 18
+he.js      Found: 30 Missing: 0
+it.js      Found: 30 Missing: 0
+mk.js      Found: 5 Missing: 25
+nb.js      Found: 30 Missing: 0
+nl.js      Found: 30 Missing: 0
+no.js      Found: 30 Missing: 0
+pt-br.js   Found: 30 Missing: 0
+ro.js      Found: 6 Missing: 24
+tr.js      Found: 30 Missing: 0
+ug.js      Found: 27 Missing: 3
+vi.js      Found: 6 Missing: 24
+zh-cn.js   Found: 30 Missing: 0

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/af.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ar.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/az.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/bg.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ca.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/cs.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/cy.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/da.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/de-ch.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/de.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/el.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/en-au.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/en-gb.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/en.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/eo.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/es-mx.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/es.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/et.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/eu.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fa.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fi.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fo.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fr-ca.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/fr.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/gl.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/gu.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/he.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/hi.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/hr.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/hu.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/id.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/it.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 9 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ja.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/km.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 10 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ko.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ku.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/lt.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/lv.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/mk.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/mn.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/nb.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/nl.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/no.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/oc.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/pl.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 13 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/pt-br.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/pt.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 12 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ro.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/ru.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 10 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/si.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 11 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/sk.js


+ 0 - 0
front/project/www/static/ckeditor/plugins/a11yhelp/dialogs/lang/sl.js


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů