Commit a4d52d69 by yuananting

feat:添加新建培训任务校验

parent 4f7e0407
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-07-29 13:57:03
* @LastEditors: yuananting
* @LastEditTime: 2021-08-02 17:25:17
* @LastEditTime: 2021-08-04 17:47:51
* @Description: 任务中心-培训任务-新建页面
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
......@@ -21,35 +21,48 @@ import './AddTrainTask.less';
import Bus from '@/core/bus';
const { TabPane } = Tabs;
const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
const DEFAULT_BASIC_INFO = {
planName: '',
coverUrl: defaultCover,
coverId: null,
durationType: 'NEVER_EXPIRES',
learnType: 'FREEDOM',
enableState: 'YES',
selectOperatorList: [],
instro: '',
operateType: 'All_Operate',
percentCompleteLive: 80,
percentCompleteVideo: 80,
percentCompletePicture: 100,
taskName: '', // 培训任务名称
// createId: User.getStoreUserId(), // 创建人id
// storeId: User.getStoreId(), // 学院id
// issueState: 'YES', // 是否发布此计划(底部按钮区分)
// scheduleMediaRequests: [], // 封面图-(简介待定)
coverUrl: '',
trainingStageList: [], // 培训内容
helpStoreUserIds: [], // 指定协同者id
timeType: 'VALIDITY', // 培训时间,默认永久有效
startTime: null, // 固定时间段-开始时间
endTime: null, // 固定时间段-结束时间
learnType: 'FREEDOM', // 学习模式,默认自由学习
assignList: [], // 指派列表-assignId assignType
instro: null, // 培训目的
};
const DEFAULT_TASK_LIST = [
const DEFAULT_STAGE_LIST = [
{
taskName: '阶段一',
courseList: [],
stageName: '阶段一',
contentList: [],
check: false,
},
];
const DEFAULT_FINISH_STANDARD = {
percentCompleteLive: 80,
percentCompleteVideo: 80, // 线上课完成百分比
percentCompletePicture: 80, // 图文课完成百分比
};
function AddTrainTask() {
const type = getParameterByName('type');
const [activeStep, setActiveStep] = useState('BASIC_INFO');
const [basicInfo, setBasicInfo] = useState(DEFAULT_BASIC_INFO);
const [taskList, setTaskList] = useState(DEFAULT_TASK_LIST);
const [stageList, setStageList] = useState(DEFAULT_STAGE_LIST);
const [finishStandard, setFinishStandard] = useState(DEFAULT_FINISH_STANDARD); // 完成百分比
const [startCheck, setStartCheck] = useState(false); // 是否启动校验
useEffect(() => {
console.log('basicInfo:', basicInfo);
}, [basicInfo]);
function renderFooter() {
return (
......@@ -57,7 +70,7 @@ function AddTrainTask() {
<When condition={activeStep === 'BASIC_INFO'}>
<div className='footer shrink-footer'>
<Button onClick={handleGoBack}>取消</Button>
<Button onClick={() => console.log('提交')}>保存</Button>
<Button onClick={handleSubmit}>保存</Button>
<Button type='primary' onClick={() => console.log('下一步')}>
下一步
</Button>
......@@ -68,7 +81,7 @@ function AddTrainTask() {
<div className='footer shrink-footer'>
<Button onClick={handleGoBack}>取消</Button>
<Button onClick={() => console.log('上一步')}>上一步</Button>
<Button onClick={() => console.log('提交')}>保存</Button>
<Button onClick={handleSubmit}>保存</Button>
<Button type='primary' onClick={() => console.log('提交')}>
保存并发布
</Button>
......@@ -79,19 +92,44 @@ function AddTrainTask() {
);
}
function handleSubmit() {
setStartCheck(true);
if (stageList.length === 0) {
return message.warning('请添加阶段');
}
}
function handleGoBack() {
window.RCHistory.goBack();
}
function handleChangeBasicInfo(field, value) {
if (field === 'trainDate') {
// 固定培训时间,设置起始
setBasicInfo({
...basicInfo,
startTime: value && value[0]?.valueOf(),
endTime: value && value[1]?.valueOf(),
});
} else {
setBasicInfo({
...basicInfo,
[field]: value,
startTime: null,
endTime: null,
});
}
}
function handleChangeTaskInfo(value) {
setTaskList(value);
function handleChangeStageInfo(field, value) {
if (field === 'stageList') {
setStageList(value);
} else {
setFinishStandard({
...finishStandard,
[field]: value,
});
}
}
return (
......@@ -118,8 +156,10 @@ function AddTrainTask() {
</span>
</span>
</div>
{activeStep === 'BASIC_INFO' && <BasicInfo data={basicInfo} onChange={handleChangeBasicInfo} />}
{activeStep === 'TRAIN_CONTENT' && <TrainContent data={taskList} onChange={handleChangeTaskInfo} />}
{activeStep === 'BASIC_INFO' && <BasicInfo basicInfo={basicInfo} startCheck={startCheck} onChange={handleChangeBasicInfo} />}
{activeStep === 'TRAIN_CONTENT' && (
<TrainContent stageList={stageList} startCheck={startCheck} finishStandard={finishStandard} onChange={handleChangeStageInfo} />
)}
</div>
{renderFooter()}
</div>
......
......@@ -2,126 +2,115 @@
* @Author: yuananting
* @Date: 2021-07-29 14:32:24
* @LastEditors: yuananting
* @LastEditTime: 2021-08-02 17:50:51
* @LastEditTime: 2021-08-04 17:25:40
* @Description: 任务中心-培训任务-新建-基本信息
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Form, Button, Input, Space, DatePicker, Radio, Tag, Col, message, Tooltip } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { withRouter } from 'react-router-dom';
// import SelectOperatorModal from '../modal/SelectOperatorModal';
// import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import React, { useEffect, useState } from 'react';
import { Form, Button, Input, Space, DatePicker, Radio, Tag, message, Tooltip } from 'antd';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import Upload from '@/core/upload';
import GraphicsEditor from '@/modules/course-manage/components/GraphicsEditor';
// import ImgClipModal from '@/components/ImgClipModal';
import ImgClipModal from '@/components/ImgClipModal';
import moment from 'moment';
import './BasicInfo.less';
const { TextArea } = Input;
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
function BasicInfo(props) {
const { data } = props;
const {
planName,
coverUrl,
durationType,
learnType,
instro,
endTime,
enableState,
operateType,
selectOperatorList,
percentCompleteLive,
percentCompleteVideo,
percentCompletePicture,
} = data;
const isDefaultCover = true;
const helpStoreUsers = [];
const { basicInfo, startCheck } = props;
const { taskName, coverUrl, helpStoreUserIds, timeType, startTime, endTime, learnType, assignList, instro } = basicInfo;
const [imageFile, setImageFile] = useState(null); // 需要被截取的图片
const [showSelectFileModal, setShowSelectFileModal] = useState(false);
const [imgClipVisible, setImgClipVisible] = useState(false);
const [coverClicpPath, setCoverClicpPath] = useState(null);
const [coverId, setCoverId] = useState(null);
// 当前是否使用的是默认图片
const defaultCover = 'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png';
const isDefaultCover = coverUrl === defaultCover;
const assignList1 = [
{
id: 1,
name: 'yat',
},
{
id: 2,
name: '而已',
},
{
id: 3,
name: '一条鱼',
},
];
const assignList2 = [
{
id: 1,
name: 'yat',
},
{
id: 2,
name: 'ya发发送到发送到发送到撒旦法撒旦法师t',
},
{
id: 3,
name: '1111111111111111111111111111111111111111111111111111111',
},
{
id: 4,
name: 'fsaf',
},
{
id: 5,
name: '示范法as',
},
{
id: 6,
name: '示范法as水电费',
},
{
id: 7,
name: '千多',
},
{
id: 8,
name: 'y阿萨at',
},
{
id: 9,
name: '发顺丰',
},
{
id: 10,
name: 'iuyti第三方感受到',
},
];
useEffect(() => {
updateCover();
}, [coverId]);
// 使用默认封面图
function handleResetCoverUrl() {
// const isDefaultCover = coverUrl === defaultCover;
// // 如果已经是默认图的话,不做任何任何处理
// if (isDefaultCover) return;
// message.success('已替换为默认图');
// this.props.onChange('coverUrl', defaultCover);
// setTimeout(() => {
// this.props.onChange('coverId', null);
// }, 1000);
message.success('已替换为默认图');
props.onChange('coverUrl', defaultCover);
}
// 从云盘选择封面
function handleSelectCover(file) {
setImgClipVisible(true);
setImageFile(file);
}
function getSignature(blob, fileName) {
Upload.uploadBlobToOSS(blob, 'cover' + new Date().valueOf(), null, 'signInfo').then((signInfo) => {
setCoverClicpPath(signInfo.fileUrl);
setCoverId(signInfo.resourceId);
setImgClipVisible(false);
});
}
function updateCover() {
setShowSelectFileModal(false);
props.onChange('coverUrl', coverClicpPath || coverUrl || defaultCover);
}
// 禁选日期
function disabledDate(current) {
return current && current < moment().startOf('day');
}
// 禁选时间
function disabledRangeTime(date, type) {
if (moment(date).isSame(moment(), 'day')) {
return {
disabledHours: () => {
const hours = [];
for (let i = 0; i < moment().hour(); i++) {
hours.push(i);
}
return hours;
},
disabledMinutes: () => {
const currentMinute = moment().minute();
const currentHour = moment(date).hour();
const minutes = [];
if (currentHour === moment().hour()) {
for (let i = 0; i < currentMinute; i++) {
minutes.push(i);
}
}
return minutes;
},
};
}
return {
disabledHours: () => [],
disabledMinutes: () => [],
disabledSeconds: () => [],
};
}
return (
<div className='basic-info__form'>
<Form>
<FormItem label='培训任务名称' required>
<FormItem
label='培训任务名称'
required
validateStatus={startCheck && (!taskName || taskName.length > 20) ? 'error' : ''}
help={startCheck && ((!taskName && '请输入培训任务名称') || (taskName.length > 20 && '任务名称最多20字'))}>
<Input
value={planName}
value={taskName}
placeholder='请输入培训任务名称(20字以内)'
maxLength={20}
style={{ width: 300 }}
// onChange={(e) => this.props.onChange('planName', e.target.value)}
onChange={(e) => props.onChange('taskName', e.target.value)}
/>
</FormItem>
<FormItem label='封面图'>
......@@ -129,9 +118,7 @@ function BasicInfo(props) {
<div className='opt-box'>
<Button
onClick={() => {
this.setState({
showSelectFileModal: true,
});
setShowSelectFileModal(true);
}}>
上传封面
</Button>
......@@ -148,15 +135,18 @@ function BasicInfo(props) {
</FormItem>
<FormItem label='培训时间'>
<div className='duration__wrap'>
<Radio.Group value={durationType} onChange={(e) => props.onChange('durationType', e.target.value)}>
<Radio.Group value={timeType} onChange={(e) => props.onChange('timeType', e.target.value)}>
<Space direction='vertical' size={16}>
<Radio value='NEVER_EXPIRES'>
<Radio value='VALIDITY'>
永久有效<span className='tips'>设置为“永久有效”,发布后任务开始生效,取消发布后失效</span>
</Radio>
<Radio value='FIXED_DURATION'>
固定时间段
{durationType === 'FIXED_DURATION' && (
{timeType === 'FIXED_DURATION' && (
<div className='picker-box'>
<FormItem
validateStatus={startCheck && !startTime && !endTime ? 'error' : ''}
help={startCheck && !startTime && !endTime && '请选择培训时间'}>
<RangePicker
style={{ width: 320 }}
showTime={{ defaultValue: [moment().add(10, 'minutes'), moment().add(10, 'minutes')] }}
......@@ -165,15 +155,15 @@ function BasicInfo(props) {
1个月: [moment().add(10, 'minute'), moment().add(1, 'month').endOf('day')],
3个月: [moment().add(10, 'minute'), moment().add(3, 'month').endOf('day')],
}}
// disabledDate={disabledDate}
// value={[examStartTime ? moment(Number(examStartTime)) : null, examEndTime ? moment(Number(examEndTime)) : null]}
// disabledTime={disabledRangeTime}
disabledDate={disabledDate}
disabledTime={disabledRangeTime}
value={[startTime ? moment(Number(startTime)) : null, endTime ? moment(Number(endTime)) : null]}
format='YYYY-MM-DD HH:mm'
// onChange={(date) => {
// setStartTime(date && date[0]?.valueOf());
// setExamEndTime(date && date[1]?.valueOf());
// }}
/>
onChange={(date) => {
props.onChange('trainDate', date);
}}
/>{' '}
</FormItem>
</div>
)}
</Radio>
......@@ -206,25 +196,44 @@ function BasicInfo(props) {
</Radio.Group>
</div>
</FormItem>
<FormItem label='指派学员' required>
<FormItem
label={
<span>
闯关学习
<Tooltip title='选择员工协同完成任务指派和督学工作'>
<i
className='icon iconfont'
style={{
marginLeft: '5px',
cursor: 'pointer',
color: '#bfbfbf',
fontSize: '14px',
fontWeight: 'normal',
}}>
&#xe61d;
</i>
</Tooltip>
</span>
}
required>
<Button style={{ display: 'block' }}>添加指派对象</Button>
{assignList1.length + assignList2.length > 0 && (
{assignList > 0 && (
<Space size={'12'} direction={'vertical'} className='select-obj'>
{assignList1.length > 0 && (
{assignList.length > 0 && (
<div className='obj-type-container'>
<div className='type-title'>已选组织:</div>
<div className='tag-box'>
{_.map(assignList1, (item) => {
{_.map(assignList, (item) => {
return <Tag closable>{item.name}</Tag>;
})}
</div>
</div>
)}
{assignList2.length > 0 && (
{assignList.length > 0 && (
<div className='obj-type-container'>
<div className='type-title'>已选学员:</div>
<div className='tag-box'>
{_.map(assignList2, (item) => {
{_.map(assignList, (item) => {
return <Tag closable>{item.name}</Tag>;
})}
</div>
......@@ -233,11 +242,29 @@ function BasicInfo(props) {
</Space>
)}
</FormItem>
<FormItem label='协同人员'>
<FormItem
label={
<span>
协同人员
<Tooltip title='选择需要培训的人员'>
<i
className='icon iconfont'
style={{
marginLeft: '5px',
cursor: 'pointer',
color: '#bfbfbf',
fontSize: '14px',
fontWeight: 'normal',
}}>
&#xe61d;
</i>
</Tooltip>
</span>
}>
<Button style={{ display: 'block' }}>添加协同者</Button>
{helpStoreUsers.length > 0 && (
{helpStoreUserIds.length > 0 && (
<div className='select-obj'>
{_.map(helpStoreUsers, (item) => {
{_.map(helpStoreUserIds, (item) => {
return <Tag closable>{item.name}</Tag>;
})}
</div>
......@@ -253,11 +280,36 @@ function BasicInfo(props) {
content: instro,
}}
onChange={(val) => {
// this.changeIntro(val);
// changeIntro(val);
}}
/>
</FormItem>
</Form>
{showSelectFileModal && (
<SelectPrepareFileModal
key='basic'
operateType='select'
multiple={false}
accept='image/jpeg,image/png,image/jpg'
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectFileModal}
onClose={() => {
setShowSelectFileModal(false);
}}
onSelect={handleSelectCover}
/>
)}
{imgClipVisible && (
<ImgClipModal
visible={imgClipVisible}
imgUrl={imageFile.ossUrl}
onConfirm={getSignature}
onClose={() => {
setImgClipVisible(false);
}}
/>
)}
</div>
);
}
......
......@@ -36,6 +36,7 @@
&.disabled {
color: #ccc;
cursor: not-allowed;
pointer-events: none;
}
}
.tips {
......@@ -57,6 +58,9 @@
.picker-box {
display: inline-block;
margin-left: 16px;
.ant-form-item {
margin-bottom: 0 !important;
}
}
}
}
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-08-01 17:28:30
* @LastEditors: yuananting
* @LastEditTime: 2021-08-02 14:26:43
* @LastEditTime: 2021-08-04 16:13:46
* @Description: 新建培训任务-关联课程抽屉
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
......@@ -99,7 +99,7 @@ class RelatedCourseDrawer extends Component {
const _data = [...this.props.data];
let currentLiveCourseListData = [];
_data.map((item) => {
item.courseList.map((childItem, childIndex) => {
item.contentList.map((childItem, childIndex) => {
if (childItem.courseType === 'LIVE') {
currentLiveCourseListData.push(childItem.courseId);
}
......@@ -130,7 +130,7 @@ class RelatedCourseDrawer extends Component {
const _data = [...this.props.data];
let currentVideoCourseListData = [];
_data.map((item, index) => {
item.courseList.map((childItem, childIndex) => {
item.contentList.map((childItem, childIndex) => {
if (childItem.courseType === 'VOICE') {
currentVideoCourseListData.push(childItem.courseId);
}
......@@ -168,7 +168,7 @@ class RelatedCourseDrawer extends Component {
const _data = [...this.props.data];
let currentPictureCourseListData = [];
_data.map((item, index) => {
item.courseList.map((childItem, childIndex) => {
item.contentList.map((childItem, childIndex) => {
if (childItem.courseType === 'PICTURE') {
currentPictureCourseListData.push(childItem.courseId);
}
......
......@@ -2,17 +2,18 @@
* @Author: yuananting
* @Date: 2021-08-03 17:05:32
* @LastEditors: yuananting
* @LastEditTime: 2021-08-03 20:02:27
* @LastEditTime: 2021-08-04 10:40:11
* @Description: 新建培训任务-关联考试抽屉
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useRef } from 'react';
import { Drawer, Form, Input, Button, Tooltip, Switch, Radio, InputNumber } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import { Drawer, Form, Input, Button, Tooltip, Switch, Radio, InputNumber, message } from 'antd';
import GraphicsEditor from '@/modules/course-manage/components/GraphicsEditor';
import moment from 'moment';
import './RelatedExamDrawer.less';
import SelectPaperModal from '@/modules/teach-tool/examination-manager/SelectPaperModal';
import User from '@/common/js/user';
function RelatedExamDrawer(props) {
const paperInfoInit = { passScore: 60 };
......@@ -32,8 +33,6 @@ function RelatedExamDrawer(props) {
const [passScore, setPassScore] = useState(100);
const [desclen, setDescLen] = useState(0);
const [check, setCheck] = useState(false);
const [getData, setGetData] = useState(false);
const [preview, setPreview] = useState(false);
const [examTotal, setExamTotal] = useState(0);
const [examList, setExamList] = useState([]);
const request = useRef(false);
......@@ -41,52 +40,22 @@ function RelatedExamDrawer(props) {
const [examDuration, setExamDuration] = useState(undefined);
useEffect(() => {
setPaperId(paperInfo.paperId);
setPassRate(paperInfo.passRate);
setExamName(paperInfo.paperName);
}, [paperInfo.paperId]);
useEffect(() => {
setPassScore(Math.round(((paperInfo.totalScore || 0) * (passRate || 0)) / 100));
setExamTotal(paperInfo.singleChoiceCnt + paperInfo.multiChoiceCnt + paperInfo.judgeCnt + paperInfo.gapFillingCnt + paperInfo.indefiniteChoiceCnt || 0);
}, [paperInfo.paperId, passRate]);
function disabledDate(current) {
// Can not select days before today and today
return current && current < moment().startOf('day');
}
function queryExamList() {
let param = {
current: 1,
size: 9999,
order: 'EXAM_START_TIME_DESC',
userId: User.getStoreUserId(),
tenantId: User.getStoreId(),
source: 0,
};
Service.Hades('public/hades/queryExamPageList', param).then((res) => {
const { result = {} } = res;
setExamList(result.records || []);
});
}
function queryExamDetail() {
Service.Hades('public/hades/queryExamDetail', {
examId: match.params.id,
tenantId: User.getStoreId(),
userId: User.getStoreUserId(),
source: 0,
}).then((res) => {
const { result } = res;
setPaperInfo(result.examPaper);
setPaperId(result.examPaper.paperId);
setStartTime(props.type === 'edit' ? result.examStartTime : '');
setExamEndTime(props.type === 'edit' ? result.examEndTime : '');
setExamName(props.type === 'edit' ? result.examName : `${result.examName}(复制)`);
setPassRate(result.passRate * 100);
setNeedPhone(result.needPhone);
setExamDesc(result.examDesc);
setExamDuration(result.examDuration / 60 / 1000);
setAnswerAnalysis(result.answerAnalysis);
setNeedOptionDisorder(result.needOptionDisorder);
setPassScore(result.passScore);
setResultContent(result.resultContent);
setResultShow(result.resultShow);
setGetData(true);
});
}
function handleSave() {
if (request.current) {
return;
......@@ -222,19 +191,6 @@ function RelatedExamDrawer(props) {
};
}
function handleGoBack() {
Modal.confirm({
title: '确定要返回吗?',
content: '返回后,本次编辑的内容将不被保存',
okText: '确认返回',
cancelText: '留在本页',
icon: <span className='icon iconfont default-confirm-icon'>&#xe6f4;</span>,
onOk: () => {
window.RCHistory.push('/examination-manage-index');
},
});
}
// 校验考试名称是否存在
function checkExist(examName) {
var result = null;
......@@ -385,6 +341,16 @@ function RelatedExamDrawer(props) {
</div>
</div>
</Form.Item>
<Form.Item label='考试结果查看' required>
<Radio.Group
onChange={(e) => {
setResultShow(e.target.value);
}}
value={resultShow}>
<Radio value={'IMMEDIATELY'}>交卷后立即显示考试结果</Radio>
<Radio value={'AFTER_EXAM_END'}>到达考试截止日期才显示结果</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label='考试结果内容' required>
<Radio.Group
......@@ -412,7 +378,7 @@ function RelatedExamDrawer(props) {
</Form>
<div className='footer shrink-footer'>
<Button onClick={props.onClose}>取消</Button>
<Button type='primary' onClick={() => console.log('提交')}>
<Button type='primary' onClick={handleSave}>
确定
</Button>
</div>
......
......@@ -68,6 +68,9 @@
.graphics-editor-container {
width: 550px;
height: 130px;
.editor-warning {
top: 130px !important;
}
}
}
}
......@@ -2,13 +2,13 @@
* @Author: yuananting
* @Date: 2021-07-30 16:33:58
* @LastEditors: yuananting
* @LastEditTime: 2021-08-03 18:32:02
* @LastEditTime: 2021-08-04 19:27:08
* @Description: 任务中心-培训任务-新建-培训内容
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from 'react';
import { Form, Input, Collapse, Dropdown, Menu } from 'antd';
import { Form, Input, Collapse, Dropdown, Menu, Modal } from 'antd';
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
import arrayMove from 'array-move';
import './TrainContent.less';
......@@ -16,11 +16,13 @@ import RelatedCourseDrawer from './RelatedCourseDrawer';
import RelatedExamDrawer from './RelatedExamDrawer';
import ExpiredCourseList from './ExpiredCourseList';
import ENUM from '../../enum';
import FormItem from 'antd/lib/form/FormItem';
const { Panel } = Collapse;
const { confirm } = Modal;
const SortableTaskContainer = sortableContainer((props) => <div {...props}></div>);
const SortableTaskItem = sortableElement((props) => <div {...props}>{props.taskitem}</div>);
const SortableStageContainer = sortableContainer((props) => <div {...props}></div>);
const SortableStageItem = sortableElement((props) => <div {...props}>{props.stageitem}</div>);
const SortableContentContainer = sortableContainer((props) => <div {...props}></div>);
const SortableContentItem = sortableElement((props) => <div {...props}>{props.contentitem}</div>);
......@@ -62,10 +64,11 @@ class TrainContent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: props.data,
stageList: props.stageList,
finishStandard: props.finishStandard,
showCourseDrawer: false,
showExamDrawer: false,
selectedTaskIndex: 0,
selectedStageIndex: 0,
expiredCourseList: [], // 失效课程
showStandardDetail: false, // 是否展开高级设置
};
......@@ -151,97 +154,135 @@ class TrainContent extends Component {
});
};
setTrianTypeOption = () => {
setTrianTypeOption = (index) => {
return (
<Menu>
<Menu.Item key='course' onClick={() => this.setState({ showCourseDrawer: true })}>
<Menu.Item key='course' onClick={() => this.setState({ showCourseDrawer: true, selectedStageIndex: index })}>
<img className='type-option-icon' src={ENUM.LearningContentIcon['COURSE']} />
<span className='type-option-text'>课程</span>
</Menu.Item>
<Menu.Item key='exam' onClick={() => this.setState({ showExamDrawer: true })}>
<Menu.Item key='exam' onClick={() => this.setState({ showExamDrawer: true, selectedStageIndex: index })}>
<img className='type-option-icon' src={ENUM.LearningContentIcon['EXAM']} />
<span className='type-option-text'>考试</span>
</Menu.Item>
<Menu.Item key='homework'>
{/* <Menu.Item key='homework'>
<img className='type-option-icon' src={ENUM.LearningContentIcon['HOMEWORK']} />
<span className='type-option-text'>实操作业</span>
</Menu.Item>
</Menu.Item> */}
</Menu>
);
};
onTaskSortEnd = ({ oldIndex, newIndex }) => {
const { dataSource } = this.state;
onStageSortEnd = ({ oldIndex, newIndex }) => {
const { stageList } = this.state;
if (oldIndex !== newIndex) {
const newData = arrayMove([].concat(dataSource), oldIndex, newIndex).filter((el) => !!el);
const _stageList = arrayMove([].concat(stageList), oldIndex, newIndex).filter((el) => !!el);
this.setState(
{
dataSource: newData,
stageList: _stageList,
},
() => {
this.props.onChange(newData);
this.props.onChange('stageList', _stageList);
}
);
}
};
onContentSortEnd = ({ oldIndex, newIndex }, parentIndex) => {
const { dataSource } = this.state;
const { stageList } = this.state;
const _dataSource = [...dataSource];
const _stageList = [...stageList];
if (oldIndex !== newIndex) {
_dataSource[parentIndex].courseList = arrayMove([].concat(dataSource[parentIndex].courseList), oldIndex, newIndex).filter((el) => !!el);
_stageList[parentIndex].contentList = arrayMove([].concat(stageList[parentIndex].contentList), oldIndex, newIndex).filter((el) => !!el);
this.setState(
{
dataSource: _dataSource,
stageList: _stageList,
},
() => {
this.props.onChange(_dataSource);
this.props.onChange('stageList', _stageList);
}
);
}
};
handleRenameTaskName = (e, item) => {
handleRenameStageName = (e, item) => {
const { value } = e.target;
const { dataSource } = this.state;
item.taskName = value;
const { stageList } = this.state;
item.stageName = value;
item.check = true;
this.setState(
{
dataSource,
stageList,
},
() => {
this.props.onChange(dataSource);
this.props.onChange('stageList', stageList);
}
);
};
handleTaskNameBlur = (e, item) => {
handleStageNameBlur = (e, item) => {
const { value } = e.target;
const { dataSource } = this.state;
const { stageList } = this.state;
let input = /^[\s]*$/;
if (value && !input.test(value)) {
item.type = 'text';
this.setState(
{
dataSource,
stageList,
},
() => {
this.props.onChange(dataSource);
this.props.onChange('stageList', stageList);
}
);
}
};
handleValidatorTaskName = (rule, value) => {
handleValidatorStageName = (value, currentIndex) => {
const { stageList } = this.state;
const sameStageName = stageList.filter((item, index) => {
return item.stageName === value && index !== currentIndex;
});
let input = /^[\s]*$/;
if (input.test(value) || !value) {
return Promise.reject(new Error('请输入任务名称'));
return '请输入阶段名称';
} else if (sameStageName.length > 0) {
return '阶段名称已存在';
}
return false;
};
// 移除阶段
handleDeleteStage = (index) => {
return confirm({
title: '删除阶段',
content: '删除该阶段会连同课程、作业、考试一起删除,删除后不可恢复,是否仍要删除?',
icon: <span className='icon iconfont default-confirm-icon'>&#xe839; </span>,
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: () => {
this.handleConfirmDeleteStage(index);
},
});
};
handleConfirmDeleteStage = (index) => {
const { stageList } = this.state;
const _stageList = [...stageList];
_stageList.splice(index, 1);
this.setState(
{
stageList: _stageList,
},
() => {
this.props.onChange('stageList', _stageList);
}
return Promise.resolve();
);
};
renderStageInfo = (item, index) => {
const { startCheck } = this.props;
return (
<div className='sort-stage-item'>
<div className='item-info'>
......@@ -250,33 +291,27 @@ class TrainContent extends Component {
<When condition={item.type === 'input'}>
<Form>
<Form.Item
initialValue={item.taskName}
validateTrigger={['onChange', 'onBlur']}
name={['stageName']}
rules={[
{
validator: (rule, value) => this.handleValidatorTaskName(rule, value),
},
]}>
initialValue={item.stageName}
validateStatus={(item.check || startCheck) && this.handleValidatorStageName(item.stageName, index) ? 'error' : ''}
help={(item.check || startCheck) && this.handleValidatorStageName(item.stageName, index)}>
<Input
className='info-input'
style={{ width: 300 }}
placeholder='请输入阶段名称'
maxLength={20}
value={item.stageName}
onChange={(e) => {
this.handleRenameTaskName(e, item);
e.stopPropagation();
this.handleRenameStageName(e, item);
}}
onBlur={(e) => {
this.handleTaskNameBlur(e, item);
e.stopPropagation();
this.handleStageNameBlur(e, item);
}}
/>
</Form.Item>
</Form>
</When>
<Otherwise>
<span className='info-text'>{item.taskName}</span>
<span className='info-text'>{item.stageName}</span>
</Otherwise>
</Choose>
</div>
......@@ -284,9 +319,11 @@ class TrainContent extends Component {
<span
className='operate__item'
onClick={(e) => {
const { dataSource } = this.state;
if (item.type === 'input') return;
const { stageList } = this.state;
item.type = 'input';
this.setState({ dataSource });
item.check = true;
this.setState({ stageList });
e.stopPropagation();
}}>
<span className='icon iconfont'>&#xe6f5;</span>
......@@ -296,7 +333,7 @@ class TrainContent extends Component {
className='operate__item'
style={{ marginLeft: 16 }}
onClick={(e) => {
this.handleDeleteTask(index);
this.handleDeleteStage(index);
e.stopPropagation();
}}>
<span className='icon iconfont'>&#xe6f6;</span>
......@@ -313,19 +350,15 @@ class TrainContent extends Component {
<Collapse ghost>
<Panel header={this.renderStageInfo(item, index)} key={index}>
<SortableContentContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={(item) => this.onContentSortEnd(item, index)}>
{item.courseList.map((contentItem, contentIndex) => (
{item.contentList.map((contentItem, contentIndex) => (
<SortableContentItem
contentitem={this.renderContentItem(contentItem, contentIndex, index)}
index={contentIndex}
key={contentIndex}></SortableContentItem>
))}
</SortableContentContainer>
<Dropdown
overlay={this.setTrianTypeOption()}
onClick={() => {
// this.showRelatedCourseModal(index);
}}>
<span className='add-content-btn'>+ 添加学习内容</span>
<Dropdown overlay={this.setTrianTypeOption(index)}>
<span className={`add-content-btn ${item.contentList.length === 20 && 'disabled'}`}>+ 添加学习内容</span>
</Dropdown>
</Panel>
</Collapse>
......@@ -362,21 +395,22 @@ class TrainContent extends Component {
// 添加阶段
addStage = () => {
const { dataSource } = this.state;
const taskObj = {
taskName: '',
index: dataSource.length,
const { stageList } = this.state;
const stageObj = {
stageName: '',
index: stageList.length,
type: 'input',
open: true,
courseList: [],
contentList: [],
check: false,
};
const newData = [...dataSource, taskObj];
const _stageList = [...stageList, stageObj];
this.setState(
{
dataSource: newData,
stageList: _stageList,
},
() => {
this.props.onChange(newData);
this.props.onChange('stageList', _stageList);
}
);
};
......@@ -394,37 +428,84 @@ class TrainContent extends Component {
};
confirmSelectCourse = (selectList) => {
const { selectedTaskIndex } = this.state;
const { dataSource } = this.state;
const newData = [...dataSource];
const selectData = [...newData[selectedTaskIndex].courseList];
const { selectedStageIndex } = this.state;
const { stageList } = this.state;
const _stageList = [...stageList];
const selectData = [..._stageList[selectedStageIndex].contentList];
const _selectData = [...selectData, ...selectList];
newData[selectedTaskIndex].courseList = _selectData;
_stageList[selectedStageIndex].contentList = _selectData;
this.setState(
{
showCourseDrawer: false,
dataSource: newData,
stageList: _stageList,
},
() => {
this.props.onChange('stageList', _stageList);
}
);
};
percentCompleteBlur = (e, field) => {
let _percentCompleteLive;
const { value } = e.target;
if (value > 100) {
_percentCompleteLive = 100;
} else {
if (value < 0) {
_percentCompleteLive = 0;
} else {
_percentCompleteLive = value;
}
}
this.props.onChange(field, _percentCompleteLive);
};
changePercentComplete = (e, field) => {
const { finishStandard } = this.state;
let _percentComplete = 0;
const value = e.target.value.replace(/\D/g, '');
if (value > 100) {
_percentComplete = 100;
} else {
if (value < 0) {
_percentComplete = 0;
} else {
_percentComplete = value;
}
}
this.setState(
{
finishStandard: {
...finishStandard,
[field]: _percentComplete,
},
},
() => {
this.props.onChange(newData);
this.props.onChange('finishStandard', this.state.finishStandard);
}
);
};
render() {
const { dataSource, showCourseDrawer, showExamDrawer, expiredCourseList, showStandardDetail } = this.state;
const { stageList, showCourseDrawer, showExamDrawer, expiredCourseList, showStandardDetail, finishStandard } = this.state;
const { percentCompleteLive, percentCompleteVideo, percentCompletePicture } = finishStandard;
const { startCheck } = this.props;
return (
<div className='train-content-page'>
<div className='train-content__warp'>
<SortableTaskContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={this.onTaskSortEnd}>
{dataSource.map((item, index) => (
<SortableTaskItem taskitem={this.renderStageItem(item, index)} index={index} key={index}></SortableTaskItem>
<SortableStageContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={this.onStageSortEnd}>
{stageList.map((item, index) => (
<SortableStageItem stageitem={this.renderStageItem(item, index)} index={index} key={index}></SortableStageItem>
))}
</SortableTaskContainer>
</SortableStageContainer>
{stageList.length < 10 && (
<div className='add-stage-btn' onClick={() => this.addStage()}>
+ 添加阶段
</div>
{showCourseDrawer && <RelatedCourseDrawer data={dataSource} onClose={this.onCloseCourseDrawer} onSelect={this.confirmSelectCourse} />}
)}
{showCourseDrawer && <RelatedCourseDrawer data={stageList} onClose={this.onCloseCourseDrawer} onSelect={this.confirmSelectCourse} />}
{showExamDrawer && <RelatedExamDrawer onClose={this.onCloseExamDrawer} />}
</div>
<div className='expired-info__wrap'>
......@@ -445,19 +526,22 @@ class TrainContent extends Component {
{showStandardDetail && (
<div className='detail-container'>
<div className='title-text'>完成标准:</div>
<div className='detail-box'>
<Form className='detail-box'>
<div className='item-info'>
<span>
<img src={ENUM.LearningContentIcon['LIVE']} />
</span>
<span>
直播课单个课程,学员学习进度达到
<FormItem validateStatus={startCheck && !percentCompleteLive ? 'error' : ''} help={startCheck && !percentCompleteLive && '请输入完成标准'}>
<Input
// value={percentCompleteLive}
value={percentCompleteLive}
onChange={(e) => {
// this.props.onChange('percentCompleteLive', e.target.value.replace(/\D/g, ''));
this.changePercentComplete(e, 'percentCompleteLive');
}}
// onBlur={(e) => this.percentCompleteBlur(e, 'percentCompleteLive')}
className='input-box'
/>
</FormItem>
%,即视为"已完成"学习
</span>
</div>
......@@ -465,14 +549,17 @@ class TrainContent extends Component {
<img src={ENUM.LearningContentIcon['VOICE']} />
<span>
线上课单个课节,学员学习进度达到
<FormItem
validateStatus={startCheck && !percentCompleteVideo ? 'error' : ''}
help={startCheck && !percentCompleteVideo && '请输入完成标准'}>
<Input
// value={percentCompleteVideo}
value={percentCompleteVideo}
onChange={(e) => {
// this.props.onChange('percentCompleteVideo', e.target.value.replace(/\D/g, ''));
this.changePercentComplete(e, 'percentCompleteVideo');
}}
// onBlur={(e) => this.percentCompleteBlur(e, 'percentCompleteVideo')}
className='input-box'
/>
</FormItem>
%,即课节视为"已完成"学习
</span>
</div>
......@@ -480,18 +567,21 @@ class TrainContent extends Component {
<img src={ENUM.LearningContentIcon['PICTURE']} />
<span>
图文课单个课程,学员学习进度达到
<FormItem
validateStatus={startCheck && !percentCompletePicture ? 'error' : ''}
help={startCheck && !percentCompletePicture && '请输入完成标准'}>
<Input
// value={percentCompletePicture}
value={percentCompletePicture}
onChange={(e) => {
// this.props.onChange('percentCompletePicture', e.target.value.replace(/\D/g, ''));
this.changePercentComplete(e, 'percentCompletePicture');
}}
// onBlur={(e) => this.percentCompleteBlur(e, 'percentCompletePicture')}
className='input-box'
/>
</FormItem>
%,即视为"已完成"学习
</span>
</div>
</div>
</Form>
</div>
)}
</div>
......
......@@ -53,8 +53,10 @@
color: #333333;
vertical-align: middle;
margin-bottom: 20px;
* {
vertical-align: middle;
.ant-form-item {
display: inline-block;
margin-bottom: 0 !important;
vertical-align: baseline;
}
img {
width: 20px;
......@@ -185,6 +187,11 @@
padding: 16px;
cursor: pointer;
display: inline-block;
&.disabled {
color: #ccc;
cursor: not-allowed;
pointer-events: none;
}
}
.ant-collapse-content {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment