diff --git a/app/controllers/course_videos_controller.rb b/app/controllers/course_videos_controller.rb index 090c7e29a..c5e0358cd 100644 --- a/app/controllers/course_videos_controller.rb +++ b/app/controllers/course_videos_controller.rb @@ -1,9 +1,9 @@ class CourseVideosController < ApplicationController before_action :require_login - before_action :validate_params + before_action :validate_params, except: [:watch_histories] before_action :find_course, only: [:create] - before_action :find_video, only: [:update] - before_action :teacher_allowed + before_action :find_video, only: [:update, :watch_histories] + before_action :teacher_allowed, except: [:watch_histories] def create title = params[:name].strip @@ -20,6 +20,33 @@ class CourseVideosController < ApplicationController render_ok end + def watch_histories + return normal_status(403, "你没有权限操作") if !current_user.teacher_of_course?(@course) + course_video = CourseVideo.find(@video.id) + + @watch_course_videos = course_video.watch_course_videos.joins(" + JOIN watch_video_histories ON watch_video_histories.watch_course_video_id = watch_course_videos.id + ").group("watch_video_histories.watch_course_video_id").where("watch_course_videos.end_at IS NOT NULL").select("watch_course_videos.id") + + @count = @watch_course_videos.count.count + + if params[:group_id].present? + @watch_course_videos = @watch_course_videos.joins(" + JOIN course_members ON course_members.user_id = watch_course_videos.user_id AND course_members.course_id = #{@course.id} + ").where("course_members.course_group_id = ?", params[:group_id]) + end + + @watch_course_videos = @watch_course_videos.select("count(watch_video_histories.id) AS freq, watch_course_videos.*") + + if params[:order].present? + key = params[:order].split("-") + if ["freq", 'total_duration'].include?(key.first) && ["desc", "asc"].include?(key.last) + @watch_course_videos = @watch_course_videos.order("#{key.first} #{key.last}") + end + end + @watch_course_videos = paginate @watch_course_videos + end + private def validate_params diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 13be4168b..fa4f949c9 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -30,7 +30,8 @@ class CoursesController < ApplicationController :informs, :update_informs, :online_learning, :update_task_position, :tasks_list, :join_excellent_course, :export_couser_info, :export_member_act_score, :new_informs, :delete_informs, :change_member_role, :course_groups, :join_course_group, :statistics, - :work_score, :act_score, :calculate_all_shixun_scores, :move_to_category, :watch_video_histories] + :work_score, :act_score, :calculate_all_shixun_scores, :move_to_category, :watch_video_histories, :watch_statics, + :own_watch_histories] before_action :user_course_identity, except: [:join_excellent_course, :index, :create, :new, :apply_to_join_course, :search_course_list, :get_historical_course_students, :mine, :search_slim, :board_list] before_action :teacher_allowed, only: [:update, :destroy, :settings, :search_teacher_candidate, @@ -1482,23 +1483,75 @@ class CoursesController < ApplicationController end end + # 课堂视频观看记录总览 def watch_video_histories - @videos = CourseVideo.find_by_sql(" - SELECT course_videos.id, videos.user_id, videos.title, IFNULL(hisotries.time,0) AS time, IFNULL(hisotries.num,0) AS num - FROM course_videos + return normal_status(403, "你没有权限操作") unless current_user.teacher_of_course?(@course) + + @videos = CourseVideo.joins(" + JOIN videos ON course_videos.course_id = #{@course.id} AND videos.id = course_videos.video_id + LEFT JOIN ( + SELECT watch_course_videos.course_video_id, SUM(watch_course_videos.total_duration) AS time, COUNT(watch_course_videos.course_video_id) AS num + FROM watch_course_videos + JOIN course_videos ON course_videos.id = watch_course_videos.course_video_id AND watch_course_videos.end_at IS NOT NULL + WHERE course_videos.course_id = #{@course.id} + GROUP BY watch_course_videos.course_video_id + ) AS hisotries ON hisotries.course_video_id = course_videos.id").select("course_videos.id") + + @count = @videos.count + + if params[:order].present? + key = params[:order].split("-") + if ["people_num", 'total_time'].include?(key.first) && ["desc", "asc"].include?(key.last) + @videos = @videos.order("#{key.first} #{key.last}") + end + end + + @videos = @videos.select("course_videos.id, videos.user_id, videos.title, IFNULL(hisotries.time,0) AS total_time, IFNULL(hisotries.num,0) AS people_num") + + @videos = paginate @videos + end + + # 学生角度观看课堂视频的记录 + def own_watch_histories + @current_user = current_user + + @videos = CourseVideo.joins(" JOIN videos ON course_videos.course_id = #{@course.id} AND videos.id = course_videos.video_id - LEFT JOIN ( - SELECT watch_course_videos.course_video_id, sum(watch_course_videos.total_duration) AS time, count(watch_course_videos.course_video_id) AS num - FROM watch_course_videos + JOIN watch_course_videos ON course_videos.id = watch_course_videos.course_video_id AND watch_course_videos.user_id = #{current_user.id} + JOIN ( + SELECT watch_course_videos.course_video_id, COUNT(watch_course_videos.course_video_id) AS num + FROM watch_course_videos JOIN course_videos ON course_videos.id = watch_course_videos.course_video_id - WHERE course_videos.course_id = #{@course.id} - GROUP BY watch_course_videos.course_video_id - ) AS hisotries ON hisotries.course_video_id = course_videos.id - ") + WHERE course_videos.course_id = #{@course.id} AND watch_course_videos.user_id = #{current_user.id} AND watch_course_videos.end_at IS NOT NULL + GROUP BY watch_course_videos.course_video_id + ) AS hisotries ON hisotries.course_video_id = course_videos.id").select("course_videos.id") + @count = @videos.count + + if params[:order].present? + key = params[:order].split("-") + if ["freq", 'total_duration'].include?(key.first) && ["desc", "asc"].include?(key.last) + @videos = @videos.order("#{key.first} #{key.last}") + end + end + + @videos = @videos.select("course_videos.id, watch_course_videos.start_at, watch_course_videos.total_duration, watch_course_videos.end_at, watch_course_videos.is_finished, videos.title, IFNULL(hisotries.num,0) AS freq") + @videos = paginate @videos end + # 课堂视频的统计总览 + def watch_statics + @total_duration = @course.course_videos.joins(:watch_course_videos).sum(:total_duration).round(2) + @frequencies = @course.course_videos.joins([watch_course_videos: :watch_video_histories]).count(:id) + @people_num = @course.course_videos.joins(:watch_course_videos).count(:id) + render json: { + total_duration: @total_duration, + freq: @frequencies, + people_num: @people_num + } + end + private # Use callbacks to share common setup or constraints between actions. diff --git a/app/views/course_videos/watch_histories.json.jbuilder b/app/views/course_videos/watch_histories.json.jbuilder new file mode 100644 index 000000000..705e2d6b7 --- /dev/null +++ b/app/views/course_videos/watch_histories.json.jbuilder @@ -0,0 +1,12 @@ +json.data do + json.array! @watch_course_videos do |d| + json.user_name d.user&.real_name + json.is_finished d.is_finished ? true : false + json.total_duration d.total_duration.round(2) + json.feq d['freq'] + json.start_at d.start_at.to_s + json.end_at d.end_at.to_s + end +end + +json.count @count \ No newline at end of file diff --git a/app/views/courses/attahcment_category_list.json.jbuilder b/app/views/courses/attahcment_category_list.json.jbuilder index fe33bf13c..6422308a2 100644 --- a/app/views/courses/attahcment_category_list.json.jbuilder +++ b/app/views/courses/attahcment_category_list.json.jbuilder @@ -1,9 +1,9 @@ json.has_course_groups @has_course_groups json.course_modules do json.array! @course_modules do |course_module| - json.id course_module.id - json.module_name course_module.module_name - json.course_second_categories course_module.first_categories do |category| + json.value course_module.id + json.title course_module.module_name + json.children course_module.first_categories do |category| json.title category.name json.value category.id json.children category.children do |child| diff --git a/app/views/courses/own_watch_histories.json.jbuilder b/app/views/courses/own_watch_histories.json.jbuilder new file mode 100644 index 000000000..982939d9c --- /dev/null +++ b/app/views/courses/own_watch_histories.json.jbuilder @@ -0,0 +1,13 @@ +json.data do + json.array! @videos.each do |d| + json.title d.title + json.user_name @current_user&.real_name + json.is_finished d.is_finished ? true : false + json.total_duration d.total_duration.round(2) + json.freq d['freq'] + json.start_at d.start_at.to_s + json.end_at d.end_at.to_s + end +end + +json.count @count \ No newline at end of file diff --git a/app/views/courses/watch_video_histories.json.jbuilder b/app/views/courses/watch_video_histories.json.jbuilder index 4e7a4b174..207b9029b 100644 --- a/app/views/courses/watch_video_histories.json.jbuilder +++ b/app/views/courses/watch_video_histories.json.jbuilder @@ -3,8 +3,8 @@ json.videos do json.id v.id json.title v.title json.user_name v.user&.real_name - json.people_num v.num - json.total_time v.time + json.people_num v['people_num'] + json.total_time v['total_time'] end end json.count @count \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 881324e61..047141ea8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -532,6 +532,8 @@ Rails.application.routes.draw do post :inform_down get :calculate_all_shixun_scores get :watch_video_histories + get :watch_statics + get :own_watch_histories end collection do @@ -542,7 +544,11 @@ Rails.application.routes.draw do get 'search_slim' end - resources :course_videos, only:[:create, :update], shallow: true + resources :course_videos, only:[:create, :update], shallow: true do + member do + get :watch_histories + end + end resources :course_stages, shallow: true do member do diff --git a/public/react/src/modules/courses/Video/VideoIndex.js b/public/react/src/modules/courses/Video/VideoIndex.js index acf2f60eb..fe52b17dc 100644 --- a/public/react/src/modules/courses/Video/VideoIndex.js +++ b/public/react/src/modules/courses/Video/VideoIndex.js @@ -294,7 +294,7 @@ class VideoIndex extends Component{ videoData && videoData.category_name && type === "video" ? {videoData.category_name} : -
+
视频 直播 diff --git a/public/react/src/modules/courses/new/CoursesNew.js b/public/react/src/modules/courses/new/CoursesNew.js index 27fcb5c1e..12f4341e0 100644 --- a/public/react/src/modules/courses/new/CoursesNew.js +++ b/public/react/src/modules/courses/new/CoursesNew.js @@ -933,7 +933,7 @@ class CoursesNew extends Component { > {getFieldDecorator("checkboxgroup", { initialValue: [ - "shixun_homework", "common_homework", "group_homework", "exercise", "attachment", "course_group","video" + "shixun_homework", "common_homework", "group_homework", "exercise", "attachment", "course_group","video","attendance" ], })( @@ -949,6 +949,7 @@ class CoursesNew extends Component { 讨论 分班 统计 + 签到 )} diff --git a/public/react/src/modules/courses/new/Goldsubject.js b/public/react/src/modules/courses/new/Goldsubject.js index 6d637552d..3cd707284 100644 --- a/public/react/src/modules/courses/new/Goldsubject.js +++ b/public/react/src/modules/courses/new/Goldsubject.js @@ -989,7 +989,7 @@ class Goldsubject extends Component { {getFieldDecorator("checkboxgroup", { initialValue: [ - "announcement","online_learning","shixun_homework","common_homework", + "announcement","online_learning","shixun_homework","common_homework","attendance" ], })( @@ -1003,6 +1003,7 @@ class Goldsubject extends Component { 讨论 分班 统计 + 签到 )} diff --git a/public/react/src/modules/courses/signin/component/Teacherentry.js b/public/react/src/modules/courses/signin/component/Teacherentry.js index f3aa4c297..593bd4a50 100644 --- a/public/react/src/modules/courses/signin/component/Teacherentry.js +++ b/public/react/src/modules/courses/signin/component/Teacherentry.js @@ -34,9 +34,9 @@ class Teacherentry extends Component { } return ( -
-
-

this.props.qiandaoxiangq(true,item.id):""}> +

+
{e.stopPropagation();this.props.qiandaoxiangq(true,item.id)}:""}> +

{ item.name } @@ -95,18 +95,18 @@ class Teacherentry extends Component { isAdmin === true ? this.props.defaultActiveKey === "1" ?

-
this.props.thisEnd(item.id)}>截止
-
this.props.thisdelete(item.id)}>删除
+
{e.stopPropagation();this.props.thisEnd(item.id)}}>截止
+
{e.stopPropagation();this.props.thisdelete(item.id)}}>删除
: item.edit_auth === true ?
-
this.props.Signinnamestypes(item.id,true,item.name)}>编辑
-
this.props.thisdelete(item.id)}>删除
+
{e.stopPropagation();this.props.Signinnamestypes(item.id,true,item.name)}}>编辑
+
{e.stopPropagation();this.props.thisdelete(item.id)}}>删除
:
-
this.props.thisdelete(item.id)}>删除
+
{e.stopPropagation();this.props.thisdelete(item.id)}}>删除
: ( @@ -116,7 +116,7 @@ class Teacherentry extends Component { item.attendance_status? ( item.attendance_status==="ABSENCE"? -
this.props.Signin(item.mode,item.id,item.attendance_code)}> +
{e.stopPropagation();this.props.Signin(item.mode,item.id,item.attendance_code)}}> 签到
: diff --git a/public/react/src/modules/courses/signin/css/signincdi.css b/public/react/src/modules/courses/signin/css/signincdi.css index 6acea807a..5fe6a87cb 100644 --- a/public/react/src/modules/courses/signin/css/signincdi.css +++ b/public/react/src/modules/courses/signin/css/signincdi.css @@ -369,3 +369,37 @@ color:rgba(136,136,136,1); line-height:40px; } +.tbrt{ + padding-top: 22px; + padding-left: 28px; +} +.tbrt .ts { + font-size:12px; + font-family:MicrosoftYaHei; + color:rgba(255,255,255,1); +} + +.tbrt .tss{ + font-size:30px; + font-family:MicrosoftYaHei-Bold,MicrosoftYaHei; + font-weight:bold; + color:rgba(255,255,255,1); + line-height: 33px; +} +.maxnamewidth150s{ + width: 150px; + max-width: 150px; + overflow:hidden; + text-overflow:ellipsis; + white-space:nowrap; + cursor: default; +} +.maxnamewidth100s{ + width: 100px; + max-width: 100px; + overflow:hidden; + text-overflow:ellipsis; + white-space:nowrap; + cursor: default; +} + diff --git a/public/react/src/modules/courses/videostatistics/Videostatistics.js b/public/react/src/modules/courses/videostatistics/Videostatistics.js index 8e6cab744..93c9f57e6 100644 --- a/public/react/src/modules/courses/videostatistics/Videostatistics.js +++ b/public/react/src/modules/courses/videostatistics/Videostatistics.js @@ -2,26 +2,49 @@ import React,{ Component } from "react"; import axios from 'axios'; import '../signin/css/signincdi.css'; import Videostatisticscom from './component/Videostatisticscom'; +import Videostatisticslist from './component/Videostatisticslist'; //在线学习 class Videostatistics extends Component{ constructor(props){ super(props); - + this.state={ + watch_staticsdata:[], + } } componentDidMount() { + this.togetdatas(); } - mygetdatas=()=>{ + details=()=>{ } + togetdatas(){ + const CourseId=this.props.match.params.coursesId; + let url=`/courses/${CourseId}/watch_statics.json`; + axios.get(url).then((response) => { + if(response){ + this.setState({ + watch_staticsdata:response.data, + }) + + } + + }).catch((error) => { + + + }); + } + + render(){ + let {watch_staticsdata}= this.state; return(
@@ -38,11 +61,17 @@ class Videostatistics extends Component{

- +
+ this.details()}> + + + + +
diff --git a/public/react/src/modules/courses/videostatistics/component/Videostatisticscom.js b/public/react/src/modules/courses/videostatistics/component/Videostatisticscom.js index 0c064f3e3..6dbe44ae3 100644 --- a/public/react/src/modules/courses/videostatistics/component/Videostatisticscom.js +++ b/public/react/src/modules/courses/videostatistics/component/Videostatisticscom.js @@ -25,6 +25,7 @@ class Videostatisticscom extends Component { } + render() { return ( @@ -42,23 +43,49 @@ class Videostatisticscom extends Component { ` .yslsprenshu{ background-image: url(${getImageUrl(`images/qiandao/sprenshu.png`)}); + background-repeat:no-repeat; + -moz-background-size:100% 100%; + background-size:100% 100%; } .yslspcishu{ background-image: url(${getImageUrl(`images/qiandao/spcishu.png`)}); + background-repeat:no-repeat; + -moz-background-size:100% 100%; + background-size:100% 100%; } .yslshipingshi{ background-image: url(${getImageUrl(`images/qiandao/shipingshi.png`)}); + background-repeat:no-repeat; + -moz-background-size:100% 100%; + background-size:100% 100%; } ` } -
-
- -
- -
+
+
+
+
观看总人数(人)
+
{this.props.watch_staticsdata&&this.props.watch_staticsdata.people_num?this.props.watch_staticsdata.people_num:0}
+ +
+ +
+ +
+
+
观看总人次(次)
+
{this.props.watch_staticsdata&&this.props.watch_staticsdata.freq?this.props.watch_staticsdata.freq:0}
+
+
+ +
+
+
总观看时长(时)
+
{this.props.watch_staticsdata&&this.props.watch_staticsdata.total_duration?this.props.watch_staticsdata.total_duration:0}
+
+
diff --git a/public/react/src/modules/courses/videostatistics/component/Videostatisticslist.js b/public/react/src/modules/courses/videostatistics/component/Videostatisticslist.js new file mode 100644 index 000000000..692958517 --- /dev/null +++ b/public/react/src/modules/courses/videostatistics/component/Videostatisticslist.js @@ -0,0 +1,204 @@ +import React, {Component} from "react"; +import '../../signin/css/signincdi.css'; +import {Pagination,Table} from 'antd'; +import {getImageUrl} from 'educoder'; +import axios from 'axios'; +import LoadingSpin from "../../../../common/LoadingSpin"; +import NoneDatas from "../../signin/component/NoneDatas"; + + +//条目 +class Videostatisticslist extends Component { + //条目组件 + constructor(props) { + super(props); + + this.state = { + columnsstu: [ + { + title: '序号', + dataIndex: 'number', + key: 'number', + align: "center", + className: 'font-14', + width: '90px', + render: (text, record) => ( + {record.number} + ), + }, + { + title: '视频名称', + dataIndex: 'title', + key: 'title', + align: "center", + className: 'font-14 maxnamewidth150s', + width: '150px', + render: (text, record) => ( + {record.title} + ), + }, + { + title: '观看人数(人)', + dataIndex: 'people_num', + key: 'people_num', + align: "center", + className: 'font-14', + width: '98px', + render: (text, record) => ( + {record.people_num} + ), + }, + { + title: '累计观看时长', + dataIndex: 'total_time', + key: 'total_time', + align: "center", + className: 'font-14 maxnamewidth150s', + width: '150px', + render: (text, record) => ( + {record.total_time} + ), + }, + { + title: '发布人', + dataIndex: 'user_name', + key: 'user_name', + align: "center", + className: 'font-14 maxnamewidth100s', + width: '100px', + render: (text, record) => ( + {record.user_name} + ), + }, + { + title: '详情', + dataIndex: 'id', + key: 'id', + align: "center", + className: 'font-14', + width: '90px', + render: (text, record) => ( + this.props.details()}>详情 + ), + } + ], + loading:false, + data:[], + page:1, + limit:10, + members_count:0, + } + } + + componentDidMount() { + this.togetdatas(1); + + } + + componentDidUpdate = (prevProps) => { + + + } + paginationonChange = (pageNumber) => { + this.setState({ + page: pageNumber, + }) + this.togetdatas(pageNumber); + } + + + togetdatas(page){ + this.setState({ + loading:true + }) + const CourseId=this.props.match.params.coursesId; + let url=`/courses/${CourseId}/watch_video_histories.json`; + axios.get(url,{params:{ + page:page + } + }).then((response) => { + if(response){ + this.setState({ + data:response.data&&response.data.videos?response.data.videos:[], + members_count:count, + }) + + } + this.setState({ + loading:false + }) + }).catch((error) => { + this.setState({ + loading:false + }) + }); + } + + + + + render() { + let {loading,data,columnsstu,page,members_count,limit}=this.state; + return ( + +
+
+
+
+
统计详情
+
+ +
+ + { + loading===true? +
+ +
+ : +
+ { + data.length===0? +
+ +
+ : + + } + + + + } + + + +
+ { + data&&data.length>0? + + :"" + } + +
+ + + + ) + } +} + +export default Videostatisticslist;