Merge branches 'dev_aliyun' and 'dev_hjm_a' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_hjm_a

dev_cs_new
杨树明 6 years ago
commit 211043397e

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -64,3 +64,13 @@ html, body {
/* 某些情况下被cm盖住了 */
z-index: 99;
}
/* antd扩展 */
.formItemInline.ant-form-item {
display: flex;
}
.formItemInline .ant-form-item-control-wrapper {
flex: 1;
}

@ -217,6 +217,10 @@ const UsersInfo = Loadable({
loader: () => import('./modules/user/usersInfo/Infos'),
loading: Loading,
})
const InfosIndex = Loadable({
loader: () => import('./modules/user/usersInfo/InfosIndex'),
loading: Loading,
})
// 教学案例
const MoopCases = Loadable({
@ -327,7 +331,10 @@ class App extends Component {
/>
<Route path="/users/:username"
render={
(props) => (<UsersInfo {...this.props} {...props} {...this.state} />)
(props) => {
return (<InfosIndex {...this.props} {...props} {...this.state} />)
}
}></Route>
<Route

@ -69,7 +69,7 @@ export function initAxiosInterceptors(props) {
// proxy = 'http://localhost:3000'
// }
// ---------------------------------------------
if (config.url.indexOf(proxy) != -1) {
if (config.url.indexOf(proxy) != -1 || config.url.indexOf(':') != -1) {
return config
}
requestProxy(config)

@ -0,0 +1,237 @@
import React, { useState, useEffect, memo } from 'react';
import { getUrl2, isDev } from 'educoder'
import axios from 'axios'
const $ = window.$
let _url_origin = getUrl2()
let _path = isDev() ? 'public' : 'build'
let uploader
let _testHost = '' ; '192.168.2.63:3001/api'
const login = 'innov'
function createUploader () {
uploader = new window.AliyunUpload.Vod({
timeout: $('#timeout').val() || 60000,
partSize: $('#partSize').val() || 1048576,
parallel: $('#parallel').val() || 5,
retryCount: $('#retryCount').val() || 3,
retryDuration: $('#retryDuration').val() || 2,
region: $('#region').val() || 'ap-southeast-1',
userId: $('#userId').val() || 1829848226361863, // 1202060945918292, // 1303984639806000,
// 添加文件成功
addFileSuccess: function (uploadInfo) {
console.log('addFileSuccess')
$('#authUpload').attr('disabled', false)
$('#resumeUpload').attr('disabled', false)
$('#status').text('添加文件成功, 等待上传...')
console.log("addFileSuccess: " + uploadInfo.file.name)
$('#pauseUpload').attr('disabled', false)
uploader.startUpload()
},
// 开始上传
onUploadstarted: function (uploadInfo) {
// 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法
// 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值调用点播的不同接口获取uploadauth和uploadAddress
// 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
// 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi
// 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
// 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
if (!uploadInfo.videoId) {
// var createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6'
// $.get(createUrl, function (data) {
// var uploadAuth = data.UploadAuth
// var uploadAddress = data.UploadAddress
// var videoId = data.VideoId
// uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId)
// }, 'json')
var createUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
axios.post(createUrl, {
title: 'testvod1',
file_name: 'aa.mp4'
}).then((response) => {
// if (response.data.status == )
const data = response.data.data
var uploadAuth = data.UploadAuth
var uploadAddress = data.UploadAddress
var videoId = data.VideoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
}).catch((error) => {
console.log(error)
})
$('#status').text('文件开始上传...')
console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
} else {
// 如果videoId有值根据videoId刷新上传凭证
// https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY
// var refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
// $.get(refreshUrl, function (data) {
// var uploadAuth = data.UploadAuth
// var uploadAddress = data.UploadAddress
// var videoId = data.VideoId
// uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId)
// }, 'json')
var refreshUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
axios.put(refreshUrl, {
video_id: uploadInfo.videoId,
}).then((response) => {
const data = response.data.data
var uploadAuth = data.UploadAuth
var uploadAddress = data.UploadAddress
var videoId = data.VideoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
}).catch((error) => {
console.log(error)
})
}
},
// 文件上传成功
onUploadSucceed: function (uploadInfo) {
console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
$('#status').text('文件上传成功!')
},
// 文件上传失败
onUploadFailed: function (uploadInfo, code, message) {
console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message)
$('#status').text('文件上传失败!')
},
// 取消文件上传
onUploadCanceled: function (uploadInfo, code, message) {
console.log("Canceled file: " + uploadInfo.file.name + ", code: " + code + ", message:" + message)
$('#status').text('文件上传已暂停!')
},
// 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
onUploadProgress: function (uploadInfo, totalSize, progress) {
console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%")
var progressPercent = Math.ceil(progress * 100)
$('#auth-progress').text(progressPercent)
$('#status').text('文件上传中...')
},
// 上传凭证超时
onUploadTokenExpired: function (uploadInfo) {
// 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时
// 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth
// 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth
$('#status').text('文件上传超时!')
// let refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
// $.get(refreshUrl, function (data) {
// var uploadAuth = data.UploadAuth
// uploader.resumeUploadWithAuth(uploadAuth)
// console.log('upload expired and resume upload with uploadauth ' + uploadAuth)
// }, 'json')
var refreshUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
axios.put(refreshUrl, {
video_id: uploadInfo.videoId,
}).then((response) => {
const data = response.data.data
var uploadAuth = data.UploadAuth
uploader.resumeUploadWithAuth(uploadAuth)
}).catch((error) => {
console.log(error)
})
},
// 全部文件上传结束
onUploadEnd: function (uploadInfo) {
$('#status').text('文件上传完毕!')
console.log("onUploadEnd: uploaded all the files")
}
})
return uploader
}
function AliyunUploader(props) {
useEffect(() => {
if (window.AliyunUpload && window.AliyunUpload.Vod) {
} else {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/es6-promise.min.js`,
(data, textStatus, jqxhr) => {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/aliyun-oss-sdk-5.3.1.min.js`,
(data, textStatus, jqxhr) => {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/aliyun-upload-sdk-1.5.0.min.js`,
(data, textStatus, jqxhr) => {
});
});
});
}
$('#fileUpload').on('change', function (e) {
var file = e.target.files[0]
if (!file) {
alert("请先选择需要上传的文件!")
return
}
var Title = file.name
var userData = '{"Vod":{}}'
if (uploader) {
uploader.stopUpload()
$('#auth-progress').text('0')
$('#status').text("")
}
if (!uploader) {
uploader = createUploader()
}
// 首先调用 uploader.addFile(event.target.files[i], null, null, null, userData)
console.log(uploader)
let result = uploader.addFile(file, null, null, null, userData)
$('#authUpload').attr('disabled', false)
// $('#pauseUpload').attr('disabled', true)
// $('#resumeUpload').attr('disabled', true)
})
return () => {
$('#fileUpload').off('change')
}
}, [])
let { source, id, className, type } = props;
function onStop() {
$('#resumeUpload').attr('disabled', false)
// $('#pauseUpload').attr('disabled', true)
uploader.stopUpload()
}
function onResume() {
// $('#resumeUpload').attr('disabled', true)
// $('#pauseUpload').attr('disabled', false)
uploader.startUpload()
}
return(
<React.Fragment>
<div>
<input type="file" id="fileUpload"></input>
<label class="status">上传状态: <span id="status"></span></label>
</div>
<div class="upload-type">
上传方式一, 使用 UploadAuth 上传:
{/* <button id="authUpload" disabled="true"></button>
<button id="pauseUpload" disabled="true" onClick={onStop}>暂停</button>
<button id="resumeUpload" disabled="true" onClick={onResume}>恢复上传</button> */}
<button id="authUpload" >开始上传</button>
<button id="pauseUpload" onClick={onStop}>暂停</button>
<button id="resumeUpload" onClick={onResume}>恢复上传</button>
<span class="progress">上传进度: <i id="auth-progress">0</i> %</span>
<span></span>
</div>
</React.Fragment>
)
}
export default AliyunUploader

@ -0,0 +1,188 @@
import React, { useState, useEffect, memo } from 'react';
import { getUrl2, isDev } from 'educoder'
const $ = window.$
let _url_origin = getUrl2()
let _path = _isDev ? 'public' : 'build'
let uploader
function createUploader () {
uploader = new AliyunUpload.Vod({
timeout: $('#timeout').val() || 60000,
partSize: $('#partSize').val() || 1048576,
parallel: $('#parallel').val() || 5,
retryCount: $('#retryCount').val() || 3,
retryDuration: $('#retryDuration').val() || 2,
region: $('#region').val() || 'ap-southeast-1',
userId: $('#userId').val() || 1202060945918292, // 1303984639806000,
// 添加文件成功
addFileSuccess: function (uploadInfo) {
console.log('addFileSuccess')
$('#authUpload').attr('disabled', false)
$('#resumeUpload').attr('disabled', false)
$('#status').text('添加文件成功, 等待上传...')
console.log("addFileSuccess: " + uploadInfo.file.name)
$('#pauseUpload').attr('disabled', false)
uploader.startUpload()
},
// 开始上传
onUploadstarted: function (uploadInfo) {
// 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法
// 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值调用点播的不同接口获取uploadauth和uploadAddress
// 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
// 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi
// 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
// 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
if (!uploadInfo.videoId) {
var createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6'
$.get(createUrl, function (data) {
var uploadAuth = data.UploadAuth
var uploadAddress = data.UploadAddress
var videoId = data.VideoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId)
}, 'json')
$('#status').text('文件开始上传...')
console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
} else {
// 如果videoId有值根据videoId刷新上传凭证
// https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY
var refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
$.get(refreshUrl, function (data) {
var uploadAuth = data.UploadAuth
var uploadAddress = data.UploadAddress
var videoId = data.VideoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId)
}, 'json')
}
},
// 文件上传成功
onUploadSucceed: function (uploadInfo) {
console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
$('#status').text('文件上传成功!')
},
// 文件上传失败
onUploadFailed: function (uploadInfo, code, message) {
console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message)
$('#status').text('文件上传失败!')
},
// 取消文件上传
onUploadCanceled: function (uploadInfo, code, message) {
console.log("Canceled file: " + uploadInfo.file.name + ", code: " + code + ", message:" + message)
$('#status').text('文件上传已暂停!')
},
// 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
onUploadProgress: function (uploadInfo, totalSize, progress) {
console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%")
var progressPercent = Math.ceil(progress * 100)
$('#auth-progress').text(progressPercent)
$('#status').text('文件上传中...')
},
// 上传凭证超时
onUploadTokenExpired: function (uploadInfo) {
// 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时
// 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth
// 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth
$('#status').text('文件上传超时!')
let refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
$.get(refreshUrl, function (data) {
var uploadAuth = data.UploadAuth
uploader.resumeUploadWithAuth(uploadAuth)
console.log('upload expired and resume upload with uploadauth ' + uploadAuth)
}, 'json')
},
// 全部文件上传结束
onUploadEnd: function (uploadInfo) {
$('#status').text('文件上传完毕!')
console.log("onUploadEnd: uploaded all the files")
}
})
return uploader
}
function AliyunUploaderDemo(props) {
useEffect(() => {
if (window.AliyunUpload && window.AliyunUpload.Vod) {
} else {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/es6-promise.min.js`,
(data, textStatus, jqxhr) => {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/aliyun-oss-sdk-5.3.1.min.js`,
(data, textStatus, jqxhr) => {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/aliyun-upload-sdk-1.5.0.min.js`,
(data, textStatus, jqxhr) => {
});
});
});
}
$('#fileUpload').on('change', function (e) {
var file = e.target.files[0]
if (!file) {
alert("请先选择需要上传的文件!")
return
}
var Title = file.name
var userData = '{"Vod":{}}'
if (uploader) {
uploader.stopUpload()
$('#auth-progress').text('0')
$('#status').text("")
}
if (!uploader) {
uploader = createUploader()
}
// 首先调用 uploader.addFile(event.target.files[i], null, null, null, userData)
console.log(uploader)
uploader.addFile(file, null, null, null, userData)
$('#authUpload').attr('disabled', false)
// $('#pauseUpload').attr('disabled', true)
// $('#resumeUpload').attr('disabled', true)
})
// return () => {
// }
}, [])
let { source, id, className, type } = props;
function onStop() {
$('#resumeUpload').attr('disabled', false)
// $('#pauseUpload').attr('disabled', true)
uploader.stopUpload()
}
function onResume() {
// $('#resumeUpload').attr('disabled', true)
// $('#pauseUpload').attr('disabled', false)
uploader.startUpload()
}
return(
<React.Fragment>
<div>
<input type="file" id="fileUpload"></input>
<label class="status">上传状态: <span id="status"></span></label>
</div>
<div class="upload-type">
上传方式一, 使用 UploadAuth 上传:
{/* <button id="authUpload" disabled="true"></button>
<button id="pauseUpload" disabled="true" onClick={onStop}>暂停</button>
<button id="resumeUpload" disabled="true" onClick={onResume}>恢复上传</button> */}
<button id="authUpload" >开始上传</button>
<button id="pauseUpload" onClick={onStop}>暂停</button>
<button id="resumeUpload" onClick={onResume}>恢复上传</button>
<span class="progress">上传进度: <i id="auth-progress">0</i> %</span>
<span></span>
</div>
</React.Fragment>
)
}
export default AliyunUploaderDemo

@ -4,7 +4,10 @@ export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
foreground_select: '#4CACFF'
foreground_select: '#4CACFF',
foreground_orange1: '#FF6800',
foreground_tip: '#333',
},
dark: {
foreground: '#ffffff',

@ -17,10 +17,10 @@ class ActionBtn extends Component {
to==undefined ?
<a href="javascript:void(0)" onClick={this.props.onClick}
{...others}
className={"Actionbtn "+`${map[style]} ${this.props.className}`}
className={"Actionbtn "+`${map[style || 'blue']} ${this.props.className}`}
>{children}</a>
:
<Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</Link>
<Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`} {...others}>{this.props.children}</Link>
}
</React.Fragment>
)

@ -56,12 +56,14 @@ export { default as MarkdownToHtml } from './components/markdown/MarkdownToHtml'
export { default as DMDEditor } from './components/markdown/DMDEditor'
export { default as Clappr } from './components/media/Clappr'
export { default as AliyunUploader } from './components/media/AliyunUploader'
export { default as ImageLayerHook } from './hooks/ImageLayerHook'
// 外部
export { default as CBreadcrumb } from '../modules/courses/common/CBreadcrumb'
export { CNotificationHOC as CNotificationHOC } from '../modules/courses/common/CNotificationHOC'
export { default as ModalWrapper } from '../modules/courses/common/ModalWrapper'
export { default as NoneData } from '../modules/courses/coursesPublic/NoneData'

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

@ -10,7 +10,7 @@ class CBreadcrumb extends Component{
render(){
let { items, className, separator }=this.props;
return(
<p className={`clearfix mb10 ${className}`}>
<p className={`clearfix mb10 ${className} cBreadcrumb`}>
{ items && items.map( (item, index) => {
if (!item.name) {
return ''

@ -16,6 +16,7 @@ class ModalWrapper extends Component{
}
onCancel = () => {
this.setVisible(false)
this.props.onCancel && this.props.onCancel()
}
onOk = () => {
this.props.onOk && this.props.onOk()

@ -6,8 +6,9 @@ class NoneData extends Component{
super(props)
}
render(){
const { style } = this.props;
return(
<div className="edu-tab-con-box clearfix edu-txt-center">
<div className="edu-tab-con-box clearfix edu-txt-center" style={style}>
<img className="edu-nodata-img mb20" src={getImageUrl("images/educoder/nodata.png")}/>
<p className="edu-nodata-p mb20">暂时还没有相关数据哦</p>
</div>

@ -1173,6 +1173,7 @@ samp {
border-right: none!important;
box-shadow: none!important;
}
/* 这个加了干嘛的影响到了带addonAfter的input */
.searchViewAfter,.searchViewAfter:focus,.searchViewAfter .ant-input:hover,.ant-input-group .ant-input:focus{
border-right: none!important;
}

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { SnackbarHOC } from 'educoder';
import {Link} from 'react-router-dom';
import {Tooltip,Menu} from 'antd';
import Loadable from 'react-loadable';
@ -8,8 +8,8 @@ import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import UpgradeModals from '../../modals/UpgradeModals';
import axios from 'axios';
import {getImageUrl} from 'educoder';
import { TPMIndexHOC } from '../../tpm/TPMIndexHOC';
import { CNotificationHOC } from '../../courses/common/CNotificationHOC'
import InfosBanner from './InfosBanner'
import "./usersInfo.css"
import "../../courses/css/members.css"
import "../../courses/css/Courses.css"
@ -39,6 +39,11 @@ const InfosProject = Loadable({
loader: () => import('./InfosProject'),
loading:Loading,
})
const InfosVideo = Loadable({
loader: () => import('./video/InfosVideo'),
loading:Loading,
})
const $ = window.$;
class Infos extends Component{
@ -196,12 +201,12 @@ class Infos extends Component{
}
// 试用申请
trialapplications =()=>{
this.setState({
isRenders: true,
showTrial:true
})
}
// trialapplications =()=>{
// this.setState({
// isRenders: true,
// showTrial:true
// })
// }
cancelModulationModels=()=>{
this.setState({
isRenders: false
@ -225,10 +230,7 @@ class Infos extends Component{
moduleName,
next_gold
}=this.state;
let {username}= this.props.match.params;
let {pathname}=this.props.location;
moduleName=pathname.split("/")[3];
return(
<div className="newMain">
{this.state.updata===undefined?"":<UpgradeModals
@ -237,165 +239,13 @@ class Infos extends Component{
{
isRenders && <Trialapplication {...this.props} {...this.state} Cancel={() => this.cancelModulationModels()}/>
}
<div className="user-main-half">
<div className="user-headImg"></div>
<div className="user-headCon">
<div className="pr" style={{"min-height": "465px"}}>
<div className="educontent pt80 clearfix edu-txt-center">
<div className="inline">
<div className="fl headtab">
<span>{is_current ? "我":"TA"}的经验值</span>
<a style={{ cursor: 'default' }}
// href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}/user_experience`}
>{data && data.experience}</a>
</div>
<em className="v-h-line fl"></em>
<div className="fl headtab">
<span>{is_current ? "我":"TA"}的金币</span>
<a style={{ cursor: 'default' }}
// href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}/user_grade`}
id="user_code">{data && data.grade}</a>
</div>
<div className="headphoto mt14">
<img alt="头像" id="user_avatar_show" nhname="avatar_image" src={data && `${getImageUrl('images/'+data.avatar_url)}`}/>
</div>
<div className="fl headtab">
<span>{is_current ? "我":"TA"}的粉丝</span>
<a style={{ cursor: 'default' }}
// href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}/user_fanslist`}
id="user_h_fan_count">{data && data.fan_count}</a>
</div>
<em className="v-h-line fl"></em>
<div className="fl headtab">
<span>{is_current ? "我":"TA"}的关注</span>
<a style={{ cursor: 'default' }}
// href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}/user_watchlist`}
>{data && data.follow_count}</a>
</div>
<span className="clearfix"></span>
<span className="myName">{data && data.name}</span>
</div>
</div>
<div className="educontent mt10 clearfix edu-txt-center">
<div className="inline">
{
data && is_current == false && data.identity =="学生" ? "" : <span className="mypost fl mr10">{data && data.identity}</span>
}
<a
// href={is_current ? `${this.props.Headertop && this.props.Headertop.old_url}/account/authentication` :"javascript:void(0)"}
// target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title={ data && data.authentication ?"已实名认证":"未实名认证"}>
<i className={ data && data.authentication ? "iconfont icon-shenfenrenzheng font-13 color-blue":"iconfont icon-shenfenrenzheng font-13 color-grey-9"}></i>
</Tooltip>
</a>
<a
// href={is_current ? `${this.props.Headertop && this.props.Headertop.old_url}/account/professional_certification` :"javascript:void(0)"}
// target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title={ data && data.professional_certification ?"已职业认证":"未职业认证"}>
<i className={ data && data.professional_certification ? "iconfont icon-zhiyerenzheng font-13 color-blue":"iconfont icon-zhiyerenzheng font-13 color-grey-9"}></i>
</Tooltip>
</a>
<a
// href={is_current ? `${this.props.Headertop && this.props.Headertop.old_url}/account/change_or_bind?type=phone` :"javascript:void(0)"}
// target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title={ data && data.phone_binded ?"已手机认证":"未手机认证"}>
<i className={ data && data.phone_binded ? "iconfont icon-shoujirenzheng font-13 color-blue":"iconfont icon-shoujirenzheng font-13 color-grey-9"}></i>
</Tooltip>
</a>
<a
// href={is_current ? `${this.props.Headertop && this.props.Headertop.old_url}/my/account` :"javascript:void(0)"}
// target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title={ data && data.email_binded ?"已邮箱认证":"未邮箱认证"}>
<i className={ data && data.email_binded ? "iconfont icon-youxiangrenzheng font-13 color-blue":"iconfont icon-youxiangrenzheng font-13 color-grey-9"}></i>
</Tooltip>
</a>
{/* <!--学院管理员身份--> */}
{
data && data.college_identifier &&
<a
// href={`${this.props.Headertop && this.props.Headertop.old_url}/colleges/${data.college_identifier}/statistics`} target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title="学院管理员">
<i className="iconfont icon-chengyuanguanli font-12 color-blue" data-tip-down="学院管理员"></i>
</Tooltip>
</a>
}
</div>
</div>
<div className="mt15 educontent clearfix edu-txt-center">
{sign && <p className="mb20" style={{"height": "28px"}}>
{/* 这家伙很懒,什么都没留下~ */}
{
is_edit && is_current ?
<input type="text" id="mysign" class="mysign-input" placeholder="请输入您的个性签名" style={{height:"20px"}} value={sign} onInput={this.inputSign} onBlur={this.savemysign}/>
:
is_current ?
<a className="mysign-span" onClick={this.editmysign} style={{"display": "block"}}>{sign || ""}</a>
:
<span className="mysign-span" style={{"display": "block","cursor":"default"}}>{sign || ""}</span>
}
</p>}
{
is_current ?
<div className="inline">
{
data && data.attendance_signed ?
<React.Fragment>
<span className="user_default_btn user_grey_btn mb5">已签到</span>
<p id="attendance_notice" className="none font-12 color-grey-6" style={{"display":"block"}}>明日签到&nbsp;<font className="color-orange">+{next_gold}</font>&nbsp;</p>
</React.Fragment>
:
<a herf="javascript:void(0);" onClick={this.signFor} id="attendance" className="user_default_btn user_orange_btn fl mb15">签到</a>
// <a herf="javascript:void(0);" onClick={this.trialapplications} id="authentication_apply" className="user_default_btn user_private_btn fl ml15">试用申请</a>
}
</div>
:
<div className="inline">
<a href="javascript:void(0);" onClick={this.followPerson} className="user_default_btn user_watch_btn user_private_btn fl mr20">{followed ? "取消关注":"关注"}</a>
<a href={`${this.props.Headertop && this.props.Headertop.old_url}/messages/${login}/message_detail?target_ids=${id}`} className="user_default_btn user_private_btn fl">私信</a>
</div>
}
</div>
<div className="edu-txt-center navInfo">
<div className="inline">
<li className={`${moduleName == 'courses' ||moduleName == undefined ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'courses'})}
to={`/users/${username}/courses`}>课堂</Link>
</li>
<li className={`${moduleName == 'shixuns' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'shixuns'})}
to={`/users/${username}/shixuns`}>实训</Link>
</li>
<li className={`${moduleName == 'paths' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'paths'})}
to={`/users/${username}/paths`}>实践课程</Link>
</li>
<li className={`${moduleName == 'projects' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'projects'})}
to={`/users/${username}/projects`}>项目</Link>
</li>
<li className={`${moduleName == 'package' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'package'})}
to={`/users/${username}/package`}>众包</Link>
</li>
{/*{ data && data.identity!="学生" && <li> <a href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}?type=m_bank`}>题库</a></li>}*/}
</div>
</div>
</div>
</div>
</div>
<InfosBanner
{...this.props}
{...this.state}
signFor={this.signFor}
followPerson={this.followPerson}
></InfosBanner>
<Switch {...this.props}>
{/* --------------------------------------------------------------------- */}
@ -438,6 +288,13 @@ class Infos extends Component{
}
></Route>
{/* 项目 */}
<Route exact path="/users/:username/videoes"
render={
(props) => (<InfosVideo {...this.props} {...props} {...this.state} />)
}
></Route>
<Route exact path="/users/:username"
render={
@ -450,4 +307,5 @@ class Infos extends Component{
)
}
}
export default CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC(Infos) ));
// CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC))
export default (Infos) ;

