Merge branches 'dev_Ysl' and 'dev_Ysm' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_Ysl

dev_oauth
杨树林 6 years ago
commit 71b7557875

@ -15,8 +15,13 @@ class AttachmentsController < ApplicationController
update_downloads(@file)
redirect_to @file.cloud_url and return
end
send_file(absolute_path(local_path(@file)), filename: @file.filename, type: @file.content_type.presence || 'application/octet-stream')
pdf_attachment = params[:disposition] || "attachment"
if pdf_attachment == "inline"
send_file absolute_path(local_path(@file)),filename: @file.filename, disposition: 'inline',type: 'application/pdf'
else
send_file(absolute_path(local_path(@file)), filename: @file.filename,stream:false, type: @file.content_type.presence || 'application/octet-stream')
end
update_downloads(@file)
end

@ -37,7 +37,7 @@ class Users::BaseController < ApplicationController
end
def per_page_value
params[:per_page].to_i > 0 && params[:per_page].to_i < 50 ? params[:per_page].to_i : 20
params[:per_page].to_i > 0 && params[:per_page].to_i <= 100 ? params[:per_page].to_i : 20
end
alias_method :limit_value, :per_page_value

@ -1,5 +1,5 @@
class Users::VideoAuthsController < Users::BaseController
before_action :private_user_resources!, :require_auth_teacher!
before_action :private_user_resources!, :check_account, :require_auth_teacher!
def create
result = Videos::CreateAuthService.call(observed_user, create_params)

@ -1,5 +1,6 @@
class Users::VideosController < Users::BaseController
before_action :private_user_resources!, :require_auth_teacher!
before_action :private_user_resources!, :check_account
before_action :require_auth_teacher!, except: [:index, :review]
helper_method :current_video

@ -52,7 +52,8 @@ module ApplicationHelper
shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",")
Shixun.select([:id, :name, :user_id, :challenges_count, :myshixuns_count, :trainee, :identifier]).where("id
in(#{shixun_id}) or homepage_show =1").unhidden.order("myshixuns_count desc, homepage_show asc").limit(3)
in(#{shixun_id})").unhidden.order("homepage_show asc, myshixuns_count desc").limit(3)
end
# 相关推荐
@ -257,8 +258,8 @@ module ApplicationHelper
end
end
def download_url attachment
attachment_path(attachment)
def download_url attachment,options={}
attachment_path(attachment,options)
end
# 耗时:天、小时、分、秒

@ -10,7 +10,15 @@ module AliyunVod::Service::Base
Rails.logger.info("[AliyunVod] response => status: #{response.status}, result: #{result}")
raise AliyunVod::Error, result['Code'] if response.status != 200
if response.status != 200
message =
case result['Code']
when 'InvalidFileName.Extension' then '不支持的文件格式'
when 'IllegalCharacters' then '文件名称包含非法字符'
else raise AliyunVod::Error, result['Message']
end
raise AliyunVod::Error, message if message.present?
end
result
rescue => ex

@ -16,15 +16,6 @@ module AliyunVod::Service::VideoUpload
result = request(:post, params)
if result['Code'].present?
message =
case result['Code']
when 'InvalidFileName.Extension' then '不支持的文件格式'
when 'IllegalCharacters' then '文件名称包含非法字符'
end
raise AliyunVod::Error, message if message.present?
end
raise AliyunVod::Error, '获取上传凭证失败' if result['UploadAddress'].blank?
result

@ -268,7 +268,7 @@ class User < ApplicationRecord
# 实训路径管理员
def creator_of_subject?(subject)
subject.user_id == id
subject.user_id == id || admin?
end
# 实训路径合作者、admin

@ -6,6 +6,7 @@ json.publish_time attachment.publish_time
json.quotes attachment.quotes_count
json.downloads_count attachment.downloads_count
json.created_on attachment.created_on
json.url attachment_path(attachment, type: 'history').gsub("/api","")
# json.url attachment_path(attachment, type: 'history').gsub("/api","")
json.is_pdf attachment.is_history_pdf?
json.url attachment.is_history_pdf? ? attachment_path(attachment, type: 'history',disposition:"inline") : attachment_path(attachment, type: 'history')
json.attachment_id attachment.attachment_id

@ -1,7 +1,8 @@
json.id attachment.id
json.title attachment.title
json.is_public attachment.publiced?
json.is_lock attachment.locked?(@is_member)
# json.is_lock attachment.locked?(@is_member)
json.is_lock !attachment.publiced?
json.is_publish attachment.published?
json.publish_time attachment.publish_time
json.unified_setting attachment.unified_setting
@ -10,4 +11,5 @@ json.quotes attachment.quotes_count
json.description attachment.description
json.downloads_count attachment.downloads_count
json.created_on attachment.created_on
json.url download_url(attachment) unless attachment.locked?(@is_member)
json.is_pdf attachment.is_pdf?
json.url attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)

@ -2,5 +2,7 @@ json.id attachment.id
json.title attachment.title
json.filesize number_to_human_size attachment.filesize
json.description attachment.description
json.url download_url(attachment)
json.is_pdf attachment.is_pdf?
json.url attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)
# json.url download_url(attachment)
json.set! :delete, delete.nil? ? true : delete if defined? delete

@ -1,6 +1,6 @@
json.id attachment.id
json.title attachment.title
json.filesize number_to_human_size(attachment.filesize)
json.url download_url(attachment)
json.is_pdf attachment.is_pdf?
json.url attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)
json.created_on attachment.created_on
json.is_pdf attachment.is_pdf?

@ -1,9 +1,3 @@
# json.partial! 'attachments/attachment_small', attachment: @file
json.id @file.id
json.title @file.title
json.filesize number_to_human_size(@file.filesize)
json.url download_url(@file).gsub("/api","")
json.created_on @file.created_on
json.is_pdf @file.is_pdf?
json.partial! 'attachments/attachment_small', attachment: @file
json.partial! "attachment_histories/list", attachment_histories: @attachment_histories

@ -21,7 +21,7 @@ if @type == "image"
elsif @type == "html"
json.iframe_src File.read("#{@user_path}/#{@user_picture[0]}")&.html_safe
elsif @type == "txt"
json.contents @contents.html_safe
json.contents @contents.to_s
elsif @type =="qrcode"
json.qrcode_str @qrcode_str
elsif @type == "mp3" || @type == "mp4"

@ -1,7 +1,6 @@
json.partial! "homework_commons/homework_public_navigation", locals: {homework: @homework, course: @course, user: @current_user}
json.work_id @work.id
json.description @work.description
json.
json.attachments @work.attachments do |atta|
json.partial! "attachments/attachment_simple", locals: {attachment: atta, delete: @work.delete_atta(atta)}

@ -11,6 +11,7 @@ json.allow_statistics @is_manager
json.allow_send @user.logged?
json.allow_visit @subject.status > 1 || @is_manager
json.allow_add_member @is_manager
json.is_creator @is_creator
if @subject.excellent
json.courses @courses do |course|

@ -10,6 +10,7 @@ json.tidding_count 0
json.user_phone_binded @user.phone.present?
json.phone @user.phone
json.profile_completed @user.profile_completed?
json.professional_certification @user.professional_certification
if @course
json.course_identity @course_identity
json.course_name @course.name

@ -45,8 +45,8 @@ namespace :excellent_course_exercise do
end
if exercise_user && exercise_user.commit_status == 0
exercise_question_ids = exercise_question_ids.sample(rand_num)
questions = exercise.exercise_questions.where(id: exercise_question_ids)
question_ids = exercise_question_ids.sample(rand_num)
questions = exercise.exercise_questions.where(id: question_ids)
create_exercise_answer questions, member.user_id
total_score = calculate_student_score(exercise, member.user)

