import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import './index.less'; import { Icon, Dropdown } from 'antd'; import FileUpload from '@src/components/FileUpload'; import Page from '@src/containers/Page'; import Assets from '@src/components/Assets'; import { asyncSMessage } from '@src/services/AsyncTools'; import { formatDate, formatSeconds, formatPercent } from '@src/services/Tools'; import UserLayout from '../../../layouts/User'; import Button from '../../../components/Button'; import ProgressText from '../../../components/ProgressText'; import IconButton from '../../../components/IconButton'; import { Icon as GIcon } from '../../../components/Icon'; import menu from '../index'; import Tabs from '../../../components/Tabs'; import UserTable from '../../../components/UserTable'; import More from '../../../components/More'; import Modal from '../../../components/Modal'; import DatePlane from '../../../components/Date'; import Note from '../../../components/Note'; import { My } from '../../../stores/my'; import { User } from '../../../stores/user'; import { Question } from '../../../stores/question'; import { Order } from '../../../stores/order'; import { Common } from '../../../stores/common'; export default class extends Page { initState() { return { tab: 'online', status: 'all', }; } formatRecord(row) { if (row.useEndTime) { if (row.isSuspend && !row.restoreTime) { row.status = 'suspend'; } else if (new Date(row.useEndTime).getTime() < new Date().getTime()) { row.status = 'end'; } else { row.status = 'ing'; } } else { row.status = 'not'; } row.paperMap = {}; row.appointmentPaperMap = {}; if (row.papers) { row.papers.forEach(paper => { if (paper.courseNo) row.paperMap[paper.courseNo] = paper; if (paper.appointment) row.appointmentPaperMap[paper.appointment] = paper; }); } row.progressMap = {}; if (row.progress) { row.progress.forEach(progress => { row.progressMap[progress.courseNoId] = progress; }); } row.courseNoMap = {}; row.courseTime = 0; if (row.courseNos) { row.courseNos.forEach(no => { row.courseNoMap[no.id] = no; row.courseTime += no.time; no.paper = row.paperMap[no.id]; no.progress = row.progressMap[no.id]; }); } if (row.currentNo) { row.currentCourseNo = row.courseNoMap[row.currentNo]; } else { row.currentNo = 0; } // 如果已经最新预约结束,则添加一个空记录作为最新预约 if (row.appointments) { row.appointments.forEach(r => { r.paper = row.appointmentPaperMap[r.id]; r.noteList = (row.comments || []).filter(c => c.type === 'note' && c.appointmentId === r.id); r.supplyList = (row.comments || []).filter(c => c.type === 'supply' && c.appointmentId === r.id); }); // 是否是最后一课时,是否过预约时间 const last = row.appointments.length - 1; const appointment = row.appointments[last]; if (new Date(appointment.endTime).getTime() < new Date().getTime()) { // row.status = 'end'; if (row.number !== row.appointments.length) { row.appointments.push([{}]); } } } row.days = new Date(row.userEndTime); return row; } initData() { const data = Object.assign(this.state, this.state.search); data.filterMap = this.state.search; if (data.order) { data.sortMap = { [data.order]: data.direction }; } this.setState(data); let isUsed = null; let isEnd = null; switch (data.status) { case 'learning': isUsed = true; isEnd = false; break; case 'end': isUsed = true; isEnd = true; break; case 'nouse': isUsed = false; isEnd = false; break; case 'all': default: } My.listCourse(Object.assign({ courseModule: data.tab }, this.state.search, { isUsed, isEnd })).then(result => { result.list = result.list.map(row => { return this.formatRecord(row); }); this.setState({ list: result.list, total: result.total, page: data.page }); }); } onAction() { } onTabChange(tab) { const data = { tab }; this.refreshQuery(data); } onStatusChange(status) { this.search({ status }); } openTime(record) { if (record.courseTimeMap) { this.setState({ data: record, showTime: true }); return; } My.timeCourse(record.id).then(result => { const courseMap = {}; const previewMap = {}; const stopMap = {}; result.forEach(row => { const date = formatDate(row.day, 'YYYY-MM-DD'); if (row.type === 'stop') { stopMap[date] = true; } else if (row.type === 'preview') { previewMap[date] = true; } else if (row.type === 'course') { courseMap[date] = true; } }); record.courseTimeMap = courseMap; record.previewTimeMap = previewMap; record.stopTimeMap = stopMap; this.setState({ data: record, showTime: true }); }); } refreshDetail(recordId) { My.detailCourse(recordId).then(result => { let { list } = this.state; list = list.map(row => { if (row.id === recordId) return this.formatRecord(result); return row; }); this.setState({ list }); }); } suspend() { const { suspend } = this.state; My.suspendCourse(suspend.id) .then(() => { asyncSMessage('停课成功'); this.setState({ showSuspend: false }); this.refreshDetail(suspend.id); }) .catch(err => { asyncSMessage(err.message, 'error'); }); } restore() { const { restore } = this.state; My.restoreCourse(restore.id) .then(() => { asyncSMessage('恢复成功'); this.setState({ showRestore: false }); this.refreshDetail(restore.id); }) .catch(err => { asyncSMessage(err.message, 'error'); }); } submitAppointmentComment(data, type) { data.type = type; if (data.id) { My.editAppointmentComment(data).then(() => { this.refreshDetail(data.recordId); }); } else { My.addAppointmentComment(data).then(() => { this.refreshDetail(data.recordId); }); } } deleteAppointmentComment(row) { My.delAppointmentComment(row.id).then(() => { this.refreshDetail(row.recordId); }); } submitQuestionFile(data) { My.uploadAppointmentQuestion(data).then(() => { this.refreshDetail(data.recordId); }); } setCCTalkName(recordId, cctalkname) { My.setCCTalkName(recordId, cctalkname).then(() => { this.refreshDetail(recordId); }); } submitComment() { const { comment } = this.state; My.addComment(comment.channel, comment.position, comment.content).then(() => { this.setState({ showComment: false, showFinish: true, comment: {} }); }); } open(recordId) { Order.useRecord(recordId).then(() => { asyncSMessage('开通成功'); this.refreshDetail(recordId); }); } closeCommentTips(recordId) { My.courseCommentTips(recordId); } uploadNote(file) { const { note = {} } = this.state; Common.uploadImage(file).then(result => { note.file = result.url; note.name = file.name; this.setState({ note }); }); } uploadSupply(file) { const { supply = {} } = this.state; Common.uploadImage(file).then(result => { supply.file = result.url; supply.name = file.name; this.setState({ supply }); }); } renderView() { const { config } = this.props; return <UserLayout active={config.key} menu={menu} center={this.renderDetail()} />; } renderDetail() { const { tab, status, showTips, showTime, showSuspend, showRestore, showUploadNote, showUploadSupply, showSupply, showNote, showComment, showFinish, comment = {}, data = {}, note = {}, supply = {}, appointment = {}, } = this.state; return ( <div className="detail-layout"> <div hidden={!showTips} className="tip"> <div className="text">理清备考思路,避开常见误区,让学习的每一分钟发光发热!</div> <a href="" target="_blank"> 去填写 > </a> <Icon type="close-circle" theme="filled" onClick={() => this.setState({ showTips: false })} /> </div> <Tabs type="division" theme="theme" size="small" space={2.5} width={100} active={tab} tabs={[{ key: 'online', title: '在线课程' }, { key: 'vs', title: '1V1私教' }]} onChange={key => this.onTabChange(key)} /> <Tabs type="tag" theme="white" size="small" space={5} width={54} active={status} tabs={[ { key: 'all', title: '全部' }, { key: 'nouse', title: '未开通' }, { key: 'learning', title: '学习中' }, { key: 'end', title: '已结束' }, ]} onChange={key => this.onStatusChange(key)} /> {this[`renderTab${tab}`]()} <Modal show={showTime} className="clock-modal" title="打卡表" width={460} onClose={() => this.setState({ showTime: false })}> <div> <div className="d-i-b w-3"> <div className="t-2 t-s-14">听课频率</div> <div className="t-4 t-s-18">{data.currentNo ? data.totalDays / data.currentNo : 0}天/课时</div> </div> <div className="d-i-b w-3"> <div className="t-2 t-s-14">作业完成度</div> <div className="t-4 t-s-18">{data.previewProgress || 0}%</div> </div> </div> <div className="b-b m-t-1 m-b-1" /> <div className="m-b-1"> <div className="tag d-i-b t-s-16"> <i style={{ background: '#6865FD' }} /> 听课 </div> <div className="tag d-i-b t-s-16"> <i style={{ background: '#4299FF' }} /> 预习 </div> <div className="tag d-i-b t-s-16"> <i style={{ background: '#E7E7E7' }} /> 停课 </div> </div> <DatePlane hideInput show={showTime} onChange={() => { }} disabledDate={(current) => { const date = current.format('YYYY-MM-DD'); return data.stopTimeMap[date]; }} dateRender={current => { const date = current.format('YYYY-MM-DD'); return ( <div className="ant-calendar-date"> {current.get('date')} {data.courseTimeMap[date] && <i className="s1" style={{ background: '#6865FD' }} />} {data.previewTimeMap[date] && <i className="s2" style={{ background: '#4299FF' }} />} </div> ); }} checkRefresh={(date, refresh) => { setTimeout(() => { refresh(); }, 2000); return true; }} /> <div className="t-2 t-s-12">*听课频率≤2天/课时,作业完成度≥90%,课程有效期可延长7-10天。</div> </Modal> <Modal show={showComment} title="评价" onConfirm={() => comment.content && this.submitComment()} onCancel={() => this.setState({ showComment: false, comment: {} })} > <textarea value={comment.content} className="b-c-1 w-10 p-10" rows={6} placeholder="您的看法对我们来说很重要!" onChange={e => { comment.content = e.target.value; this.setState({ comment }); }} /> <div className="b-b m-t-2" /> </Modal> <Modal show={showFinish} title="提交成功" confirmText="好的,知道了" btnAlign="center" onConfirm={() => this.setState({ showFinish: false })} > <div className="t-2 t-s-18"> <Icon type="check" className="t-5 m-r-5" /> 您的每一次反馈都是千行进步的动力。 </div> </Modal> <Modal show={showSuspend} title="申请停课" width={630} confirmText="立即停课" onConfirm={() => this.suspend()} onCancel={() => this.setState({ showSuspend: false })} > <div className="t-2 t-s-18">最长停课周期为1个月,到期后系统自动恢复至“学习中”状态。</div> <div className="t-2 t-s-18">每门课程只有一次申请机会,是否需要停课?</div> <div style={{ bottom: 20, left: 0 }} className="p-a t-3 t-s-14"> *停课不影响听课频率的计算; </div> <div style={{ bottom: 0, left: 0 }} className="p-a t-3 t-s-14"> {' '} 停课期间可随时恢复学习。 </div> </Modal> <Modal show={showRestore} title="恢复学习" width={630} confirmText="立即恢复" onConfirm={() => this.restore()} onCancel={() => this.setState({ showRestore: false })} > <div className="t-2 t-s-18">恢复学习后,本课程的停课功能将无法使用。是否恢复学习?</div> </Modal> <Modal show={showUploadNote} title="上传笔记" width={630} confirmText="提交" onConfirm={() => this.submitAppointmentComment(note, 'note')} onCancel={() => this.setState({ showUploadNote: false, note: {} })} > <textarea className="b-c-1 w-10 p-10 m-b-1" rows={6} value={note.content} placeholder="请输入留言内容。" onChange={e => { note.content = e.target.value; this.setState({ note }); }} /> <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}> <Button theme="file">上传文件</Button> <span className="m-l-1 t-3 t-s-14">{note.file ? `上传文件:${note.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}</span> <div className="fixed"> <span className="f-w-b t-s-18">放开文件立刻上传</span> </div> <FileUpload type="none" onDragEnter={() => this.setState({ draging: true })} onDragLeave={() => this.setState({ draging: false })} onUpload={({ file }) => Common.upload(file).then(result => { note.file = result.url; note.name = file.name; this.setState({ note }); })} /> </div> <div className="b-b m-t-2" /> </Modal> <Modal show={showNote} title="课后笔记" width={500} height={500} onClose={() => this.setState({ showNote: false })} > {appointment.noteList && appointment.noteList.map(row => { return <Note user={this.props.user} teacher={data.teacher} data={row} />; })} </Modal> <Modal show={showSupply} title="课后留言" width={500} height={500} onClose={() => this.setState({ showSupply: false })} > {appointment.supplyList && appointment.supplyList.map(row => { return <Note user={this.props.user} teacher={data.teacher} data={row} />; })} </Modal> <Modal show={showUploadSupply} title="上传留言" width={630} confirmText="提交" onConfirm={() => this.submitAppointmentComment(supply, 'supply')} onCancel={() => this.setState({ showUploadSupply: false, supply: {} })} > <textarea className="b-c-1 w-10 p-10 m-b-1" rows={6} value={supply.content} placeholder="请输入留言内容。" onChange={e => { supply.content = e.target.value; this.setState({ supply }); }} /> <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}> <Button theme="file">上传文件</Button> <span className="m-l-1 t-3 t-s-14">{supply.file ? `上传文件:${supply.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}</span> <div className="fixed"> <span className="f-w-b t-s-18">放开文件立刻上传</span> </div> <FileUpload type="none" onDragEnter={() => this.setState({ draging: true })} onDragLeave={() => this.setState({ draging: false })} onUpload={({ file }) => Common.upload(file).then(result => { supply.file = result.url; supply.name = file.name; this.setState({ supply }); })} /> </div> <div className="b-b m-t-2" /> </Modal> </div> ); } renderTabonline() { const { list = [] } = this.state; return list.map(item => { return ( <CourseOnline data={item} user={this.props.user} refreshDetail={recordId => { this.refreshDetail(recordId); }} onRestore={() => { this.setState({ showRestore: true, restore: item }); }} onSuspend={() => { this.setState({ showSuspend: true, suspend: item }); }} onTime={() => { this.openTime(item); }} onComment={() => { this.setState({ showComment: true, comment: { channel: 'course-video', position: item.course.id } }); }} closeCommentTips={() => this.closeCommentTips(item.id)} /> ); }); } renderTabvs() { const { list = [] } = this.state; return list.map(item => { return <CourseVs data={item} user={this.props.user} refreshDetail={(recordId) => { this.refreshDetail(recordId); }} onRestore={() => { this.setState({ showRestore: true, restore: item }); }} onSuspend={() => { this.setState({ showSuspend: true, suspend: item }); }} onComment={() => { this.setState({ showComment: true, comment: { channel: 'course-vs', position: item.course.id } }); }} closeCommentTips={() => this.closeCommentTips(item.id)} onUploadNote={(appointment, row) => this.setState({ showUploadNote: true, appointment, data: item, note: row || {} })} onUploadSupply={(appointment, row) => this.setState({ showUploadSupply: true, appointment, data: item, supply: row || {} })} onDeleteNote={(appointment, row) => this.deleteAppointmentComment(row)} onDeleteSupply={(appointment, row) => this.deleteAppointmentComment(row)} onNote={(appointment) => this.setState({ showNote: true, appointment, data: item })} onSupply={(appointment) => this.setState({ showSupply: true, appointment, data: item })} onUploadQuestion={(appointment, file, name) => this.submitQuestionFile({ id: appointment.id, recordId: appointment.recordId, questionFile: file, questionFileName: name })} setCCTalkName={(appointment, cctalkName) => this.setCCTalkName(appointment.recordId, cctalkName)} />; }); } } class CourseOnline extends Component { constructor(props) { super(props); this.columns = [ { title: '学习内容', key: 'title', render: (text, record) => { return `课时 ${record.no}: ${text}`; }, }, { title: '预习作业', key: 'paper', render: (text) => { text = text || {}; const progress = text.report ? formatPercent(text.report.userNumber, text.report.questionNumber) : 0; const times = text.paper ? text.paper.times : 0; return <div> <div className="v-a-m d-i-b"> <ProgressText width={50} size="small" times={times} progress={progress} unit="次" /> </div> {!text.report && <IconButton className="m-l-2" type="start" tip="Start" onClick={() => { User.needLogin() .then(() => { Question.startLink('preview', text); }); }} />} {text.report && !text.report.isFinish && <IconButton className="m-l-2" type="continue" tip="Continue" onClick={() => { User.needLogin() .then(() => { Question.continueLink('preview', text); }); }} />} {text.report && !!text.report.isFinish && <IconButton className="m-l-2" type="restart" tip="Restart" onClick={() => { User.needLogin() .then(() => { Question.restart('preview', text); }); }} />} {text.report && !!text.report.isFinish && <IconButton className="m-l-5" type="report" tip="Report" onClick={() => { User.needLogin() .then(() => { Question.reportLink('preview', text); }); }} />} </div>; }, }, { title: '进度', key: 'progress', render: (text, record) => { const { paper = {} } = record; return `${paper.paper && paper.paper.times > 0 ? `${paper.paper.times}次+` : ''}${ paper.report ? formatPercent(paper.report.userNumber, paper.report.questionNumber, false) : '0%'}`; }, }, { title: '最近学习', key: 'lastTime', render: (text, record) => { const { paper = {} } = record; return paper.report && formatDate(paper.report.updateTime, 'YYYY-MM-DD HH:mm:ss'); }, }, { title: '笔记', key: 'note', render: (text, record) => { return <GIcon name="note" active={record.note} />; }, }, { title: '问答', key: 'ask', render: (text, record) => { return ( <Link to={`/course/ask/${record.courseId}?no=${record.id}`}>{`${record.answerNumber || 0}/${record.askNumber || 0}`}</Link> ); }, }, ]; this.state = { open: props.data.status === 'ing' }; } render() { const { data = {} } = this.props; switch (data.status) { case 'ing': return this.renderIng(); case 'not': return this.renderNot(); case 'end': return this.renderEnd(); case 'suspend': return this.renderSuspend(); default: return <div />; } } renderIng() { const { data, onTime, onComment, onSuspend } = this.props; const { open } = this.state; return ( <div className="course-item ing"> <div className="title"> <div className="tag">学习中</div> <div className="text">{data.course.title}</div> <div className="right"> <More menu={[{ label: '评价', key: 'comment' }, { label: '停课', key: 'suspend' }]} onClick={value => { const { key } = value; if (key === 'comment') { onComment(); } else if (key === 'suspend') { onSuspend(); } }} /> </div> </div> {data.currentCourseNo && ( <div className="continue" onClick={() => { linkTo(`/course/detail/${data.course.id}`); }} > <Assets name="notice" /> 继续学习:课时 {data.currentNo}:{data.currentCourseNo.title} </div> )} <div className="detail"> <div className="left"> <Assets name="sun_blue" src={data.course.cover} /> <div className="info"> <div className="t1">授课老师</div> <div className="t2">{data.course.teacher}</div> <div className="t1">有效期</div> <div className="t2">{data.useExpireDays}Days</div> </div> </div> <div className="right"> <div className="item"> <GIcon name="speed-block" active noHover /> <div className="text"> <span>{data.currentNo}</span>/{data.courseNos.length} </div> </div> <div className="item"> <GIcon name="question-block" active noHover /> <div className="text"> <span>{data.answerNumber}</span>/{data.askNumber} </div> </div> <div className="item" onClick={() => onTime()}> <GIcon name="clockin-block" active noHover /> <div className="text"> <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}% </div> </div> <div className="item"> <GIcon name="note-block" active noHover /> <div className="text">{data.noteNumber}</div> </div> </div> <div className="open"> <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} /> </div> </div> {open && this.renderTable()} </div> ); } renderNot() { const { data } = this.props; return ( <div className="course-item not"> <div className="title"> <div className="tag">未开通</div> <div className="text">{data.course.title}</div> </div> <div className="detail"> <div className="left"> <Assets name="sun_blue" src={data.course.cover} /> <div className="info"> <div className="t1">授课老师</div> <div className="t2">{data.course.teacher}</div> <div> <div className="d-i-b m-r-2"> <div className="t-2">课时</div> <div className="t-1 t-s-16">{data.courseNos.length}</div> </div> <div className="d-i-b"> <div className="t-2">总时长</div> <div className="t-1 t-s-16">{formatSeconds(data.courseTime)}</div> </div> </div> </div> </div> <div className="right t-c"> <div className="text">请于 {formatDate(data.endTime, 'YYYY-MM-DD')} 前开通</div> <Button size="lager" radius onClick={() => { this.open(data.id); }} > 立即开通 </Button> </div> </div> </div> ); } renderEnd() { const { data, onTime, onComment } = this.props; const { open } = this.state; return ( <div className="course-item end"> <div className="title"> <div className="tag">已结束</div> <div className="text">{data.course.title}</div> <div className="right"> <More menu={[{ label: '评价', key: 'comment' }]} onClick={value => { const { key } = value; if (key === 'comment') { onComment(); } }} /> </div> </div> <div className="detail"> <div className="left"> <Assets name="sun_blue" src={data.course.cover} /> <div className="info"> <div className="t1">授课老师</div> <div className="t2">{data.course.teacher}</div> <div className="t1">有效期</div> <div className="t-s-12">{formatDate(data.useStartTime, 'YYYY-MM-DD')}<br />至{formatDate(data.useEndTime, 'YYYY-MM-DD')}</div> </div> </div> <div className="right"> <div className="item"> <GIcon name="question-block" active noHover /> <div className="text"> <span>{data.answerNumber}</span>/{data.askNumber} </div> </div> <div className="item" onClick={() => onTime()}> <GIcon name="clockin-block" active noHover /> <div className="text"> <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}% </div> </div> <div className="item"> <GIcon name="note-block" active noHover /> <div className="text">{data.noteNumber}</div> </div> {data.courseAward > 0 && ( <div className="item"> <GIcon name="gift-block" active /> <div className="text">赠送{data.courseAward}天</div> </div> )} </div> <div className="open"> <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} /> </div> </div> {open && this.renderTable()} </div> ); } renderSuspend() { const { data, onTime, onRestore, onComment } = this.props; const { open } = this.state; return ( <div className="course-item end"> <div className="title"> <div className="tag">已停课</div> <div className="text"> {data.course.title} <Button size="small" radius onClick={() => onRestore()}> 恢复上课 </Button> </div> <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> <div className="right"> <More menu={[{ label: '评价', key: 'comment' }]} onClick={value => { const { key } = value; if (key === 'comment') { onComment(); } }} /> </div> </div> <div className="detail"> <div className="left"> <Assets name="sun_blue" src={data.course.cover} /> <div className="info"> <div className="t1">授课老师</div> <div className="t2">{data.course.teacher}</div> <div className="t1">有效期</div> <div className="t2">{data.useExpireDays}Days</div> </div> </div> <div className="right"> <div className="item"> <GIcon name="speed-block" noHover /> <div className="text"> <span>{data.currentNo}</span>/{data.courseNos.length} </div> </div> <div className="item"> <GIcon name="question-block" active noHover /> <div className="text"> <span>{data.answerNumber}</span>/{data.askNumber} </div> </div> <div className="item" onClick={() => onTime()}> <GIcon name="clockin-block" active noHover /> <div className="text"> <span>{formatSeconds(data.totalTime)}</span> {data.previewProgress || 0}% </div> </div> <div className="item"> <GIcon name="note-block" active noHover /> <div className="text">{data.noteNumber}</div> </div> </div> <div className="open"> <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} /> </div> </div> {open && this.renderTable()} </div> ); } renderTable() { const { data = {} } = this.props; const { courseNos = [] } = data; return <UserTable size="small" columns={this.columns} data={courseNos} />; } } const titleMap = { 1: '预约时间', 2: '答疑文档', 3: '上课', 4: '课后笔记', 5: '课后补充', 6: '备考信息', 7: '完成作业', }; const iconMap = { 1: 'time-icon', 2: 'QA-icon', 3: 'class-icon', 4: 'note-icon', 5: 'supplement-icon', 6: 'information-icon', 7: 'homework-icon', }; const statusMap = { 1: appointment => { if (!appointment.id) return 'end'; return ''; }, 2: appointment => { if (!appointment.questionFile) return 'not'; return ''; }, 3: (appointment, data) => { if (!data.cctalkName) return 'not'; // if (new Date(appointment.endTime).getTime() > new Date()) return 'not'; return ''; }, 4: (appointment) => { if (!appointment.noteList || appointment.noteList.length === 0) return 'not'; return ''; }, 5: (appointment) => { if (!appointment.supplyList || appointment.supplyList.length === 0) return 'not'; return ''; }, 6: () => { return ''; }, 7: (appointment) => { const { paper = {} } = appointment; if (!paper.report || formatPercent(paper.report.userNumber, paper.report.questionNumber) < 100) return 'not'; return ''; }, }; class CourseVs extends Component { constructor(props) { super(props); this.columns = { system: [ { title: '学习内容', key: 'title' }, { title: '预习作业', key: 'paper', render: (text) => { text = text || {}; const progress = text.report ? formatPercent(text.report.userNumber, text.report.questionNumber) : 0; const times = text.paper ? text.paper.times : 0; return <div> <div className="v-a-m d-i-b"> <ProgressText width={50} size="small" times={times} progress={progress} unit="次" /> </div> {!text.report && <IconButton className="m-l-2" type="start" tip="Start" onClick={() => { User.needLogin() .then(() => { Question.startLink('preview', text); }); }} />} {text.report && !text.report.isFinish && <IconButton className="m-l-2" type="continue" tip="Continue" onClick={() => { User.needLogin() .then(() => { Question.continueLink('preview', text); }); }} />} {text.report && !!text.report.isFinish && <IconButton className="m-l-2" type="restart" tip="Restart" onClick={() => { User.needLogin() .then(() => { Question.restart('preview', text); }); }} />} {text.report && !!text.report.isFinish && <IconButton className="m-l-5" type="report" tip="Report" onClick={() => { User.needLogin() .then(() => { Question.reportLink('preview', text); }); }} />} </div>; }, }, { title: '授课时间', key: 'time', render: (text, record) => { return <div className="sub"> <div className="t-2 t-s-12">{formatDate(record.startTime, 'YYYY-MM-DD')}</div> <div className="t-6 t-s-12">{formatDate(record.startTime, 'HH:mm:ss')} ~ {formatDate(record.endTime, 'HH:mm:ss')}</div> </div>; }, }, { title: '课后笔记', key: 'note', render: (text, record) => { return record.noteList && record.noteList.length > 0 ? <a onClick={() => this.props.onNote(record)}>查看</a> : <span>查看</span>; }, }, { title: '课后补充', key: 'supply', render: (text, record) => { return record.supplyList && record.supplyList.length > 0 ? <a onClick={() => this.props.onSupply(record)}>查看</a> : <span>查看</span>; }, }, ], answer: [ { title: '学习内容', key: 'title' }, { title: '答疑文档', key: 'questionFile', render: (text, record) => { return ( <a href={text} target="_blank"> {record.questionFileName} </a> ); }, }, { title: '授课时间', key: 'time', render: (text, record) => { return `${formatDate(record.startTime, 'YYYY-MM-DD HH:mm:ss')} ~ ${formatDate(record.endTime, 'HH:mm:ss')}`; }, }, { title: '课后补充', key: 'supply', render: (text, record) => { return record.supplyList ? <a onClick={() => this.props.onSupply(record)}>查看</a> : <span>查看</span>; }, }, ], }; this.listMap = { novice: [1, 3], coach: [1, 6, 3], system: [1, 7, 3, 4], answer: [1, 2, 3], }; const index = props.data.appointments.length - 1; this.state = { open: props.data.status === 'ing', tab: 'ing', index, list: this.listMap[this.props.data.course.vsType], showTips: props.data.commentTips === 0 && (props.data.appointments.length === Math.ceil(props.data.number / 2) || props.data.appointments.length === props.data.number), }; } closeCommentTips() { My.courseCommentTips(this.props.data.id).then(() => { this.setState({ showTips: false }); }); } render() { const { data = {} } = this.props; switch (data.status) { case 'ing': return this.renderIng(); case 'not': return this.renderNot(); case 'end': return this.renderEnd(); case 'suspend': return this.renderSuspend(); default: return <div />; } } renderIng() { const { data, onComment, closeCommentTips } = this.props; const { tab, showTips, open } = this.state; return ( <div className="education-item ing"> <div className="title"> <div className="tag">学习中</div> <div className="text"> {data.course.title} {data.vsNo > 0 ? `V${data.vsNo}` : ''} {data.number > 0 ? `(${data.number}课时)` : ''} </div> <div className="right"> <More menu={[{ label: '评价', key: 'comment' }, { label: '停课', key: 'suspend' }]} onClick={value => { const { key } = value; if (key === 'comment') { onComment(); } }} /> </div> </div> {showTips && <div className="continue"> <Icon className='close m-r-5 t-3' type="close-circle" theme="filled" onClick={() => closeCommentTips()} /> 课程已过半,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a> </div>} <div className="detail"> <div className="left"> <Assets name="sun_blue" src={data.course.cover} /> <div className="info"> <div className="t1">授课老师</div> <div className="t2">{data.teacher.realname}</div> <div className="t1">有效期</div> <div className="t2">{data.useExpireDays}Days</div> </div> </div> <div className="right"> <div className="item"> <GIcon name="speed-block" active noHover /> <div className="text"> <span>{data.appointments.length}</span>/{data.number} </div> </div> <div className="item"> <GIcon name="time-block" active noHover /> <div className="text"> <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时 </div> </div> </div> <div className="open"> <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} /> </div> </div> {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && <Tabs className="t-l" type="line" theme="theme" size="small" width={80} active={tab} tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]} onChange={key => this.setState({ tab: key })} />} {open && (tab === 'ing' ? this.renderTimeLine() : this.renderTable())} </div> ); } renderNot() { const { data } = this.props; return ( <div className="education-item not"> <div className="title"> <div className="tag">未开通</div> <div className="text"> {data.course.title} {data.vsNo > 0 ? `V${data.vsNo}` : ''} {data.number > 0 ? `(${data.number}课时)` : ''} </div> </div> <div className="detail"> <div className="left"> <Assets name="sun_blue" src={data.course.cover} /> <div className="info"> <div className="t1">授课老师</div> <div className="t2">{data.teacher.realname}</div> <div> <div className="d-i-b m-r-2"> <div className="t-2">课时</div> <div className="t-1 t-s-16">{data.number}</div> </div> <div className="d-i-b"> <div className="t-2">总时长</div> <div className="t-1 t-s-16">{data.number}Hours</div> </div> </div> </div> </div> <div className="right"> <div className="qr-code"> <Assets name="qrcode" src={data.teacher.qr} /> <div className="i"> <div className="t1">请尽快与老师预约上课时间</div> <div className="t2">请于 {formatDate(data.endTime, 'YYYY-MM-DD')} 前开通课程</div> </div> </div> </div> </div> {/* {this.renderTimeLine()} */} </div> ); } renderEnd() { const { data, onComment, closeCommentTips } = this.props; const { open, tab, showTips } = this.state; return ( <div className="education-item end"> <div className="title"> <div className="tag">已结课</div> <div className="text"> {data.course.title} {data.vsNo > 0 ? `V${data.vsNo}` : ''} {data.number > 0 ? `(${data.number}课时)` : ''} </div> <div className="right"> <More menu={[{ label: '评价', key: 'comment' }]} onClick={value => { const { key } = value; if (key === 'comment') { onComment(); } }} /> </div> </div> {showTips && <div className="continue"> <Icon className='close m-r-5 t-3' type="close-circle" theme="filled" onClick={() => closeCommentTips()} /> 课程已结束,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a> </div>} <div className="detail"> <div className="left"> <Assets name="sun_blue" src={data.course.cover} /> <div className="info"> <div className="t1">授课老师</div> <div className="t2">{data.teacher.realname}</div> <div className="t1">有效期</div> <div className="t2">{data.useExpireDays}Days</div> </div> </div> <div className="right"> <div className="item"> <GIcon name="speed-block" active noHover /> <div className="text"> <span>{data.appointments.length}</span>/{data.number} </div> </div> <div className="item"> <GIcon name="time-block" active noHover /> <div className="text"> <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时 </div> </div> </div> <div className="open"> <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} /> </div> </div> {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && <Tabs className="t-l" type="line" theme="theme" size="small" width={80} active={tab} tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]} onChange={key => this.setState({ tab: key })} />} {open && (tab === 'ing' ? this.renderTimeLine() : this.renderTable())} </div> ); } renderSuspend() { const { data, onRestore, onComment } = this.props; return ( <div className="education-item end"> <div className="title"> <div className="tag">已停课</div> <div className="text"> {data.course.title} {data.vsNo > 0 ? `V${data.vsNo}` : ''} {data.number > 0 ? `(${data.number}课时)` : ''} <Button size="small" onClick={() => onRestore()}> 恢复上课 </Button> </div> <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> <div className="right"> <More menu={[{ label: '评价', key: 'comment' }]} onClick={value => { const { key } = value; if (key === 'comment') { onComment(); } }} /> </div> </div> <div className="detail"> <div className="left"> <Assets name="sun_blue" src={data.course.cover} /> <div className="info"> <div className="t1">授课老师</div> <div className="t2">{data.teacher.realname}</div> <div className="t1">有效期</div> <div className="t2">{data.useExpireDays}Days</div> </div> </div> <div className="right"> <div className="item"> <GIcon name="speed-block" active noHover /> <div className="text"> <span>{data.appointments.length}</span>/{data.number} </div> </div> <div className="item"> <GIcon name="time-block" active noHover /> <div className="text"> <span>{data.appointments.length > 0 ? data.totalDays / data.appointments.length : 0}</span>/课时 </div> </div> </div> </div> </div> ); } renderTimeLine() { const { data = {}, onUploadNote, onUploadSupply, onUploadQuestion, setCCTalkName } = this.props; const { list = [], index } = this.state; const appointment = data.appointments[index] || {}; let status = ''; return [ <div className="class-hour"> {data.number > 1 && <div className="text">课时 {appointment.no}:{appointment.title}</div>} {data.number > 1 && <div className="right"> <GIcon name="prev" onClick={() => this.setState({ index: index === 0 ? index : index - 1 })} /> <span>上一课时</span> <span>下一课时</span> <GIcon name="next" onClick={() => this.setState({ index: index >= data.appointments.length - 1 ? index : index + 1 })} /> </div>} </div>, <div className="time-line"> {list.map(item => { if (status === '') { // 上一阶段完成 status = statusMap[item](appointment, data); } else { // 上一阶段未完成 status = 'end'; } return <TimeLineItem type={`${item}`} user={this.props.user} appointment={appointment} data={data} status={status} onUploadNote={onUploadNote} onUploadSupply={onUploadSupply} onUploadQuestion={onUploadQuestion} setCCTalkName={setCCTalkName} />; })} </div>, ]; } renderTable() { const { data = {} } = this.props; const { appointments = [] } = data; return <UserTable size="small" columns={this.columns[data.course.vsType]} data={appointments.filter(row => row.id)} />; } } class TimeLineItem extends Component { constructor(props) { super(props); this.state = {}; } onClick(key) { const { onClick } = this.props; const { status } = this.props; if (status === 'not') return; if (onClick) onClick(key); } render() { const { type } = this.props; const { status } = this.props; return ( <div className={`time-line-item ${status}`}> <div className="icon-title"> <GIcon name={iconMap[type]} active={!status} noHover /> <div className="title">{titleMap[type]}</div> </div> <div className="time-line-detail">{this.renderDetail()}</div> </div> ); } renderDetail() { const { data = {}, appointment = {}, type, onUploadNote, onUploadSupply, onDeleteNote, onDeleteSupply, onUploadQuestion, setCCTalkName, } = this.props; const { status } = this.props; const { paper } = appointment; switch (type) { case '1': switch (status) { case 'end': case 'not': return ( <span> 请尽快与老师预约上课时间,老师微信:{data.teacher.wechat}扫码加微信{' '} <Dropdown overlay={<Assets name="qrcode" src={data.teacher.qr} />}> <span> <Assets className="m-l-1" name="erweima" /> </span> </Dropdown> </span> ); default: return ( <span> {formatDate(appointment.startTime, 'YYYY-MM-DD HH:mm:ss')} ~{' '} {formatDate(appointment.endTime, 'HH:mm:ss')} </span> ); } case '2': switch (status) { case 'end': return <span className="link">点此上传</span>; case 'not': return <FileUpload onUpload={(file) => { return Common.upload({ file }).then((result => onUploadQuestion(appointment, result.url, file.name))); }}><span className="link">点此上传</span></FileUpload>; default: return ( <a href={appointment.questionFile} target="_blank"> {appointment.questionFileName || '文件'} </a> ); } case '3': switch (status) { case 'end': return data.cctalkName ? <span> CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a> </span> : <div><input style={{ width: 200 }} className='b-c-1 p-l-1 p-r-1 t-s-12 m-r-1' placeholder="请输入CCtalk用户名查看授课频道" onChange={(e) => { this.setState({ cctalkName: e.target.value }); }} /><Button size="small" radius disabled>提交</Button></div>; case 'not': return data.cctalkName ? <span> CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a> </span> : <div><input style={{ width: 200 }} className='b-c-1 p-l-1 p-r-1 t-s-12 m-r-1' placeholder="请输入CCtalk用户名查看授课频道" onChange={(e) => { this.setState({ cctalkName: e.target.value }); }} /><Button size="small" radius onClick={() => { if (this.state.cctalkName) setCCTalkName(appointment, this.state.cctalkName); }} >提交</Button></div>; default: return ( <span> CCtalk 频道号 :{appointment.cctalkChannel}{' '} <a className="link" href="" target="_black"> CC talk使用手册 </a> </span> ); } case '4': switch (status) { case 'end': return <span className="link">点此上传</span>; case 'not': return <span className="link" onClick={() => onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>点此上传</span>; default: return ( <div> <div> <span className="link" onClick={() => onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>点此上传</span> </div> <div className="note-list"> {appointment.noteList.map(row => { console.log(row); return <Note user={this.props.user} teacher={data.teacher} data={row} reply={!row.userId} actionList={row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null} onAction={(key) => { switch (key) { case 'edit': onUploadNote(appointment, row); break; case 'delete': onDeleteNote(appointment, row); break; case 'reply': onUploadNote(appointment, { parentId: row.id, appointmentId: appointment.id, recordId: appointment.recordId }); break; default: } }} />; })} </div> </div> ); } case '5': switch (status) { case 'end': return <span className="link">写留言</span>; case 'not': return <span className="link" onClick={() => onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>写留言</span>; default: return ( <div> <div> <span className="link" onClick={() => onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>写留言</span> </div> <div className="note-list"> {appointment.supplyList.map(row => { return <Note user={this.props.user} teacher={data.teacher} data={row} reply={!row.userId} actionList={row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null} onAction={(key) => { switch (key) { case 'edit': onUploadSupply(appointment, row); break; case 'delete': onDeleteSupply(appointment, row); break; case 'reply': onUploadSupply(appointment, { parentId: row.id, appointmentId: appointment.id, recordId: appointment.recordId }); break; default: } }} />; })} </div> </div> ); } case '6': return [ <div> <span>基本情况</span> <a href={status !== 'end' ? '' : ''} className="link"> 填写 </a> </div>, <div> <span>备考细节</span> <a href={status !== 'end' ? '' : ''} className="link"> 填写 </a> </div>, ]; case '7': return ( <ProgressText progress={paper ? formatPercent(paper.report.userNumber, paper.report.questionNumber) : 0} size="small" /> ); default: return <div />; } } }