Commit b681142d by wufan

feat:新建线上课结构调整

parent f985047c
......@@ -8,8 +8,7 @@
*/
import React from 'react'
import { Button, Input, Radio, message, Modal, Cascader } from 'antd'
import { Button, Input, Radio, message, Modal, Cascader, Tooltip, Form, Popconfirm } from 'antd'
import { DISK_MAP, FileTypeIcon, FileVerifyMap } from '@/common/constants/academic/lessonEnum'
import { ImgCutModalNew } from '@/components'
import ShowTips from '@/components/ShowTips'
......@@ -27,9 +26,9 @@ import _ from 'underscore'
import Upload from '@/core/upload'
import { randomString } from '@/domains/basic-domain/utils'
import $ from 'jquery'
// import PhotoClip from 'photoclip';
import './AddVideoCourse.less'
const { TextArea } = Input;
const EDIT_BOX_KEY = Math.random()
const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' }
......@@ -58,7 +57,7 @@ class AddVideoCourse extends React.Component {
pageType, // 页面类型: add->新建 edit->编辑
imageFile: null, // 需要被截取的图片
courseName: null, // 线上课名称
scheduleVideoId: null, // 线上课链接
scheduleVideoIds: null, // 线上课链接
coverId: null, // 视频封面的recourceId
coverUrl: null, // 线上课封面
studentList: [], // 上课学员列表
......@@ -83,7 +82,9 @@ class AddVideoCourse extends React.Component {
whetherVisitorsJoin: 'NO', // 是否允许游客加入
showSelectCoverModal: false,
cutImageBlob: null,
introduce: ''
introduce: '',
courseChapterList:[
] // 课节列表
}
}
......@@ -124,14 +125,14 @@ class AddVideoCourse extends React.Component {
courseId
}).then((res) => {
const { result = {} } = res || {}
const { courseName, shelfState, whetherVisitorsJoin, courseMediaVOS, categoryOneName, categoryTwoName, categoryId } = result
const { courseName, shelfState, whetherVisitorsJoin, courseMediaVOS, categoryOneName, categoryTwoName, categoryId, courseChapterList =[] } = result
let coverId
let coverUrl
let videoType
let videoDuration
let videoName
let scheduleMedia = []
let scheduleVideoId
let scheduleVideoIds
let scheduleVideoUrl
let hasIntro
......@@ -144,7 +145,7 @@ class AddVideoCourse extends React.Component {
case 'SCHEDULE':
videoDuration = item.videoDuration
videoName = item.mediaName
scheduleVideoId = item.mediaContent
scheduleVideoIds = item.mediaContent
scheduleVideoUrl = item.mediaUrl
videoType = item.mediaType
break
......@@ -173,12 +174,13 @@ class AddVideoCourse extends React.Component {
videoDuration,
scheduleMedia,
courseName,
scheduleVideoId,
scheduleVideoIds,
scheduleVideoUrl,
shelfState,
whetherVisitorsJoin,
categoryName,
categoryId
categoryId,
courseChapterList
})
})
}
......@@ -196,11 +198,11 @@ class AddVideoCourse extends React.Component {
}
handleGoBack = () => {
const { coverId, videoName, videoDuration, courseName, scheduleMedia, scheduleVideoId, categoryId, shelfState, whetherVisitorsJoin } = this.state
const { coverId, videoName, videoDuration, courseName, scheduleMedia, scheduleVideoIds, categoryId, shelfState, whetherVisitorsJoin } = this.state
if (
videoName ||
videoDuration ||
scheduleVideoId ||
scheduleVideoIds ||
!_.isEqual(scheduleMedia, defaultScheduleMedia) ||
categoryId ||
courseName ||
......@@ -269,7 +271,7 @@ class AddVideoCourse extends React.Component {
// 显示预览弹窗
handleShowPreviewModal = () => {
const { coverUrl, scheduleVideoUrl, courseName, scheduleMedia, videoDuration, introduce } = this.state
const { coverUrl, scheduleVideoUrl, courseName, scheduleMedia, videoDuration, introduce, courseChapterList } = this.state
const courseBasinInfo = {
coverUrl,
......@@ -292,6 +294,7 @@ class AddVideoCourse extends React.Component {
previewCourseModal: null
})
}}
courseChapterList={courseChapterList}
/>
)
......@@ -303,20 +306,43 @@ class AddVideoCourse extends React.Component {
this.setState({
showSelectFileModal: false
})
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
let { courseChapterList } = this.state;
let _courseChapterList = [...courseChapterList];
_courseChapterList.push({
mediaContent: resourceId,
contentType: 'SCHEDULE',
mediaType: "VIDEO",
mediaName: folderName.replace('.mp4',''),
mediaNameAlias: folderName.replace('.mp4',''),
videoDuration: folderSize,
id: resourceId,
mediaUrl: ossUrl
})
const videoDom = document.createElement('video')
videoDom.src = ossUrl
videoDom.onloadedmetadata = () => {
this.setState({
size: folderSize,
videoName: folderName,
videoType: folderFormat,
scheduleVideoUrl: ossUrl,
scheduleVideoId: resourceId,
videoDuration: videoDom.duration
scheduleVideoIds: resourceId,
videoDuration: videoDom.duration,
courseChapterList: _courseChapterList
})
}
// videoDom.src = ossUrl
// videoDom.onloadedmetadata = () => {
// this.setState({
// size: folderSize,
// videoName: folderName,
// videoType: folderFormat,
// scheduleVideoUrl: ossUrl,
// scheduleVideoIds: resourceId,
// videoDuration: videoDom.duration
// })
// }
}
// 保存
......@@ -335,18 +361,19 @@ class AddVideoCourse extends React.Component {
studentList,
courseName,
scheduleMedia,
scheduleVideoId,
scheduleVideoIds,
scheduleVideoUrl,
categoryId,
shelfState,
whetherVisitorsJoin,
introduce
introduce,
courseChapterList
} = this.state
const commonParams = {
videoName,
videoDuration,
courseMediaId: scheduleVideoId,
courseMediaId: scheduleVideoIds,
scheduleMedia: scheduleMedia.filter((item) => !!item.mediaContent),
categoryId,
courseName,
......@@ -355,10 +382,11 @@ class AddVideoCourse extends React.Component {
storeId: User.getStoreId(),
shelfState,
whetherVisitorsJoin,
courseType: 'VOICE'
courseType: 'VOICE',
courseChapterList
}
// 校验必填字段:课程名称, 课程视频
this.handleValidate(courseName, scheduleVideoId, categoryId, scheduleMedia).then((res) => {
this.handleValidate(courseName, scheduleVideoIds, categoryId, scheduleMedia).then((res) => {
if (!res) return
Upload.uploadTextToOSS(introduce, `${randomString()}.txt`, (introduceId) => {
this.submitRemote({ id, pageType, commonParams: { ...commonParams, introduceId } })
......@@ -390,14 +418,14 @@ class AddVideoCourse extends React.Component {
}
}
handleValidate = (courseName, scheduleVideoId, categoryId, scheduleMedia) => {
handleValidate = (courseName, scheduleVideoIds, categoryId, scheduleMedia) => {
return new Promise((resolve) => {
if (!courseName) {
message.warning('请输入课程名称')
resolve(false)
return false
}
if (!scheduleVideoId) {
if (!scheduleVideoIds) {
message.warning('请上传视频')
resolve(false)
return false
......@@ -525,11 +553,74 @@ class AddVideoCourse extends React.Component {
}
return new Blob([ab], { type: 'image/png' })
}
handleRenameCourseChapter = (chapterId, isFromTextArea, value) => {
let val = value && value.trim();
this.handleValidateChapterName(val).then(res => {
// 校验不通过不能点确定保存修改课节名称
if (!res && !isFromTextArea) return;
let { courseChapterList } = this.state;
let _courseChapterList = [];
_courseChapterList = courseChapterList.map((item,index)=>{
if(item.id === chapterId){
// 若是文本框编辑则修改的是视频别名,否则是点击确认修改视频本名
if(isFromTextArea){
item.mediaNameAlias = val;
} else {
item.mediaName = item.mediaNameAlias;
}
}
return item
})
this.setState({
courseChapterList: _courseChapterList
})
});
}
handleDeleteCourseChapter = (chapterId) => {
console.log('chapterId---',chapterId);
let { courseChapterList } = this.state;
let _courseChapterList = courseChapterList.filter((item,index) => {
return item.id !== chapterId
})
this.setState({
courseChapterList :_courseChapterList
})
}
renderChapterTitle = (item) => {
const { chapterNameValidateStatus, chapterNameHelpMsg} = this.state;
return <div className="course-chapter-title-popover">
<div className="tag-title">课节名称</div>
<Form>
<Form.Item
validateStatus={chapterNameValidateStatus}
help={chapterNameHelpMsg}
>
<TextArea
value={item.mediaNameAlias}
placeholder="请输入课节名称"
maxLength={40}
autoSize
// autoSize={{ minRows: 1, maxRows: 3 }}
style={{ width: '318px'}}
onChange={(e) => {this.handleRenameCourseChapter(item.id, true, e.target.value) }}
/>
</Form.Item>
</Form>
</div>
}
render() {
const {
pageType,
courseName,
scheduleVideoId,
scheduleVideoIds,
coverId,
coverUrl,
scheduleVideoUrl,
......@@ -552,13 +643,13 @@ class AddVideoCourse extends React.Component {
cutImageBlob,
introduce,
loadintroduce,
id
id,
courseChapterList
} = this.state
// 已选择的上课学员数量
const hasSelectedStu = studentList.length
const courseWareIcon = FileVerifyMap[videoType] ? FileTypeIcon[FileVerifyMap[videoType].type] : FileTypeIcon[videoType]
const defaultCover = 'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png';
const isDefaultCover = coverUrl === defaultCover || coverUrl == null;
return (
<div className='page add-video-course-page'>
......@@ -586,51 +677,92 @@ class AddVideoCourse extends React.Component {
<div className='upload-video mt16'>
<div className='content flex'>
<span className='label required'>视频上传:</span>
<div className='value'>
{scheduleVideoId ? (
<div className='course-ware'>
<img className='course-ware__img' src={courseWareIcon} />
<span className='course-ware__name'>{videoName}</span>
</div>
) : (
<div className='course-ware--empty'>从资料云盘中选择视频</div>
)}
</div>
</div>
<div className='sub-content'>
<div className="btn-wrap">
<Button
onClick={() => {
if(courseChapterList.length >= 20) {
message.warning(`最多只能上传20个文件`);
return;
}
this.setState({
showSelectFileModal: true
})
}}>{`${pageType === 'add' && !scheduleVideoId ? '选择' : '更换'}视频`}</Button>
}}>{`${pageType === 'add' && !scheduleVideoIds ? '选择' : '更换'}视频`}</Button>
<div className='course-ware--empty'>从资料云盘中选择视频</div>
</div>
<div className='tips'>视频数量限制20个,每个视频文件大小不超过2G</div>
</div>
<span className='tips'>视频数量限制1个,大小不超过2G</span>
<If condition={courseChapterList.length > 0}>
<div className="course-chapter-list-wrap">
<div className="course-chapter-total">{`共${courseChapterList.length}个课节`}</div>
<div className='course-chapter-list' id="course-chapter-list">
{
_.map(courseChapterList,(item,index) => {
return <div className='course-ware' key={index}>
<div>{`0${index + 1 } `}</div>
<img className='course-ware__img' src={courseWareIcon} alt='' />
<div className='course-ware__name'>{item.mediaName && item.mediaName.length > 24 ? <Tooltip title={item.mediaName}>{item.mediaName}</Tooltip>:item.mediaName}</div>
<div className="course-chapter__opt" id={item.id}>
<Popconfirm
placement="topLeft"
className="course-chapter-tooltip"
title={this.renderChapterTitle(item)}
color='#fff' trigger="click"
overlayClassName="chapter-popover"
getPopupContainer={() =>
document.getElementById('course-chapter-list')
}
onConfirm={() => this.handleRenameCourseChapter(item.id,false,item.mediaNameAlias)}
icon={null}
maskClosable={false}
>
<div className="rename">重命名</div>
</Popconfirm>
<div className="line">|</div>
<div className="delete" onClick={()=>this.handleDeleteCourseChapter(item.id)}>移除</div>
</div>
</div>
})
}
<div className='cover-url flex mt16'>
<div className='label'>视频封面:</div>
<div className='cover-url__wrap'>
<div className='img-content'>
{/* 如果视频和封面都没有上传的话, 那么就显示缺省, 如果上传了视频, 那么封面图就默认显示视频的第一帧, 如果上传了封面图, 那么就显示上传的封面图 */}
{scheduleVideoUrl || coverUrl ? (
<img src={coverUrl || `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`} />
) : (
<div className='empty-img'>
若不上传
<br />
系统默认将视频首帧作为封面图
</div>
)}
</div>
</If>
</div>
<div className='cover-url flex mt16'>
<div className='label'>课程封面:</div>
<div className='cover-url__wrap'>
<div className='opt-btns'>
<div>
<Button
onClick={() => {
this.setState({ showSelectCoverModal: true })
}}>{`${pageType === 'add' && !scheduleVideoId && !coverUrl ? '上传' : '修改'}封面`}</Button>
}}>{`${pageType === 'add' && !coverUrl ? '上传' : '修改'}封面`}</Button>
<span
className={`span ${coverUrl ? 'defalut-span' : ''}`}
onClick={() => {
this.setState({
coverUrl: '',
coverId: ''
})
}}>
使用默认图
</span>
</div>
<div className='tips'>建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div>
<div className='img-content'>
{
isDefaultCover && <span className="tag">默认图</span>
}
{/* 如果视频和封面都没有上传的话, 那么就显示缺省, 如果上传了视频, 那么封面图就默认显示视频的第一帧, 如果上传了封面图, 那么就显示上传的封面图 */}
<img src={coverUrl || `https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png`} alt='' />
</div>
</div>
</div>
<div className='course-catalog required'>
......@@ -695,6 +827,7 @@ class AddVideoCourse extends React.Component {
{/* 选择备课文件弹窗 */}
{showSelectFileModal && (
<SelectPrepareFileModal
multiple={true}
operateType='select'
selectTypeList={['MP4']}
accept='video/mp4'
......
......@@ -49,8 +49,8 @@
}
}
.course-catalog{
margin-bottom:16px;
margin-top:16px;
margin-bottom:24px;
margin-top:24px;
}
.course-ware {
display: flex;
......@@ -75,7 +75,19 @@
.img-content {
width: 298px;
height: 172px;
position: relative;
.tag {
border-radius: 2px;
background: rgba(51, 51, 51, .2);
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
img {
width: 100%;
height: 100%;
......@@ -85,7 +97,7 @@
.empty-img {
width: 298px;
height: 172px;
border: 1px dashed #EBEBEB;
border: 1px dashed #ebebeb;
border-radius: 4px;
padding: 12px;
color: #999;
......@@ -94,12 +106,17 @@
}
.opt-btns {
margin-top: 8px;
display: flex;
align-items: center;
margin-bottom: 8px;
.span {
color: #ccc;
margin-left: 8px;
}
.defalut-span {
cursor: pointer;
color: #2966ff;
}
.tips {
margin-left: 12px;
margin-top: 8px;
color: #999;
}
}
......@@ -115,18 +132,90 @@
color: #333;
}
}
.upload-video {
display: flex;
margin-bottom: 24px;
.sub-content {
margin-left: 85px;
margin-top: 4px;
.btn-wrap {
display: flex;
.course-ware--empty {
margin-left: 12px;
line-height: 28px;
}
}
.tips {
margin-left: 4px;
margin-top: 8px;
color: #999;
}
}
}
.course-chapter-list-wrap {
position: relative;
.course-chapter-total {
height: 40px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
margin-bottom: 18px;
position: absolute;
top: 1px;
left: 1px;
z-index: 10;
background: #fff;
width: 550px;
text-indent: 16px;
line-height: 40px;
}
.course-chapter-list {
margin-top: 8px;
width: 552px;
max-height: 247px;
min-height: 130px;
overflow-y: auto ;
border-radius: 4px;
border: 1px solid #E8E8E8;
padding: 50px 16px 20px 16px;
.course-ware {
display: flex;
align-items: center;
margin-bottom: 20px;
color: #666;
&:last-child {
margin-bottom: 0;
}
&__img {
width: 24px;
margin-right: 4px;
margin-left: 16px;
}
&__name {
color: #333;
width: 356px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.course-chapter__opt {
display: flex;
color: #2966FF;
.line {
color: #BFBFBF;
margin-left: 4px;
margin-right: 4px;
}
.delete, .rename {
cursor: pointer;
}
}
}
}
}
}
.footer {
position: fixed;
left: 196px;
......
......@@ -121,8 +121,14 @@ class AddVideoIntro extends React.Component {
</div>
<div>
<div className="desc">
<div>开启:允许未绑定手机号的学员观看</div>
<div>关闭:仅限绑定了手机号的学员可以进入观看视频</div>
<Choose>
<When condition={whetherVisitorsJoin==="YES"}>
<div>已开启,学员需绑定手机号才可观看</div>
</When>
<Otherwise>
<div>已关闭,学员无需绑定手机号即可观看</div>
</Otherwise>
</Choose>
</div>
</div>
</div>
......@@ -136,8 +142,14 @@ class AddVideoIntro extends React.Component {
</Col>
<Col span={21}>
<div className="desc">
<div>开启:此视频将在学员学院的视频列表中出现</div>
<div>关闭:此视频将在学员学院的视频列表中隐藏</div>
<Choose>
<When condition={shelfState==="YES"}>
<div>已开启,课程将在该学院的学员课程列表中显示</div>
</When>
<Otherwise>
<div>已关闭,课程将在该学院的学员课程列表中隐藏</div>
</Otherwise>
</Choose>
</div>
</Col>
</Row>
......
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