Merge remote-tracking branch 'origin/develop' into develop

dev_video
杨树明 5 years ago
commit 0640e05822

@ -17,9 +17,11 @@ class AttachmentsController < ApplicationController
redirect_to @file.cloud_url and return redirect_to @file.cloud_url and return
end end
pdf_attachment = params[:disposition] || "attachment" type_attachment = params[:disposition] || "attachment"
if pdf_attachment == "inline" if type_attachment == "inline"
send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf' send_file absolute_path(local_path(@file)),filename: @file.title, disposition: 'inline',type: 'application/pdf'
elsif type_attachment == "MP4"
send_file_with_range absolute_path(local_path(@file)), disposition: 'inline', type: "video/mp4", range: true
else else
send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream') send_file(absolute_path(local_path(@file)), filename: @file.title,stream:false, type: @file.content_type.presence || 'application/octet-stream')
end end
@ -202,4 +204,31 @@ class AttachmentsController < ApplicationController
end end
end end
def send_file_with_range(path, options = {})
logger.info("########request.headers: #{request.headers}")
logger.info("########request.headers: #{File.exist?(path)}")
if File.exist?(path)
size = File.size(path)
logger.info("########request.headers: #{request.headers}")
if !request.headers["Range"]
status_code = 200 # 200 OK
offset = 0
length = File.size(path)
else
status_code = 206 # 206 Partial Content
bytes = Rack::Utils.byte_ranges(request.headers, size)[0]
offset = bytes.begin
length = bytes.end - bytes.begin
end
response.header["Accept-Ranges"] = "bytes"
response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}" if bytes
response.header["status"] = status_code
send_data IO.binread(path, length, offset), options
else
raise ActionController::MissingFile, "Cannot read file #{path}."
end
end
end end

@ -1,6 +1,7 @@
class CoursesController < ApplicationController class CoursesController < ApplicationController
include MessagesHelper include MessagesHelper
include ExportHelper include ExportHelper
include CustomSortable
# model validation error # model validation error
rescue_from ActiveRecord::RecordInvalid do |ex| rescue_from ActiveRecord::RecordInvalid do |ex|
@ -23,7 +24,7 @@ class CoursesController < ApplicationController
:course_group_list, :set_course_group, :change_course_admin, :change_course_teacher, :course_group_list, :set_course_group, :change_course_admin, :change_course_teacher,
:delete_course_teacher, :teacher_application_review, :students, :all_course_groups, :delete_course_teacher, :teacher_application_review, :students, :all_course_groups,
:transfer_to_course_group, :delete_from_course, :search_users, :add_students_by_search, :transfer_to_course_group, :delete_from_course, :search_users, :add_students_by_search,
:base_info, :get_historical_courses, :create_group_by_importing_file, :base_info, :get_historical_courses, :create_group_by_importing_file, :course_videos,
:attahcment_category_list,:export_member_scores_excel, :duplicate_course, :attahcment_category_list,:export_member_scores_excel, :duplicate_course,
:switch_to_teacher, :switch_to_assistant, :switch_to_student, :exit_course, :switch_to_teacher, :switch_to_assistant, :switch_to_student, :exit_course,
:informs, :update_informs, :online_learning, :update_task_position, :tasks_list, :informs, :update_informs, :online_learning, :update_task_position, :tasks_list,
@ -100,6 +101,14 @@ class CoursesController < ApplicationController
@courses = @courses.preload(:school, :none_hidden_course_modules, teacher: :user_extension) @courses = @courses.preload(:school, :none_hidden_course_modules, teacher: :user_extension)
end end
def course_videos
logger.info("########[#{@course}")
videos = @course.videos
videos = custom_sort(videos, params[:sort_by], params[:sort_direction])
@count = videos.count
@videos = paginate videos
end
def visits_plus_one def visits_plus_one
new_visits = @course.visits + 1 new_visits = @course.visits + 1
@course.update_visits(new_visits) @course.update_visits(new_visits)

@ -242,7 +242,7 @@ class SubjectsController < ApplicationController
## 云上实验室过滤 ## 云上实验室过滤
@courses = @courses.where(id: current_laboratory.all_courses) @courses = @courses.where(id: current_laboratory.all_courses)
@none_shixun_ids = ShixunSchool.where("school_id != #{current_user.user_extension.try(:school_id).to_i}").pluck(:shixun_id) @none_shixun_ids = @subject.shixuns.joins(:shixun_schools).where("school_id != #{current_user.user_extension.try(:school_id).to_i}").where(use_scope: 1).pluck(:id)
end end
def send_to_course def send_to_course

