KaysonCui 5 년 전
부모
커밋
50c239b9a3
38개의 변경된 파일3875개의 추가작업 그리고 214개의 파일을 삭제
  1. BIN
      front/project/www/assets/drop_down.png
  2. BIN
      front/project/www/assets/drop_down_1.png
  3. BIN
      front/project/www/assets/drop_down_small.png
  4. 1 0
      front/project/www/components/Button/index.less
  5. 1 1
      front/project/www/components/CheckboxItem/index.less
  6. 4 4
      front/project/www/components/Icon/index.less
  7. 24 0
      front/project/www/components/More/index.js
  8. 6 0
      front/project/www/components/More/index.less
  9. 1 1
      front/project/www/components/Select/index.js
  10. 6 1
      front/project/www/components/Select/index.less
  11. 1 0
      front/project/www/components/Switch/index.less
  12. 42 2
      front/project/www/components/Tabs/index.less
  13. 115 11
      front/project/www/components/UserAction/index.js
  14. 83 4
      front/project/www/components/UserAction/index.less
  15. 0 68
      front/project/www/components/UserFilter/index.js
  16. 0 49
      front/project/www/components/UserFilter/index.less
  17. 83 28
      front/project/www/components/UserTable/index.js
  18. 46 10
      front/project/www/components/UserTable/index.less
  19. 86 1
      front/project/www/routes/my/answer/index.less
  20. 214 1
      front/project/www/routes/my/answer/page.js
  21. 380 1
      front/project/www/routes/my/course/index.less
  22. 533 2
      front/project/www/routes/my/course/page.js
  23. 117 1
      front/project/www/routes/my/data/index.less
  24. 467 1
      front/project/www/routes/my/data/page.js
  25. 58 11
      front/project/www/routes/my/error/page.js
  26. 261 1
      front/project/www/routes/my/main/index.less
  27. 224 5
      front/project/www/routes/my/main/page.js
  28. 14 1
      front/project/www/routes/my/message/index.less
  29. 63 1
      front/project/www/routes/my/message/page.js
  30. 56 1
      front/project/www/routes/my/note/index.less
  31. 224 1
      front/project/www/routes/my/note/page.js
  32. 9 1
      front/project/www/routes/my/order/index.less
  33. 32 1
      front/project/www/routes/my/order/page.js
  34. 86 1
      front/project/www/routes/my/report/index.less
  35. 12 1
      front/project/www/routes/my/report/page.js
  36. 363 1
      front/project/www/routes/my/tools/index.less
  37. 262 1
      front/project/www/routes/my/tools/page.js
  38. 1 1
      front/src/components/Assets/index.less

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


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


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


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

@@ -7,6 +7,7 @@
   vertical-align: middle;
   transition: all 0.25s;
   border-radius: 2px;
+  box-sizing: border-box;
 }
 
 .button.basic {

+ 1 - 1
front/project/www/components/CheckboxItem/index.less

@@ -18,5 +18,5 @@
 }
 
 .checkbox-item.white {
-  background: #fff;
+  background-color: #fff;
 }

+ 4 - 4
front/project/www/components/Icon/index.less

@@ -104,8 +104,8 @@
   background: url('/assets/header_star_normal.png') no-repeat center;
 }
 
-.icon.star.no {
-  background-image: url('/assets/header_star_normal.png') !important;
+.icon.star.no:hover {
+  background-image: url('/assets/header_star_normal.png');
 }
 
 .icon.star.active,
@@ -197,8 +197,8 @@
   background: url('/assets/note_normal.png') no-repeat center;
 }
 
