Explorar o código

add user page

KaysonCui %!s(int64=5) %!d(string=hai) anos
pai
achega
5f1c612202
Modificáronse 91 ficheiros con 2311 adicións e 231 borrados
  1. 73 0
      front/project/www/app.less
  2. BIN=BIN
      front/project/www/assets/PDF.png
  3. BIN=BIN
      front/project/www/assets/QA_gary.png
  4. BIN=BIN
      front/project/www/assets/QA_normal.png
  5. BIN=BIN
      front/project/www/assets/VIP.png
  6. BIN=BIN
      front/project/www/assets/VIP_small_gray.png
  7. BIN=BIN
      front/project/www/assets/VIP_small_yellow.png
  8. BIN=BIN
      front/project/www/assets/alipay.png
  9. BIN=BIN
      front/project/www/assets/all.png
  10. BIN=BIN
      front/project/www/assets/book_1.png
  11. BIN=BIN
      front/project/www/assets/calendar.png
  12. BIN=BIN
      front/project/www/assets/class_gray.png
  13. BIN=BIN
      front/project/www/assets/class_normal.png
  14. BIN=BIN
      front/project/www/assets/clockin_block.png
  15. BIN=BIN
      front/project/www/assets/clockin_normal.png
  16. BIN=BIN
      front/project/www/assets/down.png
  17. BIN=BIN
      front/project/www/assets/dropdown_hover.png
  18. BIN=BIN
      front/project/www/assets/dropdown_normal.png
  19. BIN=BIN
      front/project/www/assets/dropup_hover.png
  20. BIN=BIN
      front/project/www/assets/dropup_normal.png
  21. BIN=BIN
      front/project/www/assets/email.png
  22. BIN=BIN
      front/project/www/assets/gift.png
  23. BIN=BIN
      front/project/www/assets/gift2.png
  24. BIN=BIN
      front/project/www/assets/homework_gray.png
  25. BIN=BIN
      front/project/www/assets/homework_normal.png
  26. BIN=BIN
      front/project/www/assets/information.png
  27. BIN=BIN
      front/project/www/assets/information2.png
  28. BIN=BIN
      front/project/www/assets/information_gray.png
  29. BIN=BIN
      front/project/www/assets/information_normal.png
  30. BIN=BIN
      front/project/www/assets/invite.png
  31. BIN=BIN
      front/project/www/assets/more1_hover.png
  32. BIN=BIN
      front/project/www/assets/more1_normal.png
  33. BIN=BIN
      front/project/www/assets/more2_hover.png
  34. BIN=BIN
      front/project/www/assets/more2_normal.png
  35. BIN=BIN
      front/project/www/assets/more3_hover.png
  36. BIN=BIN
      front/project/www/assets/more3_normal.png
  37. BIN=BIN
      front/project/www/assets/note_block_highlight.png
  38. BIN=BIN
      front/project/www/assets/note_block_normal.png
  39. BIN=BIN
      front/project/www/assets/note_gray.png
  40. BIN=BIN
      front/project/www/assets/page_normal.png
  41. BIN=BIN
      front/project/www/assets/phone_1.png
  42. BIN=BIN
      front/project/www/assets/question_block.png
  43. BIN=BIN
      front/project/www/assets/question_normal.png
  44. BIN=BIN
      front/project/www/assets/realname.png
  45. BIN=BIN
      front/project/www/assets/realname2.png
  46. BIN=BIN
      front/project/www/assets/seqencing1_normal.png
  47. BIN=BIN
      front/project/www/assets/seqencing1_select.png
  48. BIN=BIN
      front/project/www/assets/seqencing2_down_select.png
  49. BIN=BIN
      front/project/www/assets/seqencing2_normal.png
  50. BIN=BIN
      front/project/www/assets/seqencing2_up_select.png
  51. BIN=BIN
      front/project/www/assets/speed_block.png
  52. BIN=BIN
      front/project/www/assets/speed_normal.png
  53. BIN=BIN
      front/project/www/assets/supplement_gray.png
  54. BIN=BIN
      front/project/www/assets/supplement_normal.png
  55. BIN=BIN
      front/project/www/assets/time_gray.png
  56. BIN=BIN
      front/project/www/assets/time_normal.png
  57. BIN=BIN
      front/project/www/assets/tips_hover.png
  58. BIN=BIN
      front/project/www/assets/tips_normal.png
  59. BIN=BIN
      front/project/www/assets/up.png
  60. BIN=BIN
      front/project/www/assets/wechat.png
  61. BIN=BIN
      front/project/www/assets/wechatpay.png
  62. 9 0
      front/project/www/components/Button/index.less
  63. 19 0
      front/project/www/components/Date/index.js
  64. 102 0
      front/project/www/components/Date/index.less
  65. 218 0
      front/project/www/components/Examination/index.js
  66. 78 0
      front/project/www/components/Examination/index.less
  67. 44 0
      front/project/www/components/Invite/index.js
  68. 80 0
      front/project/www/components/Invite/index.less
  69. 29 6
      front/project/www/components/Login/index.js
  70. 61 59
      front/project/www/components/Login/index.less
  71. 54 0
      front/project/www/components/Modal/index.js
  72. 187 0
      front/project/www/components/Modal/index.less
  73. 276 0
      front/project/www/components/OtherModal/index.js
  74. 140 0
      front/project/www/components/OtherModal/index.less
  75. 83 0
      front/project/www/components/Radio/index.js
  76. 119 0
      front/project/www/components/Radio/index.less
  77. 53 0
      front/project/www/components/Ratio/index.js
  78. 32 0
      front/project/www/components/Ratio/index.less
  79. 16 19
      front/project/www/components/Select/index.less
  80. 2 2
      front/project/www/components/Tabs/index.js
  81. 1 0
      front/project/www/components/Tabs/index.less
  82. 49 0
      front/project/www/components/TotalSort/index.js
  83. 113 0
      front/project/www/components/TotalSort/index.less
  84. 114 0
      front/project/www/components/VipRenew/index.js
  85. 94 0
      front/project/www/components/VipRenew/index.less
  86. 15 0
      front/project/www/routes/my/course/index.less
  87. 57 0
      front/project/www/routes/my/course/page.js
  88. 111 1
      front/project/www/routes/my/error/page.js
  89. 31 1
      front/project/www/routes/my/main/page.js
  90. 0 111
      front/project/www/routes/my/tools/index.less
  91. 51 32
      front/project/www/routes/my/tools/page.js

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

@@ -38,6 +38,10 @@
   border-bottom: 1px dashed @theme_color;
 }
 
+.b-b {
+  border-bottom: 1px solid #eee;
+}
+
 .f-s-16 {
   font-size: 16px;
 }
@@ -134,6 +138,14 @@
   margin-bottom: 40px;
 }
 
+.p-5 {
+  padding: 5px;
+}
+
+.p-10 {
+  padding: 10px;
+}
+
 .p-l-5 {
   padding-left: 5px;
 }
@@ -206,9 +218,70 @@
   width: 50%;
 }
 
+.w-10 {
+  width: 100%;
+}
+
+.flex-layout {
+  display: flex;
+}
+
+.flex-block {
+  flex: 1;
+}
+
+.t-1 {
+  color: #303036;
+}
+
+.t-2 {
+  color: #686872;
+}
+
+.t-3 {
+  color: #B5B5BA;
+}
+
+.t-4 {
+  color: #4292F0;
+}
+
+.t-5 {
+  color: #6EC64B;
+}
+
+.b-c-1 {
+  background: #F7F7F7;
+}
+
+.t-s-18 {
+  font-size: 18px;
+}
+
+.t-s-16 {
+  font-size: 16px;
+}
+
+.t-s-12 {
+  font-size: 12px;
+}
+
+.t-s-10 {
+  font-size: 10px;
+}
+
+.t-s-20 {
+  font-size: 20px;
+}
+
+.t-s-14 {
+  font-size: 14px;
+}
+
 input,
 textarea {
   outline: none;
+  border: none;
 }
 
 input::-webkit-input-placeholder,

BIN=BIN
front/project/www/assets/PDF.png


BIN=BIN
front/project/www/assets/QA_gary.png


BIN=BIN
front/project/www/assets/QA_normal.png


BIN=BIN
front/project/www/assets/VIP.png


BIN=BIN
front/project/www/assets/VIP_small_gray.png


BIN=BIN
front/project/www/assets/VIP_small_yellow.png


BIN=BIN
front/project/www/assets/alipay.png


BIN=BIN
front/project/www/assets/all.png


BIN=BIN
front/project/www/assets/book_1.png


BIN=BIN
front/project/www/assets/calendar.png


BIN=BIN
front/project/www/assets/class_gray.png


BIN=BIN
front/project/www/assets/class_normal.png


BIN=BIN
front/project/www/assets/clockin_block.png


BIN=BIN
front/project/www/assets/clockin_normal.png


BIN=BIN
front/project/www/assets/down.png


BIN=BIN
front/project/www/assets/dropdown_hover.png


BIN=BIN
front/project/www/assets/dropdown_normal.png


BIN=BIN
front/project/www/assets/dropup_hover.png


BIN=BIN
front/project/www/assets/dropup_normal.png


BIN=BIN
front/project/www/assets/email.png


BIN=BIN
front/project/www/assets/gift.png


BIN=BIN
front/project/www/assets/gift2.png


BIN=BIN
front/project/www/assets/homework_gray.png


