page.js 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377
  1. import React, { Component } from 'react';
  2. import { Link } from 'react-router-dom';
  3. import './index.less';
  4. import { Icon } from 'antd';
  5. import FileUpload from '@src/components/FileUpload';
  6. import Page from '@src/containers/Page';
  7. import Assets from '@src/components/Assets';
  8. import { asyncSMessage } from '@src/services/AsyncTools';
  9. import { formatDate, formatSeconds, formatPercent } from '@src/services/Tools';
  10. import UserLayout from '../../../layouts/User';
  11. import Button from '../../../components/Button';
  12. import ProgressText from '../../../components/ProgressText';
  13. import { Icon as GIcon } from '../../../components/Icon';
  14. import menu from '../index';
  15. import Tabs from '../../../components/Tabs';
  16. import UserTable from '../../../components/UserTable';
  17. import More from '../../../components/More';
  18. import Modal from '../../../components/Modal';
  19. import DatePlane from '../../../components/Date';
  20. import Note from '../../../components/Note';
  21. import { My } from '../../../stores/my';
  22. import { Order } from '../../../stores/order';
  23. import { Common } from '../../../stores/common';
  24. export default class extends Page {
  25. initState() {
  26. return {
  27. tab: 'online',
  28. status: 'all',
  29. };
  30. }
  31. formatRecord(row) {
  32. if (row.useEndTime) {
  33. if (row.isSuspend && !row.restoreTime) {
  34. row.status = 'suspend';
  35. } else if (new Date(row.useEndTime).getTime() < new Date().getTime()) {
  36. row.status = 'end';
  37. } else {
  38. row.status = 'ing';
  39. }
  40. } else {
  41. row.status = 'not';
  42. }
  43. row.paperMap = {};
  44. row.appointmentPaperMap = {};
  45. if (row.papers) {
  46. row.papers.forEach(paper => {
  47. if (paper.courseNo) row.paperMap[paper.courseNo] = paper;
  48. if (paper.appointment) row.appointmentPaperMap[paper.appointment] = paper;
  49. });
  50. }
  51. row.progressMap = {};
  52. if (row.progress) {
  53. row.progress.forEach(progress => {
  54. row.progressMap[progress.courseNoId] = progress;
  55. });
  56. }
  57. row.courseNoMap = {};
  58. row.courseTime = 0;
  59. if (row.courseNos) {
  60. row.courseNos.forEach(no => {
  61. row.courseNoMap[no.id] = no;
  62. row.courseTime += no.time;
  63. no.paper = row.paperMap[no.id];
  64. no.progress = row.progressMap[no.id];
  65. });
  66. }
  67. if (row.currentNo) {
  68. row.currentCourseNo = row.courseNoMap[row.currentNo];
  69. } else {
  70. row.currentNo = 0;
  71. }
  72. // 如果已经最新预约结束,则添加一个空记录作为最新预约
  73. if (row.appointments) {
  74. row.appointments.forEach(r => {
  75. r.paper = row.appointmentPaperMap[r.id];
  76. r.noteList = (row.comments || []).filter(c => c.type === 'note' && c.appointmentId === r.id);
  77. r.supplyList = (row.comments || []).filter(c => c.type === 'supply' && c.appointmentId === r.id);
  78. });
  79. // 是否是最后一课时,是否过预约时间
  80. const last = row.appointments.length - 1;
  81. const appointment = row.appointments[last];
  82. if (new Date(appointment.endTime).getTime() < new Date().getTime()) {
  83. // row.status = 'end';
  84. if (row.number !== row.appointments.length) {
  85. row.appointments.push([{}]);
  86. }
  87. }
  88. }
  89. row.days = new Date(row.userEndTime);
  90. return row;
  91. }
  92. initData() {
  93. const data = Object.assign(this.state, this.state.search);
  94. data.filterMap = this.state.search;
  95. if (data.order) {
  96. data.sortMap = { [data.order]: data.direction };
  97. }
  98. this.setState(data);
  99. let isUsed = null;
  100. let isEnd = null;
  101. switch (data.status) {
  102. case 'learning':
  103. isUsed = true;
  104. isEnd = false;
  105. break;
  106. case 'end':
  107. isUsed = true;
  108. isEnd = true;
  109. break;
  110. case 'nouse':
  111. isUsed = false;
  112. isEnd = false;
  113. break;
  114. case 'all':
  115. default:
  116. }
  117. My.listCourse(Object.assign({ courseModule: data.tab }, this.state.search, { isUsed, isEnd })).then(result => {
  118. result.list = result.list.map(row => {
  119. return this.formatRecord(row);
  120. });
  121. this.setState({ list: result.list, total: result.total, page: data.page });
  122. });
  123. }
  124. onAction() { }
  125. onTabChange(tab) {
  126. const data = { tab };
  127. this.refreshQuery(data);
  128. }
  129. onStatusChange(status) {
  130. this.search({ status });
  131. }
  132. openTime(record) {
  133. if (record.courseTimeMap) {
  134. this.setState({ data: record, showTime: true });
  135. return;
  136. }
  137. My.timeCourse(record.id).then(result => {
  138. const courseMap = {};
  139. const previewMap = {};
  140. const stopMap = {};
  141. result.forEach((row) => {
  142. const date = formatDate(row.day, 'YYYY-MM-DD');
  143. if (row.type === 'stop') {
  144. stopMap[date] = true;
  145. } else if (row.type === 'preview') {
  146. previewMap[date] = true;
  147. } else if (row.type === 'course') {
  148. courseMap[date] = true;
  149. }
  150. });
  151. record.courseTimeMap = courseMap;
  152. record.previewTimeMap = previewMap;
  153. record.stopTimeMap = stopMap;
  154. this.setState({ data: record, showTime: true });
  155. });
  156. }
  157. refreshDetail(recordId) {
  158. My.detailCourse(recordId)
  159. .then(result => {
  160. let { list } = this.state;
  161. list = list.map(row => {
  162. if (row.id === recordId) return this.formatRecord(result);
  163. return row;
  164. });
  165. this.setState({ list });
  166. });
  167. }
  168. suspend() {
  169. const { suspend } = this.state;
  170. My.suspendCourse(suspend.id).then(() => {
  171. asyncSMessage('停课成功');
  172. this.setState({ showSuspend: false });
  173. this.refreshDetail(suspend.id);
  174. })
  175. .catch(err => {
  176. asyncSMessage(err.message, 'error');
  177. });
  178. }
  179. restore() {
  180. const { restore } = this.state;
  181. My.restoreCourse(restore.id).then(() => {
  182. asyncSMessage('恢复成功');
  183. this.setState({ showRestore: false });
  184. this.refreshDetail(restore.id);
  185. })
  186. .catch(err => {
  187. asyncSMessage(err.message, 'error');
  188. });
  189. }
  190. submitAppointmentComment(data, type) {
  191. data.type = type;
  192. if (data.id) {
  193. My.editAppointmentComment(data)
  194. .then(() => {
  195. this.refreshDetail(data.recordId);
  196. });
  197. } else {
  198. My.addAppointmentComment(data)
  199. .then(() => {
  200. this.refreshDetail(data.recordId);
  201. });
  202. }
  203. }
  204. deleteAppointmentComment(row) {
  205. My.delAppointmentComment(row.id).then(() => {
  206. this.refreshDetail(row.recordId);
  207. });
  208. }
  209. submitQuestionFile(data) {
  210. My.uploadAppointmentQuestion(data).then(() => {
  211. this.refreshDetail(data.recordId);
  212. });
  213. }
  214. setCCTalkName(recordId, cctalkname) {
  215. My.setCCTalkName(recordId, cctalkname).then(() => {
  216. this.refreshDetail(recordId);
  217. });
  218. }
  219. submitComment() {
  220. const { comment } = this.state;
  221. My.addComment(comment.channel, comment.position, comment.content).then(() => {
  222. this.setState({ showComment: false, showFinish: true, comment: {} });
  223. });
  224. }
  225. open(recordId) {
  226. Order.useRecord(recordId).then(() => {
  227. asyncSMessage('开通成功');
  228. this.refreshDetail(recordId);
  229. });
  230. }
  231. closeCommentTips(recordId) {
  232. My.courseCommentTips(recordId);
  233. }
  234. renderView() {
  235. const { config } = this.props;
  236. return <UserLayout active={config.key} menu={menu} center={this.renderDetail()} />;
  237. }
  238. renderDetail() {
  239. const { tab, status, showTips, showTime, showSuspend, showRestore, showUploadNote, showUploadSupply, showSupply, showNote, showComment, showFinish, comment = {}, data = {}, note = {}, supply = {}, appointment = {} } = this.state;
  240. return (
  241. <div className="detail-layout">
  242. <div hidden={!showTips} className="tip">
  243. <div className="text">理清备考思路,避开常见误区,让学习的每一分钟发光发热!</div>
  244. <a href="" target="_blank">去填写 ></a>
  245. <Icon type="close-circle" theme="filled" onClick={() => this.setState({ showTips: false })} />
  246. </div>
  247. <Tabs
  248. type="division"
  249. theme="theme"
  250. size="small"
  251. space={2.5}
  252. width={100}
  253. active={tab}
  254. tabs={[{ key: 'online', title: '在线课程' }, { key: 'vs', title: '1V1私教' }]}
  255. onChange={key => this.onTabChange(key)}
  256. />
  257. <Tabs
  258. type="tag"
  259. theme="white"
  260. size="small"
  261. space={5}
  262. width={54}
  263. active={status}
  264. tabs={[
  265. { key: 'all', title: '全部' },
  266. { key: 'nouse', title: '未开通' },
  267. { key: 'learning', title: '学习中' },
  268. { key: 'end', title: '已结束' },
  269. ]}
  270. onChange={key => this.onStatusChange(key)}
  271. />
  272. {this[`renderTab${tab}`]()}
  273. <Modal show={showTime} className="clock-modal" title="打卡表" width={460} onClose={() => this.setState({ showTime: false })}>
  274. <div>
  275. <div className="d-i-b w-3">
  276. <div className="t-2 t-s-14">听课频率</div>
  277. <div className="t-4 t-s-18">{data.currentNo ? data.totalDays / data.currentNo : 0}天/课时</div>
  278. </div>
  279. <div className="d-i-b w-3">
  280. <div className="t-2 t-s-14">作业完成度</div>
  281. <div className="t-4 t-s-18">{data.previewProgress || 0}%</div>
  282. </div>
  283. </div>
  284. <div className="b-b m-t-1 m-b-1" />
  285. <div className="m-b-1">
  286. <div className="tag d-i-b t-s-16">
  287. <i style={{ background: '#6865FD' }} />
  288. 听课
  289. </div>
  290. <div className="tag d-i-b t-s-16">
  291. <i style={{ background: '#4299FF' }} />
  292. 预习
  293. </div>
  294. <div className="tag d-i-b t-s-16">
  295. <i style={{ background: '#E7E7E7' }} />
  296. 停课
  297. </div>
  298. </div>
  299. <DatePlane
  300. hideInput
  301. show={showTime}
  302. onChange={() => { }}
  303. disabledDate={(current) => {
  304. const date = current.format('YYYY-MM-DD');
  305. return data.stopTimeMap[date];
  306. }}
  307. dateRender={current => {
  308. const date = current.format('YYYY-MM-DD');
  309. return (
  310. <div className="ant-calendar-date">
  311. {current.get('date')}
  312. {data.courseTimeMap[date] && < i className="s1" style={{ background: '#6865FD' }} />}
  313. {data.previewTimeMap[date] && <i className="s2" style={{ background: '#4299FF' }} />}
  314. </div>
  315. );
  316. }}
  317. checkRefresh={(date, refresh) => {
  318. setTimeout(() => {
  319. refresh();
  320. }, 2000);
  321. return true;
  322. }}
  323. />
  324. <div className="t-2 t-s-12">*听课频率≤2天/课时,作业完成度≥90%,课程有效期可延长7-10天。</div>
  325. </Modal>
  326. <Modal
  327. show={showComment}
  328. title="评价"
  329. onConfirm={() => comment.content && this.submitComment()}
  330. onCancel={() => this.setState({ showComment: false, comment: {} })}
  331. >
  332. <textarea
  333. value={comment.content}
  334. className="b-c-1 w-10 p-10"
  335. rows={6}
  336. placeholder="您的看法对我们来说很重要!"
  337. onChange={e => {
  338. comment.content = e.target.value;
  339. this.setState({ comment });
  340. }}
  341. />
  342. <div className="b-b m-t-2" />
  343. </Modal>
  344. <Modal
  345. show={showFinish}
  346. title="提交成功"
  347. confirmText="好的,知道了"
  348. btnAlign="center"
  349. onConfirm={() => this.setState({ showFinish: false })}
  350. >
  351. <div className="t-2 t-s-18">
  352. <Icon type="check" className="t-5 m-r-5" />
  353. 您的每一次反馈都是千行进步的动力。
  354. </div>
  355. </Modal>
  356. <Modal show={showSuspend} title="申请停课" onConfirm={() => this.suspend()} onCancel={() => this.setState({ showSuspend: false })}>
  357. <div className="t-2 t-s-18">最长停课时间为1个月,截止日期将顺延。</div>
  358. <div className="t-2 t-s-18">停课不影响学习频率计算。</div>
  359. <div className="t-2 t-s-18">停课期间可随时恢复学习,</div>
  360. <div className="t-2 t-s-18">每门课程只有一次申请机会,是否需要停课?</div>
  361. </Modal>
  362. <Modal show={showRestore} title="恢复学习" onConfirm={() => this.restore()} onCancel={() => this.setState({ showRestore: false })}>
  363. <div className="t-2 t-s-18">恢复学习后,本课程的停课功能将无法使用。是否恢复学习?</div>
  364. </Modal>
  365. <Modal
  366. show={showUploadNote}
  367. title="上传笔记"
  368. width={630}
  369. confirmText="提交"
  370. onConfirm={() => this.submitAppointmentComment(note, 'note')}
  371. onCancel={() => this.setState({ showUploadNote: false, note: {} })}
  372. >
  373. <textarea
  374. className="b-c-1 w-10 p-10 m-b-1"
  375. rows={6}
  376. value={note.content}
  377. placeholder="请输入留言内容。"
  378. onChange={e => {
  379. note.content = e.target.value;
  380. this.setState({ note });
  381. }}
  382. />
  383. <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}>
  384. <Button theme="file">上传文件</Button>
  385. <span className="m-l-1 t-3 t-s-14">{note.file ? `上传文件:${note.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}</span>
  386. <div className="fixed">
  387. <span className="f-w-b t-s-18">放开文件立刻上传</span>
  388. </div>
  389. <FileUpload
  390. type="none"
  391. onDragEnter={() => this.setState({ draging: true })}
  392. onDragLeave={() => this.setState({ draging: false })}
  393. onUpload={({ file }) => Common.upload(file).then(result => {
  394. note.file = result.url;
  395. note.name = file.name;
  396. this.setState({ note });
  397. })}
  398. />
  399. </div>
  400. <div className="b-b m-t-2" />
  401. </Modal>
  402. <Modal show={showNote} title="课后笔记" width={500} height={500} onClose={() => this.setState({ showNote: false })}>
  403. {appointment.noteList && appointment.noteList.map(row => {
  404. return <Note user={this.props.user} teacher={data.teacher} data={row} />;
  405. })}
  406. </Modal>
  407. <Modal show={showSupply} title="课后留言" width={500} height={500} onClose={() => this.setState({ showSupply: false })}>
  408. {appointment.supplyList && appointment.supplyList.map(row => {
  409. return <Note user={this.props.user} teacher={data.teacher} data={row} />;
  410. })}
  411. </Modal>
  412. <Modal show={showUploadSupply} title="上传留言"
  413. width={630}
  414. confirmText="提交" onConfirm={() => this.submitAppointmentComment(supply, 'supply')} onCancel={() => this.setState({ showUploadSupply: false, supply: {} })}>
  415. <textarea
  416. className="b-c-1 w-10 p-10 m-b-1"
  417. rows={6}
  418. value={supply.content}
  419. placeholder="请输入留言内容。"
  420. onChange={e => {
  421. supply.content = e.target.value;
  422. this.setState({ supply });
  423. }}
  424. />
  425. <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}>
  426. <Button theme="file">上传文件</Button>
  427. <span className="m-l-1 t-3 t-s-14">{supply.file ? `上传文件:${supply.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}</span>
  428. <div className="fixed">
  429. <span className="f-w-b t-s-18">放开文件立刻上传</span>
  430. </div>
  431. <FileUpload
  432. type="none"
  433. onDragEnter={() => this.setState({ draging: true })}
  434. onDragLeave={() => this.setState({ draging: false })}
  435. onUpload={({ file }) => Common.upload(file).then(result => {
  436. supply.file = result.url;
  437. supply.name = file.name;
  438. this.setState({ supply });
  439. })}
  440. />
  441. </div>
  442. <div className="b-b m-t-2" />
  443. </Modal>
  444. </div>
  445. );
  446. }
  447. renderTabonline() {
  448. const { list = [] } = this.state;
  449. return list.map(item => {
  450. return <CourseOnline data={item} user={this.props.user} refreshDetail={(recordId) => {
  451. this.refreshDetail(recordId);
  452. }} onRestore={() => {
  453. this.setState({ showRestore: true, restore: item });
  454. }} onSuspend={() => {
  455. this.setState({ showSuspend: true, suspend: item });
  456. }} onTime={() => {
  457. this.openTime(item);
  458. }} onComment={() => {
  459. this.setState({ showComment: true, comment: { channel: 'course-video', position: item.course.id } });
  460. }} closeCommentTips={() => this.closeCommentTips(item.id)} />;
  461. });
  462. }
  463. renderTabvs() {
  464. const { list = [] } = this.state;
  465. return list.map(item => {
  466. return <CourseVs data={item} user={this.props.user} refreshDetail={(recordId) => {
  467. this.refreshDetail(recordId);
  468. }} onRestore={() => {
  469. this.setState({ showRestore: true, restore: item });
  470. }} onSuspend={() => {
  471. this.setState({ showSuspend: true, suspend: item });
  472. }} onComment={() => {
  473. this.setState({ showComment: true, comment: { channel: 'course-vs', position: item.course.id } });
  474. }} closeCommentTips={() => this.closeCommentTips(item.id)}
  475. onUploadNote={(appointment, row) => this.setState({ showUploadNote: true, appointment, data: item, note: row || {} })}
  476. onUploadSupply={(appointment, row) => this.setState({ showUploadSupply: true, appointment, data: item, supply: row || {} })}
  477. onDeleteNote={(appointment, row) => this.deleteAppointmentComment(row)}
  478. onDeleteSupply={(appointment, row) => this.deleteAppointmentComment(row)}
  479. onNote={(appointment) => this.setState({ showNote: true, appointment, data: item })}
  480. onSupply={(appointment) => this.setState({ showSupply: true, appointment, data: item })}
  481. onUploadQuestion={(appointment, file, name) => this.submitQuestionFile({ id: appointment.id, recordId: appointment.recordId, questionFile: file, questionFileName: name })}
  482. setCCTalkName={(appointment, cctalkName) => this.setCCTalkName(appointment.recordId, cctalkName)} />;
  483. });
  484. }
  485. }
  486. class CourseOnline extends Component {
  487. constructor(props) {
  488. super(props);
  489. this.columns = [
  490. {
  491. title: '学习内容',
  492. key: 'title',
  493. render: (text, record) => {
  494. return `课时 ${record.no}: ${text}`;
  495. },
  496. },
  497. {
  498. title: '预习作业',
  499. key: 'paper',
  500. render: (text, record) => {
  501. const { paper = {} } = record;
  502. return `${paper.paper && paper.paper.times > 0 ? `${paper.paper.times}次+` : ''}${paper.report ? formatPercent(paper.report.userNumber, paper.report.questionNumber, false) : '0%'}`;
  503. },
  504. },
  505. {
  506. title: '进度',
  507. key: 'progress',
  508. render: (text, record) => {
  509. return `${record.progress && record.progress.times > 0 ? `${record.progress.times}次+` : ''}${record.progress ? record.progress.progress : 0}%`;
  510. },
  511. },
  512. {
  513. title: '最近学习',
  514. key: 'lastTime',
  515. render: (text, record) => {
  516. const { paper = {} } = record;
  517. return paper.report && formatDate(paper.report.updateTime, 'YYYY-MM-DD HH:mm:ss');
  518. },
  519. },
  520. {
  521. title: '笔记',
  522. key: 'note',
  523. render: (text) => {
  524. return text;
  525. },
  526. },
  527. {
  528. title: '问答',
  529. key: 'ask',
  530. render: (text, record) => {
  531. return <Link to={`/course/ask/${record.courseId}?no=${record.id}`}>{`${record.answerNumber || 0}/${record.askNumber || 0}`}</Link>;
  532. },
  533. },
  534. ];
  535. this.state = { open: false };
  536. }
  537. render() {
  538. const { data = {} } = this.props;
  539. switch (data.status) {
  540. case 'ing':
  541. return this.renderIng();
  542. case 'not':
  543. return this.renderNot();
  544. case 'end':
  545. return this.renderEnd();
  546. case 'suspend':
  547. return this.renderSuspend();
  548. default:
  549. return <div />;
  550. }
  551. }
  552. renderIng() {
  553. const { data, onTime, onComment, onSuspend } = this.props;
  554. const { open } = this.state;
  555. return (
  556. <div className="course-item ing">
  557. <div className="title">
  558. <div className="tag">学习中</div>
  559. <div className="text">{data.course.title}</div>
  560. <div className="right">
  561. <More menu={[{ label: '评价', key: 'comment' }, { label: '停课', key: 'suspend' }]}
  562. onClick={value => {
  563. const { key } = value;
  564. if (key === 'comment') {
  565. onComment();
  566. } else if (key === 'suspend') {
  567. onSuspend();
  568. }
  569. }} />
  570. </div>
  571. </div>
  572. {data.currentCourseNo && <div className="continue" onClick={() => {
  573. linkTo(`/course/detail/${data.course.id}`);
  574. }}>
  575. <Assets name="notice" />
  576. 继续学习:课时 {data.currentNo}:{data.currentCourseNo.title}
  577. </div>}
  578. <div className="detail">
  579. <div className="left">
  580. <Assets name="sun_blue" src={data.course.cover} />
  581. <div className="info">
  582. <div className="t1">授课老师</div>
  583. <div className="t2">{data.course.teacher}</div>
  584. <div className="t1">有效期</div>
  585. <div className="t2">{data.useExpireDays}Days</div>
  586. </div>
  587. </div>
  588. <div className="right">
  589. <div className="item">
  590. <GIcon name="speed-block" active noHover />
  591. <div className="text">
  592. <span>{data.currentNo}</span>/{data.courseNos.length}
  593. </div>
  594. </div>
  595. <div className="item">
  596. <GIcon name="question-block" active noHover />
  597. <div className="text">
  598. <span>{data.answerNumber}</span>/{data.askNumber}
  599. </div>
  600. </div>
  601. <div className="item" onClick={() => onTime()}>
  602. <GIcon name="clockin-block" active noHover />
  603. <div className="text">
  604. <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}%
  605. </div>
  606. </div>
  607. <div className="item">
  608. <GIcon name="note-block" active noHover />
  609. <div className="text">
  610. {data.noteNumber}
  611. </div>
  612. </div>
  613. </div>
  614. <div className="open">
  615. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  616. </div>
  617. </div>
  618. {open && this.renderTable()}
  619. </div>
  620. );
  621. }
  622. renderNot() {
  623. const { data } = this.props;
  624. return (
  625. <div className="course-item not">
  626. <div className="title">
  627. <div className="tag">未开通</div>
  628. <div className="text">{data.course.title}</div>
  629. </div>
  630. <div className="detail">
  631. <div className="left">
  632. <Assets name="sun_blue" src={data.course.cover} />
  633. <div className="info">
  634. <div className="t1">授课老师</div>
  635. <div className="t2">{data.course.teacher}</div>
  636. <div>
  637. <div className='d-i-b m-r-2'>
  638. <div className='t-2'>课时</div>
  639. <div className='t-1 t-s-16'>{data.courseNos.length}</div>
  640. </div>
  641. <div className='d-i-b'>
  642. <div className='t-2'>总时长</div>
  643. <div className='t-1 t-s-16'>{formatSeconds(data.courseTime)}</div>
  644. </div>
  645. </div>
  646. </div>
  647. </div>
  648. <div className="right t-c">
  649. <div className="text">请于 {formatDate(data.endTime, 'YYYY-MM-DD')} 前开通</div>
  650. <Button size="lager" radius onClick={() => {
  651. this.open(data.id);
  652. }}>
  653. 立即开通
  654. </Button>
  655. </div>
  656. </div>
  657. </div>
  658. );
  659. }
  660. renderEnd() {
  661. const { data, onTime, onComment } = this.props;
  662. const { open } = this.state;
  663. return (
  664. <div className="course-item end">
  665. <div className="title">
  666. <div className="tag">已结束</div>
  667. <div className="text">{data.course.title}</div>
  668. <div className="right">
  669. <More menu={[{ label: '评价', key: 'comment' }]}
  670. onClick={value => {
  671. const { key } = value;
  672. if (key === 'comment') {
  673. onComment();
  674. }
  675. }} />
  676. </div>
  677. </div>
  678. <div className="detail">
  679. <div className="left">
  680. <Assets name="sun_blue" src={data.course.cover} />
  681. <div className="info">
  682. <div className="t1">授课老师</div>
  683. <div className="t2">{data.course.teacher}</div>
  684. <div className="t1">有效期</div>
  685. <div className="t-s-12">{formatDate(data.useStartTime, 'YYYY-MM-DD')}<br />至{formatDate(data.useEndTime, 'YYYY-MM-DD')}</div>
  686. </div>
  687. </div>
  688. <div className="right">
  689. <div className="item">
  690. <GIcon name="question-block" active noHover />
  691. <div className="text">
  692. <span>{data.answerNumber}</span>/{data.askNumber}
  693. </div>
  694. </div>
  695. <div className="item" onClick={() => onTime()}>
  696. <GIcon name="clockin-block" active noHover />
  697. <div className="text">
  698. <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}%
  699. </div>
  700. </div>
  701. <div className="item">
  702. <GIcon name="note-block" active noHover />
  703. <div className="text">
  704. {data.noteNumber}
  705. </div>
  706. </div>
  707. {data.courseAward > 0 && <div className="item">
  708. <GIcon name="gift-block" active />
  709. <div className="text">赠送{data.courseAward}天</div>
  710. </div>}
  711. </div>
  712. <div className="open">
  713. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  714. </div>
  715. </div>
  716. {open && this.renderTable()}
  717. </div>
  718. );
  719. }
  720. renderSuspend() {
  721. const { data, onTime, onRestore, onComment } = this.props;
  722. const { open } = this.state;
  723. return (
  724. <div className="course-item end">
  725. <div className="title">
  726. <div className="tag">已停课</div>
  727. <div className="text">{data.course.title}<Button size="small" radius onClick={() => onRestore()} >恢复上课</Button></div>
  728. <div className='t-s-12'><span className='t-3'>申请时间:</span><span className='t-2 m-r-1'>{formatDate(data.suspendTime, 'YYYY-MM-DD HH:mm:ss')}</span><span className='t-3'>已停课:</span><span>{parseInt((new Date().getTime() - new Date(data.suspendTime).getTime()) / 86400000, 10)}Days/30</span></div>
  729. <div className="right">
  730. <More menu={[{ label: '评价', key: 'comment' }]}
  731. onClick={value => {
  732. const { key } = value;
  733. if (key === 'comment') {
  734. onComment();
  735. }
  736. }} />
  737. </div>
  738. </div>
  739. <div className="detail">
  740. <div className="left">
  741. <Assets name="sun_blue" src={data.course.cover} />
  742. <div className="info">
  743. <div className="t1">授课老师</div>
  744. <div className="t2">{data.course.teacher}</div>
  745. <div className="t1">有效期</div>
  746. <div className="t2">{data.useExpireDays}Days</div>
  747. </div>
  748. </div>
  749. <div className="right">
  750. <div className="item">
  751. <GIcon name="speed-block" noHover />
  752. <div className="text">
  753. <span>{data.currentNo}</span>/{data.courseNos.length}
  754. </div>
  755. </div>
  756. <div className="item">
  757. <GIcon name="question-block" active noHover />
  758. <div className="text">
  759. <span>{data.answerNumber}</span>/{data.askNumber}
  760. </div>
  761. </div>
  762. <div className="item" onClick={() => onTime()}>
  763. <GIcon name="clockin-block" active noHover />
  764. <div className="text">
  765. <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}%
  766. </div>
  767. </div>
  768. <div className="item">
  769. <GIcon name="note-block" active noHover />
  770. <div className="text">
  771. {data.noteNumber}
  772. </div>
  773. </div>
  774. </div>
  775. <div className="open">
  776. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  777. </div>
  778. </div>
  779. {open && this.renderTable()}
  780. </div>
  781. );
  782. }
  783. renderTable() {
  784. const { data = {} } = this.props;
  785. const { courseNos = [] } = data;
  786. return <UserTable size="small" columns={this.columns} data={courseNos} />;
  787. }
  788. }
  789. const titleMap = {
  790. 1: '预约时间',
  791. 2: '答疑文档',
  792. 3: '上课',
  793. 4: '课后笔记',
  794. 5: '课后补充',
  795. 6: '备考信息',
  796. 7: '完成作业',
  797. };
  798. const iconMap = {
  799. 1: 'time-icon',
  800. 2: 'QA-icon',
  801. 3: 'class-icon',
  802. 4: 'note-icon',
  803. 5: 'supplement-icon',
  804. 6: 'information-icon',
  805. 7: 'homework-icon',
  806. };
  807. const statusMap = {
  808. 1: (appointment) => {
  809. if (!appointment.id) return 'end';
  810. return '';
  811. },
  812. 2: (appointment) => {
  813. if (!appointment.questionFile) return 'not';
  814. return '';
  815. },
  816. 3: (appointment, data) => {
  817. if (!data.cctalkName) return 'not';
  818. // if (new Date(appointment.endTime).getTime() > new Date()) return 'not';
  819. return '';
  820. },
  821. 4: (appointment) => {
  822. if (!appointment.noteList || appointment.noteList.length === 0) return 'not';
  823. return '';
  824. },
  825. 5: (appointment) => {
  826. if (!appointment.supplyList || appointment.supplyList.length === 0) return 'not';
  827. return '';
  828. },
  829. 6: () => {
  830. return '';
  831. },
  832. 7: (appointment) => {
  833. const { paper = {} } = appointment;
  834. if (!paper.report || formatPercent(paper.report.userNumber, paper.report.questionNumber) < 100) return 'not';
  835. return '';
  836. },
  837. };
  838. class CourseVs extends Component {
  839. constructor(props) {
  840. super(props);
  841. this.columns = {
  842. system: [
  843. { title: '学习内容', key: 'title' },
  844. {
  845. title: '预习作业',
  846. key: 'paper',
  847. render: () => {
  848. },
  849. },
  850. {
  851. title: '授课时间',
  852. key: 'time',
  853. render: (text, record) => {
  854. return `${formatDate(record.startTime, 'YYYY-MM-DD HH:mm:ss')} ~ ${formatDate(record.endTime, 'HH:mm:ss')}`;
  855. },
  856. },
  857. {
  858. title: '课后笔记',
  859. key: 'note',
  860. render: (text, record) => {
  861. return <a onClick={() => this.props.onNote(record)}>查看</a>;
  862. },
  863. },
  864. {
  865. title: '课后补充',
  866. key: 'supply',
  867. render: (text, record) => {
  868. return <a onClick={() => this.props.onSupply(record)}>查看</a>;
  869. },
  870. },
  871. ],
  872. answer: [
  873. { title: '学习内容', key: 'title' },
  874. {
  875. title: '答疑文档',
  876. key: 'questionFile',
  877. render: (text, record) => {
  878. return <a href={text} target="_blank">{record.questionFileName}</a>;
  879. },
  880. },
  881. {
  882. title: '授课时间',
  883. key: 'time',
  884. render: (text, record) => {
  885. return `${formatDate(record.startTime, 'YYYY-MM-DD HH:mm:ss')} ~ ${formatDate(record.endTime, 'HH:mm:ss')}`;
  886. },
  887. },
  888. {
  889. title: '课后补充',
  890. key: 'supply',
  891. render: (text, record) => {
  892. return <a onClick={() => this.props.onSupply(record)}>查看</a>;
  893. },
  894. },
  895. ],
  896. };
  897. this.listMap = {
  898. novice: [1, 3],
  899. coach: [1, 6, 3],
  900. system: [1, 7, 3, 4],
  901. answer: [1, 2, 3],
  902. };
  903. const index = props.data.appointments.length - 1;
  904. this.state = { open: false, tab: 'ing', index, list: this.listMap[this.props.data.course.vsType], showTips: props.data.commentTips === 0 && (props.data.appointments.length === parseInt(props.data.number / 2, 10) || props.data.appointments.length === props.data.number) };
  905. }
  906. closeCommentTips() {
  907. My.courseCommentTips(this.props.data.id)
  908. .then(() => {
  909. this.setState({ showTips: false });
  910. });
  911. }
  912. render() {
  913. const { data = {} } = this.props;
  914. switch (data.status) {
  915. case 'ing':
  916. return this.renderIng();
  917. case 'not':
  918. return this.renderNot();
  919. case 'end':
  920. return this.renderEnd();
  921. case 'suspend':
  922. return this.renderSuspend();
  923. default:
  924. return <div />;
  925. }
  926. }
  927. renderIng() {
  928. const { data, onComment, closeCommentTips } = this.props;
  929. const { tab, showTips, open } = this.state;
  930. return (
  931. <div className="education-item ing">
  932. <div className="title">
  933. <div className="tag">学习中</div>
  934. <div className="text">{data.course.title}{data.vsNo > 0 ? `V${data.vsNo}` : ''}{data.number > 0 ? `(${data.number}课时)` : ''}</div>
  935. <div className="right">
  936. <More menu={[{ label: '评价', key: 'comment' }, { label: '停课', key: 'suspend' }]}
  937. onClick={value => {
  938. const { key } = value;
  939. if (key === 'comment') {
  940. onComment();
  941. }
  942. }} />
  943. </div>
  944. </div>
  945. {showTips && <div className="continue">
  946. <Icon className='close m-r-5 t-3' type="close-circle" theme="filled" onClick={() => closeCommentTips()} />
  947. 课程已过半,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
  948. </div>}
  949. <div className="detail">
  950. <div className="left">
  951. <Assets name="sun_blue" src={data.course.cover} />
  952. <div className="info">
  953. <div className="t1">授课老师</div>
  954. <div className="t2">{data.teacher.realname}</div>
  955. <div className="t1">有效期</div>
  956. <div className="t2">{data.useExpireDays}Days</div>
  957. </div>
  958. </div>
  959. <div className="right">
  960. <div className="item">
  961. <GIcon name="speed-block" active noHover />
  962. <div className="text">
  963. <span>{data.appointments.length}</span>/{data.number}
  964. </div>
  965. </div>
  966. <div className="item">
  967. <GIcon name="time-block" active noHover />
  968. <div className="text">
  969. <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时
  970. </div>
  971. </div>
  972. </div>
  973. <div className="open">
  974. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  975. </div>
  976. </div>
  977. {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && <Tabs
  978. className="t-l"
  979. type="line"
  980. theme="theme"
  981. size="small"
  982. width={80}
  983. active={tab}
  984. tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
  985. onChange={key => this.setState({ tab: key })}
  986. />}
  987. {open && (tab === 'ing' ? this.renderTimeLine() : this.renderTable())}
  988. </div>
  989. );
  990. }
  991. renderNot() {
  992. const { data } = this.props;
  993. return (
  994. <div className="education-item not">
  995. <div className="title">
  996. <div className="tag">未开通</div>
  997. <div className="text">{data.course.title}{data.vsNo > 0 ? `V${data.vsNo}` : ''}{data.number > 0 ? `(${data.number}课时)` : ''}</div>
  998. </div>
  999. <div className="detail">
  1000. <div className="left">
  1001. <Assets name="sun_blue" src={data.course.cover} />
  1002. <div className="info">
  1003. <div className="t1">授课老师</div>
  1004. <div className="t2">{data.teacher.realname}</div>
  1005. <div>
  1006. <div className='d-i-b m-r-2'>
  1007. <div className='t-2'>课时</div>
  1008. <div className='t-1 t-s-16'>{data.number}</div>
  1009. </div>
  1010. <div className='d-i-b'>
  1011. <div className='t-2'>总时长</div>
  1012. <div className='t-1 t-s-16'>{data.number}Hours</div>
  1013. </div>
  1014. </div>
  1015. </div>
  1016. </div>
  1017. <div className="right">
  1018. <div className="qr-code">
  1019. <Assets name="qrcode" src={data.teacher.qr} />
  1020. <div className="i">
  1021. <div className="t1">请尽快与老师预约上课时间</div>
  1022. <div className="t2">请于 {formatDate(data.endTime, 'YYYY-MM-DD')} 前开通课程</div>
  1023. </div>
  1024. </div>
  1025. </div>
  1026. </div>
  1027. {/* {this.renderTimeLine()} */}
  1028. </div>
  1029. );
  1030. }
  1031. renderEnd() {
  1032. const { data, onComment, closeCommentTips } = this.props;
  1033. const { open, tab, showTips } = this.state;
  1034. return (
  1035. <div className="education-item end">
  1036. <div className="title">
  1037. <div className="tag">已结课</div>
  1038. <div className="text">{data.course.title}{data.vsNo > 0 ? `V${data.vsNo}` : ''}{data.number > 0 ? `(${data.number}课时)` : ''}</div>
  1039. <div className="right">
  1040. <More menu={[{ label: '评价', key: 'comment' }]}
  1041. onClick={value => {
  1042. const { key } = value;
  1043. if (key === 'comment') {
  1044. onComment();
  1045. }
  1046. }} />
  1047. </div>
  1048. </div>
  1049. {showTips && <div className="continue">
  1050. <Icon className='close m-r-5 t-3' type="close-circle" theme="filled" onClick={() => closeCommentTips()} />
  1051. 课程已结束,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
  1052. </div>}
  1053. <div className="detail">
  1054. <div className="left">
  1055. <Assets name="sun_blue" src={data.course.cover} />
  1056. <div className="info">
  1057. <div className="t1">授课老师</div>
  1058. <div className="t2">{data.teacher.realname}</div>
  1059. <div className="t1">有效期</div>
  1060. <div className="t2">{data.useExpireDays}Days</div>
  1061. </div>
  1062. </div>
  1063. <div className="right">
  1064. <div className="item">
  1065. <GIcon name="speed-block" active noHover />
  1066. <div className="text">
  1067. <span>{data.appointments.length}</span>/{data.number}
  1068. </div>
  1069. </div>
  1070. <div className="item">
  1071. <GIcon name="time-block" active noHover />
  1072. <div className="text">
  1073. <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时
  1074. </div>
  1075. </div>
  1076. </div>
  1077. <div className="open">
  1078. <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
  1079. </div>
  1080. </div>
  1081. {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && <Tabs
  1082. className="t-l"
  1083. type="line"
  1084. theme="theme"
  1085. size="small"
  1086. width={80}
  1087. active={tab}
  1088. tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
  1089. onChange={key => this.setState({ tab: key })}
  1090. />}
  1091. {open && (tab === 'ing' ? this.renderTimeLine() : this.renderTable())}
  1092. </div>
  1093. );
  1094. }
  1095. renderSuspend() {
  1096. const { data, onRestore, onComment } = this.props;
  1097. return (
  1098. <div className="education-item end">
  1099. <div className="title">
  1100. <div className="tag">已停课</div>
  1101. <div className="text">{data.course.title}{data.vsNo > 0 ? `V${data.vsNo}` : ''}{data.number > 0 ? `(${data.number}课时)` : ''}<Button size="small" onClick={() => onRestore()} >恢复上课</Button></div>
  1102. <div className='t-s-12'><span className='t-3'>申请时间:</span><span className='t-2 m-r-1'>{formatDate(data.suspendTime, 'YYYY-MM-DD HH:mm:ss')}</span><span className='t-3'>已停课:</span><span>{parseInt((new Date().getTime() - new Date(data.suspendTime).getTime()) / 86400000, 10)}Days/30</span></div>
  1103. <div className="right">
  1104. <More menu={[{ label: '评价', key: 'comment' }]}
  1105. onClick={value => {
  1106. const { key } = value;
  1107. if (key === 'comment') {
  1108. onComment();
  1109. }
  1110. }} />
  1111. </div>
  1112. </div>
  1113. <div className="detail">
  1114. <div className="left">
  1115. <Assets name="sun_blue" src={data.course.cover} />
  1116. <div className="info">
  1117. <div className="t1">授课老师</div>
  1118. <div className="t2">{data.teacher.realname}</div>
  1119. <div className="t1">有效期</div>
  1120. <div className="t2">{data.useExpireDays}Days</div>
  1121. </div>
  1122. </div>
  1123. <div className="right">
  1124. <div className="item">
  1125. <GIcon name="speed-block" active noHover />
  1126. <div className="text">
  1127. <span>{data.appointments.length}</span>/{data.number}
  1128. </div>
  1129. </div>
  1130. <div className="item">
  1131. <GIcon name="time-block" active noHover />
  1132. <div className="text">
  1133. <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时
  1134. </div>
  1135. </div>
  1136. </div>
  1137. </div>
  1138. </div>
  1139. );
  1140. }
  1141. renderTimeLine() {
  1142. const { data = {}, onUploadNote, onUploadSupply, onUploadQuestion, setCCTalkName } = this.props;
  1143. const { list = [], index } = this.state;
  1144. const appointment = data.appointments[index] || {};
  1145. let status = '';
  1146. return ([
  1147. <div className="class-hour">
  1148. {data.number > 1 && <div className="text">课时 {appointment.no}:{appointment.title}</div>}
  1149. {data.number > 1 && <div className="right">
  1150. <GIcon name="prev" onClick={() => this.setState({ index: index === 0 ? index : index - 1 })} />
  1151. <span>上一课时</span>
  1152. <span>下一课时</span>
  1153. <GIcon name="next" onClick={() => this.setState({ index: index >= data.appointments.length - 1 ? index : index + 1 })} />
  1154. </div>}
  1155. </div>,
  1156. <div className="time-line">
  1157. {list.map(item => {
  1158. if (status === '') {
  1159. // 上一阶段完成
  1160. status = statusMap[item](appointment, data);
  1161. } else {
  1162. // 上一阶段未完成
  1163. status = 'end';
  1164. }
  1165. return <TimeLineItem type={`${item}`} user={this.props.user} appointment={appointment} data={data} status={status} onUploadNote={onUploadNote} onUploadSupply={onUploadSupply} onUploadQuestion={onUploadQuestion} setCCTalkName={setCCTalkName} />;
  1166. })}
  1167. </div>,
  1168. ]);
  1169. }
  1170. renderTable() {
  1171. const { data = {} } = this.props;
  1172. const { appointments = [] } = data;
  1173. return <UserTable size="small" columns={this.columns[data.course.vsType]} data={appointments.filter(row => row.id)} />;
  1174. }
  1175. }
  1176. class TimeLineItem extends Component {
  1177. constructor(props) {
  1178. super(props);
  1179. this.state = {};
  1180. }
  1181. onClick(key) {
  1182. const { onClick } = this.props;
  1183. const { status } = this.props;
  1184. if (status === 'not') return;
  1185. if (onClick) onClick(key);
  1186. }
  1187. render() {
  1188. const { type } = this.props;
  1189. const { status } = this.props;
  1190. return (
  1191. <div className={`time-line-item ${status}`}>
  1192. <div className="icon-title">
  1193. <GIcon name={iconMap[type]} active={!status} noHover />
  1194. <div className="title">{titleMap[type]}</div>
  1195. </div>
  1196. <div className="time-line-detail">{this.renderDetail()}</div>
  1197. </div>
  1198. );
  1199. }
  1200. renderDetail() {
  1201. const { data = {}, appointment = {}, type, onUploadNote, onUploadSupply, onDeleteNote, onDeleteSupply, onUploadQuestion, setCCTalkName } = this.props;
  1202. const { status } = this.props;
  1203. const { paper } = appointment;
  1204. switch (type) {
  1205. case '1':
  1206. switch (status) {
  1207. case 'end':
  1208. case 'not':
  1209. return <span>请尽快与老师预约上课时间,老师微信:{data.teacher.wechat}扫码加微信</span>;
  1210. default:
  1211. return <span>{formatDate(appointment.startTime, 'YYYY-MM-DD HH:mm:ss')} ~ {formatDate(appointment.endTime, 'HH:mm:ss')}</span>;
  1212. }
  1213. case '2':
  1214. switch (status) {
  1215. case 'end':
  1216. return <span className="link">点此上传</span>;
  1217. case 'not':
  1218. return <FileUpload onUpload={(file) => {
  1219. return Common.upload({ file }).then((result => onUploadQuestion(appointment, result.url, file.name)));
  1220. }}><span className="link">点此上传</span></FileUpload>;
  1221. default:
  1222. return <a href={appointment.questionFile} target="_blank">{appointment.questionFileName || '文件'}</a>;
  1223. }
  1224. case '3':
  1225. switch (status) {
  1226. case 'end':
  1227. return data.cctalkName ? <span>
  1228. CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a>
  1229. </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) => {
  1230. this.setState({ cctalkName: e.target.value });
  1231. }} /><Button size="small" radius disabled>提交</Button></div>;
  1232. case 'not':
  1233. return data.cctalkName ? <span>
  1234. CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a>
  1235. </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) => {
  1236. this.setState({ cctalkName: e.target.value });
  1237. }} /><Button size="small" radius onClick={() => {
  1238. if (this.state.cctalkName) setCCTalkName(appointment, this.state.cctalkName);
  1239. }} >提交</Button></div>;
  1240. default:
  1241. return <span>
  1242. CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a>
  1243. </span>;
  1244. }
  1245. case '4':
  1246. switch (status) {
  1247. case 'end':
  1248. return <span className="link" >点此上传</span>;
  1249. case 'not':
  1250. return <span className="link" onClick={() => onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>点此上传</span>;
  1251. default:
  1252. return (
  1253. <div>
  1254. <div>
  1255. <span className="link" onClick={() => onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>点此上传</span>
  1256. </div>
  1257. <div className="note-list">
  1258. {appointment.noteList.map(row => {
  1259. console.log(row);
  1260. 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) => {
  1261. switch (key) {
  1262. case 'edit':
  1263. onUploadNote(appointment, row);
  1264. break;
  1265. case 'delete':
  1266. onDeleteNote(appointment, row);
  1267. break;
  1268. case 'reply':
  1269. onUploadNote(appointment, { parentId: row.id, appointmentId: appointment.id, recordId: appointment.recordId });
  1270. break;
  1271. default:
  1272. }
  1273. }} />;
  1274. })}
  1275. </div>
  1276. </div>
  1277. );
  1278. }
  1279. case '5':
  1280. switch (status) {
  1281. case 'end':
  1282. return <span className="link" >写留言</span>;
  1283. case 'not':
  1284. return <span className="link" onClick={() => onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>写留言</span>;
  1285. default:
  1286. return (
  1287. <div>
  1288. <div>
  1289. <span className="link" onClick={() => onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>写留言</span>
  1290. </div>
  1291. <div className="note-list">
  1292. {appointment.supplyList.map(row => {
  1293. 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) => {
  1294. switch (key) {
  1295. case 'edit':
  1296. onUploadSupply(appointment, row);
  1297. break;
  1298. case 'delete':
  1299. onDeleteSupply(appointment, row);
  1300. break;
  1301. case 'reply':
  1302. onUploadSupply(appointment, { parentId: row.id, appointmentId: appointment.id, recordId: appointment.recordId });
  1303. break;
  1304. default:
  1305. }
  1306. }} />;
  1307. })}
  1308. </div>
  1309. </div>
  1310. );
  1311. }
  1312. case '6':
  1313. return [
  1314. <div>
  1315. <span>基本情况</span>
  1316. <a href={status !== 'end' ? '' : ''} className="link">填写</a>
  1317. </div>,
  1318. <div>
  1319. <span>备考细节</span>
  1320. <a href={status !== 'end' ? '' : ''} className="link">填写</a>
  1321. </div>,
  1322. ];
  1323. case '7':
  1324. return <ProgressText progress={paper ? formatPercent(paper.report.userNumber, paper.report.questionNumber) : 0} size="small" />;
  1325. default:
  1326. return <div />;
  1327. }
  1328. }
  1329. }