Merge branches 'dev_aliyun', 'develop' and 'newopenshixun0316' of https://bdgit.educoder.net/Hjqreturn/educoder into newopenshixun0316

courseware
杨树明 5 years ago
commit 02076abb84

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

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

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

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

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

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

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

@ -294,7 +294,7 @@ class VideoIndex extends Component{
videoData && videoData.category_name && type === "video" ?
<span className="font-18 fl color-dark-21 mt20 mb20">{videoData.category_name}</span>
:
<div className="task_menu_ul fl mt2" style={{width:"400px"}}>
<div className="task_menu_ul fl mt2" style={{width:"240px"}}>
<Menu mode="horizontal" selectedKeys={[type]} onClick={this.changeType}>
<Menu.Item key="video">视频</Menu.Item>
<Menu.Item key="live">直播</Menu.Item>

@ -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"
],
})(
<Checkbox.Group style={{ marginTop: "10px"}}>
@ -949,6 +949,7 @@ class CoursesNew extends Component {
<Checkbox value={"board"} className="fl">讨论</Checkbox>
<Checkbox value={"course_group"} className="fl">分班</Checkbox>
<Checkbox value={"statistics"} className="fl">统计</Checkbox>
<Checkbox value={"attendance"} className="fl">签到</Checkbox>
</Checkbox.Group>
)}
</Form.Item>

@ -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"
],
})(
<Checkbox.Group style={{ marginTop: "10px"}}>
@ -1003,6 +1003,7 @@ class Goldsubject extends Component {
<Checkbox value={"board"} className="fl">讨论</Checkbox>
<Checkbox value={"course_group"} className="fl">分班</Checkbox>
<Checkbox value={"statistics"} className="fl">统计</Checkbox>
<Checkbox value={"attendance"} className="fl">签到</Checkbox>
</Checkbox.Group>
)}
</Form.Item>

