page.js 48 KB

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