@ -0,0 +1,15 @@
class Weapps::ShixunListsController < ApplicationController
before_action :require_login
def index
results = Weapps::ShixunSearchService.call(search_params, current_laboratory)
@total_count = results.size
@results = paginate results
end
private
def search_params
params.permit(:keyword, :type, :page, :limit, :order, :status, :diff, :sort, :no_jupyter)
end
end

@ -65,6 +65,8 @@ module CoursesHelper
"/courses/#{course.id}/course_groups" "/courses/#{course.id}/course_groups"
when "statistics" when "statistics"
"/courses/#{course.id}/statistics" "/courses/#{course.id}/statistics"
when "video"
"/courses/#{course.id}/course_videos"
end end
end end

@ -81,6 +81,10 @@ class Course < ApplicationRecord
# 老版的members弃用 现用course_members # 老版的members弃用 现用course_members
has_many :members has_many :members
# 视频
has_many :course_videos, dependent: :destroy
has_many :videos, through: :course_videos
validate :validate_sensitive_string validate :validate_sensitive_string
scope :hidden, ->(is_hidden = true) { where(is_hidden: is_hidden) } scope :hidden, ->(is_hidden = true) { where(is_hidden: is_hidden) }
@ -206,7 +210,7 @@ class Course < ApplicationRecord
end end
def all_course_module_types def all_course_module_types
%w[activity announcement online_learning shixun_homework common_homework group_homework exercise attachment course_group graduation poll board statistics] %w[activity announcement online_learning shixun_homework common_homework group_homework exercise attachment course_group graduation poll board statistics video]
end end
def get_course_module_by_type(type) def get_course_module_by_type(type)
@ -406,6 +410,7 @@ class Course < ApplicationRecord
when 'exercise' then '试卷' when 'exercise' then '试卷'
when 'poll' then '问卷' when 'poll' then '问卷'
when 'attachment' then '资源' when 'attachment' then '资源'
when 'video' then '视频'
when 'board' then '讨论' when 'board' then '讨论'
when 'course_group' then '分班' when 'course_group' then '分班'
when 'statistics' then '统计' when 'statistics' then '统计'
@ -425,9 +430,10 @@ class Course < ApplicationRecord
when 'exercise' then 8 when 'exercise' then 8
when 'poll' then 9 when 'poll' then 9
when 'attachment' then 10 when 'attachment' then 10
when 'board' then 11 when 'video' then 11
when 'course_group' then 12 when 'board' then 12
when 'statistics' then 13 when 'course_group' then 13
when 'statistics' then 14
else 100 else 100
end end
end end

@ -0,0 +1,4 @@
class CourseVideo < ApplicationRecord
belongs_to :course
belongs_to :video
end

@ -4,6 +4,7 @@ class Video < ApplicationRecord
belongs_to :user belongs_to :user
has_many :video_applies, dependent: :destroy has_many :video_applies, dependent: :destroy
has_many :course_videos, dependent: :destroy
has_one :processing_video_apply, -> { where(status: :pending) }, class_name: 'VideoApply' has_one :processing_video_apply, -> { where(status: :pending) }, class_name: 'VideoApply'
aasm(:status) do aasm(:status) do

@ -27,6 +27,11 @@ class Videos::BatchPublishService < ApplicationService
video.video_applies.create! video.video_applies.create!
video_ids << video.id video_ids << video.id
# 如果是课堂上传则创建课堂记录
if params[:course_id].present?
video.course_videos.create!(course_id: params[:course_id])
end
end end
end end

@ -20,6 +20,9 @@ class Videos::DispatchCallbackService < ApplicationService
return if video.cover_url.present? return if video.cover_url.present?
video.update!(cover_url: params['CoverUrl']) video.update!(cover_url: params['CoverUrl'])
when 'TranscodeComplete' then # 转码完成
return if video.play_url.present?
video.update!(play_url: params['FileUrl'])
end end
rescue => ex rescue => ex