@ -34,9 +34,9 @@ class Teacherentry extends Component {
}
return (
<React.Fragment>
<div className={index===0?"ws100s edu-back-white ": "ws100s edu-back-white mt20"}>
<div className="ws100s teacherentrydiv ">
<p className={isAdmin?"ws100s teachedivp ymaxnamewidthdivp xiaoshou":"ws100s teachedivp ymaxnamewidthdivp"} onClick={isAdmin?()=>this.props.qiandaoxiangq(true,item.id):""}>
<div className={index===0?"ws100s edu-back-white xiaoshou": "ws100s edu-back-white mt20 xiaoshou"}>
<div className="ws100s teacherentrydiv" onClick={isAdmin?(e)=>{e.stopPropagation();this.props.qiandaoxiangq(true,item.id)}:""}>
<p className={isAdmin?"ws100s teachedivp ymaxnamewidthdivp xiaoshou color-blue":"ws100s teachedivp ymaxnamewidthdivp"} >
{
item.name
}
@ -95,18 +95,18 @@ class Teacherentry extends Component {
isAdmin === true ?
this.props.defaultActiveKey === "1" ?
<div className="ws100s xaxisreverseorder">
<div className="jiezhis h40s xiaoshou" onClick={()=>this.props.thisEnd(item.id)}>截止</div>
<div className="shanchu h40s xiaoshou" onClick={()=>this.props.thisdelete(item.id)}>删除</div>
<div className="jiezhis h40s xiaoshou" onClick={(e)=>{e.stopPropagation();this.props.thisEnd(item.id)}}>截止</div>
<div className="shanchu h40s xiaoshou" onClick={(e)=>{e.stopPropagation();this.props.thisdelete(item.id)}}>删除</div>
</div>
:
item.edit_auth === true ?
<div className="ws100s xaxisreverseorder">
<div className="jiezhis h40s xiaoshou" onClick={()=>this.props.Signinnamestypes(item.id,true,item.name)}>编辑</div>
<div className="shanchu h40s xiaoshou" onClick={()=>this.props.thisdelete(item.id)}>删除</div>
<div className="jiezhis h40s xiaoshou" onClick={(e)=>{e.stopPropagation();this.props.Signinnamestypes(item.id,true,item.name)}}>编辑</div>
<div className="shanchu h40s xiaoshou" onClick={(e)=>{e.stopPropagation();this.props.thisdelete(item.id)}}>删除</div>
</div>
:
<div className="ws100s xaxisreverseorder">
<div className="jiezhis h40s color-reds xiaoshou" onClick={()=>this.props.thisdelete(item.id)}>删除</div>
<div className="jiezhis h40s color-reds xiaoshou" onClick={(e)=>{e.stopPropagation();this.props.thisdelete(item.id)}}>删除</div>
</div>
:
(
@ -116,7 +116,7 @@ class Teacherentry extends Component {
item.attendance_status?
(
item.attendance_status==="ABSENCE"?
<div className="qiandaobutton xiaoshou" onClick={()=>this.props.Signin(item.mode,item.id,item.attendance_code)}>
<div className="qiandaobutton xiaoshou" onClick={(e)=>{e.stopPropagation();this.props.Signin(item.mode,item.id,item.attendance_code)}}>
签到
</div>
:

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

@ -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(
<React.Fragment>
<div className="ws100s">
@ -38,11 +61,17 @@ class Videostatistics extends Component{
</p>
</div>
<Videostatisticscom {...this.state} {...this.props}></Videostatisticscom>
<Videostatisticscom {...this.state} {...this.props} watch_staticsdata={watch_staticsdata}></Videostatisticscom>
<div>
<Videostatisticslist {...this.state} {...this.props} details={()=>this.details()}></Videostatisticslist>
</div>
</div>

@ -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%;
}
`
}
</style>
<div className="ws100s">
<div className="yslsprenshu"></div>
<div className="yslspcishu"></div>
<div className="yslshipingshi"></div>
<div className="ws100s spacearound mt20">
<div className="yslsprenshu" style={{width:260,height:100}}>
<div className="ws100s verticallayout tbrt">
<div className="ws100s ts">观看总人数</div>
<div className="ws100s tss">{this.props.watch_staticsdata&&this.props.watch_staticsdata.people_num?this.props.watch_staticsdata.people_num:0}</div>
</div>
</div>
<div className="yslspcishu" style={{width:260,height:100}}>
<div className="ws100s verticallayout tbrt">
<div className="ws100s ts">观看总人次</div>
<div className="ws100s tss">{this.props.watch_staticsdata&&this.props.watch_staticsdata.freq?this.props.watch_staticsdata.freq:0}</div>
</div>
</div>
<div className="yslshipingshi" style={{width:260,height:100}}>
<div className="ws100s verticallayout tbrt">
<div className="ws100 ts">总观看时长</div>
<div className="ws100s tss">{this.props.watch_staticsdata&&this.props.watch_staticsdata.total_duration?this.props.watch_staticsdata.total_duration:0}</div>
</div>
</div>
</div>

@ -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) => (
<span style={{width: '90px'}}>{record.number}</span>
),
},
{
title: '视频名称',
dataIndex: 'title',
key: 'title',
align: "center",
className: 'font-14 maxnamewidth150s',
width: '150px',
render: (text, record) => (
<span style={{width: '150px'}} className="maxnamewidth150s">{record.title}</span>
),
},
{
title: '观看人数(人)',
dataIndex: 'people_num',
key: 'people_num',
align: "center",
className: 'font-14',
width: '98px',
render: (text, record) => (
<span style={{width: '98px'}}>{record.people_num}</span>
),
},
{
title: '累计观看时长',
dataIndex: 'total_time',
key: 'total_time',
align: "center",
className: 'font-14 maxnamewidth150s',
width: '150px',
render: (text, record) => (
<span style={{width: '150px'}} className="maxnamewidth150s">{record.total_time}</span>
),
},
{
title: '发布人',
dataIndex: 'user_name',
key: 'user_name',
align: "center",
className: 'font-14 maxnamewidth100s',
width: '100px',
render: (text, record) => (
<span style={{width: '100px'}} className="maxnamewidth100s">{record.user_name}</span>
),
},
{
title: '详情',
dataIndex: 'id',
key: 'id',
align: "center",
className: 'font-14',
width: '90px',
render: (text, record) => (
<span style={{width: '90px',color:'#5091FF'}} className="xiaoshou" onClick={()=>this.props.details()}>详情</span>
),
}
],
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 (
<React.Fragment>
<div className="ws100s mt20">
<div className="ws100s edu-back-white">
<div className="ws100s teacherentrydivs ">
<div className="ws100s sortinxdirection">
<div className="ws100s sptits">统计详情</div>
</div>
</div>
<style>
{
`
.ysltableo .ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 10px 10px;
}
`
}
</style>
{
loading===true?
<div style={{
minHeight: "400px",
}} className="ws100s">
<LoadingSpin></LoadingSpin>
</div>
:
<div className="ws100s ysltableo teacherentrydivs">
{
data.length===0?
<div style={{
minHeight: "400px",
}} className="ws100s">
<NoneDatas></NoneDatas>
</div>
:
<Table columns={columnsstu} dataSource={data} pagination={false}/>
}
</div>
}
</div>
<div className="mb30 clearfix educontent mt40 intermediatecenter">
{
data&&data.length>0?
<Pagination showQuickJumper current={this.state.page} onChange={this.paginationonChange}
pageSize={this.state.limit}
total={this.state.members_count}></Pagination>
:""
}
</div>
</div>
</React.Fragment>
)
}
}
export default Videostatisticslist;
Loading…
Cancel
Save