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 { 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, 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: 'progress',
render: (text, record) => {
return record.progress ? (
<div>
<div className="v-a-m d-i-b">
<ProgressText width={50} size="small" times={1} progress={20} unit="次" />
</div>
<IconButton className="m-l-2" type="start" />
<IconButton className="m-l-5" type="report" />
</div>
) : (
'-'
);
},
},
{
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: () => {
return <GIcon name="note" active />;
},
},
{
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: 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, 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, record) => {
return record.progress ? (
<div>
<div className="v-a-m d-i-b">
<ProgressText width={50} size="small" times={1} progress={20} unit="次" />
</div>
<IconButton className="m-l-2" type="start" />
<IconButton className="m-l-5" type="report" />
</div>
) : (
'-'
);
},
},
{
title: '授课时间',
key: 'time',
render: (text, record) => {
return `${formatDate(record.startTime, 'YYYY-MM-DD HH:mm:ss')} ~ ${formatDate(record.endTime, 'HH:mm:ss')}`;
},
},
{
title: '课后笔记',
key: 'note',
render: (text, record) => {
return <a onClick={() => this.props.onNote(record)}>查看</a>;
},
},
{
title: '课后补充',
key: 'supply',
render: (text, record) => {
return <a onClick={() => this.props.onSupply(record)}>查看</a>;
},
},
],
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 <a onClick={() => this.props.onSupply(record)}>查看</a>;
},
},
],
};
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: 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),
};
}
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 />;
}
}
}