-.icon.note.no {
-  background-image: url('/assets/note_normal.png') !important;
+.icon.note.no:hover {
+  background-image: url('/assets/note_normal.png');
 }
 
 .icon.note.active,

+ 24 - 0
front/project/www/components/More/index.js

@@ -0,0 +1,24 @@
+import React from 'react';
+import './index.less';
+import { Icon, Dropdown, Menu } from 'antd';
+
+function More(props) {
+  const { menu = [], onClick } = props;
+  return (
+    <Dropdown
+      overlay={
+        <Menu onClick={key => onClick && onClick(key)}>
+          {menu.map(item => {
+            return <Menu.Item key={item.key}>{item.label}</Menu.Item>;
+          })}
+        </Menu>
+      }
+      trigger={['click']}
+    >
+      <div className="more">
+        <Icon type="more" />
+      </div>
+    </Dropdown>
+  );
+}
+export default More;

+ 6 - 0
front/project/www/components/More/index.less

@@ -0,0 +1,6 @@
+@import '../../app.less';
+
+.more {
+  display: inline-block;
+  cursor: pointer;
+}

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

@@ -32,7 +32,7 @@ export default class Select extends Component {
     }
     const title = list.length > 0 ? list[index].title : placeholder;
     return (
-      <div className={`select ${theme || ''}`}>
+      <div className={`select ${theme || ''} ${size}`}>
         <div hidden={!selecting} className="mask" onClick={() => this.close()} />
         <div className={`select-warp ${selecting ? 'active' : ''}`}>
           <Button size={size} theme={theme} radius onClick={() => this.open()}>

+ 6 - 1
front/project/www/components/Select/index.less

@@ -2,7 +2,12 @@
 
 .select.small {
   .select-warp {
-    line-height: 24px;
+    line-height: 22px;
+
+    .button {
+      line-height: 22px;
+      padding: 0 14px;
+    }
   }
 }
 

+ 1 - 0
front/project/www/components/Switch/index.less

@@ -4,6 +4,7 @@
   display: inline-block;
   vertical-align: middle;
   color: #AABCCA;
+  height: 24px;
 
   .assets {
     cursor: pointer;

+ 42 - 2
front/project/www/components/Tabs/index.less

@@ -11,7 +11,7 @@
 }
 
 .tabs.border {
-  border-bottom: 1px solid @line_color;
+  border-bottom: 1px solid #eee;
 }
 
 .tabs.line {
@@ -51,6 +51,16 @@
   }
 }
 
+.tabs.line.small {
+  .tab {
+    margin: 0;
+  }
+
+  .tab::after {
+    height: 3px;
+  }
+}
+
 .tabs.card {
   text-align: center;
   background: @theme_bg_color;
@@ -97,7 +107,6 @@
 }
 
 .tabs.tag {
-  height: 24px;
   line-height: 24px;
 
   .tabs-warpper {
@@ -124,6 +133,37 @@
   }
 }
 
+.tabs.tag.white {
+
+  .tab {
+    background: #fff;
+    color: #8897A8;
+    border: 1px solid #8897A8;
+  }
+
+  .tab:hover,
+  .tab.active {
+    background: @theme_color;
+    border: 1px solid @theme_color;
+    color: #fff;
+  }
+}
+
+.tabs.tag.small {
+  height: 40px;
+  overflow: hidden;
+
+  .tabs-warpper {
+    padding: 8px 0;
+  }
+
+  .tab {
+    line-height: 22px;
+    font-size: 12px;
+    padding: 0 5px;
+  }
+}
+
 .tabs.text {
   padding-left: 44px;
   background: #fff;

+ 115 - 11
front/project/www/components/UserAction/index.js

@@ -1,10 +1,17 @@
 import React, { Component } from 'react';
 import './index.less';
 import { Icon } from 'antd';
+import Select from '../Select';
 import CheckboxItem from '../CheckboxItem';
 import { Button } from '../Button';
+import { Icon as GIcon } from '../Icon';
 
 export default class UserAction extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { showInput: false, searchText: '' };
+  }
+
   onAction(key) {
     const { onAction } = this.props;
     if (onAction) onAction(key);
@@ -15,26 +22,123 @@ export default class UserAction extends Component {
     if (onAll) onAll(checked);
   }
 
+  onSearch(value) {
+    this.setState({ searchText: value });
+  }
+
+  onFilter(key, value) {
+    const { filterMap = {}, onFilter } = this.props;
+    filterMap[key] = value;
+    if (onFilter) onFilter(filterMap);
+  }
+
+  onSort(key, value) {
+    const { onSort, sortMap = {} } = this.props;
+    sortMap[key] = value;
+    if (onSort) onSort(sortMap);
+  }
+
   render() {
-    const { allCheckbox, help, btnList = [], right } = this.props;
+    const {
+      allCheckbox,
+      allChecked,
+      help,
+      btnList = [],
+      search,
+      selectList = [],
+      sortList = [],
+      sortMap = [],
+      right,
+      left,
+    } = this.props;
+    const { showInput, searchText } = this.state;
     return (
       <div className="user-action">
+        {left && <div className="left">{left}</div>}
         {allCheckbox && (
           <div className="all">
-            <CheckboxItem theme="white" onClick={value => this.onAll(value)} />
+            <CheckboxItem theme="white" checked={allChecked} onClick={value => this.onAll(value)} />
             全选
           </div>
         )}
-        {btnList.map(btn => {
-          return (
-            <Button radius size="small" onClick={() => this.onAction(btn.key)}>
-              {btn.title}
-            </Button>
-          );
-        })}
-        {help && <Icon type="question-circle" theme="filled" onClick={() => this.onAction('help')} />}
-        {right && <div className="right">{right}</div>}
+        <div hidden={btnList.length === 0} className="button-list">
+          {btnList.map(btn => {
+            return (
+              <Button radius size="small" onClick={() => this.onAction(btn.key)}>
+                {btn.title}
+              </Button>
+            );
+          })}
+          {help && (
+            <div className="help">
+              <Icon type="question-circle" theme="filled" onClick={() => this.onAction('help')} />
+            </div>
+          )}
+        </div>
+        <div hidden={selectList.length === 0 && sortList.length === 0} className="item-list">
+          {selectList.map(item => {
+            return (
+              <div className={`item select-item ${item.right ? 'right' : ''}`}>
+                <span>{item.label}</span>
+                {item.select && this.renderSelect(item)}
+                {item.children && item.children.map(child => this.renderSelect(child))}
+              </div>
+            );
+          })}
+          {sortList.map(item => {
+            return (
+              <div className={`item sort-item ${item.right ? 'right' : ''}`}>
+                {item.label}
+                {sortMap[item.key] ? (
+                  <GIcon
+                    active
+                    name={`arrow-${sortMap[item.key] === 'asc' ? 'up' : 'down'}`}
+                    onClick={() => this.onSort(item.key, sortMap[item.key] === 'asc' ? 'desc' : '')}
+                  />
+                ) : (
+                  <GIcon name="arrow-up" onClick={() => this.onSort(item.key, 'asc')} />
+                )}
+              </div>
+            );
+          })}
+        </div>
+        {search && (
+          <div className="search">
+            {showInput && (
+              <input
+                className="search-input"
+                placeholder="请输入您想搜索的内容"
+                value={searchText}
+                onChange={e => this.onSearch(e.target.value)}
+              />
+            )}
+            {!showInput && <Icon type="search" onClick={() => this.setState({ showInput: true })} />}
+          </div>
+        )}
+        {right && (
+          <div
+            className={`right ${
+              btnList.length === 0 && selectList.length === 0 && sortList.length === 0 ? 'only' : ''
+            }`}
+          >
+            {right}
+          </div>
+        )}
       </div>
     );
   }
+
+  renderSelect(item) {
+    const { filterMap = {} } = this.props;
+    return (
+      <Select
+        size="small"
+        theme="default"
+        value={filterMap[item.key]}
+        placeholder={item.placeholder}
+        list={item.be ? item.selectMap[filterMap[item.be]] || [] : item.select}
+        onChange={({ key }) => this.onFilter(item.key, key)}
+      />
+    );
+  }
 }

+ 83 - 4
front/project/www/components/UserAction/index.less

@@ -1,13 +1,15 @@
 @import '../../app.less';
 
 .user-action {
-  border-top: 1px solid #ECEDEE;
   height: 40px;
   line-height: 40px;
+  display: flex;
+  position: relative;
+  border-bottom: 1px solid #eee;
 
   .all {
     display: inline-block;
-    margin-right: 15px;
+    margin: 0 15px;
 
     .checkbox-item {
       transform: translateY(5px);
@@ -15,8 +17,16 @@
     }
   }
 
-  .button {
-    margin-right: 10px;
+  .button-list {
+    flex: 1;
+
+    .button {
+      margin-right: 10px;
+    }
+  }
+
+  .help {
+    display: inline-block;
   }
 
   i {
@@ -25,6 +35,75 @@
   }
 
   .right {
+    text-align: right;
+    margin-left: 15px;
+  }
+
+  .right.only {
+    flex: 1;
+  }
+
+  .left {
+    display: inline-block;
+  }
+
+  .item-list {
+    flex: 1;
+  }
+
+  .item.right {
     float: right;
+    margin-right: 0;
+  }
+
+  .item.sort-item {
+    color: #686872;
+    font-size: 12px;
+    margin-left: 15px;
+  }
+
+  .item.selct-item {
+    color: #303139;
+    font-size: 12px;
+  }
+
+  .item {
+    display: inline-block;
+    margin-right: 12px;
+
+    .label {
+      margin-left: 12px;
+    }
+
+    .select {
+      margin-left: 12px;
+      display: inline-block;
+      transform: translateY(-2px);
+
+      .button {
+        line-height: 20px;
+      }
+    }
+  }
+
+  .search {
+    margin-left: 15px;
+
+    input {
+      border-radius: 12px;
+      height: 24px;
+      padding: 2px 10px;
+      line-height: 20px;
+      width: 180px;
+      border: 1px solid #e0e0e0;
+      font-size: 12px;
+    }
+
+
+    i {
+      color: #4299FF;
+      font-size: 20px;
+      cursor: pointer;
+    }
   }
 }

+ 0 - 68
front/project/www/components/UserFilter/index.js

@@ -1,68 +0,0 @@
-import React, { Component } from 'react';
-import './index.less';
-import { Icon } from 'antd';
-import Select from '../Select';
-
-export default class UserFilter extends Component {
-  constructor(props) {
-    super(props);
-    this.state = { showInput: false, searchText: '' };
-  }
-
-  onSearch(value) {
-    this.setState({ searchText: value });
-  }
-
-  onFilter(key, value) {
-    const { filterMap = {}, onFilter } = this.props;
-    filterMap[key] = value;
-    if (onFilter) onFilter(filterMap);
-  }
-
-  render() {
-    const { search, data = [] } = this.props;
-    const { showInput, searchText } = this.state;
-    return (
-      <div className="user-filter">
-        <div className="item-list">
-          {data.map(item => {
-            return (
-              <div className={`item ${item.right ? 'right' : ''}`}>
-                <span>{item.label}</span>
-                {item.select && this.renderSelect(item)}
-                {item.children && item.children.map(child => this.renderSelect(child))}
-              </div>
-            );
-          })}
-        </div>
-        {search && (
-          <div className="search">
-            {showInput && (
-              <input
-                className="search-input"
-                placeholder="请输入您想搜索的内容"
-                value={searchText}
-                onChange={e => this.onSearch(e.target.value)}
-              />
-            )}
-            {!showInput && <Icon type="search" onClick={() => this.setState({ showInput: true })} />}
-          </div>
-        )}
-      </div>
-    );
-  }
-
-  renderSelect(item) {
-    const { filterMap = {} } = this.props;
-    return (
-      <Select
-        size="small"
-        theme="default"
-        value={filterMap[item.key]}
-        placeholder={item.placeholder}
-        list={item.be ? item.selectMap[filterMap[item.be]] || [] : item.select}
-        onChange={({ key }) => this.onFilter(item.key, key)}
-      />
-    );
-  }
-}

+ 0 - 49
front/project/www/components/UserFilter/index.less

@@ -1,49 +0,0 @@
-@import '../../app.less';
-
-.user-filter {
-  border-top: 1px solid #ECEDEE;
-  height: 40px;
-  line-height: 40px;
-  display: flex;
-
-  .item-list {
-    flex: 1;
-  }
-
-  .item.right {
-    float: right;
-  }
-
-  .item {
-    display: inline-block;
-    margin-right: 12px;
-
-    .label {
-      margin-left: 12px;
-    }
-
-    .select {
-      margin-left: 12px;
-      display: inline-block;
-    }
-  }
-
-  .search {
-    input {
-      border-radius: 12px;
-      height: 24px;
-      padding: 2px 10px;
-      line-height: 20px;
-      width: 180px;
-      border: 1px solid #e0e0e0;
-      font-size: 12px;
-    }
-
-
-    i {
-      color: #4299FF;
-      font-size: 20px;
-      cursor: pointer;
-    }
-  }
-}

+ 83 - 28
front/project/www/components/UserTable/index.js

@@ -4,8 +4,16 @@ import CheckboxItem from '../CheckboxItem';
 import Icon from '../Icon';
 
 export default class UserTable extends Component {
-  onChange() {
-    if (this.props.onChange) this.props.onChange();
+  onChangePage(page) {
+    const { current, total, onChange } = this.props;
+    if (page < current || page > total) return;
+    if (onChange) onChange(page);
+  }
+
+  onSort(key, value) {
+    const { sortMap = {}, onSort } = this.props;
+    sortMap[key] = value;
+    if (onSort) onSort(sortMap);
   }
 
   onSelect(checked, key) {
@@ -19,34 +27,81 @@ export default class UserTable extends Component {
   }
 
   render() {
-    const { columns = [], rowKey = 'key', data = [], select, selectList = [] } = this.props;
+    const {
+      columns = [],
+      rowKey = 'key',
+      data = [],
+      select,
+      selectList = [],
+      current,
+      total,
+      size = 'basic',
+      even = 'even',
+      theme = 'defalut',
+      border = true,
+      header = true,
+      sortMap = {},
+      maxHeight,
+    } = this.props;
     return (
-      <div className="user-table">
+      <div className={`user-table ${size} ${even} ${theme} ${border ? 'border' : ''}`}>
         <table>
-          <thead>
-            <tr>
-              {select && <th className="select" />}
-              {columns.map(item => {
-                return <th>{item.title}</th>;
-              })}
-            </tr>
-          </thead>
-          <tbody>
+          {header && (
+            <thead>
+              <tr>
+                {columns.map((item, i) => {
+                  return (
+                    <th
+                      className={`${item.className} ${i === 0 && select ? 'select' : ''}`}
+                      width={item.width}
+                      align={item.align}
+                    >
+                      {item.title}
+                      {item.fixSort &&
+                        (sortMap[item.key] ? (
+                          <Icon active name="arrow-down" onClick={() => this.onSort(item.key, '')} />
+                        ) : (
+                          <Icon name="arrow-up" onClick={() => this.onSort(item.key, 'desc')} />
+                        ))}
+                      {item.sort &&
+                        (sortMap[item.key] ? (
+                          <Icon
+                            active
+                            name={`arrow-${sortMap[item.key] === 'asc' ? 'up' : 'down'}`}
+                            onClick={() => this.onSort(item.key, sortMap[item.key] === 'asc' ? 'desc' : '')}
+                          />
+                        ) : (
+                          <Icon name="arrow-up" onClick={() => this.onSort(item.key, 'asc')} />
+                        ))}
+                    </th>
+                  );
+                })}
+              </tr>
+            </thead>
+          )}
+
+          <tbody style={{ maxHeight }}>
             {data.map(row => {
               const checked = selectList.indexOf(row[rowKey]) >= 0;
               return (
                 <tr>
-                  {select && (
-                    <td className="select">
-                      <CheckboxItem
-                        theme="white"
-                        checked={checked}
-                        onClick={value => this.onSelect(value, row[rowKey])}
-                      />
-                    </td>
-                  )}
-                  {columns.map(item => {
-                    return <td>{item.render ? item.render(row[item.key], row) : row[item.key]}</td>;
+                  {columns.map((item, i) => {
+                    return (
+                      <td
+                        className={`${item.className} ${i === 0 && select ? 'select' : ''}`}
+                        width={item.width}
+                        align={item.align}
+                      >
+                        {i === 0 && select && (
+                          <CheckboxItem
+                            theme="white"
+                            checked={checked}
+                            onClick={value => this.onSelect(value, row[rowKey])}
+                          />
+                        )}
+                        {item.render ? item.render(row[item.key], row) : row[item.key]}
+                      </td>
+                    );
                   })}
                 </tr>
               );
@@ -54,13 +109,13 @@ export default class UserTable extends Component {
           </tbody>
         </table>
         {data.length === 0 && <div className="empty">暂无数据</div>}
-        {data.length > 0 && (
+        {total && data.length > 0 && (
           <div className="page">
-            <Icon name="prev" />
+            <Icon name="prev" onClick={() => this.onChangePage(current - 1)} />
             <span>
-              <b>1</b>/10
+              <b>{current}</b>/{total}
             </span>
-            <Icon name="next" />
+            <Icon name="next" onClick={() => this.onChangePage(current + 1)} />
           </div>
         )}
       </div>

+ 46 - 10
front/project/www/components/UserTable/index.less

@@ -4,28 +4,30 @@
   table {
     width: 100%;
     margin-bottom: 10px;
-    border-bottom: 1px solid #ECEDEE;
+    line-height: 20px;
 
     .select {
-      padding-left: 12px;
-      padding-right: 0;
+      padding-left: 15px;
+      text-align: left;
+
+      .checkbox-item {
+        margin-right: 10px;
+        vertical-align: middle;
+      }
     }
 
     th {
       padding: 0 20px;
       background: #ECEDEE;
       height: 60px;
-      border: 1px solid #fff;
     }
 
-    td {
-      padding: 20px;
+    th.select {
+      padding-left: 40px;
     }
 
-    tbody {
-      tr:nth-of-type(even) {
-        background: #FBFBFB;
-      }
+    td {
+      padding: 20px;
     }
   }
 
@@ -50,4 +52,38 @@
       }
     }
   }
+}
+
+.user-table.border {
+  table {
+    border-bottom: 1px solid #ECEDEE;
+  }
+
+  th {
+    border: 1px solid #fff;
+  }
+}
+
+.user-table.small {
+  table {
+    th {
+      height: 40px;
+    }
+  }
+}
+
+.user-table.even {
+  tbody {
+    overflow-y: auto;
+
+    tr:nth-of-type(even) {
+      background: #FBFBFB;
+    }
+  }
+}
+
+.user-table.dark {
+  tr {
+    background: #ECEDEE;
+  }
 }

+ 86 - 1
front/project/www/routes/my/answer/index.less

@@ -1,3 +1,88 @@
 @charset "utf-8";
 
-#my-answer {}
+#my-answer {
+  .user-action {
+    .tip {
+      font-size: 12px;
+      color: #A7A7B7;
+
+      span {
+        color: #4299FF;
+        cursor: pointer;
+        margin: 0 5px;
+      }
+
+      i {
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .table-layout {
+    padding: 20px 30px;
+
+    .user-table {
+      table {
+        margin: 0;
+        font-size: 12px;
+
+        th.select {
+          padding-left: 45px;
+        }
+
+        th {
+          color: #686872;
+          font-weight: 500;
+          background: #FBFBFB;
+          padding: 20px 15px;
+          white-space: nowrap;
+          overflow: hidden;
+        }
+
+        td {
+          padding: 20px 15px;
+          background: #FBFBFB;
+          border-bottom: 1px solid #eee;
+        }
+
+        .group {
+          display: inline-block;
+        }
+
+        .sub {
+          display: inline-block;
+        }
+      }
+    }
+
+    .answer-layout {
+      border-bottom: 1px solid #eee;
+      padding-top: 20px;
+      padding-left: 15px;
+
+      .title {
+        font-size: 14px;
+        line-height: 14px;
+        margin-bottom: 20px;
+        color: #303139;
+      }
+
+      .small-tag {
+        display: inline-block;
+        height: 16px;
+        background: rgba(163, 207, 255, 1);
+        border-radius: 2px;
+        color: #fff;
+        line-height: 16px;
+        font-size: 10px;
+        padding: 0 9px;
+      }
+
+      .desc {
+        color: #303139;
+        margin-bottom: 20px;
+      }
+    }
+  }
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 214 - 1
front/project/www/routes/my/answer/page.js


+ 380 - 1
front/project/www/routes/my/course/index.less

@@ -1,3 +1,382 @@
 @charset "utf-8";
 
-#my-course {}
+#my-course {
+  .detail-layout {
+    padding: 20px 30px;
+
+    .tip {
+      margin-bottom: 5px;
+      padding: 15px 10px;
+      line-height: 14px;
+      background: #F3F3F3;
+
+      .text {
+        color: #686872;
+        display: inline;
+      }
+
+      i {
+        float: right;
+        cursor: pointer;
+        color: #D0D8E2;
+      }
+    }
+
+    .tabs {
+      border-bottom: 1px solid #ECEDEE;
+    }
+
+    .ing {
+      .title {
+        .tag {
+          background: #A3CFFF;
+        }
+      }
+    }
+
+    .not {
+      .title {
+        margin-bottom: 20px;
+
+        .tag {
+          background: #B1B0FF;
+        }
+      }
+    }
+
+    .end {
+      .title {
+        margin-bottom: 20px;
+
+        .tag {
+          background: #D6DCE2;
+        }
+      }
+    }
+
+    .course-item {
+      padding-top: 15px;
+      margin-bottom: 20px;
+
+      .title {
+        margin-bottom: 10px;
+
+        .tag {
+          color: #fff;
+          font-size: 14px;
+          font-weight: 600;
+          line-height: 24px;
+          padding: 0 10px;
+          margin-right: 5px;
+          display: inline-block;
+          vertical-align: top;
+          border-radius: 4px;
+        }
+
+        .text {
+          display: inline-block;
+          color: #303036;
+          font-size: 20px;
+          line-height: 24px;
+        }
+      }
+
+      .continue {
+        line-height: 14px;
+        margin-bottom: 10px;
+
+        .assets {
+          vertical-align: top;
+          margin-right: 10px;
+        }
+      }
+
+      .detail {
+        background: #FBFBFB;
+        padding: 20px;
+        display: flex;
+        margin-bottom: 5px;
+
+        .left {
+          min-width: 280px;
+          border-right: 1px solid #D8D8D8;
+
+          .assets {
+            width: 180px;
+            height: 110px;
+            background-color: #D8D8D8;
+            margin-right: 10px;
+          }
+
+          .info {
+            display: inline-block;
+
+            .t1 {
+              color: #686872;
+            }
+
+            .t2 {
+              color: #303036;
+              font-size: 16px;
+              margin-bottom: 5px;
+            }
+          }
+        }
+
+        .right {
+          flex: 1;
+          padding-top: 30px;
+          padding-left: 50px;
+
+          .item {
+            text-align: center;
+            display: inline-block;
+            padding: 0 25px;
+
+            .assets {}
+
+            .text {
+              color: #303036;
+
+              span {
+                color: #4299FF;
+              }
+            }
+          }
+        }
+      }
+    }
+
+
+    .education-item {
+      padding-top: 15px;
+      margin-bottom: 20px;
+
+      .tabs {
+        text-align: left;
+        margin-bottom: 5px;
+      }
+
+      .title {
+        margin-bottom: 10px;
+
+        .tag {
+          color: #fff;
+          font-size: 14px;
+          font-weight: 600;
+          line-height: 24px;
+          padding: 0 10px;
+          margin-right: 5px;
+          display: inline-block;
+          vertical-align: top;
+          border-radius: 4px;
+        }
+
+        .text {
+          display: inline-block;
+          color: #303036;
+          font-size: 20px;
+          line-height: 24px;
+        }
+
+        .right {
+          float: right;
+        }
+      }
+
+      .continue {
+        line-height: 14px;
+        margin-bottom: 10px;
+
+        .assets {
+          vertical-align: top;
+          margin-right: 10px;
+        }
+      }
+
+      .detail {
+        background: #FBFBFB;
+        padding: 20px;
+        display: flex;
+        margin-bottom: 5px;
+        position: relative;
+
+        .open {
+          position: absolute;
+          right: 20px;
+          top: 20px;
+        }
+
+        .left {
+          min-width: 280px;
+          border-right: 1px solid #D8D8D8;
+
+          .assets {
+            width: 180px;
+            height: 110px;
+            background-color: #D8D8D8;
+            margin-right: 10px;
+          }
+
+          .item {
+            display: inline-block;
+            text-align: center;
+            width: 120px;
+            padding-top: 25px;
+
+            .t1 {
+              color: #686872;
+            }
+
+            .t2 {
+              color: #303036;
+              font-size: 16px;
+              margin-bottom: 5px;
+            }
+          }
+
+          .info {
+            display: inline-block;
+
+            .t1 {
+              color: #686872;
+            }
+
+            .t2 {
+              color: #303036;
+              font-size: 16px;
+              margin-bottom: 5px;
+            }
+          }
+        }
+
+        .right {
+          flex: 1;
+          padding-left: 50px;
+          padding-top: 30px;
+
+          .qr-code {
+            margin-top: -15px;
+
+            .assets {
+              width: 80px;
+              height: 80px;
+            }
+
+            .i {
+              display: inline-block;
+              padding-left: 10px;
+              padding-top: 10px;
+
+              .t1 {
+                color: #303036;
+                font-size: 16px;
+              }
+
+              .t2 {
+                font-size: 12px;
+                color: #686872;
+              }
+            }
+          }
+
+
+          .item {
+            text-align: center;
+            display: inline-block;
+            padding: 0 25px;
+
+            .assets {}
+
+            .text {
+              color: #303036;
+
+              span {
+                color: #4299FF;
+              }
+            }
+          }
+        }
+      }
+
+      .class-hour {
+        .text {
+          color: #303036;
+          font-size: 16px;
+          display: inline-block;
+        }
+
+        .right {
+          float: right;
+
+          span {
+            color: #686872;
+            margin: 0 5px;
+            font-size: 12px;
+          }
+        }
+      }
+
+      .time-line {
+        .time-line-item {
+
+          padding: 13px 0;
+          line-height: 24px;
+
+          .icon-title {
+            width: 100px;
+            display: inline-block;
+            vertical-align: top;
+
+            .icon {
+              width: 20px;
+              height: 20px;
+              margin-top: 2px;
+              display: inline-block;
+              margin-right: 5px;
+              vertical-align: top;
+            }
+
+            .title {
+              display: inline-block;
+              color: #303036;
+              margin: 0;
+            }
+          }
+
+          .time-line-detail {
+            display: inline-block;
+            font-size: 12px;
+
+            span {
+              color: #686872;
+              margin-right: 10px;
+            }
+
+            .link {
+              color: #4299FF;
+              border-bottom: 1px solid #4299FF;
+              cursor: pointer;
+            }
+          }
+        }
+
+        .time-line-item.end {
+          .title {
+            color: #A7A7B7;
+          }
+
+          .time-line-detail {
+            color: #8D8D94;
+
+            .link {
+              color: #8D8D94;
+              border-bottom: 1px solid #8D8D94;
+              cursor: default;
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 533 - 2
front/project/www/routes/my/course/page.js

@@ -1,9 +1,540 @@
-import React from 'react';
+import React, { Component } from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
+import { Icon } from 'antd';
 import Page from '@src/containers/Page';
+import Assets from '@src/components/Assets';
+import UserLayout from '../../../layouts/User';
+import Button from '../../../components/Button';
+import ProgressText from '../../../components/ProgressText';
+import { Icon as GIcon } from '../../../components/Icon';
+import menu from '../index';
+import Tabs from '../../../components/Tabs';
+import UserTable from '../../../components/UserTable';
+import More from '../../../components/More';
 
 export default class extends Page {
+  initState() {
+    return {
+      tab1: '2',
+      tab2: '1',
+      data: [
+        {
+          status: 'ing',
+          history: [{}],
+          list: [
+            { status: 'ing', type: '1' },
+            { status: 'not', type: '2' },
+            { status: 'end', type: '3' },
+            { status: 'not', type: '4' },
+            { status: 'end', type: '5' },
+            { status: 'not', type: '6' },
+            { status: 'end', type: '7' },
+          ],
+        },
+        {
+          status: 'not',
+          history: [{}],
+          list: [
+            { status: 'ing', type: '1' },
+            { status: 'not', type: '2' },
+            { status: 'end', type: '3' },
+            { status: 'not', type: '4' },
+            { status: 'end', type: '5' },
+            { status: 'not', type: '6' },
+            { status: 'end', type: '7' },
+          ],
+        },
+        { status: 'end', history: [{}], list: [] },
+      ],
+    };
+  }
+
+  onAction() {}
+
+  refresh(tab1, tab2) {
+    this.setState({ tab1, tab2 });
+  }
+
   renderView() {
-    return <div />;
+    const { config } = this.props;
+    return <UserLayout active={config.key} menu={menu} center={this.renderDetail()} />;
+  }
+
+  renderDetail() {
+    const { tab1, tab2 } = this.state;
+    return (
+      <div className="detail-layout">
+        <div className="tip">
+          <div className="text">理清备考思路,避开常见误区,让学习的每一分钟发光发热!</div>
+          <Link to="">去填写 ></Link>
+          <Icon type="close-circle" theme="filled" />
+        </div>
+        <Tabs
+          type="division"
+          theme="theme"
+          size="small"
+          space={2.5}
+          width={100}
+          active={tab1}
+          tabs={[{ key: '1', title: '在线课程' }, { key: '2', title: '1V1私教' }]}
+          onChange={key => this.refresh(key, tab2)}
+        />
+        <Tabs
+          type="tag"
+          theme="white"
+          size="small"
+          space={5}
+          width={54}
+          active={tab2}
+          tabs={[
+            { key: '1', title: '全部' },
+            { key: '2', title: '未开通' },
+            { key: '3', title: '学习中' },
+            { key: '4', title: '已结束' },
+          ]}
+          onChange={key => this.refresh(tab1, key)}
+        />
+        {this[`renderTab${tab1}`]()}
+      </div>
+    );
+  }
+
+  renderTab1() {
+    const { data = [] } = this.state;
+    return data.map(item => {
+      return <Course data={item} />;
+    });
+  }
+
+  renderTab2() {
+    const { data = [] } = this.state;
+    return data.map(item => {
+      return <Education data={item} />;
+    });
+  }
+}
+
+class Course extends Component {
+  constructor(props) {
+    super(props);
+    this.columns = [
+      { title: '学习内容' },
+      { title: '预习作业' },
+      { title: '进度' },
+      { title: '最近学习' },
+      { title: '笔记' },
+      { title: '问答' },
+    ];
+    this.state = { open: false };
+  }
+
+  render() {
+    const { data = {} } = this.props;
+    switch (data.status) {
+      case 'ing':
+        return this.renderIng();
+      case 'not':
+        return this.renderNot();
+      case 'end':
+        return this.renderEnd();
+      default:
+        return <div />;
+    }
+  }
+
+  renderIng() {
+    const { data } = this.props;
+    const { history = [] } = data;
+    return (
+      <div className="course-item ing">
+        <div className="title">
+          <div className="tag">学习中</div>
+          <div className="text">OG20整合刷题-语法SC</div>
+        </div>
+        <div className="continue">
+          <Assets name="notice" />
+          继续学习:课时 13:解读句子属性
+        </div>
+        <div className="detail">
+          <div className="left">
+            <Assets name="sun_blue" />
+            <div className="info">
+              <div className="t1">授课老师</div>
+              <div className="t2">李小小</div>
+              <div className="t1">有效期</div>
+              <div className="t2">88Day</div>
+            </div>
+          </div>
+          <div className="right">
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+          </div>
+        </div>
+        <UserTable size="small" columns={this.columns} data={history} />
+      </div>
+    );
+  }
+
+  renderNot() {
+    return (
+      <div className="course-item not">
+        <div className="title">
+          <div className="tag">未开通</div>
+          <div className="text">OG20整合刷题-语法SC</div>
+        </div>
+        <div className="detail">
+          <div className="left">
+            <Assets name="sun_blue" />
+            <div className="info">
+              <div className="t1">授课老师</div>
+              <div className="t2">李小小</div>
+              <div className="t1">有效期</div>
+              <div className="t2">88Day</div>
+            </div>
+          </div>
+          <div className="right t-c">
+            <div className="text">请于 2020-06-26 前开通</div>
+            <Button size="lager" radius>
+              立即开通
+            </Button>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderEnd() {
+    return (
+      <div className="course-item end">
+        <div className="title">
+          <div className="tag">已结束</div>
+          <div className="text">OG20整合刷题-语法SC</div>
+        </div>
+        <div className="detail">
+          <div className="left">
+            <Assets name="sun_blue" />
+            <div className="info">
+              <div className="t1">授课老师</div>
+              <div className="t2">李小小</div>
+              <div className="t1">有效期</div>
+              <div className="t2">88Day</div>
+            </div>
+          </div>
+          <div className="right">
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+class Education extends Component {
+  constructor(props) {
+    super(props);
+    this.columns = [
+      { title: '学习内容' },
+      { title: '预习作业' },
+      { title: '授课时间' },
+      { title: '课后笔记' },
+      { title: '课后补充' },
+    ];
+    this.state = { open: true, tab: '1' };
+  }
+
+  render() {
+    const { data = {} } = this.props;
+    switch (data.status) {
+      case 'ing':
+        return this.renderIng();
+      case 'not':
+        return this.renderNot();
+      case 'end':
+        return this.renderEnd();
+      default:
+        return <div />;
+    }
+  }
+
+  renderIng() {
+    const { tab } = this.state;
+    return (
+      <div className="education-item ing">
+        <div className="title">
+          <div className="tag">学习中</div>
+          <div className="text">OG20整合刷题-语法SC</div>
+          <div className="right">
+            <More menu={[{ label: '评价', key: '1' }, { label: '停课', key: '2' }]} />
+          </div>
+        </div>
+        <div className="continue">
+          <Assets name="notice" />
+          继续学习:课时 13:解读句子属性
+        </div>
+        <div className="detail">
+          <div className="left">
+            <div className="item">
+              <div className="t1">授课老师</div>
+              <div className="t2">李小小</div>
+            </div>
+            <div className="item">
+              <div className="t1">有效期</div>
+              <div className="t2">88Day</div>
+            </div>
+          </div>
+          <div className="right">
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+          </div>
+        </div>
+        <Tabs
+          className="t-l"
+          type="line"
+          theme="theme"
+          size="small"
+          width={80}
+          active={tab}
+          tabs={[{ key: '1', title: '在线课程' }, { key: '2', title: '1V1私教' }]}
+          onChange={key => this.setState({ tab: key })}
+        />
+        <div className="class-hour">
+          <div className="text">课时 5:解读句子属性</div>
+          <div className="right">
+            <GIcon name="prev" onClick={() => {}} />
+            <span>上一课时</span>
+            <span>下一课时</span>
+            <GIcon name="next" onClick={() => {}} />
+          </div>
+        </div>
+        {tab === '1' ? this.renderTimeLine() : this.renderTable()}
+      </div>
+    );
+  }
+
+  renderNot() {
+    return (
+      <div className="education-item not">
+        <div className="title">
+          <div className="tag">未开通</div>
+          <div className="text">OG20整合刷题-语法SC</div>
+        </div>
+        <div className="detail">
+          <div className="left">
+            <Assets name="sun_blue" />
+            <div className="info">
+              <div className="t1">授课老师</div>
+              <div className="t2">李小小</div>
+              <div className="t1">有效期</div>
+              <div className="t2">88Day</div>
+            </div>
+          </div>
+          <div className="right">
+            <div className="qr-code">
+              <Assets name="qrcode" />
+              <div className="i">
+                <div className="t1">请尽快与老师预约上课时间</div>
+                <div className="t2">请于 2019-07-25 前开通课程</div>
+              </div>
+            </div>
+          </div>
+        </div>
+        {this.renderTimeLine()}
+      </div>
+    );
+  }
+
+  renderEnd() {
+    const { open } = this.state;
+    return (
+      <div className="education-item end">
+        <div className="title">
+          <div className="tag">已结课</div>
+          <div className="text">OG20整合刷题-语法SC</div>
+        </div>
+        <div className="detail">
+          <div className="left">
+            <div className="item">
+              <div className="t1">授课老师</div>
+              <div className="t2">李小小</div>
+            </div>
+            <div className="item">
+              <div className="t1">有效期</div>
+              <div className="t2">88Day</div>
+            </div>
+          </div>
+          <div className="right">
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+            <div className="item">
+              <Assets name="logic" />
+              <div className="text">
+                <span>12</span>/20
+              </div>
+            </div>
+          </div>
+          <div className="open">
+            <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
+          </div>
+        </div>
+        {open && this.renderTable()}
+      </div>
+    );
+  }
+
+  renderTimeLine() {
+    const { data = {} } = this.props;
+    const { list = [] } = data;
+    return (
+      <div className="time-line">
+        {list.map(item => {
+          return <TimeLineItem data={item} />;
+        })}
+      </div>
+    );
+  }
+
+  renderTable() {
+    const { data = {} } = this.props;
+    const { history = [] } = data;
+    return <UserTable size="small" columns={this.columns} data={history} />;
+  }
+}
+
+class TimeLineItem extends Component {
+  constructor(props) {
+    super(props);
+    this.titleMap = {
+      1: '预约时间',
+      2: '答疑文档',
+      3: '上课',
+      4: '课后笔记',
+      5: '课后补充',
+      6: '备考信息',
+      7: '完成作业',
+    };
+  }
+
+  onClick(key) {
+    const { data = {}, onClick } = this.props;
+    const { status } = data;
+    if (status === 'not') return;
+    if (onClick) onClick(key);
+  }
+
+  render() {
+    const { data = {} } = this.props;
+    const { status, type } = data;
+    return (
+      <div className={`time-line-item ${status}`}>
+        <div className="icon-title">
+          <GIcon name="star" active={status !== 'not'} noHover />
+          <div className="title">{this.titleMap[type]}</div>
+        </div>
+        <div className="time-line-detail">{this.renderDetail()}</div>
+      </div>
+    );
+  }
+
+  renderDetail() {
+    const { data = {} } = this.props;
+    const { type, status } = data;
+    switch (type) {
+      case '1':
+        return status === 'not' ? (
+          <span>请尽快与老师预约上课时间,扫码加微信</span>
+        ) : (
+          <span>2019-07-24 09:00 ~ 10:00</span>
+        );
+      case '2':
+        return <span className="link">点此上传</span>;
+      case '3':
+        return status === 'end' ? (
+          <span>
+            CCtalk 频道号 :1234567 <span className="link">CC talk使用手册</span>
+          </span>
+        ) : (
+          <input placeholder="请输入CCtalk用户名查看授课频道" />
+        );
+      case '4':
+        return <span className="link">点此上传</span>;
+      case '5':
+        return <span className="link">写留言</span>;
+      case '6':
+        return [
+          <div>
+            <span>基本情况梳理</span>
+            <span>2019-08-20 更新</span>
+            <span className="link">修改</span>
+          </div>,
+          <div>
+            <span>备考细节梳理</span>
+            <span className="link">填写</span>
+          </div>,
+        ];
+      case '7':
+        return <ProgressText progress={10} size="small" />;
+      default:
+        return <div />;
+    }
   }
 }

+ 117 - 1
front/project/www/routes/my/data/index.less

@@ -1,3 +1,119 @@
 @charset "utf-8";
 
-#my-data {}
+#my-data {
+  .tabs {
+    text-align: left;
+  }
+
+  .user-action {
+    .tip {
+      font-size: 12px;
+      color: #A7A7B7;
+
+      span {
+        color: #4299FF;
+        cursor: pointer;
+        margin: 0 5px;
+      }
+
+      i {
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .title {
+    border-left: 6px solid #7876FC;
+    line-height: 20px;
+    margin: 20px 0;
+    padding-left: 5px;
+    font-size: 18px;
+    color: #303036;
+
+    .select {
+      margin-left: 10px;
+      display: inline-block;
+    }
+  }
+
+  .table-layout {
+    padding: 20px 30px;
+  }
+
+  .user-table {
+
+    td {
+      font-size: 12px;
+    }
+
+    .t {
+      color: #686872;
+    }
+
+    .d {
+      color: #A7A7B7;
+    }
+  }
+
+  .tab-1-layout {
+    padding-bottom: 50px;
+
+    .block {
+      display: inline-block;
+      width: 50%;
+      vertical-align: top;
+      text-align: center;
+
+      .chart {
+        padding-top: 90px;
+
+        .echarts-for-react {
+          margin: 0 auto;
+        }
+
+        margin-bottom: 40px;
+      }
+
+      .value {
+        border-top: 1px solid #D1D6DF;
+        display: inline-block;
+        width: 240px;
+        position: relative;
+
+        .total {
+          position: absolute;
+          left: 0;
+          top: -30px;
+          font-size: 12px;
+          color: #050930;
+        }
+
+        .item {
+          display: inline-block;
+          width: 25%;
+
+          .t {
+            font-size: 12px;
+            color: #8897A8;
+          }
+
+          .v {
+            color: #5E677B;
+            font-size: 12px;
+
+            b {
+              color: #050930;
+              font-size: 18px;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .tab-2-layout {
+    padding-top: 10px;
+    padding-bottom: 50px;
+  }
+}

+ 467 - 1
front/project/www/routes/my/data/page.js

@@ -1,9 +1,475 @@
 import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
+// import LineChart from '@src/components/LineChart';
+import BarChart from '@src/components/BarChart';
+import PieChart from '@src/components/PieChart';
+// import { getMap } from '@src/services/Tools';
+import UserLayout from '../../../layouts/User';
+import UserTable from '../../../components/UserTable';
+import UserAction from '../../../components/UserAction';
+import Select from '../../../components/Select';
+import menu from '../index';
+import Tabs from '../../../components/Tabs';
+// import { QuestionDifficult } from '../../../../Constant';
+
+const columns = [
+  {
+    key: 'title',
+    title: '',
+    render(text) {
+      return <b>{text}</b>;
+    },
+  },
+  {
+    key: 'progress',
+    title: '进度',
+    render() {
+      return (
+        <div className="v">
+          <div className="t">50%</div>
+          <div className="d">已做20道</div>
+        </div>
+      );
+    },
+  },
+  {
+    key: 'ratio',
+    title: '正确率',
+    render() {
+      return (
+        <div className="v">
+          <div className="t">100%</div>
+          <div className="d">899/899</div>
+        </div>
+      );
+    },
+  },
+  {
+    key: 'time',
+    title: '平均用时',
+    help: '',
+  },
+];
+
+// const QuestionDifficultMap = getMap(QuestionDifficult, 'value', 'label');
+
+function pieOption1(value, text, subtext) {
+  return {
+    title: {
+      text,
+      textAlign: 'center',
+      textVerticalAlign: 'middle',
+      subtext,
+      top: '28%',
+      left: '48%',
+    },
+    // value < 50 ? '#f19057' :
+    color: ['#6966fb', '#f7f7f7'],
+    series: [
+      {
+        type: 'pie',
+        radius: ['90%', '100%'],
+        label: {
+          show: false,
+        },
+        data: [value, 100 - value],
+      },
+    ],
+  };
+}
+
+function barOption1(avgTotal, avgCorrect, avgIncorrent) {
+  return {
+    xAxis: {
+      type: 'category',
+      axisTick: { show: false },
+      axisLine: { lineStyle: { color: '#D1D6DF' } },
+      splitLine: { show: false },
+      data: [
+        {
+          value: 'Avg Time\nTotal',
+          textStyle: { color: '#686872', fontWeight: '500', fontSize: 14, lineHeight: 20 },
+        },
+        {
+          value: 'Avg Time\nCorrect',
+          textStyle: { color: '#686872', fontWeight: '500', fontSize: 14, lineHeight: 20 },
+        },
+        {
+          value: 'Avg Time\nIncorrect',
+          textStyle: { color: '#686872', fontWeight: '500', fontSize: 14, lineHeight: 20 },
+        },
+      ],
+    },
+    yAxis: {
+      show: false,
+      min: 0,
+      max: 100,
+      axisTick: { show: false },
+      axisLine: { show: false },
+      splitLine: { show: false },
+    },
+    grid: { width: 300, left: '10%' },
+    series: {
+      type: 'bar',
+      barWidth: 50,
+      data: [
+        {
+          value: avgTotal,
+          name: 'Avg Time\nTotal',
+          itemStyle: { color: '#92AFD2' },
+          label: {
+            show: true,
+            position: 'top',
+            formatter: `{a|${avgTotal}}`,
+            rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
+          },
+        },
+        {
+          value: avgCorrect,
+          name: 'Avg Time\nCorrect',
+          itemStyle: { color: '#989FC1' },
+          label: {
+            show: true,
+            position: 'top',
+            formatter: `{a|${avgCorrect}}`,
+            rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
+          },
+        },
+        {
+          value: avgIncorrent,
+          name: 'Avg Time\nIncorrect',
+          itemStyle: { color: '#BFD4EE' },
+          label: {
+            show: true,
+            position: 'top',
+            formatter: `{a|${avgIncorrent}}`,
+            rich: { a: { fontSize: 16, fontWeight: 'bold', color: '#686872' } },
+          },
+        },
+      ],
+    },
+  };
+}
+
+function barOption2(title, subTitle, data) {
+  return {
+    title: {
+      text: title,
+      subtext: subTitle,
+      textStyle: { fontSize: 16 },
+    },
+    tooltip: {
+      trigger: 'axis',
+    },
+    color: '#989FC1',
+    dataset: {
+      source: [['type', 'self'], ...data],
+    },
+    grid: { left: 30, right: 30, height: 250 },
+    xAxis: {
+      type: 'category',
+      axisLabel: { color: '#686872' },
+      axisLine: { lineStyle: { color: '#D1D6DF' } },
+    },
+    yAxis: {
+      type: 'value',
+      min: 0,
+      max: 100,
+      axisLabel: { color: '#686872' },
+      axisLine: { lineStyle: { color: '#D1D6DF' } },
+    },
+    series: {
+      type: 'bar',
+      barWidth: 40,
+    },
+  };
+}
+function barOption3(titles, source, data1, data2, color1, color2) {
+  return {
+    title: [
+      {
+        text: titles[0],
+        textStyle: { fontSize: 16, fontWeight: 'bold', color: '#686872' },
+        left: 30,
+        top: 15,
+      },
+      {
+        text: titles[1],
+        textStyle: { fontSize: 16, fontWeight: 'bold', color: '#686872' },
+        left: 100,
+        top: 15,
+      },
+      {
+        text: titles[2],
+        textStyle: { fontSize: 16, fontWeight: 'bold', color: '#686872' },
+        left: 430,
+        top: 15,
+      },
+    ],
+    grid: [{ width: 200, x: 100, bottom: 30 }, { width: 200, x: 430, bottom: 30 }],
+    xAxis: [
+      {
+        gridIndex: 0,
+        show: false,
+        axisTick: { show: false },
+        axisLine: { show: false },
+        splitLine: { show: false },
+      },
+      {
+        gridIndex: 1,
+        show: false,
+        axisTick: { show: false },
+        axisLine: { show: false },
+        splitLine: { show: false },
+      },
+    ],
+    yAxis: [
+      {
+        gridIndex: 0,
+        type: 'category',
+        axisTick: { show: false },
+        axisLine: { show: false },
+        splitLine: { show: false },
+        offset: 15,
+        data: source,
+        axisLabel: { color: '#686872', fontSize: 12 },
+      },
+      {
+        gridIndex: 1,
+        type: 'category',
+        axisTick: { show: false },
+        axisLine: { show: false },
+        splitLine: { show: false },
+        axisLabel: { show: false },
+      },
+    ],
+    series: [
+      {
+        type: 'bar',
+        xAxisIndex: 0,
+        yAxisIndex: 0,
+        barWidth: 24,
+        data: data1.map((item, index) => ({
+          value: item,
+          itemStyle: { color: index % 2 ? color1[0] : color1[1] },
+          label: {
+            show: true,
+            color: '#303036',
+            align: 'right',
+            position: [250, 5],
+            fontSize: 12,
+            formatter: item,
+          },
+        })),
+      },
+      {
+        type: 'bar',
+        xAxisIndex: 1,
+        yAxisIndex: 1,
+        barWidth: 24,
+        data: data2.map((item, index) => ({
+          value: item,
+          itemStyle: { color: index % 2 ? color2[0] : color2[1] },
+          label: {
+            show: true,
+            color: '#303036',
+            align: 'right',
+            fontSize: 12,
+            position: [260, 5],
+            formatter: item,
+          },
+        })),
+      },
+    ],
+  };
+}
 
 export default class extends Page {
+  initState() {
+    return {
+      filterMap: {},
+      data: [
+        { title: '语法SC', time: '1min20s' },
+        { title: '逻辑CR', time: '1min20s' },
+        { title: '阅读RC', time: '1min20s' },
+      ],
+      selectList: [],
+      tab1: '1',
+      tab2: '1',
+      tab3: '1',
+    };
+  }
+
+  onTabChange(tab) {
+    this.setState({ tab1: tab });
+  }
+
+  onTabChange1(tab) {
+    this.setState({ tab2: tab });
+  }
+
+  onTabChange2(tab) {
+    this.setState({ tab3: tab });
+  }
+
+  onFilter(value) {
+    this.setState({ filterMap: value });
+  }
+
+  onDataChange(page) {
+    this.setState({ page, allChecked: false, selectList: [] });
+  }
+
+  onAction() {}
+
+  onSelect(selectList) {
+    this.setState({ selectList });
+  }
+
   renderView() {
-    return <div />;
+    const { config } = this.props;
+    return <UserLayout active={config.key} menu={menu} center={this.renderTable()} />;
+  }
+
+  renderTable() {
+    const { tab1, tab2, tab3, filterMap = {}, data } = this.state;
+    return (
+      <div className="table-layout">
+        <Tabs
+          border
+          type="division"
+          theme="theme"
+          size="small"
+          space={2.5}
+          width={100}
+          active={tab1}
+          tabs={[{ key: '1', title: '练习' }, { key: '2', title: '模考' }]}
+          onChange={key => this.onTabChange(key)}
+        />
+        <UserAction
+          search
+          selectList={[
+            {
+              label: '123',
+              children: [
+                {
+                  key: 'one',
+                  default: '1',
+                  select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+                },
+                {
+                  key: 'two',
+                  be: 'one',
+                  placeholder: '全部',
+                  selectMap: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+                },
+              ],
+            },
+            {
+              label: '123',
+              right: true,
+              select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+            },
+          ]}
+          filterMap={filterMap}
+          onFilter={value => this.onFilter(value)}
+        />
+        <div className="title">整体情况</div>
+        <UserTable size="small" columns={columns} data={data} />
+        <div className="title">
+          单项分析
+          <Select
+            size="small"
+            theme="default"
+            value={tab2}
+            list={[{ key: '1', title: '全部' }, { key: '2', title: '语法' }]}
+            onChange={({ key }) => this.onTabChange1(key)}
+          />
+        </div>
+        <Tabs
+          border
+          type="line"
+          theme="theme"
+          size="small"
+          width={80}
+          active={tab3}
+          tabs={[{ key: '1', title: '基本情况' }, { key: '2', title: '难度分析' }, { key: '3', title: '考点分析' }]}
+          onChange={key => this.onTabChange2(key)}
+        />
+        {this[`renderTab${tab3}`]()}
+      </div>
+    );
+  }
+
+  renderTab1() {
+    return (
+      <div className="tab-1-layout">
+        <div className="block">
+          <div className="chart">
+            <PieChart height={110} width={110} option={pieOption1(10, '123', '321')} />
+          </div>
+          <div className="value">
+            <div className="total">共200题</div>
+            <div className="item">
+              <div className="t">已做</div>
+              <div className="v">
+                <b>265</b>题
+              </div>
+            </div>
+            <div className="item">
+              <div className="t">剩余</div>
+              <div className="v">
+                <b>8</b>题
+              </div>
+            </div>
+            <div className="item">
+              <div className="t">正确率</div>
+              <div className="v">
+                <b>50%</b>
+              </div>
+            </div>
+            <div className="item">
+              <div className="t">全站</div>
+              <div className="v">
+                <b>23%</b>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div className="block">
+          <BarChart height={300} option={barOption1(10, 20, 30)} />
+        </div>
+      </div>
+    );
+  }
+
+  renderTab2() {
+    return (
+      <div className="tab-2-layout">
+        <BarChart
+          height={350}
+          option={barOption2('平均正确率80%', '正确率', [['easy', 10], ['Medium', 30], ['Hard', 40]])}
+        />
+      </div>
+    );
+  }
+
+  renderTab3() {
+    return (
+      <div className="tab-3-layout">
+        <BarChart
+          height={350}
+          option={barOption3(
+            ['知识点', '正确率分析', '用时分析'],
+            ['Idiom', 'Comparison', 'Verb', 'Pronoun', 'Modifier', 'Parallelism'],
+            [10, 12, 12, 13, 14, 15],
+            [10, 12, 12, 13, 14, 15],
+            ['#92AFD2', '#BFD4EE'],
+            ['#989FC1', '#CCCCDC'],
+          )}
+        />
+      </div>
+    );
   }
 }

+ 58 - 11
front/project/www/routes/my/error/page.js

@@ -4,17 +4,16 @@ import { Icon } from 'antd';
 import Page from '@src/containers/Page';
 import UserLayout from '../../../layouts/User';
 import UserTable from '../../../components/UserTable';
-import UserFilter from '../../../components/UserFilter';
 import UserAction from '../../../components/UserAction';
 import menu from '../index';
 import Tabs from '../../../components/Tabs';
 
 const columns = [
-  { key: '', title: '题型' },
-  { key: '', title: '题目ID' },
+  { key: '', title: '题型', fixSort: true },
+  { key: '', title: '题目ID', fixSort: true },
   { key: '', title: '内容' },
-  { key: '', title: '耗时' },
-  { key: '', title: '错误率' },
+  { key: '', title: '耗时', sort: true },
+  { key: '', title: '错误率', sort: true },
   { key: '', title: '最近做题' },
   { key: '', title: '' },
 ];
@@ -23,6 +22,12 @@ export default class extends Page {
   initState() {
     return {
       filterMap: {},
+      sortMap: {},
+      data: [],
+      selectList: [],
+      allChecked: false,
+      page: 1,
+      total: 1,
     };
   }
 
@@ -30,16 +35,44 @@ export default class extends Page {
     this.setState({ filterMap: value });
   }
 
+  onSort(value) {
+    this.setState({ sortMap: value });
+  }
+
+  onDataChange(page) {
+    this.setState({ page, allChecked: false, selectList: [] });
+  }
+
+  onAll(checked) {
+    if (checked) {
+      const { data = [] } = this.state;
+      const list = [];
+      data.forEach(item => {
+        list.push(item.key);
+      });
+      this.setState({ selectList: list, allChecked: true });
+    } else {
+      this.setState({ selectList: [], allChecked: false });
+    }
+  }
+
+  onAction() {}
+
+  onSelect(selectList) {
+    this.setState({ selectList });
+  }
+
   renderView() {
     const { config } = this.props;
     return <UserLayout active={config.key} menu={menu} center={this.renderTable()} />;
   }
 
   renderTable() {
-    const { filterMap = {} } = this.state;
+    const { filterMap = {}, sortMap = {}, selectList = [], data = [], allChecked, page, total } = this.state;
     return (
       <div className="table-layout">
         <Tabs
+          border
           type="division"
           theme="theme"
           size="small"
@@ -48,9 +81,9 @@ export default class extends Page {
           active={'1'}
           tabs={[{ key: '1', title: '练习' }, { key: '2', title: '模考' }]}
         />
-        <UserFilter
+        <UserAction
           search
-          data={[
+          selectList={[
             {
               label: '123',
               children: [
@@ -73,11 +106,12 @@ export default class extends Page {
         />
         <UserAction
           allCheckbox
+          allChecked={allChecked}
           help
           btnList={[
             { title: '移除', key: 'remove' },
-            { title: '组卷', key: 'group', vip: true },
-            { title: '导出', key: 'export', vip: true },
+            { title: '组卷', key: 'group', tag: 'vip' },
+            { title: '导出', key: 'export', tag: 'vip' },
           ]}
           right={
             <div className="tip">
@@ -85,8 +119,21 @@ export default class extends Page {
               <Icon type="close-circle" theme="filled" />
             </div>
           }
+          onAll={checked => this.onAll(checked)}
+          onAction={key => this.onAction(key)}
+        />
+        <UserTable
+          select
+          columns={columns}
+          sortMap={sortMap}
+          data={data}
+          current={page}
+          total={total}
+          selectList={selectList}
+          onSelect={l => this.onSelect(l)}
+          onSort={v => this.onSort(v)}
+          onChange={p => this.onDataChange(p)}
         />
-        <UserTable select columns={columns} data={[{}]} />
       </div>
     );
   }

+ 261 - 1
front/project/www/routes/my/main/index.less

@@ -1,3 +1,263 @@
 @charset "utf-8";
 
-#my-main {}
+#my-main {
+  .total-layout {}
+
+  .log-layout {
+    .header {
+      padding: 24px 30px 14px;
+
+      .title {
+        display: inline-block;
+        color: #303139;
+        font-size: 16px;
+      }
+
+      .right {
+        float: right;
+
+        span {
+          color: #686872;
+          font-size: 12px;
+          margin-left: 25px;
+          display: inline-block;
+
+          b {
+            color: #303036;
+            font-size: 20px;
+            margin: 0 2px;
+          }
+        }
+      }
+    }
+
+    .action {
+      padding: 10px 30px;
+
+      .button {
+        margin-right: 10px;
+      }
+
+      .right {
+        cursor: pointer;
+        float: right;
+      }
+    }
+
+    .log-item {
+      border-top: 1px solid #eee;
+      padding: 0 30px;
+      position: relative;
+
+      .total {
+        height: 80px;
+        line-height: 80px;
+
+        .text {
+          display: inline-block;
+          color: #686872;
+
+          span {
+            color: #5E677B;
+          }
+
+          b {
+            color: #050930;
+            font-size: 18px;
+          }
+        }
+      }
+
+      .open {
+        position: absolute;
+        right: 30px;
+        top: 25px;
+      }
+
+      .table {
+        padding-top: 4px;
+        padding-bottom: 24px;
+      }
+    }
+  }
+
+  .time-layout {
+    .header {
+      padding: 20px 30px;
+      line-height: 18px;
+      border-bottom: 1px solid #eee;
+
+      .title {
+        color: #303139;
+        font-size: 16px;
+        display: inline-block;
+
+        i {
+          color: #D0D8E2;
+          font-size: 14px;
+          margin-left: 4px;
+        }
+      }
+
+      .right {
+        float: right;
+        font-size: 12px;
+        color: #686872;
+      }
+    }
+
+    .body {
+      padding: 30px;
+
+      .line {
+        height: 15px;
+        margin-bottom: 20px;
+
+        i {
+          display: inline-block;
+          height: 100%;
+        }
+      }
+
+      .list {
+        overflow: hidden;
+
+        .item {
+          float: left;
+          width: 230px;
+          line-height: 20px;
+          padding: 10px 0;
+
+          .color {
+            width: 26px;
+            height: 14px;
+            display: inline-block;
+            margin-right: 5px;
+          }
+
+          .title {
+            color: #686872;
+            margin-right: 5px;
+            display: inline-block;
+          }
+
+          .date {
+            color: #8897A8;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .info-layout {
+    .body {
+      padding: 30px 20px;
+
+      .info {
+        margin-bottom: 15px;
+
+        .assets {
+          vertical-align: top;
+          width: 50px;
+          height: 50px;
+          border-radius: 50%;
+          display: inline-block;
+          margin-right: 15px;
+        }
+
+        .detail {
+          display: inline-block;
+
+          .name {
+            font-size: 16px;
+            color: #303139;
+          }
+
+          .id {
+            font-size: 12px;
+            color: #686872;
+          }
+        }
+      }
+    }
+
+    .footer {
+      text-align: center;
+      padding: 14px;
+      font-size: 12px;
+      border-top: 1px solid #eee;
+
+      .tag {
+        background: #FFC800;
+        width: 40px;
+        height: 20px;
+        line-height: 20px;
+        color: #fff;
+        display: inline-block;
+        margin-right: 5px;
+        font-size: 16px;
+      }
+
+      .date {
+        color: #8897A8;
+        margin-right: 10px;
+      }
+    }
+  }
+
+  .message-layout {
+    .header {
+      font-size: 12px;
+      color: #686872;
+      padding: 20px;
+      padding-bottom: 0;
+
+      .assets {
+        margin-right: 8px;
+      }
+    }
+
+    .body {
+      padding: 0 30px;
+
+      .item {
+        margin: 10px 0;
+        position: relative;
+
+        .title {
+          color: #303036;
+        }
+
+        .date {
+          color: #A7A7B7;
+          font-size: 12px;
+        }
+
+        .dot::after {
+          content: '';
+          position: absolute;
+          left: -10px;
+          top: 10px;
+          width: 5px;
+          height: 5px;
+          border-radius: 50%;
+          background: rgba(252, 95, 95, 1);
+        }
+
+        .assets {
+          position: absolute;
+          right: 0;
+          top: 5px;
+          cursor: pointer;
+        }
+      }
+    }
+
+    .footer {
+      text-align: center;
+      padding: 9px;
+      border-top: 1px solid #eee;
+    }
+  }
+}

+ 224 - 5
front/project/www/routes/my/main/page.js

@@ -1,10 +1,112 @@
-import React from 'react';
+import React, { Component } from 'react';
+import { Link } from 'react-router-dom';
 import './index.less';
+import { Tooltip, Icon } from 'antd';
 import Page from '@src/containers/Page';
+import Assets from '@src/components/Assets';
 import UserLayout from '../../../layouts/User';
+import Button from '../../../components/Button';
+import { Icon as GIcon } from '../../../components/Icon';
 import menu from '../index';
+import UserTable from '../../../components/UserTable';
+
+class LogItem extends Component {
+  constructor(props) {
+    super(props);
+    this.columns = [
+      { title: '', key: 'title' },
+      { title: '语法SC', key: 'sc' },
+      { title: '逻辑CR', key: 'cr' },
+      { title: '阅读RC', key: 'rc' },
+    ];
+    this.state = { open: false };
+  }
+
+  render() {
+    const { data = {} } = this.props;
+    const { total = [], detail = [] } = data;
+    const { open } = this.state;
+    return (
+      <div className="log-item">
+        <div className="total">
+          {total.map(item => {
+            return (
+              <div className="text" style={{ width: item.width }} dangerouslySetInnerHTML={{ __html: item.title }} />
+            );
+          })}
+        </div>
+        <div className="open">
+          <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
+        </div>
+        <div hidden={!open} className="table">
+          <UserTable size="small" columns={this.columns} data={detail} />
+          <div className="t-r">
+            <Link to="">继续练习></Link>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
 
 export default class extends Page {
+  initState() {
+    return {
+      timeList: [
+        { title: '长难句', time: '3h60min', ratio: 10, color: '#3C39CC' },
+        { title: '综合推理IR', time: '3h60min', ratio: 10, color: '#9E9CFF' },
+        { title: '语法SC', time: '3h60min', ratio: 10, color: '#4292F0' },
+        { title: '作文AWA', time: '3h60min', ratio: 10, color: '#4374EC' },
+        { title: '阅读RC', time: '3h60min', ratio: 10, color: '#6865FD' },
+        { title: '模考', time: '3h60min', ratio: 10, color: '#8D65FD' },
+        { title: '逻辑CR', time: '3h60min', ratio: 10, color: '#6BABF6' },
+        { title: '自由组卷', time: '3h60min', ratio: 10, color: '#7BBEFF' },
+        { title: '数学Quant', time: '3h60min', ratio: 10, color: '#6BABF6' },
+      ],
+      logList: [
+        {
+          total: [
+            { title: '<span>练习和订正</span>', width: 130 },
+            { title: '<b>60</b>min', width: 90 },
+            { title: '<b>30</b>题', width: 80 },
+            { title: '超过了<b>30%</b>的用户' },
+          ],
+          detail: [
+            { title: '做题数', sc: '10', cr: '10', rc: '20' },
+            { title: '平均正确率', sc: '86%', cr: '86%', rc: '86%' },
+            { title: '平均用时', sc: '1min', cr: '1min20s', rc: '1min' },
+          ],
+        },
+        {
+          total: [
+            { title: '<span>模考和订正</span>', width: 130 },
+            { title: '<b>60</b>min', width: 90 },
+            { title: '<b>30</b>套卷', width: 80 },
+            { title: '超过了<b>30%</b>的用户' },
+          ],
+          detail: [
+            { title: '做题数', sc: '10', cr: '10', rc: '20' },
+            { title: '平均正确率', sc: '86%', cr: '86%', rc: '86%' },
+            { title: '平均用时', sc: '1min', cr: '1min20s', rc: '1min' },
+          ],
+        },
+        {
+          total: [
+            { title: '<span>课程学习</span>', width: 130 },
+            { title: '<b>60</b>min', width: 90 },
+            { title: '<b>30</b>课', width: 80 },
+            { title: '超过了<b>30%</b>的用户' },
+          ],
+          detail: [
+            { title: '做题数', sc: '10', cr: '10', rc: '20' },
+            { title: '平均正确率', sc: '86%', cr: '86%', rc: '86%' },
+            { title: '平均用时', sc: '1min', cr: '1min20s', rc: '1min' },
+          ],
+        },
+      ],
+    };
+  }
+
   renderView() {
     const { config } = this.props;
     return (
@@ -22,18 +124,135 @@ export default class extends Page {
   }
 
   renderLog() {
-    return <div className="log-layout">1</div>;
+    const { logList = [] } = this.state;
+    return (
+      <div className="log-layout">
+        <div className="header">
+          <div className="title">学习记录</div>
+          <div className="right">
+            <span>
+              本周学习时间<b>23</b>Hour
+            </span>
+            <span>
+              同比上周<b>15</b>%
+            </span>
+            <span>
+              同比全站<b>15</b>%
+            </span>
+          </div>
+        </div>
+        <div className="action">
+          <Button size="small" radius>
+            今天
+          </Button>
+          <Button size="small" radius>
+            昨天
+          </Button>
+          <Button size="small" radius>
+            前天
+          </Button>
+          <Assets className="right" name="calculator_icon" />
+        </div>
+        {logList.map((log, index) => {
+          return <LogItem key={index} data={log} />;
+        })}
+      </div>
+    );
   }
 
   renderTime() {
-    return <div className="time-layout">1</div>;
+    const { timeList = [] } = this.state;
+    return (
+      <div className="time-layout">
+        <div className="header">
+          <div className="title">
+            时间分配
+            <Tooltip overlayClassName="gray" title="包括听课、练习与订正">
+              <Icon type="question-circle" theme="filled" />
+            </Tooltip>
+          </div>
+          <div className="right">
+            自 2019-05-26 ,您已在千行学习<b>23</b>天,累计 <b>32h</b> 30min
+          </div>
+        </div>
+        <div className="body">
+          <div className="line">
+            {timeList.map(item => {
+              return (
+                <Tooltip overlayClassName="gray" title={`${item.title} ${item.time}`}>
+                  <i style={{ background: item.color, width: `${item.ratio}%` }} />
+                </Tooltip>
+              );
+            })}
+          </div>
+          <div className="list">
+            {timeList.map(item => {
+              return (
+                <div className="item">
+                  <div className="color" style={{ background: item.color }} />
+                  <div className="title">{item.title}</div>
+                  <div className="date">{item.time}</div>
+                </div>
+              );
+            })}
+          </div>
+        </div>
+      </div>
+    );
   }
 
   renderInfo() {
-    return <div className="info-layout">1</div>;
+    return (
+      <div className="info-layout">
+        <div className="body">
+          <div className="info">
+            <Assets name="sun_blue" />
+            <div className="detail">
+              <div className="name">怕死的胡萝卜 </div>
+              <div className="id">ID: 2392401 </div>
+            </div>
+          </div>
+          <div className="auth">
+            <span className="invite">
+              <Button radius size="small">
+                邀请
+              </Button>
+            </span>
+          </div>
+        </div>
+        <div className="footer">
+          <span className="tag">VIP</span>
+          <span className="date">2019-10-15到期</span>
+          <Link to="">续费</Link>
+        </div>
+      </div>
+    );
   }
 
   renderMessage() {
-    return <div className="message-layout">1</div>;
+    return (
+      <div className="message-layout">
+        <div className="header">
+          <Assets name="dot2" />
+          全部已读
+        </div>
+        <div className="body">
+          <div className="item">
+            <div className="title dot">老师回答了您的提问</div>
+            <div className="date">2019-05-15 16:21:06</div>
+            <Assets name="right_icon" onClick={() => {}} />
+          </div>
+          <div className="item">
+            <div className="title dot">老师回答了您的提问</div>
+            <div className="date">2019-05-15 16:21:06</div>
+          </div>
+        </div>
+        <div className="footer">
+          <Button radius size="small">
+            全部消息
+          </Button>
+        </div>
+      </div>
+    );
   }
 }

+ 14 - 1
front/project/www/routes/my/message/index.less

@@ -1,3 +1,16 @@
 @charset "utf-8";
 
-#my-message {}
+#my-message {
+  .table-layout {
+    padding: 20px 30px;
+
+    .user-action {
+      border-bottom: 0;
+      border-top: 1px solid #eee;
+    }
+
+    .user-table {
+      font-size: 12px;
+    }
+  }
+}

+ 63 - 1
front/project/www/routes/my/message/page.js

@@ -1,9 +1,71 @@
 import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
+import UserLayout from '../../../layouts/User';
+import menu from '../index';
+import UserTable from '../../../components/UserTable';
+import UserAction from '../../../components/UserAction';
+import Tabs from '../../../components/Tabs';
+
+const columns = [{ title: '消息', key: 'content' }, { title: '类型', key: 'type' }, { title: '发送时间', key: 'date' }];
 
 export default class extends Page {
+  initState() {
+    return {
+      tab: '1',
+      filterMap: {},
+      data: [
+        { content: '您的会员即将到期,请及时续费', type: '动态消息', date: '2019-07-12 \n 11:38:51' },
+        {
+          content: '请尽快完成作业 \n 消息详情消息详情消息详情消息详情 ',
+          type: '系统消息',
+          date: '2019-07-12 \n 11:38:51',
+        },
+      ],
+    };
+  }
+
+  onFilter(filterMap) {
+    this.setState({ filterMap });
+  }
+
+  onTabChange(tab) {
+    this.setState({ tab });
+  }
+
   renderView() {
-    return <div />;
+    const { config } = this.props;
+    return <UserLayout active={config.key} menu={menu} center={this.renderTable()} />;
+  }
+
+  renderTable() {
+    const { tab, data, filterMap } = this.state;
+    return (
+      <div className="table-layout">
+        <UserAction
+          left={
+            <Tabs
+              type="tag"
+              theme="white"
+              size="small"
+              space={5}
+              width={54}
+              active={tab}
+              tabs={[{ key: '1', title: '全部' }, { key: '2', title: '未读' }]}
+              onChange={key => this.onTabChange(key)}
+            />
+          }
+          right={<span>全部已读</span>}
+          selectList={[
+            {
+              select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+            },
+          ]}
+          filterMap={filterMap}
+          onFilter={value => this.onFilter(value)}
+        />
+        <UserTable size="small" columns={columns} data={data} />
+      </div>
+    );
   }
 }

+ 56 - 1
front/project/www/routes/my/note/index.less

@@ -1,3 +1,58 @@
 @charset "utf-8";
 
-#my-note {}
+#my-note {
+  .user-action {
+    .tip {
+      font-size: 12px;
+      color: #A7A7B7;
+
+      span {
+        color: #4299FF;
+        cursor: pointer;
+        margin: 0 5px;
+      }
+
+      i {
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .table-layout {
+    padding: 20px 30px;
+
+    .user-table {
+      table {
+        margin: 0;
+        font-size: 12px;
+
+        th.select {
+          padding-left: 45px;
+        }
+
+        th {
+          color: #686872;
+          font-weight: 500;
+          background: #FBFBFB;
+          padding: 20px 15px;
+          white-space: nowrap;
+          overflow: hidden;
+        }
+
+        td {
+          padding: 20px 15px;
+          border-bottom: 1px solid #eee;
+        }
+
+        .group {
+          display: inline-block;
+        }
+
+        .sub {
+          display: inline-block;
+        }
+      }
+    }
+  }
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 224 - 1
front/project/www/routes/my/note/page.js


+ 9 - 1
front/project/www/routes/my/order/index.less

@@ -1,3 +1,11 @@
 @charset "utf-8";
 
-#my-order {}
+#my-order {
+  .table-layout {
+    padding: 20px 30px;
+
+    .user-table {
+      font-size: 12px;
+    }
+  }
+}

+ 32 - 1
front/project/www/routes/my/order/page.js

@@ -1,9 +1,40 @@
 import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
+import UserLayout from '../../../layouts/User';
+import menu from '../index';
+import UserTable from '../../../components/UserTable';
+
+const columns = [
+  { title: '订单编号', key: 'no' },
+  { title: '服务', key: 'title' },
+  { title: '购买时间', key: 'date' },
+  { title: '付款方式', key: 'method' },
+  { title: '付款金额', key: 'money' },
+];
 
 export default class extends Page {
+  initState() {
+    return {
+      data: [
+        { no: '-', title: 'VIP会员', date: '2019-07-12 \n 11:38:51', method: '实名认证赠送', money: 0 },
+        { no: '12930219321321321', title: '教学资料-01', date: '2019-07-12 \n 11:38:51', method: '支付宝', money: 200 },
+        { no: '12930219321321321', title: '千行课程', date: '2019-07-12 \n 11:38:51', method: '银行付款', money: 0 },
+      ],
+    };
+  }
+
   renderView() {
-    return <div />;
+    const { config } = this.props;
+    return <UserLayout active={config.key} menu={menu} center={this.renderTable()} />;
+  }
+
+  renderTable() {
+    const { data } = this.state;
+    return (
+      <div className="table-layout">
+        <UserTable size="small" columns={columns} data={data} />
+      </div>
+    );
   }
 }

+ 86 - 1
front/project/www/routes/my/report/index.less

@@ -1,3 +1,88 @@
 @charset "utf-8";
 
-#my-answer {}
+#my-report {
+  .user-action {
+    .tip {
+      font-size: 12px;
+      color: #A7A7B7;
+
+      span {
+        color: #4299FF;
+        cursor: pointer;
+        margin: 0 5px;
+      }
+
+      i {
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .table-layout {
+    padding: 20px 30px;
+
+    .user-table {
+      table {
+        margin: 0;
+        font-size: 12px;
+
+        th.select {
+          padding-left: 45px;
+        }
+
+        th {
+          color: #686872;
+          font-weight: 500;
+          background: #FBFBFB;
+          padding: 20px 15px;
+          white-space: nowrap;
+          overflow: hidden;
+        }
+
+        td {
+          padding: 20px 15px;
+          background: #FBFBFB;
+          border-bottom: 1px solid #eee;
+        }
+
+        .group {
+          display: inline-block;
+        }
+
+        .sub {
+          display: inline-block;
+        }
+      }
+    }
+
+    .answer-layout {
+      border-bottom: 1px solid #eee;
+      padding-top: 20px;
+      padding-left: 15px;
+
+      .title {
+        font-size: 14px;
+        line-height: 14px;
+        margin-bottom: 20px;
+        color: #303139;
+      }
+
+      .small-tag {
+        display: inline-block;
+        height: 16px;
+        background: rgba(163, 207, 255, 1);
+        border-radius: 2px;
+        color: #fff;
+        line-height: 16px;
+        font-size: 10px;
+        padding: 0 9px;
+      }
+
+      .desc {
+        color: #303139;
+        margin-bottom: 20px;
+      }
+    }
+  }
+}

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

@@ -1,9 +1,20 @@
 import React from 'react';
 import './index.less';
 import Page from '@src/containers/Page';
+import UserLayout from '../../../layouts/User';
+import menu from '../index';
 
 export default class extends Page {
+  initState() {
+    return {};
+  }
+
   renderView() {
-    return <div />;
+    const { config } = this.props;
+    return <UserLayout active={config.key} menu={menu} center={this.renderTable()} />;
+  }
+
+  renderTable() {
+    return <div className="table-layout">1</div>;
   }
 }

+ 363 - 1
front/project/www/routes/my/tools/index.less

@@ -1,3 +1,365 @@
 @charset "utf-8";
 
-#my-tools {}
+#my-tools {
+
+  .user-action {
+    border-bottom: 1px solid #eee;
+
+    .tip {
+      font-size: 12px;
+      color: #A7A7B7;
+
+      span {
+        color: #4299FF;
+        cursor: pointer;
+        margin: 0 5px;
+      }
+
+      i {
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .table-layout {
+    padding: 20px 30px;
+
+    .tip-layout {
+      text-align: center;
+      padding-top: 70px;
+      padding-bottom: 70px;
+
+      .t1 {
+        color: #686872;
+        font-size: 18px;
+        line-height: 18px;
+        margin-bottom: 20px;
+      }
+
+      .desc {
+        color: #686872;
+        line-height: 14px;
+        margin-bottom: 10px;
+      }
+
+      .t2 {
+        color: #686872;
+        font-size: 18px;
+        line-height: 18px;
+        margin-bottom: 50px;
+      }
+
+      .t3 {
+        color: #686872;
+        font-size: 18px;
+        line-height: 18px;
+        margin-bottom: 5px;
+      }
+
+      .date {
+        color: #A7A7B7;
+        font-size: 12px;
+        line-height: 12px;
+        margin-bottom: 10px;
+      }
+
+    }
+  }
+
+  .tab-1-layout {
+    .total-log {
+      font-size: 12px;
+
+      .t {
+        color: #303139;
+      }
+
+      span {
+        color: #5E677B;
+        margin-right: 10px;
+
+        b {
+          color: #4299FF;
+          margin: 0 2px;
+        }
+      }
+    }
+
+    .user-action {
+      .email {
+        font-size: 12px;
+        line-height: 24px;
+        height: 24px;
+        margin-top: 8px;
+
+        .switch {
+          margin-left: 5px;
+          vertical-align: top;
+        }
+      }
+    }
+
+    .data-layout {
+      margin: 0 -20px;
+      text-align: center;
+
+      .data-item:hover {
+        .fixed {
+          display: block;
+        }
+      }
+
+      .data-item {
+        margin: 0 20px;
+        margin-top: 24px;
+        width: 230px;
+        display: inline-block;
+        text-align: left;
+        position: relative;
+
+        .assets {
+          height: 265px;
+          width: 100%;
+        }
+
+        .fixed {
+          position: absolute;
+          top: 0;
+          left: 0;
+          right: 0;
+          height: 265px;
+          display: none;
+          background: rgba(0, 0, 0, 0.5);
+
+          .btns {
+            padding: 120px 45px;
+
+            .button {}
+
+            .white {
+              cursor: pointer;
+              display: inline-block;
+              color: #fff;
+              font-size: 12px;
+              float: right;
+            }
+          }
+        }
+
+        .title {
+          color: #303139;
+          font-size: 16px;
+          margin-bottom: 5px;
+
+          span {
+            background: #A3CFFF;
+            display: inline-block;
+            padding: 0 3px;
+            height: 16px;
+            border-radius: 2px;
+            line-height: 16px;
+            text-align: center;
+            margin-right: 10px;
+            color: #fff;
+            font-size: 10px;
+          }
+
+          b {
+            color: #4299FF;
+          }
+        }
+
+        .date {
+          color: #A7A7B7;
+          font-size: 12px;
+        }
+
+        .more {
+          position: absolute;
+          bottom: 0;
+          right: 0;
+        }
+      }
+    }
+  }
+
+  .tab-2-layout {
+    .total-log {
+      font-size: 12px;
+
+      .t {
+        color: #303139;
+      }
+
+      span {
+        color: #5E677B;
+        margin-right: 10px;
+
+        b {
+          color: #4299FF;
+          margin: 0 2px;
+        }
+      }
+    }
+
+    .data-layout {
+      margin: 0 -20px;
+      text-align: center;
+
+      .data-item {
+        margin: 0 20px;
+        margin-top: 24px;
+        width: 230px;
+        display: inline-block;
+        text-align: left;
+        position: relative;
+
+        .assets {
+          height: 160px;
+          width: 100%;
+        }
+
+        .title {
+          color: #A7A7B7;
+          margin-bottom: 5px;
+
+          b {
+            color: #4299FF;
+          }
+        }
+
+        .date {
+          color: #A7A7B7;
+          font-size: 12px;
+        }
+
+        .more {
+          position: absolute;
+          bottom: 0;
+          right: 0;
+        }
+      }
+    }
+  }
+
+  .tab-3-layout {}
+
+  .tab-4-layout {}
+
+  .tab-5-layout {
+    padding-top: 80px;
+    padding-bottom: 280px;
+
+    .total-sort-num {
+      position: relative;
+      text-align: center;
+      margin-bottom: 20px;
+
+      input {
+        width: 150px;
+        height: 46px;
+        background: #F7F7F7;
+        display: inline-block;
+        border: none;
+        text-align: center;
+        padding: 26px 0;
+        line-height: 20px;
+        font-size: 18px;
+        color: #303036;
+      }
+
+      .plus {
+        cursor: pointer;
+        margin-right: 20px;
+        font-size: 24px;
+        color: #D0D8E2;
+      }
+
+      .minus {
+        cursor: pointer;
+        margin-left: 20px;
+        font-size: 24px;
+        color: #D0D8E2;
+      }
+    }
+
+    .t1 {
+      text-align: center;
+      font-size: 16px;
+      color: #686872;
+      line-height: 16px;
+      margin-bottom: 30px;
+
+      b {
+        margin: 0 5px;
+        color: #41A6F3;
+        font-weight: 500;
+      }
+    }
+
+    .list {
+      text-align: center;
+
+      .item {
+        display: inline-block;
+        margin: 0 30px;
+
+        .sort-num {
+          position: relative;
+          margin-bottom: 10px;
+
+          .sort-text {
+            display: inline-block;
+            font-size: 16px;
+            color: #686872;
+          }
+
+          input {
+            margin-left: 5px;
+            border: none;
+            background: #F7F7F7;
+            display: inline-block;
+            width: 40px;
+            height: 32px;
+            color: #303036;
+            padding: 6px 0;
+            text-align: center;
+            line-height: 20px;
+            font-size: 16px;
+          }
+
+          .up {
+            cursor: pointer;
+            position: absolute;
+            right: -20px;
+            top: 4px;
+            font-size: 12px;
+            color: #D0D8E2;
+
+          }
+
+          .down {
+            cursor: pointer;
+            position: absolute;
+            right: -20px;
+            top: 20px;
+            font-size: 12px;
+            color: #D0D8E2;
+          }
+        }
+
+        .t2 {
+          font-size: 12px;
+          color: #686872;
+
+          b {
+            color: #41A6F3;
+            margin: 0 5px;
+            font-weight: 500;
+          }
+        }
+      }
+    }
+  }
+}

+ 262 - 1
front/project/www/routes/my/tools/page.js

@@ -1,9 +1,270 @@
 import React from 'react';
 import './index.less';
+import { Icon } from 'antd';
 import Page from '@src/containers/Page';
+import Assets from '@src/components/Assets';
+import UserLayout from '../../../layouts/User';
+import UserAction from '../../../components/UserAction';
+import menu from '../index';
+import Tabs from '../../../components/Tabs';
+import More from '../../../components/More';
+import Button from '../../../components/Button';
+import Switch from '../../../components/Switch';
 
 export default class extends Page {
+  initState() {
+    return {
+      tab: '1',
+      sortMap: {},
+      filterMap: {},
+      data: [
+        { num: '30', version: '7', title: 'OG16/17/18/19语法千行', date: '2019-08-31  09:26:13' },
+        { num: '30', version: '7', title: 'OG16/17/18/19语法千行', date: '2019-08-31  09:26:13' },
+        { num: '30', version: '7', title: 'OG16/17/18/19语法千行', date: '2019-08-31  09:26:13' },
+      ],
+    };
+  }
+
+  onFilter(value) {
+    this.setState({ filterMap: value });
+  }
+
+  onSort(value) {
+    this.setState({ sortMap: value });
+  }
+
+  onTabChange(tab) {
+    this.setState({ tab });
+  }
+
   renderView() {
-    return <div />;
+    const { config } = this.props;
+    return <UserLayout active={config.key} menu={menu} center={this.renderDetail()} />;
+  }
+
+  renderDetail() {
+    const { tab } = this.state;
+    return (
+      <div className="table-layout">
+        <Tabs
+          border
+          type="division"
+          theme="theme"
+          size="small"
+          space={2.5}
+          width={100}
+          active={tab}
+          tabs={[
+            { key: '1', title: '资料' },
+            { key: '2', title: '机经' },
+            { key: '3', title: '模考' },
+            { key: '4', title: 'VIP' },
+            { key: '5', title: '考分计算器' },
+          ]}
+          onChange={key => this.onTabChange(key)}
+        />
+        {this[`renderTab${tab}`]()}
+      </div>
+    );
+  }
+
+  renderTab1() {
+    const { data = [], filterMap = {}, sortMap = {} } = this.state;
+    return (
+      <div className="tab-1-layout">
+        <UserAction
+          selectList={[
+            {
+              label: '学科',
+              key: '1',
+              select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+            },
+            {
+              label: '资料形式',
+              key: '2',
+              select: [{ title: '123', key: '1' }, { title: '123', key: '2' }, { title: '123', key: '2' }],
+            },
+          ]}
+          sortList={[{ right: true, label: '销量', key: '1' }, { right: true, label: '更新时间', key: '2' }]}
+          sortMap={sortMap}
+          filterMap={filterMap}
+          onFilter={value => this.onFilter(value)}
+          onSort={value => this.onSort(value)}
+          right={
+            <div className="email">
+              邮箱订阅 <Switch />
+            </div>
+          }
+        />
+        <div className="data-layout">
+          {data.map(item => {
+            return (
+              <div className="data-item">
+                <Assets name="sun_blue" />
+                <div className="fixed">
+                  <div className="btns">
+                    <Button size="small" radius>
+                      阅读
+                    </Button>
+                    <div className="white">下载</div>
+                  </div>
+                </div>
+                <div className="title">
+                  <span>版本{item.version}</span>
+                  {item.title}
+                </div>
+                <div className="date">{item.date}</div>
+                <More menu={[{ label: '纠错', key: '1' }, { label: '评价', key: '2' }, { label: '更新', key: '3' }]} />
+              </div>
+            );
+          })}
+        </div>
+      </div>
+    );
+  }
+
+  renderTab2() {
+    const { data = [] } = this.state;
+    return (
+      <div className="tab-2-layout">
+        <UserAction
+          left={
+            <div className="total-log">
+              <span>最新换库</span>
+              <span>2019-07-22</span>
+              <span>
+                已换库<b>10</b>天
+              </span>
+            </div>
+          }
+        />
+        <div className="data-layout">
+          {data.map(item => {
+            return (
+              <div className="data-item">
+                <Assets name="sun_blue" />
+                <div className="title">
+                  已更新至<b>{item.num}</b>题
+                </div>
+                <div className="date">{item.date}</div>
+                <More menu={[{ label: '更新', key: '1' }, { label: '反馈', key: '2' }, { label: '评价', key: '3' }]} />
+              </div>
+            );
+          })}
+        </div>
+        <div className="tip-layout">
+          <div className="t1">还未购买本月机经</div>
+          <div className="desc">¥ 888 / 月</div>
+          <Button radius size="lager" width={150}>
+            立即购买
+          </Button>
+        </div>
+        <div className="tip-layout">
+          <div className="t2">请于2019-11-20前开通</div>
+          <Button radius size="lager" width={150}>
+            立即开通
+          </Button>
+        </div>
+      </div>
+    );
+  }
+
+  renderTab3() {
+    return (
+      <div className="tab-3-layout">
+        <UserAction right={<div className="email">待开通</div>} />
+        <div className="tip-layout">
+          <div className="t1">未购买</div>
+          <div className="desc">¥ 888 / 月</div>
+          <Button radius size="lager" width={150}>
+            立即购买
+          </Button>
+        </div>
+        <div className="tip-layout">
+          <div className="t2">请于2019-11-20前开通</div>
+          <Button radius size="lager" width={150}>
+            立即开通
+          </Button>
+        </div>
+        <div className="tip-layout">
+          <div className="t1">使用中</div>
+          <div className="t2">距离到期还有 10 天</div>
+        </div>
+        <div className="tip-layout">
+          <div className="t3">已过期</div>
+          <div className="date">2019-05-11 ~ 2019-09-11</div>
+          <div className="desc">¥ 800/3个月</div>
+          <Button radius size="lager" width={150}>
+            立即购买
+          </Button>
+        </div>
+      </div>
+    );
+  }
+
+  renderTab4() {
+    return (
+      <div className="tab-4-layout">
+        <div className="tip-layout">
+          <div className="t2">未购买</div>
+          <Button radius size="lager" width={150}>
+            立即购买
+          </Button>
+        </div>
+        <div className="tip-layout">
+          <div className="t1">使用中</div>
+          <div className="desc">2019-05-20 到期</div>
+          <Button radius size="lager" width={150}>
+            续费
+          </Button>
+        </div>
+        <div className="tip-layout">
+          <div className="t1">已过期</div>
+          <div className="desc">2019-05-11 ~ 2019-09-11</div>
+          <Button radius size="lager" width={150}>
+            立即购买
+          </Button>
+        </div>
+      </div>
+    );
+  }
+
+  renderTab5() {
+    return (
+      <div className="tab-5-layout">
+        <div className="total-sort-num">
+          <Icon className="plus" type="plus" />
+          <input />
+          <Icon className="minus" type="minus" />
+        </div>
+        <div className="t1">
+          预计全球排名<b>94th</b>
+        </div>
+        <div className="list">
+          <div className="item">
+            <div className="sort-num">
+              <div className="sort-text">Quant :</div>
+              <input />
+              <Icon className="up" type="caret-up" />
+              <Icon className="down" type="caret-down" />
+            </div>
+            <div className="t2">
+              排名<b>94th</b>
+            </div>
+          </div>
+          <div className="item">
+            <div className="sort-num">
+              <div className="sort-text">Quant :</div>
+              <input />
+              <Icon className="up" type="caret-up" />
+              <Icon className="down" type="caret-down" />
+            </div>
+            <div className="t2">
+              排名<b>94th</b>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
   }
 }

+ 1 - 1
front/src/components/Assets/index.less

@@ -1,6 +1,6 @@
 .assets {
   display: inline-block;
-  vertical-align: middle;
+  vertical-align: top;
   border: none;
   max-width: 100%;
   max-height: 100%;