@ -0,0 +1,53 @@
class Weapps::ShixunSearchService < ApplicationService
attr_reader :params, :laboratory
def initialize(params, laboratory)
@params = params
@laboratory = laboratory
end
def call
# 全部实训/我的实训
type = params[:type] || "all"
shixuns = laboratory.shixuns.published.no_jupyter
if type == "mine"
shixuns = shixuns.where(id: User.current.shixuns)
else
# 超级管理员用户显示所有未隐藏的实训、非管理员显示所有已发布的实训(对本单位公开且未隐藏未关闭)
if User.current.admin? || User.current.business? || !User.current.school_id
shixuns = shixuns.where(hidden: 0)
else
shixun_ids = ShixunSchool.where(school_id: User.current.school_id).pluck(:shixun_id)
shixun_ids = shixun_ids.reject(&:blank?).length == 0 ? -1 : shixun_ids.join(",")
shixuns = shixuns.where("use_scope = 0 or shixuns.id in (#{shixun_ids})").unhidden.publiced.or(shixuns.where(id: User.current.shixuns))
end
end
## 筛选 难度
if params[:diff].present? && params[:diff].to_i != 0
shixuns = shixuns.where(trainee: params[:diff])
end
unless params[:keyword].blank?
keyword = params[:keyword].strip
shixuns = shixuns.joins(:user).
where("concat(lastname, firstname) like :keyword or shixuns.name like :keyword",
keyword: "%#{keyword}%", name: "%#{keyword.split(" ").join("%")}%").distinct
end
shixuns.order("#{sort_str} #{order_str}")
end
private
def order_str
params[:order] || "desc"
end
def sort_str
params[:sort] || "myshixuns_count"
end
end

@ -0,0 +1,2 @@
json.count @count
json.videos @videos, partial: 'users/videos/video', as: :video

@ -4,12 +4,19 @@ json.courses @courses do |course|
json.created_at course.created_at json.created_at course.created_at
end end
json.stages @subject.stages do |stage| json.stages @subject.stages.includes(shixuns: [user: :user_extension]) do |stage|
index = 1 index = 1
json.shixuns stage.shixuns do |shixun| json.shixuns stage.shixuns do |shixun|
if shixun.status == 2 && !@none_shixun_ids.include?(shixun.id) if shixun.status == 2 && !shixun.is_jupyter && !@none_shixun_ids.include?(shixun.id)
json.shixun_id shixun.id json.shixun_id shixun.id
json.id shixun.id
json.identifier shixun.identifier
json.shixun_name "#{stage.position}-#{index} #{shixun.name}" json.shixun_name "#{stage.position}-#{index} #{shixun.name}"
json.title shixun.name
json.level level_to_s(shixun.trainee)
json.study_count shixun.myshixuns_count
json.author_name shixun.user.real_name
json.author_img url_to_avatar(shixun.user)
end end
index += 1 index += 1
end end

@ -1,4 +1,4 @@
json.extract! video, :id, :title, :cover_url, :file_url, :vv json.extract! video, :id, :title, :cover_url, :file_url, :play_url, :vv
json.play_duration video.video_play_duration json.play_duration video.video_play_duration
json.published_at video.display_published_at json.published_at video.display_published_at

@ -0,0 +1,9 @@
json.shixun_list @results do |obj|
json.(obj, :id, :identifier)
json.title obj.name
json.level level_to_s(obj.trainee)
json.study_count obj.myshixuns_count
json.author_name obj.user.real_name
json.author_img url_to_avatar(obj.user)
end
json.shixuns_count @total_count

@ -515,6 +515,7 @@ Rails.application.routes.draw do
get 'work_score' get 'work_score'
get 'act_score' get 'act_score'
get 'statistics' get 'statistics'
get 'course_videos'
post :inform_up post :inform_up
post :inform_down post :inform_down
get :calculate_all_shixun_scores get :calculate_all_shixun_scores
@ -1021,6 +1022,7 @@ Rails.application.routes.draw do
post :cancel_sticky, on: :collection post :cancel_sticky, on: :collection
end end
resources :shixun_lists, only: [:index]
resources :subjects, path: :paths, only: [:index, :create, :update, :edit, :show] resources :subjects, path: :paths, only: [:index, :create, :update, :edit, :show]
resources :courses, only: [:create, :update, :edit, :show] do resources :courses, only: [:create, :update, :edit, :show] do

@ -0,0 +1,12 @@
class AddVideoToCourseModule < ActiveRecord::Migration[5.2]
def change
Course.all.each do |course|
unless course.course_modules.exists?(module_type: "video")
atta_position = course.course_modules.find_by(module_type: 'attachment')&.position.to_i
video_position = atta_position != 0 ? (atta_position + 1) : 11
course.course_modules.where("position >= #{video_position}").update_all("position = position + 1")
course.course_modules << CourseModule.new(module_type: "video", hidden: 1, module_name: "视频", position: video_position)
end
end
end
end

