Merge branch 'dev_aliyun' of http://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun

dev_aliyun_beta
cxt 5 years ago
commit 6f62a55473

@ -16,6 +16,5 @@ $(document).on('turbolinks:load', function() {
$searchFrom.find('select[name="status"]').val('pending');
}
});
}
})

@ -1,6 +1,6 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-shixun-settings-index-page').length > 0) {
$(".shixun-settings-select").on("change", function () {
$(".shixun-settings-list-container").on("change", '.shixun-settings-select', function () {
var s_value = $(this).val();
var s_name = $(this).attr("name");
var json = {};
@ -13,7 +13,7 @@ $(document).on('turbolinks:load', function() {
})
});
$(".shixun-setting-form").on("change",function () {
$(".shixun-settings-list-container").on("change", '.shixun-setting-form', function () {
var s_id = $(this).attr("data-id");
var s_value = $(this).val();
var s_name = $(this).attr("name");

@ -1,6 +1,8 @@
$(document).on('turbolinks:load', function() {
if($('body.admins-shixuns-index-page').length > 0){
$('select#tag-choosed').select2({
placeholder: "请选择分类",
allowClear: true
});
}
});

@ -0,0 +1,21 @@
//= require rails-ujs
//= require turbolinks
//= require jquery3
//= require popper
//= require bootstrap-sprockets
//= require echarts
//= require_tree ./colleges
Turbolinks.setProgressBarDelay(200);
$(document).on('turbolinks:load', function() {
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="popover"]').popover();
})
$(document).on("turbolinks:before-cache", function () {
$('[data-toggle="tooltip"]').tooltip('hide');
$('[data-toggle="popover"]').popover('hide');
});

@ -0,0 +1,156 @@
$(document).on('turbolinks:load', function() {
if($('body.colleges-statistics-page').length > 0) {
var $statisticBody = $('.statistics-body');
var $statisticBase = $('.statistic-base');
var schoolId = $statisticBody.data('id');
var $statisticCourse = $statisticBody.find('.statistic-course')
var $shixunChart = $statisticBody.find('.shixun-chart');
$.get('/colleges/' + schoolId + '/shixun_time', function(data){
$statisticBase.find('.shixun-time').html("<span>" + data.shixun_time + "</span>天");
});
$.get('/colleges/' + schoolId + '/shixun_report_count', function(data){
$statisticBase.find('.shixun-report-count').html("<span>" + data.shixun_report_count + "</span>个");
});
$.ajax({ url: '/colleges/' + schoolId + '/course_statistics', method: 'GET', dataType: 'script' });
$.ajax({ url: '/colleges/' + schoolId + '/teachers', method: 'GET', dataType: 'script' });
var initShixunChart = function(names, data){
var shixunChart = echarts.init(document.getElementById('shixun-chart'));
var options = {
series : [
{
name: '访问来源',
type: 'pie',
radius: '55%',
data: data
}
]
};
shixunChart.setOption(options);
};
$.get('/colleges/' + schoolId + '/shixun_chart_data', function(data){
$statisticBody.find('.shixun-chart-loading').hide();
if (data.data.length > 0) {
$shixunChart.css('height', '400px').css('width', '100%');
initShixunChart(data.names, data.data);
} else {
$statisticBody.find('.shixun-chart-empty').show();
}
});
$.ajax({ url: '/colleges/' + schoolId + '/student_shixun', method: 'GET', dataType: 'script' });
var initHotEvaluating = function(names, values){
var Color = ['#962e66', '#623363', '#CCCCCC', '#9A9A9A', '#FF8080', '#FF80C2', '#B980FF', '#80B9FF', '#6FE9FF', '#4DE8B4', '#F8EF63', '#FFB967'];
var option = {
backgroundColor: '#fff',
grid: {
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true
},
tooltip: {
show: "true",
trigger: 'item',
formatter: '{c0}',
backgroundColor: 'rgba(0,0,0,0.7)', // 背景
padding: [8, 10], //内边距
extraCssText: 'box-shadow: 0 0 3px rgba(255, 255, 255, 0.4);', //添加阴影
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
xAxis: {
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#CCCCCC'
}
},
splitLine: {
show: false,
lineStyle: {
color: '#CCCCCC'
}
},
axisLabel: {
textStyle: {
color: '#656565',
fontWeight: 'normal',
fontSize: '12'
},
formatter: '{value}'
}
},
yAxis: {
type: 'category',
axisLine: {
lineStyle: {
color: '#cccccc'
}
},
splitLine: {
show: false
},
axisTick: {
show: false
},
splitArea: {
show: false
},
axisLabel: {
inside: false,
textStyle: {
color: '#656565',
fontWeight: 'normal',
fontSize: '12'
}
},
data: names
},
series: [{
name: '',
type: 'bar',
itemStyle: {
normal: {
show: true,
color: function(params) {
return Color[params.dataIndex]
},
barBorderRadius: 50,
borderWidth: 0,
borderColor: '#333'
}
},
barGap: '0%',
barCategoryGap: '50%',
data: values
}
]
};
var myChart = echarts.init(document.getElementById('hot-chart'));
myChart.setOption(option);
}
$.get('/colleges/' + schoolId + '/student_hot_evaluations', function(data){
$statisticBody.find('.hot-chart-loading').hide();
if (data.names.length > 0) {
$statisticBody.find('.hot-chart').css('height', '400px').css('width', '100%');
initHotEvaluating(data.names.reverse(), data.values.reverse());
} else {
$statisticBody.find('.hot-chart-empty').show();
}
})
}
});

@ -7,6 +7,7 @@
@import "bootstrap-datepicker.standalone";
@import "lib/codemirror";
@import "common";
@import "admins/*";
body {
@ -20,20 +21,6 @@ body {
background: #efefef;
}
a {
&:hover {
text-decoration: unset;
}
}
textarea.danger, input.danger {
border-color: #dc3545!important;
}
label.error {
color: #dc3545!important;
}
.simple_form {
.form-group {
.collection_radio_buttons {
@ -50,9 +37,6 @@ input.form-control {
font-size: 14px;
}
.flex-1 {
flex: 1;
}
.btn-default{
color: #666;
background: #e1e1e1!important;
@ -62,8 +46,3 @@ input.form-control {
position: absolute;
}
.position-r{position:relative;}
.font-12 { font-size: 12px !important; }
.font-14 { font-size: 14px !important; }
.font-16 { font-size: 16px !important; }
.font-18 { font-size: 18px !important; }

@ -0,0 +1,13 @@
@import "bootstrap";
@import "font-awesome-sprockets";
@import "font-awesome";
@import "common";
@import "colleges/*";
.navbar-dark .navbar-nav .nav-link {
color: rgba(255, 255, 255, 1);
font-size: 16px;
}

@ -0,0 +1,135 @@
.colleges-statistics-page {
.college-body-container {
.statistic-header {
width: 100%;
height: 240px;
background-image: url('/images/educoder/statistics.jpg');
background-size: 100% 100%;
&-container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
&-title {
flex: 1;
display: flex;
align-items: center;
color: #4CACFF;
font-size: 32px;
}
&-content {
width: 100%;
display: flex;
justify-content: space-around;
}
&-item {
margin-bottom: 22px;
display: flex;
flex-direction: column;
align-items: center;
color: #fff;
&-label {
color: #989898;
}
&-content {
font-size: 24px;
}
}
}
.statistic-box {
border: unset;
box-shadow: 0px 0px 9px rgba(174, 175, 177, 0.2);
}
.statistic-base {
&-title {
padding: 2rem 1.25rem;
background: #fff;
border-bottom: unset;
}
&-table {
margin: 0;
padding: 0;
}
&-item {
padding: 0;
&-label {
text-align: center;
font-size: 16px;
height: 48px;
line-height: 48px;
color: #686868;
background: #F5F5F5;
border-top: 1px solid #EBEBEB;
border-bottom: 1px solid #EBEBEB;
}
&-content {
height: 100px;
font-size: 16px;
text-align: center;
line-height: 100px;
span {
margin-right: 5px;
font-size: 24px;
}
}
}
}
.statistic-container {
padding: 0;
background: #fff;
border-radius: 3px;
box-shadow: 0px 0px 9px rgba(174, 175, 177, 0.2);
.statistic-label {
padding: 2rem 1.25rem;
font-size: 1.5rem;
}
.statistic-table {
overflow-x: scroll;
table.course-table { min-width: 1100px; }
table.teacher-rank-table { min-width: 640px; }
}
table th {
background: #F5F5F5;
border-color: #EBEBEB;
}
&.statistic-course {
min-height: 400px;
}
&.statistic-teacher-rank, &.statistic-student-rank {
min-height: 500px;
}
}
.statistic-chart {
padding: 0 20px;
height: 400px;
.shixun-chart-loading, .shixun-chart-empty, .hot-chart-loading, .hot-chart-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
}
}
}

@ -0,0 +1,31 @@
body {
font-size: 14px;
background: #efefef;
}
a {
&:hover {
text-decoration: unset;
}
}
textarea.danger, input.danger {
border-color: #dc3545!important;
}
label.error {
color: #dc3545!important;
}
input.form-control {
font-size: 14px;
}
.flex-1 {
flex: 1;
}
.font-12 { font-size: 12px !important; }
.font-14 { font-size: 14px !important; }
.font-16 { font-size: 16px !important; }
.font-18 { font-size: 18px !important; }

@ -0,0 +1,171 @@
class CollegesController < ApplicationController
include Admins::PaginateHelper
layout 'college'
before_action :require_login
before_action :check_college_present!
before_action :check_manage_permission!
helper_method :current_school, :current_college
def statistics
# 教师、学生总数
count_statistic = UserExtension.where(school_id: current_school.id)
.select('SUM(IF(identity=0, 1, 0)) AS teachers_count, SUM(IF(identity=1, 1, 0)) AS students_count').first
@teachers_count = count_statistic['teachers_count']
@students_count = count_statistic['students_count']
# 课堂总数
@courses_count = Course.where(school_id: current_school.id, is_delete: 0).where.not(id: 1309).count
# 实训总数
@shixuns_count = Shixun.visible.joins('left join user_extensions on user_extensions.user_id = shixuns.user_id')
.where(user_extensions: { school_id: current_school.id }).count
end
def shixun_time
time_sum = Game.joins('left join user_extensions on user_extensions.user_id = games.user_id')
.where(user_extensions: { school_id: current_school.id }).sum(:cost_time)
shixun_time_sum = (time_sum / (24 * 60 * 60.0)).ceil
render json: { shixun_time: shixun_time_sum }
end
def shixun_report_count
shixun_report_count = StudentWork.where(work_status: [1, 2]).where('myshixun_id != 0')
.joins('left join user_extensions on user_extensions.user_id = student_works.user_id')
.where(user_extensions: { school_id: current_school.id }).count
render json: { shixun_report_count: shixun_report_count }
end
def teachers
@teachers = User.find_by_sql("SELECT users.id, users.login, users.lastname, users.firstname, users.nickname, IFNULL((SELECT count(shixuns.id) FROM shixuns where shixuns.user_id =users.id group by shixuns.user_id), 0) AS publish_shixun_count,
(SELECT count(c.id) FROM courses c, course_members m WHERE c.id != 1309 and m.course_id = c.id AND m.role in (1,2,3) and c.school_id = #{current_school.id} AND m.user_id=users.id AND c.is_delete = 0) as course_count
FROM `users`, user_extensions ue where users.id=ue.user_id and ue.identity=0 and ue.school_id=#{current_school.id} ORDER BY publish_shixun_count desc, course_count desc, id desc LIMIT 10")
# ).order("publish_shixun_count desc, experience desc").limit(10)
@teachers =
@teachers.map do |teacher|
course_ids = Course.find_by_sql("SELECT c.id FROM courses c, course_members m WHERE c.id != 1309 and m.course_id = c.id AND m.role in (1,2,3) AND m.user_id=#{teacher.id} AND c.is_delete = 0 and c.school_id = #{current_school.id}")
course_count = course_ids.size
homeworks = HomeworkCommon.where(:homework_type => 4, :course_id => course_ids.map(&:id))
un_shixun_work_count = homeworks.where("publish_time > '#{Time.now}' or publish_time is null").count
shixun_work_count = homeworks.size - un_shixun_work_count
student_count = StudentsForCourse.where(:course_id => course_ids.map(&:id)).count
myshixun_ids = StudentWork.select("myshixun_id").where("homework_common_id in (#{homeworks.map(&:id).join(',').strip == "" ? -1 : homeworks.map(&:id).join(',')}) and myshixun_id is not null")
complete_myshixun = Myshixun.select("id").where(:status => 1, :id => myshixun_ids.map(&:myshixun_id)).size
all_myshixun = Myshixun.select("id").where(:id => myshixun_ids.map(&:myshixun_id)).size
complete_rate = all_myshixun == 0 ? 0 : ((complete_myshixun * 100) / all_myshixun).try(:round, 2).to_f
real_name = teacher.show_real_name
teacher = teacher.attributes.dup.merge({
real_name: real_name,
course_count: course_count,
shixun_work_count: shixun_work_count,
un_shixun_work_count: un_shixun_work_count,
student_count: student_count,
complete_rate: complete_rate
}).to_json
JSON.parse(teacher)
end
end
def shixun_chart_data
shixun_ids = HomeworkCommonsShixun.joins(homework_common: :course).where(courses: {school_id: current_school.id, is_delete: 0}).where('courses.id != 1309').pluck('distinct shixun_id')
shixun_count_map = ShixunTagRepertoire.joins(:tag_repertoire).where(shixun_id: shixun_ids).group('tag_repertoires.name').order('count_shixun_id desc').count(:shixun_id)
names = []
data = []
shixun_count_map.each do |name, count|
break if names.size == 9
names << name
data << { value: count, name: name }
end
if shixun_count_map.keys.size > 9
other_count = shixun_count_map.values[9..-1].reduce(:+)
names << 'Others'
data << { name: 'Others', value: other_count }
end
render json: { names: names, data: data }
end
# 在线课堂
def course_statistics
courses = Course.where(school_id: current_school.id, is_delete: 0).where.not(id: 1309)
courses = courses.left_joins(practice_homeworks: { student_works: { myshixun: :games } })
.select('courses.id, courses.name, courses.is_end, sum(games.evaluate_count) evaluating_count')
.group('courses.id').order('is_end asc, evaluating_count desc')
params[:per_page] = 8
@courses = paginate courses
course_ids = @courses.map(&:id)
@student_count = StudentsForCourse.where(course_id: course_ids).group(:course_id).count
@shixun_work_count = HomeworkCommon.where(homework_type: 4, course_id: course_ids).group(:course_id).count
@attachment_count = Attachment.where(container_id: course_ids, container_type: 'Course').group(:container_id).count
@message_count = Message.joins(:board).where(boards: { parent_id: 0, course_id: course_ids }).group('boards.course_id').count
@active_time = CourseActivity.where(course_id: course_ids).group(:course_id).maximum(:created_at)
@exercise_count = Exercise.where(course_id: course_ids).group(:course_id).count
@poll_count = Poll.where(course_id: course_ids).group(:course_id).count
@other_work_count = HomeworkCommon.where(homework_type: [1,3], course_id: course_ids).group(:course_id).count
end
# 学生实训
def student_shixun
@students = User.joins(:user_extension).where(user_extensions: { school_id: current_school.id, identity: 1 }).includes(:user_extension).order('experience desc').limit(10)
student_ids = @students.map(&:id)
@shixun_count = Myshixun.where(user_id: student_ids).group(:user_id).count
@study_shixun_count = Myshixun.where(user_id: student_ids, status: 0).group(:user_id).count
end
def student_hot_evaluations
games = Game.joins(:myshixun).joins('join shixun_tag_repertoires str on str.shixun_id = myshixuns.shixun_id')
games = games.joins('join tag_repertoires tr on tr.id = str.tag_repertoire_id')
games = games.joins("join user_extensions ue on ue.user_id = myshixuns.user_id and ue.school_id = #{current_school.id}")
evaluate_count_map = games.group('tr.name').reorder('sum_games_evaluate_count desc').limit(10).sum('games.evaluate_count')
render json: { names: evaluate_count_map.keys, values: evaluate_count_map.values }
end
private
def require_login
return if User.current.logged?
redirect_to "/login?back_url=#{CGI::escape(request.fullpath)}"
end
def check_college_present!
return if current_college.present?
redirect_to '/404'
end
def check_manage_permission!
return if can_manage_college?
redirect_to '/403'
end
def can_manage_college?
return true if current_user.admin_or_business? # 超级管理员|运营
return true if current_college.is_a?(Department) && current_college.member?(current_user) # 部门管理员
return true if current_user.is_teacher? && current_user.school_id == current_school.id # 学校老师
return true if current_school.customer_id && current_user.partner&.partner_customers&.exists?(customer_id: current_school.customer_id)
false
end
def current_school
current_college.is_a?(School) ? current_college : current_college.school
end
def current_college
@_current_college ||= begin
Department.find_by(identifier: params[:id]) || School.find_by(id: params[:id])
end
end
end

@ -0,0 +1,10 @@
class Customer < ApplicationRecord
default_scope { order(created_at: :desc) }
belongs_to :school
has_many :partner_customers, dependent: :destroy
has_many :partners, through: :partner_customers
has_many :users
end

@ -9,6 +9,10 @@ class Department < ApplicationRecord
scope :without_deleted, -> { where(is_delete: false) }
def member?(user)
department_members.exists?(user_id: user.id)
end
def soft_delete!
update!(is_delete: true)
end

@ -3,7 +3,7 @@ class HomeworkCommon < ApplicationRecord
enum homework_type: { normal: 1, program: 2, group: 3, practice: 4 }, _suffix: true
has_many :homework_group_settings, dependent: :destroy
has_many :published_settings, -> { group_published }, class_name: "HomeworkGroupSetting"
has_many :student_works, -> { where("is_delete = 0") }
has_many :student_works, -> { where(is_delete: 0) }
has_many :score_student_works, -> { where("is_delete = 0 and work_status != 0").order("work_score desc") }, class_name: "StudentWork"
has_one :homework_detail_manual, dependent: :destroy

@ -1,3 +1,5 @@
class Partner < ApplicationRecord
belongs_to :school, optional: true
has_many :users
end

@ -0,0 +1,4 @@
class PartnerCustomer < ApplicationRecord
belongs_to :partner
belongs_to :customer
end

@ -13,6 +13,9 @@ class School < ApplicationRecord
has_many :school_daily_reports, dependent: :destroy
has_many :courses
has_many :customers, dependent: :destroy
has_many :partners, dependent: :destroy
# 学校管理员
def manager?(user)
ec_school_users.exists?(user_id: user.id)

@ -139,7 +139,7 @@ class User < ApplicationRecord
has_many :videos, dependent: :destroy
# 客户管理
belongs_to :partner
belongs_to :partner, optional: true
# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }

@ -15,7 +15,7 @@ class Admins::DepartmentQuery < ApplicationQuery
keyword = params[:keyword].to_s.strip
if keyword.present?
departments = departments.joins(:school)
.where('schools.name LIKE :keyword OR departments.name LIKE :keyword', keyword: keyword)
.where('schools.name LIKE :keyword OR departments.name LIKE :keyword', keyword: "%#{keyword}%")
end
if params[:with_member].to_s == 'true'

@ -14,7 +14,13 @@
<td class="member-container">
<%= render partial: 'admins/departments/shared/member_users', locals: { department: department } %>
</td>
<td><%= link_to department.identifier.to_s, '#', target: '_blank' %></td>
<td>
<% if department.identifier.present? %>
<%= link_to department.identifier.to_s, statistics_college_path(department.identifier), target: '_blank' %>
<% else %>
--
<% end %>
</td>
<td><%= department.host_count %></td>
<td><%= department.created_at&.strftime('%Y-%m-%d %H:%M') %></td>
<td class="action-container">

@ -0,0 +1,45 @@
<table class="table text-center course-table">
<thead class="thead-light">
<tr>
<th class="text-left">名称</th>
<th class="text-left">管理教师</th>
<th>评测次数</th>
<th>学生</th>
<th>实训作业</th>
<th>资源</th>
<th>帖子</th>
<th>其它任务</th>
<th>状态</th>
<th>时间</th>
</tr>
</thead>
<tbody>
<% if @courses.present? %>
<% @courses.each do |course| %>
<tr>
<td class="text-left"><a href="/courses/<%= course.id %>/students" target="_blank" class="d-inline-block text-truncate" style="max-width: 220px"><%= course.name %></a></td>
<% teacher_names = course.teacher_users.map(&:real_name).join('、') %>
<td class="text-left">
<span class="d-inline-block text-truncate" style="max-width: 150px" data-toggle="tooltip" title="<%= teacher_names %>">
<%= teacher_names || '--' %>
</span>
</td>
<td><%= course.evaluating_count.to_i %></td>
<td><%= @student_count.fetch(course.id, 0) %></td>
<td><%= @shixun_work_count.fetch(course.id, 0) %></td>
<td><%= @attachment_count.fetch(course.id, 0) %></td>
<td><%= @message_count.fetch(course.id, 0) %></td>
<td><%= @exercise_count.fetch(course.id, 0) + @poll_count.fetch(course.id, 0) + @other_work_count.fetch(course.id, 0) %></td>
<td class="<%= course.is_end ? 'text-secondary' : 'text-warning' %>"><%= course.is_end ? "已结束" : "正在进行" %></td>
<td class="text-secondary"><%= @active_time[course.id]&.strftime('%Y-%m-%d %H:%M') %></td>
</tr>
<% end %>
<% else %>
<tr><td colspan="100">暂无数据</td></tr>
<% end %>
</tbody>
</table>
<div class="d-flex justify-content-center text-center">
<%= render partial: 'admins/shared/paginate', locals: { objects: @courses } %>
</div>

@ -0,0 +1,23 @@
<% if @students.present? %>
<% @students.each_with_index do |student, index| %>
<tr>
<td>
<% if index < 3 %>
<img src="/images/educoder/competition/<%= index + 1 %>.png" width="18px" height="22px" class="mt8"/></td>
<% else %>
<%= index + 1 %>
<% end %>
</td>
<td class="color-dark">
<a href="/users/<%= student.login %>" target="_blank" class="d-inline-block text-truncate" style="max-width: 84px;"><%= student.real_name %></a>
</td>
<td><%= student.student_id %></td>
<td><%= @shixun_count.fetch(student.id, 0) %></td>
<td><%= @study_shixun_count.fetch(student.id, 0) %></td>
<td><%= student.grade %></td>
<td class="text-info"><%= student.experience %></td>
</tr>
<% end %>
<% else %>
<tr><td colspan="100">暂无数据</td></tr>
<% end %>

@ -0,0 +1,21 @@
<% if @teachers.present? %>
<% @teachers.each_with_index do |teacher, index| %>
<tr>
<td class="pl20 pr20">
<% if index < 3 %>
<img src="/images/educoder/competition/<%= index + 1 %>.png" width="18px" height="22px" class="mt8"/></td>
<% else %>
<%= index + 1 %>
<% end %>
<td class="color-dark"><a href="<%= user_path(teacher['login']) %>" target="_blank" class="task-hide" style="max-width: 84px;"><%= teacher['real_name'] %></a></td>
<td><%= teacher['course_count'] %></td>
<td><%= teacher['shixun_work_count'] %></td>
<td><%= teacher['un_shixun_work_count'] %></td>
<td><%= teacher['student_count'] %></td>
<td><%= teacher['complete_rate'] %>%</td>
<td class="color-blue"><%= teacher['publish_shixun_count'].to_i %></td>
</tr>
<% end %>
<% else %>
<tr><td colspan="100">暂无数据</td></tr>
<% end %>

@ -0,0 +1 @@
$(".statistic-course .statistic-table").html("<%= j(render 'colleges/course_statistics') %>");

@ -0,0 +1,21 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">
<img src="/images/educoder/headNavLogo.png" width="40" height="40" alt="">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a class="nav-link" href="/paths">实践课程</a></li>
<li class="nav-item"><a class="nav-link" href="/courses">翻转课堂</a></li>
<li class="nav-item"><a class="nav-link" href="/shixuns">实训项目</a></li>
<li class="nav-item"><a class="nav-link" href="/competitions">在线竞赛</a></li>
<li class="nav-item"><a class="nav-link" href="/moop_cases">教学案例</a></li>
<li class="nav-item"><a class="nav-link" href="/crowdsourcing">众包创新</a></li>
<li class="nav-item"><a class="nav-link" href="/forums">交流问答</a></li>
</ul>
</div>
</nav>

@ -0,0 +1,164 @@
<div class="statistic-header">
<div class="container statistic-header-container">
<div class="statistic-header-title"><%= current_school.name %></div>
<div class="statistic-header-content">
<div class="statistic-header-item">
<div class="statistic-header-item-label">教师</div>
<div class="statistic-header-item-content"><%= @teachers_count %></div>
</div>
<div class="statistic-header-item">
<div class="statistic-header-item-label">学生</div>
<div class="statistic-header-item-content"><%= @students_count %></div>
</div>
<div class="statistic-header-item">
<div class="statistic-header-item-label">课堂</div>
<div class="statistic-header-item-content"><%= @courses_count %></div>
</div>
<div class="statistic-header-item">
<div class="statistic-header-item-label">共建实训</div>
<div class="statistic-header-item-content"><%= @shixuns_count %></div>
</div>
</div>
</div>
</div>
<div class="container statistics-body my-4" data-id="<%= current_school.id %>">
<div class="card statistic-box statistic-base">
<h4 class="card-header statistic-base-title">基本情况</h4>
<div class="card-body statistic-base-table row">
<div class="col-4 col-md-2 statistic-base-item">
<div class="statistic-base-item-label">教师</div>
<div class="statistic-base-item-content">
<span><%= @teachers_count %></span>人
</div>
</div>
<div class="col-4 col-md-2 statistic-base-item">
<div class="statistic-base-item-label">学生</div>
<div class="statistic-base-item-content">
<span><%= @students_count %></span>人
</div>
</div>
<div class="col-4 col-md-2 statistic-base-item">
<div class="statistic-base-item-label">课堂</div>
<div class="statistic-base-item-content">
<span><%= @courses_count %></span>个
</div>
</div>
<div class="col-4 col-md-2 statistic-base-item">
<div class="statistic-base-item-label">共建实训</div>
<div class="statistic-base-item-content">
<span><%= @shixuns_count %></span>个
</div>
</div>
<div class="col-4 col-md-2 statistic-base-item">
<div class="statistic-base-item-label">实习报告</div>
<div class="statistic-base-item-content shixun-report-count">
加载中...
</div>
</div>
<div class="col-4 col-md-2 statistic-base-item">
<div class="statistic-base-item-label">学员实战时间</div>
<div class="statistic-base-item-content shixun-time">
加载中...
</div>
</div>
</div>
</div>
<div class="statistic-container my-4 statistic-course">
<div class="row">
<div class="col-12">
<div class="statistic-label">课堂</div>
<div class="statistic-table">
<table class="table text-center course-table">
<thead class="thead-light">
<tr>
<th class="text-left">名称</th>
<th class="text-left">管理教师</th>
<th>评测次数</th>
<th>学生</th>
<th>实训作业</th>
<th>资源</th>
<th>帖子</th>
<th>其它任务</th>
<th>状态</th>
<th>时间</th>
</tr>
</thead>
<tbody>
<tr><td colspan="100">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="my-4 mx-0 row">
<div class="col-12 col-md-7 statistic-container statistic-teacher-rank">
<div class="statistic-label">教师排名</div>
<div class="statistic-table">
<table class="table text-center teacher-rank-table">
<thead class="thead-light">
<tr>
<th>排名</th>
<th>姓名</th>
<th>管理课堂</th>
<th width="15%">已发布实训作业</th>
<th width="15%">未发布实训作业</th>
<th>学生数</th>
<th>完成率</th>
<th>发布实训</th>
</tr>
</thead>
<tbody>
<tr><td colspan="100">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
<div class="col-12 col-md-5 statistic-container">
<div class="statistic-label">在线实训情况</div>
<div class="statistic-chart">
<div class="shixun-chart-loading">加载中...</div>
<div class="shixun-chart-empty" style="display: none;">无数据</div>
<div class="shixun-chart" id="shixun-chart"></div>
</div>
</div>
</div>
<div class="my-4 mx-0 row">
<div class="col-12 col-md-7 statistic-container statistic-student-rank">
<div class="statistic-label">学生排名</div>
<div class="statistic-table">
<table class="table text-center student-rank-table">
<thead class="thead-light">
<tr>
<th>排名</th>
<th>姓名</th>
<th>学号</th>
<th>完成实训</th>
<th>在学实训</th>
<th>金币</th>
<th>经验值</th>
</tr>
</thead>
<tbody>
<tr><td colspan="100">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
<div class="col-12 col-md-5 statistic-container">
<div class="statistic-label">最热评测</div>
<div class="statistic-chart">
<div class="hot-chart-loading">加载中...</div>
<div class="hot-chart-empty" style="display: none;">无数据</div>
<div class="hot-chart" id="hot-chart"></div>
</div>
</div>
</div>
</div>

@ -0,0 +1 @@
$('.statistic-student-rank table.student-rank-table tbody').html("<%= j(render :partial => 'colleges/student_rank') %>")

@ -0,0 +1 @@
$('.statistic-teacher-rank table.teacher-rank-table tbody').html("<%= j(render :partial => 'colleges/teacher_rank') %>")

@ -4,6 +4,7 @@
<title>EduCoder后台管理</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>EduCoder</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel='shortcut icon' type='image/x-icon' href='/favicon.ico' />
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'college', media: 'all','data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'college', 'data-turbolinks-track': 'reload' %>
</head>
<% body_class = [params[:controller].gsub(/\//, '-').gsub('_', '-'), params[:action], 'page'].join('-') %>
<body class="<%= body_class %>">
<%= render 'colleges/shared/navbar' %>
<!-- Page Content -->
<div class="college-body-container">
<%= yield %>
</div>
</body>
</html>

@ -12,5 +12,5 @@ Rails.application.config.assets.paths << Rails.root.join('vendor/assets')
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
Rails.application.config.assets.precompile += %w( admin.js admin.css )
Rails.application.config.assets.precompile += %w( admin.js admin.css college.js college.css )

@ -850,6 +850,19 @@ Rails.application.routes.draw do
end
end
resources :colleges, only: [] do
member do
get :statistics
get :course_statistics
get :student_shixun
get :shixun_time
get :shixun_report_count
get :teachers
get :shixun_chart_data
get :student_hot_evaluations
end
end
#git 认证回调
match 'gitauth/*url', to: 'gits#auth', via: :all

File diff suppressed because one or more lines are too long

@ -37847,7 +37847,6 @@ $(document).on('turbolinks:load', function() {
$searchFrom.find('select[name="status"]').val('pending');
}
});
}
})
;
@ -38362,7 +38361,7 @@ $(document).on('turbolinks:load', function() {
;
$(document).on('turbolinks:load', function() {
if ($('body.admins-shixun-settings-index-page').length > 0) {
$(".shixun-settings-select").on("change", function () {
$(".shixun-settings-list-container").on("change", '.shixun-settings-select', function () {
var s_value = $(this).val();
var s_name = $(this).attr("name");
var json = {};
@ -38375,7 +38374,7 @@ $(document).on('turbolinks:load', function() {
})
});
$(".shixun-setting-form").on("change",function () {
$(".shixun-settings-list-container").on("change", '.shixun-setting-form', function () {
var s_id = $(this).attr("data-id");
var s_value = $(this).val();
var s_name = $(this).attr("name");
@ -38394,10 +38393,12 @@ $(document).on('turbolinks:load', function() {
});
$(document).on('turbolinks:load', function() {
if($('body.admins-shixuns-index-page').length > 0){
$('select#tag-choosed').select2({
placeholder: "请选择分类",
allowClear: true
});
}
});
$(document).on('turbolinks:load', function(){
$('#sidebarCollapse').on('click', function () {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
import { bytesToSize, getUrl2 } from 'educoder';
import { bytesToSize, getUrl, getUrl2 } from 'educoder';
const $ = window.$
export function isImageExtension(fileName) {
@ -41,7 +41,7 @@ export function markdownToHTML(oldContent, selector) {
}
}
function _doDownload(options) {
$.fileDownload("/api" + options.url, {
$.fileDownload(getUrl() + "/api" + options.url, {
successCallback: options.successCallback,
failCallback: options.failCallback
});

@ -37,6 +37,14 @@ export function getUrl(path, goTest) {
}
return `${path ? path: ''}`;
}
export function getStaticUrl() {
const local = TEST_HOST;
if (isDev) {
return local
}
// todo cdn
return ''
}
export function getUrl2(path, goTest) {
const local = 'http://localhost:3000'
if (isDev) {

@ -108,7 +108,7 @@ function buildColumns(that, student_works, studentData) {
}]
if (!niPingAndIsStudent && isAdminOrStudent) {
columns.push({
width: 88,
width: isStudent ? undefined : 88,
title: '学号',
dataIndex: 'student_id',
key: 'student_id',
@ -197,7 +197,7 @@ function buildColumns(that, student_works, studentData) {
</span>
)},
}, {
width: 106,
width: 106, // isStudent ? undefined : 106 , // 匿评中 只有这几列: 序号 姓名 提交状态 更新时间 匿评评分 操作
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',

@ -7,8 +7,7 @@ import CourseLayoutcomponent from '../common/CourseLayoutComponent'
import Titlesearchsection from '../common/titleSearch/TitleSearchSection'
import ColorCountText from '../common/titleSearch/ColorCountText'
import { WordsBtn, trigger, on, off ,downloadFile} from 'educoder'
import { WordsBtn, trigger, on, off, getUrl, downloadFile } from 'educoder'
import Modals from "../../modals/Modals";
import axios from 'axios'
import _ from 'lodash'
@ -151,10 +150,19 @@ class studentsList extends Component{
})
}
}else {
this.props.showNotification(`正在下载中`);
window.open("/api"+url, '_blank');
// this.props.showNotification(`正在下载中`);
// window.open("/api"+url, '_blank');
this.props.slowDownload(url)
// getUrl() + "/api"+
// const fileUrl = url;
// this.props.slowDownload(fileUrl)
// return;
// downloadFile({
// url: url,
// url: fileUrl,
// successCallback: (url) => {
// console.log('successCallback')
// },
@ -162,6 +170,7 @@ class studentsList extends Component{
// console.log('failCallback')
// }
// })
// window.open(fileUrl, "_self");// , '_blank'
}
}).catch((error) => {
console.log(error)

@ -126,7 +126,9 @@ function buildColumns(that) {
sortOrder: sortedInfo.columnKey === 'graduation_group' && sortedInfo.order,
render: text => (
<span>
<span className="overflowHidden1" style={{ maxWidth: '160px'}}
title={`${text && text.length > 10 ? text : ''}`}
>
{text}
</span>
),

@ -2146,9 +2146,20 @@ class PollNew extends Component {
} else {
for (var i = 0; i < arr.length; i++) {
if (index === i) {
try {
if(parseInt(value)===0){
arr[i].question.min_choices = 2;
arr[i].question.max_choices = length;
}else{
arr[i].question.min_choices = parseInt(value);
arr[i].question.max_choices = length;
}
}catch (e) {
arr[i].question.min_choices = 2;
arr[i].question.max_choices = length;
}
break;
}
}
this.setState({
adddom: arr

@ -57,6 +57,7 @@ class MemoDetailMDEditor extends Component {
errorMsg: ''
})
})
commentMDEditor.cm.focus()
}, {
watch: false,
dialogLockScreen: false,
@ -124,7 +125,7 @@ class MemoDetailMDEditor extends Component {
this.initMDEditor()
} else {
setTimeout(() => {
this.commentMDEditor && this.commentMDEditor.focus()
this.commentMDEditor && this.commentMDEditor.cm.focus()
}, 10)
}
}

@ -5,9 +5,9 @@ import PropTypes from 'prop-types';
import NewHeader from './NewHeader'
import NewFooter from './NewFooter'
import SiderBar from './SiderBar'
import { getUrl } from 'educoder'
import { getUrl, downloadFile } from 'educoder'
import axios from 'axios';
import { Spin } from 'antd'
import './TPMIndex.css'
import LoginDialog from '../login/LoginDialog';
import AccountProfile from '../user/AccountProfile';
@ -79,7 +79,9 @@ export function TPMIndexHOC(WrappedComponent) {
coursedata: {},
isRender: false,
AccountProfiletype: false
AccountProfiletype: false,
globalLoading: false
}
}
@ -133,7 +135,18 @@ export function TPMIndexHOC(WrappedComponent) {
})
}
keyupListener = (e) => {
if (e.key === "Escape") {
this.setState({ globalLoading: false })
}
}
componentWillUnmount() {
window.removeEventListener('keyup', this.keyupListener)
}
componentDidMount() {
window.addEventListener('keyup', this.keyupListener)
if(this.props.match.path==="/"){
document.title="创新源于实践";
}else if(this.props.match.path==="/403"){
@ -364,6 +377,29 @@ export function TPMIndexHOC(WrappedComponent) {
DownloadOpenPdf=(type,url)=>{
type===true?window.open(url):window.location.href=url;
}
slowDownload = (url, tip) => {
this._gLoadingTip = tip || '正在生成文件,请稍后...';
this.setState({ globalLoading: true })
const fileUrl = url;
downloadFile({
url: fileUrl,
successCallback: (url) => {
this.setState({ globalLoading: false })
console.log('successCallback')
},
failCallback: (responseHtml, url) => {
this.setState({ globalLoading: false })
console.log('failCallback')
}
})
}
showGlobalLoading = (tip) => {
this._gLoadingTip = tip || '加载中,请稍后...';
this.setState({ globalLoading: true })
}
hideGlobalLoading = () => {
this.setState({ globalLoading: false })
}
render() {
let{Headertop,Footerdown, isRender, AccountProfiletype}=this.state;
const common = {
@ -387,10 +423,15 @@ export function TPMIndexHOC(WrappedComponent) {
ShowOnlinePdf:(url)=>this.ShowOnlinePdf(url),
DownloadFileA:(title,url)=>this.DownloadFileA(title,url),
DownloadOpenPdf:(type,url)=>this.DownloadOpenPdf(type,url)
DownloadOpenPdf:(type,url)=>this.DownloadOpenPdf(type,url),
slowDownload: this.slowDownload,
showGlobalLoading: this.showGlobalLoading,
hideGlobalLoading: this.hideGlobalLoading,
}
return (
<div>
<div className="indexHOC">
{isRender===true ? <LoginDialog
Modifyloginvalue={()=>this.hideLoginDialog()}
{...this.props}
@ -423,8 +464,28 @@ export function TPMIndexHOC(WrappedComponent) {
-moz-box-shadow: 0px 0px 12px rgba(0,0,0,0.1);
box-shadow: 0px 0px 12px rgba(0,0,0,0.1);
}
.globalSpin {
max-height: 700px !important;
}
.indexHOC > .ant-spin-nested-loading {
background: #000;
}
.globalSpin .ant-spin-text {
text-shadow: none !important;
color: #fff;
}
.globalSpin .ant-spin-dot-item {
background-color: #fff;
}
`
}</style>
<Spin spinning={this.state.globalLoading} delay={0} className="globalSpin"
size="large"
tip= {this._gLoadingTip || "加载中..."}
>
<NewHeader {...this.state} {...this.props}></NewHeader>
<div className="newContainer newContainers">
<WrappedComponent initCommonState={(user)=>this.initCommonState(user)}
@ -440,6 +501,7 @@ export function TPMIndexHOC(WrappedComponent) {
<NewFooter
Footerdown={Footerdown}
/>
</Spin>
</div>
);
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save