Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xiaomai-cloud-class-web
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
xiaomai-cloud-class
xiaomai-cloud-class-web
Commits
aef0f0eb
Commit
aef0f0eb
authored
Jul 15, 2021
by
guomingpang
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'rc' of
ssh://xmgit.ixm5.cn:10022/xiaomai-cloud-class/xiaomai-cloud-class-web
into rc
parents
208affdf
9c00f7b6
Show whitespace changes
Inline
Side-by-side
Showing
79 changed files
with
3531 additions
and
2216 deletions
+3531
-2216
src/bu-components/PreviewCourseModal.less
+3
-2
src/bu-components/PreviewFileModal.less
+1
-0
src/common/constants/academic/lessonEnum.js
+6
-1
src/components/ContactWidget.less
+1
-1
src/components/ContactWidget.tsx
+1
-0
src/core/function.js
+13
-0
src/data-source/aidTool/request-apis.ts
+7
-2
src/data-source/base/request-apis.ts
+4
-1
src/data-source/course/request-api.ts
+6
-3
src/data-source/knowledge/request-api.ts
+1
-1
src/data-source/plan/request-apis.ts
+16
-16
src/domains/aid-tool-domain/AidToolService.ts
+8
-2
src/domains/basic-domain/baseService.ts
+6
-2
src/domains/course-domain/CourseService.ts
+5
-1
src/domains/resource-disk/constants.ts
+6
-2
src/index.html
+1
-1
src/modules/course-manage/AddLive.jsx
+181
-205
src/modules/course-manage/components/AddLiveBasic.jsx
+108
-78
src/modules/course-manage/components/AddLiveBasic.less
+27
-32
src/modules/course-manage/components/AddLiveIntro.jsx
+105
-92
src/modules/course-manage/components/GraphicsEditor.jsx
+95
-92
src/modules/course-manage/components/GraphicsEditor.less
+11
-4
src/modules/course-manage/graphics-course/AddGraphicsCourse.jsx
+61
-9
src/modules/course-manage/graphics-course/AddGraphicsCourse.less
+86
-28
src/modules/course-manage/graphics-course/components/AddGraphicsIntro.jsx
+81
-88
src/modules/course-manage/graphics-course/components/AddGraphicsIntro.less
+46
-45
src/modules/course-manage/graphics-course/components/GraphicsCourseList.jsx
+4
-4
src/modules/course-manage/graphics-course/components/GraphicsCourseOpt.jsx
+1
-1
src/modules/course-manage/graphics-course/index.jsx
+4
-4
src/modules/course-manage/modal/PreviewCourseModal.jsx
+191
-97
src/modules/course-manage/modal/PreviewCourseModal.less
+12
-1
src/modules/course-manage/modal/ShareLiveModal.jsx
+1
-5
src/modules/course-manage/offline-course/AddOfflineCourse.jsx
+45
-47
src/modules/course-manage/offline-course/AddOfflineCourse.less
+37
-40
src/modules/course-manage/offline-course/components/AddGraphicsIntro.jsx
+4
-4
src/modules/course-manage/offline-course/components/AddGraphicsIntro.less
+1
-1
src/modules/course-manage/offline-course/components/OfflineCourseList.jsx
+2
-2
src/modules/course-manage/offline-course/components/OfflineCourseOpt.jsx
+1
-1
src/modules/course-manage/utils/hasExportPermission.js
+1
-1
src/modules/course-manage/video-course/AddVideoCourse.jsx
+414
-126
src/modules/course-manage/video-course/AddVideoCourse.less
+129
-15
src/modules/course-manage/video-course/VideoCourseDetail.less
+90
-0
src/modules/course-manage/video-course/VideoCourseDetail.tsx
+204
-0
src/modules/course-manage/video-course/components/AddVideoIntro.jsx
+32
-19
src/modules/course-manage/video-course/components/AddVideoIntro.less
+6
-3
src/modules/course-manage/video-course/components/ChapterList.jsx
+29
-0
src/modules/course-manage/video-course/components/ChapterList.less
+37
-0
src/modules/course-manage/video-course/components/LearningDetailModal.jsx
+77
-0
src/modules/course-manage/video-course/components/LearningDetailModal.less
+38
-0
src/modules/course-manage/video-course/components/VideoCourseFilter.jsx
+14
-19
src/modules/course-manage/video-course/components/VideoCourseList.jsx
+76
-77
src/modules/course-manage/video-course/components/VideoCourseList.less
+6
-0
src/modules/course-manage/video-course/components/VieoCourseOpt.jsx
+2
-2
src/modules/course-manage/video-course/components/WatchData.jsx
+271
-0
src/modules/course-manage/video-course/components/WatchData.less
+43
-0
src/modules/course-manage/video-course/index.jsx
+6
-6
src/modules/course-manage/video-course/modal/WatchDataModal.jsx
+18
-31
src/modules/home/Home.jsx
+3
-4
src/modules/knowledge-base/ENUM.js
+1
-1
src/modules/knowledge-base/components/KnowledgeBaseFilter.jsx
+3
-29
src/modules/knowledge-base/components/KnowledgeBaseList.jsx
+8
-160
src/modules/knowledge-base/components/KnowledgeBaseList.less
+5
-8
src/modules/knowledge-base/components/KnowledgeBaseOpt.jsx
+5
-32
src/modules/knowledge-base/modal/AddCourse.jsx
+5
-5
src/modules/knowledge-base/modal/VideoList.jsx
+5
-5
src/modules/plan-manage/AddPlan.jsx
+123
-112
src/modules/plan-manage/components/BasicInfo.jsx
+37
-19
src/modules/plan-manage/components/BasicInfo.less
+71
-68
src/modules/plan-manage/components/ExpiredCourseList.jsx
+1
-1
src/modules/plan-manage/components/TrainingTask.jsx
+171
-160
src/modules/plan-manage/components/UserLearningDataFilter.jsx
+127
-101
src/modules/plan-manage/modal/UserLearnDetailModal.jsx
+178
-111
src/modules/plan-manage/modal/UserLearnDetailModal.less
+60
-133
src/modules/plan-manage/modal/relatedCourseModal.jsx
+29
-43
src/modules/prepare-lesson/modal/SelectPrepareFileModal.jsx
+5
-1
src/modules/resource-disk/modal/ScanFileModal.jsx
+2
-2
src/modules/resource-disk/modal/ScanFileModal.less
+3
-0
src/routes/config/mainRoutes.tsx
+3
-3
src/routes/config/menuList.tsx
+4
-4
No files found.
src/bu-components/PreviewCourseModal.less
View file @
aef0f0eb
...
...
@@ -13,8 +13,9 @@
}
.container {
overflow: scroll;
height: 100%;;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
.course-cover, .course-url {
width: 100%;
...
...
src/bu-components/PreviewFileModal.less
View file @
aef0f0eb
...
...
@@ -33,6 +33,7 @@
}
}
.operate{
display:inline-block;
margin-top:24px;
.btn {
padding:5px 12px;
...
...
src/common/constants/academic/lessonEnum.js
View file @
aef0f0eb
...
...
@@ -44,6 +44,10 @@ const FileVerifyMap = {
type
:
"word"
,
maxSize
:
100
},
"application/wps-writer"
:
{
type
:
"word"
,
maxSize
:
100
,
},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
:
{
type
:
"word"
,
maxSize
:
100
...
...
@@ -133,12 +137,13 @@ const FileTypeIcon = {
PPTX
:
"https://image.xiaomaiketang.com/xm/847pFAdYGW.png"
,
PDF
:
"https://image.xiaomaiketang.com/xm/rrEJMNkhTG.png"
,
MP3
:
"https://image.xiaomaiketang.com/xm/ykjnSWDyQ6.png"
,
MP4
:
"https://image.xiaomaiketang.com/xm/
whSYMTdR57
.png"
,
MP4
:
"https://image.xiaomaiketang.com/xm/
yK3ASiS8ch
.png"
,
JPG
:
"https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png"
,
JPEG
:
"https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png"
,
PNG
:
"https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png"
,
GIF
:
"https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png"
,
BMP
:
"https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png"
,
VIDEO
:
'https://image.xiaomaiketang.com/xm/yK3ASiS8ch.png'
};
const
UploadIcon
=
"https://image.xiaomaiketang.com/xm/4DXNrZWWsd.png"
;
...
...
src/components/ContactWidget.less
View file @
aef0f0eb
.
an
t-popover .ant-popover-content .ant-popover-inner {
.
contact-widge
t-popover .ant-popover-content .ant-popover-inner {
box-shadow: 0px 2px 20px 0px rgba(0, 0, 0, 0.06);
.ant-popover-inner-content {
padding: 0;
...
...
src/components/ContactWidget.tsx
View file @
aef0f0eb
...
...
@@ -27,6 +27,7 @@ function Content() {
export
default
function
ContactWidget
(
props
:
ContactWidgetProps
)
{
return
<
Popover
className=
"contact-widget-popover"
placement=
{
props
.
placement
}
arrowPointAtCenter
content=
{
Content
}
...
...
src/core/function.js
View file @
aef0f0eb
...
...
@@ -1156,3 +1156,15 @@ window.XMShowClassName = (date, itemName) => {
}
return
'new-icon'
}
// 格式化时间段为时分
window
.
formatDuration
=
function
(
time
)
{
const
diff
=
Math
.
floor
(
time
%
3600
);
let
hours
=
Math
.
floor
(
time
/
3600
);
let
mins
=
Math
.
floor
(
diff
/
60
);
let
seconds
=
Math
.
floor
(
time
%
60
);
hours
=
hours
<
10
?
(
"0"
+
hours
)
:
hours
;
mins
=
mins
<
10
?
(
"0"
+
mins
)
:
mins
;
seconds
=
seconds
<
10
?
(
"0"
+
seconds
)
:
seconds
;
return
hours
+
":"
+
mins
+
":"
+
seconds
;
};
\ No newline at end of file
src/data-source/aidTool/request-apis.ts
View file @
aef0f0eb
/*
* @Author: yuananting
* @Date: 2021-03-03 15:13:12
* @LastEditors:
Please set LastEditors
* @LastEditTime: 2021-0
6-22 14:31:46
* @LastEditors:
yuananting
* @LastEditTime: 2021-0
7-09 16:58:34
* @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
...
...
@@ -88,3 +88,7 @@ export function batchQueryQuestionDetails(params: object) {
export
function
queryQuestionPageListWithContent
(
params
:
object
)
{
return
Service
.
Hades
(
'public/hades/queryQuestionPageListWithContent'
,
params
);
}
export
function
queryCategoryTreeByPackage
(
params
:
object
)
{
return
Service
.
Hades
(
'public/externalHades/queryPackageCategory'
,
params
);
}
\ No newline at end of file
src/data-source/base/request-apis.ts
View file @
aef0f0eb
...
...
@@ -2,7 +2,7 @@
* @Author: wufan
* @Date: 2020-12-01 17:21:21
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-0
6-22 14:56:34
* @LastEditTime: 2021-0
7-09 15:33:33
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
...
...
@@ -62,6 +62,9 @@ export function saveYoZoFileVersionId(params: object) {
export
function
yoZoUpload
(
ossUrl
:
String
,
appId
:
String
,
uploadSign
:
String
){
return
axios
.
post
(
`https://dmc.yozocloud.cn/api/file/http?fileUrl=
${
ossUrl
}
&appId=
${
appId
}
&sign=
${
uploadSign
}
`
)
}
export
function
saveFileVersionIdByCourseChapter
(
params
:
object
)
{
return
Service
.
Hades
(
'public/hades/saveFileVersionIdByCourseChapter'
,
params
);
}
export
const
getOssClient
=
(
data
:
object
,
instId
:
string
,
...
...
src/data-source/course/request-api.ts
View file @
aef0f0eb
/*
* @Author: wufan
* @Date: 2020-12-12 11:57:10
* @LastEditors:
zangsuyu
n
* @LastEditTime: 2021-0
3-22 13:54:20
* @LastEditors:
wufa
n
* @LastEditTime: 2021-0
7-05 15:07:13
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
...
...
@@ -57,7 +57,7 @@ export function getLiveCloudCourseBasePage(params: object) {
return
Service
.
Hades
(
"public/courseCloud/getLiveCloudCourseBasePage"
,
params
);
}
//
视频
课相关接口
//
线上
课相关接口
export
function
changeVideoShelfState
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/changeVideoShelfState"
,
params
);
}
...
...
@@ -73,6 +73,9 @@ export function editVideoSchedule(params: object) {
export
function
userWatchInfo
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/userWatchInfo"
,
params
);
}
export
function
lineDetailWatchInfo
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/lineDetailWatchInfo"
,
params
);
}
export
function
videoScheduleDetail
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/videoScheduleDetail"
,
params
);
}
...
...
src/data-source/knowledge/request-api.ts
View file @
aef0f0eb
...
...
@@ -81,7 +81,7 @@ class KnowledgeAPI {
exportPicLearnSync
=
(
params
:
object
)
=>
{
return
Service
.
Hades
(
"public/knowledge/exportPicLearnSync"
,
params
);
}
//
视频
课观看记录导出
//
线上
课观看记录导出
exportVideoLearnSync
=
(
params
:
object
)
=>
{
return
Service
.
Hades
(
"public/knowledge/exportVideoLearnSync"
,
params
);
}
...
...
src/data-source/plan/request-apis.ts
View file @
aef0f0eb
/*
* @Author: zhangleyuan
* @Date: 2021-02-21 16:08:38
* @LastEditors:
zhangleyuan
* @LastEditTime: 2021-0
3-09 12:54:31
* @LastEditors:
yuananting
* @LastEditTime: 2021-0
7-06 11:26:27
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
Service
from
"@/common/js/service"
;
import
Service
from
'@/common/js/service'
;
export
function
getTrainingPlanPage
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/getTrainingPlanPage"
,
params
);
return
Service
.
Hades
(
'public/hades/getTrainingPlanPage'
,
params
);
}
export
function
createTrainingPlan
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/createTrainingPlan"
,
params
);
return
Service
.
Hades
(
'public/hades/createTrainingPlan'
,
params
);
}
export
function
updateStateTrainingPlan
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/updateStateTrainingPlan"
,
params
);
return
Service
.
Hades
(
'public/hades/updateStateTrainingPlan'
,
params
);
}
export
function
getTrainingPlanDetail
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/getTrainingPlanDetail"
,
params
);
return
Service
.
Hades
(
'public/hades/getTrainingPlanDetail'
,
params
);
}
export
function
updateTrainingPlan
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/updateTrainingPlan"
,
params
);
return
Service
.
Hades
(
'public/hades/updateTrainingPlan'
,
params
);
}
export
function
deleteTrainingPlan
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/deleteTrainingPlan"
,
params
);
return
Service
.
Hades
(
'public/hades/deleteTrainingPlan'
,
params
);
}
export
function
getPlanUserRecordPage
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/getPlanUserRecordPage"
,
params
);
return
Service
.
Hades
(
'public/hades/getPlanUserRecordPage'
,
params
);
}
export
function
getPlanCustomerRecordPage
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/getPlanCustomerRecordPage"
,
params
);
return
Service
.
Hades
(
'public/hades/getPlanCustomerRecordPage'
,
params
);
}
export
function
getPlanCustomerDetail
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/getPlanCustomerDetail"
,
params
);
return
Service
.
Hades
(
'public/customerHades/getPlanCustomerDetail'
,
params
);
}
export
function
getPlanCustomerAboutUser
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/getPlanCustomerAboutUser"
,
params
);
return
Service
.
Hades
(
'public/hades/getPlanCustomerAboutUser'
,
params
);
}
export
function
removePlanCustomer
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/removePlanCustomer"
,
params
);
return
Service
.
Hades
(
'public/hades/removePlanCustomer'
,
params
);
}
export
function
getStorePlanAll
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/getStorePlanAll"
,
params
);
return
Service
.
Hades
(
'public/hades/getStorePlanAll'
,
params
);
}
export
function
getTrainingCourseAutoCancel
(
params
:
object
)
{
return
Service
.
Hades
(
"public/hades/getTrainingCourseAutoCancel"
,
params
);
return
Service
.
Hades
(
'public/hades/getTrainingCourseAutoCancel'
,
params
);
}
src/domains/aid-tool-domain/AidToolService.ts
View file @
aef0f0eb
/*
* @Author: yuananting
* @Date: 2021-03-11 11:34:37
* @LastEditors:
fusanqias
ng
* @LastEditTime: 2021-0
6-16 09:56:46
* @LastEditors:
yuananti
ng
* @LastEditTime: 2021-0
7-09 16:54:54
* @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
{
queryExternalCategoryTree
,
queryCategoryTreeByPackage
,
queryCategoryTree
,
addCategory
,
delCategory
,
...
...
@@ -38,6 +39,11 @@ export default class AidToolService {
return
queryExternalCategoryTree
(
parmas
);
}
// 课程分类树(按课程包)
static
queryCategoryTreeByPackage
(
params
:
any
)
{
return
queryCategoryTreeByPackage
(
params
);
}
// 获取题目分类树
static
queryCategoryTree
(
params
:
any
)
{
return
queryCategoryTree
(
params
);
...
...
src/domains/basic-domain/baseService.ts
View file @
aef0f0eb
...
...
@@ -2,12 +2,12 @@
* @Author: wufan
* @Date: 2020-12-01 17:20:49
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-0
6-22 14:57:01
* @LastEditTime: 2021-0
7-09 15:33:59
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
{
getUserStore
,
getUserPermission
,
logout
,
getStoreUser
,
sendBizAuthCode
,
editUserPhone
,
checkBizAuthCode
,
sendNewPhoneAuthCode
,
sendLoginAuthCode
,
login
,
getLastedVersion
,
getEnterpriseUser
,
getWXWorkLoginNoCheck
,
getLesseeVersionMsg
,
getYoZoSign
,
saveYoZoFileVersionId
,
yoZoUpload
}
from
'@/data-source/base/request-apis'
;
import
{
getUserStore
,
getUserPermission
,
logout
,
getStoreUser
,
sendBizAuthCode
,
editUserPhone
,
checkBizAuthCode
,
sendNewPhoneAuthCode
,
sendLoginAuthCode
,
login
,
getLastedVersion
,
getEnterpriseUser
,
getWXWorkLoginNoCheck
,
getLesseeVersionMsg
,
getYoZoSign
,
saveYoZoFileVersionId
,
yoZoUpload
,
saveFileVersionIdByCourseChapter
}
from
'@/data-source/base/request-apis'
;
export
default
class
StoreService
{
// 获取员工列表
...
...
@@ -73,4 +73,7 @@ export default class StoreService {
static
yoZoUpload
(
ossUrl
:
String
,
appId
:
String
,
uploadSign
:
String
){
return
yoZoUpload
(
ossUrl
,
appId
,
uploadSign
);
}
static
saveFileVersionIdByCourseChapter
(
params
:
any
){
return
saveFileVersionIdByCourseChapter
(
params
);
}
}
\ No newline at end of file
src/domains/course-domain/CourseService.ts
View file @
aef0f0eb
...
...
@@ -9,7 +9,8 @@
import
{
fetchLecturerData
,
getCategoryTree
,
knowledgeMediaCoursePage
,
fetchUserData
,
exportStudentCourseData
,
exportPlayBackCourseData
,
fetchPlaybackList
,
createLiveCloudCourse
,
getLiveCloudCoursePage
,
getLiveCloudCourseDetail
,
updateLiveCloudCourse
,
turnOnOrOffLiveCloudCourse
,
delLiveCloudCourse
,
changeVideoShelfState
,
createVideoSchedule
,
delVideoSchedule
,
editVideoSchedule
,
userWatchInfo
,
videoSchedulePage
,
videoScheduleDetail
,
videoWatchInfo
,
getQrcode
,
getLiveCloudCourseBasePage
,
videoScheduleBasePage
,
relatedCourseToPlan
editVideoSchedule
,
userWatchInfo
,
videoSchedulePage
,
videoScheduleDetail
,
videoWatchInfo
,
getQrcode
,
getLiveCloudCourseBasePage
,
videoScheduleBasePage
,
relatedCourseToPlan
,
lineDetailWatchInfo
}
from
'@/data-source/course/request-api'
;
export
default
class
courseService
{
...
...
@@ -85,6 +86,9 @@ export default class courseService {
static
videoWatchInfo
(
params
:
any
)
{
return
videoWatchInfo
(
params
);
}
static
lineDetailWatchInfo
(
params
:
any
)
{
return
lineDetailWatchInfo
(
params
);
}
static
getLiveCloudCourseBasePage
(
params
:
any
)
{
return
getLiveCloudCourseBasePage
(
params
);
}
...
...
src/domains/resource-disk/constants.ts
View file @
aef0f0eb
/*
* @Author: 吴文洁
* @Date: 2020-08-20 09:21:40
* @LastEditors:
zhangleyuan
* @LastEditTime: 2021-0
3-04 17:16:30
* @LastEditors:
yuananting
* @LastEditTime: 2021-0
7-15 11:48:58
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
...
...
@@ -77,6 +77,10 @@ const FILR_VERIFY_MAP = {
type
:
"word"
,
maxSize
:
100
},
"application/wps-writer"
:
{
type
:
"word"
,
maxSize
:
100
,
},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
:
{
type
:
"word"
,
maxSize
:
100
...
...
src/index.html
View file @
aef0f0eb
...
...
@@ -19,7 +19,7 @@
/>
<meta
name=
"keywords"
content=
"小麦企学院,企业培训,员工培训,企业大学,企业内训,企业外训,培训计划,培训素材,企培,企训,资料云盘,培训课程,培训任务,直播课,
视频
课,图文课,线下课,知识库,作业,考试,排行榜,培训类别管理,定制培训计划,管理数据,学习数据,企学院,资料共享,培训数字化,数字化培训,培训工具,在线培训,线上培训,培训saas,培训管理,企业微信培训,对客培训,客户培训,直播培训,互联网培训,新员工培训,管理培训,管理者培训,工人培训,制造业培训,餐饮培训,服务业培训,零售培训,门店培训,工厂培训,车间培训,培训补贴,人事培训,财务培训,职场培训,企业学院平台,教育企业学院,教育企业平台,教育平台学院,企业学习,酷学院,小鹅通,企业学院,云学堂,时代光华,云课堂,魔学院,云大学,米知云,授课学堂"
content=
"小麦企学院,企业培训,员工培训,企业大学,企业内训,企业外训,培训计划,培训素材,企培,企训,资料云盘,培训课程,培训任务,直播课,
线上
课,图文课,线下课,知识库,作业,考试,排行榜,培训类别管理,定制培训计划,管理数据,学习数据,企学院,资料共享,培训数字化,数字化培训,培训工具,在线培训,线上培训,培训saas,培训管理,企业微信培训,对客培训,客户培训,直播培训,互联网培训,新员工培训,管理培训,管理者培训,工人培训,制造业培训,餐饮培训,服务业培训,零售培训,门店培训,工厂培训,车间培训,培训补贴,人事培训,财务培训,职场培训,企业学院平台,教育企业学院,教育企业平台,教育平台学院,企业学习,酷学院,小鹅通,企业学院,云学堂,时代光华,云课堂,魔学院,云大学,米知云,授课学堂"
/>
<!-- <link rel="apple-touch-icon" href="../src/common/images/logo.png" /> -->
<link
rel=
"shortcut icon"
href=
"https://image.xiaomaiketang.com/xm/c4KiP2epBP.png"
/>
...
...
src/modules/course-manage/AddLive.jsx
View file @
aef0f0eb
...
...
@@ -7,12 +7,12 @@
*/
import
React
from
'react'
;
import
{
withRouter
}
from
"react-router-dom"
;
import
{
withRouter
}
from
'react-router-dom'
;
import
{
Button
,
message
,
Modal
}
from
'antd'
;
import
ShowTips
from
"@/components/ShowTips"
;
import
Breadcrumbs
from
"@/components/Breadcrumbs"
;
import
Bus
from
'../../core/bus'
import
ShowTips
from
'@/components/ShowTips'
;
import
Breadcrumbs
from
'@/components/Breadcrumbs'
;
import
Bus
from
'../../core/bus'
;
import
AddLiveBasic
from
'./components/AddLiveBasic'
;
import
AddLiveClass
from
'./components/AddLiveClass'
;
...
...
@@ -20,10 +20,10 @@ import AddLiveIntro from './components/AddLiveIntro';
import
{
randomString
}
from
'@/domains/basic-domain/utils'
;
import
Upload
from
'@/core/upload'
;
import
PreviewCourseModal
from
'./modal/PreviewCourseModal'
;
import
CourseService
from
"@/domains/course-domain/CourseService"
;
import
CourseService
from
'@/domains/course-domain/CourseService'
;
import
moment
from
'moment'
;
import
User
from
'@/common/js/user'
;
import
_
from
"underscore"
;
import
_
from
'underscore'
;
import
$
from
'jquery'
;
import
'./AddLive.less'
;
...
...
@@ -31,14 +31,14 @@ const defaultCover = 'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png';
const
defaultBasicInfo
=
{
courseName
:
null
,
// 课程名称
coverUrl
:
defaultCover
,
coverId
:
null
,
categoryId
:
null
,
categoryName
:
null
,
coverId
:
null
,
categoryId
:
null
,
categoryName
:
null
,
};
const
defaultClassInfo
=
{
teacherId
:
null
,
//讲师的Id
assistant
:[],
//助教
assistant
:
[],
//助教
teacherName
:
null
,
liveDate
:
null
,
timeHorizonStart
:
null
,
...
...
@@ -50,61 +50,63 @@ const defaultClassInfo = {
const
defaultIntroInfo
=
{
needRecord
:
'YES'
,
whetherVisitorsJoin
:
'NO'
,
whetherVisitorsJoin
:
'NO'
,
liveCourseWarmMedia
:
{},
// 讲师简介
liveCourseMediaRequests
:
[{
contentType
:
"INTRO"
,
liveCourseMediaRequests
:
[
{
contentType
:
'INTRO'
,
mediaType
:
'TEXT'
,
mediaContent
:
''
,
key
:
Math
.
random
()
}],
}
key
:
Math
.
random
(),
},
],
};
class
AddLive
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
const
id
=
getParameterByName
(
"id"
);
const
type
=
getParameterByName
(
"type"
);
const
id
=
getParameterByName
(
'id'
);
const
type
=
getParameterByName
(
'type'
);
this
.
state
=
{
id
,
type
,
loading
:
false
,
isEdit
:
true
,
courseState
:
'UN_START'
,
courseState
:
'UN_START'
,
// 直播课基本信息
addLiveBasicInfo
:
{
courseName
:
null
,
// 课程名称
coverUrl
:
defaultCover
,
coverId
:
null
,
categoryId
:
null
,
categoryName
:
null
,
coverId
:
null
,
categoryId
:
null
,
categoryName
:
null
,
},
// 直播课上课信息
addLiveClassInfo
:
{
teacherId
:
null
,
teacherName
:
null
,
assistant
:[],
assistantStoreUserId
:[],
assistantNames
:[],
assistant
:
[],
assistantStoreUserId
:
[],
assistantNames
:
[],
liveDate
:
null
,
timeHorizonStart
:
null
,
timeHorizonEnd
:
null
,
calendarTime
:
[],
// 批量排课
startTime
:
new
Date
().
getTime
()
+
300000
,
// 批量开始时分
endTime
:
new
Date
().
getTime
()
+
300000
// 批量结束时分
endTime
:
new
Date
().
getTime
()
+
300000
,
// 批量结束时分
},
// 直播课简介
addLiveIntroInfo
:
{
needRecord
:
'YES'
,
whetherVisitorsJoin
:
'NO'
,
whetherVisitorsJoin
:
'NO'
,
liveCourseWarmMedia
:
{},
introduce
:
''
,
liveCourseMediaRequests
:
[],
},
}
}
;
}
componentDidMount
()
{
...
...
@@ -112,13 +114,18 @@ class AddLive extends React.Component {
if
(
type
===
'edit'
)
{
this
.
getCourseDetail
();
}
Bus
.
bind
(
'editorLimit'
,
(
editorTextLength
)
=>
{
this
.
setState
({
editorTextLength
,
});
});
}
getCourseDetail
=
()
=>
{
let
{
isEdit
}
=
this
.
state
;
this
.
setState
({
loading
:
true
});
CourseService
.
getLiveCloudCourseDetail
({
liveCourseId
:
this
.
state
.
id
liveCourseId
:
this
.
state
.
id
,
}).
then
((
res
)
=>
{
const
{
teacherId
,
...
...
@@ -134,7 +141,7 @@ class AddLive extends React.Component {
categoryId
,
categoryName
,
admins
,
courseState
courseState
,
}
=
res
.
result
;
let
coverId
;
let
coverUrl
;
...
...
@@ -142,15 +149,15 @@ class AddLive extends React.Component {
let
liveCourseWarmMedia
;
let
hasIntro
=
false
;
courseMediaVOS
.
map
((
item
)
=>
{
switch
(
item
.
contentType
)
{
case
"COVER"
:
switch
(
item
.
contentType
)
{
case
'COVER'
:
coverId
=
item
.
mediaContent
;
coverUrl
=
item
.
mediaUrl
;
break
;
case
"WARMUP"
:
case
'WARMUP'
:
liveCourseWarmMedia
=
item
;
break
;
case
"INTRO"
:
case
'INTRO'
:
hasIntro
=
true
;
this
.
getTextDetail
(
'introduce'
,
item
.
mediaUrl
);
break
;
...
...
@@ -159,22 +166,22 @@ class AddLive extends React.Component {
}
return
item
;
})
});
const
addLiveBasicInfo
=
{
courseName
,
coverUrl
:
coverUrl
||
defaultCover
,
coverId
,
categoryId
,
categoryName
categoryName
,
};
const
liveDate
=
startTime
;
const
timeHorizonStart
=
startTime
;
const
timeHorizonEnd
=
endTime
;
const
assistant
=
_
.
pluck
(
admins
,
"adminId"
);
const
assistantStoreUserId
=
_
.
pluck
(
admins
,
"adminStoreUserId"
);
//编辑时的选中的助教的查询用storeUserId查询
const
assistantNames
=
_
.
pluck
(
admins
,
"adminName"
);
const
assistant
=
_
.
pluck
(
admins
,
'adminId'
);
const
assistantStoreUserId
=
_
.
pluck
(
admins
,
'adminStoreUserId'
);
//编辑时的选中的助教的查询用storeUserId查询
const
assistantNames
=
_
.
pluck
(
admins
,
'adminName'
);
const
addLiveClassInfo
=
{
assistant
,
liveDate
,
...
...
@@ -186,20 +193,19 @@ class AddLive extends React.Component {
startTime
,
endTime
,
assistantNames
,
assistantStoreUserId
}
assistantStoreUserId
,
};
const
addLiveIntroInfo
=
{
liveCourseWarmMedia
,
needRecord
,
whetherVisitorsJoin
,
liveCourseMediaRequests
,
}
};
// 晚于开课前30分钟
if
(
new
Date
().
getTime
()
>
startTime
-
1800000
)
{
isEdit
=
false
if
(
new
Date
().
getTime
()
>
startTime
-
1800000
)
{
isEdit
=
false
;
}
this
.
setState
({
loadintroduce
:
!
hasIntro
,
...
...
@@ -210,219 +216,210 @@ class AddLive extends React.Component {
addLiveClassInfo
,
addLiveBasicInfo
,
});
})
}
});
}
;
getTextDetail
=
(
key
,
url
)
=>
{
$
.
ajax
({
data
:
{},
type
:
'GET'
,
url
,
contentType
:
'application/x-www-form-urlencoded; charset=UTF-8'
,
contentType
:
'application/x-www-form-urlencoded; charset=UTF-8'
,
success
:
(
res
)
=>
{
this
.
setState
({
addLiveIntroInfo
:
{
...
this
.
state
.
addLiveIntroInfo
,
[
key
]:
res
},
[
`load
${
key
}
`
]:
true
});
},
error
:
()
=>
{
message
.
warning
(
'获取简介失败'
)
}
})
}
message
.
warning
(
'获取简介失败'
)
;
}
,
})
;
}
;
// 修改基本信息
// 修改基本信息
handleChangeBasicInfo
=
(
field
,
value
)
=>
{
// 修改基本信息
handleChangeBasicInfo
=
(
field
,
value
)
=>
{
const
{
coverUrl
}
=
this
.
state
.
addLiveBasicInfo
;
this
.
setState
({
addLiveBasicInfo
:
{
...
this
.
state
.
addLiveBasicInfo
,
[
field
]:
value
,
}
})
}
},
});
};
// 修改上课信息
handleChangeClassInfo
=
(
field
,
value
,
type
,
optionValue
)
=>
{
handleChangeClassInfo
=
(
field
,
value
,
type
,
optionValue
)
=>
{
const
_value
=
value
?
value
.
valueOf
()
:
null
;
const
{
teacherName
}
=
this
.
state
.
addLiveClassInfo
;
const
{
assistantNames
}
=
this
.
state
.
addLiveClassInfo
;
const
{
assistantStoreUserId
}
=
this
.
state
.
addLiveClassInfo
const
{
assistantStoreUserId
}
=
this
.
state
.
addLiveClassInfo
;
this
.
setState
({
addLiveClassInfo
:
{
...
this
.
state
.
addLiveClassInfo
,
[
field
]:
_value
,
teacherName
:
type
===
'teacherType'
?
optionValue
:
teacherName
,
assistantNames
:
type
===
'assistantType'
?
_
.
pluck
(
optionValue
,
"children"
):
assistantNames
,
assistantStoreUserId
:
type
===
'assistantType'
?
_
.
pluck
(
optionValue
,
"key"
):
assistantStoreUserId
,
}
teacherName
:
type
===
'teacherType'
?
optionValue
:
teacherName
,
assistantNames
:
type
===
'assistantType'
?
_
.
pluck
(
optionValue
,
'children'
)
:
assistantNames
,
assistantStoreUserId
:
type
===
'assistantType'
?
_
.
pluck
(
optionValue
,
'key'
)
:
assistantStoreUserId
,
}
,
});
}
}
;
// 修改简介
handleChangeIntroInfo
=
(
field
,
value
)
=>
{
this
.
setState
({
addLiveIntroInfo
:
{
...
this
.
state
.
addLiveIntroInfo
,
[
field
]:
value
}
})
}
[
field
]:
value
,
}
,
})
;
}
;
// 完成创建/编辑
handleSubmit
=
()
=>
{
//过期判断
if
(
User
.
getExpirationTime
()
&&
moment
().
valueOf
()
>
Number
(
User
.
getExpirationTime
()))
{
Modal
.
warning
({
title
:
"服务已到期"
,
content
:
"当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买"
,
okText
:
"我知道了"
})
return
title
:
'服务已到期'
,
content
:
'当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买'
,
okText
:
'我知道了'
,
})
;
return
;
}
const
{
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
id
,
isEdit
,
type
}
=
this
.
state
;
const
{
liveDate
,
timeHorizonStart
}
=
addLiveClassInfo
;
const
_liveDate
=
moment
(
liveDate
).
format
(
"YYYY-MM-DD"
);
const
{
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
id
,
isEdit
,
type
,
editorTextLength
}
=
this
.
state
;
const
{
liveDate
,
timeHorizonStart
}
=
addLiveClassInfo
;
const
_liveDate
=
moment
(
liveDate
).
format
(
'YYYY-MM-DD'
);
const
_timeHorizonStart
=
moment
(
timeHorizonStart
).
format
(
'HH:mm'
);
const
startTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonStart
).
format
(
'x'
);
if
(
type
===
'edit'
&&
isEdit
&&
new
Date
().
getTime
()
>
startTime
-
1800000
)
{
if
(
type
===
'edit'
&&
isEdit
&&
new
Date
().
getTime
()
>
startTime
-
1800000
)
{
Modal
.
info
({
title
:
"提示"
,
icon
:
(
<
span
className=
"icon iconfont default-confirm-icon"
>

</
span
>
),
content
:
"晚于开课前30分钟,部分信息不可修改"
,
title
:
'提示'
,
icon
:
<
span
className=
'icon iconfont default-confirm-icon'
>

</
span
>,
content
:
'晚于开课前30分钟,部分信息不可修改'
,
okText
:
'我知道了'
,
onOk
:
()
=>
{
this
.
getCourseDetail
();
}
}
,
});
return
return
;
}
this
.
handleValidate
(
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
isEdit
).
then
((
res
)
=>
{
this
.
handleValidate
(
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
isEdit
,
editorTextLength
).
then
((
res
)
=>
{
if
(
!
res
)
return
;
Upload
.
uploadTextToOSS
(
addLiveIntroInfo
.
introduce
,
`
${
randomString
()}
.txt`
,
(
introduceId
)
=>
{
Upload
.
uploadTextToOSS
(
addLiveIntroInfo
.
introduce
,
`
${
randomString
()}
.txt`
,
(
introduceId
)
=>
{
this
.
submitRemote
({
introduceId
,
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
id
});
},
()
=>
message
.
warning
(
'上传课程简介失败'
));
})
}
},
()
=>
message
.
warning
(
'上传课程简介失败'
)
);
});
};
submitRemote
=
({
introduceId
,
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
id
})
=>
{
const
{
type
}
=
this
.
state
;
const
{
courseName
,
coverUrl
,
coverId
,
categoryId
}
=
addLiveBasicInfo
;
const
{
liveDate
,
teacherId
,
assistant
,
timeHorizonEnd
,
timeHorizonStart
,
calendarTime
,
}
=
addLiveClassInfo
;
const
{
courseName
,
coverUrl
,
coverId
,
categoryId
}
=
addLiveBasicInfo
;
const
{
liveDate
,
teacherId
,
assistant
,
timeHorizonEnd
,
timeHorizonStart
,
calendarTime
}
=
addLiveClassInfo
;
let
{
startTime
,
endTime
}
=
addLiveClassInfo
;
const
{
needRecord
,
whetherVisitorsJoin
,
liveCourseWarmMedia
}
=
addLiveIntroInfo
;
const
{
needRecord
,
whetherVisitorsJoin
,
liveCourseWarmMedia
}
=
addLiveIntroInfo
;
if
(
type
===
'add'
)
{
if
(
type
===
'add'
)
{
startTime
=
startTime
;
endTime
=
endTime
;
}
else
{
const
_liveDate
=
moment
(
liveDate
).
format
(
"YYYY-MM-DD"
);
const
_liveDate
=
moment
(
liveDate
).
format
(
'YYYY-MM-DD'
);
const
_timeHorizonStart
=
moment
(
timeHorizonStart
).
format
(
'HH:mm'
);
const
_timeHorizonEnd
=
moment
(
timeHorizonEnd
).
format
(
'HH:mm'
);
startTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonStart
).
format
(
'x'
);
endTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonEnd
).
format
(
'x'
);
}
let
coverObj
=
{
contentType
:
'COVER'
,
mediaContent
:
coverId
,
mediaType
:
'PICTURE'
,
let
coverObj
=
{
contentType
:
'COVER'
,
mediaContent
:
coverId
,
mediaType
:
'PICTURE'
,
mediaUrl
:
coverUrl
,
}
}
;
let
scheduleMediaRequests
=
[];
if
(
coverId
)
{
scheduleMediaRequests
=
[
coverObj
,
...
scheduleMediaRequests
]
if
(
coverId
)
{
scheduleMediaRequests
=
[
coverObj
,
...
scheduleMediaRequests
];
}
if
(
liveCourseWarmMedia
&&
liveCourseWarmMedia
.
mediaUrl
)
{
scheduleMediaRequests
=
[
liveCourseWarmMedia
,
...
scheduleMediaRequests
]
if
(
liveCourseWarmMedia
&&
liveCourseWarmMedia
.
mediaUrl
)
{
scheduleMediaRequests
=
[
liveCourseWarmMedia
,
...
scheduleMediaRequests
];
}
const
commonParams
=
{
adminIds
:
assistant
,
adminIds
:
assistant
,
calendarTime
,
categoryId
,
endTime
,
needRecord
,
startTime
,
courseName
:
courseName
.
trim
(),
storeId
:
User
.
getStoreId
(),
teacherId
:
teacherId
,
storeId
:
User
.
getStoreId
(),
teacherId
:
teacherId
,
whetherVisitorsJoin
,
scheduleMediaRequests
}
scheduleMediaRequests
,
}
;
if
(
type
===
'add'
)
{
const
params
=
{
...
commonParams
,
operatorId
:
User
.
getUserId
(),
introduceId
,
}
}
;
CourseService
.
createLiveCloudCourse
(
params
).
then
((
res
)
=>
{
if
(
res
.
success
){
message
.
success
(
"新建成功"
);
if
(
res
.
success
)
{
message
.
success
(
'新建成功'
);
window
.
RCHistory
.
push
({
pathname
:
`/live-course`
,
});
}
});
}
else
{
const
params
=
{
...
commonParams
,
updateUserId
:
User
.
getUserId
(),
updateUserId
:
User
.
getUserId
(),
liveCourseId
:
id
,
introduceId
,
}
}
;
CourseService
.
updateLiveCloudCourse
(
params
).
then
((
res
)
=>
{
if
(
res
.
success
){
message
.
success
(
"更新成功"
);
if
(
res
.
success
)
{
message
.
success
(
'更新成功'
);
window
.
RCHistory
.
push
({
pathname
:
`/live-course`
,
});
}
});
}
}
}
;
handleValidate
=
(
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
isEdit
)
=>
{
handleValidate
=
(
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
isEdit
,
editorTextLength
)
=>
{
return
new
Promise
((
resolve
)
=>
{
const
{
type
}
=
this
.
state
;
const
{
courseName
,
categoryId
}
=
addLiveBasicInfo
;
const
{
liveDate
,
timeHorizonStart
,
timeHorizonEnd
,
teacherId
,
calendarTime
}
=
addLiveClassInfo
;
const
{
courseName
,
categoryId
}
=
addLiveBasicInfo
;
const
{
liveDate
,
timeHorizonStart
,
timeHorizonEnd
,
teacherId
,
calendarTime
}
=
addLiveClassInfo
;
const
{
liveCourseMediaRequests
}
=
addLiveIntroInfo
;
const
currentTime
=
+
new
Date
();
if
(
!
courseName
)
{
if
(
!
courseName
)
{
message
.
warning
(
'请输入课程名称'
);
resolve
(
false
);
return
;
}
if
(
!
categoryId
)
{
if
(
!
categoryId
)
{
message
.
warning
(
'请选择课程分类'
);
resolve
(
false
);
return
;
}
if
(
type
===
'add'
)
{
if
(
type
===
'add'
)
{
const
{
startTime
,
endTime
}
=
addLiveClassInfo
;
if
(
calendarTime
.
length
===
0
)
{
if
(
calendarTime
.
length
===
0
)
{
message
.
warning
(
'请选择上课日期'
);
resolve
(
false
);
return
;
}
else
if
(
startTime
===
endTime
||
startTime
>
endTime
)
{
}
else
if
(
startTime
===
endTime
||
startTime
>
endTime
)
{
message
.
warning
(
'结束时间必须晚于开始时间'
);
resolve
(
false
);
return
;
...
...
@@ -432,12 +429,12 @@ handleChangeBasicInfo = (field, value) => {
const
itemToday
=
_
.
find
(
calendarTime
,
(
item
)
=>
{
const
itemDay
=
moment
(
item
).
format
(
'YYYY-MM-DD'
);
return
itemDay
===
currentDay
;
})
if
(
itemToday
)
{
})
;
if
(
itemToday
)
{
const
itemDay
=
moment
(
itemToday
).
format
(
'YYYY-MM-DD'
);
const
itemHour
=
moment
(
startTime
).
format
(
'HH:mm'
);
if
(
itemDay
===
currentDay
)
{
if
(
moment
(
itemDay
+
' '
+
itemHour
).
format
(
'x'
)
<
currentTime
)
{
if
(
itemDay
===
currentDay
)
{
if
(
moment
(
itemDay
+
' '
+
itemHour
).
format
(
'x'
)
<
currentTime
)
{
message
.
warning
(
'开始时间不能早于现在'
);
resolve
(
false
);
return
;
...
...
@@ -445,13 +442,13 @@ handleChangeBasicInfo = (field, value) => {
}
}
}
else
{
const
_liveDate
=
moment
(
liveDate
).
format
(
"YYYY-MM-DD"
);
const
_liveDate
=
moment
(
liveDate
).
format
(
'YYYY-MM-DD'
);
const
_timeHorizonStart
=
moment
(
timeHorizonStart
).
format
(
'HH:mm'
);
const
_timeHorizonEnd
=
moment
(
timeHorizonEnd
).
format
(
'HH:mm'
);
const
startTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonStart
).
format
(
'x'
);
const
endTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonEnd
).
format
(
'x'
);
if
(
!
startTime
||
!
endTime
)
{
if
(
!
startTime
||
!
endTime
)
{
message
.
warning
(
'日期不能为空'
);
resolve
(
false
);
return
;
...
...
@@ -472,23 +469,29 @@ handleChangeBasicInfo = (field, value) => {
resolve
(
false
);
return
;
}
else
if
(
isEdit
&&
endTime
<=
startTime
)
{
message
.
warning
(
"结束时间不能早于开始时间"
);
message
.
warning
(
'结束时间不能早于开始时间'
);
resolve
(
false
);
return
;
}
}
if
(
!
teacherId
)
{
if
(
!
teacherId
)
{
message
.
warning
(
'请选择讲师'
);
resolve
(
false
);
return
;
}
resolve
(
true
)
});
if
(
editorTextLength
>
1000
)
{
message
.
warning
(
'课程简介超过字数限定'
);
resolve
(
false
);
return
;
}
resolve
(
true
);
});
};
// 显示预览课程弹窗
handleShowPreviewModal
=
()
=>
{
const
{
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
type
,
courseState
}
=
this
.
state
;
const
{
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
type
,
courseState
}
=
this
.
state
;
const
previewLiveCourseModal
=
(
<
PreviewCourseModal
courseBasicInfo=
{
addLiveBasicInfo
}
...
...
@@ -498,104 +501,77 @@ handleChangeBasicInfo = (field, value) => {
courseState=
{
courseState
}
close=
{
()
=>
{
this
.
setState
({
previewLiveCourseModal
:
null
})
previewLiveCourseModal
:
null
,
})
;
}
}
/>
);
this
.
setState
({
previewLiveCourseModal
});
}
}
;
// 取消编辑并返回上一级路由
handleGoBack
=
()
=>
{
// 比较state的addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo和默认数据是否相等
const
{
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
}
=
this
.
state
;
if
(
!
_
.
isEqual
(
addLiveBasicInfo
,
defaultBasicInfo
)
||
!
_
.
isEqual
(
addLiveClassInfo
,
defaultClassInfo
)
||
!
_
.
isEqual
(
addLiveIntroInfo
,
defaultIntroInfo
)
)
{
if
(
!
_
.
isEqual
(
addLiveBasicInfo
,
defaultBasicInfo
)
||
!
_
.
isEqual
(
addLiveClassInfo
,
defaultClassInfo
)
||
!
_
.
isEqual
(
addLiveIntroInfo
,
defaultIntroInfo
))
{
Modal
.
confirm
({
title
:
'确定要返回吗?'
,
content
:
'返回后,本次编辑的内容将不被保存'
,
okText
:
'确认返回'
,
cancelText
:
'留在本页'
,
icon
:
<
span
className=
"icon iconfont default-confirm-icon"
>

</
span
>,
icon
:
<
span
className=
'icon iconfont default-confirm-icon'
>

</
span
>,
onOk
:
()
=>
{
window
.
RCHistory
.
push
({
pathname
:
`/live-course`
,
});
}
})
}
,
})
;
}
else
{
window
.
RCHistory
.
push
({
pathname
:
`/live-course`
,
});
}
}
}
;
render
()
{
const
{
id
,
type
,
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
isEdit
,
loadintroduce
,
}
=
this
.
state
;
const
{
id
,
type
,
addLiveBasicInfo
,
addLiveClassInfo
,
addLiveIntroInfo
,
isEdit
,
loadintroduce
}
=
this
.
state
;
return
(
<
div
className=
"page add-live-page"
>
<
Breadcrumbs
navList=
{
type
==
"add"
?
"新建直播课"
:
"编辑直播课"
}
goBack=
{
this
.
handleGoBack
}
/>
<
div
className=
"box"
>
<
div
className=
"show-tips"
>
<
ShowTips
message=
"请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企学院保有依据国家规定及平台规则进行处理的权利"
/>
<
div
className=
'page add-live-page'
>
<
Breadcrumbs
navList=
{
type
==
'add'
?
'新建直播课'
:
'编辑直播课'
}
goBack=
{
this
.
handleGoBack
}
/>
<
div
className=
'box'
>
<
div
className=
'show-tips'
>
<
ShowTips
message=
'请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企学院保有依据国家规定及平台规则进行处理的权利'
/>
</
div
>
<
div
className=
"add-live-page__form"
>
<
div
className=
"basic-info__wrap"
>
<
div
className=
"title"
>
基本信息
</
div
>
<
AddLiveBasic
isEdit=
{
isEdit
}
pageType=
{
type
}
data=
{
addLiveBasicInfo
}
onChange=
{
this
.
handleChangeBasicInfo
}
/>
<
div
className=
'add-live-page__form'
>
<
div
className=
'basic-info__wrap'
>
<
div
className=
'title'
>
基本信息
</
div
>
<
AddLiveBasic
isEdit=
{
isEdit
}
pageType=
{
type
}
data=
{
addLiveBasicInfo
}
onChange=
{
this
.
handleChangeBasicInfo
}
/>
</
div
>
<
div
className=
"class-info__wrap"
>
<
div
className=
"title"
>
上课信息
</
div
>
<
AddLiveClass
isEdit=
{
isEdit
}
pageType=
{
type
}
data=
{
{...
addLiveClassInfo
,
id
}
}
onChange=
{
this
.
handleChangeClassInfo
}
/>
<
div
className=
'class-info__wrap'
>
<
div
className=
'title'
>
上课信息
</
div
>
<
AddLiveClass
isEdit=
{
isEdit
}
pageType=
{
type
}
data=
{
{
...
addLiveClassInfo
,
id
}
}
onChange=
{
this
.
handleChangeClassInfo
}
/>
</
div
>
<
div
className=
"intro-info__wrap"
>
<
div
className=
"title"
>
更多信息
</
div
>
<
AddLiveIntro
isEdit=
{
isEdit
}
data=
{
{
...
addLiveIntroInfo
,
loadintroduce
,
id
}
}
onChange=
{
this
.
handleChangeIntroInfo
}
/>
<
div
className=
'intro-info__wrap'
>
<
div
className=
'title'
>
更多信息
</
div
>
<
AddLiveIntro
isEdit=
{
isEdit
}
data=
{
{
...
addLiveIntroInfo
,
loadintroduce
,
id
}
}
onChange=
{
this
.
handleChangeIntroInfo
}
/>
</
div
>
</
div
>
</
div
>
<
div
className=
"footer shrink-footer"
>
<
div
className=
'footer shrink-footer'
>
<
Button
onClick=
{
this
.
handleGoBack
}
>
取消
</
Button
>
<
Button
onClick=
{
this
.
handleShowPreviewModal
}
>
预览
</
Button
>
<
Button
type=
"primary"
onClick=
{
_
.
debounce
(()
=>
this
.
handleSubmit
(),
3000
,
true
)
}
>
保存
</
Button
>
<
Button
type=
'primary'
onClick=
{
_
.
debounce
(()
=>
this
.
handleSubmit
(),
3000
,
true
)
}
>
保存
</
Button
>
</
div
>
{
this
.
state
.
previewLiveCourseModal
}
{
this
.
state
.
lackConsumeStudentModal
}
{
this
.
state
.
previewLiveCourseModal
}
{
this
.
state
.
lackConsumeStudentModal
}
</
div
>
)
)
;
}
}
...
...
src/modules/course-manage/components/AddLiveBasic.jsx
View file @
aef0f0eb
...
...
@@ -7,10 +7,10 @@
*/
import
React
from
'react'
;
import
{
Input
,
Button
,
message
,
Cascader
,
Modal
}
from
'antd'
;
import
UploadOss
from
"@/core/upload"
;
import
{
Input
,
Button
,
message
,
Cascader
,
Modal
}
from
'antd'
;
import
UploadOss
from
'@/core/upload'
;
import
{
ImgCutModalNew
}
from
'@/components'
;
import
StoreService
from
"@/domains/store-domain/storeService"
;
import
StoreService
from
'@/domains/store-domain/storeService'
;
import
SelectPrepareFileModal
from
'@/modules/prepare-lesson/modal/SelectPrepareFileModal'
;
import
Upload
from
'@/core/upload'
;
import
Cropper
from
'react-cropper'
;
...
...
@@ -21,63 +21,62 @@ import './AddLiveBasic.less';
const
defaultCover
=
'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png'
;
const
fieldNames
=
{
label
:
'categoryName'
,
value
:
'id'
,
children
:
'sonCategoryList'
};
let
cutFlag
=
false
;
let
timer
=
null
let
timer
=
null
;
class
AddLiveBasic
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
imageFile
:
null
,
showCutModal
:
false
,
courseCatalogList
:[],
courseCatalogList
:
[],
showSelectFileModal
:
false
,
cutImageBlob
:
null
,
hasImgReady
:
false
,
// 图片是否上传成功
cropperInstace
:
null
}
}
componentWillUnmount
()
{
}
componentWillUnmount
()
{}
componentDidMount
(){
componentDidMount
()
{
this
.
getCourseCatalogList
();
}
getCourseCatalogList
=
()
=>
{
StoreService
.
getCourseCatalogList
({
current
:
1
,
size
:
1000
}).
then
((
res
)
=>
{
getCourseCatalogList
=
()
=>
{
StoreService
.
getCourseCatalogList
({
current
:
1
,
size
:
1000
}).
then
((
res
)
=>
{
this
.
setState
({
courseCatalogList
:
res
.
result
.
records
})
courseCatalogList
:
res
.
result
.
records
,
});
});
}
// 使用默认封面图
handleResetCoverUrl
=
()
=>
{
const
{
data
:
{
coverUrl
}
}
=
this
.
props
;
const
{
data
:
{
coverUrl
},
}
=
this
.
props
;
const
isDefaultCover
=
coverUrl
===
defaultCover
;
// 如果已经是默认图的话,不做任何任何处理
if
(
isDefaultCover
)
return
;
message
.
success
(
'已替换为默认图'
);
this
.
props
.
onChange
(
'coverUrl'
,
defaultCover
);
setTimeout
(()
=>
{
this
.
props
.
onChange
(
'coverUrl'
,
defaultCover
);
setTimeout
(()
=>
{
this
.
props
.
onChange
(
'coverId'
,
null
);
},
1000
)
}
},
1000
);
}
;
catalogChange
=
(
value
)
=>
{
catalogChange
=
(
value
)
=>
{
const
changeValueLength
=
value
.
length
;
switch
(
changeValueLength
){
switch
(
changeValueLength
)
{
case
1
:
this
.
props
.
onChange
(
'categoryId'
,
value
[
0
]);
this
.
props
.
onChange
(
'categoryId'
,
value
[
0
]);
break
;
case
2
:
this
.
props
.
onChange
(
'categoryId'
,
value
[
1
]);
this
.
props
.
onChange
(
'categoryId'
,
value
[
1
]);
break
;
default
:
this
.
props
.
onChange
(
'categoryId'
,
null
);
this
.
props
.
onChange
(
'categoryId'
,
null
);
break
;
}
}
}
;
handleSelectCover
=
(
file
)
=>
{
this
.
setState
({
visible
:
true
,
...
...
@@ -90,91 +89,122 @@ class AddLiveBasic extends React.Component {
//获取resourceId
getSignature
=
(
blob
,
fileName
)
=>
{
const
{
choosedBannerId
}
=
this
.
state
;
Upload
.
uploadBlobToOSS
(
blob
,
'cover'
+
(
new
Date
()).
valueOf
(),
null
,
'signInfo'
).
then
((
signInfo
)
=>
{
this
.
setState
({
coverClicpPath
:
signInfo
.
fileUrl
,
coverId
:
signInfo
.
resourceId
,
visible
:
false
},()
=>
this
.
updateCover
())
Upload
.
uploadBlobToOSS
(
blob
,
'cover'
+
new
Date
().
valueOf
(),
null
,
'signInfo'
).
then
((
signInfo
)
=>
{
this
.
setState
(
{
coverClicpPath
:
signInfo
.
fileUrl
,
coverId
:
signInfo
.
resourceId
,
visible
:
false
,
},
()
=>
this
.
updateCover
()
);
});
};
updateCover
=
()
=>
{
const
{
coverClicpPath
,
coverId
}
=
this
.
state
this
.
setState
({
showSelectFileModal
:
false
})
showSelectFileModal
:
false
,
})
;
this
.
props
.
onChange
(
'coverUrl'
,
coverClicpPath
);
setTimeout
(()
=>
{
setTimeout
(()
=>
{
this
.
props
.
onChange
(
'coverId'
,
coverId
);
},
1000
)
}
},
1000
);
};
render
()
{
const
{
showCutModal
,
imageFile
,
courseCatalogList
,
showSelectFileModal
,
visible
,
cutImageBlob
,
hasImgReady
}
=
this
.
state
;
const
{
data
,
pageType
,
isEdit
}
=
this
.
props
;
const
{
courseName
,
categoryName
,
coverUrl
}
=
data
;
const
{
showCutModal
,
imageFile
,
courseCatalogList
,
showSelectFileModal
,
visible
,
cutImageBlob
,
hasImgReady
}
=
this
.
state
;
const
{
data
,
pageType
,
isEdit
}
=
this
.
props
;
const
{
courseName
,
categoryName
,
coverUrl
}
=
data
;
const
fileName
=
''
;
// 当前是否使用的是默认图片
const
isDefaultCover
=
coverUrl
===
defaultCover
;
return
(
<
div
className=
"add-live__basic-info"
>
<
div
className=
"course-name"
>
<
span
className=
"label"
><
span
className=
"require"
>
*
</
span
>
课程名称:
</
span
>
<
div
className=
'add-live__basic-info'
>
<
div
className=
'course-name'
>
<
span
className=
'label'
>
<
span
className=
'require'
>
*
</
span
>
课程名称:
</
span
>
<
Input
value=
{
courseName
}
placeholder=
"请输入直播名称(40字以内)"
placeholder=
'请输入直播名称(40字以内)'
maxLength=
{
40
}
style=
{
{
width
:
240
}
}
onChange=
{
(
e
)
=>
{
this
.
props
.
onChange
(
'courseName'
,
e
.
target
.
value
)}
}
onChange=
{
(
e
)
=>
{
this
.
props
.
onChange
(
'courseName'
,
e
.
target
.
value
);
}
}
/>
</
div
>
<
div
className=
"course-cover"
>
<
span
className=
"label"
>
封面图:
</
span
>
<
div
className=
'course-cover'
>
<
span
className=
'label'
>
封面图:
</
span
>
<
div
className=
"course-cover__wrap"
>
<
div
className=
"img-content"
>
{
isDefaultCover
&&
<
span
className=
"tag"
>
默认图
</
span
>
}
<
img
src=
{
coverUrl
}
/>
</
div
>
<
div
className=
"opt-btns"
>
<
Button
onClick=
{
()
=>
{
<
div
className=
'course-cover__wrap'
>
<
div
className=
'opt-btns'
>
<
Button
onClick=
{
()
=>
{
this
.
setState
({
showSelectFileModal
:
true
})
}
}
>
上传图片
</
Button
>
<
span
className=
{
`default-btn ${isDefaultCover ? 'disabled' : ''}`
}
onClick=
{
this
.
handleResetCoverUrl
}
>
使用默认图
</
span
>
<
div
className=
"tips"
>
建议尺寸1280*720px,图片支持jpg、jpeg、png格式。
</
div
>
showSelectFileModal
:
true
,
});
}
}
>
上传图片
</
Button
>
<
span
className=
{
`default-btn ${isDefaultCover ? 'disabled' : ''}`
}
onClick=
{
this
.
handleResetCoverUrl
}
>
使用默认图
</
span
>
<
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
}
/>
</
div
>
</
div
>
</
div
>
<
div
className=
"course-catalog"
>
<
span
className=
"label"
><
span
className=
"require"
>
*
</
span
>
课程分类:
</
span
>
{
pageType
===
'add'
&&
<
Cascader
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'
}
}
>

</
span
>
}
/>
<
div
className=
'course-catalog'
>
<
span
className=
'label'
>
<
span
className=
'require'
>
*
</
span
>
课程分类:
</
span
>
{
pageType
===
'add'
&&
(
<
Cascader
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'
}
}
>

</
span
>
}
{
(
pageType
===
'edit'
&&
categoryName
)
&&
<
Cascader
disabled=
{
!
isEdit
?
true
:
false
}
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'
}
}
>

</
span
>
}
/>
/>
)
}
{
pageType
===
'edit'
&&
categoryName
&&
(
<
Cascader
disabled=
{
!
isEdit
?
true
:
false
}
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'
}
}
>

</
span
>
}
/>
)
}
</
div
>
{
showSelectFileModal
&&
<
SelectPrepareFileModal
key=
"basic"
operateType=
"select"
key=
'basic'
operateType=
'select'
multiple=
{
false
}
accept=
"image/jpeg,image/png,image/jpg"
accept=
'image/jpeg,image/png,image/jpg'
selectTypeList=
{
[
'JPG'
,
'JPEG'
,
'PNG'
]
}
tooltip=
'支持文件类型:jpg、jpeg、png'
isOpen=
{
showSelectFileModal
}
onClose=
{
()
=>
{
this
.
setState
({
showSelectFileModal
:
false
})
this
.
setState
({
showSelectFileModal
:
false
})
;
}
}
onSelect=
{
this
.
handleSelectCover
}
/>
...
...
@@ -183,7 +213,7 @@ class AddLiveBasic extends React.Component {
<
ImgClipModal
visible=
{
visible
}
imgUrl=
{
imageFile
.
ossUrl
}
onConfirm=
{
this
.
getSignature
}
onClose=
{
()
=>
{
this
.
setState
({
visible
:
false
});}
}
/>
}
</
div
>
)
)
;
}
}
...
...
src/modules/course-manage/components/AddLiveBasic.less
View file @
aef0f0eb
...
...
@@ -2,41 +2,25 @@
.label {
width: 100px;
text-align: right;
display:inline-block;
display:
inline-block;
.require {
color: #
EC4B
35;
color: #
ec4b
35;
}
}
.course-cover {
margin-left: 14px;
display: flex;
margin-top:
16
px;
margin-top:
24
px;
&__wrap {
position: relative;
.tag {
border-radius: 2px;
background: #D6D6D6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
}
.course-cover__wrap {
display: flex;
flex-direction: row;
}
.img-content {
margin-top: 8px;
margin-right: 20px;
width: 299px;
height: 169px;
position: relative;
img {
width: 100%;
...
...
@@ -44,17 +28,28 @@
object-fit: contain;
border-radius: 4px;
}
.tag {
border-radius: 2px;
background: #d6d6d6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #fff;
position: absolute;
top: 8px;
left: 8px;
}
}
.opt-btns {
.default-btn {
margin-left: 16
px;
color: #2966FF
;
margin-left: 14
px;
color: #2966ff
;
cursor: pointer;
&.disabled {
color: #CCC
;
color: #ccc
;
cursor: not-allowed;
}
}
...
...
@@ -62,21 +57,21 @@
.ant-upload-list {
display: none;
}
}
.tips {
margin-top: 8px;
color: #999;
}
}
.course-catalog{
margin:20px 0 0 14px;
}
}
.course-catalog {
margin: 20px 0 0 14px;
}
}
.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled){
font-weight:normal !important;
color:
#2966FF
!important;
.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled)
{
font-weight:
normal !important;
color:
#2966ff
!important;
}
#imgCutModalNew {
width: 500px;
...
...
src/modules/course-manage/components/AddLiveIntro.jsx
View file @
aef0f0eb
...
...
@@ -17,19 +17,17 @@ import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepare
import
{
DISK_MAP
}
from
'@/common/constants/academic/lessonEnum'
;
import
{
ImgCutModalNew
}
from
'@/components'
;
const
{
TextArea
}
=
Input
;
const
defaultCover
=
'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1599635741526.png'
;
class
AddLiveIntro
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
warmUrl
:
defaultCover
,
showSelectFileModal
:
false
,
diskList
:
[],
selectType
:
null
}
selectType
:
null
,
}
;
}
// 上传封面图
...
...
@@ -40,34 +38,34 @@ class AddLiveIntro extends React.Component {
imageFile
,
showCutModal
:
true
,
});
}
}
;
// 选择暖场资源
handleSelectVideo
=
(
file
)
=>
{
const
{
selectType
}
=
this
.
state
;
this
.
setState
({
showSelectFileModal
:
false
})
showSelectFileModal
:
false
,
})
;
const
{
ossUrl
,
resourceId
,
folderName
,
folderFormat
,
folderSize
}
=
file
;
if
(
selectType
===
'WARMUP'
)
{
if
(
selectType
===
'WARMUP'
)
{
const
liveCourseWarmMedia
=
{
contentType
:
'WARMUP'
,
mediaType
:
folderFormat
===
'MP4'
||
folderFormat
===
'video/mp4'
?
'VIDEO'
:
'PICTURE'
,
mediaContent
:
resourceId
,
mediaUrl
:
ossUrl
,
mediaName
:
folderName
,
size
:
folderSize
}
size
:
folderSize
,
}
;
this
.
props
.
onChange
(
'liveCourseWarmMedia'
,
liveCourseWarmMedia
);
}
else
{
}
else
{
// 最多添加九图片
const
{
liveCourseMediaRequests
}
=
this
.
props
.
data
;
const
list
=
_
.
filter
(
liveCourseMediaRequests
,
(
item
)
=>
{
return
item
.
mediaType
==
"PICTURE"
;
return
item
.
mediaType
==
'PICTURE'
;
});
if
(
list
.
length
>
8
)
{
message
.
warning
(
"最多添加9张图片"
);
message
.
warning
(
'最多添加9张图片'
);
return
;
}
liveCourseMediaRequests
.
push
({
...
...
@@ -80,146 +78,161 @@ class AddLiveIntro extends React.Component {
});
this
.
props
.
onChange
(
'liveCourseMediaRequests'
,
liveCourseMediaRequests
);
}
}
};
changeIntro
=
(
value
)
=>
{
this
.
props
.
onChange
(
'introduce'
,
value
);
}
}
;
whetherVisitorsJoinChange
=
()
=>
{
if
(
this
.
props
.
data
.
whetherVisitorsJoin
===
"NO"
){
this
.
props
.
onChange
(
'whetherVisitorsJoin'
,
'YES'
)
}
else
{
this
.
props
.
onChange
(
'whetherVisitorsJoin'
,
'NO'
)
}
whetherVisitorsJoinChange
=
()
=>
{
if
(
this
.
props
.
data
.
whetherVisitorsJoin
===
'NO'
)
{
this
.
props
.
onChange
(
'whetherVisitorsJoin'
,
'YES'
);
}
else
{
this
.
props
.
onChange
(
'whetherVisitorsJoin'
,
'NO'
);
}
};
render
()
{
const
{
liveType
,
isXiaomai
,
isEdit
,
data
:
{
id
,
introduce
,
needRecord
,
whetherVisitorsJoin
,
loadintroduce
,
liveCourseWarmMedia
=
{}
}
}
=
this
.
props
;
const
{
showCutModal
,
warmUrl
,
showSelectFileModal
,
diskList
,
imageFile
,
selectType
}
=
this
.
state
const
{
liveType
,
isXiaomai
,
isEdit
,
data
:
{
id
,
introduce
,
needRecord
,
whetherVisitorsJoin
,
loadintroduce
,
liveCourseWarmMedia
=
{}
},
}
=
this
.
props
;
const
{
showCutModal
,
warmUrl
,
showSelectFileModal
,
diskList
,
imageFile
,
selectType
}
=
this
.
state
;
return
(
<
div
className=
"add-live__intro-info"
>
<
div
className=
"playback"
>
<
span
className=
"label"
><
span
className=
"require"
>
*
</
span
>
直播回放:
</
span
>
<
div
className=
"content"
>
<
Radio
.
Group
value=
{
needRecord
}
onChange=
{
(
e
)
=>
{
this
.
props
.
onChange
(
'needRecord'
,
e
.
target
.
value
)
}
}
disabled=
{
!
isEdit
?
true
:
false
}
>
<
div
className=
'add-live__intro-info'
>
<
div
className=
'playback'
>
<
span
className=
'label'
>
<
span
className=
'require'
>
*
</
span
>
直播回放:
</
span
>
<
div
className=
'content'
>
<
Radio
.
Group
value=
{
needRecord
}
onChange=
{
(
e
)
=>
{
this
.
props
.
onChange
(
'needRecord'
,
e
.
target
.
value
);
}
}
disabled=
{
!
isEdit
?
true
:
false
}
>
<
Row
style=
{
{
marginBottom
:
'5px'
}
}
>
<
Col
span=
{
24
}
>
<
Radio
value=
"YES"
>
<
Radio
value=
'YES'
>
自动录制
<
span
className=
"playback__text"
>
系统自助进行全程直播录制
</
span
>
<
span
className=
'playback__text'
>
系统自助进行全程直播录制
</
span
>
</
Radio
>
</
Col
>
</
Row
>
<
Row
>
<
Col
span=
{
24
}
>
<
Radio
value=
"NO"
>
<
Radio
value=
'NO'
>
手动录制
<
span
className=
"playback__text"
>
讲师手动选择何时开始录制
</
span
>
<
span
className=
'playback__text'
>
讲师手动选择何时开始录制
</
span
>
</
Radio
>
</
Col
>
</
Row
>
</
Radio
.
Group
>
</
div
>
</
div
>
<
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
class=
"instro-text"
>
<
div
>
开启:允许未绑定手机号的学员进入直播间观看直播
</
div
>
<
div
>
关闭:仅限绑定了手机号的学员可以进入直播间观看直播
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
"warmup"
>
<
span
className=
"label"
>
直播暖场图:
</
span
>
<
div
className=
"course-cover__wrap"
>
<
div
className=
"img-content"
style=
{
liveCourseWarmMedia
.
mediaUrl
?
{
background
:
'#000'
}
:
{}
}
>
<
img
src=
{
liveCourseWarmMedia
.
mediaType
===
'VIDEO'
?
`${liveCourseWarmMedia.mediaUrl}?x-oss-process=video/snapshot,t_0,m_fast`
:
(
liveCourseWarmMedia
.
mediaUrl
?
liveCourseWarmMedia
.
mediaUrl
:
defaultCover
)
}
/>
{
liveCourseWarmMedia
.
mediaUrl
&&
<
div
className=
"img-delete-wrap"
>
<
img
src=
"https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1600073872956.png"
onClick=
{
()
=>
{
<
div
className=
'allow-tourist-join'
>
<
span
className=
'label'
>
观看设置:
</
span
>
<
div
className=
'content'
>
<
Switch
checked=
{
whetherVisitorsJoin
===
'NO'
?
true
:
false
}
onChange=
{
this
.
whetherVisitorsJoinChange
}
/>
<
div
class=
'instro-text'
>
{
whetherVisitorsJoin
===
'NO'
?
'已开启,学员需绑定手机号才可观看'
:
'已关闭,学员无需绑定手机号即可观看'
}
</
div
>
</
div
>
</
div
>
<
div
className=
'warmup'
>
<
span
className=
'label'
>
直播暖场图:
</
span
>
<
div
className=
'course-cover__wrap'
>
<
div
className=
'img-content'
style=
{
liveCourseWarmMedia
.
mediaUrl
?
{
background
:
'#000'
}
:
{}
}
>
<
img
src=
{
liveCourseWarmMedia
.
mediaType
===
'VIDEO'
?
`${liveCourseWarmMedia.mediaUrl}?x-oss-process=video/snapshot,t_0,m_fast`
:
liveCourseWarmMedia
.
mediaUrl
?
liveCourseWarmMedia
.
mediaUrl
:
defaultCover
}
/>
{
liveCourseWarmMedia
.
mediaUrl
&&
(
<
div
className=
'img-delete-wrap'
>
<
img
src=
'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1600073872956.png'
onClick=
{
()
=>
{
this
.
props
.
onChange
(
'liveCourseWarmMedia'
,
{});
}
}
/>
}
}
/>
</
div
>
}
)
}
</
div
>
<
div
className=
"opt-btns"
>
<
div
className=
'opt-btns'
>
<
Button
disabled=
{
!
isEdit
}
onClick=
{
()
=>
{
this
.
setState
({
showSelectFileModal
:
true
,
selectType
:
'WARMUP'
})
}
}
>
上传图片/视频
</
Button
>
selectType
:
'WARMUP'
,
});
}
}
>
上传图片/视频
</
Button
>
<
div
className=
"tips"
>
<
div
className=
'tips'
>
<
div
>
建议尺寸1280*720px或16:9。图片最大5M,支持jpg、jpeg和png;视频最大2G,
</
div
>
<
div
>
支持mp4。
</
div
>
</
div
>
<
Popover
content=
{
<
div
className=
"example-wrap"
>
<
p
className=
"title"
>
直播间暖场图示例
</
p
>
<
p
className=
"text"
>
直播开始前,展示在直播间视频区域
</
p
>
<
Popover
content=
{
<
div
className=
'example-wrap'
>
<
p
className=
'title'
>
直播间暖场图示例
</
p
>
<
p
className=
'text'
>
直播开始前,展示在直播间视频区域
</
p
>
<
img
src=
'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1599644482652.png'
></
img
>
</
div
>
}
>
<
div
className=
"checkExample"
>
查看示例
</
div
>
<
div
className=
'checkExample'
>
查看示例
</
div
>
</
Popover
>
</
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
||
loadintroduce
)
&&
<
div
className=
'introduce'
>
<
span
className=
'label'
>
课程简介:
</
span
>
<
div
className=
'content'
>
<
div
className=
'intro-list'
>
<
div
className=
'intro-list__item introduce-editor'
>
{
(
!
id
||
loadintroduce
)
&&
(
<
GraphicsEditor
id=
"intro"
id=
'intro'
isIntro=
{
true
}
maxLimit=
{
1000
}
detail=
{
{
content
:
introduce
content
:
introduce
,
}
}
onChange=
{
(
val
)
=>
{
this
.
changeIntro
(
val
);
}
}
onChange=
{
(
val
)
=>
{
this
.
changeIntro
(
val
)
}
}
/>
}
)
}
</
div
>
</
div
>
</
div
>
</
div
>
{
/* 选择暖场图文件弹窗 */
}
{
showSelectFileModal
&&
{
showSelectFileModal
&&
(
<
SelectPrepareFileModal
key=
"instro"
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'
}
key=
'instro'
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
})
this
.
setState
({
showSelectFileModal
:
false
});
}
}
onSelect=
{
this
.
handleSelectVideo
}
/>
}
)
}
</
div
>
)
)
;
}
}
...
...
src/modules/course-manage/components/GraphicsEditor.jsx
View file @
aef0f0eb
/*
* @Author: yuananting
* @Date: 2021-07-05 10:47:19
* @LastEditors: yuananting
* @LastEditTime: 2021-07-12 17:13:38
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
{
message
}
from
'antd'
;
import
React
from
'react'
;
import
E
from
'wangeditor'
;
...
...
@@ -9,25 +18,23 @@ const { BtnMenu } = E;
class
GraphicsEditor
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
)
super
(
props
)
;
this
.
state
=
{
editorId
:
window
.
random_string
(
16
),
textLength
:
0
,
showSelectImageModal
:
false
,
showSelectVideoModal
:
false
,
diskList
:
[],
}
}
;
this
.
editorInt
=
null
;
}
componentDidMount
()
{
this
.
renderEditor
()
this
.
renderEditor
()
;
this
.
resetIndex
(
true
);
this
.
initBus
();
}
componentWillUnmount
()
{
this
.
resetIndex
();
this
.
removeBus
();
...
...
@@ -38,11 +45,11 @@ class GraphicsEditor extends React.Component {
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
,
maxLimit
}
=
this
.
props
;
const
{
detail
,
onChange
,
isIntro
,
maxLimit
,
editorType
}
=
this
.
props
;
class
ImageMenu
extends
BtnMenu
{
constructor
(
editor
)
{
// data-title属性表示当鼠标悬停在该按钮上时提示该按钮的功能简述
...
...
@@ -50,17 +57,15 @@ class GraphicsEditor extends React.Component {
`<div class="w-e-menu" data-title="图片">
<i class="w-e-icon-image"></i>
</div>`
)
super
(
$elem
,
editor
)
)
;
super
(
$elem
,
editor
)
;
}
// 菜单点击事件
clickHandler
()
{
Bus
.
trigger
(
`graphicsEditorImage
${
isIntro
?
''
:
'Content'
}
`
)
Bus
.
trigger
(
`graphicsEditorImage
${
isIntro
?
''
:
'Content'
}
`
)
;
}
tryChangeActive
()
{
}
tryChangeActive
()
{}
}
class
VideoMenu
extends
BtnMenu
{
...
...
@@ -70,40 +75,24 @@ class GraphicsEditor extends React.Component {
`<div class="w-e-menu" data-title="视频">
<i class="w-e-icon-play"></i>
</div>`
)
super
(
$elem
,
editor
)
)
;
super
(
$elem
,
editor
)
;
}
// 菜单点击事件
clickHandler
()
{
Bus
.
trigger
(
'graphicsEditorVideo'
)
Bus
.
trigger
(
'graphicsEditorVideo'
)
;
}
tryChangeActive
()
{
}
tryChangeActive
()
{}
}
this
.
editorInt
=
new
E
(
`#editor
${
editorId
}
`
);
this
.
editorInt
.
config
.
focus
=
false
;
this
.
editorInt
.
config
.
showFullScreen
=
!
isIntro
this
.
editorInt
.
config
.
showFullScreen
=
!
isIntro
;
this
.
editorInt
.
menus
.
extend
(
'xmimage'
,
ImageMenu
);
!
isIntro
&&
this
.
editorInt
.
menus
.
extend
(
'xmvideo'
,
VideoMenu
);
this
.
editorInt
.
config
.
menus
=
isIntro
?
[
'head'
,
'bold'
,
'fontSize'
,
'fontName'
,
'italic'
,
'underline'
,
'strikeThrough'
,
'foreColor'
,
'backColor'
,
'list'
,
'justify'
,
'emoticon'
,
'xmimage'
,
]
this
.
editorInt
.
config
.
menus
=
isIntro
?
[
'head'
,
'bold'
,
'fontSize'
,
'fontName'
,
'italic'
,
'underline'
,
'strikeThrough'
,
'foreColor'
,
'backColor'
,
'list'
,
'justify'
,
'emoticon'
,
'xmimage'
]
:
[
'head'
,
'bold'
,
...
...
@@ -134,33 +123,41 @@ class GraphicsEditor extends React.Component {
{
title
:
'emoji'
,
type
:
'emoji'
,
content
:
[
'😀'
,
'😃'
,
'😄'
,
'😁'
,
'😆'
,
'😅'
,
'😂'
,
'😊'
,
'🙂'
,
'🙃'
,
'😉'
,
'😓'
,
'😅'
,
'😪'
,
'🤔'
,
'😬'
,
'🤐'
]
}
]
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
.
pasteTextHandle
=
(
content
)
=>
{
if
(
content
==
''
&&
!
content
)
return
''
;
var
str
=
content
;
// 保留空格和换行的其他字符
str
=
str
.
replace
(
/<xml>
[\s\S]
*
?
<
\/
xml>/gi
,
''
);
str
=
str
.
replace
(
/<style>
[\s\S]
*
?
<
\/
style>/gi
,
''
);
str
=
str
.
replace
(
/<
\/?
a.*
?
>/g
,
''
);
return
str
;
};
this
.
editorInt
.
config
.
onchange
=
(
html
)
=>
{
var
str
=
this
.
editorInt
.
txt
.
text
();
// 去除所有特殊字符
str
=
str
.
replace
(
/<xml>
[\s\S]
*
?
<
\/
xml>/gi
,
''
);
str
=
str
.
replace
(
/<style>
[\s\S]
*
?
<
\/
style>/gi
,
''
);
str
=
str
.
replace
(
/<
\/?[^
>
]
*>/g
,
''
);
str
=
str
.
replace
(
/
[
|
]
*
\n
/g
,
'
\
n'
);
str
=
str
.
replace
(
/
\&
nbsp
\;
/gi
,
' '
);
str
=
str
.
replace
(
/
[\r\n]
/g
,
''
);
str
=
str
.
replace
(
/<
\/?
a.*
?
>/g
,
''
);
const
videoCount
=
((
html
||
''
).
match
(
/<iframe/g
)
||
[]).
length
;
const
imageCount
=
((
html
||
''
).
match
(
/<img/g
)
||
[]).
length
;
const
textLength
=
this
.
editorInt
.
txt
.
text
().
replace
(
/
\&
nbsp
\;
/ig
,
' '
)
.
length
+
videoCount
+
imageCount
;
const
textLength
=
str
.
length
+
videoCount
+
imageCount
;
this
.
setState
({
textLength
},
()
=>
{
if
(
textLength
>
maxLimit
)
{
// message.warning('超过字数限定'
);
message
.
warning
(
`内容过长,不能超过
${
maxLimit
}
字`
);
}
Bus
.
trigger
(
'editorLimit'
,
textLength
,
editorType
);
onChange
(
html
,
this
.
state
.
textLength
);
})
}
})
;
}
;
this
.
editorInt
.
create
();
this
.
editorInt
.
txt
.
html
(
detail
.
content
);
}
...
...
@@ -170,59 +167,64 @@ class GraphicsEditor extends React.Component {
const
{
ossUrl
}
=
file
||
{};
if
(
!
ossUrl
)
return
null
;
this
.
setState
({
showSelectVideoModal
:
false
})
showSelectVideoModal
:
false
,
})
;
const
{
detail
}
=
this
.
props
;
this
.
editorInt
&&
this
.
editorInt
.
txt
.
html
(
`
${
detail
.
content
}
<p style="width: 100%;padding-top: 56.25%;position: relative;"><iframe style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;" src="
${
ossUrl
}
"></iframe><br/></p><p><br/></p>`
)
}
this
.
editorInt
&&
this
.
editorInt
.
txt
.
html
(
`
${
detail
.
content
}
<p style="width: 100%;padding-top: 56.25%;position: relative;"><iframe style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;" src="
${
ossUrl
}
"></iframe><br/></p><p><br/></p>`
);
};
handleSelectImage
=
(
file
)
=>
{
const
{
ossUrl
}
=
file
||
{};
if
(
!
ossUrl
)
return
null
;
this
.
setState
({
showSelectImageModal
:
false
})
showSelectImageModal
:
false
,
})
;
const
{
detail
}
=
this
.
props
;
this
.
editorInt
&&
this
.
editorInt
.
txt
.
html
(
`
${
detail
.
content
}
<p><img style="max-width: 100%;" src="
${
ossUrl
}
" /><br/><p>`
)
}
this
.
editorInt
&&
this
.
editorInt
.
txt
.
html
(
`
${
detail
.
content
}
<p><img style="max-width: 100%;" src="
${
ossUrl
}
" /><br/><p>`
)
;
}
;
initBus
=
()
=>
{
const
{
isIntro
}
=
this
.
props
;
Bus
.
bind
(
`graphicsEditorImage
${
isIntro
?
''
:
'Content'
}
`
,
this
.
uploadImage
)
!
isIntro
&&
Bus
.
bind
(
'graphicsEditorVideo'
,
this
.
uploadVideo
)
}
Bus
.
bind
(
`graphicsEditorImage
${
isIntro
?
''
:
'Content'
}
`
,
this
.
uploadImage
)
;
!
isIntro
&&
Bus
.
bind
(
'graphicsEditorVideo'
,
this
.
uploadVideo
)
;
}
;
removeBus
=
()
=>
{
const
{
isIntro
}
=
this
.
props
;
Bus
.
unbind
(
`graphicsEditorImage
${
isIntro
?
''
:
'Content'
}
`
,
this
.
uploadImage
)
!
isIntro
&&
Bus
.
unbind
(
'graphicsEditorVideo'
,
this
.
uploadVideo
)
}
Bus
.
unbind
(
`graphicsEditorImage
${
isIntro
?
''
:
'Content'
}
`
,
this
.
uploadImage
)
;
!
isIntro
&&
Bus
.
unbind
(
'graphicsEditorVideo'
,
this
.
uploadVideo
)
;
}
;
uploadImage
=
()
=>
{
this
.
setState
({
showSelectImageModal
:
true
})
}
this
.
setState
({
showSelectImageModal
:
true
})
;
}
;
uploadVideo
=
()
=>
{
this
.
setState
({
showSelectVideoModal
:
true
})
}
this
.
setState
({
showSelectVideoModal
:
true
})
;
}
;
render
()
{
const
{
editorId
,
textLength
,
showSelectImageModal
,
showSelectVideoModal
,
diskList
,
}
=
this
.
state
;
const
{
limitLength
=
1000
,
isIntro
,
maxLimit
}
=
this
.
props
;
return
<
div
className=
{
`graphics-editor-container${isIntro ? ' introduce' : ''} ${(textLength > maxLimit)&& 'warning'}`
}
>
<
div
className=
"editor-box"
id=
{
`editor${editorId}`
}
></
div
>
<
div
className=
"editor-tips"
>
(
{
(
textLength
>
maxLimit
)
?
<
span
style=
{
{
color
:
'red'
}
}
>
{
textLength
}
</
span
>
:
textLength
}
/
{
maxLimit
||
100000
}
)
</
div
>
{
showSelectVideoModal
&&
const
{
editorId
,
textLength
,
showSelectImageModal
,
showSelectVideoModal
,
diskList
}
=
this
.
state
;
const
{
isIntro
,
maxLimit
}
=
this
.
props
;
return
(
<
div
className=
{
`graphics-editor-container${isIntro ? ' introduce' : ''} ${textLength > maxLimit && 'warning'}`
}
>
<
div
className=
'editor-box'
id=
{
`editor${editorId}`
}
></
div
>
<
div
className=
'editor-tips'
>
(
{
textLength
>
maxLimit
?
<
span
style=
{
{
color
:
'red'
}
}
>
{
textLength
}
</
span
>
:
textLength
}
/
{
maxLimit
||
1000
}
)
</
div
>
{
textLength
>
maxLimit
&&
(
<
div
style=
{
{
top
:
isIntro
?
'245px'
:
'512px'
}
}
className=
'editor-warning'
>
最多只能输入
{
maxLimit
||
1000
}
字
</
div
>
)
}
{
showSelectVideoModal
&&
(
<
SelectPrepareFileModal
operateType=
"select"
operateType=
'select'
selectTypeList=
{
[
'MP4'
]
}
accept=
"video/mp4"
accept=
'video/mp4'
confirm=
{
{
title
:
'文件过大,无法上传'
,
content
:
'为保障学员的观看体验,上传的图文大小不能超过2G'
,
...
...
@@ -232,27 +234,28 @@ class GraphicsEditor extends React.Component {
diskList=
{
diskList
}
addVideo=
{
true
}
onClose=
{
()
=>
{
this
.
setState
({
showSelectVideoModal
:
false
})
this
.
setState
({
showSelectVideoModal
:
false
});
}
}
onSelect=
{
this
.
handleSelectVideo
}
/>
}
{
showSelectImageModal
&&
)
}
{
showSelectImageModal
&&
(
<
SelectPrepareFileModal
key=
"basic"
operateType=
"select"
key=
'basic'
operateType=
'select'
multiple=
{
false
}
accept=
"image/jpeg,image/png,image/jpg"
accept=
'image/jpeg,image/png,image/jpg'
selectTypeList=
{
[
'JPG'
,
'JPEG'
,
'PNG'
]
}
tooltip=
'支持文件类型:jpg、jpeg、png'
isOpen=
{
showSelectImageModal
}
onClose=
{
()
=>
{
this
.
setState
({
showSelectImageModal
:
false
})
this
.
setState
({
showSelectImageModal
:
false
});
}
}
onSelect=
{
this
.
handleSelectImage
}
/>
}
)
}
</
div
>
);
}
}
...
...
src/modules/course-manage/components/GraphicsEditor.less
View file @
aef0f0eb
.graphics-editor-container {
border: 1px solid #
E8E8E
8;
border: 1px solid #
e8e8e
8;
border-radius: 4px;
width: 702px;
height: 510px;
...
...
@@ -23,7 +23,7 @@
.w-e-toolbar {
background-color: #fff !important;
border: none !important;
border-bottom: 1px solid #
E8E8E
8 !important;
border-bottom: 1px solid #
e8e8e
8 !important;
}
.w-e-text-container {
...
...
@@ -38,19 +38,26 @@
color: #666;
z-index: 1;
}
.editor-warning {
position: absolute;
right: 0;
color: red;
}
.w-e-full-screen-editor {
.w-e-text-container {
height: ~'calc(100vh - 109px)' !important;
}
}
&.introduce {
height: 2
0
0px;
height: 2
4
0px;
.w-e-text-container {
height: ~'calc(100% - 69px)' !important;
}
}
&.warning{
&.warning
{
border-color: red;
}
}
src/modules/course-manage/graphics-course/AddGraphicsCourse.jsx
View file @
aef0f0eb
...
...
@@ -27,6 +27,7 @@ import _ from 'underscore';
import
Upload
from
'@/core/upload'
;
import
ImgClipModal
from
'@/components/ImgClipModal'
import
'./AddGraphicsCourse.less'
;
import
Bus
from
'@/core/bus'
;
const
EDIT_BOX_KEY
=
Math
.
random
();
const
fieldNames
=
{
label
:
'categoryName'
,
value
:
'id'
,
children
:
'sonCategoryList'
};
...
...
@@ -34,7 +35,7 @@ const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryL
//添加课程时课程默认的一些值
const
defaultShelfState
=
'YES'
;
const
whetherVisitorsJoin
=
'NO'
;
const
defaultCover
Url
=
'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png'
;
const
defaultCover
=
'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png'
;
let
cutFlag
=
false
;
class
AddGraphicsCourse
extends
React
.
Component
{
...
...
@@ -53,7 +54,7 @@ class AddGraphicsCourse extends React.Component {
introduce
:
''
,
courseMediaId
:
null
,
// 图文课链接
coverId
:
null
,
// 图文封面的recourceId
coverUrl
:
defaultCover
Url
,
// 图文课封面
coverUrl
:
defaultCover
,
// 图文课封面
studentList
:
[],
// 上课学员列表
shelfState
:
'YES'
,
//是否开启学院展示
selectedFileList
:
[],
// 已经从资料云盘中勾选的文件
...
...
@@ -73,6 +74,11 @@ class AddGraphicsCourse extends React.Component {
if
(
pageType
===
'edit'
)
{
this
.
handleFetchScheudleDetail
(
id
);
}
Bus
.
bind
(
'editorLimit'
,
(
editorTextLength
,
editorType
)
=>
{
this
.
setState
({
[
editorType
]:
editorTextLength
,
});
});
}
initBus
=
()
=>
{
...
...
@@ -314,11 +320,11 @@ class AddGraphicsCourse extends React.Component {
//过期判断
if
(
User
.
getExpirationTime
()
&&
moment
().
valueOf
()
>
Number
(
User
.
getExpirationTime
()))
{
Modal
.
warning
({
title
:
"服务已到期"
,
content
:
"当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买"
,
okText
:
"我知道了"
})
return
title
:
'服务已到期'
,
content
:
'当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买'
,
okText
:
'我知道了'
,
})
;
return
;
}
const
{
id
,
coverId
,
pageType
,
courseName
,
courseMedia
,
introduce
,
categoryId
,
shelfState
,
whetherVisitorsJoin
}
=
this
.
state
;
...
...
@@ -387,6 +393,7 @@ class AddGraphicsCourse extends React.Component {
};
handleValidate
=
(
courseName
,
courseMedia
,
categoryId
)
=>
{
const
{
graphicsCourseIntor
,
graphicsCourseContent
}
=
this
.
state
;
return
new
Promise
((
resolve
)
=>
{
if
(
!
courseName
)
{
message
.
warning
(
'请输入课程名称'
);
...
...
@@ -403,6 +410,18 @@ class AddGraphicsCourse extends React.Component {
resolve
(
false
);
return
false
;
}
if
(
graphicsCourseContent
>
1000
)
{
message
.
warning
(
'课程内容超过字数限定'
);
resolve
(
false
);
return
;
}
if
(
graphicsCourseIntor
>
1000
)
{
message
.
warning
(
'课程简介超过字数限定'
);
resolve
(
false
);
return
;
}
// 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) {
...
...
@@ -415,6 +434,17 @@ class AddGraphicsCourse extends React.Component {
});
};
// 使用默认封面图
handleResetCoverUrl
=
()
=>
{
const
{
coverUrl
}
=
this
.
state
;
const
isDefaultCover
=
coverUrl
===
defaultCover
;
// 如果已经是默认图的话,不做任何任何处理
if
(
isDefaultCover
)
return
;
this
.
setState
({
coverUrl
:
defaultCover
,
coverId
:
null
},
()
=>
{
message
.
success
(
'已替换为默认图'
);
});
};
render
()
{
const
{
id
,
...
...
@@ -442,6 +472,8 @@ class AddGraphicsCourse extends React.Component {
const
hasSelectedStu
=
studentList
.
length
;
const
courseWareIcon
=
FileVerifyMap
[
videoType
]
?
FileTypeIcon
[
FileVerifyMap
[
videoType
].
type
]
:
FileTypeIcon
[
videoType
];
// 当前是否使用的是默认图片
const
isDefaultCover
=
coverUrl
===
defaultCover
;
return
(
<
div
className=
'page add-graphics-course-page'
>
...
...
@@ -468,19 +500,39 @@ class AddGraphicsCourse extends React.Component {
<
div
className=
'cover-url flex mt16'
>
<
div
className=
'label'
>
封面图:
</
div
>
<
div
className=
'cover-url__wrap'
>
{
/* <div className='cover-url__wrap'>
<div className='opt-btns'>
<Button
onClick={() => {
this.setState({
showSelectCoverModal: true,
});
}}>{`${pageType === 'add' && !coverUrl ? '上传' : '修改'}封面`}</Button>
<div className='tips'></div>
</div>
<div className='img-content'>
<img src={coverUrl} />
</div>
</div> */
}
<
div
className=
'course-cover__wrap'
>
<
div
className=
'opt-btns'
>
<
Button
onClick=
{
()
=>
{
this
.
setState
({
showSelectCoverModal
:
true
,
});
}
}
>
{
`${pageType === 'add' && !coverUrl ? '上传' : '修改'}封面`
}
</
Button
>
}
}
>
上传图片
</
Button
>
<
span
className=
{
`default-btn ${isDefaultCover ? 'disabled' : ''}`
}
onClick=
{
this
.
handleResetCoverUrl
}
>
使用默认图
</
span
>
<
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
}
/>
</
div
>
</
div
>
</
div
>
<
div
className=
'course-catalog required'
>
...
...
src/modules/course-manage/graphics-course/AddGraphicsCourse.less
View file @
aef0f0eb
.add-graphics-course-page {
position:relative !important;
.box{
margin-bottom:52px !important;
position:
relative !important;
.box
{
margin-bottom:
52px !important;
}
.ant-radio-group {
display: flex;
flex-direction: column;
.radio-item {
margin-bottom: 12px;
...
...
@@ -28,10 +27,10 @@
.form {
margin-top: 16px;
padding: 0 16px;
.label{
display:inline-block;
text-align:right;
width:85px;
.label
{
display:
inline-block;
text-align:
right;
width:
85px;
}
.required {
position: relative;
...
...
@@ -48,9 +47,9 @@
top: 0;
}
}
.course-catalog{
margin-bottom:16px;
margin-top:16px;
.course-catalog
{
margin-bottom:
16px;
margin-top:
16px;
}
.course-ware {
display: flex;
...
...
@@ -71,10 +70,20 @@
display: flex;
}
.cover-url__wrap {
.course-cover {
margin-left: 14px;
display: flex;
margin-top: 24px;
&__wrap {
position: relative;
.img-content {
width: 298px;
height: 172px;
margin-top: 8px;
margin-right: 20px;
width: 299px;
height: 169px;
position: relative;
img {
width: 100%;
...
...
@@ -82,29 +91,78 @@
object-fit: contain;
border-radius: 4px;
}
}
.empty-img {
width: 298px;
height: 172px;
border: 1px dashed #EBEBEB;
border-radius: 4px;
padding: 12px;
color: #999;
padding: 52px 24px;
.tag {
border-radius: 2px;
background: #d6d6d6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #fff;
position: absolute;
top: 8px;
left: 8px;
}
}
.opt-btns {
margin-top: 8px;
display: flex;
align-items: center;
.default-btn {
margin-left: 14px;
color: #2966ff;
cursor: pointer;
&.disabled {
color: #ccc;
cursor: not-allowed;
}
}
.ant-upload-list {
display: none;
}
.tips {
margin-left: 12
px;
margin-top: 8
px;
color: #999;
}
}
}
}
// .cover-url__wrap {
// .img-content {
// width: 298px;
// height: 172px;
// img {
// width: 100%;
// height: 100%;
// object-fit: contain;
// border-radius: 4px;
// }
// }
// .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;
...
...
@@ -139,7 +197,7 @@
justify-content: flex-end;
padding-right: 72px;
background: #fff;
border-top: 1px solid #
E8E8E
8;
border-top: 1px solid #
e8e8e
8;
z-index: 999;
.ant-btn {
margin-left: 10px;
...
...
src/modules/course-manage/graphics-course/components/AddGraphicsIntro.jsx
View file @
aef0f0eb
...
...
@@ -17,17 +17,15 @@ import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepare
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
,
}
}
;
}
// 上传封面图
...
...
@@ -38,34 +36,34 @@ class AddGraphicsIntro extends React.Component {
imageFile
,
showCutModal
:
true
,
});
}
}
;
// 选择暖场资源
handleSelectVideo
=
(
file
)
=>
{
const
{
selectType
}
=
this
.
state
;
this
.
setState
({
showSelectFileModal
:
false
})
showSelectFileModal
:
false
,
})
;
const
{
ossUrl
,
resourceId
,
folderName
,
folderFormat
,
folderSize
}
=
file
;
if
(
selectType
===
'WARMUP'
)
{
if
(
selectType
===
'WARMUP'
)
{
const
liveCourseWarmMedia
=
{
contentType
:
'WARMUP'
,
mediaType
:
folderFormat
===
'MP4'
?
'VIDEO'
:
'PICTURE'
,
mediaContent
:
resourceId
,
mediaUrl
:
ossUrl
,
mediaName
:
folderName
,
size
:
folderSize
}
size
:
folderSize
,
}
;
this
.
props
.
onChange
(
'liveCourseWarmMedia'
,
liveCourseWarmMedia
);
}
else
{
}
else
{
// 最多添加九图片
const
{
liveCourseMediaRequests
}
=
this
.
props
.
data
;
const
list
=
_
.
filter
(
liveCourseMediaRequests
,
(
item
)
=>
{
return
item
.
mediaType
==
"PICTURE"
;
return
item
.
mediaType
==
'PICTURE'
;
});
if
(
list
.
length
>
8
)
{
message
.
warning
(
"最多添加9张图片"
);
message
.
warning
(
'最多添加9张图片'
);
return
;
}
liveCourseMediaRequests
.
push
({
...
...
@@ -78,121 +76,116 @@ class AddGraphicsIntro extends React.Component {
});
this
.
props
.
onChange
(
'liveCourseMediaRequests'
,
liveCourseMediaRequests
);
}
}
};
changeDetail
=
(
value
)
=>
{
this
.
props
.
onChange
(
'courseMedia'
,
value
);
}
}
;
changeIntro
=
(
value
)
=>
{
this
.
props
.
onChange
(
'introduce'
,
value
);
}
}
;
whetherVisitorsJoinChange
=
()
=>
{
if
(
this
.
props
.
data
.
whetherVisitorsJoin
===
"NO"
){
this
.
props
.
onChange
(
'whetherVisitorsJoin'
,
'YES'
)
}
else
{
this
.
props
.
onChange
(
'whetherVisitorsJoin'
,
'NO'
)
}
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'
)
}
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
,
loadcourseMedia
,
loadintroduce
}
}
=
this
.
props
;
const
{
data
:
{
id
,
whetherVisitorsJoin
,
courseMedia
,
introduce
,
shelfState
,
loadcourseMedia
,
loadintroduce
},
}
=
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 required"
>
<
span
className=
"label"
style=
{
{
marginTop
:
5
}
}
>
课程内容:
</
span
>
<
div
className=
"content"
>
<
div
className=
"intro-list"
>
<
div
className=
"intro-list__item content-editor"
>
{
(
!
id
||
loadcourseMedia
)
&&
<
div
className=
'add-graphic__intro-info'
>
<
div
className=
'allow-tourist-join'
>
<
span
className=
'label'
>
观看设置:
</
span
>
<
div
className=
'content'
>
<
Switch
checked=
{
whetherVisitorsJoin
===
'NO'
?
true
:
false
}
onChange=
{
this
.
whetherVisitorsJoinChange
}
/>
<
div
className=
'desc'
>
{
whetherVisitorsJoin
===
'NO'
?
'已开启,学员需绑定手机号才可观看'
:
'已关闭,学员无需绑定手机号即可观看'
}
</
div
>
</
div
>
</
div
>
<
div
className=
'store-show'
>
<
span
className=
'label'
>
学院展示:
</
span
>
<
div
className=
'content'
>
<
Switch
checked=
{
shelfState
===
'YES'
?
true
:
false
}
onChange=
{
this
.
shelfStateChange
}
/>
<
div
className=
'desc'
>
{
shelfState
===
'YES'
?
'已开启,课程将在该学院的学员课程列表中显示'
:
'已关闭,课程将在该学院的学员课程列表中隐藏'
}
</
div
>
</
div
>
</
div
>
<
div
className=
'introduce required'
>
<
span
className=
'label'
style=
{
{
marginTop
:
5
}
}
>
课程内容:
</
span
>
<
div
className=
'content'
>
<
div
className=
'intro-list'
>
<
div
className=
'intro-list__item content-editor'
>
{
(
!
id
||
loadcourseMedia
)
&&
(
<
GraphicsEditor
id=
"content"
id=
'content'
maxLimit=
{
1000
}
editorType=
{
'graphicsCourseContent'
}
detail=
{
{
content
:
courseMedia
content
:
courseMedia
,
}
}
onChange=
{
(
val
)
=>
{
this
.
changeDetail
(
val
);
}
}
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
||
loadintroduce
)
&&
<
div
className=
'introduce'
>
<
span
className=
'label'
>
课程简介:
</
span
>
<
div
className=
'content'
>
<
div
className=
'intro-list'
>
<
div
className=
'intro-list__item introduce-editor'
>
{
(
!
id
||
loadintroduce
)
&&
(
<
GraphicsEditor
id=
"intro"
id=
'intro'
isIntro=
{
true
}
maxLimit=
{
1000
}
editorType=
{
'graphicsCourseIntor'
}
detail=
{
{
content
:
introduce
content
:
introduce
,
}
}
onChange=
{
(
val
)
=>
{
this
.
changeIntro
(
val
);
}
}
onChange=
{
(
val
)
=>
{
this
.
changeIntro
(
val
)
}
}
/>
}
)
}
</
div
>
</
div
>
</
div
>
</
div
>
{
/* 选择暖场图文件弹窗 */
}
{
showSelectFileModal
&&
{
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'
}
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
})
this
.
setState
({
showSelectFileModal
:
false
})
;
}
}
onSelect=
{
this
.
handleSelectVideo
}
/>
}
)
}
</
div
>
)
)
;
}
}
...
...
src/modules/course-manage/graphics-course/components/AddGraphicsIntro.less
View file @
aef0f0eb
.add-
video
__intro-info {
.add-
graphic
__intro-info {
.w-e-full-screen-editor {
background: #fff !important;
}
.playback {
margin-bottom: 10px;
.require {
color: #EC4B
35;
color: #ec4b
35;
}
&__text {
color: #999;
}
}
.label
{
display:
inline-block;
text-align:
right;
width:
85px;
.label
{
display:
inline-block;
text-align:
right;
width:
85px;
}
.playback,
.introduce {
display: flex;
flex-direction: row;
}
.allow-tourist-join
{
display:
flex;
.content
{
display:
flex;
.allow-tourist-join
{
display:
flex;
.content
{
display:
flex;
}
.desc
{
margin-left:
16px;
font-size:
14px;
color:
#999;
display:
inline-block;
.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;
.store-show {
display: flex;
margin-top: 16px;
margin-bottom: 16px;
.content {
display: flex;
}
.desc {
margin-left: 16px;
font-size: 14px;
color: #999;
display: inline-block;
}
}
.radio {
...
...
@@ -59,13 +62,13 @@
.intro-list__item {
display: flex;
margin-bottom: 16
px;
margin-bottom: 32
px;
position: relative;
&.picture {
width: 550px;
padding: 16px;
border: 1px solid #EEE
;
border: 1px solid #eee
;
border-radius: 4px;
.img__wrap {
...
...
@@ -114,7 +117,7 @@
line-height: 80px;
padding: 16px;
margin-top: 16px;
border: 1px dashed #EBEBEB
;
border: 1px dashed #ebebeb
;
border-radius: 4px;
.ant-upload {
...
...
@@ -133,7 +136,7 @@
.iconfont {
font-size: 22px;
line-height: 22px;
color: #BFBFBF
;
color: #bfbfbf
;
text-align: center;
}
...
...
@@ -152,7 +155,7 @@
}
.checkExample {
width: 60px;
color: #FF
7519;
color: #ff
7519;
cursor: pointer;
}
.warmup {
...
...
@@ -197,21 +200,19 @@
}
}
.opt-btns {
.default-btn {
margin-left: 16px;
color: #FF
7519;
color: #ff
7519;
cursor: pointer;
&.disabled {
color: #CCC
;
color: #ccc
;
cursor: not-allowed;
}
}
}
}
.example-wrap {
}
.example-wrap {
font-family: PingFangSC-Regular, PingFang SC;
text-align: center;
.title {
...
...
@@ -227,14 +228,14 @@
img {
width: 180px;
}
}
.check-record-rule {
}
.check-record-rule {
width: 120px;
color: #FF
7519;
color: #ff
7519;
cursor: pointer;
z-index: 2;
}
.record-rule-wrap {
}
.record-rule-wrap {
text-align: left;
ul {
margin-top: 10px;
...
...
@@ -247,9 +248,9 @@
.text {
color: #999;
}
}
}
.auto-send-class-report {
.auto-send-class-report {
.label {
width: 57px;
height: 12px;
...
...
@@ -258,7 +259,8 @@
color: #666666;
line-height: 12px;
}
.open-text, .close-text {
.open-text,
.close-text {
width: 572px;
font-size: 14px;
font-weight: 400;
...
...
@@ -273,4 +275,4 @@
.close-text {
margin-bottom: 16px;
}
}
\ No newline at end of file
}
src/modules/course-manage/graphics-course/components/GraphicsCourseList.jsx
View file @
aef0f0eb
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:45
* @LastEditors:
Please set LastEditors
* @LastEditTime: 2021-0
6-21 11:24
:29
* @Description:
视频
课-列表模块
* @LastEditors:
yuananting
* @LastEditTime: 2021-0
7-15 14:12
:29
* @Description:
线上
课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import
User
from
'@/common/js/user'
;
...
...
@@ -341,7 +341,7 @@ class GraphicsCourseList extends React.Component {
// 删除视频课
handleDeleteGraphicsCourse
=
(
scheduleId
)
=>
{
Modal
.
confirm
({
title
:
'你确定要删除此
视频
课吗?'
,
title
:
'你确定要删除此
线上
课吗?'
,
content
:
'删除后,学员将不能进行观看。'
,
icon
:
<
span
className=
'icon iconfont default-confirm-icon'
>

</
span
>,
okText
:
'确定'
,
...
...
src/modules/course-manage/graphics-course/components/GraphicsCourseOpt.jsx
View file @
aef0f0eb
...
...
@@ -3,7 +3,7 @@
* @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27
* @Description:
视频
课-操作模块
* @Description:
线上
课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
...
...
src/modules/course-manage/graphics-course/index.jsx
View file @
aef0f0eb
...
...
@@ -17,13 +17,13 @@ class GraphicsCourse extends React.Component {
courseType
:
'PICTURE'
,
storeId
:
User
.
getStoreId
()
},
dataSource
:
[],
//
视频
课列表
totalCount
:
0
,
//
视频
课数据总条数
dataSource
:
[],
//
线上
课列表
totalCount
:
0
,
//
线上
课数据总条数
}
}
componentWillMount
()
{
// 获取
视频
课列表
// 获取
线上
课列表
this
.
handleFetchScheduleList
();
}
...
...
@@ -76,7 +76,7 @@ class GraphicsCourse extends React.Component {
{
/* 操作模块 */
}
<
GraphicsCourseOpt
/>
{
/*
视频
课列表模块 */
}
{
/*
线上
课列表模块 */
}
<
GraphicsCourseList
query=
{
query
}
dataSource=
{
dataSource
}
...
...
src/modules/course-manage/modal/PreviewCourseModal.jsx
View file @
aef0f0eb
/*
* @Author: 吴文洁
* @Date: 2020-07-23 14:54:16
* @LastEditors:
fusanqias
ng
* @LastEditTime: 2021-0
5-24 21:02:21
* @LastEditors:
yuananti
ng
* @LastEditTime: 2021-0
7-15 14:02:37
* @Description: 大班直播课预览弹窗
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import
React
from
'react'
import
{
Modal
}
from
'antd'
import
moment
from
'moment'
import
'./PreviewCourseModal.less'
import
React
from
"react"
;
import
{
Modal
,
Tabs
}
from
"antd"
;
import
moment
from
"moment"
;
import
ChapterList
from
"../video-course/components/ChapterList"
;
import
"./PreviewCourseModal.less"
;
const
{
TabPane
}
=
Tabs
;
const
courseStateShow
=
{
UN_START
:
{
title
:
'待开课'
title
:
"待开课"
,
},
STARTING
:
{
title
:
'上课中'
title
:
"上课中"
,
},
FINISH
:
{
title
:
'已完成'
title
:
"已完成"
,
},
EXPIRED
:
{
code
:
4
,
title
:
'未成功开课'
,
color
:
'#CCCCCC'
}
}
title
:
"未成功开课"
,
color
:
"#CCCCCC"
,
}
,
}
;
class
PreviewCourseModal
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{}
super
(
props
);
this
.
state
=
{
activeTab
:
"courseChapter"
,
};
}
dealTimeDuration
=
(
time
)
=>
{
const
diff
=
Math
.
floor
(
time
%
3600
)
let
hours
=
Math
.
floor
(
time
/
3600
)
let
mins
=
Math
.
floor
(
diff
/
60
)
let
seconds
=
Math
.
floor
(
time
%
60
)
hours
=
hours
<
10
?
'0'
+
hours
:
hours
mins
=
mins
<
10
?
'0'
+
mins
:
mins
seconds
=
seconds
<
10
?
'0'
+
seconds
:
seconds
return
hours
+
':'
+
mins
+
':'
+
seconds
}
const
diff
=
Math
.
floor
(
time
%
3600
)
;
let
hours
=
Math
.
floor
(
time
/
3600
)
;
let
mins
=
Math
.
floor
(
diff
/
60
)
;
let
seconds
=
Math
.
floor
(
time
%
60
)
;
hours
=
hours
<
10
?
"0"
+
hours
:
hours
;
mins
=
mins
<
10
?
"0"
+
mins
:
mins
;
seconds
=
seconds
<
10
?
"0"
+
seconds
:
seconds
;
return
hours
+
":"
+
mins
+
":"
+
seconds
;
}
;
dealWithTime
=
(
startTime
,
endTime
)
=>
{
const
startDate
=
new
Date
(
Number
(
startTime
))
const
endDate
=
new
Date
(
Number
(
endTime
))
const
startDate
=
new
Date
(
Number
(
startTime
))
;
const
endDate
=
new
Date
(
Number
(
endTime
))
;
const
year
=
startDate
.
getFullYear
()
const
month
=
startDate
.
getMonth
()
+
1
<
10
?
`0
${
startDate
.
getMonth
()
+
1
}
`
:
startDate
.
getMonth
()
+
1
const
day
=
startDate
.
getDate
()
<
10
?
`0
${
startDate
.
getDate
()}
`
:
startDate
.
getDate
()
const
year
=
startDate
.
getFullYear
();
const
month
=
startDate
.
getMonth
()
+
1
<
10
?
`0
${
startDate
.
getMonth
()
+
1
}
`
:
startDate
.
getMonth
()
+
1
;
const
day
=
startDate
.
getDate
()
<
10
?
`0
${
startDate
.
getDate
()}
`
:
startDate
.
getDate
();
const
startHour
=
startDate
.
getHours
()
<
10
?
`0
${
startDate
.
getHours
()}
`
:
startDate
.
getHours
()
const
startMinute
=
startDate
.
getMinutes
()
<
10
?
`0
${
startDate
.
getMinutes
()}
`
:
startDate
.
getMinutes
()
const
startHour
=
startDate
.
getHours
()
<
10
?
`0
${
startDate
.
getHours
()}
`
:
startDate
.
getHours
();
const
startMinute
=
startDate
.
getMinutes
()
<
10
?
`0
${
startDate
.
getMinutes
()}
`
:
startDate
.
getMinutes
();
const
endHour
=
endDate
.
getHours
()
<
10
?
`0
${
endDate
.
getHours
()}
`
:
endDate
.
getHours
()
const
endMinute
=
endDate
.
getMinutes
()
<
10
?
`0
${
endDate
.
getMinutes
()}
`
:
endDate
.
getMinutes
()
const
endHour
=
endDate
.
getHours
()
<
10
?
`0
${
endDate
.
getHours
()}
`
:
endDate
.
getHours
();
const
endMinute
=
endDate
.
getMinutes
()
<
10
?
`0
${
endDate
.
getMinutes
()}
`
:
endDate
.
getMinutes
();
const
liveDateStr
=
`
${
year
}
-
${
month
}
-
${
day
}
`
const
startTimeStr
=
`
${
startHour
}
:
${
startMinute
}
`
const
endTimeStr
=
`
${
endHour
}
:
${
endMinute
}
`
const
liveDateStr
=
`
${
year
}
-
${
month
}
-
${
day
}
`
;
const
startTimeStr
=
`
${
startHour
}
:
${
startMinute
}
`
;
const
endTimeStr
=
`
${
endHour
}
:
${
endMinute
}
`
;
return
{
liveDateStr
,
startTimeStr
,
endTimeStr
}
}
endTimeStr
,
}
;
}
;
render
()
{
const
{
courseBasicInfo
,
courseClassInfo
=
{},
courseIntroInfo
,
type
,
courseState
,
origin
}
=
this
.
props
const
{
coverUrl
,
courseName
,
scheduleVideoUrl
,
videoDuration
}
=
courseBasicInfo
const
{
liveDate
,
calendarTime
,
startTime
,
endTime
,
timeHorizonStart
,
timeHorizonEnd
,
teacherName
}
=
courseClassInfo
const
{
introduce
}
=
courseIntroInfo
let
liveDateStr
,
startTimeStr
,
endTimeStr
if
(
type
===
'add'
)
{
const
_liveDate
=
moment
(
calendarTime
[
0
]).
format
(
'YYYY-MM-DD'
)
console
.
log
(
'_liveDate'
,
_liveDate
)
const
_timeHorizonStart
=
moment
(
startTime
).
format
(
'HH:mm'
)
const
_timeHorizonEnd
=
moment
(
endTime
).
format
(
'HH:mm'
)
const
_startTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonStart
).
format
(
'x'
)
const
_endTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonEnd
).
format
(
'x'
)
const
{
liveDateStr
:
_liveDateStr
,
startTimeStr
:
_startTimeStr
,
endTimeStr
:
_endTimeStr
}
=
this
.
dealWithTime
(
_startTime
,
_endTime
)
liveDateStr
=
_liveDateStr
startTimeStr
=
_startTimeStr
endTimeStr
=
_endTimeStr
const
{
courseBasicInfo
,
courseClassInfo
=
{},
courseIntroInfo
,
type
,
courseState
,
courseChapterList
=
[],
}
=
this
.
props
;
const
{
coverUrl
,
courseName
,
scheduleVideoUrl
,
videoDuration
}
=
courseBasicInfo
;
const
{
liveDate
,
calendarTime
,
startTime
,
endTime
,
timeHorizonStart
,
timeHorizonEnd
,
teacherName
,
}
=
courseClassInfo
;
const
{
introduce
,
categoryName
}
=
courseIntroInfo
;
let
{
activeTab
}
=
this
.
state
;
let
liveDateStr
,
startTimeStr
,
endTimeStr
;
if
(
type
===
"add"
)
{
const
_liveDate
=
moment
(
calendarTime
[
0
]).
format
(
"YYYY-MM-DD"
);
console
.
log
(
"_liveDate"
,
_liveDate
);
const
_timeHorizonStart
=
moment
(
startTime
).
format
(
"HH:mm"
);
const
_timeHorizonEnd
=
moment
(
endTime
).
format
(
"HH:mm"
);
const
_startTime
=
moment
(
_liveDate
+
" "
+
_timeHorizonStart
).
format
(
"x"
);
const
_endTime
=
moment
(
_liveDate
+
" "
+
_timeHorizonEnd
).
format
(
"x"
);
const
{
liveDateStr
:
_liveDateStr
,
startTimeStr
:
_startTimeStr
,
endTimeStr
:
_endTimeStr
,
}
=
this
.
dealWithTime
(
_startTime
,
_endTime
);
liveDateStr
=
_liveDateStr
;
startTimeStr
=
_startTimeStr
;
endTimeStr
=
_endTimeStr
;
}
else
{
const
_liveDate
=
moment
(
liveDate
).
format
(
'YYYY-MM-DD'
)
const
_timeHorizonStart
=
moment
(
timeHorizonStart
).
format
(
'HH:mm'
)
const
_timeHorizonEnd
=
moment
(
timeHorizonEnd
).
format
(
'HH:mm'
)
const
startTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonStart
).
format
(
'x'
)
const
endTime
=
moment
(
_liveDate
+
' '
+
_timeHorizonEnd
).
format
(
'x'
)
const
{
liveDateStr
:
_liveDateStr
,
startTimeStr
:
_startTimeStr
,
endTimeStr
:
_endTimeStr
}
=
this
.
dealWithTime
(
startTime
,
endTime
)
liveDateStr
=
_liveDateStr
startTimeStr
=
_startTimeStr
endTimeStr
=
_endTimeStr
const
_liveDate
=
moment
(
liveDate
).
format
(
"YYYY-MM-DD"
);
const
_timeHorizonStart
=
moment
(
timeHorizonStart
).
format
(
"HH:mm"
);
const
_timeHorizonEnd
=
moment
(
timeHorizonEnd
).
format
(
"HH:mm"
);
const
startTime
=
moment
(
_liveDate
+
" "
+
_timeHorizonStart
).
format
(
"x"
);
const
endTime
=
moment
(
_liveDate
+
" "
+
_timeHorizonEnd
).
format
(
"x"
);
const
{
liveDateStr
:
_liveDateStr
,
startTimeStr
:
_startTimeStr
,
endTimeStr
:
_endTimeStr
,
}
=
this
.
dealWithTime
(
startTime
,
endTime
);
liveDateStr
=
_liveDateStr
;
startTimeStr
=
_startTimeStr
;
endTimeStr
=
_endTimeStr
;
}
return
(
<
Modal
title=
'预览'
title=
"预览"
visible=
{
true
}
width=
{
680
}
onCancel=
{
this
.
props
.
close
}
footer=
{
null
}
maskClosable=
{
false
}
closeIcon=
{
<
span
className=
'icon iconfont modal-close-icon'
>

</
span
>
}
className=
'preview-live-course-modal'
>
<
div
className=
'container__wrap'
>
<
div
className=
'container'
>
closeIcon=
{
<
span
className=
"icon iconfont modal-close-icon"
>

</
span
>
}
className=
"preview-live-course-modal"
>
<
div
className=
"container__wrap"
>
<
div
className=
"container"
>
<
div
className=
'container__header'
>
{
type
===
'videoCourse'
?
(
<
video
controls
src=
{
scheduleVideoUrl
}
poster=
{
coverUrl
?
coverUrl
:
`${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`
}
className=
'course-url'
/>
)
:
(
<
img
src=
{
coverUrl
}
className=
'course-cover'
/>
)
}
<
Choose
>
<
When
condition=
{
type
===
'videoCourse'
}
>
<
img
src=
{
coverUrl
?
coverUrl
:
`https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png`
}
className=
'course-cover'
alt=
''
/>
</
When
>
<
Otherwise
>
<
img
src=
{
coverUrl
}
className=
'course-cover'
alt=
''
/>
</
Otherwise
>
</
Choose
>
</
div
>
{
type
===
'videoCourse'
?
(
<
Choose
>
<
When
condition=
{
type
===
'videoCourse'
}
>
<
div
className=
'container__body'
>
<
div
className=
'title__name'
>
{
courseName
}
</
div
>
{
videoDuration
&&
<
div
>
视频时长:
{
this
.
dealTimeDuration
(
videoDuration
)
}
</
div
>
}
<
div
className=
'title__category'
>
课程分类:
{
categoryName
}
</
div
>
<
div
className=
'title__chapter'
>
共
{
courseChapterList
.
length
}
小节
</
div
>
</
div
>
)
:
(
</
When
>
<
Otherwise
>
<
div
className=
'container__body'
>
<
div
className=
'container__body__title'
>
<
div
className=
'title__name'
>
{
courseName
}
</
div
>
...
...
@@ -152,28 +203,71 @@ class PreviewCourseModal extends React.Component {
<
span
className=
'teacher__value'
>
{
teacherName
}
</
span
>
</
div
>
</
div
>
)
}
</
Otherwise
>
</
Choose
>
<
div
className=
"container__introduction"
>
<
Choose
>
<
When
condition=
{
type
===
"videoCourse"
}
>
<
Tabs
activeKey=
{
activeTab
}
onChange=
{
(
key
)
=>
{
this
.
setState
({
activeTab
:
key
,
});
}
}
>
<
TabPane
tab=
"课程目录"
key=
"courseChapter"
></
TabPane
>
<
TabPane
tab=
"课程简介"
key=
"courseIntro"
></
TabPane
>
</
Tabs
>
</
When
>
<
Otherwise
>
<
div
className=
"container__introduction__title"
>
直播课简介
</
div
>
</
Otherwise
>
</
Choose
>
<
div
className=
'container__introduction'
>
{
type
===
'videoCourse'
?
(
<
div
className=
'container__introduction__title'
>
视频课简介
</
div
>
)
:
(
<
div
className=
'container__introduction__title'
>
直播课简介
</
div
>
<
Choose
>
<
When
condition=
{
type
===
"videoCourse"
}
>
{
activeTab
===
"courseChapter"
&&
(
<
div
className=
"container__chapter"
>
{
<
ChapterList
chapterType=
"VIDEO"
courseChapterList=
{
courseChapterList
}
/>
}
</
div
>
)
}
{
activeTab
===
"courseIntro"
&&
(
<
div
className=
"container__introduction__list editor-box"
>
<
div
className=
"intro-item text"
dangerouslySetInnerHTML=
{
{
__html
:
introduce
,
}
}
/>
</
div
>
)
}
<
div
className=
'container__introduction__list editor-box'
>
</
When
>
<
Otherwise
>
<
div
className=
"container__introduction__list editor-box"
>
<
div
className=
'intro-item text'
className=
"intro-item text"
dangerouslySetInnerHTML=
{
{
__html
:
introduce
__html
:
introduce
,
}
}
/>
</
div
>
</
Otherwise
>
</
Choose
>
</
div
>
</
div
>
</
div
>
</
Modal
>
)
)
;
}
}
export
default
PreviewCourseModal
export
default
PreviewCourseModal
;
src/modules/course-manage/modal/PreviewCourseModal.less
View file @
aef0f0eb
...
...
@@ -13,7 +13,8 @@
}
.container {
overflow: scroll;
overflow-y: auto;
overflow-x: hidden;
height: 100%;;
.course-cover, .course-url {
...
...
@@ -30,6 +31,16 @@
color: #000;
}
.title__category {
color: #999999;
margin-top: 4px;
margin-bottom: 4px;
}
.title__chapter {
color: #999999;
margin-bottom: 4px;
}
.title__inst-name {
color: #666;
font-size: 12px;
...
...
src/modules/course-manage/modal/ShareLiveModal.jsx
View file @
aef0f0eb
...
...
@@ -116,11 +116,7 @@ class ShareLiveModal extends React.Component {
}
break
;
case
'videoClass'
:
// 视频课
coverImgSrc
=
coverUrl
||
(
courseDivision
===
'internal'
?
`
${
scheduleVideoUrl
}
?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring`
:
'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png'
);
coverImgSrc
=
coverUrl
||
'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png'
;
break
;
case
'graphicsClass'
:
// 图文课
coverImgSrc
=
coverUrl
||
'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png'
;
...
...
src/modules/course-manage/offline-course/AddOfflineCourse.jsx
View file @
aef0f0eb
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:07:47
* @LastEditors:
Please set LastEditors
* @LastEditTime: 2021-07-
06 14:47:23
* @LastEditors:
yuananting
* @LastEditTime: 2021-07-
15 14:24:02
* @Description: 线下课新增/编辑页
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
...
...
@@ -14,6 +14,7 @@ import Bus from '@/core/bus';
import
RangePicker
from
'@/modules/common/DateRangePicker'
;
import
ShowTips
from
'@/components/ShowTips'
;
import
Breadcrumbs
from
'@/components/Breadcrumbs'
;
import
SelectStudent
from
'../modal/select-student'
;
import
SelectPrepareFileModal
from
'../../prepare-lesson/modal/SelectPrepareFileModal'
;
import
PreviewOfflineModal
from
'./modal/PreviewOfflineModal'
;
import
StoreService
from
'@/domains/store-domain/storeService'
;
...
...
@@ -96,6 +97,11 @@ class AddOfflineCourse extends React.Component {
if
(
pageType
===
'edit'
)
{
this
.
handleFetchScheudleDetail
(
courseId
);
}
Bus
.
bind
(
'editorLimit'
,
(
editorTextLength
)
=>
{
this
.
setState
({
editorTextLength
,
});
});
}
initBus
=
()
=>
{
...
...
@@ -335,9 +341,10 @@ class AddOfflineCourse extends React.Component {
handleSelectCover
=
(
file
)
=>
{
this
.
setState
({
visible
:
true
,
imageFile
:
file
,
imageFile
:
file
});
};
}
//获取resourceId
getSignature
=
(
blob
,
fileName
)
=>
{
...
...
@@ -411,7 +418,8 @@ class AddOfflineCourse extends React.Component {
signOutStartTimeUnit
,
signOutEndTimeNum
,
signOutEndTimeUnit
,
isMore
,
// isMore,
editorTextLength
,
}
=
this
.
state
;
let
coverObj
=
{
...
...
@@ -444,7 +452,8 @@ class AddOfflineCourse extends React.Component {
startTime
,
endTime
,
calendarTime
,
isMore
,
editorTextLength
,
// isMore,
};
if
(
whetherSetApply
===
'YES'
)
{
...
...
@@ -558,8 +567,8 @@ class AddOfflineCourse extends React.Component {
}
else
if
(
data
.
whetherSetSignOut
===
'YES'
&&
((
data
.
signOutType
===
'START_LATER'
&&
!
data
.
signOutStartTimeNum
)
||
!
data
.
signOutEndTimeNum
))
{
message
.
warning
(
'请输入签退时间'
);
resolve
(
false
);
}
else
if
(
data
.
isMore
)
{
message
.
warning
(
'简介超过字数限定'
);
}
else
if
(
data
.
editorTextLength
>
1000
)
{
message
.
warning
(
'
课程
简介超过字数限定'
);
resolve
(
false
);
}
else
{
resolve
(
true
);
...
...
@@ -618,12 +627,8 @@ class AddOfflineCourse extends React.Component {
});
}
changeIntro
=
(
value
,
textLength
)
=>
{
const
isMore
=
textLength
>
1000
;
if
(
isMore
)
{
message
.
warning
(
'内容过长,不能超过1000字'
);
}
this
.
setState
({
introduce
:
value
,
isMore
});
changeIntro
=
(
value
)
=>
{
this
.
setState
({
introduce
:
value
});
};
selectMultiDate
=
(
calendarTime
)
=>
{
...
...
@@ -646,6 +651,11 @@ class AddOfflineCourse extends React.Component {
};
whetherVisitorsJoinChange
=
()
=>
{
const
{
whetherSetApply
,
whetherVisitorsJoin
}
=
this
.
state
;
if
(
whetherSetApply
==
'NO'
){
message
.
warning
(
'关闭报名无法获取手机号!'
)
return
}
if
(
this
.
state
.
whetherVisitorsJoin
===
'NO'
)
{
this
.
setState
({
whetherVisitorsJoin
:
'YES'
});
}
else
{
...
...
@@ -726,10 +736,6 @@ class AddOfflineCourse extends React.Component {
<
span
className=
'label'
>
封面图:
</
span
>
<
div
className=
'course-cover__wrap'
>
<
div
className=
'img-content'
>
{
isDefaultCover
&&
<
span
className=
'tag'
>
默认图
</
span
>
}
<
img
src=
{
coverUrl
}
alt=
''
/>
</
div
>
<
div
className=
'opt-btns'
>
<
Button
onClick=
{
()
=>
{
...
...
@@ -742,7 +748,11 @@ class AddOfflineCourse extends React.Component {
<
span
className=
{
`default-btn ${isDefaultCover ? 'disabled' : ''}`
}
onClick=
{
this
.
handleResetCoverUrl
}
>
使用默认图
</
span
>
<
div
className=
'tips'
>
建议尺寸1280*720px,图片支持jpg、jpeg、png格式。
</
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
}
/>
</
div
>
</
div
>
</
div
>
...
...
@@ -831,7 +841,7 @@ class AddOfflineCourse extends React.Component {
);
}
}
getPopupContainer=
{
()
=>
document
.
getElementById
(
'teacher'
)
}
>
{
_
.
map
(
teacherList
,
(
item
)
=>
{
{
_
.
map
(
teacherList
,
(
item
,
index
)
=>
{
return
(
<
Option
value=
{
item
.
id
}
key=
{
item
.
id
}
>
{
item
.
nickName
}
...
...
@@ -840,20 +850,6 @@ class AddOfflineCourse extends React.Component {
})
}
</
Select
>
</
div
>
<
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=
'introduce'
>
<
span
className=
'label'
>
课程简介:
</
span
>
<
div
className=
'content'
>
...
...
@@ -867,8 +863,8 @@ class AddOfflineCourse extends React.Component {
detail=
{
{
content
:
introduce
,
}
}
onChange=
{
(
val
,
textLength
)
=>
{
this
.
changeIntro
(
val
,
textLength
);
onChange=
{
(
val
)
=>
{
this
.
changeIntro
(
val
);
}
}
/>
)
}
...
...
@@ -957,6 +953,7 @@ class AddOfflineCourse extends React.Component {
startTimeApply
:
undefined
,
endTimeApply
:
undefined
,
quota
:
null
,
whetherVisitorsJoin
:
whetherSetApply
!==
'YES'
?
whetherVisitorsJoin
:
false
});
}
}
/>
...
...
@@ -1068,6 +1065,13 @@ class AddOfflineCourse extends React.Component {
)
}
</
div
>
</
div
>
<
div
className=
'allow-tourist-join'
>
<
span
className=
'label'
>
观看设置:
</
span
>
<
div
className=
'content'
>
<
Switch
checked=
{
whetherVisitorsJoin
===
'NO'
?
true
:
false
}
onChange=
{
this
.
whetherVisitorsJoinChange
}
/>
<
div
className=
'desc'
>
{
whetherVisitorsJoin
===
'NO'
?
'已开启,仅限绑定了手机号的学员报名线下课'
:
'已关闭,允许未绑定手机号的学员报名线下课'
}
</
div
>
</
div
>
</
div
>
<
div
className=
'course-catalog'
>
<
span
className=
'label'
>
考勤签到:
</
span
>
<
div
className=
'switch-box'
>
...
...
@@ -1267,17 +1271,11 @@ class AddOfflineCourse extends React.Component {
}
}
onSelect=
{
this
.
handleSelectCover
}
/>
)
}
{
visible
&&
(
<
ImgClipModal
visible=
{
visible
}
imgUrl=
{
imageFile
.
ossUrl
}
onConfirm=
{
this
.
getSignature
}
onClose=
{
()
=>
{
this
.
setState
({
visible
:
false
});
}
}
/>
)
}
)
}
{
visible
&&
<
ImgClipModal
visible=
{
visible
}
imgUrl=
{
imageFile
.
ossUrl
}
onConfirm=
{
this
.
getSignature
}
onClose=
{
()
=>
{
this
.
setState
({
visible
:
false
});
}
}
/>
}
{
this
.
state
.
previewOfflineModal
}
</
div
>
);
...
...
src/modules/course-manage/offline-course/AddOfflineCourse.less
View file @
aef0f0eb
.add-offline-course-page {
position:relative !important;
.box{
margin-bottom:52px !important;
position:
relative !important;
.box
{
margin-bottom:
52px !important;
}
.ant-radio-group {
display: flex;
...
...
@@ -39,18 +39,18 @@
position: relative;
padding-left: 14px;
&::before {
content:
""
;
content:
''
;
position: absolute;
left: 0px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 10px;
background: #2966
FF
;
background: #2966
ff
;
}
}
}
.label{
.label
{
display: inline-block;
text-align: right;
width: 120px;
...
...
@@ -75,8 +75,8 @@
}
}
.course-catalog {
margin-bottom:16px;
margin-top:16px;
margin-bottom:
16px;
margin-top:
16px;
display: flex;
.switch-box {
.switch-item {
...
...
@@ -110,19 +110,19 @@
.select-day {
margin-bottom: 4px;
.mark-day {
color: #2966
FF
;
color: #2966
ff
;
}
}
}
.allow-tourist-join{
display:flex;
margin-bottom:16px;
.allow-tourist-join
{
display:
flex;
margin-bottom:
16px;
.desc {
color:#999;
margin-left:12px;
color:
#999;
margin-left:
12px;
}
.content{
display:flex;
.content
{
display:
flex;
}
}
.course-ware {
...
...
@@ -150,45 +150,41 @@
&__wrap {
position: relative;
.tag {
border-radius: 2px;
background: #D6D6D6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
}
.course-cover__wrap {
display: flex;
flex-direction: row;
}
.img-content {
margin-top: 8px;
margin-right: 20px;
width: 299px;
height: 169px;
position: relative;
img {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 4px;
}
.tag {
border-radius: 2px;
background: #d6d6d6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #fff;
position: absolute;
top: 8px;
left: 8px;
}
}
.opt-btns {
.default-btn {
margin-left: 16
px;
color: #2966FF
;
margin-left: 14
px;
color: #2966ff
;
cursor: pointer;
&.disabled {
color: #CCC
;
color: #ccc
;
cursor: not-allowed;
}
}
...
...
@@ -196,13 +192,14 @@
.ant-upload-list {
display: none;
}
}
.tips {
margin-top: 8px;
color: #999;
}
}
}
}
.select-student {
align-items: center;
...
...
@@ -236,7 +233,7 @@
justify-content: flex-end;
padding-right: 72px;
background: #fff;
border-top: 1px solid #
E8E8E
8;
border-top: 1px solid #
e8e8e
8;
z-index: 999;
.ant-btn {
margin-left: 10px;
...
...
src/modules/course-manage/offline-course/components/AddGraphicsIntro.jsx
View file @
aef0f0eb
...
...
@@ -102,10 +102,10 @@ class AddGraphicsIntro extends React.Component {
}
=
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
className=
"add-offline__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
>
...
...
src/modules/course-manage/offline-course/components/AddGraphicsIntro.less
View file @
aef0f0eb
.add-
video
__intro-info {
.add-
offline
__intro-info {
.w-e-full-screen-editor {
background: #fff !important;
}
...
...
src/modules/course-manage/offline-course/components/OfflineCourseList.jsx
View file @
aef0f0eb
...
...
@@ -3,7 +3,7 @@
* @Date: 2020-08-05 10:12:45
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-11 16:44:42
* @Description:
视频
课-列表模块
* @Description:
线上
课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import
Service
from
'@/common/js/service'
;
...
...
@@ -30,7 +30,7 @@ class OfflineCourseList extends React.Component {
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
id
:
''
,
//
视频
课ID
id
:
''
,
//
线上
课ID
studentIds
:
[],
};
}
...
...
src/modules/course-manage/offline-course/components/OfflineCourseOpt.jsx
View file @
aef0f0eb
...
...
@@ -3,7 +3,7 @@
* @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27
* @Description:
视频
课-操作模块
* @Description:
线上
课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
...
...
src/modules/course-manage/utils/hasExportPermission.js
View file @
aef0f0eb
...
...
@@ -14,7 +14,7 @@ const hasExportPermission = (type) => {
return
Permission
.
hasInteractiveExport
();
}
//
视频
课导出权限
//
线上
课导出权限
if
(
type
===
'videoClass'
)
{
return
Permission
.
hasVideoClassExport
();
}
...
...
src/modules/course-manage/video-course/AddVideoCourse.jsx
View file @
aef0f0eb
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:07:47
* @LastEditors:
Please set LastEditors
* @LastEditTime: 2021-07-
08 19:34:10
* @Description:
视频
课新增/编辑页
* @LastEditors:
yuananting
* @LastEditTime: 2021-07-
15 18:12:59
* @Description:
线上
课新增/编辑页
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import
React
from
'react'
import
{
Button
,
Input
,
Radio
,
message
,
Modal
,
Cascader
}
from
'antd'
import
{
DISK_MAP
,
FileTypeIcon
,
FileVerifyMap
}
from
'@/common/constants/academic/lessonEnum'
import
{
Button
,
Input
,
message
,
Modal
,
Cascader
,
Tooltip
,
Form
,
Popconfirm
,
Menu
,
Dropdown
}
from
'antd'
import
{
FileTypeIcon
,
FileVerifyMap
}
from
'@/common/constants/academic/lessonEnum'
import
ShowTips
from
'@/components/ShowTips'
import
Breadcrumbs
from
'@/components/Breadcrumbs'
import
moment
from
'moment'
...
...
@@ -28,7 +27,9 @@ import { randomString } from '@/domains/basic-domain/utils'
import
ImgClipModal
from
'@/components/ImgClipModal'
import
$
from
'jquery'
import
'./AddVideoCourse.less'
import
Bus
from
'@/core/bus'
const
{
TextArea
}
=
Input
;
const
EDIT_BOX_KEY
=
Math
.
random
()
const
fieldNames
=
{
label
:
'categoryName'
,
value
:
'id'
,
children
:
'sonCategoryList'
}
...
...
@@ -53,18 +54,17 @@ class AddVideoCourse extends React.Component {
const
pageType
=
getParameterByName
(
'type'
)
this
.
state
=
{
id
,
//
视频
课ID,编辑的时候从URL上带过来
id
,
//
线上
课ID,编辑的时候从URL上带过来
pageType
,
// 页面类型: add->新建 edit->编辑
imageFile
:
null
,
// 需要被截取的图片
courseName
:
null
,
// 视频课名称
scheduleVideoId
:
null
,
// 视频课链接
courseName
:
null
,
// 线上课名称
coverId
:
null
,
// 视频封面的recourceId
coverUrl
:
null
,
//
视频
课封面
coverUrl
:
null
,
//
线上
课封面
studentList
:
[],
// 上课学员列表
shelfState
:
'YES'
,
//是否开启学院展示
scheduleMedia
:
[
{
//
视频
课媒体资源
//
线上
课媒体资源
contentType
:
'INTRO'
,
mediaType
:
'TEXT'
,
mediaContent
:
''
,
...
...
@@ -82,7 +82,13 @@ class AddVideoCourse extends React.Component {
whetherVisitorsJoin
:
'NO'
,
// 是否允许游客加入
showSelectCoverModal
:
false
,
cutImageBlob
:
null
,
introduce
:
''
introduce
:
''
,
courseChapterList
:[
],
// 课节列表
// videoType: "MP4",
mediaNameAlias
:
''
,
// 任一视频重命名的名称(气泡框)
selectTypeList
:[
'MP4'
],
accept
:
'video/mp4'
}
}
...
...
@@ -92,6 +98,11 @@ class AddVideoCourse extends React.Component {
if
(
pageType
===
'edit'
)
{
this
.
handleFetchScheudleDetail
(
id
)
}
Bus
.
bind
(
'editorLimit'
,
(
editorTextLength
)
=>
{
this
.
setState
({
editorTextLength
,
});
});
}
//获取分类列表
...
...
@@ -103,34 +114,33 @@ class AddVideoCourse extends React.Component {
})
}
catalogChange
=
(
value
)
=>
{
catalogChange
=
(
value
,
_categoryName
)
=>
{
const
categoryName
=
_
.
pluck
(
_categoryName
,
'categoryName'
).
join
(
'-'
)
const
changeValueLength
=
value
.
length
switch
(
changeValueLength
)
{
case
1
:
this
.
setState
({
categoryId
:
value
[
0
]
})
this
.
setState
({
categoryId
:
value
[
0
]
,
categoryName
})
break
case
2
:
this
.
setState
({
categoryId
:
value
[
1
]
})
this
.
setState
({
categoryId
:
value
[
1
]
,
categoryName
})
break
default
:
this
.
setState
({
categoryId
:
null
})
break
}
}
// 获取
视频
课详情
// 获取
线上
课详情
handleFetchScheudleDetail
=
(
courseId
)
=>
{
CourseService
.
videoScheduleDetail
({
courseId
}).
then
((
res
)
=>
{
const
{
result
=
{}
}
=
res
||
{}
const
{
courseName
,
shelfState
,
whetherVisitorsJoin
,
courseMediaVOS
,
categoryOneName
,
categoryTwoName
,
categoryId
}
=
result
const
{
courseName
,
shelfState
,
whetherVisitorsJoin
,
courseMediaVOS
,
categoryOneName
,
categoryTwoName
,
categoryId
,
courseChapterVOList
=
[]
}
=
result
let
coverId
let
coverUrl
let
videoType
let
videoDuration
let
videoName
// let videoDuration
// let videoName
let
scheduleMedia
=
[]
let
scheduleVideoId
let
scheduleVideoUrl
let
hasIntro
...
...
@@ -140,13 +150,6 @@ class AddVideoCourse extends React.Component {
coverId
=
item
.
mediaContent
coverUrl
=
item
.
mediaUrl
break
case
'SCHEDULE'
:
videoDuration
=
item
.
videoDuration
videoName
=
item
.
mediaName
scheduleVideoId
=
item
.
mediaContent
scheduleVideoUrl
=
item
.
mediaUrl
videoType
=
item
.
mediaType
break
case
'INTRO'
:
hasIntro
=
true
this
.
getTextDetail
(
'introduce'
,
item
.
mediaUrl
)
...
...
@@ -163,21 +166,27 @@ class AddVideoCourse extends React.Component {
}
else
{
categoryName
=
`
${
categoryOneName
}
`
}
const
_courseChapterVOList
=
courseChapterVOList
.
map
(
item
=>
{
item
.
mediaName
=
item
.
name
;
item
.
resourceId
=
item
.
id
;
return
item
})
this
.
setState
({
loadintroduce
:
!
hasIntro
,
coverId
,
coverUrl
,
videoType
,
videoName
,
videoDuration
,
// videoName,
// videoDuration,
scheduleMedia
,
courseName
,
scheduleVideoId
,
scheduleVideoUrl
,
shelfState
,
whetherVisitorsJoin
,
categoryName
,
categoryId
categoryId
,
courseChapterList
:
_courseChapterVOList
})
})
}
...
...
@@ -195,11 +204,9 @@ class AddVideoCourse extends React.Component {
}
handleGoBack
=
()
=>
{
const
{
coverId
,
videoName
,
videoDuration
,
courseName
,
scheduleMedia
,
scheduleVideoId
,
categoryId
,
shelfState
,
whetherVisitorsJoin
}
=
this
.
state
const
{
coverId
,
courseName
,
scheduleMedia
,
courseChapterList
,
categoryId
,
shelfState
,
whetherVisitorsJoin
}
=
this
.
state
if
(
videoName
||
videoDuration
||
scheduleVideoId
||
!
courseChapterList
.
length
||
!
_
.
isEqual
(
scheduleMedia
,
defaultScheduleMedia
)
||
categoryId
||
courseName
||
...
...
@@ -267,17 +274,16 @@ class AddVideoCourse extends React.Component {
// 显示预览弹窗
handleShowPreviewModal
=
()
=>
{
const
{
coverUrl
,
scheduleVideoUrl
,
courseName
,
scheduleMedia
,
videoDuration
,
introduce
}
=
this
.
state
const
{
coverUrl
,
scheduleVideoUrl
,
courseName
,
scheduleMedia
,
introduce
,
courseChapterList
,
categoryName
}
=
this
.
state
const
courseBasinInfo
=
{
coverUrl
,
scheduleVideoUrl
,
courseName
,
videoDuration
}
const
courseIntroInfo
=
{
liveCourseMediaRequests
:
scheduleMedia
,
introduce
introduce
,
categoryName
}
const
previewCourseModal
=
(
...
...
@@ -290,6 +296,7 @@ class AddVideoCourse extends React.Component {
previewCourseModal
:
null
})
}
}
courseChapterList=
{
courseChapterList
}
/>
)
...
...
@@ -297,24 +304,97 @@ class AddVideoCourse extends React.Component {
}
// 选择视频
handleSelectVideo
=
(
file
)
=>
{
handleSelectVideo
=
(
addFolderIds
,
selectedFileList
)
=>
{
this
.
setState
({
showSelectFileModal
:
false
})
const
{
ossUrl
,
resourceId
,
folderName
,
folderFormat
,
folderSize
}
=
file
let
{
courseChapterList
}
=
this
.
state
;
let
_courseChapterList
=
[...
courseChapterList
];
if
(
selectedFileList
.
length
+
courseChapterList
.
length
>
20
){
message
.
warning
(
`最多只能上传20个文件`
);
return
;
}
selectedFileList
.
map
((
file
,
index
)
=>
{
console
.
log
(
''
)
const
{
ossUrl
,
resourceId
,
folderName
,
folderFormat
,
folderSize
}
=
file
;
console
.
log
(
'folderFormat'
,
folderFormat
);
if
(
folderFormat
===
'MP4'
){
const
videoDom
=
document
.
createElement
(
'video'
)
videoDom
.
src
=
ossUrl
videoDom
.
onloadedmetadata
=
()
=>
{
_courseChapterList
.
push
({
mediaContent
:
resourceId
,
contentType
:
'SCHEDULE'
,
mediaType
:
"VIDEO"
,
mediaName
:
folderName
.
replace
(
'.mp4'
,
''
),
videoDuration
:
videoDom
.
duration
,
resourceId
,
mediaUrl
:
ossUrl
,
sort
:
_courseChapterList
.
length
})
this
.
setState
({
courseChapterList
:
_courseChapterList
})
}
}
else
{
let
_mediaName
=
folderName
;
if
(
folderFormat
===
'PDF'
){
_mediaName
=
folderName
.
replace
(
'.pdf'
,
''
)
}
else
{
_mediaName
=
folderName
.
replace
(
'.doc'
,
''
).
replace
(
'.docx'
,
''
)
}
const
suffix
=
_
.
last
(
folderName
.
split
(
'.'
)).
toUpperCase
();
_courseChapterList
.
push
({
mediaContent
:
resourceId
,
contentType
:
'SCHEDULE'
,
mediaType
:
suffix
,
mediaName
:
_mediaName
,
resourceId
,
mediaUrl
:
ossUrl
,
sort
:
_courseChapterList
.
length
})
this
.
setState
({
courseChapterList
:
_courseChapterList
})
}
})
}
// 校验课节名称
handleValidateChapterName
=
(
chapterName
)
=>
{
let
hasError
=
false
;
return
new
Promise
((
resolve
)
=>
{
if
(
!
chapterName
)
{
this
.
setState
({
chapterNameValidateStatus
:
"error"
,
chapterNameHelpMsg
:
'请输入课节名称'
})
hasError
=
true
;
resolve
(
false
)
return
false
}
if
(
chapterName
.
length
>
40
)
{
this
.
setState
({
chapterNameValidateStatus
:
"error"
,
chapterNameHelpMsg
:
'不要超过40字'
})
hasError
=
true
;
resolve
(
false
)
return
false
}
if
(
!
hasError
){
resolve
(
true
)
this
.
setState
({
size
:
folderSize
,
videoName
:
folderName
,
videoType
:
folderFormat
,
scheduleVideoUrl
:
ossUrl
,
scheduleVideoId
:
resourceId
,
videoDuration
:
videoDom
.
duration
chapterNameValidateStatus
:
""
,
chapterNameHelpMsg
:
""
})
}
})
}
// 保存
...
...
@@ -328,32 +408,26 @@ class AddVideoCourse extends React.Component {
})
return
}
const
{
instId
,
adminId
}
=
window
.
currentUserInstInfo
const
{
id
,
size
,
coverId
,
coverUrl
,
pageType
,
joinType
,
videoName
,
videoDuration
,
studentList
,
// videoName,
// videoDuration,
courseName
,
scheduleMedia
,
scheduleVideoId
,
scheduleVideoUrl
,
categoryId
,
shelfState
,
whetherVisitorsJoin
,
introduce
introduce
,
courseChapterList
,
editorTextLength
,
}
=
this
.
state
const
commonParams
=
{
videoName
,
videoDuration
,
courseMediaId
:
scheduleVideoId
,
// videoName,
videoDuration
:
0
,
//后端的必要参数,不能传空
scheduleMedia
:
scheduleMedia
.
filter
((
item
)
=>
!!
item
.
mediaContent
),
categoryId
,
courseName
,
...
...
@@ -362,10 +436,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
,
courseChapterList
,
categoryId
,
scheduleMedia
,
editorTextLength
).
then
((
res
)
=>
{
if
(
!
res
)
return
Upload
.
uploadTextToOSS
(
introduce
,
`
${
randomString
()}
.txt`
,
(
introduceId
)
=>
{
this
.
submitRemote
({
id
,
pageType
,
commonParams
:
{
...
commonParams
,
introduceId
}
})
...
...
@@ -375,7 +450,7 @@ class AddVideoCourse extends React.Component {
submitRemote
=
({
id
,
pageType
,
commonParams
})
=>
{
if
(
pageType
===
'add'
)
{
Service
.
Hades
(
'public/hades/create
MediaCours
e'
,
commonParams
).
then
((
res
)
=>
{
Service
.
Hades
(
'public/hades/create
VideoSchedul
e'
,
commonParams
).
then
((
res
)
=>
{
if
(
!
res
)
return
message
.
success
(
'新建成功'
)
window
.
RCHistory
.
push
({
...
...
@@ -387,7 +462,7 @@ class AddVideoCourse extends React.Component {
courseId
:
id
,
...
commonParams
}
Service
.
Hades
(
'public/hades/edit
MediaCours
e'
,
editParams
).
then
((
res
)
=>
{
Service
.
Hades
(
'public/hades/edit
VideoSchedul
e'
,
editParams
).
then
((
res
)
=>
{
if
(
!
res
)
return
message
.
success
(
'保存成功'
)
window
.
RCHistory
.
push
({
...
...
@@ -397,15 +472,15 @@ class AddVideoCourse extends React.Component {
}
}
handleValidate
=
(
courseName
,
scheduleVideoId
,
categoryId
,
scheduleMedia
)
=>
{
handleValidate
=
(
courseName
,
courseChapterList
,
categoryId
,
scheduleMedia
,
editorTextLength
)
=>
{
return
new
Promise
((
resolve
)
=>
{
if
(
!
courseName
)
{
message
.
warning
(
'请输入课程名称'
)
resolve
(
false
)
return
false
}
if
(
!
scheduleVideoId
)
{
message
.
warning
(
'请上传
视频
'
)
if
(
!
courseChapterList
.
length
)
{
message
.
warning
(
'请上传
课节
'
)
resolve
(
false
)
return
false
}
...
...
@@ -414,18 +489,19 @@ class AddVideoCourse extends React.Component {
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
}
if
(
editorTextLength
>
1000
)
{
message
.
warning
(
'课程简介超过字数限定'
);
resolve
(
false
);
return
;
}
resolve
(
true
)
})
}
handleSelectCover
=
(
file
)
=>
{
if
(
!
file
){
message
.
info
(
"请选择文件!"
);
return
;
}
this
.
setState
({
visible
:
true
,
imageFile
:
file
...
...
@@ -452,23 +528,166 @@ class AddVideoCourse extends React.Component {
coverId
:
coverId
})
}
handleRenameCourseChapter
=
(
chapterId
,
chapterIndex
)
=>
{
const
{
mediaNameAlias
}
=
this
.
state
;
this
.
handleValidateChapterName
(
mediaNameAlias
).
then
(
res
=>
{
// 校验不通过不能点确定保存修改课节名称
if
(
!
res
)
{
return
message
.
warning
(
'重命名失败'
);
}
let
{
courseChapterList
}
=
this
.
state
;
let
_courseChapterList
=
[];
_courseChapterList
=
courseChapterList
.
map
((
item
,
index
)
=>
{
if
(
item
.
resourceId
===
chapterId
&&
chapterIndex
===
index
){
item
.
mediaName
=
mediaNameAlias
;
item
.
visible
=
false
;
}
return
item
})
this
.
setState
({
courseChapterList
:
_courseChapterList
,
chapterNameValidateStatus
:
''
,
chapterNameHelpMsg
:
''
,
mediaNameAlias
:
''
,
})
});
}
handleChangePopConfirmVisible
=
(
chapterId
,
chapterNameIndex
,
visible
)
=>
{
let
{
courseChapterList
}
=
this
.
state
;
let
_courseChapterList
=
[];
_courseChapterList
=
courseChapterList
.
map
((
item
,
index
)
=>
{
if
(
item
.
resourceId
===
chapterId
&&
chapterNameIndex
===
index
){
item
.
visible
=
visible
}
else
{
item
.
visible
=
false
}
return
item
})
this
.
setState
({
courseChapterList
:
_courseChapterList
,
})
}
handleDeleteCourseChapter
=
(
chapterId
,
chapterIndex
)
=>
{
console
.
log
(
'chapterId---'
,
chapterId
,
chapterIndex
);
let
{
courseChapterList
}
=
this
.
state
;
let
_courseChapterList
=
courseChapterList
.
filter
((
item
,
index
)
=>
{
return
item
.
resourceId
!==
chapterId
||
item
.
resourceId
===
chapterId
&&
chapterIndex
!==
index
})
_courseChapterList
.
map
((
item
,
index
)
=>
{
item
.
sort
=
index
})
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
defaultValue=
{
item
.
mediaName
}
placeholder=
"请输入课节名称"
maxLength=
{
40
}
autoSize
style=
{
{
width
:
'318px'
}
}
onChange=
{
(
e
)
=>
{
this
.
setState
({
mediaNameAlias
:
e
.
target
.
value
.
trim
()
},
()
=>
{
this
.
handleValidateChapterName
(
this
.
state
.
mediaNameAlias
)
})
}
}
/>
</
Form
.
Item
>
</
Form
>
</
div
>
}
// 上下移动
handleChangeIndex
=
(
isUp
,
sortIndex
)
=>
{
const
{
courseChapterList
}
=
this
.
state
;
// 第一个上移和最后一个下移不能使用
if
((
isUp
&&
sortIndex
===
0
)
||
(
!
isUp
&&
sortIndex
===
(
courseChapterList
.
length
-
1
))){
return
;
}
let
_courseChapterList
=
[...
courseChapterList
];
const
temp
=
courseChapterList
[
sortIndex
];
// 若上移
if
(
isUp
){
_courseChapterList
[
sortIndex
-
1
]
=
temp
;
_courseChapterList
[
sortIndex
-
1
].
sort
=
sortIndex
-
1
;
_courseChapterList
[
sortIndex
]
=
courseChapterList
[
sortIndex
-
1
];
_courseChapterList
[
sortIndex
].
sort
=
sortIndex
;
}
else
{
// 若下移
_courseChapterList
[
sortIndex
+
1
]
=
temp
;
_courseChapterList
[
sortIndex
+
1
].
sort
=
sortIndex
+
1
;
_courseChapterList
[
sortIndex
]
=
courseChapterList
[
sortIndex
+
1
];
_courseChapterList
[
sortIndex
].
sort
=
sortIndex
;
}
this
.
setState
({
courseChapterList
:
_courseChapterList
})
}
renderTypemenu
=
()
=>
{
return
<
Menu
>
<
Menu
.
Item
>
<
span
onClick=
{
()
=>
{
this
.
selectFileType
(
"VIDEO"
)}
}
>
视频文件
</
span
>
</
Menu
.
Item
>
<
Menu
.
Item
>
<
span
onClick=
{
()
=>
{
this
.
selectFileType
(
"WORD_PDF"
)}
}
>
资料文件
</
span
>
</
Menu
.
Item
>
</
Menu
>
}
selectFileType
=
(
type
)
=>
{
const
{
courseChapterList
}
=
this
.
state
;
if
(
courseChapterList
.
length
>=
20
)
{
message
.
warning
(
`最多只能上传20个文件`
);
return
;
}
if
(
type
===
"VIDEO"
){
this
.
setState
({
showSelectFileModal
:
true
,
selectTypeList
:[
'MP4'
],
accept
:
'video/mp4'
})
}
else
{
this
.
setState
({
showSelectFileModal
:
true
,
selectTypeList
:[
'DOC'
,
'DOCX'
,
'PDF'
],
accept
:
'.doc,.docx,.pdf'
})
}
}
render
()
{
const
{
pageType
,
courseName
,
scheduleVideoId
,
coverId
,
coverUrl
,
scheduleVideoUrl
,
studentList
,
scheduleMedia
,
showCutModal
,
showSelectFileModal
,
diskList
,
imageFile
,
joinType
,
videoName
,
videoType
,
// videoType,
shelfState
,
categoryName
,
courseCatalogList
,
...
...
@@ -479,17 +698,18 @@ class AddVideoCourse extends React.Component {
cutImageBlob
,
introduce
,
loadintroduce
,
id
id
,
courseChapterList
,
imageFile
,
selectTypeList
,
accept
}
=
this
.
state
// 已选择的上课学员数量
const
hasSelectedStu
=
studentList
.
length
const
courseWareIcon
=
FileVerifyMap
[
videoType
]
?
FileTypeIcon
[
FileVerifyMap
[
videoType
].
type
]
:
FileTypeIcon
[
videoType
]
const
defaultCover
=
'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png'
;
const
isDefaultCover
=
coverUrl
===
defaultCover
||
coverUrl
==
null
;
return
(
<
div
className=
'page add-video-course-page'
>
<
Breadcrumbs
navList=
{
pageType
===
'add'
?
'新建
视频课'
:
'编辑视频
课'
}
goBack=
{
this
.
handleGoBack
}
/>
<
Breadcrumbs
navList=
{
pageType
===
'add'
?
'新建
线上课'
:
'编辑线上
课'
}
goBack=
{
this
.
handleGoBack
}
/>
<
div
className=
'box'
>
<
div
className=
'show-tips'
>
...
...
@@ -501,7 +721,7 @@ class AddVideoCourse extends React.Component {
<
span
className=
'label'
>
课程名称:
</
span
>
<
Input
value=
{
courseName
}
placeholder=
'请输入
视频
课的名称(40字以内)'
placeholder=
'请输入
线上
课的名称(40字以内)'
maxLength=
{
40
}
style=
{
{
width
:
240
}
}
onChange=
{
(
e
)
=>
{
...
...
@@ -512,59 +732,125 @@ 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
>
<
span
className=
'label required'
>
上传课节:
</
span
>
</
div
>
<
div
className=
'sub-content'
>
<
Button
<
div
className=
"btn-wrap"
>
{
/* <Button
onClick={() => {
if(courseChapterList.length >= 20) {
message.warning(`最多只能上传20个文件`);
return;
}
this.setState({
showSelectFileModal: true
})
}
}
>
{
`${pageType === 'add' && !scheduleVideoId ? '选择' : '更换'}视频`
}
</
Button
>
}}>添加视频</Button> */
}
<
div
className=
'select-file'
>
<
Dropdown
overlay=
{
this
.
renderTypemenu
()
}
>
<
Button
>
选择文件
</
Button
>
</
Dropdown
>
</
div
>
<
div
className=
'course-ware--empty'
>
从资料云盘中选择视频
</
div
>
</
div
>
<
div
className=
'tips'
>
课节数量限制20个,文件规格说明
<
Tooltip
title=
"视频支持mp4格式,大小不超过2G;文件支持PDF、docx、doc格式,大小不超过100M"
>
<
i
className=
'icon iconfont'
style=
{
{
cursor
:
'pointer'
,
color
:
'#bfbfbf'
,
fontSize
:
'14px'
}
}
>

</
i
>
</
Tooltip
>
</
div
>
</
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
className=
"course-ware__index"
>
{
index
<
9
?
`0${index + 1 } `
:
`${index + 1 } `
}
</
div
>
<
img
className=
'course-ware__img'
src=
{
FileTypeIcon
[
item
.
mediaType
]
}
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
.
resourceId
}
>
<
div
className=
{
`up ${Number(index) === 0 ? 'disabled':''}`
}
onClick=
{
()
=>
this
.
handleChangeIndex
(
true
,
item
.
sort
,
item
.
resourceId
)
}
>
上移
</
div
>
<
div
className=
"line"
>
|
</
div
>
<
div
className=
{
`down ${Number(index) === (courseChapterList.length - 1) ? 'disabled':''}`
}
onClick=
{
()
=>
this
.
handleChangeIndex
(
false
,
item
.
sort
,
item
.
resourceId
)
}
>
下移
</
div
>
<
div
className=
"line"
>
|
</
div
>
<
Popconfirm
placement=
"topLeft"
className=
"course-chapter-tooltip"
title=
{
this
.
renderChapterTitle
(
item
)
}
color=
'#fff'
trigger=
"click"
overlayClassName=
"chapter-popover"
getPopupContainer=
{
()
=>
document
.
getElementById
(
'course-chapter-list'
)
}
destroyTooltipOnHide=
{
true
}
visible=
{
item
.
visible
}
onConfirm=
{
()
=>
this
.
handleRenameCourseChapter
(
item
.
resourceId
,
index
)
}
icon=
{
null
}
onVisibleChange=
{
(
visible
)
=>
{
!
visible
&&
this
.
setState
({
chapterNameValidateStatus
:
''
,
chapterNameHelpMsg
:
''
,
mediaNameAlias
:
''
,
})
}
}
onCancel=
{
()
=>
this
.
handleChangePopConfirmVisible
(
item
.
resourceId
,
index
,
false
)
}
>
<
div
className=
"rename"
onClick=
{
()
=>
{
this
.
setState
({
mediaNameAlias
:
item
.
mediaName
},
()
=>
{
this
.
handleChangePopConfirmVisible
(
item
.
resourceId
,
index
,
true
)})
}
}
>
重命名
</
div
>
</
Popconfirm
>
<
div
className=
"line"
>
|
</
div
>
<
div
className=
"delete"
onClick=
{
()
=>
this
.
handleDeleteCourseChapter
(
item
.
resourceId
,
index
)
}
>
移除
</
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
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/TwtGPQGE4K.png`
}
alt=
''
/>
</
div
>
</
div
>
</
div
>
<
div
className=
'course-catalog required'
>
<
span
className=
'label'
>
课程分类:
</
span
>
{
pageType
===
'add'
&&
(
<
Cascader
defaultValue=
{
[
categoryName
]
}
defaultValue=
{
[]
}
options=
{
courseCatalogList
}
displayRender=
{
(
label
)
=>
label
.
join
(
'-'
)
}
fieldNames=
{
fieldNames
}
...
...
@@ -622,14 +908,16 @@ class AddVideoCourse extends React.Component {
{
/* 选择备课文件弹窗 */
}
{
showSelectFileModal
&&
(
<
SelectPrepareFileModal
multiple=
{
true
}
operateType=
'select'
selectTypeList=
{
[
'MP4'
]
}
accept=
'video/mp4'
selectTypeList=
{
selectTypeList
}
accept=
{
accept
}
queryTypeEnum=
{
'ONLINE'
}
confirm=
{
{
title
:
'文件过大,无法上传'
,
content
:
'
为保障学员的观看体验,上传的视频大小不能超过2G
'
content
:
'
上传的视频大小不能超过2G,文件大小不能超过100M
'
}
}
tooltip=
{
'
格式支持mp4,大小不超过2G
'
}
tooltip=
{
''
}
isOpen=
{
showSelectFileModal
}
diskList=
{
diskList
}
addVideo=
{
true
}
...
...
src/modules/course-manage/video-course/AddVideoCourse.less
View file @
aef0f0eb
...
...
@@ -49,14 +49,16 @@
}
}
.course-catalog{
margin-bottom:
16
px;
margin-top:
16
px;
margin-bottom:
24
px;
margin-top:
24
px;
}
.course-ware {
display: flex;
align-items: center;
margin-bottom: 4px;
&__index {
width: 18px;
}
&__img {
width: 24px;
margin-right: 4px;
...
...
@@ -75,7 +77,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 +99,7 @@
.empty-img {
width: 298px;
height: 172px;
border: 1px dashed #
EBEBEB
;
border: 1px dashed #
ebebeb
;
border-radius: 4px;
padding: 12px;
color: #999;
...
...
@@ -94,12 +108,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: 12
px;
margin-
top: 8
px;
color: #999;
}
}
...
...
@@ -115,18 +134,97 @@
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: 4
px;
margin-top: 8
px;
color: #999;
}
}
}
.course-chapter-list-wrap {
position: relative;
margin-left: 85px;
border: 1px solid #E8E8E8;
width: 630px;
.course-chapter-total {
height: 40px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
z-index: 10;
background: #fff;
width: 628px;
text-indent: 16px;
line-height: 40px;
}
.course-chapter-list {
max-height: 245px;
min-height: 130px;
overflow-y: auto ;
border-radius: 4px;
padding: 16px;
.course-ware {
display: flex;
align-items: center;
margin-bottom: 22px;
color: #666;
&:last-child {
margin-bottom: 0px;
}
&__img {
width: 24px;
margin-right: 8px;
margin-left: 16px;
}
&__name {
color: #333;
width: 353px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-right: 24px;
}
.course-chapter__opt {
display: flex;
color: #2966ff;
width: 168px;
.line {
color: #bfbfbf;
margin-left: 4px;
margin-right: 4px;
}
.delete,
.rename,
.up,
.down {
cursor: pointer;
}
.up,
.down {
&.disabled {
color: rgba(204, 204, 204, 1);
}
}
}
}
}
}
}
.footer {
position: fixed;
left: 196px;
...
...
@@ -138,10 +236,25 @@
justify-content: flex-end;
padding-right: 72px;
background: #fff;
border-top: 1px solid #
E8E8E8
;
border-top: 1px solid #
ccc2c2
;
z-index: 999;
.ant-btn {
margin-left: 10px;
}
}
}
.chapter-popover {
.ant-popover-message-title {
padding-left: 0;
}
.ant-form-item {
margin-bottom: 0!important;
}
.ant-form-item-explain.ant-form-item-explain-error {
height: 24px;
}
.ant-form-item-explain {
min-height: 0;
}
}
\ No newline at end of file
src/modules/course-manage/video-course/VideoCourseDetail.less
0 → 100644
View file @
aef0f0eb
.video-course-detail {
padding-bottom: 40px;
.box {
.course-detail {
display: flex;
border-bottom: 2px solid #E8E8E8;
padding-bottom: 24px;
&__img {
margin-right: 24px;
width: 300px;
height: 170px;
background: #FFFFFF;
border-radius: 4px;
img {
width: 300px;
height: 170px;
border-radius: 4px;
}
}
&__info {
display: flex;
flex-direction: column;
.info__title {
font-size: 20px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 28px;
margin-bottom: 16px;
}
.info__category, .info__chapterNum {
width: 100%;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 20px;
margin-bottom: 10px;
}
}
}
.course-chapter {
padding-top: 24px;
&__total {
width: 100%;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 20px;
margin-bottom: 24px;
}
&__list {
.course-ware {
display: flex;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
margin-bottom: 28px;
cursor: pointer;
&:hover {
color: #2966FF;
}
&__index {
width: 20px;
}
&__img {
width: 20px;
height: 20px;
margin-left: 16px;
margin-right: 8px;
}
&__name {
width: 700px;
}
}
}
}
}
}
\ No newline at end of file
src/modules/course-manage/video-course/VideoCourseDetail.tsx
0 → 100644
View file @
aef0f0eb
/*
* @Author: wufan
* @Date: 2020-04-28 18:05:30
* @LastEditors: yuananting
* @LastEditTime: 2021-07-15 14:04:21
* @Description: 线上课课程课节详情
*/
import
React
,{
useEffect
,
useState
}
from
'react'
;
import
Breadcrumbs
from
"@/components/Breadcrumbs"
;
import
{
withRouter
}
from
"react-router-dom"
;
import
CourseService
from
'@/domains/course-domain/CourseService'
import
underscore
from
'underscore'
;
import
ScanFileModal
from
'@/modules/resource-disk/modal/ScanFileModal.jsx'
;
import
{
FileTypeIcon
}
from
'@/common/constants/academic/lessonEnum'
import
BaseService
from
"@/domains/basic-domain/baseService"
;
import
{
YZ_APPId
,
YZ_PREVIEW_URL
}
from
'@/domains/basic-domain/constants'
;
import
PreviewFileModal
from
'@/bu-components/PreviewFileModal'
;
import
'./VideoCourseDetail.less'
;
declare
var
getParameterByName
:
any
;
declare
var
window
:
any
;
const
FileTypeIconMap
:
any
=
FileTypeIcon
function
VideoCourseDetail
(){
const
courseId
=
window
.
getParameterByName
(
"courseId"
);
// 课程ID
const
[
coverUrl
,
setCoverUrl
]
=
useState
(
""
);
const
[
videoName
,
setVideoName
]
=
useState
(
""
);
const
[
scheduleVideoUrl
,
setScheduleVideoUrl
]
=
useState
(
""
);
const
[
courseChapterList
,
setCourseChapterList
]
=
useState
<
Array
<
Object
>>
([]);
const
[
courseName
,
setCourseName
]
=
useState
(
""
);
const
[
categoryName
,
setCategoryName
]
=
useState
(
""
);
const
[
scanFileModal
,
setScanFileModal
]
=
useState
<
any
>
(
null
);
const
[
showPreviewModal
,
setShowPreviewModal
]
=
useState
(
false
);
//是否显示loading
// const [previewing,setPreviewing] = useState(false); //是否正在预览
const
[
previewStatus
,
setPreviewStatus
]
=
useState
(
'UPLOAD'
);
//预览文件的生成状态
const
[
url
,
setUrl
]
=
useState
(
''
);
let
previewing
=
false
;
useEffect
(()
=>
{
handleFetchScheudleDetail
(
courseId
);
},[
courseId
])
// 获取线上课详情
function
handleFetchScheudleDetail
(
courseId
:
string
){
CourseService
.
videoScheduleDetail
({
courseId
}).
then
((
res
)
=>
{
const
{
result
=
{}
}
=
res
||
{}
const
{
courseName
,
courseMediaVOS
,
categoryName
,
courseChapterVOList
=
[]
}
=
result
let
coverId
:
string
=
""
;
let
coverUrl
:
string
=
""
;
let
videoType
:
string
=
""
;
let
videoDuration
:
number
=
0
;
let
videoName
:
string
=
""
;
let
scheduleVideoUrl
:
string
=
""
;
courseMediaVOS
.
map
((
item
:
any
)
=>
{
switch
(
item
.
contentType
)
{
case
'COVER'
:
coverId
=
item
.
mediaContent
coverUrl
=
item
.
mediaUrl
break
case
'SCHEDULE'
:
videoDuration
=
item
.
videoDuration
videoName
=
item
.
mediaName
scheduleVideoUrl
=
item
.
mediaUrl
videoType
=
item
.
mediaType
break
default
:
break
}
return
item
})
setCoverUrl
(
coverUrl
);
setVideoName
(
videoName
);
setScheduleVideoUrl
(
scheduleVideoUrl
);
setCourseName
(
courseName
);
setCategoryName
(
categoryName
);
setCourseChapterList
(
courseChapterVOList
);
})
}
async
function
handleScanFileModal
(
fileType
:
string
=
"MP4"
,
fileObj
:
any
){
const
{
fileVersionId
,
name
,
mediaUrl
,
id
}
=
fileObj
;
if
(
fileType
===
"VIDEO"
){
const
scanFileModal
=
(
<
ScanFileModal
fileType=
{
fileType
}
item=
{
fileObj
}
close=
{
()
=>
{
setScanFileModal
(
null
);
}
}
/>
);
setScanFileModal
(
scanFileModal
)
}
else
{
if
(
!
fileVersionId
){
setShowPreviewModal
(
true
);
previewing
=
true
;
setPreviewStatus
(
'UPLOAD'
);
const
uploadParams
=
{
fileUrl
:
mediaUrl
,
yoZoTypeEnum
:
'UPLOAD'
}
const
uploadSign
:
any
=
await
BaseService
.
getYoZoSign
(
uploadParams
);
BaseService
.
yoZoUpload
(
mediaUrl
,
YZ_APPId
,
uploadSign
).
then
(
async
function
(
response
){
const
saveParams
=
{
fileVersionId
:
response
.
data
.
data
.
fileVersionId
,
courseChapterId
:
id
}
BaseService
.
saveFileVersionIdByCourseChapter
(
saveParams
);
if
(
previewing
){
const
previewParams
=
{
fileVersionId
:
response
.
data
.
data
.
fileVersionId
,
yoZoTypeEnum
:
'VIEW'
,
htmlTitle
:
name
}
const
previewSign
=
await
BaseService
.
getYoZoSign
(
previewParams
);
const
url
=
`
${
YZ_PREVIEW_URL
}
?fileVersionId=
${
response
.
data
.
data
.
fileVersionId
}
&appId=
${
YZ_APPId
}
&sign=
${
previewSign
}
&htmlTitle=
${
name
}
`
setPreviewStatus
(
'UPLOAD_SUCCESS'
);
setUrl
(
url
)
}
})
}
else
{
const
previewParams
=
{
fileVersionId
,
yoZoTypeEnum
:
'VIEW'
,
htmlTitle
:
name
}
const
previewSign
=
await
BaseService
.
getYoZoSign
(
previewParams
);
const
url
=
`
${
YZ_PREVIEW_URL
}
?fileVersionId=
${
fileVersionId
}
&appId=
${
YZ_APPId
}
&sign=
${
previewSign
}
&htmlTitle=
${
name
}
`
const
a
=
document
.
createElement
(
'a'
);
document
.
body
.
appendChild
(
a
);
a
.
setAttribute
(
'href'
,
url
);
a
.
setAttribute
(
'target'
,
'_blank'
);
a
.
click
();
document
.
body
.
removeChild
(
a
)
}
}
}
// function previewingSet(){
// return new Promise(async (resolve) => {
// setShowPreviewModal(true);
// setPreviewing(true);
// setPreviewStatus('UPLOAD');
// resolve(true);
// })
// }
function
cancelPreview
(){
setShowPreviewModal
(
false
);
// setPreviewing(false);
previewing
=
false
;
setPreviewStatus
(
'UPLOAD'
);
}
return
<
div
className=
"page video-course-detail"
>
<
Breadcrumbs
navList=
"课程详情"
goBack=
{
()
=>
{
window
.
RCHistory
.
goBack
();
}
}
/>
<
div
className=
"box"
>
<
div
className=
"course-detail"
>
<
div
className=
'course-detail__img'
>
{
/* 如果视频和封面都没有上传的话, 那么就显示缺省, 如果上传了视频, 那么封面图就默认显示视频的第一帧, 如果上传了封面图, 那么就显示上传的封面图 */
}
<
img
src=
{
coverUrl
||
`https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png`
}
alt=
''
/>
</
div
>
<
div
className=
"course-detail__info"
>
<
div
className=
"info__title"
>
{
courseName
}
</
div
>
<
div
className=
"info__category"
>
{
`分类:${categoryName}`
}
</
div
>
<
div
className=
"info__chapterNum"
>
{
`课节数量:${courseChapterList.length}`
}
</
div
>
</
div
>
</
div
>
<
div
className=
"course-chapter"
>
<
div
className=
"course-chapter__total"
>
{
`共${courseChapterList.length}个课节`
}
</
div
>
<
div
className=
"course-chapter__list"
>
<
If
condition=
{
courseChapterList
.
length
>
0
}
>
{
underscore
.
map
(
courseChapterList
,(
item
:
any
,
index
:
number
)
=>
{
return
<
div
className=
'course-ware'
onClick=
{
()
=>
{
handleScanFileModal
(
item
.
mediaType
,
item
)}
}
key=
{
index
}
>
<
div
className=
"course-ware__index"
>
{
index
<
9
?
`0${index + 1 } `
:
index
+
1
}
</
div
>
<
img
className=
'course-ware__img'
src=
{
FileTypeIconMap
[
item
.
mediaType
]
}
alt=
''
/>
<
div
className=
'course-ware__name'
>
{
item
.
name
}
</
div
>
</
div
>
})
}
</
If
>
</
div
>
</
div
>
</
div
>
{
showPreviewModal
&&
<
PreviewFileModal
onCancel=
{
()
=>
cancelPreview
()
}
previewStatus=
{
previewStatus
}
url=
{
url
}
/>
}
{
scanFileModal
}
</
div
>
}
export
default
withRouter
(
VideoCourseDetail
);
\ No newline at end of file
src/modules/course-manage/video-course/components/AddVideoIntro.jsx
View file @
aef0f0eb
...
...
@@ -111,40 +111,53 @@ class AddVideoIntro extends React.Component {
<
span
className=
'label'
>
观看设置:
</
span
>
<
div
className=
'content'
>
<
div
>
<
Switch
checked=
{
whetherVisitorsJoin
===
'YES'
?
true
:
false
}
onChange=
{
this
.
whetherVisitorsJoinChange
}
/>
<
Switch
checked=
{
whetherVisitorsJoin
===
"NO"
?
true
:
false
}
onChange=
{
this
.
whetherVisitorsJoinChange
}
/>
</
div
>
<
div
>
<
div
className=
'desc'
>
<
div
>
开启:允许未绑定手机号的学员观看
</
div
>
<
div
>
关闭:仅限绑定了手机号的学员可以进入观看视频
</
div
>
<
div
className=
"desc"
>
<
Choose
>
<
When
condition=
{
whetherVisitorsJoin
===
"NO"
}
>
<
div
>
已开启,学员需绑定手机号才可观看
</
div
>
</
When
>
<
Otherwise
>
<
div
>
已关闭,学员无需绑定手机号即可观看
</
div
>
</
Otherwise
>
</
Choose
>
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
'store-show'
>
<
span
className=
'label'
>
学院展示:
</
span
>
<
div
className=
'content'
>
<
div
className=
"store-show"
>
<
span
className=
"label"
>
学院展示:
</
span
>
<
div
className=
"content"
>
<
Row
>
<
Col
span=
{
3
}
>
<
Switch
checked=
{
shelfState
===
'YES'
?
true
:
false
}
onChange=
{
this
.
shelfStateChange
}
/>
<
Switch
checked=
{
shelfState
===
"YES"
?
true
:
false
}
onChange=
{
this
.
shelfStateChange
}
/>
</
Col
>
<
Col
span=
{
21
}
>
<
div
className=
'desc'
>
<
div
>
开启:此视频将在学员学院的视频列表中出现
</
div
>
<
div
>
关闭:此视频将在学员学院的视频列表中隐藏
</
div
>
<
div
className=
"desc"
>
<
Choose
>
<
When
condition=
{
shelfState
===
"YES"
}
>
<
div
>
已开启,课程将在该学院的学员课程列表中显示
</
div
>
</
When
>
<
Otherwise
>
<
div
>
已关闭,课程将在该学院的学员课程列表中隐藏
</
div
>
</
Otherwise
>
</
Choose
>
</
div
>
</
Col
>
</
Row
>
</
div
>
</
div
>
<
div
className=
'introduce'
>
<
span
className=
'label'
>
视频课
简介:
</
span
>
<
div
className=
'content'
>
<
div
className=
'intro-list'
>
<
div
className=
'intro-list__item introduce-editor'
>
{
(
!
id
||
loadintroduce
)
&&
(
<
div
className=
"introduce"
>
<
span
className=
"label"
>
课程
简介:
</
span
>
<
div
className=
"content"
>
<
div
className=
"intro-list"
>
<
div
className=
"intro-list__item introduce-editor"
>
{
(
!
id
||
loadintroduce
)
&&
<
GraphicsEditor
id=
'intro'
maxLimit=
{
1000
}
id=
"intro"
isIntro=
{
true
}
detail=
{
{
content
:
introduce
,
...
...
@@ -153,7 +166,7 @@ class AddVideoIntro extends React.Component {
this
.
changeIntro
(
val
);
}
}
/>
)
}
}
</
div
>
</
div
>
</
div
>
...
...
src/modules/course-manage/video-course/components/AddVideoIntro.less
View file @
aef0f0eb
...
...
@@ -32,14 +32,17 @@
}
.store-show{
display:flex;
margin-top:
16
px;
margin-bottom:
16
px;
margin-top:
24
px;
margin-bottom:
24
px;
.desc{
margin-left:1
6
px;
margin-left:1
1
px;
font-size:14px;
color:#999;
display:inline-block;
}
.content {
width: 400px;
}
}
.radio {
display: block;
...
...
src/modules/course-manage/video-course/components/ChapterList.jsx
0 → 100644
View file @
aef0f0eb
import
React
from
'react'
;
import
'./ChapterList.less'
;
function
ChapterList
(
props
){
const
{
courseChapterList
}
=
props
;
return
<
div
className=
'chapter-list-component'
>
<
If
condition=
{
courseChapterList
.
length
>
0
}
>
{
_
.
map
(
courseChapterList
,(
item
,
index
)
=>
{
return
<
div
className=
'course-ware'
>
<
div
className=
'course-ware__index'
>
{
index
<
9
?
`0${index + 1 } `
:
`${index + 1 } `
}
</
div
>
<
div
className=
"course-ware__detail"
>
<
div
className=
'course-ware__detail__name'
>
{
item
.
mediaName
}
</
div
>
{
item
.
mediaType
===
'VIDEO'
&&
<
div
className=
'course-ware__detail__duration'
>
{
window
.
formatDuration
(
item
.
videoDuration
)
}
</
div
>
}
</
div
>
</
div
>
})
}
</
If
>
</
div
>
}
export
default
ChapterList
;
\ No newline at end of file
src/modules/course-manage/video-course/components/ChapterList.less
0 → 100644
View file @
aef0f0eb
.chapter-list-component {
.course-ware {
display: flex;
padding: 10px 0;
border-bottom: 1px dashed #EEEEEE;
&:last-child {
border-bottom: none;
}
&__index {
width: 18px;
height: 18px;
font-size: 13px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #999999;
line-height: 18px;
}
&__detail {
display: flex;
flex-direction: column;
width: calc(~'100% - 18px');
&__name {
width: 267px;
font-size: 13px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
line-height: 21px;
margin-bottom: 4px;
}
&__duration {
color: #999999;
}
}
}
}
\ No newline at end of file
src/modules/course-manage/video-course/components/LearningDetailModal.jsx
0 → 100644
View file @
aef0f0eb
import
User
from
"@/common/js/user"
;
import
CourseService
from
"@/domains/course-domain/CourseService"
;
import
React
from
"react"
;
import
{
Modal
}
from
"antd"
;
import
{
FileTypeIcon
}
from
'@/common/constants/academic/lessonEnum'
import
"./LearningDetailModal.less"
;
class
LearningDetailModal
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
courseChapterList
:
[],
};
}
componentDidMount
()
{
this
.
handleFetchDataList
();
}
// 获取观看视频数据列表
handleFetchDataList
=
()
=>
{
const
{
courseId
,
data
}
=
this
.
props
;
const
params
=
{
courseId
,
storeId
:
User
.
getStoreId
(),
storeCustomerId
:
data
.
storeCustomerId
,
};
CourseService
.
lineDetailWatchInfo
(
params
).
then
((
res
)
=>
{
const
{
result
=
[]
}
=
res
;
this
.
setState
({
courseChapterList
:
result
});
});
};
render
()
{
const
{
title
,
onClose
,
onOk
}
=
this
.
props
;
const
{
courseChapterList
}
=
this
.
state
;
return
(
<
Modal
footer=
{
null
}
visible=
{
true
}
title=
{
title
}
width=
{
680
}
maskClosable=
{
false
}
closeIcon=
{
<
span
className=
"icon iconfont modal-close-icon"
>

</
span
>
}
onCancel=
{
onClose
}
onOk=
{
onOk
}
className=
"learning-detail-modal"
>
<
div
className=
"course-chapter__list"
>
<
If
condition=
{
courseChapterList
.
length
>
0
}
>
{
_
.
map
(
courseChapterList
,
(
item
,
index
)
=>
{
return
(
<
div
className=
"course-wrap"
>
<
div
className=
"course-ware"
key=
{
index
}
>
<
div
className=
"course-ware__index"
>
{
`${
index < 9 ? `
0
$
{
index
+
1
}
` : index + 1
} `
}
</
div
>
<
img
className=
'course-ware__img'
src=
{
FileTypeIcon
[
item
.
mediaType
]
}
alt=
''
/>
<
div
className=
"course-ware__name"
>
{
item
.
courseChapterName
&&
item
.
courseChapterName
.
replace
(
'.MP4'
,
''
)
}
</
div
>
</
div
>
<
div
className=
{
`progress ${item.progress === 100 ? 'finish' :''}`
}
>
{
`${item.progress === 100 ? '已完成' : `
$
{
item
.
progress
}
%
`}`
}
</
div
>
</
div
>
);
})
}
</
If
>
</
div
>
</
Modal
>
);
}
}
export
default
LearningDetailModal
;
src/modules/course-manage/video-course/components/LearningDetailModal.less
0 → 100644
View file @
aef0f0eb
.learning-detail-modal {
.course-chapter {
padding-top: 24px;
&__list {
.course-wrap {
display: flex;
justify-content: space-between;
.course-ware {
display: flex;
height: 20px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #666666;
line-height: 20px;
margin-bottom: 28px;
&__index {
width: 20px;
}
&__img {
width: 20px;
height: 20px;
margin-left: 12px;
margin-right: 8px;
}
&__name {
width: 500px;
}
}
.progress {
&.finish {
color: RGBA(101, 202, 168, 1);
}
}
}
}
}
}
src/modules/course-manage/video-course/components/VideoCourseFilter.jsx
View file @
aef0f0eb
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:11:57
* @LastEditors:
fusanqias
ng
* @LastEditTime: 2021-0
5-28 20:14:3
7
* @Description:
视频
课-搜索模块
* @LastEditors:
yuananti
ng
* @LastEditTime: 2021-0
7-12 14:14:4
7
* @Description:
线上
课-搜索模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
...
...
@@ -56,22 +56,17 @@ class VideoCourseFilter extends React.Component {
this
.
getTeacherList
()
this
.
queryCategoryTree
()
}
// 查询分类树
queryCategoryTree
=
(
categoryName
=
''
)
=>
{
let
query
=
{
bizType
:
'QUESTION'
,
source
:
2
,
categoryName
,
tenantId
:
User
.
getStoreId
(),
userId
:
User
.
getStoreUserId
(),
count
:
false
,
queryCategoryTree
=
()
=>
{
const
params
=
{
storeId
:
User
.
getStoreId
()
}
AidToolService
.
queryExternalCategoryTree
(
query
).
then
((
res
)
=>
{
const
{
categoryList
=
[]
}
=
res
.
result
console
.
log
(
this
.
renderTreeNodes
(
categoryList
))
this
.
setState
({
categoryList
:
this
.
renderTreeNodes
(
categoryList
),
categoryName
,
StoreService
.
getStoreDetail
(
params
).
then
((
res
)
=>
{
const
{
result
=
{}
}
=
res
;
result
.
coursePackageId
&&
AidToolService
.
queryCategoryTreeByPackage
({
coursePackageId
:
result
.
coursePackageId
}).
then
((
res
)
=>
{
const
{
result
=
[]
}
=
res
;
this
.
setState
({
categoryList
:
this
.
renderTreeNodes
(
result
)})
})
})
}
...
...
@@ -192,10 +187,10 @@ class VideoCourseFilter extends React.Component {
<
Row
type=
'flex'
justify=
'space-between'
align=
'top'
>
<
div
className=
'search-condition'
>
<
div
className=
'search-condition__item'
>
<
span
className=
'search-name'
>
视频
课名称:
</
span
>
<
span
className=
'search-name'
>
线上
课名称:
</
span
>
<
Search
value=
{
courseName
}
placeholder=
'搜索
视频
课名称'
placeholder=
'搜索
线上
课名称'
onChange=
{
(
e
)
=>
{
this
.
handleChangeQuery
(
'courseName'
,
e
.
target
.
value
)
}
}
...
...
src/modules/course-manage/video-course/components/VideoCourseList.jsx
View file @
aef0f0eb
import
{
Dropdown
,
message
,
Modal
,
Switch
,
Tooltip
}
from
'antd'
;
import
User
from
'@/common/js/user'
;
import
{
PageControl
}
from
'@/components'
;
import
{
LIVE_SHARE
}
from
'@/domains/course-domain/constants'
;
import
CourseService
from
'@/domains/course-domain/CourseService'
;
import
ShareLiveModal
from
'@/modules/course-manage/modal/ShareLiveModal'
;
import
React
from
'react'
;
import
RelatedPlanModal
from
'../../modal/RelatedPlanModal'
;
import
WatchDataModal
from
'../modal/WatchDataModal'
;
import
React
from
"react"
import
{
Modal
,
message
,
Tooltip
,
Switch
,
Dropdown
}
from
"antd"
import
_
from
"underscore"
import
{
PageControl
}
from
"@/components"
import
{
LIVE_SHARE
}
from
"@/domains/course-domain/constants"
import
{
Route
,
withRouter
}
from
'react-router-dom'
;
import
ShareLiveModal
from
"@/modules/course-manage/modal/ShareLiveModal"
import
CourseService
from
"@/domains/course-domain/CourseService"
import
RelatedPlanModal
from
"../../modal/RelatedPlanModal"
import
User
from
"@/common/js/user"
import
VideoCourseDetail
from
'../VideoCourseDetail'
;
import
WatchData
from
"./WatchData"
;
import
{
XMTable
}
from
'@/components'
;
import
college
from
'@/common/lottie/college'
;
import
'./VideoCourseList.less'
;
import
"./VideoCourseList.less"
class
VideoCourseList
extends
React
.
Component
{
constructor
(
props
)
{
...
...
@@ -32,21 +35,20 @@ class VideoCourseList extends React.Component {
}
}
// 跳转课程详情页
handleLinkToCourseDetail
=
(
courseId
)
=>
{
const
{
match
}
=
this
.
props
;
window
.
RCHistory
.
push
(
`
${
match
.
url
}
/video-course-detail?courseId=
${
courseId
}
`
)
}
// 观看数据弹窗
handleShowWatchDataModal
=
(
record
)
=>
{
const
watchDataModal
=
(
<
WatchDataModal
type=
'videoCourseList'
data=
{
record
}
close=
{
()
=>
{
this
.
setState
({
watchDataModal
:
null
,
});
}
}
/>
);
this
.
setState
({
watchDataModal
});
};
handleShowWatchDataModal
=
(
item
)
=>
{
const
{
match
}
=
this
.
props
;
window
.
RCHistory
.
push
({
pathname
:
`
${
match
.
url
}
/course-data?courseName=
${
item
.
courseName
}
&courseId=
${
item
.
id
}
`
})
}
// 请求表头
parseColumns
=
()
=>
{
...
...
@@ -54,23 +56,21 @@ class VideoCourseList extends React.Component {
const
{
ShelfLoading
}
=
this
.
state
;
const
columns
=
[
{
title
:
'视频课'
,
key
:
'scheduleName'
,
dataIndex
:
'scheduleName'
,
title
:
"线上课"
,
key
:
"scheduleName"
,
dataIndex
:
"scheduleName"
,
width
:
321
,
fixed
:
'left'
,
render
:
(
val
,
record
)
=>
{
const
{
coverUrl
,
scheduleVideoUrl
}
=
record
;
const
{
coverUrl
}
=
record
;
return
(
<
div
className=
'record__item'
>
{
/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */
}
<
img
className=
'course-cover'
src=
{
coverUrl
||
(
type
===
'internal'
?
`${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`
:
'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png'
)
coverUrl
||
'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png'
}
alt=
''
alt=
'
封面图
'
/>
<
Choose
>
<
When
condition=
{
record
.
courseName
.
length
>
25
}
>
...
...
@@ -99,9 +99,9 @@ class VideoCourseList extends React.Component {
</
If
>
</
span
>
),
key
:
'categoryName'
,
dataIndex
:
'categoryName'
,
width
:
20
0
,
key
:
"categoryName"
,
dataIndex
:
"categoryName"
,
width
:
15
0
,
render
:
(
val
,
record
)
=>
{
return
(
<
Choose
>
...
...
@@ -119,21 +119,15 @@ class VideoCourseList extends React.Component {
},
},
{
title
:
'创建人'
,
key
:
'createName'
,
dataIndex
:
'createName'
,
title
:
'课节数'
,
key
:
'chapterNum'
,
dataIndex
:
'chapterNum'
,
className
:
"chapterNum"
,
width
:
100
,
render
:
(
val
)
=>
{
return
(
<
div
>
{
val
&&
(
<
Tooltip
title=
{
val
}
>
<
div
>
{
val
.
length
>
4
?
`${val.slice(0, 4)}
...
`
:
val
}
</
div
>
</
Tooltip
>
)
}
</
div
>
);
},
align
:
'right'
,
render
:
(
val
,
item
)
=>
{
return
<
div
onClick=
{
()
=>
this
.
handleLinkToCourseDetail
(
item
.
id
)
}
>
{
val
||
1
}
</
div
>
}
},
{
title
:
(
...
...
@@ -171,13 +165,31 @@ class VideoCourseList extends React.Component {
},
},
{
title
:
'观看学员数'
,
width
:
110
,
key
:
'watchUserCount'
,
dataIndex
:
'watchUserCount'
,
title
:
"观看学员数"
,
width
:
150
,
key
:
"watchUserCount"
,
dataIndex
:
"watchUserCount"
,
align
:
'right'
,
render
:
(
val
,
item
)
=>
{
return
<
div
className=
'watchUserCount'
>
{
val
||
0
}
</
div
>;
return
<
div
className=
'watchUserCount'
onClick=
{
()
=>
this
.
handleShowWatchDataModal
(
item
)
}
>
{
val
||
0
}
</
div
>
}
},
{
title
:
"创建人"
,
key
:
"createName"
,
dataIndex
:
"createName"
,
width
:
100
,
render
:
(
val
)
=>
{
return
(
<
div
>
{
val
&&
(
<
Tooltip
title=
{
val
}
>
<
div
>
{
val
.
length
>
4
?
`${val.slice(0, 4)}
...
`
:
val
}
</
div
>
</
Tooltip
>
)
}
</
div
>
)
}
},
{
title
:
'创建时间'
,
...
...
@@ -231,22 +243,17 @@ class VideoCourseList extends React.Component {
title
:
'操作'
,
key
:
'operate'
,
dataIndex
:
'operate'
,
width
:
21
0
,
width
:
16
0
,
fixed
:
'right'
,
render
:
(
val
,
record
)
=>
{
return
(
<
div
className=
'operate'
>
<
div
className=
'operate__item'
onClick=
{
()
=>
this
.
handleShowWatchDataModal
(
record
)
}
>
观看数据
</
div
>
<
If
condition=
{
type
===
'internal'
}
>
<
span
className=
'operate__item split'
>
|
</
span
>
<
If
condition=
{
type
===
"internal"
}
>
<
div
className=
'operate__item'
onClick=
{
()
=>
this
.
handleShowShareModal
(
record
)
}
>
分享
</
div
>
</
If
>
<
span
className=
'operate__item split'
>
|
</
span
>
</
If
>
<
Dropdown
overlay=
{
this
.
renderMoreOperate
(
record
)
}
>
<
span
className=
'more-operate'
>
<
span
className=
'operate-text'
>
更多
</
span
>
...
...
@@ -261,7 +268,7 @@ class VideoCourseList extends React.Component {
},
];
type
!==
'internal'
&&
columns
.
splice
(
2
,
1
);
type
!==
'internal'
&&
columns
.
splice
(
5
,
1
);
return
columns
;
};
...
...
@@ -328,7 +335,7 @@ class VideoCourseList extends React.Component {
});
};
// 删除
视频
课
// 删除
线上
课
handleDeleteVideoCourse
=
(
scheduleId
)
=>
{
Modal
.
confirm
({
title
:
'你确定要删除此视频课吗?'
,
...
...
@@ -370,7 +377,7 @@ class VideoCourseList extends React.Component {
data=
{
shareData
}
type=
'videoClass'
courseDivision=
{
type
}
title=
'
视频
课'
title=
'
线上
课'
close=
{
()
=>
{
this
.
setState
({
shareLiveModal
:
null
,
...
...
@@ -447,7 +454,7 @@ class VideoCourseList extends React.Component {
);
};
render
()
{
const
{
dataSource
=
[],
totalCount
,
query
,
type
}
=
this
.
props
;
const
{
dataSource
=
[],
totalCount
,
query
,
type
,
match
}
=
this
.
props
;
const
{
current
,
size
}
=
query
;
const
{
RelatedPlanModalVisible
,
selectPlanList
,
selectCourseId
}
=
this
.
state
;
return
(
...
...
@@ -466,16 +473,6 @@ class VideoCourseList extends React.Component {
scroll=
{
{
x
:
1500
}
}
className=
'video-list-table'
/>
{
/* <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
...
...
@@ -500,9 +497,11 @@ class VideoCourseList extends React.Component {
)
}
{
this
.
state
.
shareLiveModal
}
{
this
.
state
.
watchDataModal
}
<
Route
path=
{
`${match.url}/video-course-detail`
}
component=
{
VideoCourseDetail
}
/>
<
Route
path=
{
`${match.url}/course-data`
}
component=
{
WatchData
}
/>
</
div
>
);
}
}
export
default
VideoCourseList
;
export
default
withRouter
(
VideoCourseList
)
;
src/modules/course-manage/video-course/components/VideoCourseList.less
View file @
aef0f0eb
...
...
@@ -24,11 +24,17 @@
}
}
}
.chapterNum {
color: #2966ff;
cursor: pointer;
}
}
}
.watchUserCount {
text-align: right;
padding: 16px;
color: #2966ff;
cursor: pointer;
}
.operate-text {
color: #2966FF;
...
...
src/modules/course-manage/video-course/components/VieoCourseOpt.jsx
View file @
aef0f0eb
...
...
@@ -3,7 +3,7 @@
* @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27
* @Description:
视频
课-操作模块
* @Description:
线上
课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
...
...
@@ -22,7 +22,7 @@ export default function VideoCourseOpt() {
RCHistory
.
push
(
'/create-video-course?type=add'
);
}
}
className=
"mr12"
>
新建
视频
课
</
Button
>
>
新建
线上
课
</
Button
>
</
div
>
);
}
src/modules/course-manage/video-course/components/WatchData.jsx
0 → 100644
View file @
aef0f0eb
import
User
from
"@/common/js/user"
;
import
college
from
"@/common/lottie/college"
;
import
{
PageControl
,
XMTable
}
from
"@/components"
;
import
Breadcrumbs
from
"@/components/Breadcrumbs"
;
import
CourseService
from
"@/domains/course-domain/CourseService"
;
import
{
Input
,
Select
}
from
"antd"
;
import
React
from
"react"
;
import
{
withRouter
}
from
"react-router-dom"
;
import
LearningDetailModal
from
"./LearningDetailModal"
;
import
"./WatchData.less"
;
const
{
Search
}
=
Input
;
class
WatchData
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
const
courseId
=
window
.
getParameterByName
(
"courseId"
);
const
courseName
=
window
.
getParameterByName
(
"courseName"
);
this
.
state
=
{
courseName
,
courseId
,
visible
:
true
,
dataSource
:
[],
query
:
{
current
:
1
,
size
:
10
,
sourceEnum
:
null
,
},
totalCount
:
0
,
learningDetailModal
:
null
,
};
}
componentDidMount
()
{
this
.
handleFetchDataList
();
}
// 获取观看视频数据列表
handleFetchDataList
=
()
=>
{
const
{
query
,
courseId
}
=
this
.
state
;
const
params
=
{
...
query
,
courseId
:
courseId
,
storeId
:
User
.
getStoreId
(),
};
CourseService
.
videoWatchInfo
(
params
).
then
((
res
)
=>
{
const
{
result
=
{}
}
=
res
;
const
{
records
=
[],
total
=
0
}
=
result
;
this
.
setState
({
dataSource
:
records
,
totalCount
:
Number
(
total
),
});
});
};
parseColumns
=
()
=>
{
const
SOURCENUM
=
{
WE_CHAT
:
"微信"
,
WORK_WE_CHAT
:
"企业微信"
,
};
const
columns
=
[
{
title
:
"观看学员"
,
key
:
"name"
,
dataIndex
:
"name"
,
render
:
(
val
,
item
)
=>
{
return
(
<
div
className=
"watcher"
>
<
div
className=
"watcher__name"
>
{
val
}
</
div
>
<
div
className=
{
`watcher__type ${item.sourceEnum}`
}
>
@
{
SOURCENUM
[
item
.
sourceEnum
]
}
</
div
>
</
div
>
);
},
},
{
title
:
"手机号"
,
key
:
"phone"
,
dataIndex
:
"phone"
,
},
{
title
:
"首次观看时间"
,
key
:
"firstWatch"
,
dataIndex
:
"firstWatch"
,
render
:
(
val
)
=>
{
return
formatDate
(
"YYYY-MM-DD H:i"
,
val
);
},
},
{
title
:
"学习进度"
,
key
:
"progress"
,
dataIndex
:
"progress"
,
render
:
(
val
)
=>
{
return
<
span
>
{
val
}
%
</
span
>;
},
},
{
title
:
"操作"
,
key
:
"operate"
,
dataIndex
:
"operate"
,
render
:
(
val
,
record
)
=>
{
return
(
<
div
className=
"operate"
>
<
div
className=
"operate__item"
onClick=
{
()
=>
this
.
ShowLearningDetailModal
(
record
)
}
>
学习详情
</
div
>
</
div
>
);
},
},
];
return
columns
;
};
// 显示学员数据详情弹窗
ShowLearningDetailModal
=
(
item
)
=>
{
const
learningDetailModal
=
(
<
LearningDetailModal
data=
{
item
}
title=
"学习详情"
onClose=
{
()
=>
{
this
.
setState
({
learningDetailModal
:
null
});
}
}
courseId=
{
this
.
state
.
courseId
}
/>
);
this
.
setState
({
learningDetailModal
});
};
// 搜索学员姓名或手机号
handleChangNickname
=
(
value
)
=>
{
const
isPhone
=
(
value
||
""
).
match
(
/^
\d
+$/
);
const
{
query
}
=
this
.
state
;
let
_query
=
{
...
query
};
if
(
isPhone
)
{
_query
.
phone
=
value
;
_query
.
nickName
=
null
;
}
else
{
_query
.
nickName
=
value
;
_query
.
phone
=
null
;
}
_query
.
current
=
1
;
this
.
setState
(
{
query
:
_query
,
},
this
.
handleFetchDataList
);
};
// 搜索学员类型
handleWatchTypeSelect
=
(
sourceEnum
)
=>
{
const
{
query
}
=
this
.
state
;
let
_query
=
{
...
query
};
_query
.
sourceEnum
=
sourceEnum
;
this
.
setState
(
{
query
:
_query
,
},
this
.
handleFetchDataList
);
};
render
()
{
const
{
dataSource
,
totalCount
,
query
,
courseName
}
=
this
.
state
;
const
{
current
,
size
,
sourceEnum
}
=
query
;
return
(
<
div
className=
"page video-course-watch-data"
>
<
Breadcrumbs
navList=
"观看学员数据"
goBack=
{
()
=>
{
window
.
RCHistory
.
goBack
();
}
}
/>
<
div
className=
"box"
>
<
div
className=
"box-header"
>
<
div
className=
"course-detail"
>
<
div
className=
"detail__line"
></
div
>
<
div
className=
"detail__name"
>
{
courseName
}
</
div
>
</
div
>
<
div
className=
"filter-box"
>
<
div
className=
"watcher-name"
>
<
span
className=
"label"
>
学员:
</
span
>
<
Search
placeholder=
"搜索学员姓名/手机号"
style=
{
{
width
:
200
}
}
onChange=
{
(
e
)
=>
{
this
.
handleChangNickname
(
e
.
target
.
value
);
}
}
onSearch=
{
()
=>
{
this
.
handleFetchDataList
();
}
}
enterButton=
{
<
span
className=
"icon iconfont"
>

</
span
>
}
/>
</
div
>
<
div
className=
"watch-type"
>
<
span
className=
"label"
>
学员类型:
</
span
>
<
Select
showSearch
style=
{
{
width
:
200
}
}
allowClear
onPopupScroll=
{
this
.
handleFetchMore
}
placeholder=
"请选择学员类型"
value=
{
sourceEnum
}
onChange=
{
this
.
handleWatchTypeSelect
}
>
<
Select
.
Option
tyle=
{
{
textAlign
:
"center"
}
}
value=
"WE_CHAT"
>
微信
</
Select
.
Option
>
<
Select
.
Option
tyle=
{
{
textAlign
:
"center"
}
}
value=
"WORK_WE_CHAT"
>
企业微信
</
Select
.
Option
>
</
Select
>
</
div
>
</
div
>
</
div
>
<
XMTable
renderEmpty=
{
{
image
:
college
,
description
:
"暂无数据"
,
}
}
rowKey=
{
(
record
)
=>
record
.
id
}
dataSource=
{
dataSource
}
columns=
{
this
.
parseColumns
()
}
pagination=
{
false
}
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
.
setState
(
{
query
:
_query
,
},
()
=>
{
this
.
handleFetchDataList
();
}
);
}
}
/>
</
div
>
</
div
>
{
this
.
state
.
learningDetailModal
}
</
div
>
);
}
}
export
default
withRouter
(
WatchData
);
src/modules/course-manage/video-course/components/WatchData.less
0 → 100644
View file @
aef0f0eb
.video-course-watch-data {
.watcher {
display: flex;
&__type {
margin-left: 8px;
&.WE_CHAT {
color:rgba(28, 172, 27, 1);
}
&.WORK_WE_CHAT {
color: rgba(41, 102, 255, 1);
}
}
}
.filter-box {
display: flex;
margin-bottom: 4px;
.watcher-name {
margin-right: 40px;
}
}
.course-detail {
display: flex;
align-items: center;
margin-bottom: 16px;
.detail__line {
width: 4px;
height: 12px;
background-image: linear-gradient(#2966ff 83.5%, #0acca4 16.5%);
margin-right: 8px;
}
.detail__name {
width: 880px;
height: 26px;
font-size: 19px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 26px;
}
}
}
\ No newline at end of file
src/modules/course-manage/video-course/index.jsx
View file @
aef0f0eb
...
...
@@ -15,18 +15,18 @@ class VideoCourse extends React.Component {
current
:
1
,
storeId
:
User
.
getStoreId
(),
},
dataSource
:
[],
//
视频
课列表
totalCount
:
0
,
//
视频
课数据总条数
dataSource
:
[],
//
线上
课列表
totalCount
:
0
,
//
线上
课数据总条数
currentTabKey
:
'internal'
,
};
}
componentWillMount
()
{
// 获取
视频
课列表
// 获取
线上
课列表
this
.
handleFetchScheduleList
();
}
// 获取
视频
课列表
// 获取
线上
课列表
handleFetchScheduleList
=
(
_query
=
{})
=>
{
const
{
currentTabKey
}
=
this
.
state
;
const
query
=
{
...
...
@@ -81,7 +81,7 @@ class VideoCourse extends React.Component {
const
{
dataSource
,
totalCount
,
query
,
currentTabKey
}
=
this
.
state
;
return
(
<
div
className=
'page video-course-page'
>
<
div
className=
'content-header'
>
视频
课
</
div
>
<
div
className=
'content-header'
>
线上
课
</
div
>
<
div
className=
'box'
>
<
Tabs
onChange=
{
this
.
currenTabChange
}
activeKey=
{
currentTabKey
}
>
...
...
@@ -95,7 +95,7 @@ class VideoCourse extends React.Component {
<
If
condition=
{
currentTabKey
===
'internal'
}
>
<
VideoCourseOpt
/>
</
If
>
{
/*
视频
课列表模块 */
}
{
/*
线上
课列表模块 */
}
<
VideoCourseList
type=
{
currentTabKey
}
query=
{
query
}
...
...
src/modules/course-manage/video-course/modal/WatchDataModal.jsx
View file @
aef0f0eb
...
...
@@ -3,7 +3,7 @@
* @Date: 2020-05-19 11:01:31
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-05-25 16:50:47
* @Description
余额异常
弹窗
* @Description
学员观看数据
弹窗
*/
import
User
from
'@/common/js/user'
;
import
{
PageControl
,
XMTable
}
from
'@/components'
;
...
...
@@ -128,65 +128,52 @@ class WatchDataModal extends React.Component {
const
{
visible
,
size
,
dataSource
,
totalCount
,
query
}
=
this
.
state
;
return
(
<
Modal
title=
'视频课观看数据'
title=
"线上课观看数据"
visible=
{
visible
}
footer=
{
null
}
onCancel=
{
this
.
onClose
}
maskClosable=
{
false
}
className=
'watch-data-modal'
className=
"watch-data-modal"
closable=
{
true
}
width=
{
800
}
closeIcon=
{
<
span
className=
'icon iconfont modal-close-icon'
>

</
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'
>

</
span
>
}
/>
closeIcon=
{
<
span
className=
"icon iconfont modal-close-icon"
>

</
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"
>

</
span
>
}
/>
</
div
>
<
div
>
<
XMTable
renderEmpty=
{
{
image
:
college
,
description
:
'暂无数据'
,
}
}
rowKey=
{
(
record
)
=>
record
.
id
}
rowKey=
{
record
=>
record
.
id
}
dataSource=
{
dataSource
}
columns=
{
this
.
parseColumns
()
}
pagination=
{
false
}
bordered
/>
{
dataSource
.
length
>
0
&&
(
<
div
className=
'box-footer'
>
{
dataSource
.
length
>
0
&&
<
div
className=
"box-footer"
>
<
PageControl
current=
{
query
.
current
-
1
}
pageSize=
{
size
}
total=
{
totalCount
}
size=
'small'
size=
"small"
toPage=
{
(
page
)
=>
{
const
_query
=
{
...
query
,
current
:
page
+
1
};
this
.
setState
(
{
query
:
_query
,
},
()
=>
{
this
.
handleFetchDataList
();
}
);
const
_query
=
{...
query
,
current
:
page
+
1
};
this
.
setState
({
query
:
_query
},()
=>
{
this
.
handleFetchDataList
()})
}
}
onShowSizeChange=
{
this
.
onShowSizeChange
}
/>
</
div
>
)
}
}
</
div
>
</
Modal
>
)
;
)
}
}
...
...
src/modules/home/Home.jsx
View file @
aef0f0eb
...
...
@@ -329,7 +329,7 @@ class Home extends React.Component {
</
div
>
</
div
>
<
div
className=
'course-item'
>
<
div
className=
'course-title'
>
视频
课
</
div
>
<
div
className=
'course-title'
>
线上
课
</
div
>
<
div
className=
'data'
>
<
span
className=
'course-number'
>
{
videoCourseNum
}
</
span
>
{
incVideoCourseNum
>
0
&&
<
span
className=
'icon iconfont'
>

</
span
>
}
...
...
@@ -395,9 +395,8 @@ class Home extends React.Component {
</
span
>
<
span
className=
{
`tab${scheduleType === 'VOICE' ? ' selected' : ''}`
}
onClick=
{
()
=>
this
.
setState
({
scheduleType
:
'VOICE'
},
()
=>
this
.
getHotCourse
())
}
>
视频课
</
span
>
onClick=
{
()
=>
this
.
setState
({
scheduleType
:
'VOICE'
},
()
=>
this
.
getHotCourse
())
}
>
线上课
</
span
>
<
span
className=
{
`tab${scheduleType === 'PICTURE' ? ' selected' : ''}`
}
onClick=
{
()
=>
this
.
setState
({
scheduleType
:
'PICTURE'
},
()
=>
this
.
getHotCourse
())
}
>
...
...
src/modules/knowledge-base/ENUM.js
View file @
aef0f0eb
...
...
@@ -55,7 +55,7 @@ const ENUM = {
CourseTypeEnum
:
{
LIVE
:
"直播课"
,
VOICE
:
"
视频
课"
,
VOICE
:
"
线上
课"
,
PICTURE
:
"图文课"
,
FOLDER
:
"学习资料"
,
},
...
...
src/modules/knowledge-base/components/KnowledgeBaseFilter.jsx
View file @
aef0f0eb
...
...
@@ -65,10 +65,11 @@ class KnowledgeBaseFilter extends React.Component {
<
Row
type=
"flex"
justify=
"space-between"
align=
"top"
>
<
div
className=
"search-condition"
>
<
div
className=
"search-condition__item"
>
<
span
className=
"search-name"
>
课程
名称:
</
span
>
<
span
className=
"search-name"
>
资料
名称:
</
span
>
<
Search
value=
{
name
}
placeholder=
"搜索课程名称"
// allowClear
placeholder=
"搜索资料名称"
onChange=
{
(
e
)
=>
{
this
.
handleChangeQuery
(
"name"
,
e
.
target
.
value
,
false
);
}
}
...
...
@@ -79,33 +80,6 @@ class KnowledgeBaseFilter extends React.Component {
enterButton=
{
<
span
className=
"icon iconfont"
>

</
span
>
}
/>
</
div
>
<
div
className=
"search-condition__item"
>
<
span
className=
"shelf-status"
>
课程类型:
</
span
>
<
Select
style=
{
{
width
:
"calc(100% - 84px)"
}
}
placeholder=
"请选择课程类型"
allowClear=
{
true
}
value=
{
type
}
onChange=
{
(
value
)
=>
{
this
.
handleChangeQuery
(
"type"
,
value
);
}
}
suffixIcon=
{
<
span
className=
"icon iconfont"
style=
{
{
fontSize
:
"12px"
,
color
:
"#BFBFBF"
}
}
>

</
span
>
}
>
{
Reflect
.
ownKeys
(
ENUM
.
CourseTypeEnum
).
map
((
item
)
=>
{
return
(
<
Option
key=
{
item
}
value=
{
item
}
>
{
ENUM
.
CourseTypeEnum
[
item
]
}
</
Option
>
);
})
}
</
Select
>
</
div
>
</
div
>
<
div
className=
"reset-fold-area"
>
<
Tooltip
title=
"清空筛选"
>
...
...
src/modules/knowledge-base/components/KnowledgeBaseList.jsx
View file @
aef0f0eb
...
...
@@ -2,13 +2,14 @@
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-12 14:49:40
* @LastEditors:
Please set LastEditors
* @LastEditTime: 2021-0
6-11 16:44:59
* @LastEditors:
yuananting
* @LastEditTime: 2021-0
7-15 14:07:42
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
React
from
"react"
;
import
{
Modal
,
message
,
Tooltip
,
Switch
,
Dropdown
,
Button
}
from
"antd"
;
import
{
FileTypeIcon
,
FileVerifyMap
}
from
'@/common/constants/academic/lessonEnum'
;
import
{
Route
,
withRouter
}
from
"react-router-dom"
;
import
{
PageControl
,
XMTable
}
from
"@/components"
;
import
{
LIVE_SHARE_MAP
}
from
"@/common/constants/academic/cloudClass"
;
...
...
@@ -181,161 +182,20 @@ class KnowledgeBaseList extends React.Component {
const
{
current
,
size
}
=
query
const
columns
=
[
{
title
:
"
课程
名称"
,
title
:
"名称"
,
key
:
"name"
,
dataIndex
:
"name"
,
width
:
391
,
fixed
:
"left"
,
render
:
(
val
,
record
)
=>
{
const
{
coverUrl
,
mediaCourseUrl
,
courseDivision
}
=
record
.
source
let
hasCover
=
false
const
type
=
record
.
type
return
(
<
div
>
{
type
===
"LIVE"
&&
(
<
div
className=
'record__item'
>
{
record
.
source
&&
record
.
source
.
courseMediaVOS
.
map
((
item
,
index
)
=>
{
if
(
item
.
contentType
===
"COVER"
)
{
hasCover
=
true
return
<
img
className=
'course-cover'
key=
{
index
}
src=
{
item
.
mediaUrl
}
/>
}
})
}
{
!
hasCover
&&
(
<
img
className=
"course-cover"
src=
{
"https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png"
}
/>
)
}
<
div
>
{
val
.
length
>
17
?
(
<
Tooltip
title=
{
val
}
>
<
div
className=
'course-name'
>
{
val
}
</
div
>
</
Tooltip
>
)
:
(
<
div
className=
'course-name'
>
{
val
}
</
div
>
)
}
<
div
>
<
span
className=
'course-time'
>
{
formatDate
(
"YYYY-MM-DD H:i"
,
parseInt
(
record
.
source
.
startTime
))
}
~
{
formatDate
(
"H:i"
,
parseInt
(
record
.
source
.
endTime
))
}
</
span
>
<
span
className=
'course-status'
style=
{
{
color
:
ENUM
.
courseStateShow
[
record
.
source
.
courseState
].
color
,
border
:
`1px solid ${ENUM.courseStateShow[record.source.courseState].color}`
}
}
>
{
ENUM
.
courseStateShow
[
record
.
source
.
courseState
].
title
}
</
span
>
{
record
.
hideToUser
&&
(
<
Tooltip
title=
{
<
div
>
课程未成功开课,已在学员知识列表中隐藏
</
div
>
}
>
<
i
className=
'icon iconfont'
style=
{
{
marginLeft
:
"5px"
,
cursor
:
"pointer"
,
color
:
"#FF4F4F"
,
fontSize
:
"14px"
}
}
>

</
i
>
</
Tooltip
>
)
}
</
div
>
<
div
className=
'teacher-assistant'
>
{
record
.
source
.
teacherName
.
length
>
4
?
(
<
Tooltip
title=
{
record
.
source
.
teacherName
}
>
<
span
className=
'teacher'
>
讲师:
{
record
.
source
.
teacherName
}
</
span
>
</
Tooltip
>
)
:
(
<
span
className=
'teacher'
>
讲师:
{
record
.
source
.
teacherName
}
</
span
>
)
}
{
record
.
source
.
admins
.
length
>
0
&&
(
<>
<
span
className=
'split'
>
|
</
span
>
{
this
.
handleAdminName
(
record
.
source
.
admins
).
length
>
4
?
(
<
Tooltip
title=
{
this
.
handleAdminName
(
record
.
source
.
admins
)
}
>
<
span
className=
'assistant'
>
助教:
{
record
.
source
.
admins
.
map
((
item
,
index
)
=>
{
return
(
<
span
>
{
item
.
adminName
}
{
index
<
record
.
source
.
admins
.
length
-
1
&&
<
span
>
、
</
span
>
}{
" "
}
</
span
>
)
})
}
</
span
>
</
Tooltip
>
)
:
(
<
span
className=
'assistant'
>
助教:
{
record
.
source
.
admins
.
map
((
item
,
index
)
=>
{
return
(
<
span
key=
{
index
}
>
{
item
.
adminName
}
{
index
<
record
.
source
.
admins
.
length
-
1
&&
<
span
>
、
</
span
>
}{
" "
}
</
span
>
)
})
}
</
span
>
)
}
</>
)
}
</
div
>
</
div
>
</
div
>
)
}
{
type
===
"VOICE"
&&
(
<
div
className=
'record__item'
>
{
/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */
}
<
img
className=
'course-cover'
src=
{
coverUrl
?
coverUrl
:
courseDivision
!==
"EXTERNAL"
?
`${mediaCourseUrl}?x-oss-process=video/snapshot,t_0,m_fast`
:
"https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png"
}
alt=
''
/>
{
val
.
length
>
25
?
(
<
Tooltip
title=
{
val
}
>
<
div
className=
'course-name clamp'
>
{
val
}
</
div
>
</
Tooltip
>
)
:
(
<
div
className=
'course-name clamp'
>
{
val
}
</
div
>
)
}
</
div
>
)
}
{
type
===
"PICTURE"
&&
(
<
div
className=
"record__item"
>
<
img
className=
"course-cover"
src=
{
coverUrl
||
"https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png"
}
/>
{
val
.
length
>
25
?
(
<
Tooltip
title=
{
val
}
>
<
div
className=
'course-name clamp'
>
{
val
}
</
div
>
</
Tooltip
>
)
:
(
<
div
className=
'course-name clamp'
>
{
val
}
</
div
>
)
}
</
div
>
)
}
{
type
===
"FOLDER"
&&
(
<
div
className=
'record__item'
onClick=
{
()
=>
{
this
.
handleScanFile
(
record
.
source
)
}
}
>
<
div
className=
{
`folder-type ${record.source && record.source.folderFormat}`
}
/>
<
img
src=
{
FileTypeIcon
[
record
.
source
.
folderFormat
]
}
style=
{
{
width
:
24
,
height
:
24
}
}
alt=
''
className=
'item-img'
/>
{
val
.
length
>
25
?
(
<
Tooltip
title=
{
val
}
>
<
div
className=
'course-name clamp'
>
{
val
}
</
div
>
...
...
@@ -344,22 +204,11 @@ class KnowledgeBaseList extends React.Component {
<
div
className=
'course-name clamp'
>
{
val
}
</
div
>
)
}
</
div
>
)
}
</
div
>
)
}
},
{
title
:
"课程类型"
,
key
:
"type"
,
dataIndex
:
"type"
,
align
:
"center"
,
// width: 100,
render
:
(
val
,
record
)
=>
{
return
<
div
className=
''
>
{
val
?
ENUM
.
CourseTypeEnum
[
val
]
:
"-"
}
</
div
>
}
},
{
title
:
"创建人"
,
key
:
"createName"
,
dataIndex
:
"createName"
,
...
...
@@ -393,7 +242,7 @@ class KnowledgeBaseList extends React.Component {
},
{
title
:
""
,
width
:
48
},
{
title
:
"操作"
,
...
...
@@ -452,7 +301,6 @@ class KnowledgeBaseList extends React.Component {
const
{
match
}
=
this
.
props
;
localStorage
.
setItem
(
"WatchData_CourseName"
,
item
.
name
);
window
.
RCHistory
.
push
({
// pathname: `${match.url}/course-data?type=${item.courseType}&id=${item.liveCourseId}`,
pathname
:
`
${
match
.
url
}
/course-data?type=
${
item
.
type
}
&id=
${
item
.
id
}
`
})
}
...
...
@@ -501,7 +349,7 @@ class KnowledgeBaseList extends React.Component {
dataSource=
{
dataSource
}
columns=
{
this
.
parseColumns
()
}
pagination=
{
false
}
scroll=
{
{
x
:
900
}
}
bordered
className=
"knowledge-list-table"
renderEmpty=
{
{
...
...
src/modules/knowledge-base/components/KnowledgeBaseList.less
View file @
aef0f0eb
...
...
@@ -109,7 +109,10 @@
.knowledge-base-list {
.record__item {
display: flex;
// align-items: center;
align-items: center;
.item-img{
margin-right: 4px;
}
.course-cover {
min-width: 107px;
max-width: 90px;
...
...
@@ -153,13 +156,7 @@
text-overflow: ellipsis;
white-space: nowrap;
height: 20px;
&.clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
white-space: normal;
height: 40px;
}
}
.course-time {
font-size: 12px;
...
...
src/modules/knowledge-base/components/KnowledgeBaseOpt.jsx
View file @
aef0f0eb
...
...
@@ -9,10 +9,8 @@
import
React
,
{
useState
,
useEffect
}
from
"react"
;
import
{
Button
,
Menu
,
Dropdown
,
message
,
Modal
}
from
"antd"
;
import
{
Button
,
Menu
,
Dropdown
,
message
,
Modal
}
from
"antd"
;
import
SelectPrepareFileModal
from
"@/modules/prepare-lesson/modal/SelectPrepareFileModal"
;
import
{
DownOutlined
}
from
"@ant-design/icons"
;
import
AddCourse
from
"../modal/AddCourse"
;
import
User
from
"@/common/js/user"
;
import
Service
from
"@/common/js/service"
;
import
KnowledgeAPI
from
"@/data-source/knowledge/request-api"
;
...
...
@@ -31,16 +29,7 @@ export default function KnowledgeBaseOpt({
const
[
openMoveModal
,
setOpenMoveModal
]
=
useState
(
false
);
const
[
data
,
setData
]
=
useState
([]);
const
menu
=
(
<
Menu
>
<
Menu
.
Item
key=
"1"
style=
{
{
textAlign
:
"center"
}
}
>
<
span
onClick=
{
handAddCourse
}
>
添加课程
</
span
>
</
Menu
.
Item
>
<
Menu
.
Item
key=
"2"
style=
{
{
textAlign
:
"center"
}
}
>
<
span
onClick=
{
handleAddFile
}
>
添加资料
</
span
>
</
Menu
.
Item
>
</
Menu
>
);
useEffect
(()
=>
{
queryCategoryTree
();
...
...
@@ -76,19 +65,6 @@ export default function KnowledgeBaseOpt({
return
newTreeData
;
};
function
handAddCourse
()
{
let
modal
=
(
<
AddCourse
onClose=
{
()
=>
{
setModal
(
null
);
}
}
onChange=
{
onChange
}
categoryId=
{
categoryId
}
updateCategoryTree=
{
updateCategoryTree
}
></
AddCourse
>
);
setModal
(
modal
);
}
function
handUpload
(
refIds
)
{
const
params
=
{
...
...
@@ -118,7 +94,7 @@ export default function KnowledgeBaseOpt({
isOpen=
{
true
}
accept=
".ppt,.pptx,.doc,.docx,.pdf,.jpg,.jpeg,.png,.xlsx,.xls"
tooltip=
"支持文件类型:ppt、word、excel、pdf、jpg、jpeg、png"
selectTypeList=
{
[
"JPG"
,
"JPEG"
,
"PNG"
,
'DOC'
,
'PDF'
,
'EXCEL'
,
'application/msword'
,
'application/vnd.ms-powerpoint'
]
}
// DOC 包含 .pptx,.docx,.xls.XLSX,WORD:DOC
selectTypeList=
{
[
"JPG"
,
"JPEG"
,
"PNG"
,
'DOC'
,
'PDF'
,
'EXCEL'
,
'application/msword'
,
'application/vnd.ms-powerpoint'
]
}
// DOC 包含 .pptx,.docx,.xls.XLSX,WORD:DOC
onClose=
{
()
=>
{
setModal
(
null
);
}
}
...
...
@@ -205,12 +181,9 @@ export default function KnowledgeBaseOpt({
return
(
<
div
className=
"knowledge-course-opt"
>
{
_
.
isEmpty
(
selectedRowKeys
)
?
(
categoryId
!==
'0'
&&
<
Dropdown
overlay=
{
menu
}
>
<
Button
type=
"primary"
className=
"mr8"
>
添加知识
<
DownOutlined
/>
categoryId
!==
'0'
&&
<
Button
type=
"primary"
onClick=
{
handleAddFile
}
className=
"mr8"
>
添加资料
</
Button
>
</
Dropdown
>)
:
<
div
className=
"select-container"
>
<
span
className=
"con"
>
<
div
>
...
...
src/modules/knowledge-base/modal/AddCourse.jsx
View file @
aef0f0eb
...
...
@@ -107,11 +107,11 @@ class AddCourse extends React.Component {
selectVideo
:
{
external
:
[],
internal
:
[],
},
//弹窗内已选择的
视频
课程
},
//弹窗内已选择的
线上
课程
currentVideoCourseListData
:
{
external
:
[],
internal
:
[],
},
//页面中已关联的
视频
课程
},
//页面中已关联的
线上
课程
pictureDataSource
:
[],
pictureSize
:
10
,
...
...
@@ -201,7 +201,7 @@ class AddCourse extends React.Component {
});
};
// 获取
视频
课列表
// 获取
线上
课列表
handleFetchVideoList
=
()
=>
{
const
{
videoQuery
,
videoSize
,
videoCourseDivision
,
videoDataSource
,
videoTotalCount
}
=
this
.
state
;
...
...
@@ -421,7 +421,7 @@ class AddCourse extends React.Component {
coverUrl
||
(
videoCourseDivision
===
'internal'
?
`${mediaCourseUrl}?x-oss-process=video/snapshot,t_0,m_fast`
:
'https://image.xiaomaiketang.com/xm/
mt3ZQRxGKB
.png'
)
:
'https://image.xiaomaiketang.com/xm/
TwtGPQGE4K
.png'
)
}
alt=
''
/>
...
...
@@ -866,7 +866,7 @@ class AddCourse extends React.Component {
)
}
</
div
>
</
TabPane
>
<
TabPane
tab=
'
视频
课'
key=
'VIDEO'
>
<
TabPane
tab=
'
线上
课'
key=
'VIDEO'
>
<
Radio
.
Group
value=
{
videoCourseDivision
}
onChange=
{
this
.
videoCourseDivisionChange
}
style=
{
{
marginBottom
:
8
}
}
>
<
Radio
.
Button
value=
'internal'
>
内部课程
</
Radio
.
Button
>
<
Radio
.
Button
value=
'external'
>
外部课程
</
Radio
.
Button
>
...
...
src/modules/knowledge-base/modal/VideoList.jsx
View file @
aef0f0eb
...
...
@@ -25,7 +25,7 @@ class VideoList extends React.Component {
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
id
:
""
,
//
视频
课ID
id
:
""
,
//
线上
课ID
studentIds
:
[],
selectedRowKeys
:
[],
query
:
{
...
...
@@ -33,8 +33,8 @@ class VideoList extends React.Component {
current
:
1
,
storeId
:
User
.
getStoreId
(),
},
dataSource
:
[],
//
视频
课列表
totalCount
:
0
,
//
视频
课数据总条数
dataSource
:
[],
//
线上
课列表
totalCount
:
0
,
//
线上
课数据总条数
};
}
...
...
@@ -50,11 +50,11 @@ class VideoList extends React.Component {
}
componentWillMount
()
{
// 获取
视频
课列表
// 获取
线上
课列表
this
.
handleFetchScheduleList
();
}
// 获取
视频
课列表
// 获取
线上
课列表
handleFetchScheduleList
=
(
_query
=
{})
=>
{
const
query
=
{
...
this
.
state
.
query
,
...
...
src/modules/plan-manage/AddPlan.jsx
View file @
aef0f0eb
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:13:39
* @LastEditors:
wufan
* @LastEditTime: 2021-0
5-30 20:39:16
* @LastEditors:
yuananting
* @LastEditTime: 2021-0
7-08 10:52:05
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
React
,
{
useEffect
,
useState
}
from
'react'
import
{
Button
,
message
,
Modal
}
from
'antd'
import
ShowTips
from
'@/components/ShowTips'
import
Breadcrumbs
from
'@/components/Breadcrumbs'
import
BasicInfo
from
'./components/BasicInfo'
import
TrainingTask
from
'./components/TrainingTask'
import
ExpiredCourseList
from
'./components/ExpiredCourseList'
import
PlanService
from
'@/domains/plan-domain/planService'
import
User
from
'@/common/js/user'
import
_
from
'underscore'
import
'./AddPlan.less'
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Button
,
message
,
Modal
}
from
'antd'
;
import
ShowTips
from
'@/components/ShowTips'
;
import
Breadcrumbs
from
'@/components/Breadcrumbs'
;
import
BasicInfo
from
'./components/BasicInfo'
;
import
TrainingTask
from
'./components/TrainingTask'
;
import
ExpiredCourseList
from
'./components/ExpiredCourseList'
;
import
PlanService
from
'@/domains/plan-domain/planService'
;
import
User
from
'@/common/js/user'
;
import
_
from
'underscore'
;
import
'./AddPlan.less'
;
import
Bus
from
'@/core/bus'
;
const
defaultCover
=
'https://image.xiaomaiketang.com/xm/rEAetaTEh3.png'
;
const
defaultBasicData
=
{
planName
:
''
,
...
...
@@ -28,35 +29,40 @@ const defaultBasicData = {
operateType
:
'All_Operate'
,
percentCompleteLive
:
80
,
percentCompleteVideo
:
80
,
percentCompletePicture
:
100
}
const
defaultTaskList
=
[]
percentCompletePicture
:
100
,
}
;
const
defaultTaskList
=
[]
;
function
AddPlan
()
{
const
id
=
getParameterByName
(
'id'
)
const
type
=
getParameterByName
(
'type'
)
const
[
basicData
,
setBasicData
]
=
useState
(
defaultBasicData
)
const
[
taskList
,
setTaskList
]
=
useState
(
defaultTaskList
)
const
[
expiredCourseList
,
setExpiredCourseList
]
=
useState
([])
const
[
hasGetDetail
,
setHasGetDetail
]
=
useState
(
false
)
const
[
submitDisabled
,
setSubmitDisabled
]
=
useState
(
false
)
const
id
=
getParameterByName
(
'id'
);
const
type
=
getParameterByName
(
'type'
);
const
[
basicData
,
setBasicData
]
=
useState
(
defaultBasicData
);
const
[
taskList
,
setTaskList
]
=
useState
(
defaultTaskList
);
const
[
expiredCourseList
,
setExpiredCourseList
]
=
useState
([]);
const
[
hasGetDetail
,
setHasGetDetail
]
=
useState
(
false
);
const
[
submitDisabled
,
setSubmitDisabled
]
=
useState
(
false
);
const
[
editorTextLength
,
setEditorTextLength
]
=
useState
(
0
);
useEffect
(()
=>
{
if
(
type
===
'edit'
)
{
getPlanDetail
()
getPlanCustomerState
()
getPlanDetail
()
;
getPlanCustomerState
()
;
}
},
id
)
Bus
.
bind
(
'editorLimit'
,
(
editorTextLength
)
=>
{
setEditorTextLength
(
editorTextLength
);
});
},
id
);
function
getPlanCustomerState
()
{
PlanService
.
getTrainingCourseAutoCancel
({
planId
:
id
planId
:
id
,
}).
then
((
res
)
=>
{
const
expiredCourseList
=
res
.
result
setExpiredCourseList
(
expiredCourseList
)
})
const
expiredCourseList
=
res
.
result
;
setExpiredCourseList
(
expiredCourseList
)
;
})
;
}
function
getPlanDetail
()
{
PlanService
.
getTrainingPlanDetail
({
planId
:
id
planId
:
id
,
}).
then
((
res
)
=>
{
const
{
planName
,
...
...
@@ -67,34 +73,34 @@ function AddPlan() {
percentCompleteVideo
,
percentCompletePicture
,
courseMediaVOS
,
trainingTaskList
}
=
res
.
result
let
coverId
let
coverUrl
let
instro
trainingTaskList
,
}
=
res
.
result
;
let
coverId
;
let
coverUrl
;
let
instro
;
courseMediaVOS
.
map
((
item
)
=>
{
switch
(
item
.
contentType
)
{
case
'COVER'
:
coverId
=
item
.
mediaContent
coverUrl
=
item
.
mediaUrl
break
coverId
=
item
.
mediaContent
;
coverUrl
=
item
.
mediaUrl
;
break
;
case
'INTRO'
:
instro
=
item
.
mediaContent
break
instro
=
item
.
mediaContent
;
break
;
default
:
break
break
;
}
return
item
})
let
_selectOperatorList
=
[]
return
item
;
})
;
let
_selectOperatorList
=
[]
;
if
(
operateIds
)
{
_selectOperatorList
=
operateIds
.
map
((
item
,
index
)
=>
{
let
_item
=
{}
_item
.
id
=
item
return
_item
})
let
_item
=
{}
;
_item
.
id
=
item
;
return
_item
;
})
;
}
setTaskList
(
trainingTaskList
)
setTaskList
(
trainingTaskList
)
;
setBasicData
({
planName
,
coverUrl
:
coverUrl
||
defaultCover
,
...
...
@@ -105,20 +111,20 @@ function AddPlan() {
operateType
,
percentCompleteLive
,
percentCompleteVideo
,
percentCompletePicture
})
setHasGetDetail
(
true
)
})
percentCompletePicture
,
})
;
setHasGetDetail
(
true
)
;
})
;
}
function
handleChangeBasicInfo
(
field
,
value
,
option
)
{
setBasicData
({
...
basicData
,
[
field
]:
value
})
[
field
]:
value
,
})
;
}
function
handleChangeTaskInfo
(
value
)
{
setTaskList
(
value
)
setTaskList
(
value
)
;
}
function
submitInfo
()
{
...
...
@@ -132,37 +138,37 @@ function AddPlan() {
percentCompleteVideo
,
percentCompletePicture
,
coverId
,
coverUrl
}
=
basicData
let
input
=
/^
[\s]
*$/
coverUrl
,
}
=
basicData
;
let
input
=
/^
[\s]
*$/
;
if
(
!
planName
||
input
.
test
(
planName
))
{
message
.
warning
(
'请输入的培训计划名称'
)
return
message
.
warning
(
'请输入的培训计划名称'
)
;
return
;
}
if
(
operateType
===
'Assign_Operate'
&&
selectOperatorList
.
length
===
0
)
{
message
.
warning
(
'请选择指定运营师'
)
return
message
.
warning
(
'请选择指定运营师'
)
;
return
;
}
if
(
!
percentCompleteLive
&&
percentCompleteLive
!==
0
)
{
message
.
warning
(
'请输入完成标准'
)
return
message
.
warning
(
'请输入完成标准'
)
;
return
;
}
if
(
!
percentCompleteVideo
&&
percentCompleteVideo
!==
0
)
{
message
.
warning
(
'请输入完成标准'
)
return
message
.
warning
(
'请输入完成标准'
)
;
return
;
}
if
(
!
percentCompletePicture
&&
percentCompletePicture
!==
0
)
{
message
.
warning
(
'请输入完成标准'
)
return
message
.
warning
(
'请输入完成标准'
)
;
return
;
}
if
(
taskList
.
length
===
0
)
{
message
.
warning
(
'请输入培训计划内容'
)
return
message
.
warning
(
'请输入培训计划内容'
)
;
return
;
}
for
(
let
i
=
0
;
i
<
taskList
.
length
;
i
++
)
{
if
(
input
.
test
(
taskList
[
i
].
taskName
))
{
message
.
warning
(
'培训任务名称不能为空'
)
return
false
message
.
warning
(
'培训任务名称不能为空'
)
;
return
false
;
}
if
(
taskList
[
i
].
courseList
.
length
===
0
)
{
Modal
.
confirm
({
...
...
@@ -170,28 +176,33 @@ function AddPlan() {
content
:
'每个任务下至少关联一个课程'
,
okText
:
'确定'
,
cancelText
:
'取消'
,
icon
:
<
span
className=
'icon iconfont default-confirm-icon'
>

</
span
>
})
return
false
icon
:
<
span
className=
'icon iconfont default-confirm-icon'
>

</
span
>,
});
return
false
;
}
}
if
(
editorTextLength
>
1000
)
{
message
.
warning
(
'简介超过字数限定'
);
return
;
}
let
scheduleMediaRequests
=
[]
let
scheduleMediaRequests
=
[]
;
let
coverObj
=
{
contentType
:
'COVER'
,
mediaContent
:
coverId
,
mediaType
:
'PICTURE'
,
mediaUrl
:
coverUrl
}
mediaUrl
:
coverUrl
,
}
;
if
(
coverId
)
{
scheduleMediaRequests
=
[...
scheduleMediaRequests
,
coverObj
]
scheduleMediaRequests
=
[...
scheduleMediaRequests
,
coverObj
]
;
}
let
instroObj
=
{
contentType
:
'INTRO'
,
mediaType
:
'TEXT'
,
mediaContent
:
instro
}
mediaContent
:
instro
,
}
;
if
(
instro
)
{
scheduleMediaRequests
=
[...
scheduleMediaRequests
,
instroObj
]
scheduleMediaRequests
=
[...
scheduleMediaRequests
,
instroObj
]
;
}
const
params
=
{
...
...
@@ -205,43 +216,43 @@ function AddPlan() {
planName
,
scheduleMediaRequests
,
storeId
:
User
.
getStoreId
(),
trainingTaskList
:
handleSubmitTaskData
(
taskList
)
}
trainingTaskList
:
handleSubmitTaskData
(
taskList
)
,
}
;
if
(
type
===
'add'
)
{
PlanService
.
createTrainingPlan
(
params
).
then
((
res
)
=>
{
if
(
res
.
success
)
{
message
.
success
(
'新建成功'
)
setSubmitDisabled
(
true
)
window
.
RCHistory
.
goBack
()
message
.
success
(
'新建成功'
)
;
setSubmitDisabled
(
true
)
;
window
.
RCHistory
.
goBack
()
;
}
})
})
;
}
else
{
const
_params
=
{
...
params
,
id
}
id
,
}
;
PlanService
.
updateTrainingPlan
(
_params
).
then
((
res
)
=>
{
if
(
res
.
success
)
{
message
.
success
(
'更新成功'
)
window
.
RCHistory
.
goBack
()
message
.
success
(
'更新成功'
)
;
window
.
RCHistory
.
goBack
()
;
}
})
})
;
}
}
function
handleSubmitTaskData
(
taskData
)
{
return
taskData
.
map
((
item
,
index
)
=>
{
let
_item
=
{}
_item
.
taskId
=
item
.
taskId
_item
.
taskName
=
item
.
taskName
let
_item
=
{}
;
_item
.
taskId
=
item
.
taskId
;
_item
.
taskName
=
item
.
taskName
;
_item
.
courseList
=
item
.
courseList
.
map
((
childItem
,
index
)
=>
{
let
_childItem
=
{}
_childItem
.
courseId
=
childItem
.
courseId
_childItem
.
courseName
=
childItem
.
courseName
_childItem
.
courseType
=
childItem
.
courseType
return
_childItem
})
return
_item
})
let
_childItem
=
{}
;
_childItem
.
courseId
=
childItem
.
courseId
;
_childItem
.
courseName
=
childItem
.
courseName
;
_childItem
.
courseType
=
childItem
.
courseType
;
return
_childItem
;
})
;
return
_item
;
})
;
}
// 取消编辑并返回上一级路由
function
handleGoBack
()
{
...
...
@@ -253,11 +264,11 @@ function AddPlan() {
cancelText
:
'留在本页'
,
icon
:
<
span
className=
'icon iconfont default-confirm-icon'
>

</
span
>,
onOk
:
()
=>
{
window
.
RCHistory
.
goBack
()
}
})
window
.
RCHistory
.
goBack
()
;
}
,
})
;
}
else
{
window
.
RCHistory
.
goBack
()
window
.
RCHistory
.
goBack
()
;
}
}
...
...
@@ -293,6 +304,6 @@ function AddPlan() {
</
Button
>
</
div
>
</
div
>
)
)
;
}
export
default
AddPlan
export
default
AddPlan
;
src/modules/plan-manage/components/BasicInfo.jsx
View file @
aef0f0eb
/*
* @Author: yuananting
* @Date: 2021-07-05 10:48:08
* @LastEditors: yuananting
* @LastEditTime: 2021-07-15 14:20:15
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51
* @LastEditors: Please set LastEditors
...
...
@@ -12,7 +21,8 @@ import { withRouter } from 'react-router-dom';
import
SelectOperatorModal
from
'../modal/SelectOperatorModal'
;
import
SelectPrepareFileModal
from
'@/modules/prepare-lesson/modal/SelectPrepareFileModal'
;
import
Upload
from
'@/core/upload'
;
import
ImgClipModal
from
'@/components/ImgClipModal'
;
import
GraphicsEditor
from
'@/modules/course-manage/components/GraphicsEditor'
;
import
ImgClipModal
from
'@/components/ImgClipModal'
import
'./BasicInfo.less'
;
const
{
TextArea
}
=
Input
;
...
...
@@ -123,10 +133,14 @@ class BasicInfo extends React.Component {
this
.
props
.
onChange
(
field
,
_percentCompleteLive
);
};
changeIntro
=
(
value
)
=>
{
this
.
props
.
onChange
(
'introduce'
,
value
);
};
render
()
{
const
{
operatorModalVisible
,
showSelectFileModal
,
visible
,
imageFile
}
=
this
.
state
;
const
{
data
}
=
this
.
props
;
const
{
planName
,
coverUrl
,
in
stro
,
enableState
,
operateType
,
selectOperatorList
,
percentCompleteLive
,
percentCompleteVideo
,
percentCompletePicture
}
=
const
{
planName
,
coverUrl
,
in
troduce
,
enableState
,
operateType
,
selectOperatorList
,
percentCompleteLive
,
percentCompleteVideo
,
percentCompletePicture
}
=
data
;
// 当前是否使用的是默认图片
const
isDefaultCover
=
coverUrl
===
defaultCover
;
...
...
@@ -147,10 +161,6 @@ class BasicInfo extends React.Component {
<
div
className=
'cover'
>
<
span
className=
'label'
>
封面图:
</
span
>
<
div
className=
'cover__wrap'
>
<
div
className=
'img-content'
>
{
isDefaultCover
&&
<
span
className=
'tag'
>
默认图
</
span
>
}
<
img
src=
{
coverUrl
}
width=
'690'
alt=
''
/>
</
div
>
<
div
className=
'opt-btns'
>
<
Button
onClick=
{
()
=>
{
...
...
@@ -165,36 +175,44 @@ class BasicInfo extends React.Component {
</
span
>
<
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
}
width=
'690'
alt=
''
/>
</
div
>
</
div
>
</
div
>
<
div
className=
'introduction'
>
<
span
className=
'label'
>
简介:
</
span
>
<
TextArea
{
/*
<TextArea
placeholder='请输入培训计划简介'
maxLength={200}
style={{ width: '552px', height: '110px' }}
className='instro-textarea'
value={instro}
onChange={(e) => this.props.onChange('instro', e.target.value)}
/> */
}
<
GraphicsEditor
id=
'intro'
isIntro=
{
true
}
maxLimit=
{
1000
}
detail=
{
{
content
:
introduce
,
}
}
onChange=
{
(
val
)
=>
{
this
.
changeIntro
(
val
);
}
}
/>
</
div
>
<
div
className=
'wether-use'
>
<
span
className=
'label'
>
是否启用:
</
span
>
<
div
className=
'content'
>
<
div
>
<
Switch
checked=
{
enableState
===
'YES'
?
true
:
false
}
onChange=
{
()
=>
{
this
.
enableStateChange
();
}
}
/>
</
div
>
<
div
>
<
div
className=
'instro-text'
>
<
div
>
开启:此培训计划可以分享给学员进行学习
</
div
>
<
div
>
关闭:此培训计划暂不可分享给学员进行学习,后续可开启
</
div
>
</
div
>
</
div
>
<
div
className=
'instro-text'
>
{
enableState
===
'YES'
?
'已开启,培训计划可正常分享给学员学习'
:
'已关闭,培训计划暂不进行分享和学习'
}
</
div
>
</
div
>
</
div
>
<
div
className=
'view-range'
>
...
...
@@ -212,7 +230,7 @@ class BasicInfo extends React.Component {
onChange=
{
(
e
)
=>
{
this
.
props
.
onChange
(
'operateType'
,
e
.
target
.
value
);
}
}
>
<
Row
style=
{
{
marginBottom
:
'
5
px'
}
}
>
<
Row
style=
{
{
marginBottom
:
'
16
px'
}
}
>
<
Col
span=
{
24
}
>
<
Radio
value=
'All_Operate'
>
所有运营师
...
...
@@ -238,7 +256,7 @@ class BasicInfo extends React.Component {
选择运营师
</
Button
>
<
span
>
已选择
<
span
>
{
selectOperatorList
.
length
}
</
span
>
名运营师
已选择
<
span
style=
{
{
color
:
'#2966FF'
}
}
>
{
selectOperatorList
.
length
}
</
span
>
名运营师
</
span
>
</
div
>
)
}
...
...
@@ -268,7 +286,7 @@ class BasicInfo extends React.Component {
<
div
className=
'live-standard-info'
>
<
span
className=
'icon iconfont'
>

</
span
>
<
span
className=
'instro'
>
视频课单个课程
,学员学习进度达到
线上课单个课节
,学员学习进度达到
<
Input
width=
'40'
value=
{
percentCompleteVideo
}
...
...
@@ -278,7 +296,7 @@ class BasicInfo extends React.Component {
onBlur=
{
(
e
)
=>
this
.
percentCompleteBlur
(
e
,
'percentCompleteVideo'
)
}
className=
'input-box'
/>
%,即视为"已完成"学习
%,即
课节
视为"已完成"学习
</
span
>
</
div
>
<
div
className=
'live-standard-info'
>
...
...
src/modules/plan-manage/components/BasicInfo.less
View file @
aef0f0eb
.plan-basic-info{
.plan-basic-info
{
.label {
width: 110px;
text-align: right;
display:
inline-block;
font-size:
14px;
color:
#666;
display:
inline-block;
font-size:
14px;
color:
#666;
.require {
color: #EC4B
35;
color: #ec4b
35;
}
.iconfont
{
font-size:
14px;
color:#BFBFBF
;
.iconfont
{
font-size:
14px;
color: #bfbfbf
;
}
}
.cover {
display: flex;
margin-top: 16
px;
margin-top: 24
px;
&__wrap {
position: relative;
display: flex;
.tag {
border-radius: 2px;
background: #D6D6D
6;
background: #d6d6d
6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF
;
color: #fff
;
position: absolute;
top: 8px;
left: 8px;
}
}
.img-content {
margin-right: 20px;
position: relative;
margin-top: 8px;
width: 299px;
height: 169px;
img {
...
...
@@ -42,90 +42,93 @@
object-fit: contain;
}
}
.opt-btns
{
.opt-btns
{
.default-btn {
margin:0 8
px;
color: #2966FF
;
margin-left: 14
px;
color: #2966ff
;
cursor: pointer;
&.disabled {
color: #CCC
;
color: #ccc
;
cursor: not-allowed;
}
}
.tips
{
margin-top:
8px;
font-size:
14px;
color:
#999;
.tips
{
margin-top:
8px;
font-size:
14px;
color:
#999;
}
}
}
.introduction{
margin-top:16px;
.instro-textarea{
.introduction {
display: flex;
margin-top: 24px;
.instro-textarea {
vertical-align: top;
}
}
.wether-use
{
display:
flex;
margin-top:16
px;
.instro-text
{
color:
#999;
margin-left:
12px;
.wether-use
{
display:
flex;
margin-top: 34
px;
.instro-text
{
color:
#999;
margin-left:
12px;
}
.content
{
display:
flex;
.content
{
display:
flex;
}
}
.view-range
{
display:
flex;
margin-top:16
px;
.label
{
margin-top:
2px;
.view-range
{
display:
flex;
margin-top: 24
px;
.label
{
margin-top:
2px;
}
.instro-text
{
color:
#999;
margin-left:
12px;
.instro-text
{
color:
#999;
margin-left:
12px;
}
.choose-business{
margin-top:16px;
.choose-business {
margin-top: 12px;
.ant-btn {
margin-right: 12px;
}
.playback__text{
margin-left:12px;
color:#999999;
}
.playback__text {
margin-left: 12px;
color: #999999;
}
.done-standard{
}
.done-standard {
display: flex;
margin-top:
22px;
.standard-label
{
margin-top:
3px;
margin-top:
22px;
.standard-label
{
margin-top:
3px;
}
.live-standard-info
{
margin-bottom:
10px;
.live-standard-info
{
margin-bottom:
10px;
}
input
{
display:
inline-block;
width:
90px;
height:
32px;
input
{
display:
inline-block;
width:
90px;
height:
32px;
}
.icon
{
color:#A0A0A
0;
font-size:
14px;
margin-right:
4px;
.icon
{
color: #a0a0a
0;
font-size:
14px;
margin-right:
4px;
}
.instro
{
color:
#333333;
font-size:
14px;
.instro
{
color:
#333333;
font-size:
14px;
}
.input-box
{
.input-box
{
width: 60px;
height: 32px;
border-radius: 4px;
border: 1px solid #E8E8E
8;
color:
#333333;
font-size:
14px;
margin:
0 2px;
border: 1px solid #e8e8e
8;
color:
#333333;
font-size:
14px;
margin:
0 2px;
}
}
}
src/modules/plan-manage/components/ExpiredCourseList.jsx
View file @
aef0f0eb
...
...
@@ -18,7 +18,7 @@ function ExpiredCourseList(props) {
<
span
>
直播课
</
span
>
}
{
item
.
courseType
===
"VOICE "
&&
<
span
>
视频
课
</
span
>
<
span
>
线上
课
</
span
>
}
</
div
>
<
div
className=
"course-instro"
>
...
...
src/modules/plan-manage/components/TrainingTask.jsx
View file @
aef0f0eb
/*
* @Author: yuananting
* @Date: 2021-07-05 10:49:01
* @LastEditors: yuananting
* @LastEditTime: 2021-07-05 11:02:04
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51
* @LastEditors:
fusanqias
ng
* @LastEditTime: 2021-0
5-24 15:15:06
* @LastEditors:
yuananti
ng
* @LastEditTime: 2021-0
7-01 17:16:50
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
React
from
'react'
import
{
Input
,
Form
,
Modal
}
from
'antd'
import
{
sortableContainer
,
sortableElement
,
sortableHandle
}
from
'react-sortable-hoc'
import
React
from
'react'
;
import
{
Input
,
Form
,
Modal
}
from
'antd'
;
import
{
sortableContainer
,
sortableElement
,
sortableHandle
}
from
'react-sortable-hoc'
;
import
arrayMove
from
'array-move'
import
RelatedCourseModal
from
'../modal/relatedCourseModal'
import
{
withRouter
}
from
'react-router-dom'
import
'./TrainingTask.less'
import
arrayMove
from
'array-move'
;
import
RelatedCourseModal
from
'../modal/relatedCourseModal'
;
import
{
withRouter
}
from
'react-router-dom'
;
import
'./TrainingTask.less'
;
const
{
confirm
}
=
Modal
const
{
confirm
}
=
Modal
;
const
CourseType
=
{
LIVE
:
{
text
:
'直播课'
text
:
'直播课'
,
},
VOICE
:
{
text
:
'
视频课'
text
:
'
线上课'
,
},
RECORD
:
{
text
:
'录播课'
text
:
'录播课'
,
},
PICTURE
:
{
text
:
'图文课'
}
}
text
:
'图文课'
,
}
,
}
;
const
courseStateShow
=
{
UN_START
:
{
title
:
'待开播'
title
:
'待开播'
,
},
STARTING
:
{
title
:
'直播中'
title
:
'直播中'
,
},
FINISH
:
{
title
:
'回放'
title
:
'回放'
,
},
EXPIRED
:
{
title
:
'未成功开课'
}
}
title
:
'未成功开课'
,
}
,
}
;
const
DragHandle
=
sortableHandle
(()
=>
(
<
span
className=
'operate__item'
>
<
span
className=
'icon iconfont'
>

</
span
>
<
span
className=
'text'
>
移动
</
span
>
</
span
>
))
))
;
const
SortableTaskItem
=
sortableElement
((
props
)
=>
<
div
{
...
props
}
>
{
props
.
taskitem
}
</
div
>)
const
SortableTaskContainer
=
sortableContainer
((
props
)
=>
<
div
{
...
props
}
></
div
>)
const
SortableTaskItem
=
sortableElement
((
props
)
=>
<
div
{
...
props
}
>
{
props
.
taskitem
}
</
div
>)
;
const
SortableTaskContainer
=
sortableContainer
((
props
)
=>
<
div
{
...
props
}
></
div
>)
;
const
SortableCourseItem
=
sortableElement
((
props
)
=>
<
div
{
...
props
}
>
{
props
.
courseitem
}
</
div
>)
const
SortableCourseContainer
=
sortableContainer
((
props
)
=>
<
div
{
...
props
}
></
div
>)
const
SortableCourseItem
=
sortableElement
((
props
)
=>
<
div
{
...
props
}
>
{
props
.
courseitem
}
</
div
>)
;
const
SortableCourseContainer
=
sortableContainer
((
props
)
=>
<
div
{
...
props
}
></
div
>)
;
class
TrainingTask
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
)
super
(
props
)
;
this
.
state
=
{
dataSource
:
this
.
props
.
data
,
selectedTaskIndex
:
0
,
relatedCourseModalVisible
:
false
}
relatedCourseModalVisible
:
false
,
}
;
}
componentWillMount
()
{}
componentWillReceiveProps
(
nextProps
)
{}
onTaskSortEnd
=
({
oldIndex
,
newIndex
})
=>
{
const
{
dataSource
}
=
this
.
state
const
{
dataSource
}
=
this
.
state
;
if
(
oldIndex
!==
newIndex
)
{
const
newData
=
arrayMove
([].
concat
(
dataSource
),
oldIndex
,
newIndex
).
filter
((
el
)
=>
!!
el
)
const
newData
=
arrayMove
([].
concat
(
dataSource
),
oldIndex
,
newIndex
).
filter
((
el
)
=>
!!
el
)
;
this
.
setState
(
{
dataSource
:
newData
dataSource
:
newData
,
},
()
=>
{
this
.
props
.
onChange
(
newData
)
}
)
this
.
props
.
onChange
(
newData
);
}
);
}
};
onCourseSortEnd
=
({
oldIndex
,
newIndex
},
parentIndex
)
=>
{
const
{
dataSource
}
=
this
.
state
const
{
dataSource
}
=
this
.
state
;
const
_dataSource
=
[...
dataSource
]
const
_dataSource
=
[...
dataSource
]
;
if
(
oldIndex
!==
newIndex
)
{
_dataSource
[
parentIndex
].
courseList
=
arrayMove
([].
concat
(
dataSource
[
parentIndex
].
courseList
),
oldIndex
,
newIndex
).
filter
((
el
)
=>
!!
el
)
_dataSource
[
parentIndex
].
courseList
=
arrayMove
([].
concat
(
dataSource
[
parentIndex
].
courseList
),
oldIndex
,
newIndex
).
filter
((
el
)
=>
!!
el
)
;
this
.
setState
(
{
dataSource
:
_dataSource
dataSource
:
_dataSource
,
},
()
=>
{
this
.
props
.
onChange
(
_dataSource
)
}
)
this
.
props
.
onChange
(
_dataSource
);
}
);
}
};
addTask
=
()
=>
{
const
{
dataSource
}
=
this
.
state
const
{
dataSource
}
=
this
.
state
;
const
taskObj
=
{
taskName
:
''
,
index
:
dataSource
.
length
,
type
:
'input'
,
open
:
true
,
courseList
:
[]
}
const
newData
=
[...
dataSource
,
taskObj
]
courseList
:
[]
,
}
;
const
newData
=
[...
dataSource
,
taskObj
]
;
this
.
setState
(
{
dataSource
:
newData
dataSource
:
newData
,
},
()
=>
{
this
.
props
.
onChange
(
newData
)
}
)
this
.
props
.
onChange
(
newData
);
}
);
};
handleRenameTaskName
=
(
e
,
record
)
=>
{
const
{
value
}
=
e
.
target
const
{
dataSource
}
=
this
.
state
record
.
taskName
=
value
const
{
value
}
=
e
.
target
;
const
{
dataSource
}
=
this
.
state
;
record
.
taskName
=
value
;
this
.
setState
(
{
dataSource
dataSource
,
},
()
=>
{
this
.
props
.
onChange
(
dataSource
)
}
)
this
.
props
.
onChange
(
dataSource
);
}
);
};
handleTaskNameBlur
=
(
e
,
record
)
=>
{
const
{
value
}
=
e
.
target
const
{
dataSource
}
=
this
.
state
let
input
=
/^
[\s]
*$/
const
{
value
}
=
e
.
target
;
const
{
dataSource
}
=
this
.
state
;
let
input
=
/^
[\s]
*$/
;
if
(
value
&&
!
input
.
test
(
value
))
{
record
.
type
=
'text'
record
.
type
=
'text'
;
this
.
setState
(
{
dataSource
dataSource
,
},
()
=>
{
this
.
props
.
onChange
(
dataSource
)
}
)
this
.
props
.
onChange
(
dataSource
);
}
);
}
};
handleRenameCourseName
=
(
e
,
record
)
=>
{
const
{
value
}
=
e
.
target
const
{
dataSource
}
=
this
.
state
record
.
courseName
=
value
const
{
value
}
=
e
.
target
;
const
{
dataSource
}
=
this
.
state
;
record
.
courseName
=
value
;
this
.
setState
(
{
dataSource
dataSource
,
},
()
=>
{
this
.
props
.
onChange
(
dataSource
)
}
)
this
.
props
.
onChange
(
dataSource
);
}
);
};
handleCourseNameBlur
=
(
e
,
record
)
=>
{
const
{
value
}
=
e
.
target
const
{
dataSource
}
=
this
.
state
let
input
=
/^
[\s]
*$/
const
{
value
}
=
e
.
target
;
const
{
dataSource
}
=
this
.
state
;
let
input
=
/^
[\s]
*$/
;
if
(
value
&&
!
input
.
test
(
value
))
{
record
.
type
=
'text'
record
.
type
=
'text'
;
this
.
setState
(
{
dataSource
dataSource
,
},
()
=>
{
this
.
props
.
onChange
(
dataSource
)
}
)
this
.
props
.
onChange
(
dataSource
);
}
);
}
};
handleDeleteTask
=
(
index
)
=>
{
return
confirm
({
title
:
'删除任务'
,
...
...
@@ -191,23 +200,23 @@ class TrainingTask extends React.Component {
okType
:
'danger'
,
cancelText
:
'取消'
,
onOk
:
()
=>
{
this
.
handleConfirmDeleteTask
(
index
)
}
})
}
this
.
handleConfirmDeleteTask
(
index
)
;
}
,
})
;
}
;
handleConfirmDeleteTask
=
(
index
)
=>
{
const
{
dataSource
}
=
this
.
state
const
newData
=
[...
dataSource
]
newData
.
splice
(
index
,
1
)
const
{
dataSource
}
=
this
.
state
;
const
newData
=
[...
dataSource
]
;
newData
.
splice
(
index
,
1
)
;
this
.
setState
(
{
dataSource
:
newData
dataSource
:
newData
,
},
()
=>
{
this
.
props
.
onChange
(
newData
)
}
)
this
.
props
.
onChange
(
newData
);
}
);
};
handleDeleteCourse
=
(
parentIndex
,
index
)
=>
{
return
confirm
({
...
...
@@ -218,82 +227,82 @@ class TrainingTask extends React.Component {
okType
:
'danger'
,
cancelText
:
'取消'
,
onOk
:
()
=>
{
this
.
handleConfirmDeleteCourse
(
parentIndex
,
index
)
}
})
}
this
.
handleConfirmDeleteCourse
(
parentIndex
,
index
)
;
}
,
})
;
}
;
handleConfirmDeleteCourse
=
(
parentIndex
,
index
)
=>
{
const
{
dataSource
}
=
this
.
state
const
newData
=
[...
dataSource
]
const
selectData
=
[...
newData
[
parentIndex
].
courseList
]
selectData
.
splice
(
index
,
1
)
newData
[
parentIndex
].
courseList
=
selectData
const
{
dataSource
}
=
this
.
state
;
const
newData
=
[...
dataSource
]
;
const
selectData
=
[...
newData
[
parentIndex
].
courseList
]
;
selectData
.
splice
(
index
,
1
)
;
newData
[
parentIndex
].
courseList
=
selectData
;
this
.
setState
(
{
dataSource
:
newData
dataSource
:
newData
,
},
()
=>
{
this
.
props
.
onChange
(
newData
)
}
)
this
.
props
.
onChange
(
newData
);
}
);
};
showRelatedCourseModal
=
(
index
)
=>
{
this
.
setState
({
selectedTaskIndex
:
index
,
relatedCourseModalVisible
:
true
})
}
relatedCourseModalVisible
:
true
,
})
;
}
;
closeRelatedCourseModal
=
(
index
)
=>
{
this
.
setState
({
relatedCourseModalVisible
:
false
})
}
relatedCourseModalVisible
:
false
,
})
;
}
;
confirmSelectCourse
=
(
selectList
)
=>
{
console
.
log
(
'selectList'
,
selectList
)
const
{
selectedTaskIndex
}
=
this
.
state
const
{
dataSource
}
=
this
.
state
const
newData
=
[...
dataSource
]
const
selectData
=
[...
newData
[
selectedTaskIndex
].
courseList
]
const
_selectData
=
[...
selectData
,
...
selectList
]
newData
[
selectedTaskIndex
].
courseList
=
_selectData
console
.
log
(
'selectList'
,
selectList
)
;
const
{
selectedTaskIndex
}
=
this
.
state
;
const
{
dataSource
}
=
this
.
state
;
const
newData
=
[...
dataSource
]
;
const
selectData
=
[...
newData
[
selectedTaskIndex
].
courseList
]
;
const
_selectData
=
[...
selectData
,
...
selectList
]
;
newData
[
selectedTaskIndex
].
courseList
=
_selectData
;
this
.
setState
(
{
relatedCourseModalVisible
:
false
,
dataSource
:
newData
dataSource
:
newData
,
},
()
=>
{
this
.
props
.
onChange
(
newData
)
}
)
this
.
props
.
onChange
(
newData
);
}
);
};
openOrCloseTask
=
(
index
)
=>
{
const
{
dataSource
}
=
this
.
state
const
newData
=
[...
dataSource
]
newData
[
index
].
open
=
!
newData
[
index
].
open
const
{
dataSource
}
=
this
.
state
;
const
newData
=
[...
dataSource
]
;
newData
[
index
].
open
=
!
newData
[
index
].
open
;
this
.
setState
(
{
dataSource
:
newData
dataSource
:
newData
,
},
()
=>
{
this
.
props
.
onChange
(
newData
)
}
)
this
.
props
.
onChange
(
newData
);
}
);
};
handleValidatorTaskName
=
(
rule
,
value
)
=>
{
let
input
=
/^
[\s]
*$/
let
input
=
/^
[\s]
*$/
;
if
(
input
.
test
(
value
)
||
!
value
)
{
return
Promise
.
reject
(
new
Error
(
'请输入任务名称'
))
}
return
Promise
.
resolve
()
return
Promise
.
reject
(
new
Error
(
'请输入任务名称'
));
}
return
Promise
.
resolve
();
};
handleValidatorCourseName
=
(
rule
,
value
)
=>
{
let
input
=
/^
[\s]
*$/
let
input
=
/^
[\s]
*$/
;
if
(
input
.
test
(
value
)
||
!
value
)
{
return
Promise
.
reject
(
new
Error
(
'请输入课程名称'
))
}
return
Promise
.
resolve
()
return
Promise
.
reject
(
new
Error
(
'请输入课程名称'
));
}
return
Promise
.
resolve
();
};
renderTaskItem
=
(
record
,
index
)
=>
{
return
(
...
...
@@ -314,8 +323,8 @@ class TrainingTask extends React.Component {
name=
{
[
'taskName'
]
}
rules=
{
[
{
validator
:
(
rule
,
value
)
=>
this
.
handleValidatorTaskName
(
rule
,
value
)
}
validator
:
(
rule
,
value
)
=>
this
.
handleValidatorTaskName
(
rule
,
value
)
,
}
,
]
}
>
<
Input
className=
'task-name-input'
...
...
@@ -323,10 +332,10 @@ class TrainingTask extends React.Component {
placeholder=
'请输入任务名称(20字以内)'
maxLength=
{
20
}
onChange=
{
(
e
)
=>
{
this
.
handleRenameTaskName
(
e
,
record
)
this
.
handleRenameTaskName
(
e
,
record
)
;
}
}
onBlur=
{
(
e
)
=>
{
this
.
handleTaskNameBlur
(
e
,
record
)
this
.
handleTaskNameBlur
(
e
,
record
)
;
}
}
/>
</
Form
.
Item
>
...
...
@@ -346,9 +355,9 @@ class TrainingTask extends React.Component {
<
span
className=
'operate__item'
onClick=
{
()
=>
{
const
{
dataSource
}
=
this
.
state
record
.
type
=
'input'
this
.
setState
({
dataSource
})
const
{
dataSource
}
=
this
.
state
;
record
.
type
=
'input'
;
this
.
setState
({
dataSource
})
;
}
}
>
<
span
className=
'icon iconfont'
>

</
span
>
<
span
className=
'text'
>
重命名
</
span
>
...
...
@@ -356,7 +365,7 @@ class TrainingTask extends React.Component {
<
span
className=
'operate__item'
onClick=
{
()
=>
{
this
.
handleDeleteTask
(
index
)
this
.
handleDeleteTask
(
index
)
;
}
}
>
<
span
className=
'icon iconfont'
>

</
span
>
<
span
className=
'text'
>
删除
</
span
>
...
...
@@ -377,7 +386,7 @@ class TrainingTask extends React.Component {
<
span
className=
'add-course-btn-disabled'
onClick=
{
()
=>
{
this
.
showRelatedCourseModal
(
index
)
this
.
showRelatedCourseModal
(
index
)
;
}
}
>
<
span
>
+
</
span
>
<
span
>
关联课程
</
span
>
...
...
@@ -387,7 +396,7 @@ class TrainingTask extends React.Component {
<
span
className=
'add-course-btn'
onClick=
{
()
=>
{
this
.
showRelatedCourseModal
(
index
)
this
.
showRelatedCourseModal
(
index
)
;
}
}
>
<
span
>
+
</
span
>
<
span
>
关联课程
</
span
>
...
...
@@ -398,9 +407,10 @@ class TrainingTask extends React.Component {
</
div
>
)
}
</
div
>
)
}
)
;
}
;
renderCourseItem
=
(
record
,
index
,
parentIndex
)
=>
{
console
.
log
(
record
);
return
(
<
div
className=
'plan-course-sort-item'
>
<
div
className=
'course-info'
>
...
...
@@ -414,8 +424,8 @@ class TrainingTask extends React.Component {
name=
{
[
'courseName'
]
}
rules=
{
[
{
validator
:
(
rule
,
value
)
=>
this
.
handleValidatorCourseName
(
rule
,
value
)
}
validator
:
(
rule
,
value
)
=>
this
.
handleValidatorCourseName
(
rule
,
value
)
,
}
,
]
}
>
<
Input
className=
'course-name-input'
...
...
@@ -423,10 +433,10 @@ class TrainingTask extends React.Component {
placeholder=
'请输入课程名称(40字以内)'
maxLength=
{
40
}
onChange=
{
(
e
)
=>
{
this
.
handleRenameCourseName
(
e
,
record
)
this
.
handleRenameCourseName
(
e
,
record
)
;
}
}
onBlur=
{
(
e
)
=>
{
this
.
handleCourseNameBlur
(
e
,
record
)
this
.
handleCourseNameBlur
(
e
,
record
)
;
}
}
/>
</
Form
.
Item
>
...
...
@@ -441,6 +451,7 @@ class TrainingTask extends React.Component {
{
record
.
courseState
===
'EXPIRED'
&&
<
span
className=
'icon iconfont tip'
>

</
span
>
}
{
record
.
courseType
===
'LIVE'
&&
<
span
className=
'course-state'
>
{
courseStateShow
[
record
.
courseState
].
title
}
</
span
>
}
{
record
.
courseType
===
'VOICE'
&&
<
span
>
(共
{
record
.
courseChapterNum
||
1
}
小节)
</
span
>
}
</
div
>
<
div
className=
'course-operate'
>
<
DragHandle
/>
...
...
@@ -448,18 +459,18 @@ class TrainingTask extends React.Component {
<
span
className=
'operate__item'
onClick=
{
()
=>
{
this
.
handleDeleteCourse
(
parentIndex
,
index
)
this
.
handleDeleteCourse
(
parentIndex
,
index
)
;
}
}
>
<
span
className=
'icon iconfont'
>

</
span
>
<
span
className=
'text'
>
删除
</
span
>
</
span
>
</
div
>
</
div
>
)
}
)
;
}
;
render
()
{
const
{
dataSource
,
selectedTaskIndex
,
relatedCourseModalVisible
}
=
this
.
state
console
.
log
(
'dataSource'
,
dataSource
)
const
{
dataSource
,
selectedTaskIndex
,
relatedCourseModalVisible
}
=
this
.
state
;
console
.
log
(
'dataSource'
,
dataSource
)
;
return
(
<
div
className=
'training-task'
>
<
SortableTaskContainer
useDragHandle
disableAutoscroll
helperClass=
'row-dragging'
onSortEnd=
{
this
.
onTaskSortEnd
}
className=
'plan-task-sort-container'
>
...
...
@@ -494,8 +505,8 @@ class TrainingTask extends React.Component {
/>
)
}
</
div
>
)
)
;
}
}
export
default
withRouter
(
TrainingTask
)
export
default
withRouter
(
TrainingTask
)
;
src/modules/plan-manage/components/UserLearningDataFilter.jsx
View file @
aef0f0eb
...
...
@@ -8,11 +8,11 @@
import
React
,
{
useState
,
useRef
,
useEffect
}
from
'react'
;
import
{
withRouter
}
from
'react-router-dom'
;
import
{
Row
,
Input
,
Select
,
Tooltip
}
from
'antd'
;
import
RangePicker
from
"@/modules/common/DateRangePicker"
;
import
{
Row
,
Input
,
Select
,
Tooltip
}
from
'antd'
;
import
RangePicker
from
'@/modules/common/DateRangePicker'
;
import
moment
from
'moment'
;
import
StoreService
from
"@/domains/store-domain/storeService"
;
import
User
from
'@/common/js/user'
import
StoreService
from
'@/domains/store-domain/storeService'
;
import
User
from
'@/common/js/user'
;
import
Bus
from
'@/core/bus'
;
import
'./UserLearningDataFilter.less'
;
...
...
@@ -22,55 +22,52 @@ const userRole = User.getUserRole();
const
DEFAULT_QUERY
=
{
customerName
:
null
,
startTime
:
null
,
endTime
:
null
,
learnState
:
null
,
endTime
:
null
,
learnState
:
null
,
operateId
:
null
,
}
}
;
const
defaultCreatorQuery
=
{
size
:
10
,
current
:
1
,
nickName
:
null
}
nickName
:
null
,
}
;
function
UserLearningDataFilter
(
props
)
{
const
[
expandFilter
,
setExpandFilter
]
=
useState
(
false
);
const
[
query
,
setQuery
]
=
useState
(
DEFAULT_QUERY
);
const
[
operateName
,
setOperateName
]
=
useState
(
null
)
const
[
hasNext
,
setHasNext
]
=
useState
(
false
);
const
[
creatorQuery
,
setCreatorQuery
]
=
useState
(
defaultCreatorQuery
);
const
[
creatorList
,
setCreatorList
]
=
useState
([]);
const
[
query
,
setQuery
]
=
useState
(
DEFAULT_QUERY
);
const
[
operateName
,
setOperateName
]
=
useState
(
null
);
const
[
hasNext
,
setHasNext
]
=
useState
(
false
);
const
[
creatorQuery
,
setCreatorQuery
]
=
useState
(
defaultCreatorQuery
);
const
[
creatorList
,
setCreatorList
]
=
useState
([]);
useEffect
(()
=>
{
Bus
.
bind
(
'watchDataView'
,
(
record
)
=>
handleChangeCreatorQuery
(
record
))
});
Bus
.
bind
(
'watchDataView'
,
(
record
)
=>
handleChangeCreatorQuery
(
record
));
}
,
[]
);
useEffect
(()
=>
{
getCreatorList
();
},
[]);
function
handleChangeCreatorQuery
(
record
)
{
const
_creatorQuery
=
{
...
creatorQuery
};
function
handleChangeCreatorQuery
(
record
)
{
const
_creatorQuery
=
{
...
creatorQuery
};
_creatorQuery
.
operateId
=
record
.
storeUserId
;
setCreatorQuery
(
_creatorQuery
);
handleChangeQuery
(
'operateId'
,
record
.
storeUserId
,
record
.
storeUserName
)
handleChangeQuery
(
'operateId'
,
record
.
storeUserId
,
record
.
storeUserName
);
}
// 改变搜索条件
function
handleChangeQuery
(
field
,
value
,
optionValue
)
{
const
_query
=
{
function
handleChangeQuery
(
field
,
value
,
optionValue
)
{
const
_query
=
{
...
query
,
[
field
]:
value
,
current
:
1
,
}
}
;
setQuery
(
_query
);
if
(
field
===
'operateId'
)
{
setOperateName
(
optionValue
)
if
(
field
===
'operateId'
)
{
setOperateName
(
optionValue
)
;
}
if
(
field
===
'customerName'
)
return
;
props
.
onChange
(
_query
);
props
.
onChange
(
_query
);
}
function
handleChangeDates
(
dates
){
function
handleChangeDates
(
dates
)
{
const
_query
=
_
.
clone
(
query
);
if
(
_
.
isEmpty
(
dates
))
{
delete
_query
.
startTime
;
...
...
@@ -79,27 +76,27 @@ function UserLearningDataFilter(props) {
_query
.
startTime
=
dates
[
0
].
valueOf
();
_query
.
endTime
=
dates
[
1
].
valueOf
();
}
const
param
=
{
const
param
=
{
...
_query
,
current
:
1
,
}
}
;
setQuery
(
param
);
props
.
onChange
(
param
);
}
// 重置搜索条件
function
handleReset
()
{
function
handleReset
()
{
setQuery
(
DEFAULT_QUERY
);
props
.
onChange
(
DEFAULT_QUERY
);
}
function
getCreatorList
(
current
=
1
,
selectList
){
function
getCreatorList
(
current
=
1
,
selectList
)
{
const
_query
=
{
...
creatorQuery
,
current
,
size
:
10
size
:
10
,
};
StoreService
.
getStoreUserBasicPage
(
_query
).
then
((
res
)
=>
{
StoreService
.
getStoreUserBasicPage
(
_query
).
then
((
res
)
=>
{
const
{
result
=
{}
}
=
res
;
const
{
records
=
[],
total
=
0
,
hasNext
}
=
result
;
const
list
=
current
>
1
?
creatorList
.
concat
(
records
)
:
records
;
...
...
@@ -109,119 +106,148 @@ function UserLearningDataFilter(props) {
}
// 滑动加载更多讲师列表
function
handleScrollCreatorList
(
e
){
function
handleScrollCreatorList
(
e
)
{
const
container
=
e
.
target
;
const
scrollToBottom
=
container
&&
container
.
scrollHeight
<=
container
.
clientHeight
+
container
.
scrollTop
;
if
(
scrollToBottom
&&
hasNext
)
{
const
_creatorQuery
=
{
...
creatorQuery
};
const
_creatorQuery
=
{
...
creatorQuery
};
_creatorQuery
.
current
=
creatorQuery
.
current
+
1
;
setCreatorQuery
(
_creatorQuery
);
getCreatorList
(
creatorQuery
.
current
+
1
);
}
}
return
(
<
div
className=
"user-learn-data-filter"
>
<
div
className=
'user-learn-data-filter'
>
<
Row
>
<
div
className=
"search-condition"
>
<
div
className=
"search-condition__item"
>
<
span
className=
"label customer-label"
>
学员:
</
span
>
<
div
className=
'search-condition'
>
<
div
className=
'search-condition__item'
>
<
span
className=
'label customer-label'
>
学员:
</
span
>
<
Search
value=
{
query
.
customerName
}
placeholder=
"搜索学员名称"
onChange=
{
(
e
)
=>
{
handleChangeQuery
(
'customerName'
,
e
.
target
.
value
)}
}
onSearch=
{
()
=>
{
props
.
onChange
(
query
)
}
}
style=
{
{
width
:
"calc(100% - 70px)"
}
}
enterButton=
{
<
span
className=
"icon iconfont"
>

</
span
>
}
placeholder=
'搜索学员名称'
onChange=
{
(
e
)
=>
{
handleChangeQuery
(
'customerName'
,
e
.
target
.
value
);
}
}
onSearch=
{
()
=>
{
props
.
onChange
(
query
);
}
}
style=
{
{
width
:
'calc(100% - 70px)'
}
}
enterButton=
{
<
span
className=
'icon iconfont'
>

</
span
>
}
/>
</
div
>
{
(
User
.
getUserRole
()
===
"CloudManager"
||
User
.
getUserRole
()
===
"StoreManager"
)
&&
<
div
className=
"search-condition__item"
>
<
span
className=
"label lead-label"
>
负责人:
</
span
>
{
(
User
.
getUserRole
()
===
'CloudManager'
||
User
.
getUserRole
()
===
'StoreManager'
)
&&
(
<
div
className=
'search-condition__item'
>
<
span
className=
'label lead-label'
>
负责人:
</
span
>
<
Select
id=
"leadSelect"
placeholder=
"请选择员工"
style=
{
{
width
:
"calc(100% - 70px)"
}
}
id=
'leadSelect'
placeholder=
'请选择员工'
style=
{
{
width
:
'calc(100% - 70px)'
}
}
showSearch
allowClear
filterOption=
{
(
input
,
option
)
=>
option
}
suffixIcon=
{
<
span
className=
"icon iconfont"
style=
{
{
fontSize
:
'12px'
,
color
:
'#BFBFBF'
}
}
>

</
span
>
}
suffixIcon=
{
<
span
className=
'icon iconfont'
style=
{
{
fontSize
:
'12px'
,
color
:
'#BFBFBF'
}
}
>

</
span
>
}
onPopupScroll=
{
handleScrollCreatorList
}
value=
{
operateName
}
onChange=
{
(
value
,
option
)
=>
{
if
(
option
)
{
handleChangeQuery
(
'operateId'
,
value
,
option
.
children
)
}
else
{
handleChangeQuery
(
'operateId'
,
value
,
""
)
onChange=
{
(
value
,
option
)
=>
{
if
(
option
)
{
handleChangeQuery
(
'operateId'
,
value
,
option
.
children
);
}
else
{
handleChangeQuery
(
'operateId'
,
value
,
''
);
}
}
}
onSearch=
{
(
value
)
=>
{
creatorQuery
.
nickName
=
value
setCreatorQuery
(
creatorQuery
)
creatorQuery
.
nickName
=
value
;
setCreatorQuery
(
creatorQuery
);
getCreatorList
();
}
}
onClear
={(
value
)=
>
{
}
}
onClear=
{
(
value
)
=>
{
setCreatorQuery
({
size
:
10
,
current
:
1
,
nickName
:
null
})
getCreatorList
()
}
}
>
nickName
:
null
,
});
getCreatorList
();
}
}
>
{
_
.
map
(
creatorList
,
(
item
,
index
)
=>
{
return
(
<
Select
.
Option
value=
{
item
.
id
}
key=
{
item
.
id
}
>
{
item
.
nickName
}
</
Select
.
Option
>
<
Select
.
Option
value=
{
item
.
id
}
key=
{
item
.
id
}
>
{
item
.
nickName
}
</
Select
.
Option
>
);
})
}
</
Select
>
</
div
>
}
<
div
className=
"search-condition__item"
>
<
span
className=
"label learn-date-label"
>
最近学习日期:
</
span
>
)
}
<
div
className=
'search-condition__item'
>
<
span
className=
'label learn-date-label'
>
最近学习日期:
</
span
>
<
RangePicker
id=
"course_date_picker"
id=
'course_date_picker'
allowClear=
{
false
}
value=
{
query
.
startTime
?
[
moment
(
query
.
startTime
),
moment
(
query
.
endTime
)]
:
null
}
format=
{
"YYYY-MM-DD"
}
onChange=
{
(
dates
)
=>
{
handleChangeDates
(
dates
)
}
}
style=
{
{
width
:
"calc(100% - 98px)"
}
}
value=
{
query
.
startTime
?
[
moment
(
query
.
startTime
),
moment
(
query
.
endTime
)]
:
null
}
format=
{
'YYYY-MM-DD'
}
onChange=
{
(
dates
)
=>
{
handleChangeDates
(
dates
);
}
}
style=
{
{
width
:
'calc(100% - 98px)'
}
}
/>
</
div
>
{
((
expandFilter
&&
(
User
.
getUserRole
()
===
"CloudManager"
||
User
.
getUserRole
()
===
"StoreManager"
))
||
User
.
getUserRole
===
"CloudOperator"
)
&&
<
div
className=
"search-condition__item"
>
<
span
className=
"label learn-status-label"
>
学习状态:
</
span
>
{
((
expandFilter
&&
(
User
.
getUserRole
()
===
'CloudManager'
||
User
.
getUserRole
()
===
'StoreManager'
))
||
User
.
getUserRole
===
'CloudOperator'
)
&&
(
<
div
className=
'search-condition__item'
>
<
span
className=
'label learn-status-label'
>
学习状态:
</
span
>
<
Select
style=
{
{
width
:
"calc(100% - 70px)"
}
}
placeholder=
"请选择当前状态"
style=
{
{
width
:
'calc(100% - 70px)'
}
}
placeholder=
'请选择当前状态'
allowClear=
{
true
}
value=
{
query
.
learnState
}
onChange=
{
(
value
)
=>
{
handleChangeQuery
(
'learnState'
,
value
)
}
}
suffixIcon=
{
<
span
className=
"icon iconfont"
style=
{
{
fontSize
:
'12px'
,
color
:
'#BFBFBF'
}
}
>

</
span
>
}
>
<
Option
value=
"UN_PLAY"
>
未开始
</
Option
>
<
Option
value=
"UNDER_WAY"
>
进行中
</
Option
>
<
Option
value=
"FINISH"
>
已完成
</
Option
>
onChange=
{
(
value
)
=>
{
handleChangeQuery
(
'learnState'
,
value
);
}
}
suffixIcon=
{
<
span
className=
'icon iconfont'
style=
{
{
fontSize
:
'12px'
,
color
:
'#BFBFBF'
}
}
>

</
span
>
}
>
<
Option
value=
'UN_PLAY'
>
未开始
</
Option
>
<
Option
value=
'UNDER_WAY'
>
进行中
</
Option
>
<
Option
value=
'FINISH'
>
已完成
</
Option
>
</
Select
>
</
div
>
}
)
}
</
div
>
{
(
User
.
getUserRole
()
===
"CloudManager"
||
User
.
getUserRole
()
===
"StoreManager"
)
&&
<
div
className=
"reset-fold-area"
>
<
Tooltip
title=
"清空筛选"
><
span
className=
"resetBtn iconfont icon"
onClick=
{
handleReset
}
>

</
span
></
Tooltip
>
<
span
style=
{
{
cursor
:
'pointer'
}
}
className=
"fold-btn"
onClick=
{
()
=>
{
setExpandFilter
(
!
expandFilter
)
}
}
>
{
expandFilter
?
<
span
><
span
>
收起
</
span
><
span
className=
"iconfont icon fold-icon"
>

</
span
>
</
span
>
:
<
span
>
展开
<
span
className=
"iconfont icon fold-icon"
>

</
span
></
span
>
}
</
span
>
{
(
User
.
getUserRole
()
===
'CloudManager'
||
User
.
getUserRole
()
===
'StoreManager'
)
&&
(
<
div
className=
'reset-fold-area'
>
<
Tooltip
title=
'清空筛选'
>
<
span
className=
'resetBtn iconfont icon'
onClick=
{
handleReset
}
>

{
' '
}
</
span
>
</
Tooltip
>
<
span
style=
{
{
cursor
:
'pointer'
}
}
className=
'fold-btn'
onClick=
{
()
=>
{
setExpandFilter
(
!
expandFilter
);
}
}
>
{
expandFilter
?
(
<
span
>
<
span
>
收起
</
span
>
<
span
className=
'iconfont icon fold-icon'
>

</
span
>
{
' '
}
</
span
>
)
:
(
<
span
>
展开
<
span
className=
'iconfont icon fold-icon'
>

</
span
>
</
span
>
)
}
</
span
>
</
div
>
}
)
}
</
Row
>
</
div
>
)
);
}
export
default
withRouter
(
UserLearningDataFilter
);
src/modules/plan-manage/modal/UserLearnDetailModal.jsx
View file @
aef0f0eb
/*
* @Author: yuananting
* @Date: 2021-07-05 10:50:30
* @LastEditors: yuananting
* @LastEditTime: 2021-07-14 14:32:55
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
React
from
'react'
;
import
{
Table
,
Modal
,
Input
}
from
'antd'
;
import
{
PageControl
}
from
"@/components"
;
import
PlanService
from
'@/domains/plan-domain/planService'
import
User
from
'@/common/js/user'
import
{
Table
,
Modal
,
Input
}
from
'antd'
;
import
PlanService
from
'@/domains/plan-domain/planService'
;
import
User
from
'@/common/js/user'
;
import
'./UserLearnDetailModal.less'
;
import
_
from
"underscore"
;
const
{
Search
}
=
Input
;
const
defaultCover
=
'https://image.xiaomaiketang.com/xm/
YNfi45JwFA
.png'
;
import
_
from
'underscore'
;
import
{
FileTypeIcon
}
from
'@/common/constants/academic/lessonEnum'
const
defaultCover
=
'https://image.xiaomaiketang.com/xm/
rEAetaTEh3
.png'
;
const
CourseType
=
{
LIVE
:
{
text
:
"直播课"
text
:
'直播课'
,
},
VOICE
:
{
text
:
"视频课"
VOICE
:
{
text
:
'线上课'
,
},
RECORD
:
{
text
:
'录播课'
RECORD
:
{
text
:
'录播课'
,
},
PICTURE
:
{
text
:
'图文课'
,
},
PICTURE
:{
text
:
'图文课'
}
};
const
courseStateShow
=
{
UN_START
:
{
title
:
"待开播"
,
title
:
'待开播'
,
},
STARTING
:
{
title
:
"直播中"
,
title
:
'直播中'
,
},
FINISH
:
{
title
:
"回放"
,
title
:
'回放'
,
},
EXPIRED
:
{
title
:
"未成功开课"
,
title
:
'未成功开课'
,
},
};
class
UserLearnDetailModal
extends
React
.
Component
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
planDataSource
:{},
taskDataSource
:[],
taskSize
:
10
,
planDataSource
:
{},
taskDataSource
:
[],
taskSize
:
10
,
taskQuery
:
{
current
:
1
,
},
taskTotalCount
:
0
,
courseDataSource
:[],
storeCustomerName
:
''
,
storeCustomerPhone
:
''
taskTotalCount
:
0
,
courseDataSource
:
[],
storeCustomerName
:
''
,
storeCustomerPhone
:
''
,
};
}
componentDidMount
()
{
this
.
getPlanCustomerDetail
();
}
getPlanCustomerDetail
=
()
=>
{
getPlanCustomerDetail
=
()
=>
{
PlanService
.
getPlanCustomerDetail
({
planId
:
getParameterByName
(
"id"
),
storeCustomerId
:
this
.
props
.
storeCustomerId
,
storeId
:
User
.
getStoreId
()
planId
:
getParameterByName
(
'id'
),
storeCustomerId
:
this
.
props
.
storeCustomerId
,
storeId
:
User
.
getStoreId
(),
}).
then
((
res
)
=>
{
const
{
storeCustomerName
,
storeCustomerPhone
,
planName
,
learnFinishPercentage
,
taskCustomerVOList
,
courseMediaVOS
,
}
=
res
.
result
;
const
{
storeCustomerName
,
storeCustomerPhone
,
planName
,
learnFinishPercentage
,
taskCustomerVOList
,
courseMediaVOS
}
=
res
.
result
;
let
coverUrl
;
courseMediaVOS
.
map
((
item
)
=>
{
if
(
item
.
contentType
===
"COVER"
)
{
if
(
item
.
contentType
===
'COVER'
)
{
coverUrl
=
item
.
mediaUrl
;
}
return
item
;
})
});
const
planDataSource
=
{
planName
,
learnFinishPercentage
,
coverUrl
:
coverUrl
||
defaultCover
}
coverUrl
:
coverUrl
||
defaultCover
,
};
this
.
setState
({
storeCustomerName
,
storeCustomerPhone
,
planDataSource
,
taskDataSource
:
taskCustomerVOList
})
})
}
taskDataSource
:
taskCustomerVOList
,
});
});
};
parsePlanDataColumns
=
()
=>
{
const
columns
=
[
{
title
:
'培训计划名称'
,
key
:
'planName'
,
dataIndex
:
'planName'
,
width
:
'77%'
,
render
:
(
val
,
record
,
index
)
=>
{
return
(
<
div
className=
'record-name'
>
<
div
className=
'img-con'
>
<
img
src=
{
record
.
coverUrl
}
/>
</
div
>
<
div
>
{
record
.
planName
}
</
div
>
</
div
>
);
},
},
{
title
:
'学习进度'
,
key
:
'learnFinishPercentage'
,
dataIndex
:
'learnFinishPercentage'
,
render
:
(
val
,
record
,
index
)
=>
{
return
<
div
>
{
val
}
%
</
div
>;
},
},
];
return
columns
;
};
parseTaskColumns
=
()
=>
{
const
columns
=
[
...
...
@@ -95,133 +126,169 @@ class UserLearnDetailModal extends React.Component {
title
:
'培训任务'
,
key
:
'taskName'
,
dataIndex
:
'taskName'
,
render
:
(
val
,
record
,
index
)
=>
{
width
:
'68%'
,
render
:
(
val
,
record
,
index
)
=>
{
return
(
<
div
className=
"taskName"
>
<
div
className=
'taskName'
>
{
index
+
1
}
.
{
record
.
taskName
}
</
div
>
)
}
)
;
}
,
},
{
title
:
'学习进度'
,
key
:
'learnFinishPercentage'
,
dataIndex
:
'learnFinishPercentage'
,
width
:
167
,
render
:
(
val
,
record
)
=>
{
return
(
<
div
className=
"task-learn-percentage"
>
{
val
===
100
?<
span
>
已完成
</
span
>:<
span
>
{
val
}
%
</
span
>
}
</
div
>
)
}
}
return
<
div
className=
'task-learn-percentage'
>
{
val
===
100
?
<
span
>
已完成
</
span
>
:
<
span
>
{
val
}
%
</
span
>
}
</
div
>;
},
},
];
return
columns
;
}
parseCoursecolumns
=
(
parentIndex
)
=>
{
};
parseCourseColumns
=
(
parentIndex
)
=>
{
const
columns
=
[
{
title
:
'课程'
,
key
:
'courseName'
,
dataIndex
:
'courseName'
,
render
:
(
val
,
record
,
index
)
=>
{
width
:
'70%'
,
render
:
(
val
,
record
,
index
)
=>
{
return
(
<
div
className=
"course-info"
>
<
div
className=
'course-info'
>
<
div
>
<
span
className=
"course-type"
>
{
CourseType
[
record
.
courseType
].
text
}
</
span
>
<
span
>
{
parentIndex
+
1
}
.
{
index
+
1
}
</
span
>
<
span
className=
'course-type'
>
{
CourseType
[
record
.
courseType
].
text
}
</
span
>
<
span
>
{
parentIndex
+
1
}
.
{
index
+
1
}
</
span
>
</
div
>
<
div
className=
"name-and-state"
>
<
span
className=
"course-name"
>
{
record
.
courseName
}
</
span
>
{
record
.
courseType
===
"LIVE"
&&
<
span
className=
"course-state"
>
{
courseStateShow
[
record
.
courseState
].
title
}
</
span
>
}
<
div
className=
'name-and-state'
>
<
span
className=
'course-name'
>
{
record
.
courseName
}
</
span
>
{
record
.
courseType
===
'LIVE'
&&
<
span
className=
'course-state'
>
{
courseStateShow
[
record
.
courseState
].
title
}
</
span
>
}
</
div
>
</
div
>
)
}
)
;
}
,
},
{
title
:
'学习进度'
,
key
:
'learnFinishPercentage'
,
dataIndex
:
'learnFinishPercentage'
,
width
:
152
,
render
:
(
val
,
record
)
=>
{
return
<
div
className=
'course-learn-percentage'
>
{
record
.
learnState
===
'FINISH'
?
<
span
>
已完成
</
span
>
:
<
span
>
{
val
}
%
</
span
>
}
</
div
>;
},
},
];
return
columns
;
};
parseChapterColumns
=
(
chapterIndex
)
=>
{
const
columns
=
[
{
title
:
'课节名称'
,
key
:
'name'
,
dataIndex
:
'name'
,
width
:
'80%'
,
render
:
(
val
,
record
,
index
)
=>
{
return
(
<
div
className=
"course-learn-percentage"
>
{
record
.
learnState
===
"FINISH"
?<
span
>
已完成
</
span
>:<
span
>
{
val
}
%
</
span
>
}
<
div
className=
'chapter-record'
>
<
span
>
{
index
<
9
?
`0${index + 1} `
:
index
+
1
}
</
span
>
<
img
className=
'chapter-img'
src=
{
FileTypeIcon
[
record
.
mediaType
]
}
/>
<
span
className=
'chapter-name'
>
{
record
.
name
}
</
span
>
</
div
>
)
}
}
);
},
},
{
title
:
'学习进度'
,
key
:
'watchProgress'
,
dataIndex
:
'watchProgress'
,
render
:
(
val
,
record
)
=>
{
return
<
span
>
{
val
}
%
</
span
>;
},
},
];
return
columns
;
}
};
render
()
{
const
{
storeCustomerName
,
storeCustomerPhone
,
planDataSource
,
taskDataSource
,
taskQuery
,
taskTotalCount
}
=
this
.
state
;
const
{
storeCustomerName
,
storeCustomerPhone
,
planDataSource
,
taskDataSource
,
taskQuery
,
taskTotalCount
}
=
this
.
state
;
const
{
visible
}
=
this
.
props
;
return
(
<
Modal
title=
"学员学习详情"
title=
'学员学习详情'
onCancel=
{
this
.
props
.
onClose
}
onOk=
{
this
.
props
.
onClose
}
maskClosable=
{
false
}
visible=
{
visible
}
className=
"user-Learn-modal"
className=
'user-Learn-modal'
closable=
{
true
}
width=
{
800
}
closeIcon=
{
<
span
className=
"icon iconfont modal-close-icon"
>

</
span
>
}
>
<
div
className=
"customer-info"
>
<
span
className=
"customer-name"
>
closeIcon=
{
<
span
className=
'icon iconfont modal-close-icon'
>

</
span
>
}
>
<
div
className=
'customer-info'
>
<
span
className=
'customer-name'
>
<
span
>
学员:
</
span
>
<
span
>
{
storeCustomerName
}
</
span
>
</
span
>
<
span
className=
"customer-phone"
>
<
span
className=
'customer-phone'
>
<
span
>
手机号:
</
span
>
<
span
>
{
storeCustomerPhone
}
</
span
>
</
span
>
</
div
>
<
div
className=
"plan-instro"
>
<
div
className=
"img-con"
>
<
img
src=
{
planDataSource
.
coverUrl
}
/>
</
div
>
<
div
>
<
div
className=
"plan-name"
>
{
planDataSource
.
planName
}
</
div
>
<
div
className=
"task-learn-percentage"
>
<
span
>
学习进度:
{
planDataSource
.
learnFinishPercentage
}
%
</
span
>
</
div
>
</
div
>
</
div
>
<
div
>
<
div
className=
'table-box'
>
<
Table
className=
'plan-table'
dataSource=
{
[
planDataSource
]
}
columns=
{
this
.
parsePlanDataColumns
()
}
pagination=
{
false
}
/>
<
Table
className=
'task-table'
rowKey=
{
(
record
)
=>
record
.
taskId
}
className=
"task-table"
dataSource=
{
taskDataSource
}
columns=
{
this
.
parseTaskColumns
()
}
pagination=
{
false
}
expandedRowRender=
{
(
record
,
index
)
=>
{
if
(
!
record
.
courseVOList
){
return
}
if
(
record
.
courseVOList
.
length
!==
0
){
return
<
div
>
expandable=
{
{
expandedRowRender
:
(
record
,
index
)
=>
{
return
(
<
div
>
<
Table
pagination=
{
false
}
showHeader=
{
false
}
className=
'child-table'
rowKey=
{
(
record
)
=>
record
.
courseId
}
dataSource=
{
record
.
courseVOList
}
columns=
{
this
.
parseCoursecolumns
(
index
)
}
className=
"child-table"
columns=
{
this
.
parseCourseColumns
(
index
)
}
pagination=
{
false
}
expandable=
{
{
expandedRowRender
:
(
chapterRecord
,
chapterIndex
)
=>
{
return
(
<
div
>
<
Table
showHeader=
{
false
}
dataSource=
{
chapterRecord
.
courseChapterList
}
columns=
{
this
.
parseChapterColumns
(
chapterIndex
)
}
pagination=
{
false
}
/>
</
div
>
);
},
rowExpandable
:
(
record
)
=>
record
.
courseChapterList
&&
record
.
courseChapterList
.
length
>
1
,
}
}
/>
</
div
>
);
},
rowExpandable
:
(
record
)
=>
record
.
courseVOList
&&
record
.
courseVOList
.
length
!==
0
,
}
}
rowClassName=
{
(
record
,
index
)
=>
{
if
(
index
%
2
===
0
)
{
return
'odd-row'
;
}
else
{
return
'even-row'
;
}
}
}
rowClassName=
{
(
record
,
index
)
=>
{
if
(
index
%
2
===
0
){
return
'odd-row'
}
else
{
return
'even-row'
}}
}
/>
</
div
>
</
Modal
>
)
)
;
}
}
...
...
src/modules/plan-manage/modal/UserLearnDetailModal.less
View file @
aef0f0eb
.user-Learn-modal{
.customer-info{
margin-bottom:16px;
.customer-name{
font-size:14px;
color:#333;
margin-right:32px;
.user-Learn-modal {
.customer-info {
margin-bottom: 16px;
.customer-name {
font-size: 14px;
color: #333;
margin-right: 32px;
}
.customer-phone
{
font-size:
14px;
color:
#333;
.customer-phone
{
font-size:
14px;
color:
#333;
}
}
.plan-instro
{
.plan-instro
{
display: flex;
align-items: center;
margin-bottom:
16px;
.img-con
{
margin-right:
8px;
img
{
margin-bottom:
16px;
.img-con
{
margin-right:
8px;
img
{
width: 97px;
height: 54px;
display: inline-block;
border-radius:4px;
}
}
.plan-name{
color:#333;
font-size:16px;
}
.plan-learn-percentage{
color:#333;
font-size:14px;
}
}
.task-table{
.ant-table-thead{
tr {
th{
padding:9px 16px;
border-radius: 4px;
}
}
.plan-name {
color: #333;
font-size: 16px;
}
tr{
td{
padding:14px 16px;
}
}
.taskName{
color:#333;
font-size:14px;
}
.task-learn-percentage{
color:#333;
font-size:14px;
}
.course-info{
display:flex;
margin-left:57px;
align-items: center;
.course-type{
font-size:11px;
color:#666666;
padding:0px 6px;
border: 1px solid #999999;
margin-right:4px;
border-radius: 2px;
line-height: 16px;
}
.name-and-state{
flex:1;
.course-name{
color:#666666;
font-size:14px;
margin-right:8px;
}
.tip{
font-size:14px;
color:#FF4F4F;
margin-right:2px;
}
.course-state{
color:#999;
font-size:14px;
.plan-learn-percentage {
color: #333;
font-size: 14px;
}
}
.table-box {
.child-table {
margin-left: 40px;
tr > td {
&:first-child {
padding-right: 0 !important;
}
.ant-table-expanded-row{
td{
padding:0 16px;
}
}
.ant-table-content{
border:1px solid #e8e8e8;
tr{
td{
border:none;
}
.child-table{
.ant-table-content{
border:none;
thead{
display:none;
}
tbody tr {
td{
border-bottom:none;
padding:14px 16px;
}
}
}
}
}
.odd-row{
background:transparent;
td{
background: #FFF;
}
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FFF;
}
}
&:hover{
& + .ant-table-expanded-row{
background:transparent;
td{
background: #F3f6fa !important;
}
&:nth-child(2) {
padding-left: 0 !important;
}
}
.course-info {
display: flex;
.course-type {
border-radius: 2px;
border: 1px solid #999999;
font-size: 11px;
color: #666666;
padding: 1px 8px;
margin-right: 6px;
}
.even-row{
background:transparent;
td{
background: #FAFAFA;
}
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FAFAFA;
}
.chapter-record {
display: flex;
align-items: center;
.chapter-img {
width: 18px;
height: 18px;
margin: 0 10px;
}
&:hover
{
& + .ant-table-expanded-row{
background:transparent
;
td{
background: #F3f6fa !important
;
.chapter-name
{
overflow: hidden;
white-space: nowrap
;
text-overflow: ellipsis;
width: 400px
;
}
}
.record-name {
display: flex;
align-items: center;
.img-con {
margin-right: 8px;
img {
width: 97px;
height: 54px;
border-radius: 4px;
}
}
}
...
...
src/modules/plan-manage/modal/relatedCourseModal.jsx
View file @
aef0f0eb
/*
* @Author: yuananting
* @Date: 2021-07-05 10:50:10
* @LastEditors: yuananting
* @LastEditTime: 2021-07-13 19:55:29
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import
React
from
'react'
;
import
_
from
'underscore'
;
import
{
Table
,
Radio
,
Tabs
,
Modal
,
Input
,
message
,
Button
,
Tooltip
}
from
'antd'
;
import
{
PageControl
,
XMTable
}
from
'@/components'
;
import
college
from
'@/common/lottie/college'
;
import
{
PageControl
}
from
'@/components'
;
import
CourseService
from
'@/domains/course-domain/CourseService'
;
import
User
from
'@/common/js/user'
;
...
...
@@ -79,11 +87,11 @@ class SelectOperatorModal extends React.Component {
selectVideo
:
{
external
:
[],
internal
:
[],
},
//弹窗内已选择的
视频
课程
},
//弹窗内已选择的
线上
课程
currentVideoCourseListData
:
{
external
:
[],
internal
:
[],
},
//页面中已关联的
视频
课程
},
//页面中已关联的
线上
课程
pictureDataSource
:
[],
pictureSize
:
10
,
...
...
@@ -91,10 +99,10 @@ class SelectOperatorModal extends React.Component {
current
:
1
,
},
pictureTotalCount
:
0
,
selectPicture
:
[],
//弹窗内已选择的
视频
课程
currentPictureCourseListData
:
[],
//页面中已关联的
视频
课程
selectPicture
:
[],
//弹窗内已选择的
线上
课程
currentPictureCourseListData
:
[],
//页面中已关联的
线上
课程
activeKey
:
'
video
'
,
activeKey
:
'
live
'
,
currentTaskCourseData
:
this
.
props
.
data
[
this
.
props
.
selectedTaskIndex
].
courseList
||
[],
};
}
...
...
@@ -136,7 +144,7 @@ class SelectOperatorModal extends React.Component {
});
};
// 获取
视频
课列表
// 获取
线上
课列表
handleFetchVideoDataList
=
()
=>
{
const
{
videoQuery
,
videoSize
,
videoDataSource
,
videoTotalCount
,
videoCourseDivision
}
=
this
.
state
;
...
...
@@ -370,7 +378,6 @@ class SelectOperatorModal extends React.Component {
// 请求表头
parseVideoColumns
=
()
=>
{
const
{
videoCourseDivision
}
=
this
.
state
;
const
columns
=
[
{
title
:
(
...
...
@@ -387,33 +394,23 @@ class SelectOperatorModal extends React.Component {
dataIndex
:
'course'
,
width
:
'60%'
,
render
:
(
val
,
record
)
=>
{
const
{
coverUrl
,
scheduleVideoUrl
}
=
record
;
const
{
coverUrl
}
=
record
;
return
(
<
div
className=
'course-info'
>
{
/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */
}
<
img
className=
'course-cover'
src=
{
coverUrl
||
(
videoCourseDivision
===
'internal'
?
`${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`
:
'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png'
)
}
alt=
''
/>
<
img
className=
'course-cover'
src=
{
coverUrl
||
'https://image.xiaomaiketang.com/xm/TwtGPQGE4K.png'
}
alt=
''
/>
<
div
className=
'course-name'
>
{
record
.
courseName
}
</
div
>
</
div
>
);
},
},
{
title
:
'课
程时长
'
,
key
:
'course
Time
'
,
dataIndex
:
'course
Time
'
,
title
:
'课
节数
'
,
key
:
'course
ChapterNum
'
,
dataIndex
:
'course
ChapterNum
'
,
width
:
'20%'
,
align
:
'right'
,
render
:
(
val
,
record
)
=>
{
return
<
span
className=
'course-status'
>
{
dealTimeDuration
(
record
.
videoDuration
)
}
</
span
>;
return
<
span
>
{
val
||
1
}
</
span
>;
},
},
{
...
...
@@ -555,6 +552,7 @@ class SelectOperatorModal extends React.Component {
_item
.
courseId
=
item
.
id
;
_item
.
courseType
=
'VOICE'
;
_item
.
courseName
=
item
.
courseName
;
_item
.
courseChapterNum
=
item
.
courseChapterNum
return
_item
;
});
...
...
@@ -616,7 +614,7 @@ class SelectOperatorModal extends React.Component {
className=
'link-create-course'
href=
{
window
.
location
.
origin
+
window
.
location
.
pathname
+
'#/create-video-course?type=add'
}
onClick=
{
this
.
props
.
onClose
}
>
没有找到需要的
视频
课?
<
span
>
去创建
</
span
>
没有找到需要的
线上
课?
<
span
>
去创建
</
span
>
</
a
>
);
break
;
...
...
@@ -734,11 +732,7 @@ class SelectOperatorModal extends React.Component {
</
div
>
</
div
>
<
div
>
<
XMTable
renderEmpty=
{
{
image
:
college
,
description
:
'暂无数据'
,
}
}
<
Table
rowKey=
{
(
record
)
=>
record
.
liveCourseId
}
dataSource=
{
liveDataSource
}
columns=
{
this
.
parseLiveColumns
()
}
...
...
@@ -792,7 +786,7 @@ class SelectOperatorModal extends React.Component {
</
div
>
</
TabPane
>
<
TabPane
tab=
'
视频
课'
key=
'video'
>
<
TabPane
tab=
'
线上
课'
key=
'video'
>
<
Radio
.
Group
value=
{
videoCourseDivision
}
onChange=
{
this
.
videoCourseDivisionChange
}
style=
{
{
marginBottom
:
16
}
}
>
<
Radio
.
Button
value=
'internal'
>
内部课程
</
Radio
.
Button
>
<
Radio
.
Button
value=
'external'
>
外部课程
</
Radio
.
Button
>
...
...
@@ -830,11 +824,7 @@ class SelectOperatorModal extends React.Component {
</
div
>
</
div
>
<
div
>
<
XMTable
renderEmpty=
{
{
image
:
college
,
description
:
'暂无数据'
,
}
}
<
Table
rowKey=
{
(
record
)
=>
record
.
id
}
dataSource=
{
videoDataSource
[
videoCourseDivision
]
}
columns=
{
this
.
parseVideoColumns
()
}
...
...
@@ -939,11 +929,7 @@ class SelectOperatorModal extends React.Component {
</
div
>
</
div
>
<
div
>
<
XMTable
renderEmpty=
{
{
image
:
college
,
description
:
'暂无数据'
,
}
}
<
Table
rowKey=
{
(
record
)
=>
record
.
id
}
dataSource=
{
pictureDataSource
}
columns=
{
this
.
parsePictureColumns
()
}
...
...
src/modules/prepare-lesson/modal/SelectPrepareFileModal.jsx
View file @
aef0f0eb
...
...
@@ -101,7 +101,7 @@ class SelectPrepareFileModal extends React.Component {
// 获取文件列表
handleFetchFolderList
=
(
params
=
{},
loadMore
=
false
)
=>
{
// 根据当前根节点获取文件列表
const
{
selectTypeList
}
=
this
.
props
;
const
{
selectTypeList
,
queryTypeEnum
}
=
this
.
props
;
const
{
query
,
folderList
,
currentRootDisk
}
=
this
.
state
;
const
_params
=
{
...
query
,
...
...
@@ -114,6 +114,9 @@ class SelectPrepareFileModal extends React.Component {
if
(
selectTypeList
)
{
_params
.
folderFileType
=
selectTypeList
}
if
(
queryTypeEnum
){
_params
.
queryTypeEnum
=
queryTypeEnum
}
Service
.
Hades
(
FOLDERLIST_URL_MAP
[
currentRootDisk
.
disk
],
_params
).
then
((
res
)
=>
{
const
{
result
=
{}
}
=
res
;
...
...
@@ -221,6 +224,7 @@ class SelectPrepareFileModal extends React.Component {
maxSelectLength
}
=
this
.
props
;
console
.
log
(
'selectedFileList'
,
selectedFileList
)
if
(
multiple
)
{
const
currSelectedFileList
=
[...(
prevSelectedFileList
||
[]),
...
selectedFileList
];
// 最多选择20个文件,判断是否超出了20个
...
...
src/modules/resource-disk/modal/ScanFileModal.jsx
View file @
aef0f0eb
...
...
@@ -34,10 +34,10 @@ class ScanFileModal extends React.Component {
style=
{
{
width
:
632
,
objectFit
:
"cover"
}
}
/>
)
}
{
fileType
===
"
MP4
"
&&
(
{
fileType
===
"
VIDEO
"
&&
(
<
div
>
<
Player
src=
{
item
.
ossAddress
||
item
.
ossUrl
}
src=
{
item
.
mediaUrl
||
item
.
ossAddress
||
item
.
ossUrl
}
fluid=
{
false
}
height=
{
306
}
width=
{
"100%"
}
...
...
src/modules/resource-disk/modal/ScanFileModal.less
View file @
aef0f0eb
...
...
@@ -10,4 +10,7 @@
height: 60px;
}
}
.video-react .video-react-play-progress:after {
width: max-content;
}
}
src/routes/config/mainRoutes.tsx
View file @
aef0f0eb
...
...
@@ -81,7 +81,7 @@ const mainRoutes = [
{
path
:
'/video-course'
,
component
:
VideoCoursePage
,
name
:
'
视频
课'
,
name
:
'
线上
课'
,
},
{
path
:
'/graphics-course'
,
...
...
@@ -101,7 +101,7 @@ const mainRoutes = [
{
path
:
'/create-video-course'
,
component
:
AddVideoCoursePage
,
name
:
'创建
视频
课'
,
name
:
'创建
线上
课'
,
},
{
path
:
'/knowledge-base'
,
...
...
@@ -163,7 +163,7 @@ const mainRoutes = [
{
path
:
'/create-plan'
,
component
:
AddPlanPage
,
name
:
'创建
视频
课'
,
name
:
'创建
线上
课'
,
},
{
path
:
'/store-info'
,
...
...
src/routes/config/menuList.tsx
View file @
aef0f0eb
/*
* @Author: yuananting
* @Date: 2021-02-21 15:53:31
* @LastEditors:
Please set LastEditors
* @LastEditTime: 2021-0
6-08 16:46:15
* @LastEditors:
wufan
* @LastEditTime: 2021-0
7-05 10:29:23
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
...
...
@@ -28,8 +28,8 @@ export const menuList: any = [
link
:
'/live-course'
,
},
{
groupName
:
'视频课'
,
groupCode
:
'CourseVideoClass'
,
groupName
:
"线上课"
,
groupCode
:
"CourseVideoClass"
,
link
:
'/video-course'
,
},
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment