diff --git a/public/react/src/App.js b/public/react/src/App.js index e71ce4a9a..673751791 100644 --- a/public/react/src/App.js +++ b/public/react/src/App.js @@ -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({ @@ -322,7 +326,10 @@ class App extends Component { /> () + (props) => { + + return () + } }> {children} : {this.props.children} diff --git a/public/react/src/modules/courses/common/CBreadcrumb.js b/public/react/src/modules/courses/common/CBreadcrumb.js index d9d91f4be..6e3343edf 100644 --- a/public/react/src/modules/courses/common/CBreadcrumb.js +++ b/public/react/src/modules/courses/common/CBreadcrumb.js @@ -10,7 +10,7 @@ class CBreadcrumb extends Component{ render(){ let { items, className, separator }=this.props; return( -

+

{ items && items.map( (item, index) => { if (!item.name) { return '' diff --git a/public/react/src/modules/user/usersInfo/Infos.js b/public/react/src/modules/user/usersInfo/Infos.js index 264dcbbf1..497cfb0fc 100644 --- a/public/react/src/modules/user/usersInfo/Infos.js +++ b/public/react/src/modules/user/usersInfo/Infos.js @@ -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,7 @@ 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 "./usersInfo.css" import "../../courses/css/members.css" import "../../courses/css/Courses.css" @@ -461,4 +460,5 @@ class Infos extends Component{ ) } } -export default CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC(Infos) )); \ No newline at end of file +// CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC)) +export default (Infos) ; \ No newline at end of file diff --git a/public/react/src/modules/user/usersInfo/InfosIndex.js b/public/react/src/modules/user/usersInfo/InfosIndex.js new file mode 100644 index 000000000..d6eca3401 --- /dev/null +++ b/public/react/src/modules/user/usersInfo/InfosIndex.js @@ -0,0 +1,68 @@ +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 $ = window.$; +class InfosIndex extends Component{ + constructor(props){ + super(props); + this.state={ + data:undefined, + } + } + componentDidMount =()=>{ + + } + + + //判断是否看的是当前用户的个人主页 + componentDidUpdate =(prevProps)=> { + + } + render(){ + let { + data , + }=this.state; + return( + + + {/* --------------------------------------------------------------------- */} + + + {/* 视频发布 */} + () + } + > + + + () + } + > + + + ) + } +} +export default CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC(InfosIndex) )); \ No newline at end of file diff --git a/public/react/src/modules/user/usersInfo/video/AliyunUploaderManager.js b/public/react/src/modules/user/usersInfo/video/AliyunUploaderManager.js new file mode 100644 index 000000000..ebf682fe1 --- /dev/null +++ b/public/react/src/modules/user/usersInfo/video/AliyunUploaderManager.js @@ -0,0 +1,162 @@ +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 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) { + options.addFileSuccess && options.addFileSuccess(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) + }).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刷新上传凭证 + 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) { + 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; +} \ No newline at end of file diff --git a/public/react/src/modules/user/usersInfo/video/InfosVideo.js b/public/react/src/modules/user/usersInfo/video/InfosVideo.js index 82ad87f85..85db33bc9 100644 --- a/public/react/src/modules/user/usersInfo/video/InfosVideo.js +++ b/public/react/src/modules/user/usersInfo/video/InfosVideo.js @@ -1,12 +1,17 @@ import React, { useState, useEffect, memo } from 'react'; +import {Link} from 'react-router-dom'; import { getUrl2, isDev } from 'educoder' import axios from 'axios' function InfoVideo (props) { + const username = props.match.params.username return ( -

123
+
123 + + to upload +
) } diff --git a/public/react/src/modules/user/usersInfo/video/VideoReducer.js b/public/react/src/modules/user/usersInfo/video/VideoReducer.js new file mode 100644 index 000000000..8960007a5 --- /dev/null +++ b/public/react/src/modules/user/usersInfo/video/VideoReducer.js @@ -0,0 +1,36 @@ +import update from 'immutability-helper' + +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 + }]}; + case 'updateProgress': + let _index = -1; + state.videoes.some((item, index) => { + // addFileSuccess的时候没有fileHash + // if (uploadInfo.fileHash == item.fileHash) { + if (action.uploadInfo.fileHash == item.fileHash || action.uploadInfo.file.name == item.name) { + _index = index + return true; + } + }) + return {videoes: update(state.videoes, {[_index]: { + loaded: {$set: action.progressPercent}, + fileHash: {$set: action.uploadInfo.fileHash} + }})}; + default: + throw new Error(); + } +} + +export const initialState = {videoes: []}; \ No newline at end of file diff --git a/public/react/src/modules/user/usersInfo/video/VideoUpload.js b/public/react/src/modules/user/usersInfo/video/VideoUpload.js new file mode 100644 index 000000000..fd0654eaf --- /dev/null +++ b/public/react/src/modules/user/usersInfo/video/VideoUpload.js @@ -0,0 +1,45 @@ +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 = 60 + +/** +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 } = props; + const [title, setTitle] = useState('') + const username = props.match.params.username + + function titleChange () { + setTitle(e.target.value) + } + return ( +
+
{index+1}. {name}
+
+ + cancelUpload(index)}>取消上传 +
+ + {String(title.length)}/{MAX_LENGTH} + }> +
+ ) +} + +export default VideoUpload diff --git a/public/react/src/modules/user/usersInfo/video/VideoUploadList.js b/public/react/src/modules/user/usersInfo/video/VideoUploadList.js new file mode 100644 index 000000000..3ab4536fa --- /dev/null +++ b/public/react/src/modules/user/usersInfo/video/VideoUploadList.js @@ -0,0 +1,208 @@ +import React, { useState, useEffect, useReducer, memo } from 'react'; + +import { getUrl2, isDev, CBreadcrumb } from 'educoder' +import axios from 'axios' + +import VideoUpload from './VideoUpload' +import { Button } from 'antd' + +import { getUploader } from './AliyunUploaderManager' +import { reducer, initialState } from './VideoReducer' + +let uploader +const files = [] +function VideoUploadList (props) { + + const [videoes, setVideoes] = useState([]); + const [state, dispatch] = useReducer(reducer, initialState); + + const uploaderOptions = { + + } + function onUploadChange (e) { + var file = e.target.files[0] + if (!file) { + alert("请先选择需要上传的文件!") + return + } + // TODO 判断文件类型 + var Title = file.name + file.type + 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) + + }, + // 可能需要等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) + } + } + // uploader.deleteFile(index); + function cancelUpload(index) { + // 确定取消? + setVideoes([...videoes.splice(index, 1)]) + const file = files[index] + uploader.cancelFile(file) + files.splice(index, 1) + } + // login + const username = props.match.params.username + return ( +
+ + + +
上传视频
+ + {state.videoes.map((item, vIndex) => { + return ( + + ) + })} + +
+
视频大小:不支持断点续传,单个视频文件最大200M;单次最多支持3个视频文件上传
+
视频规格:avi、flv、f4v、m4v、mov、mp4、rmvb、swf、webm
+
温馨提示:请勿上传违法视频。平台将为每一个视频分配一个地址,您可以通过引用改地址将视频使用在开发社区等模块
+
+ + + + +
+ ) +} + +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: "" + + + */ \ No newline at end of file