@ -161,7 +161,7 @@ class InfosBank extends Component{
modalSave={modalSave}
></Modals>
<Spin size="large" spinning={isSpin}>
<div className="white-panel edu-back-white pt25 pb25 clearfix ">
<div className="white-panel edu-back-white pt20 pb20 clearfix ">
<li className={type=="publicly" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeType("publicly")}>{is_current ? "我":"TA"}的题库</a></li>
<li className={type=="personal" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeType("personal")}>公共题库</a></li>
</div>

@ -0,0 +1,118 @@
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import {Tooltip,Menu} from 'antd';
import {getImageUrl} from 'educoder';
import "./usersInfo.css"
import "../../courses/css/members.css"
import "../../courses/css/Courses.css"
import banner from '../../../images/account/infobanner.png'
class InfosBanner extends Component{
constructor(props){
super(props);
}
render(){
let {
data ,
is_current,
is_edit,
sign,
type,
followed,
id,
login,
moduleName,
next_gold
}=this.props;
let {username}= this.props.match.params;
let {pathname}=this.props.location;
moduleName=pathname.split("/")[3];
return(
<div className="bannerPanel mb60">
<div className="educontent">
<div className="clearfix color-white mb25">
<p className="myPhoto mr30 fl"><img alt="头像" width='106px' height='106px' src={data && `${getImageUrl('images/'+data.avatar_url)}`}/></p>
<div className="fl">
<p className="clearfix mt20">
<span className="username task-hide" style={{"maxWidth":'370px'}}>{data && data.name}</span>
{
data && is_current == false && data.identity =="学生" ? "" : <span className="userpost">{data && data.identity}</span>
}
</p>
<p className="mt20">
<Tooltip placement='bottom' title={ data && data.professional_certification ?"已职业认证":"未职业认证"}>
<i className={ data && data.professional_certification ? "iconfont icon-shenfenzhenghaomaguizheng font-18 user-colorgrey-green mr30 ml2":"iconfont icon-shenfenzhenghaomaguizheng font-18 user-colorgrey-B8 mr30 ml2"}></i>
</Tooltip>
<Tooltip placement='bottom' title={ data && data.authentication ?"已实名认证":"未实名认证"}>
<i className={ data && data.authentication ? "iconfont icon-renzhengshangjia font-18 user-colorgrey-green":"iconfont icon-renzhengshangjia font-18 user-colorgrey-B8"}></i>
</Tooltip>
</p>
</div>
<div className="fr">
<div class="fl headtab mt20">
<span>{is_current ? "我":"TA"}的经验值</span>
<a style={{"cursor":"default"}}>{data && data.experience}</a>
</div>
<div class="fl headtab mt20 pr leftTransform pl20">
<span>{is_current ? "我":"TA"}的金币</span>
<a style={{"cursor":"default"}}>{data && data.grade}</a>
</div>
{
is_current ?
<span className="fl mt35 ml60">
{
data && data.attendance_signed ?
<span className="user_default_btn user_grey_btn font-18">已签到</span>
:
<a herf="javascript:void(0);" onClick={this.props.signFor} className="user_default_btn user_yellow_btn fl font-18">签到</a>
}
</span>
:
<span className="fl mt35 ml80">
<a href={`${this.props.Headertop && this.props.Headertop.old_url}/messages/${login}/message_detail?target_ids=${id}`} className="user_default_btn user_yellow_btn fl font-18">私信</a>
</span>
}
</div>
</div>
<div className="userNav">
<li className={`${moduleName == 'courses' ||moduleName == undefined ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'courses'})}
to={`/users/${username}/courses`}>翻转课堂</Link>
</li>
<li className={`${moduleName == 'shixuns' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'shixuns'})}
to={`/users/${username}/shixuns`}>开发社区</Link>
</li>
<li className={`${moduleName == 'paths' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'paths'})}
to={`/users/${username}/paths`}>实践课程</Link>
</li>
<li className={`${moduleName == 'projects' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'projects'})}
to={`/users/${username}/projects`}>项目</Link>
</li>
<li className={`${moduleName == 'package' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'package'})}
to={`/users/${username}/package`}>众包</Link>
</li>
{/* <li className={`${moduleName == 'videoes' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'videoes'})}
to={`/users/${username}/videoes`}>视频</Link>
</li> */}
</div>
</div>
</div>
)
}
}
export default InfosBanner;

