Commit e926cbc4 by zhangleyuan

feat:解决合并代码后的冲突

parents d519df0b 0edaa54e
......@@ -2,7 +2,7 @@
* @Author: zhangleyuan
* @Date: 2020-12-15 19:58:31
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-15 20:00:31
* @LastEditTime: 2021-01-04 20:10:06
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
-->
......@@ -13,3 +13,9 @@
+ 播种计划一期第一阶段初上线
#### 1.0.1
`2020-01-04`
+ 播种计划一期第二阶段初上线
\ No newline at end of file
......@@ -117,7 +117,6 @@ class ShareLiveModal extends React.Component {
>{`【${courseName}】开课啦,快来学习!`}</div>
<img
src={coverImgSrc}
crossOrigin="*"
className="course-cover"
alt="course-cover"
/>
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-31 09:34:31
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-22 15:24:03
* @LastEditTime: 2021-01-09 14:39:46
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -27,7 +27,7 @@ interface HeadersType{
storeId?:any,
storeUserId?:any,
userId?:any,
token?:any
xmtoken?:any
}
class Axios {
static post(
......@@ -49,7 +49,7 @@ class Axios {
headerObject.userId = User.getUserId();
}
if(User.getToken()){
headerObject.token = User.getToken();
headerObject.xmtoken = User.getToken();
}
const instance: AxiosInstance = axios.create({
timeout: TIME_OUT,
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-31 09:34:51
* @LastEditors: wufan
* @LastEditTime: 2020-12-17 14:14:43
* @LastEditTime: 2021-01-06 20:18:46
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -23,7 +23,7 @@ class Service {
return Axios.post('POST', `hades/${url}`, params, option);
}
static Sales(url: string, params: any, option: any) {
static Sales(url: string, params: any, option?: any) {
return Axios.post('POST', `sales/${url}`, params, option);
}
......
import React from 'react'
import React from 'react';
import { Modal, Button } from "antd";
import "./DownloadLiveModal.less"
......@@ -12,37 +13,12 @@ class DownloadLiveModal extends React.Component {
type: 'pre',
}
}
downloadLiveClient(){
if (type === 'pre') {
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
if(isMac){
Modal.info({
title: "抱歉,暂不支持Mac版",
content: "Mac版正在开发中,敬请期待",
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
okText: '我知道了'
});
return;
}
url && window.open(url);
this.setState({
image: 'https://image.xiaomaiketang.com/xm/wPwRdaa7MM.png',
tip: '安装完成后,再次打开即可开始直播。',
text: '我知道了',
type: 'finish',
})
} else {
this.props.onCancel();
}
}
render() {
const { url } = this.props;
const { image, tip, text, type } = this.state;
return <Modal
visible={true}
maskClosable={false}
title="下载客户端"
className="download-live-modal"
footer={null}
......@@ -56,8 +32,17 @@ class DownloadLiveModal extends React.Component {
type="primary"
className="download-button"
onClick={() => {
if (type === 'pre') {
url && window.open(url);
this.setState({
image: 'https://image.xiaomaiketang.com/xm/wPwRdaa7MM.png',
tip: '安装完成后,再次打开即可开始直播。',
text: '我知道了',
type: 'finish',
})
} else {
this.props.onCancel();
}
}}
>{text}</Button>
</Modal>
......
......@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Modal, Button } from 'antd';
import Service from '@/common/js/service';
import User from '@/common/js/user';
import PhotoClip from 'photoclip'
let cutFlag = false;
class ImgCutModalNew extends React.Component {
......@@ -30,10 +31,10 @@ class ImgCutModalNew extends React.Component {
const fileName = window.random_string(16) + name.slice(name.lastIndexOf('.'));
const params = {
bizCode: bizCode,
accessTypeEnum: 'PUBLIC',
instId: LS.get('instId'),
resourceName: fileName
accessTypeEnum:"PUBLIC",
bizCode:'CLOUD_CLASS_COMMON',
instId:User.getStoreId(),
resourceName:name
}
// 压缩
if (compress) {
......@@ -41,7 +42,7 @@ class ImgCutModalNew extends React.Component {
dataUrl = this.getBase64Size(dataUrl) > compressSizeByte ? await this.handleCompressImg(dataUrl, compressSizeByte, cutWidth) : dataUrl;
}
const cutImage = this.convertBase64UrlToBlob(dataUrl);
Service.Hades('public/hades/commonOssAuthority', param).then((res) => {
Service.Hades('public/hades/commonOssAuthority', params).then((res) => {
const { resourceId, accessId, policy, callback, signature,key, host } = res.result;
const localUrl = URL.createObjectURL(cutImage);
// 构建上传的表单
......
......@@ -2,7 +2,7 @@
* @Author: wufan
* @Date: 2020-12-01 17:21:21
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-22 15:39:00
* @LastEditTime: 2021-01-09 11:06:42
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -38,6 +38,9 @@ export function sendNewPhoneAuthCode(params: object) {
export function editUserPhone(params: object) {
return Service.Hades("public/hades/editUserPhone", params);
}
export function getLastedVersion(params: object) {
return Service.Hades("public/hades/getLastedVersion", params);
}
export const getOssClient = (
data: object,
instId: string,
......
/*
* @Author: wufan
* @Date: 2020-12-12 11:57:10
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-16 16:14:42
* @LastEditors: wufan
* @LastEditTime: 2021-01-06 20:18:16
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -12,6 +12,9 @@ import Service from "@/common/js/service";
export function fetchLecturerData(params: object) {
return Service.Hades("public/courseCloud/queryTeacherVisitData", params);
}
export function getQrcode(params: object) {
return Service.Sales("public/businessShow/convertShortUrls", params);
}
export function fetchUserData(params: object) {
return Service.Hades("public/courseCloud/queryStudentVisitData", params);
......@@ -48,4 +51,29 @@ export function turnOnOrOffLiveCloudCourse(params: object) {
}
export function delLiveCloudCourse(params: object) {
return Service.Hades("public/courseCloud/delLiveCloudCourse", params);
}
//视频课相关接口
export function changeVideoShelfState(params: object) {
return Service.Hades("public/hades/changeVideoShelfState", params);
}
export function createVideoSchedule(params: object) {
return Service.Hades("public/hades/createVideoSchedule", params);
}
export function delVideoSchedule(params: object) {
return Service.Hades("public/hades/delVideoSchedule", params);
}
export function editVideoSchedule(params: object) {
return Service.Hades("public/hades/editVideoSchedule", params);
}
export function userWatchInfo(params: object) {
return Service.Hades("public/hades/userWatchInfo", params);
}
export function videoScheduleDetail(params: object) {
return Service.Hades("public/hades/videoScheduleDetail", params);
}
export function videoSchedulePage(params: object) {
return Service.Hades("public/hades/videoSchedulePage", params);
}
export function videoWatchInfo(params: object) {
return Service.Hades("public/hades/videoWatchInfo", params);
}
\ No newline at end of file
......@@ -2,12 +2,12 @@
* @Author: wufan
* @Date: 2020-12-01 17:20:49
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-11 11:36:19
* @LastEditTime: 2021-01-09 11:08:02
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import { getUserStore, getUserPermission ,logout,getStoreUser,sendBizAuthCode,editUserPhone,checkBizAuthCode,sendNewPhoneAuthCode,sendLoginAuthCode,login} from '@/data-source/base/request-apis';
import { getUserStore, getUserPermission ,logout,getStoreUser,sendBizAuthCode,editUserPhone,checkBizAuthCode,sendNewPhoneAuthCode,sendLoginAuthCode,login,getLastedVersion} from '@/data-source/base/request-apis';
export default class StoreService {
// 获取员工列表
......@@ -46,4 +46,7 @@ export default class StoreService {
static login(params: any){
return login(params);
}
static getLastedVersion(params: any){
return getLastedVersion(params);
}
}
\ No newline at end of file
/*
* @Author: 陈剑宇
* @Date: 2020-05-07 14:43:01
* @LastEditTime: 2021-01-04 11:21:06
* @LastEditTime: 2021-01-14 19:31:30
* @LastEditors: zhangleyuan
* @Description:
* @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts
......@@ -25,5 +25,6 @@ export const USER_TYPE: string = 'B';
export const PROJECT = 'xmzj-web-b';
export const VERSION = '5.4.8';
export const PREFIX = 'cloud-class';
// host
export const BASIC_HOST: string = BASIC_HOST_MAP[ENV];
\ No newline at end of file
export const BASIC_HOST: string = BASIC_HOST_MAP[ENV];
/*
* @Author: wufan
* @Date: 2020-11-25 18:25:02
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-16 16:15:15
* @LastEditors: wufan
* @LastEditTime: 2021-01-06 20:17:40
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import { fetchLecturerData, fetchUserData, exportStudentCourseData,exportPlayBackCourseData, fetchPlaybackList,createLiveCloudCourse,getLiveCloudCoursePage,getLiveCloudCourseDetail,updateLiveCloudCourse,turnOnOrOffLiveCloudCourse,delLiveCloudCourse} from '@/data-source/course/request-api';
import {
fetchLecturerData, fetchUserData, exportStudentCourseData, exportPlayBackCourseData, fetchPlaybackList, createLiveCloudCourse, getLiveCloudCoursePage,
getLiveCloudCourseDetail, updateLiveCloudCourse, turnOnOrOffLiveCloudCourse, delLiveCloudCourse, changeVideoShelfState, createVideoSchedule, delVideoSchedule, editVideoSchedule, userWatchInfo, videoSchedulePage, videoScheduleDetail, videoWatchInfo, getQrcode
} from '@/data-source/course/request-api';
export default class courseService {
// 获取讲师上课数据
......@@ -14,6 +17,11 @@ export default class courseService {
return fetchLecturerData(params);
}
// 生成二维码
static getQrcode(params: any) {
return getQrcode(params);
}
// 获取用户上课数据
static fetchUserData(params: any) {
return fetchUserData(params);
......@@ -24,7 +32,7 @@ export default class courseService {
static getLiveCloudCoursePage(params: any) {
return getLiveCloudCoursePage(params);
}
// 导出学生上课数据
static exportStudentCourseData(params: any) {
return exportStudentCourseData(params);
......@@ -39,7 +47,7 @@ export default class courseService {
static fetchPlaybackList(params: any) {
return fetchPlaybackList(params);
}
static getLiveCloudCourseDetail(params: any) {
return getLiveCloudCourseDetail(params);
}
......@@ -52,4 +60,28 @@ export default class courseService {
static delLiveCloudCourse(params: any) {
return delLiveCloudCourse(params);
}
static changeVideoShelfState(params: any) {
return changeVideoShelfState(params);
}
static createVideoSchedule(params: any) {
return createVideoSchedule(params);
}
static delVideoSchedule(params: any) {
return delVideoSchedule(params);
}
static editVideoSchedule(params: any) {
return editVideoSchedule(params);
}
static userWatchInfo(params: any) {
return userWatchInfo(params);
}
static videoSchedulePage(params: any) {
return videoSchedulePage(params);
}
static videoScheduleDetail(params: any) {
return videoScheduleDetail(params);
}
static videoWatchInfo(params: any) {
return videoWatchInfo(params);
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-08-20 09:21:40
* @LastEditors: wufan
* @LastEditTime: 2020-12-12 11:18:53
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-01-11 15:35:49
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import { MapInterface } from '@/domains/basic-domain/interface'
const ENV: string = process.env.DEPLOY_ENV || 'dev';
const ENV: string = process.env.DEPLOY_ENV || 'rc';
const appIdMap: MapInterface = {
dev: 'wx3ea60e78ddfa277e',
dev1: 'wx3ea60e78ddfa277e',
rc: 'wx5c5a1fb71ecab7bc',
gray: "wxdd6b458500d4c224", // 小麦校讯通
prod: 'wxdd6b458500d4c224'
gray: "wx3dda02036493ada6", // 小麦校讯通
prod: 'wx3dda02036493ada6'
}
const shareUrlMap: MapInterface = {
......@@ -25,14 +25,17 @@ const shareUrlMap: MapInterface = {
'gray': 'https://prod.xiaomai5.com/share/show?appid='
}
const LIVE_SHARE_MAP: MapInterface = {
dev: 'https://dev.xiaomai5.com/xiaomai-live-share/index.html#/',
dev1: 'https://dev.xiaomai5.com/xiaomai-live-share/index.html#/',
rc: 'https://rc.xiaomai5.com/xiaomai-live-share/index.html#/',
gray: 'https://res.xiaomai5.com/xiaomai-live-share/gray/index.html#/',
prod: 'https://res.xiaomai5.com/xiaomai-live-share/index.html#/',
const LIVE_SHARE_MAP: MapInterface = {
dev: 'https://dev.xiaomai5.com/store-live/index.html#/',
dev1: 'https://dev.xiaomai5.com/store-live/index.html#/',
rc: 'https://rc.xiaomai5.com/store-live/index.html#/',
gray: 'https://res.xiaomai0.com/store-live/gray/index.html#/',
prod: 'https://res.xiaomai0.com/store-live/index.html#/',
}
export const appId: string = appIdMap[ENV];
export const shareUrl: string = shareUrlMap[ENV];
export const LIVE_SHARE: string = LIVE_SHARE_MAP[ENV];
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-24 12:20:57
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-30 13:58:49
* @LastEditTime: 2021-01-12 15:26:36
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
-->
......@@ -36,7 +36,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>小麦云课堂</title>
<title>小麦企培</title>
<script type="text/javascript" charset="utf-8" src="//g.alicdn.com/sd/ncpc/nc.js?t=2015052012"></script>
</head>
......
......@@ -13,13 +13,12 @@ class DateRangePicker extends React.Component {
}
render() {
const showTime = { showTime: true }
const showTime = { showTime: false }
return (
<RangePicker
{...this.props}
format={this.props.format || 'YYYY-MM-DD'}
allowClear={this.props.allowClear}
ranges={this.props.ranges || { '本月': [moment().startOf('month'), moment().endOf('month')], '本周': [moment().startOf('week'), moment().endOf('week')], '上月': [moment().subtract(1, 'M').startOf('month'), moment().subtract(1, 'M').endOf('month')], '上周': [moment().subtract(1, 'w').startOf('week'), moment().subtract(1, 'w').endOf('week')] }}
onChange={(date) => {
if (!_.isEmpty(date)) {
date[0] = date[0].startOf('day')
......
......@@ -82,6 +82,7 @@ class AddLive extends React.Component {
teacherId: null,
teacherName: null,
assistant:[],
assistantNames:[],
liveDate: null,
timeHorizonStart: null,
timeHorizonEnd: null,
......@@ -169,6 +170,7 @@ class AddLive extends React.Component {
const timeHorizonStart = startTime;
const timeHorizonEnd = endTime;
const assistant = _.pluck(admins, "adminId");
const assistantNames = _.pluck(admins, "adminName");
const addLiveClassInfo = {
assistant,
liveDate,
......@@ -179,6 +181,7 @@ class AddLive extends React.Component {
timeHorizonEnd,
startTime,
endTime,
assistantNames
}
// liveCourseMediaRequests = liveCourseMediaRequests.length
......@@ -388,7 +391,7 @@ handleChangeBasicInfo = (field, value) => {
message.warning('请选择上课日期');
resolve(false);
return;
} else if(startTime === endTime) {
} else if(startTime === endTime || startTime > endTime) {
message.warning('结束时间必须晚于开始时间');
resolve(false);
return;
......@@ -444,36 +447,11 @@ handleChangeBasicInfo = (field, value) => {
}
}
if(!teacherId){
message.warning('上课老师不能为空');
message.warning('请选择上课老师');
resolve(false);
return;
}
resolve(true)
// if(!teacherId) {
// message.warning('上课老师不能为空');
// resolve(false);
// return;
// } else if(!applyMode) {
// message.warning('请选择分享设置');
// resolve(false);
// return;
// } else {
// const textIntro = liveCourseMediaRequests.filter(item => { return item.mediaType === 'TEXT'; });
// for (let i = 0, len = textIntro.length; i < len; i++) {
// if (textIntro[i].mediaContent && textIntro[i].mediaContentLength.length > 1000) {
// message.warning(`第${i+1}个文字简介的字数超过了1000个字`);
// resolve(false);
// return;
// }
// }
// }
// if(window.NewVersion && type === 'add') {
// this.handleValidateLackConsumeModal(consumeHourNum, calendarTime, consumeStudentList).then(res => {
// resolve(res)
// })
// } else {
// resolve(true);
// }
});
}
......@@ -537,7 +515,7 @@ handleChangeBasicInfo = (field, value) => {
/>
<div className="box">
<div className="show-tips">
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦助教保有依据国家规定及平台规则进行处理的权利" />
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企培保有依据国家规定及平台规则进行处理的权利" />
</div>
<div className="add-live-page__form">
<div className="basic-info__wrap">
......
......@@ -151,8 +151,8 @@ class DataList extends React.Component {
},
{
title: "累计在线时长",
dataIndex: "watchDuration",
sorter: (a, b) => a.watchDuration - b.watchDuration,
dataIndex: "totalDuration",
sorter: (a, b) => a.totalDuration - b.totalDuration,
sortDirections: ["descend", "ascend"],
render: (text, record) => {
//如无离开时间,就置空
......
......@@ -95,6 +95,9 @@
&.avatar-name-phone {
justify-content: flex-start;
padding-left: 26px;
.avatar{
border-radius:50%;
}
.name {
height: 22px;
font-size: 16px;
......
......@@ -30,7 +30,8 @@ class AddLiveBasic extends React.Component {
showCutModal: false,
courseCatalogList:[],
showSelectFileModal: false,
cutImageBlob: null
cutImageBlob: null,
hasImgReady: false // 图片是否上传成功
}
}
componentWillUnmount() {
......@@ -71,7 +72,6 @@ class AddLiveBasic extends React.Component {
}
catalogChange= (value) => {
console.log('111');
const changeValueLength = value.length;
switch (changeValueLength){
case 1:
......@@ -143,6 +143,7 @@ class AddLiveBasic extends React.Component {
const _dataUrl = this.clip()
self.setState({
dataUrl: _dataUrl,
hasImgReady: true
})
}, 100)
......@@ -198,7 +199,8 @@ class AddLiveBasic extends React.Component {
}
render() {
const { showCutModal, imageFile,courseCatalogList,showSelectFileModal,visible,cutImageBlob} = this.state;
const { showCutModal, imageFile,courseCatalogList,showSelectFileModal,visible,cutImageBlob,hasImgReady
} = this.state;
const { data,pageType,isEdit} = this.props;
const { courseName,categoryName,coverUrl} = data;
const fileName = '';
......@@ -255,7 +257,7 @@ class AddLiveBasic extends React.Component {
<div className="course-catalog">
<span className="label"><span className="require">*</span>课程分类:</span>
{ pageType === 'add' &&
<Cascader defaultValue={[categoryName]} options={courseCatalogList} displayRender={ label => label.join('-')} fieldNames={fieldNames} onChange={this.catalogChange} style={{ width: 240 }} placeholder="请选择课程分类" />
<Cascader options={courseCatalogList} displayRender={ label => label.join('-')} fieldNames={fieldNames} onChange={this.catalogChange} style={{ width: 240 }} placeholder="请选择课程分类" />
}
{ (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="请选择课程分类" />
......@@ -312,6 +314,7 @@ class AddLiveBasic extends React.Component {
<Button
key="submit"
type="primary"
disabled={!hasImgReady}
onClick={() => {
if (!cutFlag) {
cutFlag = true;
......
......@@ -48,6 +48,7 @@ class AddLiveClass extends React.Component {
this.getTeacherList();
this.getAssistantList();
}
getTeacherList(current = 1, selectList){
const { teacherQuery,teacherList} = this.state;
const _query = {
......@@ -68,6 +69,7 @@ class AddLiveClass extends React.Component {
// 获取助教老师列表
getAssistantList = (current = 1, selectList) => {
const { assistantQuery,assistantList} = this.state;
const _query = {
...assistantQuery,
current,
......@@ -134,7 +136,9 @@ class AddLiveClass extends React.Component {
liveDate,
timeHorizonStart,
timeHorizonEnd,
assistant
assistant,
assistantNames,
teacherName
} = data;
console.log("teacherId",teacherId);
return (
......@@ -245,14 +249,17 @@ class AddLiveClass extends React.Component {
<span className="label"><span className="require">* </span>讲师:</span>
<Select
placeholder="请选择讲师"
// key={teacherName}
// defaultValue={teacherName}
value={teacherId}
style={{ width: 240, marginTop: 6 }}
disabled={!isEdit ? true: false}
showSearch
value={teacherId}
filterOption={(input, option) => option}
onPopupScroll={this.handleScrollTeacherList}
onChange={(value,option) => {
this.props.onChange('teacherId', value,option.children)
console.log("value",value);
this.props.onChange('teacherId',value,option.children)
}}
onSearch={(value) => {
teacherQuery.nickName = value
......@@ -271,41 +278,46 @@ class AddLiveClass extends React.Component {
}
})}
</Select>
</div>
<div className="assistant-teacher">
<span className="label">助教:</span>
<Select
id="assistant"
placeholder="请选择助教老师"
value={assistant}
disabled={!isEdit ? true: false}
mode={'multiple'}
showSearch
allowClear
style={{ width: 240, marginTop: 6 }}
filterOption={(input, option) => option}
onPopupScroll={this.handleScrollAssistantList}
onChange={(value) => {
this.props.onChange('assistant', value)
}}
onSearch={(value) => {
assistantQuery.nickName = value
this.setState({
assistantQuery
}, () => {
this.getAssistantList()
})
}}
>
{_.map(assistantList, (item, index) => {
if(item.userId !== teacherId){
return (
<Select.Option value={item.userId} key={item.userId}>{item.nickName}</Select.Option>
);
}
})}
</Select>
<span className="label">助教:</span>
<Select
id="assistant"
placeholder="请选择助教老师"
// key={assistantNames}
// defaultValue={assistantNames}
value={assistant}
disabled={!isEdit ? true: false}
mode={'multiple'}
showSearch
allowClear
style={{ width: 240, marginTop: 6 }}
filterOption={(input, option) => option}
onPopupScroll={this.handleScrollAssistantList}
onChange={(value,option) => {
console.log('option',option);
this.props.onChange('assistant',value)
}}
onSearch={(value) => {
assistantQuery.nickName = value
this.setState({
assistantQuery
}, () => {
this.getAssistantList()
})
}}
>
{_.map(assistantList, (item, index) => {
if(item.userId !== teacherId){
return (
<Select.Option value={item.userId} key={item.userId}>{item.nickName}</Select.Option>
);
}
})}
</Select>
</div>
</div>
</Spin>
......
......@@ -271,15 +271,15 @@ class AddLiveIntro extends React.Component {
</div>
</div>
<div className="allow-tourist-join">
<span className="label">允许游客加入</span>
<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>
</div>
</div>
......
......@@ -98,8 +98,10 @@ class LiveCourseFilter extends React.Component {
query.endTime = dates[1].valueOf();
}
this.setState({
query,
current: 1,
query:{
...query,
current: 1,
}
}, () => {
this.props.onChange(this.state.query);
})
......@@ -124,7 +126,6 @@ class LiveCourseFilter extends React.Component {
handleReset = () => {
this.setState({
query: {
...this.state.query,
courseName: null,
startTime: null,
endTime: null,
......@@ -132,6 +133,7 @@ class LiveCourseFilter extends React.Component {
teacherName: null,
courseState: undefined,
current: 1,
shelfState:null,
},
}, () => {
this.props.onChange(this.state.query);
......@@ -172,6 +174,7 @@ class LiveCourseFilter extends React.Component {
format={"YYYY-MM-DD"}
onChange={(dates) => { this.handleChangeDates(dates) }}
style={{ width: "calc(100% - 70px)" }}
/>
</div>
<div className="search-condition__item">
......@@ -185,7 +188,7 @@ class LiveCourseFilter extends React.Component {
onPopupScroll={this.handleScrollTeacherList}
value={teacherId}
onChange={(value) => {
this.handleChangeQuery('teacherId', value)
this.handleChangeQuery('teacherId', value);
}}
onSearch={(value) => {
teacherQuery.nickName = value
......@@ -195,6 +198,18 @@ class LiveCourseFilter extends React.Component {
this.getTeacherList()
})
}}
onClear ={(value)=>{
this.setState({
teacherQuery:{
size: 10,
current: 1,
nickName:null
}
}, () => {
this.getTeacherList()
})
}
}
>
{_.map(teacherList, (item, index) => {
return (
......
......@@ -9,28 +9,23 @@
import React from 'react';
import { Table, Modal, message, Dropdown, Button,Switch,Tooltip} from 'antd';
import { Route, withRouter } from 'react-router-dom';
// import Bus from '@/core/bus';
// import User from "@/core/user";
// import User_t from "@/teacher/core/user";
import { PageControl } from "@/components";
// import { LIVE_SHARE_MAP } from '@/common/constants/academic/cloudClass';
import DownloadLiveModal from '@/components/DownloadLiveModal';
// import LiveStudentListModal from '../modal/LiveStudentListModal';
// import CheckBalanceModal from '../modal/CheckBalanceModal';
// import StartLiveModal from '../modal/StartLiveModal';
// import ClassRecordModal from '../modal/ClassRecordModal';
// import PlayBackRecordModal from '../modal/PlayBackRecordModal';
import ManageCoursewareModal from '../modal/ManageCoursewareModal';
import ShareLiveModal from '../modal/ShareLiveModal';
// import AccountChargeModal from '../modal/AccountChargeModal';
// import SelectStudent from '../modal/select-student';
import './LiveCourseList.less';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { appId, shareUrl, LIVE_SHARE } from '@/domains/course-domain/constants';
import { appId, shareUrl, LIVE_SHARE,LIVE_REPLAY_MAP} from '@/domains/course-domain/constants';
import CourseService from "@/domains/course-domain/CourseService";
import BaseService from "@/domains/basic-domain/baseService";
import DataList from '../DataList/DataList';
import User from '@/common/js/user';
import _ from "underscore";
const { confirm } = Modal;
const courseStateShow = {
......@@ -66,26 +61,26 @@ class LiveCourseList extends React.Component {
}
componentWillMount(){
this.parseColumns();
}
componentDidMount() {
this.getDownloadVersion()
}
// 显示分享弹窗
handleShowShareModal = (item, needStr = false) => {
const _appId = appId;
const _shareUrl = shareUrl;
const { liveCourseId } = item;
const { saaSVersionEnum } = window.currentUserInstInfo;
const htmlUrl = `${LIVE_SHARE}liveShare?id=${liveCourseId}&saasVersion=${saaSVersionEnum}`;
const link = `${_appId}&redirect_uri=${encodeURIComponent(htmlUrl)}%26appid%3D${_appId}&response_type=code&scope=snsapi_base&state=state#wechat_redirect`;
const longUrl = `${_shareUrl}${link}`;
const htmlUrl = `${LIVE_SHARE}live_detail/${liveCourseId}?id=${User.getStoreId()}`;
const longUrl = htmlUrl
console.log('htmlUrl',htmlUrl,longUrl);
const shareData = { ...item, longUrl };
const shareLiveModal = (
<ShareLiveModal
needStr={needStr}
data={shareData}
type="liveClass"
close={() => {
this.setState({
shareLiveModal: null
......@@ -146,180 +141,338 @@ class LiveCourseList extends React.Component {
</div>
</div>
);
const columns = [
{
title: "直播课",
width: "25%",
key: "course",
dataIndex: "courseName",
render: (val, record) => {
let hasCover = false;
return (
<div className="record__item">
{
record.courseMediaVOS.map((item,index)=>{
if( item.contentType === "COVER"){
hasCover = true;
return <img className="course-cover" src={item.mediaUrl} />
}
})
}
{ !hasCover &&
<img className="course-cover" src={'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png'} />
}
<div>
<div className="course-name">{record.courseName}</div>
<div>
<span className="course-time">{formatDate("YYYY-MM-DD H:i",parseInt(record.startTime))}~{formatDate("H:i", parseInt(record.endTime))}</span>
<span className="course-status" style={{color:courseStateShow[record.courseState].color,border:`1px solid ${courseStateShow[record.courseState].color}`}}>{courseStateShow[record.courseState].title}</span>
</div>
<div class="teacher-assistant">
<span className="teacher">讲师:{record.teacherName}</span>
{ record.admins.length >0 &&
<>
<span className="split"> | </span>
<span className="assistant">助教:
{ record.admins.map((item,index)=>{
return <span>{item.adminName} { (index < record.admins.length-1)&&(<span></span>)} </span>
})
}
</span>
</>
let columns
const userRole = User.getUserRole();
if(userRole !=="CloudLecturer"){
columns = [
{
title: "直播课",
width: "25%",
key: "course",
dataIndex: "courseName",
render: (val, record) => {
let hasCover = false;
return (
<div className="record__item">
{
record.courseMediaVOS.map((item,index)=>{
if( item.contentType === "COVER"){
hasCover = true;
return <img className="course-cover" src={item.mediaUrl} />
}
})
}
{ !hasCover &&
<img className="course-cover" src={'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png'} />
}
<div>
{ record.courseName.length > 17?
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
:
<div className="course-name">{record.courseName}</div>
}
</div>
<div>
<span className="course-time">{formatDate("YYYY-MM-DD H:i",parseInt(record.startTime))}~{formatDate("H:i", parseInt(record.endTime))}</span>
<span className="course-status" style={{color:courseStateShow[record.courseState].color,border:`1px solid ${courseStateShow[record.courseState].color}`}}>{courseStateShow[record.courseState].title}</span>
</div>
<div className="teacher-assistant">
{ record.teacherName.length > 4 ?
<Tooltip title={record.teacherName}>
<span className="teacher">讲师:{record.teacherName}</span>
</Tooltip>
:
<span className="teacher">讲师:{record.teacherName}</span>
}
{ record.admins.length >0 &&
<>
<span className="split"> | </span>
{ this.handleAdminName(record.admins).length > 4?
<Tooltip title={this.handleAdminName(record.admins)}>
<span className="assistant">助教:
{ record.admins.map((item,index)=>{
return <span>{item.adminName} { (index < record.admins.length-1)&&(<span></span>)} </span>
})
}
</span>
</Tooltip>
:
<span className="assistant">助教:
{ record.admins.map((item,index)=>{
return <span>{item.adminName} { (index < record.admins.length-1)&&(<span></span>)} </span>
})
}
</span>
}
</>
}
</div>
</div>
</div>
</div>
)
}
},
{
title: "课程分类",
width: "10%",
key: "couseCatalog",
dataIndex: "couseCatalog",
render: (val, item) => {
return (
<div className="categoryName">{item.categoryName}</div>
)
)
}
},
},
{
title: "课件管理",
width: "8%",
key: "courseware",
dataIndex: "courseware",
render: (val, item) => {
return (
<span className="courseware"
onClick={() => {
this.setState({
editData: item,
openCoursewareModal: true,
});
}}>{item.courseDocumentCount}</span>
);
{
title: "课程分类",
width: "10%",
key: "couseCatalog",
dataIndex: "couseCatalog",
render: (val, item) => {
return (
<div className="categoryName">{item.categoryName}</div>
)
},
},
},
{
title: '上课数据',
width: "9%",
key: "quota",
dataIndex: "quota",
render: (val, item) => {
return (
<span className="iconfont icon quota-icon" onClick={() => {
this.handleLinkToClassData(item)
}}>&#xe7d6;</span>
);
{
title: "课件管理",
width: "8%",
key: "courseware",
dataIndex: "courseware",
render: (val, item) => {
return (
<span className="courseware"
onClick={() => {
this.setState({
editData: item,
openCoursewareModal: true,
});
}}>{item.courseDocumentCount}</span>
);
},
},
},
{
title: <span>
<span>店铺展示</span>
<Tooltip title="开启后,用户可在店铺内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”店铺展示。关闭后,店铺内不再展示此课程,但用户仍可通过分享的海报/链接查看此课程。"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf'}}>&#xe61d;</i></Tooltip>
</span>,
width: "7%",
dataIndex: "courseware",
render: (val, item, index) => {
return (
<Switch defaultChecked={item.shelfState==="YES"?true:false} onChange={()=>this.changeShelfState(item)}/>
)
{
title: '上课数据',
width: "9%",
key: "quota",
dataIndex: "quota",
render: (val, item) => {
return (
<span className="iconfont icon quota-icon" onClick={() => {
this.handleLinkToClassData(item)
}}>&#xe7d6;</span>
);
},
},
},
{
title: "操作",
width: "15%",
key: "operate",
dataIndex: "operate",
render: (val, item) => {
return (
<div className="operate">
{ (item.courseState==="UN_START" || item.courseState==="STARTING") &&
<div
key="enter_live_room1"
className="operate__item"
onClick={() => { this.handleEnterLiveRoom(item) }}
>进入直播间
</div>
}
{ (item.courseState==="FINISH") &&
{
title: <span>
<span>店铺展示</span>
<Tooltip title={<div>开启后,用户可在店铺内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”店铺展示。<br/>关闭后,店铺内不再展示此课程,但用户仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf'}}>&#xe61d;</i></Tooltip>
</span>,
width: "7%",
dataIndex: "courseware",
render: (val, item, index) => {
return (
<Switch defaultChecked={item.shelfState==="YES"?true:false} onChange={()=>this.changeShelfState(item)}/>
)
},
},
{
title: "操作",
width: "15%",
key: "operate",
dataIndex: "operate",
render: (val, item) => {
return (
<>
<div className="operate">
{ ((item.courseState==="UN_START" || item.courseState==="STARTING") && (item.teacherId === User.getUserId() || _.pluck(item.admins, "adminId").includes(User.getUserId()))) &&
<>
<div
key="view_play_back"
key="enter_live_room1"
className="operate__item"
>查看回放</div>
onClick={() => { this.handleEnterLiveRoom(item) }}
>进入直播间
</div>
<span className="operate__item split" key="view_play_back_split"> | </span>
</>
}
{ (item.courseState==="FINISH" && item.haveRecord==="YES") &&
}
{ item.courseState!=="EXPIRED" &&
<>
<div
key="view_play_back"
className="operate__item"
onClick={()=>{this.handleViewPlayBack(item)}}
>查看回放</div>
<span className="operate__item split" key="view_play_back_split"> | </span>
</>
}
{ item.courseState!=="EXPIRED" &&
<>
<div
key="share"
className="operate__item"
onClick={() => { this.handleShowShareModal(item); }}
>
分享
</div>
</>
}
{ item.courseState!=="EXPIRED" &&
<>
<span className="operate__item split" key="view_play_back_split"> | </span>
<div
key="share"
className="operate__item"
onClick={() => { this.handleShowShareModal(item); }}
>
分享
<span key="split1" className="operate__item split"> | </span>
<div className="big-live">
<Dropdown overlay={this.renderMoreOperate(item)}>
<span className="more-operate">
<span className="operate-text">更多</span>
<span
className="iconfont icon"
style={{ color: "#5289FA" }}
>
&#xe824;
</span>
</span>
</Dropdown>
</div>
</>
}
{ item.courseState!=="EXPIRED" &&
<>
<span key="split1" className="operate__item split"> | </span>
<div className="big-live">
<Dropdown overlay={this.renderMoreOperate(item)}>
<span className="more-operate">
<span className="operate-text">更多</span>
<span
className="iconfont icon"
style={{ color: "#5289FA" }}
>
&#xe824;
</span>
</span>
</Dropdown>
</div>
</>
}
{ item.courseState==="EXPIRED" &&
<div
className="operate__item"
onClick={()=>this.handleDelete(item)}
>删除</div>
}
</div>
)
}
{ item.courseState==="EXPIRED" &&
<div
className="operate__item"
onClick={()=>this.handleDelete(item)}
>删除</div>
}
</div>
)
}
}
}
];
];
}else{
columns = [
{
title: "直播课",
width: "25%",
key: "course",
dataIndex: "courseName",
render: (val, record) => {
let hasCover = false;
return (
<div className="record__item">
{
record.courseMediaVOS.map((item,index)=>{
if( item.contentType === "COVER"){
hasCover = true;
return <img className="course-cover" src={item.mediaUrl} />
}
})
}
{ !hasCover &&
<img className="course-cover" src={'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png'} />
}
<div>
{ record.courseName.length > 17?
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
:
<div className="course-name">{record.courseName}</div>
}
<div>
<span className="course-time">{formatDate("YYYY-MM-DD H:i",parseInt(record.startTime))}~{formatDate("H:i", parseInt(record.endTime))}</span>
<span className="course-status" style={{color:courseStateShow[record.courseState].color,border:`1px solid ${courseStateShow[record.courseState].color}`}}>{courseStateShow[record.courseState].title}</span>
</div>
<div className="teacher-assistant">
{ record.teacherName.length > 4 ?
<Tooltip title={record.teacherName}>
<span className="teacher">讲师:{record.teacherName}</span>
</Tooltip>
:
<span className="teacher">讲师:{record.teacherName}</span>
}
{ record.admins.length >0 &&
<>
<span className="split"> | </span>
{ this.handleAdminName(record.admins).length > 4?
<Tooltip title={this.handleAdminName(record.admins)}>
<span className="assistant">助教:
{ record.admins.map((item,index)=>{
return <span>{item.adminName} { (index < record.admins.length-1)&&(<span></span>)} </span>
})
}
</span>
</Tooltip>
:
<span className="assistant">助教:
{ record.admins.map((item,index)=>{
return <span>{item.adminName} { (index < record.admins.length-1)&&(<span></span>)} </span>
})
}
</span>
}
</>
}
</div>
</div>
</div>
)
}
},
{
title: "课程分类",
width: "10%",
key: "couseCatalog",
dataIndex: "couseCatalog",
render: (val, item) => {
return (
<div className="categoryName">{item.categoryName}</div>
)
},
},
{
title: "课件管理",
width: "8%",
key: "courseware",
dataIndex: "courseware",
render: (val, item) => {
return (
<span className="courseware"
onClick={() => {
this.setState({
editData: item,
openCoursewareModal: true,
});
}}>{item.courseDocumentCount}</span>
);
},
},
{
title: '上课数据',
width: "9%",
key: "quota",
dataIndex: "quota",
render: (val, item) => {
return (
<span className="iconfont icon quota-icon" onClick={() => {
this.handleLinkToClassData(item)
}}>&#xe7d6;</span>
);
},
},
];
}
this.setState({ columns })
}
handleAdminName = (adminArray)=>{
let adminStr = "";
adminArray.map((item,index)=>{
if(index < adminArray.length-1){
adminStr = adminStr + item.adminName + '、';
}else{
adminStr = adminStr + item.adminName
}
})
return adminStr
}
renderMoreOperate = (item) => {
return (
<div className="live-course-more-menu">
......@@ -385,25 +538,24 @@ class LiveCourseList extends React.Component {
),
});
} else {
// axios
// .Apollo("public/businessLive/getCourseDetail", {
// liveCourseId: item.liveCourseId,
// })
// .then((res) => {
// const url = `xiaomai5://instId=${instId}&courseId=${item.liveCourseId}&teacherId=${item.teacherId}&uid=${ teacherId ? User_t.uid() : User.uid()}&aid=${teacherId ? "" : User.aid()}&tid=${teacherId || ""}&identity=${identity}&classType=${item.liveType}&xmVersion=${window.NewVersion ? '5.0' : '4.0'}`;
// if (res.result.courseState === "FINISH") {
// Modal.warning({
// title: "刷新页面",
// icon: <QuestionCircleOutlined />,
// content: "课次已结束,请刷新一下",
// onOk: () => {
// this.refreshCourseList();
// }
// });
// } else {
this.setState({ url:'', openDownloadModal: true });
// }
// });
CourseService.getLiveCloudCourseDetail({
liveCourseId: item.liveCourseId,
})
.then((res) => {
const url = `xmqx://liveCourseId=${item.liveCourseId}`;
if (res.result.courseState === "FINISH") {
Modal.warning({
title: "刷新页面",
icon: <QuestionCircleOutlined />,
content: "课次已结束,请刷新一下",
onOk: () => {
this.refreshCourseList();
}
});
} else {
this.setState({ url, openDownloadModal: true });
}
});
}
}
onShowSizeChange = (current, size) => {
......@@ -414,7 +566,35 @@ class LiveCourseList extends React.Component {
_query.size = size;
this.props.onChange(_query)
}
getDownloadVersion() {
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
// 判断用户系统
let platform;
if(!isMac){
platform = 1
}else{
platform = 4
}
BaseService
.getLastedVersion({ model: 5, platform})
.then((res) => {
const { result = {} } = res;
this.setState({ downloadUrl: result.releaseUrl });
})
}
handleViewPlayBack = (item) => {
let htmlUrl;
if(item.teacherId === User.getUserId()){
htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?teacherId=${User.getUserId()}&id=${User.getStoreId()}`;
}else if(_.pluck(item.admins, "adminId").includes(User.getUserId())){
htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?userId=${User.getUserId()}&id=${User.getStoreId()}`;
}else{
htmlUrl = `${LIVE_SHARE}replay/${item.liveCourseId}?id=${User.getStoreId()}`;
}
window.open(htmlUrl);
}
render() {
const { total, query, courseList, loading} = this.props;
const { current, size } = query;
......@@ -424,7 +604,7 @@ class LiveCourseList extends React.Component {
editData
} = this.state;
const { match } = this.props;
return (
<div className="live-course-list">
<Table
......
......@@ -17,7 +17,7 @@
color: #333333;
line-height: 20px;
font-weight: bold;
max-width:238px;
max-width:244px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
......@@ -77,9 +77,11 @@
color: #5289FA;
line-height: 20px;
text-align:right;
cursor:pointer;
}
.quota-icon{
color:#5289FA;
cursor:pointer;
}
.operate {
display: flex;
......@@ -124,8 +126,14 @@
font-size: 12px;
}
}
}
.ant-tooltip{
max-width:700px !important;
}
.ant-tooltip-inner{
max-width:700px !important;
}
.live-course-more-menu {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
......
......@@ -8,7 +8,10 @@
import React from 'react';
import { Button, Modal, message } from 'antd';
import Service from '@/common/js/service';
import './liveCourseOpt.less';
import BaseService from "@/domains/basic-domain/baseService";
import User from '@/common/js/user'
class LiveCourseOpt extends React.Component {
constructor(props) {
super(props);
......@@ -22,30 +25,30 @@ class LiveCourseOpt extends React.Component {
handleDownloadClient = () => {
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
// 判断用户系统
if(!isMac) {
// axios
// .Apollo("anon/version/getLastedVersion", { model: 1, platform: 1 })
// .then((res) => {
// const a = document.createElement("a");
// document.body.appendChild(a);
// a.href = res.result.releaseUrl;
// a.click();
// document.body.removeChild(a);
// })
}else {
Modal.info({
title: "抱歉,暂不支持Mac版",
content: "Mac版正在开发中,敬请期待",
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
okText: '我知道了'
});
let platform;
if(!isMac){
platform = 1
}else{
platform = 4
}
BaseService
.getLastedVersion({ model: 5, platform})
.then((res) => {
const a = document.createElement("a");
document.body.appendChild(a);
a.href = res.result.releaseUrl;
a.click();
document.body.removeChild(a);
})
}
render() {
const userRole = User.getUserRole();
return (
<div className="live-course-opt">
<div className="opt__left">
<Button type="primary" onClick={this.handleCreateLiveCouese}>新建直播课</Button>
{ userRole !== "CloudLecturer" &&
<Button type="primary" onClick={this.handleCreateLiveCouese}>新建直播课</Button>
}
<Button onClick={this.handleDownloadClient}>下载直播客户端</Button>
</div>
</div>
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-07-23 14:54:16
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-24 10:23:44
* @LastEditTime: 2021-01-09 16:26:03
* @Description: 大班直播课预览弹窗
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -36,7 +36,16 @@ class PreviewCourseModal extends React.Component {
}
}
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;
}
dealWithTime = (startTime, endTime) => {
const startDate = new Date(Number(startTime));
const endDate = new Date(Number(endTime));
......@@ -64,8 +73,8 @@ class PreviewCourseModal extends React.Component {
render() {
const { courseBasicInfo, courseClassInfo = {}, courseIntroInfo, type,courseState} = this.props;
const { coverUrl, courseName, scheduleVideoUrl } = courseBasicInfo;
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 { liveCourseMediaRequests } = courseIntroInfo;
......@@ -128,13 +137,15 @@ class PreviewCourseModal extends React.Component {
<img src={coverUrl} className="course-cover" />
}
</div>
{
type === 'videoCourse' ?
<div className="container__body">
<div className="title__name">{courseName}</div>
<div className="title__inst-name">{window.currentUserInstInfo.name}</div>
</div> :
{videoDuration &&
<div>视频时长:{this.dealTimeDuration(videoDuration)}</div>
}
</div>
:
<div className="container__body">
<div className="container__body__title">
<div className="title__name">{courseName}</div>
......@@ -144,13 +155,11 @@ class PreviewCourseModal extends React.Component {
<span className="time__label">上课时间:</span>
<span className="time__value">
{
[
<span>{liveDateStr}&nbsp;</span>,
<span>{startTimeStr}~{endTimeStr }</span>
]
}
</span>
</div>
<div className="container__body__teacher">
......@@ -161,7 +170,11 @@ class PreviewCourseModal extends React.Component {
}
<div className="container__introduction">
<div className="container__introduction__title">直播简介</div>
{ type === 'videoCourse' ?
<div className="container__introduction__title">视频简介</div>
:
<div className="container__introduction__title">直播简介</div>
}
<div className="container__introduction__list editor-box">
{
liveCourseMediaRequests.map((item, index) => {
......
......@@ -13,6 +13,7 @@ import html2canvas from 'html2canvas';
import qrcode from "@/libs/qrcode/qrcode.js";
import User from '@/common/js/user';
import $ from 'jquery';
import CourseService from "@/domains/course-domain/CourseService";
import './ShareLiveModal.less';
......@@ -27,7 +28,7 @@ class ShareLiveModal extends React.Component {
shareUrl: 'https://xiaomai5.com/liveShare?courseId=12'
}
}
componentDidMount() {
// 获取短链接
this.handleConvertShortUrl();
......@@ -36,22 +37,22 @@ class ShareLiveModal extends React.Component {
handleConvertShortUrl = () => {
const { longUrl } = this.props.data;
// // 发请求
// axios.Sales('public/businessShow/convertShortUrls', {
// urls: [longUrl]
// }).then((res) => {
// const { result = [] } = res;
// this.setState({
// shareUrl: result[0].shortUrl
// }, () => {
// const qrcodeWrapDom = document.querySelector('#qrcodeWrap');
// const qrcodeNode = new qrcode({
// text: this.state.shareUrl,
// size: 98,
// })
// qrcodeWrapDom.appendChild(qrcodeNode);
// });
// })
// 发请求
CourseService.getQrcode({
urls: [longUrl]
}).then((res) => {
const { result = [] } = res;
this.setState({
shareUrl: result[0].shortUrl
}, () => {
const qrcodeWrapDom = document.querySelector('#qrcodeWrap');
const qrcodeNode = new qrcode({
text: this.state.shareUrl,
size: 98,
})
qrcodeWrapDom.appendChild(qrcodeNode);
});
})
}
componentWillUnmount() {
......@@ -87,13 +88,13 @@ class ShareLiveModal extends React.Component {
// 判断是否是默认图, 默认图不需要在URL后面增加字符串
const isDefaultCover = coverUrl === DEFAULT_COVER;
const coverImgSrc = type === 'videoClass'
// 如果是默认图, 显示视频的第一帧, 否则显示上传的视频封面
? ((!coverUrl || isDefaultCover)
? `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring`
: `${coverUrl}${!needStr ? '&anystring=anystring': ''}`)
: `${coverUrl}${(!needStr && !isDefaultCover) ? '&anystring=anystring' : ''}`
: `${coverUrl}`)
: `${coverUrl}`
return (
......@@ -112,10 +113,14 @@ class ShareLiveModal extends React.Component {
</div>
<div className="course-name-title">{type === 'videoClass' ? `${courseName}开课啦`: `邀请你观看直播:`}</div>
<div className="course-name">{courseName}</div>
{
type === "liveClass" &&
<div class="live-couse-name">{courseName}</div>
}
<img
src={coverImgSrc}
crossOrigin="*"
className="course-cover"
/>
......@@ -132,13 +137,26 @@ class ShareLiveModal extends React.Component {
<div className="right">
<div className="share-poster right__item">
<div className="title">① 海报分享</div>
<div className="sub-title">学生可通过微信识别二维码,报名观看直播</div>
{ type === "liveClass" &&
<div className="sub-title">用户可通过微信扫描海报二维码,观看直播</div>
}
{ type === "videoClass" &&
<div className="sub-title">用户可通过微信识别二维码,报名观看视频</div>
}
<div className="content" onClick={this.handleDownloadPoster}>下载海报</div>
</div>
<div className="share-url right__item">
<div className="title">② 链接分享</div>
<div className="sub-title">学生可通过微信打开链接,报名观看直播</div>
{ type === "liveClass" &&
<div className="sub-title">用户可通过微信打开以下链接,观看直播</div>
}
{ type === "videoClass" &&
<div className="sub-title">用户可通过打开链接,报名观看视频</div>
}
<div className="content">
<div className="share-url" id="shareUrl">{shareUrl}</div>
<Button type="primary" onClick={this.handleCopy}>复制</Button>
......
......@@ -14,6 +14,11 @@
line-height: 20px;
margin-bottom: 4px;
}
.live-couse-name{
font-size:16px;
color:#333333;
font-weight: 600;
}
.course-name {
color: #333;
font-size: 16px;
......
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:07:47
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-01-14 18:59:12
* @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 { ImgCutModalNew } from '@/components';
import ShowTips from "@/components/ShowTips";
import Breadcrumbs from "@/components/Breadcrumbs";
import AddVideoIntro from './components/AddVideoIntro';
import SelectStudent from '../modal/select-student';
import SelectPrepareFileModal from '../../prepare-lesson/modal/SelectPrepareFileModal';
import PreviewCourseModal from '../modal/PreviewCourseModal';
import StoreService from "@/domains/store-domain/storeService";
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user';
import _ from "underscore";
import './AddVideoCourse.less';
const EDIT_BOX_KEY = Math.random();
const fieldNames = { label: 'categoryName', value: 'id', children: 'sonCategoryList' };
//添加课程时课程默认的一些值
const defaultShelfState = 'YES';
const defaultScheduleMedia = [{
contentType:'INTRO',
mediaType: 'TEXT',
mediaContent: '',
key: EDIT_BOX_KEY
}]
const whetherVisitorsJoin = 'NO'
class AddVideoCourse extends React.Component {
constructor(props) {
super(props);
const id = getParameterByName("id");
const pageType = getParameterByName("type");
this.state = {
id, // 视频课ID,编辑的时候从URL上带过来
pageType, // 页面类型: add->新建 edit->编辑
imageFile: null, // 需要被截取的图片
courseName: null, // 视频课名称
scheduleVideoId: null, // 视频课链接
coverId: null, // 视频封面的recourceId
coverUrl: null, // 视频课封面
studentList: [], // 上课学员列表
shelfState:'YES', //是否开启店铺展示
scheduleMedia: [{ // 视频课媒体资源
contentType:"INTRO",
mediaType: 'TEXT',
mediaContent: '',
key: EDIT_BOX_KEY
}],
diskList: [], // 机构可见磁盘目录
selectedFileList: [], // 已经从资料云盘中勾选的文件
showCutModal: false, // 是否显示截图弹窗
showSelectFileModal: false,
studentModal: false,
categoryName:null, //分类名称
courseCatalogList:[], //分类列表
categoryId:null, //分类的Id值
whetherVisitorsJoin:'NO' // 是否允许游客加入
}
}
componentWillMount() {
// this.handleFetchDiskList();
const { id, pageType } = this.state;
this.getCourseCatalogList();
if (pageType === 'edit') {
this.handleFetchScheudleDetail(id);
// this.handleFetchStudentList(id);
}
}
//获取分类列表
getCourseCatalogList = ()=>{
StoreService.getCourseCatalogList({current:1,size:1000}).then((res) => {
this.setState({
courseCatalogList:res.result.records
})
});
}
catalogChange= (value) => {
const changeValueLength = value.length;
switch (changeValueLength){
case 1:
this.setState({categoryId:value[0]});
break;
case 2:
this.setState({categoryId:value[1]});
break;
default:
this.setState({categoryId:null});
break;
}
}
// 获取视频课详情
handleFetchScheudleDetail = (courseId) => {
CourseService.videoScheduleDetail({
courseId
}).then((res) => {
const { result = {} } = res || {};
const {
// coverId,
// coverUrl,
// videoType,
// videoDuration,
// videoName,
// scheduleMedia,
courseName,
// scheduleVideoId,
// scheduleVideoUrl,
shelfState,
whetherVisitorsJoin,
courseMediaVOS,
categoryOneName,
categoryTwoName,
categoryId
} = result;
let coverId;
let coverUrl;
let videoType;
let videoDuration;
let videoName;
let scheduleMedia = [];
let scheduleVideoId;
let scheduleVideoUrl;
courseMediaVOS.map((item) => {
switch (item.contentType){
case "COVER":
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":
scheduleMedia = [...scheduleMedia,item]
break;
default:
break;
}
return item;
})
let categoryName;
if( categoryTwoName ){
categoryName = `${categoryOneName}-${categoryTwoName}`;
}else{
categoryName = `${categoryOneName}`;
}
this.setState({
coverId,
coverUrl,
videoType,
videoName,
videoDuration,
scheduleMedia,
courseName,
scheduleVideoId,
scheduleVideoUrl,
shelfState,
whetherVisitorsJoin,
categoryName,
categoryId
});
})
}
handleGoBack = () => {
const {
coverId,
videoName,
videoDuration,
courseName,
scheduleMedia,
scheduleVideoId,
categoryId,
shelfState,
whetherVisitorsJoin
} = this.state;
if(videoName || videoDuration || scheduleVideoId || !_.isEqual(scheduleMedia, defaultScheduleMedia) || categoryId || courseName || coverId || shelfState !== defaultShelfState || whetherVisitorsJoin !== whetherVisitorsJoin ){
Modal.confirm({
title: '确认要返回吗?',
content: '返回后,本次编辑的内容将不被保存。',
okText: '确认返回',
cancelText: '留在本页',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
RCHistory.goBack();
}
});
}else{
RCHistory.goBack();
}
}
// 修改表单
handleChangeForm = (field, value, coverUrl) => {
console.log('field',value);
this.setState({
[field]: value,
coverUrl: coverUrl ? coverUrl : this.state.coverUrl
});
}
// 显示选择学员弹窗
handleShowSelectStuModal = () => {
this.setState({ studentModal : true });
const { studentList, selectedStuList } = this.state;
// const _studentList = _.map(studentList, (item) => {
// return item.studentId
// })
const studentModal = (
<SelectStudent
showTabs={true}
type="videoCourse"
onSelect={this.handleSelectStudent}
after={true} //表明是不是上课后的状态
studentList={studentList}
close={() => {
this.setState({
studentModal: null,
});
}}
/>
)
this.setState({ studentModal });
}
handleSelectStudent = (studentIds) => {
let studentList = [];
_.each(studentIds, (item) => {
studentList.push({ studentId: item });
});
// this.setState({ studentModal: null });
this.setState({ studentList });
this.setState({ studentModal : false });
}
// 显示预览弹窗
handleShowPreviewModal = () => {
const {
coverUrl,
scheduleVideoUrl,
courseName,
scheduleMedia,
videoDuration
} = this.state;
const courseBasinInfo = {
coverUrl,
scheduleVideoUrl,
courseName,
videoDuration
}
const courseIntroInfo = {
liveCourseMediaRequests: scheduleMedia
}
const previewCourseModal = (
<PreviewCourseModal
type="videoCourse"
courseBasicInfo={courseBasinInfo}
courseIntroInfo={courseIntroInfo}
close={() => {
this.setState({
previewCourseModal: null
})
}}
/>
);
this.setState({ previewCourseModal });
}
// 选择视频
handleSelectVideo = (file) => {
this.setState({
showSelectFileModal: false
})
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
const videoDom = document.createElement('video');
videoDom.src = ossUrl;
videoDom.onloadedmetadata = () => {
this.setState({
size: folderSize,
videoName: folderName,
videoType: folderFormat,
scheduleVideoUrl: ossUrl,
scheduleVideoId : resourceId,
videoDuration: videoDom.duration
});
}
}
// 上传封面图
handleShowImgCutModal = (event) => {
const imageFile = event.target.files[0];
if (!imageFile) return;
this.setState({
imageFile,
showCutModal: true,
});
}
// 保存
handleSubmit = () => {
const { instId, adminId } = window.currentUserInstInfo;
const {
id,
size,
coverId,
coverUrl,
pageType,
joinType,
videoName,
videoDuration,
studentList,
courseName,
scheduleMedia,
scheduleVideoId,
scheduleVideoUrl,
categoryId,
shelfState,
whetherVisitorsJoin
} = this.state;
const commonParams = {
videoName,
videoDuration,
scheduleVideoId,
scheduleMedia: scheduleMedia.filter(item => !!item.mediaContent),
categoryId,
courseName,
coverId,
operatorId:User.getStoreUserId(),
storeId:User.getStoreId(),
shelfState,
whetherVisitorsJoin
};
// 校验必填字段:课程名称, 课程视频
this.handleValidate(courseName, scheduleVideoId,categoryId, scheduleMedia).then((res) => {
if (!res) return;
if (pageType === 'add') {
CourseService.createVideoSchedule(commonParams).then((res) => {
if (!res) return;
message.success("新建成功");
window.RCHistory.goBack();
})
} else {
const editParams = {
courseId:id,
...commonParams,
}
CourseService.editVideoSchedule(editParams).then((res) => {
if (!res) return;
message.success("保存成功");
window.RCHistory.goBack();
});
}
});
}
handleValidate = (courseName, scheduleVideoId,categoryId,scheduleMedia) => {
return new Promise((resolve) => {
if (!courseName) {
message.warning('请输入课程名称');
resolve(false);
return false
}
if (!scheduleVideoId) {
message.warning('请上传视频');
resolve(false);
return false
}
if(!categoryId){
message.warning('请选择课程分类');
resolve(false);
return false
}
const textMedia = scheduleMedia.filter((item) => item.mediaType === 'TEXT');
for (let i = 0, len = textMedia.length; i < len; i++) {
if (textMedia[i].mediaContentLength && textMedia[i].mediaContentLength.length > 1000) {
message.warning(`第${i+1}个文字简介的字数超过了1000个字`);
resolve(false);
return false
}
}
resolve(true);
});
}
render() {
const {
pageType,courseName, scheduleVideoId, coverId,
coverUrl, scheduleVideoUrl, studentList, scheduleMedia,
showCutModal, showSelectFileModal, diskList,
imageFile, joinType, videoName, videoType,shelfState,
categoryName,courseCatalogList,whetherVisitorsJoin
} = this.state;
// 已选择的上课学员数量
const hasSelectedStu = studentList.length;
const courseWareIcon = FileVerifyMap[videoType] ? FileTypeIcon[FileVerifyMap[videoType].type] : FileTypeIcon[videoType];
return (
<div className="page add-video-course-page">
<Breadcrumbs
navList={pageType === "add" ? "新建视频课" : "编辑视频课"}
goBack={this.handleGoBack}
/>
<div className="box">
<div className="show-tips">
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企培保有依据国家规定及平台规则进行处理的权利" />
</div>
<div className="form">
<div className="course-name required">
<span className="label">课程名称:</span>
<Input
value={courseName}
placeholder="请输入视频课的名称(40字以内)"
maxLength={40}
style={{ width: 240 }}
onChange={(e) => { this.handleChangeForm('courseName', e.target.value)}}
/>
</div>
<div className="upload-video mt16">
<div className="content flex">
<span className="label required">视频上传:</span>
<div className="value">
{
scheduleVideoId ?
<div className="course-ware">
<img className="course-ware__img" src={courseWareIcon} />
<span className="course-ware__name">{videoName}</span>
</div> :
<div className="course-ware--empty">从资料云盘中选择视频</div>
}
</div>
</div>
<div className="sub-content">
<Button
type="primary"
onClick={() => {
this.setState({
showSelectFileModal: true
})
}}
>{`${(pageType === 'add' && !scheduleVideoId) ? '选择' : '更换'}视频`}</Button>
<span className="tips">视频数量限制1个,大小不超过500M</span>
</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>
<div className="opt-btns">
<input
type="file"
accept="image/png, image/jpeg, image/jpg"
ref="picInputFile"
style={{display: 'none'}}
onChange={(event) => { this.handleShowImgCutModal(event) }}
/>
<Button onClick={() => {
this.setState({
currentInputFile: this.refs.picInputFile
});
this.refs.picInputFile.click()
}}>{`${(pageType === 'add' && (!scheduleVideoId && !coverUrl)) ? '上传' : '修改'}封面`}</Button>
<div className="tips">建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div>
</div>
</div>
<div className="course-catalog">
<span className="label"><span className="require">*</span>课程分类:</span>
{ (pageType === 'add') &&
<Cascader defaultValue={[categoryName]} options={courseCatalogList} displayRender={ label => label.join('-')} fieldNames={fieldNames} onChange={this.catalogChange} style={{ width: 240 }} placeholder="请选择课程分类" />
}
{ (pageType === 'edit' && categoryName ) &&
<Cascader defaultValue={[categoryName]} options={courseCatalogList} displayRender={ label => label.join('-')} fieldNames={fieldNames} onChange={this.catalogChange} style={{ width: 240 }} placeholder="请选择课程分类" />
}
</div>
<div className="intro-info mt16">
<AddVideoIntro
data={{
liveCourseMediaRequests: scheduleMedia,
shelfState,
whetherVisitorsJoin,
label: '视频课简介'
}}
onChange={this.handleChangeForm}
/>
</div>
</div>
</div>
<div className="footer">
<Button onClick={this.handleGoBack}>取消</Button>
<Button onClick={this.handleShowPreviewModal}>预览</Button>
<Button type="primary" onClick={_.debounce(() => this.handleSubmit(), 3000, true)}>保存</Button>
</div>
{/* 选择备课文件弹窗 */}
<SelectPrepareFileModal
operateType="select"
selectTypeList={['MP4']}
accept="video/mp4"
confirm={{
title: '文件过大,无法上传',
content: '为保障学员的观看体验,上传的视频大小不能超过500M',
}}
tooltip={'格式支持mp4,大小不超过500M'}
isOpen={showSelectFileModal}
diskList={diskList}
addVideo={true}
onClose={() => {
this.setState({ showSelectFileModal: false })
}}
onSelect={this.handleSelectVideo}
/>
<ImgCutModalNew
title="裁剪"
width={550}
cutWidth={500}
cutHeight={282}
cutContentWidth={500}
cutContentHeight={300}
visible={showCutModal}
imageFile={imageFile}
bizCode='LIVE_COURSE_MEDIA'
onOk={(urlStr, resourceId) => {
this.setState({ showCutModal: false });
this.handleChangeForm('coverId', resourceId, urlStr)
this.state.currentInputFile.value = '';
}}
onClose={() => this.setState({ showCutModal: false })}
reUpload={() => { this.state.currentInputFile.click() }}
/>
{ this.state.previewCourseModal }
</div>
)
}
}
export default AddVideoCourse;
.add-video-course-page {
.ant-radio-group {
display: flex;
flex-direction: column;
.radio-item {
margin-bottom: 12px;
.text {
color: #333;
}
.sub-text {
color: #999;
}
}
.ant-radio {
vertical-align: top;
padding-top: 2px;
}
}
.form {
margin-top: 16px;
padding: 0 16px;
.required {
position: relative;
&::before {
position: absolute;
content: '*';
color: red;
left: -10px;
top: 6px;
}
&.label::before {
top: 0;
}
}
.course-catalog{
margin-bottom:16px;
margin-top:16px;
}
.course-ware {
display: flex;
align-items: center;
margin-bottom: 4px;
&__img {
width: 24px;
margin-right: 4px;
}
&__name {
color: #333;
}
}
.flex {
display: flex;
}
.cover-url__wrap {
.img-content {
width: 298px;
height: 172px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.empty-img {
width: 298px;
height: 172px;
border: 1px dashed #EBEBEB;
border-radius: 4px;
padding: 12px;
color: #999;
padding: 52px 24px;
text-align: center;
}
.opt-btns {
margin-top: 8px;
display: flex;
align-items: center;
.tips {
margin-left: 12px;
color: #999;
}
}
}
.select-student {
align-items: center;
margin-left: 24px;
margin-top: 8px;
.has-selected {
margin-left: 12px;
color: #333;
}
}
.sub-content {
margin-left: 70px;
margin-top: 4px;
.tips {
margin-left: 16px;
color: #999;
}
}
}
.footer {
position: fixed;
bottom: 0;
height: 58px;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 252px;
background: #fff;
border-top: 1px solid #E8E8E8;
z-index: 999;
.ant-btn {
margin-left: 10px;
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-16 11:05:17
* @Last Modified by: mikey.zhaopeng
* @Last Modified time: 2020-11-24 14:29:52
* @Description: 添加直播-简介
*/
import React from 'react';
import { Input, message, Upload, Radio, Row, Col, Button, Popover, Switch } from 'antd';
import Service from '@/common/js/service';
import EditorBox from '../../components/EditorBox';
import User from '@/common/js/user';
import UploadOss from '@/core/upload';
import './AddVideoIntro.less';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
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 AddVideoIntro extends React.Component {
constructor(props) {
super(props);
this.state = {
warmUrl: defaultCover,
showSelectFileModal: false,
diskList: [],
selectType:null
}
}
// 上传封面图
handleShowImgCutModal = (event) => {
const imageFile = event.target.files[0];
if (!imageFile) return;
this.setState({
imageFile,
showCutModal: true,
});
}
// 选择暖场资源
handleSelectVideo = (file) => {
const { selectType } = this.state;
this.setState({
showSelectFileModal: false
})
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
if(selectType === 'WARMUP'){
const liveCourseWarmMedia = {
contentType: 'WARMUP',
mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE',
mediaContent: resourceId,
mediaUrl: ossUrl,
mediaName: folderName,
size: folderSize
}
this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia);
}else{
// 最多添加九图片
const { liveCourseMediaRequests } = this.props.data;
const list = _.filter(liveCourseMediaRequests, (item) => {
return item.mediaType == "PICTURE";
});
if (list.length > 8) {
message.warning("最多添加9张图片");
return;
}
liveCourseMediaRequests.push({
contentType: 'INTRO',
size: folderSize,
mediaName: folderName,
mediaContent: resourceId,
mediaType: 'PICTURE',
mediaUrl: ossUrl,
});
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
}
// 删除简介
handleDeleteIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests.splice(index, 1);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
// 上移简介
handleMoveUpIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
const prevItem = liveCourseMediaRequests[index];
const nextItem = liveCourseMediaRequests[index + 1];
liveCourseMediaRequests.splice(index, 2, nextItem, prevItem);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
// 下移简介
handleMoveDownIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
const prevItem = liveCourseMediaRequests[index - 1];
const nextItem = liveCourseMediaRequests[index];
liveCourseMediaRequests.splice(index - 1, 2, nextItem, prevItem);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
renderLittleIcon = (index) => {
const { liveCourseMediaRequests } = this.props.data;
return (
<div className="little-icon">
<span
className="icon iconfont close"
onClick={() => { this.handleDeleteIntro(index); }}
></span>
{
index > 0 &&
<span
className="icon iconfont"
onClick={() => { this.handleMoveDownIntro(index); }}
>&#xe6d1;</span>
}
{
index !== liveCourseMediaRequests.length - 1 &&
<span
className="icon iconfont"
onClick={() => { this.handleMoveUpIntro(index); }}
>&#xe6cf;</span>
}
</div>
)
}
handleChangeIntro = (index, value, length) => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests[index].mediaContent = value;
liveCourseMediaRequests[index].mediaContentLength = length
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
handleAddIntroText = () => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests.push({
contentType:"INTRO",
mediaType: 'TEXT',
mediaContent: '',
key: Math.random()
});
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
handleUpload = (Blob) => {
this.setState({
showSelectFileModal: true,
selectType:'INTRO'
})
}
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')
}
}
componentWillMount() {
}
render() {
const {data: { whetherVisitorsJoin,liveCourseMediaRequests = [], shelfState} } = this.props;
const {showSelectFileModal,selectType} = this.state
return (
<div className="add-video__intro-info">
<div className="allow-tourist-join">
<span className="label">观看设置:</span>
<div className="content">
<Row>
<Col span={3}>
<Switch checked={whetherVisitorsJoin==="YES"? true:false} onChange={this.whetherVisitorsJoinChange}/>
</Col>
<Col span={21}>
<div className="desc">
<div>开启:允许未绑定手机号的用户进入直播间观看直播</div>
<div>关闭:仅限绑定了手机号的用户可以进入直播间观看直播</div>
</div>
</Col>
</Row>
</div>
</div>
<div className="store-show">
<span className="label">店铺展示:</span>
<div className="content">
<Row>
<Col span={3}>
<Switch checked={shelfState==="YES"? true:false} onChange={this.shelfStateChange}/>
</Col>
<Col span={21}>
<div className="desc">
<div>开启:此视频将在用户店铺的视频列表中出现</div>
<div>关闭:此视频将在用户店铺的视频列表中隐藏</div>
</div>
</Col>
</Row>
</div>
</div>
<div className="introduce">
<span className="label">视频课简介:</span>
<div className="content">
<div className="intro-list">
{
liveCourseMediaRequests.map((item, index) => {
if (item.mediaType === 'TEXT') {
return (
<div className="intro-list__item" key={item.key}>
<EditorBox
detail={{
content: item.mediaContent
}}
onChange={(val, length) => { this.handleChangeIntro(index, val, length) }}
/>
{this.renderLittleIcon(index)}
</div>
)
}
if (item.mediaType === 'PICTURE') {
return (
<div className="intro-list__item picture" key={index}>
<div className="img__wrap">
<img src={item.mediaUrl} />
</div>
{this.renderLittleIcon(index)}
</div>
)
}
})
}
</div>
<div className="operate">
<div className="operate__item" onClick={this.handleAddIntroText}>
<span className="icon iconfont">&#xe639;</span>
<span className="text">文字</span>
</div>
<div className="operate__item" onClick={this.handleUpload}>
<span className="icon iconfont">&#xe63b;</span>
<span className="text">图片</span>
</div>
</div>
<div className="tips">
• 图片支持jpeg、jpg、png、gif格式
</div>
</div>
</div>
{/* 选择暖场图文件弹窗 */}
<SelectPrepareFileModal
operateType="select"
accept={selectType==="INTRO"?"image/jpeg,image/png,image/jpg":"video/mp4,image/jpeg,image/png,image/jpg"}
selectTypeList={ selectType==="INTRO" ? ['JPG', 'JPEG', 'PNG']: ['MP4', 'JPG', 'JPEG', 'PNG'] }
tooltip={ selectType==="INTRO"?'支持文件类型:jpg、jpeg、png':'支持文件类型:jpg、jpeg、png、mp4'}
isOpen={showSelectFileModal}
onClose={() => {
this.setState({ showSelectFileModal: false })
}}
onSelect={this.handleSelectVideo}
/>
</div>
)
}
}
export default AddVideoIntro;
.add-video__intro-info {
.playback {
margin-bottom: 10px;
.require {
color: #EC4B35;
}
&__text {
color: #999;
}
}
.playback,
.introduce {
display: flex;
flex-direction: row;
}
.allow-tourist-join{
display:flex;
.desc{
margin-left:16px;
font-size:14px;
color:#999;
}
}
.store-show{
display:flex;
margin-top:16px;
margin-bottom:16px;
.desc{
margin-left:16px;
font-size:14px;
color:#999;
}
}
.radio {
display: block;
height: 30px;
line-height: 30px;
}
.interactive-playback {
display: flex;
margin-bottom: 20px;
}
textarea.ant-input {
min-height: 80px;
}
.intro-list__item {
display: flex;
margin-bottom: 16px;
position: relative;
&.picture {
width: 550px;
padding: 16px;
border: 1px solid #EEE;
border-radius: 4px;
.img__wrap {
width: 299px;
height: 168px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
.little-icon {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
right: -20px;
.iconfont {
width: 20px;
height: 20px;
line-height: 20px;
font-size: 20px;
color: #999;
margin-bottom: 4px;
cursor: pointer;
&.close {
margin-top: 8px;
background-image: url('https://image.xiaomaiketang.com/xm/eesMPhNP3e.png');
background-size: 100% 100%;
}
}
}
}
.operate {
display: flex;
align-items: center;
justify-content: center;
width: 550px;
height: 80px;
line-height: 80px;
padding: 16px;
margin-top: 16px;
border: 1px dashed #EBEBEB;
border-radius: 4px;
.ant-upload {
vertical-align: middle;
}
&__item {
display: flex;
flex-direction: column;
cursor: pointer;
&:not(:last-child) {
margin-right: 82px;
}
.iconfont {
font-size: 22px;
line-height: 22px;
color: #BFBFBF;
text-align: center;
}
.text {
color: #999;
line-height: 20px;
margin-top: 4px;
}
}
}
.tips {
color: #999;
margin-top: 16px;
margin-bottom: 8px;
}
.checkExample {
width: 60px;
color: #FF7519;
cursor: pointer;
}
.warmup {
margin-bottom: 17px;
display: flex;
}
.course-cover__wrap {
display: flex;
flex-direction: row;
}
.img-content {
position: relative;
margin-right: 20px;
width: 300px;
height: 170px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
.img-delete-wrap {
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
img {
position: absolute;
left: 50%;
top: 50%;
width: 40px;
height: 40px;
transform: translate(-50%, -50%);
}
&:hover {
opacity: 1;
cursor: pointer;
}
}
}
.opt-btns {
.default-btn {
margin-left: 16px;
color: #FF7519;
cursor: pointer;
&.disabled {
color: #CCC;
cursor: not-allowed;
}
}
}
}
.example-wrap {
font-family: PingFangSC-Regular, PingFang SC;
text-align: center;
.title {
margin-bottom: 6px;
font-size: 14px;
color: #333333;
}
.text {
margin-bottom: 16px;
font-size: 12px;
color: #999999;
}
img {
width: 180px;
}
}
.check-record-rule {
width: 120px;
color: #FF7519;
cursor: pointer;
z-index: 2;
}
.record-rule-wrap {
text-align: left;
ul {
margin-top: 10px;
padding-left: 34px;
list-style-type: disc;
li {
color: #999;
}
}
.text {
color: #999;
}
}
.auto-send-class-report {
.label {
width: 57px;
height: 12px;
font-size: 14px;
font-weight: 400;
color: #666666;
line-height: 12px;
}
.open-text, .close-text {
width: 572px;
font-size: 14px;
font-weight: 400;
color: #999999;
line-height: 20px;
margin-left: 100px;
margin-top: 5px;
}
.open-text {
margin-top: 8px;
}
.close-text {
margin-bottom: 16px;
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:11:57
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-01-15 13:52:10
* @Description: 视频课-搜索模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Row, Input, Select,Tooltip } from 'antd';
import RangePicker from "@/modules/common/DateRangePicker";
// import TeacherSelectV5 from '@/modules/classManage_V5/classDetail/TeacherSelectV5';
import './VideoCourseFilter.less';
import moment from 'moment';
import StoreService from "@/domains/store-domain/storeService";
const { Search } = Input;
const { Option } = Select;
const DEFAULT_QUERY = {
courseName: null, // 课程名称
operatorId: null, // 创建人
beginTime: null, // 开始日期
endTime: null, // 结束日期
shelfState:null,
}
const defaultTeacherQuery = {
size: 10,
current: 1,
nickName:null
}
class VideoCourseFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
query: { ...DEFAULT_QUERY }, // 使用扩展运算符,避免浅拷贝
teacherQuery: defaultTeacherQuery,
teacherList:[],
expandFilter:false
}
}
componentDidMount() {
this.getTeacherList();
}
getTeacherList(current = 1, selectList){
const { teacherQuery,teacherList} = this.state;
const _query = {
...teacherQuery,
current,
size:10
};
StoreService.getStoreUserBasicPage( _query).then((res) => {
const { result = {} } = res;
const { records = [], total = 0, hasNext } = result;
const list = current > 1 ? teacherList.concat(records) : records;
this.setState({
hasNext,
teacherList: list,
})
});
}
// 滑动加载更多讲师列表
handleScrollTeacherList = (e) => {
const { hasNext } = this.state;
const container = e.target;
const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop;
if (scrollToBottom && hasNext) {
const { teacherQuery } = this.state;
this.getTeacherList(teacherQuery.current + 1);
}
}
// 改变搜索条件
handleChangeQuery = (field, value) => {
this.setState({
query: {
...this.state.query,
[field]: value,
current: 1,
}
}, () => {
if (field === 'courseName') return;
this.props.onChange(this.state.query)
});
}
handleChangeDates = (dates) => {
const query = _.clone(this.state.query);
if (_.isEmpty(dates)) {
delete query.beginTime;
delete query.endTime;
} else {
query.beginTime = dates[0].valueOf();
query.endTime = dates[1].valueOf();
}
this.setState({
query:{
...query,
current: 1,
}
}, () => {
this.props.onChange(this.state.query);
})
}
// 重置搜索条件
handleReset = () => {
this.setState({
query: DEFAULT_QUERY,
}, () => {
this.props.onChange(this.state.query);
})
}
render() {
const {
query: {
courseName,
operator,
beginTime,
endTime,
operatorId,
shelfState
},
expandFilter,
teacherList,
teacherQuery
} = this.state;
return (
<div className="video-course-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">视频课名称:</span>
<Search
value={courseName}
placeholder="搜索视频课名称"
onChange={(e) => { this.handleChangeQuery('courseName', e.target.value)}}
onSearch={ () => { this.props.onChange(this.state.query) } }
style={{ width: "calc(100% - 84px)" }}
/>
</div>
<div className="search-condition__item">
<span>创建人:</span>
<Select
placeholder="请选择创建人"
style={{width:"calc(100% - 70px)"}}
showSearch
allowClear
filterOption={(input, option) => option}
onPopupScroll={this.handleScrollTeacherList}
value={operatorId}
onChange={(value) => {
this.handleChangeQuery('operatorId', value)
}}
onSearch={(value) => {
teacherQuery.nickName = value
this.setState({
teacherQuery
}, () => {
this.getTeacherList()
})
}}
onClear ={(value)=>{
this.setState({
teacherQuery:{
size: 10,
current: 1,
nickName:null
}
}, () => {
this.getTeacherList()
})
}
}
>
{_.map(teacherList, (item, index) => {
return (
<Select.Option value={item.id} key={item.id}>{item.nickName}</Select.Option>
);
})}
</Select>
{/* <TeacherSelectV5
ref="TeacherSelect"
showSearch={true}
allowClear={true}
style={{ width: "calc(100% - 70px)" }}
onSelect={(teacherId) => { this.handleChangeQuery('teacherId', teacherId)}}
placeholder='请选择创建人'
label='创建人'
defaultValue={teacherId}
/> */}
</div>
<div className="search-condition__item">
<span className="search-date">创建日期:</span>
<RangePicker
id="course_date_picker"
allowClear={false}
value={ beginTime ? [moment(beginTime), moment(endTime)] : null }
format={"YYYY-MM-DD"}
onChange={(dates) => { this.handleChangeDates(dates) }}
style={{ width: "calc(100% - 70px)" }}
/>
</div>
{ expandFilter &&
<div className="search-condition__item">
<span className="shelf-status">店铺展示:</span>
<Select
style={{ width: "calc(100% - 84px)" }}
placeholder="请选择"
allowClear={true}
value={shelfState}
onChange={(value) => { this.handleChangeQuery('shelfState', value) }}
>
<Option value="YES">开启</Option>
<Option value="NO">关闭</Option>
</Select>
</div>
}
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={this.handleReset}>&#xe61b; </span></Tooltip>
<span style={{ cursor: 'pointer' }} className="fold-btn" onClick={() => {
this.setState({expandFilter:!expandFilter});
}}>{this.state.expandFilter ? <span><span>收起</span><span className="iconfont icon fold-icon" >&#xe82d; </span> </span> : <span>展开<span className="iconfont icon fold-icon" >&#xe835; </span></span>}</span>
</div>
</Row>
</div>
)
}
}
export default VideoCourseFilter;
.video-course-filter {
position: relative;
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 12px;
.search-name{
vertical-align: middle;
}
.shelf-status{
width:84px;
display:inline-block;
text-align:right;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
}
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
}
}
}
.data-icon {
cursor: pointer;
}
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:45
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-01-15 19:58:12
* @Description: 视频课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Table, Modal, message , Tooltip,Switch,Dropdown} from 'antd';
import { PageControl } from "@/components";
import { LIVE_SHARE_MAP } from '@/common/constants/academic/cloudClass';
import { appId, shareUrl, LIVE_SHARE } from '@/domains/course-domain/constants';
import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal';
import WatchDataModal from '../modal/WatchDataModal'
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user'
import './VideoCourseList.less';
const ENV = process.env.DEPLOY_ENV || 'rc';
class VideoCourseList extends React.Component {
constructor(props) {
super(props);
this.state = {
id: '', // 视频课ID
studentIds:[]
}
}
componentDidMount() {
const videoCourseItem = localStorage.getItem('videoCourseItem');
if (videoCourseItem) {
const _videoCourseItem = JSON.parse(videoCourseItem);
this.handleShowShareModal(_videoCourseItem, true);
}
}
// 观看数据弹窗
handleShowWatchDataModal = (record) => {
console.log('111');
console.log("record",record);
const watchDataModal = (
<WatchDataModal
type='videoCourseList'
data={record}
close={() => {
this.setState({
watchDataModal: null
});
}}
/>
);
this.setState({ watchDataModal });
}
// 请求表头
parseColumns = () => {
const columns = [
{
title: '视频课',
key: 'scheduleName',
dataIndex: 'scheduleName',
width: '20%',
render: (val, record) => {
const { coverUrl, scheduleVideoUrl } = record;
return (
<div className="record__item">
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img className="course-cover" src={coverUrl || `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`} />
{ record.courseName.length > 25?
<Tooltip title={record.courseName}>
<div className="course-name">{record.courseName}</div>
</Tooltip>
:
<div className="course-name">{record.courseName}</div>
}
</div>
)
}
},
{
title: '课程分类',
key: 'categoryName',
dataIndex: 'categoryName',
render: (val, record) => {
return (
<div className="record__item">
{record.categoryOneName}{ record.categoryTwoName?`-${record.categoryTwoName}`:''}
</div>
)
}
},
{
title: '创建人',
key: 'createName',
dataIndex: 'createName',
render: (val) => {
return (
<div>
{ val &&
<Tooltip title={val}>
<div>
{val.length > 4 ? `${val.slice(0,4)}...` : val}
</div>
</Tooltip>
}
</div>
)
}
},
{
title: <span>
<span>店铺展示</span>
<Tooltip title={<div>开启后,用户可在店铺内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”店铺展示。<br/>关闭后,店铺内不再展示此课程,但用户仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf'}}>&#xe61d;</i></Tooltip>
</span>,
width: "7%",
dataIndex: "courseware",
render: (val, item, index) => {
return (
<Switch defaultChecked={item.shelfState==="YES"?true:false} onChange={()=>this.changeShelfState(item)}/>
)
},
},
{
title: "观看用户数",
width: "10%",
key: "watchUserCount",
dataIndex: "watchUserCount",
render: (val, item) => {
return (
<div className="watchUserCount">{val}</div>
)
},
},
{
title: '创建时间',
key: 'created',
dataIndex: 'created',
sorter: true,
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '更新时间',
key: 'updated',
dataIndex: 'updated',
sorter: true,
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
render: (val, record) => {
return (
<div className="operate">
<div className="operate__item" onClick={()=>this.handleShowWatchDataModal(record)}>观看数据</div>
<span className="operate__item split"> | </span>
<div className="operate__item" onClick={() => this.handleShowShareModal(record)}>分享</div>
<span className="operate__item split"> | </span>
<Dropdown overlay={this.renderMoreOperate(record)}>
<span className="more-operate">
<span className="operate-text">更多</span>
<span
className="iconfont icon"
style={{ color: "#5289FA" }}
>
&#xe824;
</span>
</span>
</Dropdown>
</div>
)
}
}
];
return columns;
}
renderMoreOperate = (item) => {
return (
<div className="live-course-more-menu">
<div
className="operate__item"
onClick={() => {
RCHistory.push(`/create-video-course?type=edit&id=${item.id}`);
}}
>编辑</div>
<div
className="operate__item"
onClick={() => this.handleDeleteVideoCourse(item.id)}
>删除</div>
</div>
)
}
//改变上架状态
changeShelfState = (item) =>{
let _shelfState = item.shelfState
if(_shelfState==='NO'){
_shelfState = "YES";
item.shelfState = "YES"
}else{
_shelfState = "NO"
item.shelfState = "NO"
}
const params={
courseId: item.id,
shelfState:_shelfState
}
CourseService.changeVideoShelfState(params).then((res)=>{
if(res.success){
if(_shelfState === "YES"){
message.success("已开启展示");
}else{
message.success("已取消展示");
}
}
})
}
// 删除视频课
handleDeleteVideoCourse = (scheduleId) => {
Modal.confirm({
title: '你确定要删除此视频课吗?',
content: '删除后,学员将不能进行观看。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
okText: '确定',
cancelText: '取消',
onOk: () => {
const param ={
courseId:scheduleId,
storeId:User.getStoreId()
}
CourseService.delVideoSchedule(
param
).then(() => {
message.success('删除成功');
this.props.onChange();
})
}
});
}
// 显示分享弹窗
handleShowShareModal = (record, needStr = false) => {
const { id, scheduleVideoUrl } = record;
const _appId = appId;
const htmlUrl = `${LIVE_SHARE}video_detail/${id}?id=${User.getStoreId()}`;
const longUrl = htmlUrl;
const { coverUrl, courseName } = record;
const shareData = {
longUrl,
coverUrl,
scheduleVideoUrl,
courseName,
};
const shareLiveModal = (
<ShareLiveModal
needStr={needStr}
data={shareData}
type="videoClass"
close={() => {
this.setState({
shareLiveModal: null
});
localStorage.setItem('videoCourseItem', '');
}}
/>
);
this.setState({ shareLiveModal });
}
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
const { query } = this.props;
let { order: _order } =query;
// 按创建时间升序排序
if (columnKey === 'created' && order === 'ascend') { _order = 'CREATED_ASC'; }
// 按创建时间降序排序
if (columnKey === 'created' && order === 'descend') { _order = 'CREATED_DESC'; }
// 按更新时间升序排序
if (columnKey === 'updated' && order === 'ascend') { _order = 'UPDATED_ASC'; }
// 按更新时间降序排序
if (columnKey === 'updated' && order === 'descend') { _order = 'UPDATED_DESC'; }
const _query = {
...query,
orderEnum: _order
};
this.props.onChange(_query);
}
render() {
const { dataSource = [], totalCount, query } = this.props;
const { current, size } = query;
return (
<div className="video-course-list">
<Table
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
onChange={this.handleChangeTable}
pagination={false}
/>
<div className="box-footer">
<PageControl
current={current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = {...query, current: page + 1};
this.props.onChange(_query)
}}
/>
</div>
{ this.state.shareLiveModal }
{ this.state.watchDataModal }
</div>
)
}
}
export default VideoCourseList;
.video-course-list {
margin-top: 12px;
.operate-text {
color: #FF8534;
cursor: pointer;
}
.operate {
display: flex;
&__item {
color: #FF8534;
cursor: pointer;
&.split {
margin: 0 8px;
color: #BFBFBF;
}
}
}
.record__item {
display: flex;
.course-cover {
min-width: 97px;
max-width: 97px;
height: 50px;
border-radius: 2px;
margin-right: 8px;
background-color: #666;
}
.course-name {
color: #666;
width:188px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
height:48px;
}
}
}
.video-course-more-menu {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
border-radius: 4px;
div {
line-height: 30px;
padding: 0 15px;
cursor: pointer;
&:hover {
background: #f3f6fa;
}
}
}
.ant-tooltip{
max-width:700px !important;
}
.ant-tooltip-inner{
max-width:700px !important;
}
\ No newline at end of file
.video-course-opt {
margin-top: 16px;
.link {
color: #FF8534;
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27
* @Description: 视频课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Button } from 'antd';
import './VideoCourseOpt.less';
export default function VideoCourseOpt() {
return (
<div className="video-course-opt">
<Button
type="primary"
onClick={() => {
RCHistory.push('/create-video-course?type=add');
}}
className="mr12"
>新建视频课</Button>
</div>
);
}
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:08:06
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 15:56:49
* @Description: 云课堂-视频课入口页面
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import VideoCourseFilter from './components/VideoCourseFilter';
import VideoCourseOpt from './components/VieoCourseOpt';
import VideoCourseList from './components/VideoCourseList';
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user'
class VideoCourse extends React.Component {
constructor(props) {
super(props);
this.state = {
query: {
size: 10,
current: 1,
storeId:User.getStoreId()
},
dataSource: [], // 视频课列表
totalCount: 0, // 视频课数据总条数
}
}
componentWillMount() {
// 获取视频课列表
this.handleFetchScheduleList();
}
// 获取视频课列表
handleFetchScheduleList = (_query = {}) => {
const query = {
...this.state.query,
..._query
};
// 更新请求参数
this.setState({ query });
CourseService.videoSchedulePage(query).then((res) => {
const { result = {} } = res || {};
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
});
}
render() {
const { dataSource, totalCount, query } = this.state;
return (
<div className="page video-course-page">
<div className="content-header">视频课</div>
<div className="box">
{/* 搜索模块 */}
<VideoCourseFilter
onChange={this.handleFetchScheduleList}
/>
{/* 操作模块 */}
<VideoCourseOpt />
{/* 视频课列表模块 */}
<VideoCourseList
query={query}
dataSource={dataSource}
totalCount={totalCount}
onChange={this.handleFetchScheduleList}
/>
</div>
</div>
)
}
}
export default VideoCourse;
/*
* @Author: 吴文洁
* @Date: 2020-05-19 11:01:31
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-05-25 16:50:47
* @Description 余额异常弹窗
*/
import React from 'react';
import {Table, Modal,Input} from 'antd';
import { PageControl } from "@/components";
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user'
import './WatchDataModal.less';
import dealTimeDuration from "../../utils/dealTimeDuration";
const { Search } = Input;
class WatchDataModal extends React.Component {
constructor(props) {
super(props);
this.state = {
visible:true,
dataSource:[],
size:10,
query: {
current: 1,
},
totalCount:0
};
}
componentDidMount() {
this.handleFetchDataList();
}
onClose = () =>{
this.props.close();
}
// 获取观看视频数据列表
handleFetchDataList = () => {
const {query,size,totalCount} = this.state
const { id } = this.props.data;
const params ={
...query,
size,
courseId:id,
storeId:User.getStoreId()
}
CourseService.videoWatchInfo(params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
});
}
handleChangNickname = (value)=>{
const isPhone = (value || '').match(/^\d+$/);
const { query } = this.state;
if(isPhone){
query.phone = value;
query.nickName = null;
}else{
query.nickName = value;
query.phone = null;
}
query.current = 1;
this.setState({
query
})
}
onShowSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
size
},()=>{this.handleFetchDataList()})
}
// 请求表头
parseColumns = () => {
const columns = [
{
title: '观看用户',
key: 'name',
dataIndex: 'name'
},
{
title: '手机号',
key: 'phone',
dataIndex: 'phone'
},
{
title: '观看者类型',
key: 'userRole',
dataIndex: 'userRole'
},
{
title: '首次观看时间',
key: 'firstWatch',
dataIndex: 'firstWatch',
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '观看时长',
key: 'watchDuration',
dataIndex: 'watchDuration',
render: (val) => {
return <span>{val ? dealTimeDuration(val) : "00:00:00" }</span>
}
}
];
return columns;
}
render() {
const { visible,size,dataSource,totalCount,query,current } = this.state;
return (
<Modal
title="视频课观看数据"
visible={visible}
footer={null}
onCancel={this.onClose}
maskClosable={false}
className="watch-data-modal"
closable={true}
width={720}
>
<div className="search-container">
<Search placeholder="搜索用户姓名/手机号" style={{ width: 200 }} onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} />
</div>
<div>
<Table
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
/>
{dataSource.length >0 &&
<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()})
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
</div>
</Modal>
)
}
}
export default WatchDataModal;
\ No newline at end of file
.watch-data-modal{
.search-container{
text-align:right;
margin-bottom:17px;
}
}
\ No newline at end of file
......@@ -94,7 +94,7 @@ function Header(props:headerProps){
<div>
<img src={logoImg} className="logo" alt="" />
{menuType && (
<span className="logo-name">小麦云课堂</span>
<span className="logo-name">小麦企培</span>
)}
</div>
{menuType ? (
......
......@@ -113,7 +113,7 @@ function Login(props) {
<div className="login-main">
<div className="left-banner">
<div><img src={require("../../common/images/logo.png")} alt="" style={{ width: 60,height:61}} /></div>
<div className="name">小麦云课堂</div>
<div className="name">小麦企培</div>
<div className="desc">一键开启直播授课 让知识更有价值</div>
</div>
<div className="login-box">
......
/*
* @Author: 吴文洁
* @Date: 2020-04-29 10:26:32
* @LastEditors: wufan
* @LastEditTime: 2020-12-26 14:44:41
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-01-15 20:32:56
* @Description: 内容线路由配置
*/
import EmployeesManagePage from '@/modules/store-manage/EmployeesManagePage';
......@@ -12,6 +12,10 @@ import StoreDecorationPage from '@/modules/store-manage/StoreDecorationPage';
import CourseCatalogPage from '@/modules/store-manage/CourseCatalogPage';
import LiveCoursePage from '@/modules/course-manage/LiveCoursePage';
import AddLivePage from '@/modules/course-manage/AddLive'
import VideoCoursePage from '@/modules/course-manage/video-course'
import AddVideoCoursePage from '@/modules/course-manage/video-course/AddVideoCourse'
import DataList from '@/modules/course-manage/DataList/DataList';
import ClassBook from '@/modules/resource-disk';
import ResourceDisk from '@/modules/resource-disk';
import SwitchRoute from '@/modules/root/SwitchRoute';
......@@ -44,7 +48,12 @@ const mainRoutes = [
{
path: '/live-course',
component:LiveCoursePage,
name: '课程管理'
name: '直播课'
},
{
path: '/video-course',
component:VideoCoursePage,
name: '视频课'
},
{
path: '/create-live-course',
......@@ -52,6 +61,11 @@ const mainRoutes = [
name: '创建直播课'
},
{
path: '/create-video-course',
component:AddVideoCoursePage,
name: '创建视频课'
},
{
path: '/resource-disk',
component:ResourceDisk,
name: '资料云盘'
......
......@@ -9,11 +9,11 @@ export const menuList: any = [
groupCode: "CourseLiveClass",
link: '/live-course'
},
// {
// groupName: "视频课",
// groupCode: "CourseVideoClass",
// link: '/CourseVideoClass'
// }
{
groupName: "视频课",
groupCode: "CourseVideoClass",
link: '/video-course'
}
]
},
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment