courseware
杨树明 5 years ago
commit 0dfb20c5bf

@ -140,11 +140,11 @@ module ApplicationHelper
# 用户图像url如果不存在的话source为匿名用户即默认使用匿名用户图像
def url_to_avatar(source)
if File.exist?(disk_filename(source&.class, source&.id))
ctime = File.ctime(disk_filename(source.class, source.id)).to_i
ctime = File.ctime(disk_filename(source&.class, source&.id)).to_i
if source.class.to_s == 'User'
File.join(relative_path, ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
File.join(relative_path, ["#{source&.class}", "#{source&.id}"]) + "?t=#{ctime}"
else
File.join("images/avatars", ["#{source.class}", "#{source.id}"]) + "?t=#{ctime}"
File.join("images/avatars", ["#{source&.class}", "#{source&.id}"]) + "?t=#{ctime}"
end
elsif source.class.to_s == 'User'
str = source.user_extension.try(:gender).to_i == 0 ? "b" : "g"

@ -4,7 +4,7 @@ class StudentJoinAttendanceRecordJob < ApplicationJob
def perform(member_id)
member = CourseMember.find_by(id: member_id)
course = member&.course
return if member.blank? || course.blank?
return if member.blank? || member.role != "STUDENT" || course.blank?
current_date = Date.current
current_end_time = Time.current.strftime("%H:%M:%S")

@ -165,7 +165,9 @@ class CourseMember < ApplicationRecord
private
def create_attendance_record
StudentJoinAttendanceRecordJob.perform_later(id)
if role == "STUDENT"
StudentJoinAttendanceRecordJob.perform_later(id)
end
end
end

@ -2,7 +2,7 @@ json.courses @courses do |course|
json.id course.id
json.name course.name
json.avatar_url url_to_avatar(course.teacher)
json.creator course.teacher.real_name
json.creator course.teacher&.real_name
json.school course.school&.name
json.technical_title "" # course.teacher.identity
json.course_members_count course.course_members_count

@ -132,7 +132,7 @@ export function initAxiosInterceptors(props) {
// proxy = "https://testeduplus2.educoder.net"
//proxy="http://47.96.87.25:48080"
proxy="https://pre-newweb.educoder.net"
proxy="https://test-newweb.educoder.net"
//proxy="https://test-newweb.educoder.net"
// proxy="https://test-jupyterweb.educoder.net"
// proxy="https://test-newweb.educoder.net"
// proxy="https://test-jupyterweb.educoder.net"

@ -970,6 +970,13 @@ class CoursesIndex extends Component{
}
></Route>
{/* 主签到 */}
<Route path="/classrooms/:coursesId/attendances"
render={
(props) => (<ListPageIndex {...this.props} {...props} {...this.state} />)
}
></Route>
{/*/!*实训作业and课堂详情页*!/*/}
<Route path="/classrooms/:coursesId"
render={

@ -177,6 +177,9 @@ export default ({ src, videoId, logWatchHistory, courseId = null }) => {
el.current.removeEventListener('seeking', onSeeking)
el.current.removeEventListener('seeked', onSeeked)
el.current.removeEventListener('timeupdate', onTimeupdate)
if(el.current.playing) {
log()
}
}
}, [el, src])

@ -89,7 +89,6 @@ class CoursesBanner extends Component {
}
HideAddcoursestypess=(i)=>{
console.log("调用了");
this.setState({
Addcoursestypes:false,
mydisplay:true,

@ -77,7 +77,7 @@ class Detailss extends Component {
<Progress percent={jdt} showInfo={false} strokeColor="#1890ff"/>
</div>
<div className="progressivpss h28s">
已签到{item && item.normal_count ? item.normal_count : 0} /
已签到 {item && item.normal_count ? item.normal_count : 0} /
应签到 {item && item.all_count ? item.all_count : 0}
</div>
</div>

@ -51,7 +51,7 @@ class Teacherentry extends Component {
<Progress percent={jdt} showInfo={false} strokeColor="#1890ff"/>
</div>
<div className="progressivpss">
已签到{item.normal_count ? item.normal_count : 0} / 应签到 {item.all_count ? item.all_count : 0}
已签到 {item.normal_count ? item.normal_count : 0} / 应签到 {item.all_count ? item.all_count : 0}
</div>
</div>

@ -210,4 +210,24 @@
height: auto !important;
padding: 0px !important;
white-space: nowrap !important;
}
}
.color26C7C9 .ant-select-selection-selected-value{
margin: 0 30% !important;
}
.colorEAAE4E .ant-select-selection-selected-value{
margin: 0 40% !important;
}
.color909399 .ant-select-selection-selected-value{
margin: 0 37% !important;
}
.colorFF835C .ant-select-selection-selected-value{
margin: 0 37% !important;
}
/*.allSignedinlistbox .ant-select-selection-selected-value{*/
/* margin: 0 30% !important;*/
/*}*/

@ -358,6 +358,11 @@
}
.colorbluesigin{
font-size:16px;
font-weight:bold;
color:rgba(51,51,51,1);
}
.sptits{
font-size:20px;
font-family:MicrosoftYaHeiSemibold;
@ -394,6 +399,15 @@
white-space:nowrap;
cursor: default;
}
.maxnamewidth200s{
text-align: center;
width: 200px;
max-width:200px;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
cursor: default;
}
.maxnamewidth100s{
width: 100px;
max-width: 100px;
@ -403,3 +417,9 @@
cursor: default;
}
.font-14{
font-size: 14px !important;
}
.mr32{
margin-right: 32px;
}

@ -260,9 +260,10 @@ class Signedinlist extends Component {
className: "textcenter",
render: (text, record) => (
<span>
<Select key={record.index} defaultValue={record.attendance_status}
// className={"Signedinlistbox"}
className={record.attendance_status==="NORMAL"?"color26C7C9 Signedinlistbox sginboxcolor26C7C9":record.attendance_status==="LEAVE"?"colorEAAE4E Signedinlistbox sginboxcolorEAAE4E":record.attendance_status==="ABSENCE"?this.props.defaultActiveKey==="2"?"colorFF835C Signedinlistbox sginboxcolorFF835C":"color909399 Signedinlistbox sginboxcolor909399":"Signedinlistbox"}
className={record.attendance_status==="NORMAL"?"color26C7C9 Signedinlistbox sginboxcolor26C7C9 marginleft10":record.attendance_status==="LEAVE"?"colorEAAE4E Signedinlistbox sginboxcolorEAAE4E marginleft10":record.attendance_status==="ABSENCE"?this.props.defaultActiveKey==="2"?"colorFF835C Signedinlistbox sginboxcolorFF835C marginleft10":"color909399 Signedinlistbox sginboxcolor909399 marginleft10":"Signedinlistbox"}
style={{ width: 167 }} onChange={(e)=>this.handleChange(e,record.user_id)}>
{
@ -339,6 +340,15 @@ class Signedinlist extends Component {
</Col>
</Row>
<style>
{
`
.marginleft10{
margin-left: 10px !important;
}
`
}
</style>
<div className={"backfff mt10"}>
{
this.state.loading===true?

@ -43,13 +43,10 @@ class Signindetails extends Component{
<div className="ws100s" style={{
position: "relative",
}}>
<div className="ws100s xaxisreverseorder" style={{
position: "absolute",
top: "-29px",
}}>
<div className="ws100s mb20">
<p className="sortinxdirection xiaoshou" onClick={()=>this.props.qiandaoxiangq(false)}>
<i className="iconfont icon-zuojiantou posiivsiconmyss mr5"></i>
<p className="fh mr20"> 返回</p>
<i className="iconfont icon-zuojiantou posiivsiconmyss mr5 colorbluesigin"></i>
<p className="fh mr20 colorbluesigin">正在签到</p>
</p>
</div>

@ -3,6 +3,7 @@ import axios from 'axios';
import '../signin/css/signincdi.css';
import Videostatisticscom from './component/Videostatisticscom';
import Videostatisticslist from './component/Videostatisticslist';
import Videostatisticscomtwo from './component/Videostatisticscomtwo';
//在线学习
@ -11,6 +12,8 @@ class Videostatistics extends Component{
super(props);
this.state={
watch_staticsdata:[],
tisticsbool:false,
tisid:null,
}
@ -40,41 +43,49 @@ class Videostatistics extends Component{
});
}
tisticsbools=(bool,id)=>{
this.setState({
tisticsbool:bool,
tisid:id,
})
}
render(){
let {watch_staticsdata}= this.state;
let {watch_staticsdata,tisticsbool,tisid}= this.state;
return(
<React.Fragment>
<div className="ws100s">
<div className="ws100s" style={{
position: "relative",
}}>
<div className="ws100s xaxisreverseorder" style={{
position: "absolute",
top: "-29px",
}}>
<p className="sortinxdirection xiaoshou" onClick={()=>this.props.statisticsy(false)}>
<i className="iconfont icon-zuojiantou posiivsiconmyss mr5"></i>
<p className="fh mr20"> 返回</p>
</p>
</div>
<Videostatisticscom {...this.state} {...this.props} watch_staticsdata={watch_staticsdata}></Videostatisticscom>
{
tisticsbool===false?
<div className="ws100s" style={{
position: "relative",
}}>
<div className="ws100s xaxisreverseorder" style={{
position: "absolute",
top: "-29px",
}}>
<p className="sortinxdirection xiaoshou" onClick={()=>this.props.statisticsy(false)}>
<i className="iconfont icon-zuojiantou posiivsiconmyss mr5"></i>
<p className="fh mr20"> 返回</p>
</p>
<div>
<Videostatisticslist {...this.state} {...this.props} details={()=>this.details()}></Videostatisticslist>
</div>
<Videostatisticscom {...this.state} {...this.props} watch_staticsdata={watch_staticsdata} ></Videostatisticscom>
<div>
<Videostatisticslist {...this.state} {...this.props} tisticsbools={(b,id)=>this.tisticsbools(b,id)}></Videostatisticslist>
</div>
</div>
:
<Videostatisticscomtwo {...this.state} {...this.props} tisid={tisid} tisticsbools={(b,id)=>this.tisticsbools(b,id)}></Videostatisticscomtwo>
}
</div>
</div>
</div>
</React.Fragment>
)

@ -0,0 +1,483 @@
import React, {Component} from "react";
import '../../signin/css/signincdi.css';
import {Pagination,Table, Menu, Dropdown} from 'antd';
import {getImageUrl,sortDirections} from 'educoder';
import axios from 'axios';
import LoadingSpin from "../../../../common/LoadingSpin";
import NoneDatas from "../../signin/component/NoneDatas";
//条目
class Videostatisticscom extends Component {
//条目组件
constructor(props) {
super(props);
this.state = {
data:[],
page:1,
limit:10,
members_count:0,
columnsstu: [
{
title: '序号',
dataIndex: 'number',
key: 'number',
align: "center",
className: 'font-14',
width: '50px',
render: (text, record) => (
<span style={{width: '50px'}}>{record.number}</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: 'is_finished',
key: 'is_finished',
align: "center",
className: 'font-14',
width: '98px',
render: (text, record) => (
<span style={{width: '98px'}}>{record.is_finished===true?
<span style={{color:"#5091FF"}}></span>:<span style={{color:"#E02020"}}></span>}</span>
),
},
{
title: '视频累计观看时长',
dataIndex: 'total_duration',
key: 'total_duration',
align: "center",
className: 'font-14 maxnamewidth150s',
width: '150px',
sorter: true,
sortDirections: sortDirections,
render: (text, record) => (
<span style={{width: '150px'}} className="maxnamewidth150s">{record.total_duration}</span>
),
},
{
title: '累计观看次数(次)',
dataIndex: 'feq',
key: 'feq',
align: "center",
className: 'font-14 maxnamewidth100s',
width: '100px',
sorter: true,
sortDirections: sortDirections,
render: (text, record) => (
<span style={{width: '100px'}} className="maxnamewidth100s">{record.feq}</span>
),
},
{
title: '最早观看时间',
dataIndex: 'start_at',
key: 'start_at',
align: "center",
className: 'font-14 maxnamewidth100s',
width: '100px',
render: (text, record) => (
<span style={{width: '100px',color:'#5091FF'}} className="xiaoshou" >{record.start_at}</span>
),
},
{
title: '最后观看时间',
dataIndex: 'end_at',
key: 'end_at',
align: "center",
className: 'font-14 maxnamewidth100s',
width: '100px',
render: (text, record) => (
<span style={{width: '100px',color:'#5091FF'}} className="xiaoshou" >{record.end_at}</span>
),
}
],
loading:false,
order:undefined,
course_groups:[],
fbbool:false,
groupsid:null,
}
}
componentDidMount() {
this.setState({
order:undefined
})
if(this.props.isAdmin()){
//老师
const CourseId=this.props.match.params.coursesId;
var data={
id:CourseId,
page:this.state.page,
}
this.getdatas(data);
}else{
//学生
var data={
page:this.state.page,
}
this.getdatas(data);
}
this.fenbans();
}
componentDidUpdate = (prevProps) => {
}
//分班
fenbans=()=>{
const CourseId=this.props.match.params.coursesId;
let url=`/courses/${CourseId}/course_groups.json`;
axios.get(url).then((response) => {
if(response){
console.log("分班");
console.log("response");
console.log(response);
this.setState({
course_groups:response.data.course_groups,
current_group_id:response.data.current_group_id,
none_group_member_count:response.data.none_group_member_count,
group_count:response.data.group_count,
})
}
}).catch((error) => {
});
}
//学生
getdatas=(data)=>{
this.setState({
loading:true
})
const CourseId=this.props.match.params.coursesId;
let url="";
if(this.props.isAdmin()){
url=`/course_videos/${this.props.tisid}/watch_histories.json`;
}else {
url=`/courses/${CourseId}/own_watch_histories.json`;
}
axios.get(url,{params:data}).then((response) => {
if(response){
if(response.data){
if(response.data.data.length>0){
let datalists=[];
for (var i = 0; i < response.data.data.length; i++) {
datalists.push({
number: (parseInt(page) - 1) * parseInt(limit) + (i + 1),
user_name:response.data.data[i].user_name,
is_finished:response.data.data[i].is_finished,
total_duration:response.data.data[i].total_duration,
feq:response.data.data[i].feq,
start_at:response.data.data[i].start_at,
end_at:response.data.data[i].end_at,
})
}
this.setState({
data:datalists,
members_count:response.data.count,
})
}else{
this.setState({
data:[],
members_count:response.data.count,
})
}
}else{
this.setState({
data:[],
members_count:response.data.count,
})
}
}
this.setState({
loading:false
})
}).catch((error) => {
this.setState({
loading:false
})
});
}
paginationonChange = (pageNumber) => {
this.setState({
page: pageNumber,
})
}
fenbanone=()=>{
if(this.state.fbbool===false){
this.setState({
fbbool:true
})
}else{
this.setState({
fbbool:false
})
}
}
setcourse_groups=(id)=>{
this.setState({
groupsid:id
})
//老师
const CourseId=this.props.match.params.coursesId;
var data={};
if(id){
data={
id:CourseId,
page:this.state.page,
group_id:id
}
}else {
data={
id:CourseId,
page:this.state.page
}
}
this.getdatas(data);
}
//实训作业tbale 列表塞选数据
table1handleChange = (pagination, filters, sorter) => {
if (JSON.stringify(sorter) === "{}") {
//没有选择
} else {
try {
//学生学号排序
if (sorter.columnKey === "total_duration"||sorter.columnKey === "feq") {
let mysorder="";
if (sorter.order === "ascend") {
if(sorter.columnKey === "total_duration"){
mysorder="total_duration-asc";
}else{
mysorder="freq-asc";
}
//升序
let data={}
if(this.props.isAdmin()){
//老师
const CourseId=this.props.match.params.coursesId;
if(groupsid){
data={
id:CourseId,
page:this.state.page,
order:mysorder,
}
}else{
data={
id:CourseId,
page:this.state.page,
group_id:this.state.groupsid,
order:mysorder,
}
}
}else{
//学生
data={
page:this.state.page,
order:mysorder,
}
}
this.getdatas(data);
this.setState({
order: mysorder,
})
} else if (sorter.order === "descend") {
if(sorter.columnKey === "total_duration"){
mysorder="total_duration-desc";
}else{
mysorder="freq-desc";
}
//降序
let data={}
if(this.props.isAdmin()){
//老师
const CourseId=this.props.match.params.coursesId;
if(groupsid){
data={
id:CourseId,
page:this.state.page,
order:mysorder,
}
}else{
data={
id:CourseId,
page:this.state.page,
group_id:this.state.groupsid,
order:mysorder,
}
}
}else{
//学生
data={
page:this.state.page,
order:mysorder,
}
}
this.getdatas(data);
this.setState({
order:mysorder,
})
}
}
} catch (e) {
}
}
}
render() {
let {loading,data,page,limit,members_count,columnsstu,fbbool,course_groups}=this.state;
const isAdmin =this.props.isAdmin();
const menu = (
<Menu>
<Menu.Item>
<a onClick={()=>this.setcourse_groups(null)}>
<p className="maxnamewidth200s">全部</p>
</a>
</Menu.Item>
{
course_groups&&course_groups.length>0?
(
course_groups.map((item,key) => {
return (
<Menu.Item>
<a onClick={()=>this.setcourse_groups(item.id)} key={key}>
<p className="maxnamewidth200s">{item.name}</p>
</a>
</Menu.Item>
)
})
)
:
""
}
</Menu>
);
return (
<React.Fragment>
<div className="ws100s" >
<div className="ws100s teacherentrydivs edu-back-white ">
<div className="ws100s sortinxdirection">
<div className="ws50s sptits">视频名称视频名称</div>
<div className="ws50s sptitss xaxisreverseorder font-14" style={{
color:"#5091FF",
lineHeight: "42px",
}}>
<div className="xiaoshou" onClick={()=>this.props.tisticsbools(false,null)}>
<span className="mr5 xiaoshou">视频统计总览</span><i className="iconfont icon-fanhui font-13 xiaoshou"></i>
</div>
<div className="xiaoshou" onClick={()=>this.fenbanone()}>
<Dropdown getPopupContainer={trigger => trigger.parentNode} overlay={menu} placement="bottomCenter" >
<span>
<span className="mr5 xiaoshou">分班</span>
{
fbbool===true?
<i className="iconfont icon-sanjiaoxing-down font-13 mr32 xiaoshou"></i>
:
<i className="iconfont icon-sanjiaoxing-up font-13 mr32 xiaoshou"></i>
}
</span>
</Dropdown>
</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}
onChange={this.table1handleChange}
/>
}
</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 Videostatisticscom;

@ -78,7 +78,7 @@ class Videostatisticslist extends Component {
className: 'font-14',
width: '90px',
render: (text, record) => (
<span style={{width: '90px',color:'#5091FF'}} className="xiaoshou" onClick={()=>this.props.details()}>详情</span>
<span style={{width: '90px',color:'#5091FF'}} className="xiaoshou" onClick={()=>this.props.tisticsbools(true,record.id)}>详情</span>
),
}
],
@ -120,7 +120,7 @@ class Videostatisticslist extends Component {
if(response){
this.setState({
data:response.data&&response.data.videos?response.data.videos:[],
members_count:count,
members_count:response.data.count,
})
}

@ -6,6 +6,6 @@ export default ({
callback(id)
}
return (
<a className={id === activeId ? "shaiItems shixun_repertoire active" : "shaiItems shixun_repertoire"} onClick={onClickHandler}>{text}</a>
<a className={id == activeId ? "shaiItems shixun_repertoire active" : "shaiItems shixun_repertoire"} onClick={onClickHandler}>{text}</a>
)
}

@ -12,13 +12,13 @@ const DiffObject = [
{ id: 3, text: '中高级' },
{ id: 4, text: '高级' }
]
export default ({ StatusEnquiry, allUpdatashixunlist, Updatasearchlist }) => {
export default ({ StatusEnquiry, allUpdatashixunlist, Updatasearchlist,parsedid,newpalce }) => {
const [data, setData] = useState({
diff: 0,
searchValue: 'a',
searchValue: newpalce || 'a',
navs: [],
searchKey: '',
childValue: ''
childValue:parsedid
})
const { diff, searchValue, navs, childValue, searchKey } = data
function diffSearch(diff) {
@ -28,6 +28,15 @@ export default ({ StatusEnquiry, allUpdatashixunlist, Updatasearchlist }) => {
})
StatusEnquiry([{ 'type': 2 }, { 'value': diff }])
}
useEffect(() => {
setData({
...data,
searchValue: newpalce || 'a',
childValue: parsedid
})
},[
newpalce,navs,parsedid
])
function onSearchAll() {
if (searchValue !== 'a') {
setData({ ...data, searchValue: 'a', childValue: '' })
@ -61,12 +70,12 @@ export default ({ StatusEnquiry, allUpdatashixunlist, Updatasearchlist }) => {
function overlayMenu(item, id) {
return <Menu>
{
item.map((list, k) => <Menu.Item>
<div className="mt5 subshaicontent-part" key={k} >
item.map((list, k) => <Menu.Item key={list.id}>
<div className="mt5 subshaicontent-part" >
<a style={{ height: '20px' }} className={"mb15 shixun_repertoire color-dark"} name={list.id} id={id} onClick={getChildValue}>{list.name}</a>
<div className="sub-Item clearfix">
{
list.tags.map((tag, e) => <a className={childValue === tag.id ? "shixun_repertoire active" : "shixun_repertoire"} key={e} id={tag.id} name={id} rel="subshaicontent" onClick={getChildValues}>{tag.name}</a>
list.tags.map((tag, e) => <a className={childValue == tag.id ? "shixun_repertoire color-blue" : "shixun_repertoire"} key={e} id={tag.id} name={id} rel="subshaicontent" onClick={getChildValues}>{tag.name}</a>
)
}
</div>
@ -84,7 +93,6 @@ export default ({ StatusEnquiry, allUpdatashixunlist, Updatasearchlist }) => {
}
init()
}, [])
// item.id < 4 ? "bottomRight" : item.id >= 8 ? "bottomLeft" : "bottomCenter"
return (
<div className="edu-back-white shixun-search-bar" >
<div className="educontent">
@ -111,7 +119,7 @@ export default ({ StatusEnquiry, allUpdatashixunlist, Updatasearchlist }) => {
<div className="clearfix">
<span className="shaiTitle fl">筛选</span>
<div className="fl pr shaiAllItem mt1">
{DiffObject.map(item => <A {...item} callback={diffSearch} activeId={diff} />)}
{DiffObject.map(item => <A key={item.id} {...item} callback={diffSearch} activeId={diff} />)}
</div>
</div>

Loading…
Cancel
Save