@ -61,13 +61,13 @@
"rc-select": "^8.0.12",
"rc-tree": "^1.7.11",
"rc-upload": "^2.5.1",
"react": "^16.8.0",
"react": "^16.9.0",
"react-beautiful-dnd": "^10.0.4",
"react-codemirror": "^1.0.0",
"react-codemirror2": "^6.0.0",
"react-content-loader": "^3.1.1",
"react-dev-utils": "^5.0.0",
"react-dom": "^16.8.0",
"react-dom": "^16.9.0",
"react-hot-loader": "^4.0.0",
"react-infinite-scroller": "^1.2.4",
"react-loadable": "^5.3.1",

@ -112,8 +112,25 @@ em.vertical-line{display: inline-block;width: 2px;background: #999;height: 10px}
.smallSquare:nth-child(3n+0){margin-right: 0px;}
.partimg{height: 180px;width: 100%;border-radius: 6px 6px 0px 0px;}
/*块状列表上面的绿色标签*/
.tag-green{position: absolute;left: 0px;top:20px;}
.tag-green .tag-name{display: block;width: auto;background-image: url("/images/educoder/tag1.png");background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;}
.tag-green{
position: absolute;
left: 15px;
bottom: 95px;}
.tag-green .tag-name{display: block;width: auto;
/*background-image: url("/images/educoder/tag1.png");*/
background: #000000;
border: 1px solid #fff;
border-radius: 3px;
font-size: 14px;
background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;}
.tag-orange{position: absolute;right: 0px;top:20px;}
.tag-orange .tag-name{display: block;width: auto;background-color:#FF6800;
background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;
height: 28px;
line-height: 28px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
/*发送至弹框里的下拉框*/
.downSelectOption{position: relative;height: 35px;}
.downSelectOption .showOption{background-color: #F4F4F4;border: 1px solid #EAEAEA;width: 100%;padding: 5px 40px 5px 5px;outline: none;height: 100%;box-sizing: border-box;}

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

@ -63,4 +63,14 @@ html, body {
border-left: 1px solid rgb(221, 221, 221);
/* 某些情况下被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)

@ -70,4 +70,9 @@ export function appendFileSizeToUploadFileAll(fileList) {
return item
})
}
export const uploadNameSizeSeperator = '  '
export const uploadNameSizeSeperator = '  '
export const sortDirections = ["ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend",
"ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend",
"ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend",
"ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", ]

@ -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, //, // 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>
)

@ -17,7 +17,7 @@ export { updatePageParams as updatePageParams } from './RouterUti
export { bytesToSize as bytesToSize } from './UnitUtil';
export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension,
downloadFile } from './TextUtil'
downloadFile, sortDirections } from './TextUtil'
export { handleDateString, getNextHalfHourOfMoment,formatDuring } from './DateUtil'
@ -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

@ -27,51 +27,42 @@ class Fileslistitem extends Component{
this.props.Settingtypes(discussMessage.id)
}
showfiles=(value)=>{
let {discussMessage,coursesId}=this.props
let file_id=discussMessage.id
let url="/files/"+file_id+"/histories.json"
axios.get(url,{
params:{
course_id:coursesId
},
}).then((result)=>{
if(result.data.attachment_histories.length===0){
if(result.data.is_pdf===true){
//预览pdf
axios({
method:'get',
url:result.data.url,
responseType: 'arraybuffer',
}).then((result)=>{
var binaryData = [];
binaryData.push(result.data);
this.url =window.URL.createObjectURL(new Blob(binaryData, {type:"application/pdf"}));
window.open(this.url);
})
}else{
let link = document.createElement('a');
document.body.appendChild(link);
link.href = "/api"+result.data.url;
link.download = result.data.title;
//兼容火狐浏览器
let evt = document.createEvent("MouseEvents");
evt.initEvent("click", false, false);
link.dispatchEvent(evt);
document.body.removeChild(link);
}
}else{
this.setState({
Showoldfiles:true,
allfiles:result.data
})
}
}).catch((error)=>{
console.log(error)
})
showfiles=(list)=>{
if(list.is_history_file===false){
// this.props.DownloadFileA(list.title,list.url)
window.location.href=list.url;
}else{
let {discussMessage,coursesId}=this.props
let file_id=discussMessage.id
let url="/files/"+file_id+"/histories.json"
axios.get(url,{
params:{
course_id:coursesId
},
}).then((result)=>{
if(result.data.attachment_histories.length===0){
// if(result.data.is_pdf===true){
// this.props.ShowOnlinePdf(result.data.url)
// //预览pdf
// }else{
//
// }
// this.props.DownloadFileA(result.data.title,result.data.url)
window.location.href=list.url;
}else{
this.setState({
Showoldfiles:true,
allfiles:result.data
})
}
}).catch((error)=>{
console.log(error)
})
}
}
closaoldfilesprops=()=>{
@ -146,8 +137,6 @@ class Fileslistitem extends Component{
discussMessage,
} = this.props;
return(
<div className="graduateTopicList boardsList">
@ -209,13 +198,13 @@ class Fileslistitem extends Component{
{
this.props.isAdmin ? <a
// href={"/courses/" + coursesId + "/graduation/graduation_tasks/" + categoryid + "/" + taskid + "/list"}
onClick={()=>this.showfiles(discussMessage.title)}
onClick={()=>this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a> : ""
}
{
this.props.isStudent? <a
onClick={()=>this.showfiles(discussMessage.title)}
onClick={()=>this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a> :""
}
@ -225,7 +214,7 @@ class Fileslistitem extends Component{
discussMessage.is_lock === true ?
<span className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</span>
:<a
onClick={()=>this.showfiles(discussMessage.title)}
onClick={()=>this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a>:""
}

@ -69,7 +69,7 @@ class CommonWorkPost extends Component{
status: 'done'
}
})
const _memebers = response.data.members.slice(0);
const _memebers = response.data.members ? response.data.members.slice(0) : [];
this._edit_init_memebers = _memebers
delete response.data.members;
this.setState({

@ -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()

@ -648,7 +648,6 @@ class Coursesleftnav extends Component{
}
onDragEnd=(result)=>{
debugger
// console.log(result)
// let {course_modules}=this.props;
// let newcourse_modules=course_modules;
@ -1001,8 +1000,8 @@ class Coursesleftnav extends Component{
>
<a className={ item.second_category===undefined?"fl ml20 pd0":item.second_category.length===0?"fl ml20 pd0":this.state.sandiantypes===key?"fl ml20 pd0 ebebeb":"fl ml20 pd0"}>
{
item.type==="announcement"?<i className={this.props.location.pathname===item.category_url?"color-blue iconfont icon-daima mr10 fl":"iconfont icon-daima mr10 fl"}></i>:
item.type==="online_learning"?<i className={this.props.location.pathname===item.category_url?"color-blue iconfont icon-daima mr10 fl":"iconfont icon-daima mr10 fl"}></i>:
item.type==="announcement"?<i className={this.props.location.pathname===item.category_url?"color-blue iconfont icon-xiaoxi1 mr10 fl":"iconfont icon-xiaoxi1 mr10 fl"}></i>:
item.type==="online_learning"?<i className={this.props.location.pathname===item.category_url?"color-blue iconfont icon-xuexizhongxin mr10 fl":"iconfont icon-xuexizhongxin mr10 fl"}></i>:
item.type==="shixun_homework"?<i className={this.props.location.pathname===item.category_url?"color-blue iconfont icon-daima mr10 fl":"iconfont icon-daima mr10 fl"}></i>:
item.type==="common_homework"?<i className={this.props.location.pathname===item.category_url?"color-blue iconfont icon-putongzuoye mr10 fl":"iconfont icon-putongzuoye mr10 fl"}></i>:
item.type==="group_homework"?<i className={this.props.location.pathname===item.category_url?"color-blue iconfont icon-fenzuzuoye mr10 fl":"iconfont icon-fenzuzuoye mr10 fl"}></i>:

@ -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>

@ -43,16 +43,7 @@ class Showoldfiles extends Component{
}
showfiless=(url)=>{
axios({
method:'get',
url:url,
responseType: 'arraybuffer',
}).then((result)=>{
var binaryData = [];
binaryData.push(result.data);
this.url =window.URL.createObjectURL(new Blob(binaryData, {type:"application/pdf"}));
window.open(this.url);
})
this.props.ShowOnlinePdf(url)
}
render(){
let {visible,allfiles}=this.props;
@ -187,10 +178,11 @@ class Showoldfiles extends Component{
<div className="clearfix edu-txt-center lineh-40 bor-bottom-greyE" id={allfiles.id}>
<li className="fl fontlefts">
{allfiles.is_pdf===false?
<a className={"isabox"} href={"/api"+allfiles.url} >{allfiles.title}</a>:
<a className={"isabox"} onClick={()=>this.showfiless(allfiles.url)} >{allfiles.title}</a>
}
<a className={"isabox"} href={allfiles.url} >{allfiles.title}</a>
{/*{allfiles.is_pdf===false?*/}
{/*<a className={"isabox"} href={allfiles.url} >{allfiles.title}</a>:*/}
{/*<a className={"isabox"} onClick={()=>this.showfiless(allfiles.url)} >{allfiles.title}</a>*/}
{/*}*/}
<span className={"newcolor-orange fl"}>当前版本</span>
</li>
@ -206,11 +198,11 @@ class Showoldfiles extends Component{
<div className="clearfix edu-txt-center lineh-40 bor-bottom-greyE" id={item.id} key={key}>
<li className="fl fontlefts">
{item.is_pdf===false?
<a className={"isabox"} href={"/api"+item.url}>{item.title}</a>:
<a className={"isabox"} onClick={()=>this.showfiless(item.url)} >{item.title}</a>
}
<a className={"isabox"} href={item.url}>{item.title}</a>
{/*{item.is_pdf===false?*/}
{/*<a className={"isabox"} href={item.url}>{item.title}</a>:*/}
{/*<a className={"isabox"} onClick={()=>this.showfiless(item.url)} >{item.title}</a>*/}
{/*}*/}
</li>
<li className="fl filesves ">

@ -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;
}

@ -8,7 +8,7 @@ import Titlesearchsection from '../common/titleSearch/TitleSearchSection'
import ColorCountText from '../common/titleSearch/ColorCountText'
import update from 'immutability-helper'
import { WordsBtn, ConditionToolTip, on, off ,trigger} from 'educoder'
import { WordsBtn, ConditionToolTip, on, off ,trigger, sortDirections } from 'educoder'
import axios from 'axios'
@ -63,6 +63,7 @@ function buildColumns(that) {
width: 120,
key: 'name',
sorter: showSorter,
sortDirections: sortDirections,
sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
render: (name, record) => {
return <a href={`/users/${record.login}`} target="_blank" className="overflowHidden1" style={{ maxWidth: '110px'}}
@ -75,6 +76,7 @@ function buildColumns(that) {
sorter: showSorter,
// 'ascend' | 'descend'
defaultSortOrder: 'ascend',
sortDirections: sortDirections,
sortOrder: sortedInfo.columnKey === 'role' && sortedInfo.order,
}]
@ -116,6 +118,7 @@ function buildColumns(that) {
title: '答辩组',
// width: 90,
sorter: showSorter,
sortDirections: sortDirections,
key: 'graduation_group',
dataIndex: 'graduation_group',
sortOrder: sortedInfo.columnKey === 'graduation_group' && sortedInfo.order,

@ -230,7 +230,7 @@ class ShixunsHome extends Component {
<div className="tag-green">
<span className="tag-name"> {item.tag_name}</span>
<img style={{display:item.tag_name===null?"none":'block'}} src={require(`./tag2.png`)}/>
{/*<img style={{display:item.tag_name===null?"none":'block'}} src={require(`./tag2.png`)}/>*/}
</div>
<div className={item.power === false ? "closeSquare" : "none"}>
@ -301,7 +301,7 @@ class ShixunsHome extends Component {
<div className="tag-green">
<span className="tag-name"> {item.tag_name}</span>
<img style={{display:item.tag_name===null?"none":'block'}} src={require(`./tag2.png`)}/>
{/*<img style={{display:item.tag_name===null?"none":'block'}} src={require(`./tag2.png`)}/>*/}
</div>
<div className={item.power === false ? "closeSquare" : "none"}>

@ -242,7 +242,7 @@ class Index extends Component {
updateChallengePath={context.updateChallengePath}
time_limit={context.time_limit}
time_limit={context.time_limit + 5}
resetTestSetsExpandedArray={context.resetTestSetsExpandedArray}
onRunCodeTestFinish={context.onRunCodeTestFinish}

@ -1,3 +1,33 @@
.userbluebgfont{
color:#fff !important;
.userbluebgfont{
color:#fff !important;
}
.kaike{
border-radius: 4px;
border: 1px solid rgba(255,255,255);
padding: 0px 10px;
cursor: pointer;
font-size: 14px;
display: block;
width: 120px;
text-align: center;
height: 40px;
line-height: 40px!important;
border-radius: 4px;
box-sizing: border-box;
}
.userNavs{
line-height: 96px;
background: #fff;
height:96px;
background:rgba(255,255,255,1);
box-shadow:3px 5px 11px 1px rgba(230,230,230,0.5);
border-radius:4px;
}
.userNavs li {
display: inline-block;
padding: 0 30px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}

@ -150,8 +150,11 @@ class DetailTop extends Component{
lineHeight: '30px',
};
console.log(this.props)
// <div className={this.props.courses===undefined||this.props.courses.length===0?"subhead":"subhead mb70"}>
return(
<div className="subhead">
<div className={this.props.courses===undefined?"subhead":"subhead mb70"}>
<Modals
modalsType={Modalstype}
modalsTopval={Modalstopval}
@ -161,14 +164,37 @@ class DetailTop extends Component{
loadtype={loadtype}
>
</Modals>
{/*<div className={this.props.courses===undefined||this.props.courses.length===0?"subhead_content":"subhead_content pt100"}>*/}
{
detailInfoList &&
<div className="subhead_content">
<div className={this.props.courses===undefined?"subhead_content":"subhead_content pt100"}>
<div className="font-28 color-white clearfix">
<span className="fl lineh-40">
{detailInfoList.name}
</span>
<style>
{
`
.tag-orange .tag-name{
display: block;
width: auto;
background-color: #FF6800;
background-size: 100% 100%;
padding: 0px 4px;
color: #fff;
float: left;
height: 28px;
line-height: 28px;
font-size: 14px;
border-radius: 4px;
}
`
}
</style>
{this.props.courses===undefined?"":
<span className="tag-orange">
<span className="tag-name mt6 ml10">开放课程</span>
</span>}
{detailInfoList===undefined?"":detailInfoList.allow_statistics===true?
<Tooltip placement="bottom" title={"编辑"}>
<Link to={"/paths/"+this.props.match.params.pathId+"/edit"} className="ml10 ring-green fl mt10" >
@ -178,15 +204,15 @@ class DetailTop extends Component{
:""
}
{detailInfoList===undefined?"":detailInfoList.allow_statistics===true?
{detailInfoList===undefined?"":detailInfoList.allow_statistics===true?this.props.courses===undefined?
<Link to={"/paths/"+this.props.match.params.pathId+"/statistics"} className="user_default_btn edu-greenback-btn fr font-18"
>
学习统计
</Link>:""
</Link>:"":""
}
{ detailInfoList.allow_send === true &&
<SendPanel {...this.props} {...this.state}></SendPanel>
{ detailInfoList.allow_send === true?this.props.courses===undefined?
<SendPanel {...this.props} {...this.state}></SendPanel>:"":""
}
</div>
@ -202,7 +228,7 @@ class DetailTop extends Component{
{ detailInfoList.member_count!=0 ? <li><span>学习人数</span><span>{detailInfoList.member_count}</span></li> : ""}
</ul>
}
<div className="fr pr">
{this.props.courses===undefined?<div className="fr pr">
{detailInfoList===undefined?"":detailInfoList.allow_delete===true?<a
className={detailInfoList.publish_status===-1?"fl font-18 color-white mt5 mr20":"fl font-18 color-white mt5"}
style={{opacity: '0.6'}} onClick={this.allow_deletepath}
@ -229,10 +255,39 @@ class DetailTop extends Component{
onClick={this.reovkissuePath}
>撤销发布</a>:""
}
</div>
</div>:""}
{this.props.courses===undefined?"":detailInfoList.is_creator===true?<div className="fr pr">
<a className={"fl font-18 color-white mt5 kaike mr20"}
>开课</a>
<Link to={"/paths/"+this.props.match.params.pathId+"/statistics"} className="fl font-18 color-white mt5 kaike"
>
学习统计
</Link>
</div>:""}
</div>
{/*{this.props.courses===undefined||this.props.courses.length===0?"":<div className="userNavs mt20">*/}
{this.props.courses===undefined?"":<div className="userNavs mt20">
<li className="active"><a href="/users/innov/courses">翻转课堂</a></li>
<li className=""><a href="/users/innov/shixuns">开发社区</a></li>
<li className=""><a href="/users/innov/paths">实践课程</a></li>
<li className=""><a href="/users/innov/projects">项目</a></li>
<li className=""><a href="/users/innov/package">众包</a></li>
<li className=""><a href="/users/innov/videoes">视频</a></li>
</div>}
</div>
}
</div>
)

@ -69,6 +69,9 @@ class PathDetailIndex extends Component{
constructor(props){
super(props)
this.state={
progress:undefined,
tags:undefined,
members:undefined,
detailInfoList:undefined,
clickdetailInfoListtype:false,
Modalstype:false,
@ -78,6 +81,32 @@ class PathDetailIndex extends Component{
cardsModalsave:this.cardsModalsave,
user_id:undefined,
loadtype:false,
courses:[
{
course_id: 1309,
first_category_url: "/courses/1309/informs",
start_date: "2019-07-16", // 开始时间
end_date: "2050-12-31", // 结束时间
student_count: 112, // 学习人数
course_identity: 5, // 当前用户在该课堂的身份
course_status: {
status: 1, // status0即将开课 1进行中 2已结束
time: "进行至第5周共1642周" // time当前进度
}
},
{
course_id: 1319,
first_category_url: "/courses/1319/shixun_homeworks/11549",
start_date:"2019-08-16",
end_date:"2050-12-31",
student_count: 112,
course_identity: 1,
course_status: {
status: 0,
time: ""
}
}
],
items: getItems(10)
}
this.onDragEnd = this.onDragEnd.bind(this);
@ -95,16 +124,17 @@ class PathDetailIndex extends Component{
}
const items = reorder(
this.state.detailInfoList.members,
this.state.members,
result.source.index,
result.destination.index
);
this.state.detailInfoList.members=items;
this.setState({
detailInfoList:this.state.detailInfoList,
members:items,
items
});
console.log(this.state.detailInfoList.members)
console.log(this.state.members)
console.log("items 数组数组数组数组")
console.log(items)
}
@ -157,13 +187,40 @@ class PathDetailIndex extends Component{
if(result.data.allow_visit===true){
this.setState({
detailInfoList:result.data,
items: getItems(result.data.members.length),
// courses:result.data.courses
// items: getItems(result.data.members.length),
})
}
}).catch((error)=>{
console.log(error);
})
let righturl="/paths/"+pathid+"/right_banner.json";
axios.get(righturl).then((result)=>{
if (result.data.status === 407 || result.data.status === 401) {
debugger
return;
}
if (result.data.status === 403) {
debugger
// window.location.href = "/403";
return;
}
this.setState({
// detailInfoList:result.data,
tags:result.data.tags,
progress:result.data.progress,
members:result.data.members,
items: getItems(result.data.members.length),
})
}).catch((error)=>{
console.log(error);
})
}
updatadetailInfoList=()=>{
@ -282,9 +339,10 @@ class PathDetailIndex extends Component{
console.log("上移");
// console.log(this.state.detailInfoList.members);
// console.log(response);
this.state.detailInfoList.members=response.data.members;
this.setState({
detailInfoList:this.state.detailInfoList,
members:response.data.members
});
// console.log(this.state.detailInfoList.members);
@ -307,9 +365,9 @@ class PathDetailIndex extends Component{
console.log("下移");
// console.log(this.state.detailInfoList.members);
// console.log(response);
this.state.detailInfoList.members=response.data.members;
this.setState({
detailInfoList:this.state.detailInfoList,
members:response.data.members
});
// console.log(this.state.detailInfoList.members);
}
@ -329,7 +387,11 @@ class PathDetailIndex extends Component{
Modalsbottomval,
cardsModalcancel,
cardsModalsave,
loadtype
loadtype,
progress,
members,
tags,
courses,
} = this.state
@ -403,14 +465,14 @@ class PathDetailIndex extends Component{
}
</div>
</div>
{detailInfoList === undefined ? "" : detailInfoList.tags === null ? "":
{tags === undefined ? "" :tags === null ? "":
<div className="edu-back-white padding40-20 mb10 relative">
<p className="font-16 mb20">技能标签 <span className="color-grey-c">{detailInfoList.tags.length}</span></p>
<p className="font-16 mb20">技能标签 <span className="color-grey-c">{tags.length}</span></p>
<div className={clickdetailInfoListtype===false?"newedbox newedboxheight":"newedbox newminheight"}>
<div className="clearfix" id="boxheight">
{
detailInfoList.tags && detailInfoList.tags.map((item,key)=>{
tags && tags.map((item,key)=>{
return(
<span value={key} className = {item.status == true ? "edu-filter-btn29BD8B fl" : "newedu-filter-btn fl"}>{item.tag_name}</span>
)
@ -420,7 +482,7 @@ class PathDetailIndex extends Component{
</div>
<Tooltip placement="bottom" title={"显示全部"}>
<div className={detailInfoList.tags.length>15&&clickdetailInfoListtype===false?"newsubscript mb9 color-grey-9 fr":"newsubscript mb9 color-grey-9 none"}
<div className={tags.length>15&&clickdetailInfoListtype===false?"newsubscript mb9 color-grey-9 fr":"newsubscript mb9 color-grey-9 none"}
onClick={()=>this.clickNewsubscript(0)}
><span className="mr8">...</span><Icon type="caret-down" />
</div>
@ -435,29 +497,29 @@ class PathDetailIndex extends Component{
</div>
}
{
this.props.checkIfLogin()===false?"":detailInfoList === undefined ? "" : detailInfoList.progress === null ? "" :
this.props.checkIfLogin()===false?"123213":progress === undefined ? "" : progress === null ? "" :
<div className="edu-back-white myProgress padding40-20 mb10">
<p className="mb20">
<span className="font-16 mr10">我的进展</span>
<Tooltip placement="bottom" title="获得经验值/总经验值">
<span className="color-green" >{detailInfoList.progress.my_score} / {detailInfoList.progress.all_score}</span>
<span className="color-green" >{progress.my_score} / {progress.all_score}</span>
</Tooltip>
</p>
<p className="clearfix mb10">
<span className="fl color-green">已学 {detailInfoList.progress.learned}%</span>
<span className="fr color-grey-9" id="time-consuming">学习耗时{this.timeStamp(detailInfoList.progress.time)} </span>
<span className="fl color-green">已学 {progress.learned}%</span>
<span className="fr color-grey-9" id="time-consuming">学习耗时{this.timeStamp(progress.time)} </span>
</p>
<div className="myProgressNav"><div className="myProgressGreen" style={{"width":`${detailInfoList.progress.learned+"%"}`}}></div></div>
<div className="myProgressNav"><div className="myProgressGreen" style={{"width":`${progress.learned+"%"}`}}></div></div>
</div>
}
{
detailInfoList ===undefined ?"":detailInfoList.members === null ?"":
members ===undefined ?"":members === null ?"":
<div className="teacherTeam edu-back-white clearfix" id="subject_members">
<p className="font-16 clearfix">教学团队</p>
{ detailInfoList===undefined?
detailInfoList.members && detailInfoList.members.map((item,key)=>{
{ members===undefined?
members && members.map((item,key)=>{
return(
<div className="teacherTeamItem clearfix df" key={key}>
<a href={item.user_url} target="_blank" className="fl">
@ -478,8 +540,8 @@ class PathDetailIndex extends Component{
</div>
)
})
:detailInfoList.allow_add_member===true?
detailInfoList.members && detailInfoList.members.map((item,key)=>{
:detailInfoList===undefined?"":detailInfoList.allow_add_member===true?
members && members.map((item,key)=>{
return(
<div className="teacherTeamItem clearfix df" key={key}>
<a href={item.user_url} target="_blank" className="fl">
@ -501,7 +563,7 @@ class PathDetailIndex extends Component{
{
detailInfoList===undefined?"":detailInfoList.allow_add_member===true? <div>
{key!=0?<div className="fr ml15 flex1"><a onClick={()=>this.moveup(item)}><Tooltip title="上移"><i className="color-green font-18 iconfont icon-xiangshangyi"></i></Tooltip></a></div>:""}
{key+1!=detailInfoList.members.length?<div className="fr ml15 flex1 "><a onClick={()=>this.movedown(item)}><Tooltip title="下移"><i className="color-green font-18 iconfont icon-xiangxiayi"></i></Tooltip></a></div>:""}
{key+1!= members&&members.length?<div className="fr ml15 flex1 "><a onClick={()=>this.movedown(item)}><Tooltip title="下移"><i className="color-green font-18 iconfont icon-xiangxiayi"></i></Tooltip></a></div>:""}
</div>
:""
}
@ -510,7 +572,7 @@ class PathDetailIndex extends Component{
</div>
)
})
: detailInfoList.members && detailInfoList.members.map((item,key)=>{
: members && members.map((item,key)=>{
return(
<div className="teacherTeamItem clearfix df" key={key}>
<a href={item.user_url} target="_blank" className="fl">

@ -13,6 +13,33 @@ class ShixunPathCard extends Component{
let {pathList}=this.props;
return(
<div className="educontent" id="subjects_list_content">
<style>
{
`
.tag-green{
position: absolute;
left: 15px;
bottom: 95px;}
.tag-green .tag-name{display: block;width: auto;
/*background-image: url("/images/educoder/tag1.png");*/
background: #000000;
border: 1px solid #fff;
border-radius: 3px;
font-size: 14px;
background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;}
.tag-orange{position: absolute;right: 0px;top:20px;}
.tag-orange .tag-name{display: block;width: auto;background-color:#FF6800;
background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;
height: 28px;
line-height: 28px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
`
}
</style>
{
pathList && pathList.length > 0 ?
(
@ -22,23 +49,31 @@ class ShixunPathCard extends Component{
return(
<div className="square-Item" id={"item_"+key}>
{
item.tag_name === null ? "" :
<div className="tag-green">
<span className="tag-name">{item.tag_name}</span>
<img src={getImageUrl('images/educoder/tag2.png')}/>
{/*<img src={getImageUrl('images/educoder/tag2.png')}/>*/}
</div>
}
<div className={item.allow_visit=== false ? "closeSquare" : "none"}>
{
item.excellent === false ? "" :
<div className="tag-orange">
<span className="tag-name">开放课程</span>
</div>
}
<div className={item.allow_visit=== false ? "closeSquare" : "none"}>
<img src={getImageUrl("images/educoder/icon/lockclose.svg")}
className="mt80 mb25"/>
<p className="font-14 color-white">非试用内容需要授权</p>
</div>
<Link to={"/paths/"+item.id} className="square-img" >
{/*target="_blank"*/}
<img alt="13?1543211263" src={"/"+item.image_url} style={{"display":"block"}}/>
<img alt="详情图片" src={"/"+item.image_url} style={{"display":"block"}}/>
</Link>
<div className="square-main">
<p className="task-hide">
@ -77,4 +112,5 @@ class ShixunPathCard extends Component{
)
}
}
export default ShixunPathCard;
export default ShixunPathCard;

@ -727,7 +727,7 @@ submittojoinclass=(value)=>{
src={require('./roundedRectangle.png')}
/>
</li>
<li className={`${activeMoopCases === true ? 'pr active' : 'pr'}`}><a href={this.props.Headertop===undefined?"":this.props.Headertop.moop_cases_url}>教学案例</a></li>
<li className={`${activeMoopCases === true ? 'pr active' : 'pr'}`}><a href={`/moop_cases`}>教学案例</a></li>
<li className={`${activePackages === true ? 'pr active' : 'pr'}`}>
<a href={'/crowdsourcing'}>众包创新</a>
</li>

@ -107,7 +107,7 @@ export default class TPMFork_listComponent extends Component {
<div className="tag-green">
<span className="tag-name"> {item.tag_name}</span>
<img src={require(`./shixunCss/tag2.png`)}/>
{/*<img src={require(`./shixunCss/tag2.png`)}/>*/}
</div>
<div className={item.power === false ? "closeSquare" : "none"}>

@ -314,6 +314,7 @@ export function TPMIndexHOC(WrappedComponent) {
})
}
showProfileCompleteDialog = () => {
this.dialogObj = {}
this.setState({
AccountProfiletype: true
})
@ -321,6 +322,48 @@ export function TPMIndexHOC(WrappedComponent) {
checkIfProfileCompleted = () => {
return this.state.current_user && this.state.current_user.profile_completed
}
showProfessionalCertificationDialog = () => {
this.dialogObj = {
content: '您需要去完成您的职业认证,才能使用此功能',
okText: '立即完成',
okHref: '/account/certification'
}
this.setState({
AccountProfiletype: true,
})
}
checkIfProfessionalCertification = () => {
return this.state.current_user && this.state.current_user.professional_certification
}
ShowOnlinePdf = (url) => {
return axios({
method:'get',
url:url,
responseType: 'arraybuffer',
}).then((result)=>{
var binaryData = [];
binaryData.push(result.data);
this.url =window.URL.createObjectURL(new Blob(binaryData, {type:"application/pdf"}));
window.open(this.url);
})
}
DownloadFileA=(title,url)=>{
let link = document.createElement('a');
document.body.appendChild(link);
link.href =url;
link.download = title;
//兼容火狐浏览器
let evt = document.createEvent("MouseEvents");
evt.initEvent("click", false, false);
link.dispatchEvent(evt);
document.body.removeChild(link);
}
DownloadOpenPdf=(type,url)=>{
type===true?window.open(url):window.location.href=url;
}
render() {
let{Headertop,Footerdown, isRender, AccountProfiletype}=this.state;
const common = {
@ -339,6 +382,12 @@ export function TPMIndexHOC(WrappedComponent) {
checkIfLogin: this.checkIfLogin,
showProfileCompleteDialog: this.showProfileCompleteDialog,
checkIfProfileCompleted: this.checkIfProfileCompleted,
checkIfProfessionalCertification: this.checkIfProfessionalCertification,
showProfessionalCertificationDialog: this.showProfessionalCertificationDialog,
ShowOnlinePdf:(url)=>this.ShowOnlinePdf(url),
DownloadFileA:(title,url)=>this.DownloadFileA(title,url),
DownloadOpenPdf:(type,url)=>this.DownloadOpenPdf(type,url)
}
return (
<div>
@ -347,10 +396,12 @@ export function TPMIndexHOC(WrappedComponent) {
{...this.props}
{...this.state}
/> : ""}
{/* AccountProfile 也用作职业认证 */}
{AccountProfiletype===true ? <AccountProfile
hideAccountProfile={()=>this.hideAccountProfile()}
{...this.props}
{...this.state}
{...this.dialogObj}
/>:""}
<SiderBar Headertop={Headertop}/>
{/* 注释掉了1440 影响到了手机屏幕的展示 */}

@ -88,7 +88,7 @@ class ShixunCard extends Component {
item.tag_name === null ? "":
<div className="tag-green">
<span className="tag-name"> {item.tag_name}</span>
<img style={{display:'block',height: '28px'}} src={require(`./shixunCss/tag2.png`)}/>
{/*<img style={{display:'block',height: '28px'}} src={require(`./shixunCss/tag2.png`)}/>*/}
</div>
}
<div className={item.power === false ? "closeSquare" : "none"}>

@ -46,8 +46,13 @@ class AccountProfile extends Component {
}
/**
content: '您需要去完成您的职业认证,才能使用此功能',
okText: '立即完成',
okHref: '/account/certification'
*/
render() {
const { content, okText, okHref } = this.props;
return(
<Modal
keyboard={false}
@ -60,10 +65,10 @@ render() {
width="530px"
>
<div className="task-popup-content">
<p className="task-popup-text-center font-16">您需要去完善您的个人资料才能使用此功能</p>
<p className="task-popup-text-center font-16"> {content || '您需要去完善您的个人资料,才能使用此功能'}</p>
<div className="clearfix mt30 edu-txt-center">
<a className="task-btn mr30" onClick={()=>this.gotoback()}>取消</a>
<a className="task-btn task-btn-orange" href={"/account/profile/edit"}>立即完善</a>
<a className="task-btn task-btn-orange" href={ okHref || "/account/profile/edit" }> {okText || '立即完善'}</a>
</div>
</div>
</Modal>

@ -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{
@ -46,7 +51,7 @@ class Infos extends Component{
super(props);
this.state={
data:undefined,
is_current:true,
is_current:undefined,
is_edit:false,
sign:undefined,
type:0,
@ -88,6 +93,7 @@ class Infos extends Component{
}
//获取个人主页信息
getInfo = (user_login) =>{
let url =`/users/${user_login}/homepage_info.json`;
@ -196,12 +202,12 @@ class Infos extends Component{
}
// 试用申请
trialapplications =()=>{
this.setState({
isRenders: true,
showTrial:true
})
}
// trialapplications =()=>{
// this.setState({
// isRenders: true,
// showTrial:true
// })
// }
cancelModulationModels=()=>{
this.setState({
isRenders: false
@ -214,21 +220,25 @@ class Infos extends Component{
render(){
let {
data ,
is_current,
is_edit,
sign,
type,
followed,
id,
login,
isRenders,
moduleName,
next_gold
}=this.state;
let {username}= this.props.match.params;
let {pathname}=this.props.location;
moduleName=pathname.split("/")[3];
let isCurrent = true;
let currentLogin;
if(this.props.current_user && this.props.current_user.login != this.props.match.params.username){
isCurrent=false;
currentLogin = this.props.current_user.login;
}
const _commonProps = {
is_current: isCurrent,
login: currentLogin
}
return(
<div className="newMain">
{this.state.updata===undefined?"":<UpgradeModals
@ -237,165 +247,14 @@ 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}
{..._commonProps}
signFor={this.signFor}
followPerson={this.followPerson}
></InfosBanner>
<Switch {...this.props}>
{/* --------------------------------------------------------------------- */}
@ -405,7 +264,7 @@ class Infos extends Component{
{/* http://localhost:3007/courses/1309/homework/9300/setting */}
<Route exact path="/users/:username/package"
render={
(props) => (<InfosPackage {...this.props} {...props} {...this.state} />)
(props) => (<InfosPackage {...this.props} {...props} {...this.state} {..._commonProps}/>)
}
></Route>
@ -413,35 +272,42 @@ class Infos extends Component{
{/* http://localhost:3007/courses/1309/homework/9300/setting */}
<Route exact path="/users/:username/courses"
render={
(props) => (<InfosCourse {...this.props} {...props} {...this.state} />)
(props) => (<InfosCourse {...this.props} {...props} {...this.state} {..._commonProps}/>)
}
></Route>
{/* 实训 */}
<Route exact path="/users/:username/shixuns"
render={
(props) => (<InfosShixun {...this.props} {...props} {...this.state} />)
(props) => (<InfosShixun {...this.props} {...props} {...this.state} {..._commonProps}/>)
}
></Route>
{/* 实训课程 */}
<Route exact path="/users/:username/paths"
render={
(props) => (<InfosPath {...this.props} {...props} {...this.state} />)
(props) => (<InfosPath {...this.props} {...props} {...this.state} {..._commonProps}/>)
}
></Route>
{/* 项目 */}
<Route exact path="/users/:username/projects"
render={
(props) => (<InfosProject {...this.props} {...props} {...this.state} />)
(props) => (<InfosProject {...this.props} {...props} {...this.state} {..._commonProps}/>)
}
></Route>
{/* 项目 */}
<Route exact path="/users/:username/videoes"
render={
(props) => (<InfosVideo {...this.props} {...props} {...this.state} {..._commonProps}/>)
}
></Route>
<Route exact path="/users/:username"
render={
(props) => (<InfosCourse {...this.props} {...props} {...this.state} />)
(props) => (<InfosCourse {...this.props} {...props} {...this.state} {..._commonProps}/>)
}
></Route>
@ -450,4 +316,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,115 @@
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 ,
id,
login,
moduleName,
current_user,
}=this.props;
let is_current=this.props.is_current;
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 mr20 fl"><img alt="头像" 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"><label>{data && data.identity}</label></span>
}
</p>
<p className="mt15">
<Tooltip placement='bottom' title={ data && data.professional_certification ?"已职业认证":"未职业认证"}>
<i className={ data && data.professional_certification ? "iconfont icon-shenfenzhenghaomaguizheng font-18 user-colorgrey-green mr20 ml2":"iconfont icon-shenfenzhenghaomaguizheng font-18 user-colorgrey-B8 mr20 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 ml60">
<a href={`/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>
{((is_current && current_user && current_user.is_teacher ) || current_user && current_user.admin)
&& <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>
@ -126,7 +126,7 @@ class InfosCourse extends Component{
</p>
<div className="square-list clearfix">
{
page == 1 && is_current &&
page == 1 && is_current && !category &&
this.props.current_user && this.props.current_user.user_identity != "学生" ? <Create href={"/courses/new"} name={"新建课堂"} index="1"></Create> : ""
}
{

@ -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>
@ -148,7 +148,7 @@ class InfosPath extends Component{
</div>
<div className="square-list clearfix">
{
page == 1 && is_current &&
page == 1 && is_current && !category &&
this.props.current_user && this.props.current_user.user_identity != "学生" ? <Create href={"/paths/new"} name={"新建实践课程"} index="3"></Create>:""
}
{
@ -159,7 +159,9 @@ class InfosPath extends Component{
return(
<div className="square-Item" onClick={()=>this.turnToCourses(`/paths/${item.id}`)}>
{
item.tag && <div className="tag-green"><span className="tag-name">{item.tag}</span><img src={setImagesUrl("images/educoder/tag2.png")} className="fl"/></div>
item.tag && <div className="tag-green"><span className="tag-name">{item.tag}</span>
{/*<img src={setImagesUrl("images/educoder/tag2.png")} className="fl"/>*/}
</div>
}
<a href="javascript:void(0)" className="square-img"><img alt="Subject12" src={getImageUrl(`${item.image_url}`)}/></a>
<div className="square-main">

@ -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>
@ -121,7 +121,7 @@ class InfosProject extends Component{
</p>
<div className="square-list clearfix">
{
page == 1 && is_current && this.props.current_user && this.props.current_user.user_identity != "学生" ?
page == 1 && is_current && this.props.current_user && !category && this.props.current_user.user_identity != "学生" ?
<Create href={`${this.props.Headertop && this.props.Headertop.old_url}/projects/new`} name={"新建项目"} index="4"></Create>:""
}
{

@ -1,203 +1,205 @@
import React, { Component } from 'react';
import { SnackbarHOC } from 'educoder';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import {Tooltip,Menu,Pagination,Spin} from 'antd';
import Loadable from 'react-loadable';
import Loading from '../../../Loading';
import NoneData from '../../courses/coursesPublic/NoneData'
import axios from 'axios';
import {getImageUrl,setImagesUrl} from 'educoder';
import { TPMIndexHOC } from '../../tpm/TPMIndexHOC';
import { CNotificationHOC } from '../../courses/common/CNotificationHOC'
import "./usersInfo.css"
import Create from './publicCreatNew'
class InfosShixun extends Component{
constructor(props){
super(props);
this.state={
category:undefined,
page:1,
sort_by:'time',
status:undefined,
per_page:16,
isSpin:false,
totalCount:undefined,
data:undefined
}
}
componentDidMount=()=>{
this.setState({
isSpin:true
})
let{category,status,sort_by,page}=this.state;
this.getCourses(category,status,sort_by,page);
}
getCourses=(category,status,sort_by,page)=>{
let url=`/users/${this.props.match.params.username}/shixuns.json`;
axios.get((url),{params:{
category,
status,
sort_by,
page,
per_page:this.props.is_current && category && page ==1?17:16
}}).then((result)=>{
if(result){
this.setState({
totalCount:result.data.count,
data:result.data,
isSpin:false
})
}
}).catch((error)=>{
console.log(error);
})
}
//切换种类
changeCategory=(cate)=>{
this.setState({
category:cate,
status:undefined,
page:1,
isSpin:true
})
let{sort_by}=this.state;
this.getCourses(cate,undefined,sort_by,1);
}
// 切换状态
changeStatus=(status)=>{
this.setState({
status,
page:1,
isSpin:true
})
let{category,sort_by}=this.state;
this.getCourses(category,status,sort_by,1);
}
//切换页数
changePage=(page)=>{
this.setState({
page,
isSpin:true
})
let{category,sort_by,status}=this.state;
this.getCourses(category,status,sort_by,page);
}
// 进入课堂
turnToCourses=(url)=>{
this.props.history.push(url);
}
// 切换排序方式
changeOrder= (sort)=>{
this.setState({
sort_by:sort,
isSpin:true
})
let{category,status,page}=this.state;
this.getCourses(category,status,sort,page);
}
render(){
let{
category,
status,
sort_by,
page,
data,
totalCount,
isSpin
} = this.state;
let isStudent = this.props.isStudent();
let is_current=this.props.is_current;
return(
<div className="educontent">
<Spin size="large" spinning={isSpin}>
<div className="white-panel edu-back-white pt25 pb25 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>
</div>
{
category && category == "manage" && is_current &&
<div className="edu-back-white padding20-30 clearfix secondNav bor-top-greyE">
<li className={status ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeStatus()}>全部</a></li>
<li className={status=="editing" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("editing")}>编辑中</a></li>
<li className={status=="applying" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("applying")}>待审核</a></li>
<li className={status=="published" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("published")}>已发布</a></li>
<li className={status=="closed" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("closed")}>已关闭</a></li>
</div>
}
{
category && category == "study" && is_current &&
<div className="edu-back-white padding20-30 clearfix secondNav bor-top-greyE">
<li className={status ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeStatus()}>全部</a></li>
<li className={status=="processing" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("processing")}>未通关</a></li>
<li className={status=="passed" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("passed")}>已通关</a></li>
</div>
}
<div className="pl25 pr25 clearfix font-12 mb20 mt20">
<span className="fl color-grey-9">共参与{totalCount}{category?category=="manage"?"发布":"学习":"实训"}</span>
<div className="fr">
<li className="drop_down">
<span className="color-grey-9 font-12">{sort_by=="time"?"时间最新":"语言类别"}</span><i className="iconfont icon-xiajiantou font-12 ml2 color-grey-6"></i>
<ul className="drop_down_normal">
<li onClick={()=>this.changeOrder("time")}>时间最新</li>
<li onClick={()=>this.changeOrder("language")}>语言类别</li>
</ul>
</li>
</div>
</div>
<div className="square-list clearfix">
{
page == 1 && is_current && this.props.current_user && this.props.current_user.user_identity != "学生" ?
<Create href={"/shixuns/new"} name={"新建实训"} index="2"></Create>:""
}
{
(!data || data.shixuns.length==0) && (!is_current || (this.props.current_user && this.props.current_user.user_identity === "学生" )) && <NoneData></NoneData>
}
{
data && data.shixuns && data.shixuns.map((item,key)=>{
return(
<div className="square-Item" onClick={()=>this.turnToCourses(`/shixuns/${item.identifier}/challenges`)}>
{
item.tag && <div className="tag-green"><span className="tag-name">{item.tag}</span><img className="fl" src={setImagesUrl("images/educoder/tag2.png")}/></div>
}
<a href="javascript:void(0)" className="square-img">
<img src={setImagesUrl(`${item.image_url}`)}/>
</a>
<div className="square-main">
<p className="task-hide">
<a href="javascript:void(0)" className="justify color-grey-name">{item.name}</a>
</p>
<div className="user-bar mt10">
<p style={{'width': `${parseFloat(parseInt(item.finished_challenges_count)/parseInt(item.challenges_count)).toFixed(2)*100}%`}}></p>
</div>
<p className="color-blue font-14 clearfix">
已完成 {item.finished_challenges_count} / {item.challenges_count}
</p>
</div>
</div>
)
})
}
</div>
{
totalCount > 15 &&
<div className="mt30 mb50 edu-txt-center">
<Pagination showQuickJumper total={totalCount} onChange={this.changePage} pageSize={16} current={page}/>
</div>
}
</Spin>
</div>
)
}
}
import React, { Component } from 'react';
import { SnackbarHOC } from 'educoder';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import {Tooltip,Menu,Pagination,Spin} from 'antd';
import Loadable from 'react-loadable';
import Loading from '../../../Loading';
import NoneData from '../../courses/coursesPublic/NoneData'
import axios from 'axios';
import {getImageUrl,setImagesUrl} from 'educoder';
import { TPMIndexHOC } from '../../tpm/TPMIndexHOC';
import { CNotificationHOC } from '../../courses/common/CNotificationHOC'
import "./usersInfo.css"
import Create from './publicCreatNew'
class InfosShixun extends Component{
constructor(props){
super(props);
this.state={
category:undefined,
page:1,
sort_by:'time',
status:undefined,
per_page:16,
isSpin:false,
totalCount:undefined,
data:undefined
}
}
componentDidMount=()=>{
this.setState({
isSpin:true
})
let{category,status,sort_by,page}=this.state;
this.getCourses(category,status,sort_by,page);
}
getCourses=(category,status,sort_by,page)=>{
let url=`/users/${this.props.match.params.username}/shixuns.json`;
axios.get((url),{params:{
category,
status,
sort_by,
page,
per_page:this.props.is_current && category && page ==1?17:16
}}).then((result)=>{
if(result){
this.setState({
totalCount:result.data.count,
data:result.data,
isSpin:false
})
}
}).catch((error)=>{
console.log(error);
})
}
//切换种类
changeCategory=(cate)=>{
this.setState({
category:cate,
status:undefined,
page:1,
isSpin:true
})
let{sort_by}=this.state;
this.getCourses(cate,undefined,sort_by,1);
}
// 切换状态
changeStatus=(status)=>{
this.setState({
status,
page:1,
isSpin:true
})
let{category,sort_by}=this.state;
this.getCourses(category,status,sort_by,1);
}
//切换页数
changePage=(page)=>{
this.setState({
page,
isSpin:true
})
let{category,sort_by,status}=this.state;
this.getCourses(category,status,sort_by,page);
}
// 进入课堂
turnToCourses=(url)=>{
this.props.history.push(url);
}
// 切换排序方式
changeOrder= (sort)=>{
this.setState({
sort_by:sort,
isSpin:true
})
let{category,status,page}=this.state;
this.getCourses(category,status,sort,page);
}
render(){
let{
category,
status,
sort_by,
page,
data,
totalCount,
isSpin
} = this.state;
let isStudent = this.props.isStudent();
let is_current=this.props.is_current;
return(
<div className="educontent">
<Spin size="large" spinning={isSpin}>
<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>
</div>
{
category && category == "manage" && is_current &&
<div className="edu-back-white padding20-30 clearfix secondNav bor-top-greyE">
<li className={status ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeStatus()}>全部</a></li>
<li className={status=="editing" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("editing")}>编辑中</a></li>
<li className={status=="applying" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("applying")}>待审核</a></li>
<li className={status=="published" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("published")}>已发布</a></li>
<li className={status=="closed" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("closed")}>已关闭</a></li>
</div>
}
{
category && category == "study" && is_current &&
<div className="edu-back-white padding20-30 clearfix secondNav bor-top-greyE">
<li className={status ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeStatus()}>全部</a></li>
<li className={status=="processing" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("processing")}>未通关</a></li>
<li className={status=="passed" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeStatus("passed")}>已通关</a></li>
</div>
}
<div className="pl25 pr25 clearfix font-12 mb20 mt20">
<span className="fl color-grey-9">共参与{totalCount}{category?category=="manage"?"发布":"学习":"实训"}</span>
<div className="fr">
<li className="drop_down">
<span className="color-grey-9 font-12">{sort_by=="time"?"时间最新":"语言类别"}</span><i className="iconfont icon-xiajiantou font-12 ml2 color-grey-6"></i>
<ul className="drop_down_normal">
<li onClick={()=>this.changeOrder("time")}>时间最新</li>
<li onClick={()=>this.changeOrder("language")}>语言类别</li>
</ul>
</li>
</div>
</div>
<div className="square-list clearfix">
{
page == 1 && is_current && !category && this.props.current_user && this.props.current_user.user_identity != "学生" ?
<Create href={"/shixuns/new"} name={"新建实训"} index="2"></Create>:""
}
{
(!data || data.shixuns.length==0) && (!is_current || (this.props.current_user && this.props.current_user.user_identity === "学生" )) && <NoneData></NoneData>
}
{
data && data.shixuns && data.shixuns.map((item,key)=>{
return(
<div className="square-Item" onClick={()=>this.turnToCourses(`/shixuns/${item.identifier}/challenges`)}>
{
item.tag && <div className="tag-green"><span className="tag-name">{item.tag}</span>
{/*<img className="fl" src={setImagesUrl("images/educoder/tag2.png")}/>*/}
</div>
}
<a href="javascript:void(0)" className="square-img">
<img src={setImagesUrl(`${item.image_url}`)}/>
</a>
<div className="square-main">
<p className="task-hide">
<a href="javascript:void(0)" className="justify color-grey-name">{item.name}</a>
</p>
<div className="user-bar mt10">
<p style={{'width': `${parseFloat(parseInt(item.finished_challenges_count)/parseInt(item.challenges_count)).toFixed(2)*100}%`}}></p>
</div>
<p className="color-blue font-14 clearfix">
已完成 {item.finished_challenges_count} / {item.challenges_count}
</p>
</div>
</div>
)
})
}
</div>
{
totalCount > 15 &&
<div className="mt30 mb50 edu-txt-center">
<Pagination showQuickJumper total={totalCount} onChange={this.changePage} pageSize={16} current={page}/>
</div>
}
</Spin>
</div>
)
}
}
export default InfosShixun;

@ -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

@ -119,4 +119,111 @@
.project-package-item.with-operator:hover .item-operator a {
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%;
width:100%;
height:100%
}
.myPhoto{
border-radius: 50%;
border:2px solid #fff;
width:110px;
height:110px;
}
.userpost{
padding:0px 10px;
border:1px solid #fff;
border-radius: 2px;
color:#fff!important;
display: inline;
margin-top: 6px;
float:left;
}
.userpost label{
display:block;
height: 18px;
line-height:18px;
}
.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 9px 16px -1px #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,178 @@
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() || 1829848226361863, // 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) {
options.onUploadFailed && options.onUploadFailed(uploadInfo)
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 || options.create == true) {
createUploader(options)
}
}

@ -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 {
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,348 @@
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() {
if (props.current_user.admin || (props.current_user.is_teacher && props.checkIfProfessionalCertification())) {
history.push(`/users/${username}/videoes/upload`)
} else {
props.showProfessionalCertificationDialog()
}
}
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,445 @@
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'
import uploadHoverIcon from './images/upload_hover.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 () => {
uploader = null;
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,
{
// 重新创建 才会用最新的 dispatch
create: !uploader,
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)
clearInput()
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})
},
onUploadFailed: (uploadInfo) => {
console.log('onUploadFailed', uploadInfo)
props.showNotification('视频云服务出现异常,请重新上传。')
},
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 = ''
}
function doDelete(index, isSuccess) {
uploader.deleteFile(index)
// uploader.cancelFile(index)
if (isSuccess) {
deleteVideoInCloud(username, state.videoes[index].videoId)
}
clearInput()
dispatch({type: 'removeVideo', index})
// setVideoes([...videoes.splice(index, 1)])
}
// uploader.deleteFile(index);
function cancelUpload(index, isSuccess) {
props.confirm({
content: <div>
<div>您确认要{isSuccess ? '删除' : '取消上传'}该视频吗</div>
</div>,
onOk: () => {
doDelete(index, isSuccess)
}
})
}
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;
}
.noUploads img {
width: 64px;
height: 48px;
}
.noUploads .uploadHoverIcon {
display: none;
}
.noUploads .imgWrap:hover .uploadHoverIcon {
display: inline;
}
.noUploads .imgWrap:hover .uploadIcon {
display: none;
}
.noUploads .imgWrap {
width: 72px;
height: 54px;
margin: 0 auto;
cursor: pointer;
}
`}</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'}} >
<div className="imgWrap" onClick={() => document.getElementById('fileUpload').click()}>
<img className="uploadIcon" src={uploadIcon} ></img>
<img className="uploadHoverIcon" src={uploadHoverIcon} ></img>
</div>
<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

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

@ -114,8 +114,25 @@ em.vertical-line{display: inline-block;width: 2px;background: #999;height: 10px}
.smallSquare:nth-child(3n+0){margin-right: 0px;}
.partimg{height: 180px;width: 100%;border-radius: 6px 6px 0px 0px;}
/*块状列表上面的绿色标签*/
.tag-green{position: absolute;left: 0px;top:20px;}
.tag-green .tag-name{display: block;width: auto;background-image: url("/images/educoder/tag1.png");background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;}
.tag-green{
position: absolute;
left: 15px;
bottom: 95px;}
.tag-green .tag-name{display: block;width: auto;
/*background-image: url("/images/educoder/tag1.png");*/
background: #000000;
border: 1px solid #fff;
border-radius: 3px;
font-size: 14px;
background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;}
.tag-orange{position: absolute;right: 0px;top:20px;}
.tag-orange .tag-name{display: block;width: auto;background-color:#FF6800;
background-size: 100% 100%;padding: 0px 8px;color: #fff;float: left;
height: 28px;
line-height: 28px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
/*发送至弹框里的下拉框*/
.downSelectOption{position: relative;height: 35px;}
.downSelectOption .showOption{background-color: #F4F4F4;border: 1px solid #EAEAEA;width: 100%;padding: 5px 40px 5px 5px;outline: none;height: 100%;box-sizing: border-box;}

Loading…
Cancel
Save