Browse Source

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

Go 5 years ago
parent
commit
bc134ad7eb

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

@@ -274,6 +274,10 @@
   color: #A7A7A7 !important;
 }
 
+.t-7 {
+  color: #FF7C06 !important;
+}
+
 .b-c-1 {
   background: #F7F7F7;
 }
@@ -393,6 +397,35 @@ body,
       flex: 1;
     }
   }
+
+  .dot {
+    position: relative;
+  }
+
+  .dot::after {
+    content: '';
+    position: absolute;
+    left: -10px;
+    top: 10px;
+    width: 5px;
+    height: 5px;
+    border-radius: 50%;
+    background: rgba(252, 95, 95, 1);
+  }
+
+  .require {
+    position: relative;
+    margin-right: 10px;
+  }
+
+  .require::after {
+    content: '*';
+    position: absolute;
+    font-size: 14px;
+    right: -8px;
+    top: 0px;
+    color: #EC6413FF;
+  }
 }
 
 #root {}

BIN
front/project/www/assets/up - 副本.png


+ 36 - 23
front/project/www/components/UserTable/index.js

@@ -78,30 +78,16 @@ export default class UserTable extends Component {
           )}
 
           <tbody style={{ maxHeight }}>
-            {data.map(row => {
+            {data.map((row, i) => {
               const checked = selectList.indexOf(row[rowKey]) >= 0;
-              return (
-                <tr>
-                  {columns.map((item, i) => {
-                    return (
-                      <td
-                        className={`${item.className || ''} ${i === 0 && select ? 'check' : ''}`}
-                        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>
-              );
+              const isEven = i % 2;
+              if (row.children) {
+                return [
+                  this.renderTr(row, checked, i, -1, isEven),
+                  ...row.children.map((item, index) => this.renderTr(item, checked, i, index, isEven)),
+                ];
+              }
+              return this.renderTr(row, checked, i, -1, isEven);
             })}
           </tbody>
         </table>
@@ -110,4 +96,31 @@ export default class UserTable extends Component {
       </div>
     );
   }
+
+  renderTr(row, checked, rowIndex, childIndex, isEven) {
+    const { columns } = this.props;
+    return (
+      <tr hidden={row.disabled} className={`${isEven ? 'even' : 'odd'}`}>
+        {columns.map((item, i) => {
+          return this.renderTd(row, checked, item, rowIndex, childIndex, i);
+        })}
+      </tr>
+    );
+  }
+
+  renderTd(row, checked, item, index, childIndex, columnIndex) {
+    const { select, rowKey } = this.props;
+    return (
+      <td
+        className={`${item.className || ''} ${columnIndex === 0 && select ? 'check' : ''}`}
+        width={item.width}
+        align={item.align}
+      >
+        {childIndex === -1 && columnIndex === 0 && select && (
+          <CheckboxItem theme="white" checked={checked} onClick={value => this.onSelect(value, row[rowKey])} />
+        )}
+        {item.render ? item.render(row[item.key], row, index, childIndex) : row[item.key]}
+      </td>
+    );
+  }
 }

+ 18 - 4
front/project/www/components/UserTable/index.less

@@ -6,6 +6,11 @@
     margin-bottom: 10px;
     line-height: 20px;
 
+    tbody {
+      overflow-y: auto;
+      background: #fff;
+    }
+
     .check {
       padding-left: 15px;
       text-align: left;
@@ -82,7 +87,6 @@
 
 .user-table.even {
   tbody {
-    overflow-y: auto;
 
     tr:nth-of-type(even) {
       background: #FBFBFB;
@@ -92,7 +96,6 @@
 
 .user-table.odd {
   tbody {
-    overflow-y: auto;
 
     tr:nth-of-type(odd) {
       background: #FBFBFB;
@@ -101,8 +104,19 @@
 }
 
 .user-table.dark {
-  tr {
-    background: #ECEDEE;
+  tbody {
+
+    tr {
+      background: #ECEDEE;
+    }
+  }
+}
+
+.user-table.children {
+  tbody {
+    tr.even {
+      background: #FBFBFB;
+    }
   }
 }
 

+ 59 - 19
front/project/www/routes/my/answer/page.js

@@ -23,25 +23,45 @@ const columns = [
     key: 'questionType',
     width: 140,
     render(text, row) {
-      return <div className="group">
-        <Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{QuestionTypeMap[text]}</Link>
-      </div>;
+      return (
+        <div className="group">
+          <Link
+            to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}
+          >
+            {QuestionTypeMap[text]}
+          </Link>
+        </div>
+      );
     },
   },
   {
     key: 'title',
     width: 100,
     render(text, row) {
-      return <div className="group">
-        <Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{text}</Link>
-      </div>;
+      return (
+        <div className="group">
+          <Link
+            to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}
+          >
+            {text}
+          </Link>
+        </div>
+      );
     },
   },
   {
     key: 'content',
     width: 540,
     render(text, row) {
-      return <div className="group text-hidden"><Link to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}>{text}</Link></div>;
+      return (
+        <div className="group text-hidden">
+          <Link
+            to={row.userQuestionId ? `/paper/question/${row.userQuestionId}` : `/question/detail/${row.questionNoId}`}
+          >
+            {text}
+          </Link>
+        </div>
+      );
     },
   },
 ];
@@ -138,17 +158,16 @@ export default class extends Page {
     this.initData();
   }
 
-  onAction() { }
+  onAction() {}
 
   onSelect(selectList) {
     this.setState({ selectList });
   }
 
   delAsk(id) {
-    My.delQuestionAsk(id)
-      .then(() => {
-        this.refresh();
-      });
+    My.delQuestionAsk(id).then(() => {
+      this.refresh();
+    });
   }
 
   renderView() {
@@ -253,22 +272,43 @@ export default class extends Page {
               <div className="answer-layout">
                 <div className="title">
                   提问区域: <b>{AskTargetMap[item.target]}</b>
-                  {item.answerStatus === 0 && <div className='f-r'><Button radius size='small' onClick={() => this.delAsk(item.id)}>删除</Button></div>}
+                  {item.answerStatus === 0 && (
+                    <div className="f-r">
+                      <Button radius size="small" onClick={() => this.delAsk(item.id)}>
+                        删除
+                      </Button>
+                    </div>
+                  )}
+                </div>
+                <div>
+                  <div className="small-tag">提问</div>
+                  <div className="f-r t-2 t-s-12">{formatDate(item.createTime, 'YYYY-MM-DD HH:mm:ss')}</div>
                 </div>
-                <div><div className="small-tag">提问</div><div className='f-r t-2 t-s-12'>{formatDate(item.createTime, 'YYYY-MM-DD HH:mm:ss')}</div></div>
                 <div className="desc">
                   <OpenText>{item.content}</OpenText>
                 </div>
-                {item.answerStatus > 0 && <div><div className="small-tag">回答</div><div className='f-r t-2 t-s-12'>{formatDate(item.answerTime, 'YYYY-MM-DD HH:mm:ss')}</div></div>}
-                {item.answerStatus > 0 && <div className="desc">
-                  <OpenText>{item.answer}</OpenText>
-                </div>}
+                {item.answerStatus > 0 && (
+                  <div>
+                    <div className="small-tag">回答</div>
+                    <div className="f-r t-2 t-s-12">{formatDate(item.answerTime, 'YYYY-MM-DD HH:mm:ss')}</div>
+                  </div>
+                )}
+                {item.answerStatus > 0 && (
+                  <div className="desc">
+                    <OpenText>{item.answer}</OpenText>
+                  </div>
+                )}
               </div>
             </div>
           );
         })}
         {total > 0 && list.length > 0 && (
-          <UserPagination total={total} pageSize={this.state.search.size} current={page} onChange={p => this.onChangePage(p)} />
+          <UserPagination
+            total={total}
+            pageSize={this.state.search.size}
+            current={page}
+            onChange={p => this.onChangePage(p)}
+          />
         )}
       </div>
     );

+ 429 - 190
front/project/www/routes/my/course/page.js

@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import { Link } from 'react-router-dom';
 import './index.less';
-import { Icon, Dropdown } from 'antd';
+import { Icon, Popover } from 'antd';
 import FileUpload from '@src/components/FileUpload';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
@@ -131,7 +131,7 @@ export default class extends Page {
     });
   }
 
-  onAction() { }
+  onAction() {}
 
   onTabChange(tab) {
     const data = { tab };
@@ -332,7 +332,13 @@ export default class extends Page {
           onChange={key => this.onStatusChange(key)}
         />
         {this[`renderTab${tab}`]()}
-        <Modal show={showTime} className="clock-modal" title="打卡表" width={460} onClose={() => this.setState({ showTime: false })}>
+        <Modal
+          show={showTime}
+          className="clock-modal"
+          title="打卡表"
+          width={460}
+          onClose={() => this.setState({ showTime: false })}
+        >
           <div>
             <div className="d-i-b w-3">
               <div className="t-2 t-s-14">听课频率</div>
@@ -361,8 +367,8 @@ export default class extends Page {
           <DatePlane
             hideInput
             show={showTime}
-            onChange={() => { }}
-            disabledDate={(current) => {
+            onChange={() => {}}
+            disabledDate={current => {
               const date = current.format('YYYY-MM-DD');
               return data.stopTimeMap[date];
             }}
@@ -463,7 +469,9 @@ export default class extends Page {
           />
           <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}>
             <Button theme="file">上传文件</Button>
-            <span className="m-l-1 t-3 t-s-14">{note.file ? `上传文件:${note.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}</span>
+            <span className="m-l-1 t-3 t-s-14">
+              {note.file ? `上传文件:${note.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}
+            </span>
             <div className="fixed">
               <span className="f-w-b t-s-18">放开文件立刻上传</span>
             </div>
@@ -471,11 +479,13 @@ export default class extends Page {
               type="none"
               onDragEnter={() => this.setState({ draging: true })}
               onDragLeave={() => this.setState({ draging: false })}
-              onUpload={({ file }) => Common.upload(file).then(result => {
-                note.file = result.url;
-                note.name = file.name;
-                this.setState({ note });
-              })}
+              onUpload={({ file }) => {
+                Common.upload(file).then(result => {
+                  note.file = result.url;
+                  note.name = file.name;
+                  this.setState({ note });
+                });
+              }}
             />
           </div>
           <div className="b-b m-t-2" />
@@ -524,7 +534,9 @@ export default class extends Page {
           />
           <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}>
             <Button theme="file">上传文件</Button>
-            <span className="m-l-1 t-3 t-s-14">{supply.file ? `上传文件:${supply.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}</span>
+            <span className="m-l-1 t-3 t-s-14">
+              {supply.file ? `上传文件:${supply.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}
+            </span>
             <div className="fixed">
               <span className="f-w-b t-s-18">放开文件立刻上传</span>
             </div>
@@ -532,11 +544,13 @@ export default class extends Page {
               type="none"
               onDragEnter={() => this.setState({ draging: true })}
               onDragLeave={() => this.setState({ draging: false })}
-              onUpload={({ file }) => Common.upload(file).then(result => {
-                supply.file = result.url;
-                supply.name = file.name;
-                this.setState({ supply });
-              })}
+              onUpload={({ file }) => {
+                Common.upload(file).then(result => {
+                  supply.file = result.url;
+                  supply.name = file.name;
+                  this.setState({ supply });
+                });
+              }}
             />
           </div>
           <div className="b-b m-t-2" />
@@ -576,23 +590,44 @@ export default class extends Page {
   renderTabvs() {
     const { list = [] } = this.state;
     return list.map(item => {
-      return <CourseVs data={item} user={this.props.user} refreshDetail={(recordId) => {
-        this.refreshDetail(recordId);
-      }} onRestore={() => {
-        this.setState({ showRestore: true, restore: item });
-      }} onSuspend={() => {
-        this.setState({ showSuspend: true, suspend: item });
-      }} onComment={() => {
-        this.setState({ showComment: true, comment: { channel: 'course-vs', position: item.course.id } });
-      }} closeCommentTips={() => this.closeCommentTips(item.id)}
-        onUploadNote={(appointment, row) => this.setState({ showUploadNote: true, appointment, data: item, note: row || {} })}
-        onUploadSupply={(appointment, row) => this.setState({ showUploadSupply: true, appointment, data: item, supply: row || {} })}
-        onDeleteNote={(appointment, row) => this.deleteAppointmentComment(row)}
-        onDeleteSupply={(appointment, row) => this.deleteAppointmentComment(row)}
-        onNote={(appointment) => this.setState({ showNote: true, appointment, data: item })}
-        onSupply={(appointment) => this.setState({ showSupply: true, appointment, data: item })}
-        onUploadQuestion={(appointment, file, name) => this.submitQuestionFile({ id: appointment.id, recordId: appointment.recordId, questionFile: file, questionFileName: name })}
-        setCCTalkName={(appointment, cctalkName) => this.setCCTalkName(appointment.recordId, cctalkName)} />;
+      return (
+        <CourseVs
+          data={item}
+          user={this.props.user}
+          refreshDetail={recordId => {
+            this.refreshDetail(recordId);
+          }}
+          onRestore={() => {
+            this.setState({ showRestore: true, restore: item });
+          }}
+          onSuspend={() => {
+            this.setState({ showSuspend: true, suspend: item });
+          }}
+          onComment={() => {
+            this.setState({ showComment: true, comment: { channel: 'course-vs', position: item.course.id } });
+          }}
+          closeCommentTips={() => this.closeCommentTips(item.id)}
+          onUploadNote={(appointment, row) => {
+            this.setState({ showUploadNote: true, appointment, data: item, note: row || {} });
+          }}
+          onUploadSupply={(appointment, row) => {
+            this.setState({ showUploadSupply: true, appointment, data: item, supply: row || {} });
+          }}
+          onDeleteNote={(appointment, row) => this.deleteAppointmentComment(row)}
+          onDeleteSupply={(appointment, row) => this.deleteAppointmentComment(row)}
+          onNote={appointment => this.setState({ showNote: true, appointment, data: item })}
+          onSupply={appointment => this.setState({ showSupply: true, appointment, data: item })}
+          onUploadQuestion={(appointment, file, name) => {
+            this.submitQuestionFile({
+              id: appointment.id,
+              recordId: appointment.recordId,
+              questionFile: file,
+              questionFileName: name,
+            });
+          }}
+          setCCTalkName={(appointment, cctalkName) => this.setCCTalkName(appointment.recordId, cctalkName)}
+        />
+      );
     });
   }
 }
@@ -611,39 +646,65 @@ class CourseOnline extends Component {
       {
         title: '预习作业',
         key: 'paper',
-        render: (text) => {
+        render: text => {
           text = text || {};
           const progress = text.report ? formatPercent(text.report.userNumber, text.report.questionNumber) : 0;
           const times = text.paper ? text.paper.times : 0;
-          return <div>
-            <div className="v-a-m d-i-b">
-              <ProgressText width={50} size="small" times={times} progress={progress} unit="次" />
+          return (
+            <div>
+              <div className="v-a-m d-i-b">
+                <ProgressText width={50} size="small" times={times} progress={progress} unit="次" />
+              </div>
+              {!text.report && (
+                <IconButton
+                  className="m-l-2"
+                  type="start"
+                  tip="Start"
+                  onClick={() => {
+                    User.needLogin().then(() => {
+                      Question.startLink('preview', text);
+                    });
+                  }}
+                />
+              )}
+              {text.report && !text.report.isFinish && (
+                <IconButton
+                  className="m-l-2"
+                  type="continue"
+                  tip="Continue"
+                  onClick={() => {
+                    User.needLogin().then(() => {
+                      Question.continueLink('preview', text);
+                    });
+                  }}
+                />
+              )}
+              {text.report && !!text.report.isFinish && (
+                <IconButton
+                  className="m-l-2"
+                  type="restart"
+                  tip="Restart"
+                  onClick={() => {
+                    User.needLogin().then(() => {
+                      Question.restart('preview', text);
+                    });
+                  }}
+                />
+              )}
+              {text.report && !!text.report.isFinish && (
+                <IconButton
+                  className="m-l-5"
+                  type="report"
+                  tip="Report"
+                  onClick={() => {
+                    User.needLogin().then(() => {
+                      Question.reportLink('preview', text);
+                    });
+                  }}
+                />
+              )}
             </div>
-            {!text.report && <IconButton className="m-l-2" type="start" tip="Start" onClick={() => {
-              User.needLogin()
-                .then(() => {
-                  Question.startLink('preview', text);
-                });
-            }} />}
-            {text.report && !text.report.isFinish && <IconButton className="m-l-2" type="continue" tip="Continue" onClick={() => {
-              User.needLogin()
-                .then(() => {
-                  Question.continueLink('preview', text);
-                });
-            }} />}
-            {text.report && !!text.report.isFinish && <IconButton className="m-l-2" type="restart" tip="Restart" onClick={() => {
-              User.needLogin()
-                .then(() => {
-                  Question.restart('preview', text);
-                });
-            }} />}
-            {text.report && !!text.report.isFinish && <IconButton className="m-l-5" type="report" tip="Report" onClick={() => {
-              User.needLogin()
-                .then(() => {
-                  Question.reportLink('preview', text);
-                });
-            }} />}
-          </div>;
+          );
         },
       },
       {
@@ -652,7 +713,8 @@ class CourseOnline extends Component {
         render: (text, record) => {
           const { paper = {} } = record;
           return `${paper.paper && paper.paper.times > 0 ? `${paper.paper.times}次+` : ''}${
-            paper.report ? formatPercent(paper.report.userNumber, paper.report.questionNumber, false) : '0%'}`;
+            paper.report ? formatPercent(paper.report.userNumber, paper.report.questionNumber, false) : '0%'
+          }`;
         },
       },
       {
@@ -846,7 +908,10 @@ class CourseOnline extends Component {
               <div className="t1">授课老师</div>
               <div className="t2">{data.course.teacher}</div>
               <div className="t1">有效期</div>
-              <div className="t-s-12">{formatDate(data.useStartTime, 'YYYY-MM-DD')}<br />至{formatDate(data.useEndTime, 'YYYY-MM-DD')}</div>
+              <div className="t-s-12">
+                {formatDate(data.useStartTime, 'YYYY-MM-DD')}
+                <br />至{formatDate(data.useEndTime, 'YYYY-MM-DD')}
+              </div>
             </div>
           </div>
           <div className="right">
@@ -997,18 +1062,18 @@ const statusMap = {
     // if (new Date(appointment.endTime).getTime() > new Date()) return 'not';
     return '';
   },
-  4: (appointment) => {
+  4: appointment => {
     if (!appointment.noteList || appointment.noteList.length === 0) return 'not';
     return '';
   },
-  5: (appointment) => {
+  5: appointment => {
     if (!appointment.supplyList || appointment.supplyList.length === 0) return 'not';
     return '';
   },
   6: () => {
     return '';
   },
-  7: (appointment) => {
+  7: appointment => {
     const { paper = {} } = appointment;
     if (!paper.report || formatPercent(paper.report.userNumber, paper.report.questionNumber) < 100) return 'not';
     return '';
@@ -1023,63 +1088,101 @@ class CourseVs extends Component {
         {
           title: '预习作业',
           key: 'paper',
-          render: (text) => {
+          render: text => {
             text = text || {};
             const progress = text.report ? formatPercent(text.report.userNumber, text.report.questionNumber) : 0;
             const times = text.paper ? text.paper.times : 0;
-            return <div>
-              <div className="v-a-m d-i-b">
-                <ProgressText width={50} size="small" times={times} progress={progress} unit="次" />
+            return (
+              <div>
+                <div className="v-a-m d-i-b">
+                  <ProgressText width={50} size="small" times={times} progress={progress} unit="次" />
+                </div>
+                {!text.report && (
+                  <IconButton
+                    className="m-l-2"
+                    type="start"
+                    tip="Start"
+                    onClick={() => {
+                      User.needLogin().then(() => {
+                        Question.startLink('preview', text);
+                      });
+                    }}
+                  />
+                )}
+                {text.report && !text.report.isFinish && (
+                  <IconButton
+                    className="m-l-2"
+                    type="continue"
+                    tip="Continue"
+                    onClick={() => {
+                      User.needLogin().then(() => {
+                        Question.continueLink('preview', text);
+                      });
+                    }}
+                  />
+                )}
+                {text.report && !!text.report.isFinish && (
+                  <IconButton
+                    className="m-l-2"
+                    type="restart"
+                    tip="Restart"
+                    onClick={() => {
+                      User.needLogin().then(() => {
+                        Question.restart('preview', text);
+                      });
+                    }}
+                  />
+                )}
+                {text.report && !!text.report.isFinish && (
+                  <IconButton
+                    className="m-l-5"
+                    type="report"
+                    tip="Report"
+                    onClick={() => {
+                      User.needLogin().then(() => {
+                        Question.reportLink('preview', text);
+                      });
+                    }}
+                  />
+                )}
               </div>
-              {!text.report && <IconButton className="m-l-2" type="start" tip="Start" onClick={() => {
-                User.needLogin()
-                  .then(() => {
-                    Question.startLink('preview', text);
-                  });
-              }} />}
-              {text.report && !text.report.isFinish && <IconButton className="m-l-2" type="continue" tip="Continue" onClick={() => {
-                User.needLogin()
-                  .then(() => {
-                    Question.continueLink('preview', text);
-                  });
-              }} />}
-              {text.report && !!text.report.isFinish && <IconButton className="m-l-2" type="restart" tip="Restart" onClick={() => {
-                User.needLogin()
-                  .then(() => {
-                    Question.restart('preview', text);
-                  });
-              }} />}
-              {text.report && !!text.report.isFinish && <IconButton className="m-l-5" type="report" tip="Report" onClick={() => {
-                User.needLogin()
-                  .then(() => {
-                    Question.reportLink('preview', text);
-                  });
-              }} />}
-            </div>;
+            );
           },
         },
         {
           title: '授课时间',
           key: 'time',
           render: (text, record) => {
-            return <div className="sub">
-              <div className="t-2 t-s-12">{formatDate(record.startTime, 'YYYY-MM-DD')}</div>
-              <div className="t-6 t-s-12">{formatDate(record.startTime, 'HH:mm:ss')} ~ {formatDate(record.endTime, 'HH:mm:ss')}</div>
-            </div>;
+            return (
+              <div className="sub">
+                <div className="t-2 t-s-12">{formatDate(record.startTime, 'YYYY-MM-DD')}</div>
+                <div className="t-6 t-s-12">
+                  {formatDate(record.startTime, 'HH:mm:ss')} ~ {formatDate(record.endTime, 'HH:mm:ss')}
+                </div>
+              </div>
+            );
           },
         },
         {
           title: '课后笔记',
           key: 'note',
           render: (text, record) => {
-            return record.noteList && record.noteList.length > 0 ? <a onClick={() => this.props.onNote(record)}>查看</a> : <span>查看</span>;
+            return record.noteList && record.noteList.length > 0 ? (
+              <a onClick={() => this.props.onNote(record)}>查看</a>
+            ) : (
+              <span>查看</span>
+            );
           },
         },
         {
           title: '课后补充',
           key: 'supply',
           render: (text, record) => {
-            return record.supplyList && record.supplyList.length > 0 ? <a onClick={() => this.props.onSupply(record)}>查看</a> : <span>查看</span>;
+            return record.supplyList && record.supplyList.length > 0 ? (
+              <a onClick={() => this.props.onSupply(record)}>查看</a>
+            ) : (
+              <span>查看</span>
+            );
           },
         },
       ],
@@ -1178,10 +1281,12 @@ class CourseVs extends Component {
             />
           </div>
         </div>
-        {showTips && <div className="continue">
-          <Icon className='close m-r-5 t-3' type="close-circle" theme="filled" onClick={() => closeCommentTips()} />
-          课程已过半,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
-        </div>}
+        {showTips && (
+          <div className="continue">
+            <Icon className="close m-r-5 t-3" type="close-circle" theme="filled" onClick={() => closeCommentTips()} />
+            课程已过半,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
+          </div>
+        )}
         <div className="detail">
           <div className="left">
             <Assets name="sun_blue" src={data.course.cover} />
@@ -1210,16 +1315,18 @@ class CourseVs extends Component {
             <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
           </div>
         </div>
-        {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && <Tabs
-          className="t-l"
-          type="line"
-          theme="theme"
-          size="small"
-          width={80}
-          active={tab}
-          tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
-          onChange={key => this.setState({ tab: key })}
-        />}
+        {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && (
+          <Tabs
+            className="t-l"
+            type="line"
+            theme="theme"
+            size="small"
+            width={80}
+            active={tab}
+            tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
+            onChange={key => this.setState({ tab: key })}
+          />
+        )}
         {open && (tab === 'ing' ? this.renderTimeLine() : this.renderTable())}
       </div>
     );
@@ -1294,10 +1401,12 @@ class CourseVs extends Component {
             />
           </div>
         </div>
-        {showTips && <div className="continue">
-          <Icon className='close m-r-5 t-3' type="close-circle" theme="filled" onClick={() => closeCommentTips()} />
-          课程已结束,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
-        </div>}
+        {showTips && (
+          <div className="continue">
+            <Icon className="close m-r-5 t-3" type="close-circle" theme="filled" onClick={() => closeCommentTips()} />
+            课程已结束,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
+          </div>
+        )}
         <div className="detail">
           <div className="left">
             <Assets name="sun_blue" src={data.course.cover} />
@@ -1326,16 +1435,18 @@ class CourseVs extends Component {
             <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
           </div>
         </div>
-        {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && <Tabs
-          className="t-l"
-          type="line"
-          theme="theme"
-          size="small"
-          width={80}
-          active={tab}
-          tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
-          onChange={key => this.setState({ tab: key })}
-        />}
+        {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && (
+          <Tabs
+            className="t-l"
+            type="line"
+            theme="theme"
+            size="small"
+            width={80}
+            active={tab}
+            tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
+            onChange={key => this.setState({ tab: key })}
+          />
+        )}
         {open && (tab === 'ing' ? this.renderTimeLine() : this.renderTable())}
       </div>
     );
@@ -1409,13 +1520,22 @@ class CourseVs extends Component {
     let status = '';
     return [
       <div className="class-hour">
-        {data.number > 1 && <div className="text">课时 {appointment.no}:{appointment.title}</div>}
-        {data.number > 1 && <div className="right">
-          <GIcon name="prev" onClick={() => this.setState({ index: index === 0 ? index : index - 1 })} />
-          <span>上一课时</span>
-          <span>下一课时</span>
-          <GIcon name="next" onClick={() => this.setState({ index: index >= data.appointments.length - 1 ? index : index + 1 })} />
-        </div>}
+        {data.number > 1 && (
+          <div className="text">
+            课时 {appointment.no}:{appointment.title}
+          </div>
+        )}
+        {data.number > 1 && (
+          <div className="right">
+            <GIcon name="prev" onClick={() => this.setState({ index: index === 0 ? index : index - 1 })} />
+            <span>上一课时</span>
+            <span>下一课时</span>
+            <GIcon
+              name="next"
+              onClick={() => this.setState({ index: index >= data.appointments.length - 1 ? index : index + 1 })}
+            />
+          </div>
+        )}
       </div>,
       <div className="time-line">
         {list.map(item => {
@@ -1426,7 +1546,19 @@ class CourseVs extends Component {
             // 上一阶段未完成
             status = 'end';
           }
-          return <TimeLineItem type={`${item}`} user={this.props.user} appointment={appointment} data={data} status={status} onUploadNote={onUploadNote} onUploadSupply={onUploadSupply} onUploadQuestion={onUploadQuestion} setCCTalkName={setCCTalkName} />;
+          return (
+            <TimeLineItem
+              type={`${item}`}
+              user={this.props.user}
+              appointment={appointment}
+              data={data}
+              status={status}
+              onUploadNote={onUploadNote}
+              onUploadSupply={onUploadSupply}
+              onUploadQuestion={onUploadQuestion}
+              setCCTalkName={setCCTalkName}
+            />
+          );
         })}
       </div>,
     ];
@@ -1435,7 +1567,9 @@ class CourseVs extends Component {
   renderTable() {
     const { data = {} } = this.props;
     const { appointments = [] } = data;
-    return <UserTable size="small" columns={this.columns[data.course.vsType]} data={appointments.filter(row => row.id)} />;
+    return (
+      <UserTable size="small" columns={this.columns[data.course.vsType]} data={appointments.filter(row => row.id)} />
+    );
   }
 }
 class TimeLineItem extends Component {
@@ -1487,11 +1621,11 @@ class TimeLineItem extends Component {
             return (
               <span>
                 请尽快与老师预约上课时间,老师微信:{data.teacher.wechat}扫码加微信{' '}
-                <Dropdown overlay={<Assets name="qrcode" src={data.teacher.qr} />}>
+                <Popover content={<Assets name="qrcode" src={data.teacher.qr} />}>
                   <span>
                     <Assets className="m-l-1" name="erweima" />
                   </span>
-                </Dropdown>
+                </Popover>
               </span>
             );
           default:
@@ -1507,9 +1641,15 @@ class TimeLineItem extends Component {
           case 'end':
             return <span className="link">点此上传</span>;
           case 'not':
-            return <FileUpload onUpload={(file) => {
-              return Common.upload({ file }).then((result => onUploadQuestion(appointment, result.url, file.name)));
-            }}><span className="link">点此上传</span></FileUpload>;
+            return (
+              <FileUpload
+                onUpload={file => {
+                  return Common.upload({ file }).then(result => onUploadQuestion(appointment, result.url, file.name));
+                }}
+              >
+                <span className="link">点此上传</span>
+              </FileUpload>
+            );
           default:
             return (
               <a href={appointment.questionFile} target="_blank">
@@ -1520,19 +1660,57 @@ class TimeLineItem extends Component {
       case '3':
         switch (status) {
           case 'end':
-            return data.cctalkName ? <span>
-              CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a>
-            </span> : <div><input style={{ width: 200 }} className='b-c-1 p-l-1 p-r-1 t-s-12 m-r-1' placeholder="请输入CCtalk用户名查看授课频道" onChange={(e) => {
-              this.setState({ cctalkName: e.target.value });
-            }} /><Button size="small" radius disabled>提交</Button></div>;
+            return data.cctalkName ? (
+              <span>
+                CCtalk 频道号 :{appointment.cctalkChannel}{' '}
+                <a className="link" href="" target="_black">
+                  CC talk使用手册
+                </a>
+              </span>
+            ) : (
+              <div>
+                <input
+                  style={{ width: 200 }}
+                  className="b-c-1 p-l-1 p-r-1 t-s-12 m-r-1"
+                  placeholder="请输入CCtalk用户名查看授课频道"
+                  onChange={e => {
+                    this.setState({ cctalkName: e.target.value });
+                  }}
+                />
+                <Button size="small" radius disabled>
+                  提交
+                </Button>
+              </div>
+            );
           case 'not':
-            return data.cctalkName ? <span>
-              CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a>
-            </span> : <div><input style={{ width: 200 }} className='b-c-1 p-l-1 p-r-1 t-s-12 m-r-1' placeholder="请输入CCtalk用户名查看授课频道" onChange={(e) => {
-              this.setState({ cctalkName: e.target.value });
-            }} /><Button size="small" radius onClick={() => {
-              if (this.state.cctalkName) setCCTalkName(appointment, this.state.cctalkName);
-            }} >提交</Button></div>;
+            return data.cctalkName ? (
+              <span>
+                CCtalk 频道号 :{appointment.cctalkChannel}{' '}
+                <a className="link" href="" target="_black">
+                  CC talk使用手册
+                </a>
+              </span>
+            ) : (
+              <div>
+                <input
+                  style={{ width: 200 }}
+                  className="b-c-1 p-l-1 p-r-1 t-s-12 m-r-1"
+                  placeholder="请输入CCtalk用户名查看授课频道"
+                  onChange={e => {
+                    this.setState({ cctalkName: e.target.value });
+                  }}
+                />
+                <Button
+                  size="small"
+                  radius
+                  onClick={() => {
+                    if (this.state.cctalkName) setCCTalkName(appointment, this.state.cctalkName);
+                  }}
+                >
+                  提交
+                </Button>
+              </div>
+            );
           default:
             return (
               <span>
@@ -1548,30 +1726,60 @@ class TimeLineItem extends Component {
           case 'end':
             return <span className="link">点此上传</span>;
           case 'not':
-            return <span className="link" onClick={() => onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>点此上传</span>;
+            return (
+              <span
+                className="link"
+                onClick={() => {
+                  onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId });
+                }}
+              >
+                点此上传
+              </span>
+            );
           default:
             return (
               <div>
                 <div>
-                  <span className="link" onClick={() => onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>点此上传</span>
+                  <span
+                    className="link"
+                    onClick={() => {
+                      onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId });
+                    }}
+                  >
+                    点此上传
+                  </span>
                 </div>
                 <div className="note-list">
                   {appointment.noteList.map(row => {
-                    console.log(row);
-                    return <Note user={this.props.user} teacher={data.teacher} data={row} reply={!row.userId} actionList={row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null} onAction={(key) => {
-                      switch (key) {
-                        case 'edit':
-                          onUploadNote(appointment, row);
-                          break;
-                        case 'delete':
-                          onDeleteNote(appointment, row);
-                          break;
-                        case 'reply':
-                          onUploadNote(appointment, { parentId: row.id, appointmentId: appointment.id, recordId: appointment.recordId });
-                          break;
-                        default:
-                      }
-                    }} />;
+                    return (
+                      <Note
+                        user={this.props.user}
+                        teacher={data.teacher}
+                        data={row}
+                        reply={!row.userId}
+                        actionList={
+                          row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null
+                        }
+                        onAction={key => {
+                          switch (key) {
+                            case 'edit':
+                              onUploadNote(appointment, row);
+                              break;
+                            case 'delete':
+                              onDeleteNote(appointment, row);
+                              break;
+                            case 'reply':
+                              onUploadNote(appointment, {
+                                parentId: row.id,
+                                appointmentId: appointment.id,
+                                recordId: appointment.recordId,
+                              });
+                              break;
+                            default:
+                          }
+                        }}
+                      />
+                    );
                   })}
                 </div>
               </div>
@@ -1582,29 +1790,60 @@ class TimeLineItem extends Component {
           case 'end':
             return <span className="link">写留言</span>;
           case 'not':
-            return <span className="link" onClick={() => onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>写留言</span>;
+            return (
+              <span
+                className="link"
+                onClick={() => {
+                  onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId });
+                }}
+              >
+                写留言
+              </span>
+            );
           default:
             return (
               <div>
                 <div>
-                  <span className="link" onClick={() => onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>写留言</span>
+                  <span
+                    className="link"
+                    onClick={() => {
+                      onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId });
+                    }}
+                  >
+                    写留言
+                  </span>
                 </div>
                 <div className="note-list">
                   {appointment.supplyList.map(row => {
-                    return <Note user={this.props.user} teacher={data.teacher} data={row} reply={!row.userId} actionList={row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null} onAction={(key) => {
-                      switch (key) {
-                        case 'edit':
-                          onUploadSupply(appointment, row);
-                          break;
-                        case 'delete':
-                          onDeleteSupply(appointment, row);
-                          break;
-                        case 'reply':
-                          onUploadSupply(appointment, { parentId: row.id, appointmentId: appointment.id, recordId: appointment.recordId });
-                          break;
-                        default:
-                      }
-                    }} />;
+                    return (
+                      <Note
+                        user={this.props.user}
+                        teacher={data.teacher}
+                        data={row}
+                        reply={!row.userId}
+                        actionList={
+                          row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null
+                        }
+                        onAction={key => {
+                          switch (key) {
+                            case 'edit':
+                              onUploadSupply(appointment, row);
+                              break;
+                            case 'delete':
+                              onDeleteSupply(appointment, row);
+                              break;
+                            case 'reply':
+                              onUploadSupply(appointment, {
+                                parentId: row.id,
+                                appointmentId: appointment.id,
+                                recordId: appointment.recordId,
+                              });
+                              break;
+                            default:
+                          }
+                        }}
+                      />
+                    );
                   })}
                 </div>
               </div>

+ 0 - 11
front/project/www/routes/my/main/index.less

@@ -283,17 +283,6 @@
           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);
-        }
-
         .icon {
           position: absolute;
           right: 0;

+ 97 - 45
front/project/www/routes/my/order/page.js

@@ -32,7 +32,6 @@ function formatTitle(record) {
   return '';
 }
 
-
 export default class extends Page {
   constructor(props) {
     props.size = 15;
@@ -44,7 +43,7 @@ export default class extends Page {
       {
         title: '订单编号',
         key: 'id',
-        render: (text) => {
+        render: text => {
           return text;
         },
       },
@@ -57,11 +56,14 @@ export default class extends Page {
           actionList.push({ key: 'invoice', label: '开发票' });
           // }
           actionList.push({ key: 'detail', label: '订单详情' });
-          const onAction = (value) => {
+          const onAction = value => {
             const { key } = value;
             switch (key) {
               case 'invoice':
-                this.setState({ showInvoice: true, invoice: { orderId: record.id, money: record.invoiceMoney, invoiceType: InvoiceType[0].value } });
+                this.setState({
+                  showInvoice: true,
+                  invoice: { orderId: record.id, money: record.invoiceMoney, invoiceType: InvoiceType[0].value },
+                });
                 break;
               case 'detail':
                 openLink(`/order/detail/${record.id}`);
@@ -71,40 +73,58 @@ export default class extends Page {
           };
           let content = [];
           if (record.checkouts.length > 3) {
-            content.push(<div className="flex-layout m-b-5">
-              <div className="flex-block">{formatTitle(record.checkouts[0])}<br />等{record.checkouts.length}个商品</div>
-              <More menu={actionList} onClick={onAction} ><IconButton type="more" /></More>
-            </div>);
+            content.push(
+              <div className="flex-layout m-b-5">
+                <div className="flex-block">
+                  {formatTitle(record.checkouts[0])}
+                  <br />等{record.checkouts.length}个商品
+                </div>
+                <More menu={actionList} onClick={onAction}>
+                  <IconButton type="more" />
+                </More>
+              </div>,
+            );
           } else {
             content = record.checkouts.map((row, index) => {
-              return <div className="flex-layout m-b-5">
-                <div className="flex-block">{formatTitle(row)}</div>
-                {index === 0 && <More menu={actionList} onClick={onAction} ><IconButton type="more" /></More>}
-                {row.productType === 'data' && <IconButton type="download" onClick={() => {
-                  openLink(row.data.resource);
-                }} />}
-              </div>;
+              return (
+                <div className="flex-layout m-b-5">
+                  <div className="flex-block">{formatTitle(row)}</div>
+                  {index === 0 && (
+                    <More menu={actionList} onClick={onAction}>
+                      <IconButton type="more" />
+                    </More>
+                  )}
+                  {row.productType === 'data' && (
+                    <IconButton
+                      type="download"
+                      onClick={() => {
+                        openLink(row.data.resource);
+                      }}
+                    />
+                  )}
+                </div>
+              );
             });
           }
-          return <div className="t-2">
-            {content}
-          </div >;
+          return <div className="t-2">{content}</div>;
         },
       },
       {
         title: '购买时间',
         key: 'createTime',
         render: text => {
-          return <div className="sub">
-            <div className="t-2 t-s-12">{text.split(' ')[0]}</div>
-            <div className="t-6 t-s-12">{text.split(' ')[1]}</div>
-          </div>;
+          return (
+            <div className="sub">
+              <div className="t-2 t-s-12">{text.split(' ')[0]}</div>
+              <div className="t-6 t-s-12">{text.split(' ')[1]}</div>
+            </div>
+          );
         },
       },
       {
         title: '付款方式',
         key: 'payMethod',
-        render: (text) => {
+        render: text => {
           return RecordSourceMap[text];
         },
       },
@@ -112,7 +132,7 @@ export default class extends Page {
         title: '付款金额',
         key: 'money',
         render: text => {
-          return <span className='t-7'>¥{formatMoney(text)}</span>;
+          return <span className="t-7">¥{formatMoney(text)}</span>;
         },
       },
     ];
@@ -171,7 +191,15 @@ export default class extends Page {
           current={page}
           pageSize={this.state.search.size}
         />
-        <Modal show={showInvoice} title="开发票" width={630} btnType="link" confirmText="申请" onConfirm={() => this.submitInvoice(invoice)} onCancel={() => this.setState({ showInvoice: false })}>
+        <Modal
+          show={showInvoice}
+          title="开发票"
+          width={630}
+          btnType="link"
+          confirmText="申请"
+          onConfirm={() => this.submitInvoice(invoice)}
+          onCancel={() => this.setState({ showInvoice: false })}
+        >
           <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">发票类型:</div>
             <div className="input-block">
@@ -183,34 +211,51 @@ export default class extends Page {
             <div className="label m-r-5">抬头类型:</div>
             <div className="input-block">
               {InvoiceType.map(row => {
-                return <span className="m-r-2">
-                  <Radio checked={invoice.invoiceType === row.value} onChange={() => {
-                    invoice.invoiceType = row.value;
-                    this.setState({ invoice });
-                  }} />
-                  {row.label}
-                </span>;
+                return (
+                  <span className="m-r-2">
+                    <Radio
+                      checked={invoice.invoiceType === row.value}
+                      onChange={() => {
+                        invoice.invoiceType = row.value;
+                        this.setState({ invoice });
+                      }}
+                    />
+                    {row.label}
+                  </span>
+                );
               })}
             </div>
           </div>
           <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">发票抬头:</div>
             <div className="input-block">
-              <input value={invoice.title} style={{ width: 330, paddingTop: 3, paddingBottom: 3 }} className="b-c-1 p-l-1 p-r-1 p-l-1" onChange={e => {
-                invoice.title = e.target.value;
-                this.setState({ invoice });
-              }} />
+              <input
+                value={invoice.title}
+                style={{ width: 330, paddingTop: 3, paddingBottom: 3 }}
+                className="b-c-1 p-l-1 p-r-1 p-l-1"
+                onChange={e => {
+                  invoice.title = e.target.value;
+                  this.setState({ invoice });
+                }}
+              />
             </div>
           </div>
-          {invoice.invoiceType === 'enterprise' && <div className="input-layout m-b-2 t-2 t-s-16">
-            <div className="label m-r-5">纳税人识别号:</div>
-            <div className="input-block">
-              <input value={invoice.identity} style={{ width: 300, paddingTop: 3, paddingBottom: 3 }} className="b-c-1 p-l-1 p-r-1 p-l-1" onChange={e => {
-                invoice.identity = e.target.value;
-                this.setState({ invoice });
-              }} />
+          {invoice.invoiceType === 'enterprise' && (
+            <div className="input-layout m-b-2 t-2 t-s-16">
+              <div className="label m-r-5">纳税人识别号:</div>
+              <div className="input-block">
+                <input
+                  value={invoice.identity}
+                  style={{ width: 300, paddingTop: 3, paddingBottom: 3 }}
+                  className="b-c-1 p-l-1 p-r-1 p-l-1"
+                  onChange={e => {
+                    invoice.identity = e.target.value;
+                    this.setState({ invoice });
+                  }}
+                />
+              </div>
             </div>
-          </div>}
+          )}
           <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">发票内容:</div>
             <div className="input-block">
@@ -223,7 +268,14 @@ export default class extends Page {
             <div className="input-block">¥ {formatMoney(invoice.money)}</div>
           </div>
         </Modal>
-        <Modal show={showInvoiceFinish} title="申请成功" width={630} confirmText="好的,知道了" btnAlign="center" onConfirm={() => this.setState({ showInvoiceFinish: false })}>
+        <Modal
+          show={showInvoiceFinish}
+          title="申请成功"
+          width={630}
+          confirmText="好的,知道了"
+          btnAlign="center"
+          onConfirm={() => this.setState({ showInvoiceFinish: false })}
+        >
           <div className="t-2 t-s-18">
             <Icon className="t-5 m-r-5" type="check" />
             我们会在三个工作日内将电子发票发送至您的绑定邮箱:

+ 10 - 41
front/project/www/routes/my/report/index.less

@@ -27,10 +27,6 @@
         margin: 0;
         font-size: 12px;
 
-        th.select {
-          padding-left: 45px;
-        }
-
         th {
           color: #686872;
           font-weight: 500;
@@ -42,48 +38,21 @@
 
         td {
           padding: 20px 15px;
-          background: #FBFBFB;
-          border-bottom: 1px solid #eee;
-          vertical-align: top;
-        }
-
-        .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;
-      }
+.user-report-history-overlay {
+  margin: -10px;
 
-      .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;
-      }
+  tr {
+    padding: 0 3px;
+  }
 
-      .desc {
-        color: #303139;
-        margin-bottom: 20px;
-      }
-    }
+  td {
+    font-size: 12px;
+    padding: 3px 5px;
   }
 }

+ 372 - 145
front/project/www/routes/my/report/page.js

@@ -1,5 +1,6 @@
 import React from 'react';
 import './index.less';
+import { Tooltip, Popover, Checkbox } from 'antd';
 import Page from '@src/containers/Page';
 import Assets from '@src/components/Assets';
 import { timeRange, formatPercent, formatSeconds, formatDate, getMap } from '@src/services/Tools';
@@ -16,11 +17,70 @@ import { Question } from '../../../stores/question';
 
 const QuestionnTypeMap = getMap(QuestionType, 'value', 'label');
 
-
 export default class extends Page {
   constructor(props) {
     props.size = 10;
     super(props);
+    this.columns = [
+      { key: 'title', title: '练习册名称', fixSort: true },
+      {
+        key: 'create_time',
+        title: '做题时间',
+        fixSort: true,
+        render: (text, row, index) => {
+          return (
+            <div>
+              <div className="t-2">
+                2019-12-12
+                {row.children && (
+                  <Popover
+                    trigger="click"
+                    content={
+                      <table className="user-report-history-overlay">
+                        <tbody>
+                          {row.children.map((child, i) => {
+                            return (
+                              <tr>
+                                <td>{row.children.length - i}</td>
+                                <td>2019-12-12 13:13:13</td>
+                                <td>3/20</td>
+                                <td>
+                                  <Checkbox
+                                    checked={!child.disabled}
+                                    onChange={e => this.onChangeShowHistory(index, i, e.target.checked)}
+                                  />
+                                </td>
+                              </tr>
+                            );
+                          })}
+                        </tbody>
+                      </table>
+                    }
+                  >
+                    <Tooltip overlayClassName="gray" title="历史数据">
+                      <span>
+                        <Assets className="m-l-5" name="down_normal" />
+                      </span>
+                    </Tooltip>
+                  </Popover>
+                )}
+              </div>
+              <div className="t-6">13:13:13</div>
+            </div>
+          );
+        },
+      },
+      { key: 'correct', title: '正确率', sort: true },
+      { key: 'time', title: '平均耗时', sort: true },
+      { key: 'progress', title: '完成度' },
+      {
+        key: 'report',
+        title: '报告',
+        render() {
+          return <IconButton type="report" tip="report" />;
+        },
+      },
+    ];
     Main.getExaminationNumber().then(nums => {
       this.nums = nums;
       this.setState({ load: false });
@@ -33,6 +93,7 @@ export default class extends Page {
       timerange: 'today',
       filterMap: {},
       sortMap: {},
+      list: [], // { children: [{}, {}], title: 'channgnanjue' }, {}, {}, {}
       selectList: [],
       allChecked: false,
     };
@@ -44,86 +105,118 @@ export default class extends Page {
         key: 'title',
         title: '练习册名称',
         fixSort: true,
-        render: (text, record) => {
-          const { reports } = record;
-          return reports.map((report, index) => {
-            return <div className="sub">
-              {index === 0 && text}
-            </div>;
-          });
-        },
+        // render: (text, record) => {
+        //   const { reports } = record;
+        //   return reports.map((report, index) => {
+        //     return <div className="sub">{index === 0 && text}</div>;
+        //   });
+        // },
       },
       {
         key: 'latest_time',
         title: '做题时间',
         fixSort: true,
-        render: (text, record) => {
-          const { reports } = record;
-          return reports.map(report => {
-            const time = formatDate(report.updateTime, 'YYYY-MM-DD HH:mm:ss');
-            return <div className="sub">
-              <div className="t-2 t-s-12">{time.split(' ')[0]}</div>
-              <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
-            </div>;
-          });
+        render: (text, record, index, childIndex) => {
+          const { reports, show } = record;
+          const report = childIndex === -1 ? reports[0] : record;
+          const time = formatDate(report.updateTime, 'YYYY-MM-DD HH:mm:ss');
+          return <div className="">
+            <div className="t-2 t-s-12">{time.split(' ')[0]}
+              {childIndex === -1 && reports.length > 1 && (
+                <Popover
+                  trigger="click"
+                  content={
+                    <table className="user-report-history-overlay">
+                      <tbody>
+                        {reports.map((child, ii) => {
+                          if (ii === 0) return null;
+                          return <tr>
+                            <td>{reports.length - ii}</td>
+                            <td>{formatDate(child.updateTime, 'YYYY-MM-DD HH:mm:ss')}</td>
+                            <td>{child.userNumber}/{child.questionNumber}</td>
+                            <td>
+                              <Checkbox
+                                checked={show.indexOf(child.id) >= 0}
+                                onChange={e => this.onChangeShowHistory(index, ii, e.target.checked)}
+                              />
+                            </td>
+                          </tr>;
+                        })}
+                      </tbody>
+                    </table>
+                  }
+                >
+                  <Tooltip overlayClassName="gray" title="历史数据">
+                    <span>
+                      <Assets className="m-l-5" name="down_normal" />
+                    </span>
+                  </Tooltip>
+                </Popover>
+              )}</div>
+            <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
+          </div>;
         },
       },
       {
         key: 'correct',
         title: '正确率',
         sort: true,
-        render: (text, record) => {
+        render: (text, record, index, childIndex) => {
           const { reports } = record;
-          return reports.map(report => {
-            const user = formatPercent(report.userCorrect, report.userNumber);
-            const all = formatPercent(record.stat.totalCorrect, record.stat.totalNumber);
-            return <div className="sub">
-              <div className="t-2 t-s-12">{user}%<Assets height={10} width={10} name={user > all ? 'up' : 'down'} /></div>
-              <div className="t-6 t-s-12">全站{all}%</div>
-            </div>;
-          });
+          const report = childIndex === -1 ? reports[0] : record;
+          const user = formatPercent(report.userCorrect, report.userNumber);
+          const all = formatPercent(record.stat.totalCorrect, record.stat.totalNumber);
+          return <div className="">
+            <div className="t-2 t-s-12">{user}%<Assets height={10} width={10} name={user > all ? 'up' : 'down'} /></div>
+            <div className="t-6 t-s-12">全站{all}%</div>
+          </div>;
         },
       },
       {
         key: 'time',
         title: '平均耗时',
         sort: true,
-        render: (text, record) => {
+        render: (text, record, index, childIndex) => {
           const { reports } = record;
-          return reports.map(report => {
-            const user = report.userTime / report.userNumber;
-            const all = record.stat.totalTime / record.stat.totalNumber;
-            return <div className="sub">
-              <div className="t-2 t-s-12">{formatSeconds(user)}<Assets height={10} width={10} name={user > all ? 'up' : 'down'} /></div>
-              <div className="t-6 t-s-12">全站{formatSeconds(all)}</div>
-            </div>;
-          });
+          const report = childIndex === -1 ? reports[0] : record;
+          const user = report.userTime / report.userNumber;
+          const all = record.stat.totalTime / record.stat.totalNumber;
+          return <div className="">
+            <div className="t-2 t-s-12">{formatSeconds(user)}<Assets height={10} width={10} name={user > all ? 'up' : 'down'} /></div>
+            <div className="t-6 t-s-12">全站{formatSeconds(all)}</div>
+          </div>;
         },
       },
       {
         key: 'progress',
         title: '完成度',
-        render: (text, record) => {
+        render: (text, record, index, childIndex) => {
           const { reports } = record;
-          return reports.map(report => {
-            return <div className="sub">
-              <div className="t-2 t-s-12">{formatPercent(report.userNumber, report.questionNumber)}</div>
-            </div>;
-          });
+          const report = childIndex === -1 ? reports[0] : record;
+          return (
+            <div className="">
+              <div className="t-2 t-s-12">{formatPercent(report.userNumber, report.questionNumber, false)}</div>
+            </div>
+          );
         },
       },
       {
         key: 'report',
         title: '报告',
-        render(text, record) {
+        render: (text, record, index, childIndex) => {
           const { reports } = record;
-          return reports.map(report => {
-            return <div className="sub">
-              <IconButton type="report" tip="report" onClick={() => {
-                Question.reportLink({ report });
-              }} />
-            </div>;
-          });
+          const report = childIndex === -1 ? reports[0] : record;
+          return (
+            <div className="">
+              <IconButton
+                type="report"
+                tip="report"
+                onClick={() => {
+                  Question.reportLink({ report });
+                }}
+              />
+            </div>
+          );
         },
       },
     ];
@@ -136,10 +229,12 @@ export default class extends Page {
         fixSort: true,
         render: (text, record) => {
           const time = formatDate(record.latestTime, 'YYYY-MM-DD HH:mm:ss');
-          return <div className="sub">
-            <div className="t-2 t-s-12">{time.split(' ')[0]}</div>
-            <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
-          </div>;
+          return (
+            <div className="sub">
+              <div className="t-2 t-s-12">{time.split(' ')[0]}</div>
+              <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
+            </div>
+          );
         },
       },
       {
@@ -148,11 +243,25 @@ export default class extends Page {
         render: (text, record) => {
           const [report] = record.reports;
           if (!report) return null;
-          if (!report.qxCat) return <div className="f-s-12">仅CAT模考<br />提供分数</div>;
-          return <div className="sub">
-            <div className="t-2 t-s-12">{report.score.totalScore}</div>
-            <div className="t-6 t-s-12">{record.qxCat === 1 ? Math.round(record.origin.totalScore / record.origin.totalTimes) : Math.round(record.origin.secondTotalScore / record.origin.secondTotalTimes)}</div>
-          </div>;
+          if (!report.qxCat) {
+            return (
+              <div className="f-s-12">
+                仅CAT模考
+                <br />
+                提供分数
+              </div>
+            );
+          }
+          return (
+            <div className="sub">
+              <div className="t-2 t-s-12">{report.score.totalScore}</div>
+              <div className="t-6 t-s-12">
+                {record.qxCat === 1
+                  ? Math.round(record.origin.totalScore / record.origin.totalTimes)
+                  : Math.round(record.origin.secondTotalScore / record.origin.secondTotalTimes)}
+              </div>
+            </div>
+          );
         },
       },
       {
@@ -161,11 +270,23 @@ export default class extends Page {
         render: (text, record) => {
           const [report] = record.reports;
           if (!report) return null;
-          if (!report.qxCat) return <div className="f-s-12">{formatPercent(report.setting.number.verbal, this.nums.verbal.number, false)}</div>;
-          return <div className="sub">
-            <div className="t-2 t-s-12">{report.score.verbalScore}</div>
-            <div className="t-6 t-s-12">{record.qxCat === 1 ? Math.round(record.origin.verbalScore / record.origin.totalTimes) : Math.round(record.origin.secondVerbalScore / record.origin.secondTotalTimes)}</div>
-          </div>;
+          if (!report.qxCat) {
+            return (
+              <div className="f-s-12">
+                {formatPercent(report.setting.number.verbal, this.nums.verbal.number, false)}
+              </div>
+            );
+          }
+          return (
+            <div className="sub">
+              <div className="t-2 t-s-12">{report.score.verbalScore}</div>
+              <div className="t-6 t-s-12">
+                {record.qxCat === 1
+                  ? Math.round(record.origin.verbalScore / record.origin.totalTimes)
+                  : Math.round(record.origin.secondVerbalScore / record.origin.secondTotalTimes)}
+              </div>
+            </div>
+          );
         },
       },
       {
@@ -174,11 +295,21 @@ export default class extends Page {
         render: (text, record) => {
           const [report] = record.reports;
           if (!report) return null;
-          if (!report.qxCat) return <div className="f-s-12">{formatPercent(report.setting.number.quant, this.nums.quant.number, false)}</div>;
-          return <div className="sub">
-            <div className="t-2 t-s-12">{report.score.quantScore}</div>
-            <div className="t-6 t-s-12">{record.qxCat === 1 ? Math.round(record.origin.quantScore / record.origin.totalTimes) : Math.round(record.origin.secondQuantScore / record.origin.secondTotalTimes)}</div>
-          </div>;
+          if (!report.qxCat) {
+            return (
+              <div className="f-s-12">{formatPercent(report.setting.number.quant, this.nums.quant.number, false)}</div>
+            );
+          }
+          return (
+            <div className="sub">
+              <div className="t-2 t-s-12">{report.score.quantScore}</div>
+              <div className="t-6 t-s-12">
+                {record.qxCat === 1
+                  ? Math.round(record.origin.quantScore / record.origin.totalTimes)
+                  : Math.round(record.origin.secondQuantScore / record.origin.secondTotalTimes)}
+              </div>
+            </div>
+          );
         },
       },
       {
@@ -187,11 +318,19 @@ export default class extends Page {
         render: (text, record) => {
           const [report] = record.reports;
           if (!report) return null;
-          if (!report.qxCat) return <div className="f-s-12">{formatPercent(report.setting.number.ir, this.nums.ir.number, false)}</div>;
-          return <div className="sub">
-            <div className="t-2 t-s-12">{report.score.irScore}</div>
-            <div className="t-6 t-s-12">{record.qxCat === 1 ? Math.round(record.origin.irScore / record.origin.totalTimes) : Math.round(record.origin.secondIrScore / record.origin.secondTotalTimes)}</div>
-          </div>;
+          if (!report.qxCat) {
+            return <div className="f-s-12">{formatPercent(report.setting.number.ir, this.nums.ir.number, false)}</div>;
+          }
+          return (
+            <div className="sub">
+              <div className="t-2 t-s-12">{report.score.irScore}</div>
+              <div className="t-6 t-s-12">
+                {record.qxCat === 1
+                  ? Math.round(record.origin.irScore / record.origin.totalTimes)
+                  : Math.round(record.origin.secondIrScore / record.origin.secondTotalTimes)}
+              </div>
+            </div>
+          );
         },
       },
       {
@@ -199,9 +338,15 @@ export default class extends Page {
         title: '报告',
         render: (text, record) => {
           const [report] = record.reports;
-          return <IconButton type="report" tip="report" onClick={() => {
-            Question.reportLink({ report });
-          }} />;
+          return (
+            <IconButton
+              type="report"
+              tip="report"
+              onClick={() => {
+                Question.reportLink({ report });
+              }}
+            />
+          );
         },
       },
     ];
@@ -214,9 +359,7 @@ export default class extends Page {
         render: (text, record) => {
           const { reports } = record;
           return reports.map((report, index) => {
-            return <div className="sub">
-              {index === 0 && text}
-            </div>;
+            return <div className="sub">{index === 0 && text}</div>;
           });
         },
       },
@@ -226,10 +369,12 @@ export default class extends Page {
         fixSort: true,
         render: (text, record) => {
           const time = formatDate(record.latestTime, 'YYYY-MM-DD HH:mm:ss');
-          return <div className="sub">
-            <div className="t-2 t-s-12">{time.split(' ')[0]}</div>
-            <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
-          </div>;
+          return (
+            <div className="sub">
+              <div className="t-2 t-s-12">{time.split(' ')[0]}</div>
+              <div className="t-6 t-s-12">{time.split(' ')[1]}</div>
+            </div>
+          );
         },
       },
       {
@@ -245,10 +390,14 @@ export default class extends Page {
         render: (text, record) => {
           const { reports } = record;
           return reports.map(report => {
-            return <div className="sub">
-              <div className="t-2 t-s-12">{formatPercent(report.userNumber, report.questionNumber, false)}</div>
-              <div className="t-6 t-s-12">{report.userNumber}/{report.questionNumber}</div>
-            </div>;
+            return (
+              <div className="sub">
+                <div className="t-2 t-s-12">{formatPercent(report.userNumber, report.questionNumber, false)}</div>
+                <div className="t-6 t-s-12">
+                  {report.userNumber}/{report.questionNumber}
+                </div>
+              </div>
+            );
           });
         },
       },
@@ -259,9 +408,11 @@ export default class extends Page {
         render: (text, record) => {
           const { reports } = record;
           return reports.map(report => {
-            return <div className="sub">
-              <div className="t-2 t-s-12">{formatPercent(report.userCorrect, report.userNumber, false)}</div>
-            </div>;
+            return (
+              <div className="sub">
+                <div className="t-2 t-s-12">{formatPercent(report.userCorrect, report.userNumber, false)}</div>
+              </div>
+            );
           });
         },
       },
@@ -272,9 +423,11 @@ export default class extends Page {
         render: (text, record) => {
           const { reports } = record;
           return reports.map(report => {
-            return <div className="sub">
-              <div className="t-2 t-s-12">{formatSeconds(report.userTime / report.userNumber)}</div>
-            </div>;
+            return (
+              <div className="sub">
+                <div className="t-2 t-s-12">{formatSeconds(report.userTime / report.userNumber)}</div>
+              </div>
+            );
           });
         },
       },
@@ -284,11 +437,17 @@ export default class extends Page {
         render: (text, record) => {
           const { reports } = record;
           return reports.map(report => {
-            return <div className="sub">
-              <IconButton type="report" tip="report" onClick={() => {
-                Question.reportLink({ report });
-              }} />
-            </div>;
+            return (
+              <div className="sub">
+                <IconButton
+                  type="report"
+                  tip="report"
+                  onClick={() => {
+                    Question.reportLink({ report });
+                  }}
+                />
+              </div>
+            );
           });
         },
       },
@@ -308,11 +467,15 @@ export default class extends Page {
     switch (data.tab) {
       case 'error':
       case 'collect':
-        My.listReport(Object.assign({ origin: data.tab, startTime, endTime }, this.state.search, {
-          order: Object.keys(data.sortMap).map(key => {
-            return `${key} ${data.sortMap[key]}`;
-          }).join(','),
-        })).then(result => {
+        My.listReport(
+          Object.assign({ origin: data.tab, startTime, endTime }, this.state.search, {
+            order: Object.keys(data.sortMap)
+              .map(key => {
+                return `${key} ${data.sortMap[key]}`;
+              })
+              .join(','),
+          }),
+        ).then(result => {
           result.list = result.list.map(row => {
             row.questionTypes = row.questionTypes || [];
             row.reports = row.reports || [];
@@ -325,28 +488,71 @@ export default class extends Page {
       case 'exercise':
       case 'examination':
       default:
-        refreshQuestionType(this, data.subject, data.questionType, { all: true, needSentence: true, allSubject: true })
-          .then(({ questionTypes, courseModules }) => {
-            return refreshStruct(this, data.tab, data.one, data.two, {
-              all: true, needPreview: true, needTextbook: false,
-            })
-              .then(({ structIds, latest, year }) => {
-                My.listReport(Object.assign({ module: data.tab, questionTypes, structIds, latest, year, courseModules, startTime, endTime }, this.state.search, {
-                  order: Object.keys(data.sortMap).map(key => {
-                    return `${key} ${data.sortMap[key]}`;
-                  }).join(','),
-                })).then(result => {
-                  result.list = result.list.map(row => {
-                    row.questionTypes = row.questionTypes || [];
-                    row.reports = row.reports || [];
-                    row.stat = row.stat || {};
-                    return row;
-                  });
-                  this.setState({ list: result.list, total: result.total, page: data.page, columns: data.tab === 'exercise' ? this.exerciseColumns : this.examinationColumns });
+        refreshQuestionType(this, data.subject, data.questionType, {
+          all: true,
+          needSentence: true,
+          allSubject: true,
+        }).then(({ questionTypes, courseModules }) => {
+          return refreshStruct(this, data.tab, data.one, data.two, {
+            all: true,
+            needPreview: true,
+            needTextbook: false,
+          }).then(({ structIds, latest, year }) => {
+            My.listReport(
+              Object.assign(
+                { module: data.tab, questionTypes, structIds, latest, year, courseModules, startTime, endTime },
+                this.state.search,
+                {
+                  order: Object.keys(data.sortMap)
+                    .map(key => {
+                      return `${key} ${data.sortMap[key]}`;
+                    })
+                    .join(','),
+                },
+              ),
+            ).then(result => {
+              result.list = result.list.map(row => {
+                row.questionTypes = row.questionTypes || [];
+                row.reports = row.reports || [];
+                row.stat = row.stat || {};
+                row.reports.forEach(r => {
+                  r.stat = row.stat;
                 });
+                row.children = [];
+                row.show = [];
+                return row;
               });
+              this.setState({
+                list: result.list,
+                total: result.total,
+                page: data.page,
+                columns: data.tab === 'exercise' ? this.exerciseColumns : this.examinationColumns,
+              });
+            });
           });
+        });
+    }
+  }
+
+  onChangeShowHistory(index, childIndex, checked) {
+    const { list } = this.state;
+    const item = list[index];
+    const { reports } = item;
+    const { children } = item;
+    const show = children.map(row => row.id);
+    const childId = reports[childIndex].id;
+    // 第一个不允许切换
+    if (checked) {
+      if (show.indexOf(childId) >= 0) return;
+      children.push(reports[childIndex]);
+    } else {
+      const k = show.indexOf(childId);
+      if (k < 0) return;
+      children.splice(k, 1);
     }
+    list[index].children = children;
+    list[index].show = children.map(row => row.id);
+    this.setState({ list });
   }
 
   onTabChange(tab) {
@@ -384,36 +590,51 @@ export default class extends Page {
   }
 
   renderTable() {
-    const { tab, questionSubjectSelect, questionSubjectMap = {}, oneSelect, twoSelectMap = {}, filterMap = {}, sortMap = {}, list = [], columns = [] } = this.state;
-    const { page, total } = this.state;
+    const {
+      tab,
+      questionSubjectSelect,
+      questionSubjectMap = {},
+      oneSelect,
+      twoSelectMap = {},
+      filterMap = {},
+      sortMap = {},
+      list = [],
+    } = this.state;
+    const { page, total, columns } = this.state;
     const selectList = [];
     if (tab === 'exercise') {
       selectList.push({
-        children: [{
-          key: 'subject',
-          placeholder: '学科',
-          select: questionSubjectSelect,
-        }, {
-          placeholder: '题型',
-          key: 'questionType',
-          be: 'subject',
-          selectMap: questionSubjectMap,
-        }],
+        children: [
+          {
+            key: 'subject',
+            placeholder: '学科',
+            select: questionSubjectSelect,
+          },
+          {
+            placeholder: '题型',
+            key: 'questionType',
+            be: 'subject',
+            selectMap: questionSubjectMap,
+          },
+        ],
       });
     }
     if (tab === 'exercise' || tab === 'examination') {
       selectList.push({
         label: '范围',
-        children: [{
-          key: 'one',
-          placeholder: '全部',
-          select: oneSelect,
-        }, {
-          key: 'two',
-          be: 'one',
-          placeholder: '全部',
-          selectMap: twoSelectMap,
-        }],
+        children: [
+          {
+            key: 'one',
+            placeholder: '全部',
+            select: oneSelect,
+          },
+          {
+            key: 'two',
+            be: 'one',
+            placeholder: '全部',
+            selectMap: twoSelectMap,
+          },
+        ],
       });
     }
     selectList.push({
@@ -431,7 +652,12 @@ export default class extends Page {
           space={2.5}
           width={100}
           active={tab}
-          tabs={[{ key: 'exercise', title: '练习' }, { key: 'examination', title: '模考' }, { key: 'error', title: '错误组卷' }, { key: 'collect', title: '收藏组卷' }]}
+          tabs={[
+            { key: 'exercise', title: '练习' },
+            { key: 'examination', title: '模考' },
+            { key: 'error', title: '错误组卷' },
+            { key: 'collect', title: '收藏组卷' },
+          ]}
           onChange={key => this.onTabChange(key)}
         />
         <UserAction
@@ -444,6 +670,7 @@ export default class extends Page {
           onSearch={value => this.onSearch(value)}
         />
         <UserTable
+          even="children"
           columns={columns}
           sortMap={sortMap}
           data={list}

+ 1 - 0
server/gateway-api/src/main/java/com/qxgmat/service/UserNoteQuestionService.java

@@ -181,6 +181,7 @@ public class UserNoteQuestionService extends AbstractService {
             if (note.getQaContent() != null && !note.getQaContent().equals(in.getQaContent())){
                 note.setQaTime(now);
             }
+            // 如果所有内容为空,则删除
             return edit(note);
         }
     }

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserReportService.java

@@ -85,7 +85,7 @@ public class UserReportService extends AbstractService {
                 example.createCriteria()
                         .andIn("paperId", paperIds)
         );
-        example.orderBy("id").asc();
+        example.orderBy("id").desc();
         List<UserReport> userClassList = select(userReportMapper, example);
         if(userClassList.size() == 0) return relationMap;
         for(UserReport row: userClassList){