@ -0,0 +1,9 @@
class CreateCourseVideos < ActiveRecord::Migration[5.2]
def change
create_table :course_videos do |t|
t.references :course
t.references :video
t.timestamps
end
end
end

@ -45,9 +45,6 @@ namespace :video do
task :get_play_url => :environment do task :get_play_url => :environment do
Video.all.each do |video| Video.all.each do |video|
if video.uuid.present?
end
result = AliyunVod::Service.get_play_info(video.uuid) rescue nil result = AliyunVod::Service.get_play_info(video.uuid) rescue nil
if result.present? && result["PlayInfoList"]["PlayInfo"].present? if result.present? && result["PlayInfoList"]["PlayInfo"].present?
puts result puts result

@ -1,28 +1,21 @@
import React,{ Component } from "react"; import React,{ Component } from "react";
import { WordsBtn,ActionBtn,getmyUrl } from 'educoder'; import { WordsBtn } from 'educoder';
import {Tooltip,message,Input, Button} from 'antd'; import {Tooltip,message} from 'antd';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import {getImageUrl} from 'educoder';
import axios from 'axios' import axios from 'axios'
import {getUrl} from 'educoder';
import moment from 'moment' import moment from 'moment'
import CoursesListType from '../coursesPublic/CoursesListType'; import CoursesListType from '../coursesPublic/CoursesListType';
import Showoldfiles from "../coursesPublic/Showoldfiles"; import Showoldfiles from "../coursesPublic/Showoldfiles";
import Modals from '../../modals/Modals'; import Modals from '../../modals/Modals';
import HeadlessModal from '../../user/usersInfo/common/HeadlessModal'
import ClipboardJS from 'clipboard'
import '../../user/usersInfo/video/InfosVideo.css'
let _clipboard = null;
class Fileslistitem extends Component{ class Fileslistitem extends Component{
constructor(props){ constructor(props){
super(props); super(props);
this.state = { this.state = {
videoModalObj:false,
file_url:null,
}
} }
setVisible=(bool)=>{
this.setState({
videoModalObj:bool
})
} }
settingList=()=>{ settingList=()=>{
@ -35,9 +28,6 @@ class Fileslistitem extends Component{
} }
showfiles=(list)=>{ showfiles=(list)=>{
// console.log("showfiles");
// console.log(list);
if(this.props.checkIfLogin()===false){ if(this.props.checkIfLogin()===false){
this.props.showLoginDialog() this.props.showLoginDialog()
return return
@ -56,20 +46,7 @@ class Fileslistitem extends Component{
if(list.is_history_file===false){ if(list.is_history_file===false){
// this.props.DownloadFileA(list.title,list.url) // this.props.DownloadFileA(list.title,list.url)
//window.location.href=list.url; //window.location.href=list.url;
if(list.content_type){
if(list.content_type==="video/mp4"){
this.setState({
videoModalObj:true,
file_url:getmyUrl(list.url+'?file_name='+list.title),
})
return
}else{
window.open(list.url, '_blank');
}
}else{
window.open(list.url, '_blank'); window.open(list.url, '_blank');
}
}else{ }else{
let {discussMessage,coursesId}=this.props let {discussMessage,coursesId}=this.props
let file_id=discussMessage.id let file_id=discussMessage.id
@ -88,22 +65,7 @@ class Fileslistitem extends Component{
// //
// } // }
// this.props.DownloadFileA(result.data.title,result.data.url) // this.props.DownloadFileA(result.data.title,result.data.url)
if(list.content_type){
if(list.content_type==="video/mp4"){
this.setState({
videoModalObj:true,
file_url:getmyUrl(list.url+'?file_name='+list.title),
})
return
}else{
window.open(list.url, '_blank');
}
}else{
window.open(list.url, '_blank'); window.open(list.url, '_blank');
}
}else{ }else{
this.setState({ this.setState({
Showoldfiles:true, Showoldfiles:true,
@ -125,16 +87,6 @@ class Fileslistitem extends Component{
}) })
} }
Clicktobroadcastthevideo=(bool,url)=>{
this.setState({
videoModalObj:bool,
file_url:getmyUrl(url),
})
}
onDelete = (id) => { onDelete = (id) => {
this.setState({ this.setState({
@ -159,17 +111,6 @@ class Fileslistitem extends Component{
} }
copyurls =()=>{
//复制网络链接
setTimeout(() => {
if (!_clipboard) {
_clipboard = new ClipboardJS('.copybtn');
_clipboard.on('success', (e) => {
this.props.showNotification("复制成功");
});
}
}, 200)
}
savedelete=(id)=>{ savedelete=(id)=>{
this.setState({ this.setState({
@ -211,37 +152,13 @@ class Fileslistitem extends Component{
} }
render(){ render(){
const {videoModalObj,file_url}=this.state
const { checkBox, const { checkBox,
discussMessage,index discussMessage,index
} = this.props; } = this.props;
console.log("Fileslistitem");
console.log(file_url);
return( return(
<div className="graduateTopicList boardsList"> <div className="graduateTopicList boardsList">
{
videoModalObj&&videoModalObj===true?
<HeadlessModal
visible={videoModalObj}
setVisible={(bool)=>this.setVisible(bool)}
className="showVideoModal"
width={800 - 1}
>
<video
autoplay="true"
src={file_url} controls="true" controlslist="nodownload">
您的浏览器不支持 video 标签
</video>
{/*<div className="df copyLine">*/}
{/* <Input value={file_url}*/}
{/* className="dark"*/}
{/* ></Input>*/}
{/* <ActionBtn className="copybtn" data-clipboard-text={file_url} onClick={() =>this.copyurls()}>复制视频地址</ActionBtn>*/}
{/*</div>*/}
</HeadlessModal>
:""
}
{/*提示*/} {/*提示*/}
{this.state.Modalstype&&this.state.Modalstype===true?<Modals {this.state.Modalstype&&this.state.Modalstype===true?<Modals
@ -256,7 +173,6 @@ class Fileslistitem extends Component{
{...this.props} {...this.props}
visible={this.state.Showoldfiles} visible={this.state.Showoldfiles}
allfiles={this.state.allfiles} allfiles={this.state.allfiles}
Clicktobroadcastthevideo={(bool,urls)=>this.Clicktobroadcastthevideo(bool,urls)}
closaoldfilesprops={this.closaoldfilesprops} closaoldfilesprops={this.closaoldfilesprops}
/> />
<style>{` <style>{`
@ -450,4 +366,3 @@ class Fileslistitem extends Component{
} }
} }
export default Fileslistitem; export default Fileslistitem;

@ -1,6 +1,6 @@
import React,{ Component } from "react"; import React,{ Component } from "react";
import { Input, Checkbox, Table, Tooltip, Pagination,Spin } from "antd"; import { Input, Checkbox, Table, Tooltip, Pagination,Spin } from "antd";
import { WordsBtn,on, off, trigger } from 'educoder'; import { WordsBtn,on, off, trigger ,getUrl} from 'educoder';
import axios from 'axios'; import axios from 'axios';
import Modals from '../../modals/Modals'; import Modals from '../../modals/Modals';
import Sendtofilesmodal from "../coursesPublic/SendToFilesModal"; import Sendtofilesmodal from "../coursesPublic/SendToFilesModal";
@ -15,7 +15,6 @@ import _ from 'lodash'
import './style.css'; import './style.css';
import '../css/members.css'; import '../css/members.css';
import moment from 'moment'; import moment from 'moment';
class Fileslists extends Component{ class Fileslists extends Component{
constructor(props){ constructor(props){
super(props); super(props);

@ -49,6 +49,9 @@ class Showoldfiles extends Component{
isaboxonClick=(item)=>{ isaboxonClick=(item)=>{
this.props.Clicktobroadcastthevideo(true,item.url+'&file_name='+item.title); this.props.Clicktobroadcastthevideo(true,item.url+'&file_name='+item.title);
} }
isaboxonClicks=(item)=>{
this.props.Clicktobroadcastthevideo(true,item.url+'?file_name='+item.title);
}
render(){ render(){
let {visible,allfiles}=this.props; let {visible,allfiles}=this.props;
@ -188,7 +191,7 @@ class Showoldfiles extends Component{
{ {
allfiles&&allfiles.content_type&&allfiles.content_type==="video/mp4"? allfiles&&allfiles.content_type&&allfiles.content_type==="video/mp4"?
<a className={"isabox"} target="_blank" onClick={()=>this.isaboxonClick(allfiles)}>{allfiles.title}</a> <a className={"isabox"} target="_blank" onClick={()=>this.isaboxonClicks(allfiles)}>{allfiles.title}</a>
: :
<a className={"isabox"} href={allfiles.url} target="_blank" >{allfiles.title}</a> <a className={"isabox"} href={allfiles.url} target="_blank" >{allfiles.title}</a>
} }

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe CourseVideo, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end
Loading…
Cancel
Save