Commit bd6794c0 by yuananting

feat:添加培训内容部分

parent 52a7b420
@font-face {
font-family: 'iconfont'; /* Project id 2223403 */
src: url('//at.alicdn.com/t/font_2223403_oe5p510553.woff2?t=1624259078391') format('woff2'),
url('//at.alicdn.com/t/font_2223403_oe5p510553.woff?t=1624259078391') format('woff'),
url('//at.alicdn.com/t/font_2223403_oe5p510553.ttf?t=1624259078391') format('truetype');
src: url('//at.alicdn.com/t/font_2223403_e1xgcyaur7.woff2?t=1627810786858') format('woff2'),
url('//at.alicdn.com/t/font_2223403_e1xgcyaur7.woff?t=1627810786858') format('woff'),
url('//at.alicdn.com/t/font_2223403_e1xgcyaur7.ttf?t=1627810786858') format('truetype');
}
.iconfont {
font-family: 'iconfont' !important;
......
......@@ -8,30 +8,30 @@
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="icon" href="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="小麦企学院,一站式企业培训数字化服务商,通过“工具+内容”,帮助企业快速从0到1搭建数字化培训体系,并让整个培训过程可视化,降低培训成本,提升培训效率。"
/>
<meta
name="keywords"
content="小麦企学院,企业培训,员工培训,企业大学,企业内训,企业外训,培训计划,培训素材,企培,企训,素材库,培训课程,培训任务,直播课,线上课,图文课,线下活动,知识库,作业,考试,排行榜,培训类别管理,定制培训计划,管理数据,学习数据,企学院,资料共享,培训数字化,数字化培训,培训工具,在线培训,线上培训,培训saas,培训管理,企业微信培训,对客培训,客户培训,直播培训,互联网培训,新员工培训,管理培训,管理者培训,工人培训,制造业培训,餐饮培训,服务业培训,零售培训,门店培训,工厂培训,车间培训,培训补贴,人事培训,财务培训,职场培训,企业学院平台,教育企业学院,教育企业平台,教育平台学院,企业学习,酷学院,小鹅通,企业学院,云学堂,时代光华,云课堂,魔学院,云大学,米知云,授课学堂"
/>
<!-- <link rel="apple-touch-icon" href="../src/common/images/logo.png" /> -->
<link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/c4KiP2epBP.png" />
<head>
<meta charset="utf-8" />
<link rel="icon" href="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="小麦企学院,一站式企业培训数字化服务商,通过“工具+内容”,帮助企业快速从0到1搭建数字化培训体系,并让整个培训过程可视化,降低培训成本,提升培训效率。"
/>
<meta
name="keywords"
content="小麦企学院,企业培训,员工培训,企业大学,企业内训,企业外训,培训计划,培训素材,企培,企训,素材库,培训课程,培训任务,直播课,线上课,图文课,线下活动,知识库,作业,考试,排行榜,培训类别管理,定制培训计划,管理数据,学习数据,企学院,资料共享,培训数字化,数字化培训,培训工具,在线培训,线上培训,培训saas,培训管理,企业微信培训,对客培训,客户培训,直播培训,互联网培训,新员工培训,管理培训,管理者培训,工人培训,制造业培训,餐饮培训,服务业培训,零售培训,门店培训,工厂培训,车间培训,培训补贴,人事培训,财务培训,职场培训,企业学院平台,教育企业学院,教育企业平台,教育平台学院,企业学习,酷学院,小鹅通,企业学院,云学堂,时代光华,云课堂,魔学院,云大学,米知云,授课学堂"
/>
<!-- <link rel="apple-touch-icon" href="../src/common/images/logo.png" /> -->
<link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/c4KiP2epBP.png" />
<!--
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_oe5p510553.css" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_e1xgcyaur7.css" />
<!--
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
......@@ -40,19 +40,19 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>小麦企学院</title>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/iscroll-zoom-min.js"></script>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/hammer.min.js"></script>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/lrz.all.bundle.js"></script>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/PhotoClip.js"></script>
<script type="text/javascript" charset="utf-8" src="//g.alicdn.com/sd/ncpc/nc.js?t=2015052012"></script>
<script type="text/javascript" src="https://xiaomai-js.oss-cn-hangzhou.aliyuncs.com/loghub-xm-0.0.1-beta.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<title>小麦企学院</title>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/iscroll-zoom-min.js"></script>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/hammer.min.js"></script>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/lrz.all.bundle.js"></script>
<script type="text/javascript" src="https://image.xiaomaiketang.com/xm/PhotoClip.js"></script>
<script type="text/javascript" charset="utf-8" src="//g.alicdn.com/sd/ncpc/nc.js?t=2015052012"></script>
<script type="text/javascript" src="https://xiaomai-js.oss-cn-hangzhou.aliyuncs.com/loghub-xm-0.0.1-beta.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
......@@ -62,5 +62,5 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</body>
</html>
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-07-29 13:57:03
* @LastEditors: yuananting
* @LastEditTime: 2021-07-30 16:35:59
* @LastEditTime: 2021-08-02 10:42:28
* @Description: 任务中心-培训任务-新建页面
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
......@@ -38,10 +38,18 @@ const DEFAULT_BASIC_INFO = {
percentCompletePicture: 100,
};
const DEFAULT_TASK_LIST = [
{
taskName: '阶段一',
courseList: [],
},
];
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);
function renderFooter() {
return (
......@@ -81,6 +89,10 @@ function AddTrainTask() {
});
}
function handleChangeTaskInfo(value) {
setTaskList(value);
}
return (
<div className='page add-train-task'>
<Breadcrumbs navList={type == 'add' ? '新建培训任务' : '编辑培训任务'} goBack={handleGoBack} />
......@@ -93,7 +105,7 @@ function AddTrainTask() {
<BasicInfo data={basicInfo} onChange={handleChangeBasicInfo} />
</TabPane>
<TabPane tab='2 培训内容' key='TRAIN_CONTENT'>
<TrainContent />
<TrainContent data={taskList} onChange={handleChangeTaskInfo} />
</TabPane>
</Tabs>
</div>
......
/*
* @Author: yuananting
* @Date: 2021-08-01 17:28:30
* @LastEditors: yuananting
* @LastEditTime: 2021-08-02 11:09:50
* @Description: 新建培训任务-关联课程抽屉
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { Component } from 'react';
import _ from 'underscore';
import { Radio, Tabs, Drawer, Input, message, Button, Tooltip } from 'antd';
import { PageControl, XMTable } from '@/components';
import college from '@/common/lottie/college';
import CourseService from '@/domains/course-domain/CourseService';
import User from '@/common/js/user';
import Service from '@/common/js/service';
import './RelatedCourseDrawer.less';
const { Search } = Input;
const { TabPane } = Tabs;
class RelatedCourseDrawer extends Component {
constructor(props) {
super(props);
this.state = {
// 直播课列表相关参数
liveDataSource: [],
liveSize: 10,
liveQuery: {
current: 1,
},
liveTotalCount: 0,
selectLive: [], //弹窗内已选择的直播课程
currentLiveCourseListData: [], //页面中已关联的直播课程
// 线上课列表相关参数(内外部课程)
videoCourseDivision: 'internal',
videoDataSource: {
external: [],
internal: [],
},
videoSize: {
external: 10,
internal: 10,
},
videoSearchName: {
external: '',
internal: '',
},
videoSearchDefalt: '',
videoQuery: {
external: {
current: 1,
},
internal: {
current: 1,
},
},
videoTotalCount: {
external: 0,
internal: 0,
},
selectVideo: {
external: [],
internal: [],
}, //弹窗内已选择的线上课程
currentVideoCourseListData: {
external: [],
internal: [],
}, //页面中已关联的线上课程
pictureDataSource: [],
pictureSize: 10,
pictureQuery: {
current: 1,
},
pictureTotalCount: 0,
selectPicture: [], //弹窗内已选择的线上课程
currentPictureCourseListData: [], //页面中已关联的线上课程
activeKey: 'live',
currentTaskCourseData: [],
};
}
componentDidMount() {
this.handleFetchLiveDataList();
this.handleFetchVideoDataList();
this.handleFetchPictureDataList();
}
// 获取直播课列表
handleFetchLiveDataList = () => {
const { liveQuery, liveSize } = this.state;
const _data = [...this.props.data];
let currentLiveCourseListData = [];
_data.map((item) => {
item.courseList.map((childItem, childIndex) => {
if (childItem.courseType === 'LIVE') {
currentLiveCourseListData.push(childItem.courseId);
}
return childItem;
});
return item;
});
const params = {
...liveQuery,
size: liveSize,
excludeCourseIdList: currentLiveCourseListData,
};
CourseService.getLiveCloudCourseBasePage(params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
liveDataSource: records,
liveTotalCount: Number(total),
currentLiveCourseListData,
});
});
};
// 获取线上课列表
handleFetchVideoDataList = () => {
const { videoQuery, videoSize, videoDataSource, videoTotalCount, videoCourseDivision } = this.state;
const _data = [...this.props.data];
let currentVideoCourseListData = [];
_data.map((item, index) => {
item.courseList.map((childItem, childIndex) => {
if (childItem.courseType === 'VOICE') {
currentVideoCourseListData.push(childItem.courseId);
}
return childItem;
});
return item;
});
const params = {
...videoQuery[videoCourseDivision],
size: videoSize[videoCourseDivision],
courseDivision: videoCourseDivision === 'internal' ? 'INTERNAL' : 'EXTERNAL',
excludeCourseIdList: currentVideoCourseListData,
};
CourseService.videoScheduleBasePage(params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
videoDataSource: {
...videoDataSource,
[videoCourseDivision]: records,
},
videoTotalCount: {
...videoTotalCount,
[videoCourseDivision]: Number(total),
},
currentVideoCourseListData,
});
});
};
// 获取图文课列表
handleFetchPictureDataList = () => {
const { pictureQuery, pictureSize } = this.state;
const _data = [...this.props.data];
let currentPictureCourseListData = [];
_data.map((item, index) => {
item.courseList.map((childItem, childIndex) => {
if (childItem.courseType === 'PICTURE') {
currentPictureCourseListData.push(childItem.courseId);
}
return childItem;
});
return item;
});
const params = {
...pictureQuery,
size: pictureSize,
courseType: 'PICTURE',
storeId: User.getStoreId(),
excludeCourseIdList: currentPictureCourseListData,
};
Service.Hades('public/hades/mediaCoursePage', params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
pictureDataSource: records,
pictureTotalCount: Number(total),
currentPictureCourseListData,
});
});
};
handleChangVideoCourseName = (value) => {
const { videoQuery, videoCourseDivision, videoSearchName } = this.state;
videoQuery[videoCourseDivision].courseName = value;
videoQuery[videoCourseDivision].current = 1;
this.setState({
...videoQuery,
videoSearchDefalt: value,
videoSearchName: {
...videoSearchName,
[videoCourseDivision]: value,
},
});
};
handleChangLiveCourseName = (value) => {
const { liveQuery } = this.state;
liveQuery.courseName = value;
liveQuery.current = 1;
this.setState({
liveQuery,
});
};
handleChangPictureCourseName = (value) => {
const { pictureQuery } = this.state;
pictureQuery.courseName = value;
pictureQuery.current = 1;
this.setState({
pictureQuery,
});
};
onShowLiveSizeChange = (current, size) => {
if (current === size) {
return;
}
this.setState(
{
liveSize: size,
},
() => {
this.handleFetchLiveDataList();
}
);
};
onShowVideoSizeChange = (current, size) => {
if (current === size) {
return;
}
this.setState(
{
videoSize: size,
},
() => {
this.handleFetchLiveDataList();
}
);
};
onShowPictureSizeChange = (current, size) => {
if (current === size) {
return;
}
this.setState(
{
pictureSize: size,
},
() => {
this.handleFetchPictureDataList();
}
);
};
// 请求表头
parseCourseColumns = (type) => {
const columns = [
{
title: (
<span>
<span>课程信息</span>
<Tooltip title='仅显示未关联课程,已关联课程不支持重复选择'>
<i className='icon iconfont' style={{ marginLeft: '5px', cursor: 'pointer', color: '#bfbfbf', fontSize: '14px', fontWeight: '400' }}>
&#xe61d;
</i>
</Tooltip>
</span>
),
key: 'course',
dataIndex: 'course',
width: '40%',
render: (val, record) => {
if (type === 'live') {
let hasCover = false;
return (
<div className='course-info'>
{record.courseMediaVOS.map((item) => {
if (item.contentType === 'COVER') {
hasCover = true;
return <img className='course-cover' src={item.mediaUrl} alt='' />;
}
return null;
})}
<If condition={!hasCover}>
<img className='course-cover' src={'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'} alt='' />
</If>
<div>
<div className='course-name'>{record.courseName}</div>
{/* <span
className='course-status'
style={{ color: courseStateShow[record.courseState].color, border: `1px solid ${courseStateShow[record.courseState].color}` }}>
{courseStateShow[record.courseState].title}
</span> */}
</div>
</div>
);
} else {
const { coverUrl } = record;
return (
<div className='course-info'>
<img
className='course-cover'
src={
coverUrl || (type === 'video' ? 'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png' : 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png')
}
alt=''
/>
<div className='course-name'>{record.courseName}</div>
</div>
);
}
},
},
type === 'live'
? {
title: '上课时间',
key: 'courseTime',
dataIndex: 'courseTime',
width: '40%',
render: (val, record) => {
return (
<div>
<div>{formatDate('YYYY-MM-DD', record.startTime)}</div>
<div>
{formatDate('H:i', record.startTime)}~{formatDate('H:i', record.endTime)}
</div>
</div>
);
},
}
: type === 'video'
? {
title: '课节数',
key: 'courseChapterNum',
dataIndex: 'courseChapterNum',
width: '20%',
align: 'right',
render: (val, record) => {
return <span>{val || 1}</span>;
},
}
: {
title: '更新时间',
key: 'updated',
dataIndex: 'updated',
width: '25%',
render: (val, record) => {
return <span className='course-status'>{formatDate('YYYY-MM-DD', record.updated)}</span>;
},
},
{
title: '学院展示',
key: 'shelfState',
dataIndex: 'shelfState',
width: '20%',
render: (val, record) => {
return <span>{record.shelfState === 'YES' ? '开启' : '关闭'}</span>;
},
},
];
return columns;
};
selectLiveList = (record, selected) => {
const { selectVideo, currentTaskCourseData, selectLive, selectPicture } = this.state;
let _list = [];
if (selected || !_.find(selectLive, (item) => item.liveCourseId === record.liveCourseId)) {
_list = _.uniq(selectLive.concat([record]), false, (item) => item.liveCourseId);
} else {
_list = _.reject(selectLive, (item) => item.liveCourseId === record.liveCourseId);
}
if (_list.length + currentTaskCourseData.length + selectVideo.internal.length + selectVideo.external.length + selectPicture.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({ selectLive: _list });
};
selectVideoList = (record, selected) => {
const { selectVideo, currentTaskCourseData, selectLive, selectPicture, videoCourseDivision } = this.state;
let { [videoCourseDivision]: selectList } = selectVideo;
let otherVideoCourseDivision = videoCourseDivision === 'internal' ? 'external' : 'internal';
let _list = [];
if (selected || !_.find(selectList, (item) => item.id === record.id)) {
_list = _.uniq(selectList.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectList, (item) => item.id === record.id);
}
if (_list.length + selectVideo[otherVideoCourseDivision]?.length + currentTaskCourseData.length + selectLive.length + selectPicture.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({
selectVideo: {
...selectVideo,
[videoCourseDivision]: _list,
},
});
};
selectPictureList = (record, selected) => {
const { selectVideo, currentTaskCourseData, selectLive, selectPicture } = this.state;
let _list = [];
if (selected || !_.find(selectPicture, (item) => item.id === record.id)) {
_list = _.uniq(selectPicture.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectPicture, (item) => item.id === record.id);
}
if (_list.length + currentTaskCourseData.length + selectLive.length + selectVideo.internal.length + selectVideo.external.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({ selectPicture: _list });
};
clearSelectCourse = () => {
this.setState({
selectLive: [],
selectVideo: {
internal: [],
external: [],
},
selectPicture: [],
});
};
handleSelectVideo = (selectVideo) => {
return selectVideo.map((item) => {
let _item = {};
_item.courseId = item.id;
_item.courseType = 'VOICE';
_item.courseName = item.courseName;
_item.courseChapterNum = item.courseChapterNum;
return _item;
});
};
handleSelectLive = (selectLive) => {
return selectLive.map((item, index) => {
let _item = {};
_item.courseId = item.liveCourseId;
_item.courseType = 'LIVE';
_item.courseName = item.courseName;
_item.courseState = item.courseState;
return _item;
});
};
videoCourseDivisionChange = (e) => {
const { videoSearchName } = this.state;
this.setState(
{
videoCourseDivision: e.target.value,
videoSearchDefalt: videoSearchName[e.target.value],
},
() => {
this.handleFetchVideoDataList();
}
);
};
handleSelectPicture = (selectPicture) => {
return selectPicture.map((item, index) => {
let _item = {};
_item.courseId = item.id;
_item.courseType = 'PICTURE';
_item.courseName = item.courseName;
return _item;
});
};
renderFooter = () => {
const { activeKey } = this.state;
let href = '';
switch (activeKey) {
case 'live':
href = (
<a
target='_blank'
rel='noopener noreferrer'
className='link-create-course'
href={window.location.origin + window.location.pathname + '#/create-live-course?type=add'}
onClick={this.props.onClose}>
没有找到需要的直播课?<span>去创建</span>
</a>
);
break;
case 'video':
href = (
<a
target='_blank'
rel='noopener noreferrer'
className='link-create-course'
href={window.location.origin + window.location.pathname + '#/create-video-course?type=add'}
onClick={this.props.onClose}>
没有找到需要的线上课?<span>去创建</span>
</a>
);
break;
case 'picture':
href = (
<a
target='_blank'
rel='noopener noreferrer'
className='link-create-course'
href={window.location.origin + window.location.pathname + '#/create-graphics-course?type=add'}
onClick={this.props.onClose}>
没有找到需要的图文课?<span>去创建</span>
</a>
);
break;
default:
break;
}
return href;
};
render() {
const {
liveDataSource,
liveSize,
liveQuery,
liveTotalCount,
selectLive,
videoDataSource,
videoSize,
videoQuery,
videoSearchDefalt,
videoTotalCount,
selectVideo,
currentTaskCourseData,
selectPicture,
pictureDataSource,
pictureSize,
pictureQuery,
pictureTotalCount,
videoCourseDivision,
} = this.state;
const { visible } = this.props;
return (
<Drawer
title='关联课程'
width={720}
maskClosable={false}
closable={true}
onClose={this.props.onClose}
visible={visible}
mask
className='related-course-drawer'>
<div>
<Tabs
type='line'
defaultActiveKey='live'
onChange={(activeKey) => {
this.setState({ activeKey: activeKey });
}}>
<TabPane tab='直播课' key='live'>
<div className='search-container'>
<Search
enterButton={<span className='icon iconfont'>&#xe832;</span>}
placeholder='搜索课程名称'
style={{ width: 200 }}
onChange={(e) => {
this.handleChangLiveCourseName(e.target.value);
}}
onSearch={() => {
this.handleFetchLiveDataList();
}}
/>
</div>
<div className='select-area'>
<div className='select-box'>
<div>
<span className='icon iconfont tip-icon'>&#xe61d;</span>
<span className='select-num'>
已选择{selectVideo.internal.length + selectVideo.external.length + selectLive.length + selectPicture.length}
</span>
</div>
<div>
<span className='clear-btn' onClick={this.clearSelectCourse}>
清空
</span>
</div>
</div>
<div className='related-box'>
该任务已关联{currentTaskCourseData.length}个课程,可继续选择{20 - currentTaskCourseData.length}
</div>
</div>
<div>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={(record) => record.liveCourseId}
dataSource={liveDataSource}
columns={this.parseCourseColumns('live')}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectLive, 'liveCourseId'),
onSelect: (record, selected) => {
this.selectLiveList(record, selected);
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(selectLive.concat(changeRows), false, (item) => item.liveCourseId);
} else {
_list = _.reject(selectLive, (item) => _.find(changeRows, (data) => data.liveCourseId === item.liveCourseId));
}
if (_list.length + currentTaskCourseData.length + selectVideo.internal.length + selectVideo.external.length + selectPicture.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength =
_list.length + currentTaskCourseData.length + selectVideo.internal.length + selectVideo.external.length + +selectPicture.length - 20;
_list.splice(_list.length - extraLength, extraLength);
}
this.setState({ selectLive: _list });
},
}}
/>
{liveDataSource.length > 0 && (
<div className='box-footer'>
<PageControl
current={liveQuery.current - 1}
pageSize={liveSize}
size='small'
total={liveTotalCount}
toPage={(page) => {
const _query = { ...liveQuery, current: page + 1 };
this.setState(
{
liveQuery: _query,
},
() => {
this.handleFetchLiveDataList();
}
);
}}
onShowSizeChange={this.onShowLiveSizeChange}
/>
</div>
)}
</div>
</TabPane>
<TabPane tab='线上课' key='video'>
<Radio.Group value={videoCourseDivision} onChange={this.videoCourseDivisionChange} style={{ marginBottom: 16 }}>
<Radio.Button value='internal'>内部课程</Radio.Button>
<Radio.Button value='external'>外部课程</Radio.Button>
</Radio.Group>
<div className='search-container'>
<Search
value={videoSearchDefalt}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
placeholder='搜索课程名称'
style={{ width: 200 }}
onChange={(e) => {
this.handleChangVideoCourseName(e.target.value);
}}
onSearch={() => {
this.handleFetchVideoDataList();
}}
/>
</div>
<div className='select-area'>
<div className='select-box'>
<div>
<span className='icon iconfont tip-icon'>&#xe61d;</span>
<span className='select-num'>
已选择{selectVideo.internal.length + selectVideo.external.length + selectLive.length + selectPicture.length}
</span>
</div>
<div>
<span className='clear-btn' onClick={this.clearSelectCourse}>
清空
</span>
</div>
</div>
<div className='related-box'>
该任务已关联{currentTaskCourseData.length}个课程,可继续选择{20 - currentTaskCourseData.length}
</div>
</div>
<div>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={(record) => record.id}
dataSource={videoDataSource[videoCourseDivision]}
columns={this.parseCourseColumns('video')}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectVideo[videoCourseDivision], 'id'),
onSelect: (record, selected) => {
this.selectVideoList(record, selected);
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
let otherVideoCourseDivision = videoCourseDivision === 'internal' ? 'external' : 'internal';
if (selected) {
_list = _.uniq(selectVideo[videoCourseDivision].concat(changeRows), false, (item) => item.id);
} else {
_list = _.reject(selectVideo[videoCourseDivision], (item) => _.find(changeRows, (data) => data.id === item.id));
}
if (
_list.length + selectVideo[otherVideoCourseDivision]?.length + currentTaskCourseData.length + selectLive.length + selectPicture.length >
20
) {
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength =
_list.length +
selectVideo[otherVideoCourseDivision]?.length +
currentTaskCourseData.length +
selectLive.length +
selectPicture.length -
20;
_list.splice(_list.length - extraLength, extraLength);
}
this.setState({
selectVideo: {
...selectVideo,
[videoCourseDivision]: _list,
},
});
},
}}
/>
{videoDataSource[videoCourseDivision].length > 0 && (
<div className='box-footer'>
<PageControl
current={videoQuery[videoCourseDivision].current - 1}
pageSize={videoSize[videoCourseDivision]}
size='small'
total={videoTotalCount[videoCourseDivision]}
toPage={(page) => {
const _query = { ...videoQuery[videoCourseDivision], current: page + 1 };
this.setState(
{
videoQuery: {
...videoQuery,
[videoCourseDivision]: _query,
},
},
() => {
this.handleFetchVideoDataList();
}
);
}}
onShowSizeChange={this.onShowVideoSizeChange}
/>
</div>
)}
</div>
</TabPane>
<TabPane tab='图文课' key='picture'>
<div className='search-container'>
<Search
enterButton={<span className='icon iconfont'>&#xe832;</span>}
placeholder='搜索课程名称'
style={{ width: 200 }}
onChange={(e) => {
this.handleChangPictureCourseName(e.target.value);
}}
onSearch={() => {
this.handleFetchPictureDataList();
}}
/>
</div>
<div className='select-area'>
<div className='select-box'>
<div>
<span className='icon iconfont tip-icon'>&#xe61d;</span>
<span className='select-num'>
已选择{selectVideo.internal.length + selectVideo.external.length + selectLive.length + selectPicture.length}
</span>
</div>
<div>
<span className='clear-btn' onClick={this.clearSelectCourse}>
清空
</span>
</div>
</div>
<div className='related-box'>
该任务已关联{currentTaskCourseData.length}个课程,可继续选择{20 - currentTaskCourseData.length}
</div>
</div>
<div>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={(record) => record.id}
dataSource={pictureDataSource}
columns={this.parseCourseColumns('picture')}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectPicture, 'id'),
onSelect: (record, selected) => {
this.selectPictureList(record, selected);
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(selectPicture.concat(changeRows), false, (item) => item.id);
} else {
_list = _.reject(selectPicture, (item) => _.find(changeRows, (data) => data.id === item.id));
}
if (_list.length + currentTaskCourseData.length + selectVideo.internal.length + selectVideo.external.length + selectLive.length > 20) {
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength =
_list.length + currentTaskCourseData.length + selectVideo.internal.length + selectVideo.external.length + selectLive.length - 20;
_list.splice(_list.length - extraLength, extraLength);
}
this.setState({ selectPicture: _list });
},
}}
/>
{pictureDataSource.length > 0 && (
<div className='box-footer'>
<PageControl
current={pictureQuery.current - 1}
pageSize={pictureSize}
size='small'
total={pictureTotalCount}
toPage={(page) => {
const _query = { ...pictureQuery, current: page + 1 };
this.setState(
{
pictureQuery: _query,
},
() => {
this.handleFetchPictureDataList();
}
);
}}
onShowSizeChange={this.onShowPictureSizeChange}
/>
</div>
)}
</div>
</TabPane>
</Tabs>
</div>
<div className='footer shrink-footer'>
<Button onClick={this.props.onClose}>取消</Button>
<Button type='primary'>保存</Button>
</div>
</Drawer>
);
}
}
export default RelatedCourseDrawer;
.related-course-drawer {
.link-create-course {
color: #666666;
font-size: 14px;
width: 638px;
text-align: left;
display: inline-block;
span {
color: #2966ff;
}
}
.search-container {
margin-bottom: 16px;
}
.select-area {
margin-bottom: 12px;
display: flex;
justify-content: space-between;
.select-box {
display: inline-box;
width: 186px;
background: #e9efff;
border-radius: 4px;
padding: 6px 16px;
margin-right: 8px;
display: flex;
justify-content: space-between;
.tip-icon {
color: #2966ff;
font-size: 14px;
margin-right: 4px;
}
.select-num {
color: #666666;
font-size: 14px;
}
.clear-btn {
text-align: right;
color: #2966ff;
font-size: 14px;
}
}
.related-box {
padding: 6px 16px;
background: #e9efff;
border-radius: 4px;
flex: 1;
color: #666666;
font-size: 14px;
}
}
.search-container {
margin-bottom: 16px;
}
.select-area {
margin-bottom: 12px;
display: flex;
justify-content: space-between;
.select-box {
display: inline-box;
width: 186px;
background: #e9efff;
border-radius: 4px;
padding: 6px 16px;
margin-right: 8px;
display: flex;
justify-content: space-between;
.tip-icon {
color: #2966ff;
font-size: 14px;
margin-right: 4px;
}
.select-num {
color: #666666;
font-size: 14px;
}
.clear-btn {
text-align: right;
color: #5289fa;
font-size: 14px;
}
}
.related-box {
padding: 6px 16px;
background: #e9efff;
border-radius: 4px;
flex: 1;
color: #666666;
font-size: 14px;
}
}
.course-info {
display: flex;
align-items: center;
.course-cover {
width: 97px;
height: 55px;
display: inline-block;
border-radius: 4px;
margin-right: 8px;
}
.course-name {
font-size: 14px;
color: #666;
text-overflow: -o-ellipsis-lastline;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
width: 180px;
}
.course-status {
font-size: 12px;
line-height: 18px;
display: inline-block;
border-radius: 2px;
padding: 0 8px;
margin-top: 8px;
}
}
.footer {
position: fixed;
right: 0;
bottom: 0;
height: 50px;
width: 720px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 24px;
background: #fff;
border-top: 1px solid #e8e8e8;
z-index: 9999;
.ant-btn {
margin-left: 8px;
}
}
}
......@@ -2,27 +2,239 @@
* @Author: yuananting
* @Date: 2021-07-30 16:33:58
* @LastEditors: yuananting
* @LastEditTime: 2021-07-30 16:58:03
* @LastEditTime: 2021-08-02 10:58:34
* @Description: 任务中心-培训任务-新建-培训内容
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Form, Button, Input, Space, DatePicker, Radio, Tag, Col, message, Tooltip } from 'antd';
import React, { Component } from 'react';
import { Form, Button, Input, Space, DatePicker, Radio, Tag, Col, message, Tooltip, Collapse, Dropdown, Menu, Drawer } from 'antd';
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
import arrayMove from 'array-move';
import './TrainContent.less';
import RelatedCourseDrawer from './RelatedCourseDrawer';
const { Panel } = Collapse;
const SortableTaskContainer = sortableContainer((props) => <div {...props}></div>);
const SortableTaskItem = sortableElement((props) => <div {...props}>{props.taskitem}</div>);
const DragHandle = sortableHandle(() => <span className='drag-btn'>::</span>);
class TrainContent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: props.data,
showCourseDrawer: false,
};
}
setTrianTypeOption = () => {
return (
<Menu>
<Menu.Item key='course' onClick={() => this.setState({ showCourseDrawer: true })}>
<img className='type-option-icon' src='https://image.xiaomaiketang.com/xm/6C2GjSpnDp.png' />
<span>课程</span>
</Menu.Item>
<Menu.Item key='exam'>
<img className='type-option-icon' src='https://image.xiaomaiketang.com/xm/M4BEXnRWbb.png' />
<span>考试</span>
</Menu.Item>
<Menu.Item key='homework'>
<img className='type-option-icon' src='https://image.xiaomaiketang.com/xm/ypWQcFWnxB.png' />
<span>实操作业</span>
</Menu.Item>
</Menu>
);
};
onTaskSortEnd = ({ oldIndex, newIndex }) => {
const { dataSource } = this.state;
if (oldIndex !== newIndex) {
const newData = arrayMove([].concat(dataSource), oldIndex, newIndex).filter((el) => !!el);
this.setState(
{
dataSource: newData,
},
() => {
this.props.onChange(newData);
}
);
}
};
handleRenameTaskName = (e, item) => {
const { value } = e.target;
const { dataSource } = this.state;
item.taskName = value;
this.setState(
{
dataSource,
},
() => {
this.props.onChange(dataSource);
}
);
};
handleTaskNameBlur = (e, item) => {
const { value } = e.target;
const { dataSource } = this.state;
let input = /^[\s]*$/;
if (value && !input.test(value)) {
item.type = 'text';
this.setState(
{
dataSource,
},
() => {
this.props.onChange(dataSource);
}
);
}
};
handleValidatorTaskName = (rule, value) => {
let input = /^[\s]*$/;
if (input.test(value) || !value) {
return Promise.reject(new Error('请输入任务名称'));
}
return Promise.resolve();
};
renderTaskInfo = (item, index) => {
return (
<div className='sort-task-item'>
<Choose>
<When condition={item.type === 'input'}>
<div className='task-name-con'>
{/* <span className='number'>{index + 1}.</span> */}
<Form>
<Form.Item
initialValue={item.taskName}
validateTrigger={['onChange', 'onBlur']}
name={['taskName']}
rules={[
{
validator: (rule, value) => this.handleValidatorTaskName(rule, value),
},
]}>
<Input
className='task-name-input'
style={{ width: 300 }}
placeholder='请输入阶段名称'
maxLength={20}
onChange={(e) => {
this.handleRenameTaskName(e, item);
e.stopPropagation();
}}
onBlur={(e) => {
this.handleTaskNameBlur(e, item);
e.stopPropagation();
}}
/>
</Form.Item>
</Form>
</div>
</When>
<Otherwise>
<div className='task-name-con'>
{/* <span className='number'>{index + 1}.</span> */}
<span className='task-name'>{item.taskName}</span>
</div>
</Otherwise>
</Choose>
<span className='item-operate'>
<span
className='operate__item'
onClick={(e) => {
const { dataSource } = this.state;
item.type = 'input';
this.setState({ dataSource });
e.stopPropagation();
}}>
<span className='icon iconfont'>&#xe6f5;</span>
<span className='text'>重命名</span>
</span>
<span
className='operate__item'
style={{ marginLeft: 16 }}
onClick={(e) => {
this.handleDeleteTask(index);
e.stopPropagation();
}}>
<span className='icon iconfont'>&#xe6f6;</span>
<span className='text'>删除</span>
</span>
</span>
<DragHandle />
</div>
);
};
renderTaskItem = (item, index) => {
return (
<Collapse ghost>
<Panel header={this.renderTaskInfo(item, index)} key={index}>
{/* {renderTaskItem(props.iteminfo, props.index)} */}
<Dropdown
overlay={this.setTrianTypeOption()}
className='add-course-btn'
onClick={() => {
// this.showRelatedCourseModal(index);
}}>
<span>+ 关联课程</span>
</Dropdown>
</Panel>
</Collapse>
);
};
// 添加阶段
addStage = () => {
const { dataSource } = this.state;
const taskObj = {
taskName: '',
index: dataSource.length,
type: 'input',
open: true,
courseList: [],
};
const newData = [...dataSource, taskObj];
this.setState(
{
dataSource: newData,
},
() => {
this.props.onChange(newData);
}
);
};
onCloseCourseDrawer = () => {
this.setState({
showCourseDrawer: false,
});
};
function TrainContent(props) {
const SortableTaskContainer = sortableContainer((props) => <div {...props}></div>);
return (
<div className='train-content__warp'>
<SortableTaskContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={this.onTaskSortEnd} className='plan-task-sort-container'>
{dataSource.map((item, index) => (
<SortableTaskItem taskitem={this.renderTaskItem(item, index)} index={index} key={index}></SortableTaskItem>
))}
</SortableTaskContainer>
</div>
);
render() {
const { dataSource, showCourseDrawer } = this.state;
return (
<div className='train-content__warp'>
<SortableTaskContainer useDragHandle disableAutoscroll helperClass='row-dragging' onSortEnd={this.onTaskSortEnd}>
{dataSource.map((item, index) => (
<SortableTaskItem taskitem={this.renderTaskItem(item, index)} index={index} key={index}></SortableTaskItem>
))}
</SortableTaskContainer>
<div className='add-task-btn' onClick={() => this.addStage()}>
+ 添加阶段
</div>
<RelatedCourseDrawer data={dataSource} onClose={this.onCloseCourseDrawer} visible={showCourseDrawer} />
</div>
);
}
}
export default TrainContent;
.train-content__warp {
.ant-collapse-content {
padding-left: 24px !important;
}
}
.sort-task-item {
width: calc(100% - 24px);
display: inline-flex;
align-items: center;
.ant-form-item {
margin-bottom: 0 !important;
}
.item-name {
color: #333333;
}
.item-operate {
display: none;
margin-left: 30px;
.operate__item {
cursor: pointer;
.icon {
color: #bfbfbf;
font-size: 14px;
}
.text {
color: #666666;
margin-left: 8px;
}
}
}
&:hover {
.item-operate {
display: block;
}
}
.drag-btn {
margin-left: auto;
}
}
.add-course-btn {
color: #2966ff;
}
.add-task-btn {
color: #2966ff;
height: 52px;
background: #f7f8f9;
border-radius: 2px;
padding: 16px;
margin-top: 16px;
cursor: pointer;
}
.type-option-icon {
width: 20px;
height: 20px;
margin-right: 12px;
}
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