BIN=BIN
front/project/www/assets/homework_normal.png


BIN=BIN
front/project/www/assets/information.png


BIN=BIN
front/project/www/assets/information2.png


BIN=BIN
front/project/www/assets/information_gray.png


BIN=BIN
front/project/www/assets/information_normal.png


BIN=BIN
front/project/www/assets/invite.png


BIN=BIN
front/project/www/assets/more1_hover.png


BIN=BIN
front/project/www/assets/more1_normal.png


BIN=BIN
front/project/www/assets/more2_hover.png


BIN=BIN
front/project/www/assets/more2_normal.png


BIN=BIN
front/project/www/assets/more3_hover.png


BIN=BIN
front/project/www/assets/more3_normal.png


BIN=BIN
front/project/www/assets/note_block_highlight.png


BIN=BIN
front/project/www/assets/note_block_normal.png


BIN=BIN
front/project/www/assets/note_gray.png


BIN=BIN
front/project/www/assets/page_normal.png


BIN=BIN
front/project/www/assets/phone_1.png


BIN=BIN
front/project/www/assets/question_block.png


BIN=BIN
front/project/www/assets/question_normal.png


BIN=BIN
front/project/www/assets/realname.png


BIN=BIN
front/project/www/assets/realname2.png


BIN=BIN
front/project/www/assets/seqencing1_normal.png


BIN=BIN
front/project/www/assets/seqencing1_select.png


BIN=BIN
front/project/www/assets/seqencing2_down_select.png


BIN=BIN
front/project/www/assets/seqencing2_normal.png


BIN=BIN
front/project/www/assets/seqencing2_up_select.png


BIN=BIN
front/project/www/assets/speed_block.png


BIN=BIN
front/project/www/assets/speed_normal.png


BIN=BIN
front/project/www/assets/supplement_gray.png


BIN=BIN
front/project/www/assets/supplement_normal.png


BIN=BIN
front/project/www/assets/time_gray.png


BIN=BIN
front/project/www/assets/time_normal.png


BIN=BIN
front/project/www/assets/tips_hover.png


BIN=BIN
front/project/www/assets/tips_normal.png


BIN=BIN
front/project/www/assets/up.png


BIN=BIN
front/project/www/assets/wechat.png


BIN=BIN
front/project/www/assets/wechatpay.png


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

@@ -54,6 +54,11 @@
   border: 1px solid #b8b8b8;
 }
 
+.button.link {
+  border: none;
+  color: #686872;
+}
+
 .button.default {
   background: #fff;
   color: @holder_color;
@@ -80,6 +85,10 @@
   border: 2px solid @theme_color;
 }
 
+.button.link:hover {
+  color: @theme_color;
+}
+
 .button.theme:hover {
   background: @theme_color_hover;
 }

+ 19 - 0
front/project/www/components/Date/index.js

@@ -0,0 +1,19 @@
+import React, { Component } from 'react';
+import './index.less';
+import { DatePicker } from 'antd';
+
+export default class extends Component {
+  render() {
+    const { hideInput, dateRender, disabledDate } = this.props;
+    return (
+      <div className={`g-date-block ${hideInput ? 'hide-input' : ''}`}>
+        <DatePicker
+          open
+          dropdownClassName={`g-date ${hideInput ? 'hide-input' : ''}`}
+          disabledDate={disabledDate}
+          dateRender={dateRender}
+        />
+      </div>
+    );
+  }
+}

+ 102 - 0
front/project/www/components/Date/index.less

@@ -0,0 +1,102 @@
+.g-date {
+  width: 412px;
+
+  .ant-calendar {
+    box-shadow: none;
+    width: 100%;
+
+    .ant-calendar-input-wrap {
+      padding: 13px 10px;
+      line-height: 20px;
+      background: #F7F7F7;
+      height: 46px;
+      border: none;
+      margin-bottom: 10px;
+
+      input {
+        background: #F7F7F7;
+        color: #303036;
+      }
+    }
+
+    .ant-calendar-header {
+      color: #000;
+      font-size: 16px;
+      border: none;
+      margin-bottom: 5px;
+    }
+
+    .ant-calendar-body {
+      table {
+        th {
+          height: 64px;
+          background: #F5F6F7;
+        }
+
+        td {
+          height: 50px;
+        }
+      }
+
+      .ant-calendar-date {
+        width: 35px;
+        height: 35px;
+        line-height: 33px;
+        border-radius: 50%;
+        position: relative;
+
+        .s1 {
+          width: 6px;
+          height: 6px;
+          border-radius: 50%;
+          position: absolute;
+          right: 0;
+          top: 10px;
+        }
+
+        .s2 {
+          width: 6px;
+          height: 6px;
+          border-radius: 50%;
+          position: absolute;
+          right: 0;
+          top: 18px;
+        }
+      }
+
+      .ant-calendar-today .ant-calendar-date {
+        border-color: #fff;
+      }
+
+      .ant-calendar-selected-date .ant-calendar-date,
+      .ant-calendar-selected-date.ant-calendar-today .ant-calendar-date {
+        color: #1890ff;
+        font-weight: 700;
+        border-color: #1890ff;
+        background: #fff;
+      }
+
+      .ant-calendar-disabled-cell.ant-calendar-today .ant-calendar-date:before {
+        display: none;
+      }
+    }
+
+    .ant-calendar-footer {
+      display: none;
+    }
+  }
+}
+
+.g-date.hide-input {
+  .ant-calendar-input-wrap {
+    display: none;
+  }
+}
+
+.g-date-block {
+  height: 500px;
+}
+
+.g-date-block.hide-input {
+  height: 460px;
+}

+ 218 - 0
front/project/www/components/Examination/index.js