@ -107,7 +107,7 @@ class InfosCourse extends Component{
return(
<div className="educontent">
<Spin size="large" spinning={isSpin}>
<div className="white-panel edu-back-white pt25 pb25 clearfix ">
<div className="white-panel edu-back-white pt20 pb20 clearfix ">
<li className={category ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeCategory()}>全部</a></li>
<li className={category=="manage" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("manage")}>{is_current ? "我":"TA"}管理的</a></li>
<li className={category=="study" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("study")}>{is_current ? "我":"TA"}学习的</a></li>

@ -0,0 +1,79 @@
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import { SnackbarHOC } from 'educoder';
import { TPMIndexHOC } from '../../tpm/TPMIndexHOC';
import { CNotificationHOC } from '../../courses/common/CNotificationHOC'
import Loadable from 'react-loadable';
import Loading from '../../../Loading';
const UsersInfo = Loadable({
loader: () => import('./Infos'),
loading: Loading,
})
const VideoUploadList = Loadable({
loader: () => import('./video/VideoUploadList'),
loading: Loading,
})
const VideoPublishSuccess = Loadable({
loader: () => import('./video/VideoPublishSuccess'),
loading: Loading,
})
const $ = window.$;
class InfosIndex extends Component{
constructor(props){
super(props);
this.state={
data:undefined,
}
}
componentDidMount =()=>{
}
//判断是否看的是当前用户的个人主页
componentDidUpdate =(prevProps)=> {
}
render(){
let {
data ,
}=this.state;
return(
<Switch {...this.props}>
{/* --------------------------------------------------------------------- */}
{/* 视频发布 */}
<Route exact path="/users/:username/videoes/upload"
render={
(props) => (<VideoUploadList {...this.props} {...props} {...this.state} />)
}
></Route>
<Route exact path="/users/:username/videoes/success"
render={
(props) => (<VideoPublishSuccess {...this.props} {...props} {...this.state} />)
}
></Route>
<Route path="/users/:username"
render={
(props) => (<UsersInfo {...this.props} {...props} {...this.state} />)
}
></Route>
</Switch>
)
}
}
export default CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC(InfosIndex) ));

