Commit 240b85f5 by chenshu

fix:merge

parents 911ec2d4 ec78ded3
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
"underscore": "^1.10.2", "underscore": "^1.10.2",
"url-loader": "2.3.0", "url-loader": "2.3.0",
"video-react": "0.14.1", "video-react": "0.14.1",
"wangeditor": "^3.1.1", "wangeditor": "^4.6.9",
"webpack": "4.42.0", "webpack": "4.42.0",
"webpack-dev-server": "3.11.0", "webpack-dev-server": "3.11.0",
"webpack-manifest-plugin": "2.2.0", "webpack-manifest-plugin": "2.2.0",
......
...@@ -290,7 +290,6 @@ class ChooseMembersModal extends React.Component { ...@@ -290,7 +290,6 @@ class ChooseMembersModal extends React.Component {
onSearch={this.handleSearch} onSearch={this.handleSearch}
onChange={this.handleChangeSearchKey} onChange={this.handleChangeSearchKey}
className='search search-input' className='search search-input'
enterButton={<span className="icon iconfont">&#xe832;</span>} enterButton={<span className="icon iconfont">&#xe832;</span>}
/> />
<div className='container-left-body-table'> <div className='container-left-body-table'>
......
...@@ -131,6 +131,7 @@ ...@@ -131,6 +131,7 @@
.ant-input { .ant-input {
font-size: 14px !important; font-size: 14px !important;
border-color: #d9d9d9;
} }
.ant-input[disabled] { .ant-input[disabled] {
...@@ -619,7 +620,14 @@ mr0 { ...@@ -619,7 +620,14 @@ mr0 {
td.ant-table-column-sort{ td.ant-table-column-sort{
background: none; background: none;
} }
.ant-modal-content .ant-table-thead > tr > th{
padding:9px 24px;
}
.ant-modal-content tr > td{
padding:14px 24px !important;
}
//弹框里的table样式的处理
//按钮的样式的公共处理 //按钮的样式的公共处理
......
...@@ -146,6 +146,36 @@ class Upload { ...@@ -146,6 +146,36 @@ class Upload {
xhr.send(fd); xhr.send(fd);
}) })
} }
static uploadTextToOSS(string, name, success, error) {
if (!string) return success();
const params = {
accessTypeEnum: "PUBLIC",
bizCode: 'CLOUD_CLASS_COMMON',
instId: User.getStoreId(),
resourceName: name,
}
Service.Hades('/public/hades/ossAuthority', params).then((res) => {
const { resourceId, accessId, policy, callback, signature,key, host } = res.result;
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append("OSSAccessKeyId", accessId);
formData.append("policy", policy);
formData.append("callback", callback);
formData.append("Signature", signature);
formData.append("key", key);
formData.append("file", new Blob([string]));
formData.append("success_action_status", 200);
xhr.open("POST", host);
xhr.onload = () => {
success(resourceId);
};
xhr.onerror = () => {
error();
}
xhr.send(formData);
})
}
} }
export default Upload; export default Upload;
\ No newline at end of file
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 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="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_325yz7wxu2d.css"> <link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_uxtdisq90ka.css">
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 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="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_325yz7wxu2d.css"> <link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_uxtdisq90ka.css">
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
......
...@@ -237,17 +237,6 @@ handleChangeBasicInfo = (field, value) => { ...@@ -237,17 +237,6 @@ handleChangeBasicInfo = (field, value) => {
assistantStoreUserId:type==='assistantType'?_.pluck(optionValue, "key"):assistantStoreUserId, assistantStoreUserId:type==='assistantType'?_.pluck(optionValue, "key"):assistantStoreUserId,
} }
}); });
// 批量开始时间改变,结束时间自动同步一致
// if (field === 'startTime') {
// this.setState({
// addLiveClassInfo: {
// ...this.state.addLiveClassInfo,
// [field]: _value,
// endTime: _value,
// }
// });
// }
} }
// 修改简介 // 修改简介
......
...@@ -21,7 +21,7 @@ class EditorBox extends React.Component { ...@@ -21,7 +21,7 @@ class EditorBox extends React.Component {
const { editorId } = this.state; const { editorId } = this.state;
const { detail, onChange } = this.props; const { detail, onChange } = this.props;
const editorInt = new E(`#editor${editorId}`); const editorInt = new E(`#editor${editorId}`);
editorInt.customConfig.menus = [ editorInt.config.menus = [
// 'head', // 标题 // 'head', // 标题
'bold', // 粗体 'bold', // 粗体
// 'fontSize', // 字号 // 'fontSize', // 字号
...@@ -36,18 +36,18 @@ class EditorBox extends React.Component { ...@@ -36,18 +36,18 @@ class EditorBox extends React.Component {
'emoticon', // 表情 'emoticon', // 表情
] ]
editorInt.customConfig.emotions = [ editorInt.config.emotions = [
{ {
title: 'emoji', title: 'emoji',
type: 'emoji', type: 'emoji',
content: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '😊', '🙂', '🙃', '😉', '😓', '😅', '😪', '🤔', '😬', '🤐'] content: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '😊', '🙂', '🙃', '😉', '😓', '😅', '😪', '🤔', '😬', '🤐']
} }
] ]
editorInt.customConfig.zIndex = 1; editorInt.config.zIndex = 1;
editorInt.customConfig.pasteFilterStyle = false; editorInt.config.pasteFilterStyle = false;
editorInt.customConfig.pasteIgnoreImg = true; editorInt.config.pasteIgnoreImg = true;
// 自定义处理粘贴的文本内容 // 自定义处理粘贴的文本内容
editorInt.customConfig.pasteTextHandle = function (content) { editorInt.config.pasteTextHandle = function (content) {
if (content == '' && !content) return '' if (content == '' && !content) return ''
var str = content var str = content
str = str.replace(/<xml>[\s\S]*?<\/xml>/ig, '') str = str.replace(/<xml>[\s\S]*?<\/xml>/ig, '')
...@@ -56,7 +56,7 @@ class EditorBox extends React.Component { ...@@ -56,7 +56,7 @@ class EditorBox extends React.Component {
str = str.replace(/\&nbsp\;/ig, ' ') str = str.replace(/\&nbsp\;/ig, ' ')
return str return str
} }
editorInt.customConfig.onchange = (html) => { editorInt.config.onchange = (html) => {
const textLength = editorInt.txt.text().replace(/\&nbsp\;/ig, ' ').length; const textLength = editorInt.txt.text().replace(/\&nbsp\;/ig, ' ').length;
this.setState({ textLength }, () => { this.setState({ textLength }, () => {
onChange(html, this.state.textLength); onChange(html, this.state.textLength);
...@@ -64,7 +64,6 @@ class EditorBox extends React.Component { ...@@ -64,7 +64,6 @@ class EditorBox extends React.Component {
} }
editorInt.create(); editorInt.create();
editorInt.txt.html(detail.content); editorInt.txt.html(detail.content);
editorInt.change && editorInt.change();
} }
render() { render() {
......
import React from 'react';
import E from 'wangeditor';
import Bus from '../../../core/bus';
import './GraphicsEditor.less';
const { BtnMenu } = E;
class ImageMenu extends BtnMenu {
constructor(editor) {
// data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
const $elem = E.$(
`<div class="w-e-menu" data-title="图片">
<i class="w-e-icon-image"></i>
</div>`
)
super($elem, editor)
}
// 菜单点击事件
clickHandler() {
Bus.trigger('graphicsEditorImage')
}
tryChangeActive() {
}
}
class VideoMenu extends BtnMenu {
constructor(editor) {
// data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
const $elem = E.$(
`<div class="w-e-menu" data-title="视频">
<i class="w-e-icon-play"></i>
</div>`
)
super($elem, editor)
}
// 菜单点击事件
clickHandler() {
Bus.trigger('graphicsEditorVideo')
}
tryChangeActive() {
}
}
class GraphicsEditor extends React.Component {
constructor(props) {
super(props)
this.state = {
editorId: window.random_string(16),
textLength: 0,
}
this.editorInt = null;
}
componentDidMount() {
this.renderEditor()
this.resetIndex(true);
}
componentWillReceiveProps(nextProps) {
const { content } = this.props.detail;
const { content: nextContent } = nextProps.detail;
const videoCount = ((content || '').match(/<iframe/g) || []).length;
const nextVideoCount = ((nextContent || '').match(/<iframe/g) || []).length;
const imageCount = ((content || '').match(/<img/g) || []).length;
const nextImageCount = ((nextContent || '').match(/<img/g) || []).length;
if ((videoCount !== nextVideoCount) || (imageCount !== nextImageCount)) {
this.editorInt && this.editorInt.txt.html(nextProps.detail.content);
}
}
componentWillUnmount() {
this.resetIndex();
}
resetIndex = (bool) => {
const topDom = document.querySelector('.top-container');
const leftDom = document.querySelector('.left-container');
topDom.style.zIndex = bool ? 'auto' : 112;
leftDom.style.zIndex = bool ? 'auto' : 2;
}
renderEditor() {
const { editorId } = this.state;
const { detail, onChange, isIntro } = this.props;
this.editorInt = new E(`#editor${editorId}`);
this.editorInt.config.showFullScreen = !isIntro
this.editorInt.menus.extend('xmimage', ImageMenu);
this.editorInt.menus.extend('xmvideo', VideoMenu);
this.editorInt.config.menus = isIntro ?
[
'head',
'bold',
'fontSize',
'fontName',
'italic',
'underline',
'strikeThrough',
'foreColor',
'backColor',
'list',
'justify',
'emoticon',
'xmimage',
]
: [
'head',
'bold',
'fontSize',
'fontName',
'italic',
'underline',
'strikeThrough',
'indent',
'lineHeight',
'foreColor',
'backColor',
'link',
'list',
'todo',
'justify',
'quote',
'emoticon',
'xmimage',
'xmvideo',
'table',
'code',
'splitLine',
'undo',
'redo',
];
this.editorInt.config.emotions = [
{
title: 'emoji',
type: 'emoji',
content: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '😊', '🙂', '🙃', '😉', '😓', '😅', '😪', '🤔', '😬', '🤐']
}
]
this.editorInt.config.zIndex = 1;
this.editorInt.config.pasteFilterStyle = false;
this.editorInt.config.pasteIgnoreImg = true;
// 自定义处理粘贴的文本内容
this.editorInt.config.pasteTextHandle = function (content) {
if (content == '' && !content) return ''
var str = content
str = str.replace(/<xml>[\s\S]*?<\/xml>/ig, '')
str = str.replace(/<style>[\s\S]*?<\/style>/ig, '')
str = str.replace(/[ | ]*\n/g, '\n')
str = str.replace(/\&nbsp\;/ig, ' ')
return str
}
this.editorInt.config.onchange = (html) => {
const textLength = this.editorInt.txt.text().replace(/\&nbsp\;/ig, ' ').length;
this.setState({ textLength }, () => {
onChange(html, this.state.textLength);
})
}
this.editorInt.create();
this.editorInt.txt.html(detail.content);
}
render() {
const { editorId, textLength } = this.state;
const { limitLength = 1000 } = this.props;
return <div className="wang-editor-container ">
<div className="editor-box" id={`editor${editorId}`}></div>
{textLength > limitLength && <div className="editor-tips">超了{textLength - limitLength}个字</div>}
</div>
}
}
export default GraphicsEditor;
.wang-editor-container {
border: 1px solid #E8E8E8;
border-radius: 4px;
width: 552px;
position: relative;
.w-e-text p,
.w-e-text h1,
.w-e-text h2,
.w-e-text h3,
.w-e-text h4,
.w-e-text h5,
.w-e-text table,
.w-e-text pre {
margin: 0;
}
.w-e-toolbar {
background-color: #fff !important;
border: none !important;
border-bottom: 1px solid #E8E8E8 !important;
}
.w-e-text-container {
border: none !important;
height: 88px !important;
}
.editor-tips {
position: absolute;
top: 5px;
right: 8px;
color: #f5222d;
}
}
\ No newline at end of file
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-07-14 15:43:00 * @Date: 2020-07-14 15:43:00
* @Last Modified by: mikey.zhaopeng * @Last Modified by: chenshu
* @Last Modified time: 2020-11-23 20:23:12 * @Last Modified time: 2021-03-16 17:37:23
* @Description: 大班直播、互动班课的直播课列表 * @Description: 大班直播、互动班课的直播课列表
*/ */
...@@ -83,6 +83,7 @@ class LiveCourseList extends React.Component { ...@@ -83,6 +83,7 @@ class LiveCourseList extends React.Component {
needStr={needStr} needStr={needStr}
data={shareData} data={shareData}
type="liveClass" type="liveClass"
title="直播课"
close={() => { close={() => {
this.setState({ this.setState({
shareLiveModal: null shareLiveModal: null
...@@ -293,7 +294,7 @@ class LiveCourseList extends React.Component { ...@@ -293,7 +294,7 @@ class LiveCourseList extends React.Component {
return ( return (
<div className="related-task"> <div className="related-task">
{ record.relatedPlanList ? { record.relatedPlanList ?
<Tooltip title={this.handlePlanName(record.relatedPlanList)} > <Tooltip title={this.handlePlanName(record.relatedPlanList)} placement="top" arrowPointAtCenter>
{ record.relatedPlanList.map((item,index)=>{ { record.relatedPlanList.map((item,index)=>{
return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span> return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span>
}) })
...@@ -513,7 +514,7 @@ class LiveCourseList extends React.Component { ...@@ -513,7 +514,7 @@ class LiveCourseList extends React.Component {
return ( return (
<div className="related-task"> <div className="related-task">
{ record.relatedPlanList ? { record.relatedPlanList ?
<Tooltip title={this.handlePlanName(record.relatedPlanList)} > <Tooltip title={this.handlePlanName(record.relatedPlanList)} placement="top" arrowPointAtCenter>
{ record.relatedPlanList.map((item,index)=>{ { record.relatedPlanList.map((item,index)=>{
return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span> return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span>
}) })
......
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:07:47
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-04 10:26:07
* @Description: 图文课新增/编辑页
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Button, Input, Radio, message, Modal,Cascader} from 'antd';
import $ from 'jquery';
import { DISK_MAP, FileTypeIcon, FileVerifyMap } from '@/common/constants/academic/lessonEnum';
import { ImgCutModalNew } from '@/components';
import ShowTips from "@/components/ShowTips";
import Breadcrumbs from "@/components/Breadcrumbs";
import Bus from '../../../core/bus'
import AddGraphicsIntro from './components/AddGraphicsIntro';
import SelectStudent from '../modal/select-student';
import SelectPrepareFileModal from '../../prepare-lesson/modal/SelectPrepareFileModal';
import PreviewGraphicsModal from '../modal/PreviewGraphicsModal';
import StoreService from "@/domains/store-domain/storeService";
import Service from '@/common/js/service';
import { randomString } from '@/domains/basic-domain/utils';
import User from '@/common/js/user';
import _ from "underscore";
import Upload from '@/core/upload';
import './AddGraphicsCourse.less';
const EDIT_BOX_KEY = Math.random();
const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' };
//添加课程时课程默认的一些值
const defaultShelfState = 'YES';
const whetherVisitorsJoin = 'NO'
const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
class AddGraphicsCourse extends React.Component {
constructor(props) {
super(props);
const id = getParameterByName("id");
const pageType = getParameterByName("type");
this.state = {
id, // 图文课ID,编辑的时候从URL上带过来
pageType, // 页面类型: add->新建 edit->编辑
imageFile: null, // 需要被截取的图片
courseName: null, // 图文课名称
courseMedia: '',
introduce: '',
courseMediaId: null, // 图文课链接
coverId: null, // 图文封面的recourceId
coverUrl: defaultCoverUrl, // 图文课封面
studentList: [], // 上课学员列表
shelfState:'YES', //是否开启店铺展示
diskList: [], // 机构可见磁盘目录
selectedFileList: [], // 已经从资料云盘中勾选的文件
showCutModal: false, // 是否显示截图弹窗
showSelectVideoModal: false,
studentModal: false,
categoryName:null, //分类名称
courseCatalogList:[], //分类列表
categoryId:null, //分类的Id值
whetherVisitorsJoin: 'NO', // 是否允许游客加入
isContent: '',
}
}
componentDidMount() {
this.initBus()
}
componentWillMount() {
const { id, pageType } = this.state;
this.getCourseCatalogList();
if (pageType === 'edit') {
this.handleFetchScheudleDetail(id);
}
}
initBus = () => {
Bus.bind('graphicsEditorImage', this.uploadImage)
Bus.bind('graphicsEditorVideo', this.uploadVideo)
}
uploadImage = () => {
this.setState({ showSelectImageModal: true })
}
uploadVideo = () => {
this.setState({ showSelectVideoModal: true })
}
//获取分类列表
getCourseCatalogList = ()=>{
StoreService.getCourseCatalogList({current:1,size:1000}).then((res) => {
this.setState({
courseCatalogList:res.result.records
})
});
}
catalogChange= (value) => {
const changeValueLength = value.length;
switch (changeValueLength){
case 1:
this.setState({categoryId:value[0]});
break;
case 2:
this.setState({categoryId:value[1]});
break;
default:
this.setState({categoryId:null});
break;
}
}
// 获取图文课详情
handleFetchScheudleDetail = (courseId) => {
Service.Hades('public/hades/mediaCourseDetail',{
courseId
}).then((res) => {
const { result = {} } = res || {};
const {
courseName,
shelfState,
whetherVisitorsJoin,
courseMediaVOS,
categoryOneName,
categoryTwoName,
categoryId
} = result;
let coverId;
let coverUrl = this.state.coverUrl;
courseMediaVOS.map((item) => {
switch (item.contentType){
case "COVER":
coverId = item.mediaContent;
coverUrl = item.mediaUrl;
break;
case "SCHEDULE":
this.getTextDetail('courseMedia', item.mediaUrl);
break;
case "INTRO":
this.getTextDetail('introduce', item.mediaUrl);
break;
default:
break;
}
return item;
})
let categoryName;
if( categoryTwoName ){
categoryName = `${categoryOneName}-${categoryTwoName}`;
}else{
categoryName = `${categoryOneName}`;
}
this.setState({
coverId,
coverUrl,
courseName,
shelfState,
whetherVisitorsJoin,
categoryName,
categoryId
});
})
}
getTextDetail = (key, url) => {
$.ajax({
data: {},
type: 'GET',
url,
contentType:'application/x-www-form-urlencoded; charset=UTF-8',
success: (res) => {
this.setState({ [key]: res });
}
})
}
handleGoBack = () => {
const {
coverId,
videoName,
videoDuration,
courseName,
courseMediaId,
categoryId,
shelfState,
whetherVisitorsJoin
} = this.state;
if(videoName || videoDuration || courseMediaId || categoryId || courseName || coverId || shelfState !== defaultShelfState || whetherVisitorsJoin !== whetherVisitorsJoin ){
Modal.confirm({
title: '确认要返回吗?',
content: '返回后,本次编辑的内容将不被保存。',
okText: '确认返回',
cancelText: '留在本页',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
RCHistory.goBack();
}
});
}else{
RCHistory.goBack();
}
}
// 修改表单
handleChangeForm = (field, value, coverUrl) => {
this.setState({
[field]: value,
coverUrl: coverUrl ? coverUrl : this.state.coverUrl
});
}
// 显示选择学员弹窗
handleShowSelectStuModal = () => {
this.setState({ studentModal : true });
const { studentList, selectedStuList } = this.state;
// const _studentList = _.map(studentList, (item) => {
// return item.studentId
// })
const studentModal = (
<SelectStudent
showTabs={true}
type="videoCourse"
onSelect={this.handleSelectStudent}
after={true} //表明是不是上课后的状态
studentList={studentList}
close={() => {
this.setState({
studentModal: null,
});
}}
/>
)
this.setState({ studentModal });
}
handleSelectStudent = (studentIds) => {
let studentList = [];
_.each(studentIds, (item) => {
studentList.push({ studentId: item });
});
// this.setState({ studentModal: null });
this.setState({ studentList });
this.setState({ studentModal : false });
}
// 显示预览弹窗
handleShowPreviewModal = () => {
const {
coverUrl,
courseName,
courseMedia,
introduce,
categoryName,
} = this.state;
const courseBasinInfo = {
coverUrl,
courseName,
categoryName
}
const courseIntroInfo = {
courseMedia,
introduce,
}
const previewGraphicsModal = (
<PreviewGraphicsModal
courseBasicInfo={courseBasinInfo}
courseIntroInfo={courseIntroInfo}
close={() => {
this.setState({
previewGraphicsModal: null
})
}}
/>
);
this.setState({ previewGraphicsModal });
}
// 选择图文
handleSelectVideo = (file) => {
this.setState({
showSelectVideoModal: false
})
const { ossUrl } = file;
const { courseMedia, introduce, isContent } = this.state;
this.setState({
[isContent ? 'courseMedia' : 'introduce']: `${isContent ? courseMedia : introduce}<p style="width: 100%;padding-top: 56.25%;position: relative;"><iframe style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;" src="${ossUrl}" /><br><p>`
});
}
handleSelectCover = (file) => {
this.setState({
showSelectImageModal: false
})
const { ossUrl } = file;
const { courseMedia, introduce, isContent } = this.state;
this.setState({
[isContent ? 'courseMedia' : 'introduce']: `${isContent ? courseMedia : introduce}<p><img style="max-width: 100%;" src="${ossUrl}" /><br><p>`
});
}
// 上传封面图
handleShowImgCutModal = (event) => {
const imageFile = event.target.files[0];
if (!imageFile) return;
this.setState({
imageFile,
showCutModal: true,
});
}
// 保存
handleSubmit = () => {
const {
id,
coverId,
pageType,
courseName,
courseMedia,
introduce,
categoryId,
shelfState,
whetherVisitorsJoin,
} = this.state;
const commonParams = {
categoryId,
courseName,
coverId,
operatorId:User.getStoreUserId(),
storeId:User.getStoreId(),
shelfState,
whetherVisitorsJoin,
courseType: 'PICTURE',
};
// 校验必填字段:课程名称, 课程图文
this.handleValidate(courseName, courseMedia, categoryId).then((res) => {
if (!res) return;
Upload.uploadTextToOSS(courseMedia, `${randomString()}.txt`, (courseMediaId) => {
Upload.uploadTextToOSS(introduce, `${randomString()}.txt`, (introduceId) => {
this.submitRemote({
id,
pageType,
commonParams,
courseMediaId,
introduceId,
});
}, () => message.warning('上传课程简介失败'));
}, () => message.warning('上传课程内容失败'));
});
}
submitRemote = (data) => {
const { id, pageType, commonParams, courseMediaId, introduceId } = data;
commonParams.courseMediaId = courseMediaId;
commonParams.introduceId = introduceId;
if (pageType === 'add') {
Service.Hades('public/hades/createMediaCourse', commonParams).then((res) => {
if (!res) return;
message.success("新建成功");
window.RCHistory.goBack();
})
} else {
const editParams = {
courseId:id,
...commonParams,
}
Service.Hades('public/hades/editMediaCourse', editParams).then((res) => {
if (!res) return;
message.success("保存成功");
window.RCHistory.goBack();
});
}
}
handleValidate = (courseName, courseMedia, categoryId) => {
return new Promise((resolve) => {
if (!courseName) {
message.warning('请输入课程名称');
resolve(false);
return false
}
if (!courseMedia) {
message.warning('请输入课程内容');
resolve(false);
return false
}
if(!categoryId){
message.warning('请选择课程分类');
resolve(false);
return false
}
// const textMedia = scheduleMedia.filter((item) => item.mediaType === 'TEXT');
// for (let i = 0, len = textMedia.length; i < len; i++) {
// if (textMedia[i].mediaContentLength && textMedia[i].mediaContentLength.length > 1000) {
// message.warning(`第${i+1}个文字简介的字数超过了1000个字`);
// resolve(false);
// return false
// }
// }
resolve(true);
});
}
render() {
const {
id,
pageType,
courseName,
coverUrl,
studentList,
courseMedia,
introduce,
showCutModal,
showSelectVideoModal,
showSelectImageModal,
diskList,
imageFile,
videoType,
shelfState,
categoryName,
courseCatalogList,
whetherVisitorsJoin,
} = this.state;
// 已选择的上课学员数量
const hasSelectedStu = studentList.length;
const courseWareIcon = FileVerifyMap[videoType] ? FileTypeIcon[FileVerifyMap[videoType].type] : FileTypeIcon[videoType];
return (
<div className="page add-video-course-page">
<Breadcrumbs
navList={pageType === "add" ? "新建图文课" : "编辑图文课"}
goBack={this.handleGoBack}
/>
<div className="box">
<div className="show-tips">
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企培保有依据国家规定及平台规则进行处理的权利" />
</div>
<div className="form">
<div className="course-name required">
<span className="label">课程名称:</span>
<Input
value={courseName}
placeholder="请输入图文课的名称(40字以内)"
maxLength={40}
style={{ width: 240 }}
onChange={(e) => { this.handleChangeForm('courseName', e.target.value)}}
/>
</div>
<div className="cover-url flex mt16">
<div className="label">封面图:</div>
<div className="cover-url__wrap">
<div className="img-content">
<img src={coverUrl} />
</div>
<div className="opt-btns">
<input
type="file"
accept="image/png, image/jpeg, image/jpg"
ref="picInputFile"
style={{display: 'none'}}
onChange={(event) => { this.handleShowImgCutModal(event) }}
/>
<Button onClick={() => {
this.setState({
currentInputFile: this.refs.picInputFile
});
this.refs.picInputFile.click()
}}>{`${(pageType === 'add' && !coverUrl) ? '上传' : '修改'}封面`}</Button>
<div className="tips">建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div>
</div>
</div>
<div className="course-catalog required">
<span className="label">课程分类:</span>
{ (pageType === 'add') &&
<Cascader defaultValue={[categoryName]} options={courseCatalogList} displayRender={ label => label.join('-')} fieldNames={fieldNames} onChange={this.catalogChange} style={{ width: 240 }} placeholder="请选择课程分类" suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}/>
}
{ (pageType === 'edit' && categoryName ) &&
<Cascader defaultValue={[categoryName]} options={courseCatalogList} displayRender={ label => label.join('-')} fieldNames={fieldNames} onChange={this.catalogChange} style={{ width: 240 }} placeholder="请选择课程分类" suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}/>
}
</div>
<div className="intro-info mt16">
<AddGraphicsIntro
data={{
id,
courseMedia,
introduce,
shelfState,
whetherVisitorsJoin,
label: '图文课简介'
}}
onChange={this.handleChangeForm}
/>
</div>
</div>
</div>
<div className="footer">
<Button onClick={this.handleGoBack}>取消</Button>
<Button onClick={this.handleShowPreviewModal}>预览</Button>
<Button type="primary" onClick={_.debounce(() => this.handleSubmit(), 3000, true)}>保存</Button>
</div>
{/* 选择备课文件弹窗 */}
{ showSelectVideoModal &&
<SelectPrepareFileModal
operateType="select"
selectTypeList={['MP4']}
accept="video/mp4"
confirm={{
title: '文件过大,无法上传',
content: '为保障学员的观看体验,上传的图文大小不能超过2G',
}}
tooltip={'格式支持mp4,大小不超过2G'}
isOpen={showSelectVideoModal}
diskList={diskList}
addVideo={true}
onClose={() => {
this.setState({ showSelectVideoModal: false })
}}
onSelect={this.handleSelectVideo}
/>
}
{showSelectImageModal &&
<SelectPrepareFileModal
key="basic"
operateType="select"
multiple={false}
accept="image/jpeg,image/png,image/jpg"
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectImageModal}
onClose={() => {
this.setState({ showSelectImageModal: false })
}}
onSelect={this.handleSelectCover}
/>
}
<ImgCutModalNew
title="裁剪"
width={550}
cutWidth={500}
cutHeight={282}
cutContentWidth={500}
cutContentHeight={300}
visible={showCutModal}
imageFile={imageFile}
bizCode='LIVE_COURSE_MEDIA'
onOk={(urlStr, resourceId) => {
this.setState({ showCutModal: false });
this.handleChangeForm('coverId', resourceId, urlStr)
this.state.currentInputFile.value = '';
}}
onClose={() => this.setState({ showCutModal: false })}
reUpload={() => { this.state.currentInputFile.click() }}
/>
{ this.state.previewGraphicsModal }
</div>
)
}
}
export default AddGraphicsCourse;
.add-video-course-page {
position:relative !important;
.box{
margin-bottom:66px !important;
}
.ant-radio-group {
display: flex;
flex-direction: column;
.radio-item {
margin-bottom: 12px;
.text {
color: #333;
}
.sub-text {
color: #999;
}
}
.ant-radio {
vertical-align: top;
padding-top: 2px;
}
}
.form {
margin-top: 16px;
padding: 0 16px;
.label{
display:inline-block;
text-align:right;
width:85px;
}
.required {
position: relative;
&::before {
position: absolute;
content: '*';
color: red;
left: 5px;
top: 6px;
}
&.label::before {
top: 0;
}
}
.course-catalog{
margin-bottom:16px;
margin-top:16px;
}
.course-ware {
display: flex;
align-items: center;
margin-bottom: 4px;
&__img {
width: 24px;
margin-right: 4px;
}
&__name {
color: #333;
}
}
.flex {
display: flex;
}
.cover-url__wrap {
.img-content {
width: 298px;
height: 172px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.empty-img {
width: 298px;
height: 172px;
border: 1px dashed #EBEBEB;
border-radius: 4px;
padding: 12px;
color: #999;
padding: 52px 24px;
text-align: center;
}
.opt-btns {
margin-top: 8px;
display: flex;
align-items: center;
.tips {
margin-left: 12px;
color: #999;
}
}
}
.select-student {
align-items: center;
margin-left: 24px;
margin-top: 8px;
.has-selected {
margin-left: 12px;
color: #333;
}
}
.sub-content {
margin-left: 85px;
margin-top: 4px;
.tips {
margin-left: 4px;
color: #999;
}
}
}
.footer {
position: fixed;
bottom: 0;
height: 58px;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 252px;
background: #fff;
border-top: 1px solid #E8E8E8;
z-index: 999;
.ant-btn {
margin-left: 10px;
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-16 11:05:17
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-19 15:44:56
* @Description: 添加直播-简介
*/
import React from 'react';
import { Input, message, Upload, Radio, Row, Col, Button, Popover, Switch } from 'antd';
import Service from '@/common/js/service';
import GraphicsEditor from '../../components/GraphicsEditor';
import User from '@/common/js/user';
import UploadOss from '@/core/upload';
import './AddGraphicsIntro.less';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import { DISK_MAP } from '@/common/constants/academic/lessonEnum';
import { ImgCutModalNew } from '@/components';
const { TextArea } = Input;
class AddGraphicsIntro extends React.Component {
constructor(props) {
super(props);
this.state = {
showSelectFileModal: false,
diskList: [],
selectType:null
}
}
componentDidMount() {
this.bindClick();
}
componentWillUnmount() {
this.removeClick();
}
bindClick = () => {
window.addEventListener('click', this.clickEditor)
}
removeClick = () => {
window.removeEventListener('click', this.clickEditor)
}
clickEditor = (e) => {
if (e && e.target.closest('.content-editor')) {
this.isContent = true
} else if (e && e.target.closest('.introduce-editor')) {
this.isContent = false
}
}
// 上传封面图
handleShowImgCutModal = (event) => {
const imageFile = event.target.files[0];
if (!imageFile) return;
this.setState({
imageFile,
showCutModal: true,
});
}
// 选择暖场资源
handleSelectVideo = (file) => {
const { selectType } = this.state;
this.setState({
showSelectFileModal: false
})
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
if(selectType === 'WARMUP'){
const liveCourseWarmMedia = {
contentType: 'WARMUP',
mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE',
mediaContent: resourceId,
mediaUrl: ossUrl,
mediaName: folderName,
size: folderSize
}
this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia);
}else{
// 最多添加九图片
const { liveCourseMediaRequests } = this.props.data;
const list = _.filter(liveCourseMediaRequests, (item) => {
return item.mediaType == "PICTURE";
});
if (list.length > 8) {
message.warning("最多添加9张图片");
return;
}
liveCourseMediaRequests.push({
contentType: 'INTRO',
size: folderSize,
mediaName: folderName,
mediaContent: resourceId,
mediaType: 'PICTURE',
mediaUrl: ossUrl,
});
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
}
changeDetail = (value) => {
this.props.onChange('isContent', !!this.isContent);
this.props.onChange('courseMedia', value);
}
changeIntro = (value) => {
this.props.onChange('isContent', !!this.isContent);
this.props.onChange('introduce', value);
}
whetherVisitorsJoinChange = ()=>{
if(this.props.data.whetherVisitorsJoin==="NO"){
this.props.onChange('whetherVisitorsJoin','YES')
}else{
this.props.onChange('whetherVisitorsJoin','NO')
}
}
shelfStateChange = ()=>{
if(this.props.data.shelfState==="NO"){
this.props.onChange('shelfState','YES')
}else{
this.props.onChange('shelfState','NO')
}
}
render() {
const {data: { id, whetherVisitorsJoin, courseMedia, introduce, shelfState } } = this.props;
const { showSelectFileModal, selectType } = this.state;
return (
<div className="add-video__intro-info">
<div className="allow-tourist-join">
<span className="label">观看设置:</span>
<div className="content">
<div>
<Switch checked={whetherVisitorsJoin==="YES"? true:false} onChange={this.whetherVisitorsJoinChange}/>
</div>
<div>
<div className="desc">
<div>开启:允许未绑定手机号的用户观看</div>
<div>关闭:仅限绑定了手机号的用户可以进入观看视频</div>
</div>
</div>
</div>
</div>
<div className="store-show">
<span className="label">店铺展示:</span>
<div className="content">
<Row>
<Col span={3}>
<Switch checked={shelfState==="YES"? true:false} onChange={this.shelfStateChange}/>
</Col>
<Col span={21}>
<div className="desc">
<div>开启:此视频将在用户店铺的视频列表中出现</div>
<div>关闭:此视频将在用户店铺的视频列表中隐藏</div>
</div>
</Col>
</Row>
</div>
</div>
<div className="introduce">
<span className="label">课程内容:</span>
<div className="content">
<div className="intro-list">
<div className="intro-list__item content-editor">
{(!id || courseMedia) &&
<GraphicsEditor
id="content"
detail={{
content: courseMedia
}}
onChange={(val) => { this.changeDetail(val) }}
/>
}
</div>
</div>
</div>
</div>
<div className="introduce">
<span className="label">课程简介:</span>
<div className="content">
<div className="intro-list">
<div className="intro-list__item introduce-editor">
{(!id || introduce) &&
<GraphicsEditor
id="intro"
isIntro={true}
detail={{
content: introduce
}}
onChange={(val) => { this.changeIntro(val) }}
/>
}
</div>
</div>
</div>
</div>
{/* 选择暖场图文件弹窗 */}
{ showSelectFileModal &&
<SelectPrepareFileModal
operateType="select"
accept={selectType==="INTRO"?"image/jpeg,image/png,image/jpg":"video/mp4,image/jpeg,image/png,image/jpg"}
selectTypeList={ selectType==="INTRO" ? ['JPG', 'JPEG', 'PNG']: ['MP4', 'JPG', 'JPEG', 'PNG'] }
tooltip={ selectType==="INTRO"?'支持文件类型:jpg、jpeg、png':'支持文件类型:jpg、jpeg、png、mp4'}
isOpen={showSelectFileModal}
onClose={() => {
this.setState({ showSelectFileModal: false })
}}
onSelect={this.handleSelectVideo}
/>
}
</div>
)
}
}
export default AddGraphicsIntro;
.add-video__intro-info {
.w-e-full-screen-editor {
background: #fff !important;
}
.playback {
margin-bottom: 10px;
.require {
color: #EC4B35;
}
&__text {
color: #999;
}
}
.label{
display:inline-block;
text-align:right;
width:85px;
}
.playback,
.introduce {
display: flex;
flex-direction: row;
}
.allow-tourist-join{
display:flex;
.content{
display:flex;
}
.desc{
margin-left:16px;
font-size:14px;
color:#999;
display:inline-block;
}
}
.store-show{
display:flex;
margin-top:16px;
margin-bottom:16px;
.desc{
margin-left:16px;
font-size:14px;
color:#999;
display:inline-block;
}
}
.radio {
display: block;
height: 30px;
line-height: 30px;
}
.interactive-playback {
display: flex;
margin-bottom: 20px;
}
textarea.ant-input {
min-height: 80px;
}
.intro-list__item {
display: flex;
margin-bottom: 16px;
position: relative;
&.picture {
width: 550px;
padding: 16px;
border: 1px solid #EEE;
border-radius: 4px;
.img__wrap {
width: 299px;
height: 168px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
.little-icon {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
right: -20px;
.iconfont {
width: 20px;
height: 20px;
line-height: 20px;
font-size: 20px;
color: #999;
margin-bottom: 4px;
cursor: pointer;
&.close {
margin-top: 8px;
background-image: url('https://image.xiaomaiketang.com/xm/eesMPhNP3e.png');
background-size: 100% 100%;
}
}
}
}
.operate {
display: flex;
align-items: center;
justify-content: center;
width: 550px;
height: 80px;
line-height: 80px;
padding: 16px;
margin-top: 16px;
border: 1px dashed #EBEBEB;
border-radius: 4px;
.ant-upload {
vertical-align: middle;
}
&__item {
display: flex;
flex-direction: column;
cursor: pointer;
&:not(:last-child) {
margin-right: 82px;
}
.iconfont {
font-size: 22px;
line-height: 22px;
color: #BFBFBF;
text-align: center;
}
.text {
color: #999;
line-height: 20px;
margin-top: 4px;
}
}
}
.tips {
color: #999;
margin-top: 16px;
margin-bottom: 8px;
}
.checkExample {
width: 60px;
color: #FF7519;
cursor: pointer;
}
.warmup {
margin-bottom: 17px;
display: flex;
}
.course-cover__wrap {
display: flex;
flex-direction: row;
}
.img-content {
position: relative;
margin-right: 20px;
width: 300px;
height: 170px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
.img-delete-wrap {
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
img {
position: absolute;
left: 50%;
top: 50%;
width: 40px;
height: 40px;
transform: translate(-50%, -50%);
}
&:hover {
opacity: 1;
cursor: pointer;
}
}
}
.opt-btns {
.default-btn {
margin-left: 16px;
color: #FF7519;
cursor: pointer;
&.disabled {
color: #CCC;
cursor: not-allowed;
}
}
}
}
.example-wrap {
font-family: PingFangSC-Regular, PingFang SC;
text-align: center;
.title {
margin-bottom: 6px;
font-size: 14px;
color: #333333;
}
.text {
margin-bottom: 16px;
font-size: 12px;
color: #999999;
}
img {
width: 180px;
}
}
.check-record-rule {
width: 120px;
color: #FF7519;
cursor: pointer;
z-index: 2;
}
.record-rule-wrap {
text-align: left;
ul {
margin-top: 10px;
padding-left: 34px;
list-style-type: disc;
li {
color: #999;
}
}
.text {
color: #999;
}
}
.auto-send-class-report {
.label {
width: 57px;
height: 12px;
font-size: 14px;
font-weight: 400;
color: #666666;
line-height: 12px;
}
.open-text, .close-text {
width: 572px;
font-size: 14px;
font-weight: 400;
color: #999999;
line-height: 20px;
margin-left: 100px;
margin-top: 5px;
}
.open-text {
margin-top: 8px;
}
.close-text {
margin-bottom: 16px;
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:11:57
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-02-23 16:35:37
* @Description: 视频课-搜索模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Row, Input, Select,Tooltip } from 'antd';
import RangePicker from "@/modules/common/DateRangePicker";
import './GraphicsCourseFilter.less';
import moment from 'moment';
import StoreService from "@/domains/store-domain/storeService";
const { Search } = Input;
const { Option } = Select;
const DEFAULT_QUERY = {
courseName: null, // 课程名称
operatorId: null, // 创建人
beginTime: null, // 开始日期
endTime: null, // 结束日期
shelfState:null,
}
const defaultTeacherQuery = {
size: 10,
current: 1,
nickName:null
}
class GraphicsCourseFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
query: { ...DEFAULT_QUERY }, // 使用扩展运算符,避免浅拷贝
teacherQuery: defaultTeacherQuery,
teacherList:[],
expandFilter:false
}
}
componentDidMount() {
this.getTeacherList();
}
getTeacherList(current = 1, selectList){
const { teacherQuery,teacherList} = this.state;
const _query = {
...teacherQuery,
current,
size:10
};
StoreService.getStoreUserBasicPage( _query).then((res) => {
const { result = {} } = res;
const { records = [], total = 0, hasNext } = result;
const list = current > 1 ? teacherList.concat(records) : records;
this.setState({
hasNext,
teacherList: list,
})
});
}
// 滑动加载更多讲师列表
handleScrollTeacherList = (e) => {
const { hasNext } = this.state;
const container = e.target;
const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop;
if (scrollToBottom && hasNext) {
const { teacherQuery } = this.state;
let _teacherQuery = teacherQuery;
_teacherQuery.current = _teacherQuery.current + 1
this.setState({
teacherQuery:{..._teacherQuery}
},()=>{this.getTeacherList(_teacherQuery.current)})
}
}
// 改变搜索条件
handleChangeQuery = (field, value) => {
this.setState({
query: {
...this.state.query,
[field]: value,
current: 1,
}
}, () => {
if (field === 'courseName') return;
this.props.onChange(this.state.query)
});
}
handleChangeDates = (dates) => {
const query = _.clone(this.state.query);
if (_.isEmpty(dates)) {
delete query.beginTime;
delete query.endTime;
} else {
query.beginTime = dates[0].valueOf();
query.endTime = dates[1].valueOf();
}
this.setState({
query:{
...query,
current: 1,
}
}, () => {
this.props.onChange(this.state.query);
})
}
// 重置搜索条件
handleReset = () => {
this.setState({
query: DEFAULT_QUERY,
}, () => {
this.props.onChange(this.state.query);
})
}
render() {
const {
query: {
courseName,
operator,
beginTime,
endTime,
operatorId,
shelfState
},
expandFilter,
teacherList,
teacherQuery
} = this.state;
return (
<div className="video-course-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">图文课名称:</span>
<Search
value={courseName}
placeholder="搜索视频课名称"
onChange={(e) => { this.handleChangeQuery('courseName', e.target.value)}}
onSearch={ () => { this.props.onChange(this.state.query) } }
style={{ width: "calc(100% - 84px)" }}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span>创建人:</span>
<Select
placeholder="请选择创建人"
style={{width:"calc(100% - 70px)"}}
showSearch
allowClear
filterOption={(input, option) => option}
onPopupScroll={this.handleScrollTeacherList}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}
value={operatorId}
onChange={(value) => {
this.handleChangeQuery('operatorId', value)
}}
onSearch={(value) => {
teacherQuery.nickName = value
this.setState({
teacherQuery
}, () => {
this.getTeacherList()
})
}}
onClear ={(value)=>{
this.setState({
teacherQuery:{
size: 10,
current: 1,
nickName:null
}
}, () => {
this.getTeacherList()
})
}
}
>
{_.map(teacherList, (item, index) => {
return (
<Select.Option value={item.id} key={item.id}>{item.nickName}</Select.Option>
);
})}
</Select>
</div>
<div className="search-condition__item">
<span className="search-date">创建日期:</span>
<RangePicker
id="course_date_picker"
allowClear={false}
value={ beginTime ? [moment(beginTime), moment(endTime)] : null }
format={"YYYY-MM-DD"}
onChange={(dates) => { this.handleChangeDates(dates) }}
style={{ width: "calc(100% - 70px)" }}
/>
</div>
{ expandFilter &&
<div className="search-condition__item">
<span className="shelf-status">店铺展示:</span>
<Select
style={{ width: "calc(100% - 84px)" }}
placeholder="请选择"
allowClear={true}
value={shelfState}
onChange={(value) => { this.handleChangeQuery('shelfState', value) }}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}
>
<Option value="YES">开启</Option>
<Option value="NO">关闭</Option>
</Select>
</div>
}
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={this.handleReset}>&#xe61b; </span></Tooltip>
<span style={{ cursor: 'pointer' }} className="fold-btn" onClick={() => {
this.setState({expandFilter:!expandFilter});
}}>{this.state.expandFilter ? <span><span>收起</span><span className="iconfont icon fold-icon" >&#xe82d; </span> </span> : <span>展开<span className="iconfont icon fold-icon" >&#xe835; </span></span>}</span>
</div>
</Row>
</div>
)
}
}
export default GraphicsCourseFilter;
.video-course-filter {
position: relative;
.video-list-table{
// tr:nth-child(even){
// background: transparent !important;
// }
// tr:nth-child(odd){
// td{
// background: #FAFAFA !important;
// }
// }
}
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 12px;
.search-name{
vertical-align: middle;
}
.shelf-status{
width:84px;
display:inline-block;
text-align:right;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
}
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left:4px;
}
}
}
.data-icon {
cursor: pointer;
}
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:45
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-02-01 16:34:11
* @Description: 视频课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Table, Modal, message , Tooltip,Switch,Dropdown} from 'antd';
import { PageControl } from "@/components";
import { LIVE_SHARE_MAP } from '@/common/constants/academic/cloudClass';
import { appId, shareUrl, LIVE_SHARE } from '@/domains/course-domain/constants';
import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal';
import WatchDataModal from '../modal/WatchDataModal'
import CourseService from "@/domains/course-domain/CourseService";
import RelatedPlanModal from '../../modal/RelatedPlanModal';
import User from '@/common/js/user'
import './GraphicsCourseList.less';
const ENV = process.env.DEPLOY_ENV || 'dev';
const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
class GraphicsCourseList extends React.Component {
constructor(props) {
super(props);
this.state = {
id: '', // 视频课ID
studentIds:[]
}
}
componentDidMount() {
const videoCourseItem = localStorage.getItem('videoCourseItem');
if (videoCourseItem) {
const _videoCourseItem = JSON.parse(videoCourseItem);
this.handleShowShareModal(_videoCourseItem, true);
}
}
// 观看数据弹窗
handleShowWatchDataModal = (record) => {
const watchDataModal = (
<WatchDataModal
type='videoCourseList'
data={record}
close={() => {
this.setState({
watchDataModal: null
});
}}
/>
);
this.setState({ watchDataModal });
}
// 请求表头
parseColumns = () => {
const columns = [
{
title: '图文课',
key: 'scheduleName',
dataIndex: 'scheduleName',
width:321,
fixed: 'left',
render: (val, record) => {
const { coverUrl, scheduleVideoUrl } = record;
return (
<div className="record__item">
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img className="course-cover" src={coverUrl || defaultCoverUrl} />
{ record.courseName.length > 25?
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
:
<div className="course-name">{record.courseName}</div>
}
</div>
)
}
},
{
title: '课程分类',
key: 'categoryName',
dataIndex: 'categoryName',
width: '20%',
render: (val, record) => {
return (
<div className="record__item">
{record.categoryOneName}{ record.categoryTwoName?`-${record.categoryTwoName}`:''}
</div>
)
}
},
{
title: '创建人',
key: 'createName',
dataIndex: 'createName',
width: '10%',
render: (val) => {
return (
<div>
{ val &&
<Tooltip title={val}>
<div>
{val.length > 4 ? `${val.slice(0,4)}...` : val}
</div>
</Tooltip>
}
</div>
)
}
},
{
title: <span>
<span>店铺展示</span>
<Tooltip title={<div>开启后,用户可在店铺内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”店铺展示。<br/>关闭后,店铺内不再展示此课程,但用户仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
width: '12%',
dataIndex: "courseware",
render: (val, item, index) => {
return (
<Switch defaultChecked={item.shelfState==="YES"?true:false} onChange={()=>this.changeShelfState(item)}/>
)
},
},
{
title: "观看用户数",
width: 110,
key: "watchUserCount",
dataIndex: "watchUserCount",
render: (val, item) => {
return (
<div className="watchUserCount">{val}</div>
)
},
},
{
title: '创建时间',
width: 181,
key: 'created',
dataIndex: 'created',
sorter: true,
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '最近修改时间',
width: 181,
key: 'updated',
dataIndex: 'updated',
sorter: true,
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
width: 210,
fixed: 'right',
render: (val, record) => {
return (
<div className="operate">
<div className="operate__item" onClick={()=>this.handleShowWatchDataModal(record)}>观看数据</div>
<span className="operate__item split"> | </span>
<div className="operate__item" onClick={() => this.handleShowShareModal(record)}>分享</div>
<span className="operate__item split"> | </span>
<Dropdown overlay={this.renderMoreOperate(record)}>
<span className="more-operate">
<span className="operate-text">更多</span>
<span
className="iconfont icon"
style={{ color: "#5289FA" }}
>
&#xe824;
</span>
</span>
</Dropdown>
</div>
)
}
}
];
return columns;
}
handleRelatedModalShow = (item) => {
const selectPlanList = {};
if(item.relatedPlanList){
item.relatedPlanList.map((item, index) => {
selectPlanList[item.planId] = {}
selectPlanList[item.planId].planId = item.planId;
selectPlanList[item.planId].taskBaseVOList = [{ taskId: item.taskId }];
return item
})
}
this.setState({
RelatedPlanModalVisible: true,
selectCourseId: item.id,
selectPlanList: selectPlanList,
})
}
closeRelatedPlanModalVisible = ()=>{
this.setState({
RelatedPlanModalVisible: false
})
}
onChangeSelectPlanList = (selectPlanList) => {
this.setState({
selectPlanList: selectPlanList
})
}
onConfirmSelectPlanList = () => {
this.setState({
RelatedPlanModalVisible: false
}, () => { this.props.onChange(); });
}
renderMoreOperate = (item) => {
return (
<div className="live-course-more-menu">
{/* {(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager") &&
<div
className="operate__item"
key="plan"
onClick={() => {
this.handleRelatedModalShow(item);
}}
>关联培训计划</div>
} */}
<div
className="operate__item"
key="edit"
onClick={() => {
RCHistory.push(`/create-graphics-course?type=edit&id=${item.id}`);
}}
>编辑</div>
<div
className="operate__item"
key="delete"
onClick={() => this.handleDeleteGraphicsCourse(item.id)}
>删除</div>
</div>
)
}
//改变上架状态
changeShelfState = (item) =>{
let _shelfState = item.shelfState
if(_shelfState==='NO'){
_shelfState = "YES";
item.shelfState = "YES"
}else{
_shelfState = "NO"
item.shelfState = "NO"
}
const params={
courseId: item.id,
shelfState:_shelfState
}
CourseService.changeVideoShelfState(params).then((res)=>{
if(res.success){
if(_shelfState === "YES"){
message.success("已开启展示");
}else{
message.success("已取消展示");
}
}
})
}
// 删除视频课
handleDeleteGraphicsCourse = (scheduleId) => {
Modal.confirm({
title: '你确定要删除此视频课吗?',
content: '删除后,学员将不能进行观看。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk: () => {
const param ={
courseId:scheduleId,
storeId:User.getStoreId()
}
CourseService.delVideoSchedule(
param
).then(() => {
message.success('删除成功');
this.props.onChange();
})
}
});
}
// 显示分享弹窗
handleShowShareModal = (record, needStr = false) => {
const { id, scheduleVideoUrl } = record;
const _appId = appId;
const htmlUrl = `${LIVE_SHARE}graphics_detail/${id}?id=${User.getStoreId()}`;
const longUrl = htmlUrl;
const { coverUrl, courseName } = record;
const shareData = {
longUrl,
coverUrl,
scheduleVideoUrl,
courseName,
};
const shareLiveModal = (
<ShareLiveModal
needStr={needStr}
data={shareData}
type="videoClass"
title="图文课"
close={() => {
this.setState({
shareLiveModal: null
});
localStorage.setItem('videoCourseItem', '');
}}
/>
);
this.setState({ shareLiveModal });
}
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
const { query } = this.props;
let { order: _order } =query;
// 按创建时间升序排序
if (columnKey === 'created' && order === 'ascend') { _order = 'CREATED_ASC'; }
// 按创建时间降序排序
if (columnKey === 'created' && order === 'descend') { _order = 'CREATED_DESC'; }
// 按更新时间升序排序
if (columnKey === 'updated' && order === 'ascend') { _order = 'UPDATED_ASC'; }
// 按更新时间降序排序
if (columnKey === 'updated' && order === 'descend') { _order = 'UPDATED_DESC'; }
const _query = {
...query,
orderEnum: _order
};
this.props.onChange(_query);
}
render() {
const { RelatedPlanModalVisible, selectCourseId, selectPlanList } = this.state;
const { dataSource = [], totalCount, query } = this.props;
const { current, size } = query;
return (
<div className="video-course-list">
<Table
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
onChange={this.handleChangeTable}
pagination={false}
scroll={{ x: 1500}}
bordered
className="video-list-table"
/>
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = {...query, current: page + 1};
this.props.onChange(_query)
}}
/>
</div>
{RelatedPlanModalVisible &&
<RelatedPlanModal
onClose={this.closeRelatedPlanModalVisible}
visible={RelatedPlanModalVisible}
selectCourseId={selectCourseId}
selectPlanList={selectPlanList}
onChange={this.onChangeSelectPlanList}
onConfirm={this.onConfirmSelectPlanList}
/>
}
{ this.state.shareLiveModal }
{ this.state.watchDataModal }
</div>
)
}
}
export default GraphicsCourseList;
.video-course-list {
margin-top: 12px;
.video-list-table{
tbody {
tr{
&:nth-child(even){
background: transparent !important;
td{
background:#FFF !important;
}
}
&:nth-child(odd){
background: #FAFAFA !important;
td{
background: #FAFAFA !important;
}
}
&:hover{
td{
background:#F3f6fa !important;
}
}
}
}
}
.watchUserCount{
text-align:right;
padding:16px;
}
.operate-text {
color: #5289FA;
cursor: pointer;
}
.operate {
display: flex;
&__item {
color: #5289FA;
cursor: pointer;
&.split {
margin: 0 8px;
color: #BFBFBF;
}
}
}
.more-operate{
line-height:20px;
}
.record__item {
display: flex;
.course-cover {
min-width: 97px;
max-width: 97px;
height: 50px;
border-radius: 2px;
margin-right: 8px;
background-color: #666;
}
.course-name {
color: #666;
width:188px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
height:48px;
}
}
}
.video-course-more-menu {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
border-radius: 4px;
div {
line-height: 30px;
padding: 0 15px;
cursor: pointer;
&:hover {
background: #f3f6fa;
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27
* @Description: 视频课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Button } from 'antd';
import './GraphicsCourseOpt.less';
export default function GraphicsCourseOpt() {
return (
<div className="video-course-opt">
<Button
type="primary"
onClick={() => {
RCHistory.push('/create-graphics-course?type=add');
}}
className="mr12"
>新建图文课</Button>
</div>
);
}
.video-course-opt {
margin-top:4px;
.link {
color: #FF8534;
}
}
\ No newline at end of file
import React from 'react';
import GraphicsCourseFilter from './components/GraphicsCourseFilter';
import GraphicsCourseOpt from './components/GraphicsCourseOpt';
import GraphicsCourseList from './components/GraphicsCourseList';
import Service from '@/common/js/service';
import User from '@/common/js/user'
class GraphicsCourse extends React.Component {
constructor(props) {
super(props);
this.state = {
query: {
size: 10,
current: 1,
courseType: 'PICTURE',
storeId:User.getStoreId()
},
dataSource: [], // 视频课列表
totalCount: 0, // 视频课数据总条数
}
}
componentWillMount() {
// 获取视频课列表
this.handleFetchScheduleList();
}
// 获取视频课列表
handleFetchScheduleList = (_query = {}) => {
const query = {
...this.state.query,
..._query
};
// 更新请求参数
this.setState({ query });
Service.Hades('public/hades/mediaCoursePage', query).then((res) => {
const { result = {} } = res || {};
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
})
// CourseService.videoSchedulePage(query).then((res) => {
// const { result = {} } = res || {};
// const { records = [], total = 0 } = result;
// this.setState({
// dataSource: records,
// totalCount: Number(total)
// });
// });
}
render() {
const { dataSource, totalCount, query } = this.state;
return (
<div className="page video-course-page">
<div className="content-header">图文课</div>
<div className="box">
{/* 搜索模块 */}
<GraphicsCourseFilter
onChange={this.handleFetchScheduleList}
/>
{/* 操作模块 */}
<GraphicsCourseOpt />
{/* 视频课列表模块 */}
<GraphicsCourseList
query={query}
dataSource={dataSource}
totalCount={totalCount}
onChange={this.handleFetchScheduleList}
/>
</div>
</div>
)
}
}
export default GraphicsCourse;
/*
* @Author: 吴文洁
* @Date: 2020-05-19 11:01:31
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-19 16:25:31
* @Description 余额异常弹窗
*/
import React from 'react';
import {Table, Modal,Input} from 'antd';
import { PageControl } from "@/components";
import Service from "@/common/js/service";
import User from '@/common/js/user'
import './WatchDataModal.less';
import dealTimeDuration from "../../utils/dealTimeDuration";
const { Search } = Input;
class WatchDataModal extends React.Component {
constructor(props) {
super(props);
this.state = {
visible:true,
dataSource:[],
size:10,
query: {
current: 1,
},
totalCount:0
};
}
componentDidMount() {
this.handleFetchDataList();
}
onClose = () =>{
this.props.close();
}
// 获取观看视频数据列表
handleFetchDataList = () => {
const {query,size,totalCount} = this.state
const { id } = this.props.data;
const params ={
...query,
size,
courseId:id,
storeId:User.getStoreId()
}
Service.Hades('public/hades/mediaCourseWatchInfo', params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
});
}
handleChangNickname = (value)=>{
const isPhone = (value || '').match(/^\d+$/);
const { query } = this.state;
if(isPhone){
query.phone = value;
query.nickName = null;
}else{
query.nickName = value;
query.phone = null;
}
query.current = 1;
this.setState({
query
})
}
onShowSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
size
},()=>{this.handleFetchDataList()})
}
// 请求表头
parseColumns = () => {
const columns = [
{
title: '观看用户',
key: 'name',
dataIndex: 'name'
},
{
title: '手机号',
key: 'phone',
dataIndex: 'phone'
},
{
title: '观看者类型',
key: 'userRole',
dataIndex: 'userRole'
},
{
title: '首次观看时间',
key: 'firstWatch',
dataIndex: 'firstWatch',
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '观看时长',
key: 'watchDuration',
dataIndex: 'watchDuration',
render: (val) => {
return <span>{val ? dealTimeDuration(val) : "00:00:00" }</span>
}
},
{
title: '学习进度',
key: 'progress',
dataIndex: 'progress',
render: (val) => {
return <span>{val || 0}%</span>
}
}
];
return columns;
}
render() {
const { visible,size,dataSource,totalCount,query} = this.state;
return (
<Modal
title="图文课观看数据"
visible={visible}
footer={null}
onCancel={this.onClose}
maskClosable={false}
className="watch-data-modal"
closable={true}
width={800}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
>
<div className="search-container">
<Search placeholder="搜索用户姓名/手机号" style={{ width: 200 }} onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} enterButton={<span className="icon iconfont">&#xe832;</span>}/>
</div>
<div>
<Table
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
bordered
/>
{dataSource.length >0 &&
<div className="box-footer">
<PageControl
current={query.current - 1}
pageSize={size}
total={totalCount}
size="small"
toPage={(page) => {
const _query = {...query, current: page + 1};
this.setState({
query:_query
},()=>{ this.handleFetchDataList()})
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
</div>
</Modal>
)
}
}
export default WatchDataModal;
\ No newline at end of file
.watch-data-modal{
.search-container{
text-align:right;
margin-bottom:17px;
}
}
\ No newline at end of file
import React from 'react';
import { Modal } from 'antd';
import './PreviewGraphicsModal.less';
const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
class PreviewGraphicsModal extends React.Component {
constructor(props) {
super(props);
this.state = {
type: 'detail',
}
}
render() {
const { courseBasicInfo, courseIntroInfo } = this.props;
const { coverUrl, courseName, categoryName } = courseBasicInfo;
const { courseMedia, introduce } = courseIntroInfo;
const { type } = this.state;
return (
<Modal
title="预览"
visible={true}
width={680}
onCancel={this.props.close}
footer={null}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
className="preview-live-graphics-modal"
>
<div className="container__wrap">
<div className="container">
<div className="container__header">
<img src={coverUrl || defaultCoverUrl} className="course-cover" />
</div>
<div className="container__body">
<div className="title__name">{courseName}</div>
<div className="title__categery">课程分类:{categoryName}</div>
</div>
<div className="container__introduction">
<div className="title">
<span
className={`title-word${type === 'detail' ? ' selected' : ''}`}
onClick={() => this.setState({ type: 'detail' })}
>图文详情</span>
<span
className={`title-word${type === 'introduction' ? ' selected' : ''}`}
onClick={() => this.setState({ type: 'introduction' })}
>图文简介</span>
</div>
<div className="container__introduction__list editor-box">
<div
className="intro-item text"
dangerouslySetInnerHTML={{
__html: type === 'detail' ? courseMedia : introduce
}}
/>
</div>
</div>
</div>
</div>
</Modal>
)
}
}
export default PreviewGraphicsModal;
.preview-live-graphics-modal {
.ant-modal-body {
background-image: url('https://image.xiaomaiketang.com/xm/xZWdziTCAf.png');
background-size: 100% 100%;
}
.container__wrap {
width: 340px;
height: 618px;
padding: 67px 46px 48px 47px;
margin: auto;
background-image: url('https://image.xiaomaiketang.com/xm/DHMzHiGc2E.png');
background-size: 100% 100%;
}
.container {
overflow: scroll;
height: 100%;;
.course-cover, .course-url {
width: 100%;
height: 141px;
background: #000;
}
&__body {
background-color: #FFF;
padding: 7px 0 11px 0;;
.title__name {
color: #333333;
font-weight: 500;
}
.title__categery {
font-size: 12px;
color: #999999;
}
}
&__introduction {
margin-top: 10px;
padding: 12px 0;
position: relative;
&::after {
content: '';
position: absolute;
width: 241px;
top: -10px;
height: 10px;
background: #F4F6FA;
}
.title {
height: 24px;
display: flex;
align-items: center;
font-size: 12px;
color: #333333;
padding: 0 10px;
border-bottom: 1px solid #E8E8E8;
.title-word {
position: relative;
margin-right: 15px;
cursor: pointer;
}
.selected {
color: #FFB714;
&::after {
content: '';
position: absolute;
bottom: -4px;
width: 20px;
height: 1px;
background: #FFB714;
left: 50%;
transform: translateX(-50%);
}
}
}
&__list {
margin-top: 12px;
.intro-item:not(:first-child) {
margin-top: 13px;
}
.text {
color: #666;
line-height: 17px;
p {
font-size: 12px;
}
}
.picture {
img {
width: 100%;
}
}
}
}
}
}
\ No newline at end of file
/* /*
* @Author: 吴文洁 * @Author: 吴文洁
* @Date: 2020-07-20 19:12:49 * @Date: 2020-07-20 19:12:49
* @Last Modified by: 吴文洁 * @Last Modified by: chenshu
* @Last Modified time: 2020-07-20 20:25:13 * @Last Modified time: 2021-03-16 17:41:40
* @Description: 大班直播分享弹窗 * @Description: 大班直播分享弹窗
*/ */
...@@ -130,7 +130,7 @@ class ShareLiveModal extends React.Component { ...@@ -130,7 +130,7 @@ class ShareLiveModal extends React.Component {
} }
render() { render() {
const { needStr, data, type } = this.props; const { needStr, data, type, title } = this.props;
const { courseName, coverUrl = DEFAULT_COVER, scheduleVideoUrl } = data; const { courseName, coverUrl = DEFAULT_COVER, scheduleVideoUrl } = data;
const { shareUrl ,imgData,showImg,time} = this.state; const { shareUrl ,imgData,showImg,time} = this.state;
...@@ -139,7 +139,7 @@ class ShareLiveModal extends React.Component { ...@@ -139,7 +139,7 @@ class ShareLiveModal extends React.Component {
let coverImgSrc = coverUrl; let coverImgSrc = coverUrl;
if(type === 'videoClass'){ if(type === 'videoClass'){
if(!coverUrl || isDefaultCover){ if((!coverUrl || isDefaultCover) && title !== '图文课'){
coverImgSrc = `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring` coverImgSrc = `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring`
} }
}else{ }else{
...@@ -153,7 +153,7 @@ class ShareLiveModal extends React.Component { ...@@ -153,7 +153,7 @@ class ShareLiveModal extends React.Component {
return ( return (
<Modal <Modal
title={type === 'videoClass' ? '分享视频课' : '分享直播课'} title={`分享${title}`}
width={680} width={680}
visible={true} visible={true}
footer={null} footer={null}
...@@ -197,10 +197,10 @@ class ShareLiveModal extends React.Component { ...@@ -197,10 +197,10 @@ class ShareLiveModal extends React.Component {
<div className="share-poster right__item"> <div className="share-poster right__item">
<div className="title">① 海报分享</div> <div className="title">① 海报分享</div>
{ type === "liveClass" && { type === "liveClass" &&
<div className="sub-title">用户可通过微信扫描海报二维码,观看直播</div> <div className="sub-title">用户可通过微信扫描海报二维码,观看{title}</div>
} }
{ type === "videoClass" && { type === "videoClass" &&
<div className="sub-title">用户可通过微信识别二维码,报名观看视频</div> <div className="sub-title">用户可通过微信识别二维码,报名观看{title}</div>
} }
<div className="content" onClick={this.handleDownloadPoster}>下载海报</div> <div className="content" onClick={this.handleDownloadPoster}>下载海报</div>
...@@ -209,10 +209,10 @@ class ShareLiveModal extends React.Component { ...@@ -209,10 +209,10 @@ class ShareLiveModal extends React.Component {
<div className="share-url right__item"> <div className="share-url right__item">
<div className="title">② 链接分享</div> <div className="title">② 链接分享</div>
{ type === "liveClass" && { type === "liveClass" &&
<div className="sub-title">用户可通过微信打开以下链接,观看直播</div> <div className="sub-title">用户可通过微信打开以下链接,观看{title}</div>
} }
{ type === "videoClass" && { type === "videoClass" &&
<div className="sub-title">用户可通过打开链接,报名观看视频</div> <div className="sub-title">用户可通过打开链接,报名观看{title}</div>
} }
<div className="content url-content"> <div className="content url-content">
<div className="share-url" id="shareUrl">{shareUrl}</div> <div className="share-url" id="shareUrl">{shareUrl}</div>
......
...@@ -176,7 +176,7 @@ class VideoCourseList extends React.Component { ...@@ -176,7 +176,7 @@ class VideoCourseList extends React.Component {
return ( return (
<div className="related-task"> <div className="related-task">
{ record.relatedPlanList ? { record.relatedPlanList ?
<Tooltip title={this.handlePlanName(record.relatedPlanList)} > <Tooltip title={this.handlePlanName(record.relatedPlanList)} placement="top" arrowPointAtCenter>
{ record.relatedPlanList.map((item,index)=>{ { record.relatedPlanList.map((item,index)=>{
return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span> return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span>
}) })
...@@ -326,6 +326,7 @@ class VideoCourseList extends React.Component { ...@@ -326,6 +326,7 @@ class VideoCourseList extends React.Component {
needStr={needStr} needStr={needStr}
data={shareData} data={shareData}
type="videoClass" type="videoClass"
title="视频课"
close={() => { close={() => {
this.setState({ this.setState({
shareLiveModal: null shareLiveModal: null
......
...@@ -303,6 +303,10 @@ class Home extends React.Component { ...@@ -303,6 +303,10 @@ class Home extends React.Component {
className={`tab${scheduleType === 'VOICE' ? ' selected' : ''}`} className={`tab${scheduleType === 'VOICE' ? ' selected' : ''}`}
onClick={() => this.setState({ scheduleType: 'VOICE' }, () => this.getHotCourse())} onClick={() => this.setState({ scheduleType: 'VOICE' }, () => this.getHotCourse())}
>视频课</span> >视频课</span>
<span
className={`tab${scheduleType === 'GRAPHICS' ? ' selected' : ''}`}
onClick={() => this.setState({ scheduleType: 'GRAPHICS' }, () => this.getHotCourse())}
>图文课</span>
</div> </div>
<div className="study-select"> <div className="study-select">
<span className="select-word">{moment().subtract(timeRange - 1, 'day').format('MM.DD')} ~ {moment().format('MM.DD')}</span> <span className="select-word">{moment().subtract(timeRange - 1, 'day').format('MM.DD')} ~ {moment().format('MM.DD')}</span>
......
...@@ -99,10 +99,10 @@ class LearningData extends React.Component { ...@@ -99,10 +99,10 @@ class LearningData extends React.Component {
<div className="box"> <div className="box">
{ (User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")? { (User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")?
(<Tabs activeKey={activeKey} onChange={(activeKey)=>{this.setState({activeKey})}}> (<Tabs activeKey={activeKey} onChange={(activeKey)=>{this.setState({activeKey})}}>
<Tabs.TabPane tab="员工分享数据" key="employeeShareData"> <Tabs.TabPane tab="员工分享数据" key="employeeShareData" forceRender="true">
<EmployeeShareData/> <EmployeeShareData/>
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab="用户学习数据" key="userLearningData"> <Tabs.TabPane tab="用户学习数据" key="userLearningData" forceRender="true">
<UserLearningData/> <UserLearningData/>
</Tabs.TabPane> </Tabs.TabPane>
</Tabs>) </Tabs>)
......
...@@ -324,7 +324,7 @@ class BasicInfo extends React.Component{ ...@@ -324,7 +324,7 @@ class BasicInfo extends React.Component{
</span> </span>
</div> </div>
<div className="video-standard-info"> <div className="live-standard-info">
<span className="icon iconfont">&#xe864;</span> <span className="icon iconfont">&#xe864;</span>
<span className="instro">视频课单个课程,用户学习进度达到 <span className="instro">视频课单个课程,用户学习进度达到
<Input <Input
...@@ -337,6 +337,19 @@ class BasicInfo extends React.Component{ ...@@ -337,6 +337,19 @@ class BasicInfo extends React.Component{
%,即视为"已完成"学习 %,即视为"已完成"学习
</span> </span>
</div> </div>
{/* <div className="video-standard-info">
<span className="icon iconfont">&#xe864;</span>
<span>图文课单个课程,用户学习进度达到
<Input
width="40"
value={percentCompleteVideo}
onChange={(e) => { this.props.onChange('percentCompleteVideo', e.target.value.replace(/\D/g,'')) }}
onBlur={(e) => this.percentCompleteBlur(e, 'percentCompleteVideo')}
className="input-box"
/>
%,即视为“已完成”学习
</span>
</div> */}
</div> </div>
</div> </div>
{ operatorModalVisible && { operatorModalVisible &&
......
...@@ -150,6 +150,7 @@ class EmployeeShareData extends React.Component { ...@@ -150,6 +150,7 @@ class EmployeeShareData extends React.Component {
title: '最近分享成功时间', title: '最近分享成功时间',
key: 'recentlyForwardTime', key: 'recentlyForwardTime',
dataIndex: 'recentlyForwardTime', dataIndex: 'recentlyForwardTime',
width:240,
render: (val, record) => { render: (val, record) => {
return ( return (
<div> <div>
......
.expired-course-list{ .expired-course-list{
margin:8px 10px 16px; margin:8px 10px 0px;
.course-item{ .course-item{
display:flex; display:flex;
padding:16px 0; padding:16px 0;
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
justify-content: space-between; justify-content: space-between;
&:last-child{ &:last-child{
border-bottom:none; border-bottom:none;
padding-bottom:0px;
} }
.course-left{ .course-left{
display:flex; display:flex;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* @Author: zhangleyuan * @Author: zhangleyuan
* @Date: 2021-02-20 16:46:46 * @Date: 2021-02-20 16:46:46
* @LastEditors: zhangleyuan * @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-15 16:19:53 * @LastEditTime: 2021-03-16 15:53:59
* @Description: 描述一下 * @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有 * @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/ */
...@@ -41,7 +41,7 @@ function PlanList(props) { ...@@ -41,7 +41,7 @@ function PlanList(props) {
title: '课程总数量', title: '课程总数量',
key: 'courseNum', key: 'courseNum',
dataIndex: 'courseNum', dataIndex: 'courseNum',
width: '10%', width: 110,
render: (val, record) => { render: (val, record) => {
return ( return (
<div className="course-number"> <div className="course-number">
......
.plan-list{ .plan-list{
margin-top:12px; margin-top:12px;
.course-number{
text-align:right;
margin-right:45px;
}
.plan-list-table{ .plan-list-table{
tbody { tbody {
tr{ tr{
......
...@@ -201,11 +201,10 @@ class UserLearningData extends React.Component { ...@@ -201,11 +201,10 @@ class UserLearningData extends React.Component {
key: 'latelyLearnTime', key: 'latelyLearnTime',
dataIndex: 'latelyLearnTime', dataIndex: 'latelyLearnTime',
sorter:true, sorter:true,
width:240,
render: (val, record) => { render: (val, record) => {
return ( return (
<div> `${formatDate('YYYY-MM-DD H:i',parseInt(record.latelyLearnTime))}`
{formatDate('YYYY-MM-DD H:i', val)}
</div>
) )
} }
}, },
...@@ -213,6 +212,7 @@ class UserLearningData extends React.Component { ...@@ -213,6 +212,7 @@ class UserLearningData extends React.Component {
title: '开始学习时间', title: '开始学习时间',
key: 'startLearnTime', key: 'startLearnTime',
dataIndex: 'startLearnTime', dataIndex: 'startLearnTime',
width:240,
sorter:true, sorter:true,
render: (val, record) => { render: (val, record) => {
return ( return (
......
...@@ -48,9 +48,6 @@ function UserLearningDataFilter(props) { ...@@ -48,9 +48,6 @@ function UserLearningDataFilter(props) {
}, []); }, []);
function handleChangeCreatorQuery (record){ function handleChangeCreatorQuery (record){
console.log('11');
const _creatorQuery = {...creatorQuery}; const _creatorQuery = {...creatorQuery};
_creatorQuery.operateId = record.storeUserId; _creatorQuery.operateId = record.storeUserId;
setCreatorQuery(_creatorQuery); setCreatorQuery(_creatorQuery);
......
...@@ -107,7 +107,12 @@ class SelectOperatorModal extends React.Component { ...@@ -107,7 +107,12 @@ class SelectOperatorModal extends React.Component {
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>} closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
> >
<div className="search-container"> <div className="search-container">
<Search placeholder="搜索运营师/手机号" style={{ width: 200 }} onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} /> <Search placeholder="搜索运营师/手机号"
enterButton={<span className="icon iconfont">&#xe832;</span>}
style={{ width: 200 }}
onChange={(e) => { this.handleChangNickname(e.target.value)}}
onSearch={ () => { this.handleFetchDataList()}}
/>
</div> </div>
<div> <div>
<Table <Table
...@@ -115,6 +120,7 @@ class SelectOperatorModal extends React.Component { ...@@ -115,6 +120,7 @@ class SelectOperatorModal extends React.Component {
dataSource={dataSource} dataSource={dataSource}
columns={this.parseColumns()} columns={this.parseColumns()}
pagination={false} pagination={false}
bordered
rowSelection={{ rowSelection={{
type: 'checkbox', type: 'checkbox',
selectedRowKeys: _.pluck(selectOperatorList, 'id'), selectedRowKeys: _.pluck(selectOperatorList, 'id'),
...@@ -135,6 +141,7 @@ class SelectOperatorModal extends React.Component { ...@@ -135,6 +141,7 @@ class SelectOperatorModal extends React.Component {
{dataSource.length >0 && {dataSource.length >0 &&
<div className="box-footer"> <div className="box-footer">
<PageControl <PageControl
size="small"
current={query.current - 1} current={query.current - 1}
pageSize={size} pageSize={size}
total={totalCount} total={totalCount}
......
...@@ -169,7 +169,7 @@ class SelectOperatorModal extends React.Component { ...@@ -169,7 +169,7 @@ class SelectOperatorModal extends React.Component {
title: <span><span>课程信息</span><Tooltip title="仅显示未关联课程,已关联课程不支持重复选择"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip></span>, title: <span><span>课程信息</span><Tooltip title="仅显示未关联课程,已关联课程不支持重复选择"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip></span>,
key: 'course', key: 'course',
dataIndex: 'course', dataIndex: 'course',
width:'45%', width:'40%',
render: (val, record) => { render: (val, record) => {
let hasCover = false; let hasCover = false;
return ( return (
...@@ -201,8 +201,8 @@ class SelectOperatorModal extends React.Component { ...@@ -201,8 +201,8 @@ class SelectOperatorModal extends React.Component {
render: (val, record) => { render: (val, record) => {
return ( return (
<div> <div>
<div>{formatDate('YYYY-MM-DD H:i', record.startTime)}~</div> <div>{formatDate('YYYY-MM-DD', record.startTime)}</div>
<div>{formatDate('YYYY-MM-DD H:i', record.endTime)}</div> <div>{formatDate('H:i', record.startTime)}~{formatDate('H:i', record.endTime)}</div>
</div> </div>
) )
} }
...@@ -211,7 +211,7 @@ class SelectOperatorModal extends React.Component { ...@@ -211,7 +211,7 @@ class SelectOperatorModal extends React.Component {
title: '店铺展示', title: '店铺展示',
key: 'shelfState', key: 'shelfState',
dataIndex: 'shelfState', dataIndex: 'shelfState',
width:'15%', width:'20%',
render: (val, record) => { render: (val, record) => {
return ( return (
<span> <span>
......
...@@ -182,6 +182,7 @@ ...@@ -182,6 +182,7 @@
height: 49px; height: 49px;
display: flex; display: flex;
position: relative; position: relative;
align-items: center;
.store-name { .store-name {
font-size: 14px; font-size: 14px;
color: #666; color: #666;
...@@ -191,16 +192,13 @@ ...@@ -191,16 +192,13 @@
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
max-width: 230px; max-width: 230px;
margin-right: 32px; margin-right: 16px;
} }
.line { .line {
width: 1px; width: 1px;
height: 16px; height: 16px;
background-color: #f4f4f4; background-color: #f4f4f4;
position: absolute; margin-right:16px;
left: 122px;
top: 50%;
transform: translateY(-50%);
} }
.link-to-store { .link-to-store {
display: flex; display: flex;
......
...@@ -14,8 +14,9 @@ import CourseCatalogPage from '@/modules/store-manage/CourseCatalogPage'; ...@@ -14,8 +14,9 @@ import CourseCatalogPage from '@/modules/store-manage/CourseCatalogPage';
import LiveCoursePage from '@/modules/course-manage/LiveCoursePage'; import LiveCoursePage from '@/modules/course-manage/LiveCoursePage';
import AddLivePage from '@/modules/course-manage/AddLive' import AddLivePage from '@/modules/course-manage/AddLive'
import VideoCoursePage from '@/modules/course-manage/video-course' import VideoCoursePage from '@/modules/course-manage/video-course'
import GraphicsCoursePage from '@/modules/course-manage/graphics-course'
import AddVideoCoursePage from '@/modules/course-manage/video-course/AddVideoCourse' import AddVideoCoursePage from '@/modules/course-manage/video-course/AddVideoCourse'
//TODO import AddGraphicsCoursePage from '@/modules/course-manage/graphics-course/AddGraphicsCourse'
// import DataList from '@/modules/course-manage/DataList/DataList'; // import DataList from '@/modules/course-manage/DataList/DataList';
// import ClassBook from '@/modules/resource-disk'; // import ClassBook from '@/modules/resource-disk';
import ResourceDisk from '@/modules/resource-disk'; import ResourceDisk from '@/modules/resource-disk';
...@@ -70,6 +71,11 @@ const mainRoutes = [ ...@@ -70,6 +71,11 @@ const mainRoutes = [
name: '视频课' name: '视频课'
}, },
{ {
path: '/graphics-course',
component: GraphicsCoursePage,
name: '图文课'
},
{
path: '/create-live-course', path: '/create-live-course',
component:AddLivePage, component:AddLivePage,
name: '创建直播课' name: '创建直播课'
...@@ -80,6 +86,11 @@ const mainRoutes = [ ...@@ -80,6 +86,11 @@ const mainRoutes = [
name: '创建视频课' name: '创建视频课'
}, },
{ {
path: '/create-graphics-course',
component:AddGraphicsCoursePage,
name: '创建图文课'
},
{
path: '/resource-disk', path: '/resource-disk',
component:ResourceDisk, component:ResourceDisk,
name: '资料云盘' name: '资料云盘'
......
...@@ -27,7 +27,12 @@ export const menuList: any = [ ...@@ -27,7 +27,12 @@ export const menuList: any = [
groupName: "视频课", groupName: "视频课",
groupCode: "CourseVideoClass", groupCode: "CourseVideoClass",
link: '/video-course' link: '/video-course'
} },
{
groupName: "图文课",
groupCode: "CourseVideoClass",
link: '/graphics-course'
},
] ]
}, },
{ {
......
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