@@ -0,0 +1,218 @@
+import React, { Component } from 'react';
+import './index.less';
+import { Icon } from 'antd';
+import { SpecialRadioGroup } from '../Radio';
+import Modal from '../Modal';
+import Button from '../Button';
+import TotalSort from '../TotalSort';
+import Date from '../Date';
+import Ratio from '../Ratio';
+
+export default class extends Component {
+  constructor(props) {
+    super(props);
+    this.stepProp = {
+      0: {
+        title: '我的身份',
+      },
+      1: {
+        title: '下次考试时间',
+      },
+      2: {
+        title: '目标成绩',
+      },
+      3: {
+        title: '最晚出分时间(选填)',
+      },
+      4: {
+        title: '备考信息',
+        onConfirm: props.onConfirm,
+        onCancel: props.onCancel,
+        confirmText: '关闭',
+        cancelText: '修改',
+      },
+    };
+    this.state = { step: 0, data: { type: '1', time: '1' } };
+  }
+
+  onChange(type, key) {
+    const { data, step } = this.state;
+    data[type] = key;
+    this.setState({ data, step: step + 1 });
+  }
+
+  onPrev() {
+    const { step } = this.state;
+    this.setState({ step: step - 1 });
+  }
+
+  onNext() {
+    const { step } = this.state;
+    this.setState({ step: step + 1 });
+  }
+
+  render() {
+    const { step } = this.state;
+    const { show, onClose } = this.props;
+    return (
+      <Modal
+        className="examination-modal"
+        show={show}
+        width={460}
+        {...this.stepProp[step]}
+        onClose={() => onClose && onClose()}
+      >
+        <div className="examination-modal-wrapper">{this[`renderStep${step}`]()}</div>
+      </Modal>
+    );
+  }
+
+  renderStep0() {
+    const { data } = this.state;
+    const { type } = data;
+    return (
+      <div className="step-0-layout">
+        <SpecialRadioGroup
+          list={[
+            { label: '学生-Domestic', value: '1' },
+            { label: '学生-Overseas', value: '2' },
+            { label: '在职-Domestic', value: '3' },
+            { label: '在职-Overseas', value: '4' },
+            { label: 'Gap Year', value: '5' },
+          ]}
+          value={type}
+          width={190}
+          space={10}
+          onChange={key => this.onChange('type', key)}
+        />
+        <div className="action-layout">
+          <div className="next" onClick={() => this.onNext()}>
+            下一题
+            <Icon type="right-circle" theme="filled" />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderStep1() {
+    const { data } = this.state;
+    const { time } = data;
+    return (
+      <div className="step-1-layout">
+        <SpecialRadioGroup
+          list={[
+            { label: '近1个月', value: '1' },
+            { label: '近2个月', value: '2' },
+            { label: '近3个月', value: '3' },
+            { label: '半年内', value: '4' },
+          ]}
+          value={time}
+          width={195}
+          space={10}
+          onChange={key => this.onChange('time', key)}
+        />
+        <div className="action-layout">
+          <div className="prev" onClick={() => this.onPrev()}>
+            <Icon type="left-circle" theme="filled" />
+            上一题
+          </div>
+          <div className="next" onClick={() => this.onNext()}>
+            下一题
+            <Icon type="right-circle" theme="filled" />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderStep2() {
+    return (
+      <div className="step-2-layout">
+        <TotalSort />
+        <div className="action-layout">
+          <div className="prev" onClick={() => this.onPrev()}>
+            <Icon type="left-circle" theme="filled" />
+            上一题
+          </div>
+          <div className="next" onClick={() => this.onNext()}>
+            下一题
+            <Icon type="right-circle" theme="filled" />
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderStep3() {
+    return (
+      <div className="step-3-layout">
+        <Date />
+        <div className="action-layout">
+          <div className="prev" onClick={() => this.onPrev()}>
+            <Icon type="left-circle" theme="filled" />
+            上一题
+          </div>
+          <Button size="lager" radius>
+            提交
+          </Button>
+        </div>
+      </div>
+    );
+  }
+
+  renderStep4() {
+    return (
+      <div className="step-4-layout">
+        <div className="tip">
+          <Icon type="check" />
+          7天VIP权限已赠送至您的账户。
+        </div>
+        <Ratio
+          text="身份"
+          subtext="在职(D)"
+          values={[
+            { label: '学生-D; 20%', value: 10, color: '#41A6F3' },
+            { label: '学生- O; 20%', value: 20, color: '#3F86EA' },
+            { label: '在职-D; 20%', value: 20, color: '#41A6F3' },
+            { label: '在职- O; 20%', value: 20, color: '#6DC0FF' },
+            { label: 'Gap Year; 20%', value: 20, color: '#9BD4FF' },
+          ]}
+        />
+        <Ratio
+          text="考试时间"
+          subtext="近1个月"
+          values={[
+            { label: '近1个月; 20%', value: 10, color: '#6865FD' },
+            { label: '近2个月; 20%', value: 20, color: '#322EF2' },
+            { label: '近3个月; 20%', value: 20, color: '#8F65FF' },
+            { label: '半年内; 20%', value: 20, color: '#8C8AFF' },
+          ]}
+        />
+        <Ratio
+          text="目标成绩"
+          subtext="670分"
+          values={[
+            { label: '600+; 20%', value: 10, color: '#2754E0' },
+            { label: '650+; 20%', value: 20, color: '#3F86EA' },
+            { label: '700+; 20%', value: 20, color: '#41A6F3' },
+            { label: '700+; 20%', value: 20, color: '#6DC0FF' },
+          ]}
+        />
+        <Ratio
+          text="出分日期"
+          subtext="2019-11"
+          values={[
+            { label: '近1个月; 20%', value: 10, color: '#6865FD' },
+            { label: '近2个月; 20%', value: 20, color: '#322EF2' },
+            { label: '近3个月; 20%', value: 20, color: '#8F65FF' },
+            { label: '半年内; 20%', value: 20, color: '#8C8AFF' },
+            { label: '半年内; 20%', value: 20, color: '#8C8AFF' },
+            { label: '1年内; 20%', value: 20, color: '#8F65FF' },
+            { label: '不清楚', value: 20, color: '#C3C1D1' },
+          ]}
+        />
+      </div>
+    );
+  }
+}

+ 78 - 0
front/project/www/components/Examination/index.less

@@ -0,0 +1,78 @@
+.g-modal.examination-modal {
+  .g-modal-wrapper {
+    .g-modal-body {
+      padding-bottom: 0;
+    }
+  }
+
+  .examination-modal-wrapper {
+    .g-special-radio-group {
+      padding-bottom: 30px;
+    }
+
+    .action-layout {
+      color: #8897A8;
+      font-size: 12px;
+      overflow: hidden;
+      text-align: center;
+      line-height: 40px;
+
+      i {
+        color: #D0D8E2;
+      }
+
+      .prev {
+        float: left;
+        cursor: pointer;
+        display: inline-block;
+
+        i {
+          margin-right: 5px;
+        }
+      }
+
+      .next {
+        cursor: pointer;
+        display: inline-block;
+        float: right;
+
+        i {
+          margin-left: 5px;
+        }
+      }
+    }
+
+    .total-sort {
+      padding-bottom: 50px;
+    }
+
+    .step-0-layout {}
+
+    .step-1-layout {}
+
+    .step-2-layout {}
+
+    .step-3-layout {
+
+      .button {
+        padding-left: 30px;
+        padding-right: 30px;
+      }
+    }
+
+    .step-4-layout {
+      .tip {
+        margin-bottom: 30px;
+
+        i {
+          color: #6EC64B;
+          margin-right: 5px;
+        }
+      }
+
+      .ratio-pie {
+        margin-bottom: 30px;
+      }
+    }
+  }
+}

+ 44 - 0
front/project/www/components/Invite/index.js

@@ -0,0 +1,44 @@
+import React from 'react';
+import './index.less';
+import Assets from '@src/components/Assets';
+import { Icon } from 'antd';
+import Button from '../Button';
+import { Input } from '../Login';
+
+function Invite() {
+  return (
+    <div className="invite-block">
+      <div className="title-block">
+        <b>方法一:</b> 将邀请链接发送给好友
+      </div>
+      <div className="input-layout">
+        <div className="input-block">
+          <div className="t1">http://</div>
+          <div className="t2">qianhanggmat.com/id123134341431</div>
+        </div>
+        <Button radius>复制</Button>
+      </div>
+      <div className="title-block">
+        <b>方法二:</b> 微信扫码分享
+      </div>
+      <div className="qrcode">
+        <Assets name="qrcode" />
+      </div>
+      <div className="title-block">
+        <b>方法三:</b> 向好友发送邀请邮件
+      </div>
+      <div className="input-layout">
+        <div className="input-block">
+          <Input placeholder="输入邮箱后,回车可添加多个" />
+          <div className="success">
+            <Icon type="check" />
+            邀请已发送
+          </div>
+        </div>
+        <Button radius>复制</Button>
+      </div>
+    </div>
+  );
+}
+Invite.propTypes = {};
+export default Invite;

+ 80 - 0
front/project/www/components/Invite/index.less

@@ -0,0 +1,80 @@
+@import '../../app.less';
+
+.invite-block {
+
+
+  .title-block {
+    font-size: 18px;
+    color: #686872;
+    margin-bottom: 10px;
+
+    b {
+      color: #303036;
+      font-weight: 500;
+    }
+  }
+
+  .qrcode {
+    text-align: center;
+    padding: 30px;
+
+    .assets {
+      width: 110px;
+      height: 110px;
+      box-shadow: 0px 5px 10px 0px rgba(156, 183, 223, 0.14), 0px 5px 10px 0px rgba(0, 0, 0, 0.01);
+      border-radius: 4px;
+    }
+  }
+
+  .input-layout {
+    display: flex;
+    margin-bottom: 20px;
+
+    .button {
+      height: 36px;
+      line-height: 28px;
+      margin-top: 5px;
+    }
+
+    .input-block {
+      flex: 1;
+      margin-right: 10px;
+
+      .t1 {
+        display: inline-block;
+        width: 75px;
+        height: 46px;
+        background: #F7F7F7;
+        border: 1px solid #F7F7F7;
+        line-height: 44px;
+        font-size: 16px;
+        text-align: center;
+      }
+
+      .t2 {
+        display: inline-block;
+        width: 415px;
+        height: 46px;
+        background: #fff;
+        line-height: 44px;
+        border: 1px solid #F7F7F7;
+        font-size: 16px;
+        padding-left: 15px;
+        color: #4299FF;
+      }
+
+      .success {
+        font-size: 18px;
+        text-align: right;
+
+        i {
+          color: #6EC64B;
+        }
+      }
+
+      .g-input-wrapper {
+        margin: 0;
+      }
+    }
+  }
+}

+ 29 - 6
front/project/www/components/Login/index.js

@@ -47,7 +47,7 @@ export default class Login extends Component {
     if (!area || !mobile || !mobileVerifyCode) return;
     if (needEmail && !email) return;
     User.login(area, mobile, mobileVerifyCode, email)
-      .then((result) => {
+      .then(result => {
         if (result.bindWechat) {
           this.close();
         } else {
@@ -155,7 +155,16 @@ export default class Login extends Component {
     const { type } = this.state;
     const { user } = this.props;
     return (
-      <Modal ref={(ref) => { this.modalR = ref; }} wrapClassName={`login-modal ${type}`} visible={user.needLogin} footer={null} closable={false} width={470}>
+      <Modal
+        ref={ref => {
+          this.modalR = ref;
+        }}
+        wrapClassName={`login-modal ${type}`}
+        visible={user.needLogin}
+        footer={null}
+        closable={false}
+        width={470}
+      >
         {this.renderBody(type)}
         <GIcon
           name="close"
@@ -256,7 +265,14 @@ export default class Login extends Component {
       <div className="body">
         <div className="title">微信扫码登录</div>
         <div className="qr-code">
-          <iframe frameBorder="0" src={`/login.html?appid=${WechatUserAppId}&redirectUri=${encodeURIComponent('http://www.duoshaojiaoyu.com')}`} width="300" height="300" />
+          <iframe
+            frameBorder="0"
+            src={`/login.html?appid=${WechatUserAppId}&redirectUri=${encodeURIComponent(
+              'http://www.duoshaojiaoyu.com',
+            )}`}
+            width="300"
+            height="300"
+          />
           <div className="text">请使用微信扫描二维码登录</div>
         </div>
         <Tooltip overlayClassName="gray" placement="left" title="手机号登录">
@@ -340,7 +356,14 @@ export default class Login extends Component {
           手机号注册成功!为更好的使用服务,建议您绑定微信号。
         </div>
         <div className="qr-code">
-          <iframe frameBorder="0" src={`/login.html?appid=${WechatUserAppId}&redirectUri=${encodeURIComponent('http://www.duoshaojiaoyu.com')}`} width="300" height="300" />
+          <iframe
+            frameBorder="0"
+            src={`/login.html?appid=${WechatUserAppId}&redirectUri=${encodeURIComponent(
+              'http://www.duoshaojiaoyu.com',
+            )}`}
+            width="300"
+            height="300"
+          />
           <div className="text">请使用微信扫描二维码登录</div>
           <div
             className="jump"
@@ -375,7 +398,7 @@ export default class Login extends Component {
   }
 }
 
-class Input extends Component {
+export class Input extends Component {
   render() {
     const { className = '', onChange, placeholder, value, error, left, right } = this.props;
     return (
@@ -398,7 +421,7 @@ class Input extends Component {
   }
 }
 
-class SelectInput extends Component {
+export class SelectInput extends Component {
   constructor(props) {
     super(props);
     this.state = { showSelect: false };

+ 61 - 59
front/project/www/components/Login/index.less

@@ -77,80 +77,82 @@
       transform: translateX(-50%);
     }
   }
+}
 
-  .g-input-container {
-
-    .g-input-wrapper {
-      display: flex;
-      background: #F7F7F7;
-      height: 44px;
-      padding: 8px 0;
-      line-height: 28px;
-      margin-bottom: 25px;
-
-      .g-input {
-        flex: 1;
-        background: transparent;
-        border: none;
-        padding: 10px 20px;
-        height: 28px;
-      }
 
-      .g-input-left {
-        position: relative;
-        cursor: pointer;
 
-        .g-input-left-select {
-          height: 28px;
-          border-right: 1px solid #eee;
-          padding-left: 10px;
+.g-input-container {
 
-          i {
-            margin-left: 5px;
-            font-size: 10px;
-            margin-right: 10px;
-          }
-        }
-      }
+  .g-input-wrapper {
+    display: flex;
+    background: #F7F7F7;
+    height: 44px;
+    padding: 8px 0;
+    line-height: 28px;
+    margin-bottom: 25px;
+
+    .g-input {
+      flex: 1;
+      background: transparent;
+      border: none;
+      padding: 10px 20px;
+      height: 28px;
+    }
 
-      .g-input-right {
-        padding: 0 20px;
-        cursor: pointer;
+    .g-input-left {
+      position: relative;
+      cursor: pointer;
 
-        .g-input-right-verification {
-          color: #41A6F3;
-        }
+      .g-input-left-select {
+        height: 28px;
+        border-right: 1px solid #eee;
+        padding-left: 10px;
 
-        .g-input-right-verification.loading {
-          color: #A7A7B7;
+        i {
+          margin-left: 5px;
+          font-size: 10px;
+          margin-right: 10px;
         }
       }
     }
 
-    .g-input-wrapper.error {
-      margin-bottom: 0;
-    }
+    .g-input-right {
+      padding: 0 20px;
+      cursor: pointer;
 
-    .g-input-error {
-      color: #FF562E;
-      font-size: 12px;
-      height: 25px;
-      line-height: 25px;
-    }
+      .g-input-right-verification {
+        color: #41A6F3;
+      }
 
-    .select-list {
-      top: 36px;
-      width: 61px;
-      border: 1px solid #EAEDF2;
-      max-height: 200px;
-      overflow-y: auto;
-      list-style: none;
-      margin: 0;
-      padding: 5px 10px;
-      position: absolute;
-      background: #fff;
+      .g-input-right-verification.loading {
+        color: #A7A7B7;
+      }
     }
   }
+
+  .g-input-wrapper.error {
+    margin-bottom: 0;
+  }
+
+  .g-input-error {
+    color: #FF562E;
+    font-size: 12px;
+    height: 25px;
+    line-height: 25px;
+  }
+
+  .select-list {
+    top: 36px;
+    width: 61px;
+    border: 1px solid #EAEDF2;
+    max-height: 200px;
+    overflow-y: auto;
+    list-style: none;
+    margin: 0;
+    padding: 5px 10px;
+    position: absolute;
+    background: #fff;
+  }
 }
 
 .LOGIN_PHONE .body {

+ 54 - 0
front/project/www/components/Modal/index.js

@@ -0,0 +1,54 @@
+import React, { Component } from 'react';
+import './index.less';
+import { Modal, Icon } from 'antd';
+import Button from '../Button';
+
+export default class extends Component {
+  render() {
+    const {
+      show,
+      className,
+      children,
+      width,
+      title,
+      btnAlign = 'right',
+      cancelText = '取消',
+      confirmText = '确定',
+      onCancel,
+      onConfirm,
+      onClose,
+      close = true,
+      maskClosable = false,
+      body = true,
+    } = this.props;
+    return (
+      <Modal
+        wrapClassName={`g-modal ${className || ''}`}
+        visible={show}
+        closable={false}
+        maskClosable={maskClosable}
+        footer={false}
+        width={width}
+        onCancel={onClose}
+      >
+        <div className="g-modal-wrapper">
+          {close && onClose && <Icon className="close" type="close-circle" theme="filled" onClick={() => onClose()} />}
+          <div className="g-modal-title">{title}</div>
+          {body ? <div className="g-modal-body">{children}</div> : children}
+          <div className={`g-modal-btns ${btnAlign}`}>
+            {onCancel && (
+              <Button size="lager" theme="link" onClick={() => onCancel()}>
+                {cancelText}
+              </Button>
+            )}
+            {onConfirm && (
+              <Button size="lager" radius onClick={() => onConfirm()}>
+                {confirmText}
+              </Button>
+            )}
+          </div>
+        </div>
+      </Modal>
+    );
+  }
+}

+ 187 - 0
front/project/www/components/Modal/index.less

@@ -0,0 +1,187 @@
+.g-modal {
+  .g-modal-wrapper {
+    position: relative;
+
+    .close {
+      font-size: 20px;
+      position: absolute;
+      top: 0;
+      right: 0;
+      cursor: pointer;
+      color: #D0D8E2;
+    }
+
+    .g-modal-title {
+      font-size: 20px;
+      color: #303036;
+      font-weight: 600;
+    }
+
+    .g-modal-body {
+      padding: 30px 0;
+    }
+
+    .g-modal-btns {
+      margin: 0 -15px;
+
+      .button {
+        margin: 0 15px;
+        padding-left: 30px;
+        padding-right: 30px;
+      }
+    }
+
+    .g-modal-btns.right {
+      text-align: right;
+    }
+
+    .g-modal-btns.left {
+      text-align: left;
+    }
+
+    .g-modal-btns.center {
+      text-align: center;
+    }
+  }
+}
+
+.g-modal.bind-phone-modal {
+  .step-0-layout {
+    margin-bottom: 70px;
+    font-size: 18px;
+
+    a {
+      margin-right: 20px;
+      float: right;
+    }
+  }
+
+  .step-1-layout {
+    display: flex;
+
+    .label {
+      font-size: 18px;
+      line-height: 40px;
+      margin-right: 20px;
+    }
+
+    .input-layout {
+      flex: 1;
+    }
+  }
+}
+
+.g-modal.bind-email-modal {
+  .step-0-layout {
+    margin-bottom: 70px;
+    font-size: 18px;
+
+    a {
+      margin-right: 20px;
+      float: right;
+    }
+  }
+
+  .step-1-layout {
+    display: flex;
+
+    .label {
+      font-size: 18px;
+      line-height: 40px;
+      margin-right: 10px;
+    }
+
+    .input-layout {
+      flex: 1;
+    }
+  }
+}
+
+.g-modal.edit-info-modal {
+  .edit-info-modal-block {
+    display: flex;
+
+    .label {
+      font-size: 18px;
+      line-height: 40px;
+      margin-right: 10px;
+    }
+
+    .input-layout {
+      flex: 1;
+    }
+
+    .file-upload {
+      background: #F7F7F7;
+      border: none;
+    }
+  }
+}
+
+.g-modal.real-auth-modal {
+  .real-auth-modal-wrapper {
+    position: relative;
+  }
+
+  .real-auth-text {
+    font-size: 18px;
+
+    .t2 {
+      margin-bottom: 30px;
+    }
+
+    .t3 {
+      margin-bottom: 20px;
+    }
+  }
+
+  .real-auth-qrcode {
+    position: absolute;
+    right: 0;
+    top: 0;
+  }
+}
+
+.g-modal.edit-avatar-modal {
+  .edit-avatar-modal-wrapper {
+    position: relative;
+  }
+
+  .edit-avatar-o {
+    vertical-align: top;
+    display: inline-block;
+    padding-right: 35px;
+    border-right: 1px solid #d8d8d8;
+
+    .assets {
+      width: 360px;
+    }
+  }
+
+  .edit-avatar-r {
+    display: inline-block;
+    padding-left: 30px;
+
+    .text {
+      font-size: 18px;
+      margin-bottom: 20px;
+    }
+
+    .assets {
+      border-radius: 50%;
+      width: 100px;
+    }
+  }
+}
+
+.g-modal.invite-modal {
+  .invite-modal-wrapper {
+    position: relative;
+
+    .tip {
+      font-size: 18px;
+      color: #686872;
+      margin-bottom: 30px;
+    }
+  }
+}

+ 276 - 0
front/project/www/components/OtherModal/index.js

@@ -0,0 +1,276 @@
+import React, { Component } from 'react';
+import './index.less';
+import FileUpload from '@src/components/FileUpload';
+import Assets from '@src/components/Assets';
+import { SelectInput, VerificationInput, Input } from '../Login';
+import { MobileArea } from '../../../Constant';
+import Invite from '../Invite';
+import Modal from '../Modal';
+
+export class BindPhone extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { step: 0, data: {} };
+    this.stepProp = {
+      0: {
+        title: '绑定手机',
+        onConfirm: () => this.onNext(),
+      },
+      1: {
+        title: '绑定手机',
+        onConfirm: props.onConfirm,
+        onCancel: props.onCancel,
+        confirmText: '提交',
+      },
+    };
+  }
+
+  onNext() {
+    this.setState({ step: 1 });
+  }
+
+  render() {
+    const { show } = this.props;
+    const { step } = this.state;
+    return (
+      <Modal className="bind-phone-modal" show={show} width={630} {...this.stepProp[step]}>
+        <div className="bind-phone-modal-wrapper">{this[`renderStep${step}`]()}</div>
+      </Modal>
+    );
+  }
+
+  renderStep0() {
+    return (
+      <div className="step-0-layout">
+        已绑定手机 13123123123123
+        <a onClick={() => this.onNext()}>修改</a>
+      </div>
+    );
+  }
+
+  renderStep1() {
+    return (
+      <div className="step-1-layout">
+        <div className="label">手机号</div>
+        <div className="input-layout">
+          <SelectInput
+            placeholder="请输入手机号"
+            selectValue={this.state.data.area}
+            select={MobileArea}
+            value={this.state.data.mobile}
+            error={this.state.mobileError}
+            onSelect={value => {
+              this.changeData('area', value);
+              this.validMobile(true);
+            }}
+            onChange={e => {
+              this.changeData('mobile', e.target.value);
+              this.validMobile(true);
+            }}
+          />
+          <VerificationInput
+            placeholder="请输入验证码"
+            value={this.state.data.mobileVerifyCode}
+            error={this.state.validError}
+            onSend={() => {
+              return this.sendValid();
+            }}
+            onChange={e => {
+              this.changeData('mobileVerifyCode', e.target.value);
+            }}
+          />
+        </div>
+      </div>
+    );
+  }
+}
+
+export class BindEmail extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { step: 0, data: {} };
+    this.stepProp = {
+      0: {
+        title: '绑定手机',
+        onConfirm: () => this.onNext(),
+      },
+      1: {
+        title: '绑定手机',
+        onConfirm: props.onConfirm,
+        onCancel: props.onCancel,
+        confirmText: '提交',
+      },
+    };
+  }
+
+  onNext() {
+    this.setState({ step: 1 });
+  }
+
+  render() {
+    const { show } = this.props;
+    const { step } = this.state;
+    return (
+      <Modal className="bind-email-modal" show={show} width={630} {...this.stepProp[step]}>
+        <div className="bind-email-modal-wrapper">{this[`renderStep${step}`]()}</div>
+      </Modal>
+    );
+  }
+
+  renderStep0() {
+    return (
+      <div className="step-0-layout">
+        已绑定邮箱 13123123123123
+        <a onClick={() => this.onNext()}>修改</a>
+      </div>
+    );
+  }
+
+  renderStep1() {
+    return (
+      <div className="step-1-layout">
+        <div className="label">邮箱地址</div>
+        <div className="input-layout">
+          <Input
+            placeholder="请输入邮箱"
+            value={this.state.data.mobile}
+            error={this.state.mobileError}
+            onChange={e => {
+              this.changeData('mobile', e.target.value);
+              this.validMobile(true);
+            }}
+          />
+        </div>
+      </div>
+    );
+  }
+}
+
+export class EditInfo extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { data: {} };
+  }
+
+  render() {
+    const { show, onCancel, onConfirm } = this.props;
+    return (
+      <Modal
+        className="edit-info-modal"
+        show={show}
+        width={630}
+        title="修改资料"
+        confirmText="保存"
+        onCancel={onCancel}
+        onConfirm={onConfirm}
+      >
+        <div className="edit-info-modal-wrapper">
+          <div className="edit-info-modal-block">
+            <div className="label">昵称</div>
+            <div className="input-layout">
+              <Input
+                placeholder="2-20位,不可使用特殊字符。"
+                value={this.state.data.mobile}
+                error={this.state.mobileError}
+                onChange={e => {
+                  this.changeData('mobile', e.target.value);
+                  this.validMobile(true);
+                }}
+              />
+            </div>
+          </div>
+          <div className="edit-info-modal-block">
+            <div className="label">头像</div>
+            <div className="input-layout">
+              <FileUpload />
+            </div>
+          </div>
+        </div>
+      </Modal>
+    );
+  }
+}
+
+export class RealAuth extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { data: {} };
+  }
+
+  render() {
+    const { show, onConfirm } = this.props;
+    return (
+      <Modal
+        className="real-auth-modal"
+        show={show}
+        width={630}
+        title="实名认证"
+        confirmText="好的,知道了"
+        btnAlign="center"
+        onConfirm={onConfirm}
+      >
+        <div className="real-auth-modal-wrapper">
+          <div className="real-auth-text">
+            <div className="t1">完成实名认证即可领取:</div>
+            <div className="t2">6个月VIP权限 和 5折机经优惠劵。</div>
+            <div className="t3">扫码关注公众号,点击“我的-实名认证”</div>
+          </div>
+          <div className="real-auth-qrcode">
+            <Assets name="qrcode" />
+          </div>
+        </div>
+      </Modal>
+    );
+  }
+}
+
+export class EditAvatar extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { data: {} };
+  }
+
+  render() {
+    const { show, onConfirm, onCancel } = this.props;
+    return (
+      <Modal
+        className="edit-avatar-modal"
+        show={show}
+        width={630}
+        title="调整头像"
+        confirmText="保存头像"
+        onConfirm={onConfirm}
+        onCancel={onCancel}
+      >
+        <div className="edit-avatar-modal-wrapper">
+          <div className="edit-avatar-o">
+            <Assets name="qrcode" />
+          </div>
+          <div className="edit-avatar-r">
+            <div className="text">头像预览</div>
+            <Assets name="qrcode" />
+          </div>
+        </div>
+      </Modal>
+    );
+  }
+}
+
+export class InviteModal extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { data: {} };
+  }
+
+  render() {
+    const { show, onClose } = this.props;
+    return (
+      <Modal className="invite-modal" show={show} width={630} title="邀请好友" onClose={onClose}>
+        <div className="invite-modal-wrapper">
+          <div className="tip">每邀请一位小伙伴加入“千行GMAT”, 您的VIP权限会延长7天,可累加 无上限!赶紧行动吧~</div>
+          <Invite />
+        </div>
+      </Modal>
+    );
+  }
+}

+ 140 - 0
front/project/www/components/OtherModal/index.less

@@ -0,0 +1,140 @@
+.g-modal.bind-phone-modal {
+  .step-0-layout {
+    margin-bottom: 70px;
+    font-size: 18px;
+
+    a {
+      margin-right: 20px;
+      float: right;
+    }
+  }
+
+  .step-1-layout {
+    display: flex;
+
+    .label {
+      font-size: 18px;
+      line-height: 40px;
+      margin-right: 20px;
+    }
+
+    .input-layout {
+      flex: 1;
+    }
+  }
+}
+
+.g-modal.bind-email-modal {
+  .step-0-layout {
+    margin-bottom: 70px;
+    font-size: 18px;
+
+    a {
+      margin-right: 20px;
+      float: right;
+    }
+  }
+
+  .step-1-layout {
+    display: flex;
+
+    .label {
+      font-size: 18px;
+      line-height: 40px;
+      margin-right: 10px;
+    }
+
+    .input-layout {
+      flex: 1;
+    }
+  }
+}
+
+.g-modal.edit-info-modal {
+  .edit-info-modal-block {
+    display: flex;
+
+    .label {
+      font-size: 18px;
+      line-height: 40px;
+      margin-right: 10px;
+    }
+
+    .input-layout {
+      flex: 1;
+    }
+
+    .file-upload {
+      background: #F7F7F7;
+      border: none;
+    }
+  }
+}
+
+.g-modal.real-auth-modal {
+  .real-auth-modal-wrapper {
+    position: relative;
+  }
+
+  .real-auth-text {
+    font-size: 18px;
+
+    .t2 {
+      margin-bottom: 30px;
+    }
+
+    .t3 {
+      margin-bottom: 20px;
+    }
+  }
+
+  .real-auth-qrcode {
+    position: absolute;
+    right: 0;
+    top: 0;
+  }
+}
+
+.g-modal.edit-avatar-modal {
+  .edit-avatar-modal-wrapper {
+    position: relative;
+  }
+
+  .edit-avatar-o {
+    vertical-align: top;
+    display: inline-block;
+    padding-right: 35px;
+    border-right: 1px solid #d8d8d8;
+
+    .assets {
+      width: 360px;
+    }
+  }
+
+  .edit-avatar-r {
+    display: inline-block;
+    padding-left: 30px;
+
+    .text {
+      font-size: 18px;
+      margin-bottom: 20px;
+    }
+
+    .assets {
+      border-radius: 50%;
+      width: 100px;
+    }
+  }
+}
+
+.g-modal.invite-modal {
+  .invite-modal-wrapper {
+    position: relative;
+
+    .tip {
+      font-size: 18px;
+      color: #686872;
+      margin-bottom: 30px;
+    }
+  }
+}

+ 83 - 0
front/project/www/components/Radio/index.js

@@ -0,0 +1,83 @@
+import React, { Component } from 'react';
+import './index.less';
+import { Icon } from 'antd';
+
+export class SpecialRadio extends Component {
+  render() {
+    const { checked, children, onClick, theme = '', width, space } = this.props;
+    return (
+      <div
+        className={`g-special-radio-wrapper ${theme} ${checked ? 'checked' : ''}`}
+        style={{ width: width || '', marginLeft: space || '', marginRight: space || '' }}
+      >
+        <div className="g-special-radio" onClick={() => onClick && onClick()}>
+          {children}
+          <Icon type="check" />
+        </div>
+      </div>
+    );
+  }
+}
+
+export class SpecialRadioGroup extends Component {
+  onClickItem(value) {
+    const { onChange } = this.props;
+    if (onChange) onChange(value);
+  }
+
+  render() {
+    const { list = [], value, values = [], theme, space, width } = this.props;
+    return (
+      <div className="g-special-radio-group" style={{ marginLeft: space * -1 || '', marginRight: space * -1 || '' }}>
+        {list.map(item => {
+          return (
+            <SpecialRadio
+              theme={theme}
+              checked={value === item.value || values.indexOf(item.value) >= 0}
+              onClick={() => this.onClickItem(item.value)}
+              width={width}
+              space={space}
+            >
+              {item.label}
+            </SpecialRadio>
+          );
+        })}
+      </div>
+    );
+  }
+}
+
+export default class Radio extends Component {
+  render() {
+    const { checked, children, onClick } = this.props;
+    return (
+      <div className={`g-radio-wrapper ${checked ? 'checked' : ''}`}>
+        <div className="g-radio" onClick={() => onClick && onClick()}>
+          {children}
+        </div>
+      </div>
+    );
+  }
+}
+
+export class RadioGroup extends Component {
+  onClickItem(key) {
+    const { onChange } = this.props;
+    if (onChange) onChange(key);
+  }
+
+  render() {
+    const { list = [], value } = this.props;
+    return (
+      <div className="g-radio-group">
+        {list.map(item => {
+          return (
+            <Radio checked={value === item.key} onClick={() => this.onClickItem(item.key)}>
+              {item.label}
+            </Radio>
+          );
+        })}
+      </div>
+    );
+  }
+}

+ 119 - 0
front/project/www/components/Radio/index.less

@@ -0,0 +1,119 @@
+@import '../../app.less';
+
+.g-special-radio-wrapper {
+  float: left;
+  min-width: 60px;
+  overflow: hidden;
+
+  .g-special-radio {
+    display: block;
+    text-align: center;
+    line-height: 20px;
+    padding: 14px 10px;
+    background: #ECEDEE;
+    color: #686872;
+    border-radius: 6px;
+    font-size: 18px;
+    position: relative;
+    overflow: hidden;
+    cursor: pointer;
+
+    i {
+      display: none;
+      position: absolute;
+      bottom: 0;
+      right: 0;
+      color: #fff;
+      z-index: 1;
+      font-size: 15px;
+      transform: scale(0.8);
+    }
+  }
+}
+
+.g-special-radio-wrapper.checked {
+
+  .g-special-radio {
+    background: #E7F1FD;
+    color: #4299FF;
+
+    i {
+      display: block;
+    }
+  }
+
+  .g-special-radio::after {
+    content: '';
+    display: inline-block;
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    border: 13px solid;
+    border-color: transparent #4299FF #4299FF transparent;
+  }
+}
+
+.g-special-radio-wrapper.theme {
+
+  .g-special-radio {
+    border: 1px solid #ECEDEE;
+  }
+}
+
+.g-special-radio-wrapper.checked.theme {
+
+  .g-special-radio {
+    background: #40A8E2;
+    color: #fff;
+    border: 1px solid #40A8E2;
+
+    i {
+      color: #40A8E2;
+    }
+  }
+
+  .g-special-radio::after {
+    border-color: transparent #fff #fff transparent;
+  }
+}
+
+.g-special-radio-group {
+  overflow: hidden;
+  margin: 0 -7.5px;
+
+  .g-special-radio-wrapper {
+    margin: 0 7.5px;
+    margin-bottom: 30px;
+  }
+}
+
+.g-radio-wrapper {
+  display: inline-block;
+
+  .g-radio {
+    display: inline-block;
+    text-align: center;
+    line-height: 28px;
+    background: #ECEDEE;
+    color: #686872;
+    padding: 0 18px;
+    border-radius: 14px;
+    font-size: 12px;
+  }
+}
+
+.g-radio-wrapper.checked {
+
+  .g-radio {
+    background: #E7F5FD;
+    color: #40A8E2;
+  }
+}
+
+.g-radio-group {
+  margin: 0 -7.5px;
+
+  .g-radio-wrapper {
+    margin: 0 7.5px;
+  }
+}

+ 53 - 0
front/project/www/components/Ratio/index.js

@@ -0,0 +1,53 @@
+import React from 'react';
+import './index.less';
+import PieChart from '@src/components/PieChart';
+
+function makePie(text, subtext, color, data) {
+  return {
+    title: {
+      text,
+      textAlign: 'center',
+      textVerticalAlign: 'middle',
+      textStyle: { fontSize: 14, color: '#686872' },
+      subtext,
+      subtextStyle: { fontSize: 16, color: '#303036' },
+      top: '28%',
+      left: '45%',
+    },
+    color,
+    series: [
+      {
+        type: 'pie',
+        radius: ['85%', '100%'],
+        hoverAnimation: false,
+        animation: false,
+        label: {
+          show: false,
+        },
+        data,
+      },
+    ],
+  };
+}
+export default function Ratio(props) {
+  const { text, subtext, values } = props;
+  return (
+    <div className="ratio-pie">
+      <PieChart
+        width={110}
+        height={110}
+        option={makePie(text, subtext, values.map(item => item.color), values.map(item => item.value))}
+      />
+      <div className="label-list">
+        {values.map(item => {
+          return (
+            <div className="item">
+              <i style={{ backgroundColor: item.color }} />
+              {item.label}
+            </div>
+          );
+        })}
+      </div>
+    </div>
+  );
+}

+ 32 - 0
front/project/www/components/Ratio/index.less

@@ -0,0 +1,32 @@
+@import '../../app.less';
+
+.ratio-pie {
+  .pie-chart {
+    display: inline-block;
+    margin-right: 45px;
+  }
+
+  .label-list {
+    width: 250px;
+    padding-top: 20px;
+    vertical-align: top;
+    display: inline-block;
+
+    .item {
+      display: inline-block;
+      width: 125px;
+      position: relative;
+      color: #686872;
+      margin-bottom: 5px;
+
+      i {
+        position: absolute;
+        left: -20px;
+        top: 5px;
+        width: 10px;
+        height: 10px;
+        border-radius: 50%;
+      }
+    }
+  }
+}

+ 16 - 19
front/project/www/components/Select/index.less

@@ -20,6 +20,7 @@
 .select {
   position: relative;
   text-align: center;
+  display: inline-block;
 
   .mask {
     position: fixed;
@@ -128,20 +129,12 @@
     }
 
     .button {
-      width: 98px;
-      padding: 0px;
+      padding: 0 20px;
       border-radius: 0;
       background: none;
       color: #686872;
     }
 
-    .up-arrow {
-      display: none;
-    }
-
-    .down-arrow {
-      display: none;
-    }
 
     color: #686872;
 
@@ -157,17 +150,21 @@
   }
 
   .up-arrow::after {
-    display: inline-block;
-    content: " ";
-    height: 8px;
-    width: 8px;
-    border-width: 2px 2px 0 0;
-    border-color: #fff;
+    width: auto;
+    height: auto;
+    border-width: 5px;
+    border-color: transparent transparent #8897a8 transparent;
     border-style: solid;
-    transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
-    position: absolute;
-    top: 50%;
-    margin-top: -1px;
+    transform: translateY(-4px);
+  }
+
+  .down-arrow::after {
+    width: auto;
+    height: auto;
+    border-width: 5px;
+    border-color: #8897a8 transparent transparent transparent;
+    border-style: solid;
+    transform: translateY(2px);
   }
 
   &.default {

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

@@ -3,14 +3,14 @@ import { Link } from 'react-router-dom';
 import './index.less';
 
 function getItem(props, item, onChange) {
-  const { width, space, active } = props;
+  const { width, space, active, render } = props;
   return (
     <div
       onClick={() => active !== item.key && onChange && onChange(item.key)}
       style={{ width: width || '', marginLeft: space || '', marginRight: space || '' }}
       className={`tab ${active === item.key ? 'active' : ''}`}
     >
-      {item.name || item.title}
+      {render ? render(item) : item.name || item.title}
     </div>
   );
 }

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

@@ -3,6 +3,7 @@
 .tabs {
   height: 44px;
   line-height: 44px;
+  overflow: hidden;
 }
 
 .tabs.small {

+ 49 - 0
front/project/www/components/TotalSort/index.js

@@ -0,0 +1,49 @@
+import React, { Component } from 'react';
+import './index.less';
+import { Icon } from 'antd';
+
+export default class extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {};
+  }
+
+  render() {
+    return (
+      <div className="total-sort">
+        <div className="total-sort-num">
+          <Icon className="plus" type="plus" />
+          <input />
+          <Icon className="minus" type="minus" />
+        </div>
+        <div className="total-sort-desc">
+          预计全球排名<b>94th</b>
+        </div>
+        <div className="total-sort-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>
+    );
+  }
+}

+ 113 - 0
front/project/www/components/TotalSort/index.less

@@ -0,0 +1,113 @@
+.total-sort {
+
+  .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;
+    }
+  }
+
+  .total-sort-desc {
+    text-align: center;
+    font-size: 16px;
+    color: #686872;
+    line-height: 16px;
+    margin-bottom: 30px;
+
+    b {
+      margin: 0 5px;
+      color: #41A6F3;
+      font-weight: 500;
+    }
+  }
+
+  .total-sort-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;
+        }
+      }
+    }
+  }
+}

+ 114 - 0
front/project/www/components/VipRenew/index.js

@@ -0,0 +1,114 @@
+import React, { Component } from 'react';
+import './index.less';
+import Assets from '@src/components/Assets';
+import Modal from '../Modal';
+import Tabs from '../Tabs';
+import { SpecialRadioGroup } from '../Radio';
+import Invite from '../Invite';
+import Button from '../Button';
+
+export default class extends Component {
+  constructor(props) {
+    super(props);
+    this.state = { tab: '2', pay: 'alipay', select: '1', auth: true };
+  }
+
+  render() {
+    const { show, onClose } = this.props;
+    const { tab } = this.state;
+    return (
+      <Modal className="vip-renew-modal" show={show} width={630} title="VIP续期" onClose={onClose}>
+        <div className="vip-renew-wrapper">
+          <Tabs
+            border
+            size="small"
+            active={tab}
+            width={80}
+            tabs={[{ key: '1', title: '购买' }, { key: '2', title: '免费领取' }]}
+            onChange={key => this.setState({ tab: key })}
+          />
+          {this[`renderTab${tab}`]()}
+        </div>
+      </Modal>
+    );
+  }
+
+  renderTab1() {
+    const { pay, select } = this.state;
+    return (
+      <div className="tab-1-layout">
+        <div className="select-layout">
+          <SpecialRadioGroup
+            list={[
+              { label: '1个月: ¥ 9999', value: '1' },
+              { label: '3个月: ¥ 9999', value: '2' },
+              { label: '3个月: ¥ 9999', value: '3' },
+            ]}
+            value={select}
+            width={150}
+            space={10}
+            onChange={key => this.setState({ select: key })}
+          />
+        </div>
+        <div className="pay-layout">
+          <Tabs
+            border
+            size="small"
+            active={pay}
+            width={80}
+            tabs={[{ key: 'alipay', title: '支付宝' }, { key: 'wechatpay', title: '微信' }]}
+            render={item => <Assets name={item.key} />}
+            onChange={key => this.setState({ pay: key })}
+          />
+          <div className="qrcode">
+            <Assets name="qrcode" />
+          </div>
+          <div className="t">请使用手机微信或支付宝扫码付款</div>
+          <div className="t">支付金额: ¥ 8888.88</div>
+        </div>
+      </div>
+    );
+  }
+
+  renderTab2() {
+    const { auth } = this.state;
+    return (
+      <div className="tab-2-layout">
+        <div className="list">
+          <div className="item">
+            {auth && <span className="over">已完成</span>}
+            <Assets className="icon" name="realname2" />
+            <div className="t">
+              <Assets name="gift" />
+              6个月
+            </div>
+            <Button size="small" radius disabled={auth}>
+              实名认证
+            </Button>
+          </div>
+          <div className="item">
+            <Assets className="icon" name="invite" />
+            <div className="t">
+              <Assets name="gift" />
+              7天/每位好友
+            </div>
+            <Button size="small" radius>
+              邀请好友
+            </Button>
+          </div>
+          <div className="item">
+            <Assets className="icon" name="information2" />
+            <div className="t">
+              <Assets name="gift" />
+              1个月
+            </div>
+            <Button size="small" radius>
+              完善信息
+            </Button>
+          </div>
+        </div>
+        {auth && <Invite />}
+      </div>
+    );
+  }
+}

+ 94 - 0
front/project/www/components/VipRenew/index.less

@@ -0,0 +1,94 @@
+.g-modal.vip-renew-modal {
+  .vip-renew-wrapper {
+    .tabs {
+      width: 160px;
+    }
+
+    .tab-1-layout {
+      display: flex;
+
+      .select-layout {
+        flex: 1;
+        padding-top: 30px;
+
+        .g-special-radio-wrapper {
+          margin-bottom: 15px;
+        }
+      }
+
+      .pay-layout {
+        width: 200px;
+        padding-left: 10px;
+
+        .tabs {
+          width: 160px;
+          margin-bottom: 15px;
+
+          .assets {
+            width: 80px;
+          }
+        }
+
+        .qrcode {
+          padding-left: 10px;
+          margin-bottom: 15px;
+
+          .assets {
+            width: 140px;
+          }
+        }
+
+        .t {
+          margin-left: -30px;
+          text-align: center;
+          font-size: 12px;
+          color: #8897A8;
+        }
+      }
+    }
+
+    .tab-2-layout {
+      padding-top: 20px;
+
+      .list {
+        border-bottom: 1px solid #ECEDEE;
+        margin-bottom: 20px;
+        padding-bottom: 30px;
+
+        .item {
+          width: 33.33%;
+          display: inline-block;
+          text-align: center;
+          position: relative;
+
+          .over {
+            position: absolute;
+            right: 55px;
+            top: 60px;
+            width: 44px;
+            height: 20px;
+            text-align: center;
+            background: rgba(102, 201, 76, 1);
+            border-radius: 10px;
+            color: #fff;
+            display: inline-block;
+            font-size: 10px;
+          }
+
+          .icon {
+            margin-bottom: 10px;
+          }
+
+          .t {
+            margin-bottom: 10px;
+
+            .assets {
+              margin-right: 5px;
+            }
+          }
+        }
+      }
+
+    }
+  }
+}

+ 15 - 0
front/project/www/routes/my/course/index.less

@@ -379,4 +379,19 @@
       }
     }
   }
+}
+
+.g-modal.clock-modal {
+  .tag {
+    width: 80px;
+    display: inline-block;
+
+    i {
+      width: 10px;
+      height: 10px;
+      display: inline-block;
+      border-radius: 50%;
+      margin-right: 10px;
+    }
+  }
 }

+ 57 - 0
front/project/www/routes/my/course/page.js

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
 import { Link } from 'react-router-dom';
 import './index.less';
 import { Icon } from 'antd';
+import FileUpload from '@src/components/FileUpload';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
 import UserLayout from '../../../layouts/User';
@@ -12,6 +13,8 @@ import menu from '../index';
 import Tabs from '../../../components/Tabs';
 import UserTable from '../../../components/UserTable';
 import More from '../../../components/More';
+import Modal from '../../../components/Modal';
+import Date from '../../../components/Date';
 
 export default class extends Page {
   initState() {
@@ -96,6 +99,60 @@ export default class extends Page {
           onChange={key => this.refresh(tab1, key)}
         />
         {this[`renderTab${tab1}`]()}
+        <Modal className="clock-modal" title="打卡表" width={460} onClose={() => {}}>
+          <div>
+            <div className="d-i-b w-3">
+              <div className="t-2 t-s-14">听课频率</div>
+              <div className="t-4 t-s-18">2天/课时</div>
+            </div>
+            <div className="d-i-b w-3">
+              <div className="t-2 t-s-14">作业完成度</div>
+              <div className="t-4 t-s-18">50%</div>
+            </div>
+          </div>
+          <div className="b-b m-t-1 m-b-1" />
+          <div className="m-b-1">
+            <div className="tag d-i-b t-s-16">
+              <i style={{ background: '#6865FD' }} />
+              听课
+            </div>
+            <div className="tag d-i-b t-s-16">
+              <i style={{ background: '#4299FF' }} />
+              预习
+            </div>
+            <div className="tag d-i-b t-s-16">
+              <i style={{ background: '#E7E7E7' }} />
+              停课
+            </div>
+          </div>
+          <Date
+            hideInput
+            disabledDate={() => false}
+            dateRender={current => (
+              <div className="ant-calendar-date">
+                {current.get('date')}
+                <i className="s1" style={{ background: '#6865FD' }} />
+                <i className="s2" style={{ background: '#4299FF' }} />
+              </div>
+            )}
+          />
+          <div className="t-2 t-s-12">*听课频率≤2天/课时,作业完成度≥90%,课程有效期可延长7-10天。</div>
+        </Modal>
+        <Modal title="恢复学习" onConfirm={() => {}} onCancel={() => {}}>
+          <div className="t-2 t-s-18">恢复学习后,本课程的停课功能将无法使用。是否恢复学习?</div>
+        </Modal>
+        <Modal title="上传笔记" width={630} confirmText="提交" onConfirm={() => {}} onCancel={() => {}}>
+          <textarea
+            className="b-c-1 w-10 p-10 m-b-1"
+            rows={6}
+            placeholder="请输入留言内容,也可将文件拖动到这里上传。"
+          />
+          <div className="t-2 t-s-16 m-b-1">
+            上传文件 <FileUpload type="file" title="选择文件" />
+            <span className="t-3 t-s-14">支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件</span>
+          </div>
+          <div className="b-b m-t-2" />
+        </Modal>
       </div>
     );
   }

+ 111 - 1
front/project/www/routes/my/error/page.js

@@ -1,12 +1,14 @@
 import React from 'react';
 import './index.less';
-import { Icon } from 'antd';
+import { Icon, Checkbox } from 'antd';
 import Page from '@src/containers/Page';
 import UserLayout from '../../../layouts/User';
 import UserTable from '../../../components/UserTable';
 import UserAction from '../../../components/UserAction';
 import menu from '../index';
 import Tabs from '../../../components/Tabs';
+import Modal from '../../../components/Modal';
+import Select from '../../../components/Select';
 
 const columns = [
   { key: '', title: '题型', fixSort: true },
@@ -18,6 +20,15 @@ const columns = [
   { key: '', title: '' },
 ];
 
+const exportType = [
+  { key: '1', title: '题目' },
+  { key: '2', title: '官方解析' },
+  { key: '3', title: '我的笔记' },
+  { key: '4', title: '千行解析', auth: true },
+  { key: '5', title: '题源联想', auth: true },
+  { key: '6', title: '相关问答', auth: true },
+];
+
 export default class extends Page {
   initState() {
     return {
@@ -134,7 +145,106 @@ export default class extends Page {
           onSort={v => this.onSort(v)}
           onChange={p => this.onDataChange(p)}
         />
+        {this.renderModal()}
       </div>
     );
   }
+
+  renderModal() {
+    return [
+      <Modal title="操作提示" confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
+        <div className="flex-layout m-b-2">
+          <div className="flex-block">
+            <div className="t-1 t-s-18">组卷功能</div>
+            <div className="t-2">操作数量:10-50;</div>
+            <div className="t-2">注意事项:可跨题型、不可跨学科。</div>
+          </div>
+          <div className="flex-block">
+            <div className="t-1 t-s-18">导出功能</div>
+            <div className="t-2">操作数量:1-100;</div>
+            <div className="t-2">注意事项:“综合推理IR”暂时无法导出。</div>
+          </div>
+        </div>
+
+        <div className="t-3">
+          *您可点击
+          <Icon type="question-circle" theme="filled" />
+          查阅以上信息。
+        </div>
+      </Modal>,
+      <Modal title="组卷练习" confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
+        <div className="t-2 t-s-18">不可同时选中语文题和数学题,请重新选择。</div>
+      </Modal>,
+      <Modal title="组卷练习" confirmText="开始练习" onConfirm={() => {}} onCancel={() => {}}>
+        <div className="t-2 t-s-18">
+          您共选择了 <span className="t-4">50</span> 道题目,是否开始练习?
+        </div>
+        <div className="t-2 t-s-16">
+          <Checkbox checked className="m-r-5" />
+          剔除已组卷练习 <Select
+            theme="white"
+            value="1"
+            list={[{ key: '1', title: '2' }, { key: '2', title: '3' }]}
+          />{' '}
+          次以上的错题
+        </div>
+      </Modal>,
+      <Modal title="移出" onConfirm={() => {}} onCancel={() => {}}>
+        <div className="t-2 t-s-18">
+          当前选中的 <span className="t-4">50</span> 道题即将被移出错题本,移出后无法恢复,是否继续?
+        </div>
+      </Modal>,
+      <Modal title="导出" confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
+        <div className="t-2 t-s-18">正在下载中,请不要关闭当前页面!</div>
+      </Modal>,
+      <Modal title="导出" confirmText="导出" onConfirm={() => {}} onCancel={() => {}}>
+        <div className="t-2 t-s-18 m-b-5">
+          当前共选中 <span className="t-4">50</span> 道题,请确认需要导出的内容:
+        </div>
+        <div className="t-2 t-s-16">
+          {exportType.map(item => {
+            return (
+              <div className="d-i-b m-b-5" style={{ width: 135 }}>
+                <Checkbox checked className="m-r-5" />
+                {item.title}
+              </div>
+            );
+          })}
+        </div>
+      </Modal>,
+      <Modal title="导出" confirmText="导出" onConfirm={() => {}} onCancel={() => {}}>
+        <div className="t-2 t-s-18 m-b-5">
+          当前共选中 <span className="t-4">50</span> 道题,请确认需要导出的内容:
+        </div>
+        <div className="t-2 t-s-16 m-b-2">
+          {exportType
+            .filter(item => !item.auth)
+            .map(item => {
+              return (
+                <div className="d-i-b m-b-2" style={{ width: 135 }}>
+                  <Checkbox checked className="m-r-5" />
+                  {item.title}
+                </div>
+              );
+            })}
+        </div>
+        <div className="b-b m-b-2 m-t-2" />
+        <div className="t-3 m-b-1">
+          以下内容需实名认证后才可导出: <a className="f-r">去认证 ></a>
+        </div>
+        <div className="t-2 t-s-16 m-b-2">
+          {exportType
+            .filter(item => item.auth)
+            .map(item => {
+              return (
+                <div className="d-i-b" style={{ width: 135 }}>
+                  <Checkbox disabled className="m-r-5" />
+                  {item.title}
+                </div>
+              );
+            })}
+        </div>
+      </Modal>,
+    ];
+  }
 }

+ 31 - 1
front/project/www/routes/my/main/page.js

@@ -7,8 +7,11 @@ 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';
+import Examination from '../../../components/Examination';
+import menu from '../index';
+import { BindPhone, BindEmail, EditInfo, RealAuth, EditAvatar, InviteModal } from '../../../components/OtherModal';
+import VipRenew from '../../../components/VipRenew';
 
 class LogItem extends Component {
   constructor(props) {
@@ -52,6 +55,7 @@ class LogItem extends Component {
 export default class extends Page {
   initState() {
     return {
+      showExamination: false,
       timeList: [
         { title: '长难句', time: '3h60min', ratio: 10, color: '#3C39CC' },
         { title: '综合推理IR', time: '3h60min', ratio: 10, color: '#9E9CFF' },
@@ -202,6 +206,7 @@ export default class extends Page {
   }
 
   renderInfo() {
+    const { showExamination } = this.state;
     return (
       <div className="info-layout">
         <div className="body">
@@ -225,6 +230,31 @@ export default class extends Page {
           <span className="date">2019-10-15到期</span>
           <Link to="">续费</Link>
         </div>
+        <Examination
+          show={showExamination}
+          onConfirm={() => this.setState({ showExamination: false })}
+          onCancel={() => this.setState({ showExamination: false })}
+          onClose={() => this.setState({ showExamination: false })}
+        />
+        <BindPhone
+          onConfirm={() => this.setState({ showPhone: false })}
+          onCancel={() => this.setState({ showPhone: false })}
+        />
+        <BindEmail
+          onConfirm={() => this.setState({ showEmail: false })}
+          onCancel={() => this.setState({ showEmail: false })}
+        />
+        <EditInfo
+          onConfirm={() => this.setState({ showEdit: false })}
+          onCancel={() => this.setState({ showEdit: false })}
+        />
+        <RealAuth onConfirm={() => this.setState({ showReal: false })} />
+        <EditAvatar
+          onConfirm={() => this.setState({ showAvatar: false })}
+          onCancel={() => this.setState({ showAvatar: false })}
+        />
+        <InviteModal onClose={() => this.setState({ showInvite: false })} />
+        <VipRenew onClose={() => this.setState({ showVip: false })} />
       </div>
     );
   }

+ 0 - 111
front/project/www/routes/my/tools/index.less

@@ -250,116 +250,5 @@
   .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;
-          }
-        }
-      }
-    }
   }
 }

+ 51 - 32
front/project/www/routes/my/tools/page.js

@@ -10,6 +10,17 @@ import Tabs from '../../../components/Tabs';
 import More from '../../../components/More';
 import Button from '../../../components/Button';
 import Switch from '../../../components/Switch';
+import TotalSort from '../../../components/TotalSort';
+import Modal from '../../../components/Modal';
+import UserTable from '../../../components/UserTable';
+
+const updateColumns = [
+  { title: '更新时间', key: '1', width: 120 },
+  { title: '位置', key: '2', width: 120 },
+  { title: '原内容', key: '3', width: 120 },
+  { title: '更改为', key: '4', width: 120 },
+  { title: '更新至', key: '5', width: 90 },
+];
 
 export default class extends Page {
   initState() {
@@ -64,6 +75,45 @@ export default class extends Page {
           onChange={key => this.onTabChange(key)}
         />
         {this[`renderTab${tab}`]()}
+        <Modal maskClosable close={false} body={false} width={630} onClose={() => {}}>
+          <UserTable
+            size="small"
+            columns={updateColumns}
+            data={[
+              {
+                1: '2019-07-12 11:38:51',
+                2: 'P30 第 20 行',
+                3: 'the number of wolf population',
+                4: '删除',
+                5: '版本3',
+              },
+              {
+                1: '2019-07-12 11:38:51',
+                2: 'P30 第 20 行',
+                3: 'the number of wolf population',
+                4: '删除',
+                5: '版本3',
+              },
+              {
+                1: '2019-07-12 11:38:51',
+                2: 'P30 第 20 行',
+                3: 'the number of wolf population',
+                4: '删除',
+                5: '版本3',
+              },
+            ]}
+          />
+        </Modal>
+        <Modal title="评价" onConfirm={() => {}} onCancel={() => {}}>
+          <textarea className="b-c-1 w-10 p-10" rows={6} placeholder="您的看法对我们来说很重要!" />
+          <div className="b-b m-t-2" />
+        </Modal>
+        <Modal title="提交成功" confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
+          <div className="t-2 t-s-18">
+            <Icon type="check" className="t-5 m-r-5" />
+            您的每一次反馈都是千行进步的动力。
+          </div>
+        </Modal>
       </div>
     );
   }
@@ -232,38 +282,7 @@ export default class extends Page {
   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>
+        <TotalSort />
       </div>
     );
   }