@ -168,7 +168,7 @@ class InfosPackage extends Component{
modalSave={this.state.ModalSave}
/>
<Spin size="large" spinning={isSpin}>
<div className="white-panel edu-back-white pt25 pb25 clearfix ">
<div className="white-panel edu-back-white pt20 pb20 clearfix ">
<li className={category ? "" : "active"}><a onClick={()=>this.changeCategory()}>全部</a></li>
<li className={category=="manage" ? "active" : ""}><a onClick={()=>this.changeCategory("manage")}>{is_current ? "我":"TA"}管理的</a></li>
<li className={category=="bidden" ? "active" : ""}><a onClick={()=>this.changeCategory("bidden")}>{is_current ? "我":"TA"}参与的</a></li>

@ -120,7 +120,7 @@ class InfosPath extends Component{
return(
<div className="educontent">
<Spin size="large" spinning={isSpin}>
<div className="white-panel edu-back-white pt25 pb25 clearfix ">
<div className="white-panel edu-back-white pt20 pb20 clearfix ">
<li className={category ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeCategory()}>全部</a></li>
<li className={category=="manage" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("manage")}>{is_current ? "我":"TA"}管理的</a></li>
<li className={category=="study" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("study")}>{is_current ? "我":"TA"}学习的</a></li>

@ -102,7 +102,7 @@ class InfosProject extends Component{
return(
<div className="educontent">
<Spin size="large" spinning={isSpin}>
<div className="white-panel edu-back-white pt25 pb25 clearfix ">
<div className="white-panel edu-back-white pt20 pb20 clearfix ">
<li className={category ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeCategory()}>全部</a></li>
<li className={category=="manage" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("manage")}>{is_current ? "我":"TA"}管理的</a></li>
<li className={category=="study" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("study")}>{is_current ? "我":"TA"}学习的</a></li>

@ -120,7 +120,7 @@ class InfosShixun extends Component{
return(
<div className="educontent">
<Spin size="large" spinning={isSpin}>
<div className="white-panel edu-back-white pt25 pb25 clearfix ">
<div className="white-panel edu-back-white pt20 pb20 clearfix ">
<li className={category ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeCategory()}>全部</a></li>
<li className={category=="manage" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("manage")}>{is_current ? "我":"TA"}管理的</a></li>
<li className={category=="study" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("study")}>{is_current ? "我":"TA"}学习的</a></li>

@ -0,0 +1,190 @@
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
import {Tooltip,Menu} from 'antd';
import {getImageUrl} from 'educoder';
import "./usersInfo.css"
import "../../courses/css/members.css"
import "../../courses/css/Courses.css"
class banner_out extends Component{
constructor(props){
super(props);
}
render(){
let {
data ,
is_current,
is_edit,
sign,
type,
followed,
id,
login,
moduleName,
next_gold
}=this.props;
let {username}= this.props.match.params;
return(
<div className="user-main-half">
<div className="user-headImg"></div>
<div className="user-headCon">
<div className="pr" style={{"min-height": "465px"}}>
<div className="educontent pt80 clearfix edu-txt-center">
<div className="inline">
<div className="fl headtab">
<span>{is_current ? "我":"TA"}的经验值</span>
<a style={{ cursor: 'default' }}
// href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}/user_experience`}
>{data && data.experience}</a>
</div>
<em className="v-h-line fl"></em>
<div className="fl headtab">
<span>{is_current ? "我":"TA"}的金币</span>
<a style={{ cursor: 'default' }}
// href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}/user_grade`}
id="user_code">{data && data.grade}</a>
</div>
<div className="headphoto mt14">
<img alt="头像" id="user_avatar_show" nhname="avatar_image" src={data && `${getImageUrl('images/'+data.avatar_url)}`}/>
</div>
<div className="fl headtab">
<span>{is_current ? "我":"TA"}的粉丝</span>
<a style={{ cursor: 'default' }}
// href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}/user_fanslist`}
id="user_h_fan_count">{data && data.fan_count}</a>
</div>
<em className="v-h-line fl"></em>
<div className="fl headtab">
<span>{is_current ? "我":"TA"}的关注</span>
<a style={{ cursor: 'default' }}
// href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}/user_watchlist`}
>{data && data.follow_count}</a>
</div>
<span className="clearfix"></span>
<span className="myName">{data && data.name}</span>
</div>
</div>
<div className="educontent mt10 clearfix edu-txt-center">
<div className="inline">
{
data && is_current == false && data.identity =="学生" ? "" : <span className="mypost fl mr10">{data && data.identity}</span>
}
<a
// href={is_current ? `${this.props.Headertop && this.props.Headertop.old_url}/account/authentication` :"javascript:void(0)"}
// target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title={ data && data.authentication ?"已实名认证":"未实名认证"}>
<i className={ data && data.authentication ? "iconfont icon-shenfenrenzheng font-13 color-blue":"iconfont icon-shenfenrenzheng font-13 color-grey-9"}></i>
</Tooltip>
</a>
<a
// href={is_current ? `${this.props.Headertop && this.props.Headertop.old_url}/account/professional_certification` :"javascript:void(0)"}
// target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title={ data && data.professional_certification ?"已职业认证":"未职业认证"}>
<i className={ data && data.professional_certification ? "iconfont icon-zhiyerenzheng font-13 color-blue":"iconfont icon-zhiyerenzheng font-13 color-grey-9"}></i>
</Tooltip>
</a>
<a
// href={is_current ? `${this.props.Headertop && this.props.Headertop.old_url}/account/change_or_bind?type=phone` :"javascript:void(0)"}
// target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title={ data && data.phone_binded ?"已手机认证":"未手机认证"}>
<i className={ data && data.phone_binded ? "iconfont icon-shoujirenzheng font-13 color-blue":"iconfont icon-shoujirenzheng font-13 color-grey-9"}></i>
</Tooltip>
</a>
<a
// href={is_current ? `${this.props.Headertop && this.props.Headertop.old_url}/my/account` :"javascript:void(0)"}
// target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title={ data && data.email_binded ?"已邮箱认证":"未邮箱认证"}>
<i className={ data && data.email_binded ? "iconfont icon-youxiangrenzheng font-13 color-blue":"iconfont icon-youxiangrenzheng font-13 color-grey-9"}></i>
</Tooltip>
</a>
{/* <!--学院管理员身份--> */}
{
data && data.college_identifier &&
<a
// href={`${this.props.Headertop && this.props.Headertop.old_url}/colleges/${data.college_identifier}/statistics`} target="_blank"
className={is_current ? "ringauto fl" :"ringauto fl cdefault"}>
<Tooltip placement='bottom' title="学院管理员">
<i className="iconfont icon-chengyuanguanli font-12 color-blue" data-tip-down="学院管理员"></i>
</Tooltip>
</a>
}
</div>
</div>
<div className="mt15 educontent clearfix edu-txt-center">
<p className="mb20" style={{"height": "28px"}}>
{
is_edit && is_current ?
<input type="text" id="mysign" class="mysign-input" placeholder="请输入您的个性签名" style={{height:"20px"}} value={sign} onInput={this.inputSign} onBlur={this.savemysign}/>
:
is_current ?
<a className="mysign-span" onClick={this.editmysign} style={{"display": "block"}}>{sign || "这家伙很懒,什么都没留下~"}</a>
:
<span className="mysign-span" style={{"display": "block","cursor":"default"}}>{sign || "这家伙很懒,什么都没留下~"}</span>
}
</p>
{
is_current ?
<div className="inline">
{
data && data.attendance_signed ?
<React.Fragment>
<span className="user_default_btn user_grey_btn mb5">已签到</span>
<p id="attendance_notice" className="none font-12 color-grey-6" style={{"display":"block"}}>明日签到&nbsp;<font className="color-orange">+{next_gold}</font>&nbsp;</p>
</React.Fragment>
:
<a herf="javascript:void(0);" onClick={this.props.signFor} id="attendance" className="user_default_btn user_orange_btn fl mb15">签到</a>
// <a herf="javascript:void(0);" onClick={this.trialapplications} id="authentication_apply" className="user_default_btn user_private_btn fl ml15">试用申请</a>
}
</div>
:
<div className="inline">
<a href="javascript:void(0);" onClick={this.props.followPerson} className="user_default_btn user_watch_btn user_private_btn fl mr20">{followed ? "取消关注":"关注"}</a>
<a href={`${this.props.Headertop && this.props.Headertop.old_url}/messages/${login}/message_detail?target_ids=${id}`} className="user_default_btn user_private_btn fl">私信</a>
</div>
}
</div>
<div className="edu-txt-center navInfo">
<div className="inline">
<li className={`${moduleName == 'courses' ||moduleName == undefined ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'courses'})}
to={`/users/${username}/courses`}>课堂</Link>
</li>
<li className={`${moduleName == 'shixuns' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'shixuns'})}
to={`/users/${username}/shixuns`}>实训</Link>
</li>
<li className={`${moduleName == 'paths' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'paths'})}
to={`/users/${username}/paths`}>实践课程</Link>
</li>
<li className={`${moduleName == 'projects' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'projects'})}
to={`/users/${username}/projects`}>项目</Link>
</li>
<li className={`${moduleName == 'package' ? 'active' : '' }`}>
<Link
onClick={() => this.setState({moduleName: 'package'})}
to={`/users/${username}/package`}>众包</Link>
</li>
{/*{ data && data.identity!="学生" && <li> <a href={`${this.props.Headertop && this.props.Headertop.old_url}/users/${username}?type=m_bank`}>题库</a></li>}*/}
</div>
</div>
</div>
</div>
</div>
)
}
}
export default banner_out;

@ -0,0 +1,96 @@
import React, { useState, useEffect, useContext, useRef, memo } from 'react';
import {Link} from 'react-router-dom';
import { Icon } from 'antd'
import { getUrl2, isDev, ThemeContext } from 'educoder'
import axios from 'axios'
function CRoundSelect (props) {
const [open, setOpen] = useState(false)
const theme = useContext(ThemeContext);
const { category, changeCategory, categories, right, width, items,
sortKey, onSortChange } = props;
const username = props.match.params.username
useEffect(() => {
}, [])
function onToggleOpen(over) {
if (over) {
console.log('over')
setOpen(true)
} else {
console.log('out')
setOpen(false)
}
}
function findIndexByKey(key) {
let _index = -1
items && items.some((item, index) => {
if (item.key == key) {
_index = index
return true;
}
})
return _index
}
function _onSortChange(key, index) {
if (index == 0) {
return;
}
setOpen(false)
onSortChange(key, index)
}
let index = findIndexByKey(sortKey)
return (
<React.Fragment>
<div className="" style={{position: 'relative', lineHeight: '24px'}}>
{/* onMouseOut={onToggleOpen} */}
<div className="trigger" onMouseOver={() => onToggleOpen(true)} >
<style>{`
.trigger, .droplist {
padding: 0px 6px;
border: 1px solid ${theme.foreground_select};
color: ${theme.foreground_select};
border-radius: 6px;
}
.trigger {
width: ${width || 'fit-content'};
cursor: pointer;
}
.droplist {
width: ${width || 'fit-content'};
position: absolute;
z-index: 2;
top: 0px;
background: #fff;
cursor: pointer;
}
`}</style>
<div className="currentItem">
{items[index].name} <Icon type="down" />
</div>
</div>
{true && <ul className="droplist" onMouseLeave={() => onToggleOpen(false)}
style={{display: open ? 'block' : 'none'}}
>
{items.map((item, index) =>
<li key={item.key} className=""
onClick={() => _onSortChange(item.key, index)}>{item.name}</li>
)}
{/* <li className="">AAAAAAAA</li>
<li className="">BBBBBBB</li>
<li className="">CCCCCCC</li> */}
</ul> }
</div>
</React.Fragment>
)
}
export default CRoundSelect

@ -0,0 +1,56 @@
import React, { useState, useEffect, useContext, useRef, memo } from 'react';
import {Link} from 'react-router-dom';
import { getUrl2, isDev, ThemeContext } from 'educoder'
import { Modal } from 'antd'
function HeadlessModal (props) {
// const [ visible, setVisible ] = useState(false)
const theme = useContext(ThemeContext);
const { category, visible, setVisible, className, width } = props;
useEffect(() => {
}, [])
return (
<Modal
visible={visible}
className={`headless ${className}`}
title={null}
footer={null}
width={width}
>
<style>{`
.headless .ant-modal-close {
display:none;
}
.headless .ant-modal-body {
padding: 0px;
}
.headless .closeBtn {
position: absolute;
color: ${theme.foreground_select};
top: -8px;
right: -10px;
font-size: 24px !important;
background: #fff;
width: 14px;
height: 8px;
margin-right: 0px;
}
.headless .icon-htmal5icon19:before {
left: -4px;
position: absolute;
top: -13px;
}
`}</style>
<i className="iconfont icon-htmal5icon19 closeBtn" onClick={ () => setVisible(false) }></i>
{props.children}
</Modal>
)
}
export default HeadlessModal

@ -0,0 +1,35 @@
import React, { useState, useEffect, useContext, useRef, memo } from 'react';
import {Link} from 'react-router-dom';
import { getUrl2, isDev, ThemeContext } from 'educoder'
import axios from 'axios'
function InfoTab (props) {
const theme = useContext(ThemeContext);
const { category, changeCategory, categories, right } = props;
const username = props.match.params.username
useEffect(() => {
}, [])
return (
<div className="white-panel edu-back-white pt20 pb20 clearfix ">
{categories && categories.map(item => {
return (
<li key={item.key} className={category == item.key ? "active" : ''}><a href="javascript:void(0)" onClick={()=>changeCategory(item.key)}>{item.name}</a></li>
)
})}
{/* <li className={category ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeCategory()}></a></li>
<li className={category=="manage" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("manage")}>{is_current ? "我":"TA"}管理的</a></li>
<li className={category=="study" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("study")}>{is_current ? "我":"TA"}学习的</a></li> */}
<div className="fr">
{right}
</div>
</div>
)
}
export default InfoTab

@ -120,3 +120,105 @@
display: block;
font-size: 20px;
}
/* 个人主页头部改版 */
.bannerPanel{
width:100%;
top:0px;
left:0px;
height:186px;
padding-top:25px;
box-sizing: border-box;
background: url('../../../images/account/infobanner.png') no-repeat top center;
background-color: #0d4473
}
.username{
font-size:26px;
margin-right:15px;
height: 30px;
line-height: 30px;
float: left;
color:#fff;
}
.myPhoto img{
border-radius: 50%;
}
.myPhoto{
border-radius: 50%;
/* border:2px solid #fff; */
width:110px;
height:110px;
}
.userpost{
padding:0px 10px;
height: 20px;
line-height:18px;
border:1px solid #fff;
border-radius: 2px;
color:#fff!important;
display: inline;
margin-top: 7px;
float:left;
}
.user-colorgrey-B8{color:#B8B8B8}
.user-colorgrey-green{color:#7ED321}
.user_yellow_btn {
color: #fff!important;
background-color: #FF8E02;
}
.headtab span {
color: #e8e8e8;
font-size: 14px;
}
.headtab span, .headtab a {
display: block;
width: 100%;
text-align: center;
}
.headtab {
width: 140px;
height: 70px;
text-align: center;
}
.leftTransform:before{
position: absolute;
content:'';
left:0px;
top:6px;
width:1px;
height:84%;
background: #fff;
transform: rotate(18deg);
}
.userNav{
height: 54px;
line-height: 54px;
background: #fff;
box-shadow:0px 5px 12px 3px #e6e6e6;
border-radius:7px;
padding:0px 10px;
}
.userNav li{
display: inline-block;
padding:0px 30px;
box-sizing: border-box
}
.userNav li a{
color:#333333;
font-size: 16px;
display: block
}
.userNav li.active a{
position: relative;
color:#4CACFF;
}
.userNav li.active a:after{
position: absolute;
bottom: 0px;
height: 4px;
width:100%;
content: '';
left:0px;
background: #4CACFF;
}

@ -0,0 +1,175 @@
import { getUrl2, isDev } from 'educoder'
import axios from 'axios'
let _url_origin = getUrl2()
let _path = isDev() ? 'public' : 'build'
let _testHost = '' ; // 'http://192.168.2.63:3001/api' ; // '' ;
let login = 'innov'
let uploader;
let $ = window.$
function loadLib(callback) {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/es6-promise.min.js`,
(data, textStatus, jqxhr) => {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/aliyun-oss-sdk-5.3.1.min.js`,
(data, textStatus, jqxhr) => {
$.getScript(
`${_url_origin}/react/${_path}/js/aliyun-upload/aliyun-upload-sdk-1.5.0.min.js`,
(data, textStatus, jqxhr) => {
callback && callback()
});
});
});
}
function createUploader(options) {
if (window.AliyunUpload && window.AliyunUpload.Vod) {
doCreateUploader(options)
} else {
loadLib(() => {
doCreateUploader(options)
})
}
}
function doCreateUploader (options) {
uploader = new window.AliyunUpload.Vod({
timeout: $('#timeout').val() || 60000,
partSize: $('#partSize').val() || 1048576,
parallel: $('#parallel').val() || 5,
retryCount: $('#retryCount').val() || 3,
retryDuration: $('#retryDuration').val() || 2,
region: $('#region').val() || 'ap-southeast-1',
userId: $('#userId').val() || 1202060945918292, // 1303984639806000,
// 添加文件成功
addFileSuccess: function (uploadInfo) {
console.log("addFileSuccess: " + uploadInfo.file.name)
uploader.startUpload()
},
// 开始上传
onUploadstarted: function (uploadInfo) {
// 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法
// 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值调用点播的不同接口获取uploadauth和uploadAddress
// 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
// 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi
// 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
// 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
const fileName = uploadInfo.file.name
if (!uploadInfo.videoId) {
var createUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
axios.post(createUrl, {
title: fileName,
file_name: fileName
}).then((response) => {
// if (response.data.status == )
const data = response.data.data
var uploadAuth = data.UploadAuth
var uploadAddress = data.UploadAddress
var videoId = data.VideoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
options.addFileSuccess && options.addFileSuccess(uploadInfo)
}).catch((error) => {
// 删除当前出错的,并执行下一个任务
uploader.deleteFile(uploader._curIndex)
uploader.nextUpload()
console.log(error)
})
$('#status').text('文件开始上传...')
console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
} else {
// 如果videoId有值根据videoId刷新上传凭证
var refreshUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
axios.put(refreshUrl, {
video_id: uploadInfo.videoId,
}).then((response) => {
if (response.data.status == -1) {
options.onUploadError && options.onUploadError(uploadInfo)
return;
}
const data = response.data.data
var uploadAuth = data.UploadAuth
var uploadAddress = data.UploadAddress
var videoId = data.VideoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
options.addFileSuccess && options.addFileSuccess(uploadInfo)
}).catch((error) => {
uploader.deleteFile(uploader._curIndex)
uploader.nextUpload()
console.log(error)
})
}
},
// 文件上传成功
onUploadSucceed: function (uploadInfo) {
options.onUploadSucceed && options.onUploadSucceed(uploadInfo)
console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
$('#status').text('文件上传成功!')
},
// 文件上传失败
onUploadFailed: function (uploadInfo, code, message) {
console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message)
$('#status').text('文件上传失败!')
},
// 取消文件上传
onUploadCanceled: function (uploadInfo, code, message) {
console.log("Canceled file: " + uploadInfo.file.name + ", code: " + code + ", message:" + message)
$('#status').text('文件上传已暂停!')
},
// 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
onUploadProgress: function (uploadInfo, totalSize, progress) {
options.onUploadProgress && options.onUploadProgress(uploadInfo, totalSize, progress)
console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%")
var progressPercent = Math.ceil(progress * 100)
$('#auth-progress').text(progressPercent)
$('#status').text('文件上传中...')
},
// 上传凭证超时
onUploadTokenExpired: function (uploadInfo) {
// 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时
// 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth
// 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth
$('#status').text('文件上传超时!')
var refreshUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
axios.put(refreshUrl, {
video_id: uploadInfo.videoId,
}).then((response) => {
const data = response.data.data
var uploadAuth = data.UploadAuth
uploader.resumeUploadWithAuth(uploadAuth)
}).catch((error) => {
console.log(error)
})
},
// 全部文件上传结束
onUploadEnd: function (uploadInfo) {
options.onUploadEnd && options.onUploadEnd(uploadInfo)
$('#status').text('文件上传完毕!')
console.log("onUploadEnd: uploaded all the files")
}
})
if (options.gotUploader) {
options.gotUploader(uploader)
}
}
export function getUploader (_login, options) {
_login && (login = _login)
if (!uploader) {
uploader = createUploader(options)
}
return uploader;
}

@ -0,0 +1,86 @@
import React, { useState, useEffect, useContext, useRef, memo } from 'react';
import { Progress, Input, Tooltip, Form } from 'antd'
import { getUrl2, isDev, CBreadcrumb, ActionBtn, ThemeContext, ModalWrapper } from 'educoder'
import axios from 'axios'
const MAX_LENGTH = 30
function EditVideoModal (props) {
const modalEl = useRef(null);
const theme = useContext(ThemeContext);
const { history, videoId, cover_url, title, created_at, isReview, onEditVideo, visible, setVisible,
form, editSuccess } = props;
const getFieldDecorator = form.getFieldDecorator
const username = props.match.params.username
const _title = form.getFieldsValue().title;
function toList() {
history.push(`/users/${username}/videoes`)
}
function toUpload() {
history.push(`/users/${username}/videoes/upload`)
}
function onOk() {
form.validateFieldsAndScroll((err, values) => {
if (!err) {
const url = `/users/${username}/videos/${videoId}.json`
axios.put(url, {
title: _title
}).then((response) => {
if (response.data) {
onCancel()
editSuccess()
}
}).catch((e) => {
})
} else {
// $("html").animate({ scrollTop: $('html').scrollTop() - 100 })
}
})
// setVisible(false)
}
function onCancel() {
setVisible(false)
}
useEffect(() => {
modalEl.current.setVisible(visible)
}, [visible])
useEffect(() => {
visible && form.setFieldsValue({
title,
})
}, [visible])
return (
<ModalWrapper
ref={modalEl}
width="600px"
title={`视频编辑`}
{ ...props }
onOk={onOk}
onCancel={onCancel}
className="editVideoModal"
>
<Form.Item
label="视频标题"
className="title formItemInline"
>
{getFieldDecorator('title', {
rules: [{
required: true, message: '请输入标题',
}, {
max: MAX_LENGTH, message: '最大限制为30个字符',
}],
})(
<Input placeholder="" className="titleInput" maxLength={MAX_LENGTH}
addonAfter={String(_title ? `${String(_title.length)}/${MAX_LENGTH}` : 0)} />
)}
</Form.Item>
</ModalWrapper>
)
}
const WrappedEditVideoModal = Form.create({ name: 'editVideoModal' })(EditVideoModal);
export default WrappedEditVideoModal

@ -0,0 +1,116 @@
.itemWrap {
display: flex;
flex-wrap: wrap;
}
/* item */
.videoItem {
width: 280px;
margin-right: 26px;
margin-bottom: 26px;
position: relative;
}
.videoItem:nth-child(4n+0) {
margin-right: 0px;
}
.videoItem img.cover {
width: 100%;
border-radius: 6px 6px 0px 0px;
height: 158px;
}
.nItem.videoItem:hover .mask {
display: block;
top: 0px;
width: 100%;
height: 158px;
cursor: pointer;
}
.nItem.videoItem:hover .playWrap {.videoItem img.play
display: inline-block;
}
.nItem .mask {
border-radius: 6px 6px 0px 0px;
display: none;
text-align: center;
position: absolute;
background: #000;
opacity: 0.5;
}
.videoItem .playWrap {
display: none;
width: 100%;
text-align: center;
height: 70px;
position: absolute;
top: 0px;
left: 0px;
}
.videoItem img.play {
margin-top: 20%;
position: relative;
z-index: 99;
}
.videoItem .square-main {
padding: 9px 8px;
background: #fff;
border-radius: 0px 0px 6px 6px;
}
.videoItem .square-main .title{
max-width: 256px;
line-height: 18px;
}
.videoInReviewItem .square-main {
background: #EAEAEA;
}
.videoItem .time {
color: #A0A0A0;
}
.videoItem .square-main .buttonRow {
justify-content: space-between;
line-height: 15px;
}
.nItem.videoItem:hover .square-main {
color: #fff;
background: #333;
}
/* 预览弹框 */
.showVideoModal .ant-modal-body {
display: flex;
flex-direction: column;
}
.showVideoModal video{
width: 800px;
height: 450px;
}
.showVideoModal .copyLine {
justify-content: space-between;
padding: 9px;
background: #000000;
width: 800px;
}
.showVideoModal .copyLine input {
color: #707070;
background-color: #000 !important;
border-color: #707070;
margin-right: 12px;
}
.showVideoModal .copyLine a {
flex: 0 0 106px;
}
.toolbarRow {
justify-content: space-between;
padding: 0 8px;
margin-bottom: 6px;
}
/* 跳转按钮 */
.infoVideo .toUploadBtn {
height: 48px;
margin-right: 20px;
}

@ -0,0 +1,343 @@
import React, { useState, useEffect, useContext, useRef, memo } from 'react';
import {Link} from 'react-router-dom';
import { Pagination, Input, Button } from 'antd'
import { getUrl2, isDev, ThemeContext, ActionBtn, NoneData } from 'educoder'
import axios from 'axios'
import VideoInReviewItem from './VideoInReviewItem'
import EditVideoModal from './EditVideoModal'
import './InfosVideo.css'
import InfoTab from '../common/InfoTab'
import HeadlessModal from '../common/HeadlessModal'
import CRoundSelect from '../common/CRoundSelect'
import ClipboardJS from 'clipboard'
function useModal(initValue) {
const [visible, setVisible] = useState(initValue)
return {
visible,
setVisible
}
}
function useCategory(initValue) {
const [category, setCategory] = useState(initValue)
function changeCategory(key) {
setCategory(key)
}
return {
category,
changeCategory
}
}
function usePagination() {
const [page, setPage] = useState(1)
function onPageChange(page) {
setPage(page)
}
return {
current: page,
onChange: onPageChange
}
}
const PAGE_SIZE = 16
const DEFAULT_VIDEO_WIDTH_IN_MD = "90%" // 400
const DEFAULT_VIDEO_HEIGHT_IN_MD = "55%" // 400
let videoId = {};
let _clipboard = null;
const _items=[
{key: 'published_at-desc', name: '最新上传'},
{key: 'published_at-asc', name: '最早上传'},
]
function InfoVideo (props) {
const [videoes, setVideoes] = useState(undefined)
const [reviewVideoes, setReviewVideoes] = useState(undefined)
const [count, setCount] = useState(0)
const [loading, setLoading] = useState(true)
const [sortKey, setSortKey] = useState(_items[0].key)
const editModalObj = useModal(false)
const videoModalObj = useModal(false)
const categoryObj = useCategory('all')
const pageObj = usePagination()
const theme = useContext(ThemeContext);
const editModalEl = useRef(null);
const videoEl = useRef(null);
const { showNotification, history } = props;
const username = props.match.params.username
function toUpload() {
history.push(`/users/${username}/videoes/upload`)
}
function fetchVideoes() {
const fetchUrl = `/users/${username}/videos.json`
const sorts = sortKey.split('-')
setLoading(true)
axios.get(fetchUrl, {
params: {
page: pageObj.current,
per_page: PAGE_SIZE,
sort_by: sorts[0],
sort_direction: sorts[1],
//
}
})
.then((response) => {
setLoading(false)
if (response.data.videos) {
setVideoes(response.data.videos)
setCount(response.data.count)
}
}).catch(() => {
})
}
function fetchReviewVideoes() {
const fetchUrl = `/users/${username}/videos/review.json`
setLoading(true)
axios.get(fetchUrl, {
params: {
per_page: 200
}
})
.then((response) => {
setLoading(false)
if (response.data.videos) {
setReviewVideoes(response.data.videos)
setCount(response.data.count)
}
}).catch(() => {
})
}
useEffect(() => {
fetchVideoes()
}, [pageObj.current, sortKey])
useEffect(() => {
if (categoryObj.category == 'all') {
fetchVideoes()
} else {
fetchReviewVideoes()
}
}, [categoryObj.category])
useEffect(() => {
if (videoModalObj.visible == false) {
// 关闭视频
videoEl.current && videoEl.current.pause()
if (_clipboard) {
_clipboard.destroy();
_clipboard = null;
}
} else {
setTimeout(() => {
if (!_clipboard) {
_clipboard = new ClipboardJS('.copybtn');
_clipboard.on('success', (e) => {
showNotification('复制成功')
});
}
}, 200)
}
}, [videoModalObj.visible])
useEffect(() => {
}, [])
function editSuccess() {
fetchVideoes()
}
function onEditVideo(item) {
videoId = {
videoId: item.id,
title: item.title
}
editModalObj.setVisible(true)
// editModalEl.current.toList(true, video);
// this.refs['editVideoModal'].setVisible(true, video);
}
function onMaskClick(item) {
videoId = {
videoId: item.id,
title: item.title,
file_url: item.file_url,
cover_url: item.cover_url
}
videoModalObj.setVisible(true)
}
// TODO use封装
function onSortChange(key, index) {
const _item = _items[index]
_items.splice(index, 1)
_items.unshift(_item)
setSortKey(key)
}
function getCopyText (file_url, cover_url) {
return `<video src="${file_url}" controls="true" controlslist="nodownload" width="${DEFAULT_VIDEO_WIDTH_IN_MD}" height="${DEFAULT_VIDEO_HEIGHT_IN_MD}" poster="${cover_url}">您的浏览器不支持 video 标签。</video>`
}
const _inputValue = getCopyText(videoId.file_url, videoId.cover_url)
return (
<div className="educontent infoVideo">
<EditVideoModal {...props} {...editModalObj}
editSuccess={editSuccess}
{...videoId}
></EditVideoModal>
<HeadlessModal
{...videoModalObj}
className="showVideoModal"
width={800 - 1}
>
<video
ref={videoEl}
src={videoId.file_url} controls="true" controlslist="nodownload">
您的浏览器不支持 video 标签
</video>
<div className="df copyLine">
<Input value={_inputValue}
className="dark"
></Input>
<ActionBtn className="copybtn" data-clipboard-text={_inputValue}>复制视频地址</ActionBtn>
</div>
</HeadlessModal>
<style>{`
/* item */
.videoPublishSuccess .section {
background: #fff;
padding: 16px 20px;
padding-top: 0px;
position: relative;
text-align: center;
color: ${theme.foreground_tip};
}
.videoItem .square-main .buttonRow i {
vertical-align: top;
font-size: 16px;
color: ${theme.foreground_select} !important;
margin-left: 6px;
}
/*
(26 - 24) * 3 / 2
*/
.itemWrap {
margin-left: 3px;
}
.videoItem {
margin-right: 24px;
}
`}</style>
<InfoTab
{...props}
categories={[{
key: 'all',
name: '全部视频'
}, {
key: 'review',
name: '待审核视频'
}]}
{...categoryObj}
right={
<Button type="primary" icon="upload"
onClick={() => { toUpload() }}
className="toUploadBtn"
>
上传视频
</Button>
}
></InfoTab>
<div className="toolbarRow mt20 df">
<span>
<span style={{color: theme.foreground_orange1}}> {count} </span>
个视频
</span>
{categoryObj.category == 'all' && <CRoundSelect {...props}
width={'90px'}
items={_items}
onSortChange={onSortChange}
sortKey={sortKey }
></CRoundSelect>}
</div>
{categoryObj.category == 'all' ?
<div className="itemWrap">
{
videoes == undefined ? '' :
videoes.length ?
videoes.map((item, index) => {
return (<VideoInReviewItem
{...props}
{...item}
key={item.id}
onEditVideo={onEditVideo}
onMaskClick={onMaskClick}
getCopyText={getCopyText}
>
</VideoInReviewItem>)
})
: <NoneData style={{width: '100%'}}></NoneData>
}
</div>
:
<div className="itemWrap">
{
reviewVideoes == undefined ? '' :
reviewVideoes.length ?
reviewVideoes.map((item, index) => {
return (<VideoInReviewItem
{...props}
{...item}
key={item.id}
isReview={true}
>
</VideoInReviewItem>)
})
: <NoneData style={{width: '100%'}}></NoneData>
}
</div>
}
{
categoryObj.category == 'all' && count > PAGE_SIZE &&
<div className="mt30 mb50 edu-txt-center">
<Pagination showQuickJumper total={count} pageSize={PAGE_SIZE}
{...pageObj}
/>
</div>
}
</div>
)
}
export default InfoVideo
/**
<video src="http://outin-396971199eed11e991a100163e1c7426.oss-cn-shanghai.aliyuncs.com/sv/52943d8b-16c8dc2a8ca/52943d8b-16c8dc2a8ca.mp4" controls="true" controlslist="nodownload" width="400">
您的浏览器不支持 video 标签
</video>
*/

@ -0,0 +1,77 @@
import React, { useState, useEffect, useContext, memo } from 'react';
import { Progress, Input, Tooltip } from 'antd'
import { getUrl2, isDev, CBreadcrumb, ActionBtn, ThemeContext } from 'educoder'
import axios from 'axios'
import moment from 'moment'
import playIcon from './images/play.png'
import ClipboardJS from 'clipboard'
/**
cover_url: "http://video.educoder.net/f6ba49c3944b43ee98736898e31b7d88/snapshots/12da3f7df07c499b8f0fc6dc410094e9-00005.jpg"
created_at: "2019-08-12 13:48:26"
file_url: "http://video.educoder.net/sv/4c7eb4-16c845ee09c/4c7eb4-16c845ee09c.mp4"
id: 1
published_at: "2019-08-12 15:38:00"
title: "测试标题"
updated_at: "2019-08-12 17:17:09"
*/
let _clipboard = null;
function VideoInReviewItem (props) {
const theme = useContext(ThemeContext);
const { history, file_url, cover_url, title, created_at, published_at, isReview, id
, onEditVideo, onMaskClick, getCopyText, showNotification } = props;
useEffect(()=> {
if (!isReview) {
_clipboard = new ClipboardJS(`.copybtn_item_${id}`);
_clipboard.on('success', (e) => {
showNotification('复制成功')
});
}
return () => {
_clipboard && _clipboard.destroy();
}
}, [])
const username = props.match.params.username
function toList() {
history.push(`/users/${username}/videoes`)
}
function toUpload() {
history.push(`/users/${username}/videoes/upload`)
}
return (
<div className={`${isReview ? 'videoInReviewItem' : 'nItem'} videoItem`}>
<img className="cover" src={cover_url || "http://video.educoder.net/e7d18970482a46d2a6f0e951b504256c/snapshots/491e113950d74f1dab276097dae287dd-00005.jpg"}
></img>
{!isReview && <div className="mask" onClick={() => onMaskClick(props)}>
</div>}
{!isReview &&
<div className="playWrap" onClick={() => onMaskClick(props)}>
<img className="play" src={playIcon}></img>
</div>
}
<div className="square-main">
<div className="title overflowHidden1"
title={title && title.length > 20 ? title : ''}
>{title}</div>
<div className="df buttonRow">
{/* 2019-09-01 10:00:22 */}
<span className="time">{moment(published_at || created_at).format('YYYY-MM-DD HH:mm:ss')}</span>
{ isReview != true && <div>
<Tooltip title="编辑" placement="bottom">
<i className="icon-bianji1 iconfont" onClick={() => onEditVideo(props)}
style={{ marginTop: '1px', display: 'inline-block'}}
></i>
</Tooltip>
<Tooltip title="复制视频地址" placement="bottom">
<i className={`icon-fuzhi iconfont copybtn_item_${id}`} data-clipboard-text={getCopyText(file_url, cover_url)}></i>
</Tooltip>
</div> }
</div>
</div>
</div>
)
}
export default VideoInReviewItem

@ -0,0 +1,73 @@
import React, { useState, useEffect, useContext, memo } from 'react';
import { Progress, Input } from 'antd'
import { getUrl2, isDev, CBreadcrumb, ActionBtn, ThemeContext } from 'educoder'
import axios from 'axios'
import okIcon from './images/ok_border.png'
function VideoUpload (props) {
const theme = useContext(ThemeContext);
const { history } = props;
const username = props.match.params.username
function toList() {
history.push(`/users/${username}/videoes`)
}
function toUpload() {
history.push(`/users/${username}/videoes/upload`)
}
return (
<div className={`videoPublishSuccess educontent`}>
<CBreadcrumb
className="mb26 mt16"
separator=" > "
items={[
{ to: `/users/${username}/videoes`, name: '视频'},
{ name: '上传'}
]}
></CBreadcrumb>
<style>{`
.videoPublishSuccess .section {
background: #fff;
padding: 72px 20px;
position: relative;
text-align: center;
color: ${theme.foreground_tip};
}
.videoPublishSuccess img.ok {
width: 64px;
margin: 16px;
margin-top: 0px;
}
.videoPublishSuccess .tip {
margin-top: 10px;
margin-bottom: 12px;
}
.videoPublishSuccess .toListBtn {
margin-right: 10px;
}
.videoPublishSuccess .toUploadBtn {
width: 112px;
}
`}</style>
<div className="section">
<div>
<img className="ok" src={okIcon}></img>
{/* <i className="icon-wanchenggouxuan iconfont font-36" style={{color: theme.foreground_select}}></i> */}
</div>
<div className="font-16" style={{ 'line-height': '16px'}}>恭喜</div>
<div className="font-16">提交成功</div>
<div className="tip">平台正在审核您的申请审核结果将以平台消息的形式通知您</div>
<div>
<ActionBtn className="toListBtn" onClick={toList}>查看已上传视频</ActionBtn>
<ActionBtn className="toUploadBtn" onClick={toUpload}>继续上传</ActionBtn>
</div>
</div>
</div>
)
}
export default VideoUpload

@ -0,0 +1,64 @@
import update from 'immutability-helper'
function find(state, action) {
let _index = -1
state.videoes.some((item, index) => {
// 同文件不同名字 fileHash也是一样的
if (item.loaded != 100 && (action.uploadInfo.fileHash == item.fileHash && action.uploadInfo.file.name == item.name)) {
_index = index
return true;
}
})
return _index;
}
export function reducer(state, action) {
switch (action.type) {
case 'addVideo':
const uploadInfo = action.uploadInfo
return {videoes: [...state.videoes, {
name: uploadInfo.file.name,
size: uploadInfo.file.size,
type: uploadInfo.file.type,
fileHash: uploadInfo.fileHash, // "ba1bbc53fdecd9eaaae479fbd9518442"
state: uploadInfo.state, // "Uploading" "Ready" "Success"
videoId: uploadInfo.videoId, // "719b82c875c34ac39f94feb145d25ad2"
loaded: 0,
title: ''
}]};
case 'removeVideo':
return {
videoes: update(state.videoes, {$splice: [[action.index, 1]]})
}
case 'updateProgress':
let _index = find(state, action)
let newVideoes = state.videoes
// 删除先执行
if (_index != -1) {
newVideoes = update(state.videoes, {[_index]: {
loaded: {$set: action.progressPercent},
videoId: {$set: action.uploadInfo.videoId},
// addFileSuccess的时候没有fileHash
fileHash: {$set: action.uploadInfo.fileHash}
}})
}
return {videoes: newVideoes};
case 'updateTitle':
let _upadteIndex = action.index
let newVideoes2 = state.videoes
if (_upadteIndex != -1) {
newVideoes2 = update(state.videoes, {[_upadteIndex]: {
title: {$set: action.title},
}})
}
return {videoes: newVideoes2};
default:
throw new Error();
}
}
export const initialState = {videoes: []};

@ -0,0 +1,52 @@
import React, { useState, useEffect, memo } from 'react';
import { Progress, Input } from 'antd'
import { getUrl2, isDev, CBreadcrumb, ActionBtn } from 'educoder'
import axios from 'axios'
const MAX_LENGTH = 30
/**
name: file.name,
size: file.size,
type: file.type,
fileHash: uploadInfo.fileHash, // "ba1bbc53fdecd9eaaae479fbd9518442"
state: uploadInfo.state, // "Uploading"
videoId: uploadInfo.videoId, // "719b82c875c34ac39f94feb145d25ad2"
loaded: 0
*/
function VideoUpload (props) {
const { className, index, name, loaded, state, cancelUpload, onTitleChange, title } = props;
// const [title, setTitle] = useState('')
const username = props.match.params.username
function titleChange (e) {
onTitleChange(e.target.value, index)
}
return (
<div className={`videoUpload ${className}`}>
<div className="filename">{index+1}. {name}</div>
<div className="progress df">
<Progress percent={loaded}
status={loaded == '100' ? "" : 'active'}
/>
<div className="cancelUpload">
<ActionBtn className="" onClick={() => cancelUpload(index, loaded == '100' )}>{loaded == '100' ? "删除" : "取消上传"}</ActionBtn>
</div>
</div>
<div>
<span className="titleLabel">标题</span>
<Input placeholder={`标题支持最多${MAX_LENGTH}个字符`} onInput={titleChange} maxLength={MAX_LENGTH} suffix={
<span className="color-grey-6 font-13">{String(title.length)}/{MAX_LENGTH}</span>
}
className="titleInput"
></Input>
</div>
</div>
)
}
export default VideoUpload

@ -0,0 +1,404 @@
import React, { useState, useEffect, useReducer, useContext, memo } from 'react';
import { getUrl2, isDev, CBreadcrumb, ActionBtn, ThemeContext } from 'educoder'
import axios from 'axios'
import VideoUpload from './VideoUpload'
import { Button } from 'antd'
import { getUploader } from './AliyunUploaderManager'
import { reducer, initialState } from './VideoReducer'
import { deleteVideoInCloud } from './VideoUtil'
import uploadIcon from './images/upload.png'
let uploader
const files = []
const MAX_FILE_COUNT = 3
const MAX_FILE_SIZE = 200
let noUploads = true
function VideoUploadList (props) {
const [videoes, setVideoes] = useState([]);
const [state, dispatch] = useReducer(reducer, initialState);
const theme = useContext(ThemeContext)
useEffect(() => {
window.addEventListener("beforeunload", beforeunload);
return () => {
window.removeEventListener("beforeunload", beforeunload);
}
}, [])
// TODO 闭包!
noUploads = (!state.videoes || state.videoes.length == 0);
function beforeunload(e) {
if (noUploads) {
return true;
}
var confirmationMessage = "确认要离开当前页面,当前数据不可恢复";
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
return confirmationMessage; //Webkit, Safari, Chrome etc.
}
const _beforeunload = beforeunload // .bind(this, noUploads, state)
const username = props.match.params.username
const { showNotification, history } = props;
const uploaderOptions = {
}
function onUploadChange (e) {
var file = e.target.files[0]
if (!file) {
// alert("请先选择需要上传的文件!")
return
}
if (file.size > 200 * 1024 * 1024) {
// 超过200m TODO
clearInput()
showNotification(`视频大小超过${MAX_FILE_SIZE}M`)
return;
}
let gotTheSameFileName = false;
state.videoes.some((item) => {
if (item.name == file.name) {
gotTheSameFileName = true;
return true;
}
})
if (gotTheSameFileName) {
clearInput()
showNotification(`你不能上传同一个视频文件名称,请重新选择。`)
return;
}
var Title = file.name
var userData = '{"Vod":{}}'
if (!uploader) {
getUploader(username,
// Object.assign(uploaderOptions,
{
addFileSuccess: (uploadInfo) => {
const file = uploadInfo.file
console.log('addFileSuccess', uploadInfo)
// const newVideoes = [...videoes, {
// name: file.name,
// size: file.size,
// type: file.type,
// fileHash: uploadInfo.fileHash, // "ba1bbc53fdecd9eaaae479fbd9518442"
// state: uploadInfo.state, // "Uploading" "Ready"
// videoId: uploadInfo.videoId, // "719b82c875c34ac39f94feb145d25ad2"
// loaded: 0
// }]
// setVideoes(newVideoes)
// files.push(file)
dispatch({type: 'addVideo', uploadInfo})
},
onUploadProgress: (uploadInfo, totalSize, progress) => {
var progressPercent = Math.ceil(progress * 100)
let _index = -1;
videoes.some((item, index) => {
// addFileSuccess的时候没有fileHash
// if (uploadInfo.fileHash == item.fileHash) {
if (uploadInfo.file.name == item.name) {
_index = index
return true;
}
})
// TODO 这里不用reducer会出现state被重置的问题
// if (_index == -1) {
// const newVideoes = [...videoes, {
// name: file.name,
// size: file.size,
// type: file.type,
// fileHash: uploadInfo.fileHash, // "ba1bbc53fdecd9eaaae479fbd9518442"
// state: uploadInfo.state, // "Uploading" "Ready"
// videoId: uploadInfo.videoId, // "719b82c875c34ac39f94feb145d25ad2"
// loaded: progressPercent
// }]
// setVideoes(newVideoes)
// return;
// }
// // exercise_questions : update(prevState.exercise_questions, {[index]: { isNew: {$set: false}}})
// setVideoes(update(videoes, {[_index]: { loaded: {$set: progressPercent}}}))
dispatch({type: 'updateProgress', uploadInfo, progressPercent})
},
onUploadEnd: (uploadInfo) => {
console.log('onUploadEnd', uploadInfo)
},
onUploadSucceed: (uploadInfo) => {
console.log('onUploadSucceed', uploadInfo)
},
onUploadError: (uploadInfo) => {
},
// 可能需要等lib加载完毕才能执行
gotUploader: (_uploader) => {
// 首先调用 uploader.addFile(event.target.files[i], null, null, null, userData)
console.log(_uploader)
let result = _uploader.addFile(file, null, null, null, userData)
uploader = _uploader;
window.uploader = uploader;
}
}
// )
)
} else {
let result = uploader.addFile(file, null, null, null, userData)
}
}
function clearInput() {
const _input = document.getElementById('fileUpload')
_input.value = ''
}
// uploader.deleteFile(index);
function cancelUpload(index, isSuccess) {
// TODO 确定取消?
uploader.deleteFile(index)
if (isSuccess) {
deleteVideoInCloud(username, state.videoes[index].videoId)
}
// else {
// uploader.cancelFile(index)
// }
clearInput()
dispatch({type: 'removeVideo', index})
// setVideoes([...videoes.splice(index, 1)])
}
function onPublish() {
if (state.videoes.length == 0) {
showNotification('请先上传视频')
return;
}
const publishUrl = `/users/${username}/videos/batch_publish.json`
axios.post(publishUrl, {
videos: state.videoes.map(item => {
return {
video_id: item.videoId,
// todo
title: item.title
}
})
}).then((response) => {
// to success page
if (response.data.status == 0) {
history.push(`/users/${username}/videoes/success`)
}
}).catch((error) => {
console.log(error)
})
}
function onTitleChange(title, index) {
dispatch({type: 'updateTitle', title, index})
}
// login
const protocolLine = <div>上传视频即表示您已同意<span style={{color: theme.foreground_select}}>上传内容协议</span></div>
return (
<div className="educontent videoUploadList" style={{ marginBottom: '200px' }}>
<style>{`
.videoUploadList .section {
background: #fff;
padding: 16px 20px;
padding-top: 0px;
position: relative;
padding-bottom: 36px;
}
.videoUploadList .cBreadcrumb {
margin-top: 16px;
}
.videoUploadList .uploadTip {
line-height: 18px;
margin-bottom: 16px;
}
.videoUploadList .title {
margin-bottom: 4px;
}
.videoUploadList .title .head {
display: inline-block;
margin-right: 8px;
}
.videoUploadList .title .titleDescription {
color: #555;
}
.videoUploadList .section .description {
padding-top: 10px;
margin-top: 20px;
margin-bottom: 30px;
color: #777;
}
.videoUploadList .section .description.noUploads {
text-align: 'center';
}
.videoUploadList .publishBtn {
padding: 0 16px
}
.videoUploadList .publishRow .publishBtn {
padding: 6px 24px;
height: auto;
margin-bottom: 24px;
}
.videoUploadList .addVideoBtn {
position: absolute;
right: 30px;
}
.videoUploadList .publishRow {
text-align: center;
margin-top: 42px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.noUploads {
text-align: center;
}
/* item */
.videoUploadList .cancelUpload {
flex: 0 0 200px;
}
.videoUploadList .titleInput {
width: 480px;
margin-top: 16px;
}
.videoUploadList .videoUpload {
padding: 26px 0;
border-bottom: 1px dashed #DCDCDC;
}
.videoUploadList .videoUpload:last-child {
border-bottom: none;
}
`}</style>
<CBreadcrumb
className="mb26"
separator=" > "
items={[
{ to: `/users/${username}/videoes`, name: '视频'},
{ name: '上传'}
]}
></CBreadcrumb>
<div className="title">
<h2 className="head">上传视频</h2>
{/* <span className="titleDescription">单次最多支持{MAX_FILE_COUNT}个视频文件上传,不支持断点续传,单个视频文件最大{MAX_FILE_SIZE}M</span> */}
</div>
<div className="section">
{/* noUploads */}
{noUploads && <div className="noUploads" style={{paddingTop: '72px'}}>
<img src={uploadIcon} onClick={() => document.getElementById('fileUpload').click()}></img>
<div style={{
color: '#000000',
fontSize: '18px',
fontWeight: 'bold',
marginBottom: '20px'
}}>选择您要上传的视频</div>
{protocolLine}
</div>}
<div>
{state.videoes.map((item, vIndex) => {
return (
<VideoUpload {...props} {...item} className=""
cancelUpload={cancelUpload}
onTitleChange={onTitleChange}
key={vIndex}
index={vIndex}
></VideoUpload>
)
})}
</div>
{state.videoes && state.videoes.length === MAX_FILE_COUNT &&
<div className="uploadTip">
<i className="iconfont icon-tishi" style={{color: '#FF6F6F', verticalAlign: 'text-bottom'}}></i>
<span>单次最多支持3个视频文件上传</span>
</div>}
{(!noUploads && state.videoes.length < MAX_FILE_COUNT) && <ActionBtn className="publishBtn" onClick={() => document.getElementById('fileUpload').click()}
>继续添加</ActionBtn>}
<div className={`description ${noUploads ? 'noUploads' : ''}`}>
<div className="">视频大小不支持断点续传单个视频文件最大200M单次最多支持3个视频文件上传 </div>
<div className="">视频规格aviflvf4vm4vmovmp4rmvbswfwebm </div>
<div className="">温馨提示请勿上传违法视频平台将为每一个视频分配一个地址您可以通过引用改地址将视频使用在开发社区等模块</div>
</div>
{!noUploads && <React.Fragment>
{/* {(state.videoes.length < MAX_FILE_COUNT) && <Button type="primary" icon="plus-square"
onClick={() => { document.getElementById('fileUpload').click()}}
className="fr addVideoBtn"
>
添加更多视频
</Button>} */}
<div style={{}} className="publishRow">
<ActionBtn className="publishBtn" onClick={() => onPublish()}
>立即发布</ActionBtn>
{protocolLine}
</div>
</React.Fragment>}
</div>
<input type="file" id="fileUpload" style={{display: 'none'}} onChange={onUploadChange}
accept="video/*"
></input>
</div>
)
}
export default VideoUploadList
/**
bucket: "outin-396971199eed11e991a100163e1c7426"
checkpoint: {file: File, name: "sv/2d0fd065-16c7a62fcc5/2d0fd065-16c7a62fcc5.mp4", fileSize: 491511493, partSize: 1048576, uploadId: "A8DB0663F44C44F58F3F7F45892ED08B", }
endpoint: "https://oss-cn-shanghai.aliyuncs.com"
file: File {name: "[阳光电影-www.ygdy8.com]金秘书为何这样-02.mp4", lastModified: 1532441562000, lastModifiedDate: Tue Jul 24 2018 22:12:42 GMT+0800 (China Standard Time), webkitRelativePath: "", size: 491511493, }
fileHash: "ba1bbc53fdecd9eaaae479fbd9518442"
isImage: false
loaded: 0.5927505330490405
object: "sv/2d0fd065-16c7a62fcc5/2d0fd065-16c7a62fcc5.mp4"
region: "cn-shanghai"
retry: false
ri: "F0FDC11A-9A92-4A50-882A-423C3EA499F3"
state: "Uploading"
userData: "eyJWb2QiOnt9fQ=="
videoId: "719b82c875c34ac39f94feb145d25ad2"
file
lastModified: 1532441562000
lastModifiedDate: Tue Jul 24 2018 22:12:42 GMT+0800 (China Standard Time) {}
name: "[阳光电影-www.ygdy8.com]金秘书为何这样-02.mp4"
size: 491511493
type: "video/mp4"
webkitRelativePath: ""
*/

@ -0,0 +1,13 @@
import axios from 'axios'
export function deleteVideoInCloud(login, video_id) {
const url = `/users/${login}/videos/cancel.json`
axios.post(url, {
video_id
}).then((response) => {
}).catch((error) => {
console.log(error)
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Loading…
Cancel
Save